mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
snake
This commit is contained in:
@@ -10,7 +10,7 @@ import { client } from "../bot/client.js";
|
|||||||
* @param {number} p2Score - The score for player 2.
|
* @param {number} p2Score - The score for player 2.
|
||||||
* @param {string} type - The type of game being played (e.g., 'TICTACTOE', 'CONNECT4').
|
* @param {string} type - The type of game being played (e.g., 'TICTACTOE', 'CONNECT4').
|
||||||
*/
|
*/
|
||||||
export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type) {
|
export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type, scores = null) {
|
||||||
// --- 1. Fetch Player Data ---
|
// --- 1. Fetch Player Data ---
|
||||||
const p1DB = getUser.get(p1Id);
|
const p1DB = getUser.get(p1Id);
|
||||||
const p2DB = getUser.get(p2Id);
|
const p2DB = getUser.get(p2Id);
|
||||||
@@ -43,9 +43,26 @@ export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type) {
|
|||||||
const expectedP1 = 1 / (1 + Math.pow(10, (p2CurrentElo - p1CurrentElo) / 400));
|
const expectedP1 = 1 / (1 + Math.pow(10, (p2CurrentElo - p1CurrentElo) / 400));
|
||||||
const expectedP2 = 1 / (1 + Math.pow(10, (p1CurrentElo - p2CurrentElo) / 400));
|
const expectedP2 = 1 / (1 + Math.pow(10, (p1CurrentElo - p2CurrentElo) / 400));
|
||||||
|
|
||||||
|
// Calculate raw Elo changes
|
||||||
|
const p1Change = K_FACTOR * (p1Score - expectedP1);
|
||||||
|
const p2Change = K_FACTOR * (p2Score - expectedP2);
|
||||||
|
|
||||||
|
// Make losing friendlier: loser loses 80% of what winner gains
|
||||||
|
let finalP1Change = p1Change;
|
||||||
|
let finalP2Change = p2Change;
|
||||||
|
|
||||||
|
if (p1Score > p2Score) {
|
||||||
|
// P1 won, P2 lost
|
||||||
|
finalP2Change = p2Change * 0.8;
|
||||||
|
} else if (p2Score > p1Score) {
|
||||||
|
// P2 won, P1 lost
|
||||||
|
finalP1Change = p1Change * 0.8;
|
||||||
|
}
|
||||||
|
// If it's a draw (p1Score === p2Score), keep the original changes
|
||||||
|
|
||||||
// Calculate new Elo ratings
|
// Calculate new Elo ratings
|
||||||
const p1NewElo = Math.round(p1CurrentElo + K_FACTOR * (p1Score - expectedP1));
|
const p1NewElo = Math.round(p1CurrentElo + finalP1Change);
|
||||||
const p2NewElo = Math.round(p2CurrentElo + K_FACTOR * (p2Score - expectedP2));
|
const p2NewElo = Math.round(p2CurrentElo + finalP2Change);
|
||||||
|
|
||||||
// Ensure Elo doesn't drop below a certain threshold (e.g., 100)
|
// Ensure Elo doesn't drop below a certain threshold (e.g., 100)
|
||||||
const finalP1Elo = Math.max(0, p1NewElo);
|
const finalP1Elo = Math.max(0, p1NewElo);
|
||||||
@@ -77,19 +94,37 @@ export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type) {
|
|||||||
updateElo.run({ id: p1Id, elo: finalP1Elo });
|
updateElo.run({ id: p1Id, elo: finalP1Elo });
|
||||||
updateElo.run({ id: p2Id, elo: finalP2Elo });
|
updateElo.run({ id: p2Id, elo: finalP2Elo });
|
||||||
|
|
||||||
insertGame.run({
|
if (scores) {
|
||||||
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
insertGame.run({
|
||||||
p1: p1Id,
|
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
||||||
p2: p2Id,
|
p1: p1Id,
|
||||||
p1_score: p1Score,
|
p2: p2Id,
|
||||||
p2_score: p2Score,
|
p1_score: scores.p1,
|
||||||
p1_elo: p1CurrentElo,
|
p2_score: scores.p2,
|
||||||
p2_elo: p2CurrentElo,
|
p1_elo: p1CurrentElo,
|
||||||
p1_new_elo: finalP1Elo,
|
p2_elo: p2CurrentElo,
|
||||||
p2_new_elo: finalP2Elo,
|
p1_new_elo: finalP1Elo,
|
||||||
type: type,
|
p2_new_elo: finalP2Elo,
|
||||||
timestamp: Date.now(),
|
type: type,
|
||||||
});
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
insertGame.run({
|
||||||
|
id: `${p1Id}-${p2Id}-${Date.now()}`,
|
||||||
|
p1: p1Id,
|
||||||
|
p2: p2Id,
|
||||||
|
p1_score: p1Score,
|
||||||
|
p2_score: p2Score,
|
||||||
|
p1_elo: p1CurrentElo,
|
||||||
|
p2_elo: p2CurrentElo,
|
||||||
|
p1_new_elo: finalP1Elo,
|
||||||
|
p2_new_elo: finalP2Elo,
|
||||||
|
type: type,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export let activeConnect4Games = {};
|
|||||||
// Stores active Tic-Tac-Toe games, keyed by a unique game ID.
|
// Stores active Tic-Tac-Toe games, keyed by a unique game ID.
|
||||||
export let activeTicTacToeGames = {};
|
export let activeTicTacToeGames = {};
|
||||||
|
|
||||||
|
// Stores active Snake games, keyed by a unique game ID.
|
||||||
|
export let activeSnakeGames = {};
|
||||||
|
|
||||||
// Stores active Solitaire games, keyed by user ID.
|
// Stores active Solitaire games, keyed by user ID.
|
||||||
export let activeSolitaireGames = {};
|
export let activeSolitaireGames = {};
|
||||||
|
|
||||||
@@ -54,6 +57,9 @@ export let tictactoeQueue = [];
|
|||||||
// Stores user IDs waiting to play Connect 4.
|
// Stores user IDs waiting to play Connect 4.
|
||||||
export let connect4Queue = [];
|
export let connect4Queue = [];
|
||||||
|
|
||||||
|
// Stores user IDs waiting to play Snake 1v1.
|
||||||
|
export let snakeQueue = [];
|
||||||
|
|
||||||
export let queueMessagesEndpoints = [];
|
export let queueMessagesEndpoints = [];
|
||||||
|
|
||||||
// --- Rate Limiting and Caching ---
|
// --- Rate Limiting and Caching ---
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "disc
|
|||||||
import {
|
import {
|
||||||
activeConnect4Games,
|
activeConnect4Games,
|
||||||
activeTicTacToeGames,
|
activeTicTacToeGames,
|
||||||
|
activeSnakeGames,
|
||||||
connect4Queue,
|
connect4Queue,
|
||||||
queueMessagesEndpoints,
|
queueMessagesEndpoints,
|
||||||
tictactoeQueue,
|
tictactoeQueue,
|
||||||
|
snakeQueue,
|
||||||
} from "../game/state.js";
|
} from "../game/state.js";
|
||||||
import {
|
import {
|
||||||
C4_ROWS,
|
C4_ROWS,
|
||||||
@@ -31,6 +33,7 @@ export function initializeSocket(server, client) {
|
|||||||
|
|
||||||
registerTicTacToeEvents(socket, client);
|
registerTicTacToeEvents(socket, client);
|
||||||
registerConnect4Events(socket, client);
|
registerConnect4Events(socket, client);
|
||||||
|
registerSnakeEvents(socket, client);
|
||||||
|
|
||||||
socket.on("tictactoe:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client));
|
socket.on("tictactoe:queue:leave", async ({ discordId }) => await refreshQueuesForUser(discordId, client));
|
||||||
|
|
||||||
@@ -68,6 +71,13 @@ function registerConnect4Events(socket, client) {
|
|||||||
socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", e.playerId, e.winner, "(temps écoulé)"));
|
socket.on("connect4NoTime", (e) => onGameOver(client, "connect4", e.playerId, e.winner, "(temps écoulé)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerSnakeEvents(socket, client) {
|
||||||
|
socket.on("snakeconnection", (e) => refreshQueuesForUser(e.id, client));
|
||||||
|
socket.on("snakequeue", (e) => onQueueJoin(client, "snake", e.playerId));
|
||||||
|
socket.on("snakegamestate", (e) => onSnakeGameStateUpdate(client, e));
|
||||||
|
socket.on("snakegameOver", (e) => onGameOver(client, "snake", e.playerId, e.winner));
|
||||||
|
}
|
||||||
|
|
||||||
// --- Core Handlers (Preserving Original Logic) ---
|
// --- Core Handlers (Preserving Original Logic) ---
|
||||||
|
|
||||||
async function onQueueJoin(client, gameType, playerId) {
|
async function onQueueJoin(client, gameType, playerId) {
|
||||||
@@ -189,7 +199,53 @@ async function onConnect4Move(client, eventData) {
|
|||||||
await onGameOver(client, "connect4", playerId, winnerId);
|
await onGameOver(client, "connect4", playerId, winnerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onGameOver(client, gameType, playerId, winnerId, reason = "") {
|
async function onSnakeGameStateUpdate(client, eventData) {
|
||||||
|
const { playerId, snake, food, score, gameOver, win } = eventData;
|
||||||
|
const lobby = Object.values(activeSnakeGames).find(
|
||||||
|
(l) => (l.p1.id === playerId || l.p2.id === playerId) && !l.gameOver,
|
||||||
|
);
|
||||||
|
if (!lobby) return;
|
||||||
|
|
||||||
|
const player = lobby.p1.id === playerId ? lobby.p1 : lobby.p2;
|
||||||
|
player.snake = snake;
|
||||||
|
player.food = food;
|
||||||
|
player.score = score;
|
||||||
|
player.gameOver = gameOver;
|
||||||
|
player.win = win;
|
||||||
|
|
||||||
|
lobby.lastmove = Date.now();
|
||||||
|
|
||||||
|
// Broadcast the updated state to both players
|
||||||
|
io.emit("snakegamestate", {
|
||||||
|
lobby: {
|
||||||
|
p1: lobby.p1,
|
||||||
|
p2: lobby.p2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if game should end
|
||||||
|
if (lobby.p1.gameOver && lobby.p2.gameOver) {
|
||||||
|
// Both players finished - determine winner
|
||||||
|
let winnerId = null;
|
||||||
|
if (lobby.p1.win && !lobby.p2.win) {
|
||||||
|
winnerId = lobby.p1.id;
|
||||||
|
} else if (lobby.p2.win && !lobby.p1.win) {
|
||||||
|
winnerId = lobby.p2.id;
|
||||||
|
} else if (lobby.p1.score > lobby.p2.score) {
|
||||||
|
winnerId = lobby.p1.id;
|
||||||
|
} else if (lobby.p2.score > lobby.p1.score) {
|
||||||
|
winnerId = lobby.p2.id;
|
||||||
|
}
|
||||||
|
// If scores are equal, winnerId remains null (draw)
|
||||||
|
await onGameOver(client, "snake", playerId, winnerId, "", { p1: lobby.p1.score, p2: lobby.p2.score });
|
||||||
|
} else if (lobby.p1.win || lobby.p2.win) {
|
||||||
|
// One player won by filling the grid
|
||||||
|
const winnerId = lobby.p1.win ? lobby.p1.id : lobby.p2.id;
|
||||||
|
await onGameOver(client, "snake", playerId, winnerId, "", { p1: lobby.p1.score, p2: lobby.p2.score });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onGameOver(client, gameType, playerId, winnerId, reason = "", scores = null) {
|
||||||
const { activeGames, title } = getGameAssets(gameType);
|
const { activeGames, title } = getGameAssets(gameType);
|
||||||
const gameKey = Object.keys(activeGames).find((key) => key.includes(playerId));
|
const gameKey = Object.keys(activeGames).find((key) => key.includes(playerId));
|
||||||
const game = gameKey ? activeGames[gameKey] : undefined;
|
const game = gameKey ? activeGames[gameKey] : undefined;
|
||||||
@@ -198,7 +254,7 @@ async function onGameOver(client, gameType, playerId, winnerId, reason = "") {
|
|||||||
game.gameOver = true;
|
game.gameOver = true;
|
||||||
let resultText;
|
let resultText;
|
||||||
if (winnerId === null) {
|
if (winnerId === null) {
|
||||||
await eloHandler(game.p1.id, game.p2.id, 0.5, 0.5, title.toUpperCase());
|
await eloHandler(game.p1.id, game.p2.id, 0.5, 0.5, title.toUpperCase(), scores);
|
||||||
resultText = "Égalité";
|
resultText = "Égalité";
|
||||||
} else {
|
} else {
|
||||||
await eloHandler(
|
await eloHandler(
|
||||||
@@ -207,6 +263,7 @@ async function onGameOver(client, gameType, playerId, winnerId, reason = "") {
|
|||||||
game.p1.id === winnerId ? 1 : 0,
|
game.p1.id === winnerId ? 1 : 0,
|
||||||
game.p2.id === winnerId ? 1 : 0,
|
game.p2.id === winnerId ? 1 : 0,
|
||||||
title.toUpperCase(),
|
title.toUpperCase(),
|
||||||
|
scores
|
||||||
);
|
);
|
||||||
const winnerName = game.p1.id === winnerId ? game.p1.name : game.p2.name;
|
const winnerName = game.p1.id === winnerId ? game.p1.name : game.p2.name;
|
||||||
resultText = `Victoire de ${winnerName}`;
|
resultText = `Victoire de ${winnerName}`;
|
||||||
@@ -216,6 +273,7 @@ async function onGameOver(client, gameType, playerId, winnerId, reason = "") {
|
|||||||
|
|
||||||
if (gameType === "tictactoe") io.emit("tictactoegameOver", { game, winner: winnerId });
|
if (gameType === "tictactoe") io.emit("tictactoegameOver", { game, winner: winnerId });
|
||||||
if (gameType === "connect4") io.emit("connect4gameOver", { game, winner: winnerId });
|
if (gameType === "connect4") io.emit("connect4gameOver", { game, winner: winnerId });
|
||||||
|
if (gameType === "snake") io.emit("snakegameOver", { game, winner: winnerId });
|
||||||
|
|
||||||
if (gameKey) {
|
if (gameKey) {
|
||||||
setTimeout(() => delete activeGames[gameKey], 1000);
|
setTimeout(() => delete activeGames[gameKey], 1000);
|
||||||
@@ -251,8 +309,7 @@ async function createGame(client, gameType) {
|
|||||||
gameOver: false,
|
gameOver: false,
|
||||||
lastmove: Date.now(),
|
lastmove: Date.now(),
|
||||||
};
|
};
|
||||||
} else {
|
} else if (gameType === "connect4") {
|
||||||
// connect4
|
|
||||||
lobby = {
|
lobby = {
|
||||||
p1: {
|
p1: {
|
||||||
id: p1Id,
|
id: p1Id,
|
||||||
@@ -272,6 +329,31 @@ async function createGame(client, gameType) {
|
|||||||
lastmove: Date.now(),
|
lastmove: Date.now(),
|
||||||
winningPieces: [],
|
winningPieces: [],
|
||||||
};
|
};
|
||||||
|
} else if (gameType === "snake") {
|
||||||
|
lobby = {
|
||||||
|
p1: {
|
||||||
|
id: p1Id,
|
||||||
|
name: p1.globalName,
|
||||||
|
avatar: p1.displayAvatarURL({ dynamic: true, size: 256 }),
|
||||||
|
snake: [],
|
||||||
|
food: null,
|
||||||
|
score: 0,
|
||||||
|
gameOver: false,
|
||||||
|
win: false,
|
||||||
|
},
|
||||||
|
p2: {
|
||||||
|
id: p2Id,
|
||||||
|
name: p2.globalName,
|
||||||
|
avatar: p2.displayAvatarURL({ dynamic: true, size: 256 }),
|
||||||
|
snake: [],
|
||||||
|
food: null,
|
||||||
|
score: 0,
|
||||||
|
gameOver: false,
|
||||||
|
win: false,
|
||||||
|
},
|
||||||
|
gameOver: false,
|
||||||
|
lastmove: Date.now(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgId = await updateDiscordMessage(client, lobby, title);
|
const msgId = await updateDiscordMessage(client, lobby, title);
|
||||||
@@ -328,8 +410,29 @@ async function refreshQueuesForUser(userId, client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index = snakeQueue.indexOf(userId);
|
||||||
|
if (index > -1) {
|
||||||
|
snakeQueue.splice(index, 1);
|
||||||
|
try {
|
||||||
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
|
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||||
|
const user = await client.users.fetch(userId);
|
||||||
|
const queueMsg = await generalChannel.messages.fetch(queueMessagesEndpoints[userId]);
|
||||||
|
const updatedEmbed = new EmbedBuilder()
|
||||||
|
.setTitle("Snake 1v1")
|
||||||
|
.setDescription(`**${user.globalName || user.username}** a quitté la file d'attente.`)
|
||||||
|
.setColor(0xed4245)
|
||||||
|
.setTimestamp(new Date());
|
||||||
|
await queueMsg.edit({ embeds: [updatedEmbed], components: [] });
|
||||||
|
delete queueMessagesEndpoints[userId];
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error updating queue message : ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await emitQueueUpdate(client, "tictactoe");
|
await emitQueueUpdate(client, "tictactoe");
|
||||||
await emitQueueUpdate(client, "connect4");
|
await emitQueueUpdate(client, "connect4");
|
||||||
|
await emitQueueUpdate(client, "snake");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function emitQueueUpdate(client, gameType) {
|
async function emitQueueUpdate(client, gameType) {
|
||||||
@@ -361,6 +464,13 @@ function getGameAssets(gameType) {
|
|||||||
title: "Puissance 4",
|
title: "Puissance 4",
|
||||||
url: "/connect-4",
|
url: "/connect-4",
|
||||||
};
|
};
|
||||||
|
if (gameType === "snake")
|
||||||
|
return {
|
||||||
|
queue: snakeQueue,
|
||||||
|
activeGames: activeSnakeGames,
|
||||||
|
title: "Snake 1v1",
|
||||||
|
url: "/snake",
|
||||||
|
};
|
||||||
return { queue: [], activeGames: {} };
|
return { queue: [], activeGames: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,8 +511,10 @@ async function updateDiscordMessage(client, game, title, resultText = "") {
|
|||||||
if (i % 3 === 0) gridText += "\n";
|
if (i % 3 === 0) gridText += "\n";
|
||||||
}
|
}
|
||||||
description = `### **❌ ${game.p1.name}** vs **${game.p2.name} ⭕**\n${gridText}`;
|
description = `### **❌ ${game.p1.name}** vs **${game.p2.name} ⭕**\n${gridText}`;
|
||||||
} else {
|
} else if (title === "Puissance 4") {
|
||||||
description = `**🔴 ${game.p1.name}** vs **${game.p2.name} 🟡**\n\n${formatConnect4BoardForDiscord(game.board)}`;
|
description = `**🔴 ${game.p1.name}** vs **${game.p2.name} 🟡**\n\n${formatConnect4BoardForDiscord(game.board)}`;
|
||||||
|
} else if (title === "Snake 1v1") {
|
||||||
|
description = `**🐍 ${game.p1.name}** (${game.p1.score}) vs **${game.p2.name} 🐍** (${game.p2.score})`;
|
||||||
}
|
}
|
||||||
if (resultText) description += `\n### ${resultText}`;
|
if (resultText) description += `\n### ${resultText}`;
|
||||||
|
|
||||||
@@ -438,6 +550,7 @@ function cleanupStaleGames() {
|
|||||||
};
|
};
|
||||||
cleanup(activeTicTacToeGames, "TicTacToe");
|
cleanup(activeTicTacToeGames, "TicTacToe");
|
||||||
cleanup(activeConnect4Games, "Connect4");
|
cleanup(activeConnect4Games, "Connect4");
|
||||||
|
cleanup(activeSnakeGames, "Snake");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EMITS */
|
/* EMITS */
|
||||||
|
|||||||
Reference in New Issue
Block a user