flopobot V2.1

This commit is contained in:
Milo
2025-08-01 16:15:30 +02:00
parent a9912e7ef7
commit 7cfcaa3bba
11 changed files with 137 additions and 6392 deletions

1
.idea/sqldialects.xml generated
View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/init_database.js" dialect="SQLite" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

Binary file not shown.

Binary file not shown.

711
game.js
View File

@@ -1,711 +0,0 @@
import { capitalize } from './utils.js';
import pkg from 'pokersolver';
const { Hand } = pkg;
import {
updateUserCoins,
getUser,
insertLog,
insertGame,
getUserElo,
insertElos,
updateElo,
getAllSkins, deleteSOTD, insertSOTD, clearSOTDStats, getAllSOTDStats
} from './init_database.js'
import {C4_COLS, C4_ROWS, skins} from "./index.js";
const messagesTimestamps = new Map();
const TimesChoices = [
{
name: '1 minute',
value: 60,
},
{
name: '5 minutes',
value: 300,
},
{
name: '10 minutes',
value: 600,
},
{
name: '15 minutes',
value: 900,
},
{
name: '30 minutes',
value: 1800,
},
{
name: '1 heure',
value: 3600,
},
{
name: '2 heures',
value: 3600,
},
{
name: '3 heures',
value: 10800,
},
{
name: '6 heures',
value: 21600,
},
{
name: '9 heures',
value: 32400,
},
{
name: '12 heures',
value: 43200,
},
{
name: '16 heures',
value: 57600,
},
{
name: '1 jour',
value: 86400,
},
/*{
name: '2 journées',
value: 172800,
},
{
name: '1 semaine',
value: 604800,
},
{
name: '2 semaines',
value: 604800 * 2,
},*/
];
export function getTimesChoices() {
return TimesChoices
}
export function channelPointsHandler(msg) {
const author = msg.author
const authorDB = getUser.get(author.id)
if (!authorDB) {
console.log("message from an unknown user")
return
}
if (msg.content.length < 3 || msg.content.startsWith('.')) return
const now = Date.now();
const timestamps = messagesTimestamps.get(author.id) || [];
// Remove all timestamps if first one is older than 15 minutes
const updatedTimestamps = now - timestamps[0] < 900000 ? timestamps : [];
updatedTimestamps.push(now);
messagesTimestamps.set(author.id, updatedTimestamps);
if (messagesTimestamps.get(author.id).length <= 10) {
// +10 or +50 coins
let coins = messagesTimestamps.get(author.id).length === 10
? 50
: 10
updateUserCoins.run({
id: author.id,
coins: authorDB.coins + coins,
})
insertLog.run({
id: author.id + '-' + Date.now(),
user_id: author.id,
action: 'AUTOCOINS',
target_user_id: null,
coins_amount: coins,
user_new_amount: authorDB.coins + coins,
})
}
}
export async function slowmodesHandler(msg, activeSlowmodes) {
const author = msg.author
const authorDB = getUser.get(author.id)
const authorSlowmode = activeSlowmodes[author.id]
if (!authorDB) return false
if (!authorSlowmode) return false
console.log('Message from a slowmode user')
const now = Date.now();
if (now > authorSlowmode.endAt) {
console.log('Slow mode is over')
delete activeSlowmodes[author.id]
return true
}
if (authorSlowmode.lastMessage && (authorSlowmode.lastMessage + 60 * 1000) > now) {
await msg.delete()
console.log('Message deleted')
} else {
authorSlowmode.lastMessage = Date.now()
}
return false
}
export async function eloHandler(p1, p2, p1score, p2score, type) {
const p1DB = getUser.get(p1)
const p2DB = getUser.get(p2)
if (!p1DB || !p2DB) return
let p1elo = await getUserElo.get({ id: p1 })
let p2elo = await getUserElo.get({ id: p2 })
if (!p1elo) {
await insertElos.run({
id: p1?.toString(),
elo: 100,
})
p1elo = await getUserElo.get({ id: p1 })
}
if (!p2elo) {
await insertElos.run({
id: p2?.toString(),
elo: 100,
})
p2elo = await getUserElo.get({ id: p2 })
}
if (p1score === p2score) {
insertGame.run({
id: p1?.toString() + '-' + p2?.toString() + '-' + Date.now()?.toString(),
p1: p1,
p2: p2,
p1_score: p1score,
p2_score: p2score,
p1_elo: p1elo.elo,
p2_elo: p2elo.elo,
p1_new_elo: p1elo.elo,
p2_new_elo: p2elo.elo,
type: type,
timestamp: Date.now(),
})
return
}
const prob1 = 1 / (1 + Math.pow(10, (p2elo.elo - p1elo.elo)/400))
const prob2 = 1 / (1 + Math.pow(10, (p1elo.elo - p2elo.elo)/400))
const p1newElo = Math.max(Math.floor(p1elo.elo + 10 * (p1score - prob1)), 0)
const p2newElo = Math.max(Math.floor(p2elo.elo + 10 * (p2score - prob2)), 0)
console.log(`${p1} elo update : ${p1elo.elo} -> ${p1newElo}`)
console.log(`${p2} elo update : ${p2elo.elo} -> ${p2newElo}`)
updateElo.run({ id: p1, elo: p1newElo })
updateElo.run({ id: p2, elo: p2newElo })
insertGame.run({
id: p1?.toString() + '-' + p2?.toString() + '-' + Date.now()?.toString(),
p1: p1,
p2: p2,
p1_score: p1score,
p2_score: p2score,
p1_elo: p1elo.elo,
p2_elo: p2elo.elo,
p1_new_elo: p1newElo,
p2_new_elo: p2newElo,
type: type,
timestamp: Date.now(),
})
}
export async function pokerEloHandler(room) {
if (room.fakeMoney) return
let DBplayers = []
Object.keys(room.players).forEach(playerId => {
const DBuser = getUser.get(playerId)
if (DBuser) {
DBplayers.push(DBuser)
}
})
const winnerIds = new Set(room.winners)
const playerCount = Object.keys(room.players).length
const baseK = 10
const avgOpponentElo = (player) => {
const opponents = DBplayers.filter(p => p.id !== player.id);
return opponents.reduce((sum, p) => sum + p.elo, 0) / opponents.length;
};
DBplayers.forEach(player => {
const avgElo = avgOpponentElo(player);
const expectedScore = 1 / (1 + 10 ** ((avgElo - player.elo) / 400))
let actualScore;
if (winnerIds.has(player.id)) {
actualScore = (winnerIds.size === playerCount) ? 0.5 : 1;
} else {
actualScore = 0;
}
const K = winnerIds.has(player.id) ? (baseK * playerCount) : baseK
const delta = K * (actualScore - expectedScore)
const newElo = Math.max(Math.floor(player.elo + delta), 0)
if (!isNaN(newElo)) {
console.log(`${player.id} elo update: ${player.elo} -> ${newElo} (K: ${K.toFixed(2)}, Δ: ${delta.toFixed(2)})`);
updateElo.run({ id: player.id, elo: newElo })
insertGame.run({
id: player.id + '-' + Date.now()?.toString(),
p1: player.id,
p2: null,
p1_score: actualScore,
p2_score: null,
p1_elo: player.elo,
p2_elo: avgElo,
p1_new_elo: newElo,
p2_new_elo: null,
type: 'POKER_ROUND',
timestamp: Date.now(),
})
} else {
console.log(`# ELO UPDATE ERROR -> ${player.id} elo update: ${player.elo} -> ${newElo} (K: ${K.toFixed(2)}, Δ: ${delta.toFixed(2)})`);
}
})
}
export function randomSkinPrice(id=0) {
const dbSkins = getAllSkins.all();
const randomIndex = Math.floor(Math.random() * dbSkins.length);
let randomSkin = skins.find((skin) => skin.uuid === dbSkins[randomIndex].uuid);
// Generate random level and chroma
const randomLevel = Math.floor(Math.random() * randomSkin.levels.length + 1);
let randomChroma = randomLevel === randomSkin.levels.length
? Math.floor(Math.random() * randomSkin.chromas.length + 1)
: 1;
if (randomChroma === randomSkin.chromas.length && randomSkin.chromas.length >= 2) randomChroma--
const selectedLevel = randomSkin.levels[randomLevel - 1]
const selectedChroma = randomSkin.chromas[randomChroma - 1]
// Helper functions (unchanged from your original code)
const price = () => {
let result = dbSkins[randomIndex].basePrice;
result *= (1 + (randomLevel / Math.max(randomSkin.levels.length, 2)))
result *= (1 + (randomChroma / 4))
return result.toFixed(2);
}
const returnPrice = price()
console.log(`#${id} :`, returnPrice)
return returnPrice
}
export function createConnect4Board() {
return Array(C4_ROWS).fill(null).map(() => Array(C4_COLS).fill(null));
}
export function checkConnect4Win(board, player) {
// Check horizontal
for (let r = 0; r < C4_ROWS; r++) {
for (let c = 0; c <= C4_COLS - 4; c++) {
if (board[r][c] === player && board[r][c+1] === player && board[r][c+2] === player && board[r][c+3] === player) {
return { win: true, pieces: [{row:r, col:c}, {row:r, col:c+1}, {row:r, col:c+2}, {row:r, col:c+3}] };
}
}
}
// Check vertical
for (let r = 0; r <= C4_ROWS - 4; r++) {
for (let c = 0; c < C4_COLS; c++) {
if (board[r][c] === player && board[r+1][c] === player && board[r+2][c] === player && board[r+3][c] === player) {
return { win: true, pieces: [{row:r, col:c}, {row:r+1, col:c}, {row:r+2, col:c}, {row:r+3, col:c}] };
}
}
}
// Check diagonal (down-right)
for (let r = 0; r <= C4_ROWS - 4; r++) {
for (let c = 0; c <= C4_COLS - 4; c++) {
if (board[r][c] === player && board[r+1][c+1] === player && board[r+2][c+2] === player && board[r+3][c+3] === player) {
return { win: true, pieces: [{row:r, col:c}, {row:r+1, col:c+1}, {row:r+2, col:c+2}, {row:r+3, col:c+3}] };
}
}
}
// Check diagonal (up-right)
for (let r = 3; r < C4_ROWS; r++) {
for (let c = 0; c <= C4_COLS - 4; c++) {
if (board[r][c] === player && board[r-1][c+1] === player && board[r-2][c+2] === player && board[r-3][c+3] === player) {
return { win: true, pieces: [{row:r, col:c}, {row:r-1, col:c+1}, {row:r-2, col:c+2}, {row:r-3, col:c+3}] };
}
}
}
return { win: false, pieces: [] };
}
export function checkConnect4Draw(board) {
return board[0].every(cell => cell !== null);
}
export function formatConnect4BoardForDiscord(board) {
const symbols = {
'R': '🔴',
'Y': '🟡',
null: '⚪'
};
return board.map(row => row.map(cell => symbols[cell]).join('')).join('\n');
}
/**
* Creates a seedable pseudorandom number generator (PRNG) using the Mulberry32 algorithm.
* @param {number} seed - An initial number to seed the generator.
* @returns {function} A function that, when called, returns a pseudorandom number between 0 and 1.
*/
export function createSeededRNG(seed) {
return function() {
let t = seed += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
}
/**
* Shuffles an array in place using the Fisher-Yates algorithm.
* @param {Array} array - The array to shuffle.
* @returns {Array} The shuffled array.
*/
export function shuffle(array) {
let currentIndex = array.length,
randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
return array;
}
/**
* Shuffles an array in place using a seedable PRNG via the Fisher-Yates algorithm.
* @param {Array} array - The array to shuffle.
* @param {function} rng - A seedable random number generator function.
* @returns {Array} The shuffled array.
*/
export function seededShuffle(array, rng) {
let currentIndex = array.length,
randomIndex;
// While there remain elements to shuffle.
while (currentIndex !== 0) {
// Pick a remaining element using the seeded RNG.
randomIndex = Math.floor(rng() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
/**
* Deals a shuffled deck into the initial Solitaire game state.
* @param {Array} deck - A shuffled deck of cards.
* @returns {Object} The initial gameState object.
*/
export function deal(deck) {
const gameState = {
tableauPiles: [[], [], [], [], [], [], []],
foundationPiles: [[], [], [], []],
stockPile: [],
wastePile: [],
isDone: false,
};
// Deal cards to the tableau piles
for (let i = 0; i < 7; i++) {
for (let j = i; j < 7; j++) {
gameState.tableauPiles[j].push(deck.shift());
}
}
// Flip the top card of each tableau pile
gameState.tableauPiles.forEach(pile => {
if (pile.length > 0) {
pile[pile.length - 1].faceUp = true;
}
});
// The rest of the deck becomes the stock
gameState.stockPile = deck;
return gameState;
}
/**
* Checks if a proposed move is valid according to the rules of Klondike Solitaire.
* @param {Object} gameState - The current state of the game.
* @param {Object} moveData - The details of the move.
* @returns {boolean}
*/
export function isValidMove(gameState, moveData) {
// Use more descriptive names to avoid confusion
const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex } = moveData;
let sourcePile;
// Get the actual source pile array based on its type and index
if (sourcePileType === 'tableauPiles') {
sourcePile = gameState.tableauPiles[sourcePileIndex];
} else if (sourcePileType === 'wastePile') {
sourcePile = gameState.wastePile;
} else if (sourcePileType === 'foundationPiles') {
sourcePile = gameState.foundationPiles[sourcePileIndex];
} else {
return false; // Cannot drag from foundation or stock
}
// Get the actual card being dragged (the top of the stack)
const sourceCard = sourcePile[sourceCardIndex];
// A card must exist and be face-up to be moved
if (!sourceCard || !sourceCard.faceUp) {
return false;
}
// --- Validate move TO a Tableau Pile ---
if (destPileType === 'tableauPiles') {
const destinationPile = gameState.tableauPiles[destPileIndex];
const topCard = destinationPile.length > 0 ? destinationPile[destinationPile.length - 1] : null;
if (!topCard) {
// If the destination tableau pile is empty, only a King can be moved there.
return sourceCard.rank === 'K';
}
// If the destination pile is not empty, check game rules
const sourceColor = getCardColor(sourceCard.suit);
const destColor = getCardColor(topCard.suit);
const sourceValue = getRankValue(sourceCard.rank);
const destValue = getRankValue(topCard.rank);
// Card being moved must be opposite color and one rank lower than the destination top card.
return sourceColor !== destColor && destValue - sourceValue === 1;
}
// --- Validate move TO a Foundation Pile ---
if (destPileType === 'foundationPiles' && sourcePileType !== 'foundationPiles') {
// You can only move one card at a time to a foundation pile.
const stackBeingMoved = sourcePile.slice(sourceCardIndex);
if (stackBeingMoved.length > 1) {
return false;
}
const destinationPile = gameState.foundationPiles[destPileIndex];
const topCard = destinationPile.length > 0 ? destinationPile[destinationPile.length - 1] : null;
if (!topCard) {
// If the foundation is empty, only an Ace can be moved there.
return sourceCard.rank === 'A';
}
// If not empty, card must be same suit and one rank higher.
const sourceValue = getRankValue(sourceCard.rank);
const destValue = getRankValue(topCard.rank);
return sourceCard.suit === topCard.suit && sourceValue - destValue === 1;
}
return false;
}
/**
* An array of suits and ranks to create a deck.
*/
const SUITS = ['h', 'd', 's', 'c']; // Hearts, Diamonds, Spades, Clubs
const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K'];
/**
* Gets the numerical value of a card's rank.
* @param {string} rank - e.g., 'A', 'K', '7'
* @returns {number}
*/
function getRankValue(rank) {
if (rank === 'A') return 1;
if (rank === 'T') return 10;
if (rank === 'J') return 11;
if (rank === 'Q') return 12;
if (rank === 'K') return 13;
return parseInt(rank, 10);
}
/**
* Gets the color of a card's suit.
* @param {string} suit - e.g., 'h', 's'
* @returns {string} 'red' or 'black'
*/
function getCardColor(suit) {
return suit === 'h' || suit === 'd' ? 'red' : 'black';
}
/**
* Creates a standard 52-card deck.
* @returns {Array<Object>}
*/
export function createDeck() {
const deck = [];
for (const suit of SUITS) {
for (const rank of RANKS) {
deck.push({ suit, rank, faceUp: false });
}
}
return deck;
}
/**
* Mutates the game state by performing a valid move, correctly handling stacks.
* @param {Object} gameState - The current state of the game.
* @param {Object} moveData - The details of the move.
*/
export async function moveCard(gameState, moveData) {
const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex } = moveData;
// Identify the source pile array
let sourcePile;
if (sourcePileType === 'tableauPiles') {
sourcePile = gameState.tableauPiles[sourcePileIndex];
} else if (sourcePileType === 'wastePile') {
sourcePile = gameState.wastePile;
} else if (sourcePileType === 'foundationPiles') {
sourcePile = gameState.foundationPiles[sourcePileIndex];
}
// Identify the destination pile array
let destPile;
if (destPileType === 'tableauPiles') {
destPile = gameState.tableauPiles[destPileIndex];
} else {
destPile = gameState.foundationPiles[destPileIndex];
}
// Using splice(), cut the entire stack of cards to be moved from the source pile.
const cardsToMove = sourcePile.splice(sourceCardIndex);
// Add the stack of cards to the destination pile.
// Using the spread operator (...) to add all items from the cardsToMove array.
destPile.push(...cardsToMove);
if (sourcePileType === 'foundationPiles') {
sotdMoveUpdate(gameState, -15)
} else if (destPileType === 'foundationPiles') {
sotdMoveUpdate(gameState, 10)
} else {
sotdMoveUpdate(gameState, 0)
}
// After moving, if the source was a tableau pile and it's not empty,
// flip the new top card to be face-up.
if (sourcePileType === 'tableauPiles' && sourcePile.length > 0) {
sourcePile[sourcePile.length - 1].faceUp = true;
}
}
/**
* Moves a card from the stock to the waste pile. If stock is empty, resets it from the waste.
* @param {Object} gameState - The current state of the game.
*/
export async function drawCard(gameState) {
if (gameState.stockPile.length > 0) {
const card = gameState.stockPile.pop();
card.faceUp = true;
gameState.wastePile.push(card);
} else if (gameState.wastePile.length > 0) {
// When stock is empty, move waste pile back to stock, face down
gameState.stockPile = gameState.wastePile.reverse();
gameState.stockPile.forEach(card => (card.faceUp = false));
gameState.wastePile = [];
}
sotdMoveUpdate(gameState, 0)
}
/**
* Checks if the game has been won (all cards are in the foundation piles).
* @param {Object} gameState - The current state of the game.
* @returns {boolean}
*/
export function checkWinCondition(gameState) {
const foundationCardCount = gameState.foundationPiles.reduce(
(acc, pile) => acc + pile.length,
0
);
return foundationCardCount === 52;
}
export function initTodaysSOTD() {
const rankings = getAllSOTDStats.all()
const firstPlaceId = rankings.length > 0 ? rankings[0].user_id : null
console.log(rankings)
console.log(firstPlaceId)
if (firstPlaceId) {
const firstPlaceUser = getUser.get(firstPlaceId)
console.log(firstPlaceUser)
if (firstPlaceUser) {
updateUserCoins.run({ id: firstPlaceId, coins: firstPlaceUser.coins + 1000 });
insertLog.run({
id: firstPlaceId + '-' + Date.now(),
user_id: firstPlaceId,
action: 'SOTD_FIRST_PLACE',
target_user_id: null,
coins_amount: 1000,
user_new_amount: firstPlaceUser.coins + 1000,
})
}
console.log(`${firstPlaceId} wins ${new Date().toLocaleDateString()} SOTD`)
}
const newRandomSeed = Date.now().toString(36) + Math.random().toString(36).substr(2);
let numericSeed = 0;
for (let i = 0; i < newRandomSeed.length; i++) {
numericSeed = (numericSeed + newRandomSeed.charCodeAt(i)) & 0xFFFFFFFF;
}
const rng = createSeededRNG(numericSeed);
const deck = createDeck();
const shuffledDeck = seededShuffle(deck, rng);
const todaysSOTD = deal(shuffledDeck);
todaysSOTD.seed = newRandomSeed;
clearSOTDStats.run()
deleteSOTD.run()
insertSOTD.run({
id: 0,
tableauPiles: JSON.stringify(todaysSOTD.tableauPiles),
foundationPiles: JSON.stringify(todaysSOTD.foundationPiles),
stockPile: JSON.stringify(todaysSOTD.stockPile),
wastePile: JSON.stringify(todaysSOTD.wastePile),
seed: todaysSOTD.seed,
})
console.log('Today\'s SOTD is ready')
}
export function sotdMoveUpdate(gameState, points) {
if (gameState.isSOTD) {
gameState.moves++
gameState.score += points
}
}

View File

@@ -1,157 +0,0 @@
import Database from "better-sqlite3";
export const flopoDB = new Database('flopobot.db');
export const stmtUsers = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL,
globalName TEXT,
warned BOOLEAN DEFAULT 0,
warns INTEGER DEFAULT 0,
allTimeWarns INTEGER DEFAULT 0,
totalRequests INTEGER DEFAULT 0,
coins INTEGER DEFAULT 0,
dailyQueried BOOLEAN DEFAULT 0
)
`);
stmtUsers.run();
export const stmtSkins = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS skins (
uuid TEXT PRIMARY KEY,
displayName TEXT,
contentTierUuid TEXT,
displayIcon TEXT,
user_id TEXT REFERENCES users,
tierRank TEXT,
tierColor TEXT,
tierText TEXT,
basePrice TEXT,
currentLvl INTEGER DEFAULT NULL,
currentChroma INTEGER DEFAULT NULL,
currentPrice INTEGER DEFAULT NULL,
maxPrice INTEGER DEFAULT NULL
)
`);
stmtSkins.run()
export const insertUser = flopoDB.prepare('INSERT INTO users (id, username, globalName, warned, warns, allTimeWarns, totalRequests) VALUES (@id, @username, @globalName, @warned, @warns, @allTimeWarns, @totalRequests)');
export const updateUser = flopoDB.prepare('UPDATE users SET warned = @warned, warns = @warns, allTimeWarns = @allTimeWarns, totalRequests = @totalRequests WHERE id = @id');
export const queryDailyReward = flopoDB.prepare(`UPDATE users SET dailyQueried = 1 WHERE id = ?`);
export const resetDailyReward = flopoDB.prepare(`UPDATE users SET dailyQueried = 0 WHERE id = ?`);
export const updateUserCoins = flopoDB.prepare('UPDATE users SET coins = @coins WHERE id = @id');
export const getUser = flopoDB.prepare('SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id WHERE users.id = ?');
export const getAllUsers = flopoDB.prepare('SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id ORDER BY coins DESC');
export const insertSkin = flopoDB.prepare('INSERT INTO skins (uuid, displayName, contentTierUuid, displayIcon, user_id, tierRank, tierColor, tierText, basePrice, currentLvl, currentChroma, currentPrice, maxPrice) VALUES (@uuid, @displayName, @contentTierUuid, @displayIcon, @user_id, @tierRank, @tierColor, @tierText, @basePrice, @currentLvl, @currentChroma, @currentPrice, @maxPrice)');
export const updateSkin = flopoDB.prepare('UPDATE skins SET user_id = @user_id, currentLvl = @currentLvl, currentChroma = @currentChroma, currentPrice = @currentPrice WHERE uuid = @uuid');
export const getSkin = flopoDB.prepare('SELECT * FROM skins WHERE uuid = ?');
export const getAllSkins = flopoDB.prepare('SELECT * FROM skins ORDER BY maxPrice DESC');
export const getAllAvailableSkins = flopoDB.prepare('SELECT * FROM skins WHERE user_id IS NULL');
export const getUserInventory = flopoDB.prepare('SELECT * FROM skins WHERE user_id = @user_id ORDER BY currentPrice DESC');
export const getTopSkins = flopoDB.prepare('SELECT * FROM skins ORDER BY maxPrice DESC LIMIT 10');
export const insertManyUsers = flopoDB.transaction(async (users) => {
for (const user of users) try { await insertUser.run(user) } catch (e) { /**/ }
});
export const updateManyUsers = flopoDB.transaction(async (users) => {
for (const user of users) try { await updateUser.run(user) } catch (e) { console.log('user update failed') }
});
export const insertManySkins = flopoDB.transaction(async (skins) => {
for (const skin of skins) try { await insertSkin.run(skin) } catch (e) { console.log('skin insert failed') }
});
export const updateManySkins = flopoDB.transaction(async (skins) => {
for (const skin of skins) try { await updateSkin.run(skin) } catch (e) { console.log('skin insert failed') }
});
export const stmtLogs = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS logs (
id PRIMARY KEY,
user_id TEXT REFERENCES users,
action TEXT,
target_user_id TEXT REFERENCES users,
coins_amount INTEGER,
user_new_amount INTEGER
)
`);
stmtLogs.run()
export const insertLog = flopoDB.prepare('INSERT INTO logs (id, user_id, action, target_user_id, coins_amount, user_new_amount) VALUES (@id, @user_id, @action, @target_user_id, @coins_amount, @user_new_amount)');
export const getLogs = flopoDB.prepare('SELECT * FROM logs');
export const getUserLogs = flopoDB.prepare('SELECT * FROM logs WHERE user_id = @user_id');
export const stmtGames = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS games (
id PRIMARY KEY,
p1 TEXT REFERENCES users,
p2 TEXT REFERENCES users,
p1_score INTEGER,
p2_score INTEGER,
p1_elo INTEGER,
p2_elo INTEGER,
p1_new_elo INTEGER,
p2_new_elo INTEGER,
type TEXT,
timestamp TIMESTAMP
)
`);
stmtGames.run()
export const insertGame = flopoDB.prepare('INSERT INTO games (id, p1, p2, p1_score, p2_score, p1_elo, p2_elo, p1_new_elo, p2_new_elo, type, timestamp) VALUES (@id, @p1, @p2, @p1_score, @p2_score, @p1_elo, @p2_elo, @p1_new_elo, @p2_new_elo, @type, @timestamp)');
export const getGames = flopoDB.prepare('SELECT * FROM games');
export const getUserGames = flopoDB.prepare('SELECT * FROM games WHERE p1 = @user_id OR p2 = @user_id');
export const stmtElos = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS elos (
id PRIMARY KEY REFERENCES users,
elo INTEGER
)
`);
stmtElos.run()
export const insertElos = flopoDB.prepare(`INSERT INTO elos (id, elo) VALUES (@id, @elo)`);
export const getElos = flopoDB.prepare(`SELECT * FROM elos`);
export const getUserElo = flopoDB.prepare(`SELECT * FROM elos WHERE id = @id`);
export const updateElo = flopoDB.prepare('UPDATE elos SET elo = @elo WHERE id = @id');
export const getUsersByElo = flopoDB.prepare('SELECT * FROM users JOIN elos ON elos.id = users.id ORDER BY elos.elo DESC')
export const stmtSOTD = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS sotd (
id INT PRIMARY KEY,
tableauPiles TEXT,
foundationPiles TEXT,
stockPile TEXT,
wastePile TEXT,
isDone BOOLEAN DEFAULT false,
seed TEXT
)
`);
stmtSOTD.run()
export const getSOTD = flopoDB.prepare(`SELECT * FROM sotd WHERE id = '0'`)
export const insertSOTD = flopoDB.prepare(`INSERT INTO sotd (id, tableauPiles, foundationPiles, stockPile, wastePile, seed) VALUES (@id, @tableauPiles, @foundationPiles, @stockPile, @wastePile, @seed)`)
export const deleteSOTD = flopoDB.prepare(`DELETE FROM sotd WHERE id = '0'`)
export const stmtSOTDStats = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS sotd_stats (
id TEXT PRIMARY KEY,
user_id TEXT REFERENCES users,
time INTEGER,
moves INTEGER,
score INTEGER
)
`);
stmtSOTDStats.run()
export const getAllSOTDStats = flopoDB.prepare(`SELECT sotd_stats.*, users.globalName FROM sotd_stats JOIN users ON users.id = sotd_stats.user_id ORDER BY score DESC, moves ASC, time ASC`);
export const getUserSOTDStats = flopoDB.prepare(`SELECT * FROM sotd_stats WHERE user_id = ?`);
export const insertSOTDStats = flopoDB.prepare(`INSERT INTO sotd_stats (id, user_id, time, moves, score) VALUES (@id, @user_id, @time, @moves, @score)`);
export const clearSOTDStats = flopoDB.prepare(`DELETE FROM sotd_stats`);
export const deleteUserSOTDStats = flopoDB.prepare(`DELETE FROM sotd_stats WHERE user_id = ?`);

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ 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";
import { formatTime } from "../../utils/index.js";
// Create a new router instance
const router = express.Router();

View File

@@ -38,7 +38,7 @@ export function pokerRoutes(client, io) {
});
router.post('/create', async (req, res) => {
const { creatorId } = req.body;
const { creatorId, minBet, fakeMoney } = req.body;
if (!creatorId) return res.status(400).json({ message: 'Creator ID is required.' });
if (Object.values(pokerRooms).some(room => room.host_id === creatorId || room.players[creatorId])) {
@@ -51,14 +51,15 @@ export function pokerRoutes(client, io) {
pokerRooms[id] = {
id, host_id: creatorId, host_name: creator.globalName || creator.username,
name, created_at: Date.now(), last_move_at: Date.now(),
name, created_at: Date.now(), last_move_at: null,
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,
current_turn: null, playing: false, winners: [], waiting_for_restart: false, fakeMoney: fakeMoney,
minBet: minBet,
};
await joinRoom(id, creatorId, io); // Auto-join the creator
await emitPokerUpdate({ room: pokerRooms[id] });
await emitPokerUpdate({ room: pokerRooms[id], type: 'room-created' });
res.status(201).json({ roomId: id });
});
@@ -69,6 +70,9 @@ export function pokerRoutes(client, io) {
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.' });
}
if (!pokerRooms[roomId].fakeMoney && pokerRooms[roomId].minBet > (getUser.get(userId)?.coins ?? 0)) {
return res.status(403).json({ message: 'You do not have enough coins to join this room.' });
}
await joinRoom(roomId, userId, io);
res.status(200).json({ message: 'Successfully joined.' });
@@ -82,16 +86,99 @@ export function pokerRoutes(client, io) {
return res.status(403).json({ message: 'Unauthorized or player not in queue.' });
}
if (!room.fakeMoney) {
const userDB = getUser.get(playerId);
if (userDB) {
updateUserCoins.run({ id: playerId, coins: userDB.coins - room.minBet });
insertLog.run({
id: `${playerId}-poker-${Date.now()}`,
user_id: playerId, target_user_id: null,
action: 'POKER_JOIN',
coins_amount: -room.minBet, user_new_amount: userDB.coins - room.minBet,
})
}
}
room.players[playerId] = room.queue[playerId];
delete room.queue[playerId];
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'player-accepted' });
res.status(200).json({ message: 'Player accepted.' });
});
router.post('/leave', (req, res) => {
// Implement leave logic...
res.status(501).json({ message: "Not Implemented" });
router.post('/leave', async (req, res) => {
const { userId, roomId } = req.body
if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' })
if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: 'Joueur introuvable' })
if (pokerRooms[roomId].playing && (pokerRooms[roomId].current_turn !== null && pokerRooms[roomId].current_turn !== 4)) {
pokerRooms[roomId].afk[userId] = pokerRooms[roomId].players[userId]
try {
pokerRooms[roomId].players[userId].folded = true
pokerRooms[roomId].players[userId].last_played_turn = pokerRooms[roomId].current_turn
if (pokerRooms[roomId].current_player === userId) {
await checkRoundCompletion(pokerRooms[roomId], io);
}
} catch(e) {
console.log(e)
}
await emitPokerUpdate({ type: 'player-afk' });
return res.status(200)
}
try {
updatePlayerCoins(pokerRooms[roomId].players[userId], pokerRooms[roomId].players[userId].bank, pokerRooms[roomId].fakeMoney);
delete pokerRooms[roomId].players[userId]
if (userId === pokerRooms[roomId].host_id) {
const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== userId)
if (!newHostId) {
delete pokerRooms[roomId]
} else {
pokerRooms[roomId].host_id = newHostId
}
}
} catch (e) {
//
}
await emitPokerUpdate({ type: 'player-left' });
return res.status(200)
});
router.post('/kick', async (req, res) => {
const { commandUserId, userId, roomId } = req.body
if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' })
if (!pokerRooms[roomId].players[commandUserId]) return res.status(404).send({ message: 'Joueur introuvable' })
if (pokerRooms[roomId].host_id !== commandUserId) return res.status(403).send({ message: 'Seul l\'host peut kick' })
if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: 'Joueur introuvable' })
if (pokerRooms[roomId].playing && (pokerRooms[roomId].current_turn !== null && pokerRooms[roomId].current_turn !== 4)) {
return res.status(403).send({ message: 'Playing' })
}
try {
updatePlayerCoins(pokerRooms[roomId].players[userId], pokerRooms[roomId].players[userId].bank, pokerRooms[roomId].fakeMoney);
delete pokerRooms[roomId].players[userId]
if (userId === pokerRooms[roomId].host_id) {
const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== userId)
if (!newHostId) {
delete pokerRooms[roomId]
} else {
pokerRooms[roomId].host_id = newHostId
}
}
} catch (e) {
//
}
await emitPokerUpdate({ type: 'player-kicked' });
return res.status(200)
});
// --- Game Action Endpoints ---
@@ -152,7 +239,6 @@ export function pokerRoutes(client, io) {
player.bank -= callAmount;
player.bet += callAmount;
if (player.bank === 0) player.allin = true;
updatePlayerCoins(player, -callAmount, room.fakeMoney);
await emitPokerToast({
type: 'player-call',
playerId: player.id,
@@ -168,7 +254,6 @@ export function pokerRoutes(client, io) {
player.bet += amount;
if (player.bank === 0) player.allin = true;
room.highest_bet = player.bet;
updatePlayerCoins(player, -amount, room.fakeMoney);
await emitPokerToast({
type: 'player-raise',
amount: amount,
@@ -194,23 +279,29 @@ export function pokerRoutes(client, io) {
async function joinRoom(roomId, userId, io) {
const user = await client.users.fetch(userId);
const userDB = getUser.get(userId);
const bank = (userDB?.coins ?? 0) >= 1000 ? userDB.coins : 1000;
const isFake = (userDB?.coins ?? 0) < 1000;
const room = pokerRooms[roomId];
const playerObject = {
id: userId, globalName: user.globalName || user.username,
hand: [], bank: bank, bet: 0, folded: false, allin: false,
hand: [], bank: room.minBet, bet: 0, folded: false, allin: false,
last_played_turn: null, solve: null
};
const room = pokerRooms[roomId];
if (room.playing) {
room.queue[userId] = playerObject;
} else {
room.players[userId] = playerObject;
if (!room.fakeMoney) {
updateUserCoins.run({ id: userId, coins: userDB.coins - room.minBet });
insertLog.run({
id: `${userId}-poker-${Date.now()}`,
user_id: userId, target_user_id: null,
action: 'POKER_JOIN',
coins_amount: -room.minBet, user_new_amount: userDB.coins - room.minBet,
})
}
}
if (isFake) room.fakeMoney = true;
await emitPokerUpdate({ room: room, type: 'player-joined' });
}
@@ -218,7 +309,7 @@ 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 });
await emitPokerUpdate({ room: room, type: 'new-hand' });
return;
}
@@ -229,6 +320,7 @@ async function startNewHand(room, io) {
room.winners = [];
room.waiting_for_restart = false;
room.highest_bet = 20;
room.last_move_at = Date.now();
// Rotate dealer
const oldDealerIndex = playerIds.indexOf(room.dealer);
@@ -248,9 +340,7 @@ async function startNewHand(room, io) {
room.bb = bbPlayer.id;
sbPlayer.bank -= 10; sbPlayer.bet = 10;
updatePlayerCoins(sbPlayer, -10, room.fakeMoney);
bbPlayer.bank -= 20; bbPlayer.bet = 20;
updatePlayerCoins(bbPlayer, -20, room.fakeMoney);
bbPlayer.last_played_turn = 0;
room.current_player = playerIds[(dealerIndex + 3) % playerIds.length];
@@ -269,7 +359,7 @@ async function checkRoundCompletion(room, io) {
}
} else {
room.current_player = getNextActivePlayer(room);
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'round-continue' });
}
}
@@ -293,19 +383,19 @@ async function advanceToNextPhase(room, io, phase) {
await handleShowdown(room, io, checkRoomWinners(room));
return;
case 'progressive-showdown':
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'progressive-showdown' });
while(room.tapis.length < 5) {
await sleep(500);
room.tapis.push(room.pioche.pop());
updatePlayerHandSolves(room);
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'progressive-showdown' });
}
await handleShowdown(room, io, checkRoomWinners(room));
return;
}
updatePlayerHandSolves(room); // NEW: Update hand strength after new cards
room.current_player = getFirstActivePlayerAfterDealer(room);
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'phase-advanced' });
}
async function handleShowdown(room, io, winners) {
@@ -313,6 +403,7 @@ async function handleShowdown(room, io, winners) {
room.playing = false;
room.waiting_for_restart = true;
room.winners = winners;
room.current_player = null;
let totalPot = 0;
Object.values(room.players).forEach(p => { totalPot += p.bet; });
@@ -323,16 +414,20 @@ async function handleShowdown(room, io, winners) {
const winnerPlayer = room.players[winnerId];
if(winnerPlayer) {
winnerPlayer.bank += winAmount;
updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney);
}
});
await clearAfkPlayers(room);
console.log(room)
//await pokerEloHandler(room);
await emitPokerUpdate({ room: room });
await emitPokerUpdate({ room: room, type: 'showdown' });
await emitPokerToast({
type: 'player-winner',
playerIds: winners,
roomId: room.id,
amount: winAmount,
})
}
@@ -352,12 +447,22 @@ function updatePlayerCoins(player, amount, isFake) {
const user = getUser.get(player.id);
if (!user) return;
const newCoins = player.bank; // The bank is the source of truth now
updateUserCoins.run({ id: player.id, coins: newCoins });
const userDB = getUser.get(player.id);
updateUserCoins.run({ id: player.id, coins: userDB.coins + amount });
insertLog.run({
id: `${player.id}-poker-${Date.now()}`,
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: userDB.coins + amount,
});
}
async function clearAfkPlayers(room) {
Object.keys(room.afk).forEach(playerId => {
if (room.players[playerId]) {
updatePlayerCoins(room.players[playerId], room.players[playerId].bank, room.fakeMoney);
delete room.players[playerId];
}
});
room.afk = {};
}

View File

@@ -62,9 +62,9 @@ export function solitaireRoutes(client, io) {
router.post('/start/sotd', (req, res) => {
const { userId } = req.body;
if (!userId || !getUser.get(userId)) {
/*if (!userId || !getUser.get(userId)) {
return res.status(404).json({ error: 'User not found.' });
}
}*/
if (activeSolitaireGames[userId]?.isSOTD) {
return res.json({ success: true, gameState: activeSolitaireGames[userId] });
@@ -177,7 +177,7 @@ function updateGameStats(gameState, actionType, moveData = {}) {
}
if(actionType === 'draw' && gameState.wastePile.length === 0) {
// Penalty for cycling through an empty stock pile
gameState.score -= 100;
gameState.score -= 5;
}
}
@@ -189,6 +189,7 @@ function handleWin(userId, gameState, io) {
const timeTaken = gameState.endTime - gameState.startTime;
const currentUser = getUser.get(userId);
if (!currentUser) return;
const existingStats = getUserSOTDStats.get(userId);
if (!existingStats) {

314
utils.js
View File

@@ -1,314 +0,0 @@
import 'dotenv/config';
import OpenAI from "openai";
import {GoogleGenAI} from "@google/genai";
import {Mistral} from '@mistralai/mistralai';
import pkg from 'pokersolver';
import {flopoDB, getAllUsers} from "./init_database.js";
const { Hand } = pkg;
export async function DiscordRequest(endpoint, options) {
// append endpoint to root API URL
const url = 'https://discord.com/api/v10/' + endpoint;
// Stringify payloads
if (options.body) options.body = JSON.stringify(options.body);
// Use fetch to make requests
const res = await fetch(url, {
headers: {
Authorization: `Bot ${process.env.DISCORD_TOKEN}`,
'Content-Type': 'application/json; charset=UTF-8',
'User-Agent': 'DiscordBot (https://github.com/discord/discord-example-app, 1.0.0)',
},
...options
});
// throw API errors
if (!res.ok) {
const data = await res.json();
console.log(res.status);
throw new Error(JSON.stringify(data));
}
// return original response
return res;
}
export async function InstallGlobalCommands(appId, commands) {
// API endpoint to overwrite global commands
const endpoint = `applications/${appId}/commands`;
try {
// This is calling the bulk overwrite endpoint: https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands
await DiscordRequest(endpoint, { method: 'PUT', body: commands });
} catch (err) {
console.error(err);
}
}
// Simple method that returns a random emoji from list
export function getRandomEmoji(list=0) {
let emojiList
switch (list) {
case 0:
emojiList = ['😭','😄','😌','🤓','😎','😤','🤖','😶‍🌫️','🌏','📸','💿','👋','🌊','✨']
break
case 1:
emojiList = [
'<:CAUGHT:1323810730155446322>',
'<:hinhinhin:1072510144933531758>',
'<:o7:1290773422451986533>',
'<:zhok:1115221772623683686>',
'<:nice:1154049521110765759>',
'<:nerd:1087658195603951666>',
'<:peepSelfie:1072508131839594597>',
]
break
default:
emojiList = ['']
break
}
return emojiList[Math.floor(Math.random() * emojiList.length)];
}
export function getRandomHydrateText() {
const texts = [
`Hydratez-vous`,
`Pensez à vous hydratez`,
`Vous vous êtes hydratez aujourd'hui ?`,
`Buvez de l'eau la team`,
`#etsi vous vous hydratiez`,
`Oubliez pas de vous hydratez`,
`Hydratez vous la team`,
`Hydratez vous c'est important`,
`Hydratez-vous`,
`Pensez à vous hydratez`,
`Vous vous êtes hydratez aujourd'hui ?`,
`Buvez de l'eau la team`,
`#etsi vous vous hydratiez`,
`Oubliez pas de vous hydratez`,
`Hydratez vous la team`,
`Hydratez vous c'est important`,
`Hydratez-vous`,
`Pensez à vous hydratez`,
`Vous vous êtes hydratez aujourd'hui ?`,
`Buvez de l'eau la team`,
`#etsi vous vous hydratiez`,
`Oubliez pas de vous hydratez`,
`Hydratez vous la team`,
`Hydratez vous c'est important`,
`nsm ojd ça s'hydrate pas`,
];
return texts[Math.floor(Math.random() * texts.length)];
}
export const initialCards = [
'Ad', '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d', 'Td', 'Jd', 'Qd', 'Kd',
'As', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', 'Ts', 'Js', 'Qs', 'Ks',
'Ac', '2c', '3c', '4c', '5c', '6c', '7c', '8c', '9c', 'Tc', 'Jc', 'Qc', 'Kc',
'Ah', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh',
]
export function initialShuffledCards() {
return initialCards.sort((a, b) => 0.5 - Math.random());
}
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export async function getOnlineUsersWithRole(guildId, roleId) {
const endpoint = `/guilds/${guildId}/members?limit=1000`;
const response = await DiscordRequest(endpoint, { method: 'GET' });
const members = await response.json();
return members.filter(
(m) =>
m.roles.includes(roleId) &&
m.presence?.status !== 'offline'
);
}
export function formatTime(time) {
const days = Math.floor(time / (24 * 60 * 60));
const hours = Math.floor((time % (24 * 60 * 60)) / (60 * 60));
const minutes = Math.floor((time % (60 * 60)) / 60);
const seconds = time % 60;
const parts = [];
if (days > 0) parts.push(`**${days}** jour${days > 1 ? 's' : ''}`);
if (hours > 0) parts.push(`**${hours}** heure${hours > 1 ? 's' : ''}`);
if (minutes > 0) parts.push(`**${minutes}** minute${minutes > 1 ? 's' : ''}`);
if (seconds > 0 || parts.length === 0) parts.push(`**${seconds}** seconde${seconds > 1 ? 's' : ''}`);
return parts.join(', ').replace(/,([^,]*)$/, ' et$1');
}
const openai = new OpenAI();
const gemini = new GoogleGenAI({ apiKey: process.env.GEMINI_KEY})
const leChat = new Mistral({apiKey: process.env.MISTRAL_KEY});
export async function gork(messageHistory) {
if (process.env.MODEL === 'OpenAI') {
// OPEN AI
const completion = await openai.chat.completions.create({
model: "gpt-4.1-mini",
messages: messageHistory,
});
return completion.choices[0].message.content;
}
else if (process.env.MODEL === 'Gemini') {
//GEMINI
const formattedHistory = messageHistory.map(msg => {
return `${msg.role}: ${msg.content}`;
}).join('\n');
const response = await gemini.models.generateContent({
model: "gemini-2.0-flash-lite",
contents: formattedHistory,
})
return response.text
} else if (process.env.MODEL === 'Mistral') {
// MISTRAL
const chatResponse = await leChat.chat.complete({
model: 'mistral-large-latest',
messages: messageHistory,
})
return chatResponse.choices[0].message.content
} else {
return "Pas d'IA"
}
}
export async function getAPOUsers() {
const fetchUrl = process.env.APO_BASE_URL + '/users?serverId=' + (process.env.T12_GUILD_ID ?? process.env.GUILD_ID)
console.log(fetchUrl)
return await fetch(fetchUrl)
.then(response => {
if (!response.ok) {
throw new Error('Error fetching APO users')
}
return response.json()
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
}
export async function postAPOBuy(userId, amount) {
const fetchUrl = process.env.APO_BASE_URL + '/buy?serverId=' + (process.env.T12_GUILD_ID ?? process.env.GUILD_ID) + '&userId=' + userId + '&amount=' + amount
console.log(fetchUrl)
return await fetch(fetchUrl, {
method: 'POST',
})
.then(response => response)
.catch(error => console.log('Post error:', error))
}
export function getFirstActivePlayerAfterDealer(room) {
const players = Object.values(room.players);
const dealerPosition = players.findIndex((p) => p.id === room.dealer);
for (let i = 1; i < players.length; i++) {
const nextPos = (dealerPosition + i) % players.length;
if (!players[nextPos].folded && !players[nextPos].allin) {
return players[nextPos].id;
}
}
return null;
}
export function getNextActivePlayer(room) {
const players = Object.values(room.players);
const currentPlayerPosition = players.findIndex((p) => p.id === room.current_player);
for (let i = 1; i < players.length; i++) {
const nextPos = (currentPlayerPosition + i) % players.length;
if (!players[nextPos].folded && !players[nextPos].allin) {
return players[nextPos].id;
}
}
return null;
}
export function checkEndOfBettingRound(room) {
const players = Object.values(room.players);
const activePlayers = players.filter((p) => !p.folded);
if (activePlayers.length === 1) {
return { endRound: true, winner: activePlayers[0].id, nextPhase: null };
}
const allInPlayers = activePlayers.filter(p => p.allin)
if (allInPlayers.length === activePlayers.length) {
return { endRound: true, winner: null, nextPhase: 'progressive-showdown' };
}
const allActed = activePlayers.every(p => (p.bet === room.highest_bet || p.allin) && p.last_played_turn === room.current_turn);
if (allActed) {
let nextPhase;
switch (room.current_turn) {
case 0:
nextPhase = 'flop';
break;
case 1:
nextPhase = 'turn';
break;
case 2:
nextPhase = 'river';
break;
case 3:
nextPhase = 'showdown';
break;
default:
nextPhase = null;
break;
}
return { endRound: true, winner: null, nextPhase: nextPhase };
}
return { endRound: false, winner: null, nextPhase: null };
}
export function checkRoomWinners(room) {
const players = Object.values(room.players);
const activePlayers = players.filter(p => !p.folded);
const playerSolutions = activePlayers.map(player => ({
id: player.id,
solution: Hand.solve([...room.tapis, ...player.hand], 'standard', false)
}));
const solutions = playerSolutions.map(ps => ps.solution);
const winningIndices = Hand.winners(solutions).map(winningHand =>
solutions.findIndex(sol => sol === winningHand)
);
return winningIndices.map(index => playerSolutions[index].id);
}
export async function pruneOldLogs() {
const users = flopoDB.prepare(`
SELECT user_id
FROM logs
GROUP BY user_id
HAVING COUNT(*) > ${process.env.LOGS_BY_USER}
`).all();
const transaction = flopoDB.transaction(() => {
for (const { user_id } of users) {
flopoDB.prepare(`
DELETE FROM logs
WHERE id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (ORDER BY id DESC) AS rn
FROM logs
WHERE user_id = ?
)
WHERE rn > ${process.env.LOGS_BY_USER}
)
`).run(user_id);
}
});
transaction()
}

11
valo.js
View File

@@ -1,11 +0,0 @@
export async function getValorantSkins(locale='fr-FR') {
const response = await fetch(`https://valorant-api.com/v1/weapons/skins?language=${locale}`, { method: 'GET' });
const data = await response.json();
return data.data
}
export async function getSkinTiers(locale='fr-FR') {
const response = await fetch(`https://valorant-api.com/v1/contenttiers?language=${locale}`, { method: 'GET'});
const data = await response.json();
return data.data
}