l_system 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Fern Gen 7](examples/fern_gn7.png)
|
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
|