vvandenb
2 years ago
44 changed files with 4228 additions and 2095 deletions
@ -0,0 +1,16 @@ |
|||
{ |
|||
"env": { |
|||
"browser": true, |
|||
"es2021": true |
|||
}, |
|||
"extends": "standard-with-typescript", |
|||
"overrides": [ |
|||
], |
|||
"parserOptions": { |
|||
"ecmaVersion": "latest", |
|||
"sourceType": "module", |
|||
"project": ["./tsconfig.json"] |
|||
}, |
|||
"rules": { |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,7 +1,7 @@ |
|||
import { Module } from '@nestjs/common'; |
|||
import { PongModule } from './pong/pong.module'; |
|||
import { Module } from '@nestjs/common' |
|||
import { PongModule } from './pong/pong.module' |
|||
|
|||
@Module({ |
|||
imports: [PongModule] |
|||
imports: [PongModule] |
|||
}) |
|||
export class AppModule {} |
|||
|
@ -1,10 +1,10 @@ |
|||
import { NestFactory } from '@nestjs/core'; |
|||
import { WsAdapter } from '@nestjs/platform-ws'; |
|||
import { AppModule } from './app.module'; |
|||
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); |
|||
async function bootstrap () { |
|||
const app = await NestFactory.create(AppModule) |
|||
app.useWebSocketAdapter(new WsAdapter(app)) |
|||
await app.listen(3001) |
|||
} |
|||
bootstrap(); |
|||
bootstrap() |
|||
|
@ -1,58 +1,65 @@ |
|||
import { gameInfoConstants } from './constants'; |
|||
import { Paddle } from './Paddle'; |
|||
import { Point, Rect } from './utils'; |
|||
import { gameInfoConstants } from './constants' |
|||
import { type 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; |
|||
} |
|||
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 |
|||
} |
|||
} |
|||
|
@ -1,133 +1,160 @@ |
|||
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); |
|||
import { Ball } from './Ball' |
|||
import { type WebSocket } from 'ws' |
|||
import { formatWebsocketData, Point, Rect } from './utils' |
|||
import { Player } from './Player' |
|||
import { |
|||
type GameInfo, |
|||
gameInfoConstants, |
|||
type 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; |
|||
} |
|||
id: string |
|||
timer: NodeJS.Timer |
|||
ball: Ball |
|||
players: Player[] = [] |
|||
playing: boolean |
|||
|
|||
constructor (sockets: WebSocket[], uuids: string[], names: 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, |
|||
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 |
|||
} |
|||
} |
|||
|
@ -1,28 +1,32 @@ |
|||
import { gameInfoConstants } from './constants'; |
|||
import { Point, Rect } from './utils'; |
|||
import { gameInfoConstants } from './constants' |
|||
import { type Point, Rect } from './utils' |
|||
|
|||
export class Paddle { |
|||
rect: Rect; |
|||
color: string | CanvasGradient | CanvasPattern = 'white'; |
|||
mapSize: Point; |
|||
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; |
|||
} |
|||
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); |
|||
} |
|||
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; |
|||
} |
|||
} |
|||
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 |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,29 +1,35 @@ |
|||
import { WebSocket } from 'ws'; |
|||
import { Paddle } from './Paddle'; |
|||
import { Point } from './utils'; |
|||
import { type WebSocket } from 'ws' |
|||
import { Paddle } from './Paddle' |
|||
import { type Point } from './utils' |
|||
|
|||
export class Player { |
|||
socket: WebSocket; |
|||
uuid: string; |
|||
name: string; |
|||
ready: boolean; |
|||
paddle: Paddle; |
|||
paddleCoords: Point; |
|||
mapSize: Point; |
|||
score: number; |
|||
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; |
|||
} |
|||
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); |
|||
} |
|||
newGame () { |
|||
this.score = 0 |
|||
this.paddle = new Paddle(this.paddleCoords, this.mapSize) |
|||
} |
|||
} |
|||
|
@ -1,36 +1,36 @@ |
|||
import { Point } from './utils'; |
|||
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' |
|||
}; |
|||
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; |
|||
yourPaddleIndex: number |
|||
gameId: string |
|||
} |
|||
export interface GameInfoConstants { |
|||
mapSize: Point; |
|||
paddleSize: Point; |
|||
playerXOffset: number; |
|||
ballSize: Point; |
|||
winScore: number; |
|||
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 |
|||
}; |
|||
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[]; |
|||
paddlesPositions: Point[] |
|||
ballPosition: Point |
|||
scores: number[] |
|||
} |
|||
|
@ -1,88 +1,99 @@ |
|||
export class Point { |
|||
x: number; |
|||
y: number; |
|||
x: number |
|||
y: number |
|||
|
|||
constructor(x: number, y: number) { |
|||
this.x = x; |
|||
this.y = y; |
|||
} |
|||
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); |
|||
} |
|||
// 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; |
|||
} |
|||
// Modifies `this` point
|
|||
add_inplace (other: Point) { |
|||
this.x += other.x |
|||
this.y += other.y |
|||
} |
|||
|
|||
clone(): Point { |
|||
return new Point(this.x, this.y); |
|||
} |
|||
clone (): Point { |
|||
return new Point(this.x, this.y) |
|||
} |
|||
} |
|||
|
|||
export class Rect { |
|||
center: Point; |
|||
size: Point; |
|||
center: Point |
|||
size: Point |
|||
|
|||
constructor(center: Point, size: Point) { |
|||
this.center = center; |
|||
this.size = size; |
|||
} |
|||
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); |
|||
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); |
|||
} |
|||
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; |
|||
// 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; |
|||
} |
|||
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; |
|||
// 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; |
|||
} |
|||
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); |
|||
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; |
|||
} |
|||
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 |
|||
}); |
|||
export function formatWebsocketData (event: string, data?: any): string { |
|||
return JSON.stringify({ |
|||
event, |
|||
data |
|||
}) |
|||
} |
|||
|
@ -1,18 +1,18 @@ |
|||
import { Test, TestingModule } from '@nestjs/testing'; |
|||
import { PongGateway } from './pong.gateway'; |
|||
import { Test, type TestingModule } from '@nestjs/testing' |
|||
import { PongGateway } from './pong.gateway' |
|||
|
|||
describe('PongGateway', () => { |
|||
let gateway: PongGateway; |
|||
let gateway: PongGateway |
|||
|
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
providers: [PongGateway] |
|||
}).compile(); |
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
providers: [PongGateway] |
|||
}).compile() |
|||
|
|||
gateway = module.get<PongGateway>(PongGateway); |
|||
}); |
|||
gateway = module.get<PongGateway>(PongGateway) |
|||
}) |
|||
|
|||
it('should be defined', () => { |
|||
expect(gateway).toBeDefined(); |
|||
}); |
|||
}); |
|||
it('should be defined', () => { |
|||
expect(gateway).toBeDefined() |
|||
}) |
|||
}) |
|||
|
@ -1,7 +1,7 @@ |
|||
import { Module } from '@nestjs/common'; |
|||
import { PongGateway } from './pong.gateway'; |
|||
import { Module } from '@nestjs/common' |
|||
import { PongGateway } from './pong.gateway' |
|||
|
|||
@Module({ |
|||
providers: [PongGateway] |
|||
providers: [PongGateway] |
|||
}) |
|||
export class PongModule {} |
|||
|
@ -1,18 +1,18 @@ |
|||
import { Test, TestingModule } from '@nestjs/testing'; |
|||
import { Pong } from './pong'; |
|||
import { Test, type TestingModule } from '@nestjs/testing' |
|||
import { Pong } from './pong' |
|||
|
|||
describe('Pong', () => { |
|||
let provider: Pong; |
|||
let provider: Pong |
|||
|
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
providers: [Pong] |
|||
}).compile(); |
|||
beforeEach(async () => { |
|||
const module: TestingModule = await Test.createTestingModule({ |
|||
providers: [Pong] |
|||
}).compile() |
|||
|
|||
provider = module.get<Pong>(Pong); |
|||
}); |
|||
provider = module.get<Pong>(Pong) |
|||
}) |
|||
|
|||
it('should be defined', () => { |
|||
expect(provider).toBeDefined(); |
|||
}); |
|||
}); |
|||
it('should be defined', () => { |
|||
expect(provider).toBeDefined() |
|||
}) |
|||
}) |
|||
|
@ -1,59 +1,59 @@ |
|||
import { WebSocket } from 'ws'; |
|||
import { GameInfo } from './game/constants'; |
|||
import { Game } from './game/Game'; |
|||
import { Point } from './game/utils'; |
|||
import { type WebSocket } from 'ws' |
|||
import { type GameInfo } from './game/constants' |
|||
import { Game } from './game/Game' |
|||
import { type 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]]; |
|||
} |
|||
} |
|||
private playerUUIDToGameIndex = new Map<string, number>() |
|||
private readonly games = new Array<Game>() |
|||
|
|||
newGame (sockets: WebSocket[], uuids: string[], names: 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]] |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,21 +1,24 @@ |
|||
import { Test, TestingModule } from '@nestjs/testing' |
|||
import { INestApplication } from '@nestjs/common' |
|||
import { Test, type TestingModule } from '@nestjs/testing' |
|||
import { type INestApplication } from '@nestjs/common' |
|||
import * as request from 'supertest' |
|||
import { AppModule } from './../src/app.module' |
|||
|
|||
describe('AppController (e2e)', () => { |
|||
let app: INestApplication |
|||
let app: INestApplication |
|||
|
|||
beforeEach(async () => { |
|||
const moduleFixture: TestingModule = await Test.createTestingModule({ |
|||
imports: [AppModule] |
|||
}).compile() |
|||
beforeEach(async () => { |
|||
const moduleFixture: TestingModule = await Test.createTestingModule({ |
|||
imports: [AppModule] |
|||
}).compile() |
|||
|
|||
app = moduleFixture.createNestApplication() |
|||
await app.init() |
|||
}) |
|||
app = moduleFixture.createNestApplication() |
|||
await app.init() |
|||
}) |
|||
|
|||
it('/ (GET)', () => { |
|||
return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!') |
|||
}) |
|||
it('/ (GET)', async () => { |
|||
return await request(app.getHttpServer()) |
|||
.get('/') |
|||
.expect(200) |
|||
.expect('Hello World!') |
|||
}) |
|||
}) |
|||
|
@ -1,5 +1,4 @@ |
|||
declare global { |
|||
namespace App { |
|||
} |
|||
namespace App {} |
|||
} |
|||
export {}; |
|||
export {}; |
|||
|
@ -1,139 +1,123 @@ |
|||
<script lang="ts"> |
|||
let chatIsOpen = false; |
|||
const toggleChat = () => (chatIsOpen = !chatIsOpen); |
|||
let newText = ""; |
|||
|
|||
let chatIsOpen = false; |
|||
const toggleChat = () => (chatIsOpen = !chatIsOpen); |
|||
let newText = ''; |
|||
|
|||
let chatMessages = [ |
|||
{ |
|||
name: 'Alice', |
|||
text: 'Hello guys! Happy to see you here!' |
|||
}, |
|||
{ |
|||
name: 'Bob', |
|||
text: 'Wanna play?' |
|||
}, |
|||
{ |
|||
name: 'Carl', |
|||
text: 'cyka blyat' |
|||
}, |
|||
] |
|||
|
|||
const sendMessage = () => { |
|||
if (newText !== '') |
|||
{ |
|||
const newMessage = { |
|||
name: 'You', |
|||
text: newText |
|||
}; |
|||
chatMessages = [ |
|||
...chatMessages, |
|||
newMessage |
|||
]; |
|||
newText = ''; |
|||
} |
|||
} |
|||
let chatMessages = [ |
|||
{ |
|||
name: "Alice", |
|||
text: "Hello guys! Happy to see you here!", |
|||
}, |
|||
{ |
|||
name: "Bob", |
|||
text: "Wanna play?", |
|||
}, |
|||
{ |
|||
name: "Carl", |
|||
text: "cyka blyat", |
|||
}, |
|||
]; |
|||
|
|||
const sendMessage = () => { |
|||
if (newText !== "") { |
|||
const newMessage = { |
|||
name: "You", |
|||
text: newText, |
|||
}; |
|||
chatMessages = [...chatMessages, newMessage]; |
|||
newText = ""; |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
|
|||
|
|||
<!-- Main chat div, changes style to hide/unhide the chat depending if it's open or closed --> |
|||
<div class={ |
|||
chatIsOpen |
|||
? 'chat-open chat-container' |
|||
: 'chat-container'}> |
|||
<!-- Button to toggle chat --> |
|||
<div class="chat-view-button"> |
|||
<button on:click={toggleChat}> |
|||
{#if chatIsOpen} |
|||
<img src="img/close.png" alt="Close" /> |
|||
{:else} |
|||
<img src="img/chat.png" alt="Open" /> |
|||
{/if} |
|||
</button> |
|||
</div> |
|||
<div class="chat"> |
|||
<!-- Chat history --> |
|||
<div class="messages"> |
|||
{#each chatMessages as message} |
|||
<p class="message"> |
|||
<span class="message-name"> |
|||
{message.name} |
|||
</span>: {message.text} |
|||
</p> |
|||
{/each} |
|||
</div> |
|||
<!-- Form to send message --> |
|||
<form on:submit|preventDefault={sendMessage}> |
|||
<input |
|||
type="text" |
|||
placeholder="Type a message..." |
|||
bind:value={newText} /> |
|||
<button> |
|||
<img src="img/send.png" alt="send" /> |
|||
</button> |
|||
</form> |
|||
</div> |
|||
<div class={chatIsOpen ? "chat-open chat-container" : "chat-container"}> |
|||
<!-- Button to toggle chat --> |
|||
<div class="chat-view-button"> |
|||
<button on:click={toggleChat}> |
|||
{#if chatIsOpen} |
|||
<img src="img/close.png" alt="Close" /> |
|||
{:else} |
|||
<img src="img/chat.png" alt="Open" /> |
|||
{/if} |
|||
</button> |
|||
</div> |
|||
<div class="chat"> |
|||
<!-- Chat history --> |
|||
<div class="messages"> |
|||
{#each chatMessages as message} |
|||
<p class="message"> |
|||
<span class="message-name"> |
|||
{message.name} |
|||
</span>: {message.text} |
|||
</p> |
|||
{/each} |
|||
</div> |
|||
<!-- Form to send message --> |
|||
<form on:submit|preventDefault={sendMessage}> |
|||
<input type="text" placeholder="Type a message..." bind:value={newText} /> |
|||
<button> |
|||
<img src="img/send.png" alt="send" /> |
|||
</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
|
|||
|
|||
<style> |
|||
.chat-container { |
|||
position: absolute; |
|||
top: 10rem; |
|||
height: calc(100vh - 10rem); |
|||
z-index: 42; /* Show it above everything */ |
|||
display: flex; |
|||
width: 300px; |
|||
right: -300px; |
|||
transition: right 0.3s ease-out; |
|||
} |
|||
.chat-view-button { |
|||
position: absolute; |
|||
left: -64px; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto 0; |
|||
height: 64px; |
|||
} |
|||
.chat-view-button button { |
|||
border: none; |
|||
cursor: pointer; |
|||
border-radius: 16px 0 0 16px; |
|||
padding: 16px 16px 16px 16px; |
|||
} |
|||
.chat-open { |
|||
right: 0; /* Shows chat */ |
|||
} |
|||
.chat { |
|||
display: flex; |
|||
flex-direction: column; |
|||
width: 100%; |
|||
} |
|||
.messages { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: calc(100vh - 10rem); |
|||
padding: 1rem; |
|||
overflow-y: scroll; |
|||
} |
|||
.message-name { |
|||
cursor: pointer; |
|||
} |
|||
form { |
|||
/* border-bottom: var(--grey) 1px solid; */ |
|||
margin: 1rem; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
} |
|||
input { |
|||
border: none; |
|||
} |
|||
form button { |
|||
background: transparent; |
|||
border: none; |
|||
cursor: pointer; |
|||
} |
|||
.chat-container { |
|||
position: absolute; |
|||
top: 10rem; |
|||
height: calc(100vh - 10rem); |
|||
z-index: 42; /* Show it above everything */ |
|||
display: flex; |
|||
width: 300px; |
|||
right: -300px; |
|||
transition: right 0.3s ease-out; |
|||
} |
|||
.chat-view-button { |
|||
position: absolute; |
|||
left: -64px; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto 0; |
|||
height: 64px; |
|||
} |
|||
.chat-view-button button { |
|||
border: none; |
|||
cursor: pointer; |
|||
border-radius: 16px 0 0 16px; |
|||
padding: 16px 16px 16px 16px; |
|||
} |
|||
.chat-open { |
|||
right: 0; /* Shows chat */ |
|||
} |
|||
.chat { |
|||
display: flex; |
|||
flex-direction: column; |
|||
width: 100%; |
|||
} |
|||
.messages { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: calc(100vh - 10rem); |
|||
padding: 1rem; |
|||
overflow-y: scroll; |
|||
} |
|||
.message-name { |
|||
cursor: pointer; |
|||
} |
|||
form { |
|||
/* border-bottom: var(--grey) 1px solid; */ |
|||
margin: 1rem; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
} |
|||
input { |
|||
border: none; |
|||
} |
|||
form button { |
|||
background: transparent; |
|||
border: none; |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
|
@ -1,80 +1,80 @@ |
|||
<script lang="ts" context="module"> |
|||
export interface Friend { |
|||
username: string; |
|||
status: 'online' | 'offline' | 'in a game'; |
|||
} |
|||
export interface Friend { |
|||
username: string; |
|||
status: "online" | "offline" | "in a game"; |
|||
} |
|||
</script> |
|||
|
|||
<script lang="ts"> |
|||
export let friends: Array<Friend> = []; |
|||
async function addFriend(event: any) { |
|||
console.log(typeof event); |
|||
|
|||
event.preventDefault(); |
|||
const usernameInput = event.target.querySelector('input[type="text"]'); |
|||
console.log(usernameInput); |
|||
|
|||
const username = usernameInput.value; |
|||
// const response = await fetch('', { |
|||
// method: 'POST', |
|||
// headers: { |
|||
// 'Content-Type': 'application/json' |
|||
// }, |
|||
// body: JSON.stringify({ username }) |
|||
// }); |
|||
// if (response.ok) { |
|||
// console.log('Friend added successfully'); |
|||
// } else { |
|||
// console.log('Failed to add friend'); |
|||
// } |
|||
// usernameInput.value = ''; |
|||
alert('Trying to add friend' + username); |
|||
} |
|||
export let friends: Array<Friend> = []; |
|||
async function addFriend(event: any) { |
|||
console.log(typeof event); |
|||
|
|||
event.preventDefault(); |
|||
const usernameInput = event.target.querySelector('input[type="text"]'); |
|||
console.log(usernameInput); |
|||
|
|||
const username = usernameInput.value; |
|||
// const response = await fetch('', { |
|||
// method: 'POST', |
|||
// headers: { |
|||
// 'Content-Type': 'application/json' |
|||
// }, |
|||
// body: JSON.stringify({ username }) |
|||
// }); |
|||
// if (response.ok) { |
|||
// console.log('Friend added successfully'); |
|||
// } else { |
|||
// console.log('Failed to add friend'); |
|||
// } |
|||
// usernameInput.value = ''; |
|||
alert("Trying to add friend" + username); |
|||
} |
|||
</script> |
|||
|
|||
<div class="overlay"> |
|||
<div class="friends" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if friends.length > 0} |
|||
<h2>Monkey friends</h2> |
|||
{#each friends.slice(0, 10) as friend} |
|||
<li> |
|||
<span>{friend.username} is {friend.status}</span> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No friends to display</p> |
|||
{/if} |
|||
<div> |
|||
<h3>Add a friend</h3> |
|||
<form on:submit={addFriend}> |
|||
<input type="text" /> |
|||
<button type="submit">Add</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="friends" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if friends.length > 0} |
|||
<h2>Monkey friends</h2> |
|||
{#each friends.slice(0, 10) as friend} |
|||
<li> |
|||
<span>{friend.username} is {friend.status}</span> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No friends to display</p> |
|||
{/if} |
|||
<div> |
|||
<h3>Add a friend</h3> |
|||
<form on:submit={addFriend}> |
|||
<input type="text" /> |
|||
<button type="submit">Add</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.friends { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
.friends { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
</style> |
|||
|
@ -1,58 +1,58 @@ |
|||
<script lang="ts" context="module"> |
|||
export interface Match { |
|||
winner: string; |
|||
loser: string; |
|||
points: number; |
|||
rank: string; |
|||
} |
|||
export interface Match { |
|||
winner: string; |
|||
loser: string; |
|||
points: number; |
|||
rank: string; |
|||
} |
|||
</script> |
|||
|
|||
<script lang="ts"> |
|||
export let matches: Array<Match> = []; |
|||
export let matches: Array<Match> = []; |
|||
</script> |
|||
|
|||
<div class="overlay"> |
|||
<div class="history" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if matches.length > 0} |
|||
<h2>Last 10 monkey games</h2> |
|||
{#each matches.slice(0, 10) as match} |
|||
<li> |
|||
<span>{match.winner} 1 - 0 {match.loser}</span> |
|||
{#if match.points > 0} |
|||
<span>+{match.points}</span> |
|||
{:else} |
|||
<span>{match.points}</span> |
|||
{/if} |
|||
<span>MP | rank #{match.rank}</span> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No matches to display</p> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
<div class="history" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if matches.length > 0} |
|||
<h2>Last 10 monkey games</h2> |
|||
{#each matches.slice(0, 10) as match} |
|||
<li> |
|||
<span>{match.winner} 1 - 0 {match.loser}</span> |
|||
{#if match.points > 0} |
|||
<span>+{match.points}</span> |
|||
{:else} |
|||
<span>{match.points}</span> |
|||
{/if} |
|||
<span>MP | rank #{match.rank}</span> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No matches to display</p> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.history { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
.history { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
</style> |
|||
|
@ -1,106 +1,106 @@ |
|||
<script lang="ts"> |
|||
export let links = [ |
|||
{ text: 'Home', url: 'img/pong.png' }, |
|||
{ text: 'Spectate' }, |
|||
{ text: 'Chat' }, |
|||
{ text: 'History' }, |
|||
{ text: 'Friends' }, |
|||
{ text: 'Profile' } |
|||
]; |
|||
export let clickProfile = () => {}; |
|||
export let clickHistory = () => {}; |
|||
export let clickFriends = () => {}; |
|||
export let clickSpectate = () => {}; |
|||
export let links = [ |
|||
{ text: "Home", url: "img/pong.png" }, |
|||
{ text: "Spectate" }, |
|||
{ text: "Chat" }, |
|||
{ text: "History" }, |
|||
{ text: "Friends" }, |
|||
{ text: "Profile" }, |
|||
]; |
|||
export let clickProfile = () => {}; |
|||
export let clickHistory = () => {}; |
|||
export let clickFriends = () => {}; |
|||
export let clickSpectate = () => {}; |
|||
</script> |
|||
|
|||
<nav class="navigation-bar"> |
|||
<ul> |
|||
{#each links as link} |
|||
{#if link.text === 'Spectate'} |
|||
<li> |
|||
<button on:click={clickSpectate}> |
|||
<p>Spectate</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === 'Friends'} |
|||
<li> |
|||
<button on:click={clickFriends}> |
|||
<p>Friends</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === 'Profile'} |
|||
<li> |
|||
<button on:click={clickProfile}> |
|||
<img src="img/profileicon.png" alt="profile icon" /> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === 'History'} |
|||
<li> |
|||
<button on:click={clickHistory}> |
|||
<p>History</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === 'Home'} |
|||
<li> |
|||
<a href="/"> |
|||
<img src="img/pong.png" alt="home-icon" /> |
|||
</a> |
|||
</li> |
|||
{/if} |
|||
{/each} |
|||
</ul> |
|||
<ul> |
|||
{#each links as link} |
|||
{#if link.text === "Spectate"} |
|||
<li> |
|||
<button on:click={clickSpectate}> |
|||
<p>Spectate</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === "Friends"} |
|||
<li> |
|||
<button on:click={clickFriends}> |
|||
<p>Friends</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === "Profile"} |
|||
<li> |
|||
<button on:click={clickProfile}> |
|||
<img src="img/profileicon.png" alt="profile icon" /> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === "History"} |
|||
<li> |
|||
<button on:click={clickHistory}> |
|||
<p>History</p> |
|||
</button> |
|||
</li> |
|||
{/if} |
|||
{#if link.text === "Home"} |
|||
<li> |
|||
<a href="/"> |
|||
<img src="img/pong.png" alt="home-icon" /> |
|||
</a> |
|||
</li> |
|||
{/if} |
|||
{/each} |
|||
</ul> |
|||
</nav> |
|||
|
|||
<style> |
|||
.navigation-bar { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background-color: #f5f5f5; |
|||
padding: 1rem; |
|||
} |
|||
.navigation-bar { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background-color: #f5f5f5; |
|||
padding: 1rem; |
|||
} |
|||
|
|||
.navigation-bar ul { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
list-style: none; |
|||
padding: 0; |
|||
} |
|||
.navigation-bar ul { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
list-style: none; |
|||
padding: 0; |
|||
} |
|||
|
|||
.navigation-bar li { |
|||
margin: 0 1rem; |
|||
} |
|||
.navigation-bar li { |
|||
margin: 0 1rem; |
|||
} |
|||
|
|||
.navigation-bar img { |
|||
width: 2rem; |
|||
height: 2rem; |
|||
} |
|||
.navigation-bar img { |
|||
width: 2rem; |
|||
height: 2rem; |
|||
} |
|||
|
|||
.navigation-bar button { |
|||
border: none; |
|||
} |
|||
.navigation-bar button { |
|||
border: none; |
|||
} |
|||
|
|||
@media (max-width: 768px) { |
|||
.navigation-bar { |
|||
flex-direction: column; |
|||
align-items: stretch; |
|||
} |
|||
@media (max-width: 768px) { |
|||
.navigation-bar { |
|||
flex-direction: column; |
|||
align-items: stretch; |
|||
} |
|||
|
|||
.navigation-bar ul { |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
.navigation-bar ul { |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.navigation-bar li { |
|||
margin: 0; |
|||
padding: 1rem; |
|||
text-align: center; |
|||
} |
|||
} |
|||
.navigation-bar li { |
|||
margin: 0; |
|||
padding: 1rem; |
|||
text-align: center; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,38 +1,38 @@ |
|||
<script lang="ts"> |
|||
// import { navigate } from 'svelte-routing'; |
|||
// import { navigate } from 'svelte-routing'; |
|||
|
|||
// function handleMatchmakingClick() { |
|||
// navigate('/matchmaking'); |
|||
// } |
|||
// function handleMatchmakingClick() { |
|||
// navigate('/matchmaking'); |
|||
// } |
|||
|
|||
// function handlePlayWithFriendClick() { |
|||
// navigate('/play-with-friend'); |
|||
// } |
|||
// function handlePlayWithFriendClick() { |
|||
// navigate('/play-with-friend'); |
|||
// } |
|||
</script> |
|||
|
|||
<!-- dr --> |
|||
<main> |
|||
<h1>Choose a gamemode</h1> |
|||
<!-- <button on:click={handleMatchmakingClick}>Matchmaking</button> |
|||
<h1>Choose a gamemode</h1> |
|||
<!-- <button on:click={handleMatchmakingClick}>Matchmaking</button> |
|||
<button on:click={handlePlayWithFriendClick}>Play with a friend</button> --> |
|||
<button>Matchmaking</button> |
|||
<button>Play with a friend</button> |
|||
<button>Matchmaking</button> |
|||
<button>Play with a friend</button> |
|||
</main> |
|||
|
|||
<style> |
|||
main { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
main { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
h1 { |
|||
margin-bottom: 2rem; |
|||
} |
|||
h1 { |
|||
margin-bottom: 2rem; |
|||
} |
|||
|
|||
button { |
|||
font-size: 1.5rem; |
|||
padding: 1rem 2rem; |
|||
margin-bottom: 1rem; |
|||
} |
|||
button { |
|||
font-size: 1.5rem; |
|||
padding: 1rem 2rem; |
|||
margin-bottom: 1rem; |
|||
} |
|||
</style> |
|||
|
@ -1,15 +1,19 @@ |
|||
import { Point, Rect } from './utils'; |
|||
import { Point, Rect } from "./utils"; |
|||
|
|||
export class Ball { |
|||
rect: Rect; |
|||
speed: Point; |
|||
color: string | CanvasGradient | CanvasPattern = 'white'; |
|||
rect: Rect; |
|||
speed: Point; |
|||
color: string | CanvasGradient | CanvasPattern = "white"; |
|||
|
|||
constructor(spawn: Point, size: Point = new Point(20, 20), speed: Point = new Point(10, 2)) { |
|||
this.rect = new Rect(spawn, size); |
|||
} |
|||
constructor( |
|||
spawn: Point, |
|||
size: Point = new Point(20, 20), |
|||
speed: Point = new Point(10, 2) |
|||
) { |
|||
this.rect = new Rect(spawn, size); |
|||
} |
|||
|
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.rect.draw(context, this.color); |
|||
} |
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.rect.draw(context, this.color); |
|||
} |
|||
} |
|||
|
@ -1,82 +1,96 @@ |
|||
import { Ball } from './Ball'; |
|||
import { GAME_EVENTS } from './constants'; |
|||
import type { GameInfo, GameUpdate } from './constants'; |
|||
import { Paddle } from './Paddle'; |
|||
import { Player } from './Player'; |
|||
import { formatWebsocketData, Point } from './utils'; |
|||
import { Ball } from "./Ball"; |
|||
import { GAME_EVENTS } from "./constants"; |
|||
import type { GameInfo, GameUpdate } from "./constants"; |
|||
import { Paddle } from "./Paddle"; |
|||
import { Player } from "./Player"; |
|||
import { formatWebsocketData, Point } from "./utils"; |
|||
|
|||
const BG_COLOR = 'black'; |
|||
const BG_COLOR = "black"; |
|||
|
|||
export class Game { |
|||
canvas: HTMLCanvasElement; |
|||
context: CanvasRenderingContext2D; |
|||
ball: Ball; |
|||
players: Player[]; |
|||
my_paddle: Paddle; |
|||
id: string; |
|||
canvas: HTMLCanvasElement; |
|||
context: CanvasRenderingContext2D; |
|||
ball: Ball; |
|||
players: Player[]; |
|||
my_paddle: Paddle; |
|||
id: string; |
|||
|
|||
constructor(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) { |
|||
this.canvas = canvas; |
|||
this.context = context; |
|||
this.players = []; |
|||
this.my_paddle = null; |
|||
} |
|||
constructor(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) { |
|||
this.canvas = canvas; |
|||
this.context = context; |
|||
this.players = []; |
|||
this.my_paddle = null; |
|||
} |
|||
|
|||
setInfo(data: GameInfo) { |
|||
this.canvas.width = data.mapSize.x; |
|||
this.canvas.height = data.mapSize.y; |
|||
setInfo(data: GameInfo) { |
|||
this.canvas.width = data.mapSize.x; |
|||
this.canvas.height = data.mapSize.y; |
|||
|
|||
this.ball = new Ball(new Point(this.canvas.width / 2, this.canvas.height / 2), data.ballSize); |
|||
this.ball = new Ball( |
|||
new Point(this.canvas.width / 2, this.canvas.height / 2), |
|||
data.ballSize |
|||
); |
|||
|
|||
const paddle1: Paddle = new Paddle(new Point(data.playerXOffset, this.canvas.height / 2), data.paddleSize); |
|||
const paddle2: Paddle = new Paddle( |
|||
new Point(this.canvas.width - data.playerXOffset, this.canvas.height / 2), |
|||
data.paddleSize |
|||
); |
|||
this.players = [new Player(paddle1), new Player(paddle2)]; |
|||
this.my_paddle = this.players[data.yourPaddleIndex].paddle; |
|||
this.id = data.gameId; |
|||
} |
|||
const paddle1: Paddle = new Paddle( |
|||
new Point(data.playerXOffset, this.canvas.height / 2), |
|||
data.paddleSize |
|||
); |
|||
const paddle2: Paddle = new Paddle( |
|||
new Point(this.canvas.width - data.playerXOffset, this.canvas.height / 2), |
|||
data.paddleSize |
|||
); |
|||
this.players = [new Player(paddle1), new Player(paddle2)]; |
|||
this.my_paddle = this.players[data.yourPaddleIndex].paddle; |
|||
this.id = data.gameId; |
|||
} |
|||
|
|||
start(socket: WebSocket) { |
|||
if (this.my_paddle) { |
|||
this.canvas.addEventListener('mousemove', (e) => { |
|||
this.my_paddle.move(e); |
|||
socket.send( |
|||
formatWebsocketData(GAME_EVENTS.PLAYER_MOVE, { |
|||
position: this.my_paddle.rect.center |
|||
}) |
|||
); |
|||
}); |
|||
console.log('Game started!'); |
|||
} |
|||
} |
|||
start(socket: WebSocket) { |
|||
if (this.my_paddle) { |
|||
this.canvas.addEventListener("mousemove", (e) => { |
|||
this.my_paddle.move(e); |
|||
socket.send( |
|||
formatWebsocketData(GAME_EVENTS.PLAYER_MOVE, { |
|||
position: this.my_paddle.rect.center, |
|||
}) |
|||
); |
|||
}); |
|||
console.log("Game started!"); |
|||
} |
|||
} |
|||
|
|||
update(data: GameUpdate) { |
|||
if (this.players[0].paddle != this.my_paddle) { |
|||
this.players[0].paddle.rect.center = data.paddlesPositions[0]; |
|||
} |
|||
if (this.players[1].paddle != this.my_paddle) { |
|||
this.players[1].paddle.rect.center = data.paddlesPositions[1]; |
|||
} |
|||
this.ball.rect.center = data.ballPosition; |
|||
for (let i = 0; i < data.scores.length; i++) { |
|||
this.players[i].score = data.scores[i]; |
|||
} |
|||
} |
|||
update(data: GameUpdate) { |
|||
if (this.players[0].paddle != this.my_paddle) { |
|||
this.players[0].paddle.rect.center = data.paddlesPositions[0]; |
|||
} |
|||
if (this.players[1].paddle != this.my_paddle) { |
|||
this.players[1].paddle.rect.center = data.paddlesPositions[1]; |
|||
} |
|||
this.ball.rect.center = data.ballPosition; |
|||
for (let i = 0; i < data.scores.length; i++) { |
|||
this.players[i].score = data.scores[i]; |
|||
} |
|||
} |
|||
|
|||
draw() { |
|||
this.context.fillStyle = BG_COLOR; |
|||
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); |
|||
draw() { |
|||
this.context.fillStyle = BG_COLOR; |
|||
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); |
|||
|
|||
this.players.forEach((p) => p.draw(this.context)); |
|||
this.ball.draw(this.context); |
|||
this.players.forEach((p) => p.draw(this.context)); |
|||
this.ball.draw(this.context); |
|||
|
|||
const max_width = 50; |
|||
this.context.font = '50px Arial'; |
|||
const text_width = this.context.measureText('0').width; |
|||
const text_offset = 50; |
|||
this.players[0].drawScore(this.canvas.width / 2 - (text_width + text_offset), max_width, this.context); |
|||
this.players[1].drawScore(this.canvas.width / 2 + text_offset, max_width, this.context); |
|||
} |
|||
const max_width = 50; |
|||
this.context.font = "50px Arial"; |
|||
const text_width = this.context.measureText("0").width; |
|||
const text_offset = 50; |
|||
this.players[0].drawScore( |
|||
this.canvas.width / 2 - (text_width + text_offset), |
|||
max_width, |
|||
this.context |
|||
); |
|||
this.players[1].drawScore( |
|||
this.canvas.width / 2 + text_offset, |
|||
max_width, |
|||
this.context |
|||
); |
|||
} |
|||
} |
|||
|
@ -1,29 +1,29 @@ |
|||
import { Point, Rect } from './utils'; |
|||
import { Point, Rect } from "./utils"; |
|||
|
|||
export class Paddle { |
|||
rect: Rect; |
|||
color: string | CanvasGradient | CanvasPattern = 'white'; |
|||
rect: Rect; |
|||
color: string | CanvasGradient | CanvasPattern = "white"; |
|||
|
|||
constructor(spawn: Point, size: Point = new Point(6, 100)) { |
|||
this.rect = new Rect(spawn, size); |
|||
} |
|||
constructor(spawn: Point, size: Point = new Point(6, 100)) { |
|||
this.rect = new Rect(spawn, size); |
|||
} |
|||
|
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.rect.draw(context, this.color); |
|||
} |
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.rect.draw(context, this.color); |
|||
} |
|||
|
|||
move(e: MouseEvent) { |
|||
const canvas = e.target as HTMLCanvasElement; |
|||
const rect = canvas.getBoundingClientRect(); |
|||
const new_y = ((e.clientY - rect.top) * canvas.height) / rect.height; |
|||
move(e: MouseEvent) { |
|||
const canvas = e.target as HTMLCanvasElement; |
|||
const rect = canvas.getBoundingClientRect(); |
|||
const new_y = ((e.clientY - rect.top) * canvas.height) / rect.height; |
|||
|
|||
const offset: number = this.rect.size.y / 2; |
|||
if (new_y - offset < 0) { |
|||
this.rect.center.y = offset; |
|||
} else if (new_y + offset > canvas.height) { |
|||
this.rect.center.y = canvas.height - offset; |
|||
} else { |
|||
this.rect.center.y = new_y; |
|||
} |
|||
} |
|||
const offset: number = this.rect.size.y / 2; |
|||
if (new_y - offset < 0) { |
|||
this.rect.center.y = offset; |
|||
} else if (new_y + offset > canvas.height) { |
|||
this.rect.center.y = canvas.height - offset; |
|||
} else { |
|||
this.rect.center.y = new_y; |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,19 +1,23 @@ |
|||
import type { Paddle } from './Paddle'; |
|||
import type { Paddle } from "./Paddle"; |
|||
|
|||
export class Player { |
|||
paddle: Paddle; |
|||
score: number; |
|||
paddle: Paddle; |
|||
score: number; |
|||
|
|||
constructor(paddle: Paddle) { |
|||
this.paddle = paddle; |
|||
this.score = 0; |
|||
} |
|||
constructor(paddle: Paddle) { |
|||
this.paddle = paddle; |
|||
this.score = 0; |
|||
} |
|||
|
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.paddle.draw(context); |
|||
} |
|||
draw(context: CanvasRenderingContext2D) { |
|||
this.paddle.draw(context); |
|||
} |
|||
|
|||
drawScore(score_position_x: number, max_width: number, context: CanvasRenderingContext2D) { |
|||
context.fillText(this.score.toString(), score_position_x, 50, max_width); |
|||
} |
|||
drawScore( |
|||
score_position_x: number, |
|||
max_width: number, |
|||
context: CanvasRenderingContext2D |
|||
) { |
|||
context.fillText(this.score.toString(), score_position_x, 50, max_width); |
|||
} |
|||
} |
|||
|
@ -1,96 +1,111 @@ |
|||
<script lang="ts"> |
|||
import { GAME_EVENTS } from './constants'; |
|||
import { Game } from './Game'; |
|||
import { formatWebsocketData } from './utils'; |
|||
import { GAME_EVENTS } from "./constants"; |
|||
import { Game } from "./Game"; |
|||
import { formatWebsocketData } from "./utils"; |
|||
|
|||
const FPS = 144; |
|||
const SERVER_URL = 'ws://localhost:3001'; |
|||
const FPS = 144; |
|||
const SERVER_URL = "ws://localhost:3001"; |
|||
|
|||
let connected: boolean = false; |
|||
let socket: WebSocket; |
|||
let username: string = 'John'; |
|||
let otherUsername: string = 'Garfield'; |
|||
let connected: boolean = false; |
|||
let socket: WebSocket; |
|||
let username: string = "John"; |
|||
let otherUsername: string = "Garfield"; |
|||
|
|||
//Get canvas and its context |
|||
window.onload = () => { |
|||
const canvas: HTMLCanvasElement = document.getElementById('pong_canvas') as HTMLCanvasElement; |
|||
if (canvas) { |
|||
const context: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D; |
|||
if (context) { |
|||
setupSocket(canvas, context); |
|||
} |
|||
} |
|||
}; |
|||
//Get canvas and its context |
|||
window.onload = () => { |
|||
const canvas: HTMLCanvasElement = document.getElementById( |
|||
"pong_canvas" |
|||
) as HTMLCanvasElement; |
|||
if (canvas) { |
|||
const context: CanvasRenderingContext2D = canvas.getContext( |
|||
"2d" |
|||
) as CanvasRenderingContext2D; |
|||
if (context) { |
|||
setupSocket(canvas, context); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
function setupSocket(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) { |
|||
socket = new WebSocket(SERVER_URL); |
|||
const game = new Game(canvas, context); |
|||
socket.onmessage = function (e) { |
|||
const event_json = JSON.parse(e.data); |
|||
const event = event_json.event; |
|||
const data = event_json.data; |
|||
function setupSocket( |
|||
canvas: HTMLCanvasElement, |
|||
context: CanvasRenderingContext2D |
|||
) { |
|||
socket = new WebSocket(SERVER_URL); |
|||
const game = new Game(canvas, context); |
|||
socket.onmessage = function (e) { |
|||
const event_json = JSON.parse(e.data); |
|||
const event = event_json.event; |
|||
const data = event_json.data; |
|||
|
|||
if (event == GAME_EVENTS.START_GAME) { |
|||
game.start(socket); |
|||
} else if (event == GAME_EVENTS.GAME_TICK) { |
|||
game.update(data); |
|||
} else if (event == GAME_EVENTS.GET_GAME_INFO) { |
|||
if (data && data.gameId != game.id) { |
|||
game.setInfo(data); |
|||
setInterval(() => { |
|||
game.draw(); |
|||
}, 1000 / FPS); |
|||
console.log('Game updated!'); |
|||
} |
|||
} else { |
|||
console.log('Unknown event from server: ' + event); |
|||
} |
|||
}; |
|||
socket.onopen = () => { |
|||
connected = true; |
|||
}; |
|||
socket.onclose = () => { |
|||
connected = false; |
|||
setupSocket(canvas, context); |
|||
}; |
|||
} |
|||
if (event == GAME_EVENTS.START_GAME) { |
|||
game.start(socket); |
|||
} else if (event == GAME_EVENTS.GAME_TICK) { |
|||
game.update(data); |
|||
} else if (event == GAME_EVENTS.GET_GAME_INFO) { |
|||
if (data && data.gameId != game.id) { |
|||
game.setInfo(data); |
|||
setInterval(() => { |
|||
game.draw(); |
|||
}, 1000 / FPS); |
|||
console.log("Game updated!"); |
|||
} |
|||
} else { |
|||
console.log("Unknown event from server: " + event); |
|||
} |
|||
}; |
|||
socket.onopen = () => { |
|||
connected = true; |
|||
}; |
|||
socket.onclose = () => { |
|||
connected = false; |
|||
setupSocket(canvas, context); |
|||
}; |
|||
} |
|||
|
|||
function updateGameInfo() { |
|||
socket.send(formatWebsocketData(GAME_EVENTS.GET_GAME_INFO)); |
|||
} |
|||
function updateGameInfo() { |
|||
socket.send(formatWebsocketData(GAME_EVENTS.GET_GAME_INFO)); |
|||
} |
|||
|
|||
function connectToServer() { |
|||
socket.send(formatWebsocketData(GAME_EVENTS.REGISTER_PLAYER, { playerName: username })); |
|||
setInterval(() => { |
|||
updateGameInfo(); |
|||
}, 1000); |
|||
} |
|||
function connectToServer() { |
|||
socket.send( |
|||
formatWebsocketData(GAME_EVENTS.REGISTER_PLAYER, { playerName: username }) |
|||
); |
|||
setInterval(() => { |
|||
updateGameInfo(); |
|||
}, 1000); |
|||
} |
|||
</script> |
|||
|
|||
<div> |
|||
{#if connected} |
|||
Your name: |
|||
<input bind:value={username} /> |
|||
<br /> |
|||
<button on:click={connectToServer}> Connect </button> |
|||
<br /> |
|||
Other player name: |
|||
<input bind:value={otherUsername} /> |
|||
<br /> |
|||
<button |
|||
on:click={() => { |
|||
socket.send(formatWebsocketData(GAME_EVENTS.CREATE_GAME, { playerNames: [username, otherUsername] })); |
|||
updateGameInfo(); |
|||
}} |
|||
> |
|||
Create game vs {otherUsername} |
|||
</button> |
|||
<br /> |
|||
<button on:click={() => socket.send(formatWebsocketData(GAME_EVENTS.READY))}>Ready</button> |
|||
<br /> |
|||
<br /> |
|||
{:else} |
|||
Connecting to game server... |
|||
{/if} |
|||
<canvas id="pong_canvas" /> |
|||
{#if connected} |
|||
Your name: |
|||
<input bind:value={username} /> |
|||
<br /> |
|||
<button on:click={connectToServer}> Connect </button> |
|||
<br /> |
|||
Other player name: |
|||
<input bind:value={otherUsername} /> |
|||
<br /> |
|||
<button |
|||
on:click={() => { |
|||
socket.send( |
|||
formatWebsocketData(GAME_EVENTS.CREATE_GAME, { |
|||
playerNames: [username, otherUsername], |
|||
}) |
|||
); |
|||
updateGameInfo(); |
|||
}} |
|||
> |
|||
Create game vs {otherUsername} |
|||
</button> |
|||
<br /> |
|||
<button on:click={() => socket.send(formatWebsocketData(GAME_EVENTS.READY))} |
|||
>Ready</button |
|||
> |
|||
<br /> |
|||
<br /> |
|||
{:else} |
|||
Connecting to game server... |
|||
{/if} |
|||
<canvas id="pong_canvas" /> |
|||
</div> |
|||
|
@ -1,88 +1,96 @@ |
|||
export class Point { |
|||
x: number; |
|||
y: number; |
|||
x: number; |
|||
y: number; |
|||
|
|||
constructor(x: number, y: number) { |
|||
this.x = x; |
|||
this.y = y; |
|||
} |
|||
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); |
|||
} |
|||
//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; |
|||
} |
|||
//Modifies `this` point
|
|||
add_inplace(other: Point) { |
|||
this.x += other.x; |
|||
this.y += other.y; |
|||
} |
|||
|
|||
clone(): Point { |
|||
return new Point(this.x, this.y); |
|||
} |
|||
clone(): Point { |
|||
return new Point(this.x, this.y); |
|||
} |
|||
} |
|||
|
|||
export class Rect { |
|||
center: Point; |
|||
size: Point; |
|||
center: Point; |
|||
size: Point; |
|||
|
|||
constructor(center: Point, size: Point) { |
|||
this.center = center; |
|||
this.size = size; |
|||
} |
|||
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); |
|||
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); |
|||
} |
|||
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; |
|||
//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; |
|||
} |
|||
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; |
|||
//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; |
|||
} |
|||
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); |
|||
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; |
|||
} |
|||
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 |
|||
}); |
|||
return JSON.stringify({ |
|||
event, |
|||
data, |
|||
}); |
|||
} |
|||
|
@ -1,109 +1,109 @@ |
|||
<script lang="ts"> |
|||
export let username = ''; |
|||
export let realname = ''; |
|||
export let wins = 0; |
|||
export let losses = 0; |
|||
export let elo = 0; |
|||
export let rank = -1; |
|||
export let is2faEnabled = false; |
|||
async function handleSubmit(event: Event) { |
|||
event.preventDefault(); |
|||
// const response = await fetch('', { |
|||
// method: 'POST', |
|||
// headers: { |
|||
// 'Content-Type': 'application/json' |
|||
// }, |
|||
// body: JSON.stringify({ |
|||
// username |
|||
// }) |
|||
// }); |
|||
// if (response.ok) { |
|||
// console.log('username updated'); |
|||
// } |
|||
// else { |
|||
// console.log('username update failed'); |
|||
// } |
|||
alert('Trying to update username to ' + username); |
|||
} |
|||
async function handleAvatarUpload(event: Event) { |
|||
event.preventDefault(); |
|||
alert('Trying to upload avatar'); |
|||
} |
|||
async function handle2fa(event: Event) { |
|||
event.preventDefault(); |
|||
alert('Trying to ' + (is2faEnabled ? 'disable' : 'enable') + ' 2FA'); |
|||
} |
|||
export let username = ""; |
|||
export let realname = ""; |
|||
export let wins = 0; |
|||
export let losses = 0; |
|||
export let elo = 0; |
|||
export let rank = -1; |
|||
export let is2faEnabled = false; |
|||
async function handleSubmit(event: Event) { |
|||
event.preventDefault(); |
|||
// const response = await fetch('', { |
|||
// method: 'POST', |
|||
// headers: { |
|||
// 'Content-Type': 'application/json' |
|||
// }, |
|||
// body: JSON.stringify({ |
|||
// username |
|||
// }) |
|||
// }); |
|||
// if (response.ok) { |
|||
// console.log('username updated'); |
|||
// } |
|||
// else { |
|||
// console.log('username update failed'); |
|||
// } |
|||
alert("Trying to update username to " + username); |
|||
} |
|||
async function handleAvatarUpload(event: Event) { |
|||
event.preventDefault(); |
|||
alert("Trying to upload avatar"); |
|||
} |
|||
async function handle2fa(event: Event) { |
|||
event.preventDefault(); |
|||
alert("Trying to " + (is2faEnabled ? "disable" : "enable") + " 2FA"); |
|||
} |
|||
</script> |
|||
|
|||
<div class="overlay"> |
|||
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div class="profile-header"> |
|||
<img class="profile-img" src="img/profileicon.png" alt="Profile Icon" /> |
|||
<h3>{realname}</h3> |
|||
<form on:submit={handleAvatarUpload}> |
|||
<button type="submit">Upload Avatar</button> |
|||
</form> |
|||
</div> |
|||
<div class="profile-body"> |
|||
<form on:submit={handleSubmit}> |
|||
<div class="username"> |
|||
<label for="username">Username</label> |
|||
<input type="text" id="username" bind:value={username} /> |
|||
</div> |
|||
<button type="submit">Submit</button> |
|||
</form> |
|||
<p>Wins: {wins}</p> |
|||
<p>Losses: {losses}</p> |
|||
<p>Winrate: {(wins / (wins + losses)) * 100}%</p> |
|||
<p>Elo : {elo}</p> |
|||
<p>Rank: {rank}</p> |
|||
<form class="two-factor-auth" on:submit={handle2fa}> |
|||
<button type="submit"> |
|||
{#if is2faEnabled} |
|||
Disable 2FA |
|||
{:else} |
|||
Enable 2FA |
|||
{/if} |
|||
</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div class="profile-header"> |
|||
<img class="profile-img" src="img/profileicon.png" alt="Profile Icon" /> |
|||
<h3>{realname}</h3> |
|||
<form on:submit={handleAvatarUpload}> |
|||
<button type="submit">Upload Avatar</button> |
|||
</form> |
|||
</div> |
|||
<div class="profile-body"> |
|||
<form on:submit={handleSubmit}> |
|||
<div class="username"> |
|||
<label for="username">Username</label> |
|||
<input type="text" id="username" bind:value={username} /> |
|||
</div> |
|||
<button type="submit">Submit</button> |
|||
</form> |
|||
<p>Wins: {wins}</p> |
|||
<p>Losses: {losses}</p> |
|||
<p>Winrate: {(wins / (wins + losses)) * 100}%</p> |
|||
<p>Elo : {elo}</p> |
|||
<p>Rank: {rank}</p> |
|||
<form class="two-factor-auth" on:submit={handle2fa}> |
|||
<button type="submit"> |
|||
{#if is2faEnabled} |
|||
Disable 2FA |
|||
{:else} |
|||
Enable 2FA |
|||
{/if} |
|||
</button> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.profile { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
.profile { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
|
|||
.profile-header { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.profile-header { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.profile-img { |
|||
width: 50px; |
|||
height: 50px; |
|||
margin-right: 1rem; |
|||
} |
|||
.profile-img { |
|||
width: 50px; |
|||
height: 50px; |
|||
margin-right: 1rem; |
|||
} |
|||
|
|||
.two-factor-auth { |
|||
margin-top: 1rem; |
|||
} |
|||
.two-factor-auth { |
|||
margin-top: 1rem; |
|||
} |
|||
</style> |
|||
|
@ -1,53 +1,53 @@ |
|||
<script lang="ts" context="module"> |
|||
export interface SpectateType { |
|||
id: string; |
|||
player1: string; |
|||
player2: string; |
|||
} |
|||
export interface SpectateType { |
|||
id: string; |
|||
player1: string; |
|||
player2: string; |
|||
} |
|||
</script> |
|||
|
|||
<script lang="ts"> |
|||
export let spectate: Array<SpectateType> = []; |
|||
export let watch = () => {}; |
|||
export let spectate: Array<SpectateType> = []; |
|||
export let watch = () => {}; |
|||
</script> |
|||
|
|||
<div class="overlay"> |
|||
<div class="spectate" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if spectate.length > 0} |
|||
<h2>Monkey spectating</h2> |
|||
{#each spectate.slice(0, 10) as _spectate} |
|||
<li> |
|||
<span>{_spectate.player1} VS {_spectate.player2}</span> |
|||
<button on:click={() => watch(_spectate.id)}>Spectate</button> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No matches to spectate</p> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
<div class="spectate" on:click|stopPropagation on:keydown|stopPropagation> |
|||
<div> |
|||
{#if spectate.length > 0} |
|||
<h2>Monkey spectating</h2> |
|||
{#each spectate.slice(0, 10) as _spectate} |
|||
<li> |
|||
<span>{_spectate.player1} VS {_spectate.player2}</span> |
|||
<button on:click={() => watch(_spectate.id)}>Spectate</button> |
|||
</li> |
|||
{/each} |
|||
{:else} |
|||
<p>No matches to spectate</p> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 9998; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.spectate { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
.spectate { |
|||
background-color: #fff; |
|||
border: 1px solid #ccc; |
|||
border-radius: 5px; |
|||
padding: 1rem; |
|||
width: 300px; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,13 @@ |
|||
// vite.config.ts
|
|||
import { defineConfig } from "file:///var/www/html/node_modules/vite/dist/node/index.js"; |
|||
import { svelte } from "file:///var/www/html/node_modules/@sveltejs/vite-plugin-svelte/dist/index.js"; |
|||
var vite_config_default = defineConfig({ |
|||
plugins: [svelte()], |
|||
server: { |
|||
port: 80 |
|||
} |
|||
}); |
|||
export { |
|||
vite_config_default as default |
|||
}; |
|||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvdmFyL3d3dy9odG1sXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvdmFyL3d3dy9odG1sL3ZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy92YXIvd3d3L2h0bWwvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuaW1wb3J0IHsgc3ZlbHRlIH0gZnJvbSAnQHN2ZWx0ZWpzL3ZpdGUtcGx1Z2luLXN2ZWx0ZSdcblxuLy8gaHR0cHM6Ly92aXRlanMuZGV2L2NvbmZpZy9cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gIHBsdWdpbnM6IFtzdmVsdGUoKV0sXG4gIHNlcnZlcjoge1xuICAgIHBvcnQ6IDgwXG4gIH1cbn0pXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXlOLFNBQVMsb0JBQW9CO0FBQ3RQLFNBQVMsY0FBYztBQUd2QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTLENBQUMsT0FBTyxDQUFDO0FBQUEsRUFDbEIsUUFBUTtBQUFBLElBQ04sTUFBTTtBQUFBLEVBQ1I7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
|
Loading…
Reference in new issue