feat: Adding economic system to do scoring
This commit is contained in:
@@ -7,12 +7,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CONFIG_FILE_PATH =
|
||||
process.env.CONFIG_FILE_PATH ?? path.join(__dirname, "..", "config", "game.settings.json");
|
||||
|
||||
/** @type {{ clickCooldownSeconds: number, databaseWipeoutIntervalSeconds: number, debugModeForTeams: boolean, configReloadIntervalSeconds: number }} */
|
||||
/** @type {{ clickCooldownSeconds: number, databaseWipeoutIntervalSeconds: number, debugModeForTeams: boolean, configReloadIntervalSeconds: number, resourceWorth: object }} */
|
||||
let cached = {
|
||||
clickCooldownSeconds: 5,
|
||||
databaseWipeoutIntervalSeconds: 21600,
|
||||
debugModeForTeams: true,
|
||||
configReloadIntervalSeconds: 30,
|
||||
resourceWorth: { common: {}, rare: {} },
|
||||
};
|
||||
|
||||
let lastMtimeMs = 0;
|
||||
@@ -46,6 +47,9 @@ export function loadConfigFile() {
|
||||
if (typeof j.configReloadIntervalSeconds === "number" && j.configReloadIntervalSeconds >= 5) {
|
||||
cached.configReloadIntervalSeconds = j.configReloadIntervalSeconds;
|
||||
}
|
||||
if (j.resourceWorth && typeof j.resourceWorth === "object") {
|
||||
cached.resourceWorth = j.resourceWorth;
|
||||
}
|
||||
lastMtimeMs = st.mtimeMs;
|
||||
} catch (e) {
|
||||
if (e.code === "ENOENT") {
|
||||
|
||||
@@ -29,6 +29,14 @@ export async function initGameSchema() {
|
||||
PRIMARY KEY (world_seed, team)
|
||||
);
|
||||
`);
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS team_econ_scores (
|
||||
world_seed TEXT NOT NULL,
|
||||
team TEXT NOT NULL CHECK (team IN ('blue', 'red')),
|
||||
score DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (world_seed, team)
|
||||
);
|
||||
`);
|
||||
await pool.query(`
|
||||
ALTER TABLE grid_cells ADD COLUMN IF NOT EXISTS discovered_by TEXT;
|
||||
UPDATE grid_cells SET discovered_by = 'blue' WHERE discovered_by IS NULL;
|
||||
@@ -59,6 +67,7 @@ export async function ensureSeedEpoch() {
|
||||
if (seedSlot !== lastSeedSlot) {
|
||||
await pool.query("TRUNCATE grid_cells RESTART IDENTITY");
|
||||
await pool.query("DELETE FROM team_cooldowns WHERE world_seed != $1", [worldSeed]);
|
||||
await pool.query("DELETE FROM team_econ_scores WHERE world_seed != $1", [worldSeed]);
|
||||
console.log(`[world] Slot ${lastSeedSlot} → ${seedSlot}; grid wiped, old cooldowns cleared.`);
|
||||
lastSeedSlot = seedSlot;
|
||||
}
|
||||
@@ -115,6 +124,29 @@ export async function upsertTeamCooldown(worldSeed, team) {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Economic scores ───────────────────────────────────────────────────────────
|
||||
|
||||
export async function getEconScores(worldSeed) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT team, score FROM team_econ_scores WHERE world_seed = $1`,
|
||||
[worldSeed]
|
||||
);
|
||||
const result = { blue: 0, red: 0 };
|
||||
for (const row of rows) result[row.team] = Number(row.score);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addEconScore(worldSeed, team, delta) {
|
||||
if (delta <= 0) return;
|
||||
await pool.query(
|
||||
`INSERT INTO team_econ_scores (world_seed, team, score)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (world_seed, team) DO UPDATE
|
||||
SET score = team_econ_scores.score + EXCLUDED.score`,
|
||||
[worldSeed, team, delta]
|
||||
);
|
||||
}
|
||||
|
||||
// ── Scores ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getScores(worldSeed) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "dotenv/config";
|
||||
import { loadConfigFile, getConfig } from "./configLoader.js";
|
||||
import { initGameSchema, ensureSeedEpoch } from "./db/gameDb.js";
|
||||
import { initUsersSchema } from "./db/usersDb.js";
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
getTeamCooldown,
|
||||
upsertTeamCooldown,
|
||||
getScores,
|
||||
getEconScores,
|
||||
addEconScore,
|
||||
} from "../db/gameDb.js";
|
||||
import { computeCell, rowToCellPayload } from "../helpers/cell.js";
|
||||
|
||||
@@ -43,6 +45,7 @@ router.get("/config", async (req, res) => {
|
||||
seedPeriodEndsAtUtc: ws.seedPeriodEndsAtUtc,
|
||||
seedPeriodStartsAtUtc: ws.seedPeriodStartsAtUtc,
|
||||
teamCooldownRemaining,
|
||||
resourceWorth: cfg.resourceWorth ?? { common: {}, rare: {} },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -134,6 +137,49 @@ router.post("/cell/reveal", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/admin/verify
|
||||
router.post("/admin/verify", (req, res) => {
|
||||
const password = String(req.body?.password ?? "");
|
||||
const adminPwd = process.env.ADMIN_PASSWORD;
|
||||
if (!adminPwd) {
|
||||
return res.status(503).json({ ok: false, error: "not_configured" });
|
||||
}
|
||||
if (password && password === adminPwd) {
|
||||
return res.json({ ok: true });
|
||||
}
|
||||
return res.status(401).json({ ok: false, error: "invalid_password" });
|
||||
});
|
||||
|
||||
// GET /api/econ-scores
|
||||
router.get("/econ-scores", async (_req, res) => {
|
||||
try {
|
||||
const worldSeed = await ensureSeedEpoch();
|
||||
const scores = await getEconScores(worldSeed);
|
||||
res.json(scores);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/econ-scores/tick body: { seed, blue, red }
|
||||
router.post("/econ-scores/tick", async (req, res) => {
|
||||
const seed = String(req.body?.seed ?? "");
|
||||
const blue = Number(req.body?.blue ?? 0);
|
||||
const red = Number(req.body?.red ?? 0);
|
||||
try {
|
||||
const worldSeed = await ensureSeedEpoch();
|
||||
if (seed !== worldSeed) return res.status(410).json({ error: "seed_expired", worldSeed });
|
||||
await addEconScore(worldSeed, "blue", blue);
|
||||
await addEconScore(worldSeed, "red", red);
|
||||
const scores = await getEconScores(worldSeed);
|
||||
res.json(scores);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: "database_error" });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/scores
|
||||
router.get("/scores", async (_req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user