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.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/History.rdoc +5 -0
- data/Manifest.txt +37 -0
- data/README.md +88 -0
- data/Rakefile +24 -0
- data/bin/game_master +6 -0
- data/bin/pirate_game +5 -0
- data/config.json +4 -0
- data/imgs/jolly_roger.jpg +0 -0
- data/imgs/jolly_roger_sm.png +0 -0
- data/imgs/pirate_ship.png +0 -0
- data/imgs/pirate_ship_sm.png +0 -0
- data/lib/pirate_game.rb +21 -0
- data/lib/pirate_game/animation.rb +35 -0
- data/lib/pirate_game/background.rb +95 -0
- data/lib/pirate_game/boot.rb +68 -0
- data/lib/pirate_game/bridge.rb +21 -0
- data/lib/pirate_game/bridge_button.rb +38 -0
- data/lib/pirate_game/client.rb +163 -0
- data/lib/pirate_game/client_app.rb +342 -0
- data/lib/pirate_game/game_master.rb +208 -0
- data/lib/pirate_game/image.rb +23 -0
- data/lib/pirate_game/log_book.rb +39 -0
- data/lib/pirate_game/master_app.rb +103 -0
- data/lib/pirate_game/protocol.rb +10 -0
- data/lib/pirate_game/shoes4_patch.rb +30 -0
- data/lib/pirate_game/stage.rb +96 -0
- data/lib/pirate_game/timeout_renewer.rb +30 -0
- data/lib/pirate_game/wave.rb +46 -0
- data/lib/pirate_game/waving_item.rb +17 -0
- data/state_diagram.graffle +2689 -0
- data/test/test_pirate_game_bridge.rb +14 -0
- data/test/test_pirate_game_client.rb +148 -0
- data/test/test_pirate_game_game_master.rb +107 -0
- data/test/test_pirate_game_log_book.rb +34 -0
- data/test/test_pirate_game_stage.rb +116 -0
- data/test/test_pirate_game_timeout_renewer.rb +15 -0
- metadata +204 -0
@@ -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
|
+
|