glimmer-dsl-swt 4.18.2.5 → 4.18.3.4

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