class Kalah

Constants

NUM_HOUSES

Number of houses.

STORE_INDEX_P1

Index of players' stores.

STORE_INDEX_P2

Attributes

current_player[R]
history[R]
num_seeds[R]
stores[R]

Public Class Methods

new(num_seeds = 6) click to toggle source
# File lib/kalah.rb, line 20
def initialize(num_seeds = 6)
  @stores = Array.new(NUM_HOUSES * 2 + 2, num_seeds)
  @stores[STORE_INDEX_P1] = 0
  @stores[STORE_INDEX_P2] = 0
  @history = Array.new
  @current_player = :P1
  @num_seeds = num_seeds
end

Public Instance Methods

current_players_houses() click to toggle source

Returns the index-range of the current player's houses.

# File lib/kalah.rb, line 133
def current_players_houses
  if @current_player == :P1
    player_1_houses
  else
    player_2_houses
  end
end
current_players_store() click to toggle source

Returns the index of the current player's store.

# File lib/kalah.rb, line 117
def current_players_store
  return @current_player == :P1 ? STORE_INDEX_P1 : STORE_INDEX_P2
end
has_current_player_won() click to toggle source

Checks whether current player's houses are empty.

# File lib/kalah.rb, line 122
def has_current_player_won
  current_players_houses.each do |i|
    if @stores[i] != 0
      return false
    end
  end

  true
end
player_1_houses() click to toggle source

The range of player 1's houses

# File lib/kalah.rb, line 142
def player_1_houses
  0..(STORE_INDEX_P1 - 1)
end
player_2_houses() click to toggle source

The range of player 2's houses

# File lib/kalah.rb, line 147
def player_2_houses
  (STORE_INDEX_P1 + 1)..(STORE_INDEX_P2 - 1)
end
sow(index) click to toggle source
# File lib/kalah.rb, line 29
def sow(index)
  if index < 0 or index > @stores.length
    raise IllegalMoveError, "Index #{i} does not exist (Number of houses is #{@stores.length})"
  elsif index == STORE_INDEX_P1 or index == STORE_INDEX_P2
    raise IllegalMoveError, "Player must not sow seeds from the store"
  elsif has_current_player_won
    raise IllegalMoveError, "Game is over"
  elsif (current_player == :P1 and index > STORE_INDEX_P1) or
        (current_player == :P2 and index < STORE_INDEX_P1)
    raise IllegalMoveError, "Current player must not sow from the opponent's houses"
  elsif @stores[index] == 0
    raise IllegalMoveError, "House is empty."
  end
  
  # Copy to history.
  @history.push({ player: current_player, stores: Array.new(@stores) })

  # Perform sow
  num_seeds = @stores[index]
  # Empty house.
  @stores[index] = 0
  # Sow in other houses.
  i = 1
  while num_seeds > 0 do
    house = (index + i) % @stores.length
    if (@current_player == :P1 and house == STORE_INDEX_P2) or
       (@current_player == :P2 and house == STORE_INDEX_P1)
      # Skip opponent's store.
      i += 1
      next
    end

    @stores[house] += 1
    num_seeds -= 1
    i += 1
  end

  # If last seed lands in empty house (size now 1), take opposite store.
  if current_players_houses.cover?((index + i - 1) % @stores.length) and
      i > NUM_HOUSES and
      @stores[(index + i - 1) % @stores.length] == 1
    opposite = NUM_HOUSES * 2 - ((index + i - 1) % @stores.length)
    # Put to store.
    @stores[current_players_store] += @stores[opposite]
    @stores[current_players_store] += 1
    # Empty houses.
    @stores[opposite] = 0
    @stores[(index + i - 1) % @stores.length] = 0
  end

  # Check if game has ended.
  if has_current_player_won
    # Move opponent's seeds to current player's store.
    range, store = 0
    if @current_player == :P1
      range = player_2_houses
      store = STORE_INDEX_P1
    else
      range = player_1_houses
      store = STORE_INDEX_P2
    end

    range.each do |i|
      @stores[store] += @stores[i]
      @stores[i] = 0
    end
  end

  # Change turn if last seed did not land in own store.
  if !has_current_player_won and (index + i - 1) % @stores.length != current_players_store
    @current_player = @current_player == :P1 ? :P2 : :P1
  end
end
to_s() click to toggle source

Prints the Kalah board

# File lib/kalah.rb, line 152
def to_s
  # Combine p2 (top) and p1 (bot) houses, and stores (mid)
  top = "   "
  mid = ""
  bot = "   "
  @stores.each_with_index do |v, i|
    if i == STORE_INDEX_P1
      mid << "%02d\n" % v
    elsif i == STORE_INDEX_P2
      mid.insert(0, "\n%02d " % v)
    elsif i > STORE_INDEX_P1
      top.insert(3, "%02d " % v)
    else
      bot << "%02d " % v
      mid << "   "
    end
  end

  top << mid << bot

end
undo(moves = 1) click to toggle source

Undoes a number of moves.

# File lib/kalah.rb, line 104
def undo(moves = 1)
  raise IllegalStateError, "Cannot undo #{moves} moves. "\
                           "Only #{@history.length} moves performed."\
                           if history.length < moves
  
  state = @history.pop
  @current_player = state[:player]
  @stores = state[:stores]
  
end