rachinations 0.0.7 → 0.0.8

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.
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