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,208 @@
|
|
1
|
+
require 'shuttlecraft/mothership'
|
2
|
+
|
3
|
+
class PirateGame::GameMaster < Shuttlecraft::Mothership
|
4
|
+
|
5
|
+
|
6
|
+
MIN_PLAYERS = 1 # for now
|
7
|
+
MAX_PLAYERS = 4
|
8
|
+
|
9
|
+
STATES = [:pending, :startable, :playing, :ended]
|
10
|
+
|
11
|
+
attr_accessor :stage, :stage_ary
|
12
|
+
|
13
|
+
##
|
14
|
+
# The state of the game. See STATES.
|
15
|
+
|
16
|
+
attr_reader :state
|
17
|
+
|
18
|
+
##
|
19
|
+
# Number of players in the game. Call #update to refresh
|
20
|
+
|
21
|
+
attr_reader :num_players
|
22
|
+
|
23
|
+
##
|
24
|
+
# Names of players in the game. Call #update to refresh
|
25
|
+
|
26
|
+
attr_reader :player_names
|
27
|
+
|
28
|
+
def initialize(opts={})
|
29
|
+
opts[:protocol] ||= PirateGame::Protocol.default
|
30
|
+
|
31
|
+
super(opts.merge({:verbose => true}))
|
32
|
+
|
33
|
+
set_state :pending
|
34
|
+
|
35
|
+
@last_update = Time.at 0
|
36
|
+
@num_players = 0
|
37
|
+
@player_names = []
|
38
|
+
@stage = nil
|
39
|
+
@stage_ary = []
|
40
|
+
|
41
|
+
@action_watcher = create_action_watcher
|
42
|
+
end
|
43
|
+
|
44
|
+
def registrations_text
|
45
|
+
"Num Players: #{@num_players}\n#{@player_names.join(', ')}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
def stage_info
|
49
|
+
return unless @stage
|
50
|
+
|
51
|
+
info = "Stage #{@stage.level}: \n"
|
52
|
+
if @stage.in_progress?
|
53
|
+
info << "Actions: #{@stage.actions_completed}\n"
|
54
|
+
info << "Time Left: #{@stage.time_left.to_i} seconds\n"
|
55
|
+
else
|
56
|
+
info << "Status: #{@stage.status}\n"
|
57
|
+
|
58
|
+
rundown = @stage.rundown
|
59
|
+
|
60
|
+
info << "Actions: #{rundown[:total_actions]}\n"
|
61
|
+
|
62
|
+
rundown[:player_breakdown].each do |player_uri, actions|
|
63
|
+
info << "#{player_uri}: #{actions}\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
info
|
69
|
+
end
|
70
|
+
|
71
|
+
def game_info
|
72
|
+
return if @stage_ary.empty?
|
73
|
+
|
74
|
+
info = "Game Rundown:\n"
|
75
|
+
gr = game_rundown
|
76
|
+
|
77
|
+
info << "Total Actions: #{gr[:total_actions]}\n"
|
78
|
+
|
79
|
+
gr[:player_breakdown].each do |player_uri, actions|
|
80
|
+
info << "#{player_uri}: #{actions}\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
info
|
84
|
+
end
|
85
|
+
|
86
|
+
def game_rundown
|
87
|
+
return {} if @stage_ary.empty?
|
88
|
+
|
89
|
+
rundown = {
|
90
|
+
:total_stages => @stage_ary.length,
|
91
|
+
:total_actions => @stage_ary.inject(0) {|sum,stage| sum += stage.actions_completed},
|
92
|
+
:player_breakdown => {}}
|
93
|
+
|
94
|
+
for stage in @stage_ary
|
95
|
+
stage.player_stats.each_pair do |key, value|
|
96
|
+
rundown[:player_breakdown][key] ||= 0
|
97
|
+
rundown[:player_breakdown][key] += value.size
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
rundown
|
102
|
+
end
|
103
|
+
|
104
|
+
def allow_registration?
|
105
|
+
return (@stage.nil? && @num_players < MAX_PLAYERS)
|
106
|
+
end
|
107
|
+
|
108
|
+
def startable?
|
109
|
+
update!
|
110
|
+
return (@stage.nil? || @stage.success?) &&
|
111
|
+
@num_players >= MIN_PLAYERS &&
|
112
|
+
@num_players <= MAX_PLAYERS
|
113
|
+
end
|
114
|
+
|
115
|
+
def increment_stage
|
116
|
+
@stage =
|
117
|
+
if @stage
|
118
|
+
@stage.increment
|
119
|
+
else
|
120
|
+
PirateGame::Stage.new 1, @num_players
|
121
|
+
end
|
122
|
+
|
123
|
+
@stage_ary << @stage
|
124
|
+
@stage
|
125
|
+
end
|
126
|
+
|
127
|
+
def on_registration
|
128
|
+
set_state :startable if startable?
|
129
|
+
end
|
130
|
+
|
131
|
+
def start
|
132
|
+
return unless startable?
|
133
|
+
|
134
|
+
increment_stage
|
135
|
+
|
136
|
+
return true
|
137
|
+
end
|
138
|
+
|
139
|
+
def send_stage_info_to_clients
|
140
|
+
if @stage.in_progress?
|
141
|
+
set_state :playing
|
142
|
+
send_start_to_clients
|
143
|
+
|
144
|
+
elsif @stage.success?
|
145
|
+
set_state :startable
|
146
|
+
send_return_to_pub_to_clients
|
147
|
+
|
148
|
+
elsif @stage.failure?
|
149
|
+
set_state :ended
|
150
|
+
send_end_game_to_clients
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def send_start_to_clients
|
155
|
+
each_client do |client|
|
156
|
+
bridge = @stage.bridge_for_player
|
157
|
+
client.start_stage(bridge, @stage.all_items)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def send_return_to_pub_to_clients
|
162
|
+
each_client do |client|
|
163
|
+
client.return_to_pub
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def send_end_game_to_clients
|
168
|
+
each_client do |client|
|
169
|
+
client.end_game game_rundown
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_action_watcher
|
174
|
+
Thread.new do
|
175
|
+
loop do
|
176
|
+
handle_action @ts.take([:action, nil, nil, nil])
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def handle_action action_array
|
182
|
+
if @stage && @stage.in_progress?
|
183
|
+
@stage.complete action_array[1], action_array[3]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Retrieves the latest data from the TupleSpace.
|
189
|
+
|
190
|
+
def update
|
191
|
+
ret = super
|
192
|
+
return if ret.nil?
|
193
|
+
|
194
|
+
@num_players = @registered_services_ary.length
|
195
|
+
|
196
|
+
@player_names = @registered_services_ary.map { |name,| name }
|
197
|
+
|
198
|
+
ret
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def set_state state
|
204
|
+
@state = state if STATES.include? state
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class PirateGame::Image < PirateGame::WavingItem
|
2
|
+
|
3
|
+
def initialize shoes, image, top, left
|
4
|
+
super 0, 10, 4
|
5
|
+
|
6
|
+
@shoes = shoes
|
7
|
+
@image = image
|
8
|
+
@top = top
|
9
|
+
@left = left
|
10
|
+
end
|
11
|
+
|
12
|
+
def animate frame
|
13
|
+
top_offset, left_offset = waving_offset frame
|
14
|
+
|
15
|
+
@ship.move @top + top_offset, @left + left_offset
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw
|
19
|
+
@ship = @shoes.image @image, top: @top, left: @left
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class PirateGame::LogBook
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize size = 10
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@log_book = []
|
10
|
+
@size = size
|
11
|
+
end
|
12
|
+
|
13
|
+
def add entry, author = 'unknown'
|
14
|
+
@mutex.synchronize do
|
15
|
+
@log_book << [entry, author]
|
16
|
+
|
17
|
+
@log_book.shift if @log_book.size > @size
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
return enum_for __method__ unless block_given?
|
23
|
+
|
24
|
+
@log_book.each do |(entry, author)|
|
25
|
+
yield [entry, author]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
@log_book.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def length
|
34
|
+
@log_book.length
|
35
|
+
end
|
36
|
+
alias_method :size, :length
|
37
|
+
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module PirateGame
|
2
|
+
class MasterApp
|
3
|
+
|
4
|
+
def self.run
|
5
|
+
new.run
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@game_master = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
Shoes.app width: 360, height: 360, resizeable: false, title: 'Game Master' do
|
14
|
+
|
15
|
+
def launch_screen
|
16
|
+
clear do
|
17
|
+
background Boot::COLORS[:dark]
|
18
|
+
stack margin: 20 do
|
19
|
+
title "Start Game", stroke: Boot::COLORS[:light]
|
20
|
+
edit_line text: 'Name' do |s|
|
21
|
+
@name = s.text
|
22
|
+
end
|
23
|
+
button('launch') {
|
24
|
+
@game_master = GameMaster.new(name: @name)
|
25
|
+
display_screen
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def display_screen
|
32
|
+
@game_master.update
|
33
|
+
|
34
|
+
clear do
|
35
|
+
background Boot::COLORS[:light]
|
36
|
+
|
37
|
+
stack :margin => 20 do
|
38
|
+
title "Game #{@game_master.name}", stroke: Boot::COLORS[:dark]
|
39
|
+
|
40
|
+
@button_stack = stack
|
41
|
+
@registrations = para stroke: Boot::COLORS[:dark]
|
42
|
+
@stage_info = para stroke: Boot::COLORS[:dark]
|
43
|
+
@game_info = para stroke: Boot::COLORS[:dark]
|
44
|
+
end
|
45
|
+
|
46
|
+
animate(5) {
|
47
|
+
detect_state_change {
|
48
|
+
update_button_stack
|
49
|
+
}
|
50
|
+
|
51
|
+
detect_stage_status_change {
|
52
|
+
@game_master.send_stage_info_to_clients
|
53
|
+
}
|
54
|
+
|
55
|
+
@registrations.replace @game_master.registrations_text
|
56
|
+
@stage_info.replace @game_master.stage_info
|
57
|
+
@game_info.replace @game_master.game_info
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Responsible for updating the display of the START button
|
64
|
+
|
65
|
+
def update_button_stack
|
66
|
+
@button_stack.clear do
|
67
|
+
|
68
|
+
case @state
|
69
|
+
when :startable
|
70
|
+
button('START') {
|
71
|
+
@game_master.start
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# If the game_master.state has changed, yields to the block.
|
79
|
+
|
80
|
+
def detect_state_change
|
81
|
+
return if @state == @game_master.state
|
82
|
+
|
83
|
+
@state = @game_master.state
|
84
|
+
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# If the stage status has changed, yields to the block
|
90
|
+
def detect_stage_status_change
|
91
|
+
return unless @game_master.stage
|
92
|
+
return if @stage_status == @game_master.stage.status
|
93
|
+
|
94
|
+
@stage_status = @game_master.stage.status
|
95
|
+
|
96
|
+
yield
|
97
|
+
end
|
98
|
+
|
99
|
+
launch_screen
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Shoes
|
2
|
+
module Swt
|
3
|
+
class Progress
|
4
|
+
def fraction=(value)
|
5
|
+
@real.selection = (value*100).to_i unless @real.disposed?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class TextBlockPainter
|
10
|
+
|
11
|
+
# added the return statement on line 8
|
12
|
+
def paintControl(paint_event)
|
13
|
+
graphics_context = paint_event.gc
|
14
|
+
gcs_reset graphics_context
|
15
|
+
@text_layout.setText @dsl.text
|
16
|
+
set_styles
|
17
|
+
if @dsl.width
|
18
|
+
@text_layout.setWidth @dsl.width
|
19
|
+
return if @dsl.absolute_left.nil? || @dsl.margin_left.nil? || @dsl.absolute_top.nil? || @dsl.margin_top.nil?
|
20
|
+
@text_layout.draw graphics_context, @dsl.absolute_left + @dsl.margin_left, @dsl.absolute_top + @dsl.margin_top
|
21
|
+
if @dsl.cursor
|
22
|
+
move_text_cursor
|
23
|
+
else
|
24
|
+
(@dsl.textcursor.remove; @dsl.textcursor = nil) if @dsl.textcursor
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'pirate_command'
|
2
|
+
|
3
|
+
class PirateGame::Stage
|
4
|
+
|
5
|
+
attr_accessor :actions_completed
|
6
|
+
attr_accessor :player_stats
|
7
|
+
attr_accessor :all_items
|
8
|
+
attr_accessor :begin_time
|
9
|
+
attr_accessor :level
|
10
|
+
attr_accessor :players
|
11
|
+
|
12
|
+
ITEMS_PER_BRIDGE = 6
|
13
|
+
DURATION = PirateGame::Boot.config["stage_duration"]
|
14
|
+
|
15
|
+
IN_PROGRESS = 'In Progress'
|
16
|
+
SUCCESS = 'Success'
|
17
|
+
FAILURE = 'Failure'
|
18
|
+
|
19
|
+
def initialize(level, players)
|
20
|
+
@level = level
|
21
|
+
@players = players
|
22
|
+
@actions_completed = 0
|
23
|
+
@player_stats = {}
|
24
|
+
generate_all_items
|
25
|
+
|
26
|
+
@begin_time = Time.now
|
27
|
+
end
|
28
|
+
|
29
|
+
def increment
|
30
|
+
PirateGame::Stage.new self.level + 1, self.players
|
31
|
+
end
|
32
|
+
|
33
|
+
def time_left
|
34
|
+
[0, (begin_time + DURATION) - Time.now].max
|
35
|
+
end
|
36
|
+
|
37
|
+
def status
|
38
|
+
if time_left > 0
|
39
|
+
IN_PROGRESS
|
40
|
+
else
|
41
|
+
passed? ? SUCCESS : FAILURE
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def in_progress?
|
46
|
+
status == IN_PROGRESS
|
47
|
+
end
|
48
|
+
|
49
|
+
def success?
|
50
|
+
status == SUCCESS
|
51
|
+
end
|
52
|
+
|
53
|
+
def failure?
|
54
|
+
status == FAILURE
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_all_items
|
58
|
+
@all_items = []
|
59
|
+
|
60
|
+
while @all_items.length < @players*ITEMS_PER_BRIDGE
|
61
|
+
thing = PirateCommand.thing
|
62
|
+
@all_items << thing unless @all_items.include?(thing)
|
63
|
+
end
|
64
|
+
@boards = @all_items.each_slice(ITEMS_PER_BRIDGE).to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
def bridge_for_player
|
68
|
+
@boards.shift
|
69
|
+
end
|
70
|
+
|
71
|
+
def complete action, from
|
72
|
+
@actions_completed += 1
|
73
|
+
@player_stats[from] ||= []
|
74
|
+
@player_stats[from] << action
|
75
|
+
end
|
76
|
+
|
77
|
+
def required_actions
|
78
|
+
@level * 2 + 1
|
79
|
+
end
|
80
|
+
|
81
|
+
def passed?
|
82
|
+
@actions_completed >= required_actions
|
83
|
+
end
|
84
|
+
|
85
|
+
def rundown
|
86
|
+
return if status == IN_PROGRESS
|
87
|
+
|
88
|
+
rundown = {stage: @level, total_actions: @actions_completed}
|
89
|
+
rundown[:player_breakdown] = {}
|
90
|
+
|
91
|
+
@player_stats.each {|p,v| rundown[:player_breakdown][p] = v.size}
|
92
|
+
|
93
|
+
rundown
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|