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 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,7 @@
1
+ class InvalidMoveError < StandardError
2
+ attr_reader :message
3
+
4
+ def initialize(message = nil)
5
+ @message ||= "Invalid move!"
6
+ end
7
+ 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: