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 { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { redis } from '$lib/server/redis';
interface MinesSession {
sessionToken: string;
@ -16,13 +16,17 @@ interface MinesSession {
userId: number;
}
export const activeGames = new Map<string, MinesSession>();
const MINES_SESSION_PREFIX = 'mines:session:';
export const getSessionKey = (token: string) => `${MINES_SESSION_PREFIX}${token}`;
// Clean up old games every minute. (5 Minute system)
setInterval(async () => {
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 (game.revealedTiles.length === 0) {
try {
@ -43,19 +47,22 @@ setInterval(async () => {
updatedAt: new Date()
})
.where(eq(user.id, game.userId));
} 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);
setInterval(async () => {
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) {
try {
const [userData] = await db
@ -78,9 +85,9 @@ setInterval(async () => {
})
.where(eq(user.id, game.userId));
activeGames.delete(token);
await redis.del(getSessionKey(game.sessionToken));
} 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 { user } from '$lib/server/db/schema';
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';
export const POST: RequestHandler = async ({ request }) => {
@ -17,7 +18,8 @@ export const POST: RequestHandler = async ({ request }) => {
try {
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);
if (!game) {
@ -36,18 +38,16 @@ export const POST: RequestHandler = async ({ request }) => {
let payout: 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) {
payout = game.betAmount;
newBalance = Math.round((currentBalance + payout) * 100000000) / 100000000;
} else {
// Calculate payout
payout = game.betAmount * game.currentMultiplier;
const roundedPayout = Math.round(payout * 100000000) / 100000000;
newBalance = Math.round((currentBalance + roundedPayout) * 100000000) / 100000000;
}
await tx
.update(user)
.set({
@ -56,8 +56,7 @@ export const POST: RequestHandler = async ({ request }) => {
})
.where(eq(user.id, userId));
activeGames.delete(sessionToken);
await redis.del(getSessionKey(sessionToken));
return {
newBalance,

View file

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

View file

@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
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';
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)}`);
}
// Generate mine positions
const positions = new Set<number>();
while (positions.size < mineCount) {
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
const randomBytes = new Uint8Array(8);
@ -64,19 +60,21 @@ export const POST: RequestHandler = async ({ request }) => {
const now = Date.now();
const newBalance = roundedBalance - roundedAmount;
// Create session
activeGames.set(sessionToken, {
sessionToken,
betAmount: roundedAmount,
mineCount,
minePositions: Array.from(positions),
revealedTiles: [],
startTime: now,
lastActivity: now,
currentMultiplier: 1,
status: 'active',
userId
});
await redis.set(
getSessionKey(sessionToken),
JSON.stringify({
sessionToken,
betAmount: roundedAmount,
mineCount,
minePositions: Array.from(positions),
revealedTiles: [],
startTime: now,
lastActivity: now,
currentMultiplier: 1,
status: 'active',
userId
})
);
// Update user balance
await tx
@ -87,7 +85,6 @@ export const POST: RequestHandler = async ({ request }) => {
})
.where(eq(user.id, userId));
return {
sessionToken,
newBalance