const mongoose = require('mongoose'); const { COLORS, MOVE_TIME } = require('../utils/constants'); const { makeRandomMove } = require('../handlers/handlersFunctions'); const timeoutManager = require('./timeoutManager.js'); const PawnSchema = require('./pawn'); const PlayerSchema = require('./player'); // Safe/colored box positions in Ludo (where pawns cannot be killed) const SAFE_POSITIONS = [16, 29, 42, 55]; // const HOME_ENTRY_POSITIONS = [66, 27, 40, 53]; const STAR_POSITIONS = [63, 24, 37, 50]; const isSafePosition = (position) => { return SAFE_POSITIONS.includes(position) || STAR_POSITIONS.includes(position) || position > 66; // Also safe in home stretch }; const RoomSchema = new mongoose.Schema({ name: String, private: { type: Boolean, default: false }, password: String, createDate: { type: Date, default: Date.now }, started: { type: Boolean, default: false }, full: { type: Boolean, default: false }, nextMoveTime: Number, rolledNumber: Number, players: [PlayerSchema], winner: { type: String, default: null }, pawns: { type: [PawnSchema], default: () => { const startPositions = []; for (let i = 0; i < 16; i++) { let pawn = {}; pawn.basePos = i; pawn.position = i; if (i < 4) pawn.color = COLORS[0]; else if (i < 8) pawn.color = COLORS[1]; else if (i < 12) pawn.color = COLORS[2]; else if (i < 16) pawn.color = COLORS[3]; startPositions.push(pawn); } return startPositions; }, }, }); RoomSchema.methods.beatPawns = function (position, attackingPawnColor) { // Do not beat pawns on safe/colored positions if (isSafePosition(position)) { return 0; } let beatenCount = 0; const pawnsOnPosition = this.pawns.filter(pawn => pawn.position === position); pawnsOnPosition.forEach(pawn => { if (pawn.color !== attackingPawnColor) { const index = this.getPawnIndex(pawn._id); if (index !== -1 && this.pawns[index].position !== this.pawns[index].basePos) { this.pawns[index].position = this.pawns[index].basePos; beatenCount++; } } }); return beatenCount; }; RoomSchema.methods.changeMovingPlayer = function () { if (this.winner) return; if (!Array.isArray(this.players) || this.players.length === 0) { console.warn(`[room:${this._id}] changeMovingPlayer: players array is empty or null`); return; } const playerIndex = this.players.findIndex(player => player && player.nowMoving === true); if (playerIndex === -1) { console.warn(`[room:${this._id}] changeMovingPlayer: no player currently marked as nowMoving`); // Default to first player this.players[0].nowMoving = true; } else { this.players[playerIndex].nowMoving = false; if (playerIndex + 1 === this.players.length) { this.players[0].nowMoving = true; } else { this.players[playerIndex + 1].nowMoving = true; } } this.nextMoveTime = Date.now() + MOVE_TIME; this.rolledNumber = null; timeoutManager.clear(this._id.toString()); timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString()); }; RoomSchema.methods.resetTurnForSamePlayer = function () { if (this.winner) return; // Keep the same player moving but reset the roll and move timer const now = Date.now(); const currentPlayer = this.getCurrentlyMovingPlayer(); if (!currentPlayer) { console.warn(`[room:${this._id}] resetTurnForSamePlayer: No current player found!`); return; } this.nextMoveTime = now + MOVE_TIME; this.rolledNumber = null; console.log(`[room:${this._id}] resetTurnForSamePlayer: player=${currentPlayer.color}, now=${now}, nextMoveTime=${this.nextMoveTime}, MOVE_TIME=${MOVE_TIME}`); timeoutManager.clear(this._id.toString()); timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString()); }; RoomSchema.methods.movePawn = function (pawn) { const newPositionOfMovedPawn = pawn.getPositionAfterMove(this.rolledNumber); this.changePositionOfPawn(pawn, newPositionOfMovedPawn); this.beatPawns(newPositionOfMovedPawn, pawn.color); }; RoomSchema.methods.getPawnsThatCanMove = function () { const movingPlayer = this.getCurrentlyMovingPlayer(); if (!movingPlayer) return []; const playerPawns = this.getPlayerPawns(movingPlayer.color); return (playerPawns || []).filter(pawn => pawn.canMove(this.rolledNumber)); }; RoomSchema.methods.changePositionOfPawn = function (pawn, newPosition) { const pawnIndex = this.getPawnIndex(pawn._id); this.pawns[pawnIndex].position = newPosition; }; RoomSchema.methods.canStartGame = function () { if (!Array.isArray(this.players)) return false; return this.players.filter(player => player && player.ready).length >= 2; }; RoomSchema.methods.startGame = function () { if (!Array.isArray(this.players) || this.players.length === 0) { console.warn(`[room:${this._id}] startGame: players array empty`); return; } this.started = true; this.nextMoveTime = Date.now() + MOVE_TIME; this.players.forEach(player => (player.ready = true)); this.players[0].nowMoving = true; timeoutManager.set(makeRandomMove, MOVE_TIME, this._id.toString()); }; RoomSchema.methods.endGame = function (winner) { timeoutManager.clear(this._id.toString()); this.rolledNumber = null; this.nextMoveTime = null; if (Array.isArray(this.players)) this.players.forEach(player => (player.nowMoving = false)); this.winner = winner; this.save(); }; RoomSchema.methods.getWinner = function () { if (!Array.isArray(this.pawns)) return null; if (this.pawns.filter(pawn => pawn && pawn.color === 'red' && pawn.position === 73).length === 4) { return 'red'; } if (this.pawns.filter(pawn => pawn && pawn.color === 'blue' && pawn.position === 79).length === 4) { return 'blue'; } if (this.pawns.filter(pawn => pawn && pawn.color === 'green' && pawn.position === 85).length === 4) { return 'green'; } if (this.pawns.filter(pawn => pawn && pawn.color === 'yellow' && pawn.position === 91).length === 4) { return 'yellow'; } return null; }; RoomSchema.methods.isFull = function () { if (!Array.isArray(this.players)) return false; if (this.players.length === 4) { this.full = true; } return this.full; }; RoomSchema.methods.getPlayer = function (playerId) { if (!Array.isArray(this.players)) return null; return this.players.find(player => player && player._id && player._id.toString() === playerId.toString()) || null; }; RoomSchema.methods.addPlayer = function (name, id) { if (this.full) return; if (!Array.isArray(this.players)) this.players = []; this.players.push({ sessionID: id, name: name, ready: false, color: COLORS[this.players.length], }); }; RoomSchema.methods.getPawnIndex = function (pawnId) { if (!Array.isArray(this.pawns)) return -1; return this.pawns.findIndex(pawn => pawn && pawn._id && pawn._id.toString() === pawnId.toString()); }; RoomSchema.methods.getPawn = function (pawnId) { if (!Array.isArray(this.pawns)) return null; return this.pawns.find(pawn => pawn && pawn._id && pawn._id.toString() === pawnId.toString()) || null; }; RoomSchema.methods.getPlayerPawns = function (color) { if (!Array.isArray(this.pawns)) return []; return this.pawns.filter(pawn => pawn && pawn.color === color); }; RoomSchema.methods.getPawnsOnPosition = function (position) { if (!Array.isArray(this.pawns)) return []; return this.pawns.filter(pawn => pawn && pawn.position === position); }; RoomSchema.methods.isStacked = function (position) { return this.getPawnsOnPosition(position).length > 1; }; RoomSchema.methods.getOpponentPawnsOnPosition = function (position, color) { return this.getPawnsOnPosition(position).filter(pawn => pawn.color !== color); }; RoomSchema.methods.getCurrentlyMovingPlayer = function () { if (!Array.isArray(this.players)) return null; return this.players.find(player => player && player.nowMoving === true) || null; }; const Room = mongoose.model('Room', RoomSchema); module.exports = Room;