upgraded solitaire, SOTD

This commit is contained in:
Milo
2025-07-25 14:33:00 +02:00
parent e30fceced6
commit b6a1ec2e8e
3 changed files with 107 additions and 27 deletions

36
game.js
View File

@@ -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
}
}

View File

@@ -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 });
});

View File

@@ -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')
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'`)