glimmer-dsl-swt 4.18.3.0 → 4.18.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +48 -5
- data/VERSION +1 -1
- data/glimmer-dsl-swt.gemspec +7 -6
- data/lib/ext/glimmer/config.rb +24 -7
- data/lib/glimmer/data_binding/widget_binding.rb +14 -4
- data/lib/glimmer/swt/custom/shape.rb +3 -2
- data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
- data/lib/glimmer/swt/display_proxy.rb +25 -4
- data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
- data/lib/glimmer/swt/shell_proxy.rb +13 -2
- data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
- data/lib/glimmer/swt/widget_proxy.rb +1 -1
- data/lib/glimmer/ui/custom_shell.rb +10 -9
- data/lib/glimmer/ui/custom_widget.rb +22 -14
- data/samples/elaborate/meta_sample.rb +81 -24
- data/samples/elaborate/tetris.rb +63 -32
- data/samples/elaborate/tetris/model/game.rb +180 -139
- data/samples/elaborate/tetris/model/tetromino.rb +28 -25
- data/samples/elaborate/tetris/view/game_over_dialog.rb +13 -17
- data/samples/elaborate/tetris/view/score_lane.rb +5 -5
- data/samples/elaborate/tetris/view/tetris_menu_bar.rb +72 -0
- data/samples/elaborate/tic_tac_toe.rb +4 -4
- data/samples/hello/hello_canvas_transform.rb +1 -1
- data/samples/hello/hello_link.rb +1 -1
- metadata +5 -4
@@ -696,7 +696,7 @@ module Glimmer
|
|
696
696
|
safe_block = lambda { |*args| block.call(*args) unless @swt_widget.isDisposed }
|
697
697
|
listener = listener_class.new(listener_method => safe_block)
|
698
698
|
@swt_widget.send(widget_add_listener_method, listener)
|
699
|
-
|
699
|
+
WidgetListenerProxy.new(swt_widget: @swt_widget, swt_listener: listener, widget_add_listener_method: widget_add_listener_method, swt_listener_class: listener_class, swt_listener_method: listener_method)
|
700
700
|
end
|
701
701
|
|
702
702
|
# Looks through SWT class add***Listener methods till it finds one for which
|
@@ -28,15 +28,11 @@ module Glimmer
|
|
28
28
|
include Glimmer::UI::CustomWidget
|
29
29
|
|
30
30
|
class << self
|
31
|
-
attr_reader :
|
31
|
+
attr_reader :launched_custom_shell
|
32
32
|
|
33
|
-
def launch
|
34
|
-
@
|
35
|
-
@
|
36
|
-
end
|
37
|
-
|
38
|
-
def shutdown
|
39
|
-
@custom_shell.close
|
33
|
+
def launch(*args, &content)
|
34
|
+
@launched_custom_shell = send(keyword, *args, &content) if @launched_custom_shell.nil? || @launched_custom_shell.disposed?
|
35
|
+
@launched_custom_shell.open
|
40
36
|
end
|
41
37
|
end
|
42
38
|
|
@@ -45,7 +41,7 @@ module Glimmer
|
|
45
41
|
@swt_widget.set_data('custom_shell', self)
|
46
42
|
raise Error, 'Invalid custom shell body root! Must be a shell or another custom shell.' unless body_root.swt_widget.is_a?(org.eclipse.swt.widgets.Shell)
|
47
43
|
end
|
48
|
-
|
44
|
+
|
49
45
|
# Classes may override
|
50
46
|
def open
|
51
47
|
body_root.open
|
@@ -56,6 +52,7 @@ module Glimmer
|
|
56
52
|
open
|
57
53
|
end
|
58
54
|
|
55
|
+
# TODO consider using Forwardable instead
|
59
56
|
def close
|
60
57
|
body_root.close
|
61
58
|
end
|
@@ -68,6 +65,10 @@ module Glimmer
|
|
68
65
|
body_root.visible?
|
69
66
|
end
|
70
67
|
|
68
|
+
def disposed?
|
69
|
+
swt_widget.is_disposed
|
70
|
+
end
|
71
|
+
|
71
72
|
def center_within_display
|
72
73
|
body_root.center_within_display
|
73
74
|
end
|
@@ -77,7 +77,17 @@ module Glimmer
|
|
77
77
|
def flyweight_custom_widget_classes
|
78
78
|
@flyweight_custom_widget_classes ||= {}
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
|
+
# Returns keyword to use for this custom widget
|
82
|
+
def keyword
|
83
|
+
self.name.underscore.gsub('::', '__')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns shortcut keyword to use for this custom widget (keyword minus namespace)
|
87
|
+
def shortcut_keyword
|
88
|
+
self.name.underscore.gsub('::', '__').split('__').last
|
89
|
+
end
|
90
|
+
|
81
91
|
def add_custom_widget_namespaces_for(klass)
|
82
92
|
Glimmer::UI::CustomWidget.namespaces_for_class(klass).drop(1).each do |namespace|
|
83
93
|
Glimmer::UI::CustomWidget.custom_widget_namespaces << namespace
|
@@ -138,8 +148,7 @@ module Glimmer
|
|
138
148
|
end
|
139
149
|
|
140
150
|
def before_body(&block)
|
141
|
-
@
|
142
|
-
@before_body_blocks << block
|
151
|
+
@before_body_block = block
|
143
152
|
end
|
144
153
|
|
145
154
|
def body(&block)
|
@@ -147,8 +156,7 @@ module Glimmer
|
|
147
156
|
end
|
148
157
|
|
149
158
|
def after_body(&block)
|
150
|
-
@
|
151
|
-
@after_body_blocks << block
|
159
|
+
@after_body_block = block
|
152
160
|
end
|
153
161
|
end
|
154
162
|
|
@@ -159,7 +167,7 @@ module Glimmer
|
|
159
167
|
options ||= {}
|
160
168
|
@options = self.class.options.merge(options)
|
161
169
|
@content = Util::ProcTracker.new(content) if content
|
162
|
-
|
170
|
+
execute_hook('before_body')
|
163
171
|
body_block = self.class.instance_variable_get("@body_block")
|
164
172
|
raise Glimmer::Error, 'Invalid custom widget for having no body! Please define body block!' if body_block.nil?
|
165
173
|
@body_root = instance_exec(&body_block)
|
@@ -169,7 +177,7 @@ module Glimmer
|
|
169
177
|
@parent = parent
|
170
178
|
@parent ||= @swt_widget.parent
|
171
179
|
@parent_proxy ||= @parent&.get_data('proxy')
|
172
|
-
|
180
|
+
execute_hook('after_body')
|
173
181
|
end
|
174
182
|
|
175
183
|
# Subclasses may override to perform post initialization work on an added child
|
@@ -287,13 +295,13 @@ module Glimmer
|
|
287
295
|
|
288
296
|
private
|
289
297
|
|
290
|
-
def
|
291
|
-
self.class.instance_variable_get("@#{hook_name}
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
298
|
+
def execute_hook(hook_name)
|
299
|
+
hook_block = self.class.instance_variable_get("@#{hook_name}_block")
|
300
|
+
return if hook_block.nil?
|
301
|
+
temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
|
302
|
+
singleton_class.define_method(temp_method_name, &hook_block)
|
303
|
+
send(temp_method_name)
|
304
|
+
singleton_class.send(:remove_method, temp_method_name)
|
297
305
|
end
|
298
306
|
end
|
299
307
|
end
|
@@ -25,6 +25,24 @@ require 'etc'
|
|
25
25
|
class Sample
|
26
26
|
include Glimmer::DataBinding::ObservableModel
|
27
27
|
|
28
|
+
class << self
|
29
|
+
def glimmer_directory
|
30
|
+
File.expand_path('../../..', __FILE__)
|
31
|
+
end
|
32
|
+
|
33
|
+
def user_glimmer_directory
|
34
|
+
File.join(Etc.getpwuid.dir, '.glimmer-dsl-swt')
|
35
|
+
end
|
36
|
+
|
37
|
+
def ensure_user_glimmer_directory
|
38
|
+
unless @ensured_glimmer_directory
|
39
|
+
FileUtils.rm_rf(user_glimmer_directory)
|
40
|
+
FileUtils.cp_r(glimmer_directory, user_glimmer_directory)
|
41
|
+
@ensured_glimmer_directory = true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
28
46
|
attr_accessor :sample_directory, :file, :selected
|
29
47
|
|
30
48
|
def initialize(file, sample_directory: )
|
@@ -59,25 +77,35 @@ class Sample
|
|
59
77
|
File.basename(file) != 'meta_sample.rb'
|
60
78
|
end
|
61
79
|
alias launchable editable
|
80
|
+
|
81
|
+
def file_relative_path
|
82
|
+
file.sub(self.class.glimmer_directory, '')
|
83
|
+
end
|
84
|
+
|
85
|
+
def user_file
|
86
|
+
File.join(self.class.user_glimmer_directory, file_relative_path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def user_file_parent_directory
|
90
|
+
File.dirname(user_file)
|
91
|
+
end
|
62
92
|
|
93
|
+
def directory
|
94
|
+
file.sub(/\.rb/, '')
|
95
|
+
end
|
96
|
+
|
63
97
|
def launch(modified_code)
|
64
|
-
|
65
|
-
modified_file_parent_directory = File.join(Etc.getpwuid.dir, '.glimmer', 'samples', parent_directory)
|
66
|
-
launch_file = modified_file = File.join(modified_file_parent_directory, File.basename(file))
|
98
|
+
launch_file = user_file
|
67
99
|
begin
|
68
|
-
FileUtils.
|
69
|
-
FileUtils.cp_r(
|
70
|
-
|
71
|
-
File.write(modified_file, modified_code)
|
100
|
+
FileUtils.cp_r(file, user_file_parent_directory)
|
101
|
+
FileUtils.cp_r(directory, user_file_parent_directory) if File.exist?(directory)
|
102
|
+
File.write(user_file, modified_code)
|
72
103
|
rescue => e
|
73
104
|
puts 'Error writing sample modifications. Launching original sample.'
|
74
105
|
puts e.full_message
|
75
106
|
launch_file = file # load original file if failed to write changes
|
76
107
|
end
|
77
108
|
load(launch_file)
|
78
|
-
ensure
|
79
|
-
FileUtils.rm_rf(modified_file)
|
80
|
-
FileUtils.rm_rf(modified_file.sub(/\.rb/, ''))
|
81
109
|
end
|
82
110
|
end
|
83
111
|
|
@@ -162,17 +190,18 @@ class SampleDirectory
|
|
162
190
|
end
|
163
191
|
|
164
192
|
class MetaSampleApplication
|
165
|
-
include Glimmer
|
193
|
+
include Glimmer::UI::CustomShell
|
166
194
|
|
167
|
-
|
195
|
+
before_body {
|
196
|
+
Sample.ensure_user_glimmer_directory
|
168
197
|
selected_sample_directory = SampleDirectory.sample_directories.first
|
169
198
|
selected_sample = selected_sample_directory.samples.first
|
170
199
|
selected_sample_directory.selected_sample_name = selected_sample.name
|
171
|
-
end
|
172
|
-
|
173
|
-
def launch
|
174
200
|
Display.app_name = 'Glimmer Meta-Sample'
|
175
|
-
|
201
|
+
}
|
202
|
+
|
203
|
+
body {
|
204
|
+
shell(:fill_screen) {
|
176
205
|
minimum_size 1280, 768
|
177
206
|
text 'Glimmer Meta-Sample (The Sample of Samples)'
|
178
207
|
image File.expand_path('../../icons/scaffold_app.png', __dir__)
|
@@ -218,11 +247,8 @@ class MetaSampleApplication
|
|
218
247
|
on_widget_selected {
|
219
248
|
begin
|
220
249
|
SampleDirectory.selected_sample.launch(@code_text.text)
|
221
|
-
rescue StandardError, SyntaxError => launch_error
|
222
|
-
|
223
|
-
text 'Error Launching'
|
224
|
-
message launch_error.full_message
|
225
|
-
}.open
|
250
|
+
rescue LoadError, StandardError, SyntaxError => launch_error
|
251
|
+
error_dialog(message: launch_error.full_message).open
|
226
252
|
end
|
227
253
|
}
|
228
254
|
}
|
@@ -243,10 +269,41 @@ class MetaSampleApplication
|
|
243
269
|
editable bind(SampleDirectory, 'selected_sample.editable')
|
244
270
|
}
|
245
271
|
|
246
|
-
weights 4,
|
272
|
+
weights 4, 11
|
273
|
+
}
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
# Method-based error_dialog custom widget
|
278
|
+
def error_dialog(message:)
|
279
|
+
return if message.nil?
|
280
|
+
dialog(body_root) { |dialog_proxy|
|
281
|
+
row_layout(:vertical) {
|
282
|
+
center true
|
283
|
+
}
|
284
|
+
|
285
|
+
text 'Error Launching'
|
286
|
+
|
287
|
+
styled_text(:border, :h_scroll, :v_scroll) {
|
288
|
+
layout_data {
|
289
|
+
width body_root.bounds.width*0.75
|
290
|
+
height body_root.bounds.height*0.75
|
291
|
+
}
|
292
|
+
|
293
|
+
text message
|
294
|
+
editable false
|
295
|
+
caret nil
|
296
|
+
}
|
297
|
+
|
298
|
+
button {
|
299
|
+
text 'Close'
|
300
|
+
|
301
|
+
on_widget_selected {
|
302
|
+
dialog_proxy.close
|
303
|
+
}
|
247
304
|
}
|
248
|
-
}
|
305
|
+
}
|
249
306
|
end
|
250
307
|
end
|
251
308
|
|
252
|
-
MetaSampleApplication.
|
309
|
+
MetaSampleApplication.launch
|
data/samples/elaborate/tetris.rb
CHANGED
@@ -26,59 +26,61 @@ require_relative 'tetris/model/game'
|
|
26
26
|
require_relative 'tetris/view/playfield'
|
27
27
|
require_relative 'tetris/view/score_lane'
|
28
28
|
require_relative 'tetris/view/game_over_dialog'
|
29
|
+
require_relative 'tetris/view/tetris_menu_bar'
|
29
30
|
|
30
31
|
class Tetris
|
31
32
|
include Glimmer::UI::CustomShell
|
32
33
|
|
33
34
|
BLOCK_SIZE = 25
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
|
36
|
+
option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
|
37
|
+
option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
|
38
|
+
|
39
|
+
attr_reader :game
|
38
40
|
|
39
41
|
before_body {
|
42
|
+
@mutex = Mutex.new
|
43
|
+
@game = Model::Game.new(playfield_width, playfield_height)
|
44
|
+
|
45
|
+
@game.configure_beeper do
|
46
|
+
display.beep
|
47
|
+
end
|
48
|
+
|
49
|
+
Display.app_name = 'Glimmer Tetris'
|
40
50
|
display {
|
41
|
-
on_swt_keydown { |key_event|
|
51
|
+
@keyboard_listener = on_swt_keydown { |key_event|
|
42
52
|
case key_event.keyCode
|
43
53
|
when swt(:arrow_down)
|
44
|
-
|
54
|
+
game.down!
|
45
55
|
when swt(:arrow_left)
|
46
|
-
|
56
|
+
game.left!
|
47
57
|
when swt(:arrow_right)
|
48
|
-
|
58
|
+
game.right!
|
49
59
|
when swt(:shift)
|
50
60
|
if key_event.keyLocation == swt(:right) # right shift key
|
51
|
-
|
61
|
+
game.rotate!(:right)
|
52
62
|
elsif key_event.keyLocation == swt(:left) # left shift key
|
53
|
-
|
63
|
+
game.rotate!(:left)
|
54
64
|
end
|
55
65
|
when 'd'.bytes.first, swt(:arrow_up)
|
56
|
-
|
66
|
+
game.rotate!(:right)
|
57
67
|
when 'a'.bytes.first
|
58
|
-
|
68
|
+
game.rotate!(:left)
|
59
69
|
end
|
60
70
|
}
|
61
71
|
}
|
62
|
-
|
63
|
-
Model::Game.configure_beeper do
|
64
|
-
display.beep
|
65
|
-
end
|
66
72
|
}
|
67
73
|
|
68
74
|
after_body {
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
}
|
80
|
-
}
|
81
|
-
}
|
75
|
+
@game_over_observer = observe(@game, :game_over) do |game_over|
|
76
|
+
if game_over
|
77
|
+
@game_over_dialog = game_over_dialog(parent_shell: body_root, game: @game) if @game_over_dialog.nil? || @game_over_dialog.disposed?
|
78
|
+
@game_over_dialog.show
|
79
|
+
else
|
80
|
+
start_moving_tetrominos_down
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@game.start!
|
82
84
|
}
|
83
85
|
|
84
86
|
body {
|
@@ -92,15 +94,44 @@ class Tetris
|
|
92
94
|
}
|
93
95
|
|
94
96
|
text 'Glimmer Tetris'
|
97
|
+
minimum_size 475, 500
|
95
98
|
background :gray
|
99
|
+
|
100
|
+
tetris_menu_bar(game: game)
|
96
101
|
|
97
|
-
playfield(game_playfield:
|
102
|
+
playfield(game_playfield: game.playfield, playfield_width: playfield_width, playfield_height: playfield_height, block_size: BLOCK_SIZE)
|
103
|
+
|
104
|
+
score_lane(game: game, block_size: BLOCK_SIZE) {
|
105
|
+
layout_data(:fill, :fill, true, true)
|
106
|
+
}
|
98
107
|
|
99
|
-
|
100
|
-
|
108
|
+
on_widget_disposed {
|
109
|
+
deregister_observers
|
101
110
|
}
|
102
111
|
}
|
103
112
|
}
|
113
|
+
|
114
|
+
def start_moving_tetrominos_down
|
115
|
+
Thread.new do
|
116
|
+
@mutex.synchronize do
|
117
|
+
loop do
|
118
|
+
time = Time.now
|
119
|
+
sleep @game.delay
|
120
|
+
break if @game.game_over? || body_root.disposed?
|
121
|
+
sync_exec {
|
122
|
+
@game.down! unless @game.paused?
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def deregister_observers
|
130
|
+
@game_over_observer&.deregister
|
131
|
+
@game_over_observer = nil
|
132
|
+
@keyboard_listener&.deregister
|
133
|
+
@keyboard_listener = nil
|
134
|
+
end
|
104
135
|
end
|
105
136
|
|
106
137
|
Tetris.launch
|
@@ -25,161 +25,202 @@ require_relative 'tetromino'
|
|
25
25
|
class Tetris
|
26
26
|
module Model
|
27
27
|
class Game
|
28
|
+
PLAYFIELD_WIDTH = 10
|
29
|
+
PLAYFIELD_HEIGHT = 20
|
30
|
+
PREVIEW_PLAYFIELD_WIDTH = 4
|
31
|
+
PREVIEW_PLAYFIELD_HEIGHT = 2
|
28
32
|
SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def consider_adding_tetromino
|
35
|
-
if tetrominoes.empty? || Game.current_tetromino.stopped?
|
36
|
-
preview_tetromino.launch!
|
37
|
-
preview_next_tetromino!
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def current_tetromino
|
42
|
-
tetrominoes.last
|
43
|
-
end
|
34
|
+
attr_reader :playfield_width, :playfield_height
|
35
|
+
attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level
|
36
|
+
alias game_over? game_over
|
37
|
+
alias paused? paused
|
44
38
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
39
|
+
def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
|
40
|
+
@playfield_width = playfield_width
|
41
|
+
@playfield_height = playfield_height
|
42
|
+
end
|
43
|
+
|
44
|
+
def configure_beeper(&beeper)
|
45
|
+
@beeper = beeper
|
46
|
+
end
|
47
|
+
|
48
|
+
def game_in_progress?
|
49
|
+
!game_over? && !paused?
|
50
|
+
end
|
51
|
+
|
52
|
+
def start!
|
53
|
+
self.paused = false
|
54
|
+
self.level = 1
|
55
|
+
self.score = 0
|
56
|
+
self.lines = 0
|
57
|
+
reset_playfield
|
58
|
+
reset_preview_playfield
|
59
|
+
reset_tetrominoes
|
60
|
+
preview_next_tetromino!
|
61
|
+
consider_adding_tetromino
|
62
|
+
self.game_over = false
|
63
|
+
end
|
64
|
+
alias restart! start!
|
65
|
+
|
66
|
+
def down!
|
67
|
+
return unless game_in_progress?
|
68
|
+
current_tetromino.down!
|
69
|
+
self.game_over = true if current_tetromino.row <= 0 && current_tetromino.stopped?
|
70
|
+
end
|
71
|
+
|
72
|
+
def right!
|
73
|
+
return unless game_in_progress?
|
74
|
+
current_tetromino.right!
|
75
|
+
end
|
76
|
+
|
77
|
+
def left!
|
78
|
+
return unless game_in_progress?
|
79
|
+
current_tetromino.left!
|
80
|
+
end
|
81
|
+
|
82
|
+
def rotate!(direction)
|
83
|
+
return unless game_in_progress?
|
84
|
+
current_tetromino.rotate!(direction)
|
85
|
+
end
|
86
|
+
|
87
|
+
def current_tetromino
|
88
|
+
tetrominoes.last
|
89
|
+
end
|
90
|
+
|
91
|
+
def tetrominoes
|
92
|
+
@tetrominoes ||= reset_tetrominoes
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns blocks in the playfield
|
96
|
+
def playfield
|
97
|
+
@playfield ||= @original_playfield = @playfield_height.times.map {
|
98
|
+
@playfield_width.times.map {
|
99
|
+
Block.new
|
55
100
|
}
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
# Executes a hypothetical scenario without truly changing playfield permanently
|
105
|
+
def hypothetical(&block)
|
106
|
+
@playfield = hypothetical_playfield
|
107
|
+
block.call
|
108
|
+
@playfield = @original_playfield
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns whether currently executing a hypothetical scenario
|
112
|
+
def hypothetical?
|
113
|
+
@playfield != @original_playfield
|
114
|
+
end
|
115
|
+
|
116
|
+
def hypothetical_playfield
|
117
|
+
@playfield_height.times.map { |row|
|
118
|
+
@playfield_width.times.map { |column|
|
119
|
+
playfield[row][column].clone
|
73
120
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def preview_playfield
|
125
|
+
@preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
|
126
|
+
PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
|
127
|
+
Block.new
|
81
128
|
}
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def preview_next_tetromino!
|
133
|
+
self.preview_tetromino = Tetromino.new(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
def calculate_score!(eliminated_lines)
|
137
|
+
new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
|
138
|
+
self.score += new_score
|
139
|
+
end
|
140
|
+
|
141
|
+
def level_up!
|
142
|
+
self.level += 1 if lines >= self.level*10
|
143
|
+
end
|
144
|
+
|
145
|
+
def delay
|
146
|
+
[1.1 - (level.to_i * 0.1), 0.001].max
|
147
|
+
end
|
148
|
+
|
149
|
+
def beep
|
150
|
+
@beeper&.call
|
151
|
+
end
|
152
|
+
|
153
|
+
def reset_tetrominoes
|
154
|
+
@tetrominoes = []
|
155
|
+
end
|
156
|
+
|
157
|
+
def reset_playfield
|
158
|
+
playfield.each do |row|
|
159
|
+
row.each do |block|
|
160
|
+
block.clear
|
114
161
|
end
|
115
162
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
@beeper = beeper
|
123
|
-
end
|
124
|
-
|
125
|
-
def shift_blocks_down_above_row(row)
|
126
|
-
row.downto(0) do |playfield_row|
|
127
|
-
playfield[playfield_row].each_with_index do |block, playfield_column|
|
128
|
-
previous_row = playfield[playfield_row - 1]
|
129
|
-
previous_block = previous_row[playfield_column]
|
130
|
-
block.color = previous_block.color unless block.color == previous_block.color
|
131
|
-
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def reset_preview_playfield
|
166
|
+
preview_playfield.each do |row|
|
167
|
+
row.each do |block|
|
168
|
+
block.clear
|
132
169
|
end
|
133
|
-
playfield[0].each(&:clear)
|
134
170
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
reset_playfield
|
141
|
-
reset_preview_playfield
|
142
|
-
reset_tetrominoes
|
171
|
+
end
|
172
|
+
|
173
|
+
def consider_adding_tetromino
|
174
|
+
if tetrominoes.empty? || current_tetromino.stopped?
|
175
|
+
preview_tetromino.launch!
|
143
176
|
preview_next_tetromino!
|
144
|
-
consider_adding_tetromino
|
145
|
-
self.game_over = false
|
146
|
-
end
|
147
|
-
alias restart start
|
148
|
-
|
149
|
-
def reset_tetrominoes
|
150
|
-
@tetrominoes = []
|
151
|
-
end
|
152
|
-
|
153
|
-
def reset_playfield
|
154
|
-
playfield.each do |row|
|
155
|
-
row.each do |block|
|
156
|
-
block.clear
|
157
|
-
end
|
158
|
-
end
|
159
177
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
178
|
+
end
|
179
|
+
|
180
|
+
def consider_eliminating_lines
|
181
|
+
eliminated_lines = 0
|
182
|
+
playfield.each_with_index do |row, playfield_row|
|
183
|
+
if row.all? {|block| !block.clear?}
|
184
|
+
eliminated_lines += 1
|
185
|
+
shift_blocks_down_above_row(playfield_row)
|
166
186
|
end
|
167
187
|
end
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
(
|
174
|
-
tetromino.nil? ||
|
175
|
-
(bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)).nil? ||
|
176
|
-
(playfield_row > tetromino.row + bottom_most_block[:row])
|
177
|
-
)
|
178
|
-
end || [nil, PLAYFIELD_HEIGHT])[1]
|
179
|
-
end.to_a
|
188
|
+
if eliminated_lines > 0
|
189
|
+
beep
|
190
|
+
self.lines += eliminated_lines
|
191
|
+
level_up!
|
192
|
+
calculate_score!(eliminated_lines)
|
180
193
|
end
|
181
194
|
end
|
195
|
+
|
196
|
+
def playfield_remaining_heights(tetromino = nil)
|
197
|
+
@playfield_width.times.map do |playfield_column|
|
198
|
+
(playfield.each_with_index.detect do |row, playfield_row|
|
199
|
+
!row[playfield_column].clear? &&
|
200
|
+
(
|
201
|
+
tetromino.nil? ||
|
202
|
+
(bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)).nil? ||
|
203
|
+
(playfield_row > tetromino.row + bottom_most_block[:row])
|
204
|
+
)
|
205
|
+
end || [nil, @playfield_height])[1]
|
206
|
+
end.to_a
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def shift_blocks_down_above_row(row)
|
212
|
+
row.downto(0) do |playfield_row|
|
213
|
+
playfield[playfield_row].each_with_index do |block, playfield_column|
|
214
|
+
previous_row = playfield[playfield_row - 1]
|
215
|
+
previous_block = previous_row[playfield_column]
|
216
|
+
block.color = previous_block.color unless block.color == previous_block.color
|
217
|
+
end
|
218
|
+
end
|
219
|
+
playfield[0].each(&:clear)
|
220
|
+
end
|
221
|
+
|
182
222
|
end
|
223
|
+
|
183
224
|
end
|
225
|
+
|
184
226
|
end
|
185
|
-
|