first commit
This commit is contained in:
1
server/.devmode.json
Normal file
1
server/.devmode.json
Normal file
@@ -0,0 +1 @@
|
||||
{"data":{"colyseus:nodes":[]},"hash":{"roomcount":{},"roomhistory":{}},"keys":{}}
|
||||
4
server/.env.development
Normal file
4
server/.env.development
Normal file
@@ -0,0 +1,4 @@
|
||||
NODE_ENV=development
|
||||
PORT=2567
|
||||
CLIENT_URL=http://localhost:3000
|
||||
ADMIN_URL=http://localhost:3001
|
||||
2982
server/package-lock.json
generated
Normal file
2982
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
server/package.json
Normal file
33
server/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "snatchgame-server",
|
||||
"version": "0.0.1-alpha",
|
||||
"description": "SnatchGame multiplayer server using Colyseus",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
||||
"start": "node lib/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"colyseus",
|
||||
"multiplayer",
|
||||
"game",
|
||||
"typescript"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@colyseus/schema": "^3.0.42",
|
||||
"@colyseus/tools": "^0.16.0",
|
||||
"colyseus": "^0.16.0",
|
||||
"express": "^4.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
30
server/src/app.config.ts
Normal file
30
server/src/app.config.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import config from "@colyseus/tools";
|
||||
import { GameRoom } from "./rooms/GameRoom";
|
||||
|
||||
export default config({
|
||||
options: {
|
||||
devMode: process.env.NODE_ENV !== "production",
|
||||
gracefullyShutdown: true,
|
||||
},
|
||||
|
||||
initializeGameServer: (gameServer) => {
|
||||
// Define game room handler
|
||||
gameServer.define('game', GameRoom)
|
||||
.filterBy(['gameMode'])
|
||||
.sortBy({ clients: 1 }); // Prefer rooms with fewer clients
|
||||
},
|
||||
|
||||
initializeExpress: (app) => {
|
||||
app.get("/", (req, res) => {
|
||||
res.json({
|
||||
name: "SnatchGame Server",
|
||||
status: "running",
|
||||
version: "1.0.0"
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
res.json({ status: "healthy" });
|
||||
});
|
||||
}
|
||||
});
|
||||
6
server/src/index.ts
Normal file
6
server/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { listen } from "@colyseus/tools";
|
||||
import app from "./app.config";
|
||||
|
||||
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 2567;
|
||||
|
||||
listen(app, PORT);
|
||||
100
server/src/rooms/GameRoom.ts
Normal file
100
server/src/rooms/GameRoom.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Room, Client } from "colyseus";
|
||||
import { Schema, MapSchema, type } from "@colyseus/schema";
|
||||
|
||||
export interface GameRoomOptions {
|
||||
gameMode?: string;
|
||||
playerName?: string;
|
||||
}
|
||||
|
||||
export class Player extends Schema {
|
||||
@type("string") id: string;
|
||||
@type("string") name: string;
|
||||
@type("number") score: number = 0;
|
||||
@type("boolean") ready: boolean = false;
|
||||
}
|
||||
|
||||
export class GameState extends Schema {
|
||||
@type({ map: Player }) players = new MapSchema<Player>();
|
||||
@type("boolean") gameStarted: boolean = false;
|
||||
@type("string") gameMode: string = "classic";
|
||||
@type("number") minPlayers: number = 2;
|
||||
@type("string") gamePhase: string = "waiting"; // "waiting" | "playing"
|
||||
}
|
||||
|
||||
export class GameRoom extends Room<GameState> {
|
||||
maxClients = 8;
|
||||
|
||||
onCreate(options: GameRoomOptions) {
|
||||
console.log(`GameRoom created with options:`, options);
|
||||
|
||||
this.setState(new GameState());
|
||||
this.state.gameMode = options.gameMode || 'classic';
|
||||
this.state.gamePhase = "waiting";
|
||||
|
||||
this.onMessage("click", (client, message) => {
|
||||
this.handleClick(client);
|
||||
});
|
||||
|
||||
this.onMessage("*", (client, type, message) => {
|
||||
console.log(`Message from ${client.sessionId}:`, type, message);
|
||||
});
|
||||
}
|
||||
|
||||
private handleClick(client: Client) {
|
||||
const player = this.state.players.get(client.sessionId);
|
||||
|
||||
if (!player) {
|
||||
console.log(`Player not found for client ${client.sessionId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.gamePhase !== "playing") {
|
||||
console.log(`Click ignored - game not started (phase: ${this.state.gamePhase})`);
|
||||
return;
|
||||
}
|
||||
|
||||
player.score += 1;
|
||||
console.log(`🎮 Player ${player.name} clicked! New score: ${player.score}`);
|
||||
}
|
||||
|
||||
private checkGameStart() {
|
||||
const playerCount = this.state.players.size;
|
||||
|
||||
if (playerCount >= this.state.minPlayers && this.state.gamePhase === "waiting") {
|
||||
this.state.gamePhase = "playing";
|
||||
this.state.gameStarted = true;
|
||||
console.log(`🚀 Game started! ${playerCount} players ready to play`);
|
||||
} else if (playerCount < this.state.minPlayers && this.state.gamePhase === "playing") {
|
||||
this.state.gamePhase = "waiting";
|
||||
this.state.gameStarted = false;
|
||||
console.log(`⏸️ Game paused - not enough players (${playerCount}/${this.state.minPlayers})`);
|
||||
}
|
||||
}
|
||||
|
||||
onJoin(client: Client, options: any) {
|
||||
console.log(`Client ${client.sessionId} joined the room`);
|
||||
|
||||
const player = new Player();
|
||||
player.id = client.sessionId;
|
||||
player.name = options.playerName || `Player ${this.state.players.size + 1}`;
|
||||
player.score = 0;
|
||||
player.ready = false;
|
||||
|
||||
this.state.players.set(client.sessionId, player);
|
||||
|
||||
// Check if we can start the game
|
||||
this.checkGameStart();
|
||||
}
|
||||
|
||||
onLeave(client: Client, consented: boolean) {
|
||||
console.log(`Client ${client.sessionId} left the room`);
|
||||
this.state.players.delete(client.sessionId);
|
||||
|
||||
// Check if we need to pause the game
|
||||
this.checkGameStart();
|
||||
}
|
||||
|
||||
onDispose() {
|
||||
console.log(`GameRoom ${this.roomId} disposed`);
|
||||
}
|
||||
}
|
||||
28
server/tsconfig.json
Normal file
28
server/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user