Blockly 游戏总结报告

Blockly 游戏以多重关卡设计向青少年普及编程知识,并实现从方块到代码的过渡。“拼图”章节简单介绍了方块的使用,“迷宫”章节简单介绍了循环语句与条件语句,“鸟”章节强化了条件语句,“乌龟”章节强化了循环语句,“电影”章节引入了数学计算,“音乐”章节引入了函数,“池塘教学”引入了使用代编程,“池塘”章节是一个开放性的比赛。该游戏设计合理,非常适合青少年学习。

由于在文中展示方块较为麻烦,下面统一使用 Javascript 代码。

拼图

这是一道常识题目,答案为:

迷宫

1

本关用直路介绍方块的拼法。

moveForward();
moveForward();

2

本关用弯路介绍不同方块的混合使用。

moveForward();
turnLeft();
moveForward();
turnRight();
moveForward();

3

本关用长直路介绍循环语句的用法。

while (notDone()) {
  moveForward();
}

4

本关用长弯路介绍在循环语句内部使用不同方块。

while (notDone()) {
  moveForward();
  turnLeft();
  moveForward();
  turnRight();
}

5

本关用两段直路介绍普通语句与循环语句的混合使用。

moveForward();
moveForward();
turnLeft();
while (notDone()) {
  moveForward();
}

6

本关用只需要左转弯的弯路介绍条件语句。

while (notDone()) {
  moveForward();
  if (isPathLeft()) {
    turnLeft();
    moveForward();
  }
}

7

本关用只需要右转弯的弯路介绍条件语句。

while (notDone()) {
  moveForward();
  if (isPathRight()) {
    turnRight();
    moveForward();
  }
}

8

本关用同时需要左转弯和右转弯但是没有岔路的弯路介绍条件语句。

while (notDone()) {
  if (isPathForward()) {
    moveForward();
  }
  if (isPathLeft()) {
    turnLeft();
    moveForward();
  }
  if (isPathRight()) {
    turnRight();
    moveForward();
  }
}

9

本关用同时需要存在弯路,但只需要优先直行,否则左转的弯路介绍条件语句。

while (notDone()) {
  if (isPathForward()) {
    moveForward();
  } else {
    turnLeft();
  }
}

10

本关用复杂的弯路介绍条件语句,引入了“摸墙法”。(参考资料:维基百科

简而言之:尽可能用左手(或右手)摸着墙走。这种方法适用于迷宫外墙连续的情况。

while (notDone()) {
  if (isPathLeft()) {
    turnLeft();
    moveForward();
  } else {
    turnRight();
  }
}

1

本关用直线飞行介绍“飞行方向”函数的用法。

heading(45);

2

本关用折线飞行介绍条件语句的用法。

if (noWorm()) {
  heading(0);
} else {
  heading(90);
}

3

这一关与上一关本质相同。

if (noWorm()) {
  heading(300);
} else {
  heading(60);
}

4

本关用需要避开隔板的折线飞行介绍如何写条件语句中的条件。

if (getX() < 80) {
  heading(0);
} else {
  heading(270);
}

5

这一关与上一关本质相同。

if (getY() > 20) {
  heading(270);
} else {
  heading(180);
}

6

本关用需要捉虫与避开隔板的折线飞行介绍如何写出多重条件语句。

if (noWorm()) {
  heading(345);
} else if (getY() < 80) {
  heading(90);
} else {
  heading(180);
}

7

这一关与上一关本质相同。

if (getY() > 30) {
  heading(240);
} else if (noWorm()) {
  heading(345);
} else {
  heading(180);
}

8

这一关与上一关本质相同,但难度略微上升。

if (noWorm() && getX() < 50) {
  heading(15);
} else if (noWorm() && getX() > 50) {
  heading(345);
} else if (getY() < 50) {
  heading(120);
} else {
  heading(60);
}

9

这一关与上一关本质相同。

if (noWorm() && getX() > 20) {
  heading(180);
} else if (noWorm() && getX() < 20) {
  heading(270);
} else if (getX() < 40) {
  heading(75);
} else {
  heading(300);
}

10

本关需要捉虫后原路返回,对称的代码写法更简单。

if (noWorm() && getX() < 40) {
  heading(75);
} else if (noWorm()) {
  heading(315);
} else if (getX() > 40) {
  heading(135);
} else {
  heading(255);
}

乌龟

1

本关需用循环结构画出正方形。

for (var count = 0; count < 4; count++) {
  moveForward(100);
  turnRight(90);
}

2

本关需用循环结构画出正五边形。

for (var count = 0; count < 5; count++) {
  moveForward(100);
  turnRight(72);
}

3

本关需用循环结构画出正五角星,同时介绍了如何更改笔的颜色。

penColour('#ffff00');
for (var count = 0; count < 5; count++) {
  moveForward(100);
  turnRight(144);
}

4

本关在上一关的基础上多了一竖,介绍了如何提起笔、落下笔。

penColour('#ffff00');
for (var count = 0; count < 5; count++) {
  moveForward(50);
  turnRight(144);
}
penUp();
moveForward(150);
penDown();
moveForward(20);

5

本关需要画出四个五角星,介绍了循环语句。

penColour('#ffff00');
for (var count2 = 0; count2 < 4; count2++) {
  for (var count = 0; count < 5; count++) {
    moveForward(50);
    turnRight(144);
  }
  penUp();
  moveForward(150);
  penDown();
  turnRight(90);
}

6

本关在上一关的基础上减少了一个五角星,多了一横。

penColour('#ffff00');
for (var count2 = 0; count2 < 3; count2++) {
  for (var count = 0; count < 5; count++) {
    moveForward(50);
    turnRight(144);
  }
  penUp();
  moveForward(150);
  penDown();
  turnRight(120);
}
turnLeft(90);
penUp();
moveForward(100);
penDown();
moveForward(50);

7

本关在上一关的基础上多了三笔,这意在引入如何用多条线段模拟圆。

penColour('#ffff00');
for (var count2 = 0; count2 < 3; count2++) {
  for (var count = 0; count < 5; count++) {
    moveForward(50);
    turnRight(144);
  }
  penUp();
  moveForward(150);
  penDown();
  turnRight(120);
}
turnLeft(90);
penUp();
moveForward(100);
for (var count3 = 0; count3 < 4; count3++) {
  penDown();
  moveForward(50);
  turnRight(90);
  turnRight(90);
  penUp();
  moveForward(50);
  turnLeft(90);
  turnLeft(45);
}

8

本关在上一关的基础上多画了一个圆。

penColour('#ffff00');
for (var count2 = 0; count2 < 3; count2++) {
  for (var count = 0; count < 5; count++) {
    moveForward(50);
    turnRight(144);
  }
  penUp();
  moveForward(150);
  penDown();
  turnRight(120);
}
turnLeft(90);
penUp();
moveForward(100);
for (var count3 = 0; count3 < 360; count3++) {
  penDown();
  moveForward(50);
  turnRight(90);
  turnRight(90);
  penUp();
  moveForward(50);
  turnLeft(90);
  turnLeft(90);
  turnRight(1);
}

9

本关在上一关的基础上多画了一个黑色的圆,从而画出了月牙形。这是一种巧妙的设计,但是 120° 的角度并不容易观察到,易卡关。

penColour('#ffff00');
for (var count2 = 0; count2 < 3; count2++) {
  for (var count = 0; count < 5; count++) {
    moveForward(50);
    turnRight(144);
  }
  penUp();
  moveForward(150);
  penDown();
  turnRight(120);
}
turnLeft(90);
penUp();
moveForward(100);
for (var count3 = 0; count3 < 360; count3++) {
  penDown();
  moveForward(50);
  turnRight(90);
  turnRight(90);
  penUp();
  moveForward(50);
  turnLeft(90);
  turnLeft(90);
  turnRight(1);
}
turnLeft(1);
penColour('#000000');
turnRight(120);
moveForward(20);
for (var count4 = 0; count4 < 360; count4++) {
  penDown();
  moveForward(50);
  turnRight(90);
  turnRight(90);
  penUp();
  moveForward(50);
  turnLeft(90);
  turnLeft(90);
  turnRight(1);
}

10

略。

电影

1

本关通过小人的绘制引入圆、线段、矩形的画法,注意存在重叠关系的线段与矩形的绘制顺序。

penColour('#ff0000');
circle(50, 70, 10);
penColour('#000000');
line(20, 70, 40, 50, 5);
line(80, 70, 60, 50, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

2

本关通过小人的移动的手臂引入了时间函数。

penColour('#ff0000');
circle(50, 70, 10);
penColour('#000000');
line(20, 70, 40, 50, 5);
line(80, time(), 60, 50, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

3

本关通过小人的两只移动的手臂强化了时间函数,引入了数学运算。

penColour('#ff0000');
circle(50, 70, 10);
penColour('#000000');
line(20, 100 - time(), 40, 50, 5);
line(80, time(), 60, 50, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

4

本关通过小人的两只移动的手臂与两只移动的腿强化了时间函数与数学运算。

penColour('#ff0000');
circle(50, 70, 10);
penColour('#000000');
line(20, 100 - time(), 40, 50, 5);
line(80, time(), 60, 50, 5);
line(40, 20, time(), 0, 5);
line(60, 20, 100 - time(), 0, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

5

本关通过小人的一只运动情况复杂(一端静止,一端做匀加速直线运动)的手臂强化了数学运算。

penColour('#ff0000');
circle(50, 70, 10);
penColour('#000000');
line(20, 100 - time(), 40, 50, 5);
line(80, Math.pow((time() - 50) / 5, 2), 60, 50, 5);
line(40, 20, time(), 0, 5);
line(60, 20, 100 - time(), 0, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

6

本关通过小人的手强化了数学运算。

penColour('#ff0000');
circle(50, 70, 10);
circle(20, 100 - time(), 5);
circle(80, Math.pow((time() - 50) / 5, 2), 5);
penColour('#000000');
line(20, 100 - time(), 40, 50, 5);
line(80, Math.pow((time() - 50) / 5, 2), 60, 50, 5);
line(40, 20, time(), 0, 5);
line(60, 20, 100 - time(), 0, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

7

本关通过小人的大小变化的头引入了条件语句。

penColour('#ff0000');
if (time() < 50) {
  circle(50, 70, 10);
} else {
  circle(50, 80, 20);
}
circle(20, 100 - time(), 5);
circle(80, Math.pow((time() - 50) / 5, 2), 5);
penColour('#000000');
line(20, 100 - time(), 40, 50, 5);
line(80, Math.pow((time() - 50) / 5, 2), 60, 50, 5);
line(40, 20, time(), 0, 5);
line(60, 20, 100 - time(), 0, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

8

本关通过使腿朝相反方向走一半路强化了条件语句。

penColour('#ff0000');
if (time() < 50) {
  circle(50, 70, 10);
} else {
  circle(50, 80, 20);
}
circle(20, 100 - time(), 5);
circle(80, Math.pow((time() - 50) / 5, 2), 5);
penColour('#000000');
if (time() < 50) {
  line(40, 20, time(), 0, 5);
} else {
  line(60, 20, time(), 0, 5);
}
if (time() < 50) {
  line(60, 20, 100 - time(), 0, 5);
} else {
  line(40, 20, 100 - time(), 0, 5);
}
line(20, 100 - time(), 40, 50, 5);
line(80, Math.pow((time() - 50) / 5, 2), 60, 50, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

9

本关通过背景中逐渐变大的圆强化了时间函数的使用。

penColour('#009900');
circle(50, time() / 2, time() / 2);
penColour('#ff0000');
if (time() < 50) {
  circle(50, 70, 10);
} else {
  circle(50, 80, 20);
}
circle(20, 100 - time(), 5);
circle(80, Math.pow((time() - 50) / 5, 2), 5);
penColour('#000000');
if (time() < 50) {
  line(40, 20, time(), 0, 5);
} else {
  line(60, 20, time(), 0, 5);
}
if (time() < 50) {
  line(60, 20, 100 - time(), 0, 5);
} else {
  line(40, 20, 100 - time(), 0, 5);
}
line(20, 100 - time(), 40, 50, 5);
line(80, Math.pow((time() - 50) / 5, 2), 60, 50, 5);
penColour('#3333ff');
rect(50, 40, 20, 40);

10

略。

音乐

1

本关引入弹奏音符的函数。

function start1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

2

本关通过重复的旋律引入函数的多次调用。

function start1() {
  my_1();
  my_1();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

3

本关通过两种重复的旋律引入多个函数的多次调用。

function start1() {
  my_1();
  my_1();
  my_2();
  my_2();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

4

本关通过三种重复的旋律强化多个函数的多次调用。

function start1() {
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

5

本关通过四种重复的旋律强化多个函数的多次调用。

function start1() {
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
  my_4();
  my_4();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function my_4() {
  play(0.25, 7);
  play(0.25, 4);
  play(0.5, 7);
}

6

本关通过引入小提琴乐器来介绍修改乐器的函数。

function start1() {
  setInstrument('violin');
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
  my_4();
  my_4();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function my_4() {
  play(0.25, 7);
  play(0.25, 4);
  play(0.5, 7);
}

7

本关介绍了延时块“休止”。

function start1() {
  my_0();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

function start2() {
  rest(1);
  rest(1);
  my_0();
}


function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function my_0() {
  setInstrument('violin');
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
  my_4();
  my_4();
}

function my_4() {
  play(0.25, 7);
  play(0.25, 4);
  play(0.5, 7);
}

8

本关引入了多种乐器的同时使用。

function start1() {
  my_0();
  my_0();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

function start2() {
  rest(1);
  rest(1);
  my_0();
  my_0();
}


function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function my_0() {
  setInstrument('violin');
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
  my_4();
  my_4();
}

function my_4() {
  play(0.25, 7);
  play(0.25, 4);
  play(0.5, 7);
}

9

本关强化了多种乐器的同时使用。

function start1() {
  setInstrument('piano');
  my_0();
  my_0();
}


function my_1() {
  play(0.25, 7);
  play(0.25, 8);
  play(0.25, 9);
  play(0.25, 7);
}

function my_3() {
  play(0.125, 11);
  play(0.125, 12);
  play(0.125, 11);
  play(0.125, 10);
  play(0.25, 9);
  play(0.25, 7);
}

function start2() {
  setInstrument('trumpet');
  rest(1);
  rest(1);
  my_0();
  my_0();
}


function my_2() {
  play(0.25, 9);
  play(0.25, 10);
  play(0.5, 11);
}

function start3() {
  setInstrument('banjo');
  rest(1);
  rest(1);
  rest(1);
  rest(1);
  my_0();
  my_0();
}


function start4() {
  setInstrument('violin');
  rest(1);
  rest(1);
  rest(1);
  rest(1);
  rest(1);
  rest(1);
  my_0();
  my_0();
}


function my_0() {
  my_1();
  my_1();
  my_2();
  my_2();
  my_3();
  my_3();
  my_4();
  my_4();
}

function my_4() {
  play(0.25, 7);
  play(0.25, 4);
  play(0.5, 7);
}

10

略。

池塘教学

1

本关引入了加农炮。

cannon(90, 40);

2

本关在上一关的基础上引入代码。

cannon(180, 50);

3

本关通过加农炮的多次使用引入了循环语句。

while (true) {
  cannon(45, 60);
}

4

本关在上一关的基础上引入代码。

while(true)cannon(270,60);

5

本关引入了扫描函数。

while (true) {
  cannon(180, scan(180));
}

6

本关在上一关的基础上引入代码。

while(true)cannon(0,scan(0));

7

本关引入了游泳函数。

swim(315);

8

本关在上一关的基础上结合了循环语句并引入代码。

while(true){
  swim(280);
  cannon(280,70);
}

9

本关引入了停止函数。

while (getX() < 50) {
  swim(0);
}
stop();
while (true) {
  cannon(0, 45);
}

10

本关在上一关的基础上引入代码。

while(getX()<70){
  cannon(45,45);
  swim(45,30);
}
stop();
while(true)cannon(45,45);

池塘

有多种方法。

想要衡量多种方案各自的优劣比较困难,因为该游戏的模拟耗费大量时间。

方法 1

按照顺序扫描,一发现就穷追猛打。

var dist=Infinity,angle=2.5;
function detect(){
  while((dist=scan(angle))==Infinity)angle+=5;
}
while(true){
  detect();
  if(dist==Infinity)break;
  cannon(angle,dist);
  swim(angle);
}

方法 2

本方法参考了 这篇文章。它的主要想法是边转圈边扫描敌人并打击,因此玩家很难被打到。局面一般是 Rook 和 Counter 被快速击败,玩家与 Sniper 在地图上转悠很久。

let swim_angle=225,swim_angle_inc=1;
let cannon_angle=0,cannon_angle_inc,dist=Infinity;
while(true){
  swim_angle+=swim_angle_inc;
  swim(swim_angle,100);
  cannon_angle_inc=dist>70?20:5;
  dist=scan(cannon_angle,cannon_angle_inc);
  if(dist<=70)cannon(cannon_angle,dist);
  else cannon_angle+=cannon_angle_inc;
}