boardgame_engine 0.1.1 → 0.2.1

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