chess_openings 0.0.3
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/.gitignore +2 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +22 -0
- data/README.md +52 -0
- data/chess_openings.gemspec +28 -0
- data/lib/chess_openings.rb +77 -0
- data/lib/chess_openings/json_openings/openings.json +2017 -0
- data/lib/chess_openings/opening.rb +40 -0
- data/lib/chess_openings/search_tree.rb +163 -0
- data/openings_parser.rb +85 -0
- metadata +128 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
class Opening
|
2
|
+
|
3
|
+
attr_accessor :name, :eco_code, :moves
|
4
|
+
|
5
|
+
def initialize(name, eco_code, moves)
|
6
|
+
@name = name
|
7
|
+
@eco_code = eco_code
|
8
|
+
@moves = moves.map! { |move| move.is_a?(String)? move.to_sym : move }
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{@eco_code}: #{@name}\n#{@moves}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{ "name" => @name, "eco_code" => @eco_code, "moves" => @moves }
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
@name == other.name && @eco_code == other.eco_code && @moves == other.moves
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_pgn
|
24
|
+
result = ""
|
25
|
+
index = 1
|
26
|
+
@moves.each do |move|
|
27
|
+
|
28
|
+
if index.odd?
|
29
|
+
result += "#{(index / 2.0).ceil}. #{move}"
|
30
|
+
elsif index.even? && @moves.size != index
|
31
|
+
result += " #{move} "
|
32
|
+
else
|
33
|
+
result += " #{move}"
|
34
|
+
end
|
35
|
+
|
36
|
+
index += 1
|
37
|
+
end
|
38
|
+
return result
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
class SearchTree
|
2
|
+
|
3
|
+
attr_accessor :root
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@root = Node.new(nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
def empty?
|
10
|
+
@root.is_leaf?
|
11
|
+
end
|
12
|
+
|
13
|
+
def size
|
14
|
+
size_helper(@root)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@root.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
return false unless @root.size == other.root.size
|
23
|
+
|
24
|
+
@root.keys.each do |key|
|
25
|
+
return false unless other.root.keys.include?(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
@root.keys.each do |key|
|
29
|
+
return false unless @root.nodes[key] == other.root.nodes[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def insert(moves, value)
|
36
|
+
moves = moves_in_symbols(moves)
|
37
|
+
insert_helper(moves, value, @root)
|
38
|
+
end
|
39
|
+
|
40
|
+
def search(moves)
|
41
|
+
moves = moves_in_symbols(moves)
|
42
|
+
search_helper(moves, @root)
|
43
|
+
end
|
44
|
+
|
45
|
+
def search_all_with_moves(moves)
|
46
|
+
moves = moves_in_symbols(moves)
|
47
|
+
node = find_node(moves, @root)
|
48
|
+
get_all_from_node(node).flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def moves_in_symbols(moves)
|
55
|
+
moves.map! { |move| move.is_a?(String)? move.to_sym : move }
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_node(moves, curr_node)
|
59
|
+
return curr_node if moves.empty?
|
60
|
+
|
61
|
+
curr_hash = curr_node.nodes
|
62
|
+
move = moves.first
|
63
|
+
return nil if curr_hash[move].nil?
|
64
|
+
|
65
|
+
next_node = curr_hash[move]
|
66
|
+
find_node(moves.drop(1), next_node)
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_all_from_node(curr_node)
|
70
|
+
|
71
|
+
result = curr_node.value.nil? ? [] : [curr_node.value]
|
72
|
+
return result if curr_node.is_leaf?
|
73
|
+
|
74
|
+
curr_hash = curr_node.nodes
|
75
|
+
|
76
|
+
curr_hash.each do |key, value|
|
77
|
+
next_node = value
|
78
|
+
result << get_all_from_node(next_node)
|
79
|
+
end
|
80
|
+
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def insert_helper(moves, value, curr_node)
|
85
|
+
return if moves.empty?
|
86
|
+
|
87
|
+
curr_hash = curr_node.nodes
|
88
|
+
move = moves.first
|
89
|
+
last_move = moves.size == 1
|
90
|
+
|
91
|
+
if curr_hash[move].nil?
|
92
|
+
if last_move
|
93
|
+
curr_hash[move] = Node.new(value)
|
94
|
+
else
|
95
|
+
curr_hash[move] = Node.new(nil)
|
96
|
+
end
|
97
|
+
else
|
98
|
+
curr_hash[move].value = value if last_move && curr_hash[move].value.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
next_node = curr_hash[move]
|
102
|
+
insert_helper(moves.drop(1), value, next_node)
|
103
|
+
end
|
104
|
+
|
105
|
+
def search_helper(moves, curr_node)
|
106
|
+
move = moves.first
|
107
|
+
curr_hash = curr_node.nodes
|
108
|
+
|
109
|
+
return nil if curr_hash[move].nil?
|
110
|
+
|
111
|
+
next_node = curr_hash[move]
|
112
|
+
return search_helper(moves.drop(1), next_node) || curr_hash[move].value
|
113
|
+
end
|
114
|
+
|
115
|
+
def size_helper(node)
|
116
|
+
sum = node.value.nil? ? 0 : 1
|
117
|
+
return sum if node.is_leaf?
|
118
|
+
node.keys.each do |key|
|
119
|
+
sum += size_helper(node.nodes[key])
|
120
|
+
end
|
121
|
+
return sum
|
122
|
+
end
|
123
|
+
|
124
|
+
class Node
|
125
|
+
|
126
|
+
attr_accessor :value, :nodes
|
127
|
+
|
128
|
+
def initialize(value)
|
129
|
+
@value = value
|
130
|
+
@nodes = {}
|
131
|
+
end
|
132
|
+
|
133
|
+
def is_leaf?
|
134
|
+
@nodes.empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
def size
|
138
|
+
@nodes.size
|
139
|
+
end
|
140
|
+
|
141
|
+
def keys
|
142
|
+
@nodes.keys
|
143
|
+
end
|
144
|
+
|
145
|
+
def include?(key)
|
146
|
+
@nodes.keys.include?(key)
|
147
|
+
end
|
148
|
+
|
149
|
+
def ==(other)
|
150
|
+
return false if self.size != other.size || @value != other.value
|
151
|
+
@nodes.keys.each do |key|
|
152
|
+
return false unless other.keys.include?(key)
|
153
|
+
end
|
154
|
+
|
155
|
+
@nodes.keys.each do |key|
|
156
|
+
return false if @nodes[key] != other.nodes[key]
|
157
|
+
end
|
158
|
+
|
159
|
+
true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/openings_parser.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'json'
|
4
|
+
load 'opening.rb'
|
5
|
+
|
6
|
+
def get_eco_code(name)
|
7
|
+
name.split(' ').first
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove_eco_code(name)
|
11
|
+
name.split(' ').drop(1).join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_more_than_1_code?(name)
|
15
|
+
get_eco_code(name).size > 3
|
16
|
+
end
|
17
|
+
|
18
|
+
def convert_line_to_array(moves)
|
19
|
+
moves.split(' ').select{ |move| !move.include?('.') }
|
20
|
+
end
|
21
|
+
|
22
|
+
initial_page = "http://www.365chess.com/eco.php"
|
23
|
+
|
24
|
+
openings = []
|
25
|
+
pages_to_scrap = [initial_page]
|
26
|
+
|
27
|
+
# Scrap pages in pages_to_scrap array and dump Opening objects to openings array
|
28
|
+
pages_to_scrap.each do |link|
|
29
|
+
|
30
|
+
page = Nokogiri::HTML(open(link))
|
31
|
+
page.css('ul#tree li.closed div.line').each do |line|
|
32
|
+
|
33
|
+
if has_more_than_1_code?(line.css('div.opname a').text)
|
34
|
+
|
35
|
+
pages_to_scrap << line.css('div.opname a').first["href"]
|
36
|
+
else
|
37
|
+
name = remove_eco_code(line.css('div.opname a').text)
|
38
|
+
eco = get_eco_code(line.css('div.opname a').text)
|
39
|
+
moves = convert_line_to_array(line.css('div.fright').text.empty? ? line.css('div.opmoves').text : line.css('div.fright').text)
|
40
|
+
|
41
|
+
openings << Opening.new(name, eco, moves)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
puts "Number of openings aquired: #{openings.size}"
|
47
|
+
puts "Number of pages scrapped: #{pages_to_scrap.size}"
|
48
|
+
|
49
|
+
# openings.each do |op|
|
50
|
+
# puts op.to_s
|
51
|
+
# end
|
52
|
+
|
53
|
+
# Check if there are some invalid openings
|
54
|
+
invalid_openings = openings.select do |op|
|
55
|
+
op.name.empty? || op.moves.empty? || op.eco_code.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
# If there are no invalid openings, go ahead and create a JSON
|
59
|
+
if invalid_openings.empty?
|
60
|
+
|
61
|
+
# Convert array elements to hashes
|
62
|
+
openings.map! { |op| op.to_h }
|
63
|
+
|
64
|
+
# Write to JSON
|
65
|
+
File.delete("openings.json") if File.exists?("openings.json")
|
66
|
+
File.open("openings.json", "a+") do |file|
|
67
|
+
file.write("{\n")
|
68
|
+
file.write("\"openings\": [\n")
|
69
|
+
|
70
|
+
openings.each_with_index do |op, index|
|
71
|
+
result = openings.size == index + 1 ? "" : ", "
|
72
|
+
file.write("#{JSON.generate(op)}#{result}\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
file.write("]")
|
76
|
+
file.write("}")
|
77
|
+
|
78
|
+
puts "openings.json was successfully created!"
|
79
|
+
end
|
80
|
+
|
81
|
+
else
|
82
|
+
puts "There were some invalid openings detected:"
|
83
|
+
puts invalid_openings
|
84
|
+
end
|
85
|
+
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chess_openings
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simão Neves
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pgn
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.6
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.6
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.6.6.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.6.6.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.3.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.3.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: Gem that allows you to know what chess opening was used in a chess game,
|
84
|
+
find chess openings by name or get all openings that start with certain moves
|
85
|
+
email:
|
86
|
+
- simaocostaneves@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
- chess_openings-0.0.1.gem
|
98
|
+
- chess_openings.gemspec
|
99
|
+
- lib/chess_openings.rb
|
100
|
+
- lib/chess_openings/json_openings/openings.json
|
101
|
+
- lib/chess_openings/opening.rb
|
102
|
+
- lib/chess_openings/search_tree.rb
|
103
|
+
- openings_parser.rb
|
104
|
+
homepage: http://www.github.com/simaoneves/chess_openings
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.4.5
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Gem that allows you to calculate which opening was used in a chess game
|
128
|
+
test_files: []
|