l_system 0.1.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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.simplecov +9 -0
- data/History.md +8 -0
- data/README.md +232 -0
- data/Rakefile +21 -0
- data/examples/fern_gn7.png +0 -0
- data/lib/l_system/production_adapter.rb +147 -0
- data/lib/l_system/rules_engine.rb +185 -0
- data/lib/l_system.rb +43 -0
- data/spec/l_system/production_adapter_spec.rb +169 -0
- data/spec/l_system/rules_engine_spec.rb +99 -0
- data/spec/l_system_spec.rb +61 -0
- data/spec/spec_helper.rb +30 -0
- data.tar.gz.sig +0 -0
- metadata +128 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 586b85b2e6a2981b1d5835eedaf62c451eaa51b4cdd7894db564e100ada8c9e0
|
4
|
+
data.tar.gz: efe77e8d01d01759ee5780d9d28bc77c8f0fb355d5f98956ef1304797ded02bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44879d5a4397c4ab0899685fbd33d0b7df1d64c73596cc45cbc39801ae5bc1ec9343435b75bc0170c0cd3d6c6d54dc1b191f5622d2ce26695a9b9187d31438cc
|
7
|
+
data.tar.gz: b8778fe072f57c36eb92a547b38fc594cb2b1619f7d160395a5700c7c2bfc00cf853df9f9b40fe642d60fcdb37adeff9da3607e74793798181bd6697b75a2e79
|
checksums.yaml.gz.sig
ADDED
data/.simplecov
ADDED
data/History.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
# LSystem
|
2
|
+
|
3
|
+
home
|
4
|
+
: https://hg.sr.ht/~ged/LSystem
|
5
|
+
|
6
|
+
code
|
7
|
+
: https://hg.sr.ht/~ged/LSystem
|
8
|
+
|
9
|
+
github
|
10
|
+
: https://github.com/ged/l_system
|
11
|
+
|
12
|
+
docs
|
13
|
+
: https://deveiate.org/code/l_system
|
14
|
+
|
15
|
+
|
16
|
+
## Description
|
17
|
+
|
18
|
+
A toolkit for creating and using [Lindenmayer Systems][l-system] (L-systems).
|
19
|
+
It consists of a class that allows for declaration of the L-system's grammar,
|
20
|
+
and another class that allows for the definition of how the symbols output by a
|
21
|
+
grammar should be translated into work.
|
22
|
+
|
23
|
+
|
24
|
+
### Examples
|
25
|
+
|
26
|
+
Most of these were stolen from [the examples on Wikipedia][l-system], and can be found in the `examples/` directory of the source.
|
27
|
+
|
28
|
+
#### Algae
|
29
|
+
|
30
|
+
Lindenmayer's original L-system for modelling the growth of algae.
|
31
|
+
|
32
|
+
* variables : A B
|
33
|
+
* constants : none
|
34
|
+
* axiom : A
|
35
|
+
* rules : (A → AB), (B → A)
|
36
|
+
|
37
|
+
Declare the rules:
|
38
|
+
|
39
|
+
algae = LSystem.declare do
|
40
|
+
|
41
|
+
variables :A, :B
|
42
|
+
axiom 'A'
|
43
|
+
rules 'A -> AB',
|
44
|
+
'B -> A'
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
then iterate it 8 times and print out each generation:
|
49
|
+
|
50
|
+
iter = algae.each
|
51
|
+
8.times do |i|
|
52
|
+
puts "n = %d : %s" % [ i, iter.next ]
|
53
|
+
end
|
54
|
+
|
55
|
+
This outputs:
|
56
|
+
|
57
|
+
n = 0 : A
|
58
|
+
n = 1 : AB
|
59
|
+
n = 2 : ABA
|
60
|
+
n = 3 : ABAAB
|
61
|
+
n = 4 : ABAABABA
|
62
|
+
n = 5 : ABAABABAABAAB
|
63
|
+
n = 6 : ABAABABAABAABABAABABA
|
64
|
+
n = 7 : ABAABABAABAABABAABABAABAABABAABAAB
|
65
|
+
|
66
|
+
|
67
|
+
You can also do more complex tasks with the symbols in each generation. For example, to generate pretty pictures.
|
68
|
+
|
69
|
+
#### Barnsley fern
|
70
|
+
|
71
|
+
* variables : X F
|
72
|
+
* constants : + − [ ]
|
73
|
+
* axiom : X
|
74
|
+
* rules : (X → F+[[X]-X]-F[-FX]+X), (F → FF)
|
75
|
+
* angle : 25°
|
76
|
+
|
77
|
+
Declare the rules:
|
78
|
+
|
79
|
+
fern = LSystem.declare do
|
80
|
+
|
81
|
+
variables 'X', 'F'
|
82
|
+
constants '+', '-', '[', ']'
|
83
|
+
axiom 'X'
|
84
|
+
rules \
|
85
|
+
'X → F+[[X]-X]-F[-FX]+X',
|
86
|
+
'F → FF'
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
Then hook them up to a Logo interpreter ([a monkeypatched version][tortoise-patch] of the [Tortoise gem][tortoise-gem]):
|
91
|
+
|
92
|
+
LSystem.run( fern, 8 ) do
|
93
|
+
|
94
|
+
# The size of the canvas to draw on
|
95
|
+
CANVAS_SIZE = 2000
|
96
|
+
|
97
|
+
# F: draw forward
|
98
|
+
# X : no-op
|
99
|
+
# -: turn left 25°
|
100
|
+
# +: turn right 25°
|
101
|
+
# [: push position and angle
|
102
|
+
# ]: pop position and angle
|
103
|
+
production_map \
|
104
|
+
'F' => :forward,
|
105
|
+
'-' => :turn_left,
|
106
|
+
'+' => :turn_right,
|
107
|
+
'[' => :save_pos_and_angle,
|
108
|
+
']' => :restore_pos_and_angle
|
109
|
+
|
110
|
+
### Set up some instance variables
|
111
|
+
def initialize( * )
|
112
|
+
super
|
113
|
+
@turtle = nil
|
114
|
+
@positions = []
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
on_start do |i, _|
|
119
|
+
@turtle = Tortoise::Interpreter.new( CANVAS_SIZE )
|
120
|
+
@turtle.setpos( CANVAS_SIZE / 2, 0 )
|
121
|
+
@turtle.direction = 90
|
122
|
+
@turtle.pd
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
on_finish do |i, _|
|
127
|
+
File.open( "fern_gn#{i}.png", File::WRONLY|File::TRUNC|File::CREAT, 0644, encoding: 'binary' ) do |fh|
|
128
|
+
fh.write( @turtle.to_png )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
### Draw a line forward
|
134
|
+
def forward
|
135
|
+
@turtle.fd( 5 )
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
### Turn 25° to the left
|
140
|
+
def turn_left
|
141
|
+
@turtle.lt( 25 )
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Turn 25° to the right
|
146
|
+
def turn_right
|
147
|
+
@turtle.rt( 25 )
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
### Save the drawing position and angle
|
152
|
+
def save_pos_and_angle
|
153
|
+
@positions.push([ @turtle.position, @turtle.direction ])
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
### Restore the next saved position and angle
|
158
|
+
def restore_pos_and_angle
|
159
|
+
to_restore = @positions.pop or raise IndexError, "Position stack underflow"
|
160
|
+
|
161
|
+
@turtle.setpos( *to_restore.first )
|
162
|
+
@turtle.direction = to_restore.last
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
This generates a sequence of images, which for generation 8 yields:
|
168
|
+
|
169
|
+

|
170
|
+
|
171
|
+
|
172
|
+
## Prerequisites
|
173
|
+
|
174
|
+
* Ruby
|
175
|
+
|
176
|
+
|
177
|
+
## Installation
|
178
|
+
|
179
|
+
$ gem install l_system
|
180
|
+
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
You can check out the current development source with Mercurial via its
|
185
|
+
[project page](https://hg.sr.ht/~ged/LSystem). Or if you prefer Git, via
|
186
|
+
[its Github mirror](https://github.com/ged/l_system).
|
187
|
+
|
188
|
+
After checking out the source, run:
|
189
|
+
|
190
|
+
$ rake setup
|
191
|
+
|
192
|
+
This task will install dependencies, and do any other necessary setup for development.
|
193
|
+
|
194
|
+
|
195
|
+
## Authors
|
196
|
+
|
197
|
+
- Michael Granger <ged@faeriemud.org>
|
198
|
+
|
199
|
+
|
200
|
+
## License
|
201
|
+
|
202
|
+
Copyright (c) 2020, Michael Granger
|
203
|
+
All rights reserved.
|
204
|
+
|
205
|
+
Redistribution and use in source and binary forms, with or without
|
206
|
+
modification, are permitted provided that the following conditions are met:
|
207
|
+
|
208
|
+
* Redistributions of source code must retain the above copyright notice,
|
209
|
+
this list of conditions and the following disclaimer.
|
210
|
+
|
211
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
212
|
+
this list of conditions and the following disclaimer in the documentation
|
213
|
+
and/or other materials provided with the distribution.
|
214
|
+
|
215
|
+
* Neither the name of the author/s, nor the names of the project's
|
216
|
+
contributors may be used to endorse or promote products derived from this
|
217
|
+
software without specific prior written permission.
|
218
|
+
|
219
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
220
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
221
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
222
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
223
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
224
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
225
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
226
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
227
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
228
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
229
|
+
|
230
|
+
|
231
|
+
[l-system]: https://en.wikipedia.org/wiki/L-system
|
232
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby -S rake
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/deveiate'
|
5
|
+
|
6
|
+
EXAMPLE_IMAGES = Rake::FileList[ 'examples/*.png' ]
|
7
|
+
|
8
|
+
|
9
|
+
Rake::DevEiate.setup( 'l_system' ) do |project|
|
10
|
+
project.publish_to = 'deveiate:/usr/local/www/public/code'
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
file EXAMPLE_IMAGES
|
15
|
+
|
16
|
+
task :docs => EXAMPLE_IMAGES do
|
17
|
+
mkdir_p 'docs/examples/'
|
18
|
+
cp EXAMPLE_IMAGES.to_a, 'docs/examples/'
|
19
|
+
end
|
20
|
+
|
21
|
+
|
Binary file
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
|
6
|
+
require 'l_system' unless defined?( LSystem )
|
7
|
+
|
8
|
+
|
9
|
+
# An adapter that connects method calls to an LSystem::RulesEngine's
|
10
|
+
# generations.
|
11
|
+
class LSystem::ProductionAdapter
|
12
|
+
extend Loggability
|
13
|
+
|
14
|
+
|
15
|
+
# Loggability API -- log to the l_system logger
|
16
|
+
log_to :l_system
|
17
|
+
|
18
|
+
|
19
|
+
### Inheritance callback -- add class-instance variables to the +subclass+.
|
20
|
+
def self::inherited( subclass )
|
21
|
+
super
|
22
|
+
|
23
|
+
subclass.instance_variable_set( :@production_map, {} )
|
24
|
+
subclass.instance_variable_set( :@callbacks, {} )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
#
|
29
|
+
# DSL Methods
|
30
|
+
#
|
31
|
+
|
32
|
+
### Declare a Hash of symbols to methods that should be called when one appears
|
33
|
+
### in a generation.
|
34
|
+
def self::production_map( productions={} )
|
35
|
+
unless productions.empty?
|
36
|
+
self.production_map = productions
|
37
|
+
end
|
38
|
+
|
39
|
+
return @production_map
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
### Set the Hash of symbols to methods that should be called when one appears in
|
44
|
+
### a generation.
|
45
|
+
def self::production_map=( new_map )
|
46
|
+
@production_map.replace( new_map )
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
### Register a callback that will be called at the start of each new generation.
|
51
|
+
### It will be called with the result of the last generation, or +nil+ if this
|
52
|
+
### is the 0th (axiom) generation.
|
53
|
+
def self::on_start( &block )
|
54
|
+
self.log.debug "Declaring on_start callback: %p" % [ block ]
|
55
|
+
define_method( :on_start, &block )
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
### Register a callback that will be called to obtain the result of running a
|
60
|
+
### generation. The result of calling the +block+ will be passed to this
|
61
|
+
### generation's #on_finish callback (if there is one), the next generation's
|
62
|
+
### #on_start callback (if there is one and there's successive geneation), and
|
63
|
+
### returned from #run if this was the last generation.
|
64
|
+
def self::result( &block )
|
65
|
+
self.log.debug "Declaring result callback: %p" % [ block ]
|
66
|
+
define_method( :result, &block )
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
### Register a callback that will be called at the end of each new generation.
|
71
|
+
### It will be called with the result of the this generation, which will be
|
72
|
+
### +nil+ if no #result callback is declared.
|
73
|
+
def self::on_finish( &block )
|
74
|
+
self.log.debug "Declaring on_finish callback: %p" % [ block ]
|
75
|
+
define_method( :on_finish, &block )
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
#
|
80
|
+
# Instance methods
|
81
|
+
#
|
82
|
+
|
83
|
+
### Create a new instance of the ProductionAdapter.
|
84
|
+
def initialize
|
85
|
+
@production_map = self.class.production_map
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
######
|
90
|
+
public
|
91
|
+
######
|
92
|
+
|
93
|
+
##
|
94
|
+
# The map of symbols to production methods
|
95
|
+
attr_reader :production_map
|
96
|
+
|
97
|
+
|
98
|
+
### Run productions for each generation produced by the given +rules_engine+ up to
|
99
|
+
### +iterations+ times.
|
100
|
+
def run( rules_engine, iterations )
|
101
|
+
self.log.debug "Running %p for up to %d iterations" % [ rules_engine, iterations ]
|
102
|
+
|
103
|
+
return rules_engine.each.with_index.inject( nil ) do |result, (generation, i)|
|
104
|
+
self.log.debug "Running generation %d" % [ i ]
|
105
|
+
self.on_start( i, result ) if self.respond_to?( :on_start )
|
106
|
+
self.run_generation( generation )
|
107
|
+
result = self.result( i ) if self.respond_to?( :result )
|
108
|
+
self.log.debug "Result [%d] is: %p" % [ i, result ]
|
109
|
+
self.on_finish( i, result ) if self.respond_to?( :on_finish )
|
110
|
+
|
111
|
+
break result if i >= iterations - 1
|
112
|
+
result
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
### Run the specified +generation+ by calling productions for each of its
|
118
|
+
### symbols.
|
119
|
+
def run_generation( generation )
|
120
|
+
|
121
|
+
# Make a new one every time to support self-mutating adapters
|
122
|
+
dispatch_table = self.make_dispatch_table
|
123
|
+
|
124
|
+
generation.each_char do |symbol|
|
125
|
+
callback = dispatch_table[ symbol ]
|
126
|
+
|
127
|
+
unless callback
|
128
|
+
self.log.warn "No production for symbol %p" % [ symbol ]
|
129
|
+
next
|
130
|
+
end
|
131
|
+
|
132
|
+
self.log.debug "%p -> %p" % [ symbol, callback ]
|
133
|
+
callback.call
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
### Return a Hash of symbols to bound Methods to call for their productions from
|
139
|
+
### the current #production_map.
|
140
|
+
def make_dispatch_table
|
141
|
+
return self.class.production_map.each_with_object( {} ) do |(symbol, method_name), hash|
|
142
|
+
hash[ symbol ] = self.method( method_name )
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end # class LSystem::ProductionAdapter
|
147
|
+
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'set'
|
5
|
+
require 'loggability'
|
6
|
+
|
7
|
+
require 'l_system' unless defined?( LSystem )
|
8
|
+
|
9
|
+
|
10
|
+
# An engine for iterating over successive applications of the L-System's
|
11
|
+
# ruleset to its axiom.
|
12
|
+
class LSystem::RulesEngine
|
13
|
+
extend Loggability
|
14
|
+
|
15
|
+
|
16
|
+
# Loggability API -- log to the l_system logger
|
17
|
+
log_to :l_system
|
18
|
+
|
19
|
+
|
20
|
+
### Create a new rules engine for an L-System. If the +block+ is present,
|
21
|
+
### it is called with the new instance as +self+.
|
22
|
+
def initialize( &block )
|
23
|
+
@variables = Set.new
|
24
|
+
@constants = Set.new
|
25
|
+
@axiom = nil
|
26
|
+
@rules = []
|
27
|
+
|
28
|
+
@rules_as_hash = nil
|
29
|
+
|
30
|
+
self.instance_eval( &block ) if block
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
######
|
35
|
+
public
|
36
|
+
######
|
37
|
+
|
38
|
+
#
|
39
|
+
# DSL Methods
|
40
|
+
#
|
41
|
+
|
42
|
+
### Get/set the system's variables (the replaceable parts of its alphabet).
|
43
|
+
def variables( *new_values )
|
44
|
+
self.variables = new_values unless new_values.empty?
|
45
|
+
return @variables.dup
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
### Set the systems variables to +new_values+.
|
50
|
+
def variables=( new_values )
|
51
|
+
@rules_as_hash = nil
|
52
|
+
|
53
|
+
new_values = Set.new( new_values, &:to_s )
|
54
|
+
unless new_values.disjoint?( self.constants )
|
55
|
+
common_char = (new_values & self.constants).to_a.first
|
56
|
+
raise ArgumentError, "%p is already included in the constant set" % [ common_char ]
|
57
|
+
end
|
58
|
+
|
59
|
+
@variables = new_values
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
### Get/set the system's constants (the static parts of its alphabet).
|
64
|
+
def constants( *new_values )
|
65
|
+
self.constants = new_values unless new_values.empty?
|
66
|
+
return @constants.dup
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
### Set the systems constants to +new_values+.
|
71
|
+
def constants=( new_values )
|
72
|
+
@rules_as_hash = nil
|
73
|
+
|
74
|
+
new_values = Set.new( new_values, &:to_s )
|
75
|
+
unless new_values.disjoint?( self.variables )
|
76
|
+
common_char = (new_values & self.variables).to_a.first
|
77
|
+
raise ArgumentError, "%p is already included in the variable set" % [ common_char ]
|
78
|
+
end
|
79
|
+
|
80
|
+
@constants = new_values
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
### Return the system's variables and constants.
|
85
|
+
def alphabet
|
86
|
+
return @variables | @constants
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
### Get/set the axiom of the system.
|
91
|
+
def axiom( new_value=nil )
|
92
|
+
self.axiom = new_value if new_value
|
93
|
+
return @axiom
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
### Set the axiom of the system.
|
98
|
+
def axiom=( new_value )
|
99
|
+
@axiom = new_value
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
### Get/set the system's rules.
|
104
|
+
def rules( *new_values )
|
105
|
+
self.rules = new_values unless new_values.empty?
|
106
|
+
return @rules
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
### Set the system's rules.
|
111
|
+
def rules=( new_values )
|
112
|
+
@rules_as_hash = nil
|
113
|
+
@rules = Array( new_values ).map( &:to_s )
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
#
|
118
|
+
# Iteration API
|
119
|
+
#
|
120
|
+
|
121
|
+
### Apply the system's rules to the given +state+ and return the result.
|
122
|
+
def apply_rules( state )
|
123
|
+
rules_hash = self.rules_as_hash
|
124
|
+
return state.each_char.with_object( String.new(encoding: 'utf-8') ) do |char, new_state|
|
125
|
+
new_state << rules_hash[ char ]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
### Yield each successive generation to the given +block+, or return an
|
131
|
+
### Enumerator that will do so if no block is given.
|
132
|
+
def each( &block )
|
133
|
+
iter = Enumerator.new do |yielder|
|
134
|
+
state = new_state = self.axiom.dup
|
135
|
+
|
136
|
+
begin
|
137
|
+
yielder.yield( new_state )
|
138
|
+
state = new_state
|
139
|
+
new_state = self.apply_rules( state )
|
140
|
+
end until state == new_state
|
141
|
+
end
|
142
|
+
|
143
|
+
return iter unless block
|
144
|
+
return iter.each( &block )
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
#########
|
149
|
+
protected
|
150
|
+
#########
|
151
|
+
|
152
|
+
### Return a Hash of tranforms that should be applied to a state during a
|
153
|
+
### generation.
|
154
|
+
def rules_as_hash
|
155
|
+
unless @rules_as_hash
|
156
|
+
@rules_as_hash = self.rules.each_with_object( {} ) do |rule, hash|
|
157
|
+
pred, succ = self.parse_rule( rule )
|
158
|
+
hash[ pred ] = succ
|
159
|
+
end
|
160
|
+
|
161
|
+
self.alphabet.each do |char|
|
162
|
+
@rules_as_hash[ char ] = char unless @rules_as_hash.key?( char )
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
return @rules_as_hash
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
### Return a tuple of the predecessor and successor of the given +rule+.
|
171
|
+
def parse_rule( rule )
|
172
|
+
predecessor, successor = rule.strip.split( /\s*(?:->|→)\s*/, 2 )
|
173
|
+
self.log.debug "Parsed rule: %p -> %p" % [ predecessor, successor ]
|
174
|
+
successor_set = Set.new( successor.chars )
|
175
|
+
|
176
|
+
raise "Invalid rule: predecessor %p is not in the variable set %p" %
|
177
|
+
[ predecessor, self.variables ] unless self.variables.include?( predecessor )
|
178
|
+
raise "Invalid rule: successor %p contains characters not in the alphabet %p" %
|
179
|
+
[ successor, self.alphabet ] unless self.alphabet.superset?( successor_set )
|
180
|
+
|
181
|
+
return predecessor, successor
|
182
|
+
end
|
183
|
+
|
184
|
+
end # class LSystem::RulesEngine
|
185
|
+
|
data/lib/l_system.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
|
6
|
+
|
7
|
+
# A toolkit for creating and using Lindenmayer Systems.
|
8
|
+
module LSystem
|
9
|
+
extend Loggability
|
10
|
+
|
11
|
+
# Package version
|
12
|
+
VERSION = '0.1.0'
|
13
|
+
|
14
|
+
# Version control revision
|
15
|
+
REVISION = %q$Revision$
|
16
|
+
|
17
|
+
|
18
|
+
# Loggability API -- set up a logger for l_system
|
19
|
+
log_as :l_system
|
20
|
+
|
21
|
+
|
22
|
+
autoload :RulesEngine, 'l_system/rules_engine'
|
23
|
+
autoload :ProductionAdapter, 'l_system/production_adapter'
|
24
|
+
|
25
|
+
|
26
|
+
### Declare a new L-System that is configured via the given +block+.
|
27
|
+
def self::declare( &block )
|
28
|
+
return LSystem::RulesEngine.new( &block )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
### Run a +rules_engine+ at most the given number of +iterations+ using the productions
|
33
|
+
### declared in the +block+, which runs in the context of an anonymous
|
34
|
+
### LSystem::ProductionAdapter class.
|
35
|
+
def self::run( rules_engine, iterations=1000, &block )
|
36
|
+
raise LocalJumpError, "no block given" unless block
|
37
|
+
|
38
|
+
adapter = Class.new( LSystem::ProductionAdapter, &block )
|
39
|
+
return adapter.new.run( rules_engine, iterations )
|
40
|
+
end
|
41
|
+
|
42
|
+
end # module LSystem
|
43
|
+
|
@@ -0,0 +1,169 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'l_system/production_adapter'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( LSystem::ProductionAdapter ) do
|
9
|
+
|
10
|
+
let( :algae_rules ) do
|
11
|
+
LSystem.declare do
|
12
|
+
variables :A, :B
|
13
|
+
axiom 'A'
|
14
|
+
rules \
|
15
|
+
'A -> AB',
|
16
|
+
'B -> A'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
describe "subclasses" do
|
22
|
+
|
23
|
+
let( :subclass ) do
|
24
|
+
Class.new( described_class ) do
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@calls = []
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :calls
|
31
|
+
|
32
|
+
def method_missing( sym, *args )
|
33
|
+
self.calls << sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to_missing?( sym, include_all )
|
37
|
+
return super if [:on_start, :result, :on_finish].include?( sym )
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "can declare a method map for its productions" do
|
46
|
+
subclass.production_map \
|
47
|
+
'A' => :do_a_thing,
|
48
|
+
'B' => :do_b_thing
|
49
|
+
|
50
|
+
instance = subclass.new
|
51
|
+
instance.run_generation( 'AABA' )
|
52
|
+
|
53
|
+
expect( instance.calls ).to eq([
|
54
|
+
:do_a_thing,
|
55
|
+
:do_a_thing,
|
56
|
+
:do_b_thing,
|
57
|
+
:do_a_thing,
|
58
|
+
])
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "handles symbols that don't make good method names" do
|
63
|
+
subclass.production_map \
|
64
|
+
'[' => :push_frame,
|
65
|
+
'A' => :report_depth,
|
66
|
+
']' => :pop_frame
|
67
|
+
|
68
|
+
instance = subclass.new
|
69
|
+
instance.run_generation( 'A[A[[]]A[]]]]' )
|
70
|
+
|
71
|
+
expect( instance.calls ).to eq([
|
72
|
+
:report_depth,
|
73
|
+
:push_frame,
|
74
|
+
:report_depth,
|
75
|
+
:push_frame,
|
76
|
+
:push_frame,
|
77
|
+
:pop_frame,
|
78
|
+
:pop_frame,
|
79
|
+
:report_depth,
|
80
|
+
:push_frame,
|
81
|
+
:pop_frame,
|
82
|
+
:pop_frame,
|
83
|
+
:pop_frame,
|
84
|
+
:pop_frame
|
85
|
+
])
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
it "can run a rules engine" do
|
90
|
+
subclass.production_map \
|
91
|
+
'A' => :report_a,
|
92
|
+
'B' => :report_b
|
93
|
+
|
94
|
+
instance = subclass.new
|
95
|
+
instance.run( algae_rules, 4 )
|
96
|
+
|
97
|
+
expect( instance.calls ).to eq([
|
98
|
+
:report_a,
|
99
|
+
|
100
|
+
:report_a,
|
101
|
+
:report_b,
|
102
|
+
|
103
|
+
:report_a,
|
104
|
+
:report_b,
|
105
|
+
:report_a,
|
106
|
+
|
107
|
+
:report_a,
|
108
|
+
:report_b,
|
109
|
+
:report_a,
|
110
|
+
:report_a,
|
111
|
+
:report_b,
|
112
|
+
])
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
it "can declare lifecycle callbacks" do
|
117
|
+
subclass.production_map \
|
118
|
+
'A' => :report_a,
|
119
|
+
'B' => :report_b
|
120
|
+
subclass.on_start do |i, previous_result|
|
121
|
+
self.calls << [ :on_start, i, previous_result ]
|
122
|
+
end
|
123
|
+
subclass.result do |i|
|
124
|
+
self.calls << [ :result, i ]
|
125
|
+
"result_#{i}"
|
126
|
+
end
|
127
|
+
subclass.on_finish do |i, result|
|
128
|
+
self.calls << [ :on_finish, i, result ]
|
129
|
+
end
|
130
|
+
|
131
|
+
instance = subclass.new
|
132
|
+
result = instance.run( algae_rules, 4 )
|
133
|
+
|
134
|
+
expect( instance.calls ).to eq([
|
135
|
+
[:on_start, 0, nil],
|
136
|
+
:report_a,
|
137
|
+
[:result, 0],
|
138
|
+
[:on_finish, 0, "result_0"],
|
139
|
+
|
140
|
+
[:on_start, 1, "result_0"],
|
141
|
+
:report_a,
|
142
|
+
:report_b,
|
143
|
+
[:result, 1],
|
144
|
+
[:on_finish, 1, "result_1"],
|
145
|
+
|
146
|
+
[:on_start, 2, "result_1"],
|
147
|
+
:report_a,
|
148
|
+
:report_b,
|
149
|
+
:report_a,
|
150
|
+
[:result, 2],
|
151
|
+
[:on_finish, 2, "result_2"],
|
152
|
+
|
153
|
+
[:on_start, 3, "result_2"],
|
154
|
+
:report_a,
|
155
|
+
:report_b,
|
156
|
+
:report_a,
|
157
|
+
:report_a,
|
158
|
+
:report_b,
|
159
|
+
[:result, 3],
|
160
|
+
[:on_finish, 3, "result_3"],
|
161
|
+
])
|
162
|
+
|
163
|
+
expect( result ).to eq( "result_3" )
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'l_system/rules_engine'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( LSystem::RulesEngine ) do
|
9
|
+
|
10
|
+
let( :instance ) { described_class.new }
|
11
|
+
|
12
|
+
|
13
|
+
it "can set the l-system's variables" do
|
14
|
+
instance.variables = [ 'A', 'B' ]
|
15
|
+
|
16
|
+
expect( instance.variables ).to contain_exactly( 'A', 'B' )
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
it "can declare the l-system's variables" do
|
21
|
+
instance.variables 'A', 'B'
|
22
|
+
|
23
|
+
expect( instance.variables ).to contain_exactly( 'A', 'B' )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
it "raises if variables are not disjoint with the constants" do
|
28
|
+
instance.variables 'A', 'B'
|
29
|
+
expect {
|
30
|
+
instance.constants '[', 'A', ']'
|
31
|
+
}.to raise_error( ArgumentError, /"A" is already included in the variable set/ )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
it "can set the l-system's constants'" do
|
36
|
+
instance.constants = [ '[', ']' ]
|
37
|
+
|
38
|
+
expect( instance.constants ).to contain_exactly( '[', ']' )
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
it "can declare the l-system's constants'" do
|
43
|
+
instance.constants '[', ']'
|
44
|
+
|
45
|
+
expect( instance.constants ).to contain_exactly( '[', ']' )
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
it "raises if constants are not disjoint with the variables" do
|
50
|
+
instance.constants '[', 'A', ']'
|
51
|
+
expect {
|
52
|
+
instance.variables 'A', 'B'
|
53
|
+
}.to raise_error( ArgumentError, /"A" is already included in the constant set/ )
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it "knows what its alphabet is" do
|
58
|
+
instance.variables '0', '1'
|
59
|
+
instance.constants '[', ']'
|
60
|
+
|
61
|
+
expect( instance.alphabet ).to contain_exactly( '0', '1', '[', ']' )
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
it "can apply its rules to a string" do
|
66
|
+
instance.variables 'A', 'B'
|
67
|
+
instance.rules 'A -> AB', 'B -> A'
|
68
|
+
|
69
|
+
expect( instance.apply_rules('A') ).to eq( 'AB' )
|
70
|
+
expect( instance.apply_rules('AB') ).to eq( 'ABA' )
|
71
|
+
expect( instance.apply_rules('ABA') ).to eq( 'ABAAB' )
|
72
|
+
expect( instance.apply_rules('ABAAB') ).to eq( 'ABAABABA' )
|
73
|
+
expect( instance.apply_rules('ABAABABA') ).to eq( 'ABAABABAABAAB' )
|
74
|
+
expect( instance.apply_rules('ABAABABAABAAB') ).to eq( 'ABAABABAABAABABAABABA' )
|
75
|
+
expect( instance.apply_rules('ABAABABAABAABABAABABA') ).
|
76
|
+
to eq( 'ABAABABAABAABABAABABAABAABABAABAAB' )
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
it "can create an enumerator that will yield each successive generation" do
|
81
|
+
instance.variables 'A', 'B'
|
82
|
+
instance.axiom 'A'
|
83
|
+
instance.rules 'A -> AB', 'B -> A'
|
84
|
+
|
85
|
+
result = instance.each
|
86
|
+
|
87
|
+
expect( result ).to be_an( Enumerator )
|
88
|
+
expect( result.next ).to eq( 'A' )
|
89
|
+
expect( result.next ).to eq( 'AB' )
|
90
|
+
expect( result.next ).to eq( 'ABA' )
|
91
|
+
expect( result.next ).to eq( 'ABAAB' )
|
92
|
+
expect( result.next ).to eq( 'ABAABABA' )
|
93
|
+
expect( result.next ).to eq( 'ABAABABAABAAB' )
|
94
|
+
expect( result.next ).to eq( 'ABAABABAABAABABAABABA' )
|
95
|
+
expect( result.next ).to eq( 'ABAABABAABAABABAABABAABAABABAABAAB' )
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'spec_helper'
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'l_system'
|
8
|
+
|
9
|
+
RSpec.describe( LSystem ) do
|
10
|
+
|
11
|
+
it "allows an L-System to be declared" do
|
12
|
+
result = described_class.declare {}
|
13
|
+
expect( result ).to be_an( LSystem::RulesEngine )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
it "can run an L-System within the context of one or more productions" do
|
18
|
+
rules = described_class.declare do
|
19
|
+
variables :A, :B
|
20
|
+
axiom 'A'
|
21
|
+
rules 'A -> AB',
|
22
|
+
'B -> A'
|
23
|
+
end
|
24
|
+
result = described_class.run( rules, 8 ) do
|
25
|
+
|
26
|
+
production_map \
|
27
|
+
'A' => :append_A,
|
28
|
+
'B' => :append_B
|
29
|
+
|
30
|
+
on_start do |_generation_number, _previous_result|
|
31
|
+
@output = String.new( encoding: 'utf-8' )
|
32
|
+
end
|
33
|
+
|
34
|
+
result do |_generation_number|
|
35
|
+
@output
|
36
|
+
end
|
37
|
+
|
38
|
+
on_finish do |_generation_number, _result|
|
39
|
+
@output = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@output = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def append_A
|
48
|
+
@output << 'A'
|
49
|
+
end
|
50
|
+
|
51
|
+
def append_B
|
52
|
+
@output << 'B'
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
expect( result ).to eq( 'ABAABABAABAABABAABABAABAABABAABAAB' )
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'simplecov' if ENV['COVERAGE']
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
|
8
|
+
require 'loggability/spechelpers'
|
9
|
+
|
10
|
+
|
11
|
+
### Mock with RSpec
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.mock_with( :rspec ) do |mock|
|
14
|
+
mock.syntax = :expect
|
15
|
+
end
|
16
|
+
|
17
|
+
config.disable_monkey_patching!
|
18
|
+
config.example_status_persistence_file_path = "spec/.status"
|
19
|
+
config.filter_run :focus
|
20
|
+
config.filter_run_when_matching :focus
|
21
|
+
config.order = :random
|
22
|
+
config.profile_examples = 5
|
23
|
+
config.run_all_when_everything_filtered = true
|
24
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
25
|
+
# config.warnings = true
|
26
|
+
|
27
|
+
config.include( Loggability::SpecHelpers )
|
28
|
+
end
|
29
|
+
|
30
|
+
|
data.tar.gz.sig
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: l_system
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Granger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIENDCCApygAwIBAgIBATANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdnZWQv
|
14
|
+
REM9RmFlcmllTVVEL0RDPW9yZzAeFw0xOTEwMDkwMDM2NTdaFw0yMDEwMDgwMDM2
|
15
|
+
NTdaMCIxIDAeBgNVBAMMF2dlZC9EQz1GYWVyaWVNVUQvREM9b3JnMIIBojANBgkq
|
16
|
+
hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyVhkRzvlEs0fe7145BYLfN6njX9ih5H
|
17
|
+
L60U0p0euIurpv84op9CNKF9tx+1WKwyQvQP7qFGuZxkSUuWcP/sFhDXL1lWUuIl
|
18
|
+
M4uHbGCRmOshDrF4dgnBeOvkHr1fIhPlJm5FO+Vew8tSQmlDsosxLUx+VB7DrVFO
|
19
|
+
5PU2AEbf04GGSrmqADGWXeaslaoRdb1fu/0M5qfPTRn5V39sWD9umuDAF9qqil/x
|
20
|
+
Sl6phTvgBrG8GExHbNZpLARd3xrBYLEFsX7RvBn2UPfgsrtvpdXjsHGfpT3IPN+B
|
21
|
+
vQ66lts4alKC69TE5cuKasWBm+16A4aEe3XdZBRNmtOu/g81gvwA7fkJHKllJuaI
|
22
|
+
dXzdHqq+zbGZVSQ7pRYHYomD0IiDe1DbIouFnPWmagaBnGHwXkDT2bKKP+s2v21m
|
23
|
+
ozilJg4aar2okb/RA6VS87o+d7g6LpDDMMQjH4G9OPnJENLdhu8KnPw/ivSVvQw7
|
24
|
+
N2I4L/ZOIe2DIVuYH7aLHfjZDQv/mNgpAgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYD
|
25
|
+
VR0PBAQDAgSwMB0GA1UdDgQWBBRyjf55EbrHagiRLqt5YAd3yb8k4DAcBgNVHREE
|
26
|
+
FTATgRFnZWRARmFlcmllTVVELm9yZzAcBgNVHRIEFTATgRFnZWRARmFlcmllTVVE
|
27
|
+
Lm9yZzANBgkqhkiG9w0BAQsFAAOCAYEAFqsr6o0SvQRgjQVmhbQvExRnCMCoW1yb
|
28
|
+
FJiN7A5RA2Iy2E61OG1Ul5nGmaDmx/PNB/6JIbIV3B9Uq8aTZx4uOjK7r8vMl1/t
|
29
|
+
ZfY7r6HejJfXlcO2m6JDMbpdyEVv916LncBkzZRz6vnnNCx+31f15FKddxujpAFd
|
30
|
+
qpn3JRQY+oj7ZkoccL/IUiDpxQWeS3oOoz9qr2kVTp8R50InZimt79FqCl/1m66W
|
31
|
+
kdOuf+wM3DDx7Rt4IVNHrhGlyfMr7xjKW1Q3gll+pMN1DT6Ajx/t3JDSEg7BnnEW
|
32
|
+
r7AciSO6J4ApUdqyG+coLFlGdtgFTgRHv7ihbQtDI7Z/LV7A4Spn1j2PK3j0Omri
|
33
|
+
kSl1hPVigRytfgdVGiLXzvkkrkgj9EknCaj5UHbac7XvVBrljXj9hsnnqTANaKsg
|
34
|
+
jBZSA+N+xUTgUWpXjjwsLZjzJkhWATJWq+krNXcqpwXo6HsjmdUxoFMt63RBb+sI
|
35
|
+
XrxOxp8o0uOkU7FdLSGsyqJ2LzsR4obN
|
36
|
+
-----END CERTIFICATE-----
|
37
|
+
date: 2020-02-26 00:00:00.000000000 Z
|
38
|
+
dependencies:
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: loggability
|
41
|
+
requirement: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - "~>"
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.16'
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.16'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: tortoise
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0.9'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0.9'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rake-deveiate
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0.10'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0.10'
|
81
|
+
description: |-
|
82
|
+
A toolkit for creating and using {Lindenmayer Systems}[https://en.wikipedia.org/wiki/L-system] (L-systems).
|
83
|
+
It consists of a class that allows for declaration of the L-system's grammar,
|
84
|
+
and another class that allows for the definition of how the symbols output by a
|
85
|
+
grammar should be translated into work.
|
86
|
+
email:
|
87
|
+
- ged@faeriemud.org
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".simplecov"
|
93
|
+
- History.md
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- examples/fern_gn7.png
|
97
|
+
- lib/l_system.rb
|
98
|
+
- lib/l_system/production_adapter.rb
|
99
|
+
- lib/l_system/rules_engine.rb
|
100
|
+
- spec/l_system/production_adapter_spec.rb
|
101
|
+
- spec/l_system/rules_engine_spec.rb
|
102
|
+
- spec/l_system_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
homepage: https://hg.sr.ht/~ged/LSystem
|
105
|
+
licenses:
|
106
|
+
- BSD-3-Clause
|
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
|
+
rubygems_version: 3.1.2
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: A toolkit for creating and using {Lindenmayer Systems}[https://en.wikipedia.org/wiki/L-system]
|
127
|
+
(L-systems).
|
128
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|