news 2026/9/11 11:45:47

freeCodeCamp 每日编程挑战解析:用 JavaScript 实现象棋兵的合法走法(Valid Pawn Moves)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp 每日编程挑战解析:用 JavaScript 实现象棋兵的合法走法(Valid Pawn Moves)

freeCodeCamp 每日编程挑战解析:用 JavaScript 实现象棋兵的合法走法(Valid Pawn Moves)

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

本文以 freeCodeCamp 开源仓库中的每日编程挑战(Daily Coding Challenge)第 173 题「Valid Pawn Moves」为蓝本,完整还原题面、棋盘规则、测试用例与官方参考解法,并结合仓库中的板块配置、种子脚本与 API 实现,从"一道题"延伸到"这套每日挑战系统如何运转"的源码级解析。读完本文,你将掌握 8×8 棋盘坐标建模、按规则枚举合法走法、边界条件处理等实战能力,也能独立在本地运行并验证这套每日挑战的完整数据链路。

挑战背景:Daily Coding Challenge 与第 173 题

freeCodeCamp 仓库中有一个名为daily-coding-challenges-javascript的课程板块,其中按编号存放了超过 240 道短小精悍的算法题(截至当前仓库,challengeOrder已排到 Challenge 240+),第 173 题正是其中之一,源文件位于 challenge-173 源文件。

从板块配置文件 daily-coding-challenges-javascript.json 可以看出这套题目的设计特点:

  • helpCategory: "JavaScript":按 JavaScript 语言组织分类;
  • usesMultifileEditor: true:使用多文件编辑器承载代码;
  • disableLoopProtectTests: true:禁用循环保护测试,允许自由编写循环逻辑;
  • isUpcomingChange: true:标记为即将发布的新内容;
  • 题目以Challenge N: 标题的形式顺序排列,例如 Challenge 171(Flatten the Array)、Challenge 172(Letters-Numbers)、Challenge 173(Valid Pawn Moves)、Challenge 174(Zodiac Finder)。

这套挑战还有一个重要特性:同一道题同时存在 JavaScript 与 Python 两个版本(仓库中的daily-coding-challenges-javascriptdaily-coding-challenges-python两个板块成对出现),因此本篇文章的解法同样可以作为两种语言解题思路的参考起点。

题目描述:给定兵的位置,返回所有合法走法

棋盘坐标系统

题面给出的前提是标准国际象棋棋盘(8×8),列从左到右标记为AH,行从下到上标记为18,完整坐标如下:

|A8|B8|C8|D8|E8|F8|G8|H8| | - | - | - | - | - | - | - | - | |A7|B7|C7|D7|E7|F7|G7|H7| |A6|B6|C6|D6|E6|F6|G6|H6| |A5|B5|C5|D5|E5|F5|G5|H5| |A4|B4|C4|D4|E4|F4|G4|H4| |A3|B3|C3|D3|E3|F3|G3|H3| |A2|B2|C2|D2|E2|F2|G2|H2| |A1|B1|C1|D1|E1|F1|G1|H1|

注意这里的坐标约定是"字母在前、数字在后",例如D4表示第 D 列第 4 行。这是整个题目的核心数据结构:一个坐标由"列字母(A–H)+ 行数字(1–8)"两个分量拼接而成。

走法规则

本挑战对兵的移动做了简化设定,只需遵守三条规则:

  1. 你是棋盘底部的玩家(即兵只能向上方移动,行号递增方向);
  2. 兵通常每次只能向上移动一格;
  3. 例外情况:如果兵位于起始行(第 2 行),它可以向上移动一格或两格。

换句话说,合法的目标格集合是:

  • 第 8 行:没有合法走法(已到棋盘顶端,无法再向上);
  • 第 2 行(起始行):{上一行, 上两行},例如B2["B3", "B4"]
  • 其余行(第 1 行外的 3–7 行):只有{上一行},例如D4["D5"]

说明:本挑战刻意省略了"兵吃子走斜线""过路兵(en passant)""兵升变"等真实国际象棋规则,聚焦于"直线前进 + 起始行两格特权"这一最小规则集,因此返回的目标格之间没有棋子阻挡的判定。

函数签名与示例

要求实现函数findPawnMoves(position),输入一个坐标字符串,返回一个按升序排列的合法目标格数组。题面给出的两个示例:

| 输入 | 输出 | 原因 | | - | - | - | |"D4"|["D5"]| 普通行,只能前进一格 | |"B2"|["B3", "B4"]| 起始行,可前进一格或两格,升序排列 |

测试用例(hints)全览

源文件中的--hints--部分是这道题的全部验收标准,本质上是五组断言。它们在本地校验和后续种子入库时都会被原样执行:

| 断言 | 期望结果 | | - | - | |findPawnMoves("D4")|["D5"]| |findPawnMoves("B2")|["B3", "B4"]| |findPawnMoves("A7")|["A8"]| |findPawnMoves("G2")|["G3", "G4"]| |findPawnMoves("E3")|["E4"]|

这五组用例刻意覆盖了关键分支:普通行(D4E3)、起始行两格特权(B2G2)、以及最顶端行边界(A7只能走到A8,验证了行号上限为 8 的边界约束)。

种子代码与官方参考解法

种子代码(起点)

源文件的--seed-contents--部分给出了解题起点,函数体默认直接返回传入的position,等待补充实现:

function findPawnMoves(position) { return position; }

官方参考解法(--solutions--

源文件同时提供了完整的参考实现,这也是本挑战最简洁直观的解法:

function findPawnMoves(position) { const column = position[0].toUpperCase(); const row = parseInt(position[1]); const moves = []; if (row >= 8) return moves; moves.push(`${column}${row + 1}`); if (row === 2) moves.push(`${column}${row + 2}`); return moves; }

逐行拆解这段解法:

  1. 解析坐标position[0]取列字母(并用toUpperCase()兼容小写输入),parseInt(position[1])把行字符解析为数字;
  2. 初始化结果数组moves = []
  3. 边界处理if (row >= 8) return moves;处理第 8 行(含越界输入),此时兵已在棋盘顶端,直接返回空数组;
  4. 前进一格:把${column}${row + 1}拼进结果;
  5. 起始行特权if (row === 2)时再前进一格,得到${column}${row + 2}
  6. 返回结果:由于实现顺序天然是先row + 1row + 2,输出自然满足题面要求的升序排列。

边界情况与正确性分析

把官方解法放到全输入空间审视,可以确认以下边界全部被覆盖:

  • row === 8:第 8 行无合法走法,返回[](例如输入"H8"得到空数组);
  • row === 2:起始行返回两个目标格(例如"G2"["G3", "G4"]);
  • 列字母大小写toUpperCase()使得"b2""B2"得到相同结果,增强了对输入格式的鲁棒性;
  • 数字字符解析parseInt("2")得到数字 2,字符串模板${row + 1}会把它拼回目标格字符串。

从时间复杂度看,该解法是严格O(1)的:无论输入是什么,都只执行常数次字符串操作,没有任何循环或递归,这是此类"规则枚举型"题目的最优形态。相比需要遍历棋盘或构建映射表的方案,直接基于行号做分支判断既简单又不易出错。

解法变体:正则与字符码

理解了解析逻辑后,可以自然衍生出等价的变体写法,供对比学习:

// 变体一:正则提取字母与数字 function findPawnMoves(position) { const match = position.match(/^([A-Ha-h])([1-8])$/); if (!match) return []; const column = match[1].toUpperCase(); const row = Number(match[2]); const moves = row < 8 ? [`${column}${row + 1}`] : []; if (row === 2) moves.push(`${column}${row + 2}`); return moves; } // 变体二:字符码转换列号 function findPawnMoves(position) { const colCode = position.charCodeAt(0) - 65; // 0 ~ 7 const row = +position[1]; const column = String.fromCharCode(65 + colCode); const moves = []; if (row < 8) moves.push(`${column}${row + 1}`); if (row === 2) moves.push(`${column}${row + 2}`); return moves; }

变体一额外增加了输入格式校验(非法坐标返回空数组),变体二则展示了字符码与 ASCII 之间的换算技巧——这类"字母 ↔ 数字"的双向转换在第 172 题(Letters-Numbers)等相邻挑战中同样适用。

从一道题到一套系统:挑战文件的完整数据链路

这道题的 Markdown 源文件不仅仅是一个静态文档,它处于 freeCodeCamp 每日挑战系统的源头位置。理解这条链路,能让你真正看懂"挑战题面 → 数据库 → 线上接口"的运转方式。

第一步:Curriculum 源文件 → 种子脚本

tools/daily-challenges 目录 下的 seed-daily-challenges.ts 会把dev-playground超级板块下daily-coding-challenges-javascriptdaily-coding-challenges-python两个板块的挑战批量导入 MongoDB 的DailyCodingChallenges集合。其中几个关键事实(均来自源码):

  • 数量校验:脚本硬编码EXPECTED_CHALLENGE_COUNT = 365,两个语言的挑战数量必须一致,否则直接抛错;
  • 按天排期:从START_DATE = 2025-08-11起,每天一道,new Date(START_DATE.getTime() + i * ONE_DAY_IN_MS)
  • 数据合并:helpers.ts 中的combineChallenges会把同一道题的 JavaScript 与 Python 两个版本的testschallengeFiles合并成一个文档,同时校验两个版本的标题、描述、测试数量完全一致;
  • 题面清洗removeSection会剥掉解析器添加的<section id="description">包裹标签,只保留纯题面;
  • 文档主键:直接复用挑战的id(如本道的69738771fb5a7b8b24cca29f)作为 MongoDB_id,该 ID 同时用于记录用户已完成挑战的completedDailyCodingChallenges[]数组。

第二步:数据库 → API 接口

api/src/daily-coding-challenge 目录 下的 README.md 明确说明:该模块提供"获取每日编程挑战信息"的接口,而提交评测逻辑仍留在 API 主模块中。路由与校验分别位于 routes/daily-coding-challenge.ts 与 schemas/daily-coding-challenge.ts。

日期处理工具 utils/helpers.ts 体现了这套系统的几个设计细节:

  • getNowUsCentral():按美国中部时区(America/Chicago)计算"今天"——每日挑战以美中时间切日;
  • dateStringToUtcMidnight()monthDayStringToUtcDate():分别把YYYY-MM-DDMM-DD格式的日期字符串解析为 UTC 午夜,且做了正则格式校验和"非法日期回卷"防御;
  • getSourceDate():由于只生成了 2025-08-11 至 2026-08-10 一年的题库,该函数会把任意请求日期映射回原始题库日期(含 2 月 29 日落到 2 月 28 日的特殊处理),实现"题库循环复用"。

第三步:API → 前端组件

前端在 client/src/components/daily-coding-challenge 目录下有对应的 widget.tsx、calendar.tsx 等组件,用于在页面上展示每日一题;同时 e2e/daily-coding-challenge.spec.ts 提供了端到端测试覆盖。整个链路可以概括为:

curriculum 挑战 Markdown(含 --hints-- / --seed-contents-- / --solutions--) │ seed-daily-challenges.ts(tools/daily-challenges) ▼ MongoDB DailyCodingChallenges 集合(365 条,JS+Python 双语合并) │ api/src/daily-coding-challenge(路由 + 时区/日期处理) ▼ 前端 widget / calendar 组件(client/src/components/daily-coding-challenge)

学习要点总结

  1. 坐标建模:把"字母 + 数字"的棋盘坐标拆成独立的列分量与行分量处理,是这类题目的通用第一步;
  2. 规则最小化:题面明确省略了吃子、升变等复杂规则,解题时应严格按题面规则实现,不自行扩展;
  3. 边界先行:第 8 行(无走法)与第 2 行(起始行特权)两个分支是本体的全部逻辑,row >= 8的提前返回让代码更清晰;
  4. 结果有序性:按row + 1row + 2的构造顺序天然满足升序要求,无需额外排序;
  5. 可测试性:五组 hints 断言就是验收标准,把每个分支都映射到至少一个用例,是编写高质量挑战题(以及编写任何函数)的良好示范。

如果你想在本地完整跑通这套系统,可以按 tools/daily-challenges/README.md 的指引操作:复制sample.env.env、安装依赖、以"显示即将发布内容"的方式启动主客户端(使 GraphQL 端点可用)、进入tools/daily-challenges目录后执行pnpm seed-daily-challenges,即可把题库灌入本地的freecodecamp数据库。而在此之前,不妨先在浏览器控制台或 Node REPL 中运行本文的参考解法,用五组测试用例验证findPawnMoves的全部分支——这本身就是对"输入 → 规则 → 输出"这一编程思维的最小练习。

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 11:44:44

如何3步追踪IP定位与手机号信息:GhostTrack新手实操教程

如何3步追踪IP定位与手机号信息&#xff1a;GhostTrack新手实操教程 【免费下载链接】GhostTrack Useful tool to track location or mobile number 项目地址: https://gitcode.com/GitHub_Trending/gh/GhostTrack 看到陌生IP访问你的服务&#xff0c;或收到未知号码发来…

作者头像 李华
网站建设 2026/9/11 11:44:25

编程入门指南:从Python到项目实战的完整路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:41:47

代码自动生成框架:原理、实践与优化策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 11:39:46

AFE4400血氧模拟前端开发:SPI驱动与SpO2算法实现

简介&#xff1a;面向血氧饱和度&#xff08;SpO2&#xff09;与脉搏波监测等生物医疗场景&#xff0c;TI AFE4400模拟前端的驱动源码包适合嵌入式开发者、医疗电子工程师及可穿戴设备研发人员直接调用&#xff0c;能够快速完成芯片初始化并开展光电信号采集。rar压缩包内共2个…

作者头像 李华