boardgame_engine 0.2.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +43 -3
- data/lib/boardgame_engine/board_modules.rb +174 -17
- data/lib/boardgame_engine/boardgame.rb +73 -83
- data/lib/boardgame_engine/chess.rb +51 -47
- data/lib/boardgame_engine/connect4.rb +33 -27
- data/lib/boardgame_engine/game_modules.rb +5 -5
- data/lib/boardgame_engine/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f3c14dd095359859bb7c6fb53b25e7c94ae18bb1b4e407d0e377797254930a6
|
4
|
+
data.tar.gz: 5ecf4d791871a327a1f2e2844cc21b5d0de4b23b001f478a8ef8ca89834bc803
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a8579be5461dda2d3e77a4c2fa50b92ff0a6d4179f960e2ce083261bbc5db6cfe05defa5f1e679e07576bd517d2296477666b86f4388be30030b8127a2ce5ef
|
7
|
+
data.tar.gz: afd22e1654d2ca4a66f648e96d08cfd9f52ba3c780d19d4352344214dd6b753ba85236f8e2b0bbf47ecce2c1bd272ac6c33ed6592c0a96838db3c0ea50b327c5
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
A gem that provides a template for creating boardgames. It aims to streamline
|
4
4
|
the process of making boardgames to be played on the terminal by providing a
|
5
|
-
variety of
|
5
|
+
variety of classes and modules.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -15,8 +15,43 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
15
15
|
$ gem install boardgame_engine
|
16
16
|
|
17
17
|
## Usage
|
18
|
+
### Initial Setup
|
19
|
+
To start making your own boardgame using this gem, your game module needs at least two classes -
|
20
|
+
a `Game` class inheriting from `Boardgame` and a `Board` class inheriting `Board`.
|
21
|
+
`Game` captures the core gameplay loop/logic and the `Board` captures
|
22
|
+
interactions with the board.
|
18
23
|
|
19
|
-
|
24
|
+
Depending on the game rules, select child modules from the `Games` and `Boards`
|
25
|
+
and include it into the `Game` and `Board` class respectively.
|
26
|
+
These modules will handle much of the game and board logic related to the
|
27
|
+
game mechanic/rule.
|
28
|
+
|
29
|
+
For example, if the turns in your game go in a cycle like 1 -> 2 -> 3 -> 1 -> 2 ...
|
30
|
+
your `Game` class would look like
|
31
|
+
```ruby
|
32
|
+
class Game < BoardgameEngine::Game
|
33
|
+
include Games::CyclicalGame
|
34
|
+
NUM_PLAYERS = 3
|
35
|
+
...
|
36
|
+
end
|
37
|
+
```
|
38
|
+
Including this module makes it so that the `BoardgameEngine::Game#change_turn`
|
39
|
+
method automatically hands the turn over to the correct player.
|
40
|
+
|
41
|
+
### Overriding
|
42
|
+
At the current stage of the app, there are methods that necessarily must be
|
43
|
+
overridden in the child class.
|
44
|
+
`play_turn` must be implemented on _your_ `Game` class, and must contain what happens
|
45
|
+
during a single turn (not a round, just a turn).
|
46
|
+
|
47
|
+
Additionally, the limited number of modules mean that you will probably have to implement some
|
48
|
+
methods on your own. I hope to reduce the number of these as the app matures.
|
49
|
+
I left yard comments on all module methods, so hopefully overriding won't be too
|
50
|
+
difficult.
|
51
|
+
|
52
|
+
## Example Games
|
53
|
+
Check out `connect4.rb` for a game built with rather minimal overrides and
|
54
|
+
`chess.rb` for a game that makes heavy use of customization.
|
20
55
|
|
21
56
|
## Development
|
22
57
|
|
@@ -26,4 +61,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
26
61
|
|
27
62
|
## Contributing
|
28
63
|
|
29
|
-
Bug reports
|
64
|
+
Bug reports are welcome on GitHub at https://github.com/Forthoney/boardgame_engine.
|
65
|
+
I may not get to them quickly but it will be greatly helpful
|
66
|
+
|
67
|
+
As the app is under heavy development I will not look deeply into actually
|
68
|
+
pulling pull requests (if there were to be any).
|
69
|
+
I will definitely consider the ideas proposed in them, so they are still welcome.
|
@@ -4,17 +4,7 @@
|
|
4
4
|
module Boards
|
5
5
|
# Boards laid out in grid format
|
6
6
|
module Grid
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# @param [Integer] row the number of rows
|
10
|
-
# @param [Integer] col the number of columns
|
11
|
-
#
|
12
|
-
# @return [Array<Array>] A 2D Array
|
13
|
-
def generate_board(row, col)
|
14
|
-
Array.new(row) { Array.new(col) { nil } }
|
15
|
-
end
|
16
|
-
|
17
|
-
# Print a visual representation of the
|
7
|
+
# Print a visual representation of the board
|
18
8
|
#
|
19
9
|
# @param [Boolean] show_row whether to show row labels
|
20
10
|
# @param [Boolean] show_col whether to show column labels
|
@@ -30,19 +20,42 @@ module Boards
|
|
30
20
|
return unless show_col
|
31
21
|
|
32
22
|
column_spacer = show_row ? ' ' : ''
|
33
|
-
puts
|
23
|
+
puts format_col_numbering(column_spacer)
|
34
24
|
end
|
35
25
|
|
26
|
+
# Accessor for getting piece at a location on the board
|
27
|
+
#
|
28
|
+
# @param [Array<Integer, Integer>] location the coordinates on the grid to
|
29
|
+
# get the piece from
|
30
|
+
#
|
31
|
+
# @return [Piece] the piece at the location
|
36
32
|
def get_piece_at(location)
|
37
33
|
row, col = location
|
38
34
|
@board.dig(row, col)
|
39
35
|
end
|
40
36
|
|
37
|
+
# Setter for setting a piece at a location on the board
|
38
|
+
#
|
39
|
+
# @param [Array<Integer, Integer>] location <description>
|
40
|
+
# @param [Piece] piece the piece to place down
|
41
|
+
#
|
42
|
+
# @return [void]
|
41
43
|
def set_piece_at(location, piece)
|
42
44
|
row, col = location
|
43
45
|
@board[row][col] = piece
|
44
46
|
end
|
45
47
|
|
48
|
+
# Check whether the given input refers to a valid location on the board,
|
49
|
+
# regardless of placement of other pieces, rules, etc
|
50
|
+
#
|
51
|
+
# @param [String] input the user input
|
52
|
+
# @param [Boolean] only_row optional arg for only allowing user to specify
|
53
|
+
# row
|
54
|
+
# @param [Boolean] only_col optional arg for only allowing user to specify
|
55
|
+
# col
|
56
|
+
#
|
57
|
+
# @return [Boolean] true if the input can be parsed into a location on the
|
58
|
+
# board, false otherwise
|
46
59
|
def valid_board_input?(input, only_row: false, only_col: false)
|
47
60
|
if only_row || only_col
|
48
61
|
input.match?(/[0-#{@board.length - 1}]/)
|
@@ -51,29 +64,154 @@ module Boards
|
|
51
64
|
end
|
52
65
|
end
|
53
66
|
|
67
|
+
# Check whether there exists a clear diagonal path from start to a
|
68
|
+
# destination
|
69
|
+
#
|
70
|
+
# @param [Array<Integer, Integer>] start_location the start location
|
71
|
+
# @param [Array<Integer, Integer>] end_location the intended destination
|
72
|
+
#
|
73
|
+
# @return [Boolean] true if there exists an unblocked path to destination
|
74
|
+
def clear_diag_path?(start_location, end_location)
|
75
|
+
row, col = start_location
|
76
|
+
end_row, end_col = end_location
|
77
|
+
|
78
|
+
((end_row - row).abs == (end_col - col).abs) \
|
79
|
+
&& clear_path?(row, col, end_row, end_col, board)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Check whether there exists an unblocked horizontal path from start to
|
83
|
+
# a destination
|
84
|
+
#
|
85
|
+
# @param [Array<Integer, Integer>] start_location the start location
|
86
|
+
# @param [Array<Integer, Integer>] end_location the intended destination
|
87
|
+
#
|
88
|
+
# @return [Boolean] true if there exists an unblocked path to destination
|
89
|
+
def clear_horz_path?(start_location, end_location)
|
90
|
+
row, col = start_location
|
91
|
+
end_row, end_col = end_location
|
92
|
+
|
93
|
+
(end_row == row) && clear_path?(row, col, end_row, end_col, board)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check whether there exists an unblocked vertical path from start to
|
97
|
+
# a destination
|
98
|
+
#
|
99
|
+
# @param [Array<Integer, Integer>] start_location the start location
|
100
|
+
# @param [Array<Integer, Integer>] end_location the intended destination
|
101
|
+
#
|
102
|
+
# @return [Boolean] true if there exists an unblocked path to destination
|
103
|
+
def clear_vert_path?(start_location, end_location)
|
104
|
+
row, col = start_location
|
105
|
+
end_row, end_col = end_location
|
106
|
+
|
107
|
+
(end_col == col) && clear_path?(row, col, end_row, end_col, board)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parse an input from the user that has a corresponding board location. This
|
111
|
+
# must be used in after valid_board_input? has confirmed the input is in
|
112
|
+
# the correct format
|
113
|
+
#
|
114
|
+
# @param [String] input a valid
|
115
|
+
#
|
116
|
+
# @return [Array<Integer, Integer>]
|
54
117
|
def parse_input(input)
|
55
118
|
input.split(',').map(&:to_i)
|
56
119
|
end
|
57
120
|
|
58
|
-
|
121
|
+
# Make a 2D Array representing the board
|
122
|
+
#
|
123
|
+
# @param [Integer] row the number of rows
|
124
|
+
# @param [Integer] col the number of columns
|
125
|
+
#
|
126
|
+
# @return [Array<Array>] A 2D Array
|
127
|
+
def generate_board(row, col)
|
128
|
+
Array.new(row) { Array.new(col) { nil } }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Check whether there are consecutive pieces on the board
|
132
|
+
#
|
133
|
+
# @param [Integer] num the number of consecutive pieces to check for.
|
134
|
+
# @param [Boolean] row check for row-wise consecutive pieces
|
135
|
+
# @param [Boolean] col check for column-wise consecutive pieces
|
136
|
+
# @param [Boolean] diagonal check for diagonally consecutive pieces
|
137
|
+
#
|
138
|
+
# @return [Boolean] true if any specified directins have num number of
|
139
|
+
# consecutive pieces
|
140
|
+
def consecutive?(num, row: true, col: true, diagonal: true)
|
59
141
|
configs = []
|
60
142
|
configs << @board if row
|
61
143
|
configs << @board.transpose if col
|
62
144
|
configs << align_diagonal(@board) << align_diagonal(@board.transpose) if diagonal
|
63
145
|
|
64
146
|
configs.each do |board|
|
65
|
-
board.each { |array| return true if row_consecutive?(
|
147
|
+
board.each { |array| return true if row_consecutive?(num, array) }
|
66
148
|
end
|
67
149
|
false
|
68
150
|
end
|
69
151
|
|
70
152
|
private
|
71
153
|
|
154
|
+
# calculates the location of the next cell in the sequence of cells from a
|
155
|
+
# given start location and an end location
|
156
|
+
#
|
157
|
+
# @param [Integer] row
|
158
|
+
# @param [Integer] col
|
159
|
+
# @param [Integer] end_row
|
160
|
+
# @param [Integer] end_col
|
161
|
+
#
|
162
|
+
# @return [Array<Integer, Integer>] the next cell in the sequence of cells
|
163
|
+
def next_cell(row, col, end_row, end_col)
|
164
|
+
row_move = 0
|
165
|
+
col_move = 0
|
166
|
+
|
167
|
+
col_move = (end_col - col) / (end_col - col).abs if end_col != col
|
168
|
+
row_move = (end_row - row) / (end_row - row).abs if end_row != row
|
169
|
+
|
170
|
+
[row + row_move, col + col_move]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Given a linear path, check whether there exists an unblocked path.
|
174
|
+
# It msut be checked beforehand that the path is indeed linear
|
175
|
+
#
|
176
|
+
# @param [Integer] row
|
177
|
+
# @param [Integer] col
|
178
|
+
# @param [Integer] end_row
|
179
|
+
# @param [Integer] end_col
|
180
|
+
#
|
181
|
+
# @return [Boolean] true if there exists a clear path
|
182
|
+
def clear_path?(row, col, end_row, end_col)
|
183
|
+
current_tile = get_piece_at([row, col])
|
184
|
+
|
185
|
+
if (row == end_row) && (col == end_col)
|
186
|
+
current_tile.nil? || (current_tile.owner != @owner)
|
187
|
+
elsif current_tile.nil? || current_tile.equal?(self)
|
188
|
+
next_row, next_col = next_cell(row, col, end_row, end_col)
|
189
|
+
clear_path?(next_row, next_col, end_row, end_col)
|
190
|
+
else
|
191
|
+
false
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Check a single row for consecutive pieces
|
196
|
+
#
|
197
|
+
# @param [Integer] num the number of consecutive pieces to check for
|
198
|
+
# @param [Array] array the row to check in
|
199
|
+
#
|
200
|
+
# @return [Boolean] true if there are at least num number of consecutive
|
201
|
+
# elements
|
72
202
|
def row_consecutive?(num, array)
|
73
|
-
|
74
|
-
elem_counts.
|
203
|
+
chunked_elems = array.chunk { |elem| elem }
|
204
|
+
elem_counts = chunked_elems.map { |elem, elems| [elem, elems.length] }
|
205
|
+
elem_counts.any? { |elem, count| count >= num && elem }
|
75
206
|
end
|
76
207
|
|
208
|
+
# Align the diagonals of a board. Must be called once on the original board
|
209
|
+
# and once more on the transposed board to check for both directions of
|
210
|
+
# diagonals
|
211
|
+
#
|
212
|
+
# @param [Array<Array>] board the board to align
|
213
|
+
#
|
214
|
+
# @return [Array<Array>] new board with the diagonals aligned
|
77
215
|
def align_diagonal(board)
|
78
216
|
board.map.with_index do |row, idx|
|
79
217
|
left_filler = Array.new(board.length - 1 - idx, nil)
|
@@ -82,17 +220,36 @@ module Boards
|
|
82
220
|
end
|
83
221
|
end
|
84
222
|
|
223
|
+
# Format a single row for String representation
|
224
|
+
#
|
225
|
+
# @param [Array] row the row to turn into a String
|
226
|
+
#
|
227
|
+
# @return [String] a String representation of the row
|
85
228
|
def format_row(row)
|
86
229
|
row.map { |elem| "[#{elem.nil? ? ' ' : elem}]" }.join
|
87
230
|
end
|
88
231
|
|
89
|
-
|
232
|
+
# Format the column numbering of a board
|
233
|
+
#
|
234
|
+
# @param [String] column_spacer the spacer to use between the indices
|
235
|
+
#
|
236
|
+
# @return [String] a String representation of the row
|
237
|
+
def format_col_numbering(column_spacer)
|
90
238
|
@board[0].each_index.reduce(column_spacer) { |str, idx| str + " #{idx} " }
|
91
239
|
end
|
92
240
|
end
|
93
241
|
|
94
242
|
# Boards with movable pieces
|
95
243
|
module MovablePiece
|
244
|
+
# Move a piece from at a start location to an end location. Should be called
|
245
|
+
# after checking that the movement is valid board and game logic wise.
|
246
|
+
#
|
247
|
+
# @param [<Type>] start_location the starting location of the piece
|
248
|
+
# @param [<Type>] end_location the destination of the piece
|
249
|
+
# @param [<Type>] set_start_to what to place at the start location. defaults
|
250
|
+
# to nil
|
251
|
+
#
|
252
|
+
# @return [Piece] the piece at the destination
|
96
253
|
def move_piece(start_location, end_location, set_start_to: nil)
|
97
254
|
piece = get_piece_at(start_location)
|
98
255
|
set_piece_at(start_location, set_start_to)
|
@@ -2,52 +2,44 @@
|
|
2
2
|
|
3
3
|
# All classes in this script are intended to be abstract, meaning they should
|
4
4
|
# not be called on their own.
|
5
|
-
|
6
5
|
module BoardgameEngine
|
7
|
-
# Class representing a physical board comprised of a grid in a board game. It
|
8
|
-
# acts as both the View and Model if the project were to be compared to a MVC
|
9
|
-
# model. It plays both roles as the board in a board game not only stores data,
|
10
|
-
# but also IS the data that must be shown to the players.
|
11
|
-
class Board
|
12
|
-
attr_reader :board
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def spot_playable?(piece, row, col)
|
17
|
-
piece.possible_moves.include? [row, col]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
6
|
# Class representing a player in a game
|
22
7
|
class Player
|
23
8
|
attr_reader :name
|
24
9
|
|
25
10
|
# Creates a player object with the given name
|
26
|
-
#
|
11
|
+
#
|
12
|
+
# @param [String] name the name of the player
|
27
13
|
def initialize(name)
|
28
14
|
@name = name
|
29
15
|
end
|
30
16
|
|
17
|
+
# String representation of player
|
18
|
+
#
|
19
|
+
# @return [String] the name of the player
|
31
20
|
def to_s
|
32
21
|
@name.to_s
|
33
22
|
end
|
34
23
|
end
|
35
24
|
|
36
|
-
# Class representing
|
37
|
-
|
25
|
+
# Class representing the game loop. It contains the tutorial sequence and
|
26
|
+
# information about the core gameplay loop
|
27
|
+
class Game
|
28
|
+
PLAY_INSTRUCTIONS = ''
|
38
29
|
EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
|
39
30
|
"tutorial. Type in 'exit' anytime to exit the game fully"
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
31
|
+
GAME_NAME = 'Boardgame'
|
32
|
+
NUM_PLAYERS = 2
|
33
|
+
|
34
|
+
# Begins a round of the Game
|
35
|
+
#
|
36
|
+
# @param [Boolean] do_onboarding optional argument on whether to do
|
37
|
+
# onboarding
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
def self.start(do_onboarding: true)
|
49
41
|
names = []
|
50
|
-
|
42
|
+
self::NUM_PLAYERS.times do |i|
|
51
43
|
puts "What is Player #{i}'s name?"
|
52
44
|
names.push(gets.chomp)
|
53
45
|
end
|
@@ -57,13 +49,13 @@ module BoardgameEngine
|
|
57
49
|
puts "Welcome to #{@game}!"
|
58
50
|
@game.onboarding if do_onboarding
|
59
51
|
puts "Starting #{@game}..."
|
60
|
-
@game.
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_s(game_name = 'boardgame')
|
64
|
-
"#{game_name} between #{@players.join(', ')}"
|
52
|
+
@game.play
|
65
53
|
end
|
66
54
|
|
55
|
+
# Execute onboarding sequence where the player is asked if they want a
|
56
|
+
# tutorial
|
57
|
+
#
|
58
|
+
# @return [void]
|
67
59
|
def onboarding
|
68
60
|
puts "Would you like a tutorial on how to play on this program? \n(y, n)"
|
69
61
|
|
@@ -78,28 +70,60 @@ module BoardgameEngine
|
|
78
70
|
end
|
79
71
|
end
|
80
72
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
input = gets.chomp
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def start(turn = @players[0])
|
73
|
+
# Play the game
|
74
|
+
#
|
75
|
+
# @param [Player] turn the player who is going first
|
76
|
+
#
|
77
|
+
# @return [void]
|
78
|
+
def play(turn = @players[0])
|
92
79
|
@turn = turn
|
93
80
|
@board.display
|
94
81
|
until @winner
|
95
82
|
play_turn
|
96
83
|
@board.display
|
84
|
+
change_turn
|
97
85
|
end
|
98
86
|
puts "#{@winner} wins!"
|
99
87
|
end
|
100
88
|
|
89
|
+
# String representation of the game
|
90
|
+
#
|
91
|
+
# @return [String] <description>
|
92
|
+
def to_s
|
93
|
+
"#{self.class::GAME_NAME} between #{@players.join(', ')}"
|
94
|
+
end
|
95
|
+
|
101
96
|
protected
|
102
97
|
|
98
|
+
# Constructor for a board game. Kept private so that an object of just class
|
99
|
+
# BoardGame cannot be instantiated without being inherited. It essentially
|
100
|
+
# keeps it as an abstract class
|
101
|
+
#
|
102
|
+
# @param [Board] board the board to play on
|
103
|
+
# @param [Array<String>] names the names of the players
|
104
|
+
def initialize(board, names)
|
105
|
+
@players = names.map { |name| Player.new name }
|
106
|
+
@board = setup_board(board)
|
107
|
+
@winner = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Run tutorial for the game
|
111
|
+
def tutorial
|
112
|
+
puts self.class::PLAY_INSTRUCTIONS + self.class::EXIT_INSTRUCTIONS
|
113
|
+
input = gets.chomp
|
114
|
+
until input == 'back'
|
115
|
+
exit if input == 'exit'
|
116
|
+
puts @board.valid_board_input?(input) ? 'Valid input' : 'Invalid input'
|
117
|
+
input = gets.chomp
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Prompts a user for a board input until a proper input is received
|
122
|
+
#
|
123
|
+
# @param [Array<String>] special_commands a list of commands that are not
|
124
|
+
# valid board input but are valid commands like "back"
|
125
|
+
#
|
126
|
+
# @return [String] a valid input
|
103
127
|
def get_valid_board_input(special_commands = [])
|
104
128
|
input = gets.chomp
|
105
129
|
|
@@ -113,6 +137,11 @@ module BoardgameEngine
|
|
113
137
|
input
|
114
138
|
end
|
115
139
|
|
140
|
+
# Setup the board
|
141
|
+
#
|
142
|
+
# @param [Class] board the class of the board to be used in the game
|
143
|
+
#
|
144
|
+
# @return [Board] a new board
|
116
145
|
def setup_board(board)
|
117
146
|
board.new
|
118
147
|
end
|
@@ -135,44 +164,5 @@ module BoardgameEngine
|
|
135
164
|
def to_s
|
136
165
|
@name.to_s
|
137
166
|
end
|
138
|
-
|
139
|
-
protected
|
140
|
-
|
141
|
-
def clear_diag_path?(row, col, end_row, end_col, board)
|
142
|
-
((end_row - row).abs == (end_col - col).abs) \
|
143
|
-
&& clear_path?(row, col, end_row, end_col, board)
|
144
|
-
end
|
145
|
-
|
146
|
-
def clear_horz_path?(row, col, end_row, end_col, board)
|
147
|
-
(end_row == row) && clear_path?(row, col, end_row, end_col, board)
|
148
|
-
end
|
149
|
-
|
150
|
-
def clear_vert_path?(row, col, end_row, end_col, board)
|
151
|
-
(end_col == col) && clear_path?(row, col, end_row, end_col, board)
|
152
|
-
end
|
153
|
-
|
154
|
-
private
|
155
|
-
|
156
|
-
def next_cell(row, col, end_row, end_col)
|
157
|
-
row_move = 0
|
158
|
-
col_move = 0
|
159
|
-
|
160
|
-
col_move = (end_col - col) / (end_col - col).abs if end_col != col
|
161
|
-
row_move = (end_row - row) / (end_row - row).abs if end_row != row
|
162
|
-
|
163
|
-
[row + row_move, col + col_move]
|
164
|
-
end
|
165
|
-
|
166
|
-
def clear_path?(row, col, end_row, end_col, board)
|
167
|
-
current_tile = board.dig(row, col)
|
168
|
-
if (row == end_row) && (col == end_col)
|
169
|
-
current_tile.nil? || (current_tile.owner != @owner)
|
170
|
-
elsif current_tile.nil? || current_tile.equal?(self)
|
171
|
-
next_row, next_col = next_cell(row, col, end_row, end_col)
|
172
|
-
clear_path?(next_row, next_col, end_row, end_col, board)
|
173
|
-
else
|
174
|
-
false
|
175
|
-
end
|
176
|
-
end
|
177
167
|
end
|
178
168
|
end
|
@@ -4,8 +4,47 @@ require 'boardgame_engine/boardgame'
|
|
4
4
|
require 'boardgame_engine/game_modules'
|
5
5
|
require 'boardgame_engine/board_modules'
|
6
6
|
|
7
|
-
module
|
8
|
-
|
7
|
+
# module for playing a game of chess
|
8
|
+
module Chess
|
9
|
+
# Class for a game of chess
|
10
|
+
class Game < BoardgameEngine::Game
|
11
|
+
include Games::CyclicalTurn
|
12
|
+
include Games::MovablePiece
|
13
|
+
|
14
|
+
PLAY_INSTRUCTIONS = 'You can select spots on the board by inputting the ' \
|
15
|
+
"row and column with a comma in between. See example below\n1, 1\n"
|
16
|
+
GAME_NAME = 'Chess'
|
17
|
+
NUM_PLAYERS = 2
|
18
|
+
|
19
|
+
def initialize(names)
|
20
|
+
super(Board, names)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Check whether a piece can be selected by the player
|
26
|
+
#
|
27
|
+
# @param [Piece] piece the piece being selected
|
28
|
+
#
|
29
|
+
# @return [Boolean] whether the piece exists and if so, if it belongs to
|
30
|
+
# the player currently going
|
31
|
+
def valid_piece?(piece)
|
32
|
+
piece && piece.owner == @turn
|
33
|
+
end
|
34
|
+
|
35
|
+
def play_turn
|
36
|
+
puts "#{@turn}'s turn"
|
37
|
+
killed = play_move
|
38
|
+
@winner = @turn if killed.is_a?(King)
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_board(board)
|
42
|
+
board.new(@players[0], @players[1])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Class for a chessboard
|
47
|
+
class Board
|
9
48
|
include Boards::Grid
|
10
49
|
include Boards::MovablePiece
|
11
50
|
|
@@ -41,38 +80,6 @@ module SampleChess
|
|
41
80
|
end
|
42
81
|
end
|
43
82
|
|
44
|
-
class Chess < BoardgameEngine::Boardgame
|
45
|
-
include Games::CyclicalTurn
|
46
|
-
include Games::MovablePiece
|
47
|
-
|
48
|
-
def initialize(names)
|
49
|
-
@instructions = 'You can select spots on the board by inputting the row' \
|
50
|
-
" and column with a comma in between. See example below\n1, 1\n"
|
51
|
-
super(ChessBoard, @instructions, names)
|
52
|
-
end
|
53
|
-
|
54
|
-
def to_s
|
55
|
-
super('chess')
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def valid_piece?(piece)
|
61
|
-
!piece.nil? && piece.owner == @turn
|
62
|
-
end
|
63
|
-
|
64
|
-
def play_turn
|
65
|
-
puts "#{@turn}'s turn"
|
66
|
-
killed = play_move
|
67
|
-
@winner = @turn if killed.is_a?(King)
|
68
|
-
change_turn
|
69
|
-
end
|
70
|
-
|
71
|
-
def setup_board(board)
|
72
|
-
board.new(@players[0], @players[1])
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
83
|
class Pawn < BoardgameEngine::Piece
|
77
84
|
def initialize(owner, front)
|
78
85
|
super(owner, 'p')
|
@@ -86,7 +93,7 @@ module SampleChess
|
|
86
93
|
# @param [Array<Integer, Integer>] end_location the intended destination
|
87
94
|
# @param [ChessBoard] board the chess board
|
88
95
|
#
|
89
|
-
# @return [
|
96
|
+
# @return [Boolean] whether the pawn can move to the intended destination
|
90
97
|
def valid_move?(start_location, end_location, board)
|
91
98
|
row, col = start_location
|
92
99
|
end_row, end_col = end_location
|
@@ -151,12 +158,9 @@ module SampleChess
|
|
151
158
|
end
|
152
159
|
|
153
160
|
def valid_move?(start_location, end_location, board)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
clear_diag_path?(row, col, end_row, end_col, board) \
|
158
|
-
|| clear_horz_path?(row, col, end_row, end_col, board) \
|
159
|
-
|| clear_vert_path?(row, col, end_row, end_col, board)
|
161
|
+
board.clear_diag_path?(start_location, end_location) \
|
162
|
+
|| board.clear_horz_path?(start_location, end_location) \
|
163
|
+
|| board.clear_vert_path?(start_location, end_location)
|
160
164
|
end
|
161
165
|
end
|
162
166
|
|
@@ -169,8 +173,8 @@ module SampleChess
|
|
169
173
|
row, col = start_location
|
170
174
|
end_row, end_col = end_location
|
171
175
|
|
172
|
-
clear_horz_path?(row, col, end_row, end_col, board) \
|
173
|
-
|| clear_vert_path?(row, col, end_row, end_col, board)
|
176
|
+
board.clear_horz_path?(row, col, end_row, end_col, board) \
|
177
|
+
|| board.clear_vert_path?(row, col, end_row, end_col, board)
|
174
178
|
end
|
175
179
|
end
|
176
180
|
|
@@ -183,7 +187,7 @@ module SampleChess
|
|
183
187
|
row, col = start_location
|
184
188
|
end_row, end_col = end_location
|
185
189
|
|
186
|
-
clear_diag_path?(row, col, end_row, end_col, board)
|
190
|
+
board.clear_diag_path?(row, col, end_row, end_col, board)
|
187
191
|
end
|
188
192
|
end
|
189
193
|
|
@@ -198,9 +202,9 @@ module SampleChess
|
|
198
202
|
|
199
203
|
return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
|
200
204
|
|
201
|
-
clear_diag_path?(row, col, end_row, end_col, board) \
|
202
|
-
|| clear_horz_path?(row, col, end_row, end_col, board) \
|
203
|
-
|| clear_vert_path?(row, col, end_row, end_col, board)
|
205
|
+
board.clear_diag_path?(row, col, end_row, end_col, board) \
|
206
|
+
|| board.clear_horz_path?(row, col, end_row, end_col, board) \
|
207
|
+
|| board.clear_vert_path?(row, col, end_row, end_col, board)
|
204
208
|
end
|
205
209
|
end
|
206
210
|
|
@@ -4,8 +4,33 @@ require 'boardgame_engine/boardgame'
|
|
4
4
|
require 'boardgame_engine/game_modules'
|
5
5
|
require 'boardgame_engine/board_modules'
|
6
6
|
|
7
|
-
module
|
8
|
-
|
7
|
+
# module for playing a game of Connect-4
|
8
|
+
module Connect4
|
9
|
+
# A game of Connect-4
|
10
|
+
class Game < BoardgameEngine::Game
|
11
|
+
include Games::CyclicalTurn
|
12
|
+
|
13
|
+
PLAY_INSTRUCTIONS = 'You can select which column to drop you chip into by' \
|
14
|
+
' typing in the row number.'
|
15
|
+
GAME_NAME = 'Connect-4'
|
16
|
+
NUM_PLAYERS = 2
|
17
|
+
|
18
|
+
def initialize(names)
|
19
|
+
super(Board, names)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def play_turn
|
25
|
+
puts "#{@turn}'s turn\nChoose a column to drop your chip in"
|
26
|
+
col = get_valid_board_input.to_i
|
27
|
+
@board.drop_chip(col, @turn)
|
28
|
+
@winner = @turn if @board.consecutive? 4
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# The Connect-4 board
|
33
|
+
class Board
|
9
34
|
include Boards::Grid
|
10
35
|
|
11
36
|
def initialize
|
@@ -16,6 +41,12 @@ module SampleConnect4
|
|
16
41
|
super(show_col: true)
|
17
42
|
end
|
18
43
|
|
44
|
+
# Drop a chip from a certain player into a given column
|
45
|
+
#
|
46
|
+
# @param [Integer] col The column chosen by the player
|
47
|
+
# @param [Player] owner the player dropping the chip
|
48
|
+
#
|
49
|
+
# @return [void]
|
19
50
|
def drop_chip(col, owner)
|
20
51
|
@board.reverse_each do |row|
|
21
52
|
if row[col].nil?
|
@@ -29,29 +60,4 @@ module SampleConnect4
|
|
29
60
|
super(input, only_col: true)
|
30
61
|
end
|
31
62
|
end
|
32
|
-
|
33
|
-
class Connect4 < BoardgameEngine::Boardgame
|
34
|
-
include Games::CyclicalTurn
|
35
|
-
|
36
|
-
@instructions = 'You can select which column to drop you chip into by' \
|
37
|
-
' typing in the row number.'
|
38
|
-
|
39
|
-
def initialize(names)
|
40
|
-
super(Connect4Board, @instructions, names)
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_s
|
44
|
-
super('connect-four')
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def play_turn
|
50
|
-
puts "#{@turn}'s turn\nChoose a column to drop your chip in"
|
51
|
-
col = get_valid_board_input.to_i
|
52
|
-
@board.drop_chip(col, @turn)
|
53
|
-
@winner = @turn if @board.consecutive?
|
54
|
-
change_turn
|
55
|
-
end
|
56
|
-
end
|
57
63
|
end
|
@@ -18,12 +18,12 @@ module Games
|
|
18
18
|
# @return [void]
|
19
19
|
def play_move
|
20
20
|
puts 'Select your piece'
|
21
|
-
piece,
|
21
|
+
piece, start_loc = select_piece_from_input
|
22
22
|
puts "Select where to move \"#{piece}\" to. Type \"back\" to reselect piece"
|
23
|
-
|
24
|
-
return play_move if
|
23
|
+
end_loc = select_destination(piece, start_loc)
|
24
|
+
return play_move if end_loc == 'back'
|
25
25
|
|
26
|
-
@board.move_piece(
|
26
|
+
@board.move_piece(start_loc, end_loc)
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
@@ -45,7 +45,7 @@ module Games
|
|
45
45
|
|
46
46
|
# Select a location on the board to move to from user input
|
47
47
|
#
|
48
|
-
# @param [
|
48
|
+
# @param [Piece] piece The piece to move
|
49
49
|
# @param [<Type>] start_location the starting location of the piece
|
50
50
|
#
|
51
51
|
# @return [<Type>] the location of the piece or the string literal 'back'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boardgame_engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- FortHoney
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|