Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/leaderboard #78

Merged
merged 15 commits into from
Oct 15, 2023
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "prettier.prettierPath": "./node_modules/prettier" }
{ "prettier.prettierPath": "./backend/node_modules/prettier" }
6,183 changes: 20 additions & 6,163 deletions backend/package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^10.0.3",
"@nestjs/mapped-types": "*",
"@nestjs/passport": "^9.0.3",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-socket.io": "^9.4.3",
"@nestjs/websockets": "^9.4.3",
"@nestjs/platform-socket.io": "^9.0.0",
"@nestjs/websockets": "^9.0.0",
"@prisma/client": "^4.12.0",
"argon2": "^0.30.3",
"class-transformer": "^0.5.1",
Expand All @@ -49,6 +49,7 @@
"rxjs": "^7.2.0"
},
"devDependencies": {
"@faker-js/faker": "^8.1.0",
"@nestjs/cli": "^9.5.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
Expand Down Expand Up @@ -95,6 +96,7 @@
"testEnvironment": "node"
},
"prisma": {
"schema": "src/database/schema.prisma"
"schema": "src/database/schema.prisma",
"seed": "ts-node src/database/seed.ts"
}
}
4 changes: 2 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ChatModule } from './chat/chat.module';
import { GameModule } from './game/game.module';
import { AuthModule } from './auth/auth.module';
import { FriendsModule } from './friends/friends.module';
import { LobbyModule } from './lobby/lobby.module';
import { LeaderboardModule } from './leaderboard/leaderboard.module';

@Module({
imports: [
Expand All @@ -20,7 +20,7 @@ import { LobbyModule } from './lobby/lobby.module';
ChatModule,
GameModule,
FriendsModule,
LobbyModule,
LeaderboardModule,
],
providers: [PrismaService],
})
Expand Down
1 change: 1 addition & 0 deletions backend/src/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ enum FriendshipStatus {
DECLINED
BLOCKED
}

model Message {
id String @id @default(uuid())
user User? @relation(fields: [userLogin], references: [login])
Expand Down
54 changes: 54 additions & 0 deletions backend/src/database/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';

const prisma = new PrismaClient();

/* User Schema
model User {
id String @id @default(uuid())
login String @unique
displayName String
email String
avatar String?
status UserStatus @default(ONLINE)
victory Int @default(0)
friends Friend[] @relation("Friendship")
refreshToken String?
mfaEnabled Boolean @default(false)
mfaSecret String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
chats ChatMember[]
messages Message[]
}
*/

async function main() {
// create 50 users using faker
const users = Array.from({ length: 50 }).map(() => ({
login: faker.internet.userName(),
displayName: faker.person.firstName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
victory: Math.floor(Math.random() * 100),
mfaEnabled: false,
mfaSecret: 'secret',
}));

await prisma.user.createMany({
data: users,
});
}

main()
.then(async () => {
await prisma.$disconnect();
})

.catch(async (e) => {
console.error(e);

await prisma.$disconnect();

process.exit(1);
});
5 changes: 2 additions & 3 deletions backend/src/game/dto/game.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
/* eslint-disable prettier/prettier */
import { Ball } from './game.ball.dto';
import { Canvas } from './game.canvas.dto';
import { Player } from './game.player.dto';
import { Score } from './game.score.dto';

export class GameDto {
gameId: string;
finished: boolean;
finished: boolean;
player1: Player;
player2: Player;
score: Score;
score: Score;
ball: Ball;
canvas: Canvas;
}
1 change: 1 addition & 0 deletions backend/src/game/dto/game.player.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class Player {
login: string;
socketId: string;
userId: string;
x: number;
Expand Down
17 changes: 14 additions & 3 deletions backend/src/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { GameService } from './game.service';
import { GameDto } from './dto/game.dto';
import { GameLobbyService } from './lobby/game.lobby.service';
import { GameMoveDto } from './dto/game.move';
import { JwtService } from '@nestjs/jwt';

@WebSocketGateway({ namespace: '/game' })
export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
Expand All @@ -20,6 +21,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
constructor(
private gameService: GameService,
private gameLobby: GameLobbyService,
private jwtService: JwtService,
) {
// set paddle size
this.gameService.PADDLE_HEIGHT = this.PADDLE_HEIGHT;
Expand All @@ -33,7 +35,13 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
private gamesPlaying: Map<string, GameDto> = new Map();

handleConnection(client: Socket) {
console.log(`Client ${client.id} connected`);
// Get user from cookie coming from client
const user = client.handshake.headers.cookie.split(';')[0].split('=')[1];

// Decode user from JWT
const decodedUser = this.jwtService.decode(user).sub;

console.log(`Client ${decodedUser} connected`);
}

handleDisconnect(client: Socket) {
Expand All @@ -45,11 +53,14 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {

@SubscribeMessage('joinGame')
joinGame(client: Socket) {
if (this.gameLobby.joinPlayer1(client)) {
const user = client.handshake.headers.cookie.split(';')[0].split('=')[1];
const decodedUser = this.jwtService.decode(user).sub;

if (this.gameLobby.joinPlayer1(client, decodedUser)) {
console.log(`waiting Player 2`);
client.emit('waitingPlayer2', `game_${client.id}`);
} else {
const game = this.gameLobby.joinPlayer2(client);
const game = this.gameLobby.joinPlayer2(client, decodedUser);
this.gamesPlaying[game.gameId] = game;
this.gameService.restartBall(this.gamesPlaying[game.gameId]);
this.gameServer.to(game.gameId).emit('gameCreated', game.gameId);
Expand Down
3 changes: 2 additions & 1 deletion backend/src/game/game.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Module } from '@nestjs/common';
import { GameGateway } from './game.gateway';
import { GameService } from './game.service';
import { GameLobbyService } from './lobby/game.lobby.service';
import { JwtService } from '@nestjs/jwt';

@Module({
providers: [GameGateway, GameService, GameLobbyService],
providers: [GameGateway, GameService, GameLobbyService, JwtService],
})
export class GameModule {}
43 changes: 25 additions & 18 deletions backend/src/game/game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { GameDto } from './dto/game.dto';
import { GameMoveDto } from './dto/game.move';
import { Player } from './dto/game.player.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { User } from '@prisma/client';

@Injectable()
export class GameService {
Expand Down Expand Up @@ -125,27 +126,33 @@ export class GameService {
return randomAngle;
}

private findWinner(gameDto: GameDto) {
return gameDto.score.player1 > gameDto.score.player2 ? 1 : 2;
}

private async storeGameResult(gameDto: GameDto) {
/*
const player1 = await this.prismaService.user.findFirst({
where: { id: gameDto.player1.userId },
});
let winner: User;

const player2 = await this.prismaService.user.findFirst({
where: { id: gameDto.player2.userId },
if (this.findWinner(gameDto) == 1) {
winner = await this.prismaService.user.findFirst({
where: { login: gameDto.player1.login },
});
} else {
winner = await this.prismaService.user.findFirst({
where: { login: gameDto.player2.login },
});
}

const updateUser = await this.prismaService.user.update({
where: {
login: winner.login,
},
data: {
victory: winner.victory + 1,
},
});
*/

//TODO: change to the real winner id from the database
const winner =
gameDto.score.player1 > gameDto.score.player2
? gameDto.player1
: gameDto.player2;

//TODO: add the game result into the user leaderboard database
console.log(
`Winner: ${winner.socketId} with ${gameDto.score.player1} points`,
);

console.log(`Winner: ${updateUser.displayName}`);
}

isGameFinished(gameDto: GameDto): boolean {
Expand Down
7 changes: 5 additions & 2 deletions backend/src/game/lobby/game.lobby.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ export class GameLobbyService {
private CANVAS_WIDTH = 800;
private CANVAS_HEIGHT = 600;

joinPlayer1(player: any): boolean {
joinPlayer1(player: any, login: string): boolean {
if (this.lobby.length == 0) {
const gameDto = this.initGame(player.id);
this.lobby.push(gameDto);
gameDto.player1.login = login;
console.log('player 1 joined');
player.join(`game_${gameDto.player1.socketId}`);
return true;
Expand All @@ -22,9 +23,10 @@ export class GameLobbyService {
}
}

joinPlayer2(player: any): GameDto {
joinPlayer2(player: any, login: string): GameDto {
const gameDto = this.lobby[0];
gameDto.player2 = {
login,
socketId: player.id,
userId: '',
x: gameDto.canvas.width - this.PLAYER_INITIAL_X - this.PADDLE_WIDTH,
Expand All @@ -43,6 +45,7 @@ export class GameLobbyService {
gameId: `game_${player1Id}`,
finished: false,
player1: {
login: '',
socketId: player1Id,
userId: '',
x: this.PLAYER_INITIAL_X,
Expand Down
1 change: 1 addition & 0 deletions backend/src/leaderboard/entities/leaderboard.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class Leaderboard {}
21 changes: 21 additions & 0 deletions backend/src/leaderboard/leaderboard.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LeaderboardController } from './leaderboard.controller';
import { LeaderboardService } from './leaderboard.service';
import { PrismaService } from '../prisma/prisma.service';

describe('LeaderboardController', () => {
let controller: LeaderboardController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [LeaderboardController],
providers: [LeaderboardService, PrismaService],
}).compile();

controller = module.get<LeaderboardController>(LeaderboardController);
});

it('should return a list of users', () => {
expect(controller.findAll()).toBeDefined();
});
});
17 changes: 17 additions & 0 deletions backend/src/leaderboard/leaderboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Controller, Get, Param } from '@nestjs/common';
import { LeaderboardService } from './leaderboard.service';

@Controller('leaderboard')
export class LeaderboardController {
constructor(private readonly leaderboardService: LeaderboardService) {}

@Get()
findAll() {
return this.leaderboardService.findAll();
}

@Get(':id')
findOne(@Param('id') id: string) {
return this.leaderboardService.findOne(id);
}
}
9 changes: 9 additions & 0 deletions backend/src/leaderboard/leaderboard.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { LeaderboardService } from './leaderboard.service';
import { LeaderboardController } from './leaderboard.controller';

@Module({
controllers: [LeaderboardController],
providers: [LeaderboardService],
})
export class LeaderboardModule {}
19 changes: 19 additions & 0 deletions backend/src/leaderboard/leaderboard.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LeaderboardService } from './leaderboard.service';
import { PrismaService } from '../prisma/prisma.service';

describe('LeaderboardService', () => {
let service: LeaderboardService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LeaderboardService, PrismaService],
}).compile();

service = module.get<LeaderboardService>(LeaderboardService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
36 changes: 36 additions & 0 deletions backend/src/leaderboard/leaderboard.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class LeaderboardService {
constructor(private prismaService: PrismaService) {}

findAll() {
// get all users and order by victory
return this.prismaService.user.findMany({
orderBy: {
victory: 'desc',
},
select: {
login: true,
victory: true,
avatar: true,
id: true,
},
});
}

findOne(id: string) {
// get user by id
return this.prismaService.user.findUnique({
where: {
id: id,
},
select: {
login: true,
victory: true,
avatar: true,
},
});
}
}
Loading
Loading