chingu 0.9rc7 → 0.9rc8

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.
@@ -63,7 +63,11 @@ module Chingu
63
63
  objects.each do |object|
64
64
  object.each_pair do |klassname, attributes|
65
65
  begin
66
- klass = Kernel::const_get(klassname)
66
+ klass = Object
67
+ names = klassname.split('::')
68
+ names.each do |name|
69
+ klass = klass.const_defined?(name) ? klass.const_get(name) : klass.const_missing(name)
70
+ end
67
71
  unless klass.class == "GameObject" && !except.include?(klass)
68
72
  puts "Creating #{klassname.to_s}: #{attributes.to_s}" if debug
69
73
  object = klass.create(attributes)
@@ -31,6 +31,7 @@ module Chingu
31
31
  #
32
32
  module GameState
33
33
  extend Forwardable
34
+
34
35
  def_delegator :game_state_manager, :game_states
35
36
  def_delegator :game_state_manager, :push_game_state
36
37
  def_delegator :game_state_manager, :pop_game_state
@@ -38,6 +39,7 @@ module Chingu
38
39
  def_delegator :game_state_manager, :transitional_game_state
39
40
  def_delegator :game_state_manager, :current_game_state
40
41
  def_delegator :game_state_manager, :clear_game_states
42
+ def_delegator :game_state_manager, :pop_until_game_state
41
43
  end
42
44
 
43
45
  end
@@ -49,8 +49,8 @@ module Chingu
49
49
  elsif key.is_a? Image
50
50
  GameObject.new(options.merge!(:image => key))
51
51
  elsif key.is_a? GameObject
52
- menu_item.options.merge!(options.dup)
53
- menu_item
52
+ key.options.merge!(options.dup)
53
+ key
54
54
  end
55
55
 
56
56
  item.options[:on_select] = method(:on_select)
@@ -84,23 +84,20 @@ module Chingu
84
84
 
85
85
  if options[:background]
86
86
  @background = GameObject.new(:image => options[:background])
87
- @background.attributes = self.attributes
88
- @background.color = ::Gosu::Color::WHITE
89
- @background.zorder -= 1
90
- @background.x -= @padding
91
- @background.y -= @padding
92
- @background.width = self.width + @padding * 2
93
- @background.height = self.height + @padding * 2
87
+ bg_update
88
+ @no_bg_update = options[:no_bg_update] || false # always update background attribute
94
89
  end
95
90
 
96
91
  self.height = options[:height] if options[:height]
97
- end
98
-
92
+ end
93
+
94
+
99
95
  #
100
96
  # Set a new text, a new image is created.
101
97
  #
102
98
  def text=(text)
103
- @text = text
99
+ return if text.dup == @text # Have to make a dup for content comparison
100
+ @text = text.dup # Make a copy, again to have a different Objectid
104
101
  create_image
105
102
  end
106
103
 
@@ -111,6 +108,14 @@ module Chingu
111
108
  @gosu_font.text_width(@text, @factor_x)
112
109
  end
113
110
 
111
+ #
112
+ # Update the background attributes if necessary unless specified
113
+ #
114
+ def update
115
+ super
116
+ bg_update if @background and !@no_bg_update
117
+ end
118
+
114
119
  #
115
120
  # Draws @background if present + our text in @image
116
121
  #
@@ -131,5 +136,15 @@ module Chingu
131
136
  @image = Gosu::Image.from_text($window, @text, @font, @size)
132
137
  end
133
138
  end
139
+
140
+ def bg_update
141
+ @background.attributes = self.attributes
142
+ @background.color = ::Gosu::Color::WHITE
143
+ @background.zorder -= 1
144
+ @background.x -= @padding
145
+ @background.y -= @padding
146
+ @background.width = self.width + @padding * 2
147
+ @background.height = self.height + @padding * 2
148
+ end
134
149
  end
135
150
  end
@@ -97,9 +97,9 @@ module Chingu
97
97
  raise ArgumentError.new("No image set") if image.nil?
98
98
 
99
99
  @image = if String === image
100
- # 1) Try loading the image the normal way
101
- # 2) Try looking up the picture using Chingus Image-cache
102
- Gosu::Image.new($window, image,false) rescue Gosu::Image[image]
100
+ # 1) Try looking up the picture using Chingus Image-cache
101
+ # 2) Try loading the image the normal way
102
+ Gosu::Image[image] rescue Gosu::Image.new($window, image,false)
103
103
  elsif image.respond_to? :call
104
104
  image.call
105
105
  else
@@ -28,10 +28,10 @@ module Chingu
28
28
  #
29
29
  #
30
30
  # TODO:
31
- # Implement use of viewports angle, center_x, center_y, factor_x, factor_y
31
+ # Implement use of viewports angle, center_x, center_y
32
32
  #
33
33
  class Viewport
34
- attr_accessor :x, :y, :x_target, :y_target, :x_lag, :y_lag, :game_area
34
+ attr_accessor :x, :y, :x_target, :y_target, :x_lag, :y_lag, :factor_x, :factor_y, :game_area
35
35
 
36
36
  def initialize(options = {})
37
37
  @x = options[:x] || 0
@@ -40,6 +40,8 @@ module Chingu
40
40
  @y_target = options[:y_target] || @y
41
41
  @x_lag = options[:x_lag] || 0
42
42
  @y_lag = options[:y_lag] || 0
43
+ @factor_x = options[:factor_x] || 1
44
+ @factor_y = options[:factor_y] || 1
43
45
  @game_area = Chingu::Rect.new(options[:game_area] || [@x, @y, $window.width, $window.height])
44
46
  end
45
47
 
@@ -57,8 +59,8 @@ module Chingu
57
59
  # TODO: Add support for x,y here!
58
60
  #
59
61
  def center_around(object)
60
- self.x = object.x - $window.width / 2
61
- self.y = object.y - $window.height / 2
62
+ self.x = object.x * @factor_x - $window.width / 2
63
+ self.y = object.y * @factor_y - $window.height / 2
62
64
  end
63
65
 
64
66
  #
@@ -114,14 +116,14 @@ module Chingu
114
116
  # Modify viewports x and y from target_x / target_y and x_lag / y_lag
115
117
  # Use this to have the viewport "slide" after the player
116
118
  #
117
- def move_towards_target
119
+ def move_towards_target
118
120
  if @x_target && @x != @x_target
119
- x_step = @x_target - @x
121
+ x_step = @x_target * @factor_x - @x
120
122
  self.x = @x + x_step * (1.0 - @x_lag)
121
123
  end
122
-
124
+
123
125
  if @y_target && @y != @y_target
124
- y_step = @y_target - @y
126
+ y_step = @y_target * @factor_y - @y
125
127
  self.y = @y + y_step * (1.0 - @y_lag)
126
128
  end
127
129
  end
@@ -132,9 +134,9 @@ module Chingu
132
134
  def x=(x)
133
135
  @x = x
134
136
  if @game_area
135
- @x = @game_area.x if @x < @game_area.x
136
- @x = @game_area.width-$window.width if @x > @game_area.width-$window.width
137
- end
137
+ @x = @game_area.x * @factor_x if @x < @game_area.x * @factor_x
138
+ @x = @game_area.width * @factor_x - $window.width if @x > @game_area.width * @factor_x - $window.width
139
+ end
138
140
  end
139
141
 
140
142
  #
@@ -143,8 +145,8 @@ module Chingu
143
145
  def y=(y)
144
146
  @y = y
145
147
  if @game_area
146
- @y = @game_area.y if @y < @game_area.y
147
- @y = @game_area.height-$window.height if @y > @game_area.height-$window.height
148
+ @y = @game_area.y * @factor_y if @y < @game_area.y * @factor_y
149
+ @y = @game_area.height * @factor_y - $window.height if @y > @game_area.height * @factor_y - $window.height
148
150
  end
149
151
  end
150
152
 
@@ -152,7 +154,9 @@ module Chingu
152
154
  # Apply the X/Y viewport-translation, used by trait "viewport"
153
155
  #
154
156
  def apply(&block)
155
- $window.translate(-@x.to_i, -@y.to_i, &block)
157
+ $window.translate(-@x.to_i, -@y.to_i) do
158
+ $window.scale(@factor_x, @factor_y, 0, 0, &block)
159
+ end
156
160
  end
157
161
 
158
162
  def to_s
@@ -64,6 +64,8 @@ module Chingu
64
64
  @milliseconds_since_last_tick = 0
65
65
  @factor = 1
66
66
  @cursor = false
67
+ @times_muted = 0
68
+ @volume = DEFAULT_VOLUME
67
69
 
68
70
  setup
69
71
  end
@@ -194,5 +196,57 @@ module Chingu
194
196
 
195
197
  $window = nil
196
198
  end
199
+
200
+ # GLOBAL SOUND SETTINGS
201
+
202
+ DEFAULT_VOLUME = 1.0 # However, 0.5 is a better value for general use.
203
+
204
+ # Set the global volume of all Samples and Songs, not affected by Window being muted.
205
+ def volume=(value)
206
+ raise "Bad volume setting" unless value.is_a? Numeric
207
+
208
+ old_volume = @volume
209
+ @volume = [[value, 1.0].min, 0.0].max.to_f
210
+
211
+ Song.send(:recalculate_volumes, old_volume, @volume)
212
+
213
+ volume
214
+ end
215
+
216
+ # Volume of all Samples and Songs, not affected by the Window being muted.
217
+ attr_reader :volume
218
+
219
+ # Actual volume of all Samples and Songs, affected by the Window being muted.
220
+ def effective_volume
221
+ muted? ? 0.0 : @volume
222
+ end
223
+
224
+ # Mute the window and all Samples and Songs played.
225
+ # Muting stacks, so sound will only be heard if the number of unmutes is the same as the number of mutes.
226
+ def mute
227
+ unless muted?
228
+ Song.send(:resources).each_value {|song| song.send :mute }
229
+ end
230
+ @times_muted += 1
231
+
232
+ self
233
+ end
234
+
235
+ # Unmute the window and all Samples and Songs played.
236
+ # Muting stacks, so sound will only be heard if the number of unmutes is the same as the number of mutes.
237
+ def unmute
238
+ raise "Can't unmute when not muted" unless muted?
239
+ @times_muted -= 1
240
+ unless muted?
241
+ Song.send(:resources).each_value {|song| song.send :unmute }
242
+ end
243
+
244
+ self
245
+ end
246
+
247
+ # Is the window currently muted?
248
+ def muted?
249
+ @times_muted > 0
250
+ end
197
251
  end
198
252
  end
@@ -6,8 +6,9 @@ module Chingu
6
6
  @game = Chingu::Window.new
7
7
  @test_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'images')
8
8
  Gosu::Image.autoload_dirs << @test_dir
9
- @animation_clean = Animation.new(:file => File.join(@test_dir, "droid_11x15.bmp"))
10
- @animation = Animation.new(:file => File.join(@test_dir, "droid_11x15.bmp"), :delay => 0)
9
+ @file = "droid_11x15.bmp"
10
+ @animation_clean = Animation.new(:file => @file)
11
+ @animation = Animation.new(:file => @file, :delay => 0)
11
12
  end
12
13
 
13
14
  after :each do
@@ -33,8 +34,45 @@ module Chingu
33
34
  @anim = Animation.new(:file => "images/droid_11x15.bmp")
34
35
  @anim.frames.count.should == 14
35
36
  end
37
+
38
+ it "should load from a Gosu image" do
39
+ Dir.chdir(File.dirname(File.expand_path(__FILE__)))
40
+ @anim = Animation.new(:image => Gosu::Image["images/droid_11x15.bmp"], :size => [11, 15])
41
+ @anim.frames.count.should == 14
42
+ end
36
43
  end
37
-
44
+
45
+ describe "Animation loading using :frames" do
46
+ it "should have the same frames" do
47
+ anim = Animation.new :frames => @animation_clean.frames
48
+ anim.frames.should == @animation_clean.frames
49
+ end
50
+
51
+ it "should reject non-consistent frame sizes" do
52
+ ->{ Animation.new :frames => @animation_clean.frames + [Gosu::Image[@file]]}.should raise_error ArgumentError
53
+ end
54
+ end
55
+
56
+ describe "Animation loading using :image" do
57
+ before :each do
58
+ @anim = Animation.new :image => Gosu::Image[@file]
59
+ end
60
+
61
+ it "should have the same frames" do
62
+ @anim.frames.zip(@animation_clean.frames).all? { |a, b| a.to_blob == b.to_blob }
63
+ end
64
+ end
65
+
66
+ describe "Animation loading errors" do
67
+ it "should fail unless one of the creation params is given" do
68
+ ->{ Animation.new }.should raise_error ArgumentError
69
+ end
70
+
71
+ it "should fail if more than one creation params" do
72
+ ->{ Animation.new :image => Gosu::Image[@file], :file => @file}.should raise_error ArgumentError
73
+ end
74
+ end
75
+
38
76
  describe "Animation loading file: droid_11x15.bmp" do
39
77
  it "should detect size and frames automatically from filename" do
40
78
  @animation.size.should == [11,15]
@@ -60,22 +60,69 @@ module Chingu
60
60
  describe "switch_game_state" do
61
61
  before :each do
62
62
  @game.push_game_state(Chingu::GameStates::Pause)
63
- @game.switch_game_state(Chingu::GameStates::Edit)
63
+ @game.switch_game_state(Chingu::GameStates::Debug)
64
64
  end
65
65
 
66
66
  it "should replace current game state" do
67
- @game.current_game_state.class.should == Chingu::GameStates::Edit
67
+ @game.current_game_state.should be_a Chingu::GameStates::Debug
68
68
  end
69
69
 
70
70
  it "should not change the total amount of game states" do
71
71
  @game.game_states.count.should == 1
72
72
  end
73
73
  end
74
+
75
+ describe "pop_until_game_state" do
76
+ before :each do
77
+ @game.push_game_state(Chingu::GameStates::Pause)
78
+ @game.push_game_state(Chingu::GameStates::Debug)
79
+ @game.push_game_state(Chingu::GameStates::Debug)
80
+ @states = @game.game_state_manager.instance_variable_get(:@game_states).dup
81
+ end
82
+
83
+ describe "with class" do
84
+ it "should finalize popped states" do
85
+ @states[1].should_receive(:finalize)
86
+ @states[2].should_receive(:finalize)
87
+ @game.pop_until_game_state(Chingu::GameStates::Pause)
88
+ end
89
+
90
+ it "should setup revealed states" do
91
+ @states[0].should_receive(:setup)
92
+ @states[1].should_receive(:setup)
93
+ @game.pop_until_game_state(Chingu::GameStates::Pause)
94
+ end
95
+
96
+ it "should pop down to the given game state" do
97
+ @game.pop_until_game_state(Chingu::GameStates::Pause)
98
+ @game.game_states.should eq [@states[0]]
99
+ end
100
+ end
101
+
102
+ describe "with instance" do
103
+ it "should finalize popped states" do
104
+ @states[1].should_receive(:finalize)
105
+ @states[2].should_receive(:finalize)
106
+ @game.pop_until_game_state(@states[0])
107
+ end
108
+
109
+ it "should setup revealed states" do
110
+ @states[0].should_receive(:setup)
111
+ @states[1].should_receive(:setup)
112
+ @game.pop_until_game_state(@states[0])
113
+ end
114
+
115
+ it "should pop down to the given game state" do
116
+ @game.pop_until_game_state(@states[0])
117
+ @game.game_states.should eq [@states[0]]
118
+ end
119
+ end
120
+ end
74
121
 
75
122
  describe "clear_game_states" do
76
123
  it "should clear all game states" do
77
124
  @game.push_game_state(Chingu::GameStates::Pause)
78
- @game.switch_game_state(Chingu::GameStates::Edit)
125
+ @game.push_game_state(Chingu::GameStates::Edit)
79
126
  @game.clear_game_states
80
127
  @game.game_states.count.should == 0
81
128
  end
@@ -16,14 +16,14 @@ module Chingu
16
16
 
17
17
  describe Chingu::GameStates::NetworkServer do
18
18
  it "should open listening port on start()" do
19
- @server = described_class.new(:ip => "0.0.0.0", :port => 9999)
19
+ @server = described_class.new(:address => "0.0.0.0", :port => 9999)
20
20
  @server.should_receive(:on_start)
21
21
  @server.start
22
22
  @server.stop
23
23
  end
24
24
 
25
25
  it "client should timeout when connecting to blackhole ip" do
26
- @client = Chingu::GameStates::NetworkClient.new(:ip => "1.2.3.4", :port => 1234, :debug => true)
26
+ @client = Chingu::GameStates::NetworkClient.new(:address => "1.2.3.4", :port => 1234, :debug => true)
27
27
  @client.connect
28
28
 
29
29
  #@client.should_receive(:on_timeout) ## gives on_connection_refused instead, kind of ok.
@@ -32,15 +32,15 @@ module Chingu
32
32
  end
33
33
 
34
34
  it "should call on_start_error() if failing" do
35
- @server = described_class.new(:ip => "1.2.3.999", :port => 12345678) # crazy ip:port
35
+ @server = described_class.new(:address => "1.2.3.999", :port => 12345678) # crazy address:port
36
36
  @server.should_receive(:on_start_error)
37
37
  @server.start
38
38
  @server.stop
39
39
  end
40
40
 
41
41
  it "should call on_connect() and on_disconnect() when client connects" do
42
- @server = described_class.new(:ip => "0.0.0.0", :port => 9999)
43
- @client = Chingu::GameStates::NetworkClient.new(:ip => "127.0.0.1", :port => 9999)
42
+ @server = described_class.new(:address => "0.0.0.0", :port => 9999)
43
+ @client = Chingu::GameStates::NetworkClient.new(:address => "127.0.0.1", :port => 9999)
44
44
 
45
45
  @server.should_receive(:on_start)
46
46
  @server.should_receive(:on_connect).with(an_instance_of(TCPSocket))
@@ -58,19 +58,64 @@ module Chingu
58
58
  end
59
59
 
60
60
  describe Chingu::GameStates::NetworkClient do
61
- it "should call on_connection_refused callback when connecting to closed port" do
62
- @client = described_class.new(:ip => "127.0.0.1", :port => 55421) # closed we assume
63
- @client.should_receive(:on_connection_refused)
61
+ describe "connect" do
62
+ it "should call on_connection_refused callback when connecting to closed port" do
63
+ @client = described_class.new(:address => "127.0.0.1", :port => 55421) # closed we assume
64
+ @client.should_receive(:on_connection_refused)
65
+ @client.connect
66
+ 5.times { @client.update }
67
+ end
68
+
69
+ it "should not call on_timeout callback when unable to connect for less time than the timeout" do
70
+ @client = described_class.new(:address => "127.0.0.1", :port => 55421, :timeout => 250) # closed we assume
71
+ @client.connect
72
+ @client.should_not_receive(:on_timeout)
73
+ 5.times { @client.update; sleep 0.01 }
74
+ end
75
+
76
+ it "should call on_timeout callback when unable to connect for longer than the timeout" do
77
+ @client = described_class.new(:address => "127.0.0.1", :port => 55421, :timeout => 250) # closed we assume
78
+ @client.connect
79
+ @client.update
80
+ sleep 0.3
81
+ @client.should_receive(:on_timeout)
82
+ 5.times { @client.update }
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "Connecting" do
88
+ before :each do
89
+ @client = Chingu::GameStates::NetworkClient.new(:address => "127.0.0.1", :port => 9999)
90
+ @server = Chingu::GameStates::NetworkServer.new(:port => 9999)
91
+ end
92
+
93
+ it "should connect to the server, when the server starts before it" do
94
+ #@server.start
95
+ #@client.connect
96
+ #5.times { @client.update }
97
+ #@client.should be_connected
98
+ end
99
+
100
+ it "should connect to the server, even when the server isn't initialy available" do
64
101
  @client.connect
65
- 5.times { @client.update }
102
+ 3.times { @client.update; sleep 0.2; @server.update; @client.flush }
103
+ @server.start
104
+ 3.times { @client.update; sleep 0.2; @server.update; @client.flush }
105
+ @client.should be_connected
106
+ end
107
+
108
+ after :each do
109
+ @client.close
110
+ @server.close
66
111
  end
67
112
  end
68
113
 
69
114
  describe "Network communication" do
70
115
  before :each do
71
116
  @server = Chingu::GameStates::NetworkServer.new(:port => 9999).start
72
- @client = Chingu::GameStates::NetworkClient.new(:ip => "127.0.0.1", :port => 9999).connect
73
- @client2 = Chingu::GameStates::NetworkClient.new(:ip => "127.0.0.1", :port => 9999).connect
117
+ @client = Chingu::GameStates::NetworkClient.new(:address => "127.0.0.1", :port => 9999).connect
118
+ @client2 = Chingu::GameStates::NetworkClient.new(:address => "127.0.0.1", :port => 9999).connect
74
119
  @client.update until @client.connected?
75
120
  @client2.update until @client2.connected?
76
121
  end
@@ -123,6 +168,94 @@ module Chingu
123
168
  end
124
169
  end
125
170
  end
171
+
172
+ describe "byte and packet counters" do
173
+ before :each do
174
+ @packet = "Hello! " * 10
175
+ @packet_length = Marshal.dump(@packet).length
176
+ @packet_length_with_header = @packet_length + 4
177
+ end
178
+
179
+ it "should be zeroed initially" do
180
+ [@client, @client2, @server].each do |network|
181
+ network.packets_sent.should be 0
182
+ network.bytes_sent.should be 0
183
+ network.packets_received.should be 0
184
+ network.bytes_received.should be 0
185
+ end
186
+ end
187
+
188
+ describe "client to server" do
189
+ before :each do
190
+ @client.send_msg(@packet)
191
+ @server.update
192
+ end
193
+
194
+ describe "client" do
195
+ it "should increment counters correctly when sending a message" do
196
+ @client.packets_sent.should eq 1
197
+ @client.bytes_sent.should eq @packet_length_with_header
198
+ end
199
+ end
200
+
201
+ describe "server" do
202
+ it "should increment counters correctly when receiving a message" do
203
+ @server.packets_received.should eq 1
204
+ @server.bytes_received.should eq @packet_length_with_header
205
+ end
206
+ end
207
+ end
208
+
209
+ describe "server to client" do
210
+ before :each do
211
+ @server.update
212
+ @server.send_msg(@server.sockets[0], @packet)
213
+ @client.update
214
+ end
215
+
216
+ describe "server" do
217
+ it "should increment sent counters" do
218
+ @server.packets_sent.should eq 1
219
+ @server.bytes_sent.should eq @packet_length_with_header
220
+ end
221
+ end
222
+
223
+ describe "client" do
224
+ it "should increment received counters" do
225
+ @client.packets_received.should eq 1
226
+ @client.bytes_received.should eq @packet_length_with_header
227
+ @client2.packets_received.should eq 0
228
+ @client2.bytes_received.should eq 0
229
+ end
230
+ end
231
+ end
232
+
233
+ describe "server to clients" do
234
+ before :each do
235
+ @server.update
236
+ @server.broadcast_msg(@packet)
237
+ @client.update
238
+ @client2.update
239
+ end
240
+
241
+ describe "server" do
242
+ it "should increment sent counters" do
243
+ # Single message, broadcast to two clients.
244
+ @server.packets_sent.should eq 2
245
+ @server.bytes_sent.should eq @packet_length_with_header * 2
246
+ end
247
+ end
248
+
249
+ describe "clients" do
250
+ it "should increment received counters" do
251
+ [@client, @client2].each do |client|
252
+ client.packets_received.should eq 1
253
+ client.bytes_received.should eq @packet_length_with_header
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
126
259
  end
127
260
  end
128
261
  end