blackjack beta 1

This commit is contained in:
milo
2025-09-12 14:53:52 +02:00
parent 1233fd5b39
commit 65073776ec
4 changed files with 126 additions and 39 deletions

View File

@@ -12,7 +12,7 @@ export function initializeEvents(client, io) {
// --- on 'ready' ---
// This event fires once the bot has successfully logged in and is ready to operate.
// It's a good place for setup tasks that require the bot to be online.
client.once('ready', async () => {
client.once('clientReady', async () => {
console.log(`Bot is ready and logged in as ${client.user.tag}!`);
console.log('[Startup] Bot is ready, performing initial data sync...');
await getAkhys(client);

View File

@@ -2,6 +2,9 @@
// Core blackjack helpers for a single continuous room.
// Inspired by your poker helpers API style.
import {emitToast} from "../server/socket.js";
import {getUser, insertLog, updateUserCoins} from "../database/index.js";
export const RANKS = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"];
export const SUITS = ["d","s","c","h"];
@@ -88,7 +91,7 @@ export function settleHand({ bet, playerCards, dealerCards, doubled = false, sur
if (pBJ && dBJ) return { delta: 0, result: "push" };
const outcome = compareHands(playerCards, dealerCards);
let unit = bet * (doubled ? 2 : 1);
let unit = bet;
if (outcome === "win") return { delta: unit, result: "win" };
if (outcome === "lose") return { delta: -unit, result: "lose" };
return { delta: 0, result: "push" };
@@ -131,7 +134,16 @@ export function createBlackjackRoom({
hitSoft17 = false,
blackjackPayout = 1.5,
cutCardRatio = 0.25, // reshuffle when 25% of shoe remains
phaseDurations = { bettingMs: 15000, dealMs: 1000, playMsPerPlayer: 15000, revealMs: 1000, payoutMs: 2000 }
phaseDurations = {
bettingMs: 15000,
dealMs: 1000,
playMsPerPlayer: 15000,
revealMs: 1000,
payoutMs: 10000,
},
animation = {
dealerDrawMs: 500,
}
} = {}) {
return {
id: "blackjack-room",
@@ -140,7 +152,7 @@ export function createBlackjackRoom({
status: "betting", // betting | dealing | playing | dealer | payout | shuffle
phase_ends_at: Date.now() + phaseDurations.bettingMs,
minBet, maxBet, fakeMoney,
settings: { decks, hitSoft17, blackjackPayout, cutCardRatio, phaseDurations },
settings: { decks, hitSoft17, blackjackPayout, cutCardRatio, phaseDurations, animation },
shoe: buildShoe(decks),
discard: [],
dealer: { cards: [], holeHidden: true },
@@ -226,6 +238,7 @@ export function dealerPlay(room) {
export function settleAll(room) {
room.status = "payout";
const allRes = {}
for (const p of Object.values(room.players)) {
if (!p.inRound) continue;
const hand = p.hands[p.activeHand];
@@ -237,6 +250,24 @@ export function settleAll(room) {
surrendered: hand.surrendered,
blackjackPayout: room.settings.blackjackPayout,
});
allRes[p.id] = res;
if (res.result === 'win' || res.result === 'push') {
const userDB = getUser.get(p.id);
if (userDB) {
try {
updateUserCoins.run({ id: p.id, coins: userDB.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: userDB.coins + p.currentBet + res.delta,
});
} catch (e) {
console.log(e)
}
}
}
emitToast({ type: `payout-res`, allRes });
hand.result = res.result;
hand.delta = res.delta;
}
@@ -264,6 +295,7 @@ export function applyAction(room, playerId, action) {
case "double": {
if (!canDouble(hand)) throw new Error("Cannot double now");
hand.doubled = true;
p.currentBet*=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));

View File

@@ -22,7 +22,7 @@ const FLAPI_URL = process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV :
// CORS Middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', FLAPI_URL);
res.header('Access-Control-Allow-Headers', 'Content-Type, X-API-Key, ngrok-skip-browser-warning');
res.header('Access-Control-Allow-Headers', 'Content-Type, X-API-Key, ngrok-skip-browser-warning, Cache-Control, Pragma, Expires');
next();
});

View File

@@ -1,6 +1,18 @@
// /routes/blackjack.js
import express from "express";
import { createBlackjackRoom, startBetting, dealInitial, autoActions, everyoneDone, dealerPlay, settleAll, applyAction, publicPlayerView, handValue } from "../../game/blackjack.js";
import {
createBlackjackRoom,
startBetting,
dealInitial,
autoActions,
everyoneDone,
dealerPlay,
settleAll,
applyAction,
publicPlayerView,
handValue,
dealerShouldHit, draw
} from "../../game/blackjack.js";
// Optional: hook into your DB & Discord systems if available
import { getUser, updateUserCoins, insertLog } from "../../database/index.js";
@@ -19,9 +31,65 @@ export function blackjackRoutes(io) {
hitSoft17: false, // S17 (dealer stands on soft 17) if false
blackjackPayout: 1.5, // 3:2
cutCardRatio: 0.25,
phaseDurations: { bettingMs: 15000, dealMs: 1000, playMsPerPlayer: 15000, revealMs: 1000, payoutMs: 2000 },
phaseDurations: { bettingMs: 10000, dealMs: 2000, playMsPerPlayer: 15000, revealMs: 1000, payoutMs: 7000 },
animation: { dealerDrawMs: 1000 }
});
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
let animatingDealer = false;
async function runDealerAnimation() {
if (animatingDealer) return;
animatingDealer = true;
room.status = "dealer";
room.dealer.holeHidden = false;
await sleep(room.settings.phaseDurations.revealMs ?? 1000);
room.phase_ends_at = Date.now() + (room.settings.phaseDurations.revealMs ?? 1000);
emitUpdate("dealer-reveal", snapshot(room));
await sleep(room.settings.phaseDurations.revealMs ?? 1000);
while (dealerShouldHit(room.dealer.cards, room.settings.hitSoft17)) {
room.dealer.cards.push(draw(room.shoe));
room.phase_ends_at = Date.now() + (room.settings.animation?.dealerDrawMs ?? 500);
emitUpdate("dealer-hit", snapshot(room));
await sleep(room.settings.animation?.dealerDrawMs ?? 500);
}
settleAll(room);
room.status = "payout";
room.phase_ends_at = Date.now() + (room.settings.phaseDurations.payoutMs ?? 10000);
emitUpdate("payout", snapshot(room))
animatingDealer = false;
}
function autoTimeoutAFK(now) {
if (room.status !== "playing") return false;
if (!room.phase_ends_at || now < room.phase_ends_at) return false;
let changed = false;
for (const p of Object.values(room.players)) {
if (!p.inRound) continue;
const h = p.hands[p.activeHand];
if (!h.hasActed && !h.busted && !h.stood && !h.surrendered) {
h.surrendered = true;
h.stood = true;
h.hasActed = true;
room.leavingAfterRound[p.id] = true; // kick at end of round
emitToast({ type: "player-timeout", userId: p.id });
changed = true;
} else if (h.hasActed && !h.stood) {
h.stood = true;
room.leavingAfterRound[p.id] = true; // kick at end of round
emitToast({ type: "player-auto-stand", userId: p.id });
changed = true;
}
}
if (changed) emitUpdate("auto-surrender", snapshot(room));
return changed;
}
function snapshot(r) {
return {
id: r.id,
@@ -144,7 +212,7 @@ export function blackjackRoutes(io) {
// --- Game loop ---
// Simple phase machine that runs regardless of player count.
setInterval(() => {
setInterval(async () => {
const now = Date.now();
if (room.status === "betting" && now >= room.phase_ends_at) {
@@ -158,41 +226,28 @@ export function blackjackRoutes(io) {
dealInitial(room);
autoActions(room);
emitUpdate("initial-deal", snapshot(room));
room.phase_ends_at = Date.now() + room.settings.phaseDurations.playMsPerPlayer;
emitUpdate("playing-start", snapshot(room));
return;
}
if (room.status === "playing") {
// When all active players are done, proceed to dealer play
if (everyoneDone(room)) {
dealerPlay(room);
emitUpdate("dealer-start", snapshot(room));
}
}
if (room.status === "dealer") {
settleAll(room);
// Apply coin deltas
for (const p of Object.values(room.players)) {
if (!p.inRound) continue;
const h = p.hands[p.activeHand];
if (room.settings.fakeMoney) continue;
if (typeof h.delta === "number" && h.delta !== 0) {
const userDB = getUser.get(p.id);
if (userDB) {
updateUserCoins.run({ id: p.id, coins: userDB.coins + h.delta });
insertLog.run({
id: `${p.id}-blackjack-${Date.now()}`,
user_id: p.id, target_user_id: null,
action: `BLACKJACK_${h.delta > 0 ? "WIN" : "LOSE"}`,
coins_amount: h.delta, user_new_amount: userDB.coins + h.delta,
});
}
}
// If the per-round playing timer expired, auto-surrender AFKs (you already added this)
if (room.phase_ends_at && now >= room.phase_ends_at) {
autoTimeoutAFK(now);
}
room.phase_ends_at = now + room.settings.phaseDurations.payoutMs;
emitUpdate("payout", snapshot(room));
room.status = "payout";
// Everyone acted before the timer? Cut short and go straight to dealer.
if (everyoneDone(room) && !animatingDealer) {
// Set a new server-driven deadline for the reveal pause,
// so the client's countdown immediately reflects the phase change.
room.phase_ends_at = Date.now();
emitUpdate("playing-cut-short", snapshot(room));
// Now run the animated dealer with per-step updates
runDealerAnimation();
}
}
if (room.status === "payout" && now >= room.phase_ends_at) {
@@ -204,7 +259,7 @@ export function blackjackRoutes(io) {
startBetting(room, now);
emitUpdate("new-round", snapshot(room));
}
}, 400);
}, 100);
return router;
}