Class: PGN::Node
- Inherits:
-
Object
- Object
- PGN::Node
- Defined in:
- lib/pgn/node.rb
Overview
Node is a live, lazily-built view over the MoveText tree.
A node represents the position reached by playing move from its
+parent+'s position; the root has no move and is the game's starting
position. The underlying MoveText structure is the source of
truth (the parser builds it and the serializer reads it), so a node
tree never disagrees with the serialized output.
Mutations edit the underlying MoveText arrays in place and, for
sibling reorders, normalize the affected branching point to flat
sibling storage. After any structural mutation, Game#root
returns a fresh tree — outstanding node references are stale.
Instance Attribute Summary collapse
-
#index ⇒ Object
readonly
Returns the value of attribute index.
-
#line ⇒ Object
readonly
Returns the value of attribute line.
-
#move ⇒ Object
readonly
Returns the value of attribute move.
-
#parent ⇒ Object
readonly
Returns the value of attribute parent.
Instance Method Summary collapse
- #[](idx) ⇒ Object
-
#add_main_variation(move_or_moves) ⇒ Object
Make the given moves the new mainline continuation from this node's position.
-
#add_variation(move_or_moves) ⇒ Object
Append a new variation line (a single SAN String or an Array
) branching before this node's next mainline move. - #annotation ⇒ Object
-
#children ⇒ Object
All moves playable from this node's position: the continuation move (the next MoveText in this node's line) plus every variation first-move branching at that same position, recursively through nested brackets.
- #comment ⇒ Object
-
#delete ⇒ Object
Remove this node and its subtree.
-
#demote ⇒ Object
Move this node one slot toward the end among its siblings.
-
#demote_to_last ⇒ Object
Move this node to the last position among its siblings.
-
#initialize(move:, parent:, line:, index:, starting_position: nil, game: nil) ⇒ Node
constructor
A new instance of Node.
-
#main_line ⇒ Object
Yields each mainline node from
self.nextonward (the root is excluded), somain_line.map(&:notation) == game.moves.map(&:notation)andmain_line.map(&:position) == game.positions[1..]. - #notation ⇒ Object
-
#position ⇒ Object
The position reached at this node: the starting position for the root, otherwise
parent.positionwithmove.notationapplied. -
#previous ⇒ Object
The parent node, or nil for the root.
-
#promote ⇒ Object
Move this node one slot toward the mainline among its siblings.
-
#promote_to_main ⇒ Object
Make this node the mainline at its branching point (the old mainline becomes variation #1).
- #root? ⇒ Boolean
-
#variations ⇒ Object
The non-mainline alternatives at this node's position.
Constructor Details
#initialize(move:, parent:, line:, index:, starting_position: nil, game: nil) ⇒ Node
Returns a new instance of Node.
30 31 32 33 34 35 36 37 |
# File 'lib/pgn/node.rb', line 30 def initialize(move:, parent:, line:, index:, starting_position: nil, game: nil) @move = move @parent = parent @line = line @index = index @starting_position = starting_position @game = game end |
Instance Attribute Details
#index ⇒ Object (readonly)
Returns the value of attribute index.
17 18 19 |
# File 'lib/pgn/node.rb', line 17 def index @index end |
#line ⇒ Object (readonly)
Returns the value of attribute line.
17 18 19 |
# File 'lib/pgn/node.rb', line 17 def line @line end |
#move ⇒ Object (readonly)
Returns the value of attribute move.
17 18 19 |
# File 'lib/pgn/node.rb', line 17 def move @move end |
#parent ⇒ Object (readonly)
Returns the value of attribute parent.
17 18 19 |
# File 'lib/pgn/node.rb', line 17 def parent @parent end |
Instance Method Details
#[](idx) ⇒ Object
86 87 88 |
# File 'lib/pgn/node.rb', line 86 def [](idx) children[idx] end |
#add_main_variation(move_or_moves) ⇒ Object
Make the given moves the new mainline continuation from this node's
position. At a terminal node this extends the line; otherwise the old
continuation becomes a variation of the new first move. Returns a
fresh game.root.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/pgn/node.rb', line 136 def add_main_variation(move_or_moves) new_line = build_movetexts(move_or_moves) cont = continuation_movetext if cont.nil? @line.push(*new_line) else normalize_branch_point(cont) vars = cont.variations pos = @index + 1 old_tail = @line[(pos + 1)..] || [] n0 = new_line[0] @line[pos] = n0 @line[(pos + 1)..] = (new_line[1..] || []) cont.variations = [] n0.variations = [[cont, *old_tail], *vars] end @game.root end |
#add_variation(move_or_moves) ⇒ Object
Append a new variation line (a single SAN String or an Arraygame.root. Raises ArgumentError at a terminal node (a variation
must branch before an existing move; use add_main_variation to
extend the line).
123 124 125 126 127 128 129 130 |
# File 'lib/pgn/node.rb', line 123 def add_variation(move_or_moves) cont = continuation_movetext raise ArgumentError, 'cannot add a variation at a terminal node' if cont.nil? normalize_branch_point(cont) cont.variations << build_movetexts(move_or_moves) @game.root end |
#annotation ⇒ Object
47 48 49 |
# File 'lib/pgn/node.rb', line 47 def annotation @move&.annotation end |
#children ⇒ Object
All moves playable from this node's position: the continuation move (the next MoveText in this node's line) plus every variation first-move branching at that same position, recursively through nested brackets. The first child is the mainline continuation; the rest are variations in source order.
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/pgn/node.rb', line 60 def children @children ||= begin cont = continuation_movetext list = [] collect_first_moves(cont, @line, @index + 1) do |mt, l, idx| list << Node.new(move: mt, parent: self, line: l, index: idx, game: @game) end list end end |
#comment ⇒ Object
51 52 53 |
# File 'lib/pgn/node.rb', line 51 def comment @move&.comment end |
#delete ⇒ Object
Remove this node and its subtree. If it is the mainline continuation,
the first remaining variation (if any) takes its place; otherwise the
line is truncated at this point. Returns a fresh game.root.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/pgn/node.rb', line 239 def delete return @game.root if root? i = sibling_index return @game.root if i.nil? cont = parent_continuation normalize_branch_point(cont) vars = cont.variations if i.zero? delete_mainline_continuation(cont, vars) else vars.delete_at(i - 1) end @game.root end |
#demote ⇒ Object
Move this node one slot toward the end among its siblings. At index 0
(the mainline) this is the inverse swap: variation #1 becomes the new
mainline and the old mainline becomes variation #1. No-op at the last
index. Returns a fresh game.root.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/pgn/node.rb', line 176 def demote return @game.root if root? i = sibling_index sibs = @parent.children return @game.root if i.nil? || i == sibs.size - 1 cont = parent_continuation normalize_branch_point(cont) vars = cont.variations if i.zero? v1 = vars.shift make_mainline(cont, v1, vars, old_main_position: :first) else vars[i - 1], vars[i] = vars[i], vars[i - 1] end @game.root end |
#demote_to_last ⇒ Object
Move this node to the last position among its siblings. At index 0
the last variation becomes the new mainline and the old mainline
becomes the last variation. Returns a fresh game.root.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/pgn/node.rb', line 215 def demote_to_last return @game.root if root? i = sibling_index return @game.root if i.nil? cont = parent_continuation normalize_branch_point(cont) vars = cont.variations if i.zero? return @game.root if vars.empty? last = vars.pop make_mainline(cont, last, vars, old_main_position: :last) else el = vars.delete_at(i - 1) vars.push(el) end @game.root end |
#main_line ⇒ Object
Yields each mainline node from self.next onward (the root is
excluded), so main_line.map(&:notation) == game.moves.map(&:notation)
and main_line.map(&:position) == game.positions[1..]. Returns an
Enumerator when called without a block.
94 95 96 97 98 99 100 101 102 |
# File 'lib/pgn/node.rb', line 94 def main_line return enum_for(:main_line) unless block_given? node = self.next while node yield node node = node.next end end |
#notation ⇒ Object
43 44 45 |
# File 'lib/pgn/node.rb', line 43 def notation @move&.notation end |
#position ⇒ Object
The position reached at this node: the starting position for the
root, otherwise parent.position with move.notation applied. Pure
Ruby (no native engine); raises on an illegal SAN exactly like
Game#positions. Cached on the node.
108 109 110 111 112 113 114 115 116 |
# File 'lib/pgn/node.rb', line 108 def position return @position if defined?(@position) @position = if root? @starting_position else @parent.position.then { |p| p.move(@move.notation) } end end |
#previous ⇒ Object
The parent node, or nil for the root.
82 83 84 |
# File 'lib/pgn/node.rb', line 82 def previous @parent end |
#promote ⇒ Object
Move this node one slot toward the mainline among its siblings.
No-op for the mainline (index 0) or the first variation (index 1).
Returns a fresh game.root.
159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/pgn/node.rb', line 159 def promote return @game.root if root? i = sibling_index return @game.root if i.nil? || i <= 1 cont = parent_continuation normalize_branch_point(cont) vars = cont.variations vars[i - 1], vars[i - 2] = vars[i - 2], vars[i - 1] @game.root end |
#promote_to_main ⇒ Object
Make this node the mainline at its branching point (the old mainline
becomes variation #1). No-op if already the mainline. Returns a
fresh game.root.
198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/pgn/node.rb', line 198 def promote_to_main return @game.root if root? i = sibling_index return @game.root if i.nil? || i.zero? cont = parent_continuation normalize_branch_point(cont) vars = cont.variations vk = vars.delete_at(i - 1) make_mainline(cont, vk, vars, old_main_position: :first) @game.root end |
#root? ⇒ Boolean
39 40 41 |
# File 'lib/pgn/node.rb', line 39 def root? @move.nil? end |
#variations ⇒ Object
The non-mainline alternatives at this node's position.
72 73 74 |
# File 'lib/pgn/node.rb', line 72 def variations children[1..] || [] end |