ari_chess 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ari_chess/board.rb +145 -0
- data/lib/ari_chess/error.rb +7 -0
- data/lib/ari_chess/game.rb +97 -0
- data/lib/ari_chess/named_pieces.rb +164 -0
- data/lib/ari_chess/piece.rb +78 -0
- data/lib/ari_chess.rb +22 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e585e588e19ed3831464788d43fa361379491a05
|
4
|
+
data.tar.gz: d338793003e5d77b78c10334ac8cda065f2dd50c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2d98651dd5cfa93d7fafa750e6bc6a52ceb8287c2ac522afc12c0d9e605017b74c4757e12845e5b0d20cfe8548f599522a8f40ecf717effbc2cfb005346c3c0f
|
7
|
+
data.tar.gz: fe9fb67bffaaafe01f7b2d069c39aeec1a14646017a6dcfa5d5fb5e2a1dd2ebe1c90f87be2244dbfdb1482215a507f18cfb559ac42e6f6f08a6c10a5d770f7c3
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require_relative 'named_pieces'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
class Board
|
5
|
+
attr_reader :grid
|
6
|
+
|
7
|
+
def initialize(grid = Array.new(8) { Array.new(8) })
|
8
|
+
@grid = grid
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_board?(pos)
|
12
|
+
pos.all? { |coord| coord.between?(0, 7) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def move(start_pos, end_pos)
|
16
|
+
validate_move(start_pos, end_pos)
|
17
|
+
|
18
|
+
move!(start_pos, end_pos)
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_move(start_pos, end_pos)
|
22
|
+
piece = self[start_pos]
|
23
|
+
|
24
|
+
error_message = nil
|
25
|
+
|
26
|
+
if piece.nil?
|
27
|
+
error_message = "Invalid move! Start position is empty."
|
28
|
+
elsif !piece.moves.include?(end_pos)
|
29
|
+
error_message = "Invalid move!"
|
30
|
+
elsif !piece.valid_moves.include?(end_pos)
|
31
|
+
error_message = "Invalid move! This move would move you into check."
|
32
|
+
end
|
33
|
+
|
34
|
+
raise InvalidMoveError.new(error_message) unless error_message.nil?
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def move!(start_pos, end_pos)
|
40
|
+
piece = self[start_pos]
|
41
|
+
|
42
|
+
self[end_pos] = piece
|
43
|
+
piece.update_piece(end_pos)
|
44
|
+
|
45
|
+
self[start_pos] = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def dup
|
49
|
+
new_board = Board.new
|
50
|
+
|
51
|
+
white_pieces = find_pieces(:W)
|
52
|
+
black_pieces = find_pieces(:B)
|
53
|
+
|
54
|
+
(white_pieces + black_pieces).each do |piece|
|
55
|
+
new_board[piece.pos] = piece.dup(new_board)
|
56
|
+
end
|
57
|
+
|
58
|
+
new_board
|
59
|
+
end
|
60
|
+
|
61
|
+
def checkmate?(color)
|
62
|
+
in_check?(color) && find_pieces(color).all? do |piece|
|
63
|
+
piece.valid_moves.empty?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def in_check?(color)
|
68
|
+
opposing_color = (color == :W ? :B : :W)
|
69
|
+
opposing_pieces = find_pieces(opposing_color)
|
70
|
+
king = find_pieces(color, "King")[0]
|
71
|
+
|
72
|
+
opposing_pieces.any? { |piece| piece.moves.include?(king.pos) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_pieces(color, type = nil)
|
76
|
+
colored_pieces = grid.flatten.compact.select { |piece| piece.color == color }
|
77
|
+
colored_pieces.select! { |piece| piece.class.to_s == type } unless type.nil?
|
78
|
+
|
79
|
+
colored_pieces
|
80
|
+
end
|
81
|
+
|
82
|
+
def [](pos)
|
83
|
+
x, y = pos
|
84
|
+
@grid[x][y]
|
85
|
+
end
|
86
|
+
|
87
|
+
def []=(pos, value)
|
88
|
+
x, y = pos
|
89
|
+
@grid[x][y] = value
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect
|
93
|
+
""
|
94
|
+
end
|
95
|
+
|
96
|
+
def render
|
97
|
+
square_num = 0 #tracks grid for alternating color
|
98
|
+
row_num = 0
|
99
|
+
|
100
|
+
puts " a b c d e f g h"
|
101
|
+
grid.each_with_index do |row, n|
|
102
|
+
row_string = ""
|
103
|
+
row.each do |square|
|
104
|
+
color = (square_num + row_num).even? ? :light_white : :light_black
|
105
|
+
row_string << (square.nil? ? " " : " " + square.to_s + " ").colorize(:background => color)
|
106
|
+
square_num += 1
|
107
|
+
end
|
108
|
+
puts "#{(-1 * n) + 8} " + row_string
|
109
|
+
row_num += 1
|
110
|
+
end
|
111
|
+
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_pieces
|
116
|
+
populate_row(0, :B, base_row_pieces)
|
117
|
+
populate_row(1, :B, Array.new(8) { Pawn })
|
118
|
+
|
119
|
+
populate_row(7, :W, base_row_pieces)
|
120
|
+
populate_row(6, :W, Array.new(8) { Pawn })
|
121
|
+
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def populate_row(row_idx, color, pieces)
|
126
|
+
grid[row_idx].each_index do |idx|
|
127
|
+
self[[row_idx, idx]] = pieces[idx].new([row_idx, idx], self, color)
|
128
|
+
end
|
129
|
+
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def base_row_pieces
|
134
|
+
[
|
135
|
+
Rook,
|
136
|
+
Knight,
|
137
|
+
Bishop,
|
138
|
+
Queen,
|
139
|
+
King,
|
140
|
+
Bishop,
|
141
|
+
Knight,
|
142
|
+
Rook
|
143
|
+
]
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'board'
|
2
|
+
require_relative 'error'
|
3
|
+
|
4
|
+
class Game
|
5
|
+
attr_reader :board
|
6
|
+
attr_accessor :current_player
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@board = Board.new
|
10
|
+
@board.setup_pieces
|
11
|
+
@current_player = :W
|
12
|
+
end
|
13
|
+
|
14
|
+
def play
|
15
|
+
until board.checkmate?(current_player)
|
16
|
+
system("clear")
|
17
|
+
board.render
|
18
|
+
play_turn
|
19
|
+
switch_player
|
20
|
+
end
|
21
|
+
|
22
|
+
system("clear")
|
23
|
+
board.render
|
24
|
+
switch_player
|
25
|
+
puts "Game over! #{current_player} wins!"
|
26
|
+
end
|
27
|
+
|
28
|
+
def switch_player
|
29
|
+
self.current_player = (current_player == :W ? :B : :W)
|
30
|
+
end
|
31
|
+
|
32
|
+
def play_turn
|
33
|
+
board.move(*get_move)
|
34
|
+
rescue InvalidMoveError => e
|
35
|
+
puts e.message
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_move
|
40
|
+
input = prompt
|
41
|
+
parse_positions(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def parse_positions(input)
|
46
|
+
matched_input = /\s*(\w\d),*\s*(\w\d)\s*/.match(input)
|
47
|
+
raise InvalidMoveError if matched_input.nil?
|
48
|
+
positions = matched_input.captures
|
49
|
+
parsed_positions = positions.map { |position| parse(position) }
|
50
|
+
validate_move(parsed_positions.first)
|
51
|
+
|
52
|
+
parsed_positions
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_move(start_pos)
|
56
|
+
error_message = nil
|
57
|
+
|
58
|
+
piece = board[start_pos]
|
59
|
+
if piece.nil?
|
60
|
+
error_message = "Invalid move! Start position is empty."
|
61
|
+
elsif current_player != board[start_pos].color
|
62
|
+
error_message = "Invalid move! Chosen piece is not yours."
|
63
|
+
end
|
64
|
+
|
65
|
+
raise InvalidMoveError.new(error_message) unless error_message.nil?
|
66
|
+
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def prompt
|
71
|
+
print "\n"
|
72
|
+
puts "#{current_player}'s turn. Enter a move (e.g. 'a2 a3'):"
|
73
|
+
print "> "
|
74
|
+
input = gets.chomp.downcase
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse(move)
|
78
|
+
raise InvalidMoveError if move.length != 2
|
79
|
+
col, row = move.split("")
|
80
|
+
row = to_i(row)
|
81
|
+
unless move_in_range?(row, col)
|
82
|
+
raise InvalidMoveError
|
83
|
+
end
|
84
|
+
|
85
|
+
[(-1 * row) + 8, col.ord - 97]
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_i(row)
|
89
|
+
row = Integer(row)
|
90
|
+
rescue ArgumentError
|
91
|
+
raise InvalidMoveError
|
92
|
+
end
|
93
|
+
|
94
|
+
def move_in_range?(row, col)
|
95
|
+
col.between?("a", "h") && row.between?(1, 8)
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require_relative 'piece'
|
2
|
+
|
3
|
+
class Pawn < Piece
|
4
|
+
CAPTURE_DELTAS = [
|
5
|
+
[1, -1], #capture
|
6
|
+
[1, 1], #capture
|
7
|
+
]
|
8
|
+
|
9
|
+
STEP_DELTA = [
|
10
|
+
[1, 0] #anytime
|
11
|
+
]
|
12
|
+
|
13
|
+
DOUBLE_STEP_DELTA = [
|
14
|
+
[2, 0], #first move
|
15
|
+
]
|
16
|
+
|
17
|
+
attr_accessor :first_move
|
18
|
+
|
19
|
+
def initialize(pos, board, color)
|
20
|
+
super
|
21
|
+
@first_move = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def first_move?
|
25
|
+
@first_move
|
26
|
+
end
|
27
|
+
|
28
|
+
def dup(new_board)
|
29
|
+
copy = super
|
30
|
+
copy.first_move = first_move
|
31
|
+
|
32
|
+
copy
|
33
|
+
end
|
34
|
+
|
35
|
+
def moves
|
36
|
+
possible_moves = []
|
37
|
+
|
38
|
+
possible_moves.concat(capturable_positions)
|
39
|
+
|
40
|
+
step_pos = new_pos(deltas(STEP_DELTA).first)
|
41
|
+
|
42
|
+
if board[step_pos].nil?
|
43
|
+
possible_moves << step_pos
|
44
|
+
else
|
45
|
+
return possible_moves
|
46
|
+
end
|
47
|
+
|
48
|
+
if first_move?
|
49
|
+
possible_moves << double_step_pos unless double_step_pos.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
possible_moves
|
53
|
+
end
|
54
|
+
|
55
|
+
def double_step_pos
|
56
|
+
position = new_pos(deltas(DOUBLE_STEP_DELTA).first)
|
57
|
+
return position if board[position].nil?
|
58
|
+
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
|
62
|
+
def capturable_positions
|
63
|
+
deltas(CAPTURE_DELTAS).map { |delta| new_pos(delta) }.select do |position|
|
64
|
+
!board[position].nil? && board[position].color != color
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def new_pos(delta)
|
69
|
+
x, y = pos
|
70
|
+
dx, dy = delta
|
71
|
+
|
72
|
+
[x + dx, y + dy]
|
73
|
+
end
|
74
|
+
|
75
|
+
def deltas(specific_deltas)
|
76
|
+
color == :B ? specific_deltas : specific_deltas.map { |dx, dy| [dx * -1, dy] }
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_piece(pos)
|
80
|
+
super
|
81
|
+
self.first_move = false
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
color == :W ? "\u2659" : "\u265F"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Knight < SteppingPiece
|
90
|
+
DELTAS = [
|
91
|
+
[-2, -1],
|
92
|
+
[-2, 1],
|
93
|
+
[-1, -2],
|
94
|
+
[-1, 2],
|
95
|
+
[ 1, -2],
|
96
|
+
[ 1, 2],
|
97
|
+
[ 2, -1],
|
98
|
+
[ 2, 1]
|
99
|
+
]
|
100
|
+
|
101
|
+
def to_s
|
102
|
+
color == :W ? "\u2658" : "\u265E"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class King < SteppingPiece
|
107
|
+
DELTAS = [
|
108
|
+
[ 1, 1],
|
109
|
+
[ 1, 0],
|
110
|
+
[ 1, -1],
|
111
|
+
[ 0, -1],
|
112
|
+
[-1, -1],
|
113
|
+
[-1, 0],
|
114
|
+
[-1, 1],
|
115
|
+
[ 0, 1],
|
116
|
+
]
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
color == :W ? "\u2654" : "\u265A"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Bishop < SlidingPiece
|
124
|
+
DELTAS = [
|
125
|
+
[ 1, 1],
|
126
|
+
[ 1, -1],
|
127
|
+
[-1, -1],
|
128
|
+
[-1, 1],
|
129
|
+
]
|
130
|
+
|
131
|
+
def to_s
|
132
|
+
color == :W ? "\u2657" : "\u265D"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Rook < SlidingPiece
|
137
|
+
DELTAS = [
|
138
|
+
[ 1, 0],
|
139
|
+
[ 0, -1],
|
140
|
+
[-1, 0],
|
141
|
+
[ 0, 1],
|
142
|
+
]
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
color == :W ? "\u2656" : "\u265C"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Queen < SlidingPiece
|
150
|
+
DELTAS = [
|
151
|
+
[ 1, 1],
|
152
|
+
[ 1, 0],
|
153
|
+
[ 1, -1],
|
154
|
+
[ 0, -1],
|
155
|
+
[-1, -1],
|
156
|
+
[-1, 0],
|
157
|
+
[-1, 1],
|
158
|
+
[ 0, 1],
|
159
|
+
]
|
160
|
+
|
161
|
+
def to_s
|
162
|
+
color == :W ? "\u2655" : "\u265B"
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Piece
|
2
|
+
attr_accessor :pos, :board, :color
|
3
|
+
|
4
|
+
def initialize(pos = nil, board = nil, color = nil)
|
5
|
+
@pos = pos
|
6
|
+
@board = board
|
7
|
+
@color = color
|
8
|
+
end
|
9
|
+
|
10
|
+
def moves
|
11
|
+
positions = []
|
12
|
+
|
13
|
+
deltas.each do |delta|
|
14
|
+
possible_moves = potential_moves(delta)
|
15
|
+
positions.concat(possible_moves) #unless possible_moves.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
positions
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_piece(pos)
|
22
|
+
self.pos = pos
|
23
|
+
end
|
24
|
+
|
25
|
+
def next_pos(pos, delta)
|
26
|
+
x, y = pos
|
27
|
+
dx, dy = delta
|
28
|
+
|
29
|
+
[x + dx, y + dy]
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_pos?(pos)
|
33
|
+
board.on_board?(pos) && (board[pos].nil? || board[pos].color != color)
|
34
|
+
end
|
35
|
+
|
36
|
+
def move_into_check?(possible_pos)
|
37
|
+
new_board = board.dup
|
38
|
+
new_board.move!(pos, possible_pos)
|
39
|
+
new_board.in_check?(color)
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_moves
|
43
|
+
moves.reject { |move| move_into_check?(move) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def deltas
|
47
|
+
self.class::DELTAS
|
48
|
+
end
|
49
|
+
|
50
|
+
def dup(new_board)
|
51
|
+
self.class.new(pos, new_board, color)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class SlidingPiece < Piece
|
56
|
+
def potential_moves(delta)
|
57
|
+
moves = []
|
58
|
+
captured_piece = false
|
59
|
+
current_pos = pos
|
60
|
+
|
61
|
+
until captured_piece || !valid_pos?(move = next_pos(current_pos, delta))
|
62
|
+
moves << move
|
63
|
+
captured_piece = true if !board[move].nil? && board[move].color != color
|
64
|
+
current_pos = move
|
65
|
+
end
|
66
|
+
|
67
|
+
moves
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class SteppingPiece < Piece
|
72
|
+
def potential_moves(delta)
|
73
|
+
move = next_pos(pos, delta)
|
74
|
+
return [move] if valid_pos?(move)
|
75
|
+
|
76
|
+
[]
|
77
|
+
end
|
78
|
+
end
|
data/lib/ari_chess.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'ari_chess/game'
|
2
|
+
|
3
|
+
class Chess
|
4
|
+
def self.play
|
5
|
+
system("clear")
|
6
|
+
puts "ARI'S CLI CHESS"
|
7
|
+
print "\n"
|
8
|
+
puts "This game is simple. Technically it's for two players."
|
9
|
+
puts "Enter moves in the format 'a2 a3' (omit the quotes)."
|
10
|
+
puts "The positions can be space- or comma-delimited."
|
11
|
+
puts "The game will block you from putting yourself in check."
|
12
|
+
puts "You might want to zoom in a bit."
|
13
|
+
print "\n"
|
14
|
+
puts "Hit ENTER to play."
|
15
|
+
print "\n"
|
16
|
+
|
17
|
+
until (prompt = gets.chomp) == ""
|
18
|
+
end
|
19
|
+
|
20
|
+
Game.new.play
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ari_chess
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ari Borensztein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-01 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.7.7
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.7
|
27
|
+
description: A simple CLI chess game
|
28
|
+
email: ariboren@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/ari_chess.rb
|
34
|
+
- lib/ari_chess/board.rb
|
35
|
+
- lib/ari_chess/error.rb
|
36
|
+
- lib/ari_chess/game.rb
|
37
|
+
- lib/ari_chess/named_pieces.rb
|
38
|
+
- lib/ari_chess/piece.rb
|
39
|
+
homepage: http://rubygems.org/gems/ari_chess
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.2.2
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: Chess
|
63
|
+
test_files: []
|
64
|
+
has_rdoc:
|