pirate_game 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ class PirateGame::BridgeButton < PirateGame::WavingItem
2
+
3
+ TOP = 150
4
+
5
+ def initialize shoes, text, row, column, &click_action
6
+ super rand(90), 10, 4
7
+
8
+ @shoes = shoes
9
+ @text = text
10
+ @row = row
11
+ @column = column
12
+ @click_action = click_action
13
+
14
+ @button = nil
15
+ @left = nil
16
+ @top = nil
17
+ end
18
+
19
+ def animate frame
20
+ top_offset, left_offset = waving_offset frame
21
+
22
+ @button.move @top + top_offset, @left + left_offset
23
+ end
24
+
25
+ def draw
26
+ width = @shoes.app.width
27
+ chunk = width / 6
28
+
29
+ # something is wrong in my head, these are switched
30
+ @top = chunk / 3 + 2 * @column * chunk
31
+ @left = TOP + @row * 40
32
+
33
+ @button = @shoes.button @text, &@click_action
34
+ @button.move @left, @top
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,163 @@
1
+ require 'shuttlecraft'
2
+ require 'thread'
3
+ require 'timeout'
4
+
5
+ class PirateGame::Client < Shuttlecraft
6
+
7
+ STATES = [:select_game, :pub, :stage, :end]
8
+
9
+ attr_reader :bridge
10
+
11
+ ##
12
+ # Log of messages sent
13
+
14
+ attr_reader :log_book
15
+
16
+ ##
17
+ # The state of the client. See STATES.
18
+
19
+ attr_reader :state
20
+
21
+ ##
22
+ # The time the last command was issued
23
+
24
+ attr_reader :command_start
25
+
26
+ attr_accessor :completion_time
27
+
28
+ ##
29
+ # The command the client is waiting for
30
+
31
+ attr_reader :current_action
32
+
33
+ ##
34
+ # Bucket for data being sent from game master
35
+
36
+ attr_reader :slop_bucket
37
+
38
+ def initialize(opts={})
39
+ opts[:protocol] ||= PirateGame::Protocol.default
40
+
41
+ super(opts.merge({:verbose => true}))
42
+
43
+ set_state :select_game
44
+
45
+ @bridge = nil
46
+ @command_start = nil
47
+ @command_thread = nil
48
+ @completion_time = PirateGame::Boot.config["action_duration"]
49
+ @current_action = nil
50
+ @log_book = PirateGame::LogBook.new
51
+
52
+ @slop_bucket = {}
53
+ end
54
+
55
+ def self.default_name
56
+ "Blackbeard"
57
+ end
58
+
59
+ def action_time_left
60
+ return 0 unless waiting?
61
+
62
+ @command_start - Time.now + @completion_time
63
+ end
64
+
65
+ def set_state state
66
+ raise RuntimeError, "invalid state #{state}" unless STATES.include? state
67
+
68
+ @state = state
69
+ end
70
+
71
+ def clicked button
72
+ renewer = Rinda::SimpleRenewer.new @completion_time
73
+
74
+ @mothership.write [:button, button, Time.now.to_i, DRb.uri], renewer
75
+ end
76
+
77
+ def issue_command item=nil
78
+ item ||= @bridge.sample_item if @bridge
79
+
80
+ return unless item
81
+
82
+ @command_thread = Thread.new do
83
+ wait_for_action item
84
+ end
85
+
86
+ Thread.pass until @command_start # this should be a proper barrier
87
+
88
+ @current_action = "#{PirateCommand.action} the #{item}"
89
+ end
90
+
91
+ def register
92
+ set_state :pub
93
+ super
94
+ end
95
+
96
+ def start_stage(bridge, all_items)
97
+ @bridge = PirateGame::Bridge.new(bridge, all_items)
98
+ set_state :stage
99
+ end
100
+
101
+ def return_to_pub
102
+ @bridge = nil
103
+ set_state :pub
104
+ end
105
+
106
+ def end_game data
107
+ set_state :end
108
+
109
+ @slop_bucket[:end_game] = data
110
+ end
111
+
112
+ def teammates
113
+ registered_services.collect{|name,_| name}
114
+ end
115
+
116
+ #
117
+ # Sends action message to Game Master indicating
118
+ # that action has been successfully performed
119
+ def perform_action item, time, from
120
+ if @mothership
121
+ @mothership.write [:action, item, time, from]
122
+ end
123
+ end
124
+
125
+ def broadcast(msg)
126
+ each_client {|remote| remote.say(msg, @name) }
127
+ end
128
+
129
+ def say(msg, name)
130
+ @log_book.add msg, name || 'unknown'
131
+ end
132
+
133
+ def renewer
134
+ PirateGame::TimeoutRenewer.new @completion_time
135
+ end
136
+
137
+ def wait_for_action item
138
+ @command_start = Time.now
139
+ now = @command_start.to_i
140
+
141
+ Thread.pass
142
+
143
+ from = nil
144
+
145
+ Timeout.timeout @completion_time do
146
+ _, _, _, from =
147
+ @mothership.read [:button, item, (now...now + 30), nil], renewer
148
+ end
149
+
150
+ perform_action item, Time.now, from
151
+
152
+ rescue Rinda::RequestExpiredError, Timeout::Error
153
+ ensure
154
+ @command_thread = nil
155
+ @command_start = nil
156
+ @current_action = nil
157
+ end
158
+
159
+ def waiting?
160
+ @command_thread and @command_thread.alive? and @command_start
161
+ end
162
+
163
+ end
@@ -0,0 +1,342 @@
1
+ class PirateGame::ClientApp
2
+
3
+ def self.run
4
+ @my_app = Shoes.app width: 360, height: 360, resizeable: false, title: 'Pirate Game' do
5
+
6
+ require 'pirate_game/shoes4_patch'
7
+
8
+ def animate fps, &block
9
+ opts = { framerate: fps }
10
+ PirateGame::Animation.new @app, opts, block
11
+ end
12
+
13
+ ##
14
+ # Creates the animation trigger for animating items. An animatable item
15
+ # must respond to #animate and accept a frame number.
16
+
17
+ def animate_items
18
+ @items_animation = animate(30) do |frame|
19
+ @background.animate frame
20
+ end
21
+ end
22
+
23
+ ##
24
+ # Creates the waves and ship graphics that must be animated.
25
+
26
+ def create_items
27
+ @background = PirateGame::Background.new(self)
28
+ end
29
+
30
+ ##
31
+ # Clears the app, draws the background, ship and waves, then yields to
32
+ # draw UI items.
33
+
34
+ def draw_items
35
+ clear
36
+
37
+ @background.draw do
38
+ yield
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Sets the application state
44
+
45
+ def state= state
46
+ @state = state
47
+ @client.set_state @state
48
+ end
49
+
50
+ ##
51
+ # The launch screen.
52
+
53
+ def launch_screen
54
+ @items_animation.start if @items_animation
55
+
56
+ pirate_ship do
57
+ title "What's your name", stroke: PirateGame::Boot::COLORS[:dark]
58
+
59
+ edit_line text: 'Name' do |s|
60
+ @name = s.text
61
+ end
62
+
63
+ button('launch') {
64
+ @client = PirateGame::Client.new(name: @name)
65
+ }
66
+ end
67
+
68
+ @state_watcher = animate(5) {
69
+ watch_state
70
+ }
71
+ end
72
+
73
+ ##
74
+ # Draws a pirate ship background with waves yields to draw additional
75
+ # UI.
76
+
77
+ def pirate_ship
78
+ draw_items do
79
+ stack margin: 20 do
80
+ yield
81
+ end
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Watches the state of the client to switch to a new screen.
87
+
88
+ def watch_state
89
+ return if @client.nil?
90
+ return if @state == @client.state
91
+
92
+ @state = @client.state
93
+
94
+ case @state
95
+ when :select_game
96
+ select_game_screen
97
+ when :pub
98
+ pub_screen
99
+ when :stage
100
+ stage_screen
101
+ when :end
102
+ end_screen
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Displays the UI for choosing a game to join
108
+
109
+ def select_game_screen
110
+ @items_animation.start if @items_animation
111
+
112
+ motherships = @client.find_all_motherships
113
+
114
+ title_text = if motherships.empty? then
115
+ "No Games Found"
116
+ else
117
+ "Choose Game"
118
+ end
119
+
120
+ pirate_ship do
121
+ title title_text, stroke: PirateGame::Boot::COLORS[:dark]
122
+
123
+ for mothership in motherships
124
+ draw_mothership_button mothership
125
+ end
126
+
127
+ button('rescan') {
128
+ select_game_screen
129
+ }
130
+ end
131
+ rescue DRb::DRbConnError
132
+ state = :select_game
133
+ select_game_screen
134
+ end
135
+
136
+ ##
137
+ # The pirate pub screen where you wait to start a game
138
+
139
+ def pub_screen
140
+ @stage_animation.remove if @stage_animation
141
+ @items_animation.stop if @items_animation
142
+
143
+ @pub_tagline ||= "Welcome #{@client.name}"
144
+
145
+ clear do
146
+ background PirateGame::Boot::COLORS[:pub]
147
+ stack :margin => 20 do
148
+ title "Pirate Pub", stroke: PirateGame::Boot::COLORS[:light]
149
+ tagline @pub_tagline, stroke: PirateGame::Boot::COLORS[:light]
150
+
151
+ stack do @status = para '', stroke: PirateGame::Boot::COLORS[:light] end
152
+
153
+ @registered = nil
154
+ @updating_area = stack
155
+ stack do
156
+ @chat_title = tagline 'Pub Chat', stroke: PirateGame::Boot::COLORS[:light]
157
+ @chat_input = flow
158
+ @chat_messages = stack
159
+ end
160
+ end
161
+
162
+ # checks for registration changes
163
+ # updates chat messages
164
+ @pub_animation = animate(5) {
165
+ if @client
166
+
167
+ # updates screen only when registration state changes
168
+ update_on_registration_change
169
+
170
+ # updates screen, runs every time
171
+ update_chat_room
172
+ end
173
+ }
174
+ end
175
+ rescue DRb::DRbConnError
176
+ state = :select_game
177
+ select_game_screen
178
+ end
179
+
180
+ ##
181
+ # The screen where the game occurs
182
+
183
+ def stage_screen
184
+ @pub_animation.remove if @pub_animation
185
+ @items_animation.start if @items_animation
186
+
187
+ # if we get back to pub then stage was success
188
+ @pub_tagline = 'SUCCESS!'
189
+
190
+ pirate_ship do
191
+ title PirateCommand.exclaim!, stroke: PirateGame::Boot::COLORS[:dark]
192
+
193
+ @instruction = caption stroke: PirateGame::Boot::COLORS[:dark]
194
+ @progress = progress
195
+
196
+ @client.bridge.items.each_slice(3).with_index do |items, row|
197
+ items.each_with_index do |item, column|
198
+ bridge_button =
199
+ PirateGame::BridgeButton.new self, item, row, column do
200
+ @client.clicked item
201
+ end
202
+
203
+ @background.add_extra_item bridge_button
204
+ end
205
+ end
206
+ end
207
+
208
+ @stage_animation = animate(10) {
209
+ @client.issue_command unless @client.waiting?
210
+
211
+ @instruction.replace @client.current_action
212
+
213
+ progress_fraction = (@client.action_time_left.to_f / @client.completion_time.to_f)
214
+ @progress.fraction = progress_fraction
215
+ }
216
+ rescue DRb::DRbConnError
217
+ state = :select_game
218
+ select_game_screen
219
+ end
220
+
221
+ ##
222
+ # The end-game screen
223
+
224
+ def end_screen
225
+ @pub_animation.remove if @pub_animation
226
+ @stage_animation.remove if @stage_animation
227
+ @items_animation.stop if @items_animation
228
+
229
+ clear do
230
+ jolly_roger = File.expand_path '../../../imgs/jolly_roger_sm.png', __FILE__
231
+ background PirateGame::Boot::COLORS[:dark]
232
+ background jolly_roger, height: 256
233
+ background rgb(0, 0, 0, 180)
234
+
235
+ stack margin: 20 do
236
+
237
+ title "END OF GAME", stroke: PirateGame::Boot::COLORS[:light]
238
+
239
+ if @client.slop_bucket[:end_game]
240
+ game_stats = @client.slop_bucket[:end_game]
241
+ para "Game Stats", stroke: PirateGame::Boot::COLORS[:light]
242
+ para "Total Stages Completed: #{game_stats[:total_stages]}", stroke: PirateGame::Boot::COLORS[:light]
243
+ para "Total Actions: #{game_stats[:total_actions]}", stroke: PirateGame::Boot::COLORS[:light]
244
+ para "My Contribution: #{game_stats[:player_breakdown][DRb.uri]}", stroke: PirateGame::Boot::COLORS[:light]
245
+ end
246
+ end
247
+ end
248
+ rescue DRb::DRbConnError
249
+ state = :select_game
250
+ select_game_screen
251
+ end
252
+
253
+ ##
254
+ # If the registration state has changed, yields to the block.
255
+
256
+ def detect_registration_change
257
+ return if @client.registered? == @registered
258
+
259
+ @registered = @client.registered?
260
+
261
+ @status.replace "#{"Not " unless @registered}Registered"
262
+
263
+ yield
264
+ end
265
+
266
+ ##
267
+ # Updates the chat messages with new messages
268
+
269
+ def update_chat_room
270
+ if @registered
271
+ @chat_messages.clear do
272
+ for msg, name in @client.log_book
273
+ para "#{name} said: #{msg}", stroke: PirateGame::Boot::COLORS[:light]
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ ##
280
+ # Updates the registration view in the pub screen
281
+
282
+ def update_on_registration_change
283
+ detect_registration_change do
284
+ @updating_area.clear do
285
+ button("Register") { register } unless @registered
286
+ end
287
+
288
+ # chat input box only appears when registered
289
+ @chat_input.clear do
290
+ if @registered
291
+ el = edit_line
292
+
293
+ button("Send") {
294
+ unless el.text.empty?
295
+ @client.broadcast(el.text)
296
+ el.text = ''
297
+ end
298
+ }
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ ##
305
+ # Draws a button for joining a game playing on +mothership+
306
+
307
+ def draw_mothership_button mothership
308
+ button(mothership[:name]) {|b|
309
+ begin
310
+ @client.initiate_communication_with_mothership(b.text)
311
+ @client.register
312
+ rescue
313
+ select_game_screen
314
+ end
315
+ }
316
+ end
317
+
318
+ ##
319
+ # Registers the client with a game master
320
+
321
+ def register
322
+ @client.register if @client
323
+ end
324
+
325
+ ##
326
+ # Unregisters a game with a game master
327
+
328
+ def unregister
329
+ @client.unregister if @client
330
+ end
331
+
332
+ @client = nil
333
+ create_items
334
+ animate_items
335
+ launch_screen
336
+ end
337
+ ensure
338
+ @my_app.unregister if @my_app
339
+ end
340
+
341
+ end
342
+