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.
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: []