1
1
import gg
2
2
import gx
3
3
import math
4
+ import math.easing
4
5
import os.asset
5
6
import rand
6
7
import time
7
8
9
+ const zooming_percent_per_frame = 5
10
+ const movement_percent_per_frame = 10
11
+
12
+ const window_title = 'V 2048'
13
+ const default_window_width = 544
14
+ const default_window_height = 560
15
+
16
+ const possible_moves = [Direction.up, .right, .down, .left]
17
+ const predictions_per_move = 300
18
+ const prediction_depth = 8
19
+
8
20
struct App {
9
21
mut :
10
22
gg & gg.Context = unsafe { nil }
14
26
theme_idx int
15
27
board Board
16
28
undo []Undo
17
- atickers [4 ][4 ]int
29
+ atickers [4 ][4 ]f64
30
+ mtickers [4 ][4 ]f64
18
31
state GameState = .play
19
32
tile_format TileFormat = .normal
20
33
moves int
@@ -111,14 +124,6 @@ const themes = [
111
124
]
112
125
},
113
126
]
114
- const window_title = 'V 2048'
115
- const default_window_width = 544
116
- const default_window_height = 560
117
- const animation_length = 10 // frames
118
-
119
- const possible_moves = [Direction.up, .right, .down, .left]
120
- const predictions_per_move = 300
121
- const prediction_depth = 8
122
127
123
128
struct Pos {
124
129
x int = - 1
@@ -128,6 +133,7 @@ struct Pos {
128
133
struct Board {
129
134
mut :
130
135
field [4 ][4 ]int
136
+ oidxs [4 ][4 ]u32 // old indexes of the fields, when != 0; each index is an encoding of its y,x coordinates = y << 16 | x
131
137
points int
132
138
shifts int
133
139
}
@@ -141,6 +147,7 @@ struct TileLine {
141
147
ypos int
142
148
mut :
143
149
field [5 ]int
150
+ oidxs [5 ]u32
144
151
points int
145
152
shifts int
146
153
}
@@ -201,6 +208,7 @@ fn (b Board) transpose() Board {
201
208
for y in 0 .. 4 {
202
209
for x in 0 .. 4 {
203
210
res.field[y][x] = b.field[x][y]
211
+ res.oidxs[y][x] = b.oidxs[x][y]
204
212
}
205
213
}
206
214
return res
@@ -211,6 +219,7 @@ fn (b Board) hmirror() Board {
211
219
for y in 0 .. 4 {
212
220
for x in 0 .. 4 {
213
221
res.field[y][x] = b.field[y][3 - x]
222
+ res.oidxs[y][x] = b.oidxs[y][3 - x]
214
223
}
215
224
}
216
225
return res
@@ -241,6 +250,7 @@ fn (t TileLine) to_left() TileLine {
241
250
res.shifts++
242
251
for k := x; k < right_border_idx; k++ {
243
252
res.field[k] = res.field[k + 1 ]
253
+ res.oidxs[k] = res.oidxs[k + 1 ]
244
254
}
245
255
remaining_zeros--
246
256
}
@@ -255,6 +265,7 @@ fn (t TileLine) to_left() TileLine {
255
265
if res.field[x] == res.field[x + 1 ] {
256
266
for k := x; k < right_border_idx; k++ {
257
267
res.field[k] = res.field[k + 1 ]
268
+ res.oidxs[k] = res.oidxs[k + 1 ]
258
269
}
259
270
res.shifts++
260
271
res.field[x]++
@@ -272,28 +283,39 @@ fn (b Board) to_left() Board {
272
283
}
273
284
for x in 0 .. 4 {
274
285
hline.field[x] = b.field[y][x]
286
+ hline.oidxs[x] = b.oidxs[y][x]
275
287
}
276
288
reshline := hline.to_left ()
277
289
res.shifts + = reshline.shifts
278
290
res.points + = reshline.points
279
291
for x in 0 .. 4 {
280
292
res.field[y][x] = reshline.field[x]
293
+ res.oidxs[y][x] = reshline.oidxs[x]
281
294
}
282
295
}
283
296
return res
284
297
}
285
298
286
- fn (b Board) move (d Direction) (Board, bool ) {
299
+ fn yx2i (y int , x int ) u32 {
300
+ return u32 (y) << 16 | u32 (x)
301
+ }
302
+
303
+ fn (mut b Board) move (d Direction) (Board, bool ) {
304
+ for y in 0 .. 4 {
305
+ for x in 0 .. 4 {
306
+ b.oidxs[y][x] = yx2i (y, x)
307
+ }
308
+ }
287
309
new := match d {
288
310
.left { b.to_left () }
289
311
.right { b.hmirror ().to_left ().hmirror () }
290
312
.up { b.transpose ().to_left ().transpose () }
291
313
.down { b.transpose ().hmirror ().to_left ().hmirror ().transpose () }
292
314
}
293
315
// If the board hasn't changed, it's an illegal move, don't allow it.
294
- for x in 0 .. 4 {
295
- for y in 0 .. 4 {
296
- if b.field[x][y ] != new.field[x][y ] {
316
+ for y in 0 .. 4 {
317
+ for x in 0 .. 4 {
318
+ if b.field[y][x ] != new.field[y][x ] {
297
319
return new, true
298
320
}
299
321
}
@@ -324,11 +346,10 @@ fn (mut b Board) is_game_over() bool {
324
346
fn (mut app App) update_tickers () {
325
347
for y in 0 .. 4 {
326
348
for x in 0 .. 4 {
327
- mut old := app.atickers[y][x]
328
- if old > 0 {
329
- old--
330
- app.atickers[y][x] = old
331
- }
349
+ app.atickers[y][x] = math.clip (app.atickers[y][x] - f64 (zooming_percent_per_frame) / 100.0 ,
350
+ 0.0 , 1.0 )
351
+ app.mtickers[y][x] = math.clip (app.mtickers[y][x] - f64 (movement_percent_per_frame) / 100.0 ,
352
+ 0.0 , 1.0 )
332
353
}
333
354
}
334
355
}
@@ -339,6 +360,7 @@ fn (mut app App) new_game() {
339
360
for x in 0 .. 4 {
340
361
app.board.field[y][x] = 0
341
362
app.atickers[y][x] = 0
363
+ app.mtickers[y][x] = 0
342
364
}
343
365
}
344
366
app.state = .play
@@ -387,23 +409,26 @@ fn (mut b Board) place_random_tile() (Pos, int) {
387
409
value := rand.f64n (1.0 ) or { 0.0 }
388
410
random_value := if value < 0.9 { 1 } else { 2 }
389
411
b.field[empty_pos.y][empty_pos.x] = random_value
412
+ b.oidxs[empty_pos.y][empty_pos.x] = yx2i (empty_pos.y, empty_pos.x)
390
413
return empty_pos, random_value
391
414
}
392
415
return Pos{}, 0
393
416
}
394
417
395
418
fn (mut app App) new_random_tile () {
419
+ // do not animate empty fields:
396
420
for y in 0 .. 4 {
397
421
for x in 0 .. 4 {
398
422
fidx := app.board.field[y][x]
399
423
if fidx == 0 {
400
424
app.atickers[y][x] = 0
425
+ app.board.oidxs[y][x] = 0xFFFF_FFFF
401
426
}
402
427
}
403
428
}
404
429
empty_pos , random_value := app.board.place_random_tile ()
405
430
if random_value > 0 {
406
- app.atickers[empty_pos.y][empty_pos.x] = animation_length
431
+ app.atickers[empty_pos.y][empty_pos.x] = 1.0
407
432
}
408
433
if app.state != .freeplay {
409
434
app.check_for_victory ()
@@ -414,6 +439,13 @@ fn (mut app App) new_random_tile() {
414
439
fn (mut app App) apply_new_board (new Board) {
415
440
old := app.board
416
441
app.moves++
442
+ for y in 0 .. 4 {
443
+ for x in 0 .. 4 {
444
+ if old.oidxs[y][x] != new.oidxs[y][x] {
445
+ app.mtickers[y][x] = 1.0
446
+ }
447
+ }
448
+ }
417
449
app.board = new
418
450
app.undo << Undo{old, app.state}
419
451
app.new_random_tile ()
@@ -623,59 +655,111 @@ fn (app &App) draw_tiles() {
623
655
// Draw the padding around the tiles
624
656
app.gg.draw_rounded_rect_filled (xstart, ystart, tiles_size, tiles_size, tiles_size / 24 ,
625
657
app.theme.padding_color)
626
- // Draw the actual tiles
658
+
659
+ // Draw empty tiles:
660
+ for y in 0 .. 4 {
661
+ for x in 0 .. 4 {
662
+ tw := app.ui.tile_size
663
+ th := tw // square tiles, w == h
664
+ xoffset := xstart + app.ui.padding_size + x * toffset
665
+ yoffset := ystart + app.ui.padding_size + y * toffset
666
+ app.gg.draw_rounded_rect_filled (xoffset, yoffset, tw, th, tw / 8 , app.theme.tile_colors[0 ])
667
+ }
668
+ }
669
+
670
+ // Draw the already placed and potentially moving tiles:
627
671
for y in 0 .. 4 {
628
672
for x in 0 .. 4 {
629
673
tidx := app.board.field[y][x]
630
- tile_color := if tidx < app.theme.tile_colors.len {
631
- app.theme.tile_colors[tidx]
632
- } else {
633
- // If there isn't a specific color for this tile, reuse the last color available
634
- app.theme.tile_colors.last ()
674
+ oidx := app.board.oidxs[y][x]
675
+ if tidx == 0 || oidx == 0xFFFF_FFFF {
676
+ continue
635
677
}
636
- anim_size := animation_length - app.atickers[y][x]
637
- tw := int (f64 (app.ui.tile_size) / animation_length * anim_size)
638
- th := tw // square tiles, w == h
639
- xoffset := xstart + app.ui.padding_size + x * toffset + (app.ui.tile_size - tw) / 2
640
- yoffset := ystart + app.ui.padding_size + y * toffset + (app.ui.tile_size - th) / 2
641
- app.gg.draw_rounded_rect_filled (xoffset, yoffset, tw, th, tw / 8 , tile_color)
642
- if tidx != 0 { // 0 == blank spot
643
- xpos := xoffset + tw / 2
644
- ypos := yoffset + th / 2
645
- mut fmt := app.label_format (.tile)
646
- fmt = gx.TextCfg{
678
+ app.draw_one_tile (x, y, tidx)
679
+ }
680
+ }
681
+
682
+ // Draw the newly placed random tiles on top of everything else:
683
+ for y in 0 .. 4 {
684
+ for x in 0 .. 4 {
685
+ tidx := app.board.field[y][x]
686
+ oidx := app.board.oidxs[y][x]
687
+ if oidx == 0xFFFF_FFFF && tidx != 0 {
688
+ app.draw_one_tile (x, y, tidx)
689
+ }
690
+ }
691
+ }
692
+ }
693
+
694
+ fn (app &App) draw_one_tile (x int , y int , tidx int ) {
695
+ xstart := app.ui.x_padding + app.ui.border_size
696
+ ystart := app.ui.y_padding + app.ui.border_size + app.ui.header_size
697
+ toffset := app.ui.tile_size + app.ui.padding_size
698
+ oidx := app.board.oidxs[y][x]
699
+ oy := oidx >> 16
700
+ ox := oidx & 0xFFFF
701
+ mut dx := 0
702
+ mut dy := 0
703
+ if oidx != 0xFFFF_FFFF {
704
+ scaling := app.ui.tile_size * easing.in_out_quint (app.mtickers[y][x])
705
+ if ox != x {
706
+ dx = math.clip (int (scaling * (f64 (ox) - f64 (x))), - 4 * app.ui.tile_size, 4 * app.ui.tile_size)
707
+ }
708
+ if oy != y {
709
+ dy = math.clip (int (scaling * (f64 (oy) - f64 (y))), - 4 * app.ui.tile_size, 4 * app.ui.tile_size)
710
+ }
711
+ }
712
+ tile_color := if tidx < app.theme.tile_colors.len {
713
+ app.theme.tile_colors[tidx]
714
+ } else {
715
+ // If there isn't a specific color for this tile, reuse the last color available
716
+ app.theme.tile_colors.last ()
717
+ }
718
+ anim_size := 1.0 - app.atickers[y][x]
719
+ tw := int (f64 (anim_size * app.ui.tile_size))
720
+ th := tw // square tiles, w == h
721
+ xoffset := dx + xstart + app.ui.padding_size + x * toffset + (app.ui.tile_size - tw) / 2
722
+ yoffset := dy + ystart + app.ui.padding_size + y * toffset + (app.ui.tile_size - th) / 2
723
+ app.gg.draw_rounded_rect_filled (xoffset, yoffset, tw, th, tw / 8 , tile_color)
724
+ if tidx != 0 { // 0 == blank spot
725
+ xpos := xoffset + tw / 2
726
+ ypos := yoffset + th / 2
727
+ mut fmt := app.label_format (.tile)
728
+ fmt = gx.TextCfg{
729
+ ...fmt
730
+ size: int (anim_size * (fmt.size - 1 ))
731
+ }
732
+ match app.tile_format {
733
+ .normal {
734
+ app.gg.draw_text (xpos, ypos, '${1 << tidx} ' , fmt)
735
+ }
736
+ .log {
737
+ app.gg.draw_text (xpos, ypos, '${tidx} ' , fmt)
738
+ }
739
+ .exponent {
740
+ app.gg.draw_text (xpos, ypos, '2' , fmt)
741
+ fs2 := int (f32 (fmt.size) * 0.67 )
742
+ app.gg.draw_text (xpos + app.ui.tile_size / 10 , ypos - app.ui.tile_size / 8 ,
743
+ '${tidx} ' , gx.TextCfg{
647
744
...fmt
648
- size: int (f32 (fmt.size - 1 ) / animation_length * anim_size)
649
- }
650
- match app.tile_format {
651
- .normal {
652
- app.gg.draw_text (xpos, ypos, '${1 << tidx} ' , fmt)
653
- }
654
- .log {
655
- app.gg.draw_text (xpos, ypos, '${tidx} ' , fmt)
656
- }
657
- .exponent {
658
- app.gg.draw_text (xpos, ypos, '2' , fmt)
659
- fs2 := int (f32 (fmt.size) * 0.67 )
660
- app.gg.draw_text (xpos + app.ui.tile_size / 10 , ypos - app.ui.tile_size / 8 ,
661
- '${tidx} ' , gx.TextCfg{
662
- ...fmt
663
- size: fs2
664
- align: gx.HorizontalAlign.left
665
- })
666
- }
667
- .shifts {
668
- fs2 := int (f32 (fmt.size) * 0.6 )
669
- app.gg.draw_text (xpos, ypos, '2<<${tidx - 1} ' , gx.TextCfg{
670
- ...fmt
671
- size: fs2
672
- })
673
- }
674
- .none {} // Don't draw any text here, colors only
675
- .end {} // Should never get here
676
- }
745
+ size: fs2
746
+ align: gx.HorizontalAlign.left
747
+ })
748
+ }
749
+ .shifts {
750
+ fs2 := int (f32 (fmt.size) * 0.6 )
751
+ app.gg.draw_text (xpos, ypos, '2<<${tidx - 1} ' , gx.TextCfg{
752
+ ...fmt
753
+ size: fs2
754
+ })
677
755
}
756
+ .none {} // Don't draw any text here, colors only
757
+ .end {} // Should never get here
678
758
}
759
+ // oidx_fmt := gx.TextCfg{...fmt,size: 14}
760
+ // app.gg.draw_text(xoffset + 50, yoffset + 15, 'y:${oidx >> 16}|x:${oidx & 0xFFFF}|m:${app.mtickers[y][x]:5.3f}', oidx_fmt)
761
+ // app.gg.draw_text(xoffset + 52, yoffset + 30, 'ox:${ox}|oy:${oy}', oidx_fmt)
762
+ // app.gg.draw_text(xoffset + 52, yoffset + 85, 'dx:${dx}|dy:${dy}', oidx_fmt)
679
763
}
680
764
}
681
765
@@ -733,7 +817,7 @@ fn (mut app App) handle_swipe() {
733
817
adx , ady := math.abs (dx), math.abs (dy)
734
818
dmin := if math.min (adx, ady) > 0 { math.min (adx, ady) } else { 1 }
735
819
dmax := if math.max (adx, ady) > 0 { math.max (adx, ady) } else { 1 }
736
- tdiff := int (e.time. unix_milli () - s.time. unix_milli () )
820
+ tdiff := (e.time - s.time). milliseconds ( )
737
821
// TODO: make this calculation more accurate (don't use arbitrary numbers)
738
822
min_swipe_distance := int (math.sqrt (math.min (w, h) * tdiff / 100 )) + 20
739
823
if dmax < min_swipe_distance {
0 commit comments