terminal_chess 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1525bec00c6def643f46e3da0d7b74380dfe7c8e
4
+ data.tar.gz: 5a12c4bad7a2aa61e67fb5146428041ce781c9c3
5
+ SHA512:
6
+ metadata.gz: 27593e358634d5d7bc01415d3eda57aa4c3358116475620a7ae78ab55352ee63536bc771b32c42a7f747b4370c558e013ab73c45c93818de313ec7f4bf501274
7
+ data.tar.gz: 15641a90f654dfcb15a1887784227d8301abde058e8060a3ded4d4bb03aaae8c3e321f7345121db4d13a34c8d26e86218c05fbb8f28ab2f77a38ea7d5666e643
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in terminal_chess.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jason Willems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ Terminal-Chess
2
+ ==============
3
+
4
+ A two-player chess game for the Terminal, written in Ruby (output below is colorized when run in the terminal)
5
+ <pre>
6
+ >> Welcome to Terminal Chess v0.1.0
7
+
8
+ _A__ _B__ _C__ _D__ _E__ _F__ _G__ _H__
9
+ | || || || || || || || |
10
+ 1 | || KN || BI || QU || KI || BI || KN || RO | 1
11
+ |____||____||____||____||____||____||____||____|
12
+ | || || || || || || || |
13
+ 2 | || PA || PA || PA || PA || PA || PA || PA | 2
14
+ |____||____||____||____||____||____||____||____|
15
+ | || || || || || || || |
16
+ 3 | || || || || RO || || || | 3
17
+ |____||____||____||____||____||____||____||____|
18
+ | || || || || || || || |
19
+ 4 | PA || || || || || || || | 4
20
+ |____||____||____||____||____||____||____||____|
21
+ | || || || || || || || |
22
+ 5 | PA || || || || || || || | 5
23
+ |____||____||____||____||____||____||____||____|
24
+ | || || || || || || || |
25
+ 6 | || || || || || || || | 6
26
+ |____||____||____||____||____||____||____||____|
27
+ | || || || || || || || |
28
+ 7 | || PA || PA || PA || PA || PA || PA || PA | 7
29
+ |____||____||____||____||____||____||____||____|
30
+ | || || || || || || || |
31
+ 8 | RO || KN || BI || QU || KI || BI || KN || RO | 8
32
+ |____||____||____||____||____||____||____||____|
33
+ A B C D E F G H
34
+
35
+ Piece to Move: B8
36
+ Valid destinations: C6, A6
37
+ Location: c6
38
+
39
+ </pre>
40
+
41
+ ![Screenshot](http://at1as.github.io/github_repo_assets/terminal_chess.jpg)
42
+
43
+ ### Requirements
44
+ Requires the colorize Ruby gem (listed in .gemspec file)
45
+ ```bash
46
+ $ sudo gem install colorize
47
+ ```
48
+
49
+ ### Usage
50
+ The easiest way to use terminal_chess is to install it via the [Rubygem](https://rubygems.org/gems/terminal_chess). This is likely to be a few commits behind, but generally more stable.
51
+
52
+ Otherwise, clone this repo directly and run:
53
+ ```bash
54
+ $ git clone git@github.com:at1as/Terminal-Chess.git
55
+ $ chmod +x lib/terminal_chess.rb
56
+ $ ruby lib/terminal_chess.rb
57
+ ```
58
+
59
+ ### Limitations
60
+ * Built and tested on Terminal in OS 10.10
61
+ * For now, checkmate will need to be verified manually
62
+ * TODO: Code cleanup. Printer module is painful to read.
63
+ * Niether player can be automated
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ #t.test_files = FileList['test/test*.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ desc "Run all tests..."
11
+ task :default => :test
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'terminal_chess'
data/lib/Board.rb ADDED
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ load 'printer.rb'
4
+ load 'move.rb'
5
+ require 'colorize'
6
+
7
+ class Board
8
+
9
+ include PRINTER
10
+ include MOVE
11
+
12
+ def initialize
13
+ @@piece_locations = Hash.new
14
+ @@piece_locations_buffer = Hash.new
15
+ @@row_mappings = Hash[("A".."H").zip(1..8)]
16
+ @@taken_pieces = []
17
+ @@player_turn = "black"
18
+ @@checkmate = false
19
+ end
20
+
21
+
22
+ # Game logic
23
+ def move(p1, p2)
24
+ manifest = piece_manifest
25
+ p1 = get_index_from_rowcol(p1.to_s)
26
+ p2 = get_index_from_rowcol(p2.to_s)
27
+ valid_positions = possible_moves(p1, manifest, true)
28
+
29
+ ##Subtract king current position from valid positions
30
+ valid_positions -= king_positions
31
+
32
+ # Allow piece movements, unless in checkmate
33
+ if !@@checkmate
34
+ # Ensure player is moving in turn
35
+ if @@player_turn == @@piece_locations[p1]["color"]
36
+
37
+ @@piece_locations_buffer = @@piece_locations.clone
38
+
39
+ # Chosen destination is within the list of valid destinations
40
+ if ([p2] - valid_positions).empty?
41
+
42
+ @@piece_locations_buffer[p2] = @@piece_locations_buffer[p1]
43
+ @@piece_locations_buffer[p2]["moved"] = true
44
+ @@piece_locations_buffer[p1] = {"type" => " ", "number" => nil, "color" => nil}
45
+
46
+ # If the current player is not in check at the end of the turn, allow them to proceed
47
+ if !check?(@@player_turn, @@piece_locations_buffer)
48
+
49
+ @@taken_pieces << @@piece_locations[p2] if !@@piece_locations[p2]["number"].nil?
50
+
51
+ # Check for Pawn Promotion (if pawn reaches end of the board, promote it)
52
+ if @@piece_locations_buffer[p2]["type"] == "pawn"
53
+ if p2 < 9 && @@piece_locations_buffer[p2]["color"] == "red"
54
+ promote(p2)
55
+ elsif p2 > 56 && @@piece_locations_buffer[p2]["color"] == "black"
56
+ promote(p2)
57
+ end
58
+ end
59
+
60
+ # Check for Castling
61
+ if @@piece_locations_buffer[p2]["type"] == "king" && (p2 - p1).abs == 2
62
+
63
+ p2 < 9 ? y_offset = 0 : y_offset = 56
64
+
65
+ if p2 > p1
66
+ @@piece_locations_buffer[6+y_offset] = @@piece_locations_buffer[8+y_offset]
67
+ @@piece_locations_buffer[8+y_offset] = {"type" => " ", "number" => nil, "color" => nil}
68
+ else
69
+ @@piece_locations_buffer[4+y_offset] = @@piece_locations_buffer[1+y_offset]
70
+ @@piece_locations_buffer[1+y_offset] = {"type" => " ", "number" => nil, "color" => nil}
71
+ end
72
+ end
73
+
74
+ # Clean Up
75
+ @@piece_locations = @@piece_locations_buffer
76
+ @@player_turn = (["black", "red"] - [@@player_turn]).first
77
+ board_refresh
78
+ else
79
+ puts "Please move #{@@player_turn} king out of check to continue"
80
+ end
81
+
82
+ else
83
+ puts "Please select a valid destination."
84
+ end
85
+ else
86
+ puts "It is #{@@player_turn}'s turn. Please move a #{@@player_turn} piece."
87
+ end
88
+ else
89
+ puts "Checkmate! Game Over."
90
+ end
91
+ end
92
+
93
+ # Return the valid positions for piece at current_pos to move in readable format [A-H][1-8]
94
+ def valid_destinations(current_pos)
95
+ readable_positions = []
96
+ manifest = piece_manifest
97
+ p1 = get_index_from_rowcol(current_pos.to_s)
98
+
99
+ valid_positions = possible_moves(p1, manifest, true)
100
+ valid_positions.each do |pos|
101
+ grid_pos = get_rowcol_from_index(pos)
102
+ # Map first string character 1-8 to [A-H], for column, and then add second string character as [1-8]
103
+ readable_positions << (@@row_mappings.key(grid_pos[0].to_i) + grid_pos[1].to_s)
104
+ end
105
+ return readable_positions.sort
106
+ end
107
+
108
+ # Search piece manifest for kings. Remove them from the list of positions returned
109
+ # from the MOVE module (so that players cannot take the "king" type piece)
110
+ def king_positions
111
+ king_locations = []
112
+ @@piece_locations.each do |piece, details|
113
+ if details["type"] == "king"
114
+ king_locations << piece
115
+ end
116
+ end
117
+ return king_locations
118
+ end
119
+
120
+
121
+ # Once a pawn reaches the end, this method is called to swap the pawn
122
+ # for another piece (from the list below)
123
+ def promote(p1)
124
+ puts "Promote to: [Q]ueen, [K]night, [R]ook, [B]ishop"
125
+ while true
126
+ promo_piece = gets.chomp.downcase
127
+ if promo_piece == "q" || promo_piece == "queen"
128
+ @@piece_locations_buffer[p1]["type"] = "queen"
129
+ break
130
+ elsif promo_piece == "k" || promo_piece == "knight"
131
+ @@piece_locations_buffer[p1]["type"] = "knight"
132
+ break
133
+ elsif promo_piece == "r" || promo_piece == "rook"
134
+ @@piece_locations_buffer[p1]["type"] = "rook"
135
+ break
136
+ elsif promo_piece == "b" || promo_piece == "bishop"
137
+ @@piece_locations_buffer[p1]["type"] = "bishop"
138
+ break
139
+ else
140
+ puts "Please enter one of: [Q]ueen, [K]night, [R]ook, [B]ishop"
141
+ end
142
+ end
143
+ end
144
+
145
+ private :promote
146
+
147
+ # Return whether the player of a specified color has their king currently in check
148
+ # by checking the attack vectors of all the opponents players, versus the king location
149
+ # Also, check whether king currently in check, has all of their valid moves within
150
+ # their opponents attack vectors, and therefore are in checkmate (@@checkmate)
151
+ def check?(color, proposed_manifest = @@piece_locations)
152
+ path, king_loc = [], []
153
+ enemy_color = (["black", "red"] - ["#{color}"]).first
154
+
155
+ proposed_manifest.each do |piece, details|
156
+ if details["color"] == enemy_color
157
+ path << possible_moves(piece, proposed_manifest)
158
+ end
159
+ if details["color"] == color && details["type"] == "king"
160
+ king_loc = piece
161
+ end
162
+ end
163
+
164
+ danger_vector = path.flatten.uniq
165
+ king_positions = possible_moves(king_loc, proposed_manifest)
166
+
167
+ # If the King is in the attackable locations for the opposing player
168
+ if danger_vector.include? king_loc
169
+ # If all the positions the can move to is also attackable by the opposing player
170
+ if (king_positions - danger_vector).length != 0
171
+ # This is flawed. It verified whether the king could move out check
172
+ # There are two other cases: whether a piece can remove the enemy
173
+ # And whether the enemy's attack vector can be blocked
174
+ #@@checkmate = true
175
+ end
176
+ return true
177
+ # Piece not in check
178
+ else
179
+ return false
180
+ end
181
+ end
182
+
183
+
184
+ # Board spaces that are attackable by opposing pieces
185
+ # TODO: check? method should use this function
186
+ def attack_vectors(color = @@player_turn, proposed_manifest = @@piece_locations)
187
+ enemy_color = (["black", "red"] - ["#{color}"]).first
188
+ kill_zone = []
189
+
190
+ proposed_manifest.each do |piece, details|
191
+ if details["color"] == enemy_color
192
+ kill_zone << possible_moves(piece, proposed_manifest)
193
+ end
194
+ end
195
+
196
+ kill_zone.flatten.uniq
197
+ end
198
+
199
+
200
+ # Reprint the board. Called after every valid piece move
201
+ def board_refresh
202
+ print_board(@@piece_locations)
203
+ end
204
+
205
+
206
+ # Convert index [A-H][1-8] => (1 - 64)
207
+ def get_index_from_rowcol(row_col)
208
+ offset = @@row_mappings[row_col[0]].to_i
209
+ multiplier = row_col[1].to_i - 1
210
+ return multiplier * 8 + offset
211
+ end
212
+
213
+
214
+ # Convert index (1 - 64) => [A-H][1-8]
215
+ def get_rowcol_from_index(index)
216
+ letter = get_col_from_index(index)
217
+ number = get_row_from_index(index)
218
+ "#{letter}#{number}"
219
+ end
220
+
221
+
222
+ # Intial setup of board. Put pieces into the expected locations
223
+ def setup_board
224
+ (1..64).each do |location|
225
+ @@piece_locations[location] = {"type" => " ", "number" => nil, "color" => nil}
226
+ end
227
+
228
+ # Black Pieces
229
+ @@piece_locations[1] = {"type" => "rook", "number" => 1, "color" => "black", "moved" => false}
230
+ @@piece_locations[2] = {"type" => "knight", "number" => 1, "color" => "black", "moved" => false}
231
+ @@piece_locations[3] = {"type" => "bishop", "number" => 1, "color" => "black", "moved" => false}
232
+ @@piece_locations[4] = {"type" => "queen", "number" => 1, "color" => "black", "moved" => false}
233
+ @@piece_locations[5] = {"type" => "king", "number" => 1, "color" => "black", "moved" => false}
234
+ @@piece_locations[6] = {"type" => "bishop", "number" => 2, "color" => "black", "moved" => false}
235
+ @@piece_locations[7] = {"type" => "knight", "number" => 2, "color" => "black", "moved" => false}
236
+ @@piece_locations[8] = {"type" => "rook", "number" => 2, "color" => "black", "moved" => false}
237
+ (1..8).each do |col|
238
+ @@piece_locations[col + 8] = {"type" => "pawn", "number" => col, "color" => "black", "moved" => false}
239
+ end
240
+
241
+ # White Pieces
242
+ @@piece_locations[57] = {"type" => "rook", "number" => 1, "color" => "red", "moved" => false}
243
+ @@piece_locations[58] = {"type" => "knight", "number" => 1, "color" => "red", "moved" => false}
244
+ @@piece_locations[59] = {"type" => "bishop", "number" => 1, "color" => "red", "moved" => false}
245
+ @@piece_locations[60] = {"type" => "queen", "number" => 1, "color" => "red", "moved" => false}
246
+ @@piece_locations[61] = {"type" => "king", "number" => 1, "color" => "red", "moved" => false}
247
+ @@piece_locations[62] = {"type" => "bishop", "number" => 2, "color" => "red", "moved" => false}
248
+ @@piece_locations[63] = {"type" => "knight", "number" => 2, "color" => "red", "moved" => false}
249
+ @@piece_locations[64] = {"type" => "rook", "number" => 2, "color" => "red", "moved" => false}
250
+ (1..8).each do |col|
251
+ @@piece_locations[col + 48] = {"type" => "pawn", "number" => col, "color" => "red", "moved" => false}
252
+ end
253
+ end
254
+
255
+
256
+ def piece_manifest
257
+ return @@piece_locations
258
+ end
259
+
260
+ # Not currently used
261
+ def taken_pieces
262
+ return @@taken_pieces
263
+ end
264
+
265
+ def checkmate?
266
+ return @@checkmate
267
+ end
268
+
269
+ def player_turn
270
+ return @@player_turn
271
+ end
272
+
273
+ end
data/lib/move.rb ADDED
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module MOVE
4
+
5
+ def constants(piece_mapping, color, piece)
6
+ @@pieces = piece_mapping
7
+ @@color = color
8
+ @@enemy_color = (["black", "red"] - ["#{color.downcase}"]).first
9
+ @@type = piece
10
+ end
11
+
12
+
13
+ # Calls methods below to return a list of positions which are valid moves
14
+ # for piece at index p1, given the current board layout as defined in manifest
15
+ def possible_moves(p1, manifest, castling = false)
16
+ allowed = []
17
+ type = manifest[p1]["type"]
18
+ my_color = manifest[p1]["color"]
19
+ constants(manifest, my_color, type)
20
+
21
+ unless unoccupied?(p1)
22
+
23
+ if type == "king"
24
+ allowed += [move_lateral(p1, 1)].flatten
25
+ allowed += [move_diagonal(p1, 1)].flatten
26
+ allowed += [castle(p1)].flatten if castling
27
+
28
+ elsif type == "queen"
29
+ allowed += [move_lateral(p1)].flatten
30
+ allowed += [move_diagonal(p1)].flatten
31
+
32
+ elsif type == "rook"
33
+ allowed += [move_lateral(p1)].flatten
34
+
35
+ elsif type == "bishop"
36
+ allowed += [move_diagonal(p1)].flatten
37
+
38
+ elsif type == "pawn"
39
+ allowed += [pawn(p1)].flatten
40
+
41
+ elsif type == "knight"
42
+ allowed += [knight(p1)].flatten
43
+ end
44
+
45
+ end
46
+ return allowed
47
+ end
48
+
49
+
50
+ # Returns all valid positions a pawn at index p1 can move to
51
+ def pawn(p1)
52
+ row = get_row_from_index(p1)
53
+ col = get_col_from_index(p1)
54
+ valid = []
55
+
56
+ # Piece color defines direction of travel. Enemy presence defines
57
+ # the validity of diagonal movements
58
+ if @@color == "red"
59
+ if unoccupied?(p1 - 8)
60
+ valid << (p1 - 8)
61
+ end
62
+ if piece_color(p1 - 7) == @@enemy_color && col < 8
63
+ valid << (p1 - 7)
64
+ end
65
+ if piece_color(p1 - 9) == @@enemy_color && col > 1
66
+ valid << (p1 - 9)
67
+ end
68
+ # Only if the pieces is unmoved, can it move forward two rows
69
+ if !@@pieces[p1]["moved"] && unoccupied?(p1 - 16) && unoccupied?(p1 - 8)
70
+ valid << (p1 - 16)
71
+ end
72
+ elsif @@color == "black"
73
+ if unoccupied?(p1 + 8)
74
+ valid << (p1 + 8)
75
+ end
76
+ if piece_color(p1 + 7) == @@enemy_color && col > 1
77
+ valid << (p1 + 7)
78
+ end
79
+ if piece_color(p1 + 9) == @@enemy_color && col < 8
80
+ valid << (p1 + 9)
81
+ end
82
+ if !@@pieces[p1]["moved"] && unoccupied?(p1 + 16) && unoccupied?(p1 + 8)
83
+ valid << (p1 + 16)
84
+ end
85
+ end
86
+
87
+ return valid
88
+ end
89
+
90
+
91
+ # Returns valid positions a knight at index p1 can move to
92
+ def knight(p1)
93
+ row = get_row_from_index(p1)
94
+ col = get_col_from_index(p1)
95
+ valid = []
96
+ valid_no_ff = []
97
+
98
+ # Valid knight moves based on its board position
99
+ if row < 7 && col < 8
100
+ valid << (p1 + 17)
101
+ end
102
+ if row < 7 && col > 1
103
+ valid << (p1 + 15)
104
+ end
105
+ if row < 8 && col < 7
106
+ valid << (p1 + 10)
107
+ end
108
+ if row < 8 && col > 2
109
+ valid << (p1 + 6)
110
+ end
111
+ if row > 1 && col < 7
112
+ valid << (p1 - 6)
113
+ end
114
+ if row > 1 && col > 2
115
+ valid << (p1 - 10)
116
+ end
117
+ if row > 2 && col < 8
118
+ valid << (p1 - 15)
119
+ end
120
+ if row > 2 && col > 1
121
+ valid << (p1 - 17)
122
+ end
123
+
124
+ # All possible moves for the knight based on board boundaries will added
125
+ # This iterator filters for friendly fire, and removes indexes pointing to same color pices
126
+ valid.each do |pos|
127
+ unless piece_color(pos) == @@color
128
+ valid_no_ff << pos
129
+ end
130
+ end
131
+
132
+ return valid_no_ff
133
+ end
134
+
135
+
136
+ # Lateral movements (Left, Right, Up, Down). Will return all valid lateral movies for a piece at index
137
+ # By default, it will extend the piece movement laterally until it hits a board edge
138
+ # or until it hits a piece. This can be changed by passing the limit argument
139
+ # For example, the king can only move laterally 1 position, so it would pass limit=1
140
+ def move_lateral(index, limit = 8)
141
+ row = get_row_from_index(index)
142
+ col = get_col_from_index(index)
143
+ left, right = [col-1, limit].min, [8-col, limit].min
144
+ up, down = [row-1, limit].min, [8-row, limit].min
145
+ valid = []
146
+
147
+ # Move down N places until board limit, piece in the way, or specified limit
148
+ down.times do |i|
149
+ next_pos = index + (i+1)*8
150
+ # Valid move if position is unoccupied
151
+ if unoccupied?(next_pos)
152
+ valid << next_pos
153
+ else
154
+ # Valid move is piece is an enemy, but then no subsequent tiles are attackable
155
+ # if the piece is not an enemy, it's not added as a valid move, and no subsequent tiles are attackable
156
+ # This function doesn't filter out the king from a valid enemy, but the Board class will drop King indexes
157
+ if piece_color(next_pos) == @@enemy_color
158
+ valid << next_pos
159
+ end
160
+ break
161
+ end
162
+ end
163
+
164
+ # Move up N places until board limit, piece in the way, or specified limit
165
+ up.times do |i|
166
+ next_pos = index - (i+1)*8
167
+ if unoccupied?(next_pos)
168
+ valid << next_pos
169
+ else
170
+ #puts "PC #{piece_color(next_pos)} #{enemy_color}" #INCOMEPSDFJDSLFJDKLFJDASKLF
171
+ if piece_color(next_pos) == @@enemy_color
172
+ valid << next_pos
173
+ end
174
+ break
175
+ end
176
+ end
177
+
178
+ # Move right N places until board limit, piece in the way, or specified limit
179
+ right.times do |i|
180
+ next_pos = index + (i+1)
181
+ if unoccupied?(next_pos)
182
+ valid << next_pos
183
+ else
184
+ if piece_color(next_pos) == @@enemy_color
185
+ valid << next_pos
186
+ end
187
+ break
188
+ end
189
+ end
190
+
191
+ # Move left N places until board limit, piece in the way, or specified limit
192
+ left.times do |i|
193
+ next_pos = index - (i+1)
194
+ if unoccupied?(next_pos)
195
+ valid << next_pos
196
+ else
197
+ if piece_color(next_pos) == @@enemy_color
198
+ valid << next_pos
199
+ end
200
+ break
201
+ end
202
+ end
203
+ return valid
204
+ end
205
+
206
+
207
+ # Diagonal movements. Will return all valid diagonal movies for a piece at index
208
+ # By default, it will extend the piece movement diagonally until it hits a board edge
209
+ # or until it hits a piece. This can be changed by passing the limit argument
210
+ # For example, the king can only move diagonally 1 position, so it would pass limit=1
211
+ def move_diagonal(index, limit = 8)
212
+
213
+ row = get_row_from_index(index)
214
+ col = get_col_from_index(index)
215
+ left, right = [col-1, limit].min, [8-col, limit].min
216
+ up, down = [row-1, limit].min, [8-row, limit].min
217
+ valid = []
218
+
219
+ # up and to the right
220
+ ([up, right, limit].min).times do |i|
221
+ next_pos = index - (i+1)*7
222
+ # Valid move if position is unoccupied
223
+ if unoccupied?(next_pos)
224
+ valid << next_pos
225
+ else
226
+ # Valid move is piece is an enemy, but then no subsequent tiles are attackable
227
+ # if the piece is not an enemy, it's not added as a valid move, and no subsequent tiles are attackable
228
+ # This function doesn't filter out the king from a valid enemy, but the Board class will drop King indexes
229
+ if piece_color(next_pos) == @@enemy_color
230
+ valid << next_pos
231
+ end
232
+ break
233
+ end
234
+ end
235
+
236
+ # up and to the left
237
+ ([up, left, limit].min).times do |i|
238
+ next_pos = index - (i+1)*9
239
+ if unoccupied?(next_pos)
240
+ valid << next_pos
241
+ else
242
+ if piece_color(next_pos) == @@enemy_color
243
+ valid << next_pos
244
+ end
245
+ break
246
+ end
247
+ end
248
+
249
+ # down and to the right
250
+ ([down, right, limit].min).times do |i|
251
+ next_pos = index + (i+1)*9
252
+ if unoccupied?(next_pos)
253
+ valid << next_pos
254
+ else
255
+ if piece_color(next_pos) == @@enemy_color
256
+ valid << next_pos
257
+ end
258
+ break
259
+ end
260
+ end
261
+
262
+ # down and to the left
263
+ ([down, left, limit].min).times do |i|
264
+ next_pos = index + (i+1)*7
265
+ if unoccupied?(next_pos)
266
+ valid << next_pos
267
+ else
268
+ if piece_color(next_pos) == @@enemy_color
269
+ valid << next_pos
270
+ end
271
+ break
272
+ end
273
+ end
274
+
275
+ return valid
276
+ end
277
+
278
+ # Castle: king cannot move into check, or through check
279
+ def castle(index)
280
+ valid = []
281
+ dangerous_tiles = attack_vectors
282
+
283
+ # Valid positions for a King to be in order to castle
284
+ if index == 5 || index == 61
285
+ # Empty space between a King and a Rook
286
+ if unoccupied?(index - 1) && unoccupied?(index - 2) && unoccupied?(index - 3) && @@pieces[index - 4]["moved"] == false
287
+ # Ensure king does not move through check or into check
288
+ if !dangerous_tiles.include?(index - 1) && !dangerous_tiles.include?(index - 2)
289
+ # King's castle position
290
+ valid << index - 2
291
+ end
292
+ elsif unoccupied?(index + 1) && unoccupied?(index + 2) && @@pieces[index + 3]["moved"] == false
293
+ if !dangerous_tiles.include?(index + 1) && !dangerous_tiles.include?(index + 2)
294
+ valid << index + 2
295
+ end
296
+ end
297
+ end
298
+ return valid
299
+ end
300
+
301
+
302
+ # Check if board tile currently has a piece
303
+ def unoccupied?(index)
304
+ if @@pieces[index]["color"].nil?
305
+ return true
306
+ else
307
+ return false
308
+ end
309
+ end
310
+
311
+
312
+ # Return true if the piece has moved before
313
+ def moved?(index)
314
+ if @@pieces[index]["moved"]
315
+ return true
316
+ else
317
+ return false
318
+ end
319
+ end
320
+
321
+
322
+ # Return piece color ("red" or "black") from index (1 - 64)
323
+ def piece_color(index)
324
+ return @@pieces[index]["color"]
325
+ end
326
+
327
+
328
+ # Method used when moving, to verify the piece at index (1 - 64) is not of type "king"
329
+ def not_king(index)
330
+ return @@piece_locations[index]["type"] == "king"
331
+ end
332
+
333
+
334
+ # Obtain chess board row number (1 + 8) from an index (1 - 64)
335
+ def get_row_from_index(index)
336
+ return (index - 1)/8 + 1
337
+ end
338
+
339
+
340
+ # Obtain chess board column number (1 - 8) from an index (1 - 64)
341
+ def get_col_from_index(index)
342
+ if index % 8 == 0
343
+ return 8
344
+ else
345
+ return index % 8
346
+ end
347
+ end
348
+
349
+ end
data/lib/printer.rb ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module PRINTER
4
+
5
+ VERSION = "0.1.0"
6
+ COLS = ['A','B','C','D','E','F','G','H']
7
+ @@n = 0
8
+ @@print_count = 1
9
+
10
+
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
16
+ system "clear" or system "cls"
17
+
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
+
23
+ # Print Cells (use printer block and pass cell styling in loop below)
24
+ (1..8).each do |row|
25
+ yield "| |"
26
+ yield "| XX |", "#{row}"
27
+ yield "|____|"
28
+ end
29
+
30
+ # Footer (print column labels)
31
+ print "\s\s\s"
32
+ COLS.each { |c| print " #{c} " }
33
+ puts ""
34
+ end
35
+
36
+
37
+ 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
53
+ 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
64
+ end
65
+ end
66
+
67
+ # Print succeeding row index (1...8) if applicable
68
+ if (@@n == 1 || @@n == 4) && j then print " #{j}" end
69
+
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
+ }
75
+ end
76
+
77
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << '.'
4
+
5
+ require_relative "terminal_chess/version"
6
+ require_relative "printer.rb"
7
+ require_relative "move.rb"
8
+ require_relative "board.rb"
9
+
10
+ # Setup
11
+ a = Board.new; a.setup_board; a.board_refresh
12
+
13
+ # Gameplay
14
+ while true
15
+
16
+ print "\nPiece to Move [#{a.player_turn.capitalize}]: "
17
+ from = gets.chomp.upcase
18
+
19
+ begin
20
+ print "Valid destinations: #{a.valid_destinations(from).join(", ")}"
21
+
22
+ print "\nLocation: "
23
+ to = gets.chomp.upcase
24
+ a.move(from, to)
25
+
26
+ rescue Exception => e
27
+ puts "Invalid selection"
28
+ end
29
+ end
30
+
@@ -0,0 +1,3 @@
1
+ module TerminalChess
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'terminal_chess/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'terminal_chess'
8
+ s.version = TerminalChess::VERSION
9
+ s.date = '2015-03-03'
10
+ s.summary = "Chess game playable via the terminal"
11
+ s.description = "Two player chess game through the terminal"
12
+ s.authors = ["Jason Willems"]
13
+ s.email = 'hello@jasonwillems.com'
14
+ s.homepage = 'https://github.com/atlas/Terminal-Chess'
15
+ s.license = 'MIT'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.require_paths = ["lib"]
19
+ s.executables << 'terminal_chess'
20
+
21
+ s.add_runtime_dependency "colorize"
22
+ s.add_development_dependency "bundler", "~> 1.7"
23
+ s.add_development_dependency "rake", "~> 10.0"
24
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: terminal_chess
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jason Willems
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Two player chess game through the terminal
56
+ email: hello@jasonwillems.com
57
+ executables:
58
+ - terminal_chess
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - Gemfile
63
+ - LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - bin/terminal_chess
67
+ - lib/Board.rb
68
+ - lib/move.rb
69
+ - lib/printer.rb
70
+ - lib/terminal_chess.rb
71
+ - lib/terminal_chess/version.rb
72
+ - terminal_chess.gemspec
73
+ homepage: https://github.com/atlas/Terminal-Chess
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Chess game playable via the terminal
97
+ test_files: []