kalah 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/kalah.rb +174 -0
  2. metadata +47 -0
@@ -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: []