hifsm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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