Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug fixes #17

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ spec/reports
test/tmp
test/version_tmp
tmp


tags
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# PGN
# PGN2

This is a fork from [pgn](https://github.com/capicue/pgn) gem.
A PGN parser and FEN generator for ruby.

## Usage
Expand Down Expand Up @@ -103,15 +104,15 @@ _ _ _ ♙ _ _ _ _

Add this line to your application's Gemfile:

gem 'pgn'
gem 'pgn2'

And then execute:

$ bundle

Or install it yourself as:

$ gem install pgn
$ gem install pgn2

## Contributing

Expand Down
19 changes: 9 additions & 10 deletions lib/pgn.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
require "pgn/board"
require "pgn/fen"
require "pgn/game"
require "pgn/move"
require "pgn/move_calculator"
require "pgn/parser"
require "pgn/position"
require "pgn/version"
require 'pgn/board'
require 'pgn/fen'
require 'pgn/game'
require 'pgn/move'
require 'pgn/move_calculator'
require 'pgn/parser'
require 'pgn/position'
require 'pgn/version'

module PGN

# @param pgn [String] a pgn representation of one or more chess games
# @return [Array<PGN::Game>] a list of games
#
Expand All @@ -21,7 +20,7 @@ def self.parse(pgn, encoding = Encoding::ISO_8859_1)
pgn.force_encoding(encoding) if encoding

PGN::Parser.new.parse(pgn).map do |game|
PGN::Game.new(game[:moves], game[:tags], game[:result])
PGN::Game.new(game[:moves], game[:tags], game[:result], game[:pgn], game[:comment])
end
end
end
64 changes: 23 additions & 41 deletions lib/pgn/board.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,26 @@ module PGN
# @!attribute squares
# @return [Array<Array<String>>] the pieces on the board
#

class Board
# The starting, internal representation of a chess board
#
START = [
["R", "P", nil, nil, nil, nil, "p", "r"],
["N", "P", nil, nil, nil, nil, "p", "n"],
["B", "P", nil, nil, nil, nil, "p", "b"],
["Q", "P", nil, nil, nil, nil, "p", "q"],
["K", "P", nil, nil, nil, nil, "p", "k"],
["B", "P", nil, nil, nil, nil, "p", "b"],
["N", "P", nil, nil, nil, nil, "p", "n"],
["R", "P", nil, nil, nil, nil, "p", "r"],
]
['R', 'P', nil, nil, nil, nil, 'p', 'r'],
['N', 'P', nil, nil, nil, nil, 'p', 'n'],
['B', 'P', nil, nil, nil, nil, 'p', 'b'],
['Q', 'P', nil, nil, nil, nil, 'p', 'q'],
['K', 'P', nil, nil, nil, nil, 'p', 'k'],
['B', 'P', nil, nil, nil, nil, 'p', 'b'],
['N', 'P', nil, nil, nil, nil, 'p', 'n'],
['R', 'P', nil, nil, nil, nil, 'p', 'r']
].freeze

FILE_TO_INDEX = {
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
'h' => 7,
}
INDEX_TO_FILE = Hash[FILE_TO_INDEX.map(&:reverse)]
FILE_TO_INDEX = ('a'..'h').each_with_index.to_h
INDEX_TO_FILE = FILE_TO_INDEX.map(&:reverse).to_h

RANK_TO_INDEX = {
'1' => 0,
'2' => 1,
'3' => 2,
'4' => 3,
'5' => 4,
'6' => 5,
'7' => 6,
'8' => 7,
}
INDEX_TO_RANK = Hash[RANK_TO_INDEX.map(&:reverse)]
RANK_TO_INDEX = ('1'..'8').each_with_index.to_h
INDEX_TO_RANK = RANK_TO_INDEX.map(&:reverse).to_h

# algebraic to unicode piece lookup
#
Expand All @@ -62,8 +45,8 @@ class Board
'B' => "\u{2657}",
'N' => "\u{2658}",
'P' => "\u{2659}",
nil => '_',
}
nil => '_'
}.freeze

attr_accessor :squares

Expand Down Expand Up @@ -108,9 +91,9 @@ def initialize(squares)
def at(*args)
case args.length
when 1
self.at(*coordinates_for(args.first))
at(*coordinates_for(args.first))
when 2
self.squares[args[0]][args[1]]
squares[args[0]][args[1]]
end
end

Expand All @@ -121,7 +104,7 @@ def at(*args)
#
def change!(changes)
changes.each do |square, piece|
self.update(square, piece)
update(square, piece)
end
self
end
Expand All @@ -134,7 +117,7 @@ def change!(changes)
#
def update(square, piece)
coords = coordinates_for(square)
self.squares[coords[0]][coords[1]] = piece
squares[coords[0]][coords[1]] = piece
self
end

Expand Down Expand Up @@ -166,16 +149,15 @@ def position_for(coordinates)
# pieces
#
def inspect
self.squares.transpose.reverse.map do |row|
row.map{|chr| UNICODE_PIECES[chr] }.join(' ')
squares.transpose.reverse.map do |row|
row.map { |chr| UNICODE_PIECES[chr] }.join(' ')
end.join("\n")
end

# @return [PGN::Board] a copy of self with duplicated squares
#
def dup
PGN::Board.new(self.squares.map(&:dup))
PGN::Board.new(squares.map(&:dup))
end

end
end
78 changes: 45 additions & 33 deletions lib/pgn/game.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@
module PGN
class MoveText
attr_accessor :notation, :annotation, :comment, :variations
def initialize(notation, annotation = nil, comment = nil, variations = nil)

def initialize(notation, annotation = nil, comment = nil, variations = [])
@notation = notation
@annotation = annotation
@comment = comment
@comment = clean_text(comment)
@variations = variations
end

def ==(m)
self.to_s == m.to_s
to_s == m.to_s
end

def eql?(m)
self == m
end

def hash
@notation.hash
end

def to_s
@notation
end

def clean_text(text)
text&.gsub(/{(.*)}/, '\1')&.gsub(/\s+/, ' ')&.strip
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why get rid of curly braces?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just realized I did not open a new branch for the bug fixes I proposed. My master diverged too much from the gem already. It has many fixes/features now.

The curly braces in particular I just thought it made more sense to remove them alltogether in a later commit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of more additions in my master , in the case you are interested, at least:

  1. Adding the pgn and game comment string to game object
  2. Fix bug when there are no moves in pgn ( only comments)
  3. Properly handle "don't care" variations

Copy link

@lexisvar lexisvar Nov 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@muriloime @capicue How Can I allow tag_values with special characters like Ññ?

for example in the line [WhiteTeam "NARIÑO"] I got this error:

Encoding::CompatibilityError:
       incompatible encoding regexp match (UTF-8 regexp with ISO-8859-1 string)

end
end
# {PGN::Game} holds all of the information about a game. It is either
# the result of parsing a PGN file, or created by hand.
Expand All @@ -50,51 +54,59 @@ def to_s
# game.result #=> "1-0"
#
class Game
attr_accessor :tags, :moves, :result
attr_accessor :tags, :result, :pgn, :comment
attr_reader :moves

LEFT = %r{(a|\x1B\[D)\z}
RIGHT = %r{(d|\x1B\[C)\z}
EXIT = %r{(q|\x03)\z}
LEFT = /(a|\x1B\[D)\z/.freeze
RIGHT = /(d|\x1B\[C)\z/.freeze
EXIT = /(q|\x03)\z/.freeze

# @param moves [Array<String>] a list of moves in SAN
# @param tags [Hash<String, String>] metadata about the game
# @param result [String] the outcome of the game
#
def initialize(moves, tags = nil, result = nil)
self.moves = moves
self.tags = tags
self.result = result
def initialize(moves, tags = nil, result = nil, pgn = nil, comment = nil)
self.moves = moves
self.tags = tags
self.result = result
self.pgn = pgn
self.comment = comment
end

# @param moves [Array<String>] a list of moves in SAN
#
# Standardize castling moves to use O's instead of 0's
#
def moves=(moves)
@moves = moves.map do |m|
if m.is_a? String
MoveText.new(m.gsub("0", "O"))
else
MoveText.new(m.notation.gsub("0", "O"), m.annotation, m.comment, m.variations)
@moves =
moves.map do |m|
if m.is_a? String
MoveText.new(m.gsub('0', 'O'))
else
MoveText.new(m.notation.gsub('0', 'O'), m.annotation, m.comment, m.variations)
end
end
end
end

def initial_fen
tags && tags['FEN']
end

def starting_position
@starting_position ||= if fen = (self.tags && self.tags['FEN'])
PGN::FEN.new(fen).to_position
else
PGN::Position.start
end
end
@starting_position ||= if initial_fen
PGN::FEN.new(initial_fen).to_position
else
PGN::Position.start
end
end

# @return [Array<PGN::Position>] list of the {PGN::Position}s in the game
#
def positions
@positions ||= begin
position = starting_position
arr = [position]
self.moves.each do |move|
moves.each do |move|
new_pos = position.move(move.notation)
arr << new_pos
position = new_pos
Expand All @@ -106,7 +118,7 @@ def positions
# @return [Array<String>] list of the fen representations of the positions
#
def fen_list
self.positions.map {|p| p.to_fen.inspect }
positions.map { |p| p.to_fen.inspect }
end

# Interactively step through the game
Expand All @@ -115,18 +127,18 @@ def fen_list
#
def play
index = 0
hist = Array.new(3, "")
hist = Array.new(3, '')

loop do
puts "\e[H\e[2J"
puts self.positions[index].inspect
puts positions[index].inspect
hist[0..2] = (hist[1..2] << STDIN.getch)

case hist.join
when LEFT
index -= 1 if index > 0
when RIGHT
index += 1 if index < self.moves.length
index += 1 if index < moves.length
when EXIT
break
end
Expand Down
Loading