glimmer-dsl-swt 4.18.2.4 → 4.18.3.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +240 -30
- data/VERSION +1 -1
- data/glimmer-dsl-swt.gemspec +13 -7
- data/lib/ext/glimmer/config.rb +24 -7
- data/lib/glimmer/data_binding/table_items_binding.rb +8 -5
- data/lib/glimmer/data_binding/widget_binding.rb +22 -4
- data/lib/glimmer/dsl/swt/color_expression.rb +4 -4
- data/lib/glimmer/dsl/swt/data_binding_expression.rb +3 -3
- data/lib/glimmer/dsl/swt/dsl.rb +1 -0
- data/lib/glimmer/dsl/swt/multiply_expression.rb +53 -0
- data/lib/glimmer/dsl/swt/property_expression.rb +4 -2
- data/lib/glimmer/dsl/swt/shape_expression.rb +2 -4
- data/{samples/elaborate/tetris/view/game_over_dialog.rb → lib/glimmer/dsl/swt/transform_expression.rb} +29 -46
- data/lib/glimmer/dsl/swt/widget_expression.rb +2 -1
- data/lib/glimmer/swt/color_proxy.rb +28 -6
- data/lib/glimmer/swt/custom/drawable.rb +8 -0
- data/lib/glimmer/swt/custom/shape.rb +66 -26
- data/lib/glimmer/swt/directory_dialog_proxy.rb +3 -3
- data/lib/glimmer/swt/display_proxy.rb +26 -5
- data/lib/glimmer/swt/file_dialog_proxy.rb +3 -3
- data/lib/glimmer/swt/shell_proxy.rb +24 -4
- data/lib/glimmer/swt/table_proxy.rb +31 -7
- data/lib/glimmer/swt/transform_proxy.rb +109 -0
- data/lib/glimmer/swt/widget_listener_proxy.rb +14 -5
- data/lib/glimmer/swt/widget_proxy.rb +35 -20
- data/lib/glimmer/ui/custom_shell.rb +11 -9
- data/lib/glimmer/ui/custom_widget.rb +65 -39
- data/samples/elaborate/meta_sample.rb +81 -24
- data/samples/elaborate/tetris.rb +105 -44
- data/samples/elaborate/tetris/model/block.rb +1 -1
- data/samples/elaborate/tetris/model/game.rb +233 -137
- data/samples/elaborate/tetris/model/past_game.rb +26 -0
- data/samples/elaborate/tetris/model/tetromino.rb +46 -30
- data/samples/elaborate/tetris/view/block.rb +33 -5
- data/samples/elaborate/tetris/view/high_score_dialog.rb +133 -0
- data/samples/elaborate/tetris/view/playfield.rb +1 -1
- data/samples/elaborate/tetris/view/score_lane.rb +11 -11
- data/samples/elaborate/tetris/view/tetris_menu_bar.rb +121 -0
- data/samples/elaborate/tic_tac_toe.rb +4 -4
- data/samples/hello/hello_canvas_transform.rb +40 -0
- data/samples/hello/hello_link.rb +1 -1
- metadata +11 -5
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
class Tetris
|
23
|
+
module Model
|
24
|
+
PastGame = Struct.new(:name, :score, :lines, :level)
|
25
|
+
end
|
26
|
+
end
|
@@ -38,31 +38,32 @@ class Tetris
|
|
38
38
|
Z: :red,
|
39
39
|
}
|
40
40
|
|
41
|
-
attr_reader :letter, :preview
|
41
|
+
attr_reader :game, :letter, :preview
|
42
42
|
alias preview? preview
|
43
43
|
attr_accessor :orientation, :blocks, :row, :column
|
44
44
|
|
45
|
-
def initialize
|
45
|
+
def initialize(game)
|
46
|
+
@game = game
|
46
47
|
@letter = LETTER_COLORS.keys.sample
|
47
48
|
@orientation = :north
|
48
49
|
@blocks = default_blocks
|
49
50
|
@preview = true
|
50
51
|
new_row = 0
|
51
|
-
new_column = (PREVIEW_PLAYFIELD_WIDTH - width)/2
|
52
|
+
new_column = (Model::Game::PREVIEW_PLAYFIELD_WIDTH - width)/2
|
52
53
|
update_playfield(new_row, new_column)
|
53
54
|
end
|
54
55
|
|
55
56
|
def playfield
|
56
|
-
@preview ?
|
57
|
+
@preview ? game.preview_playfield : game.playfield
|
57
58
|
end
|
58
59
|
|
59
60
|
def launch!
|
60
61
|
remove_from_playfield
|
61
62
|
@preview = false
|
62
63
|
new_row = 1 - height
|
63
|
-
new_column = (
|
64
|
+
new_column = (game.playfield_width - width)/2
|
64
65
|
update_playfield(new_row, new_column)
|
65
|
-
|
66
|
+
game.tetrominoes << self
|
66
67
|
end
|
67
68
|
|
68
69
|
def update_playfield(new_row = nil, new_column = nil)
|
@@ -76,7 +77,7 @@ class Tetris
|
|
76
77
|
|
77
78
|
def add_to_playfield
|
78
79
|
update_playfield_block do |playfield_row, playfield_column, row_index, column_index|
|
79
|
-
playfield[playfield_row][playfield_column].color = blocks[row_index][column_index].color if playfield_row >= 0 && playfield[playfield_row][playfield_column]&.clear? && !blocks[row_index][column_index].clear?
|
80
|
+
playfield[playfield_row][playfield_column].color = blocks[row_index][column_index].color if playfield_row >= 0 && playfield[playfield_row][playfield_column]&.clear? && !blocks[row_index][column_index].clear? && playfield[playfield_row][playfield_column].color != blocks[row_index][column_index].color
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
@@ -89,16 +90,16 @@ class Tetris
|
|
89
90
|
|
90
91
|
def stopped?
|
91
92
|
return true if @stopped || @preview
|
92
|
-
playfield_remaining_heights =
|
93
|
+
playfield_remaining_heights = game.playfield_remaining_heights(self)
|
93
94
|
result = bottom_most_blocks.any? do |bottom_most_block|
|
94
95
|
playfield_column = @column + bottom_most_block[:column_index]
|
95
|
-
|
96
|
-
@row + bottom_most_block[:
|
96
|
+
playfield_remaining_heights[playfield_column] &&
|
97
|
+
@row + bottom_most_block[:row_index] >= playfield_remaining_heights[playfield_column] - 1
|
97
98
|
end
|
98
|
-
if result && !
|
99
|
+
if result && !game.hypothetical?
|
99
100
|
@stopped = result
|
100
|
-
|
101
|
-
|
101
|
+
game.consider_eliminating_lines
|
102
|
+
@game.consider_adding_tetromino
|
102
103
|
end
|
103
104
|
result
|
104
105
|
end
|
@@ -113,7 +114,7 @@ class Tetris
|
|
113
114
|
bottom_most_block_row = row_blocks_with_row_index[1]
|
114
115
|
{
|
115
116
|
block: bottom_most_block,
|
116
|
-
|
117
|
+
row_index: bottom_most_block_row,
|
117
118
|
column_index: column_index
|
118
119
|
}
|
119
120
|
end
|
@@ -124,9 +125,10 @@ class Tetris
|
|
124
125
|
end
|
125
126
|
|
126
127
|
def right_blocked?
|
127
|
-
(@column ==
|
128
|
+
(@column == game.playfield_width - width) ||
|
128
129
|
right_most_blocks.any? { |right_most_block|
|
129
|
-
|
130
|
+
(@row + right_most_block[:row_index]) >= 0 &&
|
131
|
+
playfield[@row + right_most_block[:row_index]][@column + right_most_block[:column_index] + 1].occupied?
|
130
132
|
}
|
131
133
|
end
|
132
134
|
|
@@ -150,7 +152,8 @@ class Tetris
|
|
150
152
|
def left_blocked?
|
151
153
|
(@column == 0) ||
|
152
154
|
left_most_blocks.any? { |left_most_block|
|
153
|
-
|
155
|
+
(@row + left_most_block[:row_index]) >= 0 &&
|
156
|
+
playfield[@row + left_most_block[:row_index]][@column + left_most_block[:column_index] - 1].occupied?
|
154
157
|
}
|
155
158
|
end
|
156
159
|
|
@@ -179,22 +182,27 @@ class Tetris
|
|
179
182
|
@blocks.size
|
180
183
|
end
|
181
184
|
|
182
|
-
def down
|
185
|
+
def down!(immediate: false)
|
183
186
|
launch! if preview?
|
184
187
|
unless stopped?
|
185
|
-
|
188
|
+
block_count = 1
|
189
|
+
if immediate
|
190
|
+
remaining_height, bottom_touching_block = remaining_height_and_bottom_touching_block
|
191
|
+
block_count = remaining_height - @row
|
192
|
+
end
|
193
|
+
new_row = @row + block_count
|
186
194
|
update_playfield(new_row, @column)
|
187
195
|
end
|
188
196
|
end
|
189
197
|
|
190
|
-
def left
|
198
|
+
def left!
|
191
199
|
unless left_blocked?
|
192
200
|
new_column = @column - 1
|
193
201
|
update_playfield(@row, new_column)
|
194
202
|
end
|
195
203
|
end
|
196
204
|
|
197
|
-
def right
|
205
|
+
def right!
|
198
206
|
unless right_blocked?
|
199
207
|
new_column = @column + 1
|
200
208
|
update_playfield(@row, new_column)
|
@@ -202,11 +210,11 @@ class Tetris
|
|
202
210
|
end
|
203
211
|
|
204
212
|
# Rotate in specified direcation, which can be :right (clockwise) or :left (counterclockwise)
|
205
|
-
def rotate(direction)
|
213
|
+
def rotate!(direction)
|
206
214
|
return if stopped?
|
207
215
|
can_rotate = nil
|
208
216
|
new_blocks = nil
|
209
|
-
|
217
|
+
game.hypothetical do
|
210
218
|
hypothetical_rotated_tetromino = hypothetical_tetromino
|
211
219
|
new_blocks = hypothetical_rotated_tetromino.rotate_blocks(direction)
|
212
220
|
can_rotate = !hypothetical_rotated_tetromino.stopped? && !hypothetical_rotated_tetromino.right_blocked? && !hypothetical_rotated_tetromino.left_blocked?
|
@@ -222,13 +230,13 @@ class Tetris
|
|
222
230
|
end
|
223
231
|
|
224
232
|
def rotate_blocks(direction)
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
233
|
+
new_blocks = Matrix[*@blocks].transpose.to_a
|
234
|
+
if direction == :right
|
235
|
+
new_blocks = new_blocks.map(&:reverse)
|
236
|
+
else
|
237
|
+
new_blocks = new_blocks.reverse
|
238
|
+
end
|
239
|
+
Matrix[*new_blocks].to_a
|
232
240
|
end
|
233
241
|
|
234
242
|
def hypothetical_tetromino
|
@@ -242,6 +250,14 @@ class Tetris
|
|
242
250
|
end
|
243
251
|
end
|
244
252
|
|
253
|
+
def remaining_height_and_bottom_touching_block
|
254
|
+
playfield_remaining_heights = game.playfield_remaining_heights(self)
|
255
|
+
bottom_most_blocks.map do |bottom_most_block|
|
256
|
+
playfield_column = @column + bottom_most_block[:column_index]
|
257
|
+
[playfield_remaining_heights[playfield_column] - (bottom_most_block[:row_index] + 1), bottom_most_block]
|
258
|
+
end.min_by(&:first)
|
259
|
+
end
|
260
|
+
|
245
261
|
def default_blocks
|
246
262
|
case @letter
|
247
263
|
when :I
|
@@ -26,14 +26,42 @@ class Tetris
|
|
26
26
|
|
27
27
|
options :game_playfield, :block_size, :row, :column
|
28
28
|
|
29
|
+
before_body {
|
30
|
+
@bevel_constant = 20
|
31
|
+
}
|
32
|
+
|
29
33
|
body {
|
30
|
-
|
34
|
+
canvas {
|
31
35
|
layout nil
|
32
36
|
background bind(game_playfield[row][column], :color)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
+
polygon(0, 0, block_size, 0, block_size - 4, 4, 4, 4, fill: true) {
|
38
|
+
background bind(game_playfield[row][column], :color) { |color_value|
|
39
|
+
color = color(color_value)
|
40
|
+
rgb(color.red + 4*@bevel_constant, color.green + 4*@bevel_constant, color.blue + 4*@bevel_constant)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
polygon(block_size, 0, block_size - 4, 4, block_size - 4, block_size - 4, block_size, block_size, fill: true) {
|
44
|
+
background bind(game_playfield[row][column], :color) { |color_value|
|
45
|
+
color = color(color_value)
|
46
|
+
rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
|
47
|
+
}
|
48
|
+
}
|
49
|
+
polygon(block_size, block_size, 0, block_size, 4, block_size - 4, block_size - 4, block_size - 4, fill: true) {
|
50
|
+
background bind(game_playfield[row][column], :color) { |color_value|
|
51
|
+
color = color(color_value)
|
52
|
+
rgb(color.red - 2*@bevel_constant, color.green - 2*@bevel_constant, color.blue - 2*@bevel_constant)
|
53
|
+
}
|
54
|
+
}
|
55
|
+
polygon(0, 0, 0, block_size, 4, block_size - 4, 4, 4, fill: true) {
|
56
|
+
background bind(game_playfield[row][column], :color) { |color_value|
|
57
|
+
color = color(color_value)
|
58
|
+
rgb(color.red - @bevel_constant, color.green - @bevel_constant, color.blue - @bevel_constant)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
rectangle(0, 0, block_size, block_size) {
|
62
|
+
foreground bind(game_playfield[row][column], :color) { |color_value|
|
63
|
+
color_value == Model::Block::COLOR_CLEAR ? :gray : color_value
|
64
|
+
}
|
37
65
|
}
|
38
66
|
}
|
39
67
|
}
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require_relative 'tetris_menu_bar'
|
23
|
+
|
24
|
+
class Tetris
|
25
|
+
module View
|
26
|
+
class HighScoreDialog
|
27
|
+
include Glimmer::UI::CustomShell
|
28
|
+
|
29
|
+
options :parent_shell, :game
|
30
|
+
|
31
|
+
after_body {
|
32
|
+
@game_over_observer = observe(game, :game_over) do |game_over|
|
33
|
+
close if !game_over
|
34
|
+
end
|
35
|
+
}
|
36
|
+
|
37
|
+
body {
|
38
|
+
dialog(parent_shell) {
|
39
|
+
row_layout {
|
40
|
+
type :vertical
|
41
|
+
center true
|
42
|
+
}
|
43
|
+
text 'Tetris'
|
44
|
+
|
45
|
+
tetris_menu_bar(game: game)
|
46
|
+
|
47
|
+
label(:center) {
|
48
|
+
text bind(game, :game_over) {|game_over| game_over ? 'Game Over!' : 'High Scores'}
|
49
|
+
font name: FONT_NAME, height: FONT_TITLE_HEIGHT, style: FONT_TITLE_STYLE
|
50
|
+
}
|
51
|
+
@high_score_table = table {
|
52
|
+
layout_data {
|
53
|
+
height 100
|
54
|
+
}
|
55
|
+
|
56
|
+
table_column {
|
57
|
+
text 'Name'
|
58
|
+
}
|
59
|
+
table_column {
|
60
|
+
text 'Score'
|
61
|
+
}
|
62
|
+
table_column {
|
63
|
+
text 'Lines'
|
64
|
+
}
|
65
|
+
table_column {
|
66
|
+
text 'Level'
|
67
|
+
}
|
68
|
+
|
69
|
+
items bind(game, :high_scores, read_only_sort: true), column_properties(:name, :score, :lines, :level)
|
70
|
+
}
|
71
|
+
composite {
|
72
|
+
row_layout :horizontal
|
73
|
+
|
74
|
+
button {
|
75
|
+
text 'Clear'
|
76
|
+
|
77
|
+
on_widget_selected {
|
78
|
+
game.clear_high_scores!
|
79
|
+
}
|
80
|
+
}
|
81
|
+
@play_close_button = button {
|
82
|
+
text bind(game, :game_over) {|game_over| game_over ? 'Play Again?' : 'Close'}
|
83
|
+
focus true # initial focus
|
84
|
+
|
85
|
+
on_widget_selected {
|
86
|
+
async_exec { close }
|
87
|
+
game.paused = @game_paused
|
88
|
+
game.restart! if game.game_over?
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
on_swt_show {
|
94
|
+
@game_paused = game.paused?
|
95
|
+
game.paused = true
|
96
|
+
if game.game_over? && game.added_high_score?
|
97
|
+
game.added_high_score = false
|
98
|
+
game.save_high_scores!
|
99
|
+
@high_score_table.edit_table_item(
|
100
|
+
@high_score_table.items.first, # row item
|
101
|
+
0, # column
|
102
|
+
after_write: -> {
|
103
|
+
game.save_high_scores!
|
104
|
+
@play_close_button.set_focus
|
105
|
+
},
|
106
|
+
after_cancel: -> {
|
107
|
+
@play_close_button.set_focus
|
108
|
+
},
|
109
|
+
)
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
on_shell_closed {
|
114
|
+
# guard is needed because there is an observer in Tetris closing on
|
115
|
+
# game.show_high_scores change, which gets set below
|
116
|
+
unless @closing
|
117
|
+
@closing = true
|
118
|
+
@high_score_table.cancel_edit!
|
119
|
+
game.paused = @game_paused
|
120
|
+
game.show_high_scores = false
|
121
|
+
else
|
122
|
+
@closing = false
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
on_widget_disposed {
|
127
|
+
@game_over_observer.deregister
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -27,11 +27,11 @@ class Tetris
|
|
27
27
|
class ScoreLane
|
28
28
|
include Glimmer::UI::CustomWidget
|
29
29
|
|
30
|
-
options :block_size
|
30
|
+
options :block_size, :game
|
31
31
|
|
32
32
|
before_body {
|
33
|
-
@font_name =
|
34
|
-
@font_height =
|
33
|
+
@font_name = FONT_NAME
|
34
|
+
@font_height = FONT_TITLE_HEIGHT
|
35
35
|
}
|
36
36
|
|
37
37
|
body {
|
@@ -46,16 +46,16 @@ class Tetris
|
|
46
46
|
}
|
47
47
|
label(:center) {
|
48
48
|
text 'Next'
|
49
|
-
font name: @font_name, height: @font_height, style:
|
49
|
+
font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
|
50
50
|
}
|
51
|
-
playfield(game_playfield:
|
51
|
+
playfield(game_playfield: game.preview_playfield, playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
|
52
52
|
|
53
53
|
label(:center) {
|
54
54
|
text 'Score'
|
55
|
-
font name: @font_name, height: @font_height, style:
|
55
|
+
font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
|
56
56
|
}
|
57
57
|
label(:center) {
|
58
|
-
text bind(
|
58
|
+
text bind(game, :score)
|
59
59
|
font height: @font_height
|
60
60
|
}
|
61
61
|
|
@@ -63,10 +63,10 @@ class Tetris
|
|
63
63
|
|
64
64
|
label(:center) {
|
65
65
|
text 'Lines'
|
66
|
-
font name: @font_name, height: @font_height, style:
|
66
|
+
font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
|
67
67
|
}
|
68
68
|
label(:center) {
|
69
|
-
text bind(
|
69
|
+
text bind(game, :lines)
|
70
70
|
font height: @font_height
|
71
71
|
}
|
72
72
|
|
@@ -74,10 +74,10 @@ class Tetris
|
|
74
74
|
|
75
75
|
label(:center) {
|
76
76
|
text 'Level'
|
77
|
-
font name: @font_name, height: @font_height, style:
|
77
|
+
font name: @font_name, height: @font_height, style: FONT_TITLE_STYLE
|
78
78
|
}
|
79
79
|
label(:center) {
|
80
|
-
text bind(
|
80
|
+
text bind(game, :level)
|
81
81
|
font height: @font_height
|
82
82
|
}
|
83
83
|
}
|