boardgame_engine 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e57f58857092b8b08b48de9bafcf09a08297302fcbe695afde86125615d07f1
4
- data.tar.gz: c7de6b02681a7b7def284e31186fc61281fe9ae41dfe75464dc0a5e83b5bde9a
3
+ metadata.gz: 7129074317f7ea44e65ad18e5952e2401e47be99186e26dd42b0c083ebc91797
4
+ data.tar.gz: f87aebcb6502cbfd9d8ee04e60b775f1497e09527863f3f820a1edb202297330
5
5
  SHA512:
6
- metadata.gz: 2155a5ca5a0c871e277a7a0e4beb9ea25b86a17ada30481d4f37b6104385c300095ef61cd694b860cadc72ab9230f6f7d199934a15c96de3278b22756174b07c
7
- data.tar.gz: accc0b89071a919f466be01d1ecd68bc4f2fe3096817ff6340b161e731beabe1e05142399af352922cf7ac521ee95711fc6681068cf5b083c3e7ba9fb51ca02f
6
+ metadata.gz: 1c1ab01918757d936b1ee97cf8d75bbf19a300e71450b0c1896fcd9cdda0f804b25324b71ef01929d8d231f96ef7af15ec023690dc52d5a738e8b363da43c4f8
7
+ data.tar.gz: b10198cde214fe10e7a861a27419e6235b25a84ef1fac0992de66e0297668da8e3f600effaae0049c12a5343e761dccfb623594bc2621d40b24063e8b8d82cb0
@@ -3,142 +3,144 @@
3
3
  # All classes in this script are intended to be abstract, meaning they should
4
4
  # not be called on their own.
5
5
 
6
- # Class representing a physical board comprised of a grid in a board game. It
7
- # acts as both the View and Model if the project were to be compared to a MVC
8
- # model. It plays both roles as the board in a board game not only stores data,
9
- # but also IS the data that must be shown to the players.
10
- class Board
11
- attr_reader :board
12
-
13
- def initialize(row, col)
14
- @board = Array.new(row) { Array.new(col) { nil } }
15
- end
16
-
17
- def display(show_row: false, show_col: false)
18
- if show_row
19
- @board.each_with_index { |row, idx| puts("#{idx} " + format_row(row)) }
20
- else
21
- @board.each { |row| puts format_row(row) }
6
+ 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
+ def initialize(row, col)
15
+ @board = Array.new(row) { Array.new(col) { nil } }
22
16
  end
23
- return unless show_col
24
17
 
25
- column_spacer = show_row ? " " : ""
26
- puts(@board[0].each_index.reduce(column_spacer) do |str, idx|
27
- str + " #{idx} "
28
- end)
29
- end
18
+ def display(show_row: false, show_col: false)
19
+ if show_row
20
+ @board.each_with_index { |row, idx| puts("#{idx} " + format_row(row)) }
21
+ else
22
+ @board.each { |row| puts format_row(row) }
23
+ end
24
+ return unless show_col
25
+
26
+ column_spacer = show_row ? " " : ""
27
+ puts(@board[0].each_index.reduce(column_spacer) do |str, idx|
28
+ str + " #{idx} "
29
+ end)
30
+ end
30
31
 
31
- def move_piece(start_row, start_col, end_row, end_col)
32
- piece = @board[start_row][start_col]
33
- @board[start_row][start_col] = nil
34
- destination = @board[end_row][end_col]
35
- @board[end_row][end_col] = piece
32
+ def move_piece(start_row, start_col, end_row, end_col)
33
+ piece = @board[start_row][start_col]
34
+ @board[start_row][start_col] = nil
35
+ destination = @board[end_row][end_col]
36
+ @board[end_row][end_col] = piece
36
37
 
37
- destination
38
- end
38
+ destination
39
+ end
39
40
 
40
- private
41
+ private
41
42
 
42
- def format_row(row)
43
- row.map { |elem| "[#{elem.nil? ? " " : elem}]" }.join
44
- end
43
+ def format_row(row)
44
+ row.map { |elem| "[#{elem.nil? ? " " : elem}]" }.join
45
+ end
45
46
 
46
- def spot_playable?(piece, row, col)
47
- piece.possible_moves.include? [row, col]
47
+ def spot_playable?(piece, row, col)
48
+ piece.possible_moves.include? [row, col]
49
+ end
48
50
  end
49
- end
50
51
 
51
- # Class representing a player in a game
52
- class Player
53
- attr_reader :name
52
+ # Class representing a player in a game
53
+ class Player
54
+ attr_reader :name
54
55
 
55
- def initialize(name)
56
- @name = name
57
- end
56
+ def initialize(name)
57
+ @name = name
58
+ end
58
59
 
59
- def to_s
60
- @name.to_s
60
+ def to_s
61
+ @name.to_s
62
+ end
61
63
  end
62
- end
63
64
 
64
- # Class for running a board game.
65
- class Boardgame
66
- EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
67
- "tutorial. Type in 'exit' anytime to exit the game fully"
68
-
69
- def initialize(board, instructions, name1 = "Player 1", name2 = "Player 2")
70
- @player1 = Player.new(name1)
71
- @player2 = Player.new(name2)
72
- @board = setup_board(board)
73
- @instructions = instructions
74
- @winner = nil
75
- end
65
+ # Class for running a board game.
66
+ class Boardgame
67
+ EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
68
+ "tutorial. Type in 'exit' anytime to exit the game fully"
69
+
70
+ def initialize(board, instructions, name1 = "Player 1", name2 = "Player 2")
71
+ @player1 = Player.new(name1)
72
+ @player2 = Player.new(name2)
73
+ @board = setup_board(board)
74
+ @instructions = instructions
75
+ @winner = nil
76
+ end
76
77
 
77
- def self.play(do_onboarding: true)
78
- puts "What is Player 1's name?"
79
- player1 = gets.chomp
80
- puts "What is Player 2's name?"
81
- player2 = gets.chomp
82
- @game = new(player1, player2)
83
-
84
- puts "Welcome to #{@game}!"
85
- @game.onboarding if do_onboarding
86
- puts "Starting #{@game}..."
87
- @game.start
88
- end
78
+ def self.play(do_onboarding: true)
79
+ puts "What is Player 1's name?"
80
+ player1 = gets.chomp
81
+ puts "What is Player 2's name?"
82
+ player2 = gets.chomp
83
+ @game = new(player1, player2)
84
+
85
+ puts "Welcome to #{@game}!"
86
+ @game.onboarding if do_onboarding
87
+ puts "Starting #{@game}..."
88
+ @game.start
89
+ end
89
90
 
90
- def to_s(game_name = "boardgame")
91
- "#{game_name} between #{@player1} and #{@player2}"
92
- end
91
+ def to_s(game_name = "boardgame")
92
+ "#{game_name} between #{@player1} and #{@player2}"
93
+ end
93
94
 
94
- def onboarding
95
- puts "Would you like a tutorial on how to play on this program? \n(y, n)"
96
- case gets.chomp
97
- when "y"
98
- tutorial
99
- when "n"
100
- puts "Skipping tutorial"
101
- else
102
- puts 'Please answer either "y" or "n"'
103
- onboarding
95
+ def onboarding
96
+ puts "Would you like a tutorial on how to play on this program? \n(y, n)"
97
+ case gets.chomp
98
+ when "y"
99
+ tutorial
100
+ when "n"
101
+ puts "Skipping tutorial"
102
+ else
103
+ puts 'Please answer either "y" or "n"'
104
+ onboarding
105
+ end
104
106
  end
105
- end
106
107
 
107
- def tutorial
108
- puts @instructions + Boardgame::EXIT_INSTRUCTIONS
109
- input = gets.chomp
110
- until input == "back"
111
- exit if input == "exit"
112
- puts valid_input?(input) ? "Valid input!" : "Invalid input"
108
+ def tutorial
109
+ puts @instructions + Boardgame::EXIT_INSTRUCTIONS
113
110
  input = gets.chomp
111
+ until input == "back"
112
+ exit if input == "exit"
113
+ puts valid_input?(input) ? "Valid input!" : "Invalid input"
114
+ input = gets.chomp
115
+ end
114
116
  end
115
- end
116
117
 
117
- def start(turn = @player1)
118
- @turn = turn
119
- @board.display
120
- until @winner
121
- play_turn
118
+ def start(turn = @player1)
119
+ @turn = turn
122
120
  @board.display
121
+ until @winner
122
+ play_turn
123
+ @board.display
124
+ end
125
+ puts "#{@winner} wins!"
123
126
  end
124
- puts "#{@winner} wins!"
125
- end
126
127
 
127
- protected
128
+ protected
128
129
 
129
- def proper_format_input(special_commands = [])
130
- input = gets.chomp
131
- until valid_input?(input)
132
- exit if input == "exit"
133
- return input if special_commands.include?(input)
134
-
135
- puts "Input is in the wrong format or out of bounds. Try again"
130
+ def proper_format_input(special_commands = [])
136
131
  input = gets.chomp
132
+ until valid_input?(input)
133
+ exit if input == "exit"
134
+ return input if special_commands.include?(input)
135
+
136
+ puts "Input is in the wrong format or out of bounds. Try again"
137
+ input = gets.chomp
138
+ end
139
+ input
137
140
  end
138
- input
139
- end
140
141
 
141
- def setup_board(board)
142
- board.new
142
+ def setup_board(board)
143
+ board.new
144
+ end
143
145
  end
144
146
  end
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "boardgame"
4
+ require_relative "multiplayergame"
5
+
6
+ module SampleChess
7
+ class ChessBoard < BoardgameEngine::Board
8
+ attr_reader :board
9
+
10
+ def initialize(player1, player2)
11
+ super(8, 8)
12
+ setup(player1, player2)
13
+ end
14
+
15
+ def display
16
+ super(show_row: true, show_col: true)
17
+ end
18
+
19
+ private
20
+
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
+
60
+ 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
86
+ end
87
+
88
+ 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
99
+ end
100
+
101
+ def setup_board(board)
102
+ board.new(@player1, @player2)
103
+ end
104
+ end
105
+
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
127
+
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
132
+
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)
135
+ end
136
+
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)
139
+ end
140
+
141
+ private
142
+
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
149
+
150
+ [row + row_move, col + col_move]
151
+ end
152
+
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
162
+ end
163
+ end
164
+ end
165
+
166
+ class Pawn < ChessPiece
167
+ def initialize(owner, front)
168
+ super(owner, "p")
169
+ @first_move = true
170
+ @front = front
171
+ end
172
+
173
+ def valid_move?(row, col, end_row, end_col, board)
174
+ return false unless valid_forward_move?(row, end_row)
175
+
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)
183
+ end
184
+ false
185
+ end
186
+
187
+ private
188
+
189
+ def valid_forward_move?(row, end_row)
190
+ if @first_move
191
+ (row + @front * 2 == end_row) || (row + @front == end_row)
192
+ else
193
+ row + @front == end_row
194
+ end
195
+ end
196
+ end
197
+
198
+ class Queen < ChessPiece
199
+ def initialize(owner)
200
+ super(owner, "Q")
201
+ end
202
+
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)
207
+ end
208
+ end
209
+
210
+ class Rook < ChessPiece
211
+ def initialize(owner)
212
+ super(owner, "R")
213
+ end
214
+
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)
218
+ end
219
+ end
220
+
221
+ class Bishop < ChessPiece
222
+ def initialize(owner)
223
+ super(owner, "B")
224
+ end
225
+
226
+ def valid_move?(row, col, end_row, end_col, board)
227
+ valid_diag_move?(row, col, end_row, end_col, board)
228
+ end
229
+ end
230
+
231
+ class King < ChessPiece
232
+ def initialize(owner)
233
+ super(owner, "K")
234
+ end
235
+
236
+ def valid_move?(row, col, end_row, end_col, board)
237
+ return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
238
+
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)
242
+ end
243
+ end
244
+
245
+ class Knight < ChessPiece
246
+ def initialize(owner)
247
+ # K was already taken by king, so I had to choose N
248
+ super(owner, "N")
249
+ end
250
+
251
+ def valid_move?(row, col, end_row, end_col, board)
252
+ within_movement(row, col, end_row, end_col) \
253
+ && not_occupied(end_row, end_col, board)
254
+ end
255
+
256
+ private
257
+
258
+ def within_movement(row, col, end_row, end_col)
259
+ ((row - end_row).abs == 2 and (col - end_col).abs == 1) \
260
+ || ((row - end_row).abs == 1 and (col - end_col).abs == 2)
261
+ end
262
+
263
+ def not_occupied(end_row, end_col, board)
264
+ spot = board.dig(end_row, end_col)
265
+ spot.nil? || spot.owner != @owner
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "boardgame"
4
+ require_relative "multiplayergame"
5
+
6
+ module SampleConnect4
7
+ class Connect4Board < BoardgameEngine::Board
8
+ def initialize
9
+ super(6, 7)
10
+ end
11
+
12
+ def display
13
+ super(show_col: true)
14
+ end
15
+
16
+ def drop_chip(col, owner)
17
+ @board.reverse_each do |row|
18
+ if row[col].nil?
19
+ row[col] = owner
20
+ break
21
+ end
22
+ end
23
+ 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
+
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
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ require_relative "chess"
2
+ require_relative "connect4"
3
+
4
+ module SampleGames
5
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BoardgameEngine
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -2,10 +2,9 @@
2
2
 
3
3
  require_relative "boardgame_engine/version"
4
4
 
5
- require_relative "boardgame_engine/boardgame"
6
- require_relative "boardgame_engine/multiplayergame"
7
- require_relative "boardgame_engine/chess/chess"
8
- require_relative "boardgame_engine/connect4/connect4"
5
+ require "boardgame_engine/boardgame"
6
+ require "boardgame_engine/multiplayergame"
7
+ require "boardgame_engine/sample_games"
9
8
 
10
9
  module BoardgameEngine
11
10
  class Error < StandardError; 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.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - FortHoney
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-05 00:00:00.000000000 Z
11
+ date: 2022-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -40,10 +40,10 @@ files:
40
40
  - boardgame_engine.gemspec
41
41
  - lib/boardgame_engine.rb
42
42
  - lib/boardgame_engine/boardgame.rb
43
- - lib/boardgame_engine/chess/chess.rb
44
- - lib/boardgame_engine/chess/chess_pieces.rb
45
- - lib/boardgame_engine/connect4/connect4.rb
43
+ - lib/boardgame_engine/chess.rb
44
+ - lib/boardgame_engine/connect4.rb
46
45
  - lib/boardgame_engine/multiplayergame.rb
46
+ - lib/boardgame_engine/sample_games.rb
47
47
  - lib/boardgame_engine/version.rb
48
48
  - sig/boardgame_engine.rbs
49
49
  homepage: https://github.com/Forthoney/boardgame_engine
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "./lib/boardgame_engine"
4
- require "./lib/boardgame_engine/chess/chess_pieces"
5
-
6
- class ChessBoard < Board
7
- attr_reader :board
8
-
9
- def initialize(player1, player2)
10
- super(8, 8)
11
- setup(player1, player2)
12
- end
13
-
14
- def display
15
- super(show_row: true, show_col: true)
16
- end
17
-
18
- private
19
-
20
- def setup(player1, player2)
21
- set_pawns(player1, player2)
22
- set_non_pawns(player1, player2)
23
- end
24
-
25
- def set_pawns(player1, player2)
26
- @board[1] = @board[1].map { Pawn.new(player1, 1) }
27
- @board[-2] = @board[-2].map { Pawn.new(player2, -1) }
28
- end
29
-
30
- def set_non_pawns(player1, player2)
31
- pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
32
- pieces.each_with_index do |piece, idx|
33
- @board[0][idx] = piece.new(player1)
34
- @board[7][7 - idx] = piece.new(player2)
35
- end
36
- end
37
- end
38
-
39
- class Chess < Boardgame
40
- include TwoPlayers
41
-
42
- def initialize(name1 = "Player 1", name2 = "Player 2")
43
- @instructions = "You can select spots on the board by inputting the row " \
44
- "and column with a comma in between. See example below\n1, 1\n"
45
- super(ChessBoard, @instructions, name1, name2)
46
- end
47
-
48
- def to_s
49
- super("chess")
50
- end
51
-
52
- private
53
-
54
- def valid_input?(input)
55
- coords = input.split(",")
56
- coords.all? { |c| c.match?(/[[:digit:]]/) && c.to_i.between?(0, 7) }
57
- end
58
-
59
- def valid_piece?(piece)
60
- !piece.nil? && piece.owner == @turn
61
- end
62
-
63
- def select_piece
64
- input = proper_format_input
65
- input.split(",").map(&:to_i) => [row, col]
66
- if valid_piece? @board.board.dig(row, col)
67
- [row, col]
68
- else
69
- puts "Invalid piece. Try again"
70
- select_piece
71
- end
72
- end
73
-
74
- def select_destination(piece, row, col)
75
- input = proper_format_input(["back"])
76
- return "back" if input == "back"
77
-
78
- input.split(",").map(&:to_i) => [end_row, end_col]
79
- if piece.valid_move?(row, col, end_row, end_col, @board.board)
80
- [end_row, end_col]
81
- else
82
- puts "Invalid destination. Try again"
83
- select_destination(piece, row, col)
84
- end
85
- end
86
-
87
- def play_turn
88
- puts "#{@turn}'s turn\nSelect your piece"
89
- select_piece => [row, col]
90
- piece = @board.board[row][col]
91
- puts "Select where to move #{piece} to. Type back to reselect piece"
92
- dest = select_destination(piece, row, col)
93
- return if dest == "back"
94
-
95
- killed = @board.move_piece(row, col, dest[0], dest[1])
96
- @winner = piece.owner if killed.is_a?(King)
97
- change_turn
98
- end
99
-
100
- def setup_board(board)
101
- board.new(@player1, @player2)
102
- end
103
- end
@@ -1,164 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ChessPiece
4
- attr_accessor :status
5
- attr_reader :owner
6
-
7
- def initialize(owner, name)
8
- @kill_log = []
9
- @status = "alive"
10
- @owner = owner
11
- @name = name
12
- end
13
-
14
- def kill(other)
15
- @kill_log.push(other)
16
- other.status = "dead"
17
- end
18
-
19
- def to_s
20
- @name.to_s
21
- end
22
-
23
- protected
24
-
25
- def valid_diag_move?(row, col, end_row, end_col, board)
26
- ((end_row - row).abs == (end_col - col).abs) \
27
- && clear_path?(row, col, end_row, end_col, board)
28
- end
29
-
30
- def valid_horz_move?(row, col, end_row, end_col, board)
31
- (end_row == row) && clear_path?(row, col, end_row, end_col, board)
32
- end
33
-
34
- def valid_vert_move?(row, col, end_row, end_col, board)
35
- (end_col == col) && clear_path?(row, col, end_row, end_col, board)
36
- end
37
-
38
- private
39
-
40
- def next_cell(row, col, end_row, end_col)
41
- row_move = 0
42
- col_move = 0
43
-
44
- col_move = (end_col - col) / (end_col - col).abs if end_col != col
45
- row_move = (end_row - row) / (end_row - row).abs if end_row != row
46
-
47
- [row + row_move, col + col_move]
48
- end
49
-
50
- def clear_path?(row, col, end_row, end_col, board)
51
- current_tile = board.dig(row, col)
52
- if (row == end_row) && (col == end_col)
53
- current_tile.nil? || (current_tile.owner != @owner)
54
- elsif current_tile.nil? || current_tile.equal?(self)
55
- next_cell(row, col, end_row, end_col) => [next_row, next_col]
56
- clear_path?(next_row, next_col, end_row, end_col, board)
57
- else
58
- false
59
- end
60
- end
61
- end
62
-
63
- class Pawn < ChessPiece
64
- def initialize(owner, front)
65
- super(owner, "p")
66
- @first_move = true
67
- @front = front
68
- end
69
-
70
- def valid_move?(row, col, end_row, end_col, board)
71
- return false unless valid_forward_move?(row, end_row)
72
-
73
- if col == end_col # only forward
74
- valid_dest = board.dig(end_row, end_col).nil?
75
- @first_move = false if valid_dest
76
- return valid_dest
77
- elsif (col - end_col).abs == 1 # diagonal movement
78
- other_piece = board.dig(end_row, end_col)
79
- return other_piece && (other_piece.owner != @owner)
80
- end
81
- false
82
- end
83
-
84
- private
85
-
86
- def valid_forward_move?(row, end_row)
87
- if @first_move
88
- (row + @front * 2 == end_row) || (row + @front == end_row)
89
- else
90
- row + @front == end_row
91
- end
92
- end
93
- end
94
-
95
- class Queen < ChessPiece
96
- def initialize(owner)
97
- super(owner, "Q")
98
- end
99
-
100
- def valid_move?(row, col, end_row, end_col, board)
101
- valid_diag_move?(row, col, end_row, end_col, board) \
102
- || valid_horz_move?(row, col, end_row, end_col, board) \
103
- || valid_vert_move?(row, col, end_row, end_col, board)
104
- end
105
- end
106
-
107
- class Rook < ChessPiece
108
- def initialize(owner)
109
- super(owner, "R")
110
- end
111
-
112
- def valid_move?(row, col, end_row, end_col, board)
113
- valid_horz_move?(row, col, end_row, end_col, board) \
114
- || valid_vert_move?(row, col, end_row, end_col, board)
115
- end
116
- end
117
-
118
- class Bishop < ChessPiece
119
- def initialize(owner)
120
- super(owner, "B")
121
- end
122
-
123
- def valid_move?(row, col, end_row, end_col, board)
124
- valid_diag_move?(row, col, end_row, end_col, board)
125
- end
126
- end
127
-
128
- class King < ChessPiece
129
- def initialize(owner)
130
- super(owner, "K")
131
- end
132
-
133
- def valid_move?(row, col, end_row, end_col, board)
134
- return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
135
-
136
- valid_diag_move?(row, col, end_row, end_col, board) \
137
- || valid_horz_move?(row, col, end_row, end_col, board) \
138
- || valid_vert_move?(row, col, end_row, end_col, board)
139
- end
140
- end
141
-
142
- class Knight < ChessPiece
143
- def initialize(owner)
144
- # K was already taken by king, so I had to choose N
145
- super(owner, "N")
146
- end
147
-
148
- def valid_move?(row, col, end_row, end_col, board)
149
- within_movement(row, col, end_row, end_col) \
150
- && not_occupied(end_row, end_col, board)
151
- end
152
-
153
- private
154
-
155
- def within_movement(row, col, end_row, end_col)
156
- ((row - end_row).abs == 2 and (col - end_col).abs == 1) \
157
- || ((row - end_row).abs == 1 and (col - end_col).abs == 2)
158
- end
159
-
160
- def not_occupied(end_row, end_col, board)
161
- spot = board.dig(end_row, end_col)
162
- spot.nil? || spot.owner != @owner
163
- end
164
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "./lib/boardgame_engine"
4
-
5
- class Connect4Board < Board
6
- def initialize
7
- super(6, 7)
8
- end
9
-
10
- def display
11
- super(show_col: true)
12
- end
13
-
14
- def drop_chip(col, owner)
15
- @board.reverse_each do |row|
16
- if row[col].nil?
17
- row[col] = owner
18
- break
19
- end
20
- end
21
- end
22
- end
23
-
24
- class Connect4 < Boardgame
25
- include TwoPlayers
26
-
27
- @instructions = "You can select which column to drop you chip into by" \
28
- " typing in the row number."
29
-
30
- def initialize(name1 = "Player 1", name2 = "Player 2")
31
- super(Connect4Board, @instructions, name1, name2)
32
- end
33
-
34
- def to_s
35
- super("connect-four")
36
- end
37
-
38
- private
39
-
40
- def valid_input?(input)
41
- input.match?(/[[:digit:]]/) && input.to_i.between?(0, 6)
42
- end
43
-
44
- def play_turn
45
- puts "#{@turn}'s turn. Choose a column to drop your chip in"
46
- col = proper_format_input.to_i
47
- @board.drop_chip(col, @turn)
48
- @winner = @turn if win?
49
- change_turn
50
- end
51
-
52
- def win?
53
- [@board.board,
54
- @board.board.transpose,
55
- align_diagonally(@board.board),
56
- align_diagonally(@board.board.transpose)].each do |config|
57
- config.each { |direction| return true if four_in_a_row? direction }
58
- end
59
- false
60
- end
61
-
62
- def four_in_a_row?(row)
63
- counts = row.chunk { |x| x }.map { |x, xs| [x, xs.length] }
64
- return true if counts.any? { |x, count| count > 3 && !x.nil? }
65
- end
66
-
67
- def align_diagonally(board)
68
- board.map.with_index do |row, idx|
69
- left_filler = Array.new(board.length - 1 - idx, nil)
70
- right_filler = Array.new(idx, nil)
71
- left_filler + row + right_filler
72
- end
73
- end
74
- end