hifsm 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e43ef721bec5c9d473610831836bf1531ae5c782
4
+ data.tar.gz: c6396a4f8ecd0f81c2963a84685bdae93a1dae3c
5
+ SHA512:
6
+ metadata.gz: 6c586dba477b583fd85e1bb4eedac0df1610af363d178569645f51a0aa4820c385edd21709149d3dce2a997f95d847ece2a6cbf3a96b864582b01d7f689b8be5
7
+ data.tar.gz: 90ffecc59fc1bf1a8af8cf54c2a70cf7c48a52a6a37ae84bf3ce92da72a89102449d94688c63ece647c42987d10fafb60ee063feecd6612decdd9d00a01db52c
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Vladimir Meremyanin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Hierarchical Finite State Machine in Ruby
2
+
3
+ This library was created from the desire to have nested states inspired by [rFSM](https://github.com/kmarkus/rFSM).
4
+
5
+ It can be used in plain old ruby objects, but works well with `ActiveRecord`s too.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'hifsm'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install hifsm
20
+
21
+ I prefer 1.8-style hashes, and since no advanced Ruby magic used it should work in 1.8, but only tested in 2+.
22
+
23
+ __This is in early development, so be careful.__
24
+
25
+ ## Features
26
+
27
+ * Easy to use
28
+ * Any number of state machines per object
29
+ * Nested states
30
+ * Parameterised events
31
+ * Support of both Mealy and Moore machines
32
+ * Lightweight and non-obtrusive
33
+
34
+ ## Usage
35
+
36
+ Here is how to use it to model a monster in a Quake-like game. It covers most Hifsm features:
37
+
38
+ ```ruby
39
+ require 'hifsm'
40
+
41
+ class Monster
42
+ @@fsm = Hifsm::FSM.define do
43
+ state :idle, :initial => true
44
+ state :attacking do
45
+ state :acquiring_target, :initial => true do
46
+ action do
47
+ # self is the monster instance here
48
+ plan_attack
49
+ end
50
+ end
51
+ state :pursuing do
52
+ before_enter do
53
+ self.roar!
54
+ end
55
+ action do
56
+ step_towards target
57
+ end
58
+ end
59
+ state :fighting do
60
+ action do
61
+ hit target
62
+ end
63
+ end
64
+
65
+ event :acquire, :from => :acquiring_target, :to => :pursuing
66
+ event :reached, :from => :pursuing, :to => :fighting
67
+
68
+ action do |tick|
69
+ debug && puts("#{tick}: Attack!")
70
+ end
71
+ end
72
+ state :coming_back do
73
+ action do
74
+ step_towards @home
75
+ end
76
+ end
77
+ state :runaway
78
+
79
+ event :sight, :from => [:idle, :coming_back], :to => :runaway, :guard => :low_hp?
80
+ event :sight, :from => [:idle, :coming_back], :to => :attacking do
81
+ before do |t|
82
+ debug && puts("Setting target to #{t}")
83
+ self.target = t
84
+ end
85
+ end
86
+ event :enemy_dead, :from => :attacking, :to => :coming_back do
87
+ after do
88
+ debug && puts("Woohoo!")
89
+ self.target = nil
90
+ end
91
+ end
92
+ end
93
+
94
+ attr_accessor :target, :low_hp, :debug
95
+ attr_reader :state
96
+
97
+ def initialize
98
+ @debug = false
99
+ @home = 'home'
100
+ @state = @@fsm.new(self) # or @@fsm.new(self, 'attacking.pursuing')
101
+ @tick = 1
102
+ @low_hp = false
103
+ end
104
+
105
+ def act!
106
+ debug && puts("Acting @#{@state}")
107
+ @state.act!(@tick)
108
+ @tick = @tick + 1
109
+ end
110
+
111
+ def hit(target)
112
+ debug && puts("~~> #{target}")
113
+ end
114
+
115
+ def low_hp?
116
+ @low_hp
117
+ end
118
+
119
+ def plan_attack
120
+ debug && puts("planning...")
121
+ acquire
122
+ end
123
+
124
+ def roar!
125
+ debug && puts("AARGHH!")
126
+ end
127
+
128
+ def step_towards(target)
129
+ debug && puts("step step #{target}")
130
+ end
131
+
132
+ end
133
+
134
+ ogre = Monster.new
135
+ ogre.debug = true # Console output:
136
+ ogre.act! # -> Acting @idle
137
+ ogre.sight 'player' # -> Setting target to player
138
+ ogre.act! # -> Acting @attacking.acquiring_target
139
+ # -> planning...
140
+ # -> AARGHH!
141
+ # ogre.acquire -> Hifsm::MissingTransition, already acquired in act!
142
+ ogre.act! # -> Acting @attacking.pursuing
143
+ # -> step step player
144
+ ogre.enemy_dead # -> Woohoo!
145
+ ogre.act! # -> Acting @coming_back
146
+
147
+ ogre.sight 'player2' # -> Setting target to player2
148
+ ogre.acquire # -> AARGHH!
149
+ ogre.act! # -> Acting @attacking.pursuing
150
+ # -> step step player2
151
+ ogre.reached
152
+ puts ogre.state # -> attacking.fighting
153
+ ogre.act! # -> ~~> player2
154
+ 5.times { ogre.act! } # -> ...
155
+ ogre.enemy_dead # -> Woohoo!
156
+ ogre.act! # -> Acting @coming_back
157
+ # -> step step home
158
+
159
+ ```
160
+
161
+ ## Guards
162
+
163
+ Events are tried in order they were defined, if guard callback returns `false` then event is skipped.
164
+
165
+ ## Callbacks
166
+
167
+ On event:
168
+
169
+ * event.before
170
+ * to_state.before_enter
171
+ * from_state.before_exit
172
+ * *state changes*
173
+ * from_state.after_exit
174
+ * to_state.after_enter
175
+ * event.after
176
+
177
+ If `before...` callback returns Hifsm.cancel then no further processing is done
178
+
179
+ On `act!` just calls action block if it was given.
180
+
181
+ ## Contributing
182
+
183
+ 1. Fork it
184
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
185
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
186
+ 4. Push to the branch (`git push origin my-new-feature`)
187
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push 'test'
6
+ t.pattern = "test/test_*.rb"
7
+ end
data/hifsm.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hifsm/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hifsm"
8
+ spec.version = Hifsm::VERSION
9
+ spec.authors = ["Vladimir Meremyanin"]
10
+ spec.email = ["vladimir@meremyanin.com"]
11
+ spec.description = %q{FSM with support for nested states and parameterised events}
12
+ spec.summary = %q{Hierarchical state machines in Ruby}
13
+ spec.homepage = "http://github.com/stiff/hifsm"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "minitest"
24
+ end
@@ -0,0 +1,27 @@
1
+ class Callbacks
2
+
3
+ class <<self
4
+ def invoke(target, cb, *args)
5
+ if cb.nil?
6
+ elsif cb.is_a? Symbol
7
+ target.send(cb, *args)
8
+ else
9
+ target.instance_exec *args, &cb
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize
15
+ @listeners = []
16
+ end
17
+
18
+ def add(&callback)
19
+ @listeners.push callback
20
+ end
21
+
22
+ def trigger(target, *args)
23
+ @listeners.each do |callback|
24
+ Callbacks.invoke target, callback, *args
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module Hifsm
2
+ class Event
3
+ CALLBACKS = [:before, :after].freeze
4
+
5
+ attr_reader :name, :to
6
+
7
+ def initialize(name, to, guard)
8
+ @name = name
9
+ @guard = guard
10
+ @to = to
11
+
12
+ @callbacks = Hash.new {|h, key| h[key] = Callbacks.new }
13
+ end
14
+
15
+ CALLBACKS.each do |cb|
16
+ define_method(cb) { |&block| @callbacks[cb].add(&block) }
17
+ end
18
+
19
+ def trigger(target, cb, *args)
20
+ @callbacks[cb].trigger(target, *args)
21
+ end
22
+
23
+ def guard?(target)
24
+ !@guard || Callbacks.invoke(target, @guard)
25
+ end
26
+ end
27
+ end
data/lib/hifsm/fsm.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Hifsm
2
+ class FSM
3
+ attr_reader :states, :transitions
4
+
5
+ class <<self
6
+ def define(&block)
7
+ Hifsm::FSM.new(&block)
8
+ end
9
+ end
10
+
11
+ def initialize(parent = nil, &block)
12
+ @parent = parent
13
+ @states = {}
14
+ @initial_state
15
+
16
+ instance_eval &block if block
17
+ end
18
+
19
+ def new(target = nil, initial_state = nil)
20
+ Hifsm::Machine.new(target, self, initial_state)
21
+ end
22
+
23
+ def all_events
24
+ @states.collect {|name, st| st.events }.flatten.uniq
25
+ end
26
+
27
+ def initial_state!
28
+ @initial_state || raise(Hifsm::MissingState.new("<initial>"))
29
+ end
30
+
31
+ def get_state!(name)
32
+ @states[name.to_s] || raise(Hifsm::MissingState.new(name.to_s))
33
+ end
34
+
35
+ def event(name, options, &block)
36
+ ev = Hifsm::Event.new(name, get_state!(options[:to]), options[:guard])
37
+ from_states = array_wrap(options[:from])
38
+ from_states = @states.keys if from_states.empty?
39
+ from_states.each do |from|
40
+ st = get_state!(from)
41
+ st.add_transition(ev)
42
+ end
43
+ ev.instance_eval &block if block
44
+ end
45
+
46
+ def state(name, options = {}, &block)
47
+ st = @states[name.to_s] = Hifsm::State.new(name, @parent)
48
+ @initial_state = st if options[:initial]
49
+ st.instance_eval &block if block
50
+ end
51
+
52
+ private
53
+ # like in ActiveSupport
54
+ def array_wrap(anything)
55
+ anything.is_a?(Array) ? anything : [anything].compact
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ module Hifsm
2
+ class Machine
3
+ def initialize(target, fsm, initial_state = nil)
4
+ @target = target || self
5
+ @fsm = fsm
6
+
7
+ @state = fsm.states[initial_state] || fsm.initial_state!
8
+
9
+ mach = self
10
+ fsm.all_events.each do |event_name, event|
11
+ @target.singleton_class.instance_exec do
12
+ define_method(event_name) {|*args| mach.fire(event_name, *args) }
13
+ end
14
+ end
15
+ end
16
+
17
+ def act!(*args)
18
+ @state.act!(@target, *args)
19
+ end
20
+
21
+ def state
22
+ @state.to_s
23
+ end
24
+
25
+ def fire(event, *args)
26
+ @state.fire(@target, event, *args) do |new_state|
27
+ @state = new_state
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ @state.to_s
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,93 @@
1
+ module Hifsm
2
+ class State
3
+ CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit].freeze
4
+
5
+ def initialize(name, parent = nil)
6
+ @name = name
7
+ @parent = parent
8
+ @action
9
+
10
+ @callbacks = Hash.new {|h, key| h[key] = Callbacks.new }
11
+ @transitions = Hash.new {|h, key| h[key] = Array.new }
12
+ end
13
+
14
+ def add_transition(ev)
15
+ name = ev.name.to_s
16
+ @transitions[name].push ev
17
+ end
18
+
19
+ def action(&block)
20
+ @action = block
21
+ end
22
+
23
+ def state(*args, &block)
24
+ sub_fsm!.state *args, &block
25
+ end
26
+
27
+ def event(*args, &block)
28
+ sub_fsm!.event *args, &block
29
+ end
30
+
31
+ def events
32
+ @transitions.keys + (@sub_fsm && @sub_fsm.all_events || [])
33
+ end
34
+
35
+ def fire(target, event_name, *args, &new_state_callback)
36
+ event_name = event_name.to_s
37
+ @transitions[event_name].each do |ev|
38
+ if ev.guard?(target)
39
+ from_state = self
40
+ to_state = ev.to
41
+ if ev.trigger(target, :before, *args) &&
42
+ to_state.trigger(target, :before_enter, *args) &&
43
+ from_state.trigger(target, :before_exit, *args)
44
+ new_state_callback.call(to_state.enter!)
45
+ from_state.trigger(target, :after_exit, *args)
46
+ to_state.trigger(target, :after_enter, *args)
47
+ ev.trigger(target, :after, *args)
48
+ end
49
+ return
50
+ end
51
+ end
52
+ if @parent
53
+ return @parent.fire(target, event_name, *args, &new_state_callback)
54
+ end
55
+ raise Hifsm::MissingTransition.new(to_s, event_name)
56
+ end
57
+
58
+ CALLBACKS.each do |cb|
59
+ define_method(cb) { |&block| @callbacks[cb].add(&block) }
60
+ end
61
+
62
+ def trigger(target, cb, *args)
63
+ @callbacks[cb].trigger(target, *args)
64
+ end
65
+
66
+ def act!(target, *args)
67
+ @action && Callbacks.invoke(target, @action, *args)
68
+ if @sub_fsm
69
+ end
70
+ end
71
+
72
+ def enter!
73
+ if @sub_fsm
74
+ @sub_fsm.initial_state!
75
+ else
76
+ self
77
+ end
78
+ end
79
+
80
+ def to_s
81
+ if @parent
82
+ "#{@parent.to_s}.#{@name.to_s}"
83
+ else
84
+ @name.to_s
85
+ end
86
+ end
87
+
88
+ private
89
+ def sub_fsm!
90
+ @sub_fsm ||= Hifsm::FSM.new(self)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ module Hifsm
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hifsm.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "hifsm/callbacks"
2
+ require "hifsm/fsm"
3
+ require "hifsm/event"
4
+ require "hifsm/machine"
5
+ require "hifsm/state"
6
+ require "hifsm/version"
7
+
8
+ module Hifsm
9
+ class MissingTransition < StandardError
10
+ def initialize(state, name)
11
+ super "No transition :#{name} from :#{state}"
12
+ end
13
+ end
14
+
15
+ class MissingState < StandardError
16
+ def initialize(name)
17
+ super "No state :#{name} defined"
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,4 @@
1
+ require 'hifsm'
2
+
3
+ gem 'minitest'
4
+ require 'minitest/autorun'
data/test/monster.rb ADDED
@@ -0,0 +1,121 @@
1
+ require 'hifsm'
2
+
3
+ class Monster
4
+ @@fsm = Hifsm::FSM.define do
5
+ state :idle, :initial => true
6
+ state :attacking do
7
+ state :acquiring_target, :initial => true do
8
+ action do
9
+ # self is the monster instance here
10
+ plan_attack
11
+ end
12
+ end
13
+ state :pursuing do
14
+ before_enter do
15
+ self.roar!
16
+ end
17
+ action do
18
+ step_towards target
19
+ end
20
+ end
21
+ state :fighting do
22
+ action do
23
+ hit target
24
+ end
25
+ end
26
+
27
+ event :acquire, :from => :acquiring_target, :to => :pursuing
28
+ event :reached, :from => :pursuing, :to => :fighting
29
+
30
+ action do |tick|
31
+ debug && puts("#{tick}: Attack!")
32
+ end
33
+ end
34
+ state :coming_back do
35
+ action do
36
+ step_towards @home
37
+ end
38
+ end
39
+ state :runaway
40
+
41
+ event :sight, :from => [:idle, :coming_back], :to => :runaway, :guard => :low_hp?
42
+ event :sight, :from => [:idle, :coming_back], :to => :attacking do
43
+ before do |t|
44
+ debug && puts("Setting target to #{t}")
45
+ self.target = t
46
+ end
47
+ end
48
+ event :enemy_dead, :from => :attacking, :to => :coming_back do
49
+ after do
50
+ debug && puts("Woohoo!")
51
+ self.target = nil
52
+ end
53
+ end
54
+ end
55
+
56
+ attr_accessor :target, :low_hp, :debug
57
+ attr_reader :state
58
+
59
+ def initialize
60
+ @debug = false
61
+ @home = 'home'
62
+ @state = @@fsm.new(self) # or @@fsm.new(self, 'attacking.pursuing')
63
+ @tick = 1
64
+ @low_hp = false
65
+ end
66
+
67
+ def act!
68
+ debug && puts("Acting @#{@state}")
69
+ @state.act!(@tick)
70
+ @tick = @tick + 1
71
+ end
72
+
73
+ def hit(target)
74
+ debug && puts("~~> #{target}")
75
+ end
76
+
77
+ def low_hp?
78
+ @low_hp
79
+ end
80
+
81
+ def plan_attack
82
+ debug && puts("planning...")
83
+ acquire
84
+ end
85
+
86
+ def roar!
87
+ debug && puts("AARGHH!")
88
+ end
89
+
90
+ def step_towards(target)
91
+ debug && puts("step step #{target}")
92
+ end
93
+
94
+ end
95
+
96
+ if $0 == __FILE__
97
+ ogre = Monster.new
98
+ ogre.debug = true # Console output:
99
+ ogre.act! # -> Acting @idle
100
+ ogre.sight 'player' # -> Setting target to player
101
+ ogre.act! # -> Acting @attacking.acquiring_target
102
+ # -> planning...
103
+ # -> AARGHH!
104
+ # ogre.acquire -> Hifsm::MissingTransition, already acquired in act!
105
+ ogre.act! # -> Acting @attacking.pursuing
106
+ # -> step step player
107
+ ogre.enemy_dead # -> Woohoo!
108
+ ogre.act! # -> Acting @coming_back
109
+
110
+ ogre.sight 'player2' # -> Setting target to player2
111
+ ogre.acquire # -> AARGHH!
112
+ ogre.act! # -> Acting @attacking.pursuing
113
+ # -> step step player2
114
+ ogre.reached
115
+ puts ogre.state # -> attacking.fighting
116
+ ogre.act! # -> ~~> player2
117
+ 5.times { ogre.act! } # -> ...
118
+ ogre.enemy_dead # -> Woohoo!
119
+ ogre.act! # -> Acting @coming_back
120
+ # -> step step home
121
+ end
@@ -0,0 +1,2 @@
1
+ require "minitest/autorun"
2
+ require "hifsm"
@@ -0,0 +1,28 @@
1
+ require 'setup_tests'
2
+
3
+ class TestAnyStateEvent < Minitest::Test
4
+ def setup
5
+ @fsm = Hifsm::FSM.define do
6
+ state :off, :initial => true
7
+ state :on
8
+ state :halt
9
+
10
+ event :toggle, :from => :off, :to => :on
11
+ event :toggle, :from => :on, :to => :off
12
+ event :halt, :to => :halt
13
+ end
14
+ @machine = @fsm.new
15
+ end
16
+
17
+ def test_halt_from_off
18
+ @machine.halt
19
+ assert_equal 'halt', @machine.state
20
+ end
21
+
22
+ def test_halt_from_on
23
+ @machine.toggle
24
+ @machine.halt
25
+ assert_equal 'halt', @machine.state
26
+ end
27
+
28
+ end
@@ -0,0 +1,29 @@
1
+ require 'setup_tests'
2
+
3
+ class TestBasicFSM < Minitest::Test
4
+ def setup
5
+ @fsm = Hifsm::FSM.define do
6
+ state :off, :initial => true
7
+ state :on
8
+
9
+ event :toggle, :from => :off, :to => :on
10
+ event :toggle, :from => :on, :to => :off
11
+ end
12
+ @machine = @fsm.new
13
+ end
14
+
15
+ def test_initial_state_is_off
16
+ assert_equal 'off', @machine.state
17
+ end
18
+
19
+ def test_toggle_switches_state_to_on
20
+ @machine.toggle
21
+ assert_equal 'on', @machine.state
22
+ end
23
+
24
+ def test_toggle_twice_switches_state_back_to_off
25
+ @machine.toggle
26
+ @machine.toggle
27
+ assert_equal 'off', @machine.state
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ require 'setup_tests'
2
+
3
+ class TestEventGuard < Minitest::Test
4
+ def setup
5
+ @wall = Struct.new(:stones).new(10)
6
+
7
+ @fsm = Hifsm::FSM.define do
8
+ state :constructed, :initial => true
9
+ state :broken
10
+
11
+ event :break, :from => :constructed, :to => :broken, :guard => proc { stones < 5 }
12
+ end
13
+ @machine = @fsm.new(@wall)
14
+ end
15
+
16
+ def test_cant_break_wall_10_stones_thick
17
+ assert_raises(Hifsm::MissingTransition) do
18
+ @wall.break
19
+ end
20
+ end
21
+
22
+ def test_cant_break_thin_wall
23
+ @wall.stones = 3
24
+ @wall.break
25
+ assert_equal 'broken', @machine.state
26
+ end
27
+
28
+ end
@@ -0,0 +1,44 @@
1
+ require 'setup_tests'
2
+ require 'monster'
3
+
4
+ class TestMonster < Minitest::Test
5
+ def setup
6
+ @monster = Monster.new
7
+ end
8
+
9
+ def test_initial_state_is_idle
10
+ assert_equal 'idle', @monster.state.to_s
11
+ end
12
+
13
+ def test_acting_from_idle_state
14
+ @monster.act!
15
+ pass
16
+ end
17
+
18
+ def test_will_attack_player_on_sight_if_alot_hp
19
+ @monster.sight 'player'
20
+ assert_equal 'attacking.acquiring_target', @monster.state.to_s
21
+ assert_equal 'player', @monster.target
22
+ end
23
+
24
+ def test_will_runaway_on_sight_if_low_hp
25
+ @monster.low_hp = true
26
+ @monster.sight 'player'
27
+ assert_equal 'runaway', @monster.state.to_s
28
+ assert_nil @monster.target
29
+ end
30
+
31
+ def test_will_pursue_player_on_acquire
32
+ @monster.sight 'player'
33
+ @monster.acquire
34
+ assert_equal 'attacking.pursuing', @monster.state.to_s
35
+ end
36
+
37
+ def test_kill_in_middle_of_attack
38
+ @monster.sight 'player'
39
+ @monster.acquire
40
+ @monster.enemy_dead
41
+ assert_equal 'coming_back', @monster.state.to_s
42
+ end
43
+
44
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hifsm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vladimir Meremyanin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: FSM with support for nested states and parameterised events
56
+ email:
57
+ - vladimir@meremyanin.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - hifsm.gemspec
68
+ - lib/hifsm.rb
69
+ - lib/hifsm/callbacks.rb
70
+ - lib/hifsm/event.rb
71
+ - lib/hifsm/fsm.rb
72
+ - lib/hifsm/machine.rb
73
+ - lib/hifsm/state.rb
74
+ - lib/hifsm/version.rb
75
+ - test/minitest_helper.rb
76
+ - test/monster.rb
77
+ - test/setup_tests.rb
78
+ - test/test_any_state_event.rb
79
+ - test/test_basic_fsm.rb
80
+ - test/test_event_guard.rb
81
+ - test/test_monster.rb
82
+ homepage: http://github.com/stiff/hifsm
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.2.2
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Hierarchical state machines in Ruby
106
+ test_files:
107
+ - test/minitest_helper.rb
108
+ - test/monster.rb
109
+ - test/setup_tests.rb
110
+ - test/test_any_state_event.rb
111
+ - test/test_basic_fsm.rb
112
+ - test/test_event_guard.rb
113
+ - test/test_monster.rb