原文: http://zetcode.com/gui/phpgtktutorial/nibbles/

在 PHP GTK 编程教程的这一部分中,我们将创建一个贪食蛇游戏克隆。

贪食蛇是较旧的经典视频游戏。 它最初是在 70 年代后期创建的。 后来它被带到 PC 上。 在这个游戏中,玩家控制蛇。 目的是尽可能多地吃苹果。 蛇每次吃一个苹果,它的身体就会长大。 蛇必须避开墙壁和自己的身体。

开发

蛇的每个关节的大小为 10px。 蛇由光标键控制。 最初,蛇具有三个关节。 游戏立即开始。 游戏结束后,我们在窗口中心显示"Game Over"消息。

该代码分为两个文件。 board.phpnibbles.php

  1. <?php
  2. // board.php
  3. define("WIDTH", 300);
  4. define("HEIGHT", 270);
  5. define("DOT_SIZE", 10);
  6. define("ALL_DOTS", WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE));
  7. define("RAND_POS", 26);
  8. class Board extends GtkDrawingArea {
  9. public function __construct() {
  10. parent::__construct();
  11. $this->modify_bg(Gtk::STATE_NORMAL, new GdkColor(6400, 6400, 6440));
  12. $this->connect('expose_event', array($this, 'on_expose'));
  13. $this->init_game();
  14. }
  15. public function init_game() {
  16. $this->x = array_fill(0, ALL_DOTS, 0);
  17. $this->y = array_fill(0, ALL_DOTS, 0);
  18. $this->left = false;
  19. $this->right = true;
  20. $this->up = false;
  21. $this->down = false;
  22. $this->inGame = true;
  23. $this->dots = 3;
  24. for ($i=0; $i<=$this->dots; $i++) {
  25. $this->x[$i] = 50 - $i * 10;
  26. $this->y[$i] = 50;
  27. }
  28. try {
  29. $this->dot = CairoImageSurface::createFromPng("dot.png");
  30. $this->head = CairoImageSurface::createFromPng("head.png");
  31. $this->apple = CairoImageSurface::createFromPng("apple.png");
  32. } catch( Exception $e) {
  33. echo $e->getMessage();
  34. echo "cannot load images";
  35. exit;
  36. }
  37. $this->locate_apple();
  38. $this->set_can_focus(true);
  39. Gtk::timeout_add(100, array($this, 'on_timer'));
  40. }
  41. public function on_timer() {
  42. if ($this->inGame) {
  43. $this->check_apple();
  44. $this->check_collision();
  45. $this->move();
  46. $this->queue_draw();
  47. return true;
  48. } else {
  49. return false;
  50. }
  51. }
  52. public function on_expose() {
  53. $cr = $this->window->cairo_create();
  54. if ($this->inGame) {
  55. $this->draw_objects($cr);
  56. } else {
  57. $this->game_over($cr);
  58. }
  59. }
  60. public function draw_objects($cr) {
  61. $cr->SetSourceRgb(0, 0, 0);
  62. $cr->paint();
  63. $cr->setSourceSurface($this->apple,
  64. $this->apple_x, $this->apple_y);
  65. $cr->paint();
  66. for ($z=0; $z<=$this->dots; $z++) {
  67. if ($z == 0) {
  68. $cr->setSourceSurface($this->head,
  69. $this->x[$z], $this->y[$z]);
  70. $cr->paint();
  71. } else {
  72. $cr->setSourceSurface($this->dot,
  73. $this->x[$z], $this->y[$z]);
  74. $cr->paint();
  75. }
  76. }
  77. }
  78. public function game_over($cr) {
  79. $c_x = $this->get_allocation()->width/2;
  80. $c_y = $this->get_allocation()->height/2;
  81. $cr->SetFontSize(15);
  82. $cr->SetSourceRgb(65535, 65535, 65535);
  83. $te = $cr->TextExtents("Game Over");
  84. $cr->MoveTo($c_x - $te['width']/2, $c_y);
  85. $cr->ShowText("Game Over");
  86. }
  87. public function check_apple() {
  88. if ($this->x[0] == $this->apple_x
  89. and $this->y[0] == $this->apple_y) {
  90. $this->dots = $this->dots + 1;
  91. $this->locate_apple();
  92. }
  93. }
  94. public function move() {
  95. $z = $this->dots;
  96. while ($z > 0) {
  97. $this->x[$z] = $this->x[($z - 1)];
  98. $this->y[$z] = $this->y[($z - 1)];
  99. $z--;
  100. }
  101. if ($this->left) {
  102. $this->x[0] -= DOT_SIZE;
  103. }
  104. if ($this->right) {
  105. $this->x[0] += DOT_SIZE;
  106. }
  107. if ($this->up) {
  108. $this->y[0] -= DOT_SIZE;
  109. }
  110. if ($this->down) {
  111. $this->y[0] += DOT_SIZE;
  112. }
  113. }
  114. public function check_collision() {
  115. $z = $this->dots;
  116. while ($z > 0) {
  117. if ($z > 4 and $this->x[0] == $this->x[$z]
  118. and $this->y[0] == $this->y[$z]) {
  119. $this->inGame = false;
  120. }
  121. $z--;
  122. }
  123. if ($this->y[0] > HEIGHT - DOT_SIZE) {
  124. $this->inGame = false;
  125. }
  126. if ($this->y[0] < 0) {
  127. $this->inGame = false;
  128. }
  129. if ($this->x[0] > WIDTH - DOT_SIZE) {
  130. $this->inGame = false;
  131. }
  132. if ($this->x[0] < 0) {
  133. $this->inGame = false;
  134. }
  135. }
  136. public function locate_apple() {
  137. $r = rand(0, RAND_POS);
  138. $this->apple_x = $r * DOT_SIZE;
  139. $r = rand(0, RAND_POS);
  140. $this->apple_y = $r * DOT_SIZE;
  141. }
  142. public function on_key_down($event) {
  143. $key = $event->keyval;
  144. if ($key == Gdk::KEY_Left and !$this->right) {
  145. $this->left = true;
  146. $this->up = false;
  147. $this->down = false;
  148. }
  149. if ($key == Gdk::KEY_Right and !$this->left) {
  150. $this->right = true;
  151. $this->up = false;
  152. $this->down = false;
  153. }
  154. if ($key == Gdk::KEY_Up and !$this->down) {
  155. $this->up = true;
  156. $this->right = false;
  157. $this->left = false;
  158. }
  159. if ($key == Gdk::KEY_Down and !$this->up) {
  160. $this->down = true;
  161. $this->right = false;
  162. $this->left = false;
  163. }
  164. }
  165. }
  166. ?>

这是 board.php 文件。

  1. define("WIDTH", 300);
  2. define("HEIGHT", 270);
  3. define("DOT_SIZE", 10);
  4. define("ALL_DOTS", WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE));
  5. define("RAND_POS", 26);

WIDTHHEIGHT常数确定电路板的大小。 DOT_SIZE是苹果的大小和蛇的点。 ALL_DOTS常数定义了板上可能的最大点数。 RAND_POS常数用于计算苹果的随机位置。

  1. $this->x = array_fill(0, ALL_DOTS, 0);
  2. $this->y = array_fill(0, ALL_DOTS, 0);

这两个数组存储蛇的所有可能关节的 x,y 坐标。

init_game()方法初始化变量,加载图像并启动超时功能。

  1. if ($this->inGame) {
  2. $this->check_apple();
  3. $this->check_collision();
  4. $this->move();
  5. $this->queue_draw();
  6. return true;
  7. } else {
  8. return false;
  9. }

每 140 毫秒,将调用on_timer()方法。 如果我们参与了游戏,我们将调用三种构建游戏逻辑的方法。 queue_draw()方法强制重新绘制窗口小部件。 这将反映游戏板上的变化。 否则,我们返回false,它将停止计时器事件。

  1. $cr = $this->window->cairo_create();
  2. if ($this->inGame) {
  3. $this->draw_objects($cr);
  4. } else {
  5. $this->game_over($cr);
  6. }

on_expose()方法内部,我们检查$this->inGame变量。 如果为真,则绘制对象。 苹果和蛇的关节。 否则,我们显示"Game Over"文本。

  1. public function draw_objects($cr) {
  2. $cr->SetSourceRgb(0, 0, 0);
  3. $cr->paint();
  4. $cr->setSourceSurface($this->apple,
  5. $this->apple_x, $this->apple_y);
  6. $cr->paint();
  7. for ($z=0; $z<=$this->dots; $z++) {
  8. if ($z == 0) {
  9. $cr->setSourceSurface($this->head,
  10. $this->x[$z], $this->y[$z]);
  11. $cr->paint();
  12. } else {
  13. $cr->setSourceSurface($this->dot,
  14. $this->x[$z], $this->y[$z]);
  15. $cr->paint();
  16. }
  17. }
  18. }

draw_objects()方法绘制苹果和蛇的关节。 蛇的第一个关节是其头部,用红色圆圈表示。

如果游戏结束,则调用game_over()方法。 此方法在窗口中心显示"Game Over"

  1. $c_x = $this->get_allocation()->width/2;
  2. $c_y = $this->get_allocation()->height/2;

在这里,我们获得窗口的中心点。

  1. $cr->SetFontSize(15);
  2. $cr->SetSourceRgb(65535, 65535, 65535);

我们设置文本的字体大小和颜色。 背景为黑色,因此字体将为白色。

  1. $te = $cr->TextExtents("Game Over");

我们得到字符串的文本范围。 为了将文本放置在窗口的中央,这是必需的。

  1. $cr->MoveTo($c_x - $te['width']/2, $c_y);
  2. $cr->ShowText("Game Over");

我们移到中心并显示文本。

  1. public function check_apple() {
  2. if ($this->x[0] == $this->apple_x
  3. and $this->y[0] == $this->apple_y) {
  4. $this->dots = $this->dots + 1;
  5. $this->locate_apple();
  6. }
  7. }

check_apple()方法检查蛇是否击中了苹果对象。 如果是这样,我们添加另一个蛇形关节并调用locate_apple()方法,该方法将随机放置一个新的Apple对象。

move()方法中,我们有游戏的关键算法。 要了解它,请看一下蛇是如何运动的。 您控制蛇的头。 您可以使用光标键更改其方向。 其余关节在链上向上移动一个位置。 第二关节移动到第一个关节的位置,第三关节移动到第二个关节的位置,依此类推。

  1. while ($z > 0) {
  2. $this->x[$z] = $this->x[($z - 1)];
  3. $this->y[$z] = $this->y[($z - 1)];
  4. $z--;
  5. }

该代码将关节向上移动。

  1. if ($this->left) {
  2. $this->x[0] -= DOT_SIZE;
  3. }

将头向左移动。

check_collision()方法中,我们确定蛇是否击中了自己或撞墙之一。

  1. while ($z > 0) {
  2. if ($z > 4 and $this->x[0] == $this->x[$z]
  3. and $this->y[0] == $this->y[$z]) {
  4. $this->inGame = false;
  5. }
  6. $z--;
  7. }

如果蛇用头撞到关节之一,我们就结束游戏。

  1. if ($this->y[0] > HEIGHT - DOT_SIZE) {
  2. $this->inGame = false;
  3. }

如果蛇击中了棋盘的底部,我们就结束了游戏。

locate_apple()方法在板上随机放置一个苹果。

  1. $r = rand(0, RAND_POS);

我们得到一个从 0 到RAND_POS-1的随机数。

  1. $this->apple_x = $r * DOT_SIZE;
  2. ...
  3. $this->apple_y = $r * DOT_SIZE;

这些行设置了apple对象的 x,y 坐标。

Board类的on_key_down()方法中,我们确定按下的键。

  1. if ($key == Gdk::KEY_Left and !$this->right) {
  2. $this->left = true;
  3. $this->up = false;
  4. $this->down = false;
  5. }

如果单击左光标键,则将$this->left变量设置为 true。 在move()方法中使用此变量来更改蛇对象的坐标。 还要注意,当蛇向右行驶时,我们不能立即向左转。

  1. <?php
  2. /*
  3. ZetCode PHP GTK tutorial
  4. In this program, we create a Nibbles
  5. game clone.
  6. author: Jan Bodnar
  7. website: www.zetcode.com
  8. last modified: September 2011
  9. */
  10. include 'board.php';
  11. class Example extends GtkWindow {
  12. public function __construct() {
  13. parent::__construct();
  14. $this->init_ui();
  15. }
  16. private function init_ui() {
  17. $this->set_title('Nibbles');
  18. $this->connect_simple('destroy', array('gtk', 'main_quit'));
  19. $this->board = new Board();
  20. $this->board->connect('key-press-event', array($this, 'on_key_down'));
  21. $this->add($this->board);
  22. $this->set_default_size(300, 270);
  23. $this->set_position(GTK::WIN_POS_CENTER);
  24. $this->show_all();
  25. }
  26. public function on_key_down($sender, $event) {
  27. $key = $event->keyval;
  28. $this->board->on_key_down($event);
  29. }
  30. }
  31. new Example();
  32. Gtk::main();
  33. ?>

这是nibbles.php文件。 在此文件中,我们设置了贪食蛇游戏。

  1. public function on_key_down($sender, $event) {
  2. $key = $event->keyval;
  3. $this->board->on_key_down($event);
  4. }

在这个类中,我们捕获按键事件。 并将处理委托给板类的on_key_down()方法。

贪食蛇 - 图1

图:贪食蛇

这是用 GTK 库和 PHP 编程语言编程的贪食蛇电脑游戏。