# n-back-game **Repository Path**: yechen653/n-back-game ## Basic Information - **Project Name**: n-back-game - **Description**: n-back记忆训练小游戏, - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-29 - **Last Updated**: 2026-09-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # n-back-game 休闲小游戏, 技术栈简单 N-Back 认知训练小游戏(PyQt5 桌面版)。 N-Back 是经典的**工作记忆**训练任务:屏幕上连续出现刺激(字母 / 数字 / 网格位置), 你需要在每个刺激出现时判断它**是否与 N 步之前那一个相同**——相同就按「匹配」,不同就按「不匹配」。 ## 快速开始 ### 1. 安装依赖 ```bash pip install -r requirements.txt ``` 需要开发(跑测试)时: ```bash pip install -r requirements-dev.txt ``` ### 2. 运行 ```bash python main.py ``` 在项目根目录执行。`main.py` 作为入口调用 `src/nback/gui/main_window.py` 里的 `main()`。 ### 3. 跑测试 ```bash python -m pytest tests -q ``` 测试只覆盖 `game/` 逻辑层,**不依赖 PyQt5**,所以即使没装界面库也能跑。 ## 打包成 exe(Windows) 用 [PyInstaller](https://pyinstaller.org/) 打包,产物可以直接双击运行,目标机器不需要装 Python。 ### 1. 安装打包工具 ```bash pip install pyinstaller ``` 建议在只装了 `requirements.txt` 的干净虚拟环境里打包,否则 pytest 之类无关依赖也会被塞进去。 ### 2. 打包 **单文件**(一个 exe,方便分发,启动稍慢): ```bash pyinstaller --noconsole --onefile --name n-back main.py ``` **单目录**(启动更快,但要连 `dist\n-back\` 整个文件夹一起拷贝): ```bash pyinstaller --noconsole --onedir --name n-back main.py ``` 产物位置: | 方式 | 产物 | |---|---| | `--onefile` | `dist/n-back.exe` | | `--onedir` | `dist/n-back/n-back.exe` | 参数说明: | 参数 | 作用 | |---|---| | `--noconsole` | 不弹黑色控制台窗口,GUI 程序必加 | | `--onefile` / `--onedir` | 打成一个 exe / 一个目录 | | `--name n-back` | 指定 exe 文件名 | | `--clean` | 清掉上次的分析缓存,遇到莫名的打包错误时加上 | | `--icon app.ico` | 设置 exe 图标(需要 .ico 文件) | ### 3. 常见坑 - **必须在项目根目录执行**(`main.py` 所在目录),否则 `src.nback` 包找不到。 - Qt 插件(platforms、styles 等)会由 PyInstaller 自带的 PyQt5 hook 自动收集,一般不 用手动指定。若启动时报 `could not find or load the Qt platform plugin "windows"`, 在命令里补 `--collect-all PyQt5` 重新打包。 - `--onefile` 每次启动会把文件解压到临时目录,首次启动会慢几秒。 - 杀毒软件可能对 PyInstaller 产物误报,需要手动放行。 ## 玩法 1. 在主界面点「选择玩法」,选一张玩法卡片进入训练页;左侧「训练设置」里调好参数后点「开始训练」。 2. 前 N 个刺激属于**初始化阶段**,只需按 `Q`(或点矩阵右侧按钮)推进,不用作答。 3. 从第 N+1 个刺激开始正式作答: | 操作 | 按键 | |---|---| | 匹配 | `E` | | 不匹配 | `Q` | | 位置 / 双重模式 | 矩阵左侧按钮 = 匹配,右侧按钮 = 不匹配 | 双重 N-back 是位置 + 字母双通道:`E` 位置匹配,`R` 字母匹配,`Q` 都不匹配。 4. 答完会短暂显示对错,然后自动进入下一个刺激。序列跑完自动出成绩。 「历史序列」里的每个色块代表一个已经出现过的刺激,**点击色块可以显示 / 隐藏内容**, 答过的题会按对错着色(绿 = 对,红 = 错)。点击色块可以临时显示 / 隐藏该位置的刺激内容。 ## 软件架构 整体是**分层架构**,核心原则是**游戏逻辑完全不依赖界面框架**: ``` n-back/ ├── main.py 入口,只有三行 ├── requirements.txt 运行依赖 ├── requirements-dev.txt 开发依赖 ├── src/nback/ │ ├── game/ ── 逻辑层(纯 Python,零 PyQt5 依赖) │ │ ├── models.py GameConfig / StimulusElement / TrialResult │ │ ├── sequence.py 序列生成策略(可注入随机数,便于测试复现) │ │ ├── stats.py 计分与混淆矩阵(纯函数,最容易测) │ │ └── engine.py 状态机:推进刺激、记录作答、汇总结果 │ └── gui/ ── 界面层(PyQt5) │ ├── theme.py 配色常量 + 全部样式表 │ ├── widgets.py 各类组件:只显示、只发信号 │ └── main_window.py 控制器:串起引擎、计时器和各个面板 └── tests/ 逻辑层单元测试 ├── test_sequence.py ├── test_stats.py └── test_engine.py ``` ### 分层职责 | 层 | 可以做什么 | 不可以做什么 | |---|---|---| | `game/`(模型) | 生成序列、判定对错、统计成绩 | 导入 Qt、弹窗口、读界面控件 | | `gui/widgets.py`(视图) | 渲染内容、通过 `pyqtSignal` 上报操作 | 计算对错、改游戏状态 | | `gui/main_window.py`(控制器) | 监听信号、调用引擎、把状态写回界面 | 写游戏规则、拼样式字符串 | | `gui/theme.py` | 提供颜色与 QSS | 持有任何状态 | ### 数据流 ``` 用户按键 / 点击 ↓ pyqtSignal 主窗口的槽函数(handle_match / handle_no_match / _handle_timeout) ↓ 调用 engine.record_response(...) 引擎更新 trials 与 current_index ↓ 主窗口回读状态(engine.correct_answer / history_status ...) 组件刷新显示(set_stimulus / show_feedback / set_history) ``` 调度由两个 `QTimer` 完成:`timer`(约 30 帧/秒刷新计时)与 `timeout_timer`(单题限时倒计时)。 计时使用 `QElapsedTimer`(单调时钟),不受系统时间调整影响。 ## 设置项说明 | 设置 | 默认 | 说明 | |---|---|---| | N 值 | 2 | 要往回比较多少步。1 最简单,3 以上明显变难 | | 序列长度 | 20 | 一轮出现多少个刺激 | | 单题限时 | 0 秒 | 0 表示不限时;超时按「回答不匹配」记入并单独统计 | | 反馈延迟 | 800 ms | 答完到下个刺激之间的停顿,太短会来不及看反馈 | | 匹配率 | 50% | 出现「与 N 步前相同」的目标比例 | | 刺激类型 | 字母 | 字母 / 数字 / 位置(3×3),部分玩法会锁定类型 | | 反馈强度 | 仅对错 | 完整揭示(画出 N 步前的刺激)/ 仅对错(√ ✗)/ 完全隐藏 | | 渐进难度 | 关 | 开启后按准确率自动升降 N:一轮 >90% 升一级,<70% 降一级 | 序列生成时对**连续匹配 / 连续不匹配**有次数上限(默认 4 次),避免被试摸出规律靠猜。 ## 成绩指标 - **准确率**:答对数 / 总作答数。 - **混淆矩阵**: | | 你答匹配 | 你答不匹配 | |---|---|---| | 实际匹配 | TP(命中) | FN(漏报) | | 实际不匹配 | FP(虚报) | TN(正确拒绝) | - **灵敏度** = TP / (TP + FN):真正该匹配时你抓得住多少。 - **特异度** = TN / (TN + FP):不该匹配时你能正确放过多少。 灵敏度与特异度往往比准确率更有参考价值——做题时「全按匹配」或「全不按」都能拿到约 50% 的准确率, 但灵敏度 / 特异度会立刻暴露这种策略。 ## 开发说明 ### 加一种新的刺激类型 1. 在 `game/models.py` 的 `StimulusType` 里加一个枚举值。 2. 在 `game/sequence.py` 的 `_random_element` 里加一个构造分支。 3. 在 `gui/widgets.py` 的 `TrainingSettings._STIMULUS_OPTIONS` 里加上下拉选项。 ### 改主题 颜色和样式全部集中在 `gui/theme.py`,改这一个文件就能换肤。 ### 写测试 `game/` 层是纯 Python,直接 `import` 就能测。序列生成支持传入固定种子的 `random.Random`,因此随机序列在测试中完全可复现: ```python from src.nback.game import GameConfig, NBackEngine import random engine = NBackEngine(GameConfig(n=2, sequence_length=20), rng=random.Random(42)) engine.start() ```