Lab 12:Project 3 入门
**重要:**在 IntelliJ 中,应当在
proj3目录这一层打开项目,而不是只打开lab12目录。否则 package 配置会出现问题。
目录#
Lab 前准备#
- 运行
git pull skeleton master,获取 Project 3、Lab 12 和 Lab 13 的起始文件。 - 观看课程链接中的往届 Project 3 入门视频。视频中的名称和 API 与本学期略有不同,但总体设计仍然适用。
- 阅读 Project 3 Phase 1 的规格。
- Project 3 是一场马拉松,而不是短跑。不要拖到最后才开始。
- 提前联系 Project 3 搭档。最好和搭档共同完成本 Lab。
- 整个 Project 3 必须始终与同一位搭档合作。
简介#
本 Lab 将让你熟悉 Project 3 中提供的 Tile Rendering Engine,并练习把一个复杂绘图任务分解成具有清晰抽象层次的辅助方法。
第一部分会查看两个演示程序,了解怎样建立并显示一个由 TETile 组成的二维世界。第二部分会要求你使用同一套引擎绘制六边形,并把多个六边形组合为镶嵌图案。
第一部分:认识 Tile Rendering Engine#
Boring World#
在 IntelliJ 中打开 proj3,而不是 lab12。通过 proj3/pom.xml 导入项目。对于本 Lab,只需关注 byow.lab12 package。
运行 BoringWorldDemo。这个演示程序创建一个非常简单的世界。它的过程可以分为三步:
- 初始化一个
TERenderer; - 创建一个二维
TETile[][]数组; - 要求 renderer 显示这个数组。
创建并初始化 renderer 的代码如下:
TERenderer ter = new TERenderer();
ter.initialize(width, height);java这里的宽度和高度以 tile 数量为单位。每个 tile 是 16×16 像素。例如,initialize(10, 20) 会建立一个 10×20 tile 的窗口,即 160×320 像素。
你可以使用 TETile 构造器创建自己的 tile,也可以使用 Tileset.java 中已经创建好的 tile。下面的代码会创建一个空世界:
TETile[][] world = new TETile[WIDTH][HEIGHT];
for (int x = 0; x < WIDTH; x += 1) {
for (int y = 0; y < HEIGHT; y += 1) {
world[x][y] = Tileset.NOTHING;
}
}java之后,可以覆盖数组中的某个区域:
for (int x = 20; x < 35; x += 1) {
for (int y = 5; y < 10; y += 1) {
world[x][y] = Tileset.WALL;
}
}java最后,使用下面的调用显示世界:
ter.renderFrame(world);java修改数组之后,屏幕不会自动更新。只有再次调用 renderFrame,renderer 才会显示新状态。
尝试把 WALL 换成其他 tile,并修改循环边界,观察世界如何变化。
TETile 是不可变对象,因此不能通过下面这种方式修改某个 tile 的字符:
world[x][y].character = 'X';javaRandom World#
运行 RandomWorldDemo.java。这个演示展示了三个概念:
java.util.Random伪随机数生成器;switch语句;- 把任务委托给多个方法,而不是把所有逻辑都塞进
main。
考虑下面的代码:
Random r = new Random(1000);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());java伪随机序列实际上是确定性的。相同的 seed 会产生完全相同的序列:
Random r = new Random(82731);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
r = new Random(82731);
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());
System.out.println(r.nextInt());java上面两组四个整数会完全相同。调用 new Random() 而不传入 seed 时,Java 会根据时间等不断变化的值选择 seed。
起始代码使用固定 seed 2873123,因此每次运行都会生成相同的世界。Project 3 必须利用这种确定性,使相同输入总是能够重建同一个世界。
观察 RandomWorldDemo 如何使用 switch,以及它如何把不同任务交给独立方法。这里最重要的设计原则是:不断把复杂任务拆分为行为清楚的小方法,并建立分层抽象。
第二部分:使用 Tile Rendering Engine#
Hex World 简介#
接下来,在 HexWorld 中创建一个由随机六边形地形组成的世界。最终结果应当类似一个六边形棋盘,并使用草地、花、沙漠、森林和山脉等不同 tile。
绘制一个六边形#
首先实现一个方法,它的作用类似:
addHexagon(..., int s, ...)java这个方法应当在指定位置绘制边长为 s 的六边形。
你的实现应当:
- 支持
s = 2, 3, 4, 5, ...; - 让六边形最宽的“中间”始终由两行同样长度的 tile 组成,以便多个六边形能够无缝镶嵌;
- 使用辅助方法拆分绘制逻辑和几何计算;
- 可以为关键计算辅助方法编写 JUnit 测试;
- 可以设计一个
Hexagon类,但应当认真思考一个六边形对象应当知道什么、应当能够做什么; - 自己设计
addHexagon的完整方法签名,这本身也是任务的一部分。
如果想生成随机颜色,可以参考:
TETile.colorVariant(...)java这个任务可能会占用整节 Lab。没有做完并不要紧;重点是认真思考代码设计。
绘制六边形镶嵌图案#
完成单个六边形后,尝试把它们排列成目标图案。最终图案应当包含 19 个六边形。
不要把全部工作塞进一个巨大的嵌套循环而不编写辅助方法。如果没有分层抽象,代码会难以理解和调试;在有限的 Office Hours 时间中,TA 也很难帮助你。
思考可以使用哪些辅助方法。例如,一个方法可以绘制一列六边形,另一个高层方法可以组合多列。坐标和间距的计算也可以放在独立方法中。
现场编程演示#
观看课程提供的现场编程演示,观察工作人员如何一步一步拆分问题,并让低层绘图操作服务于高层世界生成逻辑。
继续完成 Project 3#
阅读 Phase 1 规格,并查看 project3prep.md。与搭档或 TA 讨论后填写这个文件。
Project 3 的世界生成虽然比 Hex World 更复杂,但核心过程相同:先识别能够独立测试的小任务,再把它们组合成更高层的世界生成器。
提交#
提交填写完整的:
project3prep.mdtext只要完整填写并提交这个文件,就可以获得本 Lab 的满分。
原始页面:https://sp21.datastructur.es/materials/lab/lab12/lab12 ↗