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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +563 -125
- data/VERSION +1 -1
- data/examples/area_gallery.rb +11 -18
- data/examples/area_gallery2.rb +31 -7
- data/examples/area_gallery3.rb +14 -21
- data/examples/area_gallery4.rb +34 -10
- data/examples/histogram.rb +6 -13
- data/examples/tetris/model/game.rb +2 -0
- data/examples/tetris/model/tetromino.rb +2 -2
- data/examples/tetris.rb +279 -49
- data/examples/tic_tac_toe/board.rb +145 -0
- data/examples/tic_tac_toe/cell.rb +48 -0
- data/examples/tic_tac_toe.rb +85 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/shape_expression.rb +1 -0
- data/lib/glimmer/libui/control_proxy/area_proxy.rb +25 -0
- data/lib/glimmer/libui/control_proxy/path_proxy.rb +19 -7
- data/lib/glimmer/libui/control_proxy/window_proxy.rb +40 -1
- data/lib/glimmer/libui/shape/figure.rb +4 -2
- data/lib/glimmer/libui/shape/polybezier.rb +45 -0
- data/lib/glimmer/libui/shape/polygon.rb +46 -0
- data/lib/glimmer/libui/shape/polyline.rb +45 -0
- data/lib/glimmer/libui/shape.rb +9 -3
- data/lib/glimmer/libui.rb +29 -1
- metadata +8 -2
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
|
-
|
47
|
-
|
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::
|
56
|
+
Model::Game::PLAYFIELD_WIDTH.times do |column|
|
62
57
|
Glimmer::DataBinding::Observer.proc do |new_color|
|
63
|
-
|
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
|
70
|
-
|
71
|
-
@
|
72
|
-
|
73
|
-
|
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
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
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
|
data/glimmer-dsl-libui.gemspec
CHANGED
Binary file
|