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

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

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

开发

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

board.rb

  1. WIDTH = 300
  2. HEIGHT = 270
  3. DOT_SIZE = 10
  4. ALL_DOTS = WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE)
  5. RAND_POS = 26
  6. DELAY = 100
  7. $x = [0] * ALL_DOTS
  8. $y = [0] * ALL_DOTS
  9. class Board < Gtk::DrawingArea
  10. def initialize
  11. super
  12. override_background_color :normal, Gdk::RGBA.new(0, 0, 0, 1)
  13. signal_connect "draw" do
  14. on_draw
  15. end
  16. init_game
  17. end
  18. def on_timer
  19. if @inGame
  20. check_apple
  21. check_collision
  22. move
  23. queue_draw
  24. return true
  25. else
  26. return false
  27. end
  28. end
  29. def init_game
  30. @left = false
  31. @right = true
  32. @up = false
  33. @down = false
  34. @inGame = true
  35. @dots = 3
  36. for i in 0..@dots
  37. $x[i] = 50 - i * 10
  38. $y[i] = 50
  39. end
  40. begin
  41. @dot = Cairo::ImageSurface.from_png "dot.png"
  42. @head = Cairo::ImageSurface.from_png "head.png"
  43. @apple = Cairo::ImageSurface.from_png "apple.png"
  44. rescue Exception => e
  45. puts "cannot load images"
  46. exit
  47. end
  48. locate_apple
  49. GLib::Timeout.add(DELAY) { on_timer }
  50. end
  51. def on_draw
  52. cr = window.create_cairo_context
  53. if @inGame
  54. draw_objects cr
  55. else
  56. game_over cr
  57. end
  58. end
  59. def draw_objects cr
  60. cr.set_source_rgb 0, 0, 0
  61. cr.paint
  62. cr.set_source @apple, @apple_x, @apple_y
  63. cr.paint
  64. for z in 0..@dots
  65. if z == 0
  66. cr.set_source @head, $x[z], $y[z]
  67. cr.paint
  68. else
  69. cr.set_source @dot, $x[z], $y[z]
  70. cr.paint
  71. end
  72. end
  73. end
  74. def game_over cr
  75. w = allocation.width / 2
  76. h = allocation.height / 2
  77. cr.set_font_size 15
  78. te = cr.text_extents "Game Over"
  79. cr.set_source_rgb 65535, 65535, 65535
  80. cr.move_to w - te.width/2, h
  81. cr.show_text "Game Over"
  82. end
  83. def check_apple
  84. if $x[0] == @apple_x and $y[0] == @apple_y
  85. @dots = @dots + 1
  86. locate_apple
  87. end
  88. end
  89. def move
  90. z = @dots
  91. while z > 0
  92. $x[z] = $x[(z - 1)]
  93. $y[z] = $y[(z - 1)]
  94. z = z - 1
  95. end
  96. if @left
  97. $x[0] -= DOT_SIZE
  98. end
  99. if @right
  100. $x[0] += DOT_SIZE
  101. end
  102. if @up
  103. $y[0] -= DOT_SIZE
  104. end
  105. if @down
  106. $y[0] += DOT_SIZE
  107. end
  108. end
  109. def check_collision
  110. z = @dots
  111. while z > 0
  112. if z > 4 and $x[0] == $x[z] and $y[0] == $y[z]
  113. @inGame = false
  114. end
  115. z = z - 1
  116. end
  117. if $y[0] > HEIGHT - DOT_SIZE
  118. @inGame = false
  119. end
  120. if $y[0] < 0
  121. @inGame = false
  122. end
  123. if $x[0] > WIDTH - DOT_SIZE
  124. @inGame = false
  125. end
  126. if $x[0] < 0
  127. @inGame = false
  128. end
  129. end
  130. def locate_apple
  131. r = rand RAND_POS
  132. @apple_x = r * DOT_SIZE
  133. r = rand RAND_POS
  134. @apple_y = r * DOT_SIZE
  135. end
  136. def on_key_down event
  137. key = event.keyval
  138. if key == Gdk::Keyval::GDK_KEY_Left and not @right
  139. @left = true
  140. @up = false
  141. @down = false
  142. end
  143. if key == Gdk::Keyval::GDK_KEY_Right and not @left
  144. @right = true
  145. @up = false
  146. @down = false
  147. end
  148. if key == Gdk::Keyval::GDK_KEY_Up and not @down
  149. @up = true
  150. @right = false
  151. @left = false
  152. end
  153. if key == Gdk::Keyval::GDK_KEY_Down and not @up
  154. @down = true
  155. @right = false
  156. @left = false
  157. end
  158. end
  159. end

首先,我们将定义一些在游戏中使用的全局变量。 WIDTHHEIGHT常数确定Board的大小。 DOT_SIZE是苹果的大小和蛇的点。 ALL_DOTS常数定义Board上可能的最大点数。 RAND_POS常数用于计算苹果的随机位置。 DELAY常数确定游戏的速度。

  1. $x = [0] * ALL_DOTS
  2. $y = [0] * ALL_DOTS

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

init_game方法初始化游戏。

  1. @left = false
  2. @right = true
  3. @up = false
  4. @down = false
  5. @inGame = true
  6. @dots = 3

我们初始化我们在游戏中使用的变量。

  1. for i in 0..@dots
  2. $x[i] = 50 - i * 10
  3. $y[i] = 50
  4. end

我们给蛇关节初始坐标。 它总是从同一位置开始。

  1. begin
  2. @dot = Cairo::ImageSurface.from_png "dot.png"
  3. @head = Cairo::ImageSurface.from_png "head.png"
  4. @apple = Cairo::ImageSurface.from_png "apple.png"
  5. rescue Exception => e
  6. puts "cannot load images"
  7. exit
  8. end

加载了必要的图像。

  1. locate_apple

苹果进入初始随机位置。

  1. GLib::Timeout.add(DELAY) { on_timer }

GLib::Timeout.add方法将on_timer方法设置为每DELAY毫秒调用一次。

  1. if @inGame
  2. draw_objects cr
  3. else
  4. game_over cr
  5. end

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

  1. def draw_objects cr
  2. cr.set_source_rgb 0, 0, 0
  3. cr.paint
  4. cr.set_source @apple, @apple_x, @apple_y
  5. cr.paint
  6. for z in 0..@dots
  7. if z == 0
  8. cr.set_source @head, $x[z], $y[z]
  9. cr.paint
  10. else
  11. cr.set_source @dot, $x[z], $y[z]
  12. cr.paint
  13. end
  14. end
  15. end

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

  1. def check_apple
  2. if $x[0] == @apple_x and $y[0] == @apple_y
  3. @dots = @dots + 1
  4. locate_apple
  5. end
  6. end

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

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

  1. while z > 0
  2. $x[z] = $x[(z - 1)]
  3. $y[z] = $y[(z - 1)]
  4. z = z - 1
  5. end

该代码将关节向上移动。

  1. if @left
  2. $x[0] -= DOT_SIZE
  3. end

如果向左移动,则磁头向左移动。

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

  1. while z > 0
  2. if z > 4 and $x[0] == $x[z] and $y[0] == $y[z]
  3. @inGame = false
  4. end
  5. z = z - 1
  6. end

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

  1. if $y[0] > HEIGHT - DOT_SIZE
  2. @inGame = false
  3. end

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

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

  1. r = rand 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. check_apple
  3. check_collision
  4. move
  5. queue_draw
  6. return true
  7. else
  8. return false
  9. end

DELAY ms 会调用一次on_timer方法。 如果我们参与了游戏,我们将调用三种构建游戏逻辑的方法。 否则,我们返回false,它将停止计时器事件。

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

  1. if key == Gdk::Keyval::GDK_KEY_Left and not @right
  2. @left = true
  3. @up = false
  4. @down = false
  5. end

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

nibbles.rb

  1. #!/usr/bin/ruby
  2. '''
  3. ZetCode Ruby GTK tutorial
  4. This is a simple Nibbles game
  5. clone.
  6. Author: Jan Bodnar
  7. Website: www.zetcode.com
  8. Last modified: May 2014
  9. '''
  10. require 'gtk3'
  11. require './board'
  12. class RubyApp < Gtk::Window
  13. def initialize
  14. super
  15. set_title "Nibbles"
  16. signal_connect "destroy" do
  17. Gtk.main_quit
  18. end
  19. @board = Board.new
  20. signal_connect "key-press-event" do |w, e|
  21. on_key_down w, e
  22. end
  23. add @board
  24. set_default_size WIDTH, HEIGHT
  25. set_window_position :center
  26. show_all
  27. end
  28. def on_key_down widget, event
  29. key = event.keyval
  30. @board.on_key_down event
  31. end
  32. end
  33. Gtk.init
  34. window = RubyApp.new
  35. Gtk.main

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

  1. def on_key_down widget, event
  2. key = event.keyval
  3. @board.on_key_down event
  4. end

我们捕获按键事件,并将处理委托给电路板类的on_key_down方法。

Ruby GTK 中的贪食蛇 - 图1

图:贪食蛇

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