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