Browse Source

added back

master
narnaud 2 years ago
parent
commit
119ab9f42d
  1. 6
      cont/back/Dockerfile
  2. 4
      cont/front/Dockerfile
  3. 2
      docker-compose.yml
  4. 131
      volumes/back/.gitignore
  5. 73
      volumes/back/README.md
  6. 5
      volumes/back/nest-cli.json
  7. 14949
      volumes/back/package-lock.json
  8. 75
      volumes/back/package.json
  9. 7
      volumes/back/src/app.module.ts
  10. 10
      volumes/back/src/main.ts
  11. 58
      volumes/back/src/pong/game/Ball.ts
  12. 133
      volumes/back/src/pong/game/Game.ts
  13. 28
      volumes/back/src/pong/game/Paddle.ts
  14. 29
      volumes/back/src/pong/game/Player.ts
  15. 36
      volumes/back/src/pong/game/constants.ts
  16. 88
      volumes/back/src/pong/game/utils.ts
  17. 18
      volumes/back/src/pong/pong.gateway.spec.ts
  18. 99
      volumes/back/src/pong/pong.gateway.ts
  19. 7
      volumes/back/src/pong/pong.module.ts
  20. 18
      volumes/back/src/pong/pong.spec.ts
  21. 59
      volumes/back/src/pong/pong.ts
  22. 21
      volumes/back/test/app.e2e-spec.ts
  23. 9
      volumes/back/test/jest-e2e.json
  24. 4
      volumes/back/tsconfig.build.json
  25. 21
      volumes/back/tsconfig.json
  26. 2
      volumes/front/package.json
  27. 2
      volumes/front/src/components/Pong/Pong.svelte
  28. 3
      volumes/front/vite.config.ts

6
cont/back/Dockerfile

@ -1,6 +1,8 @@
FROM alpine:3.15
RUN apk update && apk upgrade && apk add --no-cache npm \
RUN apk update && apk upgrade && apk add npm \
&& npm install -g @nestjs/cli
WORKDIR /var/html/
WORKDIR /var/www/html
ENTRYPOINT npm install && npm run build && npm run start

4
cont/front/Dockerfile

@ -1,8 +1,8 @@
FROM alpine:3.15
RUN apk update && apk upgrade && apk add npm nginx
RUN apk update && apk upgrade && apk add npm
RUN mkdir -p /var/ssl
WORKDIR /var/www/html
ENTRYPOINT npm install && npm build && npm preview
ENTRYPOINT npm install && npm run build && npm run dev

2
docker-compose.yml

@ -18,7 +18,7 @@ services:
build: cont/back/
env_file: .env
depends_on: [postgres]
ports: [3030:3030]
ports: [3001:3001]
networks: [transcendence]
volumes: [./volumes/back:/var/www/html]
restart: always

131
volumes/back/.gitignore

@ -0,0 +1,131 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

73
volumes/back/README.md

@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

5
volumes/back/nest-cli.json

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

14949
volumes/back/package-lock.json

File diff suppressed because it is too large

75
volumes/back/package.json

@ -0,0 +1,75 @@
{
"name": "pong_server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@nestjs/websockets": "^9.2.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "28.1.3",
"prettier": "^2.3.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4",
"ws": "^8.11.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"dependencies": {
"@types/ws": "^8.5.3"
}
}

7
volumes/back/src/app.module.ts

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

10
volumes/back/src/main.ts

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

58
volumes/back/src/pong/game/Ball.ts

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

133
volumes/back/src/pong/game/Game.ts

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

28
volumes/back/src/pong/game/Paddle.ts

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

29
volumes/back/src/pong/game/Player.ts

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

36
volumes/back/src/pong/game/constants.ts

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

88
volumes/back/src/pong/game/utils.ts

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

18
volumes/back/src/pong/pong.gateway.spec.ts

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

99
volumes/back/src/pong/pong.gateway.ts

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

7
volumes/back/src/pong/pong.module.ts

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

18
volumes/back/src/pong/pong.spec.ts

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

59
volumes/back/src/pong/pong.ts

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

21
volumes/back/test/app.e2e-spec.ts

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

9
volumes/back/test/jest-e2e.json

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
volumes/back/tsconfig.build.json

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
volumes/back/tsconfig.json

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

2
volumes/front/package.json

@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"

2
volumes/front/src/components/Pong/Pong.svelte

@ -9,7 +9,6 @@
let socket: WebSocket;
onMount(async () => {
//Get canvas and its context
window.onload = () => {
const canvas: HTMLCanvasElement = document.getElementById('pong_canvas') as HTMLCanvasElement;
@ -20,7 +19,6 @@ onMount(async () => {
}
}
};
})
function setupSocket(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) {
socket = new WebSocket(SERVER_URL);
const game = new Game(canvas, context);

3
volumes/front/vite.config.ts

@ -4,4 +4,7 @@ import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
server: {
port: 80,
},
})

Loading…
Cancel
Save