chingu 0.7.6.8 → 0.7.6.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  pkg
3
3
  .gem
4
4
  .git
5
+ .idea
5
6
  coverage
data/chingu.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{chingu}
8
- s.version = "0.7.6.8"
8
+ s.version = "0.7.6.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["ippa"]
12
- s.date = %q{2010-08-15}
12
+ s.date = %q{2010-08-18}
13
13
  s.description = %q{OpenGL accelerated 2D game framework for Ruby. Builds on Gosu (Ruby/C++) which provides all the core functionality. Chingu adds simple yet powerful game states, prettier input handling, deployment safe asset-handling, a basic re-usable game object and stackable game logic.}
14
14
  s.email = %q{ippa@rubylicio.us}
15
15
  s.extra_rdoc_files = [
@@ -138,6 +138,7 @@ Gem::Specification.new do |s|
138
138
  "lib/chingu/particle.rb",
139
139
  "lib/chingu/rect.rb",
140
140
  "lib/chingu/require_all.rb",
141
+ "lib/chingu/simple_menu.rb",
141
142
  "lib/chingu/text.rb",
142
143
  "lib/chingu/traits/animation.rb",
143
144
  "lib/chingu/traits/bounding_box.rb",
@@ -153,7 +154,10 @@ Gem::Specification.new do |s|
153
154
  "lib/chingu/window.rb",
154
155
  "spec/chingu/fpscounter_spec.rb",
155
156
  "spec/chingu/game_object_spec.rb",
157
+ "spec/chingu/helpers/input_client_spec.rb",
158
+ "spec/chingu/helpers/input_dispatcher_spec.rb",
156
159
  "spec/chingu/helpers/options_setter_spec.rb",
160
+ "spec/chingu/images/rect_20x20.png",
157
161
  "spec/chingu/inflector_spec.rb",
158
162
  "spec/chingu/rect_20x20.png",
159
163
  "spec/chingu/text_spec.rb",
@@ -169,6 +173,8 @@ Gem::Specification.new do |s|
169
173
  s.test_files = [
170
174
  "spec/chingu/fpscounter_spec.rb",
171
175
  "spec/chingu/game_object_spec.rb",
176
+ "spec/chingu/helpers/input_client_spec.rb",
177
+ "spec/chingu/helpers/input_dispatcher_spec.rb",
172
178
  "spec/chingu/helpers/options_setter_spec.rb",
173
179
  "spec/chingu/inflector_spec.rb",
174
180
  "spec/chingu/text_spec.rb",
@@ -116,7 +116,7 @@ class Level < Chingu::GameState
116
116
  @player = Player.create
117
117
 
118
118
  #
119
- # The below code can mostly be replaced with the use of method "holding?" in Level#update
119
+ # The below code can mostly be replaced with the use of methods "holding?", "holding_all?" or "holding_any?" in Level#update
120
120
  # Using holding? in update could be good if you need fine grained controll over when input is dispatched.
121
121
  #
122
122
  @player.input = { :holding_left => :move_left,
@@ -138,10 +138,10 @@ class Level < Chingu::GameState
138
138
  #
139
139
  # Another way of checking input
140
140
  #
141
- # @player.move_left if holding?(:left, :a)
142
- # @player.move_right if holding?(:right, :d)
143
- # @player.move_up if holding?(:up, :w)
144
- # @player.move_down if holding?(:down, :s)
141
+ # @player.move_left if holding_any?(:left, :a)
142
+ # @player.move_right if holding_any?(:right, :d)
143
+ # @player.move_up if holding_any?(:up, :w)
144
+ # @player.move_down if holding_any?(:down, :s)
145
145
  # @player.fire if holding?(:space)
146
146
 
147
147
  Bullet.destroy_if {|bullet| bullet.outside_window? }
data/lib/chingu.rb CHANGED
@@ -33,7 +33,7 @@ require_all "#{CHINGU_ROOT}/chingu/traits"
33
33
  require_all "#{CHINGU_ROOT}/chingu"
34
34
 
35
35
  module Chingu
36
- VERSION = "0.7.6.8"
36
+ VERSION = "0.7.6.9"
37
37
 
38
38
  DEBUG_COLOR = Gosu::Color.new(0xFFFF0000)
39
39
  DEBUG_ZORDER = 9999
@@ -100,7 +100,7 @@ module Chingu
100
100
  # Makes it easy to clone a objects x,y,angle etc.
101
101
  #
102
102
  def attributes
103
- [@x, @y, @angle, @center_x, @center_y, @factor_x, @factor_y, @color, @mode, @zorder]
103
+ [@x, @y, @angle, @center_x, @center_y, @factor_x, @factor_y, @color.dup, @mode, @zorder]
104
104
  end
105
105
 
106
106
  #
@@ -63,6 +63,10 @@ module Chingu
63
63
  @game_objects.size
64
64
  end
65
65
 
66
+ def empty?
67
+ @game_objects.empty?
68
+ end
69
+
66
70
  def draw
67
71
  @game_objects.each{ |object| object.visible }.each do |object|
68
72
  object.draw_trait
@@ -53,7 +53,7 @@ module Chingu
53
53
  options = {:draw_grid => true, :snap_to_grid => true, :resize_to_grid => true}.merge(options)
54
54
 
55
55
  @grid = options[:grid] || [8,8]
56
- @grid_color = options[:grid_color] || Color.new(0xaa222222)
56
+ @grid_color = options[:grid_color] || Gosu::Color.new(0xaa222222)
57
57
  @draw_grid = options[:draw_grid]
58
58
  @snap_to_grid = options[:snap_to_grid] # todo
59
59
  @resize_to_grid = options[:resize_to_grid] # todo
@@ -501,7 +501,7 @@ END_OF_STRING
501
501
  end
502
502
 
503
503
  def quit
504
- pop_game_state(:setup => false)
504
+ pop_game_state
505
505
  end
506
506
  def save
507
507
  save_game_objects(:game_objects => editable_game_objects, :file => @file, :classes => @classes)
@@ -654,26 +654,14 @@ END_OF_STRING
654
654
  def create_new_game_object_from(template)
655
655
  game_object = template.class.create(:parent => previous_game_state)
656
656
  game_object.update
657
- #game_object.options[:selected] = true
658
- game_object.options[:created_with_editor] = true
657
+
658
+ # If we don't create it from the toolbar, we're cloning another object
659
+ # When cloning we wan't the cloned objects attributes
660
+ game_object.attributes = template.attributes unless template.options[:toolbar]
659
661
  game_object.x = self.mouse_x
660
662
  game_object.y = self.mouse_y
661
-
662
- unless template.options[:toolbar]
663
- game_object.angle = template.angle
664
- game_object.factor_x = template.factor_x
665
- game_object.factor_y = template.factor_y
666
- game_object.color = template.color.dup
667
- game_object.zorder = template.zorder
668
- game_object.update
669
- else
670
- # Resize the new game object to fit the grid perfectly!
671
- wanted_width = game_object.image.width + @grid[0] - (game_object.image.width % @grid[0])
672
- wanted_height = game_object.image.height + @grid[1] - (game_object.image.height % @grid[1])
673
- game_object.factor_x = wanted_width.to_f / game_object.image.width.to_f
674
- game_object.factor_y = wanted_height.to_f / game_object.image.height.to_f
675
- end
676
-
663
+ game_object.options[:created_with_editor] = true
664
+
677
665
  game_object.options[:mouse_x_offset] = (game_object.x - self.mouse_x) rescue 0
678
666
  game_object.options[:mouse_y_offset] = (game_object.y - self.mouse_y) rescue 0
679
667
 
@@ -44,60 +44,222 @@ module Chingu
44
44
  #
45
45
  #
46
46
  module InputClient
47
+ # The current input handlers. This may contain data which is different to the input set,
48
+ # but it will be equivalent in function [Hash].
49
+ def input
50
+ @input ||= Hash.new { |hash, key| hash[key] = [] }
51
+ end
52
+
53
+ # Is a particular key being held down at this moment?
54
+ #
55
+ # === Parameters
56
+ # +key+:: Chingu key code [Symbol]
57
+ #
58
+ public
59
+ def holding?(key)
60
+ $window.button_down?(Chingu::Input::SYMBOL_TO_CONSTANT[key])
61
+ end
62
+
63
+ # Are any of a list of keys being held down?
64
+ #
65
+ # === Parameters
66
+ # +key+:: Chingu key code [Symbol]
47
67
  #
48
- # Returns true or false depending if any of the given keys are pressed
68
+ # :call-seq:
69
+ # holding_any?(key [, key]*) { } -> true or false
70
+ #
71
+ public
72
+ def holding_any?(*keys)
73
+ keys.any? { |key| holding?(key) }
74
+ end
75
+
76
+ # Are all keys in a list being held down simultaneously?
77
+ #
78
+ # === Parameters
79
+ # +key+:: Chingu key code [Symbol]
80
+ #
81
+ # :call-seq:
82
+ # holding_all?(key [, key]*) { } -> true or false
83
+ #
84
+ public
85
+ def holding_all?(*keys)
86
+ keys.all? { |key| holding?(key) }
87
+ end
88
+
89
+ # Ensures that the key code exists and ensures it has a consistent name.
49
90
  #
50
- # Example:
51
- # @player.go_left if holding?(:left, :a)
91
+ # === Parameters
92
+ # +key+:: Chingu key code [Symbol]
52
93
  #
53
- def holding?(*keys)
54
- Array(keys).each do |key|
55
- return true if $window.button_down?(Chingu::Input::SYMBOL_TO_CONSTANT[key])
94
+ # Returns: Key code [Symbol]
95
+ #
96
+ protected
97
+ def validate_input_key(key)
98
+ raise ArgumentError, "Input must be a Symbol or #{MultiInput}, but received #{key} (#{key.class})" unless key.is_a? Symbol
99
+
100
+ # The base key is the key without a prefix, so we can find it in the lookups.
101
+ base_key = Chingu::Input::SYMBOL_TO_CONSTANT.has_key?(key) ? key : nil
102
+ prefix_used = ""
103
+ ["holding_", "released_"].each do |prefix|
104
+ break if base_key
105
+ if key =~ /^#{prefix}(.*)$/
106
+ base_key = Chingu::Input::SYMBOL_TO_CONSTANT.has_key?($1.to_sym) ? $1.to_sym : nil
107
+ prefix_used = prefix
108
+ end
56
109
  end
57
- return false
110
+
111
+ raise ArgumentError, "No such input as #{key.inspect}" unless base_key
112
+
113
+ # Standardise the symbol used.
114
+ :"#{prefix_used}#{Input::CONSTANT_TO_SYMBOL[Input::SYMBOL_TO_CONSTANT[base_key]].first}"
58
115
  end
59
-
116
+
117
+ # Ensures that the key code exists and ensures it has a consistent name.
60
118
  #
61
- # Input-map writer
119
+ # === Parameters
120
+ # +key+:: Chingu key code [Symbol]
121
+ # +action+:: Action to perform [Method/Proc/String/Symbol/Class/Chingu::GameState]
62
122
  #
63
- def input=(input_map)
64
- @input ||= Hash.new
123
+ # Returns: Key code, action [Array]
124
+ #
125
+ protected
126
+ def validate_input(key, action)
127
+ key = validate_input_key(key)
65
128
 
66
- if input_map.is_a? Array
67
- #
68
- # Un-nest input_map [:left, :right, :space]
69
- # Into: { :left => :left, :right => :right, :space => :space}
70
- #
71
- input_map.each do |symbol|
72
- @input[symbol] = symbol
73
- end
74
- elsif input_map.is_a? Hash
75
- #
76
- # Un-nest input: { [:pad_left, :arrow_left, :a] => :move_left }
77
- # Into: { :pad_left => :move_left, :arrow_left => :move_left, :a => :move_left }
78
- #
79
- input_map.each_pair do |possible_array, action|
80
- if possible_array.is_a? Array
81
- possible_array.each do |symbol|
82
- @input[symbol] = action
83
- end
84
- elsif possible_array.is_a? Symbol
85
- @input[possible_array] = action
129
+ message = case action
130
+ when Method, Proc, Chingu::GameState
131
+ nil
132
+
133
+ when String, Symbol
134
+ "#{self.class} does not have a #{action} method" unless self.respond_to? action
135
+ # Resolve to a method.
136
+ action = method action
137
+
138
+ nil
139
+
140
+ when Class
141
+ if action.ancestors.include? Chingu::GameState
142
+ nil
143
+ else
144
+ "Input action is a class (#{action.class}) not inheriting from Chingu::GameState"
86
145
  end
146
+
147
+ else
148
+ "Input action must be a Method, Proc, String, Symbol, Chingu::GameState or a class inheriting from Chingu::GameState (Received a #{action.class})"
149
+ end
150
+
151
+ raise ArgumentError, "(For input #{key.inspect}) #{message}" if message
152
+
153
+ [key, action]
154
+ end
155
+
156
+ # Add input handlers all at once. This is an alternative to on_input, which sets input handlers individually.
157
+ #
158
+ # === Parameters
159
+ # +input_list+:: [Hash or Array]
160
+ #
161
+ # === Examples
162
+ # As a hash (Will call move_left or move_right methods when keys pressed):
163
+ # add_inputs :left_arrow => :move_left,
164
+ # :right_arrow => :move_right
165
+ #
166
+ # As an array (Will call left_arrow or right_arrow methods when keys pressed):
167
+ # add_inputs :left_arrow, :right_arrow
168
+
169
+ # :call-seq:
170
+ # add_inputs(input [, input]*] -> self
171
+ # add_inputs(input => action [, input => action]*) -> self
172
+ #
173
+ public
174
+ def add_inputs(*input_list)
175
+ raise "Need to provide inputs as either parameters or hashed parameters" if input_list.empty?
176
+ input_list = input_list[0] if input_list[0].is_a? Hash
177
+
178
+ case input_list
179
+ when Array
180
+ #
181
+ # Un-nest input_map [:left, :right, :space]
182
+ # Into: { :left => :left, :right => :right, :space => :space }
183
+ #
184
+ input_list.each { |symbol| on_input(symbol) }
185
+
186
+ when Hash
187
+ #
188
+ # Un-nest input: { [:pad_left, :arrow_left, :a] => :move_left }
189
+ # Into: { :pad_left => :move_left, :arrow_left => :move_left, :a => :move_left }
190
+ #
191
+ input_list.each_pair do |possible_array, action|
192
+ case possible_array
193
+ when Array
194
+ possible_array.each { |symbol| on_input(symbol, action) }
195
+ when Symbol
196
+ on_input(possible_array, action)
197
+ end
87
198
  end
88
199
  end
89
-
90
- if @parent
91
- if (@input == nil || @input == {})
200
+
201
+ self
202
+ end
203
+
204
+ # Backwards compatibility function. Use on_inputs or on_input instead.
205
+ def input=(input_list)
206
+ @input = nil
207
+ input_list.is_a?(Array) ? add_inputs(*input_list) : add_inputs(input_list)
208
+ end
209
+
210
+ # Adds an event handler for a key or any of a list of keys. This is an alternative to input=, which sets all
211
+ # handlers at once.
212
+ #
213
+ # === Parameters
214
+ # +input+:: Chingu input code(s) [Symbol or Array of Symbols]
215
+ # +action+:: [Method/Proc/String/Symbol/Class/Chingu::GameState]
216
+ #
217
+ # Returns: self
218
+ #
219
+ # === Examples
220
+ # Using actions:
221
+ # on_input(:space) # Will call the space method when input detected.
222
+ # on_input(:space, :fire_laser)
223
+ # on_input([:left, :a], method(:go_left))
224
+ #
225
+ # Using blocks:
226
+ # on_input :space do
227
+ # # Fire the laser
228
+ # end
229
+ # on_input [:left, :a] do
230
+ # # Move the player left.
231
+ # end
232
+ #
233
+ # :call-seq:
234
+ # on_input(input) -> self
235
+ # on_input(input) { } -> self
236
+ # on_input(input, action) -> self
237
+ #
238
+ public
239
+ def on_input(input, action = nil, &block)
240
+ raise ArgumentError, "#{self.class}#on_input takes either an input OR an input and action OR an input with a block" if action and block
241
+
242
+ inputs = input.is_a?(Array) ? input : [input] # Can be a single input or an array of them.
243
+ action = block if block
244
+ action = input unless action
245
+
246
+ @input ||= Hash.new { |hash, key| hash[key] = [] }
247
+
248
+ # Ensure that the new input array is reasonable.
249
+ inputs.each do |input|
250
+ standardised_symbol, action = validate_input(input, action)
251
+ @input[standardised_symbol] << action
252
+ end
253
+
254
+ if @parent
255
+ if @input.empty?
92
256
  @parent.remove_input_client(self)
93
257
  else
94
258
  @parent.add_input_client(self)
95
259
  end
96
260
  end
97
- end
98
-
99
- def input
100
- @input
261
+
262
+ self
101
263
  end
102
264
  end
103
265
  end
@@ -21,89 +21,74 @@
21
21
 
22
22
  module Chingu
23
23
  module Helpers
24
-
25
24
  #
26
25
  # Methods for parsing and dispatching Chingus input-maps
27
26
  # Mixed into Chingu::Window and Chingu::GameState
28
27
  #
29
28
  module InputDispatcher
30
29
  attr_reader :input_clients
31
-
30
+
31
+ # Called by input_clients to add themselves from forwarding.
32
32
  def add_input_client(object)
33
33
  @input_clients << object unless @input_clients.include?(object)
34
34
  end
35
-
35
+
36
+ # Called by input_clients to remove themselves from forwarding.
36
37
  def remove_input_client(object)
37
38
  @input_clients.delete(object)
38
39
  end
39
40
 
41
+ # Dispatch button press to one of your clients.
40
42
  def dispatch_button_down(id, object)
41
- return if(object.nil? || object.input.nil?)
42
-
43
- object.input.each do |symbol, action|
44
- if Input::SYMBOL_TO_CONSTANT[symbol] == id
45
- dispatch_action(action, object)
46
- end
43
+ if actions = object.input[Input::CONSTANT_TO_SYMBOL[id].first]
44
+ dispatch_actions(actions)
47
45
  end
48
46
  end
49
-
47
+
48
+ # Dispatch button release to one of your clients.
50
49
  def dispatch_button_up(id, object)
51
- return if object.nil? || object.input.nil?
52
-
53
- object.input.each do |symbol, action|
54
- if symbol.to_s.include? "released_"
55
- symbol = symbol.to_s.sub("released_", "").to_sym
56
- if Input::SYMBOL_TO_CONSTANT[symbol] == id
57
- dispatch_action(action, object)
58
- end
59
- end
50
+ if actions = object.input[:"released_#{Input::CONSTANT_TO_SYMBOL[id].first}"]
51
+ dispatch_actions(actions)
60
52
  end
61
53
  end
62
-
54
+
63
55
  #
64
56
  # Dispatches input-mapper for any given object
65
57
  #
66
58
  def dispatch_input_for(object, prefix = "holding_")
67
- return if object.nil? || object.input.nil?
68
-
69
- object.input.each do |symbol, action|
70
- if symbol.to_s.include? prefix
71
- symbol = symbol.to_s.sub(prefix, "").to_sym
72
- if $window.button_down?(Input::SYMBOL_TO_CONSTANT[symbol])
73
- dispatch_action(action, object)
74
- end
59
+ pattern = /^#{prefix}/
60
+ object.input.each do |symbol, actions|
61
+ if symbol =~ pattern and $window.button_down?(Input::SYMBOL_TO_CONSTANT[$'.to_sym])
62
+ dispatch_actions(actions)
75
63
  end
76
64
  end
77
65
  end
78
-
66
+
79
67
  #
80
68
  # For a given object, dispatch "action".
81
- # An action can be:
69
+ # An action can be an array containing any of:
82
70
  #
83
- # * Symbol (:p, :space) or String, translates into a method-call
84
71
  # * Proc/Lambda or Method, call() it
85
72
  # * GameState-instance, push it on top of stack
86
73
  # * GameState-inherited class, create a new instance, cache it and push it on top of stack
87
74
  #
88
- def dispatch_action(action, object)
89
- #puts "Dispatch Action: #{action} - Objects class: #{object.class.to_s}"
90
-
91
- case action
92
- when Symbol, String
93
- object.send(action)
94
- when Proc, Method
95
- action[]
96
- when Chingu::GameState
97
- push_game_state(action)
98
- when Class
99
- if action.ancestors.include?(Chingu::GameState)
100
- push_game_state(action)
75
+ protected
76
+ def dispatch_actions(actions)
77
+ # puts "Dispatch Action: #{action} - Objects class: #{object.class.to_s}"
78
+ actions.each do |action|
79
+ case action
80
+ when Proc, Method
81
+ action[]
82
+ when Chingu::GameState, Class
83
+ # Don't need to check if the Class is a GameState, since that is already checked.
84
+ push_game_state(action)
85
+ else
86
+ raise ArgumentError, "Unexpected action #{action}"
101
87
  end
102
- else
103
- # TODO possibly raise an error? This ought to be handled when the input is specified in the first place.
104
88
  end
89
+ # Other types will already have been resolved to one of these, so no need for checking.
105
90
  end
106
91
  end
107
-
92
+
108
93
  end
109
94
  end
@@ -0,0 +1,119 @@
1
+ #--
2
+ #
3
+ # Chingu -- OpenGL accelerated 2D game framework for Ruby
4
+ # Copyright (C) 2009 ippa / ippa@rubylicio.us
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+
22
+ module Chingu
23
+ #
24
+ #
25
+ #
26
+ class SimpleMenu < BasicGameObject
27
+ include Chingu::Helpers::InputClient
28
+ attr_accessor :menu_items
29
+
30
+ def initialize(options = {})
31
+ super
32
+
33
+ @menu_items = options.delete(:menu_items)# || {"Exit" => :exit}
34
+
35
+ @x = options.delete(:x) || $window.width/2
36
+ @y = options.delete(:x) || 100
37
+ @spacing = options.delete(:x) || 100
38
+ @items = []
39
+
40
+ y = @spacing
41
+ menu_items.each do |key, value|
42
+
43
+ item = if key.is_a? String
44
+ Text.new(key, options.dup)
45
+ elsif key.is_a? Image
46
+ GameObject.new(options.merge!(:image => key))
47
+ elsif key.is_a? GameObject
48
+ menu_item.options.merge!(options.dup)
49
+ menu_item
50
+ end
51
+
52
+ item.options[:on_select] = method(:on_select)
53
+ item.options[:on_deselect] = method(:on_deselect)
54
+ item.options[:action] = value
55
+
56
+ item.rotation_center = :center_center
57
+ item.x = @x
58
+ item.y = y
59
+ y += item.height + @spacing
60
+ @items << item
61
+ end
62
+ @selected = options[:selected] || 0
63
+ step(0)
64
+
65
+ self.input = {:up => lambda{step(-1)}, :down => lambda{step(1)}, [:return, :space, :right] => :select}
66
+ end
67
+
68
+ def step(value)
69
+ selected.options[:on_deselect].call(selected)
70
+ @selected += value
71
+ @selected = @items.count-1 if @selected < 0
72
+ @selected = 0 if @selected == @items.count
73
+ selected.options[:on_select].call(selected)
74
+ end
75
+
76
+ def select
77
+ dispatch_action(selected.options[:action], self.parent)
78
+ end
79
+
80
+ def selected
81
+ @items[@selected]
82
+ end
83
+
84
+ def on_deselect(object)
85
+ object.color = Color::WHITE
86
+ end
87
+
88
+ def on_select(object)
89
+ object.color = Color::RED
90
+ end
91
+
92
+ def draw
93
+ @items.each { |item| item.draw }
94
+ end
95
+
96
+ private
97
+
98
+ #
99
+ # TODO - DRY this up with input dispatcher somehow
100
+ #
101
+ def dispatch_action(action, object)
102
+ case action
103
+ when Symbol, String
104
+ object.send(action)
105
+ when Proc, Method
106
+ action[]
107
+ when Chingu::GameState
108
+ push_game_state(action)
109
+ when Class
110
+ if action.ancestors.include?(Chingu::GameState)
111
+ push_game_state(action)
112
+ end
113
+ else
114
+ # TODO possibly raise an error? This ought to be handled when the input is specified in the first place.
115
+ end
116
+ end
117
+ end
118
+ end
119
+
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  module Chingu
4
4
 
5
5
  describe GameObject do
6
-
6
+
7
7
  before(:all) do
8
8
  @game = Chingu::Window.new
9
9
  end
@@ -59,30 +59,28 @@ module Chingu
59
59
  end
60
60
 
61
61
  describe "GameObject with an image" do
62
- before do
63
- p Image.autoload_dirs
64
- subject { GameObject.new(:image => "rect_20x20.png") }
65
- end
66
-
62
+
63
+ let(:game_object) { GameObject.new(:image => "rect_20x20.png") }
64
+
67
65
  it "should have width,height & size" do
68
- subject.height.should == 20
69
- subject.width.should == 20
70
- subject.size.should == [20,20]
66
+ game_object.height.should == 20
67
+ game_object.width.should == 20
68
+ game_object.size.should == [20,20]
71
69
  end
72
70
 
73
71
  it "should adapt width,height & size to scaling" do
74
- subject.factor = 2
75
- subject.height.should == 40
76
- subject.width.should == 40
77
- subject.size.should == [40,40]
72
+ game_object.factor = 2
73
+ game_object.height.should == 40
74
+ game_object.width.should == 40
75
+ game_object.size.should == [40,40]
78
76
  end
79
77
 
80
78
  it "should adapt factor_x/factor_y to new size" do
81
- subject.size = [10,40] # half the width, double the height
82
- subject.height.should == 10
83
- subject.width.should == 40
84
- subject.factor_x.should == 0.5
85
- subject.factor_y.should == 2
79
+ game_object.size = [10,40] # half the width, double the height
80
+ game_object.width.should == 10
81
+ game_object.height.should == 40
82
+ game_object.factor_x.should == 0.5
83
+ game_object.factor_y.should == 2
86
84
  end
87
85
 
88
86
  end
@@ -93,4 +91,4 @@ module Chingu
93
91
 
94
92
  end
95
93
 
96
- end
94
+ end
@@ -0,0 +1,180 @@
1
+ require "spec_helper"
2
+
3
+ module Chingu
4
+ describe Helpers::InputClient do
5
+ before :each do
6
+ $window = mock Gosu::Window
7
+ $window.stub!(:button_down?).and_return(false)
8
+
9
+ @subject = Object.new.extend described_class
10
+ @subject.stub!(:handler1).and_return(nil)
11
+ @handler1 = @subject.method :handler1
12
+ @subject.stub!(:handler2).and_return(nil)
13
+ @handler2 = @subject.method :handler2
14
+ end
15
+
16
+ describe "#holding?" do
17
+ it "should be true if that key is being held down" do
18
+ $window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(true)
19
+ @subject.holding?(:space).should be_true
20
+ end
21
+
22
+ it "should be false if that key is being held down" do
23
+ $window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(false)
24
+ @subject.holding?(:space).should be_false
25
+ end
26
+ end
27
+
28
+ describe "#holding_all?" do
29
+ it "should be true if all of those keys are being held down" do
30
+ $window.should_receive(:button_down?).with(Gosu::KbSpace).and_return(true)
31
+ $window.should_receive(:button_down?).with(Gosu::KbA).and_return(true)
32
+ @subject.holding_all?(:space, :a).should be_true
33
+ end
34
+
35
+ it "should be false if all of those keys are not being held down" do
36
+ @subject.holding_all?(:space, :a).should be_false
37
+ end
38
+
39
+ it "should be false if some of those keys are not being held down" do
40
+ $window.stub!(:button_down?).with(Gosu::KbSpace).and_return(true)
41
+ @subject.holding_all?(:space, :a).should be_false
42
+ end
43
+ end
44
+
45
+ describe "#holding_any?" do
46
+ it "should be true if any of those keys are being held down" do
47
+ $window.stub!(:button_down?).with(Gosu::KbA).and_return(true)
48
+ $window.stub!(:button_down?).with(Gosu::KbSpace).and_return(true)
49
+ @subject.holding_any?(:space, :a).should be_true
50
+ end
51
+
52
+ it "should be false if none of those keys are being held down" do
53
+ @subject.holding_any?(:space, :a).should be_false
54
+ end
55
+ end
56
+
57
+ describe "#input" do
58
+ it "should initially be an empty hash" do
59
+ @subject.input.should == {}
60
+ end
61
+ end
62
+
63
+ describe "#input=" do
64
+ it "should set the input hash" do
65
+ @subject.input = { :a => GameStates::Pause, :b => GameState }
66
+ @subject.input.should == { :a => [GameStates::Pause], :b => [GameState] }
67
+ end
68
+
69
+ it "should set the input array" do
70
+ @subject.stub!(:a)
71
+ @subject.stub!(:b)
72
+ @subject.input = [:a, :b]
73
+ @subject.input.should == { :a => [@subject.method(:a)], :b => [@subject.method(:b)] }
74
+ end
75
+ end
76
+
77
+ describe "#add_inputs" do
78
+ it "should set the input hash" do
79
+ @subject.add_inputs :a => GameStates::Pause, :b => GameState
80
+ @subject.input.should == { :a => [GameStates::Pause], :b => [GameState] }
81
+ end
82
+
83
+ it "should set the input array" do
84
+ @subject.stub!(:a)
85
+ @subject.stub!(:b)
86
+ @subject.add_inputs :a, :b
87
+ @subject.input.should == { :a => [@subject.method(:a)], :b => [@subject.method(:b)] }
88
+ end
89
+
90
+ # Not bothering with all the options, since it is tested fully, though indirectly, in #on_input already.
91
+ # I suspect it might be better to put the logic in on_input rather than in input too. Mmm.
92
+ it "should do other stuff"
93
+ end
94
+
95
+ describe "#on_input" do
96
+ it "should add a handler that is given as a block" do
97
+ block = lambda { }
98
+ @subject.on_input :a, &block
99
+ @subject.input.should == { :a => [block] }
100
+ end
101
+
102
+ it "should add a handler that is given as a method" do
103
+ @subject.on_input :a, @handler1
104
+ @subject.input.should == { :a => [@handler1] }
105
+ end
106
+
107
+ it "should add a handler that is given as a proc" do
108
+ proc = lambda { puts "Hello" }
109
+ @subject.on_input :a, proc
110
+ @subject.input.should == { :a => [proc] }
111
+ end
112
+
113
+ [:handler1, "handler1"].each do |handler|
114
+ it "should add a handler that is given as a #{handler.class}" do
115
+ @subject.on_input :a, handler
116
+ @subject.input.should == { :a => [@handler1] }
117
+ end
118
+ end
119
+
120
+ it "should add multiple handlers for the same event" do
121
+ @subject.on_input :a, @handler1
122
+ @subject.on_input :a, @handler2
123
+ @subject.input.should == { :a => [@handler1, @handler2] }
124
+ end
125
+
126
+ it "should automatically handle to a method if only the input is given" do
127
+ @subject.stub!(:a)
128
+ @subject.on_input :a
129
+ @subject.input.should == { :a => [ @subject.method(:a) ] }
130
+ end
131
+
132
+ it "should add multiple handlers for the same event, even if given different key names" do
133
+ @subject.on_input :left, @handler1
134
+ @subject.on_input :left_arrow, @handler2
135
+ @subject.input.should == { :left_arrow => [@handler1, @handler2] }
136
+ end
137
+
138
+ it "should add a handler that is given as a Chingu::GameState class" do
139
+ @subject.on_input :a, GameStates::Pause
140
+ @subject.input.should == { :a => [GameStates::Pause] }
141
+ end
142
+
143
+ it "should add a handler that is given as a Chingu::GameState instance" do
144
+ state = GameState.new
145
+ @subject.on_input :a, state
146
+ @subject.input.should == { :a => [state] }
147
+ end
148
+
149
+ it "should raise an error if given an unknown key" do
150
+ lambda { @subject.on_input :aardvark, @handler1 }.should raise_error ArgumentError
151
+ end
152
+
153
+ it "should raise an error if given an incorrect action" do
154
+ lambda { @subject.on_input :a, 47 }.should raise_error ArgumentError
155
+ end
156
+
157
+ it "should add a new handler if one already exists for that input" do
158
+ @subject.on_input :a, @handler1
159
+ @subject.on_input :b, @handler2
160
+ @subject.input.should == { :a => [@handler1], :b => [@handler2] }
161
+ end
162
+
163
+ it "should consider all key synonyms the same" do
164
+ @subject.on_input :left, @handler1
165
+ @subject.on_input :left_arrow, @handler2
166
+ @subject.input.should == { :left_arrow => [@handler1, @handler2] }
167
+ end
168
+
169
+ it "should split up and standardise key arrays" do
170
+ @subject.on_input([:space, :left], @handler1)
171
+ @subject.input.should == { :" " => [@handler1], :left_arrow => [@handler1] }
172
+ end
173
+
174
+ it "should raise an error if both an action and a hander are given" do
175
+ block = lambda { p "hello world" }
176
+ lambda { @subject.on_input :a, "Hello", &block }.should raise_error ArgumentError
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ describe Chingu::Helpers::InputDispatcher do
4
+ before :each do
5
+ @subject = Object.new.extend described_class
6
+ @client = Object.new
7
+ end
8
+
9
+ it "should respond to methods" do
10
+ @subject.should respond_to :input_clients
11
+ @subject.should respond_to :add_input_client
12
+ @subject.should respond_to :remove_input_client
13
+ end
14
+
15
+ {"button_down" => :a, "button_up" => :released_a}.each_pair do |event, key|
16
+ describe "#dispatch_#{event}" do
17
+ it "should dispatch key event if key is handled" do
18
+ @client.should_receive(:handler).with(no_args)
19
+ @client.stub!(:input).with(no_args).and_return({ key => [@client.method(:handler)] })
20
+ @subject.send("dispatch_#{event}", Gosu::KbA, @client)
21
+ end
22
+
23
+ it "should not dispatch key event if key is not handled" do
24
+ @client.stub!(:input).with(no_args).and_return({})
25
+ @subject.send("dispatch_#{event}", Gosu::KbA, @client)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#dispatch_input_for" do
31
+ before :each do
32
+ $window = mock Chingu::Window
33
+ $window.stub!(:button_down?).and_return(false)
34
+ end
35
+
36
+ it "should dispatch if a key is being held" do
37
+ @client.should_receive(:handler).with(no_args)
38
+ $window.stub!(:button_down?).with(Gosu::KbA).and_return(true)
39
+ @client.stub!(:input).with(no_args).and_return({:holding_a => [@client.method(:handler)]})
40
+ @subject.dispatch_input_for(@client)
41
+ end
42
+
43
+ it "should do nothing if a key is not held" do
44
+ @client.stub!(:input).with(no_args).and_return({:holding_a => [lambda { raise "Shouldn't handle input!"}]})
45
+ @subject.dispatch_input_for(@client)
46
+ end
47
+ end
48
+
49
+
50
+ describe "#dispatch_actions" do
51
+ it "should call a method" do
52
+ @client.should_receive(:handler).with(no_args)
53
+ @subject.send(:dispatch_actions, [@client.method(:handler)])
54
+ end
55
+
56
+ it "should call a proc" do
57
+ @client.should_receive(:handler)
58
+ @subject.send(:dispatch_actions, [lambda { @client.handler }])
59
+ end
60
+
61
+ it "should push a game-state instance" do
62
+ state = Chingu::GameState.new
63
+ @subject.should_receive(:push_game_state).with(state)
64
+ @subject.send(:dispatch_actions, [state])
65
+ end
66
+
67
+ it "should push a game-state class" do
68
+ @subject.should_receive(:push_game_state).with(Chingu::GameState)
69
+ @subject.send(:dispatch_actions, [Chingu::GameState])
70
+ end
71
+
72
+ it "should call multiple actions if more have been set" do
73
+ other = Object.new
74
+ other.should_receive(:handler).with(no_args)
75
+ @client.should_receive(:handler).with(no_args)
76
+ @subject.send(:dispatch_actions, [@client.method(:handler), other.method(:handler)])
77
+ end
78
+
79
+ # Note, doesn't check if a passed class is incorrect. Life is too short.
80
+ it "should raise an error with unexpected data" do
81
+ lambda { @subject.send(:dispatch_actions, [12]) }.should raise_error ArgumentError
82
+ end
83
+ end
84
+ end
@@ -16,7 +16,7 @@ module Chingu
16
16
  module Helpers
17
17
  describe OptionsSetter do
18
18
 
19
- describe "using without defaults" do
19
+ context "using without defaults" do
20
20
  before do
21
21
  @car = Car.new(:angle => 44)
22
22
  end
Binary file
data/spec/spec_helper.rb CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
5
  # encoding: utf-8
6
6
  require 'rubygems'
7
7
  require 'rspec'
8
- require 'require_all'
8
+ require 'chingu/require_all'
9
9
 
10
10
  require 'chingu'
11
11
  require 'chingu/helpers/options_setter'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chingu
3
3
  version: !ruby/object:Gem::Version
4
- hash: 127
4
+ hash: 125
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
9
  - 6
10
- - 8
11
- version: 0.7.6.8
10
+ - 9
11
+ version: 0.7.6.9
12
12
  platform: ruby
13
13
  authors:
14
14
  - ippa
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-08-15 00:00:00 +02:00
19
+ date: 2010-08-18 00:00:00 +02:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -43,7 +43,7 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- hash: -957518095
46
+ hash: -138847361
47
47
  segments:
48
48
  - 2
49
49
  - 0
@@ -212,6 +212,7 @@ files:
212
212
  - lib/chingu/particle.rb
213
213
  - lib/chingu/rect.rb
214
214
  - lib/chingu/require_all.rb
215
+ - lib/chingu/simple_menu.rb
215
216
  - lib/chingu/text.rb
216
217
  - lib/chingu/traits/animation.rb
217
218
  - lib/chingu/traits/bounding_box.rb
@@ -227,7 +228,10 @@ files:
227
228
  - lib/chingu/window.rb
228
229
  - spec/chingu/fpscounter_spec.rb
229
230
  - spec/chingu/game_object_spec.rb
231
+ - spec/chingu/helpers/input_client_spec.rb
232
+ - spec/chingu/helpers/input_dispatcher_spec.rb
230
233
  - spec/chingu/helpers/options_setter_spec.rb
234
+ - spec/chingu/images/rect_20x20.png
231
235
  - spec/chingu/inflector_spec.rb
232
236
  - spec/chingu/rect_20x20.png
233
237
  - spec/chingu/text_spec.rb
@@ -270,6 +274,8 @@ summary: OpenGL accelerated 2D game framework for Ruby
270
274
  test_files:
271
275
  - spec/chingu/fpscounter_spec.rb
272
276
  - spec/chingu/game_object_spec.rb
277
+ - spec/chingu/helpers/input_client_spec.rb
278
+ - spec/chingu/helpers/input_dispatcher_spec.rb
273
279
  - spec/chingu/helpers/options_setter_spec.rb
274
280
  - spec/chingu/inflector_spec.rb
275
281
  - spec/chingu/text_spec.rb