game/server/build/match_handler.js
2026-01-01 02:21:09 +08:00

171 lines
6.6 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchSignal = exports.matchTerminate = exports.matchLoop = exports.matchLeave = exports.matchJoin = exports.matchJoinAttempt = exports.matchInit = void 0;
var types_1 = require("./types");
var MAX_PLAYERS = 4;
var GRID_SIZE = 100; // 10x10
var BOMB_COUNT = 30;
var ITEM_COUNT_MIN = 5;
var ITEM_COUNT_MAX = 10;
var matchInit = function (ctx, logger, nk, params) {
logger.info('Match initialized');
// Generate Grid
var grid = Array(GRID_SIZE).fill(null).map(function () { return ({ type: 'empty', revealed: false }); });
// Place Bombs
var bombsPlaced = 0;
while (bombsPlaced < BOMB_COUNT) {
var idx = Math.floor(Math.random() * GRID_SIZE);
if (grid[idx].type === 'empty') {
grid[idx].type = 'bomb';
bombsPlaced++;
}
}
// Place Items
var itemCount = Math.floor(Math.random() * (ITEM_COUNT_MAX - ITEM_COUNT_MIN + 1)) + ITEM_COUNT_MIN;
var itemsPlaced = 0;
while (itemsPlaced < itemCount) {
var idx = Math.floor(Math.random() * GRID_SIZE);
if (grid[idx].type === 'empty') {
grid[idx].type = 'item';
grid[idx].itemId = types_1.ItemTypes[Math.floor(Math.random() * types_1.ItemTypes.length)];
itemsPlaced++;
}
}
var state = {
players: {},
grid: grid,
turnOrder: [],
currentTurnIndex: 0,
round: 1,
winnerId: null,
gameStarted: false
};
return {
state: state,
tickRate: 1, // 1 tick per second is enough for turn-based, but maybe higher for responsiveness
label: 'Animal Minesweeper'
};
};
exports.matchInit = matchInit;
var matchJoinAttempt = function (ctx, logger, nk, dispatcher, tick, state, presence, metadata) {
if (state.gameStarted) {
return { state: state, accept: false, rejectMessage: 'Game already started' };
}
if (Object.keys(state.players).length >= MAX_PLAYERS) {
return { state: state, accept: false, rejectMessage: 'Match full' };
}
return { state: state, accept: true };
};
exports.matchJoinAttempt = matchJoinAttempt;
var matchJoin = function (ctx, logger, nk, dispatcher, tick, state, presences) {
presences.forEach(function (presence) {
// Random character assignment for now
var character = types_1.CharacterTypes[Math.floor(Math.random() * types_1.CharacterTypes.length)];
state.players[presence.userId] = {
userId: presence.userId,
sessionId: presence.sessionId,
username: presence.username,
avatar: '🦁', // Placeholder, map character to emoji later
hp: 4, // Default HP
maxHp: 4,
status: [],
character: character
};
state.turnOrder.push(presence.userId);
logger.info("Player joined: ".concat(presence.userId));
});
// Check if full to start game
// Fix: Use >= to handle potential race condition where multiple players join in same tick
if (Object.keys(state.players).length >= MAX_PLAYERS && !state.gameStarted) {
state.gameStarted = true;
logger.info('Game Started! Broadcasting GAME_START');
// Broadcast Game Start
dispatcher.broadcastMessage(types_1.OpCode.GAME_START, JSON.stringify(state));
}
else {
logger.info("Player joined. Count: ".concat(Object.keys(state.players).length, ". Broadcasting UPDATE_STATE"));
// Broadcast updated state (new player joined)
dispatcher.broadcastMessage(types_1.OpCode.UPDATE_STATE, JSON.stringify(state));
}
return { state: state };
};
exports.matchJoin = matchJoin;
var matchLeave = function (ctx, logger, nk, dispatcher, tick, state, presences) {
presences.forEach(function (presence) {
delete state.players[presence.userId];
var idx = state.turnOrder.indexOf(presence.userId);
if (idx > -1) {
state.turnOrder.splice(idx, 1);
// Adjust turn index if necessary
if (idx < state.currentTurnIndex) {
state.currentTurnIndex--;
}
if (state.currentTurnIndex >= state.turnOrder.length) {
state.currentTurnIndex = 0;
}
}
});
// Broadcast updated state (player left)
if (state.gameStarted && Object.keys(state.players).length > 0) {
dispatcher.broadcastMessage(types_1.OpCode.UPDATE_STATE, JSON.stringify(state));
}
// If game in progress and players drop, handle logic (end game or continue)
if (Object.keys(state.players).length < 2 && state.gameStarted) {
state.winnerId = Object.keys(state.players)[0] || null;
dispatcher.broadcastMessage(types_1.OpCode.GAME_OVER, JSON.stringify({ winnerId: state.winnerId }));
return null; // End match
}
// Broadcast updated state (player left)
if (state.gameStarted) {
dispatcher.broadcastMessage(types_1.OpCode.UPDATE_STATE, JSON.stringify(state));
}
return { state: state };
};
exports.matchLeave = matchLeave;
var matchLoop = function (ctx, logger, nk, dispatcher, tick, state, messages) {
if (!state.gameStarted)
return { state: state };
messages.forEach(function (message) {
if (message.opCode === types_1.OpCode.MOVE) {
var data = JSON.parse(nk.binaryToString(message.data));
handleMove(state, message.sender.userId, data.index, logger, dispatcher);
}
});
return { state: state };
};
exports.matchLoop = matchLoop;
var matchTerminate = function (ctx, logger, nk, dispatcher, tick, state, graceSeconds) {
return { state: state };
};
exports.matchTerminate = matchTerminate;
var matchSignal = function (ctx, logger, nk, dispatcher, tick, state, data) {
return { state: state, data: data };
};
exports.matchSignal = matchSignal;
// --- Helper Functions ---
function handleMove(state, userId, cellIndex, logger, dispatcher) {
// Validate Turn
var currentUserId = state.turnOrder[state.currentTurnIndex];
if (userId !== currentUserId) {
return; // Not your turn
}
var cell = state.grid[cellIndex];
if (cell.revealed)
return; // Already revealed
// Reveal Cell
cell.revealed = true;
var player = state.players[userId];
// Logic
if (cell.type === 'bomb') {
player.hp -= 2;
// Check death...
}
else if (cell.type === 'item') {
// Add item logic...
}
// Next Turn
state.currentTurnIndex = (state.currentTurnIndex + 1) % state.turnOrder.length;
// Broadcast Update
dispatcher.broadcastMessage(types_1.OpCode.UPDATE_STATE, JSON.stringify(state));
}