boardgame_engine 0.1.1 → 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.
@@ -1,191 +1,148 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame"
4
- require_relative "multiplayergame"
3
+ require 'boardgame_engine/boardgame'
4
+ require 'boardgame_engine/game_modules'
5
+ require 'boardgame_engine/board_modules'
5
6
 
6
- module SampleChess
7
- class ChessBoard < BoardgameEngine::Board
8
- attr_reader :board
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
9
13
 
10
- def initialize(player1, player2)
11
- super(8, 8)
12
- setup(player1, player2)
13
- end
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
14
18
 
15
- def display
16
- super(show_row: true, show_col: true)
19
+ def initialize(names)
20
+ super(Board, names)
17
21
  end
18
22
 
19
23
  private
20
24
 
21
- def setup(player1, player2)
22
- set_pawns(player1, player2)
23
- set_non_pawns(player1, player2)
24
- end
25
-
26
- def set_pawns(player1, player2)
27
- @board[1] = @board[1].map { Pawn.new(player1, 1) }
28
- @board[-2] = @board[-2].map { Pawn.new(player2, -1) }
29
- end
30
-
31
- def set_non_pawns(player1, player2)
32
- pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
33
- pieces.each_with_index do |piece, idx|
34
- @board[0][idx] = piece.new(player1)
35
- @board[7][7 - idx] = piece.new(player2)
36
- end
37
- end
38
- end
39
-
40
- class Chess < BoardgameEngine::Boardgame
41
- include TwoPlayers
42
-
43
- def initialize(name1 = "Player 1", name2 = "Player 2")
44
- @instructions = "You can select spots on the board by inputting the row " \
45
- "and column with a comma in between. See example below\n1, 1\n"
46
- super(ChessBoard, @instructions, name1, name2)
47
- end
48
-
49
- def to_s
50
- super("chess")
51
- end
52
-
53
- private
54
-
55
- def valid_input?(input)
56
- coords = input.split(",")
57
- coords.all? { |c| c.match?(/[[:digit:]]/) && c.to_i.between?(0, 7) }
58
- end
59
-
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
60
31
  def valid_piece?(piece)
61
- !piece.nil? && piece.owner == @turn
62
- end
63
-
64
- def select_piece
65
- input = proper_format_input
66
- input.split(",").map(&:to_i) => [row, col]
67
- if valid_piece? @board.board.dig(row, col)
68
- [row, col]
69
- else
70
- puts "Invalid piece. Try again"
71
- select_piece
72
- end
73
- end
74
-
75
- def select_destination(piece, row, col)
76
- input = proper_format_input(["back"])
77
- return "back" if input == "back"
78
-
79
- input.split(",").map(&:to_i) => [end_row, end_col]
80
- if piece.valid_move?(row, col, end_row, end_col, @board.board)
81
- [end_row, end_col]
82
- else
83
- puts "Invalid destination. Try again"
84
- select_destination(piece, row, col)
85
- end
32
+ piece && piece.owner == @turn
86
33
  end
87
34
 
88
35
  def play_turn
89
- puts "#{@turn}'s turn\nSelect your piece"
90
- select_piece => [row, col]
91
- piece = @board.board[row][col]
92
- puts "Select where to move #{piece} to. Type back to reselect piece"
93
- dest = select_destination(piece, row, col)
94
- return if dest == "back"
95
-
96
- killed = @board.move_piece(row, col, dest[0], dest[1])
97
- @winner = piece.owner if killed.is_a?(King)
98
- change_turn
36
+ puts "#{@turn}'s turn"
37
+ killed = play_move
38
+ @winner = @turn if killed.is_a?(King)
99
39
  end
100
40
 
101
41
  def setup_board(board)
102
- board.new(@player1, @player2)
42
+ board.new(@players[0], @players[1])
103
43
  end
104
44
  end
105
45
 
106
- class ChessPiece
107
- attr_accessor :status
108
- attr_reader :owner
109
-
110
- def initialize(owner, name)
111
- @kill_log = []
112
- @status = "alive"
113
- @owner = owner
114
- @name = name
115
- end
116
-
117
- def kill(other)
118
- @kill_log.push(other)
119
- other.status = "dead"
120
- end
121
-
122
- def to_s
123
- @name.to_s
124
- end
125
-
126
- protected
46
+ # Class for a chessboard
47
+ class Board
48
+ include Boards::Grid
49
+ include Boards::MovablePiece
127
50
 
128
- def valid_diag_move?(row, col, end_row, end_col, board)
129
- ((end_row - row).abs == (end_col - col).abs) \
130
- && clear_path?(row, col, end_row, end_col, board)
131
- end
51
+ attr_reader :board
132
52
 
133
- def valid_horz_move?(row, col, end_row, end_col, board)
134
- (end_row == row) && clear_path?(row, col, end_row, end_col, board)
53
+ def initialize(player1, player2)
54
+ @board = generate_board(8, 8)
55
+ setup(player1, player2)
135
56
  end
136
57
 
137
- def valid_vert_move?(row, col, end_row, end_col, board)
138
- (end_col == col) && clear_path?(row, col, end_row, end_col, board)
58
+ def display
59
+ super(show_row: true, show_col: true)
139
60
  end
140
61
 
141
62
  private
142
63
 
143
- def next_cell(row, col, end_row, end_col)
144
- row_move = 0
145
- col_move = 0
146
-
147
- col_move = (end_col - col) / (end_col - col).abs if end_col != col
148
- row_move = (end_row - row) / (end_row - row).abs if end_row != row
64
+ def setup(player1, player2)
65
+ set_pawns(player1, player2)
66
+ set_non_pawns(player1, player2)
67
+ end
149
68
 
150
- [row + row_move, col + col_move]
69
+ def set_pawns(player1, player2)
70
+ @board[1] = @board[1].map { Pawn.new(player1, 1) }
71
+ @board[-2] = @board[-2].map { Pawn.new(player2, -1) }
151
72
  end
152
73
 
153
- def clear_path?(row, col, end_row, end_col, board)
154
- current_tile = board.dig(row, col)
155
- if (row == end_row) && (col == end_col)
156
- current_tile.nil? || (current_tile.owner != @owner)
157
- elsif current_tile.nil? || current_tile.equal?(self)
158
- next_cell(row, col, end_row, end_col) => [next_row, next_col]
159
- clear_path?(next_row, next_col, end_row, end_col, board)
160
- else
161
- false
74
+ def set_non_pawns(player1, player2)
75
+ pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
76
+ pieces.each_with_index do |piece, idx|
77
+ @board[0][idx] = piece.new(player1)
78
+ @board[7][7 - idx] = piece.new(player2)
162
79
  end
163
80
  end
164
81
  end
165
82
 
166
- class Pawn < ChessPiece
83
+ class Pawn < BoardgameEngine::Piece
167
84
  def initialize(owner, front)
168
- super(owner, "p")
85
+ super(owner, 'p')
169
86
  @first_move = true
170
87
  @front = front
171
88
  end
172
89
 
173
- def valid_move?(row, col, end_row, end_col, board)
90
+ # Check whether the intended destination is a valid destination
91
+ #
92
+ # @param [Array<Integer, Integer>] start_location the start coords
93
+ # @param [Array<Integer, Integer>] end_location the intended destination
94
+ # @param [ChessBoard] board the chess board
95
+ #
96
+ # @return [Boolean] whether the pawn can move to the intended destination
97
+ def valid_move?(start_location, end_location, board)
98
+ row, col = start_location
99
+ end_row, end_col = end_location
100
+
101
+ # Checks if moving 1 (or 2 if its the first move) cell forward
174
102
  return false unless valid_forward_move?(row, end_row)
175
103
 
176
- if col == end_col # only forward
177
- valid_dest = board.dig(end_row, end_col).nil?
178
- @first_move = false if valid_dest
179
- return valid_dest
180
- elsif (col - end_col).abs == 1 # diagonal movement
181
- other_piece = board.dig(end_row, end_col)
182
- return other_piece && (other_piece.owner != @owner)
104
+ if col == end_col
105
+ valid_line_move?(end_row, end_col, board)
106
+ elsif (col - end_col).abs == 1 && (row + @front == end_row)
107
+ valid_diag_move?(end_row, end_col, board)
108
+ else
109
+ false
183
110
  end
184
- false
185
111
  end
186
112
 
187
113
  private
188
114
 
115
+ # Check if the pawn can move in a straight line forward to the specified
116
+ # coord
117
+ #
118
+ # @param [Integer] end_row the destination row number
119
+ # @param [Integer] end_col the destination column number
120
+ #
121
+ # @return [Boolean] whether the pawn can move or not
122
+ def valid_line_move?(end_row, end_col, board)
123
+ is_valid_dest = board.get_piece_at([end_row, end_col]).nil?
124
+ @first_move = false if is_valid_dest
125
+ is_valid_dest
126
+ end
127
+
128
+ # Check if the pawn can move in a diagonal line to the specified coord
129
+ #
130
+ # @param [Integer] end_row the destination row number
131
+ # @param [Integer] end_col the destination column number
132
+ #
133
+ # @return [Boolean] whether the pawn can move or not
134
+ def valid_diag_move?(end_row, end_col, board)
135
+ other_piece = board.get_piece_at([end_row, end_col])
136
+ @first_move = false if other_piece
137
+ other_piece && (other_piece.owner != @owner)
138
+ end
139
+
140
+ # Check if the pawn movement is valid row-wise
141
+ #
142
+ # @param [Integer] row the row the pawn starts from
143
+ # @param [Integer] end_row the destination row
144
+ #
145
+ # @return [Boolean] whether the pawn's row movement is valid numerically
189
146
  def valid_forward_move?(row, end_row)
190
147
  if @first_move
191
148
  (row + @front * 2 == end_row) || (row + @front == end_row)
@@ -195,60 +152,72 @@ module SampleChess
195
152
  end
196
153
  end
197
154
 
198
- class Queen < ChessPiece
155
+ class Queen < BoardgameEngine::Piece
199
156
  def initialize(owner)
200
- super(owner, "Q")
157
+ super(owner, 'Q')
201
158
  end
202
159
 
203
- def valid_move?(row, col, end_row, end_col, board)
204
- valid_diag_move?(row, col, end_row, end_col, board) \
205
- || valid_horz_move?(row, col, end_row, end_col, board) \
206
- || valid_vert_move?(row, col, end_row, end_col, board)
160
+ def valid_move?(start_location, end_location, 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)
207
164
  end
208
165
  end
209
166
 
210
- class Rook < ChessPiece
167
+ class Rook < BoardgameEngine::Piece
211
168
  def initialize(owner)
212
- super(owner, "R")
169
+ super(owner, 'R')
213
170
  end
214
171
 
215
- def valid_move?(row, col, end_row, end_col, board)
216
- valid_horz_move?(row, col, end_row, end_col, board) \
217
- || valid_vert_move?(row, col, end_row, end_col, board)
172
+ def valid_move?(start_location, end_location, board)
173
+ row, col = start_location
174
+ end_row, end_col = end_location
175
+
176
+ board.clear_horz_path?(row, col, end_row, end_col, board) \
177
+ || board.clear_vert_path?(row, col, end_row, end_col, board)
218
178
  end
219
179
  end
220
180
 
221
- class Bishop < ChessPiece
181
+ class Bishop < BoardgameEngine::Piece
222
182
  def initialize(owner)
223
- super(owner, "B")
183
+ super(owner, 'B')
224
184
  end
225
185
 
226
- def valid_move?(row, col, end_row, end_col, board)
227
- valid_diag_move?(row, col, end_row, end_col, board)
186
+ def valid_move?(start_location, end_location, board)
187
+ row, col = start_location
188
+ end_row, end_col = end_location
189
+
190
+ board.clear_diag_path?(row, col, end_row, end_col, board)
228
191
  end
229
192
  end
230
193
 
231
- class King < ChessPiece
194
+ class King < BoardgameEngine::Piece
232
195
  def initialize(owner)
233
- super(owner, "K")
196
+ super(owner, 'K')
234
197
  end
235
198
 
236
- def valid_move?(row, col, end_row, end_col, board)
199
+ def valid_move?(start_location, end_location, board)
200
+ row, col = start_location
201
+ end_row, end_col = end_location
202
+
237
203
  return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
238
204
 
239
- valid_diag_move?(row, col, end_row, end_col, board) \
240
- || valid_horz_move?(row, col, end_row, end_col, board) \
241
- || valid_vert_move?(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)
242
208
  end
243
209
  end
244
210
 
245
- class Knight < ChessPiece
211
+ class Knight < BoardgameEngine::Piece
246
212
  def initialize(owner)
247
213
  # K was already taken by king, so I had to choose N
248
- super(owner, "N")
214
+ super(owner, 'N')
249
215
  end
250
216
 
251
- def valid_move?(row, col, end_row, end_col, board)
217
+ def valid_move?(start_location, end_location, board)
218
+ row, col = start_location
219
+ end_row, end_col = end_location
220
+
252
221
  within_movement(row, col, end_row, end_col) \
253
222
  && not_occupied(end_row, end_col, board)
254
223
  end
@@ -1,18 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame"
4
- require_relative "multiplayergame"
3
+ require 'boardgame_engine/boardgame'
4
+ require 'boardgame_engine/game_modules'
5
+ require 'boardgame_engine/board_modules'
6
+
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
34
+ include Boards::Grid
5
35
 
6
- module SampleConnect4
7
- class Connect4Board < BoardgameEngine::Board
8
36
  def initialize
9
- super(6, 7)
37
+ @board = generate_board(6, 7)
10
38
  end
11
39
 
12
40
  def display
13
41
  super(show_col: true)
14
42
  end
15
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]
16
50
  def drop_chip(col, owner)
17
51
  @board.reverse_each do |row|
18
52
  if row[col].nil?
@@ -21,57 +55,9 @@ module SampleConnect4
21
55
  end
22
56
  end
23
57
  end
24
- end
25
-
26
- class Connect4 < BoardgameEngine::Boardgame
27
- include TwoPlayers
28
-
29
- @instructions = "You can select which column to drop you chip into by" \
30
- " typing in the row number."
31
-
32
- def initialize(name1 = "Player 1", name2 = "Player 2")
33
- super(Connect4Board, @instructions, name1, name2)
34
- end
35
58
 
36
- def to_s
37
- super("connect-four")
38
- end
39
-
40
- private
41
-
42
- def valid_input?(input)
43
- input.match?(/[[:digit:]]/) && input.to_i.between?(0, 6)
44
- end
45
-
46
- def play_turn
47
- puts "#{@turn}'s turn. Choose a column to drop your chip in"
48
- col = proper_format_input.to_i
49
- @board.drop_chip(col, @turn)
50
- @winner = @turn if win?
51
- change_turn
52
- end
53
-
54
- def win?
55
- [@board.board,
56
- @board.board.transpose,
57
- align_diagonally(@board.board),
58
- align_diagonally(@board.board.transpose)].each do |config|
59
- config.each { |direction| return true if four_in_a_row? direction }
60
- end
61
- false
62
- end
63
-
64
- def four_in_a_row?(row)
65
- counts = row.chunk { |x| x }.map { |x, xs| [x, xs.length] }
66
- return true if counts.any? { |x, count| count > 3 && !x.nil? }
67
- end
68
-
69
- def align_diagonally(board)
70
- board.map.with_index do |row, idx|
71
- left_filler = Array.new(board.length - 1 - idx, nil)
72
- right_filler = Array.new(idx, nil)
73
- left_filler + row + right_filler
74
- end
59
+ def valid_board_input?(input)
60
+ super(input, only_col: true)
75
61
  end
76
62
  end
77
63
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module for different game rules
4
+ module Games
5
+ # Module for games with a cyclical turn system
6
+ module CyclicalTurn
7
+ # Changes the turn to the next player
8
+ def change_turn
9
+ idx = @players.find_index(@turn)
10
+ @turn = @players[(idx + 1) % @players.length]
11
+ end
12
+ end
13
+
14
+ # Module for games where game pieces are moved
15
+ module MovablePiece
16
+ # Execute a single move according to player input
17
+ #
18
+ # @return [void]
19
+ def play_move
20
+ puts 'Select your piece'
21
+ piece, start_loc = select_piece_from_input
22
+ puts "Select where to move \"#{piece}\" to. Type \"back\" to reselect piece"
23
+ end_loc = select_destination(piece, start_loc)
24
+ return play_move if end_loc == 'back'
25
+
26
+ @board.move_piece(start_loc, end_loc)
27
+ end
28
+
29
+ private
30
+
31
+ # Select a piece on the board from user input
32
+ #
33
+ # @return [Array] the piece, and the piece's location
34
+ def select_piece_from_input
35
+ location = @board.parse_input(get_valid_board_input)
36
+ piece = @board.get_piece_at(location)
37
+
38
+ if valid_piece?(piece)
39
+ [piece, location]
40
+ else
41
+ puts 'Invalid piece. Try again'
42
+ select_piece_from_input
43
+ end
44
+ end
45
+
46
+ # Select a location on the board to move to from user input
47
+ #
48
+ # @param [Piece] piece The piece to move
49
+ # @param [<Type>] start_location the starting location of the piece
50
+ #
51
+ # @return [<Type>] the location of the piece or the string literal 'back'
52
+ def select_destination(piece, start_location)
53
+ input = get_valid_board_input(['back'])
54
+ return 'back' if input == 'back'
55
+
56
+ end_location = @board.parse_input(input)
57
+ if piece.valid_move?(start_location, end_location, @board)
58
+ end_location
59
+ else
60
+ puts 'Invalid destination. Try again'
61
+ select_destination(piece, start_location)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,5 +1,7 @@
1
- require_relative "chess"
2
- require_relative "connect4"
1
+ # frozen_string_literal: true
2
+
3
+ require 'boardgame_engine/chess'
4
+ require 'boardgame_engine/connect4'
3
5
 
4
6
  module SampleGames
5
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BoardgameEngine
4
- VERSION = "0.1.1"
4
+ VERSION = '0.2.1'
5
5
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame_engine/version"
3
+ require 'boardgame_engine/version'
4
+ require 'boardgame_engine/boardgame'
5
+ require 'boardgame_engine/game_modules'
6
+ require 'boardgame_engine/board_modules'
7
+ require 'boardgame_engine/sample_games'
4
8
 
5
- require "boardgame_engine/boardgame"
6
- require "boardgame_engine/multiplayergame"
7
- require "boardgame_engine/sample_games"
8
-
9
- module BoardgameEngine
10
- class Error < StandardError; end
11
- end
9
+ module BoardgameEngine end
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.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - FortHoney
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2023-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -24,7 +24,7 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
- description:
27
+ description:
28
28
  email:
29
29
  - castlehoneyjung@gmail.com
30
30
  executables: []
@@ -39,10 +39,11 @@ files:
39
39
  - Rakefile
40
40
  - boardgame_engine.gemspec
41
41
  - lib/boardgame_engine.rb
42
+ - lib/boardgame_engine/board_modules.rb
42
43
  - lib/boardgame_engine/boardgame.rb
43
44
  - lib/boardgame_engine/chess.rb
44
45
  - lib/boardgame_engine/connect4.rb
45
- - lib/boardgame_engine/multiplayergame.rb
46
+ - lib/boardgame_engine/game_modules.rb
46
47
  - lib/boardgame_engine/sample_games.rb
47
48
  - lib/boardgame_engine/version.rb
48
49
  - sig/boardgame_engine.rbs
@@ -52,7 +53,7 @@ metadata:
52
53
  homepage_uri: https://github.com/Forthoney/boardgame_engine
53
54
  source_code_uri: https://github.com/Forthoney/boardgame_engine
54
55
  changelog_uri: https://github.com/Forthoney/boardgame_engine/blob/main/CHANGELOG.md
55
- post_install_message:
56
+ post_install_message:
56
57
  rdoc_options: []
57
58
  require_paths:
58
59
  - lib
@@ -60,15 +61,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
61
  requirements:
61
62
  - - ">="
62
63
  - !ruby/object:Gem::Version
63
- version: 2.7.0
64
+ version: 3.0.0
64
65
  required_rubygems_version: !ruby/object:Gem::Requirement
65
66
  requirements:
66
67
  - - ">="
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0'
69
70
  requirements: []
70
- rubygems_version: 3.3.7
71
- signing_key:
71
+ rubygems_version: 3.4.4
72
+ signing_key:
72
73
  specification_version: 4
73
74
  summary: A gem that makes digitizing/creating board games to be played on the terminal
74
75
  quick and easy.
@@ -1,5 +0,0 @@
1
- module TwoPlayers
2
- def change_turn
3
- @turn = @turn == @player1 ? @player2 : @player1
4
- end
5
- end