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/game-invite #137

Merged
merged 45 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5d1555b
feat: activate loggers on game
iaurg Nov 27, 2023
1331bcf
feat: component to invite user to a game
iaurg Nov 27, 2023
569e2c1
feat: game invite prototype
GuiMartinelli Nov 28, 2023
f0f85c8
ref: inviteError event name change
GuiMartinelli Nov 28, 2023
e7593b2
Feature: Implement chat DMs between friends (#128)
vcwild Nov 27, 2023
c9ff948
feat/127-add-alt (#132)
iaurg Nov 27, 2023
ad24e88
Fix/125-chat-reload (#133)
iaurg Nov 27, 2023
4a2ccc4
feat/game-actions (#134)
iaurg Nov 27, 2023
3bec812
feat: unmute user automatically after 5s (#136)
iaurg Nov 27, 2023
1af54af
feat/owner-chat-actions (#135)
iaurg Nov 28, 2023
b48a045
Feat/profile popover (#138)
iaurg Nov 28, 2023
54619ed
Feat/descriptive top buttons (#139)
iaurg Nov 28, 2023
b01a2d5
Fixes 2fa (#130)
julianochoi Nov 28, 2023
27c9042
Feat/front block user (#144)
iaurg Nov 28, 2023
aba52af
feat: implement block user (#148)
vcwild Nov 29, 2023
1b848a2
Fix: Cleaning abandoned lobby rooms (#142)
GuiMartinelli Nov 29, 2023
27f22f3
feat: activate loggers on game
iaurg Nov 27, 2023
3b2a9fc
feat: component to invite user to a game
iaurg Nov 27, 2023
b09d05d
feat: make game a global context
iaurg Nov 30, 2023
462b27e
refactor: game invite to game
iaurg Dec 1, 2023
45e1c63
style: lint file
iaurg Dec 1, 2023
870c0bd
feat: implement invite player to game
iaurg Dec 1, 2023
31b3dc7
fix: returning game copy instead game reference
GuiMartinelli Dec 1, 2023
c264920
ref: cleaning invite lobby in disconnect
GuiMartinelli Dec 1, 2023
3ccb5f0
fix:game invite
GuiMartinelli Dec 1, 2023
08e1adb
feat: modal to handle invite actions
iaurg Dec 1, 2023
c84039e
refactor: remove not used imports
iaurg Dec 1, 2023
08763f0
feat: add guestRejected emit socket
iaurg Dec 2, 2023
f486802
refactor: update match history create to update with login
iaurg Dec 2, 2023
955a0d3
refactor: remove not used player user id
iaurg Dec 2, 2023
a0f017b
feat: add invite rejected actions and alerts
iaurg Dec 2, 2023
01a8bd0
fix: inviting offline player
GuiMartinelli Dec 2, 2023
e9a6811
ref: class attributes
GuiMartinelli Dec 2, 2023
29a3a83
feat: stopGame event handler
GuiMartinelli Dec 2, 2023
c39a773
fix: event call stopGame on gameFinished handler
GuiMartinelli Dec 2, 2023
da8ee5a
feat: modal in game not show
iaurg Dec 2, 2023
7208f3b
fix: breaking line names
iaurg Dec 2, 2023
970b8d4
refactor: remove not used template
iaurg Dec 2, 2023
c09144e
style: responsive elements
iaurg Dec 2, 2023
7fd1d9f
Merge branch 'main' into feat/game-invite
iaurg Dec 2, 2023
eb8e365
fix: client not leaving socket room
GuiMartinelli Dec 5, 2023
31ca0e2
fix: abandoned game still running
GuiMartinelli Dec 5, 2023
e8f47a3
feat: player that abandone game looses
GuiMartinelli Dec 5, 2023
9e6e3fe
ref: removing useless code
GuiMartinelli Dec 5, 2023
83c4611
ref: removing useless code
GuiMartinelli Dec 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/src/friends/friends.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ export class FriendsService {
}

async blockUser(userId: string, createFriendDto: CreateFriendDto) {

const blocked = await this.prisma.user.findUnique({
where: { id: userId },
select: { blocked: { where: { id: createFriendDto.friend_id } } },
});


if (blocked.blocked.length > 0) {
throw new NotAcceptableException('User already blocked');
}
Expand All @@ -116,6 +118,7 @@ export class FriendsService {
where: { id: createFriendDto.friend_id },
data: { blocked: { connect: [{ id: userId }] } },
});

// check if user is friend
const friendship = await this.prisma.user.findUnique({
where: { id: userId },
Expand Down
4 changes: 4 additions & 0 deletions backend/src/game/dto/game.invite.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class GameInviteDto {
inviting: string; //Socket ID player 1
guest: string; //Login player 2
}
1 change: 0 additions & 1 deletion backend/src/game/dto/game.player.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export class Player {
login: string;
socketId: string;
userId: string;
x: number;
y: number;
width: number;
Expand Down
98 changes: 86 additions & 12 deletions backend/src/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ 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 { GameInviteDto } from './dto/game.invite.dto';
import { JwtService } from '@nestjs/jwt';
import { Logger } from '@nestjs/common';

@WebSocketGateway({ namespace: '/game' })
export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {

@WebSocketServer()
gameServer: Server;
private PADDLE_WIDTH = 10;
private PADDLE_HEIGHT = 150;
private FRAMES_PER_SECOND = 60;
private readonly logger = new Logger(GameGateway.name);
private pool = new Map<string, Socket>();
private gamesPlaying: Map<string, GameDto> = new Map();

constructor(
private gameService: GameService,
Expand All @@ -30,26 +38,31 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
this.gameLobby.PADDLE_WIDTH = this.PADDLE_WIDTH;
}

@WebSocketServer()
gameServer: Server;
private gamesPlaying: Map<string, GameDto> = new Map();

handleConnection(client: Socket) {
// Get user from cookie coming from client
const user = client.handshake.auth.token;

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

console.log(`Client ${decodedUser} connected`);
this.pool.set(decodedUser, client);

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

handleDisconnect(client: Socket) {
// Get user from cookie coming from client
const user = client.handshake.auth.token;

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

const gameId = this.finishGame(client);
client.leave(gameId);
this.gameLobby.abandoneLobby(client.id);
this.gameServer.to(gameId).emit('gameAbandoned', this.gamesPlaying[gameId]);
console.log(`Client ${client.id} disconnected`);
this.pool.delete(decodedUser);
this.logger.log(`Client ${client.id} disconnected`);
}

@SubscribeMessage('joinGame')
Expand All @@ -58,7 +71,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
const decodedUser = this.jwtService.decode(user).sub;

if (this.gameLobby.joinPlayer1(client, decodedUser)) {
console.log(`waiting Player 2`);
this.logger.log(`Client ${client.id} joined game`);
client.emit('waitingPlayer2', `game_${client.id}`);
} else {
const game = this.gameLobby.joinPlayer2(client, decodedUser);
Expand All @@ -69,6 +82,56 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
}
}

@SubscribeMessage('createInvite')
inviteGame(client: Socket, info: GameInviteDto) {
const user = client.handshake.auth.token;
const decodedUser = this.jwtService.decode(user).sub;

this.logger.debug(
`Client ${client.id} created a invite game for ${info.guest}`,
);

if (
this.gameService.checkGuestAvailability(
info.guest,
this.pool,
)
) {
this.gameLobby.invitePlayer1(client, decodedUser);
this.logger.log(`Client ${client.id} created a invite game`);
info.inviting = client.id;
const guest = this.pool.get(info.guest);
guest.emit('invited', info);
} else {
client.emit('guestRejected', `Convidado não disponível`);
}
}

@SubscribeMessage('inviteAccepted')
inviteAccepted(client: Socket, info: GameInviteDto) {
const user = client.handshake.auth.token;
const decodedUser = this.jwtService.decode(user).sub;

const game = this.gameLobby.invitePlayer2(client, decodedUser, info);

if (game == undefined) {
client.emit('inviteError', `Jogo não encontrado`);
return;
}

this.gamesPlaying[game.gameId] = game;
this.gameService.restartBall(this.gamesPlaying[game.gameId]);
this.gameServer.to(game.gameId).emit('gameCreated', game.gameId);
this.startGame(client, game.gameId);
}

@SubscribeMessage('inviteRejected')
inviteRejected(client: Socket, info: GameInviteDto) {
this.gameLobby.inviteRejected(info);
client.leave(`game_${info.inviting}`);
this.gameServer.to(`game_${info.inviting}`).emit('guestRejected');
}

@SubscribeMessage('startGame')
startGame(client: Socket, gameId: string) {
const game = this.gamesPlaying[gameId];
Expand All @@ -78,10 +141,11 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
this.gameService.addPoint(game);
this.gameService.restartBall(game);
}

if (this.gameService.isGameFinished(game)) {
this.gameServer.to(gameId).emit('gameFinished', game);
this.finishGame(client);
}

if (game.finished) {
return;
}
Expand All @@ -94,6 +158,11 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
}, 1000 / this.FRAMES_PER_SECOND);
}

@SubscribeMessage('stopGame')
stopGame(client: Socket) {
this.finishGame(client);
}

@SubscribeMessage('movePlayer')
movePlayer(_: Socket, info: GameMoveDto) {
this.gameService.updatePlayerPosition(this.gamesPlaying[info.gameId], info);
Expand All @@ -102,13 +171,18 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
private finishGame(client: Socket): string {
const gameId = Object.keys(this.gamesPlaying).find((gameId) => {
return (
this.gamesPlaying[gameId].player1.id === client.id ||
this.gamesPlaying[gameId].player2.id === client.id
this.gamesPlaying[gameId].player1.socketId === client.id ||
this.gamesPlaying[gameId].player2.socketId === client.id
);
});
if (gameId) {
this.gamesPlaying[gameId].finished = true;
this.gamesPlaying.delete(gameId);
if (this.gamesPlaying[gameId].finished == false)
this.gameService.setWinner(this.gamesPlaying[gameId], client.id);
this.gameServer.to(gameId).emit('gameFinished', this.gamesPlaying[gameId]);
client.leave(gameId);
setTimeout(() => {
this.gamesPlaying.delete(gameId);
}, 1000);
return gameId;
}
return null;
Expand Down
31 changes: 27 additions & 4 deletions backend/src/game/game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Player } from './dto/game.player.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { User } from '@prisma/client';
import { MatchHistoryService } from 'src/match-history/match-history.service';
import { Socket } from 'socket.io';

@Injectable()
export class GameService {
Expand All @@ -17,8 +18,8 @@ export class GameService {

public PADDLE_WIDTH: number;
public PADDLE_HEIGHT: number;
private MAX_SCORE = 2;
private BALL_SPEED = 6;
private MAX_SCORE = 5;
private BALL_SPEED = 5;
private BALL_ACCELERATION = 1.1;

updateBallPosition(gameDto: GameDto) {
Expand Down Expand Up @@ -174,10 +175,11 @@ export class GameService {
winnerPoints = gameDto.score.player2;
loserPoints = gameDto.score.player1;
}

try {
await this.matchHistoryService.create({
winnerId: winner.userId,
loserId: loser.userId,
winnerLogin: winner.login,
loserLogin: loser.login,
winnerPoints,
loserPoints,
});
Expand All @@ -186,6 +188,27 @@ export class GameService {
}
}

checkGuestAvailability(
player: string,
pool: Map<string, Socket>,
): boolean {
if (pool.get(player) == undefined)
return false;
return true;
}

setWinner(gameDto: GameDto, socketId: string) {
if (gameDto.player1.socketId == socketId) {
gameDto.score.player1 = 0;
gameDto.score.player2 = 5;
gameDto.finished = true;
} else {
gameDto.score.player2 = 0;
gameDto.score.player1 = 5;
gameDto.finished = true;
}
}

private async storeGameResult(gameDto: GameDto) {
await this.updatePlayerVictory(gameDto);

Expand Down
53 changes: 47 additions & 6 deletions backend/src/game/lobby/game.lobby.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { GameDto } from '../dto/game.dto';
import { GameInviteDto } from '../dto/game.invite.dto';

@Injectable()
export class GameLobbyService {
public PADDLE_WIDTH: number;
public PADDLE_HEIGHT: number;
private lobby: GameDto[] = [];
private invite_lobby = new Map<string, GameDto>();
private PLAYER_INITIAL_X = 0;
private CANVAS_WIDTH = 858;
private CANVAS_HEIGHT = 525;
private readonly logger = new Logger(GameLobbyService.name);

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');
this.lobby.push(gameDto);
this.logger.log(`Client player 1 joined`);
player.join(`game_${gameDto.player1.socketId}`);
return true;
} else {
Expand All @@ -28,26 +31,60 @@ export class GameLobbyService {
gameDto.player2 = {
login,
socketId: player.id,
userId: '',
x: gameDto.canvas.width - this.PLAYER_INITIAL_X - this.PADDLE_WIDTH,
y: this.CANVAS_HEIGHT / 2 - this.PADDLE_HEIGHT / 2,
width: this.PADDLE_WIDTH,
height: this.PADDLE_HEIGHT,
};
player.join(`game_${gameDto.player1.socketId}`);
console.log('Player 2 joined');
this.logger.log(`Client player 2 joined`);
this.lobby.splice(0, 1);
return gameDto;
}

invitePlayer1(player: any, login: string) {
const gameDto = this.initGame(player.id);
gameDto.player1.login = login;
this.invite_lobby.set(`game_${player.id}`, gameDto);
this.logger.log(`Client player 1 joined invite game`);
player.join(`game_${gameDto.player1.socketId}`);
}

invitePlayer2(player: any, login: string, info: GameInviteDto) {
if (this.invite_lobby.get(`game_${info.inviting}`) == undefined) {
return;
}

const gameDto = Object.assign(
this.invite_lobby.get(`game_${info.inviting}`),
);

gameDto.player2 = {
login,
socketId: player.id,
x: gameDto.canvas.width - this.PLAYER_INITIAL_X - this.PADDLE_WIDTH,
y: this.CANVAS_HEIGHT / 2 - this.PADDLE_HEIGHT / 2,
width: this.PADDLE_WIDTH,
height: this.PADDLE_HEIGHT,
};
player.join(`game_${info.inviting}`);
this.logger.log(`Client player 2 joined invited game`);
this.invite_lobby.delete(`game_${info.inviting}`);
return gameDto;
}

inviteRejected(info: GameInviteDto) {
this.logger.log(`Client player 2 rejected invited game`);
this.invite_lobby.delete(`game_${info.inviting}`);
}

initGame(player1Id: string): GameDto {
const gameDto: GameDto = {
gameId: `game_${player1Id}`,
finished: false,
player1: {
login: '',
socketId: player1Id,
userId: '',
x: this.PLAYER_INITIAL_X,
y: this.CANVAS_HEIGHT / 2 - this.PADDLE_HEIGHT / 2,
width: this.PADDLE_WIDTH,
Expand Down Expand Up @@ -78,5 +115,9 @@ export class GameLobbyService {
(item) => item.gameId == `game_${playerId}`,
);
if (index >= 0) this.lobby.splice(index);

if (this.invite_lobby.get(`game_${playerId}`) != undefined)
this.invite_lobby.delete(`game_${playerId}`);

}
}
4 changes: 2 additions & 2 deletions backend/src/match-history/dto/create-match-history.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { IsInt, IsNotEmpty, IsString } from 'class-validator';
export class CreateMatchHistoryDto {
@IsNotEmpty()
@IsString()
winnerId: string;
winnerLogin: string;

@IsNotEmpty()
@IsString()
loserId: string;
loserLogin: string;

@IsNotEmpty()
@IsInt()
Expand Down
Loading
Loading