chess_openings 0.0.3 → 1.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.
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.7
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+
86
+
87
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="ChessOpenings.html" title="ChessOpenings (class)">ChessOpenings</a></span>, <span class='object_link'><a href="ChessOpeningsHelper.html" title="ChessOpeningsHelper (class)">ChessOpeningsHelper</a></span>, <span class='object_link'><a href="InvalidPGNError.html" title="InvalidPGNError (class)">InvalidPGNError</a></span>, <span class='object_link'><a href="Opening.html" title="Opening (class)">Opening</a></span>, <span class='object_link'><a href="SearchTree.html" title="SearchTree (class)">SearchTree</a></span>
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Thu Jan 12 10:12:16 2017 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.7 (ruby-2.2.0).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,18 @@
1
+ # Helper class with utility functions
2
+ class ChessOpeningsHelper
3
+ # Transform contents of array to symbols
4
+ #
5
+ # @param [Array] moves Moves as strings or symbols
6
+ # @return [Array] Array with all moves as symbols
7
+ def self.moves_as_symbols(moves)
8
+ moves.map { |move| move.is_a?(String) ? move.to_sym : move }
9
+ end
10
+
11
+ # Transform contents of array to strings
12
+ #
13
+ # @param [Array] moves Moves as strings or symbols
14
+ # @return [Array] Array with all moves as strings
15
+ def self.moves_as_strings(moves)
16
+ moves.map { |move| move.is_a?(Symbol) ? move.to_s : move }
17
+ end
18
+ end
@@ -636,8 +636,8 @@
636
636
  {"name":"Sicilian, Pin, Koch variation","eco_code":"B40","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nf6","Nc3","Bb4","e5"]},
637
637
  {"name":"Sicilian defence","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6"]},
638
638
  {"name":"Sicilian, Szen (`anti-Taimanov') variation","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6","Nb5"]},
639
- {"name":"Sicilian, Szen, hedgehog variation","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6","Nb5","d6","c4","Nf6","Nb1-c3","a6","Na3","Be7","Be2","O-O","O-O","b6"]},
640
- {"name":"Sicilian, Szen variation, Dely-Kasparov gambit","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6","Nb5","d6","c4","Nf6","Nb1-c3","a6","Na3","d5"]},
639
+ {"name":"Sicilian, Szen, hedgehog variation","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6","Nb5","d6","c4","Nf6","N1c3","a6","Na3","Be7","Be2","O-O","O-O","b6"]},
640
+ {"name":"Sicilian, Szen variation, Dely-Kasparov gambit","eco_code":"B44","moves":["e4","c5","Nf3","e6","d4","cxd4","Nxd4","Nc6","Nb5","d6","c4","Nf6","N1c3","a6","Na3","d5"]},
641
641
  {"name":"Sicilian","eco_code":"B50","moves":["e4","c5","Nf3","d6"]},
642
642
  {"name":"Sicilian, wing gambit deferred","eco_code":"B50","moves":["e4","c5","Nf3","d6","b4"]},
643
643
  {"name":"Sicilian, Chekhover variation","eco_code":"B53","moves":["e4","c5","Nf3","d6","d4","cxd4","Qxd4"]},
@@ -1678,7 +1678,7 @@
1678
1678
  {"name":"French, Winawer, Kondratiyev variation","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Bd3","c5","exd5","Qxd5","Bd2"]},
1679
1679
  {"name":"French, Winawer, fingerslip variation","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Bd2"]},
1680
1680
  {"name":"French, Winawer, Alekhine (Maroczy) gambit","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Nge2"]},
1681
- {"name":"French, Winawer, Alekhine gambit, Alatortsev variation","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Nge2","dxe4","a3","Be7","Nxe4","Nf6","Ne2-g3","O-O","Be2","Nc6"]},
1681
+ {"name":"French, Winawer, Alekhine gambit, Alatortsev variation","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Nge2","dxe4","a3","Be7","Nxe4","Nf6","N2g3","O-O","Be2","Nc6"]},
1682
1682
  {"name":"French, Winawer, Alekhine gambit","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Nge2","dxe4","a3","Bxc3+"]},
1683
1683
  {"name":"French, Winawer, Alekhine gambit, Kan variation","eco_code":"C15","moves":["e4","e6","d4","d5","Nc3","Bb4","Nge2","dxe4","a3","Bxc3+","Nxc3","Nc6"]},
1684
1684
  {"name":"French, Winawer, advance variation","eco_code":"C16","moves":["e4","e6","d4","d5","Nc3","Bb4","e5"]},
@@ -1,40 +1,56 @@
1
- class Opening
1
+ require 'pgn'
2
+ require_relative 'chess_openings_helper.rb'
2
3
 
4
+ # Class that represents a chess Opening
5
+ class Opening
3
6
  attr_accessor :name, :eco_code, :moves
4
7
 
5
8
  def initialize(name, eco_code, moves)
6
9
  @name = name
7
10
  @eco_code = eco_code
8
- @moves = moves.map! { |move| move.is_a?(String)? move.to_sym : move }
11
+ @moves = moves.map! { |move| move.is_a?(String) ? move.to_sym : move }
9
12
  end
10
13
 
14
+ # String representation of Opening
15
+ #
16
+ # @return [String] String that represents an Opening
11
17
  def to_s
12
18
  "#{@eco_code}: #{@name}\n#{@moves}"
13
19
  end
14
20
 
21
+ # Hash representation of Opening
22
+ #
23
+ # @return [Hash] Hash that represents an Opening
15
24
  def to_h
16
- { "name" => @name, "eco_code" => @eco_code, "moves" => @moves }
25
+ { 'name' => @name, 'eco_code' => @eco_code, 'moves' => @moves }
17
26
  end
18
27
 
28
+ # Compares two Openings
29
+ #
30
+ # @param [Opening] other Opening to be compared
31
+ # @return [Boolean] True if both Openings have same values, false otherwise
19
32
  def ==(other)
20
33
  @name == other.name && @eco_code == other.eco_code && @moves == other.moves
21
34
  end
22
35
 
36
+ # Returns PGN formatted String of the Opening
37
+ #
38
+ # @return [String] String that represents a game where Opening was used
23
39
  def to_pgn
24
- result = ""
40
+ result = ''
25
41
  index = 1
26
42
  @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
-
43
+ result += index.odd? ? "#{(index / 2.0).ceil}.#{move}" : " #{move} "
36
44
  index += 1
37
45
  end
38
- return result
46
+ result.chop!
47
+ end
48
+
49
+ # Returns FEN formatted String representation of the Opening
50
+ #
51
+ # @return [String] String that represents a game where Opening was used
52
+ def to_fen
53
+ moves = ChessOpeningsHelper.moves_as_strings(@moves)
54
+ PGN::Game.new(moves).fen_list.last
39
55
  end
40
- end
56
+ end
@@ -1,128 +1,145 @@
1
- class SearchTree
1
+ require_relative 'chess_openings_helper.rb'
2
2
 
3
+ # Class that is a Tree-like data structure to hold all Openings
4
+ class SearchTree
3
5
  attr_accessor :root
4
6
 
5
7
  def initialize
6
8
  @root = Node.new(nil)
7
9
  end
8
10
 
11
+ # Check if tree doesnt have child Nodes and root is empty
12
+ #
13
+ # @return [Boolean] True if the tree doesnt have childs and root value is nil, false otherwise
9
14
  def empty?
10
- @root.is_leaf?
15
+ @root.empty? && @root.leaf?
11
16
  end
12
17
 
18
+ # Number of not empty Nodes
19
+ #
20
+ # @return [int] Total of not empty Nodes in the tree
13
21
  def size
14
22
  size_helper(@root)
15
23
  end
16
24
 
25
+ # String representation of the tree
26
+ #
27
+ # @return [String] Representation of the Nodes in the tree
17
28
  def to_s
18
29
  @root.to_s
19
30
  end
20
31
 
32
+ # Compares two trees
33
+ #
34
+ # @param [SearchTree] other SearchTree to be compared with
35
+ # @return [Boolean] True if both trees have the same children with the same values
21
36
  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
37
+ @root == other.root
33
38
  end
34
39
 
40
+ # Insert new value in SearchTree at the depth of the moves
41
+ #
42
+ # @param [Array] moves Path to the place to insert the value
43
+ # @param [Opening] value Value to be inserted in the tree
35
44
  def insert(moves, value)
36
- moves = moves_in_symbols(moves)
45
+ moves = ChessOpeningsHelper.moves_as_symbols(moves)
37
46
  insert_helper(moves, value, @root)
38
47
  end
39
48
 
49
+ # Search in the tree with the path moves
50
+ #
51
+ # @param [Array] moves Array with Strings or symbols that represent the path
52
+ # @return [Opening] Opening that was found in the tree, Nil otherwise
40
53
  def search(moves)
41
- moves = moves_in_symbols(moves)
54
+ moves = ChessOpeningsHelper.moves_as_symbols(moves)
42
55
  search_helper(moves, @root)
43
56
  end
44
57
 
58
+ # Search the tree for all the values from the path and values of its children
59
+ #
60
+ # @param [Array] moves Array with Strings or symbols that represent the path
61
+ # @return [Array] Array with values found
45
62
  def search_all_with_moves(moves)
46
- moves = moves_in_symbols(moves)
63
+ moves = ChessOpeningsHelper.moves_as_symbols(moves)
47
64
  node = find_node(moves, @root)
48
65
  get_all_from_node(node).flatten
49
66
  end
50
67
 
68
+ # Get all values at a certain depth
69
+ #
70
+ # @param [int] num Depth to be used to find values
71
+ # @return [Array] Array with all the values found at depth num
72
+ def get_moves_in_depth(num)
73
+ get_moves_in_depth_helper(num, @root, 0).flatten
74
+ end
51
75
 
52
76
  private
53
77
 
54
- def moves_in_symbols(moves)
55
- moves.map! { |move| move.is_a?(String)? move.to_sym : move }
78
+ def get_moves_in_depth_helper(num_moves, curr_node, depth)
79
+ return [] if depth == num_moves && curr_node.value.nil?
80
+ return [curr_node.value] if depth == num_moves
81
+ result = []
82
+ curr_node.nodes.values.each do |node|
83
+ result << get_moves_in_depth_helper(num_moves, node, depth + 1)
56
84
  end
85
+ result
86
+ end
57
87
 
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
88
+ def find_node(moves, curr_node)
89
+ return curr_node if moves.empty?
68
90
 
69
- def get_all_from_node(curr_node)
91
+ curr_hash = curr_node.nodes
92
+ move = moves.first
93
+ return nil if curr_hash[move].nil?
70
94
 
71
- result = curr_node.value.nil? ? [] : [curr_node.value]
72
- return result if curr_node.is_leaf?
95
+ next_node = curr_hash[move]
96
+ find_node(moves.drop(1), next_node)
97
+ end
73
98
 
74
- curr_hash = curr_node.nodes
99
+ def get_all_from_node(curr_node)
100
+ result = curr_node.value.nil? ? [] : [curr_node.value]
101
+ return result if curr_node.leaf?
75
102
 
76
- curr_hash.each do |key, value|
77
- next_node = value
78
- result << get_all_from_node(next_node)
79
- end
80
-
81
- result
103
+ curr_hash = curr_node.nodes
104
+ curr_hash.values.each do |next_node|
105
+ result << get_all_from_node(next_node)
82
106
  end
83
107
 
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
108
+ result
109
+ end
100
110
 
101
- next_node = curr_hash[move]
102
- insert_helper(moves.drop(1), value, next_node)
111
+ def insert_helper(moves, value, curr_node)
112
+ return if moves.empty?
113
+ curr_hash = curr_node.nodes
114
+ move = moves.first
115
+ last_move = moves.size == 1
116
+ if curr_hash[move].nil?
117
+ curr_hash[move] = last_move ? Node.new(value) : Node.new(nil)
118
+ elsif last_move && curr_hash[move].value.nil?
119
+ curr_hash[move].value = value
103
120
  end
121
+ insert_helper(moves.drop(1), value, curr_hash[move])
122
+ end
104
123
 
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
124
+ def search_helper(moves, curr_node)
125
+ move = moves.first
126
+ curr_hash = curr_node.nodes
127
+ return nil if curr_hash[move].nil?
128
+ next_node = curr_hash[move]
129
+ search_helper(moves.drop(1), next_node) || curr_hash[move].value
130
+ end
114
131
 
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
132
+ def size_helper(node)
133
+ sum = node.value.nil? ? 0 : 1
134
+ return sum if node.leaf?
135
+ node.keys.each do |key|
136
+ sum += size_helper(node.nodes[key])
122
137
  end
138
+ sum
139
+ end
123
140
 
141
+ # Class that represents a node of a tree data structure
124
142
  class Node
125
-
126
143
  attr_accessor :value, :nodes
127
144
 
128
145
  def initialize(value)
@@ -130,7 +147,11 @@ class SearchTree
130
147
  @nodes = {}
131
148
  end
132
149
 
133
- def is_leaf?
150
+ def empty?
151
+ @value.nil?
152
+ end
153
+
154
+ def leaf?
134
155
  @nodes.empty?
135
156
  end
136
157
 
@@ -147,17 +168,12 @@ class SearchTree
147
168
  end
148
169
 
149
170
  def ==(other)
150
- return false if self.size != other.size || @value != other.value
171
+ return false if size != other.size || @value != other.value
151
172
  @nodes.keys.each do |key|
152
173
  return false unless other.keys.include?(key)
153
- end
154
-
155
- @nodes.keys.each do |key|
156
174
  return false if @nodes[key] != other.nodes[key]
157
175
  end
158
-
159
176
  true
160
177
  end
161
178
  end
162
-
163
- end
179
+ end
@@ -4,74 +4,105 @@ require 'json'
4
4
  require 'chess_openings/opening.rb'
5
5
  require 'chess_openings/search_tree.rb'
6
6
 
7
+ # Class responsible for searching for chess Openings
7
8
  class ChessOpenings
8
-
9
9
  attr_accessor :tree, :list
10
10
 
11
11
  def initialize
12
12
  @tree = SearchTree.new
13
13
  @list = []
14
-
15
- file = "/chess_openings/json_openings/openings.json"
14
+ file = '/chess_openings/json_openings/openings.json'
16
15
  openings_file = File.join(File.dirname(__FILE__), file)
17
-
18
- openings = JSON.load(File.open(openings_file))["openings"]
16
+ openings = JSON.load(File.open(openings_file))['openings']
19
17
  openings.each do |op|
20
- opening = Opening.new(op["name"], op["eco_code"], op["moves"])
18
+ opening = Opening.new(op['name'], op['eco_code'], op['moves'])
21
19
  @list << opening
22
20
  @tree.insert opening.moves, opening
23
21
  end
24
-
25
22
  end
26
23
 
27
- def get_all
24
+ # Get all openings available
25
+ #
26
+ # @return [Array] Array with all the Openings possible
27
+ def all
28
28
  @list.dup
29
29
  end
30
30
 
31
+ # Get Opening from moves (as symbols or strings)
32
+ #
33
+ # @param [Array] moves Array with strings or symbols
34
+ # @return [Opening] Opening that has exactly the moves in the argument
31
35
  def from_moves(moves)
32
- # moves.map! { |move| move.to_s if move.is_a? Symbol }
33
36
  @tree.search moves
34
37
  end
35
38
 
39
+ # Get Opening from a PGN file
40
+ #
41
+ # @param [String] file_name String with the path to the PGN file
42
+ # @return [Opening] Opening that was used in the PGN game
43
+ # @raise [InvalidPGNError] Path points to invalid PGN file
44
+ # @raise [LoadError] Path points to non existent file
36
45
  def from_pgn(file_name)
37
- raise LoadError, "File does not exist" unless File.exist?(file_name)
46
+ raise LoadError, 'File does not exist' unless File.exist?(file_name)
38
47
  begin
39
48
  game = PGN.parse(File.read(file_name)).first
40
- @tree.search game.moves
49
+ @tree.search game.moves.map(&:notation)
41
50
  rescue
42
- raise InvalidPGNError, "Invalid PGN file"
51
+ raise InvalidPGNError, 'Invalid PGN file'
43
52
  end
44
53
  end
45
54
 
55
+ # Search Openings by name
56
+ #
57
+ # @param [String] name String with the name to search for
58
+ # @return [Array] Array with all the Openings found
46
59
  def with_name(name)
47
60
  @list.select { |op| op.name.downcase.include?(name.downcase) }
48
61
  end
49
62
 
63
+ # Search all Openings that start with the moves in the argument
64
+ #
65
+ # @param [Array] moves Moves to be searched for
66
+ # @return [Array] Array with all Openings that start with the moves provided
50
67
  def that_start_with(moves)
51
68
  @tree.search_all_with_moves moves
52
69
  end
53
70
 
71
+ # Get Opening from a PGN formatted String
72
+ #
73
+ # @param [String] string String in PGN format
74
+ # @return [Opening] Opening that was used in PGN formatted String
54
75
  def from_string(string)
55
76
  array = string.split
56
77
  moves = []
57
78
  array.each do |move|
58
- if move.include?(".")
59
- next if move.end_with?(".")
60
- move = move.split(".").last
79
+ if move.include?('.')
80
+ next if move.end_with?('.')
81
+ move = move.split('.').last
61
82
  end
62
83
  moves << move
63
84
  end
64
85
  @tree.search moves
65
86
  end
66
87
 
67
- private
88
+ # Get Opening from FEN string
89
+ #
90
+ # @param [String] fen_string FEN formatted String
91
+ # @return [Opening] Opening that was used in the game of the FEN String
92
+ def from_fen(fen_string)
93
+ fen = PGN::FEN.new(fen_string)
94
+ move_number = (fen.fullmove.to_i - 1) * 2
95
+ final_list = @tree.get_moves_in_depth(move_number)
96
+ final_list.select { |op| op.to_fen == fen_string }.first
97
+ end
68
98
 
69
- def to_s
70
- @tree.to_s
71
- end
99
+ private
72
100
 
101
+ def to_s
102
+ @tree.to_s
103
+ end
73
104
  end
74
105
 
106
+ # Exception raised when there is a error reading a PGN file
75
107
  class InvalidPGNError < RuntimeError
76
-
77
108
  end