首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >红宝石中的Gomoku游戏

红宝石中的Gomoku游戏
EN

Code Review用户
提问于 2019-01-03 05:11:16
回答 1查看 64关注 0票数 0

我从上一篇文章红宝石中的生命游戏中学到了很多关于红宝石的知识,所以我下一次尝试的是红宝石,那就是Gomoku。

它仍然是一个控制台游戏,我使用三个类Game Grid Cell,其结构类似于@Johan Wentholt的S示例代码,但游戏规则当然是不同的。

游戏类

游戏类来运行游戏

代码语言:javascript
复制
class Game
  def initialize(width=15)
    @width = width
    @users = ["A", "B"]
    @user_piece = {"A"=>"+", "B"=>"*"}
    @user_index = 0
  end

  def reset
    @grid = Grid.new(@width)
  end

  def start
    reset
    puts @grid
    until @grid.gameover
      user = @users[@user_index]
      print "Now for user<#{user}>, Enter your move(split by space)[0-#{@width-1}]:"
      begin
        move = gets.chomp.split.map(&:to_i)
        if not @grid.update?(move, @user_piece[user])
          puts "Invalid move!!!"
        else
          switch_user
          puts @grid
        end
      rescue
        puts "Invalid move!!!"
      end
    end
    show_result
  end

  def switch_user
    @user_index = (@user_index + 1) % @users.length
  end

  def show_result
    if not @grid.draw
      switch_user
      print "Game Over the Winner is <#{@users[@user_index]}>"
    else
      print "Game Over Draw"
    end
  end
end

网格类

网格显示是gomoku游戏板,请继续更新棋盘,以及游戏是否结束。

代码语言:javascript
复制
class Grid
  attr_reader :gameover, :draw
  def initialize(width)
    @cells = Array.new(width * width).map { Cell.new }
    @grid = @cells.each_slice(width).to_a
    @gameover = false
    @draw = false
    @width = width
    assign_cell_neighbours
  end

  def update?(move, piece)
    x, y = move
    if x.negative? || x >= @width || y.negative? || y >= @width
      return false
    end
    cell = @grid.dig(x,y)
    if not cell.place?(piece)
      return false
    end

    if not full?
      @gameover = cell.win?
    else
      @gameover = true
      @draw = true
    end
    return true
  end

  def full?
    @cells.none?{|cell| cell.empty?}
  end

  def to_s
    @grid.map {|row| row.map(&:to_s).join}.join("\n")
  end

  private

  def assign_cell_neighbours
    @grid.each_with_index do |row, row_index|
      row.each_with_index do |cell, column_index|
        Cell::RELATIVE_NEIGHBOUR_COORDINATES.each do |dir, rel_coord|
          (rel_row_index, rel_column_index) = rel_coord
          neighbour_row_index = row_index
          neighbour_column_index = column_index
          neighbours = []
          loop do
            neighbour_row_index += rel_row_index
            neighbour_column_index += rel_column_index

            break if neighbour_row_index.negative? ||
                     neighbour_column_index.negative? ||
                     neighbour_row_index >= @width ||
                     neighbour_column_index >= @width
            neighbours << @grid.dig(neighbour_row_index, neighbour_column_index)
          end
          cell[dir] = neighbours
        end
      end
    end
  end
end

信元类

单元类保持单元格状态,并判断当前对单元格的更新是否会导致胜利。

代码语言:javascript
复制
class Cell
  RELATIVE_NEIGHBOUR_COORDINATES = {
    north: [-1, 0].freeze, north_east: [-1, 1].freeze,
    east:  [0, 1].freeze,  south_east: [1, 1].freeze,
    south: [1, 0].freeze,  south_west: [1, -1].freeze,
    west:  [0, -1].freeze, north_west: [-1, -1].freeze,
  }.freeze

  NEIGHBOUR_DIRECTIONS = RELATIVE_NEIGHBOUR_COORDINATES.keys.freeze
  PAIR_DIRECTIONS = [[:north, :south].freeze,
                     [:east, :west].freeze, 
                     [:north_east, :south_west].freeze, 
                     [:north_west, :south_east].freeze].freeze
  EMPTY = "."
  attr_accessor(*NEIGHBOUR_DIRECTIONS)
  def initialize
    @cell = EMPTY
  end

  def empty?
    @cell == EMPTY
  end

  def place?(piece)
    if empty? and valid_piece?(piece)
      @cell = piece
      return true
    end
    return false
  end

  def win?
    neighbours.compact.select{|x| x>=4}.length > 0
  end

  def [](direction)
    validate_direction(direction)
    send(direction)
  end

  def []=(direction, neighbour)
    validate_direction(direction)
    send("#{direction}=", neighbour)
  end

  def neighbours
    PAIR_DIRECTIONS.map{ |directions| 
      directions.map{|direction|
        self[direction].find_index{|neighbour| neighbour.to_s != self.to_s}
      }.compact.inject(:+)
    }
  end

  def to_s
    @cell
  end

  def inspect
    "<#{self.class} #{@cell}>"
  end

  private

  def validate_direction(direction)
    unless NEIGHBOUR_DIRECTIONS.map(&:to_s).include?(direction.to_s)
      raise "unsupported direction #{direction}"
    end
  end

  def valid_piece?(piece)
    piece != EMPTY
  end

end

全码

代码语言:javascript
复制
#!/usr/bin/ruby
class Cell
  RELATIVE_NEIGHBOUR_COORDINATES = {
    north: [-1, 0].freeze, north_east: [-1, 1].freeze,
    east:  [0, 1].freeze,  south_east: [1, 1].freeze,
    south: [1, 0].freeze,  south_west: [1, -1].freeze,
    west:  [0, -1].freeze, north_west: [-1, -1].freeze,
  }.freeze

  NEIGHBOUR_DIRECTIONS = RELATIVE_NEIGHBOUR_COORDINATES.keys.freeze
  PAIR_DIRECTIONS = [[:north, :south].freeze,
                     [:east, :west].freeze, 
                     [:north_east, :south_west].freeze, 
                     [:north_west, :south_east].freeze].freeze
  EMPTY = "."
  attr_accessor(*NEIGHBOUR_DIRECTIONS)
  def initialize
    @cell = EMPTY
  end

  def empty?
    @cell == EMPTY
  end

  def place?(piece)
    if empty? and valid_piece?(piece)
      @cell = piece
      return true
    end
    return false
  end

  def win?
    neighbours.compact.select{|x| x>=4}.length > 0
  end

  def [](direction)
    validate_direction(direction)
    send(direction)
  end

  def []=(direction, neighbour)
    validate_direction(direction)
    send("#{direction}=", neighbour)
  end

  def neighbours
    PAIR_DIRECTIONS.map{ |directions| 
      directions.map{|direction|
        self[direction].find_index{|neighbour| neighbour.to_s != self.to_s}
      }.compact.inject(:+)
    }
  end

  def to_s
    @cell
  end

  def inspect
    "<#{self.class} #{@cell}>"
  end

  private

  def validate_direction(direction)
    unless NEIGHBOUR_DIRECTIONS.map(&:to_s).include?(direction.to_s)
      raise "unsupported direction #{direction}"
    end
  end

  def valid_piece?(piece)
    piece != EMPTY
  end

end

class Grid
  attr_reader :gameover, :draw
  def initialize(width)
    @cells = Array.new(width * width).map { Cell.new }
    @grid = @cells.each_slice(width).to_a
    @gameover = false
    @draw = false
    @width = width
    assign_cell_neighbours
  end

  def update?(move, piece)
    x, y = move
    if x.negative? || x >= @width || y.negative? || y >= @width
      return false
    end
    cell = @grid.dig(x,y)
    if not cell.place?(piece)
      return false
    end

    if not full?
      @gameover = cell.win?
    else
      @gameover = true
      @draw = true
    end
    return true
  end

  def full?
    @cells.none?{|cell| cell.empty?}
  end

  def to_s
    @grid.map {|row| row.map(&:to_s).join}.join("\n")
  end

  private

  def assign_cell_neighbours
    @grid.each_with_index do |row, row_index|
      row.each_with_index do |cell, column_index|
        Cell::RELATIVE_NEIGHBOUR_COORDINATES.each do |dir, rel_coord|
          (rel_row_index, rel_column_index) = rel_coord
          neighbour_row_index = row_index
          neighbour_column_index = column_index
          neighbours = []
          loop do
            neighbour_row_index += rel_row_index
            neighbour_column_index += rel_column_index

            break if neighbour_row_index.negative? ||
                     neighbour_column_index.negative? ||
                     neighbour_row_index >= @width ||
                     neighbour_column_index >= @width
            neighbours << @grid.dig(neighbour_row_index, neighbour_column_index)
          end
          cell[dir] = neighbours
        end
      end
    end
  end
end

class Game
  def initialize(width=15)
    @width = width
    @users = ["A", "B"]
    @user_piece = {"A"=>"+", "B"=>"*"}
    @user_index = 0
  end

  def reset
    @grid = Grid.new(@width)
  end

  def start
    reset
    puts @grid
    until @grid.gameover
      user = @users[@user_index]
      print "Now for user<#{user}>, Enter your move(split by space)[0-#{@width-1}]:"
      begin
        move = gets.chomp.split.map(&:to_i)
        if not @grid.update?(move, @user_piece[user])
          puts "Invalid move!!!"
        else
          switch_user
          puts @grid
        end
      rescue
        puts "Invalid move!!!"
      end
    end
    show_result
  end

  def switch_user
    @user_index = (@user_index + 1) % @users.length
  end

  def show_result
    if not @grid.draw
      switch_user
      print "Game Over the Winner is <#{@users[@user_index]}>"
    else
      print "Game Over Draw"
    end
  end
end

game = Game.new()
game.start

欢迎所有评论!

EN

回答 1

Code Review用户

发布于 2019-03-03 12:12:27

  1. 使用@user/@user_index/switch_user简化Array#rotate!
代码语言:javascript
复制
user_queue = ['A', 'B']
user_queue.rotate!.first # 'B'
user_queue.rotate!.first # 'A'
  1. 对于这个小程序来说,布尔-成功返回值是可以的,但是通常情况下,这是很奇怪的,因为像update?这样的方法不仅是读的,它们也是操作。这就像如果number.even?以某种方式修改了number underneath.,我认为如果操作和验证分开,代码会流得更好。例如:
代码语言:javascript
复制
until @grid.gameover?
  turn_user = @user_queue.first
  move = gets_move(turn_user) until @grid.valid_move?(move, turn_user)
  @grid.update!(turn_user, move)
  @user_queue.rotate!
  # ...
end
  1. 利用Ruby的内置边界检查和简洁语法:
代码语言:javascript
复制
def valid_move?(move, user)
  !!@grid.dig(*move)&.empty? # simplified to one line, same as:
  # @grid.dig(*move)         # nil if out of bounds
  #             cell&.empty? # same as `cell && cell.empty?`
end

not full?                            # substitutes to:
not @cells.none?{|cell| cell.empty?} # double negative, easily avoidable:

@cells.any?(&:empty?) # `&:empty?` same as sending a `{ |x| x.empty? }` block
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/210788

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档