glimmer-dsl-swt 4.18.3.0 → 4.18.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +440 -18
  4. data/VERSION +1 -1
  5. data/glimmer-dsl-swt.gemspec +11 -7
  6. data/lib/ext/glimmer/config.rb +24 -7
  7. data/lib/ext/rouge/themes/glimmer.rb +29 -0
  8. data/lib/glimmer-dsl-swt.rb +0 -1
  9. data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
  10. data/lib/glimmer/data_binding/widget_binding.rb +22 -4
  11. data/lib/glimmer/dsl/swt/image_expression.rb +14 -6
  12. data/lib/glimmer/dsl/swt/layout_data_expression.rb +4 -4
  13. data/lib/glimmer/dsl/swt/layout_expression.rb +5 -3
  14. data/lib/glimmer/swt/custom/code_text.rb +132 -37
  15. data/lib/glimmer/swt/custom/drawable.rb +3 -2
  16. data/lib/glimmer/swt/custom/shape.rb +25 -11
  17. data/lib/glimmer/swt/date_time_proxy.rb +1 -3
  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/font_proxy.rb +1 -0
  22. data/lib/glimmer/swt/image_proxy.rb +68 -1
  23. data/lib/glimmer/swt/shell_proxy.rb +23 -3
  24. data/lib/glimmer/swt/table_proxy.rb +43 -15
  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 +259 -137
  32. data/samples/elaborate/tetris/{view/game_over_dialog.rb → model/past_game.rb} +11 -44
  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 +131 -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 +1 -1
  43. data/samples/hello/hello_code_text.rb +84 -0
  44. data/samples/hello/hello_link.rb +1 -1
  45. metadata +9 -5
@@ -19,167 +19,289 @@
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 'json'
25
+ require 'glimmer/data_binding/observer'
26
+ require 'glimmer/config'
27
+
22
28
  require_relative 'block'
23
29
  require_relative 'tetromino'
30
+ require_relative 'past_game'
24
31
 
25
32
  class Tetris
26
33
  module Model
27
34
  class Game
35
+ PLAYFIELD_WIDTH = 10
36
+ PLAYFIELD_HEIGHT = 20
37
+ PREVIEW_PLAYFIELD_WIDTH = 4
38
+ PREVIEW_PLAYFIELD_HEIGHT = 2
28
39
  SCORE_MULTIPLIER = {1 => 40, 2 => 100, 3 => 300, 4 => 1200}
29
40
 
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
41
+ attr_reader :playfield_width, :playfield_height
42
+ attr_accessor :game_over, :paused, :preview_tetromino, :lines, :score, :level, :high_scores, :beeping, :added_high_score, :show_high_scores, :up_arrow_action
43
+ alias game_over? game_over
44
+ alias paused? paused
45
+ alias beeping? beeping
46
+ alias added_high_score? added_high_score
44
47
 
45
- def tetrominoes
46
- @tetrominoes ||= reset_tetrominoes
48
+ def initialize(playfield_width = PLAYFIELD_WIDTH, playfield_height = PLAYFIELD_HEIGHT)
49
+ @playfield_width = playfield_width
50
+ @playfield_height = playfield_height
51
+ @high_scores = []
52
+ @show_high_scores = false
53
+ @beeping = true
54
+ @up_arrow_action = :instant_down
55
+ load_high_scores!
56
+ end
57
+
58
+ def configure_beeper(&beeper)
59
+ @beeper = beeper
60
+ end
61
+
62
+ def game_in_progress?
63
+ !game_over? && !paused?
64
+ end
65
+
66
+ def start!
67
+ self.show_high_scores = false
68
+ self.paused = false
69
+ self.level = 1
70
+ self.score = 0
71
+ self.lines = 0
72
+ reset_playfield
73
+ reset_preview_playfield
74
+ reset_tetrominoes
75
+ preview_next_tetromino!
76
+ consider_adding_tetromino
77
+ self.game_over = false
78
+ end
79
+ alias restart! start!
80
+
81
+ def game_over!
82
+ add_high_score!
83
+ beep
84
+ self.game_over = true
85
+ end
86
+
87
+ def clear_high_scores!
88
+ high_scores.clear
89
+ end
90
+
91
+ def add_high_score!
92
+ self.added_high_score = true
93
+ high_scores.prepend(PastGame.new("Player #{high_scores.count + 1}", score, lines, level))
94
+ end
95
+
96
+ def save_high_scores!
97
+ high_score_file_content = @high_scores.map {|past_game| past_game.to_a.join("\t") }.join("\n")
98
+ FileUtils.mkdir_p(tetris_dir)
99
+ File.write(tetris_high_score_file, high_score_file_content)
100
+ rescue => e
101
+ # Fail safely by keeping high scores in memory if unable to access disk
102
+ Glimmer::Config.logger.error {"Failed to save high scores in: #{tetris_high_score_file}\n#{e.full_message}"}
103
+ end
104
+
105
+ def load_high_scores!
106
+ if File.exist?(tetris_high_score_file)
107
+ self.high_scores = File.read(tetris_high_score_file).split("\n").map {|line| PastGame.new(*line.split("\t")) }
47
108
  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
- }
109
+ rescue => e
110
+ # Fail safely by keeping high scores in memory if unable to access disk
111
+ Glimmer::Config.logger.error {"Failed to load high scores from: #{tetris_high_score_file}\n#{e.full_message}"}
112
+ end
113
+
114
+ def tetris_dir
115
+ @tetris_dir ||= File.join(Etc.getpwuid.dir, '.glimmer-tetris')
116
+ end
117
+
118
+ def tetris_high_score_file
119
+ File.join(tetris_dir, "high_scores.txt")
120
+ end
121
+
122
+ def down!(instant: false)
123
+ return unless game_in_progress?
124
+ current_tetromino.down!(instant: instant)
125
+ game_over! if current_tetromino.row <= 0 && current_tetromino.stopped?
126
+ end
127
+
128
+ def right!
129
+ return unless game_in_progress?
130
+ current_tetromino.right!
131
+ end
132
+
133
+ def left!
134
+ return unless game_in_progress?
135
+ current_tetromino.left!
136
+ end
137
+
138
+ def rotate!(direction)
139
+ return unless game_in_progress?
140
+ current_tetromino.rotate!(direction)
141
+ end
142
+
143
+ def current_tetromino
144
+ tetrominoes.last
145
+ end
146
+
147
+ def tetrominoes
148
+ @tetrominoes ||= reset_tetrominoes
149
+ end
150
+
151
+ # Returns blocks in the playfield
152
+ def playfield
153
+ @playfield ||= @original_playfield = @playfield_height.times.map {
154
+ @playfield_width.times.map {
155
+ Block.new
55
156
  }
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
- }
157
+ }
158
+ end
159
+
160
+ # Executes a hypothetical scenario without truly changing playfield permanently
161
+ def hypothetical(&block)
162
+ @playfield = hypothetical_playfield
163
+ block.call
164
+ @playfield = @original_playfield
165
+ end
166
+
167
+ # Returns whether currently executing a hypothetical scenario
168
+ def hypothetical?
169
+ @playfield != @original_playfield
170
+ end
171
+
172
+ def hypothetical_playfield
173
+ @playfield_height.times.map { |row|
174
+ @playfield_width.times.map { |column|
175
+ playfield[row][column].clone
73
176
  }
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
- }
177
+ }
178
+ end
179
+
180
+ def preview_playfield
181
+ @preview_playfield ||= PREVIEW_PLAYFIELD_HEIGHT.times.map {|row|
182
+ PREVIEW_PLAYFIELD_WIDTH.times.map {|column|
183
+ Block.new
81
184
  }
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)
185
+ }
186
+ end
187
+
188
+ def preview_next_tetromino!
189
+ self.preview_tetromino = Tetromino.new(self)
190
+ end
191
+
192
+ def calculate_score!(eliminated_lines)
193
+ new_score = SCORE_MULTIPLIER[eliminated_lines] * (level + 1)
194
+ self.score += new_score
195
+ end
196
+
197
+ def level_up!
198
+ self.level += 1 if lines >= self.level*10
199
+ end
200
+
201
+ def delay
202
+ [1.1 - (level.to_i * 0.1), 0.001].max
203
+ end
204
+
205
+ def beep
206
+ @beeper&.call if beeping
207
+ end
208
+
209
+ def instant_down_on_up=(value)
210
+ self.up_arrow_action = :instant_down if value
211
+ end
212
+
213
+ def instant_down_on_up
214
+ self.up_arrow_action == :instant_down
215
+ end
216
+
217
+ def rotate_right_on_up=(value)
218
+ self.up_arrow_action = :rotate_right if value
219
+ end
220
+
221
+ def rotate_right_on_up
222
+ self.up_arrow_action == :rotate_right
223
+ end
224
+
225
+ def rotate_left_on_up=(value)
226
+ self.up_arrow_action = :rotate_left if value
227
+ end
228
+
229
+ def rotate_left_on_up
230
+ self.up_arrow_action == :rotate_left
231
+ end
232
+
233
+ def reset_tetrominoes
234
+ @tetrominoes = []
235
+ end
236
+
237
+ def reset_playfield
238
+ playfield.each do |row|
239
+ row.each do |block|
240
+ block.clear
114
241
  end
115
242
  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
243
+ end
244
+
245
+ def reset_preview_playfield
246
+ preview_playfield.each do |row|
247
+ row.each do |block|
248
+ block.clear
132
249
  end
133
- playfield[0].each(&:clear)
134
250
  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
251
+ end
252
+
253
+ def consider_adding_tetromino
254
+ if tetrominoes.empty? || current_tetromino.stopped?
255
+ preview_tetromino.launch!
143
256
  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
257
  end
152
-
153
- def reset_playfield
154
- playfield.each do |row|
155
- row.each do |block|
156
- block.clear
157
- end
258
+ end
259
+
260
+ def consider_eliminating_lines
261
+ eliminated_lines = 0
262
+ playfield.each_with_index do |row, playfield_row|
263
+ if row.all? {|block| !block.clear?}
264
+ eliminated_lines += 1
265
+ shift_blocks_down_above_row(playfield_row)
158
266
  end
159
267
  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
268
+ if eliminated_lines > 0
269
+ beep
270
+ self.lines += eliminated_lines
271
+ level_up!
272
+ calculate_score!(eliminated_lines)
167
273
  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
274
+ end
275
+
276
+ def playfield_remaining_heights(tetromino = nil)
277
+ @playfield_width.times.map do |playfield_column|
278
+ bottom_most_block = tetromino.bottom_most_block_for_column(playfield_column)
279
+ (playfield.each_with_index.detect do |row, playfield_row|
280
+ !row[playfield_column].clear? &&
281
+ (
282
+ tetromino.nil? ||
283
+ bottom_most_block.nil? ||
284
+ (playfield_row > tetromino.row + bottom_most_block[:row_index])
285
+ )
286
+ end || [nil, @playfield_height])[1]
287
+ end.to_a
288
+ end
289
+
290
+ private
291
+
292
+ def shift_blocks_down_above_row(row)
293
+ row.downto(0) do |playfield_row|
294
+ playfield[playfield_row].each_with_index do |block, playfield_column|
295
+ previous_row = playfield[playfield_row - 1]
296
+ previous_block = previous_row[playfield_column]
297
+ block.color = previous_block.color unless block.color == previous_block.color
298
+ end
180
299
  end
300
+ playfield[0].each(&:clear)
181
301
  end
302
+
182
303
  end
304
+
183
305
  end
306
+
184
307
  end
185
-
@@ -20,52 +20,19 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  class Tetris
23
- module View
24
- class GameOverDialog
25
- include Glimmer::UI::CustomShell
26
-
27
- options :parent_shell
23
+ module Model
24
+ class PastGame
25
+ attr_accessor :name, :score, :lines, :level
28
26
 
29
- body {
30
- dialog(parent_shell) {
31
- row_layout {
32
- type :vertical
33
- center true
34
- }
35
- text 'Tetris'
36
-
37
- label(:center) {
38
- text 'Game Over!'
39
- font name: 'Menlo', height: 30, style: :bold
40
- }
41
- label # filler
42
- button {
43
- text 'Play Again?'
44
-
45
- on_widget_selected {
46
- Model::Game.restart
47
-
48
- body_root.close
49
- }
50
- }
51
-
52
- on_shell_activated {
53
- Model::Game.game_over = true
54
- display.beep
55
- }
56
-
57
- on_shell_closed {
58
- exit_game
59
- }
60
-
61
- on_key_pressed { |event|
62
- exit_game if event.keyCode == swt(:cr)
63
- }
64
- }
65
- }
27
+ def initialize(name, score, lines, level)
28
+ @name = name
29
+ @score = score.to_i
30
+ @lines = lines.to_i
31
+ @level = level.to_i
32
+ end
66
33
 
67
- def exit_game
68
- display.dispose if Model::Game.game_over?
34
+ def to_a
35
+ [@name, @score, @lines, @level]
69
36
  end
70
37
  end
71
38
  end