just_backgammon 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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +4 -0
  4. data/CODE_OF_CONDUCT.md +49 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +68 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/just_backgammon.gemspec +27 -0
  12. data/lib/just_backgammon/bar.rb +107 -0
  13. data/lib/just_backgammon/combined_move.rb +55 -0
  14. data/lib/just_backgammon/common.rb +38 -0
  15. data/lib/just_backgammon/dice_set.rb +74 -0
  16. data/lib/just_backgammon/die.rb +54 -0
  17. data/lib/just_backgammon/errors/bear_off_error.rb +20 -0
  18. data/lib/just_backgammon/errors/blocked_error.rb +20 -0
  19. data/lib/just_backgammon/errors/dice_mismatch_error.rb +20 -0
  20. data/lib/just_backgammon/errors/empty_bar_error.rb +20 -0
  21. data/lib/just_backgammon/errors/empty_point_error.rb +20 -0
  22. data/lib/just_backgammon/errors/moves_possible_error.rb +20 -0
  23. data/lib/just_backgammon/errors/not_players_turn_error.rb +20 -0
  24. data/lib/just_backgammon/errors/pieces_on_bar_error.rb +20 -0
  25. data/lib/just_backgammon/errors/point_not_found_error.rb +20 -0
  26. data/lib/just_backgammon/errors/point_ownership_error.rb +20 -0
  27. data/lib/just_backgammon/errors/wrong_direction_error.rb +20 -0
  28. data/lib/just_backgammon/errors/wrong_phase_error.rb +20 -0
  29. data/lib/just_backgammon/game_state.rb +332 -0
  30. data/lib/just_backgammon/move.rb +112 -0
  31. data/lib/just_backgammon/move_list.rb +151 -0
  32. data/lib/just_backgammon/off_board.rb +73 -0
  33. data/lib/just_backgammon/piece.rb +33 -0
  34. data/lib/just_backgammon/point.rb +104 -0
  35. data/lib/just_backgammon/point_set.rb +95 -0
  36. data/lib/just_backgammon/version.rb +4 -0
  37. data/lib/just_backgammon.rb +6 -0
  38. metadata +123 -0
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = MovesPossibleError
4
+ #
5
+ # A moves possible error with a message
6
+ class MovesPossibleError
7
+
8
+ # A new instance of MovesPossibleError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new MovesPossibleError
15
+ # JustBackgammon::MovesPossibleError.new("Custom Message")
16
+ def initialize(message="Moves are still possible.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = NotPlayersTurnError
4
+ #
5
+ # A not player's turn error with a message
6
+ class NotPlayersTurnError
7
+
8
+ # A new instance of NotPlayersTurnError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new NotPlayersTurnError
15
+ # JustBackgammon::NotPlayersTurnError.new("Custom Message")
16
+ def initialize(message="It is not the player's turn yet.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = PiecesOnBarError
4
+ #
5
+ # A pieces on bar error with a message
6
+ class PiecesOnBarError
7
+
8
+ # A new instance of PiecesOnBarError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new PiecesOnBarError
15
+ # JustBackgammon::PiecesOnBarError.new("Custom Message")
16
+ def initialize(message="There are still pieces on the bar.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = PointNotFoundError
4
+ #
5
+ # A point not found error with a message
6
+ class PointNotFoundError
7
+
8
+ # A new instance of PointNotFoundError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new PointNotFoundError
15
+ # JustBackgammon::PointNotFoundError.new("Custom Message")
16
+ def initialize(message="Point cannot be found.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = PointOwnershipError
4
+ #
5
+ # A point ownership error with a message
6
+ class PointOwnershipError
7
+
8
+ # A new instance of PointOwnershipError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new PointOwnershipError
15
+ # JustBackgammon::PointOwnershipError.new("Custom Message")
16
+ def initialize(message="Point is not owned by player.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = WrongDirectionError
4
+ #
5
+ # A wrong direction error with a message
6
+ class WrongDirectionError
7
+
8
+ # A new instance of WrongDirectionError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new WrongDirectionError
15
+ # JustBackgammon::WrongDirectionError.new("Custom Message")
16
+ def initialize(message="A piece cannot move backwards.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module JustBackgammon
2
+
3
+ # = WrongPhaseError
4
+ #
5
+ # A wrong phase error with a message
6
+ class WrongPhaseError
7
+
8
+ # A new instance of WrongPhaseError.
9
+ #
10
+ # @option [String] message
11
+ # the message.
12
+ #
13
+ # ==== Example:
14
+ # # Instantiates a new WrongPhaseError
15
+ # JustBackgammon::WrongPhaseError.new("Custom Message")
16
+ def initialize(message="It is the wrong phase.")
17
+ @message = message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,332 @@
1
+ require 'just_backgammon/dice_set'
2
+ require 'just_backgammon/bar'
3
+ require 'just_backgammon/point_set'
4
+ require 'just_backgammon/off_board'
5
+ require 'just_backgammon/move'
6
+ require 'just_backgammon/move_list'
7
+
8
+ require 'just_backgammon/errors/not_players_turn_error'
9
+ require 'just_backgammon/errors/wrong_phase_error'
10
+ require 'just_backgammon/errors/point_not_found_error'
11
+ require 'just_backgammon/errors/empty_bar_error'
12
+ require 'just_backgammon/errors/empty_point_error'
13
+ require 'just_backgammon/errors/point_ownership_error'
14
+ require 'just_backgammon/errors/blocked_error'
15
+ require 'just_backgammon/errors/wrong_direction_error'
16
+ require 'just_backgammon/errors/pieces_on_bar_error'
17
+ require 'just_backgammon/errors/bear_off_error'
18
+ require 'just_backgammon/errors/dice_mismatch_error'
19
+ require 'just_backgammon/errors/moves_possible_error'
20
+
21
+ module JustBackgammon
22
+
23
+ # = Game State
24
+ #
25
+ # Represents a game of Backgammon in progress.
26
+ class GameState
27
+
28
+ # The roll phase of the turn where the players roll dice.
29
+ ROLL = 'roll'
30
+
31
+ # The move phase of the turn where the players move pieces.
32
+ MOVE = 'move'
33
+
34
+ # A new instance of GameState.
35
+ #
36
+ # @param [Fixnum] current_player_number
37
+ # Who's turn it is, 1 or 2
38
+ #
39
+ # @param [Fixnum] current_phase
40
+ # The current phase of the turn. Either move or roll.
41
+ #
42
+ # @param [Array<Hash>] dice
43
+ # An array of dice, each with a number.
44
+ #
45
+ # @param [Hash] bar
46
+ # The bar where hit pieces go. Contains an array of pieces.
47
+ #
48
+ # @param [Array<Hash>] points
49
+ # An array of points, each with a number and an array of pieces.
50
+ #
51
+ # @param [Hash] off_board
52
+ # Off the board where pieces bear off. Contains an array of pieces.
53
+ #
54
+ # ==== Example:
55
+ # # Instantiates a new Game of Backgammon
56
+ # JustBackgammon::GameState.new({
57
+ # current_player_number: 1,
58
+ # current_phase: 'move',
59
+ # dice: [
60
+ # { number: 1 },
61
+ # { number: 4 },
62
+ # ],
63
+ # bar: {
64
+ # pieces: [],
65
+ # },
66
+ # points: [
67
+ # { number: 1, pieces: [{owner: 1}, {owner: 1}] },
68
+ # { number: 2, pieces: [] }
69
+ # ],
70
+ # off_board: {
71
+ # pieces: [],
72
+ # }
73
+ # })
74
+ def initialize(current_player_number:, current_phase:, dice:, bar:, points:, off_board:)
75
+ @current_player_number = current_player_number
76
+ @current_phase = current_phase
77
+ @dice = JustBackgammon::DiceSet.load(dice: dice)
78
+ @bar = JustBackgammon::Bar.load(bar)
79
+ @points = JustBackgammon::PointSet.load(points: points)
80
+ @off_board = JustBackgammon::OffBoard.load(off_board)
81
+ @errors = []
82
+ end
83
+
84
+ # Instantiates a new GameState object in the starting position
85
+ #
86
+ # @return [GameState]
87
+ def self.default
88
+ new({
89
+ current_player_number: 1,
90
+ current_phase: ROLL,
91
+ dice: [
92
+ { number: nil },
93
+ { number: nil }
94
+ ],
95
+ bar: { pieces: [] },
96
+ points: [
97
+ { number: 1, pieces: [{owner: 1}, {owner: 1}] },
98
+ { number: 2, pieces: [] },
99
+ { number: 3, pieces: [] },
100
+ { number: 4, pieces: [] },
101
+ { number: 5, pieces: [] },
102
+ { number: 6, pieces: [{owner: 2}, {owner: 2}, {owner: 2}, {owner: 2}, {owner: 2}] },
103
+
104
+ { number: 7, pieces: [] },
105
+ { number: 8, pieces: [{owner: 2}, {owner: 2}, {owner: 2}] },
106
+ { number: 9, pieces: [] },
107
+ { number: 10, pieces: [] },
108
+ { number: 11, pieces: [] },
109
+ { number: 12, pieces: [{owner: 1}, {owner: 1}, {owner: 1}, {owner: 1}, {owner: 1}] },
110
+
111
+ { number: 13, pieces: [{owner: 2}, {owner: 2}, {owner: 2}, {owner: 2}, {owner: 2}] },
112
+ { number: 14, pieces: [] },
113
+ { number: 15, pieces: [] },
114
+ { number: 16, pieces: [] },
115
+ { number: 17, pieces: [{owner: 1}, {owner: 1}, {owner: 1}] },
116
+ { number: 18, pieces: [] },
117
+
118
+ { number: 19, pieces: [{owner: 1}, {owner: 1}, {owner: 1}, {owner: 1}, {owner: 1}] },
119
+ { number: 20, pieces: [] },
120
+ { number: 21, pieces: [] },
121
+ { number: 22, pieces: [] },
122
+ { number: 23, pieces: [] },
123
+ { number: 24, pieces: [{owner: 2}, {owner: 2}] },
124
+ ],
125
+ off_board: { pieces: [] }
126
+ })
127
+ end
128
+
129
+ # @return [Fixnum] who's turn it is
130
+ attr_reader :current_player_number
131
+
132
+ # @return [String] the current phase of the turn. Either move or roll
133
+ attr_reader :current_phase
134
+
135
+ # @return [DiceSet] an array of dice, each with a number
136
+ attr_reader :dice
137
+
138
+ # @return [Bar] the bar where hit pieces go. Contains an array of pieces
139
+ attr_reader :bar
140
+
141
+ # @return [PointSet] an array of points, each with a number and an array of pieces
142
+ attr_reader :points
143
+
144
+ # @return [Fixnum] who's turn it is.
145
+ attr_reader :off_board
146
+
147
+ # @return [Array<Error>] errors if any.
148
+ attr_reader :errors
149
+
150
+ # Rolls the dice
151
+ #
152
+ # Two dice are rolled and returns true on success.
153
+ # The results can be found on the dice attribute.
154
+ # If the dice have the same result, then the dice are duplicated.
155
+ # If it's not the player's turn or the phase is move, it will return false.
156
+ #
157
+ # ==== Example:
158
+ # # Rolls the dice for player 1
159
+ # game_state.roll(1)
160
+ #
161
+ # @param [Fixnum] player_number
162
+ # the player number, 1 or 2.
163
+ #
164
+ # @return [Boolean]
165
+ def roll(player_number)
166
+ @errors = []
167
+
168
+ if player_number != current_player_number
169
+ @errors.push JustBackgammon::NotPlayersTurnError.new
170
+ elsif current_phase != ROLL
171
+ @errors.push JustBackgammon::WrongPhaseError.new
172
+ else
173
+ @dice.roll
174
+ step
175
+ end
176
+
177
+ @errors.none?
178
+ end
179
+
180
+ # Moves each piece specified from one point, to another
181
+ #
182
+ # It moves each piece specified and returns true on success.
183
+ # It returns false if the move is invalid, it's not the player's turn or the phase is roll..
184
+ #
185
+ # ==== Example:
186
+ # # Moves two pieces for player 1
187
+ # game_state.move(1, [{from: 1, to: 2}, {from: 3, to: 4}])
188
+ #
189
+ # @param [Fixnum] player_number
190
+ # the player number, 1 or 2.
191
+ #
192
+ # @param [Array<Hash>] list
193
+ # a list of all the moves, each containing a from and to key.
194
+ #
195
+ # @return [Boolean]
196
+ def move(player_number, list)
197
+ move_list = sanitize_list(list)
198
+
199
+ @errors = []
200
+ if player_number != current_player_number
201
+ @errors.push JustBackgammon::NotPlayersTurnError.new
202
+ elsif current_phase != MOVE
203
+ @errors.push JustBackgammon::WrongPhaseError.new
204
+ elsif move_valid?(move_list)
205
+ perform_move(move_list)
206
+ step
207
+ end
208
+
209
+ @errors.none?
210
+ end
211
+
212
+ # The player number of the winner. It returns nil if there is no winner.
213
+ #
214
+ # @return [Fixnum,NilClass]
215
+ def winner
216
+ [1, 2].find { |player_number| @off_board.number_of_pieces_owned_by_player(player_number) == 15 }
217
+ end
218
+
219
+ # A hashed serialized representation of the game state
220
+ #
221
+ # @return [Hash]
222
+ def as_json
223
+ {
224
+ current_player_number: current_player_number,
225
+ current_phase: current_phase,
226
+ dice: dice.as_json,
227
+ bar: bar.as_json,
228
+ points: points.as_json,
229
+ off_board: off_board.as_json
230
+ }
231
+ end
232
+
233
+ private
234
+
235
+ def move_valid?(move_list) # :nodoc:
236
+ @errors = []
237
+
238
+ case
239
+ when move_list.any_missing_point?
240
+ @errors.push JustBackgammon::PointNotFoundError.new
241
+ when move_list.any_point_empty?
242
+ @errors.push JustBackgammon::EmptyPointError.new
243
+ when move_list.any_bar_empty_for_player?(current_player_number)
244
+ @errors.push JustBackgammon::EmptyBarError.new
245
+ when move_list.any_point_owned_by_opponent?(current_player_number)
246
+ @errors.push JustBackgammon::PointOwnershipError.new
247
+ when move_list.any_blocked?(current_player_number)
248
+ @errors.push JustBackgammon::BlockedError.new
249
+ when move_list.any_wrong_direction?(current_player_number)
250
+ @errors.push JustBackgammon::WrongDirectionError.new
251
+ when !move_list.all_moves_from_bar? && \
252
+ move_list.number_of_moves_from_bar != bar.number_of_pieces_owned_by_player(current_player_number) && \
253
+ points.destinations(bar, dice, current_player_number).size >= move_list.number_of_moves_from_bar
254
+ @errors.push JustBackgammon::PiecesOnBarError.new
255
+ when move_list.any_bear_off? && \
256
+ !(points.not_home(current_player_number).map(&:number) - move_list.map { |m| m.from.number }).empty?
257
+ @errors.push JustBackgammon::BearOffError.new
258
+ when current_player_has_moves? && move_list.dice_mismatch?(current_player_number, dice)
259
+ @errors.push JustBackgammon::DiceMismatchError.new
260
+ end
261
+
262
+ @errors.none?
263
+ end
264
+
265
+ def current_player_has_moves? # :nodoc:
266
+ if bar.any_pieces_for_player?(@current_player_number)
267
+ @points.destinations(bar, @dice, @current_player_number).any?
268
+ else
269
+ @points.owned_by_player(@current_player_number).any? do |p|
270
+ @points.destinations(p, @dice, @current_player_number).any?
271
+ end
272
+ end
273
+ end
274
+
275
+ def perform_move(list) # :nodoc:
276
+ list.each do |move|
277
+ from = find_point(move.from.number)
278
+ to = find_point(move.to.number)
279
+
280
+ if to.is_a?(JustBackgammon::Point) && to.owned_by_opponent?(current_player_number) && to.blot?
281
+ @bar.push(to.pop)
282
+ end
283
+
284
+ popped = if from.is_a?(JustBackgammon::Bar)
285
+ from.pop_for_player(current_player_number)
286
+ else
287
+ from.pop
288
+ end
289
+
290
+ to.push(popped)
291
+ end
292
+ end
293
+
294
+ def step # :nodoc:
295
+ case current_phase
296
+ when ROLL
297
+ @current_phase = MOVE
298
+ when MOVE
299
+ @current_phase = ROLL
300
+ @dice.reset
301
+ turn
302
+ end
303
+ end
304
+
305
+ def turn # :nodoc:
306
+ case current_player_number
307
+ when 1
308
+ @current_player_number = 2
309
+ when 2
310
+ @current_player_number = 1
311
+ end
312
+ end
313
+
314
+ def find_point(n) # :nodoc:
315
+ case n
316
+ when 'bar'
317
+ bar
318
+ when 'off_board'
319
+ off_board
320
+ else
321
+ @points.find_by_number(n.to_i)
322
+ end
323
+ end
324
+
325
+ def sanitize_list(list) # :nodoc:
326
+ move_list = list.map do |i|
327
+ JustBackgammon::Move.new({ from: find_point(i[:from]), to: find_point(i[:to]) })
328
+ end
329
+ JustBackgammon::MoveList.new(moves: move_list)
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,112 @@
1
+ module JustBackgammon
2
+
3
+ # = Move
4
+ #
5
+ # A move from a point to a point.
6
+ class Move
7
+ extend Forwardable
8
+
9
+ # A new instance of Move.
10
+ #
11
+ # @param [Point] from
12
+ # The point where the move starts.
13
+ #
14
+ # @param [Point] to
15
+ # The point where the move ends.
16
+ #
17
+ # ==== Example:
18
+ # # Instantiates a new Bar
19
+ # JustBackgammon::Move.new({from: point_a, to: point_b})
20
+ def initialize(from: , to:)
21
+ @from = from
22
+ @to = to
23
+ end
24
+
25
+ # @return [Point] the point where the move starts
26
+ attr_reader :from
27
+
28
+ # @return [Point] the point where the move ends
29
+ attr_reader :to
30
+
31
+ def_delegator :from, :empty_for_player?
32
+
33
+ # The distance of the move for the specified player.
34
+ #
35
+ # @return [Fixnum]
36
+ def distance_for_player(player_number)
37
+ case player_number
38
+ when 1
39
+ from_number = from.instance_of?(Bar) ? 0 : from.number
40
+ to_number = to.instance_of?(OffBoard) ? 25 : to.number
41
+ to_number - from_number
42
+ when 2
43
+ from_number = from.instance_of?(Bar) ? 25 : from.number
44
+ to_number = to.instance_of?(OffBoard) ? 0 : to.number
45
+ to_number - from_number
46
+ else
47
+ 0
48
+ end
49
+ end
50
+
51
+ # The absolute distance of the move for the specified player.
52
+ #
53
+ # @return [Fixnum]
54
+ def absolute_distance_for_player(player_number)
55
+ distance_for_player(player_number).abs
56
+ end
57
+
58
+ # Checks if the move is in the wrong direction for the specified player.
59
+ #
60
+ # @return [Boolean]
61
+ def wrong_direction?(player_number)
62
+ case player_number
63
+ when 1
64
+ distance_for_player(player_number) < 0
65
+ when 2
66
+ distance_for_player(player_number) > 0
67
+ else
68
+ true
69
+ end
70
+ end
71
+
72
+ # Checks if the move is blocked.
73
+ #
74
+ # @return [Boolean]
75
+ def blocked?(player_number)
76
+ case to
77
+ when OffBoard
78
+ false
79
+ else
80
+ to.owned_by_opponent?(player_number) && to.blocked?
81
+ end
82
+ end
83
+
84
+ # Checks if the move is missing points.
85
+ #
86
+ # @return [Boolean]
87
+ def missing_point?
88
+ from.nil? || to.nil?
89
+ end
90
+
91
+ # Checks if the move is from the bar.
92
+ #
93
+ # @return [Boolean]
94
+ def from_bar?
95
+ from.instance_of?(JustBackgammon::Bar)
96
+ end
97
+
98
+ # Checks if the move is to a point.
99
+ #
100
+ # @return [Boolean]
101
+ def to_point?
102
+ to.instance_of?(JustBackgammon::Point)
103
+ end
104
+
105
+ # Checks if the move is bearing off.
106
+ #
107
+ # @return [Boolean]
108
+ def bear_off?
109
+ to.instance_of?(JustBackgammon::OffBoard)
110
+ end
111
+ end
112
+ end