boardgame_engine 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7129074317f7ea44e65ad18e5952e2401e47be99186e26dd42b0c083ebc91797
4
- data.tar.gz: f87aebcb6502cbfd9d8ee04e60b775f1497e09527863f3f820a1edb202297330
3
+ metadata.gz: 8f3c14dd095359859bb7c6fb53b25e7c94ae18bb1b4e407d0e377797254930a6
4
+ data.tar.gz: 5ecf4d791871a327a1f2e2844cc21b5d0de4b23b001f478a8ef8ca89834bc803
5
5
  SHA512:
6
- metadata.gz: 1c1ab01918757d936b1ee97cf8d75bbf19a300e71450b0c1896fcd9cdda0f804b25324b71ef01929d8d231f96ef7af15ec023690dc52d5a738e8b363da43c4f8
7
- data.tar.gz: b10198cde214fe10e7a861a27419e6235b25a84ef1fac0992de66e0297668da8e3f600effaae0049c12a5343e761dccfb623594bc2621d40b24063e8b8d82cb0
6
+ metadata.gz: 8a8579be5461dda2d3e77a4c2fa50b92ff0a6d4179f960e2ce083261bbc5db6cfe05defa5f1e679e07576bd517d2296477666b86f4388be30030b8127a2ce5ef
7
+ data.tar.gz: afd22e1654d2ca4a66f648e96d08cfd9f52ba3c780d19d4352344214dd6b753ba85236f8e2b0bbf47ecce2c1bd272ac6c33ed6592c0a96838db3c0ea50b327c5
data/.rubocop.yml CHANGED
@@ -1,13 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 3.1
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 "https://rubygems.org"
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 "rake", "~> 13.0"
8
+ gem 'rake', '~> 13.0'
9
9
 
10
- gem "rubocop", "~> 1.21"
10
+ gem 'rubocop', '~> 1.21'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- boardgame_engine (0.1.0)
4
+ boardgame_engine (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A gem that provides a template for creating boardgames. It aims to streamline
4
4
  the process of making boardgames to be played on the terminal by providing a
5
- variety of methods and classes.
5
+ variety of classes and modules.
6
6
 
7
7
  ## Installation
8
8
 
@@ -15,8 +15,43 @@ If bundler is not being used to manage dependencies, install the gem by executin
15
15
  $ gem install boardgame_engine
16
16
 
17
17
  ## Usage
18
+ ### Initial Setup
19
+ To start making your own boardgame using this gem, your game module needs at least two classes -
20
+ a `Game` class inheriting from `Boardgame` and a `Board` class inheriting `Board`.
21
+ `Game` captures the core gameplay loop/logic and the `Board` captures
22
+ interactions with the board.
18
23
 
19
- TODO: Write usage instructions here
24
+ Depending on the game rules, select child modules from the `Games` and `Boards`
25
+ and include it into the `Game` and `Board` class respectively.
26
+ These modules will handle much of the game and board logic related to the
27
+ game mechanic/rule.
28
+
29
+ For example, if the turns in your game go in a cycle like 1 -> 2 -> 3 -> 1 -> 2 ...
30
+ your `Game` class would look like
31
+ ```ruby
32
+ class Game < BoardgameEngine::Game
33
+ include Games::CyclicalGame
34
+ NUM_PLAYERS = 3
35
+ ...
36
+ end
37
+ ```
38
+ Including this module makes it so that the `BoardgameEngine::Game#change_turn`
39
+ method automatically hands the turn over to the correct player.
40
+
41
+ ### Overriding
42
+ At the current stage of the app, there are methods that necessarily must be
43
+ overridden in the child class.
44
+ `play_turn` must be implemented on _your_ `Game` class, and must contain what happens
45
+ during a single turn (not a round, just a turn).
46
+
47
+ Additionally, the limited number of modules mean that you will probably have to implement some
48
+ methods on your own. I hope to reduce the number of these as the app matures.
49
+ I left yard comments on all module methods, so hopefully overriding won't be too
50
+ difficult.
51
+
52
+ ## Example Games
53
+ Check out `connect4.rb` for a game built with rather minimal overrides and
54
+ `chess.rb` for a game that makes heavy use of customization.
20
55
 
21
56
  ## Development
22
57
 
@@ -26,4 +61,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
26
61
 
27
62
  ## Contributing
28
63
 
29
- Bug reports and pull requests are welcome on GitHub at https://github.com/Forthoney/boardgame_engine.
64
+ Bug reports are welcome on GitHub at https://github.com/Forthoney/boardgame_engine.
65
+ I may not get to them quickly but it will be greatly helpful
66
+
67
+ As the app is under heavy development I will not look deeply into actually
68
+ pulling pull requests (if there were to be any).
69
+ I will definitely consider the ideas proposed in them, so they are still welcome.
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rubocop/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
5
 
6
6
  RuboCop::RakeTask.new
7
7
 
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/boardgame_engine/version"
3
+ require_relative 'lib/boardgame_engine/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "boardgame_engine"
6
+ spec.name = 'boardgame_engine'
7
7
  spec.version = BoardgameEngine::VERSION
8
- spec.authors = ["FortHoney"]
9
- spec.email = ["castlehoneyjung@gmail.com"]
8
+ spec.authors = ['FortHoney']
9
+ spec.email = ['castlehoneyjung@gmail.com']
10
10
 
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 = ">= 2.7.0"
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["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"
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 = "exe"
28
+ spec.bindir = 'exe'
29
29
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
30
+ spec.require_paths = ['lib']
31
31
 
32
- spec.add_development_dependency "rspec", "~> 3.2"
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,262 @@
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
+ # Print a visual representation of the board
8
+ #
9
+ # @param [Boolean] show_row whether to show row labels
10
+ # @param [Boolean] show_col whether to show column labels
11
+ #
12
+ # @return [void]
13
+ def display(show_row: false, show_col: false)
14
+ if show_row
15
+ @board.each_with_index { |row, idx| puts("#{idx} " + format_row(row)) }
16
+ else
17
+ @board.each { |row| puts format_row(row) }
18
+ end
19
+
20
+ return unless show_col
21
+
22
+ column_spacer = show_row ? ' ' : ''
23
+ puts format_col_numbering(column_spacer)
24
+ end
25
+
26
+ # Accessor for getting piece at a location on the board
27
+ #
28
+ # @param [Array<Integer, Integer>] location the coordinates on the grid to
29
+ # get the piece from
30
+ #
31
+ # @return [Piece] the piece at the location
32
+ def get_piece_at(location)
33
+ row, col = location
34
+ @board.dig(row, col)
35
+ end
36
+
37
+ # Setter for setting a piece at a location on the board
38
+ #
39
+ # @param [Array<Integer, Integer>] location <description>
40
+ # @param [Piece] piece the piece to place down
41
+ #
42
+ # @return [void]
43
+ def set_piece_at(location, piece)
44
+ row, col = location
45
+ @board[row][col] = piece
46
+ end
47
+
48
+ # Check whether the given input refers to a valid location on the board,
49
+ # regardless of placement of other pieces, rules, etc
50
+ #
51
+ # @param [String] input the user input
52
+ # @param [Boolean] only_row optional arg for only allowing user to specify
53
+ # row
54
+ # @param [Boolean] only_col optional arg for only allowing user to specify
55
+ # col
56
+ #
57
+ # @return [Boolean] true if the input can be parsed into a location on the
58
+ # board, false otherwise
59
+ def valid_board_input?(input, only_row: false, only_col: false)
60
+ if only_row || only_col
61
+ input.match?(/[0-#{@board.length - 1}]/)
62
+ else
63
+ input.match?(/[0-#{@board.length - 1}], [0-#{@board[0].length - 1}]$/)
64
+ end
65
+ end
66
+
67
+ # Check whether there exists a clear diagonal path from start to a
68
+ # destination
69
+ #
70
+ # @param [Array<Integer, Integer>] start_location the start location
71
+ # @param [Array<Integer, Integer>] end_location the intended destination
72
+ #
73
+ # @return [Boolean] true if there exists an unblocked path to destination
74
+ def clear_diag_path?(start_location, end_location)
75
+ row, col = start_location
76
+ end_row, end_col = end_location
77
+
78
+ ((end_row - row).abs == (end_col - col).abs) \
79
+ && clear_path?(row, col, end_row, end_col, board)
80
+ end
81
+
82
+ # Check whether there exists an unblocked horizontal path from start to
83
+ # a destination
84
+ #
85
+ # @param [Array<Integer, Integer>] start_location the start location
86
+ # @param [Array<Integer, Integer>] end_location the intended destination
87
+ #
88
+ # @return [Boolean] true if there exists an unblocked path to destination
89
+ def clear_horz_path?(start_location, end_location)
90
+ row, col = start_location
91
+ end_row, end_col = end_location
92
+
93
+ (end_row == row) && clear_path?(row, col, end_row, end_col, board)
94
+ end
95
+
96
+ # Check whether there exists an unblocked vertical path from start to
97
+ # a destination
98
+ #
99
+ # @param [Array<Integer, Integer>] start_location the start location
100
+ # @param [Array<Integer, Integer>] end_location the intended destination
101
+ #
102
+ # @return [Boolean] true if there exists an unblocked path to destination
103
+ def clear_vert_path?(start_location, end_location)
104
+ row, col = start_location
105
+ end_row, end_col = end_location
106
+
107
+ (end_col == col) && clear_path?(row, col, end_row, end_col, board)
108
+ end
109
+
110
+ # Parse an input from the user that has a corresponding board location. This
111
+ # must be used in after valid_board_input? has confirmed the input is in
112
+ # the correct format
113
+ #
114
+ # @param [String] input a valid
115
+ #
116
+ # @return [Array<Integer, Integer>]
117
+ def parse_input(input)
118
+ input.split(',').map(&:to_i)
119
+ end
120
+
121
+ # Make a 2D Array representing the board
122
+ #
123
+ # @param [Integer] row the number of rows
124
+ # @param [Integer] col the number of columns
125
+ #
126
+ # @return [Array<Array>] A 2D Array
127
+ def generate_board(row, col)
128
+ Array.new(row) { Array.new(col) { nil } }
129
+ end
130
+
131
+ # Check whether there are consecutive pieces on the board
132
+ #
133
+ # @param [Integer] num the number of consecutive pieces to check for.
134
+ # @param [Boolean] row check for row-wise consecutive pieces
135
+ # @param [Boolean] col check for column-wise consecutive pieces
136
+ # @param [Boolean] diagonal check for diagonally consecutive pieces
137
+ #
138
+ # @return [Boolean] true if any specified directins have num number of
139
+ # consecutive pieces
140
+ def consecutive?(num, row: true, col: true, diagonal: true)
141
+ configs = []
142
+ configs << @board if row
143
+ configs << @board.transpose if col
144
+ configs << align_diagonal(@board) << align_diagonal(@board.transpose) if diagonal
145
+
146
+ configs.each do |board|
147
+ board.each { |array| return true if row_consecutive?(num, array) }
148
+ end
149
+ false
150
+ end
151
+
152
+ private
153
+
154
+ # calculates the location of the next cell in the sequence of cells from a
155
+ # given start location and an end location
156
+ #
157
+ # @param [Integer] row
158
+ # @param [Integer] col
159
+ # @param [Integer] end_row
160
+ # @param [Integer] end_col
161
+ #
162
+ # @return [Array<Integer, Integer>] the next cell in the sequence of cells
163
+ def next_cell(row, col, end_row, end_col)
164
+ row_move = 0
165
+ col_move = 0
166
+
167
+ col_move = (end_col - col) / (end_col - col).abs if end_col != col
168
+ row_move = (end_row - row) / (end_row - row).abs if end_row != row
169
+
170
+ [row + row_move, col + col_move]
171
+ end
172
+
173
+ # Given a linear path, check whether there exists an unblocked path.
174
+ # It msut be checked beforehand that the path is indeed linear
175
+ #
176
+ # @param [Integer] row
177
+ # @param [Integer] col
178
+ # @param [Integer] end_row
179
+ # @param [Integer] end_col
180
+ #
181
+ # @return [Boolean] true if there exists a clear path
182
+ def clear_path?(row, col, end_row, end_col)
183
+ current_tile = get_piece_at([row, col])
184
+
185
+ if (row == end_row) && (col == end_col)
186
+ current_tile.nil? || (current_tile.owner != @owner)
187
+ elsif current_tile.nil? || current_tile.equal?(self)
188
+ next_row, next_col = next_cell(row, col, end_row, end_col)
189
+ clear_path?(next_row, next_col, end_row, end_col)
190
+ else
191
+ false
192
+ end
193
+ end
194
+
195
+ # Check a single row for consecutive pieces
196
+ #
197
+ # @param [Integer] num the number of consecutive pieces to check for
198
+ # @param [Array] array the row to check in
199
+ #
200
+ # @return [Boolean] true if there are at least num number of consecutive
201
+ # elements
202
+ def row_consecutive?(num, array)
203
+ chunked_elems = array.chunk { |elem| elem }
204
+ elem_counts = chunked_elems.map { |elem, elems| [elem, elems.length] }
205
+ elem_counts.any? { |elem, count| count >= num && elem }
206
+ end
207
+
208
+ # Align the diagonals of a board. Must be called once on the original board
209
+ # and once more on the transposed board to check for both directions of
210
+ # diagonals
211
+ #
212
+ # @param [Array<Array>] board the board to align
213
+ #
214
+ # @return [Array<Array>] new board with the diagonals aligned
215
+ def align_diagonal(board)
216
+ board.map.with_index do |row, idx|
217
+ left_filler = Array.new(board.length - 1 - idx, nil)
218
+ right_filler = Array.new(idx, nil)
219
+ left_filler + row + right_filler
220
+ end
221
+ end
222
+
223
+ # Format a single row for String representation
224
+ #
225
+ # @param [Array] row the row to turn into a String
226
+ #
227
+ # @return [String] a String representation of the row
228
+ def format_row(row)
229
+ row.map { |elem| "[#{elem.nil? ? ' ' : elem}]" }.join
230
+ end
231
+
232
+ # Format the column numbering of a board
233
+ #
234
+ # @param [String] column_spacer the spacer to use between the indices
235
+ #
236
+ # @return [String] a String representation of the row
237
+ def format_col_numbering(column_spacer)
238
+ @board[0].each_index.reduce(column_spacer) { |str, idx| str + " #{idx} " }
239
+ end
240
+ end
241
+
242
+ # Boards with movable pieces
243
+ module MovablePiece
244
+ # Move a piece from at a start location to an end location. Should be called
245
+ # after checking that the movement is valid board and game logic wise.
246
+ #
247
+ # @param [<Type>] start_location the starting location of the piece
248
+ # @param [<Type>] end_location the destination of the piece
249
+ # @param [<Type>] set_start_to what to place at the start location. defaults
250
+ # to nil
251
+ #
252
+ # @return [Piece] the piece at the destination
253
+ def move_piece(start_location, end_location, set_start_to: nil)
254
+ piece = get_piece_at(start_location)
255
+ set_piece_at(start_location, set_start_to)
256
+ destination_piece = get_piece_at(end_location)
257
+ set_piece_at(end_location, piece)
258
+
259
+ destination_piece
260
+ end
261
+ end
262
+ end
@@ -2,145 +2,167 @@
2
2
 
3
3
  # All classes in this script are intended to be abstract, meaning they should
4
4
  # not be called on their own.
5
-
6
5
  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 } }
16
- end
17
-
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
31
-
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
37
-
38
- destination
39
- end
40
-
41
- private
42
-
43
- def format_row(row)
44
- row.map { |elem| "[#{elem.nil? ? " " : elem}]" }.join
45
- end
46
-
47
- def spot_playable?(piece, row, col)
48
- piece.possible_moves.include? [row, col]
49
- end
50
- end
51
-
52
6
  # Class representing a player in a game
53
7
  class Player
54
8
  attr_reader :name
55
9
 
10
+ # Creates a player object with the given name
11
+ #
12
+ # @param [String] name the name of the player
56
13
  def initialize(name)
57
14
  @name = name
58
15
  end
59
16
 
17
+ # String representation of player
18
+ #
19
+ # @return [String] the name of the player
60
20
  def to_s
61
21
  @name.to_s
62
22
  end
63
23
  end
64
24
 
65
- # Class for running a board game.
66
- class Boardgame
25
+ # Class representing the game loop. It contains the tutorial sequence and
26
+ # information about the core gameplay loop
27
+ class Game
28
+ PLAY_INSTRUCTIONS = ''
67
29
  EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
68
30
  "tutorial. Type in 'exit' anytime to exit the game fully"
31
+ GAME_NAME = 'Boardgame'
32
+ NUM_PLAYERS = 2
33
+
34
+ # Begins a round of the Game
35
+ #
36
+ # @param [Boolean] do_onboarding optional argument on whether to do
37
+ # onboarding
38
+ #
39
+ # @return [void]
40
+ def self.start(do_onboarding: true)
41
+ names = []
42
+ self::NUM_PLAYERS.times do |i|
43
+ puts "What is Player #{i}'s name?"
44
+ names.push(gets.chomp)
45
+ end
69
46
 
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
77
-
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)
47
+ @game = new(names)
84
48
 
85
49
  puts "Welcome to #{@game}!"
86
50
  @game.onboarding if do_onboarding
87
51
  puts "Starting #{@game}..."
88
- @game.start
89
- end
90
-
91
- def to_s(game_name = "boardgame")
92
- "#{game_name} between #{@player1} and #{@player2}"
52
+ @game.play
93
53
  end
94
54
 
55
+ # Execute onboarding sequence where the player is asked if they want a
56
+ # tutorial
57
+ #
58
+ # @return [void]
95
59
  def onboarding
96
60
  puts "Would you like a tutorial on how to play on this program? \n(y, n)"
61
+
97
62
  case gets.chomp
98
- when "y"
63
+ when 'y'
99
64
  tutorial
100
- when "n"
101
- puts "Skipping tutorial"
65
+ when 'n'
66
+ puts 'Skipping Tutorial'
102
67
  else
103
68
  puts 'Please answer either "y" or "n"'
104
69
  onboarding
105
70
  end
106
71
  end
107
72
 
108
- def tutorial
109
- puts @instructions + Boardgame::EXIT_INSTRUCTIONS
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
116
- end
117
-
118
- def start(turn = @player1)
73
+ # Play the game
74
+ #
75
+ # @param [Player] turn the player who is going first
76
+ #
77
+ # @return [void]
78
+ def play(turn = @players[0])
119
79
  @turn = turn
120
80
  @board.display
121
81
  until @winner
122
82
  play_turn
123
83
  @board.display
84
+ change_turn
124
85
  end
125
86
  puts "#{@winner} wins!"
126
87
  end
127
88
 
89
+ # String representation of the game
90
+ #
91
+ # @return [String] <description>
92
+ def to_s
93
+ "#{self.class::GAME_NAME} between #{@players.join(', ')}"
94
+ end
95
+
128
96
  protected
129
97
 
130
- def proper_format_input(special_commands = [])
98
+ # Constructor for a board game. Kept private so that an object of just class
99
+ # BoardGame cannot be instantiated without being inherited. It essentially
100
+ # keeps it as an abstract class
101
+ #
102
+ # @param [Board] board the board to play on
103
+ # @param [Array<String>] names the names of the players
104
+ def initialize(board, names)
105
+ @players = names.map { |name| Player.new name }
106
+ @board = setup_board(board)
107
+ @winner = nil
108
+ end
109
+
110
+ # Run tutorial for the game
111
+ def tutorial
112
+ puts self.class::PLAY_INSTRUCTIONS + self.class::EXIT_INSTRUCTIONS
113
+ input = gets.chomp
114
+ until input == 'back'
115
+ exit if input == 'exit'
116
+ puts @board.valid_board_input?(input) ? 'Valid input' : 'Invalid input'
117
+ input = gets.chomp
118
+ end
119
+ end
120
+
121
+ # Prompts a user for a board input until a proper input is received
122
+ #
123
+ # @param [Array<String>] special_commands a list of commands that are not
124
+ # valid board input but are valid commands like "back"
125
+ #
126
+ # @return [String] a valid input
127
+ def get_valid_board_input(special_commands = [])
131
128
  input = gets.chomp
132
- until valid_input?(input)
133
- exit if input == "exit"
134
- return input if special_commands.include?(input)
135
129
 
136
- puts "Input is in the wrong format or out of bounds. Try again"
130
+ until @board.valid_board_input?(input) || special_commands.include?(input)
131
+ exit if input == 'exit'
132
+
133
+ puts 'Invalid input. Try again'
137
134
  input = gets.chomp
138
135
  end
136
+
139
137
  input
140
138
  end
141
139
 
140
+ # Setup the board
141
+ #
142
+ # @param [Class] board the class of the board to be used in the game
143
+ #
144
+ # @return [Board] a new board
142
145
  def setup_board(board)
143
146
  board.new
144
147
  end
145
148
  end
149
+
150
+ class Piece
151
+ attr_accessor :status
152
+ attr_reader :owner
153
+
154
+ def initialize(owner, name)
155
+ @status = 'alive'
156
+ @owner = owner
157
+ @name = name
158
+ end
159
+
160
+ def kill(other)
161
+ other.status = 'dead'
162
+ end
163
+
164
+ def to_s
165
+ @name.to_s
166
+ end
167
+ end
146
168
  end