bugfix: safety against breaking the AI if you press undo while it's thinking
[kaya.git] / lib / descriptor.rb
blob945f32bdc105a2a6250b676a931d357fd63e2cf6
1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 class Descriptor
9   attr_reader :name, :opts, :children
10   
11   def initialize(name, opts = { })
12     @name = name
13     @opts = opts
14     @children = []
15   end
16   
17   def add_child(desc)
18     @children << desc
19   end
20   
21   def merge_child(desc)
22     mp = @opts[:merge_points].first if @opts[:merge_points]
23     if mp
24       @children.insert(mp.position, desc)
25       @opts[:merge_points].step!
26     else
27       add_child(desc)
28     end
29   end
30   
31   def first_valid_merge_point
32     if @opts[:merge_points]
33       @opts[:merge_points].first
34     end
35   end
36   
37   def to_sexp
38     "(#{@name} #{@opts.inspect}#{@children.map{|c| ' ' + c.to_sexp}.join})"
39   end
40   
41   def merge!(other, prefix = "")
42     if name == other.name and
43         opts[:name] == other.opts[:name]
44       other.children.each do |child2|
45         merged = false
46         children.each do |child|
47           if child.merge!(child2, prefix + "    ")
48             merged = true
49             break
50           end
51         end
52         merge_child(child2.dup) unless merged
53       end
54       true
55     elsif name == :group and other.opts[:group] == opts[:name]
56       merge_child(other)
57     else
58       false
59     end
60   end
61   
62   class MergePoint
63     attr_accessor :position, :count
64     
65     class List
66       def initialize
67         @mps = []
68       end
69       
70       def first
71         @mps.first.dup
72       end
73       
74       def add(mp)
75         @mps << mp
76       end
77       
78       def step!
79         raise "Stepping invalid merge point list" if @mps.empty?
80         @mps.each do |mp|
81           mp.position += 1
82         end
83         @mps.first.count -= 1
84         clean!
85       end
86       
87       private
88       
89       def clean!
90         @mps.delete_if {|mp| not mp.valid? }
91       end
92     end
93     
94     def initialize(position, count = -1)
95       @position = position
96       @count = count
97       raise "Creating invalid merge point" if @count == 0
98     end
99     
100     def valid?
101       @count != 0
102     end
103   end
104   
105   class Builder
106     attr_reader :__desc__
107     private :__desc__
108     
109     def initialize(desc)
110       @__desc__ = desc
111     end
112     
113     def method_missing(name, *args, &blk)
114       opts = if args.empty?
115         { }
116       elsif args.size == 1
117         if args.first.is_a? Hash
118           args.first
119         else
120           { :name => args.first }
121         end
122       else
123         args[-1].merge(:name => args.first)
124       end
125       child = Descriptor.new(name, opts)
126       blk[self.class.new(child)] if block_given?
127       __desc__.add_child(child)
128     end
129     
130     def merge_point(count = -1)
131       mp = MergePoint.new(@__desc__.children.size, count)
132       @__desc__.opts[:merge_points] ||= MergePoint::List.new
133       @__desc__.opts[:merge_points].add(mp)
134     end
135   end