diff --git a/src/bot/handlers/messageCreate.js b/src/bot/handlers/messageCreate.js index c0af9b0..8777673 100644 --- a/src/bot/handlers/messageCreate.js +++ b/src/bot/handlers/messageCreate.js @@ -16,7 +16,7 @@ import { calculateMaxPrice } from '../../utils/index.js'; import { channelPointsHandler, slowmodesHandler, randomSkinPrice, initTodaysSOTD } from '../../game/points.js'; -import { requestTimestamps, activeSlowmodes, activePolls, skins } from '../../game/state.js'; +import {requestTimestamps, activeSlowmodes, activePolls, skins, activeSolitaireGames} from '../../game/state.js'; import { flopoDB, getUser, @@ -27,6 +27,7 @@ import { getAllSkins, hardUpdateSkin } from '../../database/index.js'; import {client} from "../client.js"; +import {autoSolveMoves} from "../../game/solitaire.js"; // Constants for the AI rate limiter const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5"); @@ -280,5 +281,9 @@ async function handleAdminCommands(message) { }) console.log('Reworked', dbSkins.length, 'skins.'); break; + case `${prefix}:solve-solitaire`: + autoSolveMoves( + { "tableauPiles": [ [ { "suit": "d", "rank": "K", "faceUp": true }, { "suit": "s", "rank": "Q", "faceUp": true }, { "suit": "d", "rank": "J", "faceUp": true }, { "suit": "c", "rank": "T", "faceUp": true }, { "suit": "h", "rank": "9", "faceUp": true }, { "suit": "c", "rank": "8", "faceUp": true }, { "suit": "h", "rank": "7", "faceUp": true }, { "suit": "c", "rank": "6", "faceUp": true }, { "suit": "h", "rank": "5", "faceUp": true } ], [ { "suit": "h", "rank": "K", "faceUp": true }, { "suit": "c", "rank": "Q", "faceUp": true }, { "suit": "h", "rank": "J", "faceUp": true }, { "suit": "s", "rank": "T", "faceUp": true }, { "suit": "d", "rank": "9", "faceUp": true } ], [ { "suit": "s", "rank": "K", "faceUp": true }, { "suit": "d", "rank": "Q", "faceUp": true }, { "suit": "c", "rank": "J", "faceUp": true }, { "suit": "h", "rank": "T", "faceUp": true }, { "suit": "c", "rank": "9", "faceUp": true }, { "suit": "h", "rank": "8", "faceUp": true } ], [], [], [ { "suit": "c", "rank": "K", "faceUp": true }, { "suit": "h", "rank": "Q", "faceUp": true }, { "suit": "s", "rank": "J", "faceUp": true }, { "suit": "d", "rank": "T", "faceUp": true }, { "suit": "s", "rank": "9", "faceUp": true }, { "suit": "d", "rank": "8", "faceUp": true }, { "suit": "c", "rank": "7", "faceUp": true }, { "suit": "h", "rank": "6", "faceUp": true }, { "suit": "c", "rank": "5", "faceUp": true }, { "suit": "h", "rank": "4", "faceUp": true } ], [ { "suit": "h", "rank": "3", "faceUp": true } ] ], "foundationPiles": [ [ { "suit": "c", "rank": "A", "faceUp": true }, { "suit": "c", "rank": "2", "faceUp": true }, { "suit": "c", "rank": "3", "faceUp": true }, { "suit": "c", "rank": "4", "faceUp": true } ], [ { "suit": "h", "rank": "A", "faceUp": true }, { "suit": "h", "rank": "2", "faceUp": true } ], [ { "suit": "s", "rank": "A", "faceUp": true }, { "suit": "s", "rank": "2", "faceUp": true }, { "suit": "s", "rank": "3", "faceUp": true }, { "suit": "s", "rank": "4", "faceUp": true }, { "suit": "s", "rank": "5", "faceUp": true }, { "suit": "s", "rank": "6", "faceUp": true }, { "suit": "s", "rank": "7", "faceUp": true }, { "suit": "s", "rank": "8", "faceUp": true } ], [ { "suit": "d", "rank": "A", "faceUp": true }, { "suit": "d", "rank": "2", "faceUp": true }, { "suit": "d", "rank": "3", "faceUp": true }, { "suit": "d", "rank": "4", "faceUp": true }, { "suit": "d", "rank": "5", "faceUp": true }, { "suit": "d", "rank": "6", "faceUp": true }, { "suit": "d", "rank": "7", "faceUp": true } ] ], "stockPile": [], "wastePile": [], "seed": "mgqnxweyjp8fggj6ol9", "isSOTD": false, "score": 205, "moves": 90, "hist": [ { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 3, "sourceCardIndex": 3, "destPileType": "tableauPiles", "destPileIndex": 4, "cardsMoved": [ { "suit": "c", "rank": "9", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 6, "sourceCardIndex": 6, "destPileType": "foundationPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "A", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 5, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "c", "rank": "5", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 4, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "h", "rank": "4", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 6, "sourceCardIndex": 5, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "h", "rank": "9", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 6, "sourceCardIndex": 4, "destPileType": "foundationPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "h", "rank": "A", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 6, "sourceCardIndex": 3, "destPileType": "tableauPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "d", "rank": "8", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 6, "sourceCardIndex": 2, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "8", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "h", "rank": "2", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "foundationPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "h", "rank": "2", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "h", "rank": "Q", "faceUp": true } }, { "move": "draw", "card": { "suit": "h", "rank": "5", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "h", "rank": "5", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "d", "rank": "K", "faceUp": true } }, { "move": "draw", "card": { "suit": "c", "rank": "J", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 2, "destPileType": "tableauPiles", "destPileIndex": 6, "cardsMoved": [ { "suit": "c", "rank": "J", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 4, "sourceCardIndex": 4, "destPileType": "tableauPiles", "destPileIndex": 6, "cardsMoved": [ { "suit": "h", "rank": "T", "faceUp": true }, { "suit": "c", "rank": "9", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "d", "rank": "4", "faceUp": true } }, { "move": "draw", "card": { "suit": "h", "rank": "K", "faceUp": true } }, { "move": "draw", "card": { "suit": "s", "rank": "3", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 4, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "s", "rank": "3", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "K", "faceUp": true } }, { "move": "draw", "card": { "suit": "h", "rank": "7", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 5, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "h", "rank": "7", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 3, "sourceCardIndex": 2, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "6", "faceUp": true }, { "suit": "h", "rank": "5", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "h", "rank": "8", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 5, "destPileType": "tableauPiles", "destPileIndex": 6, "cardsMoved": [ { "suit": "h", "rank": "8", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "Q", "faceUp": true } }, { "move": "draw", "card": { "suit": "s", "rank": "A", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 6, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "A", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "d", "rank": "J", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 6, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "d", "rank": "J", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 0, "sourceCardIndex": 0, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "c", "rank": "T", "faceUp": true }, { "suit": "h", "rank": "9", "faceUp": true }, { "suit": "c", "rank": "8", "faceUp": true }, { "suit": "h", "rank": "7", "faceUp": true }, { "suit": "c", "rank": "6", "faceUp": true }, { "suit": "h", "rank": "5", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "4", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 6, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "c", "rank": "4", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "s", "rank": "5", "faceUp": true } }, { "move": "draw", "card": { "suit": "h", "rank": "J", "faceUp": true } }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 4, "sourceCardIndex": 3, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "d", "rank": "3", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "2", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 8, "destPileType": "foundationPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "2", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "c", "rank": "7", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 8, "destPileType": "tableauPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "c", "rank": "7", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 1, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "h", "rank": "6", "faceUp": true }, { "suit": "c", "rank": "5", "faceUp": true }, { "suit": "h", "rank": "4", "faceUp": true }, { "suit": "s", "rank": "3", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "d", "rank": "5", "faceUp": true } }, { "move": "draw", "card": { "suit": "d", "rank": "A", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 9, "destPileType": "foundationPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "A", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "c", "rank": "3", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 9, "destPileType": "foundationPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "3", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "d", "rank": "7", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 9, "destPileType": "tableauPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "7", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "d", "rank": "6", "faceUp": true } }, { "move": "draw-reset" }, { "move": "draw", "card": { "suit": "h", "rank": "Q", "faceUp": true } }, { "move": "draw", "card": { "suit": "d", "rank": "K", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "d", "rank": "K", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 3, "destPileType": "tableauPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "s", "rank": "Q", "faceUp": true }, { "suit": "d", "rank": "J", "faceUp": true }, { "suit": "c", "rank": "T", "faceUp": true }, { "suit": "h", "rank": "9", "faceUp": true }, { "suit": "c", "rank": "8", "faceUp": true }, { "suit": "h", "rank": "7", "faceUp": true }, { "suit": "c", "rank": "6", "faceUp": true }, { "suit": "h", "rank": "5", "faceUp": true }, { "suit": "c", "rank": "4", "faceUp": true }, { "suit": "d", "rank": "3", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 2, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "2", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 2, "sourceCardIndex": 8, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "3", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 1, "sourceCardIndex": 0, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "4", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 1, "destPileType": "foundationPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "2", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 0, "sourceCardIndex": 10, "destPileType": "foundationPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "3", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 0, "sourceCardIndex": 9, "destPileType": "foundationPiles", "destPileIndex": 0, "cardsMoved": [ { "suit": "c", "rank": "4", "faceUp": true } ], "cardWasFlipped": true, "points": 11 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 5, "sourceCardIndex": 0, "destPileType": "tableauPiles", "destPileIndex": 6, "cardsMoved": [ { "suit": "s", "rank": "7", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "d", "rank": "4", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 1, "destPileType": "foundationPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "4", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "h", "rank": "K", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "h", "rank": "K", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "K", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "c", "rank": "K", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "h", "rank": "Q", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 4, "sourceCardIndex": 2, "destPileType": "tableauPiles", "destPileIndex": 5, "cardsMoved": [ { "suit": "s", "rank": "J", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "draw", "card": { "suit": "c", "rank": "Q", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "c", "rank": "Q", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "draw", "card": { "suit": "s", "rank": "5", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "5", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "h", "rank": "J", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "h", "rank": "J", "faceUp": true } ], "cardWasFlipped": false, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 4, "sourceCardIndex": 1, "destPileType": "tableauPiles", "destPileIndex": 1, "cardsMoved": [ { "suit": "s", "rank": "T", "faceUp": true } ], "cardWasFlipped": true, "points": 1 }, { "move": "move", "sourcePileType": "tableauPiles", "sourcePileIndex": 4, "sourceCardIndex": 0, "destPileType": "foundationPiles", "destPileIndex": 2, "cardsMoved": [ { "suit": "s", "rank": "6", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "d", "rank": "5", "faceUp": true } }, { "move": "move", "sourcePileType": "wastePile", "sourcePileIndex": null, "sourceCardIndex": 0, "destPileType": "foundationPiles", "destPileIndex": 3, "cardsMoved": [ { "suit": "d", "rank": "5", "faceUp": true } ], "cardWasFlipped": false, "points": 11 }, { "move": "draw", "card": { "suit": "d", "rank": "6", "faceUp": true } } ], "hardMode": false, "autocompleting": false } + ); } } \ No newline at end of file diff --git a/src/game/blackjack.js b/src/game/blackjack.js index acc789b..ca3729f 100644 --- a/src/game/blackjack.js +++ b/src/game/blackjack.js @@ -7,7 +7,8 @@ import {getUser, insertLog, updateUserCoins} from "../database/index.js"; import {client} from "../bot/client.js"; import {EmbedBuilder} from "discord.js"; -export const RANKS = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; +// export const RANKS = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]; +export const RANKS = ["A", "2"]; export const SUITS = ["d","s","c","h"]; // Build a single 52-card deck like "Ad","Ts", etc. @@ -123,6 +124,7 @@ export function publicPlayerView(player) { result: h.result ?? null, total: handValue(h.cards).total, soft: handValue(h.cards).soft, + bet: h.bet, })), }; } @@ -139,7 +141,7 @@ export function createBlackjackRoom({ phaseDurations = { bettingMs: 15000, dealMs: 1000, - playMsPerPlayer: 15000, + playMsPerPlayer: 20000, revealMs: 1000, payoutMs: 10000, }, @@ -178,7 +180,7 @@ export function resetForNewRound(room) { for (const p of Object.values(room.players)) { p.inRound = false; p.currentBet = 0; - p.hands = [ { cards: [], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false } ]; + p.hands = [ { cards: [], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false, bet: 0 } ]; p.activeHand = 0; } } @@ -200,7 +202,7 @@ export function dealInitial(room) { const actives = Object.values(room.players).filter(p => p.currentBet >= room.minBet); for (const p of actives) { p.inRound = true; - p.hands = [ { cards: [draw(room.shoe)], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false } ]; + p.hands = [ { cards: [draw(room.shoe)], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false, bet: p.currentBet } ]; } room.dealer.cards = [draw(room.shoe), draw(room.shoe)]; room.dealer.holeHidden = true; @@ -225,8 +227,7 @@ export function autoActions(room) { export function everyoneDone(room) { return Object.values(room.players).every(p => { if (!p.inRound) return true; - const h = p.hands[p.activeHand]; - return h.stood || h.busted || isBlackjack(h.cards) || h.surrendered; + return p.hands.filter(h => !h.stood && !h.busted && !h.surrendered)?.length === 0; }); } @@ -243,58 +244,70 @@ export async function settleAll(room) { const allRes = {} for (const p of Object.values(room.players)) { if (!p.inRound) continue; - const hand = p.hands[p.activeHand]; - const res = settleHand({ - bet: p.currentBet, - playerCards: hand.cards, - dealerCards: room.dealer.cards, - doubled: hand.doubled, - surrendered: hand.surrendered, - blackjackPayout: room.settings.blackjackPayout, - }); - allRes[p.id] = res; - p.totalDelta += res.delta - if (res.result === 'win' || res.result === 'push') { - const userDB = getUser.get(p.id); - if (userDB) { - const coins = userDB.coins; - try { - updateUserCoins.run({ id: p.id, coins: coins + p.currentBet + res.delta }); - insertLog.run({ - id: `${p.id}-blackjack-${Date.now()}`, - user_id: p.id, target_user_id: null, - action: 'BLACKJACK_PAYOUT', - coins_amount: res.delta + p.currentBet, user_new_amount: coins + p.currentBet + res.delta, - }); - p.bank = coins + p.currentBet + res.delta - } catch (e) { - console.log(e) + for (const hand of p.hands) { + const res = settleHand({ + bet: hand.bet, + playerCards: hand.cards, + dealerCards: room.dealer.cards, + doubled: hand.doubled, + surrendered: hand.surrendered, + blackjackPayout: room.settings.blackjackPayout, + }); + if (allRes[p.id]) { + allRes[p.id].push(res); + } else { + allRes[p.id] = [res]; + } + + p.totalDelta += res.delta + p.totalBets++ + if (res.result === 'win' || res.result === 'push' || res.result === 'blackjack') { + const userDB = getUser.get(p.id); + if (userDB) { + const coins = userDB.coins; + try { + updateUserCoins.run({ id: p.id, coins: coins + hand.bet + res.delta }); + insertLog.run({ + id: `${p.id}-blackjack-${Date.now()}`, + user_id: p.id, target_user_id: null, + action: 'BLACKJACK_PAYOUT', + coins_amount: res.delta + hand.bet, user_new_amount: coins + hand.bet + res.delta, + }); + p.bank = coins + hand.bet + res.delta + } catch (e) { + console.log(e) + } } } - } - emitToast({ type: `payout-res`, allRes }); - hand.result = res.result; - hand.delta = res.delta; - 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 msg = await generalChannel.messages.fetch(p.msgId); - const updatedEmbed = new EmbedBuilder() - .setDescription(`<@${p.id}> joue au Blackjack.`) - .addFields( - { - name: `Gains`, - value: `**${p.totalDelta >= 0 ? '+' + p.totalDelta : p.totalDelta}** Flopos`, - inline: true - }, - ) - .setColor(p.totalDelta >= 0 ? 0x22A55B : 0xED4245) - .setTimestamp(new Date()); - await msg.edit({ embeds: [updatedEmbed], components: [] }); - } catch (e) { - console.log(e); + emitToast({ type: `payout-res`, allRes }); + hand.result = res.result; + hand.delta = res.delta; + 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 msg = await generalChannel.messages.fetch(p.msgId); + const updatedEmbed = new EmbedBuilder() + .setDescription(`<@${p.id}> joue au Blackjack.`) + .addFields( + { + name: `Gains`, + value: `**${p.totalDelta >= 0 ? '+' + p.totalDelta : p.totalDelta}** Flopos`, + inline: true + }, + { + name: `Mises jouées`, + value: `**${p.totalBets}**`, + inline: true + } + ) + .setColor(p.totalDelta >= 0 ? 0x22A55B : 0xED4245) + .setTimestamp(new Date()); + await msg.edit({ embeds: [updatedEmbed], components: [] }); + } catch (e) { + console.log(e); + } } } } @@ -316,25 +329,56 @@ export function applyAction(room, playerId, action) { case "stand": { hand.stood = true; hand.hasActed = true; + p.activeHand++; return "stand"; } case "double": { if (!canDouble(hand)) throw new Error("Cannot double now"); hand.doubled = true; - p.currentBet*=2 + hand.bet*=2 + p.currentBet+=hand.bet/2 hand.hasActed = true; // The caller (routes) must also handle additional balance lock on the bet if using real coins hand.cards.push(draw(room.shoe)); if (isBust(hand.cards)) hand.busted = true; else hand.stood = true; + p.activeHand++; return "double"; } - case "surrender": { - if (hand.cards.length !== 2 || hand.hasActed) throw new Error("Cannot surrender now"); - hand.surrendered = true; - hand.stood = true; - hand.hasActed = true; - return "surrender"; + case "split": { + if (hand.cards.length !== 2) throw new Error("Cannot split: not exactly 2 cards"); + const r0 = hand.cards[0][0]; + const r1 = hand.cards[1][0]; + if (r0 !== r1) throw new Error("Cannot split: cards not same rank"); + + const cardA = hand.cards[0]; + const cardB = hand.cards[1]; + + hand.cards = [cardA]; + hand.stood = false; + hand.busted = false; + hand.doubled = false; + hand.surrendered = false; + hand.hasActed = false; + + const newHand = { + cards: [cardB], + stood: false, + busted: false, + doubled: false, + surrendered: false, + hasActed: false, + bet: hand.bet, + } + + p.currentBet *= 2 + + p.hands.splice(p.activeHand + 1, 0, newHand); + + hand.cards.push(draw(room.shoe)); + newHand.cards.push(draw(room.shoe)); + + return "split"; } default: throw new Error("Invalid action"); diff --git a/src/game/solitaire.js b/src/game/solitaire.js index faf0e82..a5f76eb 100644 --- a/src/game/solitaire.js +++ b/src/game/solitaire.js @@ -1,4 +1,7 @@ // --- Constants for Deck Creation --- +import {sleep} from "openai/core"; +import {emitSolitaireUpdate, emitUpdate} from "../server/socket.js"; + 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']; @@ -296,6 +299,72 @@ export function checkWinCondition(gameState) { return foundationCardCount === 52; } +/** + * Checks if the game can be automatically solved (all tableau cards are face-up). + * @param {Object} gameState - The current state of the game. + * @returns {boolean} True if the game can be auto-solved. + */ +export function checkAutoSolve(gameState) { + if (gameState.stockPile.length > 0 || gameState.wastePile.length > 0) return false; + for (const pile of gameState.tableauPiles) { + for (const card of pile) { + if (!card.faceUp) return false; + } + } + return true; +} + +export function autoSolveMoves(userId, gameState) { + const moves = []; + const foundations = JSON.parse(JSON.stringify(gameState.foundationPiles)); + const tableau = JSON.parse(JSON.stringify(gameState.tableauPiles)); + + function canMoveToFoundation(card) { + let foundationPile = foundations.find(pile => pile[pile.length - 1]?.suit === card.suit); + if (!foundationPile) { + foundationPile = foundations.find(pile => pile.length === 0); + } + if (foundationPile.length === 0) { + return card.rank === 'A'; // Only Ace can be placed on empty foundation + } else { + const topCard = foundationPile[foundationPile.length - 1]; + return card.suit === topCard.suit && getRankValue(card.rank) === getRankValue(topCard.rank) + 1; + } + } + + let moved; + do { + moved = false; + + for (let i = 0; i < tableau.length; i++) { + const column = tableau[i]; + if (column.length === 0) continue; + + const card = column[column.length - 1]; // Top card of the tableau column + let foundationIndex = foundations.findIndex(pile => pile[pile.length - 1]?.suit === card.suit); + if (foundationIndex === -1) { + foundationIndex = foundations.findIndex(pile => pile.length === 0); + } + if(canMoveToFoundation(card)) { + let moveData = { + destPileIndex: foundationIndex, + destPileType: 'foundationPiles', + sourceCardIndex: column.length - 1, + sourcePileIndex: i, + sourcePileType: 'tableauPiles', + userId: userId, + } + tableau[i].pop() + foundations[foundationIndex].push(card) + //moveCard(gameState, moveData) + moves.push(moveData); + moved = true; + } + } + } while (moved)//(foundations.reduce((acc, pile) => acc + pile.length, 0)); + emitSolitaireUpdate(userId, moves) +} + /** * Reverts the game state to its previous state based on the last move in the history. * This function mutates the gameState object directly. diff --git a/src/server/routes/blackjack.js b/src/server/routes/blackjack.js index 8069644..fe8296f 100644 --- a/src/server/routes/blackjack.js +++ b/src/server/routes/blackjack.js @@ -32,7 +32,7 @@ export function blackjackRoutes(io) { hitSoft17: false, // S17 (dealer stands on soft 17) if false blackjackPayout: 1.5, // 3:2 cutCardRatio: 0.25, - phaseDurations: { bettingMs: 10000, dealMs: 2000, playMsPerPlayer: 15000, revealMs: 1000, payoutMs: 7000 }, + phaseDurations: { bettingMs: 10000, dealMs: 2000, playMsPerPlayer: 20000, revealMs: 1000, payoutMs: 7000 }, animation: { dealerDrawMs: 1000 } }); @@ -124,11 +124,12 @@ export function blackjackRoutes(io) { bank, currentBet: 0, inRound: false, - hands: [{ cards: [], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false }], + hands: [{ cards: [], stood: false, busted: false, doubled: false, surrendered: false, hasActed: false, bet: 0 }], activeHand: 0, joined_at: Date.now(), msgId: null, totalDelta: 0, + totalBets: 0, }; try { @@ -139,11 +140,16 @@ export function blackjackRoutes(io) { const embed = new EmbedBuilder() .setDescription(`<@${userId}> joue au Blackjack`) .addFields( - { - name: `Gains`, - value: `**${room.players[userId].totalDelta >= 0 ? '+' + room.players[userId].totalDelta : room.players[userId].totalDelta}** Flopos`, - inline: true - }, + { + name: `Gains`, + value: `**${room.players[userId].totalDelta >= 0 ? '+' + room.players[userId].totalDelta : room.players[userId].totalDelta}** Flopos`, + inline: true + }, + { + name: `Mises jouées`, + value: `**${room.players[userId].totalBets}**`, + inline: true + } ) .setColor('#5865f2') .setTimestamp(new Date()); @@ -176,6 +182,11 @@ export function blackjackRoutes(io) { value: `**${room.players[userId].totalDelta >= 0 ? '+' + room.players[userId].totalDelta : room.players[userId].totalDelta}** Flopos`, inline: true }, + { + name: `Mises jouées`, + value: `**${room.players[userId].totalBets}**`, + inline: true + } ) .setColor(room.players[userId].totalDelta >= 0 ? 0x22A55B : 0xED4245) .setTimestamp(new Date()); @@ -220,6 +231,7 @@ export function blackjackRoutes(io) { } p.currentBet = bet; + p.hands[p.activeHand].bet = bet; emitToast({ type: "player-bet", userId, amount: bet }); emitUpdate("bet-placed", snapshot(room)); return res.status(200).json({ message: "bet-accepted" }); @@ -236,15 +248,32 @@ export function blackjackRoutes(io) { if (action === "double" && !room.settings.fakeMoney) { const userDB = getUser.get(userId); const coins = userDB?.coins ?? 0; - if (coins < p.currentBet) return res.status(403).json({ message: "insufficient-funds-for-double" }); - updateUserCoins.run({ id: userId, coins: coins - p.currentBet }); + const hand = p.hands[p.activeHand]; + if (coins < hand.bet) return res.status(403).json({ message: "insufficient-funds-for-double" }); + updateUserCoins.run({ id: userId, coins: coins - hand.bet }); insertLog.run({ id: `${userId}-blackjack-${Date.now()}`, user_id: userId, target_user_id: null, action: 'BLACKJACK_DOUBLE', - coins_amount: -p.currentBet, user_new_amount: coins - p.currentBet, + coins_amount: -hand.bet, user_new_amount: coins - hand.bet, }); - p.bank = coins - p.currentBet; + p.bank = coins - hand.bet; + // effective bet size is handled in settlement via hand.doubled flag + } + + if (action === "split" && !room.settings.fakeMoney) { + const userDB = getUser.get(userId); + const coins = userDB?.coins ?? 0; + const hand = p.hands[p.activeHand]; + if (coins < hand.bet) return res.status(403).json({ message: "insufficient-funds-for-split" }); + updateUserCoins.run({ id: userId, coins: coins - hand.bet }); + insertLog.run({ + id: `${userId}-blackjack-${Date.now()}`, + user_id: userId, target_user_id: null, + action: 'BLACKJACK_SPLIT', + coins_amount: -hand.bet, user_new_amount: coins - hand.bet, + }); + p.bank = coins - hand.bet; // effective bet size is handled in settlement via hand.doubled flag } diff --git a/src/server/routes/solitaire.js b/src/server/routes/solitaire.js index a0b579f..9082d0e 100644 --- a/src/server/routes/solitaire.js +++ b/src/server/routes/solitaire.js @@ -3,7 +3,7 @@ import express from 'express'; // --- Game Logic Imports --- import { createDeck, shuffle, deal, isValidMove, moveCard, drawCard, - checkWinCondition, createSeededRNG, seededShuffle, undoMove, draw3Cards + checkWinCondition, createSeededRNG, seededShuffle, undoMove, draw3Cards, checkAutoSolve, autoSolveMoves } from '../../game/solitaire.js'; // --- Game State & Database Imports --- @@ -60,6 +60,7 @@ export function solitaireRoutes(client, io) { gameState.moves = 0; gameState.hist = []; gameState.hardMode = hardMode ?? false; + gameState.autocompleting = false; activeSolitaireGames[userId] = gameState; res.json({ success: true, gameState }); @@ -94,6 +95,7 @@ export function solitaireRoutes(client, io) { seed: sotd.seed, hist: [], hardMode: false, + autocompleting: false, }; activeSolitaireGames[userId] = gameState; @@ -140,8 +142,17 @@ export function solitaireRoutes(client, io) { moveCard(gameState, moveData); updateGameStats(gameState, 'move', moveData); + if (!gameState.autocompleting) { + const canAutoSolve = checkAutoSolve(gameState); + if (canAutoSolve) { + gameState.autocompleting = true; + autoSolveMoves(userId, gameState) + } + } + const win = checkWinCondition(gameState); if (win) { + console.log("win") gameState.isDone = true; await handleWin(userId, gameState, io); } diff --git a/src/server/socket.js b/src/server/socket.js index c5fa0db..fc3a28b 100644 --- a/src/server/socket.js +++ b/src/server/socket.js @@ -344,4 +344,6 @@ export async function emitPokerToast(data) { } export const emitUpdate = (type, room) => io.emit("blackjack:update", { type, room }); -export const emitToast = (payload) => io.emit("blackjack:toast", payload); \ No newline at end of file +export const emitToast = (payload) => io.emit("blackjack:toast", payload); + +export const emitSolitaireUpdate = (userId, moves) => io.emit('solitaire:update', {userId, moves}); \ No newline at end of file