Skip to content

🗺️ One source of truth: extract the map pool + weapon tables into shared/tables.js #7

Description

@longmaolab

TL;DR (English)

First off — this codebase is genuinely impressive. A solo-built browser FPS with ~117 weapons, 39 maps, bots, a killcam, a full shop economy, and an AI chat is a lot of moving parts, and the fact that it all hangs together is real engineering.

This issue is about a pro habit, not a bug: a few important lists are currently copy-pasted into several files, and you have to remember to edit every copy by hand. That's called the DRY principle — "Don't Repeat Yourself," a.k.a. having a single source of truth. Right now most copies agree (nice!), but I found one place where they've already started to drift — which is exactly the trap this habit prevents.

The big idea: make one file, shared/tables.js, that holds the map pool and the weapon tables, and have both server.js and game.js read from that one copy.


What I verified (real file:line evidence)

1. The map pool exists in 4 JavaScript copies — all the same big 39-map array:

  • server.js:722-727 (inside checkLobbyStart)
  • server.js:792-797 (inside tryPairPvpQueue)
  • public/game.js:15018 (the const pool = [...] for match startup)
  • (plus the UI — see below)

2. The map list is ALSO hardcoded twice in the menu UI — and these two have ALREADY drifted:

  • public/index.html:483-519 — first MAP row. Missing airport, trenches, chernobyl, and uses labels like CLASSIC.
  • public/index.html:537-562 — second MAP row. Includes airport/trenches/chernobyl, uses emoji labels like 🟩 BLANK.

👀 This is the whole point in a nutshell: two lists that were supposed to match no longer do. Nobody did anything wrong — it's just what happens to copies over time. One source of truth makes this impossible.

3. The weapon tables are mirrored across server.js and game.js — and right now they DO agree (great!):

  • WEAPON_COSTSserver.js:47 and public/game.js:1085. Both 52 entries; spot-checked ak20: 250 identical in both. The server file even has a comment at server.js:45: "Mirrors the client-side WEAPON_COSTS table in game.js — keep them in sync."
  • BUNDLESserver.js:132 (an object) and public/game.js:1169 (an array). Both 25 entries; comment at server.js:130-131 says "Keep in sync with public/game.js."
  • WEAPON_DAMAGE — defined at server.js:812.

The fact that you wrote those "keep in sync" comments yourself shows you already felt the problem. This issue is just the next step: make the computer keep them in sync so you don't have to.


Why one copy is safer (even though they agree today)

Two copies that agree today is luck plus discipline, and discipline runs out at 11pm when you're adding weapon #118. The risk isn't "it's wrong now" — it's:

  • Add a map → you must edit it in 5 places (and the UI row that's easy to forget is the one that already drifted).
  • Add a weapon → edit WEAPON_COSTS in 2 files; miss one and the client shows a price the server won't honor.
  • The bug is silent: nothing crashes, the lists just quietly disagree, and you debug a "weird map/price bug" weeks later.

With one source of truth, "add a map" or "add a weapon" becomes a one-line edit in one file, and every consumer updates for free. 🎯


Suggested first step (small, safe, high-payoff)

Start with just the map pool — it has the most copies and already shows drift, so it's the clearest win. Tackle the weapon tables in a follow-up.

  1. Create shared/tables.js. One catch: public/game.js is loaded as a plain <script src="game.js"> (public/index.html:828), not a module — so the browser can't import. A friendly pattern that works in both Node and the browser:

    // shared/tables.js
    (function (root) {
      const MAP_POOL = ['blank','urban','warehouse','forest','volcano','cyber','desert',
        'tundra','space','airport','trenches','chernobyl','refinery','skydock','sewer',
        'gravity_lab','glassworks','carrier','overgrowth','orbital_station','foundry',
        'carnival','biosphere','lockdown','studio','temple','holiday','labyrinth','arena',
        'opera','doomsday','train','dreamscape','pearl_harbor','titanic','supermarket',
        'pyongyang','traffic_cone_republic','flying_moai'];
    
      const tables = { MAP_POOL };
      if (typeof module !== 'undefined' && module.exports) module.exports = tables; // Node (server.js)
      else root.SHARED_TABLES = tables;                                            // browser (game.js)
    })(typeof window !== 'undefined' ? window : globalThis);
  2. In server.js: const { MAP_POOL } = require('./shared/tables'); near the top, then delete the two inline arrays at 722-727 and 792-797 and use the shared one.

  3. In public/index.html: add <script src="shared/tables.js"></script> before the game.js tag at line 828.

  4. In public/game.js: replace the inline pool at 15018 with SHARED_TABLES.MAP_POOL.

  5. Bonus (optional, the satisfying part): instead of hand-writing the two <div class="map-card"> rows in the HTML, give each map a tiny entry (label + color + emoji) and loop to build both rows from MAP_POOL. That deletes ~60 lines of HTML and makes the two-rows-disagree bug above structurally impossible.

⚠️ One small heads-up so it doesn't bite you: BUNDLES is an object in server.js:132 but an array in game.js:1169. When you get to that one, pick a single shape in shared/tables.js and adapt whichever consumer needs the other shape — don't try to make it "both" at once. The map pool is identical in shape everywhere, which is why it's the perfect place to start.

How to verify when you're done

  • Search the repo for the map array — there should be exactly one literal copy (in shared/tables.js).
  • node --check server.js passes, server boots, a match still loads a random map.
  • Open the menu — both map rows render and show the same maps.

中文详细说明

一句话总结

先说真心话:你一个人做出一个浏览器 FPS —— 约 117 把武器、39 张地图、机器人、击杀回放、商店经济系统、还有 AI 聊天 —— 这个工程量非常惊人,而且这么多系统居然能稳稳跑起来,是真功夫。👏

这个 issue 讲的不是 bug,而是一个专业习惯:现在有几个重要的列表被复制粘贴到了好几个文件里,每次改都得手动改所有副本。这个原则叫 DRY(Don't Repeat Yourself,不要重复自己),也叫单一数据源(single source of truth)。目前大部分副本还是一致的(很棒!),但我发现已经有一处悄悄对不上了—— 而这正是这个习惯要帮你避免的坑。

核心想法:建一个文件 shared/tables.js,把地图池和武器表都放进去,让 server.jsgame.js 都从这一份里读。

我核对到的证据(真实的 文件:行号)

1. 地图池在 4 处 JS 代码里被复制(都是同一个 39 张地图的数组):

  • server.js:722-727checkLobbyStart 函数里)
  • server.js:792-797tryPairPvpQueue 函数里)
  • public/game.js:15018(开局选地图的 const pool = [...]

2. 地图列表在菜单 UI 里又写死了两遍 —— 而且这两遍已经对不上了:

  • public/index.html:483-519 —— 第一行 MAP,缺少 airporttrencheschernobyl,标签是 CLASSIC 之类。
  • public/index.html:537-562 —— 第二行 MAP,包含 airport/trenches/chernobyl,标签是 🟩 BLANK 之类带 emoji 的。

👀 这就是整件事的最佳例子:两个本来应该一样的列表,现在不一样了。这不是谁犯了错 —— 副本随着时间推移就是会这样。只要改成单一数据源,这种情况根本不可能发生。

3. 武器表在 server.jsgame.js 之间各有一份 —— 目前两边是一致的(很好!):

  • WEAPON_COSTS —— server.js:47public/game.js:1085。都是 52 条;抽查 ak20: 250 两边一致。server.js:45 你自己还写了注释:"Mirrors the client-side WEAPON_COSTS table in game.js — keep them in sync."
  • BUNDLES —— server.js:132(是个对象)和 public/game.js:1169(是个数组)。都是 25 条;server.js:130-131 注释写着 "Keep in sync with public/game.js."
  • WEAPON_DAMAGE —— 定义在 server.js:812

你自己写下那几句 "keep them in sync"(记得保持同步),说明你已经感觉到这个问题了。这个 issue 只是下一步:让电脑替你保持同步,这样你就不用自己记着了。😄

为什么"一份"更安全(虽然现在两边是一致的)

两份现在一致,靠的是运气 + 自律。可自律是会用完的 —— 比如晚上 11 点加第 118 把武器的时候。真正的风险不是"现在错了",而是:

  • 加一张地图 → 你得在 5 个地方改(而且最容易忘的那行 UI,恰恰就是已经对不上的那行)。
  • 加一把武器 → 要改 2 个文件里的 WEAPON_COSTS;漏掉一个,客户端显示的价格服务器就不认。
  • 这种 bug 是静悄悄的:不会崩溃,列表只是默默地对不上,然后你几周后对着一个"奇怪的地图/价格 bug"抓狂。

改成单一数据源后,"加地图""加武器"就变成只在一个文件里改一行,所有用到它的地方自动更新。🎯

建议的第一步(小、安全、收益高)

先只搞地图池就好 —— 它副本最多、而且已经出现漂移,是最明显的胜利。武器表放到后面再单独处理。

  1. 新建 shared/tables.js 有个小坑:public/game.js 是用普通的 <script src="game.js"> 加载的(public/index.html:828),不是 module,所以浏览器里不能用 import。上面英文段给了一个在 Node 和浏览器里都能用的小写法(module.exports + 挂全局变量),照抄即可。

  2. server.js 里: 顶部加 const { MAP_POOL } = require('./shared/tables');,然后删掉 722-727792-797 这两段内联数组,改用共享的那个。

  3. public/index.html 里: 在第 828 行的 game.js 之前,加一行 <script src="shared/tables.js"></script>

  4. public/game.js 里:15018 行的内联 pool 换成 SHARED_TABLES.MAP_POOL

  5. 加分项(可选,但最爽的部分): 与其手写两行 <div class="map-card">,不如给每张地图存一个小条目(名字 + 颜色 + emoji),然后用循环MAP_POOL 生成这两行。这样能删掉约 60 行 HTML,顺便让上面那个"两行对不上"的 bug 结构上不可能再发生

⚠️ 一个小提醒,免得到时候被坑:BUNDLESserver.js:132对象,在 game.js:1169数组。等你处理这个的时候,在 shared/tables.js选一种形状,在需要另一种形状的那一端做个转换 —— 不要想着一次就让它"两种都行"。地图池在所有地方形状都一样,所以它是最适合开刀的第一刀。

完成后怎么验证

  • 在整个仓库里搜地图数组 —— 应该只剩一份字面量(在 shared/tables.js 里)。
  • node --check server.js 通过,服务器能启动,开局还是会随机一张地图。
  • 打开菜单 —— 两行地图都正常显示,而且显示的是同一批地图。

你已经做到了大多数人不会做的一步 —— 给自己写下"保持同步"的注释,说明你的工程直觉很准。这一步只是把那份责任从你脑子里搬到代码里。搞定第一个表之后,你会上瘾的 —— 加新东西从此只改一个地方,爽得很。继续加油,期待第 118 把武器!🚀


🔗 Related / 相关: the duplicate id="map-row" is also tracked as a quick fix in #4 (good first issue). This issue is the bigger "one source of truth" picture — its optional step 5 (generate the map rows from data) would dissolve that duplicate entirely. / 重复的 id="map-row" 也作为快速修复记录在 #4(good first issue)里;本 issue 是更大的「单一数据源」蓝图,这里的可选第 5 步(用数据循环生成地图行)能从结构上彻底消除那个重复。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions