rubygame 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,454 @@
1
+ #--
2
+ # Rubygame -- Ruby code and bindings to SDL to facilitate game creation
3
+ # Copyright (C) 2004-2008 John Croisant
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ #++
19
+
20
+ require 'rubygame/event_hook'
21
+
22
+
23
+ # EventHandler provides a simple, extensible system for
24
+ # hook-based event handling.
25
+ #
26
+ # An EventHandler holds a list of EventHook objects. When
27
+ # the EventHandler receives a new event (passed to #handle),
28
+ # it tests the event against each EventHook. If the event
29
+ # matches the EventHook, the EventHandler passes the event to
30
+ # the EventHook to perform an action (such as calling a
31
+ # method or executing a block).
32
+ #
33
+ # Although the EventHandler and EventHook classes are very
34
+ # simple in themselves, they can be used as building blocks
35
+ # to create flexible and complex systems, whatever is needed
36
+ # by your application.
37
+ #
38
+ # Here are a few ways you could use EventHandler:
39
+ #
40
+ # * One central EventHandler with EventHooks to perform
41
+ # all types of actions. This is good for simple apps.
42
+ #
43
+ # * Multiple EventHandlers, one for each category of
44
+ # event. For example, one for keyboard input, one for
45
+ # mouse input, one for game logic events, etc.
46
+ #
47
+ # * An EventHandler in every game object (using the
48
+ # HasEventHandler mixin module), being fed events from
49
+ # a central EventHandler.
50
+ #
51
+ # You can also extend the possibilities of EventHandler and
52
+ # EventHook by creating your own event trigger and action
53
+ # classes. See EventHook for more information.
54
+ #
55
+ class Rubygame::EventHandler
56
+
57
+ # call-seq:
58
+ # EventHandler.new { |handler| ... } -> new_handler
59
+ #
60
+ # Create a new EventHandler. The optional block can be used
61
+ # for further initializing the EventHandler.
62
+ #
63
+ def initialize(&block)
64
+ @hooks = []
65
+ yield self if block_given?
66
+ end
67
+
68
+ attr_accessor :hooks
69
+
70
+ # call-seq:
71
+ # append_hook( hook ) -> hook
72
+ # append_hook( description ) -> hook
73
+ #
74
+ # Puts an EventHook at the bottom of the stack (it will be handled last).
75
+ # If the EventHandler already has that hook, it is moved to the bottom.
76
+ # See EventHook and #handle.
77
+ #
78
+ # This method has two distinct forms. The first form adds an existing Hook
79
+ # instance; the second form constructs a new EventHook instance and adds it.
80
+ #
81
+ # hook:: the hook to add. (EventHook, for first form only)
82
+ #
83
+ # description:: a Hash to initialize a new EventHook.
84
+ # (Hash, for second form only)
85
+ #
86
+ # Returns:: the hook that was added. (EventHook)
87
+ #
88
+ # Contrast this method with #prepend, which puts the EventHook at
89
+ # the top of the stack.
90
+ #
91
+ def append_hook( hook )
92
+ hook = EventHook.new( hook ) if hook.kind_of?( Hash )
93
+ @hooks = (@hooks - [hook]) | [hook]
94
+ return hook
95
+ end
96
+
97
+ # call-seq:
98
+ # prepend_hook( hook ) -> hook
99
+ # prepend_hook( description ) -> hook
100
+ #
101
+ # Exactly like #append_hook, except that the EventHook is put at the
102
+ # top of the stack (it will be handled first).
103
+ # If the EventHandler already has that hook, it is moved to the top.
104
+ #
105
+ def prepend_hook( hook )
106
+ hook = EventHook.new( hook ) if hook.kind_of?( Hash )
107
+ @hooks = [hook] | @hooks
108
+ return hook
109
+ end
110
+
111
+ # call-seq:
112
+ # handle( event ) -> nil
113
+ #
114
+ # Triggers every hook in the stack which matches the given event.
115
+ # See EventHook.
116
+ #
117
+ # If one of the matching hooks has @consumes enabled, no hooks
118
+ # after it will receive that event. (Example use: a mouse click that
119
+ # only affects the top-most object it hits, not any below it.)
120
+ #
121
+ # event: the event to handle. (Object, required)
122
+ #
123
+ # Returns:: nil.
124
+ #
125
+ def handle( event )
126
+ matching_hooks = @hooks.select { |hook| hook.match?( event ) }
127
+
128
+ catch :event_consumed do
129
+ matching_hooks.each do |hook|
130
+ hook.perform( event )
131
+ throw :event_consumed if hook.consumes
132
+ end
133
+ end
134
+
135
+ return nil
136
+ end
137
+
138
+ # Returns true if the given EventHook instance is on the stack.
139
+ def has_hook?( hook )
140
+ @hooks.include?( hook )
141
+ end
142
+
143
+ # Removes the given EventHook instance from the stack, if it exists
144
+ # on the stack.
145
+ #
146
+ # Returns:: the hook that was removed, or nil if the hook did not
147
+ # exist on the stack.
148
+ #
149
+ # NOTE: You must pass the exact EventHook instance to remove it!
150
+ # Passing another EventHook that is "similar" will not work.
151
+ # So, you should store a reference to the hook when it is returned
152
+ # by #append_hook or #prepend_hook.
153
+ #
154
+ def remove_hook( hook )
155
+ @hooks.delete( hook )
156
+ end
157
+
158
+ end
159
+
160
+
161
+
162
+ # HasEventHandler is a mixin module to conveniently integrate
163
+ # EventHandler into any class, allowing instances of the class
164
+ # to hold event hooks and handle incoming events.
165
+ #
166
+ # To use HasEventHandler, you simply 'include' it in your class:
167
+ #
168
+ # class Player
169
+ # include Rubygame::EventHandler::HasEventHandler
170
+ #
171
+ # # ... the rest of your class ...
172
+ #
173
+ # end
174
+ #
175
+ # You can then use all of the functionality of HasEventHandler.
176
+ #
177
+ # HasEventHandler provides several methods for adding new
178
+ # event hooks to the object. The two basic methods for that are
179
+ # #append_hook and #prepend_hook. The #make_magic_hooks method can
180
+ # create multiple hooks very simply and conveniently.
181
+ #
182
+ # HasEventHandler also defines the #handle method, which accepts
183
+ # an event and gives it to the object's event handler. This is
184
+ # the recommended way to make the object process an event.
185
+ #
186
+ module Rubygame::EventHandler::HasEventHandler
187
+
188
+ # Appends a new hook to the end of the list. If the hook does
189
+ # not have an owner, the owner is set to this object before
190
+ # appending.
191
+ #
192
+ # hook:: the hook to append.
193
+ # (EventHook or Hash description, required)
194
+ #
195
+ # See also EventHandler#append_hook.
196
+ #
197
+ # Example:
198
+ #
199
+ # # Create and append new hook from a description:
200
+ # trigger = KeyPressedTrigger.new(:space)
201
+ # action = MethodAction.new(:jump)
202
+ # player.append_hook( :trigger => trigger, :action => action )
203
+ #
204
+ # # You can also give it an EventHook instance, if you want.
205
+ # hook = EventHook.new( :trigger => trigger, :action => action )
206
+ # player.append_hook( hook )
207
+ #
208
+ def append_hook( hook )
209
+ hook = _prepare_hook( hook )
210
+ @event_handler.append_hook( hook )
211
+ rescue NoMethodError
212
+ _make_event_handler
213
+ retry
214
+ end
215
+
216
+ # Passes the given event to the object's event handler.
217
+ def handle( event )
218
+ @event_handler.handle( event )
219
+ rescue NoMethodError
220
+ _make_event_handler
221
+ retry
222
+ end
223
+
224
+ # Returns true if the object's event handler includes the given
225
+ # EventHook instance.
226
+ def has_hook?( hook )
227
+ @event_handler.has_hook?( hook )
228
+ rescue NoMethodError
229
+ _make_event_handler
230
+ retry
231
+ end
232
+
233
+
234
+ # Convenience method for creating and appending hooks easily.
235
+ # It takes a Hash of {trigger_seed => action_seed} pairs, and
236
+ # creates and appends a new EventHook for each pair.
237
+ #
238
+ # Returns:: an Array of the EventHook instances that were
239
+ # created and appended.
240
+ #
241
+ # May raise:: ArgumentError, if an object doesn't match any
242
+ # conversion rules.
243
+ #
244
+ # Trigger and action can be symbols, classes, or other types of
245
+ # object. The method uses simple rules to convert the "seed"
246
+ # objects into appropriate event triggers or event actions.
247
+ #
248
+ # By default, triggers are converted according to these rules:
249
+ #
250
+ # * Symbols starting with "mouse" become a MouseClickTrigger.
251
+ # * Keyboard symbols become a KeyPressTrigger.
252
+ # * Classes become an InstanceOfTrigger.
253
+ # * Objects with a #match? method are duplicated and used
254
+ # as the trigger without being converted.
255
+ #
256
+ # By default, actions are converted according to these rules:
257
+ #
258
+ # * Symbols become a MethodAction.
259
+ # * Proc and Method instances become a BlockAction.
260
+ # * Objects with a #perform method are duplicated and used
261
+ # as the action without being converted.
262
+ #
263
+ # This method raises ArgumentError if an object doesn't match
264
+ # any of the conversion rules.
265
+ #
266
+ # You can define your own custom conversion rules by overriding
267
+ # the private methods #_make_magic_trigger and #make_magic_action
268
+ # in your class.
269
+ #
270
+ # NOTE: Additional default rules may be added in the future, but
271
+ # objects which match the existing rules will continue to match
272
+ # them. However, objects which are invalid in one version might
273
+ # become valid in future versions, if a new rule is added. So, you
274
+ # should never depend on ArgumentError being raised for a paricular
275
+ # object!
276
+ #
277
+ # Example:
278
+ #
279
+ # died_action = proc { |owner, event|
280
+ # owner.say "Blargh, I'm dead!" if event.who_died == owner
281
+ # }
282
+ #
283
+ # player.make_magic_hooks( :space => :jump,
284
+ # :left => :move_left,
285
+ # :right => :move_right,
286
+ # :mouse_left => :shoot,
287
+ # DiedEvent => died_action )
288
+ #
289
+ def make_magic_hooks( hash )
290
+ hash.collect do |trigger, action|
291
+ append_hook( :trigger => _make_magic_trigger( trigger ),
292
+ :action => _make_magic_action( action ))
293
+ end
294
+ end
295
+
296
+ # Exactly like #append_hook, except that the hook is put at the
297
+ # top of the stack (it will be handled first).
298
+ #
299
+ # See also EventHandler#prepend_hook.
300
+ #
301
+ def prepend_hook( hook )
302
+ hook = _prepare_hook( hook )
303
+ @event_handler.prepend_hook( hook )
304
+ rescue NoMethodError
305
+ _make_event_handler
306
+ retry
307
+ end
308
+
309
+ # Remove the given EventHook instance from the stack, if it
310
+ # exists on the stack.
311
+ # See EventHandler#remove_hook for details and restrictions.
312
+ #
313
+ # Returns:: the hook that was removed, or nil if the hook did not
314
+ # exist on the stack.
315
+ #
316
+ def remove_hook( hook )
317
+ @event_handler.remove_hook( hook )
318
+ rescue NoMethodError
319
+ _make_event_handler
320
+ retry
321
+ end
322
+
323
+ private
324
+
325
+ # Sets @event_handler to a new EventHandler if needed.
326
+ def _make_event_handler
327
+ @event_handler = EventHandler.new() if @event_handler.nil?
328
+ end
329
+
330
+ # This method is called by #make_magic_hooks to convert an
331
+ # object into an event action instance. For example, when
332
+ # this method is given a Proc, it creates and returns a
333
+ # BlockAction using that Proc. See #make_magic_hooks for
334
+ # information about how other objects are converted.
335
+ #
336
+ # You can override this method in your own classes to
337
+ # define your own custom conversion rules. Example:
338
+ #
339
+ # class Player
340
+ # include Rubygame::EventHandler::HasEventHandler
341
+ #
342
+ # private
343
+ #
344
+ # def _make_magic_action( action )
345
+ # if( action == :move_left )
346
+ # return BlockAction.new { |owner, event|
347
+ # owner.move_by( [-1, 0] )
348
+ # }
349
+ # else
350
+ # super
351
+ # end
352
+ # end
353
+ #
354
+ # end
355
+ #
356
+ #
357
+ # Returns:: an event action instance
358
+ #
359
+ # May raise:: ArgumentError, if the given object does not
360
+ # match any of the conversion rules.
361
+ #
362
+ def _make_magic_action( action )
363
+ case action
364
+
365
+ when Symbol
366
+ EventActions::MethodAction.new(action)
367
+
368
+ when Proc, Method
369
+ EventActions::BlockAction.new(&action)
370
+
371
+ else
372
+ if action.respond_to? :perform
373
+ action.dup
374
+ else
375
+ raise( ArgumentError,
376
+ "invalid action '#{action.inspect}'. " +\
377
+ "See HasEventHandler#make_magic_hooks docs for " +\
378
+ "allowed action types." )
379
+ end
380
+ end
381
+ end
382
+
383
+
384
+ # This method is called by #make_magic_hooks to convert an
385
+ # object into an event trigger instance. For example, when
386
+ # this method is given the symbol :mouse_left, it creates
387
+ # and returns a MousePressTrigger that matches :mouse_left.
388
+ # See #make_magic_hooks for information about how other objects
389
+ # are converted.
390
+ #
391
+ # You can override this method in your own classes to
392
+ # define your own custom conversion rules. Example:
393
+ #
394
+ # class Player
395
+ # include Rubygame::EventHandler::HasEventHandler
396
+ #
397
+ # private
398
+ #
399
+ # def _make_magic_trigger( trigger )
400
+ # if( trigger == :game_over )
401
+ # return GameOverTrigger.new()
402
+ # else
403
+ # super
404
+ # end
405
+ # end
406
+ #
407
+ # end
408
+ #
409
+ #
410
+ # Returns:: an event trigger instance
411
+ #
412
+ # May raise:: ArgumentError, if the given object does not
413
+ # match any of the conversion rules.
414
+ #
415
+ def _make_magic_trigger( trigger )
416
+ case trigger
417
+
418
+ when Symbol
419
+ case(trigger.to_s)
420
+ when /mouse/
421
+ EventTriggers::MousePressTrigger.new(trigger)
422
+ else
423
+ EventTriggers::KeyPressTrigger.new(trigger)
424
+ end
425
+
426
+ when Class
427
+ EventTriggers::InstanceOfTrigger.new(trigger)
428
+
429
+ else
430
+ if trigger.respond_to? :match?
431
+ trigger.dup
432
+ else
433
+ raise( ArgumentError,
434
+ "invalid trigger '#{trigger.inspect}'. " +\
435
+ "See HasEventHandler#make_magic_hooks docs for " +\
436
+ "allowed trigger types." )
437
+ end
438
+ end
439
+ end
440
+
441
+
442
+ def _prepare_hook( hook )
443
+ if( hook.kind_of? Hash )
444
+ hook = EventHook.new( {:owner => self}.merge(hook) )
445
+ end
446
+
447
+ if( hook.owner == nil )
448
+ hook = hook.dup
449
+ hook.owner = self
450
+ end
451
+
452
+ return hook
453
+ end
454
+ end