Merge pull request #65 from cassoule/milo-260121

feat: monke game
This commit is contained in:
Milo Gourvest
2026-01-22 10:38:00 +01:00
committed by GitHub
5 changed files with 155 additions and 25 deletions

View File

@@ -584,9 +584,11 @@ export const updateUser = flopoDB.prepare(
export const updateUserAvatar = flopoDB.prepare("UPDATE users SET avatarUrl = @avatarUrl WHERE id = @id");
export const queryDailyReward = flopoDB.prepare(`UPDATE users
SET dailyQueried = 1
WHERE id = ?`);
WHERE id = ?`
);
export const resetDailyReward = flopoDB.prepare(`UPDATE users
SET dailyQueried = 0`);
SET dailyQueried = 0`
);
export const updateUserCoins = flopoDB.prepare("UPDATE users SET coins = @coins WHERE id = @id");
export const getUser = flopoDB.prepare(
"SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id WHERE users.id = ?",
@@ -647,7 +649,6 @@ export const getMarketOffers = flopoDB.prepare(`
FROM market_offers
ORDER BY market_offers.posted_at DESC
`);
export const getMarketOfferById = flopoDB.prepare(`
SELECT market_offers.*,
skins.displayName AS skinName,
@@ -662,7 +663,6 @@ export const getMarketOfferById = flopoDB.prepare(`
LEFT JOIN users AS buyer ON buyer.id = market_offers.buyer_id
WHERE market_offers.id = ?
`);
export const getMarketOffersBySkin = flopoDB.prepare(`
SELECT market_offers.*,
skins.displayName AS skinName,
@@ -677,12 +677,10 @@ export const getMarketOffersBySkin = flopoDB.prepare(`
LEFT JOIN users AS buyer ON buyer.id = market_offers.buyer_id
WHERE market_offers.skin_uuid = ?
`);
export const insertMarketOffer = flopoDB.prepare(`
INSERT INTO market_offers (id, skin_uuid, seller_id, starting_price, buyout_price, status, opening_at, closing_at)
VALUES (@id, @skin_uuid, @seller_id, @starting_price, @buyout_price, @status, @opening_at, @closing_at)
`);
export const updateMarketOffer = flopoDB.prepare(`
UPDATE market_offers
SET final_price = @final_price,
@@ -690,7 +688,6 @@ export const updateMarketOffer = flopoDB.prepare(`
buyer_id = @buyer_id
WHERE id = @id
`);
export const deleteMarketOffer = flopoDB.prepare(`
DELETE
FROM market_offers
@@ -708,25 +705,21 @@ export const getBids = flopoDB.prepare(`
JOIN users AS bidder ON bidder.id = bids.bidder_id
ORDER BY bids.offer_amount DESC, bids.offered_at ASC
`);
export const getBidById = flopoDB.prepare(`
SELECT bids.*
FROM bids
WHERE bids.id = ?
`);
export const getOfferBids = flopoDB.prepare(`
SELECT bids.*
FROM bids
WHERE bids.market_offer_id = ?
ORDER BY bids.offer_amount DESC, bids.offered_at ASC
`);
export const insertBid = flopoDB.prepare(`
INSERT INTO bids (id, bidder_id, market_offer_id, offer_amount)
VALUES (@id, @bidder_id, @market_offer_id, @offer_amount)
`);
export const deleteBid = flopoDB.prepare(`
DELETE
FROM bids
@@ -742,7 +735,6 @@ export const insertManyUsers = flopoDB.transaction((users) => {
insertUser.run(user);
} catch (e) {}
});
export const updateManyUsers = flopoDB.transaction((users) => {
for (const user of users)
try {
@@ -751,7 +743,6 @@ export const updateManyUsers = flopoDB.transaction((users) => {
console.log(`User update failed`);
}
});
export const insertManySkins = flopoDB.transaction((skins) => {
for (const skin of skins)
try {
@@ -791,14 +782,16 @@ export const getUserGames = flopoDB.prepare(
ELOS
----------------------------*/
export const insertElos = flopoDB.prepare(`INSERT INTO elos (id, elo)
VALUES (@id, @elo)`);
VALUES (@id, @elo)`
);
export const getElos = flopoDB.prepare(`SELECT *
FROM elos`);
FROM elos`
);
export const getUserElo = flopoDB.prepare(`SELECT *
FROM elos
WHERE id = @id`);
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",
);
@@ -808,14 +801,15 @@ export const getUsersByElo = flopoDB.prepare(
----------------------------*/
export const getSOTD = flopoDB.prepare(`SELECT *
FROM sotd
WHERE id = '0'`);
WHERE id = '0'`
);
export const insertSOTD =
flopoDB.prepare(`INSERT INTO sotd (id, tableauPiles, foundationPiles, stockPile, wastePile, seed)
VALUES (0, @tableauPiles, @foundationPiles, @stockPile, @wastePile, @seed)`);
export const deleteSOTD = flopoDB.prepare(`DELETE
FROM sotd
WHERE id = '0'`);
WHERE id = '0'`
);
export const getAllSOTDStats = flopoDB.prepare(`SELECT sotd_stats.*, users.globalName
FROM sotd_stats
JOIN users ON users.id = sotd_stats.user_id
@@ -831,10 +825,6 @@ export const deleteUserSOTDStats = flopoDB.prepare(`DELETE
FROM sotd_stats
WHERE user_id = ?`);
/* -------------------------
Market queries already declared above (kept for completeness)
----------------------------*/
/* -------------------------
pruneOldLogs
----------------------------*/

View File

@@ -20,6 +20,8 @@ export let pokerRooms = {};
// Stores active erinyes rooms, keyed by a unique room ID (uuidv4).
export let erinyesRooms = {};
export let monkePaths = {};
// --- User and Session State ---
// Stores active user inventories for paginated embeds, keyed by the interaction ID.

View File

@@ -11,6 +11,7 @@ import { solitaireRoutes } from "./routes/solitaire.js";
import { getSocketIo } from "./socket.js";
import { blackjackRoutes } from "./routes/blackjack.js";
import { marketRoutes } from "./routes/market.js";
import { monkeRoutes } from "./routes/monke.js";
// --- EXPRESS APP INITIALIZATION ---
const app = express();
@@ -62,4 +63,7 @@ app.use("/api/market-place", marketRoutes(client, io));
// erinyes-specific routes
// app.use("/api/erinyes", erinyesRoutes(client, io));
// monke-specific routes
app.use("/api/monke-game", monkeRoutes(client, io));
export { app };

134
src/server/routes/monke.js Normal file
View File

@@ -0,0 +1,134 @@
import express from "express";
import { v4 as uuidv4 } from "uuid";
import { monkePaths } from "../../game/state.js";
import { socketEmit } from "../socket.js";
import { getUser, updateUserCoins, insertLog } from "../../database/index.js";
import { init } from "openai/_shims/index.mjs";
const router = express.Router();
/**
* Factory function to create and configure the monke API routes.
* @param {object} client - The Discord.js client instance.
* @param {object} io - The Socket.IO server instance.
* @returns {object} The configured Express router.
*/
export function monkeRoutes(client, io) {
// --- Router Management Endpoints
router.get("/:userId", (req, res) => {
const { userId } = req.params;
if (!userId) return res.status(400).json({ error: "User ID is required" });
const user = getUser.get(userId);
if (!user) return res.status(404).json({ error: "User not found" });
const userGamePath = monkePaths[userId] || null;
if (!userGamePath) return res.status(404).json({ error: "No active game found for this user" });
return res.status(200).json({ userGamePath });
});
router.post("/:userId/start", (req, res) => {
const { userId } = req.params;
const { initialBet } = req.body;
if (!userId) return res.status(400).json({ error: "User ID is required" });
const user = getUser.get(userId);
if (!user) return res.status(404).json({ error: "User not found" });
if (!initialBet) return res.status(400).json({ error: "Initial bet is required" });
try {
const newCoins = user.coins - initialBet;
updateUserCoins.run({
id: userId,
coins: newCoins,
});
insertLog.run({
id: `${userId}-monke-bet-${Date.now()}`,
user_id: userId,
target_user_id: null,
action: "MONKE_BET",
coins_amount: -initialBet,
user_new_amount: newCoins,
});
} catch (error) {
return res.status(500).json({ error: "Failed to update user coins" });
}
monkePaths[userId] = [{ round: 0, choice: null, result: null, bet: initialBet, extractValue: null, timestamp: Date.now() }];
return res.status(200).json({ message: "Monke game started", userGamePath: monkePaths[userId] });
});
router.post("/:userId/play", (req, res) => {
const { userId } = req.params;
const { choice, step } = req.body;
if (!userId) return res.status(400).json({ error: "User ID is required" });
const user = getUser.get(userId);
if (!user) return res.status(404).json({ error: "User not found" });
if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" });
const currentRound = monkePaths[userId].length - 1;
if (step !== currentRound) return res.status(400).json({ error: "Invalid step for the current round" });
if (monkePaths[userId][currentRound].choice !== null) return res.status(400).json({ error: "This round has already been played" });
const randomLoseChoice = Math.floor(Math.random() * 3); // 0, 1, or 2
if (choice !== randomLoseChoice) {
monkePaths[userId][currentRound].choice = choice;
monkePaths[userId][currentRound].result = randomLoseChoice;
monkePaths[userId][currentRound].extractValue = Math.round(monkePaths[userId][currentRound].bet * 1.33);
monkePaths[userId][currentRound].timestamp = Date.now();
monkePaths[userId].push({ round: currentRound + 1, choice: null, result: null, bet: monkePaths[userId][currentRound].extractValue, extractValue: null, timestamp: Date.now() });
return res.status(200).json({ message: "Round won", userGamePath: monkePaths[userId], lost: false });
} else {
monkePaths[userId][currentRound].choice = choice;
monkePaths[userId][currentRound].result = randomLoseChoice;
monkePaths[userId][currentRound].extractValue = 0;
monkePaths[userId][currentRound].timestamp = Date.now();
const userGamePath = monkePaths[userId];
delete monkePaths[userId];
return res.status(200).json({ message: "Round lost", userGamePath, lost: true });
}
});
router.post("/:userId/stop", (req, res) => {
const { userId } = req.params;
if (!userId) return res.status(400).json({ error: "User ID is required" });
const user = getUser.get(userId);
if (!user) return res.status(404).json({ error: "User not found" });
if (!monkePaths[userId]) return res.status(400).json({ error: "No active game found for this user" });
const userGamePath = monkePaths[userId];
delete monkePaths[userId];
const extractValue = userGamePath[userGamePath.length - 1].bet;
const coins = user.coins || 0;
const newCoins = coins + extractValue;
try {
updateUserCoins.run({
id: userId,
coins: newCoins,
});
insertLog.run({
id: `${userId}-monke-withdraw-${Date.now()}`,
user_id: userId,
target_user_id: null,
action: "MONKE_WITHDRAW",
coins_amount: extractValue,
user_new_amount: newCoins,
});
return res.status(200).json({ message: "Game stopped", userGamePath });
} catch (error) {
return res.status(500).json({ error: "Failed to update user coins" });
}
});
return router;
}

View File

@@ -475,7 +475,7 @@ const VCT_TEAMS = {
],
"vct-cn": [
/x ag\)$/g, /x blg\)$/g, /x edg\)$/g, /x fpx\)$/g, /x jdg\)$/g, /x nova\)$/g, /x tec\)$/g,
/x te\)$/g, /x tyl\)$/g, /x wol\)$/g, /x xlg\)$/g, /x xlg\)$/g, /x drg\)$/g, /x drg\)$/g
/x te\)$/g, /x tyl\)$/g, /x wol\)$/g, /x xlg\)$/g, /x xlg\)$/g, /x drg\)$/g
]
};