make mines use redis

This commit is contained in:
Face 2025-06-24 12:53:52 +03:00
parent ad1739f7f4
commit af18d7f423
4 changed files with 56 additions and 52 deletions

View file

@ -1,7 +1,7 @@
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema'; import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { redis } from '$lib/server/redis';
interface MinesSession { interface MinesSession {
sessionToken: string; sessionToken: string;
@ -16,13 +16,17 @@ interface MinesSession {
userId: number; userId: number;
} }
const MINES_SESSION_PREFIX = 'mines:session:';
export const activeGames = new Map<string, MinesSession>(); export const getSessionKey = (token: string) => `${MINES_SESSION_PREFIX}${token}`;
// Clean up old games every minute. (5 Minute system) // Clean up old games every minute. (5 Minute system)
setInterval(async () => { setInterval(async () => {
const now = Date.now(); const now = Date.now();
for (const [token, game] of activeGames.entries()) { const keys = await redis.keys(`${MINES_SESSION_PREFIX}*`);
for (const key of keys) {
const sessionRaw = await redis.get(key);
if (!sessionRaw) continue;
const game = JSON.parse(sessionRaw) as MinesSession;
if (now - game.lastActivity > 5 * 60 * 1000) { if (now - game.lastActivity > 5 * 60 * 1000) {
if (game.revealedTiles.length === 0) { if (game.revealedTiles.length === 0) {
try { try {
@ -43,19 +47,22 @@ setInterval(async () => {
updatedAt: new Date() updatedAt: new Date()
}) })
.where(eq(user.id, game.userId)); .where(eq(user.id, game.userId));
} catch (error) { } catch (error) {
console.error(`Failed to refund inactive game ${token}:`, error); console.error(`Failed to refund inactive game ${game.sessionToken}:`, error);
} }
} }
activeGames.delete(token); await redis.del(getSessionKey(game.sessionToken));
} }
} }
}, 60000); }, 60000);
setInterval(async () => { setInterval(async () => {
const now = Date.now(); const now = Date.now();
for (const [token, game] of activeGames.entries()) { const keys = await redis.keys(`${MINES_SESSION_PREFIX}*`);
for (const key of keys) {
const sessionRaw = await redis.get(key);
if (!sessionRaw) continue;
const game = JSON.parse(sessionRaw) as MinesSession;
if (game.status === 'active' && game.revealedTiles.length > 0 && now - game.lastActivity > 20000) { if (game.status === 'active' && game.revealedTiles.length > 0 && now - game.lastActivity > 20000) {
try { try {
const [userData] = await db const [userData] = await db
@ -78,9 +85,9 @@ setInterval(async () => {
}) })
.where(eq(user.id, game.userId)); .where(eq(user.id, game.userId));
activeGames.delete(token); await redis.del(getSessionKey(game.sessionToken));
} catch (error) { } catch (error) {
console.error(`Failed to auto cashout game ${token}:`, error); console.error(`Failed to auto cashout game ${game.sessionToken}:`, error);
} }
} }
} }

View file

@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema'; import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { activeGames } from '$lib/server/games/mines'; import { redis } from '$lib/server/redis';
import { getSessionKey } from '$lib/server/games/mines';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ request }) => {
@ -17,7 +18,8 @@ export const POST: RequestHandler = async ({ request }) => {
try { try {
const { sessionToken } = await request.json(); const { sessionToken } = await request.json();
const game = activeGames.get(sessionToken); const sessionRaw = await redis.get(getSessionKey(sessionToken));
const game = sessionRaw ? JSON.parse(sessionRaw) : null;
const userId = Number(session.user.id); const userId = Number(session.user.id);
if (!game) { if (!game) {
@ -36,18 +38,16 @@ export const POST: RequestHandler = async ({ request }) => {
let payout: number; let payout: number;
let newBalance: number; let newBalance: number;
// If no tiles revealed, treat as abort and return full bet. This could be changed later to keep the initial bet on the Server // If no tiles revealed, treat as abort and return full bet.
if (game.revealedTiles.length === 0) { if (game.revealedTiles.length === 0) {
payout = game.betAmount; payout = game.betAmount;
newBalance = Math.round((currentBalance + payout) * 100000000) / 100000000; newBalance = Math.round((currentBalance + payout) * 100000000) / 100000000;
} else { } else {
// Calculate payout
payout = game.betAmount * game.currentMultiplier; payout = game.betAmount * game.currentMultiplier;
const roundedPayout = Math.round(payout * 100000000) / 100000000; const roundedPayout = Math.round(payout * 100000000) / 100000000;
newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000; newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000;
} }
await tx await tx
.update(user) .update(user)
.set({ .set({
@ -56,8 +56,7 @@ export const POST: RequestHandler = async ({ request }) => {
}) })
.where(eq(user.id, userId)); .where(eq(user.id, userId));
await redis.del(getSessionKey(sessionToken));
activeGames.delete(sessionToken);
return { return {
newBalance, newBalance,

View file

@ -1,10 +1,12 @@
import { auth } from '$lib/auth'; import { auth } from '$lib/auth';
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import { activeGames, calculateMultiplier } from '$lib/server/games/mines'; import { calculateMultiplier } from '$lib/server/games/mines';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema'; import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { redis, } from '$lib/server/redis';
import { getSessionKey } from '$lib/server/games/mines';
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ request }) => {
const session = await auth.api.getSession({ const session = await auth.api.getSession({
@ -17,7 +19,8 @@ export const POST: RequestHandler = async ({ request }) => {
try { try {
const { sessionToken, tileIndex } = await request.json(); const { sessionToken, tileIndex } = await request.json();
const game = activeGames.get(sessionToken); const sessionRaw = await redis.get(getSessionKey(sessionToken));
const game = sessionRaw ? JSON.parse(sessionRaw) : null;
if (!game) { if (!game) {
return json({ error: 'Invalid session' }, { status: 400 }); return json({ error: 'Invalid session' }, { status: 400 });
@ -29,7 +32,6 @@ export const POST: RequestHandler = async ({ request }) => {
game.lastActivity = Date.now(); game.lastActivity = Date.now();
if (game.minePositions.includes(tileIndex)) { if (game.minePositions.includes(tileIndex)) {
game.status = 'lost'; game.status = 'lost';
const minePositions = game.minePositions; const minePositions = game.minePositions;
@ -44,7 +46,6 @@ export const POST: RequestHandler = async ({ request }) => {
const currentBalance = Number(userData.baseCurrencyBalance); const currentBalance = Number(userData.baseCurrencyBalance);
await db await db
.update(user) .update(user)
.set({ .set({
@ -53,8 +54,7 @@ export const POST: RequestHandler = async ({ request }) => {
}) })
.where(eq(user.id, userId)); .where(eq(user.id, userId));
activeGames.delete(sessionToken); await redis.del(getSessionKey(sessionToken));
return json({ return json({
hitMine: true, hitMine: true,
@ -65,8 +65,7 @@ export const POST: RequestHandler = async ({ request }) => {
}); });
} }
// Safe tile
// Safe tile (Yipeee)
game.revealedTiles.push(tileIndex); game.revealedTiles.push(tileIndex);
game.currentMultiplier = calculateMultiplier( game.currentMultiplier = calculateMultiplier(
game.revealedTiles.length, game.revealedTiles.length,
@ -97,7 +96,7 @@ export const POST: RequestHandler = async ({ request }) => {
}) })
.where(eq(user.id, userId)); .where(eq(user.id, userId));
activeGames.delete(sessionToken); await redis.del(getSessionKey(sessionToken));
return json({ return json({
hitMine: false, hitMine: false,
@ -108,6 +107,8 @@ export const POST: RequestHandler = async ({ request }) => {
}); });
} }
await redis.set(getSessionKey(sessionToken), JSON.stringify(game));
return json({ return json({
hitMine: false, hitMine: false,
currentMultiplier: game.currentMultiplier, currentMultiplier: game.currentMultiplier,

View file

@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema'; import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { activeGames } from '$lib/server/games/mines'; import { redis } from '$lib/server/redis';
import { getSessionKey } from '$lib/server/games/mines';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ request }) => {
@ -43,16 +44,11 @@ export const POST: RequestHandler = async ({ request }) => {
throw new Error(`Insufficient funds. You need *${roundedAmount.toFixed(2)} but only have *${roundedBalance.toFixed(2)}`); throw new Error(`Insufficient funds. You need *${roundedAmount.toFixed(2)} but only have *${roundedBalance.toFixed(2)}`);
} }
// Generate mine positions // Generate mine positions
const positions = new Set<number>(); const positions = new Set<number>();
while (positions.size < mineCount) { while (positions.size < mineCount) {
positions.add(Math.floor(Math.random() * 25)); positions.add(Math.floor(Math.random() * 25));
} }
const safePositions = [];
for (let i = 0; i < 25; i++) {
if (!positions.has(i)) safePositions.push(i);
}
// transaction token for authentication stuff // transaction token for authentication stuff
const randomBytes = new Uint8Array(8); const randomBytes = new Uint8Array(8);
@ -64,19 +60,21 @@ export const POST: RequestHandler = async ({ request }) => {
const now = Date.now(); const now = Date.now();
const newBalance = roundedBalance - roundedAmount; const newBalance = roundedBalance - roundedAmount;
// Create session await redis.set(
activeGames.set(sessionToken, { getSessionKey(sessionToken),
sessionToken, JSON.stringify({
betAmount: roundedAmount, sessionToken,
mineCount, betAmount: roundedAmount,
minePositions: Array.from(positions), mineCount,
revealedTiles: [], minePositions: Array.from(positions),
startTime: now, revealedTiles: [],
lastActivity: now, startTime: now,
currentMultiplier: 1, lastActivity: now,
status: 'active', currentMultiplier: 1,
userId status: 'active',
}); userId
})
);
// Update user balance // Update user balance
await tx await tx
@ -87,7 +85,6 @@ export const POST: RequestHandler = async ({ request }) => {
}) })
.where(eq(user.id, userId)); .where(eq(user.id, userId));
return { return {
sessionToken, sessionToken,
newBalance newBalance