Problem solving in Karel
Decomposition
The Idea of an algorithm
1. 翻山越岭的Karl
Karl爬山,不管是百步梯还是千步梯,类似图1
图 1
这个范例主要学习将问题分解成一个个简单的模块,化繁为简,逐步求精。
思路:
1> Karel来到山脚下(爬山当然要先到山边,没有自驾车,没有景区bus,没有。。。,只有2条腿;)。
2> 开始爬山,就是上楼梯,下楼梯;
3> 离开(爬山结束,该干嘛干嘛去,反正不能堵了山门干收费的勾当)。
1>和3>好说,Karel够单一,认准一个方向(east)直走,碰到墙就停下。
2>应该可以再分为3步:上山、登顶、下山。
上山:只要前面有楼梯(墙)就上,也就是左转 –> 移动 –> 右转 –> 移动;如此循环,就登顶了。
登顶:好不容易爬上来,会当临决定,总要做些啥,不管是插旗还是撒尿都可以表达到此一游,Karel只能放个方块表示。
下山:应该类似上山,只要前面为空(悬崖,别抬杠),就前进 –> 右转 –> 前进 –> 左转,如此循环,直到下到山脚。
分析到这里,似乎一切都ok,敲代码验证就完事儿了。但是,假设山是名山,修得特标准,上下都是一级级的台阶,但人就不同了,形形色色,四面八方,借问Karel一句:施主从何而来,又向何而去? Karel当然说:俺从西边来,向东边去。至于来有多近,去有多远。Karel就不操心了,按照指令走就是。这时,在看看上面的算法,似乎都ok,对应图1,下山也没问题,但要是环境变为11格,希望Karel走的更远,Karel就会撞地上了。悲催!
所以,下山的步骤就需要修改下:前面的问题就在于Karel不晓得自己是否已下到山脚,所以下山的条件是脚下是空的才能下,
下山:前进 –> 脚下是否为空(是 –> 右转 –> 前进 –> 左转 –> 看前面还有路否?(是 –> 前进)) 如此循环。
代码:
1 /*
2 * File: MountainKarel7.java
3 * -------------------------
4 * The MountainKarel7 subclass gets Karel to climb a simple
5 * mountain, plant a flag, and descend to the ground. This
6 * version generalizes the program so that it can climb a
7 * stair-step mountain of any size.
8 */
9
10 import stanford.karel.*;
11
12 /* Main program */
13
14 public class MountainKarel7 extends SuperKarel {
15
16 /**
17 * Runs the program.
18 */
19 public void run() {
20 moveToWall();
21 climbMountain();
22 moveToWall();
23 }
24
25 /**
26 * Purports to climb up and down a stair-step mountain of
27 * any size. This code is considerably better than the
28 * last attempt, but still has a small bug.
29 */
30 private void climbMountain() {
31 while (frontIsBlocked()) {
32 stepUp();
33 }
34 putBeeper();
35 move();
36 while (rightIsClear()) {
37 dropDown();
38 }
39 }
40
41 /**
42 * Send Karel up the step ahead of it.
43 */
44 private void stepUp() {
45 turnLeft();
46 move();
47 turnRight();
48 move();
49 }
50
51 /**
52 * Drops down from the midair position just past a
53 * descending step.
54 */
55 private void dropDown() {
56 turnRight();
57 move();
58 turnLeft();
59 if (frontIsClear()) {
60 move();
61 }
62 }
63
64 /**
65 * Moves Karel forward until it is blocked by a wall.
66 */
67 private void moveToWall() {
68 while (frontIsClear()) {
69 move();
70 }
71 }
72 }
2. “种树”的Karel
春寒料峭,Stanford校园里的树都光秃秃的(啥,怎没有冬青树),Karel从西向东闲逛。路见秃树,就想把其装饰下,装饰前后的效果如图2、3.
图 2
图 3
Karel不知道树有多高,多远。只确定有足够的方块装饰每棵树。
算法分析:
Karel要做的就是找到树,装饰它。
找树简单,就好比碰到墙就停下。
装饰树:爬树 –> 放树叶 –> 下树。
爬树:左转 –> 只要右边有树(墙)就前进 –> 右转。
放树叶:按图3所示放4个方块 –> 前进 –> 右转。
放4个方块:放1个方块 –> 前进 –> 左转 (循环4次)。
下树:类似上树,只要没碰到地就一直前进。
代码:
1 /*
2 * File: BanishWinter.java
3 * -----------------------
4 * The BanishWinter subclass gets Karel adorn a series
5 * of trees with a cluster of beeper leaves.
6 */
7
8 import stanford.karel.*;
9
10 public class BanishWinter extends SuperKarel {
11
12 /* Main program */
13
14 public void run() {
15 while (beepersInBag()) {
16 findTree();
17 addLeavesToTree();
18 }
19 moveToWall();
20
21 }
22
23 /*
24 * Moves Karel up to the next tree.
25 *
26 * Programming style note: Since a tree is simply a wall,
27 * this method can simply call moveToWall. You could
28 * therefore replace the findTree call in the main program
29 * with moveToWall, but the program might then be harder to
30 * read because it violates the "tree" metaphor used at the
31 * level of the main program.
32 */
33 private void findTree() {
34 moveToWall();
35 }
36
37 /*
38 * Adorns a single tree with a cluster of leaves. The
39 * precondition is that Karel must be immediately
40 * west of the tree, facing east; the postcondition is
41 * that Karel is at the bottom of the other side of the
42 * tree, facing east.
43 */
44 private void addLeavesToTree() {
45 turnLeft();
46 climbTree();
47 addLeaves();
48 descendToGround();
49 turnLeft();
50 }
51
52 /*
53 * Climbs to the top of the tree.
54 */
55 private void climbTree() {
56 while (rightIsBlocked()) {
57 move();
58 }
59 }
60
61 /*
62 * Moves Karel back to the ground. Both the pre- and
63 * postconditions have Karel facing south.
64 */
65 private void descendToGround() {
66 moveToWall();
67 }
68
69 /*
70 * Creates the cluster of leaves at the top of a tree.
71 * The precondition is that Karel must be facing north at
72 * the top of the tree; the postcondition is that Karel
73 * is still at the top, but on the other side of the
74 * trunk, facing south.
75 */
76 private void addLeaves() {
77 turnRight();
78 createBeeperSquare();
79 move();
80 turnRight();
81 }
82
83 /*
84 * Moves Karel forward until it is blocked by a wall.
85 */
86 private void moveToWall() {
87 while (frontIsClear()) {
88 move();
89 }
90 }
91
92 /*
93 * Creates a square of four beepers, leaving Karel in its
94 * original orientation. The resulting square is positioned
95 * ahead and to the left of Karel's starting position.
96 */
97 private void createBeeperSquare() {
98 for (int i = 0; i < 4; i++) {
99 putBeeper();
100 move();
101 turnLeft();
102 }
103 }
104
105 }
106
107
PS:这个代码里假定只有4棵树,所以Karel的包里有20个方块,如果编辑方块的数量多于20,就会出错,因为Karel不能区别墙和树。
3. 闯迷宫的Karel
无它,右手法则,盲人摸象般,贴着右手的墙走,不通就左转。
代码:
1 public class karelMazeSolver extens SuperKarel {
2 public void run() {
3 while (noBeepersPresent()) {
4 if (rightIsClear()) {
5 turnRight();
6 } else {
7 while (frontIsBlocked()) {
8 turnLeft();
9 }
10 }
11 move();
12 }
13 }
14 }