拓冰建站拓冰建站
首页 / 资讯中心 / 正文

freeCodeCamp 每日编程挑战第 111 题「Ball Trajectory」全解:在二维矩阵网格上模拟 Pong 的球体反弹轨迹

freeCodeCamp 每日编程挑战第 111 题「Ball Trajectory」全解在二维矩阵网格上模拟 Pong 的球体反弹轨迹【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本文围绕 freeCodeCamp 课程仓库中的「Daily Coding Challenge 111: Ball Trajectory」展开题目要求根据矩阵中球的当前位置2与上一位置1推导出球的下一个落点坐标并把矩阵四边当作会反弹的墙。读完本文你将掌握用位置差分求运动方向 越界判断翻转方向分量这套离散网格上模拟直线运动与镜面反射的通用解法并能在 freeCodeCamp 的每日编程挑战环境中直接通过全部测试。题目原文速览与仓库定位这道题对应仓库中的课程 Markdown 文件curriculum/challenges/english/blocks/daily-coding-challenges-javascript/69162d64f96574d9bb629efc.md。其 front matter 记录了该题在课程体系中的身份id: 69162d64f96574d9bb629efc title: Challenge 111: Ball Trajectory challengeType: 28 dashedName: challenge-111其中challengeType: 28在 packages/shared/src/config/challenge-types.ts 中被定义为常量dailyChallengeJs 28即JavaScript 每日编程挑战类型同文件中还能看到dailyChallengePy 29是它的 Python 姊妹类型。文件正文采用 freeCodeCamp 统一的题目区块语法--description--题目描述、--hints--5 组自动化断言、--seed-contents--初始代码与--solutions--官方题解四大部分。该题隶属于daily-coding-challenges-javascript内容块其块级配置顺序、版式、帮助分类记录在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中Challenge 111 在该块中排在 Challenge 110Word Guesser之后、Challenge 112AI Detector之前。题目背景致敬了 1972 年 11 月 29 日发布的经典街机游戏 Pong——游戏中的小球在矩形场地内做直线运动撞到上下左右边界后反弹。本题正是用编程语言把这一直线运动 边界反弹的物理规律离散化地搬到一张用二维数组表示的矩阵棋盘上。输入输出模型一张会反弹的棋盘题目给出的输入是一个矩阵array of arrays其中恰好包含两种非零标记标记含义0空格没有球经过 / 与球无关的普通单元格1球在上一时刻上一帧所处的位置2球在当前时刻当前帧所处的位置要求实现的函数签名与初始模板如下文件中的--seed-contents--区块function getNextLocation(matrix) { return matrix; }函数接收矩阵返回一个形如[row, col]的长度为 2 的数组表示球下一步将会到达的单元格下标。题目明确了三条物理规则球总是沿直线运动The ball always moves in a straight line运动方向由球从1走到2的方式决定——也就是把上一位置 → 当前位置的位移当作当前速度方向矩阵的边缘被视为墙壁walls。若球撞到上墙或下墙翻转竖直方向vertical direction左墙或右墙翻转水平方向horizontal direction墙角两个方向同时翻转。由于每一帧球只移动一格1与2是相邻格下一帧也只会落在2的相邻格因此这里的反弹表现为先按当前方向向量外推一格若目标格超出矩阵范围就把对应轴的方向分量取反再重新计算落点。规则拆解从差分到方向向量二维矩阵中我们用[row, col]描述坐标row向下增长第 0 行在顶部col向右增长第 0 列在左端。设上一位置prev [prevRow, prevCol]当前位置curr [currRow, currCol]那么球的运动方向就是二者之差dirRow currRow - prevRow // 竖直方向1 向下、-1 向上、0 水平 dirCol currCol - prevCol // 水平方向1 向右、-1 向左、0 竖直因为相邻两格移动dirRow与dirCol的取值只会落在集合{-1, 0, 1}中。随后无墙情况下的预测落点即nextRow currRow dirRow nextCol currCol dirCol墙的检查只需要看nextRow/nextCol是否越界nextRow 0或nextRow 行数-1撞到上/下墙翻转dirRownextCol 0或nextCol 列数-1撞到左/右墙翻转dirCol。两个方向同时越界时两个分量各自取反恰好对应撞到墙角的情形。需要特别理解的是这道题使用先外推、后检测、再取反、最后落点的简化离散模型检测的是预测落点是否越界而不是球体在运动途中擦边的连续物理过程——由于球每帧只移动一格且反弹发生在落入越界格之前的那一刻所以对方向向量各分量做一次翻转即可得到正确落点无需循环多帧推演。这一点与后面测试用例的验算结果完全吻合。官方题解逐行精讲题目文件--solutions--区块给出了一段完整可运行的参考实现下面逐段拆解其逻辑。第一步扫描矩阵定位两个标记格function getNextLocation(matrix) { let prev null; let curr null; for (let row 0; row matrix.length; row) { for (let col 0; col matrix[0].length; col) { if (matrix[row][col] 1) prev [row, col]; if (matrix[row][col] 2) curr [row, col]; } } // 全部遍历后prev 球的上一个位置curr 球的当前位置这里用两个null变量保存坐标matrix[0].length取列数。由于题目保证矩阵中恰好各有一个1与2扫描完整张矩阵后prev与curr一定被赋值。第二步计算方向向量const [prevRow, prevCol] prev; const [currRow, currCol] curr; let dirX currCol - prevCol; // 水平方向1 右 / -1 左 / 0 不动 let dirY currRow - prevRow; // 竖直方向1 下 / -1 上 / 0 不动注意这里把X对应col列、左右把Y对应row行、上下与矩阵的先行后列记法保持一致。第三步预测下一个落点并检测边界let nextRow currRow dirY; let nextCol currCol dirX; const maxRow matrix.length - 1; // 最底部行的下标 const maxCol matrix[0].length - 1; // 最右侧列的下标maxRow/maxCol是合法的最大下标任何小于0或大于它们的坐标都属于越界——也就是撞墙。第四步按命中墙的类型翻转方向分量if (nextCol 0 || nextCol maxCol) dirX * -1; // 命中左墙或右墙 if (nextRow 0 || nextRow maxRow) dirY * -1; // 命中上墙或下墙 nextRow currRow dirY; // 用修正后的方向重新外推 nextCol currCol dirX; return [nextRow, nextCol]; // 返回 [row, col] 形式的下一个落点 }水平越界时仅翻转dirX竖直越界时仅翻转dirY当预测落点落在矩阵对角之外时两个if都会命中两个分量同时取反自然实现撞墙角双向反弹。翻转后必须重新计算nextRow/nextCol而不是沿用越界的旧值这是保证返回结果一定落在合法范围内的关键。整段解法的时间复杂度为O(rows × cols)主要消耗在扫描定位标记空间复杂度为O(1)。五个测试用例全验算题目--hints--区块用 5 组assert.deepEqual断言了函数的正确性。逐一验算如下。用例一水平向右移动无需反弹assert.deepEqual(getNextLocation([[0,0,0,0], [0,0,0,0], [0,1,2,0], [0,0,0,0]]), [2, 3]);prev (2,1)curr (2,2)故dirY 0、dirX 1。预测落点(2,3)矩阵为 4×4maxRow maxCol 3nextCol 3恰好等于maxCol不越界。返回[2, 3]。用例二向右下对角运动到角落assert.deepEqual(getNextLocation([[0,0,0,0], [0,0,1,0], [0,2,0,0], [0,0,0,0]]), [3, 0]);prev (1,2)curr (2,1)故dirY 1、dirX -1。预测落点(3,0)落在第 0 列左墙且第 3 行底部但均在合法范围内。返回[3, 0]。用例三撞上墙竖直方向反弹assert.deepEqual(getNextLocation([[0,2,0,0], [1,0,0,0], [0,0,0,0], [0,0,0,0]]), [1, 2]);prev (1,0)curr (0,1)故dirY -1向上、dirX 1向右。预测落点(-1, 2)nextRow -1 0命中上墙翻转dirY为1。重算得nextRow 0 1 1nextCol 1 1 2。返回[1, 2]——球从上方边界弹回并继续向右。用例四撞左墙水平方向反弹assert.deepEqual(getNextLocation([[0,0,0,0], [0,0,0,0], [2,0,0,0], [0,1,0,0]]), [1, 1]);prev (3,1)curr (2,0)故dirY -1、dirX -1。预测落点(1,-1)nextCol -1 0命中左墙翻转dirX为1。重算得nextRow 2 - 1 1nextCol 0 1 1。返回[1, 1]。用例五撞右下墙角双向同时反弹assert.deepEqual(getNextLocation([[0,0,0,0], [0,0,0,0], [0,0,1,0], [0,0,0,2]]), [2, 2]);prev (2,2)curr (3,3)故dirY 1、dirX 1。预测落点(4,4)nextRow 4 maxRow(3)且nextCol 4 maxCol(3)两个if同时命中dirY、dirX各自取反为-1。重算得nextRow 3 - 1 2nextCol 3 - 1 2。返回[2, 2]——这正模拟了 Pong 中小球砸到右下角后沿原路弹回对角方向的场景。深入理解为什么外推一次再翻转就足够一个常见的疑惑是现实中的反弹是撞墙瞬间改变方向而本解法是先看下一帧落点再决定翻转为何不需要判断中途撞墙关键在于这道题的离散模型1与2是相邻格速度向量各分量的模长至多为1。这意味着从curr出发的下一帧球要么留在同一行/列要么恰好向某个方向移动一格。如果这一格越界说明球在即将迈出边界的那一刻发生了反弹而反弹后的位置就是curr沿修正后方向移动一格的结果。由于一次移动最多同时越出两个维度横纵每个方向分量至多需要翻转一次因此预测 → 翻转 → 重算三步即可无需循环逼近。若把棋盘边长推广成任意rows × cols这套逻辑仍然成立——maxRow、maxCol是从矩阵形状动态推导的并不依赖具体的 4×4 尺寸因此同一份实现可以处理非正方形矩阵。从这道题延伸出去的思考从源码结构和测试设计看这道题考察的核心能力可拆为三层二维数组的坐标心智模型习惯matrix[row][col]中行在前、列在后的顺序以及行号向下增长、列号向右增长的约定用位移差分编码方向把一个运动方向问题转化为相邻两帧坐标差的符号问题避免引入额外的方向状态变量边界条件的完整枚举上、下、左、右与墙角共 5 类边界命中情况对应单分量取反与双分量取反两类操作。感兴趣的读者可以进一步尝试以下变体作为对本解的加固训练若题目要求一次返回多帧轨迹例如输出未来 5 步落点数组需要把翻转后的新方向持久化为状态并在循环中复用此时每一帧都要重新执行边界检测若球被允许在空格上滑行多格每帧步长可变则需用步长把移动切分成多次单格移动逐格检测撞墙若矩阵中的0之外还存在障碍物格则反弹逻辑需要从边界翻转泛化为碰到障碍物所在坐标即翻转对应分量。这道题在 freeCodeCamp 仓库中的工程形态把目光从题目本身拉开可以看到这道题在 freeCodeCamp 的整个工程中有一套完整的落点与数据结构支持类型归属challengeType: 28对应 packages/shared/src/config/challenge-types.ts 中的dailyChallengeJs同文件还声明了它在经典多文件编辑器中的渲染视图类型classic第 134 行、提交方式为tests第 175 行以及用于判断是否为每日编码挑战的工具函数getIsDailyCodingChallenge与语言映射getDailyCodingChallengeLanguage第 182-205 行内容块编排块的challengeOrder数组按序号排列的 Challenge 1 到 200定义在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中该块还带有usesMultifileEditor: true、helpCategory: JavaScript、blockLayout: legacy-challenge-list、disableLoopProtectTests: true与isUpcomingChange: true等元信息说明它是随每日节奏发布、使用经典列表布局的 JavaScript 挑战块前端展示层客户端daily-coding-challenge组件目录client/src/components/daily-coding-challenge/中的日历、日期工具与小组件负责把这类每日一题按日期渲染出来。例如 helpers.ts 里的getTodayUsCentral()以美国中部时区计算今天对应的yyyy-MM-dd并提供lastDailyChallengeIsReleased()判断新题是否已全部发布完毕其锚点日期常量LAST_DAY_OF_NEW_DAILY_CHALLENGES 2026-08-10位于 helpers.ts 第 21 行。因此69162d64f96574d9bb629efc.md这类 Markdown 文件是每日编程挑战内容生产管线的最小单元贡献者只需按front matter description hints seed solutions的约定填写挑战便会进入结构化数据、被客户端读取并最终以多文件编辑器中的测试用例形式呈现给学习者。小结「Ball Trajectory」是一道把经典街机游戏 Pong 的物理规律离散化到二维数组上的好题定位1与2两个标记、用坐标差分求得速度方向、外推一步预测落点、再依据上/下/左/右/墙角五类越界情形对方向分量取反重算。官方题解给出的实现清晰短小、时间复杂度O(rows × cols)、空间复杂度O(1)并且可推广到任意尺寸矩阵。掌握它等于掌握了网格世界中的直线运动 边界反射这一在矩阵遍历、迷宫寻路、贪吃蛇、弹珠台乃至简单物理引擎中反复出现的基础算法骨架。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门