Skip to content

About

neu-cs4530-classroom-sp26-spring26-project-spring-2026-template created by GitHub Classroom

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

CI Pipeline Verified

Review Assignment Due Date The individual and team project for this class are designed to mirror the experiences of a software engineer joining a new development team: you will be “onboarded” to our codebase, make several individual contributions, and then form a team to propose, develop and implement new features. The codebase that we'll be developing is GameNite, a website that answers the question "what if Twitch, but for correspondence chess?" hello

You will get an opportunity to work with the starter code which provides basic skeleton for the app and then additional features will be proposed and implemented by you! All implementation will take place in the TypeScript programming language, using React for the user interface.

Getting Started

Run npm install in the root directory to install all dependencies for the client, server, and shared folders.

Working on the application

While you're working on the application, it's useful to run it in "development mode" locally. Development mode watches files for changes and updates the application when changes happen.

To run gamenite locally in development mode, do one of the following:

  1. Run npm run dev in the top-level directory
  2. Open two terminal windows
    • In the first, navigate to the server directory and run npm run dev
    • In the second, navigate to the client directory and also run npm run dev

The second terminal window, the one in the client directory, shows a URL that you should go to to preview the application, probably http://localhost:4530/. You can use the default username/password combinations user0/pwd0000, user1/pwd1111, user2/pwd2222, and user3/pwd3333 to log in.

Checking the application

Checks can be run on every part of the application at once by running the following commands from the repository root:

  • npm run check - Checks all three projects with TypeScript
  • npm run lint - Checks all three projects with ESLint
  • npm run test - Runs Vitest tests on all three projects and end-to-end Playwright tests

Building the application

If you want to deploy the application or build it in production mode, running npm run build -w=client in the root of the repository will create the production build of the client. Then, the server can be started in production mode by running npm start -w=server and accessed by going to http://localhost:8000/.

Codebase Folder Structure

  • client: Contains the frontend application code, responsible for the user interface and interacting with the backend. This directory includes all React components and related assets.
  • server: Contains the backend application code, handling the logic, APIs, and database interactions. It serves requests from the client and processes data accordingly.
  • shared: Contains all shared type definitions that are used by both the client and server. This helps maintain consistency and reduces duplication of code between the two folders.

API Routes

The server provides the following REST endpoints: requests are routed to these endpoints in server/src/app.ts.

/api/game

Endpoint Method Description
/create POST Create new game
/list GET List all games
/:id GET Get information about a specific game

/api/thread

Endpoint Method Description
/create POST Create new forum post
/list GET List all forum posts
/:id GET Get information about a form post
/:id/comment POST Add a comment to a forum post

/api/user

Endpoint Method Description
/list POST Get details of a list of users
/login POST Validate username/password entry
/signup POST Create a new user
/:username POST Update user's displayname or password
/:username GET Get information about a user

/api/friend

Method Route Purpose
POST /request Send a friend request ({ auth, payload: {toUsername} })
POST /respond Accept/reject a request ({ auth, payload: {requestId, action} })
POST /remove Remove a friend ({ auth, payload: {friendUsername} })
GET /list/:username Get user's accepted friends list
GET /requests/:username Get pending incoming/outgoing requests

/api/dm

Method Route Purpose
GET /list/:username Get all DM conversations for a user
GET /:id Get a specific DM conversation (with messages)

/list/:username needs "x-password: [insert user password]" as a header in the request to authenticate themselves /:id needs both "x-username" and "x-password"

/api/block

Method Route Purpose
POST /block Block a user ({ auth, payload: {targetUsername} })
POST /unblock Unblock a user ({ auth, payload: {targetUsername} })
GET /list/:username Get user's blocked list

/list/:username needs "x-password: [insert user password]" as a header in the request to authenticate themselves

Side-effects of blocking
  • Blocking a user should cascade: remove existing friendship, cancel pending friend requests, and remove DMs between both users.
  • Blocking is one-directional (A blocks B does not mean B blocks A).
  • sendFriendRequest and DM messaging should check the block list and reject if either user has blocked the other.

/api/lobby

Method Route Description
POST /create Create a new lobby (public/private toggle, game type, settings)
GET /list List all public lobbies that haven't started yet
POST /invited Get all lobbies where the authenticated user has a pending invite
GET /:id Get information about a specific lobby
POST /:id/invite Invite a player to the lobby (host only)
POST /:id/join Join a public lobby or accept a pending invite
POST /join-by-code Join a lobby using a unique lobby code
POST /:id/leave Leave a lobby before the game starts
POST /:id/decline Decline a pending lobby invitation
POST /:id/remove Remove a player from the lobby (host only)
POST /:id/settings Update lobby settings — mode, difficulty, timer (host only)
POST /:id/start Start the game from the lobby (host only, min players required)

Most lobby endpoints require authentication via { auth: { username, password } } in the request body. The lobby creator is the host and has exclusive access to invite, remove, settings, and start actions.

/api/stats

Method Route Description
GET /player/:username Get aggregated stats (wins, losses, win rate, badges)
GET /history/:username Get paginated match history with optional filters
GET /leaderboard Get global leaderboard ranked by win rate
POST /leaderboard-opt-out Toggle leaderboard opt-out for the authenticated user
GET /leaderboard-opt-out/:username Check if a player has opted out of the leaderboard

Query parameters for /history/:username:

  • page (default: 1), limit (default: 10, max: 50)
  • gameType — filter by game type (nim/guess)
  • opponent — filter by opponent username
  • dateFrom, dateTo — ISO date strings for date range filtering

Query parameters for /leaderboard:

  • page (default: 1), limit (default: 10, max: 50)
  • period — time filter: week, month, or all (default)

Websockets

The Socket.io API for event-driven communication between clients and the server is detailed in shared/src/socket.types.ts.

Data Architecture

This web application stores information about users, forum posts, games, lobbies, friends, direct messages, and player stats. The structure of the data can be described by this diagram:

erDiagram
    Auth {
        string username "unique key"
        RecordId userId "unique"
        string password ""
    }

    User {
        RecordId userId "generated key"
        string username "unique"
        string display ""
        DateISO createdAt ""
        Record friends "userId set"
        Record friendInReqs "userId to requestId"
        Record friendOutReqs "userId to requestId"
        Record directChats "userId to chatId"
        Record blocked "userId set"
    }
    User ||--|| Auth: "Auth.userId"

    Thread {
        RecordId threadId "generated key"
        string title ""
        string text ""
        DateISO createdAt ""
        RecordId createdBy ""
        RecordId[] comments ""
    }
    Thread }o--|| User: "Thread.createdBy"
    Thread ||--o{ Comment: "Thread.comments"

    Comment {
        RecordId commentId "generated key"
        string text ""
        RecordId createdBy ""
        DateISO createdAt ""
        DateISO editedAt "optional"
    }
    Comment }o--|| User: "Comment.createdBy"

    Game {
        RecordId gameId "generated key"
        GameKey type "nim or guess"
        unknown state "optional"
        boolean done ""
        boolean isPrivate ""
        RecordId chat ""
        RecordId[] players ""
        number timerDurationSeconds "optional"
        DateISO timerEndsAt "optional"
        DateISO createdAt ""
        RecordId createdBy ""
    }
    Game ||--|| Chat: "Game.chat"
    Game }o--|| User: "Game.createdBy"
    Game }o--o{ User: "Game.players"

    GameHistory {
        RecordId gameHistoryId "generated key"
        GameKey type "nim or guess"
        unknown state "final state"
        RecordId[] players ""
        DateISO endedAt ""
        boolean endedByTimer "optional"
    }
    GameHistory }o--o{ User: "GameHistory.players"

    Chat {
        RecordId chatId "generated key"
        RecordId[] messages ""
        MoveLogEntry[] moveLog ""
        DateISO createdAt ""
    }
    Chat ||--o{ Message: "Chat.messages"

    Message {
        RecordId messageId "generated key"
        string text ""
        RecordId createdBy ""
        DateISO createdAt ""
    }
    Message }o--|| User: "Message.createdBy"

    Lobby {
        RecordId lobbyId "generated key"
        GameKey type "nim or guess"
        boolean isPrivate ""
        string code "unique join code"
        RecordId createdBy ""
        LobbyPlayer[] players ""
        LobbySettings settings ""
        RecordId chatId ""
        RecordId startedGameId "optional"
        DateISO createdAt ""
    }
    Lobby }o--|| User: "Lobby.createdBy"
    Lobby ||--|| Chat: "Lobby.chatId"
    Lobby |o--o| Game: "Lobby.startedGameId"

    FriendRequest {
        RecordId id "generated key"
        RecordId fromUser ""
        RecordId toUser ""
        string status "pending, accepted, or rejected"
        DateISO createdAt ""
        DateISO respondedAt "optional"
    }
    FriendRequest }o--|| User: "FriendRequest.fromUser"
    FriendRequest }o--|| User: "FriendRequest.toUser"

    DirectChat {
        RecordId id "generated key"
        RecordId[] participants "two user IDs"
        RecordId[] messages ""
        DateISO createdAt ""
        Record lastReadAt "userId to DateISO"
    }
    DirectChat }o--o{ User: "DirectChat.participants"
    DirectChat ||--o{ Message: "DirectChat.messages"

    PlayerStats {
        RecordId playerStatsId "generated key"
        RecordId userId ""
        string username ""
        number wins ""
        number losses ""
        number gamesPlayed ""
        number winRate "0 to 1"
        string[] badges ""
        number currentWinStreak ""
        number bestWinStreak ""
        boolean leaderboardOptOut ""
        DateISO lastPlayedAt ""
    }
    PlayerStats ||--|| User: "PlayerStats.userId"
Loading

Games

To create a new game example, you need to take the following steps:

  • In a new file shared/src/games/example.types.ts, define the game's state: what gets stored on the server as an ExampleState, what gets sent to players as an ExampleView, and what players send as moves as an ExampleMove.
  • In the existing file shared/src/game.types.ts:
    • The ExampleView needs to be imported from shared/src/games/example.types.ts.
    • Everything in shared/src/games/example.ts file needs be exported (so it can be used in other files that import game.types.ts).
    • The GameKey example needs to be added to zGameKey and { type: 'example'; view: ExampleView } needs to be added to TaggedGameView.
  • In a new file server/src/games/example.ts, the rules of the game, which are evaluated in the backend server, need to be added. This file should export exampleLogic and exampleGameService.
  • In the existing file server/src/services/game.service.ts, the mapping from example to exampleGameService needs to be added to gameServices.
  • In a new file client/src/games/ExampleGame.tsx, a React component ExampleGame needs to be defined, which takes GameProps<ExampleView, ExampleMove> as its props.
  • In the existing file client/src/games/GameDispatch.tsx, a case statement for 'example' needs to be added.
  • In the existing file client/src/util/consts.ts, a mapping from example to the user-facing name for the game needs to be added.

About

neu-cs4530-classroom-sp26-spring26-project-spring-2026-template created by GitHub Classroom

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages