第 6 周:JavaScript 基础与控制台版 2048
本周目标
本周完成后,应能够:
- 使用变量、数据类型和运算符表达程序状态与计算过程
- 使用条件和循环解决基础流程问题
- 使用数组和对象组织一组数据
- 使用函数拆分逻辑并减少重复代码
- 使用常用字符串、数组、Math 和日期能力处理数据
- 阅读基础错误信息,并使用控制台和断点定位问题
- 综合使用数组、对象和纯函数完成可操作的控制台版 2048
学习资料
JavaScript 参考视频待定。当前按以下知识范围和练习任务执行,确定课程后再补充视频章节映射。
需要掌握的知识点
运行环境与语法基础
- JavaScript 在浏览器中的运行方式
- 内部脚本和外部脚本的引入方式
console.log()、浏览器控制台和基础断点- 语句、表达式、注释和代码块
let、const的使用场景和命名规范
数据类型与运算
- 字符串、数字、布尔值、
undefined和null typeof和基础类型判断- 显式与隐式类型转换
- 算术、赋值、比较和逻辑运算符
===与==的区别,默认使用严格相等- 模板字符串和字符串拼接
流程控制
if、else if、elseswitch的使用场景for、while循环break和continue- 循环边界、初始化条件和终止条件
- 使用流程图或伪代码描述复杂判断
数组
- 数组创建、索引、长度和遍历
- 数组元素的增加、删除和修改
push、pop、shift、unshift、splice- 查找、筛选、转换和汇总的基本思路
- 二维数组的结构和遍历
纯函数与二维数组处理
- 函数的结果只由参数决定,不依赖或修改外部状态
- 返回新数组,不直接修改传入的数组
- 使用嵌套循环遍历二维数组
- 比较两个二维数组的内容是否一致
- 将复杂规则拆分为多个可单独测试的小函数
函数
- 函数声明、函数表达式和箭头函数
- 参数、默认值、返回值和提前返回
- 局部作用域与外部变量
- 将复杂问题拆分为多个职责单一的函数
- 回调函数的基本概念
对象与常用能力
- 对象、属性、方法和遍历
- 数组与对象组合的数据结构
- 字符串常用方法
Math的随机数、取整和极值能力- 日期对象的基础使用
- 解构和展开语法
调试与代码质量
SyntaxError、ReferenceError、TypeError等基础错误- 使用日志缩小问题范围
- 使用断点观察变量和执行顺序
- 变量、函数命名和适当的函数长度
- 为练习准备输入、预期输出和实际输出
控制台输入与输出
- 使用
console.table()输出二维棋盘 - 使用
console.clear()清理上一轮输出 - 使用
prompt()接收玩家指令 - 对输入进行大小写转换和合法性检查
- 使用循环维持游戏流程,并提供明确的退出条件
必做练习
以下练习用于掌握 JavaScript 基础,同时为控制台版 2048 准备可复用函数。
- 方向指令判断:实现
isValidDirection(input),判断输入是否为W、A、S、D,不区分大小写。 - 创建空棋盘:实现
createEmptyBoard(size = 4),使用循环返回指定尺寸的二维数组,所有位置初始值为 0。 - 统计空位数量:实现
countEmptyCells(board),使用嵌套循环统计二维棋盘中 0 的数量。 - 查找最大数字:实现
getMaxTile(board),返回棋盘中的最大数字;空棋盘返回 0。 - 反转一行数字的位置:实现
reverseLine(line),将数组元素按照原位置从后往前重新排列。反转只改变元素顺序,不修改数字本身,也不是按数字大小排序。例如,输入[2, 0, 4, 8],输出[8, 4, 0, 2];输入[2, 2, 4, 0],输出[0, 4, 2, 2]。函数必须返回新数组,不能修改传入数组。 - 复制二维棋盘:实现
cloneBoard(board),修改返回的棋盘时不能影响原棋盘。 - 提取指定列:实现
getColumn(board, colIndex),把二维棋盘中的指定列提取为一维数组。 - 写回指定列:实现
setColumn(board, colIndex, column),把一维数组写入指定列,并返回新棋盘。 - 随机整数:实现
randomIndex(length),返回0到length - 1之间的随机整数;length不合法时返回明确结果。 - 记录测试结果:为以上每个函数至少准备一个正常用例和一个边界用例,记录输入、预期输出和实际输出。
这些练习分别对应后续综合任务中的输入校验、棋盘初始化、空位查找、左右移动、上下移动、状态复制和随机数字生成。
本周综合任务:控制台版 2048
使用原生 JavaScript 实现一个可以在浏览器控制台中运行的基础可玩版 2048。游戏使用 console.table() 显示棋盘,使用 prompt() 接收操作。
难度评估:中等,约为完整版本的 60%-70%。完成上述必做练习后,大部分辅助函数可以直接复用。应按以下任务顺序逐步实现,每完成一个函数先通过测试,再进入下一项任务。
输入约定:本综合任务按标准 2048 棋盘验收。line 参数为长度 4 的一维数组,元素为 0 或正整数;board 参数为 4×4 二维数组,每个格子为 0 或正整数;玩家指令为字符串或关闭输入框得到的 null。
任务一:压缩一行数字
实现 compactLine(line),将所有非零数字移动到左侧,并在右侧补零。
- 函数签名:
compactLine(line) - 输入:长度 4 的一维数组
line,表示棋盘中的一行,例如[2, 0, 2, 4] - 输出:新的同长度一维数组,非零数字保持原相对顺序并移动到左侧,例如
[2, 2, 4, 0] - 约束:返回新数组,保持传入的
line不变
查看提示
先使用 filter() 或循环取出所有非零数字,再根据缺少的数量补 0。复制数组后再处理,避免修改参数。
任务二:向左合并一行数字
实现 mergeLineLeft(line),返回合并后的数组。一次移动中,每个数字最多参与一次合并。
- 函数签名:
mergeLineLeft(line) - 输入:长度 4 的一维数组
line,表示移动前的一行数字 - 输出:新的同长度一维数组,表示向左移动并合并后的结果
- 示例:
[2, 2, 2, 2]返回[4, 4, 0, 0] - 示例:
[2, 2, 4, 0]返回[4, 4, 0, 0] - 示例:
[4, 0, 4, 4]返回[8, 4, 0, 0] - 示例:
[0, 0, 0, 0]返回[0, 0, 0, 0] - 约束:返回新数组,保持传入的
line不变
查看提示
先压缩,再从左向右扫描;发现相邻数字相同时合并,并让索引额外前进一步,避免同一个数字连续合并两次。
任务三:查找所有空位
实现 getEmptyCells(board),返回 4×4 棋盘中所有值为 0 的位置。每个位置使用 { row, col } 表示。
- 函数签名:
getEmptyCells(board) - 输入:4×4 二维数组
board,表示当前棋盘 - 输出:数组,每一项是一个空位坐标对象
{ row, col } - 示例:输入
[[2, 0, 0, 0], [0, 4, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]],输出包含{ row: 0, col: 1 }和{ row: 1, col: 0 }等空位 - 边界:没有空位时输出空数组
[]
查看提示
在必做练习 countEmptyCells() 的两层循环基础上,把“数量加一”改为将 { row, col } 加入结果数组。
任务四:比较两个棋盘
实现 boardsEqual(boardA, boardB),逐项比较两个二维数组。尺寸或任意数字不同都返回 false。
- 函数签名:
boardsEqual(boardA, boardB) - 输入:两个二维数组
boardA和boardB;主要用于比较两个 4×4 棋盘,也要能识别尺寸不同的情况 - 输出:布尔值;完全一致返回
true,否则返回false - 示例:
boardsEqual([[2, 0]], [[2, 0]])返回true - 示例:
boardsEqual([[2, 0]], [[0, 2]])返回false - 边界:行数不同、列数不同都返回
false
查看提示
先比较行数和每行长度,再逐项比较;发现不同后立即返回 false。
任务五:判断是否达到目标数字
实现 hasTarget(board, target = 2048),判断棋盘中是否存在目标数字。
- 函数签名:
hasTarget(board, target = 2048) - 输入:4×4 二维数组
board;可选正整数target,默认值为2048 - 输出:布尔值;棋盘中存在目标数字返回
true,否则返回false - 示例:
hasTarget([[2, 4, 8, 16], [32, 64, 128, 256], [512, 1024, 2048, 0], [0, 0, 0, 0]])返回true - 示例:
hasTarget([[2, 4, 8, 16], [32, 64, 128, 256], [512, 1024, 0, 0], [0, 0, 0, 0]], 2048)返回false
查看提示
可以使用两层循环,也可以组合 some();找到目标后不需要继续遍历。
任务六:判断是否还可以移动
实现 canMove(board)。满足以下任意条件时返回 true:
- 棋盘中仍有空位
- 水平方向存在相邻且相同的数字
- 垂直方向存在相邻且相同的数字
只需要向右和向下检查相邻位置,避免重复判断或数组越界。
- 函数签名:
canMove(board) - 输入:4×4 二维数组
board,表示当前棋盘 - 输出:布尔值;仍可移动返回
true,不能移动返回false - 示例:
canMove([[2, 0, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2], [4, 8, 16, 32]])返回true - 示例:
canMove([[2, 4, 2, 4], [4, 2, 4, 2], [2, 4, 2, 4], [4, 2, 4, 2]])返回false - 示例:
canMove([[2, 2, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2], [4, 8, 16, 32]])返回true
查看提示
先调用 getEmptyCells() 判断是否存在空位;没有空位时,只比较当前位置与右侧、下方的数字。
任务七:判断游戏是否失败
实现 isGameOver(board)。棋盘没有空位,并且水平方向、垂直方向都不存在可合并数字时,返回 true。
- 函数签名:
isGameOver(board) - 输入:4×4 二维数组
board,表示当前棋盘 - 输出:布尔值;游戏失败返回
true,否则返回false - 示例:
isGameOver([[2, 4, 2, 4], [4, 2, 4, 2], [2, 4, 2, 4], [4, 2, 4, 2]])返回true - 示例:
isGameOver([[2, 2, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2], [4, 8, 16, 32]])返回false - 示例:
isGameOver([[2, 0, 4, 8], [16, 32, 64, 128], [256, 512, 1024, 2], [4, 8, 16, 32]])返回false
查看提示
不要重复编写判断规则,可以直接复用 canMove() 并对结果取反。
任务八:实现四个方向移动
在 mergeLineLeft() 的基础上实现 moveLeft()、moveRight()、moveUp() 和 moveDown()。每次移动返回新棋盘及棋盘是否变化。
- 函数签名:
moveLeft(board)、moveRight(board)、moveUp(board)、moveDown(board) - 输入:4×4 二维数组
board,表示移动前的棋盘 - 输出:对象
{ board: newBoard, changed } - 输出说明:
newBoard是移动后的新棋盘;changed是布尔值,表示棋盘是否发生变化 - 示例:
moveLeft([[2, 0, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])输出{ board: [[4, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], changed: true } - 示例:
moveLeft([[2, 4, 8, 16], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])输出changed: false - 约束:返回新棋盘,保持传入的
board不变
查看提示
复用必做练习中的 reverseLine()、getColumn() 和 setColumn()。向右移动可以先反转每一行,左移后再反转;上下移动可以先提取一列,按一行处理后再写回新棋盘。先完成左右,再处理上下。
任务九:生成随机数字
实现 addRandomTile(board),随机选择一个空位并生成数字 2。
- 函数签名:
addRandomTile(board) - 输入:4×4 二维数组
board,表示生成新数字前的棋盘 - 输出:新的二维数组,在一个随机空位上写入数字
2 - 示例:输入一个全 0 的 4×4 棋盘,输出棋盘中应只有一个位置是
2,其他位置仍是0 - 边界:如果没有空位,返回与输入内容一致的新棋盘
- 约束:返回新棋盘,保持传入的
board不变
查看提示
复用 getEmptyCells()、randomIndex() 和 cloneBoard(),先选择随机空位并复制棋盘,再写入数字。
任务十:初始化游戏
创建 4×4 空棋盘,并随机生成两个初始数字。
- 函数签名:
initGame() - 输入:无
- 输出:4×4 二维数组,包含两个随机生成的数字
2 - 示例:输出棋盘中应有 2 个非零格子,并且这 2 个格子的值都为
2 - 约束:两个初始数字不能出现在同一个格子
查看提示
复用必做练习中的 createEmptyBoard(),再连续调用两次 addRandomTile() 生成初始棋盘。
任务十一:实现控制台游戏循环
使用循环输出棋盘和操作提示,并通过 prompt() 接收指令:W 上、A 左、S 下、D 右。指令不区分大小写。
- 函数建议:
startGame() - 输入:玩家通过
prompt()输入的字符串,例如W、A、S、D;关闭输入框时输入值为null - 输出:通过
console.table()输出当前棋盘;通过alert()或console.log()输出无效输入、胜利或失败提示 - 流程输出:每次有效移动后展示更新后的棋盘
- 边界:无效指令保持棋盘不变;无效移动保持棋盘不变;关闭输入框时结束脚本
查看提示
先将输入统一转换为大写,再使用对象映射或 switch 选择移动函数。根据移动结果中的 changed 判断是否生成新数字;关闭输入框时可以结束脚本。
任务十二:完成胜负流程
出现 2048 时提示胜利;棋盘无法继续移动时提示失败,随后结束游戏循环。
- 函数建议:在
startGame()中调用hasTarget()和isGameOver() - 输入:每次有效移动并生成新数字后的棋盘
- 输出:胜利时输出明确胜利提示并结束循环;失败时输出明确失败提示并结束循环
- 示例:棋盘中出现
2048时,输出你赢了 - 示例:
isGameOver(board)为true时,输出游戏结束 - 约束:胜负判断应发生在棋盘状态更新之后
查看提示
一次有效移动的顺序应为“移动 -> 判断棋盘变化 -> 生成数字 2 -> 检查胜负 -> 输出新棋盘”。
实现要求
- 每个函数只负责一个明确任务
- 使用控制台完成输入输出
- 通过函数参数和返回值传递游戏状态
- 不直接修改传入的数组或棋盘
- 核心函数至少包含一个正常用例和一个边界用例
- 游戏逻辑与控制台输入输出分开组织
- 游戏代码放入
console-2048.js - 核心逻辑测试放入
console-2048.test.js或独立测试区域
本周产出
- 2048 JavaScript 基础练习及测试记录
- 可实际游玩的控制台版 2048
- 2048 核心逻辑测试记录
- 一份错误与调试记录
- 一份 JavaScript 与 C 语言差异笔记
完成检查
-
能够区分
let和const - 能够正确使用条件、循环和函数
- 能够选择数组或对象组织数据
- 能够把长逻辑拆成多个职责清晰的函数
- 能够使用控制台和断点定位基础错误
- 已完成可实际游玩的控制台版 2048
- 支持四向移动、固定生成数字 2 及胜负判断
- 无效输入和无效移动不会改变棋盘
- 核心逻辑未直接修改输入数据
- 每个 2048 核心函数均具有可重复执行的测试用例