From b6a1ec2e8e1ad52861d263d7fd698030d8eb3e4f Mon Sep 17 00:00:00 2001 From: Milo Date: Fri, 25 Jul 2025 14:33:00 +0200 Subject: [PATCH] upgraded solitaire, SOTD --- game.js | 36 ++++++++++++++++++++-- index.js | 80 ++++++++++++++++++++++++++++++++++-------------- init_database.js | 18 ++++++++++- 3 files changed, 107 insertions(+), 27 deletions(-) diff --git a/game.js b/game.js index f49f667..78f1802 100644 --- a/game.js +++ b/game.js @@ -10,7 +10,7 @@ import { getUserElo, insertElos, updateElo, - getAllSkins + getAllSkins, deleteSOTD, insertSOTD } from './init_database.js' import {C4_COLS, C4_ROWS, skins} from "./index.js"; @@ -542,7 +542,7 @@ export function createDeck() { * @param {Object} gameState - The current state of the game. * @param {Object} moveData - The details of the move. */ -export function moveCard(gameState, moveData) { +export async function moveCard(gameState, moveData) { const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex } = moveData; // Identify the source pile array @@ -570,6 +570,14 @@ export function moveCard(gameState, moveData) { // 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) { @@ -581,7 +589,7 @@ export function moveCard(gameState, moveData) { * 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 function drawCard(gameState) { +export async function drawCard(gameState) { if (gameState.stockPile.length > 0) { const card = gameState.stockPile.pop(); card.faceUp = true; @@ -592,6 +600,7 @@ export function drawCard(gameState) { gameState.stockPile.forEach(card => (card.faceUp = false)); gameState.wastePile = []; } + sotdMoveUpdate(gameState, 0) } /** @@ -605,4 +614,25 @@ export function checkWinCondition(gameState) { 0 ); return foundationCardCount === 52; +} + +export function initTodaysSOTD() { + const deck = shuffle(createDeck()); + const todaysSOTD = deal(deck) + 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), + }) + console.log('Today\' SOTD is ready') +} + +export function sotdMoveUpdate(gameState, points) { + if (gameState.isSOTD) { + gameState.moves++ + gameState.score += points + } } \ No newline at end of file diff --git a/index.js b/index.js index 3db1de4..6930ea6 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,7 @@ import { pokerEloHandler, randomSkinPrice, slowmodesHandler, - deal, isValidMove, moveCard, shuffle, drawCard, checkWinCondition, createDeck, + deal, isValidMove, moveCard, shuffle, drawCard, checkWinCondition, createDeck, initTodaysSOTD, } from './game.js'; import { Client, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import cron from 'node-cron'; @@ -50,9 +50,19 @@ import { getSkin, getAllAvailableSkins, getUserInventory, - getTopSkins, updateUserCoins, - insertLog, stmtLogs, - getLogs, getUserLogs, getUserElo, getUserGames, getUsersByElo, resetDailyReward, queryDailyReward, + getTopSkins, + updateUserCoins, + insertLog, + stmtLogs, + getLogs, + getUserLogs, + getUserElo, + getUserGames, + getUsersByElo, + resetDailyReward, + queryDailyReward, + deleteSOTD, + insertSOTD, getSOTD, } from './init_database.js'; import { getValorantSkins, getSkinTiers } from './valo.js'; import {sleep} from "openai/core"; @@ -81,7 +91,7 @@ const activeInventories = {}; const activeSearchs = {}; const activeSlowmodes = {}; const activePredis = {}; -let todaysHydrateCron = '' +let todaysSOTD = {}; const SPAM_INTERVAL = process.env.SPAM_INTERVAL const client = new Client({ @@ -548,6 +558,9 @@ client.on('messageCreate', async (message) => { } console.log(`Result for ${amount} skins`) } + else if (message.content.toLowerCase().startsWith('?sotd')) { + initTodaysSOTD() + } else if (message.author.id === process.env.DEV_ID) { const prefix = process.env.DEV_SITE === 'true' ? 'dev' : 'flopo' if (message.content === prefix + ':add-coins-to-users') { @@ -635,10 +648,6 @@ client.on('messageCreate', async (message) => { client.once('ready', async () => { console.log(`Logged in as ${client.user.tag}`); console.log(`[Connected with ${FLAPI_URL}]`) - const randomMinute = Math.floor(Math.random() * 60); - const randomHour = Math.floor(Math.random() * (18 - 8 + 1)) + 8; - todaysHydrateCron = `${randomMinute} ${randomHour} * * *` - console.log(todaysHydrateCron) await getAkhys(); console.log('FlopoBOT marked as ready') @@ -683,13 +692,8 @@ client.once('ready', async () => { } }); - // ─── 💀 Midnight Chaos Timer ────────────────────── + // at midnight cron.schedule(process.env.CRON_EXPR, async () => { - const randomMinute = Math.floor(Math.random() * 60); - const randomHour = Math.floor(Math.random() * (18 - 8 + 1)) + 8; - todaysHydrateCron = `${randomMinute} ${randomHour} * * *` - console.log(todaysHydrateCron) - try { const akhys = getAllUsers.all() akhys.forEach((akhy) => { @@ -698,6 +702,8 @@ client.once('ready', async () => { } catch (e) { console.log(e) } + + initTodaysSOTD() }); // users/skins dayly fetch at 7am @@ -4460,6 +4466,33 @@ app.post('/solitaire/start', async (req, res) => { res.json({ success: true, gameState }); }); +app.post('/solitaire/start/sotd', async (req, res) => { + const userId = req.body.userId + const sotd = getSOTD.get() + + const gameState = { + tableauPiles: JSON.parse(sotd.tableauPiles), + foundationPiles: JSON.parse(sotd.foundationPiles), + stockPile: JSON.parse(sotd.stockPile), + wastePile: JSON.parse(sotd.wastePile), + isDone: false, + isSOTD: true, + hasFinToday: false, + startTime: Date.now(), + moves: 0, + score: 0, + } + + activeSolitaireGames[userId] = gameState + res.json({ success: true, gameState }); +}) + +app.post('/solitaire/reset', async (req, res) => { + const userId = req.body.userId; + delete activeSolitaireGames[userId] + res.json({ success: true }); +}); + /** * GET /solitaire/state/:userId * Gets the current game state for a user. If no game exists, creates a new one. @@ -4467,12 +4500,11 @@ app.post('/solitaire/start', async (req, res) => { app.get('/solitaire/state/:userId', (req, res) => { const { userId } = req.params; let gameState = activeSolitaireGames[userId]; - if (!gameState) { - console.log(`Creating new Solitaire game for user: ${userId}`); + /*if (!gameState) { const deck = shuffle(createDeck()); gameState = deal(deck); activeSolitaireGames[userId] = gameState; - } + }*/ res.json({ success: true, gameState }); }); @@ -4480,7 +4512,7 @@ app.get('/solitaire/state/:userId', (req, res) => { * POST /solitaire/move * Receives all necessary move data from the frontend. */ -app.post('/solitaire/move', (req, res) => { +app.post('/solitaire/move', async (req, res) => { // Destructure the complete move data from the request body // Frontend must send all these properties. const { @@ -4501,9 +4533,11 @@ app.post('/solitaire/move', (req, res) => { // Pass the entire data object to the validation function if (isValidMove(gameState, req.body)) { // If valid, mutate the state - moveCard(gameState, req.body); + await moveCard(gameState, req.body); const win = checkWinCondition(gameState); - if (win) gameState.isDone = true + if (win) { + gameState.isDone = true + } res.json({ success: true, gameState, win }); } else { // If the move is invalid, send a specific error message @@ -4515,13 +4549,13 @@ app.post('/solitaire/move', (req, res) => { * POST /solitaire/draw * Draws a card from the stock pile to the waste pile. */ -app.post('/solitaire/draw', (req, res) => { +app.post('/solitaire/draw', async (req, res) => { const { userId } = req.body; const gameState = activeSolitaireGames[userId]; if (!gameState) { return res.status(404).json({ error: `Game not found for user ${userId}` }); } - drawCard(gameState); + await drawCard(gameState); res.json({ success: true, gameState }); }); diff --git a/init_database.js b/init_database.js index a5c5322..fe4e43b 100644 --- a/init_database.js +++ b/init_database.js @@ -120,4 +120,20 @@ 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') \ No newline at end of file +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 + ) +`); +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) VALUES (@id, @tableauPiles, @foundationPiles, @stockPile, @wastePile)`) +export const deleteSOTD = flopoDB.prepare(`DELETE FROM sotd WHERE id = '0'`) \ No newline at end of file