boardgame_engine 0.1.0 → 0.1.1

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