glimmer-dsl-libui 0.2.17 → 0.2.21

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