pirate_game 0.0.1

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