import { Socket } from "socket.io"; import randomstring from "randomstring"; import { PracticalTombolaAction, Room, RoomJoinError, SocketID, SocketWrapper, TombolaAction } from "../types"; import cartelle from "../cartelle"; const rooms = new Map(); const flatbellone = [...Array(90).keys()].map(n => n + 1); export function createRoom(socket: Socket, id: SocketID, username: string) { // console.log(username); const key = randomstring.generate({ length: 6, capitalization: "uppercase", charset: "hex", readable: true }); socket.emit("createRoom", { key }); rooms.set(key, { tabellone: [], players: [ { socketData: { id, socket }, username, hasTabellone: false, cartelle: [], choseAllCartelle: false, } ], gameStarted: false, id: key, nextProgress: TombolaAction.AMBO, winners: { 2: null, 3: null, 4: null, 5: null, 15: null, } }) socket.on("disconnect", () => { const room = rooms.get(key); rooms.delete(key); room?.players.forEach(player => { player.socketData.socket.emit("hostClosed"); player.socketData.socket.disconnect(); }); }); } export function joinRoom(socket: Socket, id: SocketID, username: string, roomID: string) { const room = rooms.get(roomID); if (!room) { socket.emit("joinRoomError", RoomJoinError.NoSuchRoom); return; } if (room.gameStarted) { socket.emit("joinRoomError", RoomJoinError.GameHasStarted); return; } room.players.push({ socketData: { id, socket }, username, hasTabellone: false, cartelle: [], choseAllCartelle: false }); room.players.forEach(player => { player.socketData.socket.emit("playersUpdate", room.players.map(player => { return { id: player.socketData.id, name: player.username } })); }); socket.emit("joinRoom", "OK"); socket.on("disconnect", () => { room.players = room.players.filter(member => { member.socketData.id !== id; }); socket.emit("playersUpdate", room.players.map(player => { return { id: player.socketData.id, name: player.username } })); }); } export function updatePlayers(socket: Socket, id: SocketID, roomID: string) { if (!rooms.has(roomID)) return socket.emit("playersUpdate", []); socket.emit("playersUpdate", rooms.get(roomID)!.players.map(player => { return { id: player.socketData.id, name: player.username } })); } export function extractNumber(socket: Socket, id: SocketID) { const roomID = roomIDForID(id); const room = rooms.get(roomID ?? ""); if (!roomID || !room || room.players.find(p => p.hasTabellone)?.socketData.id != id) return; const available = flatbellone.filter(n => !room.tabellone.includes(n)); const extracted = available[Math.floor(Math.random() * available.length)]; if (extracted) { room.tabellone.push(extracted); const progressing = room.players.map(player => { const checked = player.hasTabellone ? checkTabellone(room) : checkCartelle(room, player.cartelle.map(c => cartelle[c])); return { checked, player } }).filter(({ checked }) => { // console.log(checked, TombolaAction[checked[1]], TombolaAction[room.nextProgress]) return checked[0] && checked[1] === room.nextProgress; }).sort((dataA, dataB) => { return dataA.checked[1] - dataB.checked[1]; }); if (progressing.length) { room.nextProgress = nextOne(progressing[0].checked[1]); room.winners[previousOne(room.nextProgress) as PracticalTombolaAction] = progressing.map(({ player }) => { return { username: player.username, id: player.socketData.id } }); } return everySocketData(id).forEach(data => { data.socket.emit("extractedNumber", extracted, room.tabellone); // console.log(progressing); if (!progressing.length) return; data.socket.emit("progress", previousOne(room.nextProgress), progressing.map(p => p.player.socketData.id).includes(data.id), ...progressing.map(({ player }) => { return player.socketData.id == data.id ? "" : player.username; }) ); if (room.nextProgress == TombolaAction.DONE) { data.socket.emit("endGame", room.winners); } }); } } // Skip over the jumps function nextOne(action: TombolaAction): Exclude { switch(action) { case TombolaAction.NONE: return TombolaAction.AMBO; case TombolaAction.CINQUINA: return TombolaAction.TOMBOLA; default: return action + 1; } } // opposite of nextOne above function previousOne(action: TombolaAction): Exclude { switch(action) { case TombolaAction.AMBO: return TombolaAction.NONE; case TombolaAction.TOMBOLA: return TombolaAction.CINQUINA; default: return action - 1; } } export function chooseCartella(socket: Socket, id: SocketID, cartella: number) { const roomID = roomIDForID(id) ?? ""; const room = rooms.get(roomID); if (!room) return; const i = room.players.findIndex(({ socketData: socket }) => socket.id === id); room.players[i].cartelle.push(cartella); allButOne(id).forEach(socket => socket.emit("chosenCartella", cartella)); } export function unchooseCartella(socket: Socket, id: SocketID, cartella: number) { const roomID = roomIDForID(id) ?? ""; const room = rooms.get(roomID); if (!room) return; const i = room.players.findIndex(({ socketData: socket }) => socket.id === id); room.players[i].cartelle = room.players[i].cartelle .filter(c => c !== cartella); allButOne(id).forEach(socket => socket.emit("unchosenCartella", cartella)); } export function choseAllCartelle(socket: Socket, id: SocketID) { const roomID = roomIDForID(id) ?? ""; const room = rooms.get(roomID); if (!room) return; room.players[room.players.findIndex(v => v.socketData.id === id)].choseAllCartelle = true; if (room.players.map(player => player.choseAllCartelle || player.hasTabellone).every(p => p)) everySocket(id).forEach(socket => { socket.emit("everyoneChose"); }) } export function startGame(socket: Socket, id: SocketID) { const roomID = roomIDForID(id) ?? ""; const room = rooms.get(roomID); if (!room) return; if (room.players.length == 1) { socket.emit("startGameError"); return; } room.tabellone = []; room.gameStarted = true; room.players.forEach(player => player.cartelle = []); const sockets = socketObjects(roomID); if (!sockets.length) return; const tabelloneGiver = sockets[Math.floor(Math.random() * sockets.length)]; const tgIndex = room.players.findIndex(v => v.socketData.id === tabelloneGiver.id); room.players.forEach((_, i) => { room.players[i].hasTabellone = i === tgIndex; }); const others = allButOne(tabelloneGiver.id); tabelloneGiver.socket.emit("startingGame", true); others.forEach(socket => { socket.emit("startingGame", false); }) } export function pleaseGiveMeCartelle(socket: Socket, id: SocketID) { // console.log("pgmc") const roomID = roomIDForID(id) ?? ""; const room = rooms.get(roomID); if (!room) { socket.emit("gaveMeCartelle", [0]); return; } socket.emit("gaveMeCartelle", room.players.find(player => player.socketData.id == id)?.cartelle ?? [0]); } function roomIDForID(id: SocketID): string | undefined { return [...rooms.entries()].find(([k, v]) => { return v.players.map(s => s.socketData.id).includes(id); })?.[0]; } function allButOne(id: SocketID): Socket[] { return rooms.get(roomIDForID(id) ?? "")?.players.filter(player => { return player.socketData.id !== id; }).map(player => player.socketData.socket) ?? []; } function everySocketData(id: SocketID): SocketWrapper[] { return rooms.get(roomIDForID(id) ?? "")?.players.map(player => player.socketData) ?? []; } function everySocket(id: SocketID): Socket[] { return everySocketData(id).map(s => s.socket); } function socketObjects(room?: string): SocketWrapper[] { return rooms.get(room ?? "")?.players.map(player => player.socketData) ?? []; } // function subdivideTabellone(tabellone: number[]): [ // number[], number[], number[], number[], number[], number[] // ] { // return [[],[],[],[],[],[]]; // } const matrix = [1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 21, 22, 23, 24, 25]; const subdividedTabellone = [ matrix, matrix.map(n => n + 5), matrix.map(n => n + 30), matrix.map(n => n + 35), matrix.map(n => n + 60), matrix.map(n => n + 65), ] as const; function checkTabellone(room: Room): [boolean, TombolaAction] { for (let cartella of subdividedTabellone) { const lines = [ cartella.filter((n, i) => i < 5 && room.tabellone.includes(n)), cartella.filter((n, i) => i >= 5 && i < 10 && room.tabellone.includes(n)), cartella.filter((n, i) => i >= 10 && i < 15 && room.tabellone.includes(n)), ] // console.log(lines, "lines") if (lines.flat().length === 15) return [true, TombolaAction.TOMBOLA]; else for (let i of [5, 4, 3, 2].sort((b, a) => a - b).filter(a => a >= room.nextProgress)) { // // console.log("Checking", i); if (lines.some(line => line.length === i)) { return [true, i]; } } } return [false, TombolaAction.NONE]; } function checkCartelle(room: Room, cartelle: number[][]): [boolean, TombolaAction] { for (let cartella of cartelle) { const lines = [ cartella.filter((n, i) => i < 9 && n && room.tabellone.includes(n)), cartella.filter((n, i) => i >= 9 && i < 18 && n && room.tabellone.includes(n)), cartella.filter((n, i) => i >= 18 && i < 27 && n && room.tabellone.includes(n)), ] // console.log(lines); if (lines.flat().length === 15) { return [true, TombolaAction.TOMBOLA]; } else for (let i of [5, 4, 3, 2].sort((b, a) => a - b).filter(a => a >= room.nextProgress)) { // console.log(lines.some(line => line.length === i)); if (lines.some(line => line.length === i)) { return [true, i]; } } } return [false, TombolaAction.NONE]; }