glimmer-dsl-swt 4.18.3.0 → 4.18.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-