almost there

This commit is contained in:
Milo
2025-07-31 15:47:32 +02:00
parent a5c262270a
commit a9912e7ef7
9 changed files with 559 additions and 156 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -37,7 +37,12 @@ server.listen(PORT, async () => {
console.log(`[Connected with ${FLAPI_URL}]`);
// Initial data fetch and setup
await getAkhys(client);
try {
await getAkhys(client);
} catch (error) {
console.log('Initial Fetch Error');
}
// Setup scheduled tasks
setupCronJobs(client, io);

View File

@@ -14,10 +14,6 @@ export function initializeEvents(client, io) {
// It's a good place for setup tasks that require the bot to be online.
client.once('ready', async () => {
console.log(`Bot is ready and logged in as ${client.user.tag}!`);
// You can add any post-login setup tasks here if needed.
// For example, setting the bot's activity:
client.user.setActivity('FlopoSite.com', { type: 'WATCHING' });
console.log('[Startup] Bot is ready, performing initial data sync...');
await getAkhys(client);
console.log('[Startup] Setting up scheduled tasks...');

View File

@@ -8,9 +8,11 @@ import { client } from '../bot/client.js';
import { apiRoutes } from './routes/api.js';
import { pokerRoutes } from './routes/poker.js';
import { solitaireRoutes } from './routes/solitaire.js';
import {getSocketIo} from "./socket.js";
// --- EXPRESS APP INITIALIZATION ---
const app = express();
const io = getSocketIo();
const FLAPI_URL = process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL;
// --- GLOBAL MIDDLEWARE ---
@@ -39,13 +41,13 @@ app.use('/public', express.static('public'));
// --- API ROUTES ---
// General API routes (users, polls, etc.)
app.use('/', apiRoutes(client));
app.use('/api', apiRoutes(client, io));
// Poker-specific routes
app.use('/', pokerRoutes(client));
app.use('/api/poker', pokerRoutes(client, io));
// Solitaire-specific routes
app.use('/solitaire', solitaireRoutes(client));
app.use('/api/solitaire', solitaireRoutes(client, io));
export { app };

View File

@@ -17,6 +17,8 @@ import { DiscordRequest } from '../../api/discord.js';
// --- Discord.js Builder Imports ---
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import {emitDataUpdated, socketEmit} from "../socket.js";
import {formatTime} from "../../../utils.js";
// Create a new router instance
const router = express.Router();
@@ -123,7 +125,7 @@ export function apiRoutes(client, io) {
}
});
router.post('/user/:id/daily', (req, res) => {
router.post('/user/:id/daily', async (req, res) => {
const { id } = req.params;
try {
const akhy = getUser.get(id);
@@ -139,7 +141,7 @@ export function apiRoutes(client, io) {
coins_amount: amount, user_new_amount: newCoins,
});
io.emit('data-updated', { table: 'users' });
await emitDataUpdated({ table: 'users' });
res.status(200).json({ message: `+${amount} FlopoCoins! Récompense récupérée !` });
} catch (error) {
res.status(500).json({ error: "Failed to process daily reward." });
@@ -179,11 +181,14 @@ export function apiRoutes(client, io) {
const newCoins = commandUser.coins - 1000;
updateUserCoins.run({ id: commandUserId, coins: newCoins });
insertLog.run({
id: `${commandUserId}-changenick-${Date.now()}`, user_id: commandUserId, action: 'CHANGE_NICKNAME',
target_user_id: userId, coins_amount: -1000, user_new_amount: newCoins,
id: `${commandUserId}-changenick-${Date.now()}`,
user_id: commandUserId,
action: 'CHANGE_NICKNAME',
target_user_id: userId,
coins_amount: -1000,
user_new_amount: newCoins,
});
io.emit('data-updated', { table: 'users' });
res.status(200).json({ message: `Le pseudo de ${member.user.username} a été changé.` });
} catch (error) {
res.status(500).json({ message: `Erreur: Impossible de changer le pseudo.` });
@@ -191,8 +196,44 @@ export function apiRoutes(client, io) {
});
router.post('/spam-ping', async (req, res) => {
// Implement spam-ping logic here...
res.status(501).json({ message: "Not Implemented" });
const { userId, commandUserId } = req.body;
const user = getUser.get(userId);
const commandUser = getUser.get(commandUserId);
if (!commandUser || !user) return res.status(404).json({ message: 'Oups petit soucis' });
if (commandUser.coins < 10000) return res.status(403).json({ message: 'Pas assez de coins' });
try {
const discordUser = await client.users.fetch(userId);
await discordUser.send(`<@${userId}>`)
res.status(200).json({ message : 'C\'est parti ehehe' });
updateUserCoins.run({
id: commandUserId,
coins: commandUser.coins - 10000,
})
insertLog.run({
id: commandUserId + '-' + Date.now(),
user_id: commandUserId,
action: 'SPAM_PING',
target_user_id: userId,
coins_amount: -10000,
user_new_amount: commandUser.coins - 10000,
})
await emitDataUpdated({ table: 'users', action: 'update' });
for (let i = 0; i < 29; i++) {
await discordUser.send(`<@${userId}>`)
await sleep(1000);
}
} catch (err) {
console.log(err)
res.status(500).json({ message : "Oups ça n'a pas marché" });
}
});
// --- Slowmode Routes ---
@@ -201,9 +242,53 @@ export function apiRoutes(client, io) {
res.status(200).json({ slowmodes: activeSlowmodes });
});
router.post('/slowmode', (req, res) => {
// Implement slowmode logic here...
res.status(501).json({ message: "Not Implemented" });
router.post('/slowmode', async (req, res) => {
let { userId, commandUserId} = req.body
const user = getUser.get(userId)
const commandUser = getUser.get(commandUserId);
if (!commandUser || !user) return res.status(404).json({ message: 'Oups petit soucis' });
if (commandUser.coins < 10000) return res.status(403).json({ message: 'Pas assez de coins' });
if (!user) return res.status(403).send({ message: 'Oups petit problème'})
if (activeSlowmodes[userId]) {
if (userId === commandUserId) {
delete activeSlowmodes[userId];
return res.status(200).json({ message: 'Slowmode retiré'})
} else {
let timeLeft = (activeSlowmodes[userId].endAt - Date.now())/1000
timeLeft = timeLeft > 60 ? (timeLeft/60).toFixed()?.toString() + 'min' : timeLeft.toFixed()?.toString() + 'sec'
return res.status(403).json({ message: `${user.globalName} est déjà en slowmode (${timeLeft})`})
}
} else if (userId === commandUserId) {
return res.status(403).json({ message: 'Impossible de te mettre toi-même en slowmode'})
}
activeSlowmodes[userId] = {
userId: userId,
endAt: Date.now() + 60 * 60 * 1000, // 1 heure
lastMessage: null,
};
await socketEmit('new-slowmode', { action: 'new slowmode' });
updateUserCoins.run({
id: commandUserId,
coins: commandUser.coins - 10000,
})
insertLog.run({
id: commandUserId + '-' + Date.now(),
user_id: commandUserId,
action: 'SLOWMODE',
target_user_id: userId,
coins_amount: -10000,
user_new_amount: commandUser.coins - 10000,
})
await emitDataUpdated({ table: 'users', action: 'update' });
return res.status(200).json({ message: `${user.globalName} est maintenant en slowmode pour 1h`})
});
// --- Prediction Routes ---
@@ -214,18 +299,275 @@ export function apiRoutes(client, io) {
});
router.post('/start-predi', async (req, res) => {
// Implement prediction start logic here...
res.status(501).json({ message: "Not Implemented" });
let { commandUserId, label, options, closingTime, payoutTime } = req.body
const commandUser = getUser.get(commandUserId)
if (!commandUser) return res.status(403).send({ message: 'Oups petit problème'})
if (commandUser.coins < 100) return res.status(403).send({ message: 'Tu n\'as pas assez de FlopoCoins'})
if (Object.values(activePredis).find(p => p.creatorId === commandUserId && (p.endTime > Date.now() && !p.closed))) {
return res.status(403).json({ message: `Tu ne peux pas lancer plus d'une prédi à la fois !`})
}
const startTime = Date.now()
const newPrediId = commandUserId?.toString() + '-' + startTime?.toString()
let msgId;
try {
const guild = await client.guilds.fetch(process.env.GUILD_ID);
const generalChannel = guild.channels.cache.find(
ch => ch.name === 'général' || ch.name === 'general'
);
const embed = new EmbedBuilder()
.setTitle(`Prédiction de ${commandUser.username}`)
.setDescription(`**${label}**`)
.addFields(
{ name: `${options[0]}`, value: ``, inline: true },
{ name: ``, value: `ou`, inline: true },
{ name: `${options[1]}`, value: ``, inline: true }
)
.setFooter({ text: `${formatTime(closingTime).replaceAll('*', '')} pour voter` })
.setColor('#5865f2')
.setTimestamp(new Date());
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`option_0_${newPrediId}`)
.setLabel(`+10 sur '${options[0]}'`)
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`option_1_${newPrediId}`)
.setLabel(`+10 sur '${options[1]}'`)
.setStyle(ButtonStyle.Primary)
);
const row2 = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setLabel('Voter sur FlopoSite')
.setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/dashboard`)
.setStyle(ButtonStyle.Link)
)
const msg = await generalChannel.send({ embeds: [embed], components: [/*row,*/ row2] });
msgId = msg.id;
} catch (e) {
console.log(e)
return res.status(500).send({ message: 'Erreur lors de l\'envoi du message'})
}
const formattedOptions = [
{ label: options[0], votes: [], total: 0, percent: 0, },
{ label: options[1], votes: [], total: 0, percent: 0, },
]
activePredis[newPrediId] = {
creatorId: commandUserId,
label: label,
options: formattedOptions,
startTime: startTime,
closingTime: startTime + (closingTime * 1000),
endTime: startTime + (closingTime * 1000) + (payoutTime * 1000),
closed: false,
winning: null,
cancelledTime: null,
paidTime: null,
msgId: msgId,
};
await socketEmit('new-predi', { action: 'new predi' });
updateUserCoins.run({
id: commandUserId,
coins: commandUser.coins - 100,
})
insertLog.run({
id: commandUserId + '-' + Date.now(),
user_id: commandUserId,
action: 'START_PREDI',
target_user_id: null,
coins_amount: -100,
user_new_amount: commandUser.coins - 100,
})
await emitDataUpdated({ table: 'users', action: 'update' });
return res.status(200).json({ message: `Ta prédi '${label}' a commencée !`})
});
router.post('/vote-predi', (req, res) => {
// Implement prediction vote logic here...
res.status(501).json({ message: "Not Implemented" });
router.post('/vote-predi', async (req, res) => {
const { commandUserId, predi, amount, option } = req.body
let warning = false;
let intAmount = parseInt(amount)
if (intAmount < 10 || intAmount > 250000) return res.status(403).send({ message: 'Montant invalide'})
const commandUser = getUser.get(commandUserId)
if (!commandUser) return res.status(403).send({ message: 'Oups, je ne te connais pas'})
if (commandUser.coins < intAmount) return res.status(403).send({ message: 'Tu n\'as pas assez de FlopoCoins'})
const prediObject = activePredis[predi]
if (!prediObject) return res.status(403).send({ message: 'Prédiction introuvable'})
if (prediObject.endTime < Date.now()) return res.status(403).send({ message: 'Les votes de cette prédiction sont clos'})
const otherOption = option === 0 ? 1 : 0;
if (prediObject.options[otherOption].votes.find(v => v.id === commandUserId) && commandUserId !== process.env.DEV_ID) return res.status(403).send({ message: 'Tu ne peux pas voter pour les 2 deux options'})
if (prediObject.options[option].votes.find(v => v.id === commandUserId)) {
activePredis[predi].options[option].votes.forEach(v => {
if (v.id === commandUserId) {
if (v.amount === 250000) {
return res.status(403).send({ message: 'Tu as déjà parié le max (250K)'})
}
if (v.amount + intAmount > 250000) {
intAmount = 250000-v.amount
warning = true
}
v.amount += intAmount
}
})
} else {
activePredis[predi].options[option].votes.push({
id: commandUserId,
amount: intAmount,
})
}
activePredis[predi].options[option].total += intAmount
activePredis[predi].options[option].percent = (activePredis[predi].options[option].total / (activePredis[predi].options[otherOption].total + activePredis[predi].options[option].total)) * 100
activePredis[predi].options[otherOption].percent = 100 - activePredis[predi].options[option].percent
await socketEmit('new-predi', { action: 'new vote' });
updateUserCoins.run({
id: commandUserId,
coins: commandUser.coins - intAmount,
})
insertLog.run({
id: commandUserId + '-' + Date.now(),
user_id: commandUserId,
action: 'PREDI_VOTE',
target_user_id: null,
coins_amount: -intAmount,
user_new_amount: commandUser.coins - intAmount,
})
await emitDataUpdated({ table: 'users', action: 'update' });
return res.status(200).send({ message : `Vote enregistré!` });
});
router.post('/end-predi', (req, res) => {
// Implement prediction end logic here...
res.status(501).json({ message: "Not Implemented" });
router.post('/end-predi', async (req, res) => {
const { commandUserId, predi, confirm, winningOption } = req.body
const commandUser = getUser.get(commandUserId)
if (!commandUser) return res.status(403).send({ message: 'Oups, je ne te connais pas'})
if (commandUserId !== process.env.DEV_ID) return res.status(403).send({ message: 'Tu n\'as pas les permissions requises' })
const prediObject = activePredis[predi]
if (!prediObject) return res.status(403).send({ message: 'Prédiction introuvable'})
if (prediObject.closed) return res.status(403).send({ message: 'Prédiction déjà close'})
if (!confirm) {
activePredis[predi].cancelledTime = new Date();
activePredis[predi].options[0].votes.forEach((v) => {
const tempUser = getUser.get(v.id)
try {
updateUserCoins.run({
id: v.id,
coins: tempUser.coins + v.amount
})
insertLog.run({
id: v.id + '-' + Date.now(),
user_id: v.id,
action: 'PREDI_REFUND',
target_user_id: v.id,
coins_amount: v.amount,
user_new_amount: tempUser.coins + v.amount,
})
} catch (e) {
console.log(`Impossible de rembourser ${v.id} (${v.amount} coins)`)
}
})
activePredis[predi].options[1].votes.forEach((v) => {
const tempUser = getUser.get(v.id)
try {
updateUserCoins.run({
id: v.id,
coins: tempUser.coins + v.amount
})
insertLog.run({
id: v.id + '-' + Date.now(),
user_id: v.id,
action: 'PREDI_REFUND',
target_user_id: v.id,
coins_amount: v.amount,
user_new_amount: tempUser.coins + v.amount,
})
} catch (e) {
console.log(`Impossible de rembourser ${v.id} (${v.amount} coins)`)
}
})
activePredis[predi].closed = true;
}
else {
const losingOption = winningOption === 0 ? 1 : 0;
activePredis[predi].options[winningOption].votes.forEach((v) => {
const tempUser = getUser.get(v.id)
const ratio = activePredis[predi].options[winningOption].total === 0 ? 0 : activePredis[predi].options[losingOption].total / activePredis[predi].options[winningOption].total
try {
updateUserCoins.run({
id: v.id,
coins: tempUser.coins + (v.amount * (1 + ratio))
})
insertLog.run({
id: v.id + '-' + Date.now(),
user_id: v.id,
action: 'PREDI_RESULT',
target_user_id: v.id,
coins_amount: v.amount * (1 + ratio),
user_new_amount: tempUser.coins + (v.amount * (1 + ratio)),
})
} catch (e) {
console.log(`Impossible de créditer ${v.id} (${v.amount} coins pariés, *${1 + ratio})`)
}
})
activePredis[predi].paidTime = new Date();
activePredis[predi].closed = true;
activePredis[predi].winning = winningOption;
}
try {
const guild = await client.guilds.fetch(process.env.GUILD_ID);
const generalChannel = guild.channels.cache.find(
ch => ch.name === 'général' || ch.name === 'general'
);
const message = await generalChannel.messages.fetch(activePredis[predi].msgId)
const updatedEmbed = new EmbedBuilder()
.setTitle(`Prédiction de ${commandUser.username}`)
.setDescription(`**${activePredis[predi].label}**`)
.setFields({ name: `${activePredis[predi].options[0].label}`, value: ``, inline: true },
{ name: ``, value: `ou`, inline: true },
{ name: `${activePredis[predi].options[1].label}`, value: ``, inline: true },
)
.setFooter({ text: `${activePredis[predi].cancelledTime !== null ? 'Prédi annulée' : 'Prédi confirmée !' }` })
.setTimestamp(new Date());
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setLabel('Voir')
.setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/dashboard`)
.setStyle(ButtonStyle.Link)
)
await message.edit({ embeds: [updatedEmbed], components: [row] });
} catch (err) {
console.error('Error updating prédi message:', err);
}
await socketEmit('new-predi', { action: 'closed predi' });
await emitDataUpdated({ table: 'users', action: 'fin predi' });
return res.status(200).json({ message: 'Prédi close' });
});
// --- Admin Routes ---
@@ -242,7 +584,6 @@ export function apiRoutes(client, io) {
coins_amount: coins, user_new_amount: newCoins
});
io.emit('data-updated', { table: 'users' });
res.status(200).json({ message: `Added ${coins} coins.` });
});

View File

@@ -1,32 +1,34 @@
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import { uniqueNamesGenerator, adjectives } from 'unique-names-generator';
import pkg from 'pokersolver';
const { Hand } = pkg;
import { pokerRooms } from '../../game/state.js';
import { initialShuffledCards, getFirstActivePlayerAfterDealer, getNextActivePlayer, checkEndOfBettingRound, checkRoomWinners } from '../../game/poker.js';
import { pokerEloHandler } from '../../game/elo.js';
import { getUser, updateUserCoins, insertLog } from '../../database/index.js';
import {sleep} from "openai/core";
import { sleep } from "openai/core";
import {client} from "../../bot/client.js";
import { io } from '../../../index.js'
import {emitPokerToast, emitPokerUpdate} from "../socket.js";
// Create a new router instance
const router = express.Router();
/**
* Factory function to create and configure the poker API routes.
* @param {object} client - The Discord.js client instance.
* @param {object} io - The Socket.IO server instance. // FIX: Pass io in as a parameter
* @returns {object} The configured Express router.
*/
export function pokerRoutes(client) {
export function pokerRoutes(client, io) {
// --- Room Management Endpoints ---
router.get('/poker-rooms', (req, res) => {
router.get('/', (req, res) => {
res.status(200).json({ rooms: pokerRooms });
});
router.get('/poker-rooms/:id', (req, res) => {
router.get('/:id', (req, res) => {
const room = pokerRooms[req.params.id];
if (room) {
res.status(200).json({ room });
@@ -35,7 +37,7 @@ export function pokerRoutes(client) {
}
});
router.post('/create-poker-room', async (req, res) => {
router.post('/create', async (req, res) => {
const { creatorId } = req.body;
if (!creatorId) return res.status(400).json({ message: 'Creator ID is required.' });
@@ -48,57 +50,53 @@ export function pokerRoutes(client) {
const name = uniqueNamesGenerator({ dictionaries: [adjectives, ['Poker']], separator: ' ', style: 'capital' });
pokerRooms[id] = {
id,
host_id: creatorId,
host_name: creator.globalName || creator.username,
name,
created_at: Date.now(),
last_move_at: Date.now(),
players: {},
queue: {},
afk: {},
pioche: initialShuffledCards(),
tapis: [],
dealer: null,
sb: null,
bb: null,
highest_bet: 0,
current_player: null,
current_turn: null, // 0: pre-flop, 1: flop, 2: turn, 3: river, 4: showdown
playing: false,
winners: [],
waiting_for_restart: false,
fakeMoney: false,
id, host_id: creatorId, host_name: creator.globalName || creator.username,
name, created_at: Date.now(), last_move_at: Date.now(),
players: {}, queue: {}, afk: {}, pioche: initialShuffledCards(), tapis: [],
dealer: null, sb: null, bb: null, highest_bet: 0, current_player: null,
current_turn: null, playing: false, winners: [], waiting_for_restart: false, fakeMoney: false,
};
// Auto-join the creator to their own room
await joinRoom(id, creatorId, io);
io.emit('poker-update', { type: 'room-created', roomId: id });
await joinRoom(id, creatorId, io); // Auto-join the creator
await emitPokerUpdate({ room: pokerRooms[id] });
res.status(201).json({ roomId: id });
});
router.post('/poker-room/join', async (req, res) => {
router.post('/join', async (req, res) => {
const { userId, roomId } = req.body;
if (!userId || !roomId) return res.status(400).json({ message: 'User ID and Room ID are required.' });
if (!pokerRooms[roomId]) return res.status(404).json({ message: 'Room not found.' });
if (Object.values(pokerRooms).some(r => r.players[userId])) {
return res.status(403).json({ message: 'You are already in a room.' });
if (Object.values(pokerRooms).some(r => r.players[userId] || r.queue[userId])) {
return res.status(403).json({ message: 'You are already in a room or queue.' });
}
await joinRoom(roomId, userId, io);
res.status(200).json({ message: 'Successfully joined room.' });
res.status(200).json({ message: 'Successfully joined.' });
});
router.post('poker-room/leave', (req, res) => {
// NEW: Endpoint to accept a player from the queue
router.post('/accept', async (req, res) => {
const { hostId, playerId, roomId } = req.body;
const room = pokerRooms[roomId];
if (!room || room.host_id !== hostId || !room.queue[playerId]) {
return res.status(403).json({ message: 'Unauthorized or player not in queue.' });
}
room.players[playerId] = room.queue[playerId];
delete room.queue[playerId];
await emitPokerUpdate({ room: room });
res.status(200).json({ message: 'Player accepted.' });
});
router.post('/leave', (req, res) => {
// Implement leave logic...
res.status(501).json({ message: "Not Implemented" });
});
// --- Game Action Endpoints ---
router.post('/poker-room/start', async (req, res) => {
router.post('/start', async (req, res) => {
const { roomId } = req.body;
const room = pokerRooms[roomId];
if (!room) return res.status(404).json({ message: 'Room not found.' });
@@ -108,7 +106,18 @@ export function pokerRoutes(client) {
res.status(200).json({ message: 'Game started.' });
});
router.post('/poker-room/action/:action', async (req, res) => {
// NEW: Endpoint to start the next hand
router.post('/next-hand', async (req, res) => {
const { roomId } = req.body;
const room = pokerRooms[roomId];
if (!room || !room.waiting_for_restart) {
return res.status(400).json({ message: 'Not ready for the next hand.' });
}
await startNewHand(room, io);
res.status(200).json({ message: 'Next hand started.' });
});
router.post('/action/:action', async (req, res) => {
const { playerId, amount, roomId } = req.body;
const { action } = req.params;
const room = pokerRooms[roomId];
@@ -122,38 +131,53 @@ export function pokerRoutes(client) {
switch(action) {
case 'fold':
player.folded = true;
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
await emitPokerToast({
type: 'player-fold',
playerId: player.id,
playerName: player.globalName,
roomId: room.id,
})
break;
case 'check':
if (player.bet < room.highest_bet) return res.status(400).json({ message: 'Cannot check, you must call or raise.' });
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
if (player.bet < room.highest_bet) return res.status(400).json({ message: 'Cannot check.' });
await emitPokerToast({
type: 'player-check',
playerId: player.id,
playerName: player.globalName,
roomId: room.id,
})
break;
case 'call':
const callAmount = room.highest_bet - player.bet;
if (callAmount > player.bank) { // All-in call
player.bet += player.bank;
player.bank = 0;
player.allin = true;
} else {
player.bet += callAmount;
player.bank -= callAmount;
}
const callAmount = Math.min(room.highest_bet - player.bet, player.bank);
player.bank -= callAmount;
player.bet += callAmount;
if (player.bank === 0) player.allin = true;
updatePlayerCoins(player, -callAmount, room.fakeMoney);
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
await emitPokerToast({
type: 'player-call',
playerId: player.id,
playerName: player.globalName,
roomId: room.id,
})
break;
case 'raise':
const totalBet = player.bet + amount;
if (amount > player.bank || totalBet <= room.highest_bet) return res.status(400).json({ message: 'Invalid raise amount.' });
player.bet = totalBet;
if (amount <= 0 || amount > player.bank || (player.bet + amount) <= room.highest_bet) {
return res.status(400).json({ message: 'Invalid raise amount.' });
}
player.bank -= amount;
if(player.bank === 0) player.allin = true;
room.highest_bet = totalBet;
player.bet += amount;
if (player.bank === 0) player.allin = true;
room.highest_bet = player.bet;
updatePlayerCoins(player, -amount, room.fakeMoney);
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, amount, globalName: player.globalName });
await emitPokerToast({
type: 'player-raise',
amount: amount,
playerId: player.id,
playerName: player.globalName,
roomId: room.id,
})
break;
default:
return res.status(400).json({ message: 'Invalid action.' });
default: return res.status(400).json({ message: 'Invalid action.' });
}
player.last_played_turn = room.current_turn;
@@ -170,26 +194,34 @@ export function pokerRoutes(client) {
async function joinRoom(roomId, userId, io) {
const user = await client.users.fetch(userId);
const userDB = getUser.get(userId);
const bank = userDB?.coins >= 1000 ? userDB.coins : 1000;
const isFake = userDB?.coins < 1000;
const bank = (userDB?.coins ?? 0) >= 1000 ? userDB.coins : 1000;
const isFake = (userDB?.coins ?? 0) < 1000;
pokerRooms[roomId].players[userId] = {
id: userId,
globalName: user.globalName || user.username,
hand: [],
bank: bank,
bet: 0,
folded: false,
allin: false,
last_played_turn: null,
const playerObject = {
id: userId, globalName: user.globalName || user.username,
hand: [], bank: bank, bet: 0, folded: false, allin: false,
last_played_turn: null, solve: null
};
if(isFake) pokerRooms[roomId].fakeMoney = true;
const room = pokerRooms[roomId];
if (room.playing) {
room.queue[userId] = playerObject;
} else {
room.players[userId] = playerObject;
}
io.emit('poker-update', { type: 'player-join', roomId, player: pokerRooms[roomId].players[userId] });
if (isFake) room.fakeMoney = true;
await emitPokerUpdate({ room: room, type: 'player-joined' });
}
async function startNewHand(room, io) {
const playerIds = Object.keys(room.players);
if (playerIds.length < 2) {
room.playing = false; // Not enough players to continue
await emitPokerUpdate({ room: room });
return;
}
room.playing = true;
room.current_turn = 0; // Pre-flop
room.pioche = initialShuffledCards();
@@ -198,32 +230,31 @@ async function startNewHand(room, io) {
room.waiting_for_restart = false;
room.highest_bet = 20;
// Reset players for the new hand
// Rotate dealer
const oldDealerIndex = playerIds.indexOf(room.dealer);
room.dealer = playerIds[(oldDealerIndex + 1) % playerIds.length];
Object.values(room.players).forEach(p => {
p.hand = [room.pioche.pop(), room.pioche.pop()];
p.bet = 0;
p.folded = false;
p.allin = false;
p.last_played_turn = null;
p.bet = 0; p.folded = false; p.allin = false; p.last_played_turn = null;
});
updatePlayerHandSolves(room); // NEW: Calculate initial hand strength
// Handle blinds
const playerIds = Object.keys(room.players);
const sbPlayer = room.players[playerIds[0]];
const bbPlayer = room.players[playerIds[1]];
// Handle blinds based on new dealer
const dealerIndex = playerIds.indexOf(room.dealer);
const sbPlayer = room.players[playerIds[(dealerIndex + 1) % playerIds.length]];
const bbPlayer = room.players[playerIds[(dealerIndex + 2) % playerIds.length]];
room.sb = sbPlayer.id;
room.bb = bbPlayer.id;
sbPlayer.bet = 10;
sbPlayer.bank -= 10;
sbPlayer.bank -= 10; sbPlayer.bet = 10;
updatePlayerCoins(sbPlayer, -10, room.fakeMoney);
bbPlayer.bet = 20;
bbPlayer.bank -= 20;
bbPlayer.bank -= 20; bbPlayer.bet = 20;
updatePlayerCoins(bbPlayer, -20, room.fakeMoney);
bbPlayer.last_played_turn = 0;
room.current_player = playerIds[2 % playerIds.length];
io.emit('poker-update', { type: 'new-hand', room });
room.current_player = playerIds[(dealerIndex + 3) % playerIds.length];
await emitPokerUpdate({ room: room, type: 'room-started' });
}
async function checkRoundCompletion(room, io) {
@@ -232,27 +263,23 @@ async function checkRoundCompletion(room, io) {
if (roundResult.endRound) {
if (roundResult.winner) {
// Handle single winner case (everyone else folded)
await handleShowdown(room, io, [roundResult.winner]);
} else {
// Proceed to the next phase
await advanceToNextPhase(room, io, roundResult.nextPhase);
}
} else {
// Continue the round
room.current_player = getNextActivePlayer(room);
io.emit('poker-update', { type: 'next-player', room });
await emitPokerUpdate({ room: room });
}
}
async function advanceToNextPhase(room, io, phase) {
// Reset player turn markers for the new betting round
Object.values(room.players).forEach(p => p.last_played_turn = null);
Object.values(room.players).forEach(p => { if (!p.folded) p.last_played_turn = null; });
switch(phase) {
case 'flop':
room.current_turn = 1;
room.tapis = [room.pioche.pop(), room.pioche.pop(), room.pioche.pop()];
room.tapis.push(room.pioche.pop(), room.pioche.pop(), room.pioche.pop());
break;
case 'turn':
room.current_turn = 2;
@@ -263,23 +290,22 @@ async function advanceToNextPhase(room, io, phase) {
room.tapis.push(room.pioche.pop());
break;
case 'showdown':
const winners = checkRoomWinners(room);
await handleShowdown(room, io, winners);
await handleShowdown(room, io, checkRoomWinners(room));
return;
case 'progressive-showdown':
// Show cards and deal remaining community cards one by one
io.emit('poker-update', { type: 'show-cards', room });
await emitPokerUpdate({ room: room });
while(room.tapis.length < 5) {
await sleep(1500);
await sleep(500);
room.tapis.push(room.pioche.pop());
io.emit('poker-update', { type: 'community-card-deal', room });
updatePlayerHandSolves(room);
await emitPokerUpdate({ room: room });
}
const finalWinners = checkRoomWinners(room);
await handleShowdown(room, io, finalWinners);
await handleShowdown(room, io, checkRoomWinners(room));
return;
}
updatePlayerHandSolves(room); // NEW: Update hand strength after new cards
room.current_player = getFirstActivePlayerAfterDealer(room);
io.emit('poker-update', { type: 'phase-change', room });
await emitPokerUpdate({ room: room });
}
async function handleShowdown(room, io, winners) {
@@ -288,17 +314,37 @@ async function handleShowdown(room, io, winners) {
room.waiting_for_restart = true;
room.winners = winners;
const totalPot = Object.values(room.players).reduce((sum, p) => sum + p.bet, 0);
const winAmount = Math.floor(totalPot / winners.length);
let totalPot = 0;
Object.values(room.players).forEach(p => { totalPot += p.bet; });
const winAmount = winners.length > 0 ? Math.floor(totalPot / winners.length) : 0;
winners.forEach(winnerId => {
const winnerPlayer = room.players[winnerId];
winnerPlayer.bank += winAmount;
updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney);
if(winnerPlayer) {
winnerPlayer.bank += winAmount;
updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney);
}
});
//await pokerEloHandler(room);
io.emit('poker-update', { type: 'showdown', room, winners, winAmount });
await emitPokerUpdate({ room: room });
await emitPokerToast({
type: 'player-winner',
playerIds: winners,
roomId: room.id,
})
}
// NEW: Function to calculate and update hand strength for all players
function updatePlayerHandSolves(room) {
const communityCards = room.tapis;
for (const player of Object.values(room.players)) {
if (!player.folded) {
const allCards = [...communityCards, ...player.hand];
player.solve = Hand.solve(allCards).descr;
}
}
}
function updatePlayerCoins(player, amount, isFake) {
@@ -306,14 +352,12 @@ function updatePlayerCoins(player, amount, isFake) {
const user = getUser.get(player.id);
if (!user) return;
const newCoins = user.coins + amount;
const newCoins = player.bank; // The bank is the source of truth now
updateUserCoins.run({ id: player.id, coins: newCoins });
insertLog.run({
id: `${player.id}-poker-${Date.now()}`,
user_id: player.id,
target_user_id: null,
user_id: player.id, target_user_id: null,
action: `POKER_${amount > 0 ? 'WIN' : 'BET'}`,
coins_amount: amount,
user_new_amount: newCoins,
coins_amount: amount, user_new_amount: newCoins,
});
}

View File

@@ -39,18 +39,19 @@ export function solitaireRoutes(client, io) {
if (userSeed) {
// Use the provided seed to create a deterministic game
seed = userSeed;
let numericSeed = 0;
for (let i = 0; i < seed.length; i++) {
numericSeed = (numericSeed + seed.charCodeAt(i)) & 0xFFFFFFFF;
}
const rng = createSeededRNG(numericSeed);
deck = seededShuffle(createDeck(), rng);
} else {
// Create a standard random game
// Create a random seed if none is provided
seed = Date.now().toString(36) + Math.random().toString(36).substr(2);
deck = shuffle(createDeck());
}
let numericSeed = 0;
for (let i = 0; i < seed.length; i++) {
numericSeed = (numericSeed + seed.charCodeAt(i)) & 0xFFFFFFFF;
}
const rng = createSeededRNG(numericSeed);
deck = seededShuffle(createDeck(), rng);
const gameState = deal(deck);
gameState.seed = seed;
gameState.isSOTD = false;

View File

@@ -13,8 +13,6 @@ export function initializeSocket(server, client) {
io = server;
io.on('connection', (socket) => {
console.log(`[Socket.IO] User connected: ${socket.id}`);
socket.on('user-connected', async (userId) => {
if (!userId) return;
await refreshQueuesForUser(userId, client);
@@ -24,7 +22,7 @@ export function initializeSocket(server, client) {
registerConnect4Events(socket, client);
socket.on('disconnect', () => {
console.log(`[Socket.IO] User disconnected: ${socket.id}`);
//
});
});
@@ -288,4 +286,20 @@ function cleanupStaleGames() {
};
cleanup(activeTicTacToeGames, 'TicTacToe');
cleanup(activeConnect4Games, 'Connect4');
}
/* EMITS */
export async function socketEmit(event, data) {
io.emit(event, data);
}
export async function emitDataUpdated(data) {
io.emit('data-updated', data);
}
export async function emitPokerUpdate(data) {
io.emit('poker-update', data);
}
export async function emitPokerToast(data) {
io.emit('poker-toast', data);
}