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.
@@ -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
- widget_listener_proxy = 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)
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 :custom_shell
31
+ attr_reader :launched_custom_shell
32
32
 
33
- def launch
34
- @custom_shell = send(self.name.underscore.gsub('::', '__'))
35
- @custom_shell.open
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
- @before_body_blocks ||= []
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
- @after_body_blocks ||= []
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
- execute_hooks('before_body')
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
- execute_hooks('after_body')
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 execute_hooks(hook_name)
291
- self.class.instance_variable_get("@#{hook_name}_blocks")&.each do |hook_block|
292
- temp_method_name = "#{hook_name}_block_#{hook_block.hash.abs}_#{(Time.now.to_f * 1_000_000).to_i}"
293
- singleton_class.define_method(temp_method_name, &hook_block)
294
- send(temp_method_name)
295
- singleton_class.send(:remove_method, temp_method_name)
296
- end
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
- parent_directory = File.basename(File.dirname(file))
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.mkdir_p(modified_file_parent_directory)
69
- FileUtils.cp_r(file, modified_file_parent_directory)
70
- FileUtils.cp_r(file.sub(/\.rb/, ''), modified_file_parent_directory) if File.exist?(file.sub(/\.rb/, ''))
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
- def initialize
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
- shell {
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
- message_box {
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, 9
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
- }.open
305
+ }
249
306
  end
250
307
  end
251
308
 
252
- MetaSampleApplication.new.launch
309
+ MetaSampleApplication.launch
@@ -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
- PLAYFIELD_WIDTH = 10
35
- PLAYFIELD_HEIGHT = 20
36
- PREVIEW_PLAYFIELD_WIDTH = 4
37
- PREVIEW_PLAYFIELD_HEIGHT = 2
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
- Model::Game.current_tetromino.down
54
+ game.down!
45
55
  when swt(:arrow_left)
46
- Model::Game.current_tetromino.left
56
+ game.left!
47
57
  when swt(:arrow_right)
48
- Model::Game.current_tetromino.right
58
+ game.right!
49
59
  when swt(:shift)
50
60
  if key_event.keyLocation == swt(:right) # right shift key
51
- Model::Game.current_tetromino.rotate(:right)
61
+ game.rotate!(:right)
52
62
  elsif key_event.keyLocation == swt(:left) # left shift key
53
- Model::Game.current_tetromino.rotate(:left)
63
+ game.rotate!(:left)
54
64
  end
55
65
  when 'd'.bytes.first, swt(:arrow_up)
56
- Model::Game.current_tetromino.rotate(:right)
66
+ game.rotate!(:right)
57
67
  when 'a'.bytes.first
58
- Model::Game.current_tetromino.rotate(:left)
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
- Model::Game.start
70
-
71
- Thread.new {
72
- loop {
73
- sleep(Model::Game.delay)
74
- sync_exec {
75
- unless Model::Game.game_over?
76
- Model::Game.current_tetromino.down
77
- game_over_dialog(parent_shell: body_root).open if Model::Game.current_tetromino.row <= 0 && Model::Game.current_tetromino.stopped?
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: Model::Game.playfield, playfield_width: PLAYFIELD_WIDTH, playfield_height: PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
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
- score_lane(block_size: BLOCK_SIZE) {
100
- layout_data(:fill, :fill, false, true)
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
- class << self
31
- attr_accessor :game_over, :preview_tetromino, :lines, :score, :level
32
- alias game_over? game_over
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
- def tetrominoes
46
- @tetrominoes ||= reset_tetrominoes
47
- end
48
-
49
- # Returns blocks in the playfield
50
- def playfield
51
- @playfield ||= @original_playfield = PLAYFIELD_HEIGHT.times.map {
52
- PLAYFIELD_WIDTH.times.map {
53
- Block.new
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
- end
57
-
58
- def hypothetical(&block)
59
- @playfield = hypothetical_playfield
60
- block.call
61
- @playfield = @original_playfield
62
- end
63
-
64
- def hypothetical?
65
- @playfield != @original_playfield
66
- end
67
-
68
- def hypothetical_playfield
69
- PLAYFIELD_HEIGHT.times.map { |row|
70
- PLAYFIELD_WIDTH.times.map { |column|
71
- playfield[row][column].clone
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
- end
75
-
76
- def preview_playfield
77
- @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
78
- PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
79
- Block.new
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
- end
83
-
84
- def preview_next_tetromino!
85
- self.preview_tetromino = Tetromino.new
86
- end
87
-
88
- def calculate_score!(eliminated_lines)
89
- new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
90
- self.score += new_score
91
- end
92
-
93
- def level_up!
94
- self.level += 1 if lines >= self.level*10
95
- end
96
-
97
- def delay
98
- [1.1 - (level.to_i * 0.1), 0.001].max
99
- end
100
-
101
- def consider_eliminating_lines
102
- eliminated_lines = 0
103
- playfield.each_with_index do |row, playfield_row|
104
- if row.all? {|block| !block.clear?}
105
- eliminated_lines += 1
106
- shift_blocks_down_above_row(playfield_row)
107
- end
108
- end
109
- if eliminated_lines > 0
110
- beep
111
- self.lines += eliminated_lines
112
- level_up!
113
- calculate_score!(eliminated_lines)
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
- def beep
118
- @beeper&.call
119
- end
120
-
121
- def configure_beeper(&beeper)
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
- def start
137
- self.level = 1
138
- self.score = 0
139
- self.lines = @previoius_lines = 0
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
- def reset_preview_playfield
162
- preview_playfield.each do |row|
163
- row.each do |block|
164
- block.clear
165
- end
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
- def playfield_remaining_heights(tetromino = nil)
170
- PLAYFIELD_WIDTH.times.map do |playfield_column|
171
- (playfield.each_with_index.detect do |row, playfield_row|
172
- !row[playfield_column].clear? &&
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
-