Skip to content

Commit

Permalink
app: Major refactor.
Browse files Browse the repository at this point in the history
- Converted .d.ts to .ts in the backend.
- Importing types wherever necessary.
- implemented endpoints for join and create game.
- Implement api calls in the frontend form join
and create game
- Attached id attribute to GameEngine.
- converted userRoutes to typescript
- setup nodemon
  • Loading branch information
kuv2707 committed Jun 15, 2024
1 parent b59e0e8 commit 4a87a43
Show file tree
Hide file tree
Showing 24 changed files with 213 additions and 109 deletions.
6 changes: 6 additions & 0 deletions backend/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"watch": ["src/**/*.ts", "src/**/*.js"],
"ext": "js,ts",
"ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"],
"exec": "ts-node ./src/index.ts"
}
45 changes: 44 additions & 1 deletion backend/src/controllers/gameControllers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Response } from 'express';
import { enqueueForSend } from '../eventRecipients';
import { AuthRequest } from '../middlewares/authMiddleware';
import { retrieveGame } from '../gameStore';
import { createGame, retrieveGame } from '../gameStore';

export async function handleGameEvent(req: AuthRequest, res: Response) {
const event = req.body;
Expand Down Expand Up @@ -35,3 +35,46 @@ export async function handleGameEvent(req: AuthRequest, res: Response) {
res.status(200).send({ message: 'Event propagated to clients.' });
}
}

export async function handleGameJoin(req: AuthRequest, res: Response) {
const gameCode = req.body.code;
const activeGameId = req.user.activeGameId;
if (activeGameId) {
res.status(400).send({
message: 'User is already playing a game',
});
return;
}
const game = retrieveGame(gameCode);
if (!game) {
res.status(404).send({ message: 'Game not found' });
return;
}
//note: when retrieving game from database, it is not an instance of GameEngine
// we'd need to add these functions to the mongodb game schema
game.dispatchEvent({ type: 'JOIN_GAME', playerId: req.user.id });
req.user.activeGameId = gameCode;
await req.user.save();
res.status(200).send({
message: 'Game joined successfully',
gameState: game,
});
}

export async function handleGameCreate(req: AuthRequest, res: Response) {
const game = createGame();
const eventResult = game.dispatchEvent({
type: 'JOIN_GAME',
playerId: req.user.id,
});
if (eventResult.type === 'ERROR') {
res.status(500).send({ message: 'Failed to create game' });
return;
}
req.user.activeGameId = game.id;
await req.user.save();
res.status(200).send({
message: 'Game created successfully',
gameState: game,
});
}
1 change: 1 addition & 0 deletions backend/src/eventRecipients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// from the queue to the client.

import { Response } from 'express';
import { AppEvent } from './types';

type ClientId = string;

Expand Down
15 changes: 3 additions & 12 deletions backend/src/gameStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@ import { v4 as uuid } from 'uuid';
import { GameEngine } from './uno-game-engine/engine';
const games: Map<string, GameEngine> = new Map();

/**
* Create a new game and store it in the games map
* @returns {string} gameId
*/
export function createGame() {
const gameId = uuid();
const game = new GameEngine();
const game = new GameEngine(gameId);
games.set(gameId, game);
return gameId;
return game;
}

/**
* Retrieve a game from the games map
* @param {string} id gameId
* @returns {GameEngine|null} GameEngine instance
*/
export function retrieveGame(id: string) {
export function retrieveGame(id: string): GameEngine | null {
return games.get(id) || null;
}
4 changes: 3 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import express, { json } from 'express';
import { connect } from 'mongoose';
import cors from 'cors';
import { config } from 'dotenv';
import userRoutes from './routes/userRoutes';
import gameRoutes from './routes/gameRoutes';

config();

Expand All @@ -21,9 +23,9 @@ app.get('/', (req, res) => {
});

//Routes
import userRoutes from './routes/userRoutes.js';

app.use('/api/v1/auth', userRoutes);
app.use('/api/v1/game', gameRoutes);

app.listen(port, () => {
console.log(`Server running on port ${port}`);
Expand Down
1 change: 0 additions & 1 deletion backend/src/middlewares/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const verifyToken = async (
) => {
try {
const accessToken: string = req.body.token;

if (!accessToken) {
return res.status(401).json({ error: 'Access token is required' });
}
Expand Down
9 changes: 8 additions & 1 deletion backend/src/routes/gameRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import express from 'express';
import { addClient } from '../eventRecipients';
import { handleGameEvent } from '../controllers/gameControllers';
import {
handleGameCreate,
handleGameEvent,
handleGameJoin,
} from '../controllers/gameControllers';
import { AuthRequest, verifyToken } from '../middlewares/authMiddleware';

const router = express.Router();
Expand All @@ -15,4 +19,7 @@ router.get('/events', function (req: AuthRequest, res) {

router.post('/events', handleGameEvent);

router.post('/join', handleGameJoin);
router.post('/create', handleGameCreate);

export default router;
File renamed without changes.
43 changes: 30 additions & 13 deletions backend/src/types.d.ts → backend/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
// We declare those types which are used throughout the application here.
// For types that are used only in one file, we can declare them in that file itself.

type CardType = 'number' | 'special' | 'wild';
export type CardType = 'number' | 'special' | 'wild';

type CardColor = 'red' | 'blue' | 'green' | 'yellow' | 'wild';
export type CardColor = 'red' | 'blue' | 'green' | 'yellow' | 'wild';

type SpecialCardName = 'skip' | 'reverse' | 'draw2' | 'draw4' | 'colchange';
type CardNumber = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
export type SpecialCardName =
| 'skip'
| 'reverse'
| 'draw2'
| 'draw4'
| 'colchange';
export type CardNumber =
| '0'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9';

type CardValue = SpecialCardName | CardNumber;
export type CardValue = SpecialCardName | CardNumber;

type UNOCard = {
export type UNOCard = {
type: CardType;
color: CardColor;
value: CardValue;
id: string;
};

type Player = {
export type Player = {
id: string;
cards: UNOCard[];
};

type EventResult = {
export type EventResult = {
type: 'SUCCESS' | 'ERROR';
message: string;
};

type GameEventType = 'DRAW_CARD' | 'THROW_CARD' | 'JOIN_GAME' | 'LEAVE_GAME';
export type GameEventType =
| 'DRAW_CARD'
| 'THROW_CARD'
| 'JOIN_GAME'
| 'LEAVE_GAME';

type GameEvent =
export type GameEvent =
| {
type: 'DRAW_CARD';
playerId: string;
Expand All @@ -47,14 +66,12 @@ type GameEvent =
| {
type: 'JOIN_GAME';
playerId: string;
data: null;
}
| {
type: 'LEAVE_GAME';
playerId: string;
data: null;
};

// Represent all the events that can be sent to the client
type AppEvent = GameEvent;
export type AppEvent = GameEvent;
//todo: Add more events
9 changes: 9 additions & 0 deletions backend/src/uno-game-engine/deck.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import type {
CardColor,
CardNumber,
CardType,
CardValue,
SpecialCardName,
UNOCard,
} from '../types';

const colors: Array<CardColor> = ['red', 'yellow', 'green', 'blue', 'wild'];
const numValues: Array<CardNumber> = [
'0',
Expand Down
5 changes: 4 additions & 1 deletion backend/src/uno-game-engine/engine.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { EventResult, GameEvent, Player, UNOCard } from '../types';
import { getShuffledCardDeck, shuffle } from './deck';
import { handleEvent } from './gameEvents';

export const NUM_CARDS_PER_PLAYER = 7;

export class GameEngine {
id: string;
cardDeck: UNOCard[];
thrownCards: UNOCard[];
players: Player[];
Expand All @@ -13,7 +15,8 @@ export class GameEngine {
direction: number;
status: 'READY' | 'STARTED';

constructor() {
constructor(id: string) {
this.id = id;
this.cardDeck = getShuffledCardDeck();
this.thrownCards = [];
this.players = [];
Expand Down
9 changes: 2 additions & 7 deletions backend/src/uno-game-engine/events/drawCard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import {
checkCurrentPlayer,
getPlayer,
registerEventHandler,
} from '../gameEvents';
import { GameEngine } from '../engine';
import assert from 'assert';
import { canThrowCard, throwCard } from './throwCard';
import { EventResult, GameEvent } from '../../types';
import { checkCurrentPlayer, getPlayer } from './eventHandlerUtils';

export function drawCard(game: GameEngine, event: GameEvent): EventResult {
// validate the event so that typescript knows that event is of type 'DRAW_CARD'
Expand Down Expand Up @@ -50,5 +47,3 @@ export function drawCard(game: GameEngine, event: GameEvent): EventResult {
};
}
}

registerEventHandler('DRAW_CARD', drawCard);
27 changes: 27 additions & 0 deletions backend/src/uno-game-engine/events/eventHandlerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// some utility functions shared by event handlers

import { Player, EventResult } from '../../types';
import { GameEngine } from '../engine';

export function getPlayer(game: GameEngine, playerId: string) {
return game.players.find((p) => p.id === playerId);
}

export function getPlayerCard(player: Player, cardId: string) {
return player.cards.find((c) => c.id === cardId);
}

export function checkCurrentPlayer(
game: GameEngine,
player: Player
): EventResult {
const { currentPlayerIndex, players } = game;
const currentPlayer = players[currentPlayerIndex];

// check if the player is the current player
if (currentPlayer.id !== player.id) {
return { type: 'ERROR', message: 'It is not your turn' };
}

return { type: 'SUCCESS', message: 'Can draw/throw card' };
}
4 changes: 1 addition & 3 deletions backend/src/uno-game-engine/events/joinGame.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { assert } from 'console';
import { GameEngine } from '../engine';
import { registerEventHandler } from '../gameEvents';
import { EventResult, GameEvent, Player } from '../../types';

export function joinGame(game: GameEngine, event: GameEvent): EventResult {
assert(event.type === 'JOIN_GAME', 'Invalid event type');
const player: Player = { id: event.playerId, cards: [] };
game.addPlayer(player);
return { type: 'SUCCESS', message: 'player joined successfully' };
}

registerEventHandler('JOIN_GAME', joinGame);
5 changes: 2 additions & 3 deletions backend/src/uno-game-engine/events/leaveGame.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assert } from 'console';
import { GameEngine } from '../engine';
import { getPlayer, registerEventHandler } from '../gameEvents';
import { EventResult, GameEvent } from '../../types';
import { getPlayer } from './eventHandlerUtils';

export function leaveGame(game: GameEngine, event: GameEvent): EventResult {
assert(event.type === 'LEAVE_GAME', 'Invalid event type');
Expand All @@ -11,5 +12,3 @@ export function leaveGame(game: GameEngine, event: GameEvent): EventResult {
game.removePlayer(player);
return { type: 'SUCCESS', message: 'player left successfully' };
}

registerEventHandler('LEAVE_GAME', leaveGame);
10 changes: 4 additions & 6 deletions backend/src/uno-game-engine/events/throwCard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { GameEngine } from '../engine';
import assert from 'assert';
import { EventResult, GameEvent, Player, UNOCard } from '../../types';
import {
checkCurrentPlayer,
getPlayer,
getPlayerCard,
registerEventHandler,
} from '../gameEvents';
import { GameEngine } from '../engine';
import assert from 'assert';
} from './eventHandlerUtils';

export function canThrowCard(
game: GameEngine,
Expand Down Expand Up @@ -94,5 +94,3 @@ function handleSpecialCard(game: GameEngine, card: UNOCard) {
break;
}
}

registerEventHandler('THROW_CARD', throwCard);
Loading

0 comments on commit 4a87a43

Please sign in to comment.