Skip to content

Commit

Permalink
Merge branch 'minigame-roomba'
Browse files Browse the repository at this point in the history
  • Loading branch information
NouCake committed Feb 12, 2024
2 parents 464e29c + c9ae4c2 commit 0dc9dfe
Show file tree
Hide file tree
Showing 20 changed files with 623 additions and 35 deletions.
Binary file modified assets/crown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/default_avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
"build": "tsc && vite build && cp -r assets/ dist/",
"preview": "vite preview"
},
"dependencies": {
"@types/color-convert": "^2.0.3",
"color-convert": "^2.0.1",
"phaser": "^3.70.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2"
Expand Down
2 changes: 1 addition & 1 deletion src/board/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class GameBoard extends Phaser.GameObjects.Container {
}

const coords = GameBoard.getCoordForPos(startPos);
const color = getColorFromUUID(this.aznopoly.room.getPlayerName(uuid));
const color = getColorFromUUID(uuid);
const player = {
gameObject: new Phaser.GameObjects.Rectangle(this.scene, coords.x * this.TILE_SIZE, coords.y * this.TILE_SIZE, PLAYER_SIZE, PLAYER_SIZE, color),
position: startPos,
Expand Down
19 changes: 19 additions & 0 deletions src/debug-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import AzNopolyClient from "./client";
import AzNopolyGame from "./game";
import Room from "./room";

export function mock(aznopoly: AzNopolyGame) {
let game = aznopoly as any;
game._client = {
id: "1111-2222-3333-4444",
sendPacket: () => {},
addEventListener: () => {},
removeEventListener: () => {},
};
game._room = {
connectedPlayerIds: ["1111-2222-3333-4444"],
host: "1111-2222-3333-4444",
};
game._name = "mockius maximus";

}
17 changes: 17 additions & 0 deletions src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,27 @@ export default class AzNopolyGame {
return this._room;
}

public get players(): string[] {
return this.room.connectedPlayerIds;
}

public get isHost(): boolean {
return this.client.id == this.room.host;
}

public broadcastPacket(packet: {type: string, data: any}) {
this.client.sendPacket(packet);
}

public addPacketListener(type: string, listener: EventListener) {
this.client.addEventListener(type, listener);
return listener;
}

public removePacketListener(type: string, listener: EventListener) {
this.client.removeEventListener(type, listener);
}

public isPlayerHost(uuid: string) {
return this.room.host == uuid;
}
Expand Down
18 changes: 16 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Phaser from 'phaser';
import TitleScene from './scene/title-scene';
import GameScene from './scene/game-scene';
import BoardScene from './scene/game-scene';
import LobbyScene from './scene/lobby-scene';
import { SimonSaysScene } from './scene/minigame/simon-says-scene';
import AzNopolyGame from './game';
import { RoombaScene } from './scene/minigame/roomba-scene';

export const WIDTH = 1280;
export const HEIGHT = 720;
Expand All @@ -14,6 +15,13 @@ window.onload = () => {
width: WIDTH,
height: HEIGHT,
parent: 'app',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: true
}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
Expand All @@ -24,13 +32,19 @@ window.onload = () => {
});

const aznopoly = new AzNopolyGame();
//await mock(aznopoly);
//console.log("mocked");

game.scene.add('title', new TitleScene(aznopoly));
game.scene.add('lobby', new LobbyScene(aznopoly));
game.scene.add('game', new GameScene(aznopoly));
game.scene.add('game', new BoardScene(aznopoly));

//Minigames
game.scene.add('minigame-simon-says', new SimonSaysScene(aznopoly))
game.scene.add('minigame-roomba', new RoombaScene(aznopoly))

//game.scene.start('minigame-roomba');
game.scene.start('title');

game.scene.start('title');
Object.assign(window, { game });
Expand Down
45 changes: 45 additions & 0 deletions src/minigame/color-progressbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@


export default class ColorProgressBar extends Phaser.GameObjects.Container {

private graphics: Phaser.GameObjects.Graphics;

private colors: Map<number, number> = new Map();
private barWidth: number;
private barHeight: number;

constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number) {
super(scene, x, y);
this.barWidth = width;
this.barHeight = height;

this.graphics = new Phaser.GameObjects.Graphics(scene);
this.drawBar();

this.add(this.graphics);
}

public setColors(colors: Map<number, number>) {
this.colors = colors;
this.drawBar();
}

private drawBar() {
this.graphics.clear();

let progress = 0;
for (const[color, percentage] of this.colors.entries()) {
this.graphics.fillStyle(color);
this.graphics.fillRect(this.barWidth * progress, 0, this.barWidth * percentage, this.barHeight);
progress += percentage;
}
this.graphics.fillStyle(0x000000);
this.graphics.fillRect(this.barWidth * progress, 0, this.barWidth * (1 - progress), this.barHeight);

this.graphics.lineStyle(2, 0xffffff);
this.graphics.strokeRoundedRect(-1, -1, this.barWidth+2, this.barHeight+2, 5);
this.graphics.lineStyle(1, 0x000000);
this.graphics.strokeRoundedRect(-3, -3, this.barWidth+6, this.barHeight+6, 5);
}

}
126 changes: 126 additions & 0 deletions src/minigame/roomba.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { HEIGHT, WIDTH } from "../main";

const SIZE = 85;
const ARROW_COLOR = 0x00ff00;
export interface RoombaConfig {
id: string;
x: number;
y: number;
angle: number;
color: number;
paintColor: number;
speed: number
}


export class Roomba extends Phaser.GameObjects.Container {
static SIZE = SIZE;

private graphics: Phaser.GameObjects.Graphics;
private arrow: Phaser.GameObjects.Graphics;

private lastPaintPosition: Phaser.Math.Vector2;

private color: number;
private paintColor: number;
private speed: number;

public readonly id: string;

constructor(scene: Phaser.Scene, { id, x, y, angle, color, paintColor, speed} : RoombaConfig) {
super(scene, x, y);

this.id = id;
this.color = color;
this.paintColor = paintColor;
this.speed = speed;

this.graphics = new Phaser.GameObjects.Graphics(scene);
this.arrow = new Phaser.GameObjects.Graphics(scene);
this.lastPaintPosition = new Phaser.Math.Vector2(x, y);

this.graphics.fillStyle(this.color);
this.graphics.fillCircle(0, 0, SIZE / 2);
this.graphics.fillCircle(SIZE/5 * 2, 0, SIZE / 4);

scene.physics.world.enable(this);
const body = this.body! as Phaser.Physics.Arcade.Body;
body.setCollideWorldBounds(true)
body.setOffset(-SIZE / 2, -SIZE / 2);
body.setCircle(SIZE/2);

this.add(this.arrow);
this.add(this.graphics);

this.updateDirection(new Phaser.Math.Vector2(Math.cos(angle), Math.sin(angle)));

this.initDragEvents();
}

private initDragEvents() {
this.graphics.setInteractive({
hitArea: new Phaser.Geom.Circle(0, 0, SIZE / 2),
hitAreaCallback: Phaser.Geom.Circle.Contains,
useHandCursor: false,
draggable: true
}, Phaser.Geom.Circle.Contains);

this.graphics.on('dragstart', () => {
this.arrow.visible = true;
});

this.graphics.on('drag', (event: any) => {
const dragOffset = new Phaser.Math.Vector2(event.x - this.x, event.y - this.y);
this.drawArrow(Phaser.Math.Vector2.ZERO, dragOffset);
});

this.graphics.on('dragend', (event: any) => {
const dragOffset = new Phaser.Math.Vector2(event.x - this.x, event.y - this.y);
this.scene.events.emit('roomba-dragged', { id: this.id, offset: dragOffset});
this.drawArrow(new Phaser.Math.Vector2(0, 0), new Phaser.Math.Vector2(0, 0));
this.arrow.visible = false;
});
}

private drawArrow(start: Phaser.Math.Vector2, end: Phaser.Math.Vector2) {
const arrow = this.arrow;
arrow.clear();

arrow.lineStyle(5, ARROW_COLOR);
arrow.fillStyle(ARROW_COLOR);

this.arrow.lineBetween(start.x, start.y, end.x, end.y);

const arrowAngle = Math.atan2(end.y - start.y, end.x - start.x);
const arrowAngleNormal = arrowAngle + Math.PI/2;
const arrowHeadLength = 10;
const arrowHeadWidth = 10;

this.arrow.fillTriangle(
end.x + Math.cos(arrowAngleNormal) * arrowHeadWidth,
end.y + Math.sin(arrowAngleNormal) * arrowHeadWidth,
end.x + Math.cos(arrowAngle) * arrowHeadLength,
end.y + Math.sin(arrowAngle) * arrowHeadLength,
end.x - Math.cos(arrowAngleNormal) * arrowHeadWidth,
end.y - Math.sin(arrowAngleNormal) * arrowHeadWidth,
);
}

public updateDirection(direction: Phaser.Math.Vector2) {
this.graphics.rotation = direction.angle();

const normalized = direction.normalize();
const body = this.body! as Phaser.Physics.Arcade.Body;
body.setVelocity(normalized.x * this.speed, normalized.y * this.speed);
}


public paintPath(graphic: Phaser.GameObjects.Graphics) {
graphic.lineStyle(SIZE, this.paintColor);
graphic.lineBetween(this.lastPaintPosition.x, this.lastPaintPosition.y, this.x, this.y);
graphic.fillStyle(this.paintColor)
graphic.fillCircle(this.x, this.y, SIZE/2);
this.lastPaintPosition = new Phaser.Math.Vector2(this.x, this.y);
}

}
103 changes: 103 additions & 0 deletions src/scene/base-scene-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import AzNopolyGame from "../game";


export default class BaseSceneController {

protected aznopoly: AzNopolyGame;

private _scene: Phaser.Scene;
private packetType: string;

private registeredMethods: string[] = [];

/**
* A proxy object that allows for calling methods on the controller.
* Functions called on the proxy will be sent to all clients and executed asynchonously
* Functions must be registered with registerSyncedMethod
*/
protected syncProxy = new Proxy(this, {
get: (_, prop) => {
return this.onProxyCall(prop);
},
});

constructor(scene: Phaser.Scene, aznopoly: AzNopolyGame) {
this._scene = scene;
this.aznopoly = aznopoly;
this.packetType = "CLIENT_MINIGAME_" + this.constructor.name.toUpperCase();
}

/**
* Should be called when the scene has initialized all of it's state
* and is ready to start receiving packets
*/
public onSceneReady() {
const listener = this.aznopoly.addPacketListener(this.packetType, this.onPacket.bind(this) as EventListener);
this._scene.events.once(Phaser.Scenes.Events.SHUTDOWN, () => this.aznopoly.removePacketListener(this.packetType, listener));
}

/**
* Registers a method to be allowed to be called with syncProxy
* @param method The method to be registered
*/
public registerSyncedMethod(method: Function) {
const name = method.name;
if (this.registeredMethods.includes(name)) {
throw new Error(`Packet executor with name ${name} already exists`);
}
this.registeredMethods.push(name);
}

private isMethodAllowed(method: string) {
return this.registeredMethods.includes(method);
}

private onProxyCall(prop: symbol | string) {
if (!this.isMethodAllowed(String(prop))) {
throw new Error(`This method was not registered for sync: ${String(prop)}`);
}

return (...args: any[]) => {
const packet = this.sendExecPacket(String(prop), ...args);
this.executePacket(packet);
}
}

private onPacket(event: CustomEvent<{type: string, data: any}>) {
const packet = event.detail;
this.executePacket(packet);
}

private sendExecPacket(method: string, ...args: any[]) {
const packet = {
type: this.packetType,
data: {
method,
arguments: args
}
}

this.aznopoly.broadcastPacket(packet);
return packet;
}

private executePacket(packet: {type: string, data: {method: string, arguments: any[]}}) {
if (packet.type !== this.packetType) {
console.error(`Packet type ${packet.type} does not match expected type ${this.packetType}`);
return;
}

if (!this.isMethodAllowed(packet.data.method)) {
console.error(`Method ${packet.data.method} is not allowed`);
return;
}

const method = (this as any)[packet.data.method];
if (typeof method !== "function") {
console.error(`Method ${packet.data.method} is not a function`);
return;
}
(method).apply(this, packet.data.arguments);
}

}
Loading

0 comments on commit 0dc9dfe

Please sign in to comment.