kalah 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|