class Board

class Board describes game board

Attributes

player_name[RW]

Public Class Methods

new(size_x = 10, size_y = 10, name = '') click to toggle source
# File lib/sea_battle/board.rb, line 16
def initialize(size_x = 10, size_y = 10, name = '')
  unless (1..20).include?(size_x) && (1..20).include?(size_y)
    fail BoardSizeError, 'size should be in (1..20) range'
  end

  @size_x, @size_y, @player_name = size_x, size_y, name

  @cells, @ships = [], []

  @alphabet = ('A'..'Z').map { |a| a }

  0.upto(size_x - 1) do |x|
    @cells[x] = []
    0.upto(size_y - 1) { |y| @cells[x][y] = Cell.new(x, y) }
  end
end

Public Instance Methods

cell(col, row) click to toggle source
# File lib/sea_battle/board.rb, line 33
def cell(col, row)
  @cells[col][row]
end
display(str, hide_ships = false) click to toggle source
# File lib/sea_battle/board.rb, line 120
def display(str, hide_ships = false)
  fail TypeError, 'str should be Fixnum' unless str.class == Fixnum
  offset = 2
  res = ''
  case str
  when 0
    res << @player_name.center(@size_x + 4)
    res << ' '
  when 1, 1 + @size_y + 1
    res << ' ' * 2
    (@size_x).times { |i| res << @alphabet[i] }
    res << ' ' * 3
  when (offset..@size_y - 1 + offset)
    res << (@size_y - 1 - (str - offset)).to_s
    res << '|'
    @size_x.times do |x|
      case @cells[x][@size_y - 1 - (str - offset)].state
      when SHIP then hide_ships ? (res << ' ') : (res << 'O')
      when BROKEN then res << 'X'
      when MISSED then res << '*'
      else res << ' '
      end
    end
    res << '|'
    res << (@size_y - 1 - (str - offset)).to_s
    res << ' '
  end
  res
end
generate(*list) click to toggle source
# File lib/sea_battle/board.rb, line 150
def generate(*list)
  5.downto(1).each do |size|
    count = list[size - 1]
    count.times do
      100.times do |try|
        begin
          head_x = rand(@size_x)
          head_y = rand(@size_y)
          dir = rand(4)
          put_ship head_x, head_y, size, dir
          break
        rescue NotRoomError
          raise NotRoomError, 'Too much ships' if try == 100 - 1
        end
      end
    end
  end
end
is_ship_alive?() click to toggle source
# File lib/sea_battle/board.rb, line 115
def is_ship_alive?
  @ships.each { |sh| return true if sh.alive? }
  false
end
put_ship(head_x, head_y, size, dir) click to toggle source
# File lib/sea_battle/board.rb, line 37
def put_ship(head_x, head_y, size, dir)
  head_x, head_y  = x_y_normalize head_x, head_y

  unless (1..5).include? size
    fail ShipSizeError, 'Ship size should be in range (1..5)'
  end

  # it makes list of cells which will be occupied by ship
  # and checks for board border crossing
  cell_list = []
  x = head_x
  y = head_y
  size.times do |i|
    case dir
    when NORTH
      fail NotRoomError if y == 0 && i != size - 1
      cell_list[i] = [x, y]
      y -= 1
    when EAST
      fail NotRoomError if x == 0 && i != size - 1
      cell_list[i] = [x, y]
      x -= 1
    when SOUTH
      fail NotRoomError if y == @size_y - 1 && i != size - 1
      cell_list[i] = [x, y]
      y += 1
    when WEST
      fail NotRoomError if x == @size_x - 1 && i != size - 1
      cell_list[i] = [x, y]
      x += 1
    end
  end

  # it checks if the nearest cells have already been occupied
  # algorithm isn't optimal because some cells are being checked many times
  cell_list.each do |x, y|
    fail NotRoomError, 'Other ship is here' if cell(x, y).state != EMPTY
    if x != 0
      if  (cell(x - 1, y).state != EMPTY) ||
          (y != 0 && cell(x - 1,  y - 1).state != EMPTY) ||
          (y != @size_y - 1 && cell(x - 1, y + 1).state != EMPTY)
        fail NotRoomError, "Other ship is near [#{x}:#{y}]"
      end
    end
    if  (y != 0 && cell(x, y - 1).state != EMPTY) ||
        (y != @size_y - 1 && cell(x, y + 1).state != EMPTY)
      fail NotRoomError, "Other ship is near [#{x}:#{y}]"
    end
    if x != @size_x - 1
      if  (cell(x + 1, y).state != EMPTY) ||
          (y != 0 && cell(x + 1, y - 1).state != EMPTY) ||
          (y != @size_y - 1 && cell(x + 1, y + 1).state != EMPTY)
        fail NotRoomError, 'Other ship is near [#{x}:#{y}]'
      end
    end
  end

  @ships << Ship.new(cell_list.map { |x, y|  cell x, y })
end
shoot(x, y) click to toggle source
# File lib/sea_battle/board.rb, line 97
def shoot(x, y)
  # there is a posibility of using letters as X coordinate
  x, y = x_y_normalize x, y
  c = cell(x, y)
  case c.state
  when EMPTY
    c.state = MISSED
    return 'MISSED'
  when SHIP
    ship = @ships.find { |sh| sh.include? c }
    fail FatalError, 'Mismatch between cell and ship tables' if ship.nil?
    c. state = BROKEN
    ship.alive? ? (return 'INJURED') : (return 'KILLED')
  when MISSED, BROKEN
    return 'ALREADY SHOT'
  end
end

Private Instance Methods

x_y_normalize(x, y) click to toggle source

it converts x to number if it is a letter then validates them returns x, y

# File lib/sea_battle/board.rb, line 174
def x_y_normalize(x, y)
  x = @alphabet.index(x.upcase) if x.is_a?(String)
  p = x_y_normalize_params
  fail OutOfBoardError, "x should be in range #{p[:x_n]} or #{p[:x_l]}"\
    unless (0..@size_x - 1).include? x
  fail OutOfBoardError, "y should be in range #{p[:y]}"\
    unless (0..@size_y - 1).include? y
  [x, y]
end
x_y_normalize_params() click to toggle source
# File lib/sea_battle/board.rb, line 184
def x_y_normalize_params
  {
    x_n:  "(0..#{@size_x - 1})",
    x_l:  "(\"A\"..\"#{@alphabet[@size_x - 1]}\"))",
    y:    "(0..#{@size_x - 1})"
  }
end