ari_chess 1.1.0

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: 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: