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

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

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

开发

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

Board.java

  1. package com.zetcode;
  2. import com.trolltech.qt.core.QBasicTimer;
  3. import com.trolltech.qt.core.QPoint;
  4. import com.trolltech.qt.core.QTimerEvent;
  5. import com.trolltech.qt.core.Qt;
  6. import com.trolltech.qt.gui.QColor;
  7. import com.trolltech.qt.gui.QFont;
  8. import com.trolltech.qt.gui.QFontMetrics;
  9. import com.trolltech.qt.gui.QFrame;
  10. import com.trolltech.qt.gui.QImage;
  11. import com.trolltech.qt.gui.QKeyEvent;
  12. import com.trolltech.qt.gui.QPaintEvent;
  13. import com.trolltech.qt.gui.QPainter;
  14. public class Board extends QFrame {
  15. private final int WIDTH = 300;
  16. private final int HEIGHT = 300;
  17. private final int DOT_SIZE = 10;
  18. private final int ALL_DOTS = 900;
  19. private final int RAND_POS = 29;
  20. private final int DELAY = 140;
  21. private int x[] = new int[ALL_DOTS];
  22. private int y[] = new int[ALL_DOTS];
  23. private int dots;
  24. private int apple_x;
  25. private int apple_y;
  26. private boolean left = false;
  27. private boolean right = true;
  28. private boolean up = false;
  29. private boolean down = false;
  30. private boolean inGame = true;
  31. private QBasicTimer timer;
  32. private QImage ball;
  33. private QImage apple;
  34. private QImage head;
  35. public Board() {
  36. setStyleSheet("QWidget { background-color: black }");
  37. setFocusPolicy(Qt.FocusPolicy.StrongFocus);
  38. ball = new QImage("dot.png");
  39. apple = new QImage("apple.png");
  40. head = new QImage("head.png");
  41. initGame();
  42. }
  43. private void initGame() {
  44. dots = 3;
  45. for (int z = 0; z < dots; z++) {
  46. x[z] = 50 - z*10;
  47. y[z] = 50;
  48. }
  49. locateApple();
  50. timer = new QBasicTimer();
  51. timer.start(DELAY, this);
  52. }
  53. @Override
  54. public void paintEvent(QPaintEvent event) {
  55. super.paintEvent(event);
  56. QPainter painter = new QPainter();
  57. painter.begin(this);
  58. if (inGame) {
  59. drawObjects(painter);
  60. } else {
  61. gameOver(painter);
  62. }
  63. painter.end();
  64. }
  65. private void drawObjects(QPainter painter) {
  66. painter.drawImage(apple_x, apple_y, apple);
  67. for (int z = 0; z < dots; z++) {
  68. if (z == 0)
  69. painter.drawImage(x[z], y[z], head);
  70. else painter.drawImage(x[z], y[z], ball);
  71. }
  72. }
  73. private void gameOver(QPainter painter) {
  74. String msg = "Game Over";
  75. QFont small = new QFont("Helvetica", 12,
  76. QFont.Weight.Bold.value());
  77. QFontMetrics metr = new QFontMetrics(small);
  78. int textWidth = metr.width(msg);
  79. int h = height();
  80. int w = width();
  81. painter.setPen(QColor.white);
  82. painter.setFont(small);
  83. painter.translate(new QPoint(w/2, h/2));
  84. painter.drawText(-textWidth/2, 0, msg);
  85. }
  86. private void checkApple() {
  87. if ((x[0] == apple_x) && (y[0] == apple_y)) {
  88. dots++;
  89. locateApple();
  90. }
  91. }
  92. private void move() {
  93. for (int z = dots; z > 0; z--) {
  94. x[z] = x[(z - 1)];
  95. y[z] = y[(z - 1)];
  96. }
  97. if (left) {
  98. x[0] -= DOT_SIZE;
  99. }
  100. if (right) {
  101. x[0] += DOT_SIZE;
  102. }
  103. if (up) {
  104. y[0] -= DOT_SIZE;
  105. }
  106. if (down) {
  107. y[0] += DOT_SIZE;
  108. }
  109. }
  110. private void checkCollision() {
  111. for (int z = dots; z > 0; z--) {
  112. if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
  113. inGame = false;
  114. }
  115. }
  116. if (y[0] > HEIGHT) {
  117. inGame = false;
  118. }
  119. if (y[0] < 0) {
  120. inGame = false;
  121. }
  122. if (x[0] > WIDTH) {
  123. inGame = false;
  124. }
  125. if (x[0] < 0) {
  126. inGame = false;
  127. }
  128. }
  129. private void locateApple() {
  130. int r = (int) (Math.random() * RAND_POS);
  131. apple_x = ((r * DOT_SIZE));
  132. r = (int) (Math.random() * RAND_POS);
  133. apple_y = ((r * DOT_SIZE));
  134. }
  135. @Override
  136. protected void timerEvent(QTimerEvent event) {
  137. if (inGame) {
  138. checkApple();
  139. checkCollision();
  140. move();
  141. } else {
  142. timer.stop();
  143. }
  144. repaint();
  145. }
  146. @Override
  147. public void keyPressEvent(QKeyEvent event)
  148. {
  149. int key = event.key();
  150. if (key == Qt.Key.Key_Left.value() && !right) {
  151. left = true;
  152. up = false;
  153. down = false;
  154. }
  155. if ((key == Qt.Key.Key_Right.value()) && !left) {
  156. right = true;
  157. up = false;
  158. down = false;
  159. }
  160. if ((key == Qt.Key.Key_Up.value()) && !down) {
  161. up = true;
  162. right = false;
  163. left = false;
  164. }
  165. if ((key == Qt.Key.Key_Down.value()) && !up) {
  166. down = true;
  167. right = false;
  168. left = false;
  169. }
  170. }
  171. }

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

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()方法初始化变量,加载图像并启动超时功能。

  1. if (inGame) {
  2. drawObjects(painter);
  3. } else {
  4. gameOver(painter);
  5. }

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

  1. private void drawObjects(QPainter painter) {
  2. painter.drawImage(apple_x, apple_y, apple);
  3. for (int z = 0; z < dots; z++) {
  4. if (z == 0)
  5. painter.drawImage(x[z], y[z], head);
  6. else painter.drawImage(x[z], y[z], ball);
  7. }
  8. }

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

  1. private void checkApple() {
  2. if ((x[0] == apple_x) && (y[0] == apple_y)) {
  3. dots++;
  4. locateApple();
  5. }
  6. }

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

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 坐标。

  1. if (inGame) {
  2. checkApple();
  3. checkCollision();
  4. move();
  5. } else {
  6. timer.stop();
  7. }

每 140 毫秒,将调用timerEvent()方法。 如果我们参与了游戏,我们将调用三种构建游戏逻辑的方法。 否则,我们将停止计时器。

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

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

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

Nibbles.java

  1. package com.zetcode;
  2. import com.trolltech.qt.gui.QApplication;
  3. import com.trolltech.qt.gui.QMainWindow;
  4. /**
  5. * ZetCode QtJambi tutorial
  6. *
  7. * In this program, we create
  8. * a Nibbles game clone.
  9. *
  10. * @author jan bodnar
  11. * website zetcode.com
  12. * last modified April 2009
  13. */
  14. public class Nibbles extends QMainWindow {
  15. public Nibbles() {
  16. setWindowTitle("Nibbles");
  17. resize(310, 310);
  18. setCentralWidget(new Board());
  19. move(300, 300);
  20. show();
  21. }
  22. public static void main(String[] args) {
  23. QApplication.initialize(args);
  24. new Nibbles();
  25. QApplication.exec();
  26. }
  27. }

在这个类中,我们设置了贪食蛇游戏。

贪食蛇 - 图1

图:贪食蛇

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