glimmer-dsl-swt 4.18.2.5 → 4.18.3.4

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +344 -35
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +13 -7
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  8. data/lib/glimmer/data_binding/widget_binding.rb +22 -4
  9. data/lib/glimmer/dsl/swt/dsl.rb +1 -0
  10. data/lib/glimmer/dsl/swt/image_expression.rb +14 -6
  11. data/lib/glimmer/dsl/swt/layout_data_expression.rb +4 -4
  12. data/lib/glimmer/dsl/swt/layout_expression.rb +5 -3
  13. data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
  14. data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
  15. data/{samples/elaborate/tetris/view/game_over_dialog.rb → lib/glimmer/dsl/swt/transform_expression.rb} +29 -46
  16. data/lib/glimmer/swt/custom/drawable.rb +4 -3
  17. data/lib/glimmer/swt/custom/shape.rb +37 -14
  18. data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
  19. data/lib/glimmer/swt/display_proxy.rb +26 -5
  20. data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
  21. data/lib/glimmer/swt/image_proxy.rb +68 -1
  22. data/lib/glimmer/swt/shell_proxy.rb +23 -3
  23. data/lib/glimmer/swt/table_proxy.rb +31 -7
  24. data/lib/glimmer/swt/transform_proxy.rb +109 -0
  25. data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
  26. data/lib/glimmer/swt/widget_proxy.rb +7 -3
  27. data/lib/glimmer/ui/custom_shell.rb +11 -9
  28. data/lib/glimmer/ui/custom_widget.rb +32 -17
  29. data/samples/elaborate/meta_sample.rb +81 -24
  30. data/samples/elaborate/tetris.rb +146 -42
  31. data/samples/elaborate/tetris/model/game.rb +258 -137
  32. data/samples/elaborate/tetris/model/past_game.rb +26 -0
  33. data/samples/elaborate/tetris/model/tetromino.rb +45 -29
  34. data/samples/elaborate/tetris/view/block.rb +8 -13
  35. data/samples/elaborate/tetris/view/high_score_dialog.rb +133 -0
  36. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  37. data/samples/elaborate/tetris/view/score_lane.rb +11 -11
  38. data/samples/elaborate/tetris/view/tetris_menu_bar.rb +139 -0
  39. data/samples/elaborate/tic_tac_toe.rb +4 -4
  40. data/samples/hello/hello_canvas.rb +4 -4
  41. data/samples/hello/hello_canvas_animation.rb +3 -3
  42. data/samples/hello/hello_canvas_transform.rb +40 -0
  43. data/samples/hello/hello_link.rb +1 -1
  44. metadata +11 -5
@@ -25,60 +25,88 @@ require_relative 'tetris/model/game'
25
25
 
26
26
  require_relative 'tetris/view/playfield'
27
27
  require_relative 'tetris/view/score_lane'
28
- require_relative 'tetris/view/game_over_dialog'
28
+ require_relative 'tetris/view/high_score_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
+ FONT_NAME = 'Menlo'
36
+ FONT_TITLE_HEIGHT = 32
37
+ FONT_TITLE_STYLE = :bold
38
+ BEVEL_CONSTANT = 20
39
+
40
+ option :playfield_width, default: Model::Game::PLAYFIELD_WIDTH
41
+ option :playfield_height, default: Model::Game::PLAYFIELD_HEIGHT
42
+
43
+ attr_reader :game
38
44
 
39
45
  before_body {
46
+ @mutex = Mutex.new
47
+ @game = Model::Game.new(playfield_width, playfield_height)
48
+
49
+ @game.configure_beeper do
50
+ display.beep
51
+ end
52
+
53
+ Display.app_name = 'Glimmer Tetris'
40
54
  display {
41
- on_swt_keydown { |key_event|
55
+ @keyboard_listener = on_swt_keydown { |key_event|
42
56
  case key_event.keyCode
43
- when swt(:arrow_down)
44
- Model::Game.current_tetromino.down
45
- when swt(:arrow_left)
46
- Model::Game.current_tetromino.left
47
- when swt(:arrow_right)
48
- Model::Game.current_tetromino.right
49
- when swt(:shift)
57
+ when swt(:arrow_down), 's'.bytes.first
58
+ game.down!
59
+ when swt(:arrow_up)
60
+ case game.up_arrow_action
61
+ when :instant_down
62
+ game.down!(instant: true)
63
+ when :rotate_right
64
+ game.rotate!(:right)
65
+ when :rotate_left
66
+ game.rotate!(:left)
67
+ end
68
+ when swt(:arrow_left), 'a'.bytes.first
69
+ game.left!
70
+ when swt(:arrow_right), 'd'.bytes.first
71
+ game.right!
72
+ when swt(:shift), swt(:alt)
50
73
  if key_event.keyLocation == swt(:right) # right shift key
51
- Model::Game.current_tetromino.rotate(:right)
74
+ game.rotate!(:right)
52
75
  elsif key_event.keyLocation == swt(:left) # left shift key
53
- Model::Game.current_tetromino.rotate(:left)
76
+ game.rotate!(:left)
54
77
  end
55
- when 'd'.bytes.first, swt(:arrow_up)
56
- Model::Game.current_tetromino.rotate(:right)
57
- when 'a'.bytes.first
58
- Model::Game.current_tetromino.rotate(:left)
78
+ when swt(:ctrl)
79
+ game.rotate!(:left)
59
80
  end
60
81
  }
82
+
83
+ # if running in app mode, set the Mac app about dialog (ignored in platforms)
84
+ @about_observer = on_about {
85
+ show_about_dialog
86
+ }
87
+
88
+ @quit_observer = on_quit {
89
+ exit(0)
90
+ }
61
91
  }
62
-
63
- Model::Game.configure_beeper do
64
- display.beep
65
- end
66
92
  }
67
93
 
68
94
  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
- }
95
+ @game_over_observer = observe(@game, :game_over) do |game_over|
96
+ if game_over
97
+ show_high_score_dialog
98
+ else
99
+ start_moving_tetrominos_down
100
+ end
101
+ end
102
+ @show_high_scores_observer = observe(@game, :show_high_scores) do |show_high_scores|
103
+ if show_high_scores
104
+ show_high_score_dialog
105
+ else
106
+ @high_score_dialog.close unless @high_score_dialog.nil? || @high_score_dialog.disposed? || !@high_score_dialog.visible?
107
+ end
108
+ end
109
+ @game.start!
82
110
  }
83
111
 
84
112
  body {
@@ -92,15 +120,91 @@ class Tetris
92
120
  }
93
121
 
94
122
  text 'Glimmer Tetris'
95
- background :gray
96
-
97
- playfield(game_playfield: Model::Game.playfield, playfield_width: PLAYFIELD_WIDTH, playfield_height: PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
98
-
99
- score_lane(block_size: BLOCK_SIZE) {
100
- layout_data(:fill, :fill, false, true)
123
+ minimum_size 475, 500
124
+ image tetris_icon
125
+
126
+ tetris_menu_bar(game: game)
127
+
128
+ playfield(game_playfield: game.playfield, playfield_width: playfield_width, playfield_height: playfield_height, block_size: BLOCK_SIZE)
129
+
130
+ score_lane(game: game, block_size: BLOCK_SIZE) {
131
+ layout_data(:fill, :fill, true, true)
132
+ }
133
+
134
+ on_widget_disposed {
135
+ deregister_observers
101
136
  }
102
137
  }
103
138
  }
139
+
140
+ def tetris_icon
141
+ icon_block_size = 64
142
+ icon_bevel_size = icon_block_size.to_f / 25.to_f
143
+ icon_bevel_pixel_size = 0.16*icon_block_size.to_f
144
+ icon_size = 8
145
+ icon_pixel_size = icon_block_size * icon_size
146
+ image(icon_pixel_size, icon_pixel_size) {
147
+ icon_size.times { |row|
148
+ icon_size.times { |column|
149
+ colored = row >= 1 && column.between?(1, 6)
150
+ color = colored ? color(([:white] + Model::Tetromino::LETTER_COLORS.values).sample) : color(:white)
151
+ x = column * icon_block_size
152
+ y = row * icon_block_size
153
+ rectangle(x, y, icon_block_size, icon_block_size, fill: true) {
154
+ background color
155
+ }
156
+ polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size, fill: true) {
157
+ background rgb(color.red + 4*BEVEL_CONSTANT, color.green + 4*BEVEL_CONSTANT, color.blue + 4*BEVEL_CONSTANT)
158
+ }
159
+ polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size, fill: true) {
160
+ background rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
161
+ }
162
+ polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, fill: true) {
163
+ background rgb(color.red - 2*BEVEL_CONSTANT, color.green - 2*BEVEL_CONSTANT, color.blue - 2*BEVEL_CONSTANT)
164
+ }
165
+ polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size, fill: true) {
166
+ background rgb(color.red - BEVEL_CONSTANT, color.green - BEVEL_CONSTANT, color.blue - BEVEL_CONSTANT)
167
+ }
168
+ }
169
+ }
170
+ }
171
+ end
172
+
173
+ def start_moving_tetrominos_down
174
+ Thread.new do
175
+ @mutex.synchronize do
176
+ loop do
177
+ time = Time.now
178
+ sleep @game.delay
179
+ break if @game.game_over? || body_root.disposed?
180
+ sync_exec {
181
+ @game.down! unless @game.paused?
182
+ }
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def show_high_score_dialog
189
+ return if @high_score_dialog&.visible?
190
+ @high_score_dialog = high_score_dialog(parent_shell: body_root, game: @game) if @high_score_dialog.nil? || @high_score_dialog.disposed?
191
+ @high_score_dialog.show
192
+ end
193
+
194
+ def show_about_dialog
195
+ message_box {
196
+ text 'Glimmer Tetris'
197
+ message "Glimmer Tetris\n\nGlimmer DSL for SWT Sample\n\nCopyright (c) 2007-2021 Andy Maleh"
198
+ }.open
199
+ end
200
+
201
+ def deregister_observers
202
+ @show_high_scores_observer&.deregister
203
+ @game_over_observer&.deregister
204
+ @keyboard_listener&.deregister
205
+ @about_observer&.deregister
206
+ @quit_observer&.deregister
207
+ end
104
208
  end
105
209
 
106
210
  Tetris.launch
@@ -19,167 +19,288 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'fileutils'
23
+ require 'etc'
24
+ require 'glimmer/data_binding/observer'
25
+ require 'glimmer/config'
26
+
22
27
  require_relative 'block'
23
28
  require_relative 'tetromino'
29
+ require_relative 'past_game'
24
30
 
25
31
  class Tetris
26
32
  module Model
27
33
  class Game
34
+ PLAYFIELD_WIDTH = 10
35
+ PLAYFIELD_HEIGHT = 20
36
+ PREVIEW_PLAYFIELD_WIDTH = 4
37
+ PREVIEW_PLAYFIELD_HEIGHT = 2
28
38
  SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
29
39
 
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
40
+ attr_reader :playfield_width, :playfield_height
41
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score, :show_high_scores, :up_arrow_action
42
+ alias game_over? game_over
43
+ alias paused? paused
44
+ alias beeping? beeping
45
+ alias added_high_score? added_high_score
44
46
 
45
- def tetrominoes
46
- @tetrominoes ||= reset_tetrominoes
47
+ def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
48
+ @playfield_width = playfield_width
49
+ @playfield_height = playfield_height
50
+ @high_scores = []
51
+ @show_high_scores = false
52
+ @beeping = true
53
+ @up_arrow_action = :instant_down
54
+ load_high_scores!
55
+ end
56
+
57
+ def configure_beeper(&beeper)
58
+ @beeper = beeper
59
+ end
60
+
61
+ def game_in_progress?
62
+ !game_over? && !paused?
63
+ end
64
+
65
+ def start!
66
+ self.show_high_scores = false
67
+ self.paused = false
68
+ self.level = 1
69
+ self.score = 0
70
+ self.lines = 0
71
+ reset_playfield
72
+ reset_preview_playfield
73
+ reset_tetrominoes
74
+ preview_next_tetromino!
75
+ consider_adding_tetromino
76
+ self.game_over = false
77
+ end
78
+ alias restart! start!
79
+
80
+ def game_over!
81
+ add_high_score!
82
+ beep
83
+ self.game_over = true
84
+ end
85
+
86
+ def clear_high_scores!
87
+ high_scores.clear
88
+ end
89
+
90
+ def add_high_score!
91
+ self.added_high_score = true
92
+ high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
93
+ end
94
+
95
+ def save_high_scores!
96
+ high_score_file_content = @high_scores.map {|past_game| past_game.to_a.join("\t") }.join("\n")
97
+ FileUtils.mkdir_p(tetris_dir)
98
+ File.write(tetris_high_score_file, high_score_file_content)
99
+ rescue => e
100
+ # Fail safely by keeping high scores in memory if unable to access disk
101
+ Glimmer::Config.logger.error {"Failed to save high scores in: #{tetris_high_score_file}\n#{e.full_message}"}
102
+ end
103
+
104
+ def load_high_scores!
105
+ if File.exist?(tetris_high_score_file)
106
+ self.high_scores = File.read(tetris_high_score_file).split("\n").map {|line| PastGame.new(*line.split("\t")) }
47
107
  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
- }
108
+ rescue => e
109
+ # Fail safely by keeping high scores in memory if unable to access disk
110
+ Glimmer::Config.logger.error {"Failed to load high scores from: #{tetris_high_score_file}\n#{e.full_message}"}
111
+ end
112
+
113
+ def tetris_dir
114
+ @tetris_dir ||= File.join(Etc.getpwuid.dir, '.glimmer-tetris')
115
+ end
116
+
117
+ def tetris_high_score_file
118
+ File.join(tetris_dir, "high_scores.txt")
119
+ end
120
+
121
+ def down!(instant: false)
122
+ return unless game_in_progress?
123
+ current_tetromino.down!(instant: instant)
124
+ game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
125
+ end
126
+
127
+ def right!
128
+ return unless game_in_progress?
129
+ current_tetromino.right!
130
+ end
131
+
132
+ def left!
133
+ return unless game_in_progress?
134
+ current_tetromino.left!
135
+ end
136
+
137
+ def rotate!(direction)
138
+ return unless game_in_progress?
139
+ current_tetromino.rotate!(direction)
140
+ end
141
+
142
+ def current_tetromino
143
+ tetrominoes.last
144
+ end
145
+
146
+ def tetrominoes
147
+ @tetrominoes ||= reset_tetrominoes
148
+ end
149
+
150
+ # Returns blocks in the playfield
151
+ def playfield
152
+ @playfield ||= @original_playfield = @playfield_height.times.map {
153
+ @playfield_width.times.map {
154
+ Block.new
55
155
  }
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
- }
156
+ }
157
+ end
158
+
159
+ # Executes a hypothetical scenario without truly changing playfield permanently
160
+ def hypothetical(&block)
161
+ @playfield = hypothetical_playfield
162
+ block.call
163
+ @playfield = @original_playfield
164
+ end
165
+
166
+ # Returns whether currently executing a hypothetical scenario
167
+ def hypothetical?
168
+ @playfield != @original_playfield
169
+ end
170
+
171
+ def hypothetical_playfield
172
+ @playfield_height.times.map { |row|
173
+ @playfield_width.times.map { |column|
174
+ playfield[row][column].clone
73
175
  }
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
- }
176
+ }
177
+ end
178
+
179
+ def preview_playfield
180
+ @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
181
+ PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
182
+ Block.new
81
183
  }
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)
184
+ }
185
+ end
186
+
187
+ def preview_next_tetromino!
188
+ self.preview_tetromino = Tetromino.new(self)
189
+ end
190
+
191
+ def calculate_score!(eliminated_lines)
192
+ new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
193
+ self.score += new_score
194
+ end
195
+
196
+ def level_up!
197
+ self.level += 1 if lines >= self.level*10
198
+ end
199
+
200
+ def delay
201
+ [1.1 - (level.to_i * 0.1), 0.001].max
202
+ end
203
+
204
+ def beep
205
+ @beeper&.call if beeping
206
+ end
207
+
208
+ def instant_down_on_up=(value)
209
+ self.up_arrow_action = :instant_down if value
210
+ end
211
+
212
+ def instant_down_on_up
213
+ self.up_arrow_action == :instant_down
214
+ end
215
+
216
+ def rotate_right_on_up=(value)
217
+ self.up_arrow_action = :rotate_right if value
218
+ end
219
+
220
+ def rotate_right_on_up
221
+ self.up_arrow_action == :rotate_right
222
+ end
223
+
224
+ def rotate_left_on_up=(value)
225
+ self.up_arrow_action = :rotate_left if value
226
+ end
227
+
228
+ def rotate_left_on_up
229
+ self.up_arrow_action == :rotate_left
230
+ end
231
+
232
+ def reset_tetrominoes
233
+ @tetrominoes = []
234
+ end
235
+
236
+ def reset_playfield
237
+ playfield.each do |row|
238
+ row.each do |block|
239
+ block.clear
114
240
  end
115
241
  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
242
+ end
243
+
244
+ def reset_preview_playfield
245
+ preview_playfield.each do |row|
246
+ row.each do |block|
247
+ block.clear
132
248
  end
133
- playfield[0].each(&:clear)
134
249
  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
250
+ end
251
+
252
+ def consider_adding_tetromino
253
+ if tetrominoes.empty? || current_tetromino.stopped?
254
+ preview_tetromino.launch!
143
255
  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
256
  end
152
-
153
- def reset_playfield
154
- playfield.each do |row|
155
- row.each do |block|
156
- block.clear
157
- end
257
+ end
258
+
259
+ def consider_eliminating_lines
260
+ eliminated_lines = 0
261
+ playfield.each_with_index do |row, playfield_row|
262
+ if row.all? {|block| !block.clear?}
263
+ eliminated_lines += 1
264
+ shift_blocks_down_above_row(playfield_row)
158
265
  end
159
266
  end
160
-
161
- def reset_preview_playfield
162
- preview_playfield.each do |row|
163
- row.each do |block|
164
- block.clear
165
- end
166
- end
267
+ if eliminated_lines > 0
268
+ beep
269
+ self.lines += eliminated_lines
270
+ level_up!
271
+ calculate_score!(eliminated_lines)
167
272
  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
273
+ end
274
+
275
+ def playfield_remaining_heights(tetromino = nil)
276
+ @playfield_width.times.map do |playfield_column|
277
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
278
+ (playfield.each_with_index.detect do |row, playfield_row|
279
+ !row[playfield_column].clear? &&
280
+ (
281
+ tetromino.nil? ||
282
+ bottom_most_block.nil? ||
283
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
284
+ )
285
+ end || [nil, @playfield_height])[1]
286
+ end.to_a
287
+ end
288
+
289
+ private
290
+
291
+ def shift_blocks_down_above_row(row)
292
+ row.downto(0) do |playfield_row|
293
+ playfield[playfield_row].each_with_index do |block, playfield_column|
294
+ previous_row = playfield[playfield_row - 1]
295
+ previous_block = previous_row[playfield_column]
296
+ block.color = previous_block.color unless block.color == previous_block.color
297
+ end
180
298
  end
299
+ playfield[0].each(&:clear)
181
300
  end
301
+
182
302
  end
303
+
183
304
  end
305
+
184
306
  end
185
-