class Snakes::Game

Public Class Methods

new() click to toggle source
# File lib/snakes/game.rb, line 22
def initialize
  @snake_head_icon = "o"  #"*"
  @snake_node_icon = "·"  # "#"
  @food_icon = "+"  # "#"

  @game_speed = 5
  @game_update_time = 1.0 / @game_speed

  @non_block_timeout = 0.001

  @frame = nil
  @board = nil

  @window_width = nil
  @window_height = nil

  @board_width = nil
  @board_height = nil

  @pause = false
  @game_status = :RUN # :RUN ,:PAUSE, :GAME_OVER

  @direction = [:UP, :DOWN, :LEFT, :RIGHT]
  @current_direction = @direction.sample

  self.init_game_board

  @snake = (1..2).map { |i| [(@board_height / 2).to_i + i, (@board_width / 2).to_i] }

  @food = [@board_height / 2 + 2, @board_width / 2 + 2]

  @press = nil
  @score = 0
  @speed = 1
end
run() click to toggle source
# File lib/snakes/game.rb, line 339
def run
  @app = self.new
  @app.run
end
stop() click to toggle source
# File lib/snakes/game.rb, line 344
def stop
  Curses.close_screen
end

Public Instance Methods

_event_listener() click to toggle source
# File lib/snakes/game.rb, line 229
def _event_listener
  while true
    c = @board.getch
    if c
      @press = c
    end

    head_y, head_x = @snake.first.dup

    if c == Curses::KEY_UP || c == "w"
      @current_direction = :UP
    elsif c == Curses::KEY_DOWN || c == "s"
      @current_direction = :DOWN
    elsif c == Curses::KEY_LEFT || c == "a"
      @current_direction = :LEFT
    elsif c == Curses::KEY_RIGHT || c == "d"
      @current_direction = :RIGHT
    elsif c == Curses::KEY_BACKSPACE || c == "p"
      @game_status = @game_status == :PAUSE ? :RUN : :PAUSE
    elsif c == "m"
      self.inc_speed
    elsif c == "n"
      self.dec_speed
    else
      return # 这里其实断掉了。其实接住了外循环,每次读取数据
    end
  end
end
add_step() click to toggle source
# File lib/snakes/game.rb, line 193
def add_step
  head_y, head_x = @snake.first.dup

  if @current_direction == :UP
    head_y -= 1
    if head_y <= 0
      head_y = 1
    end
  elsif @current_direction == :DOWN
    head_y += 1
    if head_y >= @board_height
      head_y = @board_height - 1
    end
  elsif @current_direction == :LEFT
    head_x -= 1
    if head_x <= 0
      head_x = 1
    end
  elsif @current_direction == :RIGHT
    head_x += 1
    if head_x >= @board_width
      head_x = @board_width - 1
    end
  else
    return
  end

  next_point = [head_y, head_x]

  result = self.check_game(next_point)
  if result
    @snake.unshift(next_point)
    @snake.pop
  end
end
check_game(next_point) click to toggle source
# File lib/snakes/game.rb, line 171
def check_game(next_point)
  if @snake.include?(next_point)
    @game_status = :GAME_OVER
    return false
  end

  next_y, next_x = next_point

  if !(next_x > 0 && next_x < @board_width && next_y > 0 && next_y < @board_height)
    @game_status = :GAME_OVER
    return false
  end

  food_y, food_x = @food
  if food_y == next_y && food_x == next_x
    @score += 1
    @snake.unshift(@food)
    self.create_food
  end
  return true
end
check_speed() click to toggle source
# File lib/snakes/game.rb, line 266
def check_speed
  @game_speed = @score % 5 + 5
  self.update_tick
end
create_food() click to toggle source
# File lib/snakes/game.rb, line 94
def create_food
  @food = [rand(2..@board_height - 2), rand(2..@board_width - 2)]

  unless is_safe_food?
    @food = [rand(2..@board_height - 2), rand(2..@board_width - 2)]
  end
end
dec_speed() click to toggle source
# File lib/snakes/game.rb, line 117
def dec_speed
  speed = @game_speed - 1
  if speed <= 5
    @game_speed = 5
  else
    @game_speed = speed
  end

  self.update_tick
end
inc_speed() click to toggle source
# File lib/snakes/game.rb, line 106
def inc_speed
  speed = @game_speed + 1
  if speed >= 30
    @game_speed = 30
  else
    @game_speed = speed
  end

  self.update_tick
end
init_game_board() click to toggle source
# File lib/snakes/game.rb, line 58
def init_game_board

  # Window Frame
  @window_height = Curses.lines
  @window_width = Curses.cols

  @board_height = (@window_height * 0.8).to_i
  @board_width = (@window_width * 0.6).to_i

  @info_board_height = (@window_height * 0.5).to_i
  @info_board_width = (@window_width * 0.3).to_i

  @frame = Curses::Window.new(@window_height, @window_width, 0, 0)

  title = "Snakes"
  @frame.setpos(0, @board_width / 2 - title.length / 2)
  @frame.addstr(title)
  @frame.refresh

  @board = @frame.subwin(@board_height, @board_width, 1, 1)
  @board.keypad = true
  @board.timeout = @non_block_timeout

  @board.box(0, 0)
  @board.refresh

  @info_board = @frame.subwin(
    @info_board_height,
    @info_board_width,
    1,
    @board_width + 1
  )
  @info_board.box(0, 0)
  @info_board.refresh
end
is_safe_food?() click to toggle source
# File lib/snakes/game.rb, line 102
def is_safe_food?
  return !@snake.include?(@food)
end
main_loop() click to toggle source
# File lib/snakes/game.rb, line 271
def main_loop
  loop do
    self.init_game_board
    self.reset_game
    game_result = catch(:end_game) do
      while @game_status != :GAME_OVER
        self._event_listener

        @board.clear
        @info_board.clear
        @board.box(0, 0)
        @info_board.box(0, 0)

        if @game_status == :RUN
          self.add_step
        end
        self.check_speed
        self.render_food
        self.render_snake
        self.render_game_info

        @board.refresh
        @info_board.refresh
        sleep @game_update_time
      end

      throw :end_game, :GAME_OVER
    end

    next_status = catch(:next_action) do
      if game_result == :GAME_OVER
        @board.timeout = -1
        @board.clear
        @board.box(0, 0)
        @board.setpos(@board_height / 2, @board_width / 2 - 10)
        @board.addstr("GAME OVER")
        @board.setpos(@board_height / 2 + 2, @board_width / 2 - 10)
        @board.addstr("Play Again?")
        @board.setpos(@board_height / 2 + 3, @board_width / 2 - 10)
        @board.addstr("Y:Yes/ N: Exit")
        @board.refresh
        @board.timeout = -1

        next_goto = nil
        while next_goto != "y"
          next_goto = @board.getch
          if next_goto == "n"
            exit 0
          elsif next_goto == "y"
            @board.setpos(@board_height / 2 + 4, @board_width / 2 - 10)
            @board.addstr(next_goto)
            @board.refresh

            throw :next_action, :RUN
          end
        end
      end
    end

    @game_status = next_status
  end
end
render_food() click to toggle source
# File lib/snakes/game.rb, line 150
def render_food
  food_y, food_x = @food
  @board.setpos(food_y, food_x)
  @board.addch(@food_icon)
end
render_game_info() click to toggle source
# File lib/snakes/game.rb, line 128
def render_game_info
  # Press Key
  @info_board.setpos(1, 1)
  @info_board.addstr("[Press]: #{@current_direction.to_s}")

  # score
  @info_board.setpos(3, 1)
  @info_board.addstr("[Score]: #{@score}")

  # speed
  @info_board.setpos(5, 1)
  @info_board.addstr("[Speed]: #{@game_speed}")

  # tick time
  @info_board.setpos(7, 1)
  @info_board.addstr("[Tick]: #{@game_update_time}")

  # status
  @info_board.setpos(9, 1)
  @info_board.addstr("[Status]: #{@game_status}")
end
render_snake() click to toggle source
# File lib/snakes/game.rb, line 156
def render_snake
  snake_path = @snake.dup

  head_y, head_x = snake_path.shift

  @board.setpos(head_y, head_x)
  @board.addch(@snake_head_icon)

  snake_path.each do |node_pos|
    node_y, node_x = node_pos
    @board.setpos(node_y, node_x)
    @board.addstr(@snake_node_icon)
  end
end
reset_game() click to toggle source
# File lib/snakes/game.rb, line 258
def reset_game
  self.initialize
end
run() click to toggle source
# File lib/snakes/game.rb, line 334
def run
  self.main_loop
end
update_tick() click to toggle source
# File lib/snakes/game.rb, line 262
def update_tick
  @game_update_time = (1.0 / @game_speed).round(2)
end