glimmer-dsl-libui 0.2.17 → 0.2.21

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.
data/examples/tetris.rb CHANGED
@@ -1,24 +1,3 @@
1
- # Copyright (c) 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
1
  require 'glimmer-dsl-libui'
23
2
 
24
3
  require_relative 'tetris/model/game'
@@ -28,85 +7,314 @@ class Tetris
28
7
 
29
8
  BLOCK_SIZE = 25
30
9
  BEVEL_CONSTANT = 20
10
+ COLOR_GRAY = {r: 192, g: 192, b: 192}
31
11
 
32
- attr_reader :game
33
-
34
12
  def initialize
35
13
  @game = Model::Game.new
36
- create_gui
37
- register_observers
38
14
  end
39
15
 
40
16
  def launch
17
+ create_gui
18
+ register_observers
41
19
  @game.start!
42
20
  @main_window.show
43
21
  end
44
22
 
45
23
  def create_gui
46
- @main_window = window('Glimmer Tetris', Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE) {
47
- playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
24
+ menu_bar
25
+
26
+ @main_window = window('Glimmer Tetris') {
27
+ content_size Model::Game::PLAYFIELD_WIDTH * BLOCK_SIZE, Model::Game::PLAYFIELD_HEIGHT * BLOCK_SIZE + 98
28
+ resizable false
29
+
30
+ vertical_box {
31
+ label { # filler
32
+ stretchy false
33
+ }
34
+
35
+ score_board(block_size: BLOCK_SIZE) {
36
+ stretchy false
37
+ }
38
+
39
+ @playfield_blocks = playfield(playfield_width: Model::Game::PLAYFIELD_WIDTH, playfield_height: Model::Game::PLAYFIELD_HEIGHT, block_size: BLOCK_SIZE)
40
+ }
48
41
  }
49
42
  end
50
43
 
51
44
  def register_observers
52
45
  Glimmer::DataBinding::Observer.proc do |game_over|
53
46
  if game_over
47
+ @pause_menu_item.enabled = false
54
48
  show_game_over_dialog
55
49
  else
50
+ @pause_menu_item.enabled = true
56
51
  start_moving_tetrominos_down
57
52
  end
58
53
  end.observe(@game, :game_over)
59
54
 
60
55
  Model::Game::PLAYFIELD_HEIGHT.times do |row|
61
- Model::Game::PLAYFIELD_HEIGHT.times do |column|
56
+ Model::Game::PLAYFIELD_WIDTH.times do |column|
62
57
  Glimmer::DataBinding::Observer.proc do |new_color|
63
- @blocks[row][column].fill = new_color
58
+ Glimmer::LibUI.queue_main do
59
+ color = Glimmer::LibUI.interpret_color(new_color)
60
+ block = @playfield_blocks[row][column]
61
+ block[:background_square].fill = color
62
+ block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
63
+ block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
64
+ block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
65
+ block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
66
+ block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
67
+ end
64
68
  end.observe(@game.playfield[row][column], :color)
65
69
  end
66
70
  end
71
+
72
+ Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
73
+ Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
74
+ Glimmer::DataBinding::Observer.proc do |new_color|
75
+ Glimmer::LibUI.queue_main do
76
+ color = Glimmer::LibUI.interpret_color(new_color)
77
+ block = @preview_playfield_blocks[row][column]
78
+ block[:background_square].fill = color
79
+ block[:top_bevel_edge].fill = {r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT}
80
+ block[:right_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
81
+ block[:bottom_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
82
+ block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
83
+ block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
84
+ end
85
+ end.observe(@game.preview_playfield[row][column], :color)
86
+ end
87
+ end
88
+
89
+ Glimmer::DataBinding::Observer.proc do |new_score|
90
+ Glimmer::LibUI.queue_main do
91
+ @score_label.text = new_score.to_s
92
+ end
93
+ end.observe(@game, :score)
94
+
95
+ Glimmer::DataBinding::Observer.proc do |new_lines|
96
+ Glimmer::LibUI.queue_main do
97
+ @lines_label.text = new_lines.to_s
98
+ end
99
+ end.observe(@game, :lines)
100
+
101
+ Glimmer::DataBinding::Observer.proc do |new_level|
102
+ Glimmer::LibUI.queue_main do
103
+ @level_label.text = new_level.to_s
104
+ end
105
+ end.observe(@game, :level)
67
106
  end
68
107
 
69
- def playfield(playfield_width: , playfield_height: , block_size: )
70
- area {
71
- @blocks = playfield_height.times.map do |row|
72
- playfield_width.times.map do |column|
73
- block(row: row, column: column, block_size: block_size)
108
+ def menu_bar
109
+ menu('Game') {
110
+ @pause_menu_item = check_menu_item('Pause') {
111
+ enabled false
112
+
113
+ on_clicked do
114
+ @game.paused = @pause_menu_item.checked?
115
+ end
116
+ }
117
+ menu_item('Restart') {
118
+ on_clicked do
119
+ @game.restart!
120
+ end
121
+ }
122
+ separator_menu_item
123
+ menu_item('Exit') {
124
+ on_clicked do
125
+ exit(0)
126
+ end
127
+ }
128
+ quit_menu_item if OS.mac?
129
+ }
130
+
131
+ menu('View') {
132
+ menu_item('Show High Scores') {
133
+ on_clicked do
134
+ show_high_scores
135
+ end
136
+ }
137
+ menu_item('Clear High Scores') {
138
+ on_clicked {
139
+ @game.clear_high_scores!
140
+ }
141
+ }
142
+ }
143
+
144
+ menu('Options') {
145
+ radio_menu_item('Instant Down on Up Arrow') {
146
+ on_clicked do
147
+ @game.instant_down_on_up = true
148
+ end
149
+ }
150
+ radio_menu_item('Rotate Right on Up Arrow') {
151
+ on_clicked do
152
+ @game.rotate_right_on_up = true
153
+ end
154
+ }
155
+ radio_menu_item('Rotate Left on Up Arrow') {
156
+ on_clicked do
157
+ @game.rotate_left_on_up = true
158
+ end
159
+ }
160
+ }
161
+
162
+ menu('Help') {
163
+ if OS.mac?
164
+ about_menu_item {
165
+ on_clicked do
166
+ show_about_dialog
167
+ end
168
+ }
169
+ end
170
+ menu_item('About') {
171
+ on_clicked do
172
+ show_about_dialog
74
173
  end
174
+ }
175
+ }
176
+ end
177
+
178
+ def playfield(playfield_width: , playfield_height: , block_size: , &extra_content)
179
+ blocks = []
180
+ vertical_box {
181
+ padded false
182
+
183
+ playfield_height.times.map do |row|
184
+ blocks << []
185
+ horizontal_box {
186
+ padded false
187
+
188
+ playfield_width.times.map do |column|
189
+ blocks.last << block(row: row, column: column, block_size: block_size)
190
+ end
191
+ }
75
192
  end
76
193
 
194
+ extra_content&.call
195
+ }
196
+ blocks
197
+ end
198
+
199
+ def block(row: , column: , block_size: , &extra_content)
200
+ block = {}
201
+ bevel_pixel_size = 0.16 * block_size.to_f
202
+ color = Glimmer::LibUI.interpret_color(Model::Block::COLOR_CLEAR)
203
+ area {
204
+ block[:background_square] = path {
205
+ square(0, 0, block_size)
206
+
207
+ fill color
208
+ }
209
+ block[:top_bevel_edge] = path {
210
+ polygon(0, 0, block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size, bevel_pixel_size)
211
+
212
+ fill r: color[:r] + 4*BEVEL_CONSTANT, g: color[:g] + 4*BEVEL_CONSTANT, b: color[:b] + 4*BEVEL_CONSTANT
213
+ }
214
+ block[:right_bevel_edge] = path {
215
+ polygon(block_size, 0, block_size - bevel_pixel_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size, block_size)
216
+
217
+ fill r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT
218
+ }
219
+ block[:bottom_bevel_edge] = path {
220
+ polygon(block_size, block_size, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size, block_size - bevel_pixel_size)
221
+
222
+ fill r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT
223
+ }
224
+ block[:left_bevel_edge] = path {
225
+ polygon(0, 0, 0, block_size, bevel_pixel_size, block_size - bevel_pixel_size, bevel_pixel_size, bevel_pixel_size)
226
+
227
+ fill r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT
228
+ }
229
+ block[:border_square] = path {
230
+ square(0, 0, block_size)
231
+
232
+ stroke COLOR_GRAY
233
+ }
234
+
77
235
  on_key_down do |key_event|
78
236
  case key_event
79
237
  in ext_key: :down
80
- game.down!
238
+ @game.down!
239
+ in key: ' '
240
+ @game.down!(instant: true)
81
241
  in ext_key: :up
82
- case game.up_arrow_action
242
+ case @game.up_arrow_action
83
243
  when :instant_down
84
- game.down!(instant: true)
244
+ @game.down!(instant: true)
85
245
  when :rotate_right
86
- game.rotate!(:right)
246
+ @game.rotate!(:right)
87
247
  when :rotate_left
88
- game.rotate!(:left)
248
+ @game.rotate!(:left)
89
249
  end
90
250
  in ext_key: :left
91
- game.left!
251
+ @game.left!
92
252
  in ext_key: :right
93
- game.right!
253
+ @game.right!
94
254
  in modifier: :shift
95
- game.rotate!(:right)
255
+ @game.rotate!(:right)
96
256
  in modifier: :control
97
- game.rotate!(:left)
257
+ @game.rotate!(:left)
98
258
  else
99
259
  # Do Nothing
100
260
  end
101
261
  end
262
+
263
+ extra_content&.call
102
264
  }
265
+ block
103
266
  end
104
267
 
105
- def block(row: , column: , block_size: )
106
- path {
107
- square(column * block_size, row * block_size, block_size)
108
-
109
- fill Model::Block::COLOR_CLEAR
268
+ def score_board(block_size: , &extra_content)
269
+ vertical_box {
270
+ horizontal_box {
271
+ label # filler
272
+ @preview_playfield_blocks = playfield(playfield_width: Model::Game::PREVIEW_PLAYFIELD_WIDTH, playfield_height: Model::Game::PREVIEW_PLAYFIELD_HEIGHT, block_size: block_size)
273
+ label # filler
274
+ }
275
+
276
+ horizontal_box {
277
+ label # filler
278
+ grid {
279
+ stretchy false
280
+
281
+ label('Score') {
282
+ left 0
283
+ top 0
284
+ halign :fill
285
+ }
286
+ @score_label = label {
287
+ left 0
288
+ top 1
289
+ halign :center
290
+ }
291
+
292
+ label('Lines') {
293
+ left 1
294
+ top 0
295
+ halign :fill
296
+ }
297
+ @lines_label = label {
298
+ left 1
299
+ top 1
300
+ halign :center
301
+ }
302
+
303
+ label('Level') {
304
+ left 2
305
+ top 0
306
+ halign :fill
307
+ }
308
+ @level_label = label {
309
+ left 2
310
+ top 1
311
+ halign :center
312
+ }
313
+ }
314
+ label # filler
315
+ }
316
+
317
+ extra_content&.call
110
318
  }
111
319
  end
112
320
 
@@ -117,7 +325,29 @@ class Tetris
117
325
  end
118
326
 
119
327
  def show_game_over_dialog
120
- msg_box('Game Over', "Score: #{@game.high_scores.first.score}")
328
+ Glimmer::LibUI.queue_main do
329
+ msg_box('Game Over!', "Score: #{@game.high_scores.first.score}\nLines: #{@game.high_scores.first.lines}\nLevel: #{@game.high_scores.first.level}")
330
+ @game.restart!
331
+ end
332
+ end
333
+
334
+ def show_high_scores
335
+ Glimmer::LibUI.queue_main do
336
+ if @game.high_scores.empty?
337
+ high_scores_string = "No games have been scored yet."
338
+ else
339
+ high_scores_string = @game.high_scores.map do |high_score|
340
+ "#{high_score.name} | Score: #{high_score.score} | Lines: #{high_score.lines} | Level: #{high_score.level}"
341
+ end.join("\n")
342
+ end
343
+ msg_box('High Scores', high_scores_string)
344
+ end
345
+ end
346
+
347
+ def show_about_dialog
348
+ Glimmer::LibUI.queue_main do
349
+ msg_box('About', 'Glimmer Tetris - Glimmer DSL for LibUI Example - Copyright (c) 2021 Andy Maleh')
350
+ end
121
351
  end
122
352
  end
123
353
 
@@ -0,0 +1,145 @@
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 'cell'
23
+
24
+ class TicTacToe
25
+ class Board
26
+ DRAW = :draw
27
+ IN_PROGRESS = :in_progress
28
+ WIN = :win
29
+ attr :winning_sign
30
+ attr_accessor :game_status
31
+
32
+ def initialize
33
+ @sign_state_machine = {nil => "X", "X" => "O", "O" => "X"}
34
+ build_grid
35
+ @winning_sign = Cell::EMPTY
36
+ @game_status = IN_PROGRESS
37
+ end
38
+
39
+ #row and column numbers are 1-based
40
+ def mark(row, column)
41
+ self[row, column].mark(current_sign)
42
+ game_over? #updates winning sign
43
+ end
44
+
45
+ def current_sign
46
+ @current_sign = @sign_state_machine[@current_sign]
47
+ end
48
+
49
+ def [](row, column)
50
+ @grid[row-1][column-1]
51
+ end
52
+
53
+ def game_over?
54
+ win? or draw?
55
+ end
56
+
57
+ def win?
58
+ win = (row_win? or column_win? or diagonal_win?)
59
+ self.game_status=WIN if win
60
+ win
61
+ end
62
+
63
+ def reset!
64
+ (1..3).each do |row|
65
+ (1..3).each do |column|
66
+ self[row, column].reset!
67
+ end
68
+ end
69
+ @winning_sign = Cell::EMPTY
70
+ @current_sign = nil
71
+ self.game_status=IN_PROGRESS
72
+ end
73
+
74
+ private
75
+
76
+ def build_grid
77
+ @grid = []
78
+ 3.times do |row_index| #0-based
79
+ @grid << []
80
+ 3.times { @grid[row_index] << Cell.new }
81
+ end
82
+ end
83
+
84
+ def row_win?
85
+ (1..3).each do |row|
86
+ if row_has_same_sign(row)
87
+ @winning_sign = self[row, 1].sign
88
+ return true
89
+ end
90
+ end
91
+ false
92
+ end
93
+
94
+ def column_win?
95
+ (1..3).each do |column|
96
+ if column_has_same_sign(column)
97
+ @winning_sign = self[1, column].sign
98
+ return true
99
+ end
100
+ end
101
+ false
102
+ end
103
+
104
+ #needs refactoring if we ever decide to make the board size dynamic
105
+ def diagonal_win?
106
+ if (self[1, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[3, 3].sign) and self[1, 1].marked
107
+ @winning_sign = self[1, 1].sign
108
+ return true
109
+ end
110
+ if (self[3, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[1, 3].sign) and self[3, 1].marked
111
+ @winning_sign = self[3, 1].sign
112
+ return true
113
+ end
114
+ false
115
+ end
116
+
117
+ def draw?
118
+ @board_full = true
119
+ 3.times do |x|
120
+ 3.times do |y|
121
+ @board_full = false if self[x, y].empty
122
+ end
123
+ end
124
+ self.game_status = DRAW if @board_full
125
+ @board_full
126
+ end
127
+
128
+ def row_has_same_sign(number)
129
+ row_sign = self[number, 1].sign
130
+ [2, 3].each do |column|
131
+ return false unless row_sign == (self[number, column].sign)
132
+ end
133
+ true if self[number, 1].marked
134
+ end
135
+
136
+ def column_has_same_sign(number)
137
+ column_sign = self[1, number].sign
138
+ [2, 3].each do |row|
139
+ return false unless column_sign == (self[row, number].sign)
140
+ end
141
+ true if self[1, number].marked
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,48 @@
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 TicTacToe
23
+ class Cell
24
+ EMPTY = ""
25
+ attr_accessor :sign, :empty
26
+
27
+ def initialize
28
+ reset!
29
+ end
30
+
31
+ def mark(sign)
32
+ self.sign = sign
33
+ end
34
+
35
+ def reset!
36
+ self.sign = EMPTY
37
+ end
38
+
39
+ def sign=(sign_symbol)
40
+ @sign = sign_symbol
41
+ self.empty = sign == EMPTY
42
+ end
43
+
44
+ def marked
45
+ !empty
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,85 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ require_relative "tic_tac_toe/board"
4
+
5
+ class TicTacToe
6
+ include Glimmer
7
+
8
+ def initialize
9
+ @tic_tac_toe_board = Board.new
10
+ end
11
+
12
+ def launch
13
+ create_gui
14
+ register_observers
15
+ @main_window.show
16
+ end
17
+
18
+ def register_observers
19
+ Glimmer::DataBinding::Observer.proc do |game_status|
20
+ display_win_message if game_status == Board::WIN
21
+ display_draw_message if game_status == Board::DRAW
22
+ end.observe(@tic_tac_toe_board, :game_status)
23
+
24
+ 3.times.map do |row|
25
+ 3.times.map do |column|
26
+ Glimmer::DataBinding::Observer.proc do |sign|
27
+ @cells[row][column].string = sign
28
+ end.observe(@tic_tac_toe_board[row + 1, column + 1], :sign) # board model is 1-based
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_gui
34
+ @main_window = window('Tic-Tac-Toe', 180, 180) {
35
+ resizable false
36
+
37
+ @cells = []
38
+ vertical_box {
39
+ padded false
40
+
41
+ 3.times.map do |row|
42
+ @cells << []
43
+ horizontal_box {
44
+ padded false
45
+
46
+ 3.times.map do |column|
47
+ area {
48
+ path {
49
+ square(0, 0, 60)
50
+
51
+ stroke :black, thickness: 2
52
+ }
53
+ text(23, 19) {
54
+ @cells[row] << string('') {
55
+ font family: 'Arial', size: 20
56
+ }
57
+ }
58
+ on_mouse_up do
59
+ @tic_tac_toe_board.mark(row + 1, column + 1) # board model is 1-based
60
+ end
61
+ }
62
+ end
63
+ }
64
+ end
65
+ }
66
+ }
67
+ end
68
+
69
+ def display_win_message
70
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
71
+ end
72
+
73
+ def display_draw_message
74
+ display_game_over_message("Draw!")
75
+ end
76
+
77
+ def display_game_over_message(message_text)
78
+ Glimmer::LibUI.queue_main do
79
+ msg_box('Game Over', message_text)
80
+ @tic_tac_toe_board.reset!
81
+ end
82
+ end
83
+ end
84
+
85
+ TicTacToe.new.launch
Binary file
@@ -37,6 +37,7 @@ module Glimmer
37
37
  end
38
38
 
39
39
  def interpret(parent, keyword, *args, &block)
40
+ args = [args] if args.size > 1 && Glimmer::LibUI::Shape.shape_class(keyword).parameters.size == 1
40
41
  Glimmer::LibUI::Shape.create(keyword, parent, args, &block)
41
42
  end
42
43