boardgame_engine 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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