chess_game 0.0.11

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.
@@ -0,0 +1,185 @@
1
+ class String
2
+
3
+ # "-" overloaded on string to assist in differencing algebraic notation strings
4
+ def -(next_loc)
5
+ if next_loc.length == 2 && self.length == 2
6
+ x = self[0].ord - next_loc[0].ord
7
+ y = self[1].ord - next_loc[1].ord
8
+ return x, y
9
+ end
10
+ end
11
+
12
+ def passed?
13
+ nil
14
+ end
15
+
16
+ def passable?
17
+ nil
18
+ end
19
+
20
+ def passable=(o)
21
+ nil
22
+ end
23
+
24
+ def color
25
+ nil
26
+ end
27
+
28
+ def opponent?(o = nil)
29
+ nil
30
+ end
31
+
32
+ def location
33
+ nil
34
+ end
35
+
36
+ def location=(o = nil)
37
+ nil
38
+ end
39
+
40
+ def move(o = nil, p = nil)
41
+ nil
42
+ end
43
+
44
+ def friend?(o = nil)
45
+ nil
46
+ end
47
+
48
+ def rank?
49
+ ("1".."8").any? { |x| self[0] == x }
50
+ end
51
+
52
+ def file?
53
+ ("A".."H").any? { |x| self[0] == x }
54
+ end
55
+
56
+
57
+ end
58
+
59
+ def squares_between(loc_1, loc_2)
60
+ x, y = loc_2 - loc_1
61
+
62
+ if x == y && y == 0
63
+ return nil
64
+ end
65
+
66
+ if x > 0
67
+ x_range = (loc_1[0]..loc_2[0]).to_a
68
+ elsif x < 0
69
+ x_range = (loc_2[0]..loc_1[0]).to_a.reverse
70
+ end
71
+ if y > 0
72
+ y_range = (loc_1[1]..loc_2[1]).to_a
73
+ elsif y < 0
74
+ y_range = (loc_2[1]..loc_1[1]).to_a.reverse
75
+ end
76
+
77
+ if x != 0 && y == 0
78
+ y_range = Array.new(x_range.length, loc_1[1])
79
+ elsif x == 0 && y != 0
80
+ x_range = Array.new(y_range.length, loc_1[0])
81
+ end
82
+
83
+ # zip up the ranges to made a clean list of square between
84
+ squares_between = []
85
+ x_range.zip(y_range).each { |x| squares_between << x.join }
86
+
87
+ # trim off the first and last squares
88
+ squares_between.delete_at(0)
89
+ squares_between.delete_at(-1)
90
+ return squares_between
91
+ end
92
+
93
+ def check_castle(king, board, horiz_move, vert_move)
94
+ if king.moves > 0 || horiz_move.abs != 2
95
+ return false
96
+ end
97
+
98
+ unless vert_move == 0
99
+ return false
100
+ end
101
+
102
+ if king.black?
103
+ return false unless king.location == "E8"
104
+ queen_rook = board.board["A8"]
105
+ king_rook = board.board["H8"]
106
+ horiz_move < 0 ? attack = "D8" : attack = "F8"
107
+ elsif king.white?
108
+ return false unless king.location == "E1"
109
+ queen_rook = board.board["A1"]
110
+ king_rook = board.board["H1"]
111
+ horiz_move < 0 ? attack = "D1" : attack = "F1"
112
+ else
113
+ return nil
114
+ end
115
+
116
+ # check if the square the king passes through is under attack
117
+ # place a "test king" and see if he is in check
118
+ test_king = King.new(king.color, attack)
119
+
120
+ # need to remove the old king to place a test piece
121
+ board.rv_piece(king.location)
122
+ if board[attack] == " "
123
+ board.set_piece(test_king)
124
+ else
125
+ board.set_piece(king)
126
+ return false
127
+ end
128
+
129
+ if test_king.check?(board)
130
+ board.set_piece(king)
131
+ board.rv_piece(attack)
132
+ return false
133
+ end
134
+
135
+ # remove the test piece and reset the king
136
+ board.rv_piece(attack)
137
+ board.set_piece(king)
138
+
139
+ if horiz_move < 0
140
+ queen_rook.is_a?(Rook) && queen_rook.moves == 0 ? true : (return false)
141
+ elsif horiz_move > 0
142
+ king_rook.is_a?(Rook) && king_rook.moves == 0 ? true : (return false)
143
+ end
144
+ end
145
+
146
+ # check if a castling move was done by the king and move the complementary rook
147
+ def castle_complement(piece, board)
148
+ if piece.moves == 1
149
+ if piece.is_a?(King)
150
+
151
+ case piece.location
152
+ when "C1"
153
+ rook = board["A1"]
154
+ rook_new_loc = "D1"
155
+ when "G1"
156
+ rook = board["H1"]
157
+ rook_new_loc = "F1"
158
+ when "C8"
159
+ rook = board["A8"]
160
+ rook_new_loc = "D8"
161
+ when "G8"
162
+ rook = board["H8"]
163
+ rook_new_loc = "F8"
164
+ end
165
+
166
+ if rook.is_a?(Rook)
167
+ # pick up the king so the rook can move
168
+ board.rv_piece(piece.location)
169
+
170
+ # move the rook
171
+
172
+ board.rv_piece(rook.location)
173
+ rook.location = rook_new_loc
174
+ board.set_piece(rook)
175
+
176
+ # replace the king after
177
+ board.set_piece(piece)
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ def relative_square(location, x, y)
184
+ (location[0].ord + x).chr + (location[1].ord + y).chr
185
+ end
data/lib/chess/pawn.rb ADDED
@@ -0,0 +1,58 @@
1
+ require_relative 'piece.rb'
2
+
3
+ class Pawn < Piece
4
+
5
+ WHITE_PAWN_ICON = "\u2659"
6
+ BLACK_PAWN_ICON = "\u265F"
7
+
8
+ def initialize(color = "white", location = "A1")
9
+ case color.downcase
10
+ when "white"
11
+ @icon = WHITE_PAWN_ICON
12
+ when "black"
13
+ @icon = BLACK_PAWN_ICON
14
+ end
15
+ super
16
+ end
17
+
18
+ def move(new_loc, board)
19
+ x, y = new_loc - @location
20
+ @passable = true if y.abs == 2
21
+ super
22
+ end
23
+
24
+ def valid_move?(new_loc, board)
25
+ x, y = new_loc - @location
26
+
27
+ y *= -1 if self.black?
28
+
29
+ if x == 0 && y == 1 && board[new_loc] == " "
30
+ elsif x == 0 && y == 2 && @moves == 0 && board[new_loc] == " "
31
+ elsif y == 1 && x.abs == 1 && board.board[new_loc].opponent?(self)
32
+ elsif en_passant?(new_loc, board)
33
+ else
34
+ board.move_status = "Pawns cannot make that move."
35
+ return false
36
+ end
37
+ super
38
+
39
+ end
40
+
41
+ def en_passant?(new_loc, board)
42
+ x, y = new_loc - @location
43
+ return false unless x.abs == 1 && y.abs == 1
44
+ passing_square = relative_square(@location, x, 0)
45
+ passed_piece = board[passing_square]
46
+ return false unless passed_piece.is_a?(Pawn)
47
+ # conditions for en passant, must be opposing pawn that moved forward twice to
48
+ # the square it would have occupied if it was only moved one square
49
+ if self.opponent?(passed_piece) && passed_piece.passable?
50
+ # board.rv_piece(passing_square)
51
+ passed_piece.passed = true
52
+ return passed_piece.location
53
+ else
54
+ return false
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,134 @@
1
+ require_relative 'move_helper.rb'
2
+
3
+ class Piece
4
+
5
+ def initialize(color = "white", location = 'A1')
6
+ @color = color
7
+ @location = location.upcase
8
+ @moves = 0
9
+
10
+ # for determining en passant
11
+ @passable = false
12
+ @passed = false
13
+ end
14
+
15
+ def passable?
16
+ @passable
17
+ end
18
+
19
+ def passable=(o)
20
+ @passable = o
21
+ end
22
+
23
+ def passed?
24
+ @passed
25
+ end
26
+
27
+ def passed=(o)
28
+ @passed = o
29
+ end
30
+
31
+ def moves
32
+ @moves
33
+ end
34
+
35
+ def moves=(moves)
36
+ @moves = moves
37
+ end
38
+
39
+ def color
40
+ @color
41
+ end
42
+
43
+ def location
44
+ @location
45
+ end
46
+
47
+ def file
48
+ @location[0]
49
+ end
50
+
51
+ def rank
52
+ @location[1]
53
+ end
54
+
55
+ def location=(location)
56
+ @location = location
57
+ end
58
+
59
+ def icon
60
+ @icon
61
+ end
62
+
63
+ def to_s
64
+ @icon
65
+ end
66
+
67
+ def black?
68
+ @color == "black"
69
+ end
70
+
71
+ def white?
72
+ @color == "white"
73
+ end
74
+
75
+ def opponent?(piece)
76
+ piece.color != @color
77
+ end
78
+
79
+ def friend?(piece)
80
+ piece.color == @color
81
+ end
82
+
83
+ def move(new_loc, board)
84
+ if self.valid_move?(new_loc, board)
85
+ @moves += 1
86
+ @location = new_loc
87
+ else
88
+ return nil
89
+ end
90
+ end
91
+
92
+ def can_promote?
93
+ ((@color == "white" && self.rank == "8") || (@color == "black" && self.rank == "1")) && self.is_a?(Pawn)
94
+ end
95
+
96
+ def my_king(board)
97
+ # tells a player where his beloved monarch is
98
+ board.board.each do |location, piece|
99
+ if piece.is_a?(King) && piece.color == self.color
100
+ return piece
101
+ end
102
+ end
103
+ end
104
+
105
+ def valid_move?(new_loc, board)
106
+ if board[new_loc].nil?
107
+ board.move_status = "Position is off the board"
108
+ return false
109
+ elsif @location == new_loc
110
+ board.move_status = "Piece must move"
111
+ return false
112
+ elsif board.piece_between?(@location, new_loc)
113
+ board.move_status = "Piece in between move"
114
+ return false
115
+ elsif self.friend?(board[new_loc])
116
+ board.move_status = "Can't take your own piece"
117
+ return false
118
+ else
119
+ return true
120
+ end
121
+ end
122
+
123
+ def ==(o)
124
+ o.class == self.class && o.state == state
125
+ end
126
+ alias_method :eql?, :==
127
+
128
+ protected
129
+
130
+ def state
131
+ [@color, @location, @moves]
132
+ end
133
+
134
+ end
@@ -0,0 +1,36 @@
1
+ class Player
2
+
3
+ def initialize(color = "white")
4
+ @color = color
5
+ end
6
+
7
+
8
+ def color
9
+ @color
10
+ end
11
+
12
+ def move(loc_1, loc_2, board)
13
+
14
+ return nil unless loc_1.is_a?(String) && loc_2.is_a?(String)
15
+
16
+ loc_1.upcase!
17
+ loc_2.upcase!
18
+
19
+ return nil unless loc_1[0].file? && loc_2[0].file? && loc_1[1].rank? && loc_2[1].rank?
20
+ if board.board[loc_1].color == @color
21
+ unless self.king(board).will_be_in_check?(loc_1, loc_2, board)
22
+ board.move(loc_1, loc_2)
23
+ else
24
+ board.move_status = "Can't put your king into Check"
25
+ return nil
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ def king(board)
32
+ king = board.board.select do |square, piece|
33
+ return piece if piece.color == self.color && piece.class == King
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'piece.rb'
2
+
3
+ class Queen < Piece
4
+
5
+ WHITE_QUEEN_ICON = "\u2655"
6
+ BLACK_QUEEN_ICON = "\u265B"
7
+
8
+ def initialize(color = "white", location = "A1")
9
+ case color.downcase
10
+ when "white"
11
+ @icon = WHITE_QUEEN_ICON
12
+ when "black"
13
+ @icon = BLACK_QUEEN_ICON
14
+ end
15
+ super
16
+ end
17
+
18
+ def valid_move?(new_loc, board)
19
+ x, y = new_loc - @location
20
+ if x.abs == y.abs
21
+ elsif x == 0 && y.abs > 0
22
+ elsif y == 0 && x.abs > 0
23
+ else
24
+ board.move_status = "Queens cannot make that move."
25
+ return false
26
+ end
27
+ super
28
+ end
29
+
30
+ end
data/lib/chess/rook.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative 'piece.rb'
2
+
3
+ class Rook < Piece
4
+
5
+ WHITE_ROOK_ICON = "\u2656"
6
+ BLACK_ROOK_ICON = "\u265C"
7
+
8
+ def initialize(color = "white", location = "A1")
9
+ case color.downcase
10
+ when "white"
11
+ @icon = WHITE_ROOK_ICON
12
+ when "black"
13
+ @icon = BLACK_ROOK_ICON
14
+ end
15
+ super
16
+ end
17
+
18
+ def valid_move?(new_loc, board)
19
+ x, y = new_loc - @location
20
+
21
+ if x == 0 && y.abs > 0
22
+ elsif y == 0 && x.abs > 0
23
+ else
24
+ board.move_status = "Rooks cannot make that move."
25
+ return false
26
+ end
27
+ super
28
+ end
29
+
30
+ end
@@ -0,0 +1,3 @@
1
+ module Chess
2
+ VERSION = "0.0.1"
3
+ end
data/lib/chess.rb ADDED
@@ -0,0 +1,71 @@
1
+ require_relative 'chess/board.rb'
2
+
3
+ module Chess
4
+ def self.play
5
+ @game = Game.new
6
+
7
+ @board = @game.board
8
+
9
+ players = [@game.white_player, @game.black_player]
10
+
11
+ player = @game.turn
12
+ opponent = @game.opponent
13
+
14
+ loop do
15
+
16
+ @board.display
17
+
18
+ # look for pieces that need promotion
19
+ promote = @board.piece_to_promote?
20
+ if promote
21
+ puts "Promote your piece, enter 'Queen', 'Knight', 'Bishop', or 'Rook'"
22
+ new_piece = Kernel.const_get(gets.chomp.to_sym)
23
+ @board.promote(promote.location, new_piece)
24
+ end
25
+
26
+ # look for check and checkmate conditions
27
+ if player.king(@board).checkmate?(@board)
28
+ puts "#{player.color.capitalize} Player, you are in " + "CHECKMATE".colorize(:red)
29
+ puts "#{opponent.color.capitalize} Player " + "WINS".colorize(:green)
30
+ break
31
+ elsif player.king(@board).stalemate?(@board)
32
+ puts "#{player.color.capitalize} Player, you are in " + "STALEMATE".colorize(:yellow)
33
+ puts "DRAW"
34
+ break
35
+ elsif player.king(@board).check?(@board)
36
+ puts "#{player.color.capitalize} Player, you are in " + "CHECK".colorize(:yellow)
37
+ end
38
+
39
+ puts "#{player.color.capitalize} Player, enter your move:"
40
+ locs = gets.chomp.split(" ")
41
+
42
+ # handle special cases, otherwise run the move
43
+ case locs[0]
44
+ when "exit", "quit"
45
+ puts "Are you sure? (y/n)"
46
+ y_or_n = gets.chomp
47
+ break if y_or_n[0].upcase == "Y"
48
+ when "save"
49
+ puts "enter a name to save the game as:"
50
+ file_name = gets.chomp
51
+ @game.save("save/#{file_name}")
52
+ break
53
+ when "load"
54
+ puts "enter the name of the game you want to load:"
55
+ file_name = gets.chomp
56
+ @game = Game.load("save/#{file_name}")
57
+ @board = @game.board
58
+ players = [@game.white_player, @game.black_player]
59
+ player, opponent = @game.turn, @game.opponent
60
+ else
61
+ unless player.move(locs[0], locs[1], @board).nil?
62
+ player, opponent = opponent, player
63
+ @game.turn = player
64
+ @game.opponent = opponent
65
+ else
66
+ puts "Invalid move, try again".colorize(:red)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
data/save/test ADDED
Binary file
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/chess/bishop.rb'
3
+
4
+ describe Bishop do
5
+
6
+ it { should be_a_kind_of(Piece) }
7
+ it { should respond_to :color }
8
+ it { should respond_to :location }
9
+
10
+ it "should know what its character is" do
11
+ @bishop = Bishop.new("White", "A1")
12
+ expect(@bishop.icon).to eq("\u2657")
13
+ end
14
+
15
+ describe "valid moves" do
16
+ before { @bishop = Bishop.new("white", "D4") }
17
+ before { @board = Board.new }
18
+
19
+ it "should not be able to move vertically to any position" do
20
+ expect(@bishop.valid_move?("A3", @board)).to be_falsey
21
+ end
22
+
23
+ it "should not be able to move horizontally to any position" do
24
+ expect(@bishop.valid_move?("D8", @board)).to be_falsey
25
+ end
26
+
27
+ it "should be able to move diagonally" do
28
+ expect(@bishop.valid_move?("C3", @board)).to be_truthy
29
+ end
30
+ end
31
+
32
+ end