terminal_chess 0.1.2 → 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.
@@ -1,77 +1,152 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- module PRINTER
3
+ module Printer
4
4
 
5
- VERSION ||= "0.1.0"
6
- COLS ||= ['A','B','C','D','E','F','G','H']
7
- @@n = 0
8
- @@print_count = 1
5
+ # TODO : Replace text pieces with unicode symbols
6
+ PIECE_TO_UNICODE_MAPPING ||= {
7
+ "pa": "♙",
8
+ "ro": "♖",
9
+ "bi": "♗",
10
+ "kn": "♘",
11
+ "ki": "♔",
12
+ "qu": "♕"
13
+ }
14
+ COLS ||= ('A'..'H')
9
15
 
16
+ @@subrow = 0 # Reference to current row within a cell
17
+ @@cell_number = 1 # Reference to the current cell
10
18
 
11
- # Prints the board to terminal, based on layout defined by piece_locations
12
- def printer
13
-
14
- # Clear and reset counters
15
- @@print_count = 1
19
+
20
+ def print_header
21
+ # Print chess board Header (Title and then Column Labels A to H)
22
+ print "\n\t>> Welcome to Terminal Chess v#{TerminalChess::VERSION}\n\n"
23
+ print "\s\s\s" # cell padding
24
+
25
+ COLS.each { |c| print " #{c} " }
26
+ puts
27
+ end
28
+
29
+ def print_footer
30
+ # Print chess board footer (Column Labels A to H)
31
+ print "\s\s\s" # cell padding
32
+
33
+ COLS.each { |c| print " #{c} " }
34
+ puts
35
+ end
36
+
37
+ def print_start_of_row(row_num)
38
+ # Pad the start of each row with spacing or the row numbers 1..8
39
+ if row_num
40
+ print " #{row_num} "
41
+ else
42
+ print " " * 3
43
+ end
44
+ end
45
+
46
+ def print_end_of_row(row_num)
47
+ # Print row number 1...8 at end of each row
48
+ if (@@subrow == 1 || @@subrow == 4) && row_num
49
+ print " #{row_num}"
50
+ end
51
+ end
52
+
53
+ def clear_all
54
+ # Reset counters and clear terminal
55
+ @@cell_number = 1
16
56
  system "clear" or system "cls"
57
+ end
17
58
 
18
- # Header (title & column labels)
19
- print "\n\t>> Welcome to Terminal Chess v#{VERSION}\n\n\s\s\s"
20
- COLS.each { |c| print " _#{c}__ " }
21
- puts "\n"
22
59
 
23
- # Print Cells (use printer block and pass cell styling in loop below)
60
+ def printer
61
+ # Prints the board to terminal, based on layout defined by piece_locations
62
+
63
+ clear_all
64
+ print_header
65
+
66
+ # Print first cell of each row, with row number
24
67
  (1..8).each do |row|
25
- yield "| |"
26
- yield "| XX |", "#{row}"
27
- yield "|____|"
68
+ yield " "
69
+ yield " XX ", "#{row}"
70
+ yield " "
28
71
  end
29
72
 
30
- # Footer (print column labels)
31
- print "\s\s\s"
32
- COLS.each { |c| print " #{c} " }
33
- puts ""
73
+ print_footer
34
74
  end
35
75
 
76
+ def substitute_pieces(text, index, color, background_color, piece_locations)
77
+ piece = piece_to_string(piece_locations[index][:type])
78
+
79
+ piece.upcase! unless piece == "pa"
80
+ piece = piece.colorize(color)
81
+
82
+ if background_color == :white
83
+ text.gsub("XX", piece).on_light_white
84
+ else
85
+ text.gsub("XX", piece).on_light_black
86
+ end
87
+ end
88
+
89
+ def piece_to_string(piece_name)
90
+ # Print pieces as two characters
91
+ # "pawn" -> "pa" , "bishop" -> "BI" , "king" -> "KI" , ...
92
+ # print nil as " " so it takes up a two character width on the printed board
93
+ piece_name == nil ? " " : piece_name[0..1]
94
+ end
36
95
 
37
96
  def print_board(piece_locations)
38
- printer { |i, j|
39
-
40
- # Print preceeding row index (1...8) if applicable
41
- if j then print " #{j} " else print " " end
42
- if @@n < 3
43
- # Print cell (4 characters wide, 3 characters tall)
44
- 4.times do
45
- color = piece_locations[@@print_count]["color"] || "black"
46
- next_color = piece_locations[@@print_count+1]["color"] || "black"
47
- print "#{i}".gsub("XX", piece_locations[@@print_count]["type"][0..1].upcase).colorize(:"#{color}").on_light_white
48
- print "#{i}".gsub("XX", piece_locations[@@print_count+1]["type"][0..1].upcase).colorize(:"#{next_color}").on_light_black
49
- if "#{i}".include? "XX"
50
- # Incremenet print_count, unless last cell is being printed, to avoid an out of range error
51
- @@print_count += 2 unless @@print_count == 63
52
- end
97
+ printer do |tile_text, row_num|
98
+
99
+ print_start_of_row(row_num)
100
+
101
+ 4.times do
102
+
103
+ # Print cell and next neighboring cell,
104
+ # then loop until 4 pairs of 2 cells have been printed, completing row
105
+ color = piece_locations[@@cell_number][:color] || :black
106
+ next_color = piece_locations[@@cell_number + 1][:color] || :black
107
+
108
+ # Print two rows at a time as every two rows repeat
109
+ # alternating tile colors
110
+ # ________________________
111
+ # | ### ### ### ###| -> subrow 0
112
+ # | ### ### ### ###| -> subrow 1
113
+ # | ### ### ### ###| -> subrow 2
114
+ # |### ### ### ### | -> subrow 3
115
+ # |### ### ### ### | -> subrow 4
116
+ # |### ### ### ### | -> subrow 5
117
+ #
118
+
119
+ if @@subrow < 3
120
+ print substitute_pieces(
121
+ tile_text, @@cell_number, color, :white, piece_locations
122
+ )
123
+ print substitute_pieces(
124
+ tile_text, @@cell_number + 1, next_color, :black, piece_locations
125
+ )
126
+ else
127
+ print substitute_pieces(
128
+ tile_text, @@cell_number, color, :black, piece_locations
129
+ )
130
+ print substitute_pieces(
131
+ tile_text, @@cell_number + 1, next_color, :white, piece_locations
132
+ )
53
133
  end
54
- else
55
- # Print cell starting with alternative color (4 characters wide, 3 characters tall)
56
- 4.times do
57
- color = piece_locations[@@print_count]["color"] || "black"
58
- next_color = piece_locations[@@print_count+1]["color"] || "black"
59
- print "#{i}".gsub("XX", piece_locations[@@print_count]["type"][0..1].upcase).colorize(:"#{color}").on_light_black
60
- print "#{i}".gsub("XX", piece_locations[@@print_count+1]["type"][0..1].upcase).colorize(:"#{next_color}").on_light_white
61
- if "#{i}".include? "XX"
62
- @@print_count += 2 unless @@print_count == 63
63
- end
134
+
135
+ if tile_text.include? "XX"
136
+ # Incremenet cell_number unless last cell is being printed
137
+ # to avoid an out of range error
138
+ @@cell_number += 2 unless @@cell_number == 63
64
139
  end
65
140
  end
66
141
 
67
- # Print succeeding row index (1...8) if applicable
68
- if (@@n == 1 || @@n == 4) && j then print " #{j}" end
142
+ print_end_of_row(row_num)
69
143
 
70
- # Incriment row index. Reset once n reaches 6 (i.e., two complete cell rows have been printed - the pattern to repeat)
71
- @@n += 1
72
- if @@n == 6 then @@n = 0 end
73
- puts ""
74
- }
144
+ # Incriment row index.
145
+ # Reset once n reaches 6 (i.e., two complete cell rows have been printed - the pattern to repeat)
146
+ @@subrow += 1
147
+ @@subrow = 0 if @@subrow == 6
148
+ puts
149
+ end
75
150
  end
76
151
 
77
152
  end
@@ -0,0 +1,65 @@
1
+ require 'em-websocket'
2
+
3
+ EventMachine.run do
4
+
5
+ clients = []
6
+ game_ongoing = false
7
+ p [:start, "Waiting for clients to connect..."]
8
+
9
+ EM::WebSocket.start(:host => '0.0.0.0', :port => '4567') do |ws|
10
+
11
+ ws.onopen do |handshake|
12
+ p [:open]
13
+ if @game_ongoing
14
+ ws.send "Game already in progress"
15
+ ws.close
16
+ else
17
+ puts "Now connected to client"
18
+ end
19
+
20
+ if clients.length == 0
21
+ puts "Waiting for second client"
22
+ clients << ws
23
+ ws.send "INFO: Player now connected to #{handshake.path}"
24
+ ws.send "INFO: Awaiting second player..."
25
+
26
+ elsif clients.length == 1
27
+ clients << ws
28
+ puts "Starting Game..."
29
+ ws.send "INFO: Player now connected to server at #{handshake.path}"
30
+
31
+ clients.each_with_index do |client, idx|
32
+ client.send "INFO: Connected to remote player"
33
+ client.send "SETUP: You are player #{idx+1}"
34
+ end
35
+ end
36
+ end
37
+
38
+ ws.onmessage do |msg|
39
+ p [:message, msg]
40
+ opposing_player = (clients - [ws]).first
41
+
42
+ # Send opposing player the new move
43
+ opposing_player.send msg
44
+ end
45
+
46
+ ws.onerror do |err|
47
+ p [:error, err]
48
+ end
49
+
50
+ ws.onclose do
51
+ clients.each do |client|
52
+ client.send "INFO: Player has left the game"
53
+ client.close unless client.state == :closed
54
+ end
55
+
56
+ p [:close, "Client disconnected. Disconnecting all players"]
57
+
58
+ # End session for all clients
59
+ clients = []
60
+ game_ongoing = false
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -1,32 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH << '.'
3
+ $LOAD_PATH << __FILE__ # '.'
4
4
 
5
- require_relative "terminal_chess/version"
6
- require_relative "printer.rb"
7
- require_relative "move.rb"
8
- require_relative "board.rb"
5
+ require_relative 'local_chess_client'
6
+ require_relative 'network_chess_client'
9
7
 
10
- # Setup
11
- board = Board.new;
12
- board.setup_board;
13
- board.board_refresh
14
-
15
- # Gameplay
16
- loop do
17
-
18
- print "\nPiece to Move [#{board.player_turn.capitalize}]: "
19
- from = gets.chomp.upcase
20
-
21
- begin
22
- print "Valid destinations: #{board.valid_destinations(from).join(", ")}"
23
-
24
- print "\nLocation: "
25
- to = gets.chomp.upcase
26
- board.move(from, to)
27
-
28
- rescue Exception => e
29
- puts "Invalid selection #{e if !ENV["DEV"].nil?}"
30
- end
8
+ if ENV["NGROK"]
9
+ NetworkChessClient.new(ENV["NGROK"])
10
+ else
11
+ LocalChessClient.new
31
12
  end
32
13
 
@@ -0,0 +1,27 @@
1
+ class Messages
2
+
3
+ @red_turn = "It is red's turn. Please move a red piece."
4
+ @black_turn = "It is black's turn. Please move a black piece."
5
+
6
+ @red_in_check = "Please move red king out of check to continue"
7
+ @black_in_check = "Please move black king out of check to continue"
8
+ @red_winner = "Checkmate! Red Player Wins!"
9
+ @black_winner = "Checkmate! Black Player Wins!"
10
+ @invalid = "Invalid Selection"
11
+
12
+ @piece_moved = "Piece moved"
13
+
14
+ class << self
15
+ attr_reader(
16
+ :red_turn,
17
+ :black_turn,
18
+ :red_in_check,
19
+ :black_in_check,
20
+ :red_winner,
21
+ :black_winner,
22
+ :checkmate,
23
+ :invalid,
24
+ :piece_moved
25
+ )
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module TerminalChess
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -6,7 +6,7 @@ require 'terminal_chess/version'
6
6
  Gem::Specification.new do |s|
7
7
  s.name = 'terminal_chess'
8
8
  s.version = TerminalChess::VERSION
9
- s.date = '2015-03-03'
9
+ s.date = '2017-12-12'
10
10
  s.summary = "Chess game playable via the terminal"
11
11
  s.description = "Two player chess game through the terminal"
12
12
  s.authors = ["Jason Willems"]
@@ -19,6 +19,10 @@ Gem::Specification.new do |s|
19
19
  s.executables << 'terminal_chess'
20
20
 
21
21
  s.add_runtime_dependency "colorize"
22
+ s.add_runtime_dependency "em-websocket"
23
+ s.add_runtime_dependency "eventmachine"
24
+ s.add_runtime_dependency "faye-websocket"
22
25
  s.add_development_dependency "bundler", "~> 1.7"
23
26
  s.add_development_dependency "rake", "~> 10.0"
27
+ s.add_development_dependency "minitest"
24
28
  end
@@ -1,30 +1,37 @@
1
- #require 'test/unit'
2
1
  require 'minitest/autorun'
3
2
  require "./lib/terminal_chess/version"
3
+ require "./lib/terminal_chess/messages"
4
4
  require "printer.rb"
5
5
  require "move.rb"
6
6
  require "board.rb"
7
7
 
8
8
  class MyIO
9
- def gets
9
+ def gets
10
10
  "Q\n"
11
11
  end
12
12
  end
13
13
 
14
- class TestBoard < MiniTest::Unit::TestCase
15
-
14
+ <<CHESSBOARD
15
+ Tile Positions:
16
+
17
+ A,B,C,D,E,F,G,H
18
+ 1 1-8
19
+ 2 9-16
20
+ 3 17-24
21
+ 4 25-32
22
+ 5 33-40
23
+ 6 41-48
24
+ 7 49-56
25
+ 8 57-64
26
+ CHESSBOARD
27
+
28
+ class TestBoard < MiniTest::Test
29
+
16
30
  def setup
17
-
18
- @columns = %w[A B C D E F G H]
19
- @invalid_msg = "Invalid selection"
20
- @red_turn_msg = "It is red's turn. Please move a red piece."
21
- @black_turn_msg = "It is black's turn. Please move a black piece."
22
- @red_in_check = "Please move red king out of check to continue"
23
- @black_in_check = "Please move black king out of check to continue"
24
31
 
32
+ @columns = %w[A B C D E F G H]
25
33
  @board = Board.new
26
- @board.setup_board
27
- @board.board_refresh
34
+ @board.display_board
28
35
 
29
36
  def valid_piece_movement(from)
30
37
  @board.valid_destinations(from)
@@ -37,33 +44,42 @@ class TestBoard < MiniTest::Unit::TestCase
37
44
  def get_board
38
45
  @board.piece_manifest
39
46
  end
40
-
47
+
41
48
  def piece_quantity(piece)
42
49
  all_tiles = get_board
43
50
  count = 0
44
51
  all_tiles.each do |num, details|
45
- count += 1 if details.fetch("type") == piece
52
+ count += 1 if details.fetch(:type) == piece
46
53
  end
54
+
47
55
  count
48
56
  end
49
57
 
50
58
  def tile_empty(lowerbound, upperbound)
51
59
  all_tiles = get_board
52
60
  empty = true
61
+
53
62
  all_tiles.each do |num, details|
54
63
  if num >= lowerbound && num <= upperbound
55
- empty = empty && details.fetch("type") == " "
64
+ empty = empty && details.fetch(:type).nil? # == " "
56
65
  end
57
66
  end
67
+
58
68
  empty
59
69
  end
60
-
70
+
61
71
  def type_on_tile(index)
62
72
  all_tiles = get_board
63
73
  all_tiles.each do |num, details|
64
- return details.fetch("type") if num == index
74
+ return details.fetch(:type) if num == index
65
75
  end
66
76
  end
77
+
78
+ def normalized_tile_name(indexes)
79
+ # 13 -> A3 , 24 -> B4 , etc
80
+ first, second = indexes.to_s.split("")
81
+ Hash[("1".."8").zip("A".."H")][first] + second
82
+ end
67
83
  end
68
84
 
69
85
 
@@ -74,36 +90,51 @@ class TestBoard < MiniTest::Unit::TestCase
74
90
 
75
91
 
76
92
  def test_red_cannot_move_out_of_turn
77
- assert_equal(@black_turn_msg, @board.move("C7", "C5"))
93
+ assert_equal(Messages.black_turn, @board.move("C7", "C5"))
78
94
  end
79
95
 
80
96
  def test_turn_does_not_change_after_invalid_move
81
- move_piece("H2", "H9") # Error moving black pawn
97
+ # Error moving black pawn (invalid)
98
+ move_piece("H2", "H9")
82
99
  assert_equal(false, tile_empty(16, 16))
100
+
101
+ # Success moving black pawn
83
102
  move_piece("H2", "H3")
84
103
  assert_equal(true, tile_empty(16, 16))
85
104
  end
86
105
 
87
106
  def test_unmoved_pawns_can_move_one_space
88
- assert_equal(piece_quantity("pawn"), 16)
107
+ assert_equal(piece_quantity(:pawn), 16)
108
+
89
109
  @columns.each do |c|
90
110
  move_piece("#{c}2", "#{c}3")
91
111
  move_piece("#{c}7", "#{c}6")
92
112
  end
93
- assert_equal(16, piece_quantity("pawn"), "There are no longer 16 pawns on the board")
94
- assert_equal(true, tile_empty(9,16), "Tiles 9 - 16 are still occupied")
95
- assert_equal(true, tile_empty(49, 56), "Tiles 49 - 58 are still occupied")
113
+
114
+ assert_equal(16, piece_quantity(:pawn), "There are no longer 16 pawns on the board")
115
+
116
+ assert_equal(true, tile_empty(9, 16), "Pawns failed to move out of their starting positions")
117
+ assert_equal(false, tile_empty(17, 24), "Pawns failed to move forward to positions one tile forward (tiles 17-24)")
118
+
119
+ assert_equal(false, tile_empty(41, 48), "Pawns failed to move forward to positions one tile forward")
120
+ assert_equal(true, tile_empty(49, 56), "Pawns failed to move out of their starting positions (tiles 49-56)")
96
121
  end
97
122
 
98
123
  def test_unmoved_pawns_can_move_two_spaces
99
- assert_equal(piece_quantity("pawn"), 16)
124
+ assert_equal(piece_quantity(:pawn), 16)
125
+
100
126
  @columns.each do |c|
101
127
  move_piece("#{c}2", "#{c}4")
102
128
  move_piece("#{c}7", "#{c}5")
103
129
  end
104
- assert_equal(16, piece_quantity("pawn"), "There are no longer 16 pawns on the board")
105
- assert_equal(true, tile_empty(9, 24), "Tiles 9 - 24 are still occupied")
106
- assert_equal(true, tile_empty(41, 56), "Tiles 41 - 56 are still occupied")
130
+
131
+ assert_equal(16, piece_quantity(:pawn), "There are no longer 16 pawns on the board")
132
+
133
+ assert_equal(true, tile_empty(9, 24), "Pawns are still in their initial starting positions (tiles 9-24)")
134
+ assert_equal(false, tile_empty(25, 32), "Pawns failed to move forward two positions")
135
+
136
+ assert_equal(false, tile_empty(33, 40), "Pawns failed to move forward two positions")
137
+ assert_equal(true, tile_empty(41, 56), "Pawns are still in their initial starting position (tiles 41-56)")
107
138
  end
108
139
 
109
140
  def test_pawns_only_attack_diagonal
@@ -113,8 +144,19 @@ class TestBoard < MiniTest::Unit::TestCase
113
144
  move_piece("G5", "G4")
114
145
  move_piece("B5", "B6")
115
146
  move_piece("G4", "G3")
147
+
116
148
  assert_equal(["A7", "C7"], valid_piece_movement("B6"), "black pawn should only attack diagonally")
117
- assert_equal(["F2", "H2"], valid_piece_movement("G3"), "red pawn should only attack diagonally")
149
+ assert_equal(["F2", "H2"], valid_piece_movement("G3"), "red pawn should only attack diagonally")
150
+ end
151
+
152
+ def test_bishops_can_move_over_pawns
153
+ # Black
154
+ assert_equal(["A3", "C3"], valid_piece_movement("B1"), "bishop should be able to jump over pawns")
155
+ assert_equal(["F3", "H3"], valid_piece_movement("G1"), "bishop should be able to jump over pawns")
156
+
157
+ # Red
158
+ assert_equal(["A6", "C6"], valid_piece_movement("B8"), "bishop should be able to jump over pawns")
159
+ assert_equal(["F6", "H6"], valid_piece_movement("G8"), "bishop should be able to jump over pawns")
118
160
  end
119
161
 
120
162
  def test_king_cannot_castle_through_check_to_right
@@ -127,11 +169,12 @@ class TestBoard < MiniTest::Unit::TestCase
127
169
  move_piece("G4", "G5") # black pawn
128
170
  move_piece("G6", "G5") # red rook
129
171
  assert_equal(["F1"], valid_piece_movement("E1"), "King should only be allowed to move right one tile")
172
+
130
173
  move_piece("A2", "A3") # black pawn
131
174
  move_piece("G5", "G2") # red rook
132
175
  move_piece("A3", "A4") # black pawn
133
176
  move_piece("G2", "F2") # red rook
134
- assert_equal(@black_in_check, move_piece("E1", "F1"), "King should not be allowed to move into check")
177
+ assert_equal(Messages.black_in_check, move_piece("E1", "F1"), "King should not be allowed to move into check")
135
178
  end
136
179
 
137
180
  def test_king_cannot_castle_through_check_to_left
@@ -144,9 +187,11 @@ class TestBoard < MiniTest::Unit::TestCase
144
187
  move_piece("D1", "D2") # black queen
145
188
  move_piece("B6", "B2") # black pawn
146
189
  assert_equal(["C1", "D1"], valid_piece_movement("E1"), "King should only be allowed to move left one or two tiles")
190
+
147
191
  move_piece("F4", "G5") # black bishop
148
192
  move_piece("B2", "C2") # red rook
149
193
  assert_equal(["D1"], valid_piece_movement("E1"), "King should only be allowed to move left one tile (or it's traversing check)")
194
+
150
195
  move_piece("G5", "H5") # black bishop
151
196
  move_piece("C2", "D2") # red rook
152
197
  assert_equal(["D1"], valid_piece_movement("E1"), "King should only be allowed to move left one tile (or it's traversing check)")
@@ -174,6 +219,80 @@ class TestBoard < MiniTest::Unit::TestCase
174
219
  assert_equal(["C1", "D1"], valid_piece_movement("E1"), "King should be allowed to move left one or two tiles")
175
220
  end
176
221
 
222
+ def test_king_cannot_castle_to_right_after_king_has_moved
223
+ move_piece("G2", "G4") # black pawn
224
+ move_piece("A7", "A5") # red pawn
225
+ move_piece("F1", "H3") # black bishop
226
+ move_piece("A8", "A6") # red rook
227
+ move_piece("G1", "F3") # black knight
228
+ move_piece("A5", "A4") # red pawn
229
+ move_piece("E1", "F1") # move king right one
230
+ move_piece("H7", "H6") # red pawn
231
+ move_piece("F1", "E1") # move king back to its original position
232
+ assert_equal(["F1"], valid_piece_movement("E1"), "King should not be allowed to castle right after moving")
233
+ end
234
+
235
+ def test_king_cannot_castle_to_left_after_king_has_moved
236
+ move_piece("B1", "A3") #
237
+ move_piece("A7", "A6") #
238
+ move_piece("D2", "D4") #
239
+ move_piece("B7", "B6") #
240
+ move_piece("C1", "F4") #
241
+ move_piece("C7", "C6") #
242
+ move_piece("D1", "D2") #
243
+ move_piece("D7", "D6") #
244
+ move_piece("E1", "D1") # Move king left one position
245
+ move_piece("H7", "H6") # red pawn
246
+ move_piece("D1", "E1") # Move king back to original position
247
+ assert_equal(["D1"], valid_piece_movement("E1"), "King should not be allowed to castle left after moving")
248
+ end
249
+
250
+ def test_king_cannot_castle_to_right_after_right_rook_has_moved
251
+ move_piece("G2", "G4") # black pawn
252
+ move_piece("A7", "A5") # red pawn
253
+ move_piece("F1", "H3") # black bishop
254
+ move_piece("A8", "A6") # red rook
255
+ move_piece("G1", "F3") # black knight
256
+ move_piece("A5", "A4") # red pawn
257
+ move_piece("H1", "G1") # move right rook left one position
258
+ move_piece("H7", "H6") # red pawn
259
+ move_piece("G1", "H1") # move right rook back to its original position
260
+ assert_equal(["F1"], valid_piece_movement("E1"), "King should not be allowed to castle right after right rook has moved")
261
+ end
262
+
263
+ def test_king_cannot_castle_to_left_after_left_rook_has_moved
264
+ move_piece("B1", "A3") #
265
+ move_piece("A7", "A6") #
266
+ move_piece("D2", "D4") #
267
+ move_piece("B7", "B6") #
268
+ move_piece("C1", "F4") #
269
+ move_piece("C7", "C6") #
270
+ move_piece("D1", "D2") #
271
+ move_piece("D7", "D6") #
272
+ move_piece("A1", "B1") # Move left rook right one position
273
+ move_piece("H7", "H6") # red pawn
274
+ move_piece("B1", "A1") # Move left rook back to original position
275
+ assert_equal(["D1"], valid_piece_movement("E1"), "King should not be allowed to castle left after left rook has moved")
276
+ end
277
+
278
+ def test_king_can_castle_to_left_or_right_when_both_moves_valid
279
+ move_piece("B1", "A3") # black knight
280
+ move_piece("A7", "A6") # red pawn
281
+ move_piece("D2", "D4") # black pawn
282
+ move_piece("B7", "B6") # red pawn
283
+ move_piece("C1", "F4") # black bishop
284
+ move_piece("C7", "C6") # red pawn
285
+ move_piece("D1", "D3") # black queen #=> castle left now valid
286
+ move_piece("D7", "D6") # red pawn
287
+ move_piece("G1", "H3") # black knight
288
+ move_piece("E7", "E6") # red pawn
289
+ move_piece("G2", "G4") # black pawn
290
+ move_piece("F7", "F6") # red pawn
291
+ move_piece("F1", "G2") # black bishop #=> castle right now valid
292
+ move_piece("G7", "G6") # red pawn
293
+ assert_equal(["C1", "D1", "D2", "F1", "G1"], valid_piece_movement("E1"), "King should be allowed to castle left and right if both moves are valid")
294
+ end
295
+
177
296
  def test_king_can_kill_check_attacker
178
297
  move_piece("A2", "A3") # Error moving black pawn
179
298
  move_piece("A7", "A5") # Error moving red pawn
@@ -183,8 +302,9 @@ class TestBoard < MiniTest::Unit::TestCase
183
302
  move_piece("A6", "E6") # Error moving red rook
184
303
  move_piece("B3", "B4") # Error moving black pawn
185
304
  move_piece("E6", "E2") # Error moving red rook
186
- assert_equal(@black_in_check, move_piece("B4", "B5"))
305
+ assert_equal(Messages.black_in_check, move_piece("B4", "B5"))
187
306
  assert_equal(false, tile_empty(5, 5))
307
+
188
308
  move_piece("E1", "E2") # Error moving black king
189
309
  assert_equal(true, tile_empty(5, 5))
190
310
  end
@@ -198,8 +318,9 @@ class TestBoard < MiniTest::Unit::TestCase
198
318
  move_piece("A6", "E6") # red rook
199
319
  move_piece("B3", "B4") # black pawn
200
320
  move_piece("E6", "E2") # red rook
201
- assert_equal(@black_in_check, move_piece("B4", "B5"))
321
+ assert_equal(Messages.black_in_check, move_piece("B4", "B5"))
202
322
  assert_equal(false, tile_empty(4, 4))
323
+
203
324
  move_piece("D1", "E2")
204
325
  assert_equal(true, tile_empty(4, 4))
205
326
  end
@@ -213,8 +334,9 @@ class TestBoard < MiniTest::Unit::TestCase
213
334
  move_piece("A6", "E6") # red rook
214
335
  move_piece("G4", "G5") # black pawn
215
336
  move_piece("E6", "E2") # red rook
216
- assert_equal(@black_in_check, move_piece("G5", "G6"))
337
+ assert_equal(Messages.black_in_check, move_piece("G5", "G6"))
217
338
  assert_equal(false, tile_empty(4, 4))
339
+
218
340
  move_piece("E1", "F1")
219
341
  assert_equal(true, tile_empty(5,5))
220
342
  end
@@ -230,7 +352,7 @@ class TestBoard < MiniTest::Unit::TestCase
230
352
  move_piece("H4", "H3")
231
353
  $stdin = MyIO.new
232
354
  move_piece("B7", "C8")
233
- assert_equal("queen", type_on_tile(59))
355
+ assert_equal(:queen, type_on_tile(59))
234
356
  end
235
357
 
236
358
  def test_pawn_cannot_be_promoted_out_of_turn
@@ -241,8 +363,8 @@ class TestBoard < MiniTest::Unit::TestCase
241
363
  move_piece("A5", "A6")
242
364
  move_piece("H5", "H4")
243
365
  move_piece("A6", "B7")
244
- $stdin = MyIO.new
245
- assert_equal(@red_turn_msg, move_piece("B7", "C8"))
366
+ $stdin = MyIO.new # send 'Q' to stdin for pawn promotion
367
+ assert_equal(Messages.red_turn, move_piece("B7", "C8"))
246
368
  end
247
369
 
248
370
  def test_pawn_cannot_be_promoted_while_check
@@ -254,12 +376,89 @@ class TestBoard < MiniTest::Unit::TestCase
254
376
  move_piece("H6", "E6")
255
377
  move_piece("A6", "B7")
256
378
  move_piece("E6", "E2")
257
- assert_equal(@black_in_check, move_piece("B7", "C8"))
258
- assert_equal("pawn", type_on_tile(50))
259
- assert_equal("bishop", type_on_tile(59))
379
+ assert_equal(Messages.black_in_check, move_piece("B7", "C8"), "Should not be able to promote pawn while it's in check")
380
+ assert_equal(:pawn, type_on_tile(50))
381
+ assert_equal(:bishop, type_on_tile(59))
382
+ end
383
+
384
+ def test_checkmate_smothered_mate_kings_pawn
385
+ move_piece("E2", "E4")
386
+ move_piece("E7", "E5")
387
+ move_piece("G1", "E2")
388
+ move_piece("B8", "C6")
389
+ move_piece("B1", "C3")
390
+ move_piece("C6", "D4")
391
+ move_piece("G2", "G3")
392
+ assert_equal(Messages.red_winner, move_piece("D4", "F3"), "Checkmate. Red should be victorious!")
393
+ end
394
+
395
+ def test_checkmate_fools_mate
396
+ move_piece("F2", "F3")
397
+ move_piece("E7", "E5")
398
+ move_piece("G2", "G4")
399
+ assert_equal(Messages.red_winner, move_piece("D8", "H4"), "Game should have ended as black is in checkmate")
400
+ end
401
+
402
+ def test_not_checkmate_when_piece_can_remove_checker
403
+ # Black Rook should be able to take red bishop that has black king in check
404
+ # hence piece is not in checkmate
405
+ move_piece("F2", "F3") # black pawn
406
+ move_piece("E7", "E5") # red pawn
407
+ move_piece("G2", "G4") # black pawn
408
+ move_piece("A7", "A6") # red pawn
409
+ move_piece("H2", "H4") # black pawn
410
+ move_piece("A6", "A5") # red pawn
411
+ move_piece("H4", "H5") # black pawn
412
+ move_piece("D8", "H4") # Check (from red queen)
413
+ move_piece("H1", "H4") # Take queen -> no longer in check
414
+ assert_equal(:rook, type_on_tile(32)) #H5
260
415
  end
261
416
 
262
- def test_invalid_moves_not_accepted
263
- # TODO
417
+ def test_not_checkmate_when_piece_can_block_check_path
418
+ move_piece("F2", "F3") # black pawn
419
+ move_piece("E7", "E5") # red pawn
420
+ move_piece("G2", "G4") # black pawn
421
+ move_piece("A7", "A6") # red pawn
422
+ move_piece("H2", "H4") # black pawn
423
+ move_piece("A6", "A5") # red pawn
424
+ move_piece("H4", "H5") # black pawn
425
+ move_piece("A5", "A4") # red pawn
426
+ move_piece("H1", "H2") # black rook (will be able to block check path)
427
+ move_piece("D8", "H4") # red queen (black now in check)
428
+ assert_equal(Messages.black_in_check, move_piece("A2", "A3"), "Should only be able to move out of check for next move")
429
+ move_piece("H2", "F2") # black rook (check now blocked)
430
+ move_piece("H4", "G3") # red queen
431
+ move_piece("B1", "A3") # verify black is no longer in check and can move any piece
264
432
  end
433
+
434
+ def test_invalid_pawn_moves_not_accepted
435
+ piece_to_move = (9.. 16).to_a.sample # piece from black pawn row
436
+ piece_type = type_on_tile(piece_to_move)
437
+ invalid_location = ((1..8).to_a + (33..64).to_a).sample # rows outside of scope
438
+ location_type = type_on_tile(invalid_location)
439
+
440
+ from = normalized_tile_name(Board.new.get_rowcol_from_index(piece_to_move))
441
+ to = normalized_tile_name(Board.new.get_rowcol_from_index(invalid_location))
442
+
443
+ move_piece(from, to)
444
+
445
+ assert_equal(location_type, type_on_tile(invalid_location))
446
+ assert_equal(piece_type, type_on_tile(piece_to_move))
447
+ end
448
+
449
+ def test_non_knights_can_not_move_over_populated_pawn_row
450
+ piece_to_move = ((1..8).to_a - [2, 7]).sample # first row without knights
451
+ piece_type = type_on_tile(piece_to_move)
452
+ invalid_location = ((17..64).to_a).sample # rows outside of scope
453
+ location_type = type_on_tile(invalid_location)
454
+
455
+ from = normalized_tile_name(Board.new.get_rowcol_from_index(piece_to_move))
456
+ to = normalized_tile_name(Board.new.get_rowcol_from_index(invalid_location))
457
+
458
+ move_piece(from, to)
459
+
460
+ assert_equal(location_type, type_on_tile(invalid_location))
461
+ assert_equal(piece_type, type_on_tile(piece_to_move))
462
+ end
463
+
265
464
  end