this merge with master should come with the right backend stuff to make friendship requests work

This commit is contained in:
Me
2022-12-09 15:46:40 +01:00
94 changed files with 694 additions and 162 deletions

View File

@@ -1,53 +0,0 @@
@font-face {
font-family: "Bit5x3";
src: url("http://localhost:8080/Bit5x3.woff2") format("woff2"),
url("http://localhost:8080/Bit5x3.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
#preload_font {
font-family: "Bit5x3";
opacity:0;
height:0;
width:0;
display:inline-block;
}
body {
margin: 0;
background-color: #222425;
}
#canvas_container {
text-align: center;
/* border: dashed rgb(245, 245, 245) 5px; */
/* max-height: 80vh; */
/* overflow: hidden; */
}
#div_game_options {
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#div_game_options fieldset {
max-width: 50vw;
width: auto;
margin: 0 auto;
}
#div_game_options fieldset div {
padding: 10px;
}
#play_pong_button {
font-family: "Bit5x3";
color: rgb(245, 245, 245);
background-color: #333333;
font-size: x-large;
padding: 10px;
}
canvas {
background-color: #333333;
max-width: 75vw;
/* max-height: 100vh; */
width: 80%;
}

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="pong.css">
</head>
<body>
<div id="preload_font">.</div>
<div id="div_game_options">
<fieldset>
<legend>game options</legend>
<div>
<input type="checkbox" id="multi_balls" name="multi_balls">
<label for="multi_balls">multiples balls</label>
</div>
<div>
<input type="checkbox" id="moving_walls" name="moving_walls">
<label for="moving_walls">moving walls</label>
</div>
<div>
<label>sound :</label>
<input type="radio" id="sound_on" name="sound_selector" checked>
<label for="sound_on">on</label>
<input type="radio" id="sound_off" name="sound_selector">
<label for="sound_off">off</label>
</div>
<div>
<button id="play_pong_button">PLAY</button>
</div>
</fieldset>
</div>
<div id="canvas_container">
<!-- <p> =) </p> -->
</div>
<script src="http://localhost:8080/js/pong.js" type="module" defer></script>
</body>
</html>

View File

@@ -1,9 +0,0 @@
export * from "../shared_js/constants.js"
// 15ms == 1000/66.666
export const serverGameLoopIntervalMS = 15; // millisecond
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
// 33.333ms == 1000/30
export const clientsUpdateIntervalMS = 1000/30; // millisecond

View File

@@ -1,8 +0,0 @@
export * from "../shared_js/utils.js"
function shortId(id: string): string {
return id.substring(0, id.indexOf("-"));
}
export {shortId}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,8 +2,8 @@
import { WebSocket } from "../wsServer.js";
import { Racket } from "../../shared_js/class/Rectangle.js";
import { GameSession } from "./GameSession.js";
import * as ev from "../../shared_js/class/Event.js"
import * as en from "../../shared_js/enums.js"
import * as ev from "./Event.js"
import * as en from "../enums.js"
class Client {
socket: WebSocket;

View File

@@ -0,0 +1,65 @@
import * as c from "../constants.js"
import * as en from "../enums.js"
import { VectorInteger } from "./Vector.js";
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
import { random } from "../utils.js";
class GameComponents {
wallTop: Rectangle | MovingRectangle;
wallBottom: Rectangle | MovingRectangle;
playerLeft: Racket;
playerRight: Racket;
ballsArr: Ball[] = [];
constructor(options: en.MatchOptions)
{
const pos = new VectorInteger;
// Rackets
pos.assign(0+c.pw, c.h_mid-c.ph/2);
this.playerLeft = new Racket(pos, c.pw, c.ph, c.racketSpeed);
pos.assign(c.w-c.pw-c.pw, c.h_mid-c.ph/2);
this.playerRight = new Racket(pos, c.pw, c.ph, c.racketSpeed);
// Balls
let ballsCount = 1;
if (options & en.MatchOptions.multiBalls) {
ballsCount = c.multiBallsCount;
}
pos.assign(-c.ballSize, -c.ballSize); // ball out =)
while (this.ballsArr.length < ballsCount) {
this.ballsArr.push(new Ball(pos, c.ballSize, c.ballSpeed, c.ballSpeedIncrease))
}
this.ballsArr.forEach((ball) => {
ball.dir.x = 1;
if (random() > 0.5) {
ball.dir.x *= -1;
}
ball.dir.y = random(0, 0.2);
if (random() > 0.5) {
ball.dir.y *= -1;
}
ball.dir = ball.dir.normalized();
});
// Walls
if (options & en.MatchOptions.movingWalls) {
pos.assign(0, 0);
this.wallTop = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
(<MovingRectangle>this.wallTop).dir.y = -1;
pos.assign(0, c.h-c.wallSize);
this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
(<MovingRectangle>this.wallBottom).dir.y = 1;
}
else {
pos.assign(0, 0);
this.wallTop = new Rectangle(pos, c.w, c.wallSize);
pos.assign(0, c.h-c.wallSize);
this.wallBottom = new Rectangle(pos, c.w, c.wallSize);
}
}
}
export {GameComponents}

View File

@@ -1,6 +1,6 @@
import * as c from "../constants.js"
import * as en from "../../shared_js/enums.js"
import * as en from "../enums.js"
import { GameComponents } from "../../shared_js/class/GameComponents.js";
class GameComponentsServer extends GameComponents {

View File

@@ -1,6 +1,6 @@
import * as en from "../../shared_js/enums.js"
import * as ev from "../../shared_js/class/Event.js"
import * as en from "../enums.js"
import * as ev from "./Event.js"
import * as c from "../constants.js"
import { ClientPlayer } from "./Client";
import { GameComponentsServer } from "./GameComponentsServer.js";

View File

@@ -0,0 +1,35 @@
export * from "../shared_js/constants.js"
// 15ms == 1000/66.666
export const serverGameLoopIntervalMS = 15; // millisecond
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
// 33.333ms == 1000/30
export const clientsUpdateIntervalMS = 1000/30; // millisecond
export const CanvasWidth = 1500;
export const CanvasRatio = 1.66666;
/* ratio 5/3 (1.66) */
export const w = CanvasWidth;
export const h = CanvasWidth / CanvasRatio;
export const w_mid = Math.floor(w/2);
export const h_mid = Math.floor(h/2);
export const pw = Math.floor(w*0.017);
export const ph = pw*6;
export const ballSize = pw;
export const wallSize = Math.floor(w*0.01);
export const racketSpeed = Math.floor(w*0.66); // pixel per second
export const ballSpeed = Math.floor(w*0.66); // pixel per second
export const ballSpeedIncrease = Math.floor(ballSpeed*0.05); // pixel per second
export const normalizedSpeed = false; // for consistency in speed independent of direction
export const matchStartDelay = 3000; // millisecond
export const newRoundDelay = 1500; // millisecond
// Game Variantes
export const multiBallsCount = 3;
export const movingWallPosMax = Math.floor(w*0.12);
export const movingWallSpeed = Math.floor(w*0.08);

33
JEU2/src/server/utils.ts Normal file
View File

@@ -0,0 +1,33 @@
import { MovingRectangle } from "./class/Rectangle.js";
export * from "../shared_js/utils.js"
function shortId(id: string): string {
return id.substring(0, id.indexOf("-"));
}
export {shortId}
function random(min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
function sleep (ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function clamp(n: number, min: number, max: number) : number
{
if (n < min)
n = min;
else if (n > max)
n = max;
return (n);
}
// Typescript hack, unused
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
return;
}
export {random, sleep, clamp, assertMovingRectangle}

View File

@@ -8,8 +8,8 @@ export class WebSocket extends BaseLibWebSocket {
import { IncomingMessage } from "http";
import { v4 as uuidv4 } from 'uuid';
import * as en from "../shared_js/enums.js"
import * as ev from "../shared_js/class/Event.js"
import * as en from "./enums.js"
import * as ev from "./class/Event.js"
import { Client, ClientPlayer } from "./class/Client.js"
import { GameSession } from "./class/GameSession.js"
import { shortId } from "./utils.js";

6
package-lock.json generated
View File

@@ -1,6 +0,0 @@
{
"name": "group_transcendence",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

View File

@@ -3,9 +3,9 @@ import { FriendshipStatus } from '../entities/friendship.entity';
export class CreateFriendshipDto {
@IsString()
readonly requesterId: string;
readonly requesterUsername: string;
@IsString()
readonly addresseeId: string;
readonly addresseeUsername: string;
@IsEnum(FriendshipStatus)
readonly status: FriendshipStatus;
}

View File

@@ -18,12 +18,12 @@ export class Friendship {
date : Date;
@Column()
@ManyToOne(type => User, user => user.requesterId)
requesterId: string;
@ManyToOne(type => User, user => user.username)
requesterUsername: string;
@Column()
@ManyToOne(type => User, user => user.addresseeId)
addresseeId: string;
@ManyToOne(type => User, user => user.username)
addresseeUsername: string;
@Column({ type: 'enum', enum: FriendshipStatus, default: FriendshipStatus.REQUESTED})
status: FriendshipStatus;

View File

@@ -36,14 +36,8 @@ export class FriendshipController {
@UseGuards(TwoFactorGuard)
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
const user = req.user;
console.log("WHAT IS UP MY GUYS IT IS YOUR BOI THAT IDIOT YOU HATE 22222");
// console.log(`User id: ${user.id}\nFriend id: ${createFriendshipDto.requesterId}\nStatus: ${createFriendshipDto.status}`);
// console.log(`My User id: ${createFriendshipDto.requesterId}`)
console.log(`My User id: ${user.id}`)
console.log(`User id: ${user.id}\nFriend id: ${createFriendshipDto.addresseeId}\nStatus: ${createFriendshipDto.status}`);
if (user.id === +createFriendshipDto.requesterId)
console.log(`User id: ${user.id}\nFriend id: ${createFriendshipDto.requesterUsername}\nStatus: ${createFriendshipDto.status}`);
if (user.username === createFriendshipDto.requesterUsername)
return this.friendshipService.create(createFriendshipDto, user);
return new HttpException('You can\'t request a frienship for another user', HttpStatus.FORBIDDEN);
}
@@ -73,7 +67,7 @@ export class FriendshipController {
@UseGuards(TwoFactorGuard)
findAllBlocked(@Req() req) {
const user = req.user;
return this.friendshipService.findAllBlockedFriends(user.id);
return this.friendshipService.findAllBlockedFriends(user.username);
}
// GET http://transcendance:8080/api/v2/network/pending

View File

@@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { User } from 'src/users/entities/user.entity';
import { Repository } from 'typeorm';
import { CreateFriendshipDto } from './dto/create-friendship.dto';
import { UpdateFriendshipDto } from './dto/update-friendship.dto';
import { Friendship, FriendshipStatus } from './entities/friendship.entity';
@Injectable()
@@ -17,8 +16,8 @@ export class FriendshipService {
) { }
async findOneFriend(friendshipId: string, userId: string) {
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, requesterId: userId, status: FriendshipStatus.ACCEPTED } });
async findOneFriend(friendshipId: string, username: string) {
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, requesterUsername: username, status: FriendshipStatus.ACCEPTED } });
if (!friendship)
throw new HttpException(`The requested friend not found.`, HttpStatus.NOT_FOUND);
return friendship;
@@ -31,12 +30,12 @@ export class FriendshipService {
return friendship;
}
async findAllFriends(userId: string) {
async findAllFriends(username: string) {
const friendship = await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
.andWhere('friendship.addresseeId = :addressee', { addressee: userId })
.orWhere('friendship.requesterId = :requester', { requester: userId })
.andWhere('friendship.addresseeUsername = :addressee', { addressee: username })
.orWhere('friendship.requesterUsername = :requester', { requester: username })
.andWhere('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
.getMany();
for (const friend of friendship)
@@ -44,40 +43,39 @@ export class FriendshipService {
return friendship;
}
async findAllBlockedFriends(userId: string) {
async findAllBlockedFriends(username: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userId })
.orWhere('friendship.addresseeId = :addressee', { addressee: userId })
.where('friendship.requesterUsername = :requestee', { requestee: username })
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
.getMany();
}
async findAllPendantRequestsForFriendship(userId: string) {
async findAllPendantRequestsForFriendship(username: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userId })
.where('friendship.requesterUsername = :requestee', { requestee: username })
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
.getMany();
}
async findAllReceivedRequestsForFriendship(userId: string) {
async findAllReceivedRequestsForFriendship(username: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.addresseeId = :addressee', { addressee: userId })
.where('friendship.addresseeUsername = :addressee', { addressee: username })
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
.getMany();
}
//GROS CHANTIER
async create(createFriendshipDto: CreateFriendshipDto, creator : User) {
const addressee = await this.userRepository.findOneBy({ id: +createFriendshipDto.addresseeId });
const addressee = await this.userRepository.findOneBy({username: createFriendshipDto.requesterUsername});
if (!addressee)
throw new HttpException(`The addressee does not exist.`, HttpStatus.NOT_FOUND);
if (creator.id === addressee.id)
throw new HttpException(`You can't add yourself.`, HttpStatus.FORBIDDEN);
if (createFriendshipDto.status !== FriendshipStatus.REQUESTED && createFriendshipDto.status !== FriendshipStatus.BLOCKED)
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
const friendship = await this.friendshipRepository.findOneBy({ requesterId: createFriendshipDto.requesterId, addresseeId: createFriendshipDto.addresseeId });
const friendship = await this.friendshipRepository.findOneBy({ requesterUsername: createFriendshipDto.requesterUsername, addresseeUsername: createFriendshipDto.addresseeUsername });
if (friendship) {
if (friendship.status && friendship.status === FriendshipStatus.ACCEPTED)
throw new HttpException(`The friendship request has already been accepted.`, HttpStatus.OK);
@@ -96,7 +94,7 @@ export class FriendshipService {
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
if (!relation)
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
if (+relation.requesterId === user.id) {
if (+relation.requesterUsername === user.id) {
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
}
if (status === FriendshipStatus.ACCEPTED)
@@ -122,8 +120,8 @@ export class FriendshipService {
async findIfUserIsBlockedOrHasBlocked(userConnectedId: string, userToCheckId: string) {
const friendship = await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userConnectedId })
.orWhere('friendship.requesterId = :requesteeBis', { requesteeBis: userToCheckId })
.where('friendship.requesterUsername = :requestee', { requestee: userConnectedId })
.orWhere('friendship.requesterUsername = :requesteeBis', { requesteeBis: userToCheckId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
.getOne();
if (friendship)

View File

@@ -0,0 +1,16 @@
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class CreateUsersDto {
@IsString()
@IsNotEmpty()
readonly username: string;
readonly fortyTwoId: string;
@IsEmail()
readonly email: string;
@IsString()
readonly image_url: string;
@IsString()
readonly status: string;
@IsBoolean()
readonly isEnabledTwoFactorAuth: boolean;
}

View File

@@ -0,0 +1,22 @@
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity('gameParty')
export class gameParty {
@PrimaryGeneratedColumn()
id: number;
@Column()
playerOne: string
@Column()
playerTwo: string
@Column()
resultOfTheMatch: string
@Column()
gameServerIdOfTheMatch: string
}

View File

@@ -44,11 +44,11 @@ export class User {
secretTwoFactorAuth: string;
@JoinTable()
@OneToMany(type => Friendship , (friendship) => friendship.requesterId)
@OneToMany(type => Friendship , (friendship) => friendship.requesterUsername)
requesterId: Friendship[];
@JoinTable()
@OneToMany(type => Friendship , (friendship) => friendship.addresseeId)
@OneToMany(type => Friendship , (friendship) => friendship.addresseeUsername)
addresseeId: Friendship[];
@JoinColumn()

View File

@@ -0,0 +1,107 @@
import * as en from "../enums.js"
/* From Server */
class ServerEvent {
type: en.EventTypes;
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
class EventAssignId extends ServerEvent {
id: string;
constructor(id: string) {
super(en.EventTypes.assignId);
this.id = id;
}
}
class EventMatchmakingComplete extends ServerEvent {
side: en.PlayerSide;
constructor(side: en.PlayerSide) {
super(en.EventTypes.matchmakingComplete);
this.side = side;
}
}
class EventGameUpdate extends ServerEvent {
playerLeft = {
y: 0
};
playerRight = {
y: 0
};
ballsArr: {
x: number,
y: number,
dirX: number,
dirY: number,
speed: number
}[] = [];
wallTop? = {
y: 0
};
wallBottom? = {
y: 0
};
lastInputId = 0;
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
super(en.EventTypes.gameUpdate);
}
}
class EventScoreUpdate extends ServerEvent {
scoreLeft: number;
scoreRight: number;
constructor(scoreLeft: number, scoreRight: number) {
super(en.EventTypes.scoreUpdate);
this.scoreLeft = scoreLeft;
this.scoreRight = scoreRight;
}
}
class EventMatchEnd extends ServerEvent {
winner: en.PlayerSide;
constructor(winner: en.PlayerSide) {
super(en.EventTypes.matchEnd);
this.winner = winner;
}
}
/* From Client */
class ClientEvent {
type: en.EventTypes; // readonly ?
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
class ClientAnnounce extends ClientEvent {
role: en.ClientRole;
clientId: string;
matchOptions: en.MatchOptions;
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
super(en.EventTypes.clientAnnounce);
this.role = role;
this.clientId = clientId;
this.matchOptions = matchOptions;
}
}
class EventInput extends ClientEvent {
input: en.InputEnum;
id: number;
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
super(en.EventTypes.clientInput);
this.input = input;
this.id = id;
}
}
export {
ServerEvent, EventAssignId, EventMatchmakingComplete,
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
ClientEvent, ClientAnnounce, EventInput
}

View File

@@ -0,0 +1,144 @@
import { Vector, VectorInteger } from "./Vector.js";
import { Component, Moving } from "./interface.js";
import * as c from "../constants.js"
class Rectangle implements Component {
pos: VectorInteger;
width: number;
height: number;
constructor(pos: VectorInteger, width: number, height: number) {
this.pos = new VectorInteger(pos.x, pos.y);
this.width = width;
this.height = height;
}
collision(collider: Rectangle): boolean {
const thisLeft = this.pos.x;
const thisRight = this.pos.x + this.width;
const thisTop = this.pos.y;
const thisBottom = this.pos.y + this.height;
const colliderLeft = collider.pos.x;
const colliderRight = collider.pos.x + collider.width;
const colliderTop = collider.pos.y;
const colliderBottom = collider.pos.y + collider.height;
if ((thisBottom < colliderTop)
|| (thisTop > colliderBottom)
|| (thisRight < colliderLeft)
|| (thisLeft > colliderRight)) {
return false;
}
else {
return true;
}
}
}
class MovingRectangle extends Rectangle implements Moving {
dir: Vector = new Vector(0,0);
speed: number;
readonly baseSpeed: number;
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
super(pos, width, height);
this.baseSpeed = baseSpeed;
this.speed = baseSpeed;
}
move(delta: number) { // Math.floor WIP until VectorInteger debug
// console.log(`delta: ${delta}, speed: ${this.speed}, speed*delta: ${this.speed * delta}`);
// this.pos.x += Math.floor(this.dir.x * this.speed * delta);
// this.pos.y += Math.floor(this.dir.y * this.speed * delta);
this.pos.x += this.dir.x * this.speed * delta;
this.pos.y += this.dir.y * this.speed * delta;
}
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
this._moveAndCollideAlgo(delta, colliderArr);
}
protected _moveAndCollideAlgo(delta: number, colliderArr: Rectangle[]) {
let oldPos = new VectorInteger(this.pos.x, this.pos.y);
this.move(delta);
if (colliderArr.some(this.collision, this)) {
this.pos = oldPos;
}
}
}
class Racket extends MovingRectangle {
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
super(pos, width, height, baseSpeed);
}
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
// let oldPos = new VectorInteger(this.pos.x, this.pos.y); // debug
this._moveAndCollideAlgo(delta, colliderArr);
// console.log(`y change: ${this.pos.y - oldPos.y}`);
}
}
class Ball extends MovingRectangle {
readonly speedIncrease: number;
ballInPlay: boolean = false;
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number) {
super(pos, size, size, baseSpeed);
this.speedIncrease = speedIncrease;
}
bounce(collider?: Rectangle) {
this._bounceAlgo(collider);
}
protected _bounceAlgo(collider?: Rectangle) {
/* Could be more generic, but testing only Racket is enough,
because in Pong collider can only be Racket or Wall. */
if (collider instanceof Racket) {
this._bounceRacket(collider);
}
else {
this._bounceWall();
}
}
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
this.move(delta);
let i = colliderArr.findIndex(this.collision, this);
if (i != -1)
{
this.bounce(colliderArr[i]);
this.move(delta);
}
}
protected _bounceWall() { // Should be enough for Wall
this.dir.y = this.dir.y * -1;
}
protected _bounceRacket(racket: Racket) {
this._bounceRacketAlgo(racket);
}
protected _bounceRacketAlgo(racket: Racket) {
this.speed += this.speedIncrease;
let x = this.dir.x * -1;
const angleFactorDegree = 60;
const angleFactor = angleFactorDegree / 90;
const racketHalf = racket.height/2;
const ballMid = this.pos.y + this.height/2;
const racketMid = racket.pos.y + racketHalf;
let impact = ballMid - racketMid;
const horizontalMargin = racketHalf * 0.15;
if (impact < horizontalMargin && impact > -horizontalMargin) {
impact = 0;
}
else if (impact > 0) {
impact = impact - horizontalMargin;
}
else if (impact < 0) {
impact = impact + horizontalMargin;
}
let y = impact / (racketHalf - horizontalMargin) * angleFactor;
this.dir.assign(x, y);
// Normalize Vector (for consistency in speed independent of direction)
if (c.normalizedSpeed) {
this.dir = this.dir.normalized();
}
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
}
}
export {Rectangle, MovingRectangle, Racket, Ball}

View File

@@ -0,0 +1,49 @@
class Vector {
x: number;
y: number;
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
assign(x: number, y: number) {
this.x = x;
this.y = y;
}
normalized() : Vector {
const normalizationFactor = Math.abs(this.x) + Math.abs(this.y);
return new Vector(this.x/normalizationFactor, this.y/normalizationFactor);
}
}
class VectorInteger extends Vector {
// PLACEHOLDER
// VectorInteger with set/get dont work (No draw on the screen). Why ?
}
/*
class VectorInteger {
// private _x: number = 0;
// private _y: number = 0;
// constructor(x: number = 0, y: number = 0) {
// this._x = x;
// this._y = y;
// }
// get x(): number {
// return this._x;
// }
// set x(v: number) {
// // this._x = Math.floor(v);
// this._x = v;
// }
// get y(): number {
// return this._y;
// }
// set y(v: number) {
// // this._y = Math.floor(v);
// this._y = v;
// }
}
*/
export {Vector, VectorInteger}

View File

@@ -0,0 +1,21 @@
import { Vector, VectorInteger } from "./Vector.js";
interface Component {
pos: VectorInteger;
}
interface GraphicComponent extends Component {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
clear: (pos?: VectorInteger) => void;
}
interface Moving {
dir: Vector;
speed: number; // pixel per second
move(delta: number): void;
}
export {Component, GraphicComponent, Moving}

View File

@@ -0,0 +1,47 @@
enum EventTypes {
// Class Implemented
gameUpdate = 1,
scoreUpdate,
matchEnd,
assignId,
matchmakingComplete,
// Generic
matchmakingInProgress,
matchStart,
matchNewRound, // unused
matchPause, // unused
matchResume, // unused
// Client
clientAnnounce,
clientPlayerReady,
clientInput,
}
enum InputEnum {
noInput = 0,
up = 1,
down,
}
enum PlayerSide {
left = 1,
right
}
enum ClientRole {
player = 1,
spectator
}
enum MatchOptions {
// binary flags, can be mixed
noOption = 0b0,
multiBalls = 1 << 0,
movingWalls = 1 << 1
}
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}

View File

@@ -0,0 +1,20 @@
import * as c from "./constants.js";
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
import { GameComponents } from "./class/GameComponents.js";
function wallsMovements(delta: number, gc: GameComponents)
{
const wallTop = <MovingRectangle>gc.wallTop;
const wallBottom = <MovingRectangle>gc.wallBottom;
if (wallTop.pos.y <= 0 || wallTop.pos.y >= c.movingWallPosMax) {
wallTop.dir.y *= -1;
}
if (wallBottom.pos.y >= c.h-c.wallSize || wallBottom.pos.y <= c.h-c.movingWallPosMax) {
wallBottom.dir.y *= -1;
}
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
}
export {wallsMovements}

View File

@@ -0,0 +1,99 @@
<script>
import "public/game/pong.js"
</script>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="preload_font">.</div>
<div id="div_game_options">
<fieldset>
<legend>game options</legend>
<div>
<input type="checkbox" id="multi_balls" name="multi_balls">
<label for="multi_balls">multiples balls</label>
</div>
<div>
<input type="checkbox" id="moving_walls" name="moving_walls">
<label for="moving_walls">moving walls</label>
</div>
<div>
<label>sound :</label>
<input type="radio" id="sound_on" name="sound_selector" checked>
<label for="sound_on">on</label>
<input type="radio" id="sound_off" name="sound_selector">
<label for="sound_off">off</label>
</div>
<div>
<button id="play_pong_button">PLAY</button>
</div>
</fieldset>
</div>
<div id="canvas_container">
<!-- <p> =) </p> -->
</div>
<script src="public/game/pong.js" type="module" defer></script>
</body>
<style>
@font-face {
font-family: "Bit5x3";
src: url("/fonts/Bit5x3.woff2") format("woff2"),local("Bit5x3"), url("/fonts/Bit5x3.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
#preload_font {
font-family: "Bit5x3";
opacity:0;
height:0;
width:0;
display:inline-block;
}
body {
margin: 0;
background-color: #222425;
}
#canvas_container {
text-align: center;
/* border: dashed rgb(245, 245, 245) 5px; */
/* max-height: 80vh; */
/* overflow: hidden; */
}
#div_game_options {
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#div_game_options fieldset {
max-width: 50vw;
width: auto;
margin: 0 auto;
}
#div_game_options fieldset div {
padding: 10px;
}
#play_pong_button {
font-family: "Bit5x3";
color: rgb(245, 245, 245);
background-color: #333333;
font-size: x-large;
padding: 10px;
}
canvas {
background-color: #333333;
max-width: 75vw;
/* max-height: 100vh; */
width: 80%;
}
</style>

View File

@@ -1,6 +1,6 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*"],
"include": ["src/**/*", "public/game"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}
}