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

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

贪食蛇

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

开发

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

board.java

  1. package com.zetcode;
  2. import java.io.FileNotFoundException;
  3. import java.util.Timer;
  4. import java.util.TimerTask;
  5. import org.freedesktop.cairo.Context;
  6. import org.gnome.gdk.Color;
  7. import org.gnome.gdk.EventExpose;
  8. import org.gnome.gdk.EventKey;
  9. import org.gnome.gdk.Keyval;
  10. import org.gnome.gdk.ModifierType;
  11. import org.gnome.gdk.Pixbuf;
  12. import org.gnome.gtk.DrawingArea;
  13. import org.gnome.gtk.Justification;
  14. import org.gnome.gtk.Label;
  15. import org.gnome.gtk.StateType;
  16. import org.gnome.gtk.Widget;
  17. public class Board extends DrawingArea implements Widget.ExposeEvent {
  18. private final int WIDTH = 300;
  19. private final int HEIGHT = 300;
  20. private final int DOT_SIZE = 10;
  21. private final int ALL_DOTS = 900;
  22. private final int RAND_POS = 29;
  23. private final int DELAY = 140;
  24. private final int PERIOD = 80;
  25. private int x[] = new int[ALL_DOTS];
  26. private int y[] = new int[ALL_DOTS];
  27. private int dots;
  28. private int apple_x;
  29. private int apple_y;
  30. private boolean left = false;
  31. private boolean right = true;
  32. private boolean up = false;
  33. private boolean down = false;
  34. private boolean inGame = true;
  35. private Timer timer;
  36. private Pixbuf dot;
  37. private Pixbuf apple;
  38. private Pixbuf head;
  39. private Label statusbar;
  40. public Board(Label statusbar) {
  41. this.statusbar = statusbar;
  42. connect(new SnakeKeyListener());
  43. modifyBackground(StateType.NORMAL, Color.BLACK);
  44. try {
  45. dot = new Pixbuf("dot.png");
  46. apple = new Pixbuf("apple.png");
  47. head = new Pixbuf("head.png");
  48. } catch (FileNotFoundException e) {
  49. e.printStackTrace();
  50. }
  51. connect(this);
  52. setCanFocus(true);
  53. initGame();
  54. }
  55. public Timer getTimer() { return timer; }
  56. public void initGame() {
  57. dots = 3;
  58. for (int z = 0; z < dots; z++) {
  59. x[z] = 50 - z * 10;
  60. y[z] = 50;
  61. }
  62. locateApple();
  63. timer = new Timer();
  64. timer.scheduleAtFixedRate(new ScheduleTask(), DELAY, PERIOD);
  65. }
  66. public void drawObjects(Context cr) {
  67. if (inGame) {
  68. cr.setSource(apple, apple_x, apple_y);
  69. cr.paint();
  70. for (int z = 0; z < dots; z++) {
  71. if (z == 0) {
  72. cr.setSource(head, x[z], y[z]);
  73. cr.paint();
  74. } else {
  75. cr.setSource(dot, x[z], y[z]);
  76. cr.paint();
  77. }
  78. }
  79. } else {
  80. gameOver();
  81. }
  82. }
  83. public void gameOver() {
  84. timer.cancel();
  85. statusbar.setJustify(Justification.LEFT);
  86. statusbar.setAlignment(0f, 0.5f);
  87. statusbar.setLabel("Game Over");
  88. }
  89. public void checkApple() {
  90. if ((x[0] == apple_x) && (y[0] == apple_y)) {
  91. dots++;
  92. locateApple();
  93. }
  94. }
  95. public void move() {
  96. for (int z = dots; z > 0; z--) {
  97. x[z] = x[(z - 1)];
  98. y[z] = y[(z - 1)];
  99. }
  100. if (left) {
  101. x[0] -= DOT_SIZE;
  102. }
  103. if (right) {
  104. x[0] += DOT_SIZE;
  105. }
  106. if (up) {
  107. y[0] -= DOT_SIZE;
  108. }
  109. if (down) {
  110. y[0] += DOT_SIZE;
  111. }
  112. }
  113. public void checkCollision() {
  114. for (int z = dots; z > 0; z--) {
  115. if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
  116. inGame = false;
  117. }
  118. }
  119. if (y[0] > HEIGHT) {
  120. inGame = false;
  121. }
  122. if (y[0] < 0) {
  123. inGame = false;
  124. }
  125. if (x[0] > WIDTH) {
  126. inGame = false;
  127. }
  128. if (x[0] < 0) {
  129. inGame = false;
  130. }
  131. }
  132. public void locateApple() {
  133. int r = (int) (Math.random() * RAND_POS);
  134. apple_x = ((r * DOT_SIZE));
  135. r = (int) (Math.random() * RAND_POS);
  136. apple_y = ((r * DOT_SIZE));
  137. }
  138. public boolean onExposeEvent(Widget widget, EventExpose eventExpose) {
  139. Context cr = new Context(widget.getWindow());
  140. drawObjects(cr);
  141. return false;
  142. }
  143. class ScheduleTask extends TimerTask {
  144. public void run() {
  145. if (inGame) {
  146. checkApple();
  147. checkCollision();
  148. move();
  149. }
  150. queueDraw();
  151. }
  152. }
  153. class SnakeKeyListener implements Widget.KeyPressEvent {
  154. public boolean onKeyPressEvent(Widget widget, EventKey eventKey) {
  155. final Keyval key;
  156. final ModifierType mod;
  157. key = eventKey.getKeyval();
  158. mod = eventKey.getState();
  159. if ((key == key.Left) && (!right)) {
  160. left = true;
  161. up = false;
  162. down = false;
  163. }
  164. if ((key == key.Right) && (!left)) {
  165. right = true;
  166. up = false;
  167. down = false;
  168. }
  169. if ((key == key.Up) && (!down)) {
  170. up = true;
  171. right = false;
  172. left = false;
  173. }
  174. if ((key == key.Down) && (!up)) {
  175. down = true;
  176. right = false;
  177. left = false;
  178. }
  179. return false;
  180. }
  181. }
  182. }

首先,我们将定义一些在游戏中使用的全局变量。

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

  1. private int x[] = new int[ALL_DOTS];
  2. private int y[] = new int[ALL_DOTS];

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

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

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

  1. for (int z = dots; z > 0; z--) {
  2. x[z] = x[(z - 1)];
  3. y[z] = y[(z - 1)];
  4. }

该代码将关节向上移动。

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

将头向左移动。

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

  1. for (int z = dots; z > 0; z--) {
  2. if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
  3. inGame = false;
  4. }
  5. }

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

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

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

locateApple()方法在表格上随机定位一个苹果。

  1. int r = (int) (Math.random() * RAND_POS);

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

  1. apple_x = ((r * DOT_SIZE));
  2. ...
  3. apple_y = ((r * DOT_SIZE));

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

Board类的onKeyPressEvent()方法中,我们确定玩家按下了哪些键。

  1. if ((key == key.Left) && (!right)) {
  2. left = true;
  3. up = false;
  4. down = false;
  5. }

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

nibbles.java

  1. package com.zetcode;
  2. import java.util.Timer;
  3. import org.gnome.gdk.Event;
  4. import org.gnome.gtk.Gtk;
  5. import org.gnome.gtk.Label;
  6. import org.gnome.gtk.VBox;
  7. import org.gnome.gtk.Widget;
  8. import org.gnome.gtk.Window;
  9. import org.gnome.gtk.WindowPosition;
  10. /**
  11. * ZetCode Java Gnome tutorial
  12. *
  13. * This program creates a Nibbles game clone.
  14. *
  15. * @author jan bodnar
  16. * website zetcode.com
  17. * last modified March 2009
  18. */
  19. public class GNibbles extends Window {
  20. Board board;
  21. Label statusbar;
  22. public GNibbles() {
  23. setTitle("Nibbles");
  24. initUI();
  25. setDefaultSize(320, 320);
  26. setPosition(WindowPosition.CENTER);
  27. showAll();
  28. }
  29. public void initUI() {
  30. VBox vbox = new VBox(false, 0);
  31. statusbar = new Label("");
  32. board = new Board(statusbar);
  33. vbox.packStart(board);
  34. vbox.packStart(statusbar, false, false, 0);
  35. add(vbox);
  36. connect(new Window.DeleteEvent() {
  37. public boolean onDeleteEvent(Widget source, Event event) {
  38. Timer timer = board.getTimer();
  39. timer.cancel();
  40. Gtk.mainQuit();
  41. return false;
  42. }
  43. });
  44. }
  45. public static void main(String[] args) {
  46. Gtk.init(args);
  47. new GNibbles();
  48. Gtk.main();
  49. }
  50. }

在这个类中,我们设置了贪食蛇游戏。 注意,我们从板上获得了计时器对象。 这是执行干净的退出。

Java Gnome 中的贪食蛇 - 图1

图:贪食蛇

这是使用 Java Gnome 编程库编程的贪食蛇电脑游戏。