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