terminal_chess 0.1.1

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.
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: []