kalah 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/kalah.rb +174 -0
- metadata +47 -0
data/lib/kalah.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
class Kalah
|
2
|
+
|
3
|
+
class IllegalMoveError < StandardError; end
|
4
|
+
class IllegalStateError < StandardError; end
|
5
|
+
|
6
|
+
# Number of houses.
|
7
|
+
NUM_HOUSES = 6
|
8
|
+
# Index of players' stores.
|
9
|
+
STORE_INDEX_P1 = NUM_HOUSES
|
10
|
+
STORE_INDEX_P2 = NUM_HOUSES * 2 + 1
|
11
|
+
|
12
|
+
# Holds the number of seeds in each house.
|
13
|
+
@stores
|
14
|
+
@history
|
15
|
+
@current_player
|
16
|
+
@num_seeds
|
17
|
+
|
18
|
+
attr_reader :stores, :history, :current_player, :num_seeds
|
19
|
+
|
20
|
+
def initialize(num_seeds = 6)
|
21
|
+
@stores = Array.new(NUM_HOUSES * 2 + 2, num_seeds)
|
22
|
+
@stores[STORE_INDEX_P1] = 0
|
23
|
+
@stores[STORE_INDEX_P2] = 0
|
24
|
+
@history = Array.new
|
25
|
+
@current_player = :P1
|
26
|
+
@num_seeds = num_seeds
|
27
|
+
end
|
28
|
+
|
29
|
+
def sow(index)
|
30
|
+
if index < 0 or index > @stores.length
|
31
|
+
raise IllegalMoveError, "Index #{i} does not exist (Number of houses is #{@stores.length})"
|
32
|
+
elsif index == STORE_INDEX_P1 or index == STORE_INDEX_P2
|
33
|
+
raise IllegalMoveError, "Player must not sow seeds from the store"
|
34
|
+
elsif has_current_player_won
|
35
|
+
raise IllegalMoveError, "Game is over"
|
36
|
+
elsif (current_player == :P1 and index > STORE_INDEX_P1) or
|
37
|
+
(current_player == :P2 and index < STORE_INDEX_P1)
|
38
|
+
raise IllegalMoveError, "Current player must not sow from the opponent's houses"
|
39
|
+
elsif @stores[index] == 0
|
40
|
+
raise IllegalMoveError, "House is empty."
|
41
|
+
end
|
42
|
+
|
43
|
+
# Copy to history.
|
44
|
+
@history.push({ player: current_player, stores: Array.new(@stores) })
|
45
|
+
|
46
|
+
# Perform sow
|
47
|
+
num_seeds = @stores[index]
|
48
|
+
# Empty house.
|
49
|
+
@stores[index] = 0
|
50
|
+
# Sow in other houses.
|
51
|
+
i = 1
|
52
|
+
while num_seeds > 0 do
|
53
|
+
house = (index + i) % @stores.length
|
54
|
+
if (@current_player == :P1 and house == STORE_INDEX_P2) or
|
55
|
+
(@current_player == :P2 and house == STORE_INDEX_P1)
|
56
|
+
# Skip opponent's store.
|
57
|
+
i += 1
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
@stores[house] += 1
|
62
|
+
num_seeds -= 1
|
63
|
+
i += 1
|
64
|
+
end
|
65
|
+
|
66
|
+
# If last seed lands in empty house (size now 1), take opposite store.
|
67
|
+
if current_players_houses.cover?((index + i - 1) % @stores.length) and
|
68
|
+
i > NUM_HOUSES and
|
69
|
+
@stores[(index + i - 1) % @stores.length] == 1
|
70
|
+
opposite = NUM_HOUSES * 2 - ((index + i - 1) % @stores.length)
|
71
|
+
# Put to store.
|
72
|
+
@stores[current_players_store] += @stores[opposite]
|
73
|
+
@stores[current_players_store] += 1
|
74
|
+
# Empty houses.
|
75
|
+
@stores[opposite] = 0
|
76
|
+
@stores[(index + i - 1) % @stores.length] = 0
|
77
|
+
end
|
78
|
+
|
79
|
+
# Check if game has ended.
|
80
|
+
if has_current_player_won
|
81
|
+
# Move opponent's seeds to current player's store.
|
82
|
+
range, store = 0
|
83
|
+
if @current_player == :P1
|
84
|
+
range = player_2_houses
|
85
|
+
store = STORE_INDEX_P1
|
86
|
+
else
|
87
|
+
range = player_1_houses
|
88
|
+
store = STORE_INDEX_P2
|
89
|
+
end
|
90
|
+
|
91
|
+
range.each do |i|
|
92
|
+
@stores[store] += @stores[i]
|
93
|
+
@stores[i] = 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Change turn if last seed did not land in own store.
|
98
|
+
if !has_current_player_won and (index + i - 1) % @stores.length != current_players_store
|
99
|
+
@current_player = @current_player == :P1 ? :P2 : :P1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Undoes a number of moves.
|
104
|
+
def undo(moves = 1)
|
105
|
+
raise IllegalStateError, "Cannot undo #{moves} moves. "\
|
106
|
+
"Only #{@history.length} moves performed."\
|
107
|
+
if history.length < moves
|
108
|
+
|
109
|
+
state = @history.pop
|
110
|
+
@current_player = state[:player]
|
111
|
+
@stores = state[:stores]
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Returns the index of the current player's store.
|
117
|
+
def current_players_store
|
118
|
+
return @current_player == :P1 ? STORE_INDEX_P1 : STORE_INDEX_P2
|
119
|
+
end
|
120
|
+
|
121
|
+
# Checks whether current player's houses are empty.
|
122
|
+
def has_current_player_won
|
123
|
+
current_players_houses.each do |i|
|
124
|
+
if @stores[i] != 0
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the index-range of the current player's houses.
|
133
|
+
def current_players_houses
|
134
|
+
if @current_player == :P1
|
135
|
+
player_1_houses
|
136
|
+
else
|
137
|
+
player_2_houses
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# The range of player 1's houses
|
142
|
+
def player_1_houses
|
143
|
+
0..(STORE_INDEX_P1 - 1)
|
144
|
+
end
|
145
|
+
|
146
|
+
# The range of player 2's houses
|
147
|
+
def player_2_houses
|
148
|
+
(STORE_INDEX_P1 + 1)..(STORE_INDEX_P2 - 1)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Prints the Kalah board
|
152
|
+
def to_s
|
153
|
+
# Combine p2 (top) and p1 (bot) houses, and stores (mid)
|
154
|
+
top = " "
|
155
|
+
mid = ""
|
156
|
+
bot = " "
|
157
|
+
@stores.each_with_index do |v, i|
|
158
|
+
if i == STORE_INDEX_P1
|
159
|
+
mid << "%02d\n" % v
|
160
|
+
elsif i == STORE_INDEX_P2
|
161
|
+
mid.insert(0, "\n%02d " % v)
|
162
|
+
elsif i > STORE_INDEX_P1
|
163
|
+
top.insert(3, "%02d " % v)
|
164
|
+
else
|
165
|
+
bot << "%02d " % v
|
166
|
+
mid << " "
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
top << mid << bot
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kalah
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mads Kalør
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-02 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Kalah is a Ruby based Kalah/Kalaha/Mancala engine. Useful for making
|
15
|
+
intelligent game agents or interactive Kalah games. Flexible in terms of board structure.
|
16
|
+
email: mads@kaloer.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/kalah.rb
|
22
|
+
homepage: https://github.com/mKaloer/Kalah
|
23
|
+
licenses:
|
24
|
+
- MIT
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 1.8.24
|
44
|
+
signing_key:
|
45
|
+
specification_version: 3
|
46
|
+
summary: A simple Kalah game engine.
|
47
|
+
test_files: []
|