"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)); }