boardgame_engine 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|