eco-classifier 0.5
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/Gemfile +3 -0
- data/Gemfile.lock +27 -0
- data/README.md +40 -0
- data/Rakefile +10 -0
- data/bin/console +13 -0
- data/data/scid.eco +20818 -0
- data/eco-classifier.gemspec +24 -0
- data/lib/eco_classifier.rb +20 -0
- data/lib/eco_classifier/eco_data_file_parser.rb +22 -0
- data/lib/eco_classifier/opening.rb +54 -0
- data/lib/eco_classifier/opening_tree.rb +90 -0
- data/lib/eco_classifier/version.rb +3 -0
- data/test/eco_classifier_test.rb +28 -0
- data/test/test_helper.rb +9 -0
- metadata +101 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$:.unshift(lib) unless $:.include?(lib)
|
5
|
+
|
6
|
+
require 'eco_classifier/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = "eco-classifier"
|
10
|
+
s.version = EcoClassifier::VERSION
|
11
|
+
s.authors = ["Linmiao Xu"]
|
12
|
+
s.email = ["lin@robotmoon.com"]
|
13
|
+
s.homepage = "https://github.com/linrock/eco-classifier"
|
14
|
+
|
15
|
+
s.summary = "Classifies chess openings from a list of opening moves"
|
16
|
+
s.description = "Classifies chess openings from a list of opening moves using the SCID ECO data file"
|
17
|
+
s.license = "MIT"
|
18
|
+
|
19
|
+
s.files = `git ls-files -z`.split("\0")
|
20
|
+
|
21
|
+
s.add_development_dependency "rake", "~> 10.0"
|
22
|
+
s.add_development_dependency "minitest", "~> 5.0"
|
23
|
+
s.add_development_dependency "pry", "~> 0.10"
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "eco_classifier/eco_data_file_parser"
|
2
|
+
require "eco_classifier/opening_tree"
|
3
|
+
require "eco_classifier/version"
|
4
|
+
|
5
|
+
module EcoClassifier
|
6
|
+
def self.opening_tree
|
7
|
+
return @@opening_tree if defined? @@opening_tree
|
8
|
+
@@opening_tree = OpeningTree.new
|
9
|
+
@@opening_tree.generate!
|
10
|
+
@@opening_tree
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.classify_moves(moves)
|
14
|
+
opening_tree.get_opening moves
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.classify_pgn(pgn)
|
18
|
+
opening_tree.get_opening_from_pgn pgn
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Parses the scid.eco data file into openings
|
2
|
+
#
|
3
|
+
class EcoDataFileParser
|
4
|
+
ROW_SCANNER = /\A(?<eco>[A-E]\d{2}[a-z]?)\s+"(?<name>.*)"\s+(?<pgn>.*)\z/
|
5
|
+
|
6
|
+
attr_accessor :openings
|
7
|
+
|
8
|
+
def initialize(eco_file)
|
9
|
+
@text = open(eco_file, 'r').read.strip
|
10
|
+
@text = @text.gsub(/\n /, ' ')
|
11
|
+
@openings = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def scan_into_openings
|
15
|
+
@text.split(/\n/).each do |row|
|
16
|
+
match = row.match(ROW_SCANNER)
|
17
|
+
next unless match
|
18
|
+
@openings << Opening.new(match)
|
19
|
+
end
|
20
|
+
@openings
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Represents an opening as parsed from scid.eco
|
2
|
+
#
|
3
|
+
class Opening
|
4
|
+
|
5
|
+
attr_accessor :eco, :name, :pgn
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
self.eco = options[:eco]
|
9
|
+
self.name = options[:name]
|
10
|
+
self.pgn = options[:pgn]
|
11
|
+
end
|
12
|
+
|
13
|
+
def base_eco
|
14
|
+
self.eco[/\A([A-E]\d{2})/, 1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def base_name
|
18
|
+
self.name.split(":").first
|
19
|
+
end
|
20
|
+
|
21
|
+
def variation
|
22
|
+
return @variation if defined?(@variation)
|
23
|
+
match = self.name[/:(.*)/, 1]
|
24
|
+
return unless match
|
25
|
+
@variation = match.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def variation_name
|
29
|
+
return unless variation
|
30
|
+
return @variation_name if defined?(@variation_name)
|
31
|
+
@variation_name =
|
32
|
+
variation.split(",").map(&:strip).select {|str| str[0] !~ /\d/ }.join(", ")
|
33
|
+
end
|
34
|
+
|
35
|
+
def variation_line
|
36
|
+
return unless variation
|
37
|
+
return @variation_line if defined?(@variation_line)
|
38
|
+
@variation_line =
|
39
|
+
variation.split(",").map(&:strip).select {|str| str[0] =~ /\d/ }.join(", ")
|
40
|
+
end
|
41
|
+
|
42
|
+
def move_list
|
43
|
+
pgn.gsub(/\d+\./, '').gsub(/\*/, '').strip.split(/\s+/)
|
44
|
+
end
|
45
|
+
|
46
|
+
def as_json(options = {})
|
47
|
+
{
|
48
|
+
eco: base_eco,
|
49
|
+
name: base_name,
|
50
|
+
variation: variation,
|
51
|
+
full_name: self.name
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Used for fast opening lookups, given a move list
|
2
|
+
#
|
3
|
+
require 'eco_classifier/opening'
|
4
|
+
|
5
|
+
class OpeningTree
|
6
|
+
DATA_FILE = File.join(File.dirname(__FILE__), '../../data/scid.eco')
|
7
|
+
|
8
|
+
# children -> maps move names to more nodes
|
9
|
+
#
|
10
|
+
class Node
|
11
|
+
attr_accessor :opening, :children
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@children = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_opening(opening)
|
18
|
+
@opening = opening
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_leaf?
|
22
|
+
@children.size == 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
if @opening
|
27
|
+
%(
|
28
|
+
<Node @eco="#{@opening.eco}"
|
29
|
+
@name="#{@opening.name}"
|
30
|
+
@n_children=#{@children.size}>
|
31
|
+
).gsub(/\s+/, ' ')
|
32
|
+
else
|
33
|
+
%(<Node @n_children=#{@children.size}>)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :root, :size
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@root = Node.new
|
42
|
+
@size = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_tree(openings)
|
46
|
+
openings.each do |opening|
|
47
|
+
current = @root
|
48
|
+
moves = opening.move_list
|
49
|
+
while (move = moves.shift)
|
50
|
+
if !current.children[move]
|
51
|
+
current.children[move] = Node.new
|
52
|
+
end
|
53
|
+
current = current.children[move]
|
54
|
+
end
|
55
|
+
current.set_opening opening
|
56
|
+
@size += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate!
|
61
|
+
openings = EcoDataFileParser.new(DATA_FILE).scan_into_openings
|
62
|
+
build_tree(openings)
|
63
|
+
end
|
64
|
+
|
65
|
+
def search_for_opening(move_list)
|
66
|
+
current = @root
|
67
|
+
last_opening = nil
|
68
|
+
while (move = move_list.shift)
|
69
|
+
break unless current.children[move]
|
70
|
+
current = current.children[move]
|
71
|
+
last_opening = current.opening if current.opening
|
72
|
+
end
|
73
|
+
{
|
74
|
+
opening: last_opening,
|
75
|
+
search_done: current.is_leaf? || move_list.length > 0
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_opening(move_list)
|
80
|
+
search_for_opening(move_list)[:opening]
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_opening_from_pgn(pgn)
|
84
|
+
get_opening pgn.gsub(/\d+\./, '').gsub(/\*/, '').strip.split(/\s+/)
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
%(<OpeningTree @size=#{@size}>)
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EcoClassifierTest < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@klass = EcoClassifier
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_classifier_detects_queens_pawn_game
|
10
|
+
opening = @klass.classify_moves %w( d4 d5 )
|
11
|
+
assert opening.name == "Queen's Pawn Game"
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_classifier_detects_kings_gambit
|
15
|
+
opening = @klass.classify_moves %w( e4 e5 f4)
|
16
|
+
assert opening.name == "King's Gambit"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_classifier_detects_queens_gambit
|
20
|
+
opening = @klass.classify_moves %w( d4 d5 c4)
|
21
|
+
assert opening.name == "Queen's Gambit"
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_classifier_detects_ruy_lopez
|
25
|
+
opening = @klass.classify_moves %w( e4 e5 Nf3 Nc6 Bb5 )
|
26
|
+
assert opening.name == "Spanish (Ruy Lopez)"
|
27
|
+
end
|
28
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eco-classifier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Linmiao Xu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.10'
|
55
|
+
description: Classifies chess openings from a list of opening moves using the SCID
|
56
|
+
ECO data file
|
57
|
+
email:
|
58
|
+
- lin@robotmoon.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- Gemfile
|
64
|
+
- Gemfile.lock
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- bin/console
|
68
|
+
- data/scid.eco
|
69
|
+
- eco-classifier.gemspec
|
70
|
+
- lib/eco_classifier.rb
|
71
|
+
- lib/eco_classifier/eco_data_file_parser.rb
|
72
|
+
- lib/eco_classifier/opening.rb
|
73
|
+
- lib/eco_classifier/opening_tree.rb
|
74
|
+
- lib/eco_classifier/version.rb
|
75
|
+
- test/eco_classifier_test.rb
|
76
|
+
- test/test_helper.rb
|
77
|
+
homepage: https://github.com/linrock/eco-classifier
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.7.6
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Classifies chess openings from a list of opening moves
|
101
|
+
test_files: []
|