# Project export: TrivAI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: TrivAI brings trivia to meet you where you are, helping you learn about your interests. Compete with your friends to find out who's the most knowledgable on your favorite topics, powered by AI!
- Devpost: https://devpost.com/software/trivai-vle72m
- GitHub: https://github.com/kavelrao/TrivAI
- Team: 1 GitHub contributor(s) — Kavel Rao (9 commits)

## Devpost submission (written by the team)

### Inspiration

TrivAI was born from the desire to revolutionize the traditional trivia game experience. My girlfriend and I like trivia and we noticed that conventional trivia games often lack personalization, asking questions about topics we don't care about. By leveraging AI technology, TrivAI seizes an opportunity to create dynamic, engaging, and customizable trivia experiences that adapt to players' interests, helping us learn more about our favorite topics!

### What it does

TrivAI is an interactive multiplayer trivia game that uses AI to generate unique questions based on player-chosen topics. Key features include: Real-time multiplayer gameplay using Pusher for live updates Custom topic selection AI-powered question generation using gpt-4o Dynamic scoring system Instant answer validation using AI Fun, playful UI with bouncing shapes and smooth transitions

### How we built it

We built TrivAI using a modern tech stack: Frontend: Next.js with TypeScript for type safety and better developer experience Styling: Tailwind CSS for responsive design and custom animations Real-time Communications: Pusher for websocket connections and live game updates AI Integration: OpenAI's gpt-3.5-turbo for question generation and answer validation State Management: Server-side global state for managing game sessions and player data

### Challenges we ran into

Answer Validation: Creating a fair and accurate system for validating player answers was complex. We solved this by using GPT-3.5 Turbo to compare answers contextually rather than exact matching. Real-time Synchronization: Coordinating game state across multiple players presented challenges. We implemented a robust Pusher-based system to ensure all players stay synchronized. Question Quality: Ensuring AI-generated questions were both challenging and accurate required careful prompt engineering and validation.

### Accomplishments we're proud of

Created a seamless multiplayer experience with real-time updates Implemented an animated UI that gives the game a fun vibe Successfully integrated AI for both question generation and answer validation Built a scalable architecture that can handle multiple concurrent game sessions

### What we learned

Websocket implementation with Pusher Prompt engineering for AI interactions State management in a real-time multiplayer context Complex animation implementations with CSS TypeScript best practices in a Next.js environment

### What's next

Custom Game Modes: Implement different game modes like time trials, tournament-style competitions, and Jeopardy style betting Difficulty adjustment based on player performance Friend lists and public lobbies Achievement system A dashboard for players to track their performance and learning progress over time

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 19 recognized source files, 46 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel AI SDK (technology) — detected in the code

## Codebase structure (from repository index)

### Files (22 of 22)

```
.gitignore
app/api/add-topic/route.ts
app/api/create-game/route.ts
app/api/generate-questions/route.ts
app/api/get-players/route.ts
app/api/get-questions/route.ts
app/api/get-topics/route.ts
app/api/join-game/route.ts
app/api/start-game/route.ts
app/api/submit-answer/route.ts
app/create-game/page.tsx
app/game/[code]/page.tsx
app/globals.css
app/join-game/page.tsx
app/layout.tsx
app/lobby/[code]/page.tsx
app/page.tsx
app/utils/game-state.ts
package.json
postcss.config.js
tailwind.config.js
tsconfig.json
```

### Dependencies

- package.json: @ai-sdk/openai@latest, @types/node@^20.0.0, @types/react@^18.2.0, ai@latest, autoprefixer@^10.4.20, nanoid@^5.0.0, next@^14.0.0, openai@^4.85.1, postcss@^8.5.2, pusher@^5.0.0, pusher-js@^8.0.0, react@^18.2.0, react-dom@^18.2.0, tailwindcss@^3.4.17, typescript@^5.0.0

### Recent commits (newest first)

- fix npm build
- update answer reveal ui
- update styling
- home page styling
- update name
- ux updates
- main game flow works
- create and join game works, working on answering questions flow
- scaffolding

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "trivia-game",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@ai-sdk/openai": "latest",
    "ai": "latest",
    "nanoid": "^5.0.0",
    "next": "^14.0.0",
    "openai": "^4.85.1",
    "pusher": "^5.0.0",
    "pusher-js": "^8.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/react": "^18.2.0",
    "autoprefixer": "^10.4.20",
    "postcss": "^8.5.2",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.0.0"
  }
}

```

### app/layout.tsx

```typescript
import './globals.css'

export const metadata = {
  title: 'TrivAI',
  description: 'An AI-powered trivia game experience',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div className="min-h-screen bg-yellow-50 relative overflow-hidden">
          {/* Bouncing shapes */}
          <div className="fixed inset-0 w-screen h-screen pointer-events-none">
            {/* Triangle 1 */}
            <div className="absolute top-10 left-10 w-40 h-40 animate-bounce-1">
              <svg viewBox="0 0 24 24" className="w-full h-full text-pink-400 opacity-60">
                <path fill="currentColor" d="M12 2L2 19h20L12 2z"/>
              </svg>
            </div>
          </div>

          {/* Main content */}
          <div className="relative z-10">
            {children}
          </div>
        </div>
      </body>
    </html>
  )
}

```

### app/page.tsx

```typescript
import Link from "next/link"
import { Titan_One } from 'next/font/google'

const titan = Titan_One({ 
  weight: '400',
  subsets: ['latin'],
})

export default function Home() {
  return (
    <div className="min-h-screen bg-yellow-50 relative overflow-hidden">
      {/* Bouncing shapes */}
      <div className="fixed inset-0 w-screen h-screen pointer-events-none">
        {/* Triangle 1 */}
        <div className="absolute top-10 left-10 w-40 h-40 animate-bounce-1">
          <svg viewBox="0 0 24 24" className="w-full h-full text-pink-400 opacity-60">
            <path fill="currentColor" d="M12 2L2 19h20L12 2z"/>
          </svg>
        </div>
      </div>

      <div className="relative z-10">
        {/* Title section with a fun tilt */}
        <div className="bg-white/90 shadow-xl mx-auto mt-16 max-w-2xl rounded-3xl p-8 backdrop-blur-sm">
          <div className="animate-bounce-gentle">
            <h1 className={`${titan.className} text-8xl text-purple-400 filter drop-shadow-[0_8px_8px_rgba(0,0,0,0.2)] hover:scale-105 transition-transform`}>
              TrivAI
            </h1>
          </div>
          <p className="text-2xl font-medium text-purple-800 mt-4">
            Get quizzed on your favorite topics
          </p>
        </div>

        {/* Quirky buttons */}
        <div className="mt-12 flex flex-col sm:flex-row gap-8 justify-center items-center">
          <Link 
            href="/create-game" 
            className="group transition-all"
          >
            <div className="bg-purple-500 rounded-[2rem] p-1">
              <div className="px-10 py-4 rounded-[calc(2rem-2px)] bg-white hover:bg-opacity-0 transition-all">
                <span className="text-xl font-bold text-purple-500 group-hover:text-white transition-all">
                  Create Game
                </span>
              </div>
            </div>
          </Link>
          <Link 
            href="/join-game" 
            className="group transition-all"
          >
            <div className="bg-purple-500 rounded-[2rem] p-1">
              <div className="px-10 py-4 rounded-[calc(2rem-2px)] bg-white hover:bg-opacity-0 transition-all">
                <span className="text-xl font-bold text-purple-500 group-hover:text-white transition-all">
                  Join Game
                </span>
              </div>
            </div>
          </Link>
        </div>

        {/* Fun features section */}
        <div className="mt-20 px-4 flex flex-col sm:flex-row gap-8 justify-center items-center max-w-6xl mx-auto">
          <div className="transition-all">
            <div className="bg-white/90 backdrop-blur-sm p-6 rounded-[3rem] shadow-lg hover:shadow-xl w-72 text-center">
              <h3 className="text-xl font-bold text-purple-500">Magic Questions!</h3>
              <p className="text-purple-700 mt-2">AI brews up the perfect trivia just for you!</p>
            </div>
          </div>
          <div className="transition-all">
            <div className="bg-white/90 backdrop-blur-sm p-6 rounded-[3rem] shadow-lg hover:shadow-xl w-72 text-center">
              <h3 className="text-xl font-bold text-purple-500">Party Time!</h3>
              <p className="text-purple-700 mt-2">Challenge your friends to epic battles!</p>
            </div>
          </div>
          <div className="transition-all">
            <div className="bg-white/90 backdrop-blur-sm p-6 rounded-[3rem] shadow-lg hover:shadow-xl w-72 text-center">
              <h3 className="text-xl font-bold text-purple-500">Your Rules!</h3>
              <p className="text-purple-700 mt-2">Pick any topic under the sun!</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}



```

### app/join-game/page.tsx

```typescript
"use client"

import { useState } from "react"
import { useRouter } from "next/navigation"

export default function JoinGame() {
  const [lobbyCode, setLobbyCode] = useState("")
  const [playerName, setPlayerName] = useState("")
  const router = useRouter()

  const joinGame = async () => {
    if (!lobbyCode || !playerName) return
    
    await fetch("/api/join-game", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ lobbyCode, playerName }),
    })
    
    router.push(`/lobby/${lobbyCode}?playerName=${encodeURIComponent(playerName)}`)
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-8">Join a Game</h1>
      <input
        type="text"
        value={playerName}
        onChange={(e) => setPlayerName(e.target.value)}
        placeholder="Enter your name"
        className="border-2 border-gray-300 rounded-[1rem] p-3 mb-4 w-64 text-center focus:outline-none focus:border-purple-500"
      />
      <input
        type="text"
        value={lobbyCode}
        onChange={(e) => setLobbyCode(e.target.value)}
        placeholder="Enter lobby code"
        className="border-2 border-gray-300 rounded-[1rem] p-3 mb-4 w-64 text-center focus:outline-none focus:border-purple-500"
      />
      <button 
        onClick={joinGame} 
        className="group transition-all"
        disabled={!lobbyCode || !playerName}
      >
        <div className="bg-purple-500 rounded-[2rem] p-1">
          <div className="px-10 py-4 rounded-[calc(2rem-2px)] bg-white hover:bg-opacity-0 transition-all">
            <span className="text-xl font-bold text-purple-500 group-hover:text-white transition-all">
              Join Game
            </span>
          </div>
        </div>
      </button>
    </div>
  )
}



```

### app/create-game/page.tsx

```typescript
"use client"

import { useState } from "react"
import { useRouter } from "next/navigation"

export default function CreateGame() {
  const [isCreating, setIsCreating] = useState(false)
  const [playerName, setPlayerName] = useState("")
  const router = useRouter()

  const createGame = async () => {
    if (!playerName || isCreating) return
    setIsCreating(true)
    
    try {
      const response = await fetch("/api/create-game", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ playerName })
      })
      const data = await response.json()
      router.push(`/lobby/${data.lobbyCode}?playerName=${encodeURIComponent(playerName)}`)
    } catch (error) {
      setIsCreating(false)
    }
  }

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-4xl font-bold mb-8">Create a Game</h1>
      <input
        type="text"
        value={playerName}
        onChange={(e) => setPlayerName(e.target.value)}
        placeholder="Enter your name"
        className="border-2 border-gray-300 rounded-[1rem] p-3 mb-4 w-64 text-center focus:outline-none focus:border-purple-500"
        onKeyDown={(e) => {
          if (e.key === 'Enter' && playerName) {
            createGame()
          }
        }}
      />
      <button
        onClick={createGame}
        className="group transition-all"
        disabled={!playerName || isCreating}
      >
        <div className="bg-purple-500 rounded-[2rem] p-1">
          <div className="px-10 py-4 rounded-[calc(2rem-2px)] bg-white hover:bg-opacity-0 transition-all">
            <span className="text-xl font-bold text-purple-500 group-hover:text-white transition-all">
              {isCreating ? "Creating..." : "Create Game"}
            </span>
          </div>
        </div>
      </button>
    </div>
  )
}



```

### app/api/get-players/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { getPlayers } from "../../utils/game-state"

export function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const lobbyCode = searchParams.get("lobbyCode")

  if (!lobbyCode) {
    return NextResponse.json({ error: "Missing lobby code" }, { status: 400 })
  }

  return NextResponse.json({ players: getPlayers(lobbyCode) })
} 
```

### app/api/get-topics/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { getTopics } from "../../utils/game-state"

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const lobbyCode = searchParams.get("lobbyCode")

  if (!lobbyCode) {
    return NextResponse.json({ error: "Missing lobby code" }, { status: 400 })
  }

  const topics = getTopics(lobbyCode)
  return NextResponse.json({ topics })
} 
```

### app/api/get-questions/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { getQuestions } from "../../utils/game-state"

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const lobbyCode = searchParams.get("lobbyCode")

  if (!lobbyCode) {
    return NextResponse.json({ error: "Missing lobby code" }, { status: 400 })
  }

  const questions = getQuestions(lobbyCode)
  console.log("GET questions for lobby:", lobbyCode, "Questions:", questions)
  return NextResponse.json(questions)
}



```

### app/api/generate-questions/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import { openai } from "@ai-sdk/openai"
import { generateText } from "ai"
import { QUESTIONS_PER_GAME } from "../../utils/game-state"

export async function POST(req: NextRequest) {
  const { topics } = await req.json()

  const prompt = `Generate ${QUESTIONS_PER_GAME} trivia questions and answers based on the following topics: ${topics.join(", ")}. Format the output as a JSON array of objects, each with 'question' and 'answer' properties. Do NOT create any question where the topic is the answer.`

  try {
    const { text } = await generateText({
      model: openai("gpt-4o"),
      prompt: prompt,
    })

    const questions = JSON.parse(text)
    return NextResponse.json(questions)
  } catch (error) {
    console.error("Error generating questions:", error)
    return NextResponse.json({ error: "Failed to generate questions" }, { status: 500 })
  }
}



```

### app/api/join-game/route.ts

```typescript
import { type NextRequest, NextResponse } from "next/server"
import Pusher from "pusher"
import { addPlayer } from "../../utils/game-state"

const pusher = new Pusher({
  appId: process.env.PUSHER_APP_ID!,
  key: process.env.PUSHER_KEY!,
  secret: process.env.PUSHER_SECRET!,
  cluster: process.env.PUSHER_CLUSTER!,
  useTLS: true,
})

// Access the global game state
const globalForGameState = global as typeof globalThis & {
  gameStates: Record<string, {
    currentQuestion: number;
    scores: Record<string, number>;
    answeredPlayers: Record<number, Set<string>>;
    players: Set<string>;
  }>
}

if (!globalForGameState.gameStates) {
  globalForGameState.gameStates = {}
}

export async function POST(req: NextRequest) {
  const { lobbyCode, playerName } = await req.json()

  // Initialize game state if it doesn't exist
  if (!globalForGameState.gameStates[lobbyCode]) {
    globalForGameState.gameStates[lobbyCode] = {
      currentQuestion: 0,
      scores: {},
      answeredPlayers: { 0: new Set() },
      players: new Set()
    }
  }

  // Add player to the game state
  globalForGameState.gameStates[lobbyCode].players.add(playerName)

  // Add the player to our tracking (keeping this for backwards compatibility)
  addPlayer(lobbyCode, playerName)

  await pusher.trigger(`lobby-${lobbyCode}`, "player-joined", {
    player: playerName,
  })

  return NextResponse.json({ success: true })
} 
```

[10 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]