rachinations 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/README.md +184 -5
  4. data/lib/rachinations.rb +5 -4
  5. data/lib/rachinations/domain/diagrams/diagram.rb +35 -24
  6. data/lib/rachinations/domain/exceptions/bad_config.rb +1 -1
  7. data/lib/rachinations/domain/{exceptions → modules/common}/bad_options.rb +0 -0
  8. data/lib/rachinations/domain/modules/common/hash_init.rb +1 -0
  9. data/lib/rachinations/domain/modules/common/refiners/number_modifiers.rb +2 -1
  10. data/lib/rachinations/domain/modules/diagrams/verbose.rb +1 -1
  11. data/lib/rachinations/domain/nodes/gate.rb +23 -27
  12. data/lib/rachinations/domain/nodes/node.rb +4 -20
  13. data/lib/rachinations/domain/nodes/pool.rb +2 -3
  14. data/lib/rachinations/domain/nodes/resourceful_node.rb +4 -3
  15. data/lib/rachinations/domain/nodes/sink.rb +1 -1
  16. data/lib/rachinations/dsl/bad_dsl.rb +3 -0
  17. data/lib/rachinations/dsl/bootstrap.rb +10 -8
  18. data/lib/rachinations/dsl/diagram_shorthand_methods.rb +10 -5
  19. data/lib/rachinations/dsl/helpers/parser.rb +83 -49
  20. data/lib/rachinations/helpers/edge_helper.rb +105 -8
  21. data/lib/rachinations/utils/math_utils.rb +83 -0
  22. data/lib/rachinations/utils/string_utils.rb +8 -0
  23. data/lib/rachinations/version.rb +1 -1
  24. data/rachinations.gemspec +2 -1
  25. data/testing/spec/diagram_spec.rb +78 -17
  26. data/testing/spec/gate_spec.rb +223 -2
  27. data/testing/spec/non_deterministic_diagram_spec.rb +1 -1
  28. data/testing/spec/release1/dsl_spec.rb +100 -3
  29. data/testing/spec/release1/individual_cases_spec.rb +39 -0
  30. data/testing/spec/release1/monografia_spec.rb +69 -0
  31. metadata +24 -5
  32. data/lib/rachinations/utils/string_helper.rb +0 -7
@@ -1,12 +1,15 @@
1
+ require 'fraction'
2
+
1
3
  module Helpers
2
4
  module EdgeHelper
5
+ MathUtils = ::Utils::MathUtils
3
6
 
4
7
  # Returns true if all edges passed as arguments can execute a push
5
8
  # when called in the same round. The difference between calling this
6
9
  # method and calling Edge#test_ping? on each edge is that this method
7
10
  # knows that each edge removes at least one Resource when it performs
8
11
  # a push.
9
- # @param [Array<Edge>] edges the edges
12
+ # @param [Enumerable<Edge>] edges the edges
10
13
  # @param [Boolean] require_all whether to require that the maximum amount
11
14
  # of resources supported by each edge (i.e. its label) be available in
12
15
  # order to succeed.
@@ -17,24 +20,118 @@ module Helpers
17
20
  number_of_resources: 0
18
21
  }
19
22
 
20
- raise NotImplementedError, 'only require_all is implemented so far' unless require_all
23
+ raise ::NotImplementedError, 'only require_all is implemented so far' unless require_all
24
+
25
+ final = edges.inject(initial) do |acc, elem|
26
+
27
+ edge = elem
21
28
 
22
- final = edges.inject(initial) do |accumulator, edge|
23
- accumulator[:number_of_resources] += edge.label
29
+ acc[:number_of_resources] += edge.label
24
30
 
25
31
  resources_available = edge.from.resource_count(expr: edge.push_expression)
26
32
 
27
- if resources_available >= accumulator[:number_of_resources]
28
- accumulator[:partial_success] &&= true
33
+ if resources_available >= acc[:number_of_resources]
34
+ acc[:partial_success] &&= true
29
35
  else
30
- accumulator[:partial_success] &&= false
36
+ acc[:partial_success] &&= false
31
37
  end
32
- accumulator
38
+ acc
33
39
  end
34
40
 
35
41
  final[:partial_success]
36
42
 
37
43
  end
38
44
 
45
+ # Validates that labels are consistent
46
+ def self.labels_valid?(edges)
47
+ all_labels_integer?(edges) || (all_labels_float?(edges) && float_labels_add_up_to_one?(edges))
48
+ end
49
+
50
+ def self.all_labels_of_same_kind?(edges)
51
+ all_labels_integer?(edges) || all_labels_float?(edges)
52
+ end
53
+
54
+ # maybe returns an edge
55
+ def self.pick_one(edges:, mode:, index: nil)
56
+ raise ::RuntimeError.new('Labels must be of the same kind') unless all_labels_of_same_kind?(edges)
57
+ raise ::RuntimeError.new('Unknown mode') unless [:deterministic, :probabilistic].include?(mode)
58
+
59
+ if edges.empty?
60
+ nil
61
+ else
62
+ if mode === :deterministic
63
+ pick_next(edges, index)
64
+ elsif mode === :probabilistic
65
+ pick_any(edges)
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def self.pick_next(edges, index)
73
+ raise ::ArgumentError.new('Invalid index: '+index) unless index.is_a?(Fixnum)
74
+
75
+
76
+ frequency_hash = edges.reduce(Hash.new) do |acc,elem|
77
+ acc[elem] = elem.label
78
+ acc
79
+ end
80
+
81
+ edge_lineup = MathUtils.get_cycle_lineup(frequency_hash)
82
+ normalized_index = index % edge_lineup.size
83
+
84
+ edge_lineup[normalized_index]
85
+ end
86
+
87
+ # pick one edge according to weights, if any
88
+ def self.pick_any(edges)
89
+
90
+ if all_labels_integer?(edges) && edges.all? { |e| e.label == 1 }
91
+ #edges is probably an enumerable but only arrays can be sample()'d
92
+ edges.to_a.sample
93
+ elsif all_labels_float?(edges)
94
+ #{edge=>weight} is the shape required by WeightedDistribution
95
+ weights = edges.reduce(Hash.new) { |acc, el| acc[el] = el.label; acc }
96
+
97
+ sum_of_labels = edges.reduce(0) { |acc, el| acc + el.label }
98
+
99
+ remaining = 1.0 - sum_of_labels
100
+
101
+ # chances that a resource 'vanishes'
102
+ weights[nil] = remaining
103
+
104
+ WeightedDistribution.new(weights).sample
105
+
106
+ elsif all_labels_integer?(edges)
107
+
108
+ sum_of_labels = edges.reduce(0) { |acc, el| acc + el.label }
109
+
110
+ # since labels are integers, we must normalize them to get probablities
111
+ weights = edges.reduce(Hash.new) { |acc, el| acc[el] = el.label/sum_of_labels; acc }
112
+
113
+ WeightedDistribution.new(weights).sample
114
+
115
+ else
116
+ raise RuntimeError.new('Invalid setup')
117
+ end
118
+ end
119
+
120
+ def self.all_labels_integer?(edges)
121
+ edges.all? { |e| e.label.is_a?(Fixnum) }
122
+ end
123
+
124
+ def self.all_labels_float?(edges)
125
+ edges.all? { |e| e.label.is_a?(Float) }
126
+ end
127
+
128
+ def self.float_labels_add_up_to_one?(edges)
129
+
130
+ labels_as_fractions = edges.reduce(Array.new){|acc,el| acc.push(el.label.to_fraction(100)); acc }
131
+
132
+ MathUtils.add_up_to_one?(labels_as_fractions)
133
+
134
+ end
135
+
39
136
  end
40
137
  end
@@ -0,0 +1,83 @@
1
+ require 'fraction'
2
+
3
+ module Utils
4
+ module MathUtils
5
+
6
+ # Returns an array containing references to the objects, in the correct
7
+ # order, such that it can be used to represent the minimum possible cycle
8
+ # that can be implemented using these objects.
9
+ #
10
+ # @example Simple example
11
+ # {"foo"=>3,"bar"=>2} yields ["foo","foo","foo","bar","bar"]
12
+ # @param [Hash<Object,Numeric>] weights A hash where the keys are
13
+ # objects and the values their respective weights
14
+ # @return [Array[Object]] the lineup for the given weight hash
15
+ def self.get_cycle_lineup(weights)
16
+ raise ArgumentError.new('Weights should be supplied as a Hash') unless weights.is_a?(Hash)
17
+ raise ArgumentError.new('Weights should be numbers') unless weights.values.all?{|v| v.is_a?(Fixnum) || v.is_a?(Float) }
18
+
19
+ if weights.values.all?{|v| v.is_a?(Fixnum) }
20
+ get_integer_lineup(weights)
21
+ elsif weights.values.all?{|v| v.is_a?(Float) }
22
+ get_float_lineup(weights)
23
+ end
24
+
25
+ end
26
+
27
+ # takes an enumerable of fractions
28
+ def self.add_up_to_one?(fractions)
29
+
30
+ numerators = fractions.map{|el| el[0] }
31
+ denominators = fractions.map{|el| el[1] }
32
+
33
+ # lowest_common_multiple
34
+ # lcm(denominators)
35
+
36
+ sum=numerators.map.with_index do |elem,i|
37
+ multiplier = (lcm(denominators) / denominators[i]).to_i
38
+ multiplier * elem
39
+ end.reduce(:+)
40
+
41
+ sum == lcm(denominators)
42
+
43
+ end
44
+
45
+ private
46
+
47
+ # used when weights are integers
48
+ def self.get_integer_lineup(weights)
49
+ weights.reduce(Array.new) do |acc,(key,val)|
50
+ val.times{|i| acc.push(key) }
51
+ acc
52
+ end
53
+ end
54
+
55
+ # used when weights are floats (will be turned to fractions)
56
+ def self.get_float_lineup(weights)
57
+ weights_as_fractions = weights.reduce(Hash.new){|acc,(key,val)| acc[key] = val.to_fraction(100); acc }
58
+ raise RuntimeError('Fractional weights must add up to one') unless add_up_to_one?(weights_as_fractions.values)
59
+
60
+ lcm = lcm(weights_as_fractions.values.map{|el| el[1] } )
61
+
62
+ # the higher its weight, the more times the object appears
63
+ weights_as_fractions.reduce(Array.new) do |acc,(key,val)|
64
+ numerator = val[0]
65
+ denominator = val[1]
66
+ error = val[2] # not used so far
67
+ appearances = ( (lcm / denominator) * numerator ).to_i
68
+
69
+ # one reference for each appearance
70
+ appearances.times{ acc.push(key) }
71
+ acc
72
+ end
73
+
74
+ end
75
+
76
+ def self.lcm(integers)
77
+ integers.reduce do |acc,elem|
78
+ acc.to_i.lcm(elem)
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,8 @@
1
+ module Utils
2
+ module StringUtils
3
+
4
+ def self.valid_ruby_variable_name?(str)
5
+ (/^[a-z_][a-zA-Z_0-9]*$/ =~ str) == 0
6
+ end
7
+ end
8
+ end
@@ -1,3 +1,3 @@
1
1
  module Rachinations
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
data/rachinations.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Felipe Almeida"]
10
10
  spec.email = ["falmeida1988@gmail.com"]
11
11
  spec.summary = %q{Ruby port for Dr. J. Dormans' Machinations Game Mechanics Diagrams.}
12
- spec.description = %q{This project provides a Ruby-based DiagramShorthandMethods to enable game designers to
12
+ spec.description = %q{This project provides a Ruby-based Diagram to enable game designers to
13
13
  design and also test tentative game designs and/or prototypes}
14
14
  spec.homepage = "https://github.com/queirozfcom/rachinations"
15
15
  spec.license = "MIT"
@@ -31,5 +31,6 @@ design and also test tentative game designs and/or prototypes}
31
31
  spec.add_dependency "activesupport","3.0.0"
32
32
  spec.add_dependency "i18n","0.6.11"
33
33
  spec.add_dependency "weighted_distribution", "1.0.0"
34
+ spec.add_dependency 'fraction','0.3.2'
34
35
 
35
36
  end
@@ -14,27 +14,27 @@ describe Diagram do
14
14
 
15
15
  it 'should be created with a source and a pool and run n times with no errors' do
16
16
 
17
- d=Diagram.new 'simple'
17
+ d=Diagram.new 'simples'
18
18
 
19
19
  d.add_node! Source, {
20
- :name => 'source'
20
+ :name => 'fonte'
21
21
  }
22
22
 
23
23
  d.add_node! Pool, {
24
- :name => 'deposit',
24
+ :name => 'depósito',
25
25
  :initial_value => 0
26
26
  }
27
27
 
28
28
  d.add_edge! Edge, {
29
- :name => 'connector',
30
- :from => 'source',
31
- :to => 'deposit'
29
+ :name => 'conector',
30
+ :from => 'fonte',
31
+ :to => 'depósito'
32
32
  }
33
33
 
34
34
  d.run!(10)
35
35
 
36
36
  expect(d.resource_count).to eq 10
37
- expect(d.get_node('deposit').resource_count).to eq 10
37
+ expect(d.get_node('depósito').resource_count).to eq 10
38
38
 
39
39
  end
40
40
 
@@ -459,11 +459,13 @@ describe Diagram do
459
459
 
460
460
  d = Diagram.new
461
461
 
462
+ d.add_node! Sink, name: 'sink'
462
463
  d.add_node! Source, name: 's'
463
464
  d.add_node! Gate, name: 'g'
464
465
  d.add_node! Pool, name: 'p'
465
466
  d.add_edge! Edge, from: 's', to: 'g'
466
467
  d.add_edge! Edge, from: 'g', to: 'p', label: 50.percent
468
+ d.add_edge! Edge, from: 'g', to: 'sink', label: 50.percent
467
469
 
468
470
  d.run!(20)
469
471
 
@@ -472,8 +474,70 @@ describe Diagram do
472
474
  end
473
475
  end
474
476
 
477
+ describe 'stop conditions' do
478
+
479
+ it 'should stop when a stop condition is met before all turns run' do
475
480
 
476
- describe 'Comprehensive examples'do
481
+ d = Diagram.new
482
+
483
+ d.add_node! Source, name: 's'
484
+ d.add_node! Gate, name: 'g'
485
+ d.add_node! Pool, name: 'po'
486
+ d.add_edge! Edge, from: 's', to: 'g'
487
+ d.add_edge! Edge, from: 'g', to: 'po'
488
+
489
+ d.add_stop_condition! message: 'first', condition: expr { d.po.resource_count >= 5 }
490
+
491
+ # useless but still
492
+ d.add_stop_condition! message: 'second', condition: expr { d.po.resource_count >= 10 }
493
+
494
+ d.run!(20)
495
+
496
+ expect(d.po.resource_count).to eq 5
497
+ end
498
+
499
+ it 'should continue until the last round if no stop conditions are met' do
500
+ d = Diagram.new
501
+
502
+ d.add_node! Source, name: 's'
503
+ d.add_node! Gate, name: 'g'
504
+ d.add_node! Pool, name: 'po'
505
+ d.add_edge! Edge, from: 's', to: 'g'
506
+ d.add_edge! Edge, from: 'g', to: 'po'
507
+
508
+ d.add_stop_condition! message: 'first', condition: expr { d.po.resource_count > 99 }
509
+
510
+ # also useless but still
511
+ d.add_stop_condition! message: 'second', condition: expr { d.po.resource_count < -1 }
512
+
513
+ d.run!(20)
514
+
515
+ expect(d.po.resource_count).to eq 20
516
+
517
+ end
518
+
519
+ it 'complains' do
520
+ # when users try to add two stop conditions but give them no names
521
+
522
+ expect {
523
+ d = Diagram.new
524
+
525
+ d.add_node! Source, name: 's'
526
+ d.add_node! Gate, name: 'g'
527
+ d.add_node! Pool, name: 'po'
528
+ d.add_edge! Edge, from: 's', to: 'g'
529
+ d.add_edge! Edge, from: 'g', to: 'po'
530
+
531
+ d.add_stop_condition! condition: expr { d.po.resource_count > 99 }
532
+ d.add_stop_condition! condition: expr { d.po.resource_count < -1 }
533
+
534
+ }.to raise_error
535
+
536
+ end
537
+
538
+ end
539
+
540
+ describe 'Comprehensive examples' do
477
541
 
478
542
  it 'example using pull_all and activators' do
479
543
 
@@ -485,14 +549,14 @@ describe Diagram do
485
549
  d.add_node! Pool, name: 'p4', initial_value: 2
486
550
  d.add_node! Pool, name: 'p5', activation: :automatic, initial_value: 6, mode: :push_any
487
551
 
488
- d.add_edge! Edge, from: 'p2',to:'p1'
489
- d.add_edge! Edge, from: 'p3',to:'p1'
490
- d.add_edge! Edge, from: 'p4',to:'p1'
552
+ d.add_edge! Edge, from: 'p2', to: 'p1'
553
+ d.add_edge! Edge, from: 'p3', to: 'p1'
554
+ d.add_edge! Edge, from: 'p4', to: 'p1'
491
555
 
492
- d.add_edge! Edge, from: 'p5',to:'p3'
493
- d.add_edge! Edge, from: 'p5',to:'p4'
556
+ d.add_edge! Edge, from: 'p5', to: 'p3'
557
+ d.add_edge! Edge, from: 'p5', to: 'p4'
494
558
 
495
- d.p5.attach_condition{ d.p3.resource_count < 1 && d.p4.resource_count < 1 }
559
+ d.p5.attach_condition { d.p3.resource_count < 1 && d.p4.resource_count < 1 }
496
560
 
497
561
  d.run! 8
498
562
 
@@ -505,9 +569,6 @@ describe Diagram do
505
569
  end
506
570
 
507
571
 
508
-
509
-
510
-
511
572
  end
512
573
 
513
574
  end
@@ -2,6 +2,7 @@ require_relative 'spec_helper'
2
2
 
3
3
  describe 'Diagrams with gates' do
4
4
 
5
+
5
6
  context 'general tests' do
6
7
 
7
8
  it 'works' do
@@ -20,12 +21,232 @@ describe 'Diagrams with gates' do
20
21
 
21
22
  end
22
23
 
24
+ it 'has default settings' do
25
+
26
+
27
+ g = Gate.new name: 'g'
28
+
29
+ expect(g.mode).to eq(:deterministic)
30
+ expect(g.name).to eq('g')
31
+ expect(g.activation).to eq(:passive)
32
+ end
33
+
23
34
  end
24
35
 
25
36
  context 'specific features' do
26
37
 
27
- it 'does not keep resources over turns' do
28
- skip
38
+ # in other words,
39
+ # either all floats adding up to 1 (which will be interpreted as probabilities that one will be chosen)
40
+ # or all integers (which will be interpreted )
41
+ context 'outgoing edges' do
42
+
43
+ it 'errors when one is fraction and another integer' do
44
+
45
+ expect {
46
+
47
+ d = Diagram.new
48
+
49
+ d.add_node! Source, name: 's'
50
+ d.add_node! Gate, name: 'g'
51
+ d.add_node! Pool, name: 'p1'
52
+ d.add_node! Pool, name: 'p2'
53
+
54
+
55
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
56
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
57
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 4
58
+
59
+ d.run! 10
60
+
61
+ }.to raise_error(BadConfig)
62
+ end
63
+
64
+ it 'compiles when both are integer' do
65
+
66
+ expect {
67
+
68
+ d = Diagram.new
69
+
70
+ d.add_node! Source, name: 's'
71
+ d.add_node! Gate, name: 'g'
72
+ d.add_node! Pool, name: 'p1'
73
+ d.add_node! Pool, name: 'p2'
74
+
75
+
76
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
77
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 5
78
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 4
79
+
80
+ d.run! 10
81
+
82
+ }.not_to raise_error
83
+
84
+ end
85
+
86
+ it 'compiles when both are fractions and add up to 1' do
87
+
88
+ # well-behaved labels
89
+ expect {
90
+
91
+ d = Diagram.new
92
+
93
+ d.add_node! Source, name: 's'
94
+ d.add_node! Gate, name: 'g'
95
+ d.add_node! Pool, name: 'p1'
96
+ d.add_node! Pool, name: 'p2'
97
+
98
+
99
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
100
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
101
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/2
102
+
103
+ d.run! 10
104
+
105
+ }.not_to raise_error
106
+
107
+ # badly behaved labels
108
+ expect {
109
+
110
+ d = Diagram.new
111
+
112
+ d.add_node! Source, name: 's'
113
+ d.add_node! Gate, name: 'g'
114
+ d.add_node! Pool, name: 'p1'
115
+ d.add_node! Pool, name: 'p2'
116
+
117
+
118
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
119
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 3/21
120
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 18/21
121
+
122
+ d.run! 10
123
+
124
+ }.not_to raise_error
125
+
126
+ end
127
+
128
+ it "errors when fraction labels don't add up to one" do
129
+ # less than 1
130
+ expect{
131
+ d = Diagram.new
132
+
133
+ d.add_node! Source, name: 's'
134
+ d.add_node! Gate, name: 'g'
135
+ d.add_node! Pool, name: 'p1'
136
+ d.add_node! Pool, name: 'p2'
137
+ d.add_node! Pool, name: 'p3'
138
+
139
+
140
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
141
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/9
142
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 8/77
143
+ d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 2/17
144
+
145
+ d.run! 10
146
+ }.to raise_error(BadConfig)
147
+
148
+ # greater than 1 but each one not greater than 1
149
+ expect{
150
+ d = Diagram.new
151
+
152
+ d.add_node! Source, name: 's'
153
+ d.add_node! Gate, name: 'g'
154
+ d.add_node! Pool, name: 'p1'
155
+ d.add_node! Pool, name: 'p2'
156
+
157
+
158
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
159
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 4/9
160
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 11/12
161
+
162
+ d.run! 10
163
+ }.to raise_error(BadConfig)
164
+
165
+ # greater than 1 but a single label is greater than 1
166
+ expect{
167
+ d = Diagram.new
168
+
169
+ d.add_node! Source, name: 's'
170
+ d.add_node! Gate, name: 'g'
171
+ d.add_node! Pool, name: 'p1'
172
+ d.add_node! Pool, name: 'p2'
173
+
174
+
175
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
176
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 22/9
177
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/12
178
+
179
+ d.run! 10
180
+ }.to raise_error(BadConfig)
181
+ end
182
+
183
+ end
184
+
185
+ it 'probabilistic gates send resources in a non-deterministic way' do
186
+
187
+ no_of_turns = 100
188
+
189
+ d = Diagram.new
190
+
191
+ d.add_node! Source, name: 's'
192
+ d.add_node! Gate, name: 'g', mode: :probabilistic
193
+
194
+ d.add_node! Pool, name: 'p1'
195
+ d.add_node! Pool, name: 'p2'
196
+ d.add_node! Pool, name: 'p3'
197
+
198
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
199
+
200
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
201
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/4
202
+ d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 1/4
203
+
204
+ d.run! no_of_turns
205
+
206
+ # have to deal with probabilities here
207
+ expect(d.get_node('p1').resource_count).to be_within(10).of(50)
208
+ expect(d.get_node('p2').resource_count).to be_within(10).of(25)
209
+ expect(d.get_node('p3').resource_count).to be_within(10).of(25)
210
+
211
+ # this should work in most cases
212
+ expect(d.get_node('p2').resource_count).not_to eq(d.get_node('p3').resource_count)
213
+
214
+ # the sum must remain constant
215
+ p1_res = d.get_node('p1').resource_count
216
+ p2_res = d.get_node('p2').resource_count
217
+ p3_res = d.get_node('p3').resource_count
218
+
219
+ sum_res = p1_res + p2_res + p3_res
220
+ expect(sum_res).to eq no_of_turns
221
+
222
+ end
223
+
224
+ it 'deterministic gates always distribute resources in the same order (integer labels)' do
225
+
226
+ d = Diagram.new
227
+
228
+ d.add_node! Source, name: 's'
229
+ d.add_node! Gate, name: 'g', mode: :deterministic
230
+
231
+ d.add_node! Pool, name: 'p1'
232
+ d.add_node! Pool, name: 'p2'
233
+ d.add_node! Pool, name: 'p3'
234
+
235
+ d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
236
+
237
+ d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 2
238
+ d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1
239
+ d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 1
240
+
241
+ # if resources are shared deterministically and the number of turns
242
+ # is a multiple of the number of outgoing edges then there should be
243
+ # a predictably defined number of resources in each pool
244
+ d.run! 12
245
+
246
+ expect(d.get_node('p1').resource_count).to eq(6)
247
+ expect(d.get_node('p2').resource_count).to eq(3)
248
+ expect(d.get_node('p3').resource_count).to eq(3)
249
+
29
250
  end
30
251
 
31
252
  end