boardgame_engine 0.1.0 → 0.2.0
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 +4 -4
- data/.rubocop.yml +1 -9
- data/Gemfile +3 -3
- data/Gemfile.lock +1 -1
- data/Rakefile +2 -2
- data/boardgame_engine.gemspec +13 -13
- data/lib/boardgame_engine/board_modules.rb +105 -0
- data/lib/boardgame_engine/boardgame.rb +143 -109
- data/lib/boardgame_engine/chess.rb +233 -0
- data/lib/boardgame_engine/connect4.rb +57 -0
- data/lib/boardgame_engine/game_modules.rb +65 -0
- data/lib/boardgame_engine/sample_games.rb +7 -0
- data/lib/boardgame_engine/version.rb +1 -1
- data/lib/boardgame_engine.rb +6 -9
- metadata +13 -12
- data/lib/boardgame_engine/chess/chess.rb +0 -103
- data/lib/boardgame_engine/chess/chess_pieces.rb +0 -164
- data/lib/boardgame_engine/connect4/connect4.rb +0 -74
- data/lib/boardgame_engine/multiplayergame.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8a00224a55f64e290296e59c90686e9984141c43b3ce1d588cbe2a6f382d906
|
4
|
+
data.tar.gz: 83ec3bd763ae34688b1f99f8e2c54d9bc9c0229430fc80ae5211db81a9ce47a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3d1d0647a5ff2b85b619aa85f72dc6a1f3638074a2333b22fed2535239c0feb64c43e4ac97eb98bbff45c77ebf513bd9d401299b8046483a2520dca005fdf1f
|
7
|
+
data.tar.gz: eb8768b54c655792cf5f5270ddcc30867b1afd4e80d8382e97704873742b1dd8964925ed1683eff327b386c0d0f9d2aebf454ff1ecfe4cd1bed326dfd1cb3c41
|
data/.rubocop.yml
CHANGED
@@ -1,13 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 3.
|
3
|
-
|
4
|
-
Style/StringLiterals:
|
5
|
-
Enabled: true
|
6
|
-
EnforcedStyle: double_quotes
|
7
|
-
|
8
|
-
Style/StringLiteralsInInterpolation:
|
9
|
-
Enabled: true
|
10
|
-
EnforcedStyle: double_quotes
|
2
|
+
TargetRubyVersion: 3.0
|
11
3
|
|
12
4
|
Layout/LineLength:
|
13
5
|
Max: 120
|
data/Gemfile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in boardgame_engine.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem
|
8
|
+
gem 'rake', '~> 13.0'
|
9
9
|
|
10
|
-
gem
|
10
|
+
gem 'rubocop', '~> 1.21'
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/boardgame_engine.gemspec
CHANGED
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'lib/boardgame_engine/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = 'boardgame_engine'
|
7
7
|
spec.version = BoardgameEngine::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ['FortHoney']
|
9
|
+
spec.email = ['castlehoneyjung@gmail.com']
|
10
10
|
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage =
|
13
|
-
spec.required_ruby_version =
|
11
|
+
spec.summary = 'A gem that makes digitizing/creating board games to be played on the terminal quick and easy.'
|
12
|
+
spec.homepage = 'https://github.com/Forthoney/boardgame_engine'
|
13
|
+
spec.required_ruby_version = '>= 3.0.0'
|
14
14
|
|
15
15
|
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
16
16
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Forthoney/boardgame_engine'
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/Forthoney/boardgame_engine/blob/main/CHANGELOG.md'
|
20
20
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -25,11 +25,11 @@ Gem::Specification.new do |spec|
|
|
25
25
|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
26
26
|
end
|
27
27
|
end
|
28
|
-
spec.bindir =
|
28
|
+
spec.bindir = 'exe'
|
29
29
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
-
spec.require_paths = [
|
30
|
+
spec.require_paths = ['lib']
|
31
31
|
|
32
|
-
spec.add_development_dependency
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
33
33
|
|
34
34
|
# For more information and examples about making a new gem, check out our
|
35
35
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for different board types
|
4
|
+
module Boards
|
5
|
+
# Boards laid out in grid format
|
6
|
+
module Grid
|
7
|
+
# Make a 2D Array representing the board
|
8
|
+
#
|
9
|
+
# @param [Integer] row the number of rows
|
10
|
+
# @param [Integer] col the number of columns
|
11
|
+
#
|
12
|
+
# @return [Array<Array>] A 2D Array
|
13
|
+
def generate_board(row, col)
|
14
|
+
Array.new(row) { Array.new(col) { nil } }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Print a visual representation of the
|
18
|
+
#
|
19
|
+
# @param [Boolean] show_row whether to show row labels
|
20
|
+
# @param [Boolean] show_col whether to show column labels
|
21
|
+
#
|
22
|
+
# @return [void]
|
23
|
+
def display(show_row: false, show_col: false)
|
24
|
+
if show_row
|
25
|
+
@board.each_with_index { |row, idx| puts("#{idx} " + format_row(row)) }
|
26
|
+
else
|
27
|
+
@board.each { |row| puts format_row(row) }
|
28
|
+
end
|
29
|
+
|
30
|
+
return unless show_col
|
31
|
+
|
32
|
+
column_spacer = show_row ? ' ' : ''
|
33
|
+
puts format_col(column_spacer)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_piece_at(location)
|
37
|
+
row, col = location
|
38
|
+
@board.dig(row, col)
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_piece_at(location, piece)
|
42
|
+
row, col = location
|
43
|
+
@board[row][col] = piece
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_board_input?(input, only_row: false, only_col: false)
|
47
|
+
if only_row || only_col
|
48
|
+
input.match?(/[0-#{@board.length - 1}]/)
|
49
|
+
else
|
50
|
+
input.match?(/[0-#{@board.length - 1}], [0-#{@board[0].length - 1}]$/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_input(input)
|
55
|
+
input.split(',').map(&:to_i)
|
56
|
+
end
|
57
|
+
|
58
|
+
def consecutive?(row: true, col: true, diagonal: true)
|
59
|
+
configs = []
|
60
|
+
configs << @board if row
|
61
|
+
configs << @board.transpose if col
|
62
|
+
configs << align_diagonal(@board) << align_diagonal(@board.transpose) if diagonal
|
63
|
+
|
64
|
+
configs.each do |board|
|
65
|
+
board.each { |array| return true if row_consecutive?(4, array) }
|
66
|
+
end
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def row_consecutive?(num, array)
|
73
|
+
elem_counts = array.chunk { |x| x }.map { |x, xs| [x, xs.length] }
|
74
|
+
elem_counts.any? { |x, count| count >= num && x }
|
75
|
+
end
|
76
|
+
|
77
|
+
def align_diagonal(board)
|
78
|
+
board.map.with_index do |row, idx|
|
79
|
+
left_filler = Array.new(board.length - 1 - idx, nil)
|
80
|
+
right_filler = Array.new(idx, nil)
|
81
|
+
left_filler + row + right_filler
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def format_row(row)
|
86
|
+
row.map { |elem| "[#{elem.nil? ? ' ' : elem}]" }.join
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_col(column_spacer)
|
90
|
+
@board[0].each_index.reduce(column_spacer) { |str, idx| str + " #{idx} " }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Boards with movable pieces
|
95
|
+
module MovablePiece
|
96
|
+
def move_piece(start_location, end_location, set_start_to: nil)
|
97
|
+
piece = get_piece_at(start_location)
|
98
|
+
set_piece_at(start_location, set_start_to)
|
99
|
+
destination_piece = get_piece_at(end_location)
|
100
|
+
set_piece_at(end_location, piece)
|
101
|
+
|
102
|
+
destination_piece
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -3,142 +3,176 @@
|
|
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
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
+
private
|
15
|
+
|
16
|
+
def spot_playable?(piece, row, col)
|
17
|
+
piece.possible_moves.include? [row, col]
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
# Class representing a player in a game
|
22
|
+
class Player
|
23
|
+
attr_reader :name
|
24
|
+
|
25
|
+
# Creates a player object with the given name
|
26
|
+
# @param [String] name
|
27
|
+
def initialize(name)
|
28
|
+
@name = name
|
22
29
|
end
|
23
|
-
return unless show_col
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end)
|
31
|
+
def to_s
|
32
|
+
@name.to_s
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@board[end_row][end_col] = piece
|
36
|
+
# Class representing a board game.
|
37
|
+
class Boardgame
|
38
|
+
EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
|
39
|
+
"tutorial. Type in 'exit' anytime to exit the game fully"
|
36
40
|
|
37
|
-
|
38
|
-
|
41
|
+
def initialize(board, instructions, names)
|
42
|
+
@players = names.map { |name| Player.new name }
|
43
|
+
@board = setup_board(board)
|
44
|
+
@instructions = instructions
|
45
|
+
@winner = nil
|
46
|
+
end
|
39
47
|
|
40
|
-
|
48
|
+
def self.play(do_onboarding: true, num_players: 2)
|
49
|
+
names = []
|
50
|
+
num_players.times do |i|
|
51
|
+
puts "What is Player #{i}'s name?"
|
52
|
+
names.push(gets.chomp)
|
53
|
+
end
|
41
54
|
|
42
|
-
|
43
|
-
row.map { |elem| "[#{elem.nil? ? " " : elem}]" }.join
|
44
|
-
end
|
55
|
+
@game = new(names)
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
57
|
+
puts "Welcome to #{@game}!"
|
58
|
+
@game.onboarding if do_onboarding
|
59
|
+
puts "Starting #{@game}..."
|
60
|
+
@game.start
|
61
|
+
end
|
50
62
|
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
def to_s(game_name = 'boardgame')
|
64
|
+
"#{game_name} between #{@players.join(', ')}"
|
65
|
+
end
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
def onboarding
|
68
|
+
puts "Would you like a tutorial on how to play on this program? \n(y, n)"
|
69
|
+
|
70
|
+
case gets.chomp
|
71
|
+
when 'y'
|
72
|
+
tutorial
|
73
|
+
when 'n'
|
74
|
+
puts 'Skipping Tutorial'
|
75
|
+
else
|
76
|
+
puts 'Please answer either "y" or "n"'
|
77
|
+
onboarding
|
78
|
+
end
|
79
|
+
end
|
58
80
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
81
|
+
def tutorial
|
82
|
+
puts @instructions + Boardgame::EXIT_INSTRUCTIONS
|
83
|
+
input = gets.chomp
|
84
|
+
until input == 'back'
|
85
|
+
exit if input == 'exit'
|
86
|
+
puts valid_input?(input) ? 'Valid input!' : 'Invalid input'
|
87
|
+
input = gets.chomp
|
88
|
+
end
|
89
|
+
end
|
63
90
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@instructions = instructions
|
74
|
-
@winner = nil
|
75
|
-
end
|
91
|
+
def start(turn = @players[0])
|
92
|
+
@turn = turn
|
93
|
+
@board.display
|
94
|
+
until @winner
|
95
|
+
play_turn
|
96
|
+
@board.display
|
97
|
+
end
|
98
|
+
puts "#{@winner} wins!"
|
99
|
+
end
|
76
100
|
|
77
|
-
|
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
|
101
|
+
protected
|
89
102
|
|
90
|
-
|
91
|
-
|
92
|
-
end
|
103
|
+
def get_valid_board_input(special_commands = [])
|
104
|
+
input = gets.chomp
|
93
105
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
puts 'Please answer either "y" or "n"'
|
103
|
-
onboarding
|
106
|
+
until @board.valid_board_input?(input) || special_commands.include?(input)
|
107
|
+
exit if input == 'exit'
|
108
|
+
|
109
|
+
puts 'Invalid input. Try again'
|
110
|
+
input = gets.chomp
|
111
|
+
end
|
112
|
+
|
113
|
+
input
|
104
114
|
end
|
105
|
-
end
|
106
115
|
|
107
|
-
|
108
|
-
|
109
|
-
input = gets.chomp
|
110
|
-
until input == "back"
|
111
|
-
exit if input == "exit"
|
112
|
-
puts valid_input?(input) ? "Valid input!" : "Invalid input"
|
113
|
-
input = gets.chomp
|
116
|
+
def setup_board(board)
|
117
|
+
board.new
|
114
118
|
end
|
115
119
|
end
|
116
120
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
@
|
121
|
+
class Piece
|
122
|
+
attr_accessor :status
|
123
|
+
attr_reader :owner
|
124
|
+
|
125
|
+
def initialize(owner, name)
|
126
|
+
@status = 'alive'
|
127
|
+
@owner = owner
|
128
|
+
@name = name
|
123
129
|
end
|
124
|
-
puts "#{@winner} wins!"
|
125
|
-
end
|
126
130
|
|
127
|
-
|
131
|
+
def kill(other)
|
132
|
+
other.status = 'dead'
|
133
|
+
end
|
128
134
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
exit if input == "exit"
|
133
|
-
return input if special_commands.include?(input)
|
135
|
+
def to_s
|
136
|
+
@name.to_s
|
137
|
+
end
|
134
138
|
|
135
|
-
|
136
|
-
|
139
|
+
protected
|
140
|
+
|
141
|
+
def clear_diag_path?(row, col, end_row, end_col, board)
|
142
|
+
((end_row - row).abs == (end_col - col).abs) \
|
143
|
+
&& clear_path?(row, col, end_row, end_col, board)
|
137
144
|
end
|
138
|
-
input
|
139
|
-
end
|
140
145
|
|
141
|
-
|
142
|
-
|
146
|
+
def clear_horz_path?(row, col, end_row, end_col, board)
|
147
|
+
(end_row == row) && clear_path?(row, col, end_row, end_col, board)
|
148
|
+
end
|
149
|
+
|
150
|
+
def clear_vert_path?(row, col, end_row, end_col, board)
|
151
|
+
(end_col == col) && clear_path?(row, col, end_row, end_col, board)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def next_cell(row, col, end_row, end_col)
|
157
|
+
row_move = 0
|
158
|
+
col_move = 0
|
159
|
+
|
160
|
+
col_move = (end_col - col) / (end_col - col).abs if end_col != col
|
161
|
+
row_move = (end_row - row) / (end_row - row).abs if end_row != row
|
162
|
+
|
163
|
+
[row + row_move, col + col_move]
|
164
|
+
end
|
165
|
+
|
166
|
+
def clear_path?(row, col, end_row, end_col, board)
|
167
|
+
current_tile = board.dig(row, col)
|
168
|
+
if (row == end_row) && (col == end_col)
|
169
|
+
current_tile.nil? || (current_tile.owner != @owner)
|
170
|
+
elsif current_tile.nil? || current_tile.equal?(self)
|
171
|
+
next_row, next_col = next_cell(row, col, end_row, end_col)
|
172
|
+
clear_path?(next_row, next_col, end_row, end_col, board)
|
173
|
+
else
|
174
|
+
false
|
175
|
+
end
|
176
|
+
end
|
143
177
|
end
|
144
178
|
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'boardgame_engine/boardgame'
|
4
|
+
require 'boardgame_engine/game_modules'
|
5
|
+
require 'boardgame_engine/board_modules'
|
6
|
+
|
7
|
+
module SampleChess
|
8
|
+
class ChessBoard < BoardgameEngine::Board
|
9
|
+
include Boards::Grid
|
10
|
+
include Boards::MovablePiece
|
11
|
+
|
12
|
+
attr_reader :board
|
13
|
+
|
14
|
+
def initialize(player1, player2)
|
15
|
+
@board = generate_board(8, 8)
|
16
|
+
setup(player1, player2)
|
17
|
+
end
|
18
|
+
|
19
|
+
def display
|
20
|
+
super(show_row: true, show_col: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def setup(player1, player2)
|
26
|
+
set_pawns(player1, player2)
|
27
|
+
set_non_pawns(player1, player2)
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_pawns(player1, player2)
|
31
|
+
@board[1] = @board[1].map { Pawn.new(player1, 1) }
|
32
|
+
@board[-2] = @board[-2].map { Pawn.new(player2, -1) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_non_pawns(player1, player2)
|
36
|
+
pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
|
37
|
+
pieces.each_with_index do |piece, idx|
|
38
|
+
@board[0][idx] = piece.new(player1)
|
39
|
+
@board[7][7 - idx] = piece.new(player2)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Chess < BoardgameEngine::Boardgame
|
45
|
+
include Games::CyclicalTurn
|
46
|
+
include Games::MovablePiece
|
47
|
+
|
48
|
+
def initialize(names)
|
49
|
+
@instructions = 'You can select spots on the board by inputting the row' \
|
50
|
+
" and column with a comma in between. See example below\n1, 1\n"
|
51
|
+
super(ChessBoard, @instructions, names)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
super('chess')
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def valid_piece?(piece)
|
61
|
+
!piece.nil? && piece.owner == @turn
|
62
|
+
end
|
63
|
+
|
64
|
+
def play_turn
|
65
|
+
puts "#{@turn}'s turn"
|
66
|
+
killed = play_move
|
67
|
+
@winner = @turn if killed.is_a?(King)
|
68
|
+
change_turn
|
69
|
+
end
|
70
|
+
|
71
|
+
def setup_board(board)
|
72
|
+
board.new(@players[0], @players[1])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Pawn < BoardgameEngine::Piece
|
77
|
+
def initialize(owner, front)
|
78
|
+
super(owner, 'p')
|
79
|
+
@first_move = true
|
80
|
+
@front = front
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check whether the intended destination is a valid destination
|
84
|
+
#
|
85
|
+
# @param [Array<Integer, Integer>] start_location the start coords
|
86
|
+
# @param [Array<Integer, Integer>] end_location the intended destination
|
87
|
+
# @param [ChessBoard] board the chess board
|
88
|
+
#
|
89
|
+
# @return [<Type>] whether the pawn can move to the intended destination
|
90
|
+
def valid_move?(start_location, end_location, board)
|
91
|
+
row, col = start_location
|
92
|
+
end_row, end_col = end_location
|
93
|
+
|
94
|
+
# Checks if moving 1 (or 2 if its the first move) cell forward
|
95
|
+
return false unless valid_forward_move?(row, end_row)
|
96
|
+
|
97
|
+
if col == end_col
|
98
|
+
valid_line_move?(end_row, end_col, board)
|
99
|
+
elsif (col - end_col).abs == 1 && (row + @front == end_row)
|
100
|
+
valid_diag_move?(end_row, end_col, board)
|
101
|
+
else
|
102
|
+
false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Check if the pawn can move in a straight line forward to the specified
|
109
|
+
# coord
|
110
|
+
#
|
111
|
+
# @param [Integer] end_row the destination row number
|
112
|
+
# @param [Integer] end_col the destination column number
|
113
|
+
#
|
114
|
+
# @return [Boolean] whether the pawn can move or not
|
115
|
+
def valid_line_move?(end_row, end_col, board)
|
116
|
+
is_valid_dest = board.get_piece_at([end_row, end_col]).nil?
|
117
|
+
@first_move = false if is_valid_dest
|
118
|
+
is_valid_dest
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check if the pawn can move in a diagonal line to the specified coord
|
122
|
+
#
|
123
|
+
# @param [Integer] end_row the destination row number
|
124
|
+
# @param [Integer] end_col the destination column number
|
125
|
+
#
|
126
|
+
# @return [Boolean] whether the pawn can move or not
|
127
|
+
def valid_diag_move?(end_row, end_col, board)
|
128
|
+
other_piece = board.get_piece_at([end_row, end_col])
|
129
|
+
@first_move = false if other_piece
|
130
|
+
other_piece && (other_piece.owner != @owner)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Check if the pawn movement is valid row-wise
|
134
|
+
#
|
135
|
+
# @param [Integer] row the row the pawn starts from
|
136
|
+
# @param [Integer] end_row the destination row
|
137
|
+
#
|
138
|
+
# @return [Boolean] whether the pawn's row movement is valid numerically
|
139
|
+
def valid_forward_move?(row, end_row)
|
140
|
+
if @first_move
|
141
|
+
(row + @front * 2 == end_row) || (row + @front == end_row)
|
142
|
+
else
|
143
|
+
row + @front == end_row
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class Queen < BoardgameEngine::Piece
|
149
|
+
def initialize(owner)
|
150
|
+
super(owner, 'Q')
|
151
|
+
end
|
152
|
+
|
153
|
+
def valid_move?(start_location, end_location, board)
|
154
|
+
row, col = start_location
|
155
|
+
end_row, end_col = end_location
|
156
|
+
|
157
|
+
clear_diag_path?(row, col, end_row, end_col, board) \
|
158
|
+
|| clear_horz_path?(row, col, end_row, end_col, board) \
|
159
|
+
|| clear_vert_path?(row, col, end_row, end_col, board)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Rook < BoardgameEngine::Piece
|
164
|
+
def initialize(owner)
|
165
|
+
super(owner, 'R')
|
166
|
+
end
|
167
|
+
|
168
|
+
def valid_move?(start_location, end_location, board)
|
169
|
+
row, col = start_location
|
170
|
+
end_row, end_col = end_location
|
171
|
+
|
172
|
+
clear_horz_path?(row, col, end_row, end_col, board) \
|
173
|
+
|| clear_vert_path?(row, col, end_row, end_col, board)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class Bishop < BoardgameEngine::Piece
|
178
|
+
def initialize(owner)
|
179
|
+
super(owner, 'B')
|
180
|
+
end
|
181
|
+
|
182
|
+
def valid_move?(start_location, end_location, board)
|
183
|
+
row, col = start_location
|
184
|
+
end_row, end_col = end_location
|
185
|
+
|
186
|
+
clear_diag_path?(row, col, end_row, end_col, board)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class King < BoardgameEngine::Piece
|
191
|
+
def initialize(owner)
|
192
|
+
super(owner, 'K')
|
193
|
+
end
|
194
|
+
|
195
|
+
def valid_move?(start_location, end_location, board)
|
196
|
+
row, col = start_location
|
197
|
+
end_row, end_col = end_location
|
198
|
+
|
199
|
+
return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
|
200
|
+
|
201
|
+
clear_diag_path?(row, col, end_row, end_col, board) \
|
202
|
+
|| clear_horz_path?(row, col, end_row, end_col, board) \
|
203
|
+
|| clear_vert_path?(row, col, end_row, end_col, board)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class Knight < BoardgameEngine::Piece
|
208
|
+
def initialize(owner)
|
209
|
+
# K was already taken by king, so I had to choose N
|
210
|
+
super(owner, 'N')
|
211
|
+
end
|
212
|
+
|
213
|
+
def valid_move?(start_location, end_location, board)
|
214
|
+
row, col = start_location
|
215
|
+
end_row, end_col = end_location
|
216
|
+
|
217
|
+
within_movement(row, col, end_row, end_col) \
|
218
|
+
&& not_occupied(end_row, end_col, board)
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def within_movement(row, col, end_row, end_col)
|
224
|
+
((row - end_row).abs == 2 and (col - end_col).abs == 1) \
|
225
|
+
|| ((row - end_row).abs == 1 and (col - end_col).abs == 2)
|
226
|
+
end
|
227
|
+
|
228
|
+
def not_occupied(end_row, end_col, board)
|
229
|
+
spot = board.dig(end_row, end_col)
|
230
|
+
spot.nil? || spot.owner != @owner
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'boardgame_engine/boardgame'
|
4
|
+
require 'boardgame_engine/game_modules'
|
5
|
+
require 'boardgame_engine/board_modules'
|
6
|
+
|
7
|
+
module SampleConnect4
|
8
|
+
class Connect4Board < BoardgameEngine::Board
|
9
|
+
include Boards::Grid
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@board = generate_board(6, 7)
|
13
|
+
end
|
14
|
+
|
15
|
+
def display
|
16
|
+
super(show_col: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
def drop_chip(col, owner)
|
20
|
+
@board.reverse_each do |row|
|
21
|
+
if row[col].nil?
|
22
|
+
row[col] = owner
|
23
|
+
break
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_board_input?(input)
|
29
|
+
super(input, only_col: true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Connect4 < BoardgameEngine::Boardgame
|
34
|
+
include Games::CyclicalTurn
|
35
|
+
|
36
|
+
@instructions = 'You can select which column to drop you chip into by' \
|
37
|
+
' typing in the row number.'
|
38
|
+
|
39
|
+
def initialize(names)
|
40
|
+
super(Connect4Board, @instructions, names)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
super('connect-four')
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def play_turn
|
50
|
+
puts "#{@turn}'s turn\nChoose a column to drop your chip in"
|
51
|
+
col = get_valid_board_input.to_i
|
52
|
+
@board.drop_chip(col, @turn)
|
53
|
+
@winner = @turn if @board.consecutive?
|
54
|
+
change_turn
|
55
|
+
end
|
56
|
+
end
|
57
|
+
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, row, col = select_piece_from_input
|
22
|
+
puts "Select where to move \"#{piece}\" to. Type \"back\" to reselect piece"
|
23
|
+
dest = select_destination(piece, row, col)
|
24
|
+
return play_move if dest == 'back'
|
25
|
+
|
26
|
+
@board.move_piece([row, col], dest)
|
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 [<Type>] 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
|
data/lib/boardgame_engine.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
6
|
-
require_relative "boardgame_engine/multiplayergame"
|
7
|
-
require_relative "boardgame_engine/chess/chess"
|
8
|
-
require_relative "boardgame_engine/connect4/connect4"
|
9
|
-
|
10
|
-
module BoardgameEngine
|
11
|
-
class Error < StandardError; end
|
12
|
-
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- FortHoney
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-17 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,11 +39,12 @@ 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
|
-
- lib/boardgame_engine/chess
|
44
|
-
- lib/boardgame_engine/
|
45
|
-
- lib/boardgame_engine/
|
46
|
-
- lib/boardgame_engine/
|
44
|
+
- lib/boardgame_engine/chess.rb
|
45
|
+
- lib/boardgame_engine/connect4.rb
|
46
|
+
- lib/boardgame_engine/game_modules.rb
|
47
|
+
- lib/boardgame_engine/sample_games.rb
|
47
48
|
- lib/boardgame_engine/version.rb
|
48
49
|
- sig/boardgame_engine.rbs
|
49
50
|
homepage: https://github.com/Forthoney/boardgame_engine
|
@@ -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:
|
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.
|
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,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
|