孩子把表现换成积分,积分换成想要的东西。攒得越多,账本里那栋房子越大 —— 从破帐篷一路盖到云端宫殿,花掉了就缩回去。
「表现好给你记 5 分」这句话,用纸和笔记不了三天 —— 家长记不全、孩子不信、攒到最后谁也说不清有多少。麻烦不在加减法上,在下面这些地方:
| 真实难点 | 这套系统的取舍 |
|---|---|
| 数字不会让人心动 | 攒到 4500 分和攒到 8000 分,对孩子来说都只是「一个数」。所以给总市值划了 11 个家园档位,跨档时整页换装:破帐篷 → 茅草屋 → 砖瓦房 → 花园洋房 → 城堡 → 云端天堂。换场景不花积分,只看当前市值 —— 挣分房子长,花分房子缩,财富的诚实具象化,没有第二套逻辑。 |
| 孩子会给自己加分 | 权限故意不对称:孩子拿长效登录态,只能看账本、兑换、刷题;家长每次加分都要现场输口令,且不发 cookie。孩子拿到一台已登录的平板也加不了分 —— 能自己给自己加分的积分系统,第二天就变成刷分游戏。 |
| 唯一免口令的口子会被刷 | 「刷题自动得分」是唯一不需要家长在场的加分通道,所以额度由服务端按人按天封顶(每天 30 分)。改前端、直接调接口,都刷不出上限以外的分。 |
| 规则有 83 条、5 种形态 | 不写死在代码里。固定分 / 按个数 / 家长在区间内定 / 档位计算器 / 换电视时间,全在一份规则配置里。像「跳绳 3 组 190 个记 120 分」「语文 85 分以下少一分扣 5 分」这类公式本身也是配置,改规则不动代码。 |
| 预览和记账算出两个数 | 填数字时页面上要实时显示「这么算是多少分」。预览与真正记账走的是同一个计算函数,前端不复刻一份算法 —— 两边各算各的,迟早算出不同的数。 |
| 余额和流水对不上 | 账本 append-only,余额永远从流水累加出来,不单独存一个余额字段。存了就会漂移,而漂移的账本比没有账本更糟。 |
| 不只有一种货币 | 有些事只换电视时间不换钱(认识生字、背单词),有些事扣时间(拖延);「满足一个心愿」是第三本账。三本账走同一条时间线,各自累加各自的余额。 |
| 记错了怎么撤 | 撤销 = 把那条挪进留底区 + 重算其后每一条的余额快照,页面和走势图当它没发生过。早期做的是会计式红冲(记一笔反向流水),实测被否:走势图留一个凸起、流水里一排「撤销 xxx」,过程比错误本身还难看。 |
| 把纸表转成配置会编数 | 规则源头是家长手写的一张表。转成 83 条结构化规则时,有 6 处填了原表根本没有的值(「扣 10 以上」自己补了下限、「酌情」自己补了区间)—— 所有机器检查全绿、页面也正常。后来把原表逐格转成纯文本一起入库,用出处校验脚本逐条比对,才把这条路堵死。 |
加几笔分看市值涨、房子换装;再去商店花掉,看它缩回去。加分和撤销要家长口令(演示口令 2468),兑换和刷题不用 —— 这就是那条不对称的权限线。
| 部件 | 做法与理由 |
|---|---|
| 后端 | Python 单文件、只用标准库,数据是几个 JSON 文件,systemd 起一个进程 + 一行 nginx 反代。一个家庭的量级,装依赖、拆服务、上数据库全是净成本。 |
| 账本 | append-only,只有一个写入口函数。余额 = 流水累加,按币种各算各的(积分 / 电视分钟 / 心愿次数)。扣分条款可以把积分扣成负的(欠着下次挣回来),但兑换那条路径不开透支的口子。 |
| 规则引擎 | 83 条规则 + 5 个档位计算器写在一份配置里,服务端和页面读同一份。5 种形态:固定分 / 按个数乘 / 家长在区间内填 / 档位计算器 / 换电视时间。档位从上往下第一个命中的生效,不重复累计。 |
| 表达式求值 | 配置里存的是字符串公式(15 + floor((n - 48) / 5))。绝不用 eval —— 那等于把任意代码执行权交给配置文件。走 AST 白名单:只放行数字、变量、四则、比较、and/or 和 7 个数学函数,看不懂的语法一律抛错,不猜、不放行。 |
| 皮肤分级 | 11 档阈值 + 5 套时代皮肤是另一份配置(本页把它原样搬过来了)。服务端只回答「多少钱住什么房」,页面拿到时代标识给根元素挂一个属性,整套 CSS 变量和装饰跟着换。加一档、改一个阈值、换一套底图,都是改数据。 |
| 会话 | HMAC 签名的长效 token 走 cookie(孩子端);家长口令每次现验、不换 cookie。两种身份的生命周期不同,是设计不是遗漏。 |
| 出处门 | 规则的一手来源(家长手写表)连同逐格纯文本转录一起入库,校验脚本逐条比对结构化后的 83 条规则。纯文本能 diff 能 grep —— 这是唯一能抓住「编了一个看着合理的数」的手段,因为那种错所有机器门都是绿的。 |
| 这个演示页 | 单文件、零外部请求、零后端。上面那套计算逻辑(表达式求值器、档位计算器、五种规则形态、市值分档)用原生 JS 按同样的语义重写了一遍,数字是当场算的,不是写死的假数。 |