rb_chess 0.0.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/LICENSE +21 -0
- data/README.md +68 -0
- data/bin/rb_chess +6 -0
- data/lib/rb_chess/board.rb +171 -0
- data/lib/rb_chess/castling_rights.rb +31 -0
- data/lib/rb_chess/cli/cli.rb +237 -0
- data/lib/rb_chess/cli/game_saver.rb +33 -0
- data/lib/rb_chess/cli/players.rb +20 -0
- data/lib/rb_chess/errors.rb +5 -0
- data/lib/rb_chess/fen_parser.rb +150 -0
- data/lib/rb_chess/game.rb +166 -0
- data/lib/rb_chess/game_modules/move_generator.rb +154 -0
- data/lib/rb_chess/game_modules/move_parser.rb +43 -0
- data/lib/rb_chess/game_modules/move_validator.rb +47 -0
- data/lib/rb_chess/letter_display.rb +42 -0
- data/lib/rb_chess/move.rb +33 -0
- data/lib/rb_chess/move_set.rb +18 -0
- data/lib/rb_chess/pieces/bishop.rb +24 -0
- data/lib/rb_chess/pieces/king.rb +26 -0
- data/lib/rb_chess/pieces/knight.rb +25 -0
- data/lib/rb_chess/pieces/pawn.rb +45 -0
- data/lib/rb_chess/pieces/piece.rb +19 -0
- data/lib/rb_chess/pieces/piece_constants.rb +26 -0
- data/lib/rb_chess/pieces/queen.rb +25 -0
- data/lib/rb_chess/pieces/rook.rb +24 -0
- data/lib/rb_chess/position.rb +72 -0
- data/lib/rb_chess.rb +3 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 04be0bab3ec8dd8abbb014e44d3a209cf727c52973752bb371d46cc9f40d19f0
|
|
4
|
+
data.tar.gz: 077b182bb41c50319c21dbba238d51d36f7d7f8760722168a24cb8d4ae675661
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 79dbefdc24671860bc959442bcfcaf62c77329db1e879a2d34a56551cf557a499f8e22e1a5baa413a1dee4c05be93f82376781d28a970636d770d21012137514
|
|
7
|
+
data.tar.gz: 7bbfc032021fc78f66dd6c0727d06cbc4d89ae10addc092c72e5f8a2a3a1dd721bddd558a8b92ce231814465d37f5ea1550fa80f7d9174d19519d15f2aaf82c6
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Peter Abah
|
|
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,68 @@
|
|
|
1
|
+
# rb_chess
|
|
2
|
+
A chess library written in ruby. It provides all a representation of a chess game with all rules and serialization. It also provides a command line interface for playing the game
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
Add this line to your application's Gemfile:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem 'just_chess'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
And then run:
|
|
12
|
+
```
|
|
13
|
+
$ bundle
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or install it yourself as:
|
|
17
|
+
```
|
|
18
|
+
$ gem install rb_chess
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
- Supports all chess moves including:
|
|
23
|
+
+ En passant.
|
|
24
|
+
+ Castling.
|
|
25
|
+
+ Pawn promotion.
|
|
26
|
+
- The game detects and declares:
|
|
27
|
+
+ Checkmate.
|
|
28
|
+
+ Stalemate.
|
|
29
|
+
+ Insufficient material.
|
|
30
|
+
+ Fivefold repetition
|
|
31
|
+
+ Seventy five moves rule
|
|
32
|
+
- Move generation.
|
|
33
|
+
- Move validation.
|
|
34
|
+
- Serializing (json and binary).
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
The code below plays a random game of chess:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
require 'rb_chess'
|
|
42
|
+
|
|
43
|
+
game = RbChess::Game.new
|
|
44
|
+
|
|
45
|
+
until game.game_over?
|
|
46
|
+
puts game.board.ascii
|
|
47
|
+
game.make_move(game.all_moves.sample)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts game.board.to_fen
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
You can find the full example of using this library in the [RbChess::CLI](lib/rb_chess/cli/cli.rb) class in lib/rb_chess/cli/cli.rb.
|
|
54
|
+
|
|
55
|
+
## Playing
|
|
56
|
+
- To start run ``` rb_chess ```.
|
|
57
|
+
- Moves are made by typing in coordinate system i.e `e2e4`.
|
|
58
|
+
- Castling moves are made by typing `0-0` for kingside and `0-0-0` for queenside.
|
|
59
|
+
- Promotion moves are made by typing the move with the promotion piece after it e.g `a7a8Q`.
|
|
60
|
+
- To save type `save` or `s`.
|
|
61
|
+
- To quit the game type `exit`.
|
|
62
|
+
|
|
63
|
+
## To do
|
|
64
|
+
- Support for PGN.
|
|
65
|
+
- Support for SAN move format.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
Distributed under the MIT License. See LICENSE for more information.
|
data/bin/rb_chess
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'require_all'
|
|
4
|
+
|
|
5
|
+
require_relative 'fen_parser'
|
|
6
|
+
require_relative 'position'
|
|
7
|
+
require_relative 'errors'
|
|
8
|
+
require_relative 'letter_display'
|
|
9
|
+
require_rel 'pieces/pawn', 'pieces/piece_constants'
|
|
10
|
+
|
|
11
|
+
# A class to represent a chess board
|
|
12
|
+
module RbChess
|
|
13
|
+
class Board
|
|
14
|
+
include LetterDisplay
|
|
15
|
+
include PieceConstants
|
|
16
|
+
|
|
17
|
+
attr_reader :pieces, :active_color, :en_passant_square, :halfmove_clock, :fullmove_no, :castling_rights
|
|
18
|
+
|
|
19
|
+
def initialize(fen_notation: nil, segments: nil)
|
|
20
|
+
segments ||= fen_notation ? FENParser.new(fen_notation).parse : FENParser.new.parse
|
|
21
|
+
@pieces = segments[:pieces].freeze
|
|
22
|
+
@active_color = segments[:active_color]
|
|
23
|
+
@castling_rights = segments[:castling_rights]
|
|
24
|
+
@en_passant_square = segments[:en_passant_square]
|
|
25
|
+
@halfmove_clock = segments[:halfmove_clock]
|
|
26
|
+
@fullmove_no = segments[:fullmove_no]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def make_move(move)
|
|
30
|
+
segments = {
|
|
31
|
+
pieces: update_pieces(move),
|
|
32
|
+
active_color: active_color == :white ? :black : :white,
|
|
33
|
+
castling_rights: update_castling_rights(move),
|
|
34
|
+
en_passant_square: update_en_passant_square(move),
|
|
35
|
+
halfmove_clock: update_halfmove_clock(move),
|
|
36
|
+
fullmove_no: active_color == :black ? fullmove_no + 1 : fullmove_no
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
self.class.new(segments: segments)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def player_pieces(color)
|
|
43
|
+
pieces.select { |piece| piece.color == color }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def opponent_color
|
|
47
|
+
active_color == :white ? :black : :white
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def piece_at(pos, pieces_n: pieces)
|
|
51
|
+
pos = Position.parse(pos) unless pos.is_a? Position
|
|
52
|
+
|
|
53
|
+
pieces_n.find { |piece| piece.position == pos }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def can_castle_kingside?(color)
|
|
57
|
+
castling_rights.kingside[color]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def can_castle_queenside?(color)
|
|
61
|
+
castling_rights.queenside[color]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_fen
|
|
65
|
+
FENParser.board_to_fen self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
to_fen
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def update_castling_rights(move)
|
|
75
|
+
new_castling_rights = castling_rights.dup
|
|
76
|
+
|
|
77
|
+
new_castling_rights.kingside[:white] = invalidate_kingside_castling(move, :white)
|
|
78
|
+
new_castling_rights.queenside[:white] = invalidate_queenside_castling(move, :white)
|
|
79
|
+
|
|
80
|
+
new_castling_rights.kingside[:black] = invalidate_kingside_castling(move, :black)
|
|
81
|
+
new_castling_rights.queenside[:black] = invalidate_queenside_castling(move, :black)
|
|
82
|
+
|
|
83
|
+
new_castling_rights
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def invalidate_kingside_castling(move, color)
|
|
87
|
+
invalidate = move.moved.all? do |hash|
|
|
88
|
+
if color == active_color
|
|
89
|
+
hash[:from].king_pos?(color) || hash[:from].kingside_rook_pos?(color)
|
|
90
|
+
elsif move.removed
|
|
91
|
+
move.removed.kingside_rook_pos?(color)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
castling_rights.kingside[color] && !invalidate
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def invalidate_queenside_castling(move, color)
|
|
99
|
+
invalidate = move.moved.all? do |hash|
|
|
100
|
+
if color == active_color
|
|
101
|
+
hash[:from].king_pos?(color) || hash[:from].queenside_rook_pos?(color)
|
|
102
|
+
elsif move.removed
|
|
103
|
+
move.removed.queenside_rook_pos?(color)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
castling_rights.queenside[color] && !invalidate
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def update_pieces(move)
|
|
111
|
+
pieces = remove_piece(move)
|
|
112
|
+
move_pieces(pieces, move)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def update_en_passant_square(move)
|
|
116
|
+
move.moved.reduce('-') do |res, hash|
|
|
117
|
+
if en_passant_possible?(hash)
|
|
118
|
+
direction = piece_at(hash[:from]).direction
|
|
119
|
+
return hash[:to].increment(y: -direction)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
res
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def update_halfmove_clock(move)
|
|
127
|
+
reset_clock = move.moved.reduce(false) do |res, hash|
|
|
128
|
+
piece = piece_at(hash[:from])
|
|
129
|
+
piece.is_a?(Pawn) ? true : res
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
move.removed || reset_clock ? 0 : halfmove_clock + 1
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def en_passant_possible?(hash)
|
|
136
|
+
piece = piece_at(hash[:from])
|
|
137
|
+
|
|
138
|
+
piece.is_a?(Pawn) && hash[:from].starting_pawn_rank?(piece.color) &&
|
|
139
|
+
hash[:to].en_passant_rank?(piece.color)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def remove_piece(move)
|
|
143
|
+
return pieces.dup unless move.removed
|
|
144
|
+
raise ChessError, 'Cannot remove your own piece' if piece_at(move.removed).color == active_color
|
|
145
|
+
|
|
146
|
+
pieces.reject { |piece| piece.position == move.removed }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def move_pieces(pieces, move)
|
|
150
|
+
move.moved.reduce(pieces) do |res, hash|
|
|
151
|
+
piece = move_piece(res, hash)
|
|
152
|
+
piece = promote_piece(pieces, hash, move.promotion) if move.promotion
|
|
153
|
+
res = res.reject { |piece| piece.position == hash[:from] }
|
|
154
|
+
res.push(piece)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def move_piece(pieces, hash)
|
|
159
|
+
piece = piece_at(hash[:from], pieces_n: pieces)
|
|
160
|
+
raise ChessError, 'Can only move your own piece' unless piece.color == active_color
|
|
161
|
+
|
|
162
|
+
piece.update_position(hash[:to])
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def promote_piece(pieces, hash, piece_letter)
|
|
166
|
+
piece_class = LETTER_TO_PIECE_CLASS[piece_letter.downcase.to_sym]
|
|
167
|
+
color = piece_at(hash[:from], pieces_n: pieces).color
|
|
168
|
+
piece_class.new(color, hash[:to])
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RbChess
|
|
4
|
+
class CastlingRights
|
|
5
|
+
attr_reader :kingside, :queenside
|
|
6
|
+
|
|
7
|
+
def initialize(kingside: nil, queenside: nil)
|
|
8
|
+
@kingside = kingside || { white: false, black: false }
|
|
9
|
+
@queenside = queenside || { white: false, black: false }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_s
|
|
13
|
+
res = ''
|
|
14
|
+
|
|
15
|
+
res += 'K' if kingside[:white]
|
|
16
|
+
res += 'Q' if queenside[:white]
|
|
17
|
+
res += 'k' if kingside[:black]
|
|
18
|
+
res += 'q' if queenside[:black]
|
|
19
|
+
|
|
20
|
+
res = '-' if res.empty?
|
|
21
|
+
|
|
22
|
+
res
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dup
|
|
26
|
+
new_kingside = kingside.dup
|
|
27
|
+
new_queenside = queenside.dup
|
|
28
|
+
CastlingRights.new(kingside: new_kingside, queenside: new_queenside)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'require_all'
|
|
4
|
+
|
|
5
|
+
require_relative '../game'
|
|
6
|
+
require_rel '.'
|
|
7
|
+
|
|
8
|
+
module RbChess
|
|
9
|
+
class CLI
|
|
10
|
+
def initialize
|
|
11
|
+
@game_saver = GameSaver.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
welcome
|
|
16
|
+
@game, @players = game_option == :new ? new_game : load_game
|
|
17
|
+
play_game
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def welcome
|
|
21
|
+
puts 'Welcome to Chess.'
|
|
22
|
+
help
|
|
23
|
+
puts 'Enjoy the game.'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def game_option
|
|
27
|
+
choice = -1
|
|
28
|
+
until choice.between?(1, 2)
|
|
29
|
+
puts <<~HEREDOC
|
|
30
|
+
---------------------
|
|
31
|
+
1 - New game
|
|
32
|
+
2 - Load saved game
|
|
33
|
+
HEREDOC
|
|
34
|
+
|
|
35
|
+
choice = gets.chomp.to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
choice == 1 ? :new : :load
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def new_game
|
|
42
|
+
players = get_players
|
|
43
|
+
[Game.new, players]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_game
|
|
47
|
+
files = @game_saver.saved_games
|
|
48
|
+
if files.empty?
|
|
49
|
+
puts 'No Games to load. Starting new game'
|
|
50
|
+
return new_game
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
choice = -1
|
|
54
|
+
until choice.between?(0, files.size - 1)
|
|
55
|
+
display_list(files)
|
|
56
|
+
puts 'Choose game to load. (Enter number)'
|
|
57
|
+
choice = gets.chomp.to_i - 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@game_saver.load(files[choice])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def play_game
|
|
64
|
+
game_loop until @game.game_over?
|
|
65
|
+
|
|
66
|
+
puts @game.board.ascii
|
|
67
|
+
puts end_game_msg
|
|
68
|
+
play_again? && start
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def game_loop
|
|
72
|
+
puts @game.board.ascii
|
|
73
|
+
puts "#{player.name} in check!" if @game.check?
|
|
74
|
+
input = player_input
|
|
75
|
+
|
|
76
|
+
if commands[input.to_sym]
|
|
77
|
+
commands[input.to_sym].call
|
|
78
|
+
elsif pos? input
|
|
79
|
+
show_pos_moves input
|
|
80
|
+
elsif @game.valid_move? input
|
|
81
|
+
@game.make_move input
|
|
82
|
+
else
|
|
83
|
+
puts 'Invalid move or command!'
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def player
|
|
88
|
+
@players.find { |p| p.color == @game.current_player }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def end_game_msg
|
|
92
|
+
player = @players.find { |p| p.color == @game.winner }
|
|
93
|
+
|
|
94
|
+
if @game.checkmate?
|
|
95
|
+
"Checkmate #{player.name} wins!"
|
|
96
|
+
elsif @game.stalemate?
|
|
97
|
+
'Draw by stalemate!'
|
|
98
|
+
elsif @game.fivefold?
|
|
99
|
+
'Draw by fivefold repetition!'
|
|
100
|
+
elsif @game.seventy_five_moves?
|
|
101
|
+
'Draw by seventy five moves rule (No pawn move or capture in last seventy five moves)'
|
|
102
|
+
elsif @game.insufficient_material?
|
|
103
|
+
'Draw by insufficient material'
|
|
104
|
+
else
|
|
105
|
+
'Game over!'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def play_again?
|
|
110
|
+
p 'Do you want to play again?(Y|N default is N): '
|
|
111
|
+
choice = gets.chomp.downcase
|
|
112
|
+
choice == 'y'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def player_input
|
|
116
|
+
return player.move(@game) if player.is_a? ComputerPlayer
|
|
117
|
+
|
|
118
|
+
puts "#{player.name} Enter move"
|
|
119
|
+
gets.chomp.downcase
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def help
|
|
123
|
+
puts <<~HEREDOC
|
|
124
|
+
Moves are in coordinate system format e.g e2e4 with exception of castling moves
|
|
125
|
+
which are 0-0 for kingside and 0-0-0 for queenside.
|
|
126
|
+
|
|
127
|
+
You can also enter a piece position e.g b1.
|
|
128
|
+
If the piece has legal moves available, the game will display them or the the
|
|
129
|
+
player must choose another piece.
|
|
130
|
+
|
|
131
|
+
For promotion moves append the promotion piece (Q, R, B, N) after the move
|
|
132
|
+
e.g a2a1R
|
|
133
|
+
|
|
134
|
+
If a player is in check, they can only select pieces with moves that take them
|
|
135
|
+
out of check.
|
|
136
|
+
|
|
137
|
+
Enter [moves] or [m] to view all available moves.
|
|
138
|
+
|
|
139
|
+
Enter [save] or [s] at any time to save the game.
|
|
140
|
+
|
|
141
|
+
Enter [exit] at any time to end the game
|
|
142
|
+
|
|
143
|
+
Enter [help] or [h] to view this guide again
|
|
144
|
+
|
|
145
|
+
The game ends by checkmate or draw (stalemate, fivefold repetition,
|
|
146
|
+
insufficient material or seventy five turns without a capture or pawn move)
|
|
147
|
+
HEREDOC
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def moves
|
|
151
|
+
display_list(@game.all_moves)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def save
|
|
155
|
+
filename = ''
|
|
156
|
+
while filename.empty?
|
|
157
|
+
puts 'Enter filename: '
|
|
158
|
+
filename = gets.chomp
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
@game_saver.save(@game, @players, filename)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def get_players
|
|
165
|
+
mode = get_mode
|
|
166
|
+
return [Player.new(:white), Player.new(:black)] if mode == :human
|
|
167
|
+
|
|
168
|
+
color = get_color
|
|
169
|
+
computer_color = color == :white ? :black : :white
|
|
170
|
+
[Player.new(color), RandomAI.new(computer_color)]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def get_mode
|
|
174
|
+
choice = -1
|
|
175
|
+
until choice.between?(1, 2)
|
|
176
|
+
puts <<~HEREDOC
|
|
177
|
+
-------------
|
|
178
|
+
Choose mode
|
|
179
|
+
-------------
|
|
180
|
+
1 - vs Human
|
|
181
|
+
2 - vs Computer (just random moves)
|
|
182
|
+
HEREDOC
|
|
183
|
+
choice = gets.chomp.to_i
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
choice == 1 ? :human : :computer
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def get_color
|
|
190
|
+
choice = -1
|
|
191
|
+
until choice.between?(1, 2)
|
|
192
|
+
puts <<~HEREDOC
|
|
193
|
+
-------------
|
|
194
|
+
Choose color
|
|
195
|
+
-------------
|
|
196
|
+
1 - White
|
|
197
|
+
2 - Black
|
|
198
|
+
HEREDOC
|
|
199
|
+
choice = gets.chomp.to_i
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
choice == 1 ? :white : :black
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def commands
|
|
206
|
+
@commands ||= {
|
|
207
|
+
help: method(:help),
|
|
208
|
+
h: method(:help),
|
|
209
|
+
moves: method(:moves),
|
|
210
|
+
m: method(:moves),
|
|
211
|
+
exit: method(:exit),
|
|
212
|
+
save: method(:save),
|
|
213
|
+
s: method(:save)
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def pos?(pos)
|
|
218
|
+
/^[a-h][1-8]$/.match pos
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def show_pos_moves(pos)
|
|
222
|
+
moves = @game.moves_at pos
|
|
223
|
+
if moves.empty?
|
|
224
|
+
puts "No moves at #{pos}"
|
|
225
|
+
return
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
display_list(moves)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def display_list(list)
|
|
232
|
+
list.each_with_index do |data, i|
|
|
233
|
+
puts "#{i + 1} - #{data}"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module RbChess
|
|
6
|
+
class GameSaver
|
|
7
|
+
def initialize
|
|
8
|
+
Dir.exist?(dirname) || FileUtils.mkdir_p(dirname)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def saved_games
|
|
12
|
+
files = Dir.entries(dirname).reject { |f| ['.', '..'].include?(f) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save(game, players, filename)
|
|
16
|
+
filename = File.join(dirname, filename)
|
|
17
|
+
data = Marshal.dump([game, players])
|
|
18
|
+
File.open(filename, 'wb') { |f| f.write(data) }
|
|
19
|
+
puts 'Game saved'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load(filename)
|
|
23
|
+
filename = File.join(dirname, filename)
|
|
24
|
+
data = File.open(filename, 'rb') { |f| f.read }
|
|
25
|
+
|
|
26
|
+
Marshal.load(data)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dirname
|
|
30
|
+
File.join(Dir.home, '.rb_chess/saved')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RbChess
|
|
4
|
+
class Player
|
|
5
|
+
attr_reader :color, :name
|
|
6
|
+
|
|
7
|
+
def initialize(color)
|
|
8
|
+
@color = color
|
|
9
|
+
@name = color.to_s.capitalize
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ComputerPlayer < Player; end
|
|
14
|
+
|
|
15
|
+
class RandomAI < ComputerPlayer
|
|
16
|
+
def move(game)
|
|
17
|
+
game.all_moves.sample
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|