chingu 0.7.6.8 → 0.7.6.9

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