boardgame_engine 0.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7129074317f7ea44e65ad18e5952e2401e47be99186e26dd42b0c083ebc91797
4
- data.tar.gz: f87aebcb6502cbfd9d8ee04e60b775f1497e09527863f3f820a1edb202297330
3
+ metadata.gz: c8a00224a55f64e290296e59c90686e9984141c43b3ce1d588cbe2a6f382d906
4
+ data.tar.gz: 83ec3bd763ae34688b1f99f8e2c54d9bc9c0229430fc80ae5211db81a9ce47a0
5
5
  SHA512:
6
- metadata.gz: 1c1ab01918757d936b1ee97cf8d75bbf19a300e71450b0c1896fcd9cdda0f804b25324b71ef01929d8d231f96ef7af15ec023690dc52d5a738e8b363da43c4f8
7
- data.tar.gz: b10198cde214fe10e7a861a27419e6235b25a84ef1fac0992de66e0297668da8e3f600effaae0049c12a5343e761dccfb623594bc2621d40b24063e8b8d82cb0
6
+ metadata.gz: c3d1d0647a5ff2b85b619aa85f72dc6a1f3638074a2333b22fed2535239c0feb64c43e4ac97eb98bbff45c77ebf513bd9d401299b8046483a2520dca005fdf1f
7
+ data.tar.gz: eb8768b54c655792cf5f5270ddcc30867b1afd4e80d8382e97704873742b1dd8964925ed1683eff327b386c0d0f9d2aebf454ff1ecfe4cd1bed326dfd1cb3c41
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.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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,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
@@ -11,39 +11,8 @@ module BoardgameEngine
11
11
  class Board
12
12
  attr_reader :board
13
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
14
  private
42
15
 
43
- def format_row(row)
44
- row.map { |elem| "[#{elem.nil? ? " " : elem}]" }.join
45
- end
46
-
47
16
  def spot_playable?(piece, row, col)
48
17
  piece.possible_moves.include? [row, col]
49
18
  end
@@ -53,6 +22,8 @@ module BoardgameEngine
53
22
  class Player
54
23
  attr_reader :name
55
24
 
25
+ # Creates a player object with the given name
26
+ # @param [String] name
56
27
  def initialize(name)
57
28
  @name = name
58
29
  end
@@ -62,25 +33,26 @@ module BoardgameEngine
62
33
  end
63
34
  end
64
35
 
65
- # Class for running a board game.
36
+ # Class representing a board game.
66
37
  class Boardgame
67
38
  EXIT_INSTRUCTIONS ||= "Try a sample input or input 'back' to leave the " \
68
39
  "tutorial. Type in 'exit' anytime to exit the game fully"
69
40
 
70
- def initialize(board, instructions, name1 = "Player 1", name2 = "Player 2")
71
- @player1 = Player.new(name1)
72
- @player2 = Player.new(name2)
41
+ def initialize(board, instructions, names)
42
+ @players = names.map { |name| Player.new name }
73
43
  @board = setup_board(board)
74
44
  @instructions = instructions
75
45
  @winner = nil
76
46
  end
77
47
 
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)
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
54
+
55
+ @game = new(names)
84
56
 
85
57
  puts "Welcome to #{@game}!"
86
58
  @game.onboarding if do_onboarding
@@ -88,17 +60,18 @@ module BoardgameEngine
88
60
  @game.start
89
61
  end
90
62
 
91
- def to_s(game_name = "boardgame")
92
- "#{game_name} between #{@player1} and #{@player2}"
63
+ def to_s(game_name = 'boardgame')
64
+ "#{game_name} between #{@players.join(', ')}"
93
65
  end
94
66
 
95
67
  def onboarding
96
68
  puts "Would you like a tutorial on how to play on this program? \n(y, n)"
69
+
97
70
  case gets.chomp
98
- when "y"
71
+ when 'y'
99
72
  tutorial
100
- when "n"
101
- puts "Skipping tutorial"
73
+ when 'n'
74
+ puts 'Skipping Tutorial'
102
75
  else
103
76
  puts 'Please answer either "y" or "n"'
104
77
  onboarding
@@ -108,14 +81,14 @@ module BoardgameEngine
108
81
  def tutorial
109
82
  puts @instructions + Boardgame::EXIT_INSTRUCTIONS
110
83
  input = gets.chomp
111
- until input == "back"
112
- exit if input == "exit"
113
- puts valid_input?(input) ? "Valid input!" : "Invalid input"
84
+ until input == 'back'
85
+ exit if input == 'exit'
86
+ puts valid_input?(input) ? 'Valid input!' : 'Invalid input'
114
87
  input = gets.chomp
115
88
  end
116
89
  end
117
90
 
118
- def start(turn = @player1)
91
+ def start(turn = @players[0])
119
92
  @turn = turn
120
93
  @board.display
121
94
  until @winner
@@ -127,15 +100,16 @@ module BoardgameEngine
127
100
 
128
101
  protected
129
102
 
130
- def proper_format_input(special_commands = [])
103
+ def get_valid_board_input(special_commands = [])
131
104
  input = gets.chomp
132
- until valid_input?(input)
133
- exit if input == "exit"
134
- return input if special_commands.include?(input)
135
105
 
136
- puts "Input is in the wrong format or out of bounds. Try again"
106
+ until @board.valid_board_input?(input) || special_commands.include?(input)
107
+ exit if input == 'exit'
108
+
109
+ puts 'Invalid input. Try again'
137
110
  input = gets.chomp
138
111
  end
112
+
139
113
  input
140
114
  end
141
115
 
@@ -143,4 +117,62 @@ module BoardgameEngine
143
117
  board.new
144
118
  end
145
119
  end
120
+
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
129
+ end
130
+
131
+ def kill(other)
132
+ other.status = 'dead'
133
+ end
134
+
135
+ def to_s
136
+ @name.to_s
137
+ end
138
+
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)
144
+ end
145
+
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
177
+ end
146
178
  end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame"
4
- require_relative "multiplayergame"
3
+ require 'boardgame_engine/boardgame'
4
+ require 'boardgame_engine/game_modules'
5
+ require 'boardgame_engine/board_modules'
5
6
 
6
7
  module SampleChess
7
8
  class ChessBoard < BoardgameEngine::Board
9
+ include Boards::Grid
10
+ include Boards::MovablePiece
11
+
8
12
  attr_reader :board
9
13
 
10
14
  def initialize(player1, player2)
11
- super(8, 8)
15
+ @board = generate_board(8, 8)
12
16
  setup(player1, player2)
13
17
  end
14
18
 
@@ -38,154 +42,100 @@ module SampleChess
38
42
  end
39
43
 
40
44
  class Chess < BoardgameEngine::Boardgame
41
- include TwoPlayers
45
+ include Games::CyclicalTurn
46
+ include Games::MovablePiece
42
47
 
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)
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)
47
52
  end
48
53
 
49
54
  def to_s
50
- super("chess")
55
+ super('chess')
51
56
  end
52
57
 
53
58
  private
54
59
 
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
60
  def valid_piece?(piece)
61
61
  !piece.nil? && piece.owner == @turn
62
62
  end
63
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
64
  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)
65
+ puts "#{@turn}'s turn"
66
+ killed = play_move
67
+ @winner = @turn if killed.is_a?(King)
98
68
  change_turn
99
69
  end
100
70
 
101
71
  def setup_board(board)
102
- board.new(@player1, @player2)
72
+ board.new(@players[0], @players[1])
103
73
  end
104
74
  end
105
75
 
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
76
+ class Pawn < BoardgameEngine::Piece
167
77
  def initialize(owner, front)
168
- super(owner, "p")
78
+ super(owner, 'p')
169
79
  @first_move = true
170
80
  @front = front
171
81
  end
172
82
 
173
- def valid_move?(row, col, end_row, end_col, board)
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
174
95
  return false unless valid_forward_move?(row, end_row)
175
96
 
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)
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
183
103
  end
184
- false
185
104
  end
186
105
 
187
106
  private
188
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
189
139
  def valid_forward_move?(row, end_row)
190
140
  if @first_move
191
141
  (row + @front * 2 == end_row) || (row + @front == end_row)
@@ -195,60 +145,75 @@ module SampleChess
195
145
  end
196
146
  end
197
147
 
198
- class Queen < ChessPiece
148
+ class Queen < BoardgameEngine::Piece
199
149
  def initialize(owner)
200
- super(owner, "Q")
150
+ super(owner, 'Q')
201
151
  end
202
152
 
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)
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)
207
160
  end
208
161
  end
209
162
 
210
- class Rook < ChessPiece
163
+ class Rook < BoardgameEngine::Piece
211
164
  def initialize(owner)
212
- super(owner, "R")
165
+ super(owner, 'R')
213
166
  end
214
167
 
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)
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)
218
174
  end
219
175
  end
220
176
 
221
- class Bishop < ChessPiece
177
+ class Bishop < BoardgameEngine::Piece
222
178
  def initialize(owner)
223
- super(owner, "B")
179
+ super(owner, 'B')
224
180
  end
225
181
 
226
- def valid_move?(row, col, end_row, end_col, board)
227
- valid_diag_move?(row, col, end_row, end_col, board)
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)
228
187
  end
229
188
  end
230
189
 
231
- class King < ChessPiece
190
+ class King < BoardgameEngine::Piece
232
191
  def initialize(owner)
233
- super(owner, "K")
192
+ super(owner, 'K')
234
193
  end
235
194
 
236
- def valid_move?(row, col, end_row, end_col, board)
195
+ def valid_move?(start_location, end_location, board)
196
+ row, col = start_location
197
+ end_row, end_col = end_location
198
+
237
199
  return false unless (row - end_row).abs == 1 && (col - end_col).abs == 1
238
200
 
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)
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)
242
204
  end
243
205
  end
244
206
 
245
- class Knight < ChessPiece
207
+ class Knight < BoardgameEngine::Piece
246
208
  def initialize(owner)
247
209
  # K was already taken by king, so I had to choose N
248
- super(owner, "N")
210
+ super(owner, 'N')
249
211
  end
250
212
 
251
- def valid_move?(row, col, end_row, end_col, board)
213
+ def valid_move?(start_location, end_location, board)
214
+ row, col = start_location
215
+ end_row, end_col = end_location
216
+
252
217
  within_movement(row, col, end_row, end_col) \
253
218
  && not_occupied(end_row, end_col, board)
254
219
  end
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame"
4
- require_relative "multiplayergame"
3
+ require 'boardgame_engine/boardgame'
4
+ require 'boardgame_engine/game_modules'
5
+ require 'boardgame_engine/board_modules'
5
6
 
6
7
  module SampleConnect4
7
8
  class Connect4Board < BoardgameEngine::Board
9
+ include Boards::Grid
10
+
8
11
  def initialize
9
- super(6, 7)
12
+ @board = generate_board(6, 7)
10
13
  end
11
14
 
12
15
  def display
@@ -21,57 +24,34 @@ module SampleConnect4
21
24
  end
22
25
  end
23
26
  end
27
+
28
+ def valid_board_input?(input)
29
+ super(input, only_col: true)
30
+ end
24
31
  end
25
32
 
26
33
  class Connect4 < BoardgameEngine::Boardgame
27
- include TwoPlayers
34
+ include Games::CyclicalTurn
28
35
 
29
- @instructions = "You can select which column to drop you chip into by" \
30
- " typing in the row number."
36
+ @instructions = 'You can select which column to drop you chip into by' \
37
+ ' typing in the row number.'
31
38
 
32
- def initialize(name1 = "Player 1", name2 = "Player 2")
33
- super(Connect4Board, @instructions, name1, name2)
39
+ def initialize(names)
40
+ super(Connect4Board, @instructions, names)
34
41
  end
35
42
 
36
43
  def to_s
37
- super("connect-four")
44
+ super('connect-four')
38
45
  end
39
46
 
40
47
  private
41
48
 
42
- def valid_input?(input)
43
- input.match?(/[[:digit:]]/) && input.to_i.between?(0, 6)
44
- end
45
-
46
49
  def play_turn
47
- puts "#{@turn}'s turn. Choose a column to drop your chip in"
48
- col = proper_format_input.to_i
50
+ puts "#{@turn}'s turn\nChoose a column to drop your chip in"
51
+ col = get_valid_board_input.to_i
49
52
  @board.drop_chip(col, @turn)
50
- @winner = @turn if win?
53
+ @winner = @turn if @board.consecutive?
51
54
  change_turn
52
55
  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
56
  end
77
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
@@ -1,5 +1,7 @@
1
- require_relative "chess"
2
- require_relative "connect4"
1
+ # frozen_string_literal: true
2
+
3
+ require 'boardgame_engine/chess'
4
+ require 'boardgame_engine/connect4'
3
5
 
4
6
  module SampleGames
5
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BoardgameEngine
4
- VERSION = "0.1.1"
4
+ VERSION = '0.2.0'
5
5
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "boardgame_engine/version"
3
+ require 'boardgame_engine/version'
4
+ require 'boardgame_engine/boardgame'
5
+ require 'boardgame_engine/game_modules'
6
+ require 'boardgame_engine/board_modules'
7
+ require 'boardgame_engine/sample_games'
4
8
 
5
- require "boardgame_engine/boardgame"
6
- require "boardgame_engine/multiplayergame"
7
- require "boardgame_engine/sample_games"
8
-
9
- module BoardgameEngine
10
- class Error < StandardError; end
11
- end
9
+ module BoardgameEngine end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boardgame_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.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: 2022-10-07 00:00:00.000000000 Z
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,10 +39,11 @@ files:
39
39
  - Rakefile
40
40
  - boardgame_engine.gemspec
41
41
  - lib/boardgame_engine.rb
42
+ - lib/boardgame_engine/board_modules.rb
42
43
  - lib/boardgame_engine/boardgame.rb
43
44
  - lib/boardgame_engine/chess.rb
44
45
  - lib/boardgame_engine/connect4.rb
45
- - lib/boardgame_engine/multiplayergame.rb
46
+ - lib/boardgame_engine/game_modules.rb
46
47
  - lib/boardgame_engine/sample_games.rb
47
48
  - lib/boardgame_engine/version.rb
48
49
  - sig/boardgame_engine.rbs
@@ -52,7 +53,7 @@ metadata:
52
53
  homepage_uri: https://github.com/Forthoney/boardgame_engine
53
54
  source_code_uri: https://github.com/Forthoney/boardgame_engine
54
55
  changelog_uri: https://github.com/Forthoney/boardgame_engine/blob/main/CHANGELOG.md
55
- post_install_message:
56
+ post_install_message:
56
57
  rdoc_options: []
57
58
  require_paths:
58
59
  - lib
@@ -60,15 +61,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
61
  requirements:
61
62
  - - ">="
62
63
  - !ruby/object:Gem::Version
63
- version: 2.7.0
64
+ version: 3.0.0
64
65
  required_rubygems_version: !ruby/object:Gem::Requirement
65
66
  requirements:
66
67
  - - ">="
67
68
  - !ruby/object:Gem::Version
68
69
  version: '0'
69
70
  requirements: []
70
- rubygems_version: 3.3.7
71
- signing_key:
71
+ rubygems_version: 3.4.4
72
+ signing_key:
72
73
  specification_version: 4
73
74
  summary: A gem that makes digitizing/creating board games to be played on the terminal
74
75
  quick and easy.
@@ -1,5 +0,0 @@
1
- module TwoPlayers
2
- def change_turn
3
- @turn = @turn == @player1 ? @player2 : @player1
4
- end
5
- end