Gomoku Game in Ruby












0














I learnt lots about ruby from my last post Game of Life in Ruby, so I have my next try in Ruby, it is Gomoku.



It is still an console game and I use three classes Game Grid Cell, the structure is similar with the @Johan Wentholt 's example code. But of course game rule is different.



Game class



Game class to run the game



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


Grid class



Grid present is the gomoku game board, keep updating piece on board and whether the game is over



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


Cell class



Cell class keep the cell state and alse judge whether current update on cell lead to a win



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


Full code



#!/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


All reviews are welcome!









share



























    0














    I learnt lots about ruby from my last post Game of Life in Ruby, so I have my next try in Ruby, it is Gomoku.



    It is still an console game and I use three classes Game Grid Cell, the structure is similar with the @Johan Wentholt 's example code. But of course game rule is different.



    Game class



    Game class to run the game



    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


    Grid class



    Grid present is the gomoku game board, keep updating piece on board and whether the game is over



    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


    Cell class



    Cell class keep the cell state and alse judge whether current update on cell lead to a win



    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


    Full code



    #!/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


    All reviews are welcome!









    share

























      0












      0








      0







      I learnt lots about ruby from my last post Game of Life in Ruby, so I have my next try in Ruby, it is Gomoku.



      It is still an console game and I use three classes Game Grid Cell, the structure is similar with the @Johan Wentholt 's example code. But of course game rule is different.



      Game class



      Game class to run the game



      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


      Grid class



      Grid present is the gomoku game board, keep updating piece on board and whether the game is over



      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


      Cell class



      Cell class keep the cell state and alse judge whether current update on cell lead to a win



      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


      Full code



      #!/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


      All reviews are welcome!









      share













      I learnt lots about ruby from my last post Game of Life in Ruby, so I have my next try in Ruby, it is Gomoku.



      It is still an console game and I use three classes Game Grid Cell, the structure is similar with the @Johan Wentholt 's example code. But of course game rule is different.



      Game class



      Game class to run the game



      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


      Grid class



      Grid present is the gomoku game board, keep updating piece on board and whether the game is over



      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


      Cell class



      Cell class keep the cell state and alse judge whether current update on cell lead to a win



      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


      Full code



      #!/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


      All reviews are welcome!







      ruby





      share












      share










      share



      share










      asked 7 mins ago









      Aries_is_there

      687211




      687211






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210788%2fgomoku-game-in-ruby%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210788%2fgomoku-game-in-ruby%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Quarter-circle Tiles

          build a pushdown automaton that recognizes the reverse language of a given pushdown automaton?

          Mont Emei