new file: .DS_Store
new file: CTF/.ipynb_checkpoints/fixed_map_example-checkpoint.png new file: CTF/.ipynb_checkpoints/random_map_example-checkpoint.png new file: CTF/CTF/README.md new file: CTF/CTF/backend/example_init.json new file: CTF/CTF/backend/example_plan_next_actions.json new file: CTF/CTF/backend/lib/game_engine.py new file: CTF/CTF/backend/main.ipynb new file: CTF/CTF/backend/pick_closest_flag.py new file: CTF/CTF/backend/pick_flag_ai.py new file: CTF/CTF/backend/python_tutorial.ipynb new file: CTF/CTF/backend/start.sh new file: CTF/CTF/backend/test_lib.ipynb new file: CTF/CTF/fixed_map_example.png new file: CTF/CTF/frontend/assets/characters.png new file: CTF/CTF/frontend/assets/characters_red_flag.png new file: CTF/CTF/frontend/assets/characters_yellow_flag.png new file: CTF/CTF/frontend/assets/red_flag_16_16.png new file: CTF/CTF/frontend/assets/red_flag_32_32.png new file: CTF/CTF/frontend/assets/tilemap.json new file: CTF/CTF/frontend/assets/tilemap.tmx new file: CTF/CTF/frontend/assets/tiles.png new file: CTF/CTF/frontend/assets/yellow_flag_16_16.png new file: CTF/CTF/frontend/assets/yellow_flag_32_32.png new file: CTF/CTF/frontend/game_config.json new file: CTF/CTF/frontend/index.html new file: CTF/CTF/frontend/phaser.js new file: CTF/CTF/frontend/project.config new file: CTF/CTF/frontend/src/assets.js new file: CTF/CTF/frontend/src/gameObjects/Flag.js new file: CTF/CTF/frontend/src/gameObjects/Player.js new file: CTF/CTF/frontend/src/main.js new file: CTF/CTF/frontend/src/scenes/Boot.js new file: CTF/CTF/frontend/src/scenes/Game.js new file: CTF/CTF/frontend/src/scenes/GameOver.js new file: CTF/CTF/frontend/src/scenes/Preloader.js new file: CTF/CTF/frontend/start.sh new file: CTF/CTF/random_map_example.png new file: CTF/README.md new file: CTF/backend/.ipynb_checkpoints/main-checkpoint.ipynb new file: CTF/backend/.ipynb_checkpoints/pick_closest_flag-checkpoint.py new file: CTF/backend/.ipynb_checkpoints/pick_flag_ai-checkpoint.py new file: CTF/backend/.ipynb_checkpoints/python_tutorial-checkpoint.ipynb new file: CTF/backend/.ipynb_checkpoints/start-checkpoint.sh new file: CTF/backend/.ipynb_checkpoints/test_lib-checkpoint.ipynb new file: CTF/backend/example_init.json new file: CTF/backend/example_plan_next_actions.json new file: CTF/backend/lib/__pycache__/game_engine.cpython-313.pyc new file: CTF/backend/lib/game_engine.py new file: CTF/backend/main.ipynb new file: CTF/backend/pick_closest_flag.py new file: CTF/backend/pick_flag_ai.py new file: CTF/backend/python_tutorial.ipynb new file: CTF/backend/start.sh new file: CTF/backend/test_lib.ipynb new file: CTF/fixed_map_example.png new file: CTF/frontend/.ipynb_checkpoints/game_config-checkpoint.json new file: CTF/frontend/assets/.ipynb_checkpoints/tilemap-checkpoint.json new file: CTF/frontend/assets/characters.png new file: CTF/frontend/assets/characters_red_flag.png new file: CTF/frontend/assets/characters_yellow_flag.png new file: CTF/frontend/assets/red_flag_16_16.png new file: CTF/frontend/assets/red_flag_32_32.png new file: CTF/frontend/assets/tilemap.json new file: CTF/frontend/assets/tilemap.tmx new file: CTF/frontend/assets/tiles.png new file: CTF/frontend/assets/yellow_flag_16_16.png new file: CTF/frontend/assets/yellow_flag_32_32.png new file: CTF/frontend/game_config.json new file: CTF/frontend/index.html new file: CTF/frontend/phaser.js new file: CTF/frontend/project.config new file: CTF/frontend/src/assets.js new file: CTF/frontend/src/gameObjects/Flag.js new file: CTF/frontend/src/gameObjects/Player.js new file: CTF/frontend/src/main.js new file: CTF/frontend/src/scenes/Boot.js new file: CTF/frontend/src/scenes/Game.js new file: CTF/frontend/src/scenes/GameOver.js new file: CTF/frontend/src/scenes/Preloader.js new file: CTF/frontend/start.sh new file: CTF/random_map_example.png
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
export default {
|
||||
// 'audio': {
|
||||
// score: {
|
||||
// key: 'sound',
|
||||
// args: ['assets/sound.mp3', 'assets/sound.m4a', 'assets/sound.ogg']
|
||||
// },
|
||||
// },
|
||||
// 'image': {
|
||||
// spikes: {
|
||||
// key: 'spikes',
|
||||
// args: ['assets/spikes.png']
|
||||
// },
|
||||
// },
|
||||
'image': {
|
||||
red_flag_img: {
|
||||
key: 'red_flag_img',
|
||||
args: ['assets/red_flag_32_32.png']
|
||||
},
|
||||
yellow_flag_img: {
|
||||
key: 'yellow_flag_img',
|
||||
args: ['assets/yellow_flag_32_32.png']
|
||||
},
|
||||
},
|
||||
'spritesheet': {
|
||||
tiles: {
|
||||
key: 'tiles',
|
||||
args: ['assets/tiles.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
characters: {
|
||||
key: 'characters',
|
||||
args: ['assets/characters.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
characters_L_flag: {
|
||||
key: 'characters_L_flag',
|
||||
args: ['assets/characters_yellow_flag.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
characters_R_flag: {
|
||||
key: 'characters_R_flag',
|
||||
args: ['assets/characters_red_flag.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
L_flag: {
|
||||
key: 'L_flag',
|
||||
args: ['assets/red_flag_32_32.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
R_flag: {
|
||||
key: 'R_flag',
|
||||
args: ['assets/yellow_flag_32_32.png', {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
}]
|
||||
},
|
||||
},
|
||||
'tilemapTiledJSON': {
|
||||
map: {
|
||||
key: 'map',
|
||||
args: ['assets/tilemap.json']
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import ASSETS from '../assets.js';
|
||||
|
||||
export default class Flag extends Phaser.Physics.Arcade.Sprite
|
||||
{
|
||||
constructor(scene, x, y, team, canPickup)
|
||||
{
|
||||
if (team == "L") {
|
||||
super(scene, x, y, ASSETS.spritesheet.L_flag.key);
|
||||
} else {
|
||||
super(scene, x, y, ASSETS.spritesheet.R_flag.key);
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
scene.physics.add.existing(this);
|
||||
|
||||
this.team = team;
|
||||
this.mapOffset = scene.getMapOffset();
|
||||
this.posX = x;
|
||||
this.posY = y;
|
||||
this.setPosition(this.mapOffset.x + (x * this.mapOffset.tileSize), this.mapOffset.y + (y * this.mapOffset.tileSize));
|
||||
this.setDepth(90);
|
||||
this.scene = scene;
|
||||
this.canPickup = canPickup;
|
||||
}
|
||||
|
||||
collect() {
|
||||
if (!this.canPickup) {
|
||||
return false;
|
||||
}
|
||||
this.scene.removeFlagItem(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// return player status to send to remote backend
|
||||
getStatus() {
|
||||
return {
|
||||
"canPickup": this.canPickup,
|
||||
"posX": this.posX,
|
||||
"posY": this.posY,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import ASSETS from '../assets.js';
|
||||
|
||||
const PlayerDirection = Object.freeze({
|
||||
UP: "up",
|
||||
DOWN: "down",
|
||||
LEFT: "left",
|
||||
RIGHT: "right",
|
||||
});
|
||||
|
||||
export default class Player extends Phaser.Physics.Arcade.Sprite {
|
||||
moveSpeed = 300; // time in milliseconds to move from one tile to another
|
||||
frameDuration = 0;
|
||||
accumulator = 0;
|
||||
target = { x: 0, y: 0 };
|
||||
|
||||
constructor(scene, name, x, y, team, spriteChoice = 1, useAWSD = true) {
|
||||
super(scene, x, y, ASSETS.spritesheet.characters.key, (spriteChoice - 1) * 12 + 1);
|
||||
|
||||
scene.add.existing(this);
|
||||
scene.physics.add.existing(this);
|
||||
|
||||
this.name = name;
|
||||
this.team = team;
|
||||
this.inPrison = false;
|
||||
this.inPrisonTimeLeft = 0;
|
||||
this.inPrisonDuration = 20000; // time in milliseconds to stay in prison unless a teammate saves the player.
|
||||
this.hasFlag = false;
|
||||
this.spriteChoice = spriteChoice;
|
||||
|
||||
this.mapOffset = scene.getMapOffset();
|
||||
this.target.x = this.mapOffset.x + (x * this.mapOffset.tileSize);
|
||||
this.target.y = this.mapOffset.y + (y * this.mapOffset.tileSize);
|
||||
this.setPosition(this.target.x, this.target.y);
|
||||
this.setCollideWorldBounds(true);
|
||||
this.setDepth(100);
|
||||
this.scene = scene;
|
||||
this.frameDuration = this.moveSpeed / this.mapOffset.tileSize;
|
||||
|
||||
this.remoteControl = null;
|
||||
// key control
|
||||
if (useAWSD) {
|
||||
this.keys = this.scene.awsd_keys;
|
||||
} else {
|
||||
this.keys = this.scene.cursors;
|
||||
}
|
||||
|
||||
this.can_go_next_tile = false; // will go next tile only if this is true
|
||||
}
|
||||
|
||||
collectFlag() {
|
||||
this.hasFlag = true;
|
||||
}
|
||||
|
||||
dropFlag() {
|
||||
this.hasFlag = false;
|
||||
}
|
||||
|
||||
setRemoteControl(remoteControl) {
|
||||
this.remoteControl = remoteControl;
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
this.accumulator += delta;
|
||||
|
||||
while (this.accumulator > this.frameDuration) {
|
||||
this.accumulator -= this.frameDuration;
|
||||
if (this.inPrison) {
|
||||
this.inPrisonTimeLeft -= this.frameDuration;
|
||||
if (this.inPrisonTimeLeft <= 0) {
|
||||
this.inPrison = false;
|
||||
this.inPrisonTimeLeft = 0;
|
||||
}
|
||||
}
|
||||
if (!this.inPrison) {
|
||||
this.checkInput();
|
||||
this.move();
|
||||
} else {
|
||||
this.showStaticImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkInput() {
|
||||
// check if player is at target position
|
||||
if (this.can_go_next_tile && this.target.x === this.x && this.target.y === this.y) {
|
||||
this.can_go_next_tile = false;
|
||||
const moveDirection = { x: 0, y: 0 }; // default move direction
|
||||
|
||||
// Keys take priority over heuristics
|
||||
if (this.keys.left.isDown) moveDirection.x--;
|
||||
else if (this.keys.right.isDown) moveDirection.x++;
|
||||
else if (this.keys.up.isDown) moveDirection.y--;
|
||||
else if (this.keys.down.isDown) moveDirection.y++;
|
||||
else if (this.remoteControl == PlayerDirection.LEFT) moveDirection.x--;
|
||||
else if (this.remoteControl == PlayerDirection.RIGHT) moveDirection.x++;
|
||||
else if (this.remoteControl == PlayerDirection.UP) moveDirection.y--;
|
||||
else if (this.remoteControl == PlayerDirection.DOWN) moveDirection.y++;
|
||||
|
||||
// set next tile coordinates to move towards
|
||||
const nextPosition = {
|
||||
x: this.x + (moveDirection.x * this.mapOffset.tileSize),
|
||||
y: this.y + (moveDirection.y * this.mapOffset.tileSize)
|
||||
};
|
||||
|
||||
// check if next tile to move towards is walkable
|
||||
if (!this.scene.isWall(nextPosition.x, nextPosition.y)) {
|
||||
// set target position to move towards
|
||||
this.target.x = nextPosition.x;
|
||||
this.target.y = nextPosition.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move player towards target position
|
||||
move() {
|
||||
let animation_key = "player" + this.spriteChoice + (this.hasFlag ? "-characters_"+this.team+"_flag-": "-characters-");
|
||||
|
||||
if (this.x < this.target.x) {
|
||||
this.x ++;
|
||||
this.anims.play(animation_key + "right", true);
|
||||
}
|
||||
else if (this.x > this.target.x) {
|
||||
this.x --;
|
||||
this.anims.play(animation_key + "left", true);
|
||||
}
|
||||
if (this.y < this.target.y) {
|
||||
this.y ++;
|
||||
this.anims.play(animation_key + "down", true);
|
||||
}
|
||||
else if (this.y > this.target.y) {
|
||||
this.y --;
|
||||
this.anims.play(animation_key + "up", true);
|
||||
}
|
||||
}
|
||||
|
||||
showStaticImage() {
|
||||
let animation_key = "player" + this.spriteChoice + "-characters-down";
|
||||
this.anims.play(animation_key, true);
|
||||
}
|
||||
|
||||
toPrison(prisonX, prisonY) {
|
||||
this.target.x = this.mapOffset.x + (prisonX * this.mapOffset.tileSize);
|
||||
this.target.y = this.mapOffset.y + (prisonY * this.mapOffset.tileSize);
|
||||
this.setPosition(this.target.x, this.target.y);
|
||||
this.inPrison = true;
|
||||
this.inPrisonTimeLeft = this.inPrisonDuration;
|
||||
}
|
||||
|
||||
hit() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// return player status to send to remote backend
|
||||
getStatus() {
|
||||
return {
|
||||
"name": this.name,
|
||||
"team": this.team,
|
||||
"hasFlag": this.hasFlag,
|
||||
"posX": (this.target.x - this.mapOffset.x) / this.mapOffset.tileSize,
|
||||
"posY": (this.target.y - this.mapOffset.y) / this.mapOffset.tileSize,
|
||||
"inPrison": this.inPrison,
|
||||
"inPrisonTimeLeft": this.inPrisonTimeLeft,
|
||||
"inPrisonDuration": this.inPrisonDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Boot } from './scenes/Boot.js';
|
||||
import { Preloader } from './scenes/Preloader.js';
|
||||
import { Game } from './scenes/Game.js';
|
||||
import { GameOver } from './scenes/GameOver.js';
|
||||
|
||||
// Find out more information about the Game Config at:
|
||||
// https://newdocs.phaser.io/docs/3.70.0/Phaser.Types.Core.GameConfig
|
||||
const config = {
|
||||
type: Phaser.AUTO,
|
||||
// REMEMBER to keep consistent with tilemap.json
|
||||
width: (20 + 10) * 32,
|
||||
height: (20 + 10) * 32,
|
||||
parent: 'game-container',
|
||||
backgroundColor: '#2d3436',
|
||||
scale: {
|
||||
mode: Phaser.Scale.FIT,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH,
|
||||
},
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: {
|
||||
debug: false,
|
||||
gravity: { y: 0 }
|
||||
}
|
||||
},
|
||||
scene: [
|
||||
Boot,
|
||||
Preloader,
|
||||
Game,
|
||||
GameOver
|
||||
]
|
||||
};
|
||||
|
||||
new Phaser.Game(config);
|
||||
@@ -0,0 +1,16 @@
|
||||
export class Boot extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('Boot');
|
||||
}
|
||||
|
||||
preload() {
|
||||
// The Boot Scene is typically used to load in any assets you require for your Preloader, such as a game logo or background.
|
||||
// The smaller the file size of the assets, the better, as the Boot Scene itself has no preloader.
|
||||
|
||||
// this.load.image('background', 'assets/background.png');
|
||||
}
|
||||
|
||||
create() {
|
||||
this.scene.start('Preloader');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,821 @@
|
||||
/*
|
||||
* Asset from: https://kenney.nl/assets/pixel-platformer
|
||||
*/
|
||||
import ASSETS from '../assets.js';
|
||||
import Player from '../gameObjects/Player.js';
|
||||
import PlayerDirection from '../gameObjects/Player.js';
|
||||
import Flag from '../gameObjects/Flag.js';
|
||||
|
||||
export class Game extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('Game');
|
||||
}
|
||||
|
||||
create() {
|
||||
// do nothing
|
||||
// we only do in create_after_preload()
|
||||
}
|
||||
|
||||
create_after_preload() {
|
||||
// constant
|
||||
|
||||
// this.NUM_PLAYERS = 3;
|
||||
// this.NUM_FLAGS = 9;
|
||||
// this.useRandomFlags = false;
|
||||
|
||||
this.NUM_OBSTACLES_1 = 8;
|
||||
this.NUM_OBSTACLES_2 = 4;
|
||||
this.stageSent = false;
|
||||
|
||||
this.initVariables();
|
||||
this.initGameUi();
|
||||
this.initAnimations();
|
||||
this.initInput();
|
||||
this.initMap();
|
||||
this.initBoundary();
|
||||
this.initTeams();
|
||||
this.initPhysics();
|
||||
}
|
||||
|
||||
startOrPauseOrContinue() {
|
||||
if (!this.gameStarted) {
|
||||
this.startGame();
|
||||
} else {
|
||||
this.gamePaused = !this.gamePaused;
|
||||
}
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
if (!this.gameStarted || this.gamePaused) return;
|
||||
// move players
|
||||
let players_ready = 0, total_players = 0;
|
||||
|
||||
let tick = (/** @type {string} */ can_go_next_tile) => {
|
||||
this.lteamPlayers.getChildren().forEach( player => {
|
||||
++total_players;
|
||||
player.can_go_next_tile |= can_go_next_tile;
|
||||
player.update(time, delta);
|
||||
if(player.x == player.target.x && player.y == player.target.y)
|
||||
++players_ready;
|
||||
});
|
||||
this.rteamPlayers.getChildren().forEach( player => {
|
||||
++total_players;
|
||||
player.can_go_next_tile |= can_go_next_tile;
|
||||
player.update(time, delta);
|
||||
if(player.x == player.target.x && player.y == player.target.y)
|
||||
++players_ready;
|
||||
});
|
||||
};
|
||||
tick(false);
|
||||
|
||||
if(players_ready !== total_players) {
|
||||
this.stageSent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tick(true);
|
||||
|
||||
if (this.stageSent && time - this.lastSendTime < 600)
|
||||
return;
|
||||
setTimeout(() => {
|
||||
this.stageSent = true;
|
||||
|
||||
// notify server backend
|
||||
let lteamPlayerStatus = this.lteamPlayers.getChildren().map( player => player.getStatus() );
|
||||
let lteamFlagStatus = this.lteamFlags.getChildren().map( flag => flag.getStatus() );
|
||||
let rteamPlayerStatus = this.rteamPlayers.getChildren().map( player => player.getStatus() );
|
||||
let rteamFlagStatus = this.rteamFlags.getChildren().map( flag => flag.getStatus() );
|
||||
|
||||
// each team gets its own perspective
|
||||
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
|
||||
const lteamPlayerStatus = this.lteamPlayers.getChildren().map( player => player.getStatus() );
|
||||
const payload = {
|
||||
action: "status",
|
||||
|
||||
time: time,
|
||||
myteamPlayer: lteamPlayerStatus,
|
||||
myteamFlag: lteamFlagStatus,
|
||||
myteamScore: this.lteamState.score,
|
||||
|
||||
opponentPlayer: rteamPlayerStatus,
|
||||
opponentFlag: rteamFlagStatus,
|
||||
opponentScore: this.rteamState.score,
|
||||
}
|
||||
this.lteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
|
||||
const payload = {
|
||||
action: "status",
|
||||
|
||||
time: time,
|
||||
myteamPlayer: rteamPlayerStatus,
|
||||
myteamFlag: rteamFlagStatus,
|
||||
myteamScore: this.rteamState.score,
|
||||
|
||||
opponentPlayer: lteamPlayerStatus,
|
||||
opponentFlag: lteamFlagStatus,
|
||||
opponentScore: this.lteamState.score,
|
||||
}
|
||||
this.rteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
this.lastSendTime = time;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
async preload() {
|
||||
// load team info from JSON file
|
||||
const resp = await fetch("game_config.json");
|
||||
const data = await resp.json();
|
||||
this.team_config = data.teams;
|
||||
this.team_servers = data.servers;
|
||||
this.initSockets();
|
||||
this.NUM_PLAYERS = data.setup.numPlayers;
|
||||
this.NUM_FLAGS = data.setup.numFlags;
|
||||
this.useRandomFlags = data.setup.useRandomFlags;
|
||||
this.create_after_preload();
|
||||
}
|
||||
|
||||
updatePlayerInfo(teamName, data) {
|
||||
try {
|
||||
const actions = JSON.parse(data);
|
||||
Object.keys(actions.players).forEach (p => {
|
||||
let d = actions.players[p];
|
||||
console.assert(p.startsWith(teamName), `Invalid operation to control player ${p} for team ${teamName}`);
|
||||
console.assert(d == "up" || d == "down" || d == "left" || d == "right",
|
||||
`Invalid operation to move player to direction ${d}`);
|
||||
});
|
||||
let teamPlayers = null;
|
||||
if (teamName === "L") {
|
||||
teamPlayers = this.lteamPlayers.getChildren();
|
||||
} else if (teamName === "R") {
|
||||
teamPlayers = this.rteamPlayers.getChildren();
|
||||
}
|
||||
// For each team player, we will set its direction.
|
||||
teamPlayers.forEach ( player => {
|
||||
let remoteControl = actions.players[player.name];
|
||||
player.setRemoteControl(remoteControl);
|
||||
})
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON from backend:", e, data);
|
||||
}
|
||||
}
|
||||
|
||||
initSockets() {
|
||||
this.lteamSocket = null;
|
||||
this.rteamSocket = null;
|
||||
let lTeamWho = "-";
|
||||
let rTeamWho = "-"
|
||||
// connect to LTeam and RTeam backends
|
||||
for (let i = 0; i < this.team_config.length; ++i) {
|
||||
const team = this.team_config[i];
|
||||
if (team.name != "L" && team.name != "R") {
|
||||
console.log(`Unknown team ${team.name} found in remote_config.json. Skip.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (team["ws_url"] == null && (team["who"] == null || this.team_servers[team.who] == null)) {
|
||||
console.log(`Unknown server ${team["who"]} for ${team.name} found in remote_config.json. Skip.`);
|
||||
continue;
|
||||
}
|
||||
if (team["who"] != null) {
|
||||
if (team.name == "L") {
|
||||
lTeamWho = team.who;
|
||||
} else {
|
||||
rTeamWho = team.who;
|
||||
}
|
||||
}
|
||||
|
||||
let ws = team["ws_url"] == null ? new WebSocket(this.team_servers[team.who]) : new WebSocket(team.ws_url);
|
||||
ws.onopen = () => console.log(`${team.name} connected`);
|
||||
ws.onmessage = (msg) => this.updatePlayerInfo(team.name, msg.data);
|
||||
ws.onerror = (err) => console.error("WebSocket error", err);
|
||||
if (team.name === "L") {
|
||||
this.lteamSocket = ws;
|
||||
}
|
||||
else if (team.name === "R") {
|
||||
this.rteamSocket = ws;
|
||||
}
|
||||
};
|
||||
this.lastSendTime = 0;
|
||||
|
||||
this.lTeamWhoText = this.add.text(30, 60, `${lTeamWho}`, {
|
||||
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8, align: 'left'
|
||||
})
|
||||
.setDepth(100);
|
||||
this.rTeamWhoText = this.add.text(this.scale.width - 450, 60, `${rTeamWho}`, {
|
||||
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8, align: 'right'
|
||||
})
|
||||
.setDepth(100);
|
||||
}
|
||||
|
||||
initVariables() {
|
||||
this.gameStarted = false;
|
||||
this.gamePaused = false;
|
||||
this.centerX = this.scale.width * 0.5;
|
||||
this.centerY = this.scale.height * 0.5;
|
||||
|
||||
this.tileSize = 32; // width and height of a tile in pixels
|
||||
this.halfTileSize = this.tileSize * 0.5; // width and height of a tile in pixels
|
||||
|
||||
this.mapHeight = (this.scale.height / this.tileSize) - 5 * 2; // height of the tile map (in tiles)
|
||||
this.mapWidth = (this.scale.width / this.tileSize) - 5 * 2; // width of the tile map (in tiles)
|
||||
this.mapX = this.centerX - (this.mapWidth * this.tileSize * 0.5); // x position of the top-left corner of the tile map
|
||||
this.mapY = this.centerY - (this.mapHeight * this.tileSize * 0.5); // y position of the top-left corner of the tile map
|
||||
|
||||
// used to generate random background image
|
||||
this.backgroundTiles = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 44 ];
|
||||
this.targetTiles = [13, 14, 15, 25, 26, 27, 37, 38, 39];
|
||||
this.prisonTiles = [97, 98, 99, 109, 110, 111, 121, 122, 123];
|
||||
this.wallTiles = [45, 46, 47, 57, 59, 69, 70, 71];
|
||||
this.tree1Tiles = [6, 18, 30, 29, 28];
|
||||
this.tree2Tiles = [[4, 16], [5, 17]];
|
||||
|
||||
// generate walls
|
||||
this.walls = [
|
||||
{x: 0, y: 0, tileId: 45}, {x: this.mapWidth -1, y: 0, tileId: 47}, {x: 0, y: this.mapHeight - 1, tileId:69}, {x: this.mapWidth - 1, y: this.mapHeight - 1, tileId: 71}
|
||||
].concat(
|
||||
Array.from({ length: this.mapWidth - 2 }, (_, i) => ({ x: i + 1, y: 0, tileId: 46 }))
|
||||
).concat(
|
||||
Array.from({ length: this.mapWidth - 2 }, (_, i) => ({ x: i + 1, y: this.mapHeight - 1, tileId: 46 }))
|
||||
).concat(
|
||||
Array.from({ length: this.mapHeight - 2 }, (_, i) => ({ x: 0, y: i + 1, tileId: 57 }))
|
||||
).concat(
|
||||
Array.from({ length: this.mapHeight - 2 }, (_, i) => ({ x: this.mapWidth - 1, y: i + 1, tileId: 59 }))
|
||||
);
|
||||
|
||||
function notContains(xyArrays, x, y) {
|
||||
const ret = xyArrays.find(obj => (obj.x === x && obj.y === y));
|
||||
return ret == null;
|
||||
}
|
||||
|
||||
// generate obstacles
|
||||
this.obstacles1 = [];
|
||||
for (let i = 0; i < this.NUM_OBSTACLES_1; ++i) {
|
||||
while (true) {
|
||||
const x = Phaser.Math.RND.integerInRange(4, this.mapWidth - 5);
|
||||
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 2);
|
||||
if (notContains(this.obstacles1, x, y)) {
|
||||
this.obstacles1.push({x: x, y: y});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.obstacles2 = [];
|
||||
for (let i = 0; i< this.NUM_OBSTACLES_2; ++i) {
|
||||
while (true) {
|
||||
const x = Phaser.Math.RND.integerInRange(4, this.mapWidth - 5);
|
||||
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
|
||||
if (notContains(this.obstacles1, x, y)
|
||||
&& notContains(this.obstacles1, x, y + 1)
|
||||
&& notContains(this.obstacles2, x, y - 1)
|
||||
&& notContains(this.obstacles2, x, y)) {
|
||||
this.obstacles2.push({x: x, y: y});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Randomly generate flags
|
||||
let lFlags = [];
|
||||
for (let i = 0; i< this.NUM_FLAGS; ++i) {
|
||||
while (true) {
|
||||
const x = Phaser.Math.RND.integerInRange(2, this.mapWidth / 2 - 1);
|
||||
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
|
||||
if (notContains(this.obstacles1, x, y)
|
||||
&& notContains(this.obstacles2, x, y - 1)
|
||||
&& notContains(this.obstacles2, x, y)
|
||||
&& notContains(lFlags, x, y)) {
|
||||
lFlags.push({x: x, y: y});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let rFlags = [];
|
||||
for (let i = 0; i< this.NUM_FLAGS; ++i) {
|
||||
while (true) {
|
||||
const x = Phaser.Math.RND.integerInRange(this.mapWidth / 2, this.mapWidth - 2);
|
||||
const y = Phaser.Math.RND.integerInRange(1, this.mapHeight - 3);
|
||||
if (notContains(this.obstacles1, x, y)
|
||||
&& notContains(this.obstacles2, x, y - 1)
|
||||
&& notContains(this.obstacles2, x, y)
|
||||
&& notContains(rFlags, x, y)) {
|
||||
|
||||
rFlags.push({x: x, y: y});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate flag and player position for LTeam and RTeam
|
||||
// left team
|
||||
this.lteamState = {
|
||||
score: 0,
|
||||
player_sprite_choice: 1,
|
||||
flags: this.useRandomFlags ? lFlags : Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: 1, y: i + 1})),
|
||||
players: this.useRandomFlags? Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: 1, y: i + 1, name: "L" + i})) : Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: 2, y: i + 1, name: "L" + i})),
|
||||
target: this.create3x3grid(2, this.mapHeight / 2),
|
||||
prison: this.create3x3grid(2, this.mapHeight - 3),
|
||||
};
|
||||
|
||||
// right team
|
||||
this.rteamState = {
|
||||
score: 0,
|
||||
player_sprite_choice: 4,
|
||||
// flags: Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1})),
|
||||
flags: this.useRandomFlags ? rFlags : Array.from({ length: this.NUM_FLAGS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1})),
|
||||
players: this.useRandomFlags ? Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: this.mapWidth - 2, y: i + 1, name: "R" + i})) : Array.from({ length: this.NUM_PLAYERS }, (_, i) => ({ x: this.mapWidth - 3, y: i + 1, name: "R" + i})),
|
||||
target: this.create3x3grid(this.mapWidth - 3, this.mapHeight / 2),
|
||||
prison: this.create3x3grid(this.mapWidth - 3, this.mapHeight - 3),
|
||||
};
|
||||
|
||||
this.map; // rference to tile map
|
||||
this.groundLayer; // used to create background layer of tile map
|
||||
this.levelLayer; // reference to level layer of tile map
|
||||
}
|
||||
|
||||
initGameUi() {
|
||||
// Create tutorial text
|
||||
this.tutorialText = this.add.text(this.centerX, this.centerY, 'Arrow keys to move!\nPress Spacebar to Start', {
|
||||
fontFamily: 'Arial Black', fontSize: 48, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8,
|
||||
align: 'center'
|
||||
})
|
||||
.setOrigin(0.5)
|
||||
.setDepth(100);
|
||||
|
||||
// Create score text
|
||||
this.lScoreText = this.add.text(30, 20, 'LTeam #Flags: 0', {
|
||||
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8, align: 'left'
|
||||
})
|
||||
.setDepth(100);
|
||||
this.rScoreText = this.add.text(this.scale.width - 450, 20, 'RTeam #Flags: 0', {
|
||||
fontFamily: 'Arial Black', fontSize: 36, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8, align: 'right'
|
||||
})
|
||||
.setDepth(100);
|
||||
|
||||
// Create game over text
|
||||
this.gameOverText = this.add.text(this.scale.width * 0.5, this.scale.height * 0.5, 'Game Over', {
|
||||
fontFamily: 'Arial Black', fontSize: 64, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8,
|
||||
align: 'center'
|
||||
})
|
||||
.setOrigin(0.5)
|
||||
.setDepth(100)
|
||||
.setVisible(false);
|
||||
}
|
||||
|
||||
initAnimations() {
|
||||
const flag_choices = ["characters", "characters_L_flag", "characters_R_flag"];
|
||||
const dir_choices = ["left", "down", "up", "right"]
|
||||
|
||||
for (let k = 0; k < 3; ++k) {
|
||||
for (let i = 1; i <= 6; ++i) {
|
||||
for (let j = 0; j < 4; ++j) {
|
||||
const key = "player" + i + "-" + flag_choices[k] + "-" + dir_choices[j];
|
||||
const config = { frames: [(i - 1) * 12 + j, (i - 1) * 12 + j + 4, (i - 1) * 12 + j + 8] };
|
||||
this.anims.create({
|
||||
key: key,
|
||||
frames: this.anims.generateFrameNumbers(flag_choices[k], config),
|
||||
frameRate: 10,
|
||||
repeat: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initPhysics() {
|
||||
this.physics.add.overlap(this.lteamPlayers, this.rteamPlayers, this.hitPlayer, null, this);
|
||||
|
||||
this.physics.add.overlap(this.lteamPlayers, this.rteamFlags, this.collectFlag, null, this);
|
||||
this.physics.add.overlap(this.rteamPlayers, this.lteamFlags, this.collectFlag, null, this);
|
||||
|
||||
this.physics.add.overlap(this.lteamPlayers, this.lteamTargetZone, this.dropFlag, null, this);
|
||||
this.physics.add.overlap(this.rteamPlayers, this.rteamTargetZone, this.dropFlag, null, this);
|
||||
|
||||
this.physics.add.overlap(this.lteamPlayers, this.lteamPrisonZone, this.freePlayer, null, this);
|
||||
this.physics.add.overlap(this.rteamPlayers, this.rteamPrisonZone, this.freePlayer, null, this);
|
||||
}
|
||||
|
||||
initTeams() {
|
||||
// init L Team
|
||||
this.lteamFlags = this.add.group();
|
||||
this.lteamPlayers = this.add.group();
|
||||
|
||||
this.lteamState.flags.forEach( flag => {
|
||||
const flagObj = new Flag(this, flag.x, flag.y, "L", true);
|
||||
this.lteamFlags.add(flagObj);
|
||||
});
|
||||
this.lteamState.players.forEach( player => {
|
||||
const playerObj = new Player(this, player.name, player.x, player.y, "L", this.lteamState.player_sprite_choice, true);
|
||||
this.lteamPlayers.add(playerObj);
|
||||
});
|
||||
// this.lteamTargetZone = this.add.zone(this.lteamState.target[0].x * this.tileSize, this.lteamState.target[0].y * this.tileSize, 3 * this.tileSize, 3 * this.tileSize);
|
||||
this.lteamTargetZone = this.add.zone(
|
||||
this.mapX + (this.lteamState.target[0].x * this.tileSize + 1.5 * this.tileSize),
|
||||
this.mapY + (this.lteamState.target[0].y * this.tileSize + 1.5 * this.tileSize),
|
||||
3 * this.tileSize, 3 * this.tileSize);
|
||||
this.physics.add.existing(this.lteamTargetZone);
|
||||
this.lteamTargetZone.body.setAllowGravity(false);
|
||||
this.lteamTargetZone.body.setImmovable(true);
|
||||
|
||||
// this.lteamPrisonZone = this.add.zone(this.lteamState.prison[0].x * this.tileSize, this.lteamState.prison[0].y * this.tileSize, 3 * this.tileSize, 3 * this.tileSize);
|
||||
this.lteamPrisonZone = this.add.zone(
|
||||
this.mapX + (this.lteamState.prison[0].x * this.tileSize + 1.5 * this.tileSize),
|
||||
this.mapY + (this.lteamState.prison[0].y * this.tileSize + 1.5 * this.tileSize),
|
||||
3 * this.tileSize, 3 * this.tileSize);
|
||||
this.physics.add.existing(this.lteamPrisonZone);
|
||||
this.lteamPrisonZone.body.setAllowGravity(false);
|
||||
this.lteamPrisonZone.body.setImmovable(true);
|
||||
|
||||
// init R Team
|
||||
this.rteamFlags = this.add.group();
|
||||
this.rteamPlayers = this.add.group();
|
||||
|
||||
this.rteamState.flags.forEach( flag => {
|
||||
const flagObj = new Flag(this, flag.x, flag.y, "R", true);
|
||||
this.rteamFlags.add(flagObj);
|
||||
});
|
||||
this.rteamState.players.forEach( player => {
|
||||
const playerObj = new Player(this, player.name, player.x, player.y, "R", this.rteamState.player_sprite_choice, false);
|
||||
this.rteamPlayers.add(playerObj);
|
||||
});
|
||||
|
||||
this.rteamTargetZone = this.add.zone(
|
||||
this.mapX + (this.rteamState.target[0].x * this.tileSize + 1.5 * this.tileSize),
|
||||
this.mapY + (this.rteamState.target[0].y * this.tileSize + 1.5 * this.tileSize),
|
||||
3 * this.tileSize, 3 * this.tileSize);
|
||||
this.physics.add.existing(this.rteamTargetZone);
|
||||
this.rteamTargetZone.body.setAllowGravity(false);
|
||||
this.rteamTargetZone.body.setImmovable(true);
|
||||
|
||||
this.rteamPrisonZone = this.add.zone(
|
||||
this.mapX + (this.rteamState.prison[0].x * this.tileSize + 1.5 * this.tileSize),
|
||||
this.mapY + (this.rteamState.prison[0].y * this.tileSize + 1.5 * this.tileSize),
|
||||
3 * this.tileSize, 3 * this.tileSize);
|
||||
this.physics.add.existing(this.rteamPrisonZone);
|
||||
this.rteamPrisonZone.body.setAllowGravity(false);
|
||||
this.rteamPrisonZone.body.setImmovable(true);
|
||||
}
|
||||
|
||||
initInput() {
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
this.awsd_keys = this.input.keyboard.addKeys({
|
||||
up: Phaser.Input.Keyboard.KeyCodes.W,
|
||||
left: Phaser.Input.Keyboard.KeyCodes.A,
|
||||
down: Phaser.Input.Keyboard.KeyCodes.S,
|
||||
right: Phaser.Input.Keyboard.KeyCodes.D
|
||||
});
|
||||
|
||||
// check for spacebar press only once
|
||||
// this.cursors.space.once('down', (key, event) => {
|
||||
// this.startGame();
|
||||
// });
|
||||
|
||||
this.cursors.space.on('down', (key, event) => {
|
||||
this.startOrPauseOrContinue();
|
||||
});
|
||||
}
|
||||
|
||||
// create tile map data
|
||||
initMap() {
|
||||
this.map = this.make.tilemap({ key: ASSETS.tilemapTiledJSON.map.key });
|
||||
const tileset = this.map.addTilesetImage(ASSETS.spritesheet.tiles.key);
|
||||
|
||||
// create background layer, randomly pick the tiles
|
||||
this.groundLayer = this.map.createBlankLayer('ground', tileset, this.mapX, this.mapY);
|
||||
for (let y = 0; y < this.mapHeight; y++) {
|
||||
for (let x = 0; x < this.mapWidth; x++) {
|
||||
// randomly choose a tile id from this.tiles
|
||||
// weightedPick favours items earlier in the array
|
||||
const tileIndex = Phaser.Math.RND.pick(this.backgroundTiles);
|
||||
this.groundLayer.putTileAt(tileIndex, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// create level layer to show game level elements
|
||||
this.levelLayer = this.map.createBlankLayer('level', tileset, this.mapX, this.mapY);
|
||||
this.levelLayer.fill(0, 0, 0, this.mapWidth, this.mapHeight);
|
||||
// show prison tiles
|
||||
for (let i = 0; i < this.lteamState.prison.length; ++i) {
|
||||
const prison = this.lteamState.prison[i];
|
||||
const tile = this.levelLayer.getTileAt(prison.x, prison.y);
|
||||
tile.index = this.prisonTiles[i];
|
||||
}
|
||||
for (let i = 0; i < this.rteamState.prison.length; ++i) {
|
||||
const prison = this.rteamState.prison[i];
|
||||
const tile = this.levelLayer.getTileAt(prison.x, prison.y);
|
||||
tile.index = this.prisonTiles[i];
|
||||
}
|
||||
for (let i = 0; i < this.lteamState.target.length; ++i) {
|
||||
const target = this.lteamState.target[i];
|
||||
const tile = this.levelLayer.getTileAt(target.x, target.y);
|
||||
tile.index = this.targetTiles[i];
|
||||
}
|
||||
for (let i = 0; i < this.rteamState.target.length; ++i) {
|
||||
const target = this.rteamState.target[i];
|
||||
const tile = this.levelLayer.getTileAt(target.x, target.y);
|
||||
tile.index = this.targetTiles[i];
|
||||
}
|
||||
|
||||
// create wall
|
||||
for (let i = 0; i < this.walls.length; ++i) {
|
||||
const wall = this.walls[i];
|
||||
const tile = this.levelLayer.getTileAt(wall.x, wall.y);
|
||||
tile.index = wall.tileId;
|
||||
const collisionId = (wall.x - 1) * this.mapWidth + wall.y;
|
||||
this.map.setCollision(collisionId);
|
||||
}
|
||||
|
||||
// create obstacles
|
||||
for (let i = 0; i < this.obstacles1.length; ++i) {
|
||||
const obs = this.obstacles1[i];
|
||||
const tile = this.levelLayer.getTileAt(obs.x, obs.y);
|
||||
tile.index = Phaser.Math.RND.pick(this.tree1Tiles);
|
||||
const collisionId = (obs.x - 1) * this.mapWidth + obs.y;
|
||||
this.map.setCollision(collisionId);
|
||||
}
|
||||
for (let i = 0; i < this.obstacles2.length; ++i) {
|
||||
const obs = this.obstacles2[i];
|
||||
const treeTile = Phaser.Math.RND.pick(this.tree2Tiles);
|
||||
const tile1 = this.levelLayer.getTileAt(obs.x, obs.y);
|
||||
tile1.index = treeTile[0];
|
||||
const tile2 = this.levelLayer.getTileAt(obs.x, obs.y + 1);
|
||||
tile2.index = treeTile[1];
|
||||
const collisionId = (obs.x - 1) * this.mapWidth + obs.y;
|
||||
this.map.setCollision(collisionId, collisionId + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// create a thin black line in the middle
|
||||
initBoundary() {
|
||||
const startY = this.centerY - this.mapHeight * this.tileSize / 2;
|
||||
const endY = this.centerY + this.mapHeight * this.tileSize / 2;
|
||||
this.add.line(0, 0, this.centerX, startY, this.centerX, endY, 0x000000)
|
||||
.setOrigin(0, 0)
|
||||
.setLineWidth(1);
|
||||
}
|
||||
|
||||
startGame() {
|
||||
this.gameStarted = true;
|
||||
this.tutorialText.setVisible(false);
|
||||
const mapPayload = {
|
||||
"width": this.mapWidth,
|
||||
"height": this.mapHeight,
|
||||
"walls": this.walls.map(w => {return {x: w.x, y: w.y}}),
|
||||
"obstacles": this.obstacles1.concat(this.obstacles2).concat(
|
||||
this.obstacles2.map(w => {return {"x": w.x, "y": w.y + 1}})
|
||||
),
|
||||
};
|
||||
|
||||
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
|
||||
const payload = {
|
||||
"action": "init",
|
||||
"map": mapPayload,
|
||||
"numPlayers": this.NUM_PLAYERS,
|
||||
"numFlags": this.NUM_FLAGS,
|
||||
"myteamName": "L",
|
||||
// this is where you will be sent to, if you were caught by opponent
|
||||
"myteamPrison": this.lteamState.prison,
|
||||
// this is where you will drop the flags
|
||||
"myteamTarget": this.lteamState.target,
|
||||
"opponentPrison": this.rteamState.prison,
|
||||
"opponentTarget": this.rteamState.target,
|
||||
}
|
||||
this.lteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
|
||||
const payload = {
|
||||
"action": "init",
|
||||
"map": mapPayload,
|
||||
"numPlayers": this.NUM_PLAYERS,
|
||||
"numFlags": this.NUM_FLAGS,
|
||||
"myteamName": "R",
|
||||
// this is where you will send the opponent to prison
|
||||
"myteamPrison": this.rteamState.prison,
|
||||
// this is where you will drop the flags
|
||||
"myteamTarget": this.rteamState.target,
|
||||
"opponentPrison": this.lteamState.prison,
|
||||
"opponentTarget": this.lteamState.target,
|
||||
}
|
||||
this.rteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
}
|
||||
|
||||
hitPlayer(player1, player2) {
|
||||
// does not matter if player1 is not in the same team as player2
|
||||
if (player1.team === player2.team) {
|
||||
return;
|
||||
}
|
||||
if (player1.inPrison || player2.inPrison) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When collision happens around the center, we use the middle X
|
||||
let playerCenterX = (player1.x + player2.x) / 2;
|
||||
|
||||
// in L team's side
|
||||
if (playerCenterX < this.centerX) {
|
||||
// find the prison tile to send the R team player
|
||||
// If the flag is held by the R team player, drop the flag at where it was caught.
|
||||
const spot = this.findAvailablePrisonTile(this.rteamPlayers.getChildren(), this.rteamState.prison);
|
||||
const caughtPlayer = player1.team === "R" ? player1 : player2;
|
||||
if (caughtPlayer.hasFlag) {
|
||||
const tile = this.getTileAt(caughtPlayer.x, caughtPlayer.y);
|
||||
const flag = new Flag(this, tile.x, tile.y, "L", true);
|
||||
this.lteamFlags.add(flag);
|
||||
caughtPlayer.hasFlag = false;
|
||||
}
|
||||
caughtPlayer.toPrison(spot.x, spot.y);
|
||||
} else {
|
||||
const spot = this.findAvailablePrisonTile(this.lteamPlayers.getChildren(), this.lteamState.prison);
|
||||
const caughtPlayer = player1.team === "L" ? player1 : player2;
|
||||
if (caughtPlayer.hasFlag) {
|
||||
const tile = this.getTileAt(caughtPlayer.x, caughtPlayer.y);
|
||||
const flag = new Flag(this, tile.x, tile.y, "R", true);
|
||||
this.rteamFlags.add(flag);
|
||||
caughtPlayer.hasFlag = false;
|
||||
}
|
||||
caughtPlayer.toPrison(spot.x, spot.y);
|
||||
}
|
||||
}
|
||||
|
||||
findAvailablePrisonTile(players, prisons) {
|
||||
for (let i = 0; i < prisons.length; ++i) {
|
||||
let isAvailable = true;
|
||||
for (let j = 0; j < players.length; ++j) {
|
||||
if (!players[j].inPrison) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.getTileAt(players[j].x, players[j].y);
|
||||
if (tile.x == prisons[i].x && tile.y == prisons[i].y) {
|
||||
isAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAvailable) {
|
||||
return {x: prisons[i].x, y: prisons[i].y};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dropFlag(player) {
|
||||
if (!player.hasFlag) {
|
||||
return;
|
||||
}
|
||||
player.dropFlag();
|
||||
if (player.team == "L") {
|
||||
const spot = this.findAvailableFlagTile(this.rteamFlags.getChildren(), this.lteamState.target);
|
||||
const flag = new Flag(this, spot.x, spot.y, "R", false);
|
||||
this.rteamFlags.add(flag);
|
||||
this.updateTeamScore("L");
|
||||
} else {
|
||||
const spot = this.findAvailableFlagTile(this.lteamFlags.getChildren(), this.rteamState.target);
|
||||
const flag = new Flag(this, spot.x, spot.y, "L", false);
|
||||
this.lteamFlags.add(flag);
|
||||
this.updateTeamScore("R");
|
||||
}
|
||||
}
|
||||
|
||||
findAvailableFlagTile(flags, targets) {
|
||||
for (let i = 0; i < targets.length; ++i) {
|
||||
let isAvailable = true;
|
||||
for (let j = 0; j < flags.length; ++j) {
|
||||
if (flags[j].canPickup) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.getTileAt(flags[j].x, flags[j].y);
|
||||
if (tile.x == targets[i].x && tile.y == targets[i].y) {
|
||||
isAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAvailable) {
|
||||
return {x: targets[i].x, y: targets[i].y};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectFlag(player, flag) {
|
||||
// cannot collect flag from my team
|
||||
if (player.team == flag.team) {
|
||||
return;
|
||||
}
|
||||
if (player.inPrison) {
|
||||
return;
|
||||
}
|
||||
// a player cannot collect >1 flag
|
||||
if (player.hasFlag) {
|
||||
return;
|
||||
}
|
||||
// cannot collect flag that cannot be collected
|
||||
if (!flag.canPickup) {
|
||||
return;
|
||||
}
|
||||
flag.collect();
|
||||
player.collectFlag();
|
||||
}
|
||||
|
||||
removeFlagItem(flag) {
|
||||
if (flag.team == "L") {
|
||||
this.lteamFlags.remove(flag, true, true);
|
||||
} else if (flag.team == "R") {
|
||||
this.rteamFlags.remove(flag, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
freePlayer(player) {
|
||||
// player sent to prison cannot free others
|
||||
if (player.inPrison) {
|
||||
return;
|
||||
}
|
||||
if (player.team == "L") {
|
||||
this.lteamPlayers.getChildren().forEach( player => {
|
||||
if (player.inPrison) { player.inPrison = false; }
|
||||
})
|
||||
} else {
|
||||
this.rteamPlayers.getChildren().forEach( player => {
|
||||
if (player.inPrison) { player.inPrison = false; }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
updateTeamScore(team) {
|
||||
if (team == "L") {
|
||||
++this.lteamState.score;
|
||||
this.lScoreText.setText(`LTeam #Flags: ${this.lteamState.score}`);
|
||||
if (this.lteamState.score == this.NUM_FLAGS) {
|
||||
this.GameOver(team);
|
||||
}
|
||||
} else if (team == "R") {
|
||||
++this.rteamState.score;
|
||||
this.rScoreText.setText(`RTeam #Flags: ${this.rteamState.score}`);
|
||||
if (this.rteamState.score == this.NUM_FLAGS) {
|
||||
this.GameOver(team);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMapOffset() {
|
||||
return {
|
||||
x: this.mapX + this.halfTileSize,
|
||||
y: this.mapY + this.halfTileSize,
|
||||
width: this.mapWidth,
|
||||
height: this.mapHeight,
|
||||
tileSize: this.tileSize
|
||||
}
|
||||
}
|
||||
|
||||
getTileAt(x, y) {
|
||||
const tile = this.levelLayer.getTileAtWorldXY(x, y, true);
|
||||
return tile;
|
||||
}
|
||||
|
||||
isWall(x, y) {
|
||||
const tile = this.levelLayer.getTileAtWorldXY(x, y, true);
|
||||
return this.wallTiles.indexOf(tile.index) >= 0 ||
|
||||
this.tree1Tiles.indexOf(tile.index) >= 0 ||
|
||||
this.tree2Tiles[0].indexOf(tile.index) >= 0 ||
|
||||
this.tree2Tiles[1].indexOf(tile.index) >= 0
|
||||
;
|
||||
}
|
||||
|
||||
// return a 3x3 grid from x, y
|
||||
create3x3grid(x, y) {
|
||||
return [
|
||||
{x: x - 1, y: y - 1}, {x: x, y: y - 1}, {x: x + 1, y: y - 1},
|
||||
{x: x - 1, y: y}, {x: x, y: y}, {x: x + 1, y: y},
|
||||
{x: x - 1, y: y + 1}, {x: x, y: y + 1}, {x: x + 1, y: y + 1},
|
||||
]
|
||||
}
|
||||
|
||||
GameOver(team) {
|
||||
this.gameStarted = false;
|
||||
this.gameOverText.setText(team+"Team Won!")
|
||||
this.gameOverText.setVisible(true);
|
||||
|
||||
if (this.lteamSocket && this.lteamSocket.readyState == WebSocket.OPEN) {
|
||||
const payload = {
|
||||
action: "finished",
|
||||
myteamScore: this.lteamState.score,
|
||||
opponentScore: this.rteamState.score,
|
||||
}
|
||||
this.lteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
if (this.rteamSocket && this.rteamSocket.readyState == WebSocket.OPEN) {
|
||||
const payload = {
|
||||
action: "finished",
|
||||
myteamScore: this.rteamState.score,
|
||||
opponentScore: this.lteamState.score,
|
||||
}
|
||||
this.rteamSocket.send(JSON.stringify(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export class GameOver extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('GameOver');
|
||||
}
|
||||
|
||||
create() {
|
||||
this.background1 = this.add.image(0, 0, 'background').setOrigin(0);
|
||||
|
||||
this.add.text(this.scale.width * 0.5, this.scale.height * 0.5, 'Game Over', {
|
||||
fontFamily: 'Arial Black', fontSize: 64, color: '#ffffff',
|
||||
stroke: '#000000', strokeThickness: 8,
|
||||
align: 'center'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import ASSETS from '../assets.js';
|
||||
|
||||
export class Preloader extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('Preloader');
|
||||
}
|
||||
|
||||
init() {
|
||||
const centreX = this.scale.width * 0.5;
|
||||
const centreY = this.scale.height * 0.5;
|
||||
|
||||
const barWidth = 468;
|
||||
const barHeight = 32;
|
||||
const barMargin = 4;
|
||||
// We loaded this image in our Boot Scene, so we can display it here
|
||||
|
||||
// A simple progress bar. This is the outline of the bar.
|
||||
this.add.rectangle(centreX, centreY, barWidth, barHeight).setStrokeStyle(1, 0xffffff);
|
||||
|
||||
// This is the progress bar itself. It will increase in size from the left based on the % of progress.
|
||||
const bar = this.add.rectangle(centreX - (barWidth * 0.5) + barMargin, centreY, barMargin, barHeight - barMargin, 0xffffff);
|
||||
|
||||
// Use the 'progress' event emitted by the LoaderPlugin to update the loading bar
|
||||
this.load.on('progress', (progress) => {
|
||||
// Update the progress bar (our bar is 464px wide, so 100% = 464px)
|
||||
bar.width = barMargin + ((barWidth - (barMargin * 2)) * progress);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
preload() {
|
||||
// Load the assets for the game - see ./src/assets.js
|
||||
for (let type in ASSETS) {
|
||||
for (let key in ASSETS[type]) {
|
||||
let args = ASSETS[type][key].args.slice();
|
||||
args.unshift(ASSETS[type][key].key);
|
||||
this.load[type].apply(this.load, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
// When all the assets have loaded, it's often worth creating global objects here that the rest of the game can use.
|
||||
// For example, you can define global animations here, so we can use them in other scenes.
|
||||
|
||||
// Move to the MainMenu. You could also swap this for a Scene Transition, such as a camera fade.
|
||||
this.scene.start('Game');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user