terminal_chess 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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