diff --git a/backend/handlers/roomHandler.js b/backend/handlers/roomHandler.js index bdf8921..49162da 100644 --- a/backend/handlers/roomHandler.js +++ b/backend/handlers/roomHandler.js @@ -1,4 +1,4 @@ -const { getRooms, getRoom, updateRoom, createNewRoom } = require('../services/roomService'); +const { getRooms, getRoom, updateRoom, createNewRoom, deleteRoom } = require('../services/roomService'); const { sendToOnePlayerRooms, sendToOnePlayerData, sendWinner } = require('../socket/emits'); module.exports = socket => { @@ -8,6 +8,7 @@ module.exports = socket => { const room = await getRoom(req.session.roomId); // Handle the situation when the server crashes and any player reconnects after the time has expired // Typically, the responsibility for changing players is managed by gameHandler.js. + if(!room) return; if (room.nextMoveTime <= Date.now()) { room.changeMovingPlayer(); await updateRoom(room); @@ -26,7 +27,23 @@ module.exports = socket => { sendToOnePlayerRooms(socket.id, await getRooms()); }; + const handleDeleteRoom = async roomId => { + try { + console.log('🗑️ Attempting to delete room:', roomId); + const result = await deleteRoom(roomId); + console.log('✅ Room deleted successfully:', result); + const updatedRooms = await getRooms(); + console.log('📋 Updated room count:', updatedRooms.length); + console.log('📤 Sending updated rooms to socket:', socket.id); + sendToOnePlayerRooms(socket.id, updatedRooms); + } catch (error) { + console.error('❌ Error deleting room:', error); + socket.emit('error:deleteRoom', 'Failed to delete room'); + } + }; + socket.on('room:data', handleGetData); socket.on('room:rooms', handleGetAllRooms); socket.on('room:create', handleCreateRoom); + socket.on('room:delete', handleDeleteRoom); }; diff --git a/backend/models/room.js b/backend/models/room.js index b427494..12b12ec 100644 --- a/backend/models/room.js +++ b/backend/models/room.js @@ -5,6 +5,18 @@ 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) + || HOME_ENTRY_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 }, @@ -36,6 +48,11 @@ const RoomSchema = new mongoose.Schema({ }); RoomSchema.methods.beatPawns = function (position, attackingPawnColor) { + // Do not beat pawns on safe/colored positions + if (isSafePosition(position)) { + return; + } + const pawnsOnPosition = this.pawns.filter(pawn => pawn.position === position); pawnsOnPosition.forEach(pawn => { if (pawn.color !== attackingPawnColor) { diff --git a/backend/services/roomService.js b/backend/services/roomService.js index 9e2c714..534e0d0 100644 --- a/backend/services/roomService.js +++ b/backend/services/roomService.js @@ -23,8 +23,21 @@ const createNewRoom = async data => { return room; }; +const deleteRoom = async roomId => { + return await Room.findByIdAndDelete(roomId).exec(); +}; + Room.watch().on('change', async data => { - sendToPlayersData(await getRoom(data.documentKey._id)); + // Ignore delete operations + if (data.operationType === 'delete') { + console.log('🗑️ Room deleted, skipping sendToPlayersData'); + return; + } + + const room = await getRoom(data.documentKey._id); + if (room) { + sendToPlayersData(room); + } }); -module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom }; +module.exports = { getRoom, getRooms, updateRoom, getJoinableRoom, createNewRoom, deleteRoom }; diff --git a/src/components/Gameboard/Map/Map.jsx b/src/components/Gameboard/Map/Map.jsx index fdfd211..84637ea 100644 --- a/src/components/Gameboard/Map/Map.jsx +++ b/src/components/Gameboard/Map/Map.jsx @@ -33,7 +33,7 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => { cursorX = event.clientX - rect.left, cursorY = event.clientY - rect.top; for (const pawn of pawns) { - if (ctx.isPointInPath(pawn.touchableArea, cursorX, cursorY)) { + if (pawn.touchableArea && ctx.isPointInPath(pawn.touchableArea, cursorX, cursorY)) { if (canPawnMove(pawn, rolledNumber)) socket.emit('game:move', pawn._id); } } @@ -50,11 +50,14 @@ const Map = ({ pawns, nowMoving, rolledNumber }) => { canvas.style.cursor = 'default'; for (const pawn of pawns) { if ( + pawn.touchableArea && ctx.isPointInPath(pawn.touchableArea, x, y) && player.color === pawn.color && canPawnMove(pawn, rolledNumber) ) { const pawnPosition = getPositionAfterMove(pawn, rolledNumber); + console.log('previous position:', pawn.position); + console.log('Hovered pawn can move to position:', pawnPosition); if (pawnPosition) { canvas.style.cursor = 'pointer'; if (hintPawn && hintPawn.id === pawn._id) return; diff --git a/src/components/LoginPage/JoinServer/JoinServer.jsx b/src/components/LoginPage/JoinServer/JoinServer.jsx index c267007..5c4812a 100644 --- a/src/components/LoginPage/JoinServer/JoinServer.jsx +++ b/src/components/LoginPage/JoinServer/JoinServer.jsx @@ -20,12 +20,23 @@ const JoinServer = () => { useEffect(() => { socket.emit('room:rooms'); socket.on('room:rooms', () => { + console.log('✅ Room list updated'); setIsLoading(false); }); + socket.on('error:deleteRoom', (error) => { + console.error('❌ Delete error:', error); + alert('Failed to delete server: ' + error); + getRooms(); + }); + return () => { + socket.off('error:deleteRoom'); + socket.off('room:rooms'); + }; }, [socket]); const getRooms = () => { setRooms([]); + setIsLoading(true); socket.emit('room:rooms'); }; @@ -34,6 +45,15 @@ const JoinServer = () => { setJoining(true); }; + const handleDeleteClick = roomId => { + if (window.confirm('Are you sure you want to delete this server?')) { + console.log('🗑️ Deleting room:', roomId); + setIsLoading(true); + setRooms([]); // Clear the list immediately + socket.emit('room:delete', roomId); + } + }; + const ServersTableWithLoading = withLoading(ServersTable); return ( @@ -51,6 +71,7 @@ const JoinServer = () => { isLoading={isLoading} rooms={rooms} handleJoinClick={handleJoinClick} + handleDeleteClick={handleDeleteClick} /> } diff --git a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.jsx b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.jsx index 8a62ff7..4532d34 100644 --- a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.jsx +++ b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.jsx @@ -1,7 +1,7 @@ import lock from '../../../../images/login-page/lock.png'; import styles from './ServersTable.module.css'; -const ServerListTable = ({ rooms, handleJoinClick }) => { +const ServerListTable = ({ rooms, handleJoinClick, handleDeleteClick }) => { return ( @@ -23,6 +23,9 @@ const ServerListTable = ({ rooms, handleJoinClick }) => { ); diff --git a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css index a176bf8..c04b98d 100644 --- a/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css +++ b/src/components/LoginPage/JoinServer/ServersTable/ServersTable.module.css @@ -36,8 +36,18 @@ } .lastColumn { - width: 70px; + width: 130px; } .firstColumn { width: 40px; } + +.deleteBtn { + margin-left: 5px; + background-color: #ff4444; + color: white; + border: none; + padding: 4px 8px; + cursor: pointer; + border-radius: 4px; +} diff --git a/src/images/map-bkup.jpg b/src/images/map-bkup.jpg new file mode 100644 index 0000000..8c158fd Binary files /dev/null and b/src/images/map-bkup.jpg differ diff --git a/src/images/map.jpg b/src/images/map.jpg index 8c158fd..a85b4cb 100644 Binary files a/src/images/map.jpg and b/src/images/map.jpg differ
{room.isStarted ? 'started' : 'waiting'} +