Browse Source

some more make rules

master
nicolas-arnaud 2 years ago
parent
commit
843494c6ce
  1. 3
      Makefile
  2. 3
      Makesudo
  3. 4
      back/Dockerfile
  4. 16
      back/volume/.eslintrc.json
  5. 2096
      back/volume/package-lock.json
  6. 12
      back/volume/package.json
  7. 6
      back/volume/src/app.module.ts
  8. 16
      back/volume/src/main.ts
  9. 117
      back/volume/src/pong/game/Ball.ts
  10. 285
      back/volume/src/pong/game/Game.ts
  11. 48
      back/volume/src/pong/game/Paddle.ts
  12. 54
      back/volume/src/pong/game/Player.ts
  13. 50
      back/volume/src/pong/game/constants.ts
  14. 147
      back/volume/src/pong/game/utils.ts
  15. 26
      back/volume/src/pong/pong.gateway.spec.ts
  16. 182
      back/volume/src/pong/pong.gateway.ts
  17. 6
      back/volume/src/pong/pong.module.ts
  18. 26
      back/volume/src/pong/pong.spec.ts
  19. 112
      back/volume/src/pong/pong.ts
  20. 29
      back/volume/test/app.e2e-spec.ts
  21. 4
      docker-compose.yml
  22. 6
      front/Dockerfile

3
Makefile

@ -9,6 +9,9 @@ prod:
dev: dev:
NODE_ENV="development" docker compose -f docker-compose.yml up --build NODE_ENV="development" docker compose -f docker-compose.yml up --build
lint:
NODE_ENV="beautify" docker compose -f docker-compose.yml up --build
debug: debug:
NODE_ENV="debug" BUILDKIT_PROGRESS=plain docker compose -f docker-compose.yml up --build NODE_ENV="debug" BUILDKIT_PROGRESS=plain docker compose -f docker-compose.yml up --build

3
Makesudo

@ -9,6 +9,9 @@ prod:
dev: dev:
sudo NODE_ENV="development" docker compose -f docker-compose.yml up --build sudo NODE_ENV="development" docker compose -f docker-compose.yml up --build
lint:
sudo NODE_ENV="beautify" docker compose -f docker-compose.yml up --build
debug: debug:
sudo NODE_ENV="debug" BUILDKIT_PROGRESS=plain docker compose -f docker-compose.yml up --build sudo NODE_ENV="debug" BUILDKIT_PROGRESS=plain docker compose -f docker-compose.yml up --build

4
back/Dockerfile

@ -8,7 +8,9 @@ ENTRYPOINT npm install; \
if [[ $NODE_ENV == "production" ]]; then \ if [[ $NODE_ENV == "production" ]]; then \
npm run build && npm run start:prod; \ npm run build && npm run start:prod; \
elif [[ $NODE_ENV == "debug" ]]; then \ elif [[ $NODE_ENV == "debug" ]]; then \
npm run test:debug; \ npm run start:debug; \
elif [[ $NODE_ENV == "beautify" ]]; then \
npm run format && npm run lint; \
elif [[ $NODE_ENV == "development" ]]; then \ elif [[ $NODE_ENV == "development" ]]; then \
npm run dev; \ npm run dev; \
else echo "NODE_ENV value isn't known."; \ else echo "NODE_ENV value isn't known."; \

16
back/volume/.eslintrc.json

@ -0,0 +1,16 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": "standard-with-typescript",
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": ["./tsconfig.json"]
},
"rules": {
}
}

2096
back/volume/package-lock.json

File diff suppressed because it is too large

12
back/volume/package.json

@ -30,9 +30,9 @@
"@nestjs/websockets": "^9.2.0", "@nestjs/websockets": "^9.2.0",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"rxjs": "^7.2.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"ws": "^8.11.0" "ws": "^8.11.0"
}, },
"devDependencies": { "devDependencies": {
@ -40,11 +40,15 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "28.1.8", "@types/jest": "28.1.8",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1", "eslint": "^8.34.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"jest": "28.1.3", "jest": "28.1.3",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
@ -53,7 +57,7 @@
"ts-loader": "^9.2.3", "ts-loader": "^9.2.3",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0", "tsconfig-paths": "4.1.0",
"typescript": "^4.7.4" "typescript": "^4.9.5"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

6
back/volume/src/app.module.ts

@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common'
import { PongModule } from './pong/pong.module'; import { PongModule } from './pong/pong.module'
@Module({ @Module({
imports: [PongModule] imports: [PongModule]
}) })
export class AppModule {} export class AppModule {}

16
back/volume/src/main.ts

@ -1,10 +1,10 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core'
import { WsAdapter } from '@nestjs/platform-ws'; import { WsAdapter } from '@nestjs/platform-ws'
import { AppModule } from './app.module'; import { AppModule } from './app.module'
async function bootstrap() { async function bootstrap () {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule)
app.useWebSocketAdapter(new WsAdapter(app)); app.useWebSocketAdapter(new WsAdapter(app))
await app.listen(3001); await app.listen(3001)
} }
bootstrap(); bootstrap()

117
back/volume/src/pong/game/Ball.ts

@ -1,58 +1,65 @@
import { gameInfoConstants } from './constants'; import { gameInfoConstants } from './constants'
import { Paddle } from './Paddle'; import { type Paddle } from './Paddle'
import { Point, Rect } from './utils'; import { Point, Rect } from './utils'
export class Ball { export class Ball {
rect: Rect; rect: Rect
speed: Point; speed: Point
spawn: Point; spawn: Point
indexPlayerScored: number; indexPlayerScored: number
constructor(spawn: Point, size: Point = gameInfoConstants.ballSize, speed: Point = new Point(10, 2)) { constructor (
this.rect = new Rect(spawn, size); spawn: Point,
this.speed = speed; size: Point = gameInfoConstants.ballSize,
this.spawn = spawn.clone(); speed: Point = new Point(10, 2)
} ) {
this.rect = new Rect(spawn, size)
getIndexPlayerScored(): number { this.speed = speed
return this.indexPlayerScored; this.spawn = spawn.clone()
} }
update(canvas_rect: Rect, paddles: Paddle[]) { getIndexPlayerScored (): number {
if (!canvas_rect.contains_x(this.rect)) { return this.indexPlayerScored
this.indexPlayerScored = this.score(); }
} else {
this.indexPlayerScored = -1; update (canvas_rect: Rect, paddles: Paddle[]) {
this.move(canvas_rect, paddles); if (!canvas_rect.contains_x(this.rect)) {
} this.indexPlayerScored = this.score()
} } else {
this.indexPlayerScored = -1
move(canvas_rect: Rect, paddles: Paddle[]) { this.move(canvas_rect, paddles)
for (const paddle of paddles) { }
if (paddle.rect.collides(this.rect)) { }
if (this.speed.x < 0) this.rect.center.x = paddle.rect.center.x + paddle.rect.size.x;
else this.rect.center.x = paddle.rect.center.x - paddle.rect.size.x; move (canvas_rect: Rect, paddles: Paddle[]) {
this.speed.x = this.speed.x * -1; for (const paddle of paddles) {
this.speed.y = ((this.rect.center.y - paddle.rect.center.y) / paddle.rect.size.y) * 20; if (paddle.rect.collides(this.rect)) {
break; if (this.speed.x < 0) {
} this.rect.center.x = paddle.rect.center.x + paddle.rect.size.x
} } else this.rect.center.x = paddle.rect.center.x - paddle.rect.size.x
if (!canvas_rect.contains_y(this.rect)) this.speed.y = this.speed.y * -1; this.speed.x = this.speed.x * -1
this.rect.center.add_inplace(this.speed); this.speed.y =
} ((this.rect.center.y - paddle.rect.center.y) / paddle.rect.size.y) *
20
//A player scored: return his index and reposition the ball break
score(): number { }
let index_player_scored: number; }
if (this.rect.center.x <= this.spawn.x) { if (!canvas_rect.contains_y(this.rect)) this.speed.y = this.speed.y * -1
index_player_scored = 1; this.rect.center.add_inplace(this.speed)
} else { }
index_player_scored = 0;
} // A player scored: return his index and reposition the ball
score (): number {
this.rect.center = this.spawn.clone(); let index_player_scored: number
this.speed.x = this.speed.x * -1; if (this.rect.center.x <= this.spawn.x) {
index_player_scored = 1
return index_player_scored; } else {
} index_player_scored = 0
}
this.rect.center = this.spawn.clone()
this.speed.x = this.speed.x * -1
return index_player_scored
}
} }

285
back/volume/src/pong/game/Game.ts

@ -1,133 +1,160 @@
import { Ball } from './Ball'; import { Ball } from './Ball'
import { WebSocket } from 'ws'; import { type WebSocket } from 'ws'
import { formatWebsocketData, Point, Rect } from './utils'; import { formatWebsocketData, Point, Rect } from './utils'
import { Player } from './Player'; import { Player } from './Player'
import { GameInfo, gameInfoConstants, GameUpdate, GAME_EVENTS } from './constants'; import {
import { randomUUID } from 'crypto'; type GameInfo,
gameInfoConstants,
const GAME_TICKS = 30; type GameUpdate,
GAME_EVENTS
function gameLoop(game: Game) { } from './constants'
const canvas_rect = new Rect( import { randomUUID } from 'crypto'
new Point(gameInfoConstants.mapSize.x / 2, gameInfoConstants.mapSize.y / 2),
new Point(gameInfoConstants.mapSize.x, gameInfoConstants.mapSize.y) const GAME_TICKS = 30
);
game.ball.update( function gameLoop (game: Game) {
canvas_rect, const canvas_rect = new Rect(
game.players.map((p) => p.paddle) new Point(gameInfoConstants.mapSize.x / 2, gameInfoConstants.mapSize.y / 2),
); new Point(gameInfoConstants.mapSize.x, gameInfoConstants.mapSize.y)
const index_player_scored: number = game.ball.getIndexPlayerScored(); )
if (index_player_scored != -1) { game.ball.update(
game.players[index_player_scored].score += 1; canvas_rect,
if (game.players[index_player_scored].score >= gameInfoConstants.winScore) { game.players.map((p) => p.paddle)
console.log(`${game.players[index_player_scored].name} won!`); )
game.stop(); const index_player_scored: number = game.ball.getIndexPlayerScored()
} if (index_player_scored != -1) {
} game.players[index_player_scored].score += 1
if (game.players[index_player_scored].score >= gameInfoConstants.winScore) {
const data: GameUpdate = { console.log(`${game.players[index_player_scored].name} won!`)
paddlesPositions: game.players.map((p) => p.paddle.rect.center), game.stop()
ballPosition: game.ball.rect.center, }
scores: game.players.map((p) => p.score) }
};
const websocketData: string = formatWebsocketData(GAME_EVENTS.GAME_TICK, data); const data: GameUpdate = {
game.broadcastGame(websocketData); paddlesPositions: game.players.map((p) => p.paddle.rect.center),
ballPosition: game.ball.rect.center,
scores: game.players.map((p) => p.score)
}
const websocketData: string = formatWebsocketData(
GAME_EVENTS.GAME_TICK,
data
)
game.broadcastGame(websocketData)
} }
export class Game { export class Game {
id: string; id: string
timer: NodeJS.Timer; timer: NodeJS.Timer
ball: Ball; ball: Ball
players: Player[] = []; players: Player[] = []
playing: boolean; playing: boolean
constructor(sockets: Array<WebSocket>, uuids: Array<string>, names: Array<string>) { constructor (sockets: WebSocket[], uuids: string[], names: string[]) {
this.id = randomUUID(); this.id = randomUUID()
this.timer = null; this.timer = null
this.ball = new Ball(new Point(gameInfoConstants.mapSize.x / 2, gameInfoConstants.mapSize.y / 2)); this.ball = new Ball(
for (let i = 0; i < uuids.length; i++) { new Point(
this.addPlayer(sockets[i], uuids[i], names[i]); gameInfoConstants.mapSize.x / 2,
} gameInfoConstants.mapSize.y / 2
} )
)
getGameInfo(uuid: string): GameInfo { for (let i = 0; i < uuids.length; i++) {
const yourPaddleIndex = this.players.findIndex((p) => p.uuid == uuid); this.addPlayer(sockets[i], uuids[i], names[i])
return { }
...gameInfoConstants, }
yourPaddleIndex: yourPaddleIndex,
gameId: this.id getGameInfo (uuid: string): GameInfo {
}; const yourPaddleIndex = this.players.findIndex((p) => p.uuid == uuid)
} return {
...gameInfoConstants,
private addPlayer(socket: WebSocket, uuid: string, name: string) { yourPaddleIndex,
let paddleCoords = new Point(gameInfoConstants.playerXOffset, gameInfoConstants.mapSize.y / 2); gameId: this.id
if (this.players.length == 1) { }
paddleCoords = new Point( }
gameInfoConstants.mapSize.x - gameInfoConstants.playerXOffset,
gameInfoConstants.mapSize.y / 2 private addPlayer (socket: WebSocket, uuid: string, name: string) {
); let paddleCoords = new Point(
} gameInfoConstants.playerXOffset,
this.players.push(new Player(socket, uuid, name, paddleCoords, gameInfoConstants.mapSize)); gameInfoConstants.mapSize.y / 2
} )
if (this.players.length == 1) {
removePlayer(uuid: string) { paddleCoords = new Point(
const player_index = this.players.findIndex((p) => p.uuid == uuid); gameInfoConstants.mapSize.x - gameInfoConstants.playerXOffset,
if (player_index != -1) { gameInfoConstants.mapSize.y / 2
this.players.splice(player_index, 1); )
if (this.players.length < 2) { }
this.stop(); this.players.push(
} new Player(socket, uuid, name, paddleCoords, gameInfoConstants.mapSize)
} )
} }
ready(uuid: string) { removePlayer (uuid: string) {
const player_index = this.players.findIndex((p) => p.uuid == uuid); const player_index = this.players.findIndex((p) => p.uuid == uuid)
if (player_index != -1) { if (player_index != -1) {
this.players[player_index].ready = true; this.players.splice(player_index, 1)
console.log(`${this.players[player_index].name} is ready!`); if (this.players.length < 2) {
if (this.players.every((p) => p.ready)) { this.stop()
this.start(); }
} }
} }
}
ready (uuid: string) {
private start(): boolean { const player_index = this.players.findIndex((p) => p.uuid == uuid)
if (!this.timer && this.players.length == 2) { if (player_index != -1) {
this.ball = new Ball(new Point(gameInfoConstants.mapSize.x / 2, gameInfoConstants.mapSize.y / 2)); this.players[player_index].ready = true
this.players.forEach((p) => p.newGame()); console.log(`${this.players[player_index].name} is ready!`)
if (this.players.every((p) => p.ready)) {
this.timer = setInterval(gameLoop, 1000 / GAME_TICKS, this); this.start()
this.broadcastGame(formatWebsocketData(GAME_EVENTS.START_GAME)); }
console.log('Started game'); }
this.playing = true; }
return true;
} private start (): boolean {
return false; if (!this.timer && this.players.length == 2) {
} this.ball = new Ball(
new Point(
stop() { gameInfoConstants.mapSize.x / 2,
if (this.timer) { gameInfoConstants.mapSize.y / 2
clearInterval(this.timer); )
this.timer = null; )
this.players = []; this.players.forEach((p) => {
this.playing = false; p.newGame()
console.log('Stopped game'); })
}
} this.timer = setInterval(gameLoop, 1000 / GAME_TICKS, this)
this.broadcastGame(formatWebsocketData(GAME_EVENTS.START_GAME))
movePaddle(uuid: string, position: Point) { console.log('Started game')
const playerIndex = this.players.findIndex((p) => p.uuid == uuid); this.playing = true
return true
if (this.timer && playerIndex != -1) { }
this.players[playerIndex].paddle.move(position.y); return false
} }
}
stop () {
broadcastGame(data: string) { if (this.timer) {
this.players.forEach((p) => p.socket.send(data)); clearInterval(this.timer)
} this.timer = null
this.players = []
isPlaying(): boolean { this.playing = false
return this.playing; console.log('Stopped game')
} }
}
movePaddle (uuid: string, position: Point) {
const playerIndex = this.players.findIndex((p) => p.uuid == uuid)
if (this.timer && playerIndex != -1) {
this.players[playerIndex].paddle.move(position.y)
}
}
broadcastGame (data: string) {
this.players.forEach((p) => {
p.socket.send(data)
})
}
isPlaying (): boolean {
return this.playing
}
} }

48
back/volume/src/pong/game/Paddle.ts

@ -1,28 +1,32 @@
import { gameInfoConstants } from './constants'; import { gameInfoConstants } from './constants'
import { Point, Rect } from './utils'; import { type Point, Rect } from './utils'
export class Paddle { export class Paddle {
rect: Rect; rect: Rect
color: string | CanvasGradient | CanvasPattern = 'white'; color: string | CanvasGradient | CanvasPattern = 'white'
mapSize: Point; mapSize: Point
constructor(spawn: Point, gameSize: Point, size: Point = gameInfoConstants.paddleSize) { constructor (
this.rect = new Rect(spawn, size); spawn: Point,
this.mapSize = gameSize; gameSize: Point,
} size: Point = gameInfoConstants.paddleSize
) {
this.rect = new Rect(spawn, size)
this.mapSize = gameSize
}
draw(context: CanvasRenderingContext2D) { draw (context: CanvasRenderingContext2D) {
this.rect.draw(context, this.color); this.rect.draw(context, this.color)
} }
move(new_y: number) { move (new_y: number) {
const offset: number = this.rect.size.y / 2; const offset: number = this.rect.size.y / 2
if (new_y - offset < 0) { if (new_y - offset < 0) {
this.rect.center.y = offset; this.rect.center.y = offset
} else if (new_y + offset > this.mapSize.y) { } else if (new_y + offset > this.mapSize.y) {
this.rect.center.y = this.mapSize.y - offset; this.rect.center.y = this.mapSize.y - offset
} else { } else {
this.rect.center.y = new_y; this.rect.center.y = new_y
} }
} }
} }

54
back/volume/src/pong/game/Player.ts

@ -1,29 +1,35 @@
import { WebSocket } from 'ws'; import { type WebSocket } from 'ws'
import { Paddle } from './Paddle'; import { Paddle } from './Paddle'
import { Point } from './utils'; import { type Point } from './utils'
export class Player { export class Player {
socket: WebSocket; socket: WebSocket
uuid: string; uuid: string
name: string; name: string
ready: boolean; ready: boolean
paddle: Paddle; paddle: Paddle
paddleCoords: Point; paddleCoords: Point
mapSize: Point; mapSize: Point
score: number; score: number
constructor(socket: WebSocket, uuid: string, name: string, paddleCoords: Point, mapSize: Point) { constructor (
this.socket = socket; socket: WebSocket,
this.uuid = uuid; uuid: string,
this.name = name; name: string,
this.paddle = new Paddle(paddleCoords, mapSize); paddleCoords: Point,
this.paddleCoords = paddleCoords; mapSize: Point
this.mapSize = mapSize; ) {
this.score = 0; this.socket = socket
} this.uuid = uuid
this.name = name
this.paddle = new Paddle(paddleCoords, mapSize)
this.paddleCoords = paddleCoords
this.mapSize = mapSize
this.score = 0
}
newGame() { newGame () {
this.score = 0; this.score = 0
this.paddle = new Paddle(this.paddleCoords, this.mapSize); this.paddle = new Paddle(this.paddleCoords, this.mapSize)
} }
} }

50
back/volume/src/pong/game/constants.ts

@ -1,36 +1,36 @@
import { Point } from './utils'; import { Point } from './utils'
export const GAME_EVENTS = { export const GAME_EVENTS = {
START_GAME: 'START_GAME', START_GAME: 'START_GAME',
READY: 'READY', READY: 'READY',
GAME_TICK: 'GAME_TICK', GAME_TICK: 'GAME_TICK',
PLAYER_MOVE: 'PLAYER_MOVE', PLAYER_MOVE: 'PLAYER_MOVE',
GET_GAME_INFO: 'GET_GAME_INFO', GET_GAME_INFO: 'GET_GAME_INFO',
CREATE_GAME: 'CREATE_GAME', CREATE_GAME: 'CREATE_GAME',
REGISTER_PLAYER: 'REGISTER_PLAYER' REGISTER_PLAYER: 'REGISTER_PLAYER'
}; }
export interface GameInfo extends GameInfoConstants { export interface GameInfo extends GameInfoConstants {
yourPaddleIndex: number; yourPaddleIndex: number
gameId: string; gameId: string
} }
export interface GameInfoConstants { export interface GameInfoConstants {
mapSize: Point; mapSize: Point
paddleSize: Point; paddleSize: Point
playerXOffset: number; playerXOffset: number
ballSize: Point; ballSize: Point
winScore: number; winScore: number
} }
export const gameInfoConstants: GameInfoConstants = { export const gameInfoConstants: GameInfoConstants = {
mapSize: new Point(600, 400), mapSize: new Point(600, 400),
paddleSize: new Point(6, 50), paddleSize: new Point(6, 50),
playerXOffset: 50, playerXOffset: 50,
ballSize: new Point(20, 20), ballSize: new Point(20, 20),
winScore: 2 winScore: 2
}; }
export interface GameUpdate { export interface GameUpdate {
paddlesPositions: Point[]; paddlesPositions: Point[]
ballPosition: Point; ballPosition: Point
scores: number[]; scores: number[]
} }

147
back/volume/src/pong/game/utils.ts

@ -1,88 +1,99 @@
export class Point { export class Point {
x: number; x: number
y: number; y: number
constructor(x: number, y: number) { constructor (x: number, y: number) {
this.x = x; this.x = x
this.y = y; this.y = y
} }
//Returns a new point // Returns a new point
add(other: Point) { add (other: Point) {
return new Point(this.x + other.x, this.y + other.y); return new Point(this.x + other.x, this.y + other.y)
} }
//Modifies `this` point // Modifies `this` point
add_inplace(other: Point) { add_inplace (other: Point) {
this.x += other.x; this.x += other.x
this.y += other.y; this.y += other.y
} }
clone(): Point { clone (): Point {
return new Point(this.x, this.y); return new Point(this.x, this.y)
} }
} }
export class Rect { export class Rect {
center: Point; center: Point
size: Point; size: Point
constructor(center: Point, size: Point) { constructor (center: Point, size: Point) {
this.center = center; this.center = center
this.size = size; this.size = size
} }
draw(context: CanvasRenderingContext2D, color: string | CanvasGradient | CanvasPattern) { draw (
const offset: Point = new Point(this.size.x / 2, this.size.y / 2); context: CanvasRenderingContext2D,
color: string | CanvasGradient | CanvasPattern
) {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2)
context.fillStyle = color; context.fillStyle = color
context.fillRect(this.center.x - offset.x, this.center.y - offset.y, this.size.x, this.size.y); context.fillRect(
} this.center.x - offset.x,
this.center.y - offset.y,
this.size.x,
this.size.y
)
}
//True if `this` rect contains `other` rect in the x-axis // True if `this` rect contains `other` rect in the x-axis
contains_x(other: Rect): boolean { contains_x (other: Rect): boolean {
const offset: number = this.size.x / 2; const offset: number = this.size.x / 2
const offset_other: number = other.size.x / 2; const offset_other: number = other.size.x / 2
if ( if (
this.center.x - offset <= other.center.x - offset_other && this.center.x - offset <= other.center.x - offset_other &&
this.center.x + offset >= other.center.x + offset_other this.center.x + offset >= other.center.x + offset_other
) ) {
return true; return true
return false; }
} return false
}
//True if `this` rect contains `other` rect in the y-axis // True if `this` rect contains `other` rect in the y-axis
contains_y(other: Rect): boolean { contains_y (other: Rect): boolean {
const offset: number = this.size.y / 2; const offset: number = this.size.y / 2
const offset_other: number = other.size.y / 2; const offset_other: number = other.size.y / 2
if ( if (
this.center.y - offset <= other.center.y - offset_other && this.center.y - offset <= other.center.y - offset_other &&
this.center.y + offset >= other.center.y + offset_other this.center.y + offset >= other.center.y + offset_other
) ) {
return true; return true
return false; }
} return false
}
collides(other: Rect): boolean { collides (other: Rect): boolean {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2); const offset: Point = new Point(this.size.x / 2, this.size.y / 2)
const offset_other: Point = new Point(other.size.x / 2, other.size.y / 2); const offset_other: Point = new Point(other.size.x / 2, other.size.y / 2)
if ( if (
this.center.x - offset.x < other.center.x + offset_other.x && this.center.x - offset.x < other.center.x + offset_other.x &&
this.center.x + offset.x > other.center.x - offset_other.x && this.center.x + offset.x > other.center.x - offset_other.x &&
this.center.y - offset.y < other.center.y + offset_other.y && this.center.y - offset.y < other.center.y + offset_other.y &&
this.center.y + offset.y > other.center.y - offset_other.y this.center.y + offset.y > other.center.y - offset_other.y
) ) {
return true; return true
return false; }
} return false
}
} }
export function formatWebsocketData(event: string, data?: any): string { export function formatWebsocketData (event: string, data?: any): string {
return JSON.stringify({ return JSON.stringify({
event, event,
data data
}); })
} }

26
back/volume/src/pong/pong.gateway.spec.ts

@ -1,18 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, type TestingModule } from '@nestjs/testing'
import { PongGateway } from './pong.gateway'; import { PongGateway } from './pong.gateway'
describe('PongGateway', () => { describe('PongGateway', () => {
let gateway: PongGateway; let gateway: PongGateway
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [PongGateway] providers: [PongGateway]
}).compile(); }).compile()
gateway = module.get<PongGateway>(PongGateway); gateway = module.get<PongGateway>(PongGateway)
}); })
it('should be defined', () => { it('should be defined', () => {
expect(gateway).toBeDefined(); expect(gateway).toBeDefined()
}); })
}); })

182
back/volume/src/pong/pong.gateway.ts

@ -1,99 +1,115 @@
import { WebSocket } from 'ws'; import { type WebSocket } from 'ws'
import { import {
ConnectedSocket, ConnectedSocket,
MessageBody, MessageBody,
OnGatewayConnection, type OnGatewayConnection,
OnGatewayDisconnect, type OnGatewayDisconnect,
SubscribeMessage, SubscribeMessage,
WebSocketGateway WebSocketGateway
} from '@nestjs/websockets'; } from '@nestjs/websockets'
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto'
import { Pong } from './pong'; import { Pong } from './pong'
import { formatWebsocketData, Point } from './game/utils'; import { formatWebsocketData, Point } from './game/utils'
import { GAME_EVENTS } from './game/constants'; import { GAME_EVENTS } from './game/constants'
interface WebSocketWithId extends WebSocket { interface WebSocketWithId extends WebSocket {
id: string; id: string
} }
@WebSocketGateway() @WebSocketGateway()
export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect {
private pong: Pong = new Pong(); private readonly pong: Pong = new Pong()
private socketToPlayerName: Map<WebSocketWithId, string> = new Map(); private readonly socketToPlayerName = new Map<WebSocketWithId, string>()
handleConnection(client: WebSocketWithId) { handleConnection (client: WebSocketWithId) {
const uuid = randomUUID(); const uuid = randomUUID()
client.id = uuid; client.id = uuid
} }
handleDisconnect( handleDisconnect (
@ConnectedSocket() @ConnectedSocket()
client: WebSocketWithId client: WebSocketWithId
) { ) {
if (this.pong.isInAGame(client.id)) { if (this.pong.isInAGame(client.id)) {
console.log(`Disconnected ${this.socketToPlayerName.get(client)}`); console.log(`Disconnected ${this.socketToPlayerName.get(client)}`)
if (this.pong.playerGame(client.id).isPlaying()) { if (this.pong.playerGame(client.id).isPlaying()) {
this.pong.playerGame(client.id).stop(); this.pong.playerGame(client.id).stop()
} }
this.socketToPlayerName.delete(client); this.socketToPlayerName.delete(client)
} }
} }
@SubscribeMessage(GAME_EVENTS.REGISTER_PLAYER) @SubscribeMessage(GAME_EVENTS.REGISTER_PLAYER)
registerPlayer( registerPlayer (
@ConnectedSocket() @ConnectedSocket()
client: WebSocketWithId, client: WebSocketWithId,
@MessageBody('playerName') playerName: string @MessageBody('playerName') playerName: string
) { ) {
this.socketToPlayerName.set(client, playerName); this.socketToPlayerName.set(client, playerName)
console.log(`Connected ${this.socketToPlayerName.get(client)}`); console.log(`Connected ${this.socketToPlayerName.get(client)}`)
} }
@SubscribeMessage(GAME_EVENTS.GET_GAME_INFO) @SubscribeMessage(GAME_EVENTS.GET_GAME_INFO)
getPlayerCount(@ConnectedSocket() client: WebSocketWithId) { getPlayerCount (@ConnectedSocket() client: WebSocketWithId) {
client.send(formatWebsocketData(GAME_EVENTS.GET_GAME_INFO, this.pong.getGameInfo(client.id))); client.send(
} formatWebsocketData(
GAME_EVENTS.GET_GAME_INFO,
this.pong.getGameInfo(client.id)
)
)
}
@SubscribeMessage(GAME_EVENTS.PLAYER_MOVE) @SubscribeMessage(GAME_EVENTS.PLAYER_MOVE)
movePlayer( movePlayer (
@ConnectedSocket() @ConnectedSocket()
client: WebSocketWithId, client: WebSocketWithId,
@MessageBody('position') position: Point @MessageBody('position') position: Point
) { ) {
this.pong.movePlayer(client.id, position); this.pong.movePlayer(client.id, position)
} }
@SubscribeMessage(GAME_EVENTS.CREATE_GAME) @SubscribeMessage(GAME_EVENTS.CREATE_GAME)
createGame( createGame (
@ConnectedSocket() @ConnectedSocket()
client: WebSocketWithId, client: WebSocketWithId,
@MessageBody('playerNames') playerNames: string[] @MessageBody('playerNames') playerNames: string[]
) { ) {
const allPlayerNames: Array<string> = Array.from(this.socketToPlayerName.values()); const allPlayerNames: string[] = Array.from(
if (playerNames && playerNames.length === 2 && allPlayerNames && allPlayerNames.length >= 2) { this.socketToPlayerName.values()
const player1Socket: WebSocketWithId = Array.from(this.socketToPlayerName.keys()).find( )
(key) => this.socketToPlayerName.get(key) === playerNames[0] if (
); playerNames &&
const player2Socket: WebSocketWithId = Array.from(this.socketToPlayerName.keys()).find( playerNames.length === 2 &&
(key) => this.socketToPlayerName.get(key) === playerNames[1] allPlayerNames &&
); allPlayerNames.length >= 2
) {
const player1Socket: WebSocketWithId = Array.from(
this.socketToPlayerName.keys()
).find((key) => this.socketToPlayerName.get(key) === playerNames[0])
const player2Socket: WebSocketWithId = Array.from(
this.socketToPlayerName.keys()
).find((key) => this.socketToPlayerName.get(key) === playerNames[1])
if ( if (
player1Socket && player1Socket &&
player2Socket && player2Socket &&
(client.id === player1Socket.id || client.id === player2Socket.id) && (client.id === player1Socket.id || client.id === player2Socket.id) &&
player1Socket.id !== player2Socket.id player1Socket.id !== player2Socket.id
) { ) {
this.pong.newGame([player1Socket, player2Socket], [player1Socket.id, player2Socket.id], playerNames); this.pong.newGame(
} [player1Socket, player2Socket],
} [player1Socket.id, player2Socket.id],
} playerNames
)
}
}
}
@SubscribeMessage(GAME_EVENTS.READY) @SubscribeMessage(GAME_EVENTS.READY)
ready( ready (
@ConnectedSocket() @ConnectedSocket()
client: WebSocketWithId client: WebSocketWithId
) { ) {
this.pong.ready(client.id); this.pong.ready(client.id)
} }
} }

6
back/volume/src/pong/pong.module.ts

@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common'
import { PongGateway } from './pong.gateway'; import { PongGateway } from './pong.gateway'
@Module({ @Module({
providers: [PongGateway] providers: [PongGateway]
}) })
export class PongModule {} export class PongModule {}

26
back/volume/src/pong/pong.spec.ts

@ -1,18 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, type TestingModule } from '@nestjs/testing'
import { Pong } from './pong'; import { Pong } from './pong'
describe('Pong', () => { describe('Pong', () => {
let provider: Pong; let provider: Pong
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [Pong] providers: [Pong]
}).compile(); }).compile()
provider = module.get<Pong>(Pong); provider = module.get<Pong>(Pong)
}); })
it('should be defined', () => { it('should be defined', () => {
expect(provider).toBeDefined(); expect(provider).toBeDefined()
}); })
}); })

112
back/volume/src/pong/pong.ts

@ -1,59 +1,59 @@
import { WebSocket } from 'ws'; import { type WebSocket } from 'ws'
import { GameInfo } from './game/constants'; import { type GameInfo } from './game/constants'
import { Game } from './game/Game'; import { Game } from './game/Game'
import { Point } from './game/utils'; import { type Point } from './game/utils'
export class Pong { export class Pong {
private playerUUIDToGameIndex = new Map<string, number>(); private playerUUIDToGameIndex = new Map<string, number>()
private games = new Array<Game>(); private readonly games = new Array<Game>()
newGame(sockets: Array<WebSocket>, uuids: Array<string>, names: Array<string>) { newGame (sockets: WebSocket[], uuids: string[], names: string[]) {
this.games.push(new Game(sockets, uuids, names)); this.games.push(new Game(sockets, uuids, names))
this.playerUUIDToGameIndex[uuids[0]] = this.games.length - 1; this.playerUUIDToGameIndex[uuids[0]] = this.games.length - 1
this.playerUUIDToGameIndex[uuids[1]] = this.games.length - 1; this.playerUUIDToGameIndex[uuids[1]] = this.games.length - 1
console.log(`Created game ${names[0]} vs ${names[1]}`); console.log(`Created game ${names[0]} vs ${names[1]}`)
} }
removePlayer(uuid: string) { removePlayer (uuid: string) {
this.playerGame(uuid).removePlayer(uuid); this.playerGame(uuid).removePlayer(uuid)
} }
ready(uuid: string) { ready (uuid: string) {
if (this.isInAGame(uuid)) { if (this.isInAGame(uuid)) {
this.playerGame(uuid).ready(uuid); this.playerGame(uuid).ready(uuid)
} }
} }
stopGame(uuid: string) { stopGame (uuid: string) {
if (this.isInAGame(uuid)) { if (this.isInAGame(uuid)) {
this.playerGame(uuid).stop(); this.playerGame(uuid).stop()
delete this.playerUUIDToGameIndex[uuid]; delete this.playerUUIDToGameIndex[uuid]
delete this.games[this.playerUUIDToGameIndex[uuid]]; delete this.games[this.playerUUIDToGameIndex[uuid]]
} }
} }
getGameInfo(uuid: string): GameInfo { getGameInfo (uuid: string): GameInfo {
if (this.isInAGame(uuid)) { if (this.isInAGame(uuid)) {
return this.playerGame(uuid).getGameInfo(uuid); return this.playerGame(uuid).getGameInfo(uuid)
} }
} }
movePlayer(uuid: string, position: Point) { movePlayer (uuid: string, position: Point) {
if (this.isInAGame(uuid)) { if (this.isInAGame(uuid)) {
this.playerGame(uuid).movePaddle(uuid, position); this.playerGame(uuid).movePaddle(uuid, position)
} }
} }
isInAGame(uuid: string): boolean { isInAGame (uuid: string): boolean {
if (this.playerUUIDToGameIndex[uuid] === undefined) { if (this.playerUUIDToGameIndex[uuid] === undefined) {
return false; return false
} }
return true; return true
} }
playerGame(uuid: string): Game { playerGame (uuid: string): Game {
if (this.isInAGame(uuid)) { if (this.isInAGame(uuid)) {
return this.games[this.playerUUIDToGameIndex[uuid]]; return this.games[this.playerUUIDToGameIndex[uuid]]
} }
} }
} }

29
back/volume/test/app.e2e-spec.ts

@ -1,21 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing' import { Test, type TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common' import { type INestApplication } from '@nestjs/common'
import * as request from 'supertest' import * as request from 'supertest'
import { AppModule } from './../src/app.module' import { AppModule } from './../src/app.module'
describe('AppController (e2e)', () => { describe('AppController (e2e)', () => {
let app: INestApplication let app: INestApplication
beforeEach(async () => { beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({ const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule] imports: [AppModule]
}).compile() }).compile()
app = moduleFixture.createNestApplication() app = moduleFixture.createNestApplication()
await app.init() await app.init()
}) })
it('/ (GET)', () => { it('/ (GET)', async () => {
return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!') return await request(app.getHttpServer())
}) .get('/')
.expect(200)
.expect('Hello World!')
})
}) })

4
docker-compose.yml

@ -14,7 +14,7 @@ services:
ports: [80:80] ports: [80:80]
volumes: [./front/volume:/var/www/html] volumes: [./front/volume:/var/www/html]
networks: [transcendence] networks: [transcendence]
restart: always restart: on-failure
back: back:
container_name: back container_name: back
build: back/ build: back/
@ -25,7 +25,7 @@ services:
ports: [3001:3001] ports: [3001:3001]
networks: [transcendence] networks: [transcendence]
volumes: [./back/volume:/var/www/html] volumes: [./back/volume:/var/www/html]
restart: always restart: on-failure
postgres: postgres:
container_name: postgres container_name: postgres
image: postgres image: postgres

6
front/Dockerfile

@ -6,7 +6,9 @@ WORKDIR /var/www/html
ENTRYPOINT npm install; \ ENTRYPOINT npm install; \
if [[ $NODE_ENV == "production" ]]; then \ if [[ $NODE_ENV == "production" ]]; then \
npm run build && npm run preview; \ npm run build && npm run preview; \
elif [[ $NODE_ENV == "development" || $NODE_ENV == "debug" ]]; then \ elif [[ $NODE_ENV == "development" ]]; then \
npm run dev; \ npm run dev; \
else echo "NODE_ENV value isn't known."; \ elif [[ $NODE_ENV == "debug" ]]; then \
npm run check && npm run dev; \
else echo "Nothing to do for that NODE_ENV context."; \
fi; fi;

Loading…
Cancel
Save