pgn2 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,129 @@
1
+ module PGN
2
+ # {PGN::Position} encapsulates all of the information necessary to
3
+ # completely understand a chess position. It can be turned into a FEN string
4
+ # or perform a move.
5
+ #
6
+ # @!attribute board
7
+ # @return [PGN::Board] the board for the position
8
+ #
9
+ # @!attribute player
10
+ # @return [Symbol] the player who moves next
11
+ # @example
12
+ # position.player #=> :white
13
+ #
14
+ # @!attribute castling
15
+ # @return [Array<String>] the castling moves that are still available
16
+ # @example
17
+ # position.castling #=> ["K", "k", "q"]
18
+ #
19
+ # @!attribute en_passant
20
+ # @return [String] the en passant square if applicable
21
+ #
22
+ # @!attribute halfmove
23
+ # @return [Integer] the number of halfmoves since the last pawn move or
24
+ # capture
25
+ #
26
+ # @!attribute fullmove
27
+ # @return [Integer] the number of fullmoves made so far
28
+ #
29
+
30
+ class Position
31
+ PLAYERS = %i[white black].freeze
32
+ CASTLING = %w[K Q k q].freeze
33
+
34
+ attr_accessor :board
35
+ attr_accessor :player
36
+ attr_accessor :castling
37
+ attr_accessor :en_passant
38
+ attr_accessor :halfmove
39
+ attr_accessor :fullmove
40
+
41
+ # @return [PGN::Position] the starting position of a chess game
42
+ #
43
+ def self.start
44
+ PGN::Position.new(
45
+ PGN::Board.start,
46
+ PLAYERS.first
47
+ )
48
+ end
49
+
50
+ # @param board [PGN::Board] the board for the position
51
+ # @param player [Symbol] the player who moves next
52
+ # @param castling [Array<String>] the castling moves that are still
53
+ # available
54
+ # @param en_passant [String, nil] the en passant square if applicable
55
+ # @param halfmove [Integer] the number of halfmoves since the last pawn
56
+ # move or capture
57
+ # @param fullmove [Integer] the number of fullmoves made so far
58
+ #
59
+ # @example
60
+ # PGN::Position.new(
61
+ # PGN::Board.start,
62
+ # :white,
63
+ # )
64
+ #
65
+ def initialize(board, player, castling = CASTLING, en_passant = nil, halfmove = 0, fullmove = 1)
66
+ self.board = board
67
+ self.player = player
68
+ self.castling = castling
69
+ self.en_passant = en_passant
70
+ self.halfmove = halfmove
71
+ self.fullmove = fullmove
72
+ end
73
+
74
+ # @param str [String] the move to make in SAN
75
+ # @return [PGN::Position] the resulting position
76
+ #
77
+ # @example
78
+ # queens_pawn = PGN::Position.start.move("d4")
79
+ #
80
+ def move(str)
81
+ move = PGN::Move.new(str, player)
82
+ calculator = PGN::MoveCalculator.new(board, move)
83
+
84
+ new_castling = castling - calculator.castling_restrictions
85
+ new_halfmove = if calculator.increment_halfmove?
86
+ halfmove + 1
87
+ else
88
+ 0
89
+ end
90
+ new_fullmove = if calculator.increment_fullmove?
91
+ fullmove + 1
92
+ else
93
+ fullmove
94
+ end
95
+ no_move = str == '--'
96
+ PGN::Position.new(
97
+ no_move ? board : calculator.result_board,
98
+ next_player,
99
+ new_castling,
100
+ calculator.en_passant_square,
101
+ new_halfmove,
102
+ new_fullmove
103
+ )
104
+ end
105
+
106
+ # @return [Symbol] the next player to move
107
+ #
108
+ def next_player
109
+ (PLAYERS - [player]).first
110
+ end
111
+
112
+ def inspect
113
+ "\n" + board.inspect
114
+ end
115
+
116
+ # @return [PGN::FEN] a {PGN::FEN} object representing the current position
117
+ #
118
+ def to_fen
119
+ PGN::FEN.from_attributes(
120
+ board: board,
121
+ active: player == :white ? 'w' : 'b',
122
+ castling: castling.join(''),
123
+ en_passant: en_passant,
124
+ halfmove: halfmove.to_s,
125
+ fullmove: fullmove.to_s
126
+ )
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ module PGN
2
+ VERSION = '0.4.0'.freeze
3
+ end
data/lib/pgn.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'pgn/board'
2
+ require 'pgn/fen'
3
+ require 'pgn/game'
4
+ require 'pgn/move'
5
+ require 'pgn/move_calculator'
6
+ require 'pgn/parser'
7
+ require 'pgn/position'
8
+ require 'pgn/version'
9
+
10
+ module PGN
11
+ # @param pgn [String] a pgn representation of one or more chess games
12
+ # @return [Array<PGN::Game>] a list of games
13
+ #
14
+ # @note The PGN spec specifies Latin-1 as the encoding for PGN files, so
15
+ # this is default.
16
+ #
17
+ # @see http://www.chessclub.com/help/PGN-spec PGN Specification
18
+ #
19
+ def self.parse(pgn, encoding = Encoding::ISO_8859_1)
20
+ pgn.force_encoding(encoding) if encoding
21
+
22
+ PGN::Parser.new.parse(pgn).map do |game|
23
+ PGN::Game.new(game[:moves], game[:tags], game[:result], game[:pgn], game[:comment])
24
+ end
25
+ end
26
+ end
data/pgn2.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'pgn/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'pgn2'
7
+ spec.version = PGN::VERSION
8
+ spec.authors = ['Stacey Touset', 'Murilo Vasconcelos']
9
+ spec.email = ['stacey@touset.org', 'muriloime@gmail.com']
10
+ spec.description = 'A PGN parser and FEN generator for Ruby'
11
+ spec.summary = 'A PGN parser for Ruby'
12
+ spec.homepage = 'https://github.com/muriloime/pgn'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'whittle'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 2.3'
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ end
data/spec/fen_spec.rb ADDED
@@ -0,0 +1,100 @@
1
+ require "spec_helper"
2
+
3
+ describe PGN::FEN do
4
+ describe "castling availability" do
5
+ it "should remove all castling availabilitiy after castling" do
6
+ pos = PGN::FEN.new("rnbqk2r/1p3pbp/p2p1np1/2pP4/P3PB2/2N2N2/1P3PPP/R2QKB1R b KQkq e3 0 8").to_position
7
+ next_pos = pos.move("O-O")
8
+ next_pos.to_fen.castling.should_not match(/k|q/)
9
+ end
10
+
11
+ it "should remove all castling availability after moving a king" do
12
+ pos = PGN::FEN.new("r1b1kb1r/pp2pppp/2n5/4P3/1nB5/P4N2/1P3PPP/RNBqK2R w KQkq - 0 9").to_position
13
+ next_pos = pos.move("Kxd1")
14
+ next_pos.to_fen.castling.should_not match(/K|Q/)
15
+ end
16
+
17
+ it "should remove only the one castling option after moving a rook" do
18
+ pos = PGN::FEN.new("r3k2r/1pp2pp1/p1pb1qn1/4p3/3PP1p1/8/PPPN1PPN/R1BQR1K1 b kq - 1 11").to_position
19
+ next_pos = pos.move("Rxh2")
20
+ next_pos.to_fen.castling.should_not match(/k/)
21
+ next_pos.to_fen.castling.should match(/q/)
22
+ end
23
+
24
+ it "should remove one castling option when a rook is taken" do
25
+ pos = PGN::FEN.new("rn1qkbnr/pbpppppp/1p6/8/6P1/2N4P/PPPPPP2/R1BQKBNR b KQkq - 2 3").to_position
26
+ next_pos = pos.move("Bxh1")
27
+ next_pos.to_fen.castling.should_not match(/K/)
28
+ end
29
+
30
+ it "should change to a hyphen once no side can castle" do
31
+ pos = PGN::FEN.new("r1bq1rk1/pp1nbppp/3ppn2/8/2PP1N2/P1N5/1P2BPPP/R1BQK2R w KQ - 2 9").to_position
32
+ pos.to_fen.castling.should_not == "-"
33
+ next_pos = pos.move("O-O")
34
+ next_pos.to_fen.castling.should == "-"
35
+ end
36
+ end
37
+
38
+ describe "en passant" do
39
+ it "should display the en passant square whenever a pawn moves two spaces" do
40
+ pos = PGN::FEN.new("rnbqkb1r/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKB1R w KQkq - 2 1").to_position
41
+ next_pos = pos.move("c4")
42
+ next_pos.to_fen.en_passant.should == "c3"
43
+ end
44
+
45
+ it "should be a hyphen if no pawn moved two spaces the previous move" do
46
+ pos = PGN::FEN.new("rnbqkb1r/pppppppp/5n2/8/2P5/5N2/PP1PPPPP/RNBQKB1R b KQkq c3 0 1").to_position
47
+ next_pos = pos.move("d6")
48
+ next_pos.to_fen.en_passant.should == "-"
49
+ end
50
+ end
51
+
52
+ describe "halfmove counter" do
53
+ it "should reset after a pawn advance" do
54
+ pos = PGN::FEN.new("2b2rk1/2pp1ppp/1p6/r3P2q/3Q4/2P5/PP3PPP/RN3RK1 w - - 3 15").to_position
55
+ pos.to_fen.halfmove.should == "3"
56
+ next_pos = pos.move("f4")
57
+ next_pos.to_fen.halfmove.should == "0"
58
+ end
59
+
60
+ it "should reset after a capture" do
61
+ pos = PGN::FEN.new("2r2rk1/1p2ppbp/1q1p1np1/pN4B1/Pnb1PP2/2N5/1PP1B1PP/R2Q1R1K w - - 5 14").to_position
62
+ pos.to_fen.halfmove.should == "5"
63
+ next_pos = pos.move("Bxc4")
64
+ next_pos.to_fen.halfmove.should == "0"
65
+ end
66
+
67
+ it "should not reset otherwise" do
68
+ pos = PGN::FEN.new("2k2b1r/p4p1p/q1p2p2/8/2br4/5P2/PPQB2PP/R1N1K2R w KQ - 0 17").to_position
69
+ pos.to_fen.halfmove.should == "0"
70
+ moves = %w{Qf5+ Rd7 Bc3 Bh6 Qa5 Re8+ Kf2 Be3+ Kg3 Rg8+ Kh4}
71
+ moves.each_with_index do |move, i|
72
+ pos = pos.move(move)
73
+ pos.to_fen.halfmove.should == (i+1).to_s
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "fullmove counter" do
79
+ it "should not increase after white moves" do
80
+ pos = PGN::FEN.new("4br1k/ppqnr1b1/3p3p/P1pP1p2/2P1pB2/6PP/1P2BP1N/R2QR1K1 w - - 3 25").to_position
81
+ pos.to_fen.fullmove.should == "25"
82
+ next_pos = pos.move("Qd2")
83
+ next_pos.to_fen.fullmove.should == "25"
84
+ end
85
+
86
+ it "should increase after black moves" do
87
+ pos = PGN::FEN.new("4br1k/ppqnr1b1/3p3p/P1pP1p2/2P1pB2/6PP/1P1QBP1N/R3R1K1 b - - 4 25").to_position
88
+ pos.to_fen.fullmove.should == "25"
89
+ next_pos = pos.move("Kh7")
90
+ next_pos.to_fen.fullmove.should == "26"
91
+ end
92
+ end
93
+
94
+ describe "displaying FEN notation" do
95
+ it "should return a string on inspect" do
96
+ fen = PGN::FEN.start
97
+ fen.inspect.should be_a(String)
98
+ end
99
+ end
100
+ end
data/spec/game_spec.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe PGN::Game do
4
+ describe '#positions' do
5
+ it 'does not raise an error' do
6
+ tags = { 'White' => 'Deep Blue', 'Black' => 'Kasparov' }
7
+ moves = %w[e4 c5 c3 d5 exd5 Qxd5 d4 Nf6 Nf3 Bg4 Be2 e6 h3 Bh5 O-O Nc6 Be3 cxd4 cxd4 Bb4 a3 Ba5 Nc3 Qd6 Nb5 Qe7 Ne5 Bxe2 Qxe2 O-O Rac1 Rac8 Bg5 Bb6 Bxf6 gxf6 Nc4 Rfd8 Nxb6 axb6 Rfd1 f5 Qe3 Qf6 d5 Rxd5 Rxd5 exd5 b3 Kh8 Qxb6 Rg8 Qc5 d4 Nd6 f4 Nxb7 Ne5 Qd5 f3 g3 Nd3 Rc7 Re8 Nd6 Re1+ Kh2 Nxf2 Nxf7+ Kg7 Ng5+ Kh6 Rxh7+]
8
+ result = '1-0'
9
+ game = PGN::Game.new(moves, tags, result)
10
+ expect { game.positions }.not_to raise_error
11
+ end
12
+
13
+ it 'has fullmove 2 after 1.e4 c5' do
14
+ moves = %w[e4 c5]
15
+ game = PGN::Game.new(moves)
16
+ last_pos = game.positions.last
17
+
18
+ expect(last_pos.fullmove).to eq 2
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'pry'
3
+
4
+ describe PGN do
5
+ describe '.parse' do
6
+ it 'should return a list of games' do
7
+ games = PGN.parse(File.read('./examples/immortal_game.pgn'))
8
+ expect(games.length).to eq 1
9
+ game = games.first
10
+ expect(game.result).to eq '1-0'
11
+ expect(game.tags['White']).to eq 'Adolf Anderssen'
12
+ expect(game.moves.last).to eq 'Be7#'
13
+ end
14
+
15
+ it 'should handle alternate castling notation' do
16
+ games = PGN.parse(File.read('./spec/pgn_files/alternate_castling.pgn'))
17
+ game = games.first
18
+ expect(game.tags['White']).to eq 'Somebody'
19
+ expect(game.result).to eq '*'
20
+ expect(game.moves.last).to eq 'O-O-O'
21
+ end
22
+
23
+ it 'should handle annotations' do
24
+ games = PGN.parse(File.read('./spec/pgn_files/annotations.pgn'))
25
+ games.each do |game|
26
+ expect(game.tags['White']).to eq 'Fool'
27
+ expect(game.result).to eq '0-1'
28
+ expect(game.moves.last).to eq 'Qh4#'
29
+ end
30
+ end
31
+
32
+ it 'should handle many games in order' do
33
+ games = PGN.parse(File.read('./spec/pgn_files/two_games.pgn'))
34
+ expect(games.first.moves(&:notation)).to eq ['f3', 'e5', 'g4', 'Qh4#']
35
+ end
36
+
37
+ it 'should handle comments' do
38
+ games = PGN.parse(File.read('./spec/pgn_files/comments.pgn'))
39
+ game = games.first
40
+ expect(game.tags['White']).to eq 'Scholar'
41
+ expect(game.result).to eq '1-0'
42
+ expect(game.moves.last).to eq 'Qxf7#'
43
+ end
44
+
45
+ it 'should handle multiline comments' do
46
+ games = PGN.parse(File.read('./spec/pgn_files/multiline_comments.pgn'))
47
+ game = games.first
48
+ expect(game.tags['White']).to eq 'Scholar'
49
+ expect(game.result).to eq '1-0'
50
+ expect(game.moves.last).to eq 'Qxf7#'
51
+ end
52
+
53
+ it 'should handle nested comments' do
54
+ games = PGN.parse(File.read('./spec/pgn_files/nested_comments.pgn'))
55
+ game = games.first
56
+ expect(game.result).to eq '*'
57
+ expect(game.moves.last).to eq 'Nf6'
58
+ end
59
+
60
+ it 'handles two annotations' do
61
+ games = PGN.parse(File.read('./spec/pgn_files/two_annotations.pgn'))
62
+ game = games.first
63
+ expect(game.moves[1].annotation).to eq ['$2', '$11']
64
+ end
65
+
66
+ it 'returns empty array when no variations' do
67
+ games = PGN.parse(File.read('./spec/pgn_files/variations.pgn'))
68
+ game = games.first
69
+ expect(game.moves.first.variations).to eq []
70
+ end
71
+
72
+ it 'should handle variations' do
73
+ games = PGN.parse(File.read('./spec/pgn_files/variations.pgn'))
74
+ game = games.first
75
+ expect(game.tags['Black']).to eq 'Petrov'
76
+ expect(game.result).to eq '*'
77
+ expect(game.moves.last).to eq 'Nf6'
78
+ end
79
+
80
+ it 'should handle variations longer than 1' do
81
+ games = PGN.parse(File.read('./spec/pgn_files/variations.pgn'))
82
+ game = games.first
83
+ # puts game.moves.map{|x| x.variations.to_a.count }
84
+ expect(game.moves[-2].variations.first).to eq %w[f4 exf4]
85
+ end
86
+
87
+ it 'should handle empty variation moves' do
88
+ games = PGN.parse(File.read('./spec/pgn_files/empty_variation_move.pgn'))
89
+ game = games.first
90
+ expect(game.result).to eq '*'
91
+ expect(game.moves.last).to eq 'Ng5'
92
+ end
93
+
94
+ it 'should handle complex files' do
95
+ games = PGN.parse(File.read('./spec/pgn_files/test.pgn'))
96
+ game = games.first
97
+ expect(game.tags['Black']).to eq 'Gelfand, Boris'
98
+ expect(game.result).to eq '1-0'
99
+ expect(game.moves[13]).to eq 'Nfd7'
100
+ expect(game.moves[34]).to eq 'f3'
101
+ expect(game.moves[35].annotation).to eq ['$6']
102
+ expect(game.moves[35].comment).to eq 'Gelfand decide tomar medidas.'
103
+ expect(game.moves[35].variations.size).to eq 1
104
+ variation = game.moves[35].variations[0]
105
+ expect(variation.size).to eq 2
106
+ expect(variation[0]).to eq 'exf3'
107
+ end
108
+
109
+ it 'should handle files with starting position' do
110
+ games = PGN.parse(File.read('./spec/pgn_files/fen.pgn'))
111
+ game = games.first
112
+ first_pos = game.positions.first
113
+ last_pos = game.positions.last
114
+ expect(first_pos.to_fen.to_s).to eq game.tags['FEN']
115
+ expect(last_pos.to_fen.to_s).to eq '5rkn/5p1p/2b2NpP/8/2B5/1P6/2PP3P/1K6 b - - 0 4'
116
+ end
117
+
118
+ it 'returns original game pgn' do
119
+ games = PGN.parse(File.read('./spec/pgn_files/fen.pgn'))
120
+ game = games.first
121
+ expect(game.pgn).to eq "[Event \"Event 1\"]\n[FEN \"4brkn/4bp1p/3q2pP/8/2B3N1/1P4N1/2PP3P/1K2Q3 w - - 0 1\"]\n[PlyCount \"7\"]\n\n1. Qxe7 Qxe7 2. Ne4 Bc6 3. Nef6+ Qxf6 4. Nxf6# 1-0"
122
+ end
123
+
124
+ it 'returns original game pgn for second game' do
125
+ games = PGN.parse(File.read('./spec/pgn_files/two_games.pgn'))
126
+ game = games.last
127
+ expect(game.pgn).to eq "[White \"Fool 2\"]\n[Black \"Somebody else 2\"]\n\n1. e4 e5 0-1\n"
128
+ end
129
+
130
+ it 'parses empty game' do
131
+ games = PGN.parse(File.read('./spec/pgn_files/no_moves.pgn'))
132
+ game = games.first
133
+ expect { game.positions }.not_to raise_error
134
+ expect(game.moves).not_to be_empty
135
+ end
136
+
137
+ it 'gets game comment' do
138
+ games = PGN.parse(File.read('./spec/pgn_files/no_moves.pgn'))
139
+ game = games.last
140
+ expect(game.comment).to eq('{game comment}')
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,4 @@
1
+ [White "Somebody"]
2
+ [Black "Somebody Else"]
3
+
4
+ 1.e4 e5 2.Nf3 Nc6 3.Bb5 d6 4.0-0 Bg4 5.Nc3 Qe7 6.d3 0-0-0 *
@@ -0,0 +1,9 @@
1
+ [White "Fool"]
2
+ [Black "Somebody else"]
3
+
4
+ 1. f3 e5 2. g4 $4 Qh4# 0-1
5
+
6
+ [White "Fool"]
7
+ [Black "Somebody else"]
8
+
9
+ 1. f3 e5 2. g4 ?? Qh4# 0-1
@@ -0,0 +1,8 @@
1
+ [White "Scholar"]
2
+ [Black "Somebody else"]
3
+
4
+ { This is a multi-line commment,
5
+ where a comment spans in multiple lines. }
6
+
7
+ 1. e4 e5 2. Qh5 Nc6 3. {White about to move} Bc4 {White
8
+ moves} Nf6 {I pity the fool!} 4. Qxf7# 1-0
@@ -0,0 +1,11 @@
1
+ [White "Somebody"]
2
+ [Black "Somebody Else"]
3
+
4
+ 1. d4 e6 2. c4 d5 3. Nc3 c6 4. Nf3 Nf6 5. Bg5 Nbd7 6. e3 Qa5 7. a3 (7. cxd5
5
+ Nxd5 8. Rc1 {Analyzer's primary variation: 8... Bb4 9. Qd2 e5 10. e4 Nxc3 11.
6
+ bxc3 Ba3 12. Rb1 h6 13. Be3 O-O 14. Bc4 exd4 15. cxd4 Qxd2+ 16. Nxd2 a5 17. O-O
7
+ b5 18. Be2 Rd8 19. d5 cxd5 20. exd5 Nc5 21. Rxb5 Rxd5 {+0.30/21} }) 7... Ne4 8.
8
+ Rc1 Nxg5 9. Nxg5 dxc4 10. Bxc4 Qxg5 11. O-O Nf6 12. Qf3 Qg4 13. Qxg4 Nxg4 14.
9
+ Ne4 Be7 15. b4 O-O 16. Rce1 Nf6 17. Ng5 ({Better is} 17. Nxf6+ gxf6 {-4.08/31}
10
+ (17... Bxf6) (17... gxf6)) (17. Nxf6+) 17... h6 18. Nf3 Ne4 ({Treatening} 18...
11
+ -- 19. Ra1 b5 {-4.05/31}) 19. Bd3 Ng5 *
@@ -0,0 +1,5 @@
1
+ [Event "Event 1"]
2
+ [FEN "4brkn/4bp1p/3q2pP/8/2B3N1/1P4N1/2PP3P/1K2Q3 w - - 0 1"]
3
+ [PlyCount "7"]
4
+
5
+ 1. Qxe7 Qxe7 2. Ne4 Bc6 3. Nef6+ Qxf6 4. Nxf6# 1-0
@@ -0,0 +1,5 @@
1
+ [White "Scholar"]
2
+ [Black "Somebody else"]
3
+
4
+ 1. e4 e5 2. Qh5 Nc6 3. Bc4 Nf6 {I pity
5
+ the fool!} 4. Qxf7# 1-0
@@ -0,0 +1,4 @@
1
+ [White "Somebody"]
2
+ [Black "Somebody Else"]
3
+
4
+ 1. e4 e5 2. Nf3 {this {is a} nested {comment}} 2... Nf6 *
@@ -0,0 +1,18 @@
1
+ % BOOKTITLE = book title
2
+ [Event "?"]
3
+ [Date "????.??.??"]
4
+ [Result "*"]
5
+ [Annotator "Mumu"]
6
+ [PlyCount "1"]
7
+
8
+ 1. -- {Trying to make a long comments in an empty game.it
9
+ goes in two lines } (1.
10
+ -- {Just putting something here too.}) *
11
+
12
+ [Event "?"]
13
+ [Date "????.??.??"]
14
+ [Result "*"]
15
+ [Annotator "Mumu"]
16
+ [PlyCount "1"]
17
+
18
+ {game comment} *
@@ -0,0 +1,55 @@
1
+ [Event "Zurich Chess Challenge"]
2
+ [Site "Zurich"]
3
+ [Date "2014.01.30"]
4
+ [Round "1"]
5
+ [White "Carlsen, Magnus"]
6
+ [Black "Gelfand, Boris"]
7
+ [Result "1-0"]
8
+ [ECO "D78"]
9
+ [WhiteElo "2878"]
10
+ [BlackElo "2777"]
11
+ [Annotator "Iago Porto"]
12
+ [PlyCount "73"]
13
+ [EventDate "2014.01.30"]
14
+ [EventCountry "SUI"]
15
+ [SourceDate "2014.01.04"]
16
+
17
+ {Esta es la primera partida de Carlsen como campeón del mundo. La voy a
18
+ comentar para el blog porque me ha gustado una idea bastante inusual que
19
+ ejecuta el jugador noruego. El análisis no pretende ser exhaustivo ni en ideas
20
+ ni en variantes, sino tan sólo reflejar las ideas y posiciones que me han
21
+ llamado la atención. He visto el análisis de Daniel King y estoy claramente
22
+ influenciado por él. Sin embargo, cualquier error es mío.} 1. c4 g6 2. d4 Nf6
23
+ 3. Nf3 Bg7 4. g3 c6 5. Bg2 d5 6. Qa4 O-O 7. O-O Nfd7 {No me voy a meter en la
24
+ apertura, no conozco nada de estas líneas, aunque es cierto que esta jugada
25
+ choca.} 8. Qc2 Nf6 9. Bf4 Bf5 10. Qb3 Qb6 11. Nbd2 Ne4 12. e3 {Hasta este
26
+ momento, la posición es bastante simétrica. El blanco tiene, en mi opinión,
27
+ una ligerísima ventaja gracias a tener más presión central, aunque la posición
28
+ no presenta ningún desbalance estratégico.} Qxb3 13. axb3 {Se ha creado un
29
+ desbalance en la posición. El blanco obtiene la columna a para su torre a
30
+ cambio de los peones doblados.} Na6 {Y ahora Carlsen debió de decir: "yo
31
+ quiero que mi alfil de g2 juegue" y sus siguientes jugadas son implacables
32
+ para lograr este objetivo.} 14. cxd5 cxd5 15. g4 $1 {Brillante sacrificio de
33
+ peón.} Bxg4 16. Nxe4 dxe4 17. Nd2 f5 18. f3 {Finalmente se consigue el
34
+ objetivo, el alfil de g2 juega y el blanco tiene una presión desagradable
35
+ sobre el ala de dama negra. Me recuerda a un gambito Benko.} e5 $6 {Gelfand
36
+ decide tomar medidas.} (18... exf3 19. Nxf3 {A pesar de tener un peón de menos,
37
+ todas las piezas del blanco están muy activas y son mejores que las de su
38
+ rival. El blanco debería estar mejor.}) 19. dxe5 exf3 20. Nxf3 {Con respecto a
39
+ la variante anterior, aquí Gelfand devuelve el peón para lograr que las piezas
40
+ blancas no están tan activas. Considero que el blanco está también mejor y no
41
+ hay que olvidar que el peón de e5 está pasado.} Rae8 21. Ra5 Nb4 {Tratando de
42
+ poner el caballo en juego vía c6, donde defenderá a7 y atacará e5.} 22. Nd4 {
43
+ Evita la amenaza negra de Cc6.} b6 {Gelfand cede ante la presión.} 23. Rxa7
44
+ Bxe5 {Se mantiene la igualdad de material, pero ahora el rey negro empieza a
45
+ parecer débil.} 24. Bh6 Rf6 (24... Rf7 25. Ra4 {y cuando el caballo se vaya,
46
+ Ad5 gana la torre de f7.}) 25. h3 Bh5 {Carlsen encierra un poco más el alfil
47
+ blanco de negras.} 26. Nc2 $1 {Otra bonita combinación.} g5 (26... Nxc2 27.
48
+ Bd5+ Kh8 28. Bg7# {En su análisis, Daniel King decía que la torre entra por la
49
+ banda, pase de la muerte hacia atrás al alfil blanco, el portero la detiene
50
+ pero queda muerta para que el alfil negro la empuje al jaque mate.}) 27. Bxg5
51
+ Rg6 28. Rxf5 h6 29. Bxh6 Rxh6 30. Nxb4 $18 {Con material de más, a partir de
52
+ ahora es cuestión de técnica.} Bxb2 31. Nd5 Kh8 32. Rb7 Bd1 33. b4 Rg8 34. Ne7
53
+ Rd8 35. Be4 {El alfil se suma al ataque. El rey negro sigue débil.} Bf6 36.
54
+ Rxb6 Kg7 37. Rf2 $1 {Preciso remate. El caballo de e7 no se puede comer porque}
55
+ (37. Rf2 Bxe7 38. Rg2+ {y cae la torre de h6.}) 1-0
@@ -0,0 +1,4 @@
1
+ [Event "?"]
2
+ [Site "?"]
3
+
4
+ 1. d4 2. d5 $2 $11 1/2-1/2
@@ -0,0 +1,9 @@
1
+ [White "Fool"]
2
+ [Black "Somebody else"]
3
+
4
+ 1. f3 e5 2. g4 $4 Qh4# 0-1
5
+
6
+ [White "Fool 2"]
7
+ [Black "Somebody else 2"]
8
+
9
+ 1. e4 e5 0-1
@@ -0,0 +1,4 @@
1
+ [White "Somebody"]
2
+ [Black "Petrov"]
3
+
4
+ 1. e4 e5 2. Nf3 {comment} (2. Nc3 {other} d5 (2... f5) 3. exd5) (2. f4 exf4 {final variation}) 2... Nf6 *
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe PGN::Position do
4
+
5
+ describe "start" do
6
+
7
+ it "should have fullmove 1" do
8
+ pos = PGN::Position.start
9
+ pos.fullmove.should == 1
10
+ end
11
+
12
+ end
13
+
14
+ context "disambiguating moves" do
15
+ describe "using SAN square disambiguation" do
16
+ pos = PGN::FEN.new("r1bqkb1r/pp1p1ppp/2n1pn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 3 6").to_position
17
+ next_pos = pos.move("Ndb5")
18
+
19
+ it "should move the specified piece" do
20
+ next_pos.board.at("d4").should be_nil
21
+ end
22
+
23
+ it "should not move the other piece" do
24
+ next_pos.board.at("c3").should == "N"
25
+ end
26
+ end
27
+
28
+ describe "using discovered check" do
29
+ pos = PGN::FEN.new("rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 5").to_position
30
+ next_pos = pos.move("Ne2")
31
+
32
+ it "should move the piece that doesn't give discovered check" do
33
+ next_pos.board.at("g1").should be_nil
34
+ end
35
+
36
+ it "shouldn't move the other piece" do
37
+ next_pos.board.at("c3").should == "N"
38
+ end
39
+ end
40
+
41
+ describe "with two pawns on the same file" do
42
+ pos = PGN::FEN.new("r2q1rk1/4bppp/p3n3/1p2n3/4N3/1B2BP2/PP3P1P/R2Q1RK1 w - - 4 19").to_position
43
+ next_pos = pos.move("f4")
44
+
45
+ it "should move the pawn in front" do
46
+ next_pos.board.at("f3").should be_nil
47
+ end
48
+
49
+ it "should not move the other pawn" do
50
+ next_pos.board.at("f2").should == "P"
51
+ end
52
+ end
53
+ end
54
+ end