mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
examples/2048: run v -w fmt 2048.v
This commit is contained in:
parent
f9ce7f3542
commit
71e1ca72ec
@ -8,19 +8,19 @@ import time
|
||||
|
||||
struct App {
|
||||
mut:
|
||||
gg &gg.Context = 0
|
||||
touch TouchInfo
|
||||
ui Ui
|
||||
theme &Theme = themes[0]
|
||||
theme_idx int
|
||||
board Board
|
||||
undo []Undo
|
||||
atickers [4][4]int
|
||||
state GameState = .play
|
||||
tile_format TileFormat = .normal
|
||||
moves int
|
||||
perf &Perf = 0
|
||||
is_ai_mode bool
|
||||
gg &gg.Context = 0
|
||||
touch TouchInfo
|
||||
ui Ui
|
||||
theme &Theme = themes[0]
|
||||
theme_idx int
|
||||
board Board
|
||||
undo []Undo
|
||||
atickers [4][4]int
|
||||
state GameState = .play
|
||||
tile_format TileFormat = .normal
|
||||
moves int
|
||||
perf &Perf = 0
|
||||
is_ai_mode bool
|
||||
}
|
||||
|
||||
struct Ui {
|
||||
@ -47,68 +47,68 @@ struct Theme {
|
||||
}
|
||||
|
||||
const (
|
||||
themes = [
|
||||
&Theme {
|
||||
themes = [
|
||||
&Theme{
|
||||
bg_color: gx.rgb(250, 248, 239)
|
||||
padding_color: gx.rgb(143, 130, 119)
|
||||
victory_color: gx.rgb(100, 160, 100)
|
||||
game_over_color: gx.rgb(190, 50, 50)
|
||||
text_color: gx.black
|
||||
tile_colors: [
|
||||
gx.rgb(205, 193, 180) // Empty / 0 tile
|
||||
gx.rgb(238, 228, 218) // 2
|
||||
gx.rgb(237, 224, 200) // 4
|
||||
gx.rgb(242, 177, 121) // 8
|
||||
gx.rgb(245, 149, 99) // 16
|
||||
gx.rgb(246, 124, 95) // 32
|
||||
gx.rgb(246, 94, 59) // 64
|
||||
gx.rgb(237, 207, 114) // 128
|
||||
gx.rgb(237, 204, 97) // 256
|
||||
gx.rgb(237, 200, 80) // 512
|
||||
gx.rgb(237, 197, 63) // 1024
|
||||
gx.rgb(237, 194, 46) // 2048
|
||||
gx.rgb(205, 193, 180), // Empty / 0 tile
|
||||
gx.rgb(238, 228, 218), // 2
|
||||
gx.rgb(237, 224, 200), // 4
|
||||
gx.rgb(242, 177, 121), // 8
|
||||
gx.rgb(245, 149, 99), // 16
|
||||
gx.rgb(246, 124, 95), // 32
|
||||
gx.rgb(246, 94, 59), // 64
|
||||
gx.rgb(237, 207, 114), // 128
|
||||
gx.rgb(237, 204, 97), // 256
|
||||
gx.rgb(237, 200, 80), // 512
|
||||
gx.rgb(237, 197, 63), // 1024
|
||||
gx.rgb(237, 194, 46), // 2048
|
||||
]
|
||||
},
|
||||
&Theme {
|
||||
&Theme{
|
||||
bg_color: gx.rgb(55, 55, 55)
|
||||
padding_color: gx.rgb(68, 60, 59)
|
||||
victory_color: gx.rgb(100, 160, 100)
|
||||
game_over_color: gx.rgb(190, 50, 50)
|
||||
text_color: gx.white
|
||||
tile_colors: [
|
||||
gx.rgb(123, 115, 108)
|
||||
gx.rgb(142, 136, 130)
|
||||
gx.rgb(142, 134, 120)
|
||||
gx.rgb(145, 106, 72)
|
||||
gx.rgb(147, 89, 59)
|
||||
gx.rgb(147, 74, 57)
|
||||
gx.rgb(147, 56, 35)
|
||||
gx.rgb(142, 124, 68)
|
||||
gx.rgb(142, 122, 58)
|
||||
gx.rgb(142, 120, 48)
|
||||
gx.rgb(142, 118, 37)
|
||||
gx.rgb(142, 116, 27)
|
||||
gx.rgb(123, 115, 108),
|
||||
gx.rgb(142, 136, 130),
|
||||
gx.rgb(142, 134, 120),
|
||||
gx.rgb(145, 106, 72),
|
||||
gx.rgb(147, 89, 59),
|
||||
gx.rgb(147, 74, 57),
|
||||
gx.rgb(147, 56, 35),
|
||||
gx.rgb(142, 124, 68),
|
||||
gx.rgb(142, 122, 58),
|
||||
gx.rgb(142, 120, 48),
|
||||
gx.rgb(142, 118, 37),
|
||||
gx.rgb(142, 116, 27),
|
||||
]
|
||||
},
|
||||
&Theme {
|
||||
&Theme{
|
||||
bg_color: gx.rgb(38, 38, 66)
|
||||
padding_color: gx.rgb(58, 50, 74)
|
||||
victory_color: gx.rgb(100, 160, 100)
|
||||
game_over_color: gx.rgb(190, 50, 50)
|
||||
text_color: gx.white
|
||||
tile_colors: [
|
||||
gx.rgb(92, 86, 140)
|
||||
gx.rgb(106, 99, 169)
|
||||
gx.rgb(106, 97, 156)
|
||||
gx.rgb(108, 79, 93)
|
||||
gx.rgb(110, 66, 76)
|
||||
gx.rgb(110, 55, 74)
|
||||
gx.rgb(110, 42, 45)
|
||||
gx.rgb(106, 93, 88)
|
||||
gx.rgb(106, 91, 75)
|
||||
gx.rgb(106, 90, 62)
|
||||
gx.rgb(106, 88, 48)
|
||||
gx.rgb(106, 87, 35)
|
||||
gx.rgb(92, 86, 140),
|
||||
gx.rgb(106, 99, 169),
|
||||
gx.rgb(106, 97, 156),
|
||||
gx.rgb(108, 79, 93),
|
||||
gx.rgb(110, 66, 76),
|
||||
gx.rgb(110, 55, 74),
|
||||
gx.rgb(110, 42, 45),
|
||||
gx.rgb(106, 93, 88),
|
||||
gx.rgb(106, 91, 75),
|
||||
gx.rgb(106, 90, 62),
|
||||
gx.rgb(106, 88, 48),
|
||||
gx.rgb(106, 87, 35),
|
||||
]
|
||||
},
|
||||
]
|
||||
@ -117,9 +117,9 @@ const (
|
||||
default_window_height = 560
|
||||
animation_length = 10 // frames
|
||||
frames_per_ai_move = 8
|
||||
possible_moves = [Direction.up, .right, .down, .left]
|
||||
predictions_per_move = 200
|
||||
prediction_depth = 8
|
||||
possible_moves = [Direction.up, .right, .down, .left]
|
||||
predictions_per_move = 200
|
||||
prediction_depth = 8
|
||||
)
|
||||
|
||||
// Used for performance monitoring when `-d showfps` is passed, unused / optimized out otherwise
|
||||
@ -203,17 +203,29 @@ enum Direction {
|
||||
// Utility functions
|
||||
[inline]
|
||||
fn min(a, b int) int {
|
||||
if a < b { return a } else { return b }
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
fn max(a, b int) int {
|
||||
if a > b { return a } else { return b }
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
fn abs(a int) int {
|
||||
if a < 0 { return -a } else { return a }
|
||||
if a < 0 {
|
||||
return -a
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
@ -223,8 +235,8 @@ fn avg(a, b int) int {
|
||||
|
||||
fn (b Board) transpose() Board {
|
||||
mut res := b
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
res.field[y][x] = b.field[x][y]
|
||||
}
|
||||
}
|
||||
@ -233,8 +245,8 @@ fn (b Board) transpose() Board {
|
||||
|
||||
fn (b Board) hmirror() Board {
|
||||
mut res := b
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
res.field[y][x] = b.field[y][3 - x]
|
||||
}
|
||||
}
|
||||
@ -250,7 +262,11 @@ fn (t TileLine) to_left() TileLine {
|
||||
mut nonzeros := 0
|
||||
// gather meta info about the line:
|
||||
for x in res.field {
|
||||
if x == 0 { zeros++ } else { nonzeros++ }
|
||||
if x == 0 {
|
||||
zeros++
|
||||
} else {
|
||||
nonzeros++
|
||||
}
|
||||
}
|
||||
if nonzeros == 0 {
|
||||
// when all the tiles are empty, there is nothing left to do
|
||||
@ -289,15 +305,17 @@ fn (t TileLine) to_left() TileLine {
|
||||
|
||||
fn (b Board) to_left() Board {
|
||||
mut res := b
|
||||
for y in 0..4 {
|
||||
mut hline := TileLine{ypos: y}
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
mut hline := TileLine{
|
||||
ypos: y
|
||||
}
|
||||
for x in 0 .. 4 {
|
||||
hline.field[x] = b.field[y][x]
|
||||
}
|
||||
reshline := hline.to_left()
|
||||
res.shifts += reshline.shifts
|
||||
res.points += reshline.points
|
||||
for x in 0..4 {
|
||||
for x in 0 .. 4 {
|
||||
res.field[y][x] = reshline.field[x]
|
||||
}
|
||||
}
|
||||
@ -306,14 +324,14 @@ fn (b Board) to_left() Board {
|
||||
|
||||
fn (b Board) move(d Direction) (Board, bool) {
|
||||
new := match d {
|
||||
.left { b.to_left() }
|
||||
.left { b.to_left() }
|
||||
.right { b.hmirror().to_left().hmirror() }
|
||||
.up { b.transpose().to_left().transpose() }
|
||||
.down { b.transpose().hmirror().to_left().hmirror().transpose() }
|
||||
.up { b.transpose().to_left().transpose() }
|
||||
.down { b.transpose().hmirror().to_left().hmirror().transpose() }
|
||||
}
|
||||
// If the board hasn't changed, it's an illegal move, don't allow it.
|
||||
for x in 0..4 {
|
||||
for y in 0..4 {
|
||||
for x in 0 .. 4 {
|
||||
for y in 0 .. 4 {
|
||||
if b.field[x][y] != new.field[x][y] {
|
||||
return new, true
|
||||
}
|
||||
@ -323,17 +341,17 @@ fn (b Board) move(d Direction) (Board, bool) {
|
||||
}
|
||||
|
||||
fn (mut b Board) is_game_over() bool {
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
fidx := b.field[y][x]
|
||||
if fidx == 0 {
|
||||
// there are remaining zeros
|
||||
return false
|
||||
}
|
||||
if (x > 0 && fidx == b.field[y][x - 1])
|
||||
|| (x < 4 - 1 && fidx == b.field[y][x + 1])
|
||||
|| (y > 0 && fidx == b.field[y - 1][x])
|
||||
|| (y < 4 - 1 && fidx == b.field[y + 1][x]) {
|
||||
if (x > 0 && fidx == b.field[y][x - 1]) ||
|
||||
(x < 4 - 1 && fidx == b.field[y][x + 1]) ||
|
||||
(y > 0 && fidx == b.field[y - 1][x]) ||
|
||||
(y < 4 - 1 && fidx == b.field[y + 1][x]) {
|
||||
// there are remaining merges
|
||||
return false
|
||||
}
|
||||
@ -343,8 +361,8 @@ fn (mut b Board) is_game_over() bool {
|
||||
}
|
||||
|
||||
fn (mut app App) update_tickers() {
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
mut old := app.atickers[y][x]
|
||||
if old > 0 {
|
||||
old--
|
||||
@ -356,14 +374,14 @@ fn (mut app App) update_tickers() {
|
||||
|
||||
fn (mut app App) new_game() {
|
||||
app.board = Board{}
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
app.board.field[y][x] = 0
|
||||
app.atickers[y][x] = 0
|
||||
}
|
||||
}
|
||||
app.state = .play
|
||||
app.undo = []Undo{cap:4096}
|
||||
app.undo = []Undo{cap: 4096}
|
||||
app.moves = 0
|
||||
app.new_random_tile()
|
||||
app.new_random_tile()
|
||||
@ -371,8 +389,8 @@ fn (mut app App) new_game() {
|
||||
|
||||
[inline]
|
||||
fn (mut app App) check_for_victory() {
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
fidx := app.board.field[y][x]
|
||||
if fidx == 11 {
|
||||
app.state = .victory
|
||||
@ -392,8 +410,8 @@ fn (mut app App) check_for_game_over() {
|
||||
fn (mut b Board) place_random_tile() (Pos, int) {
|
||||
mut etiles := [16]Pos{}
|
||||
mut empty_tiles_max := 0
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
fidx := b.field[y][x]
|
||||
if fidx == 0 {
|
||||
etiles[empty_tiles_max] = Pos{x, y}
|
||||
@ -413,8 +431,8 @@ fn (mut b Board) place_random_tile() (Pos, int) {
|
||||
}
|
||||
|
||||
fn (mut app App) new_random_tile() {
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
fidx := app.board.field[y][x]
|
||||
if fidx == 0 {
|
||||
app.atickers[y][x] = 0
|
||||
@ -425,7 +443,9 @@ fn (mut app App) new_random_tile() {
|
||||
if random_value > 0 {
|
||||
app.atickers[empty_pos.y][empty_pos.x] = animation_length
|
||||
}
|
||||
if app.state != .freeplay { app.check_for_victory() }
|
||||
if app.state != .freeplay {
|
||||
app.check_for_victory()
|
||||
}
|
||||
app.check_for_game_over()
|
||||
}
|
||||
|
||||
@ -433,7 +453,7 @@ fn (mut app App) apply_new_board(new Board) {
|
||||
old := app.board
|
||||
app.moves++
|
||||
app.board = new
|
||||
app.undo << Undo{ old, app.state }
|
||||
app.undo << Undo{old, app.state}
|
||||
app.new_random_tile()
|
||||
}
|
||||
|
||||
@ -447,7 +467,7 @@ fn (mut app App) move(d Direction) {
|
||||
|
||||
struct Prediction {
|
||||
mut:
|
||||
move Direction
|
||||
move Direction
|
||||
mpoints f64
|
||||
mcmoves f64
|
||||
}
|
||||
@ -466,20 +486,26 @@ fn (mut app App) ai_move() {
|
||||
mut mpoints := 0
|
||||
mut mshifts := 0
|
||||
mut mcmoves := 0
|
||||
for _ in 0..predictions_per_move {
|
||||
for _ in 0 .. predictions_per_move {
|
||||
mut cboard := app.board
|
||||
cboard, is_valid = cboard.move(move)
|
||||
if !is_valid || cboard.is_game_over() { continue }
|
||||
if !is_valid || cboard.is_game_over() {
|
||||
continue
|
||||
}
|
||||
mpoints += cboard.points
|
||||
cboard.place_random_tile()
|
||||
mut cmoves := 0
|
||||
for !cboard.is_game_over() {
|
||||
nmove := possible_moves[rand.intn(possible_moves.len)]
|
||||
cboard, is_valid = cboard.move(nmove)
|
||||
if !is_valid { continue }
|
||||
if !is_valid {
|
||||
continue
|
||||
}
|
||||
cboard.place_random_tile()
|
||||
cmoves++
|
||||
if cmoves > prediction_depth { break }
|
||||
if cmoves > prediction_depth {
|
||||
break
|
||||
}
|
||||
}
|
||||
mpoints += cboard.points
|
||||
mshifts += cboard.shifts
|
||||
@ -489,8 +515,10 @@ fn (mut app App) ai_move() {
|
||||
predictions[move_idx].mcmoves = f64(mcmoves) / predictions_per_move
|
||||
}
|
||||
think_time := think_watch.elapsed().milliseconds()
|
||||
mut bestprediction := Prediction{mpoints:-1}
|
||||
for move_idx in 0..possible_moves.len {
|
||||
mut bestprediction := Prediction{
|
||||
mpoints: -1
|
||||
}
|
||||
for move_idx in 0 .. possible_moves.len {
|
||||
if bestprediction.mpoints < predictions[move_idx].mpoints {
|
||||
bestprediction = predictions[move_idx]
|
||||
}
|
||||
@ -501,47 +529,48 @@ fn (mut app App) ai_move() {
|
||||
|
||||
fn (app &App) label_format(kind LabelKind) gx.TextCfg {
|
||||
match kind {
|
||||
.points {
|
||||
return {
|
||||
color: if app.state in [.over, .victory] { gx.white } else { app.theme.text_color }
|
||||
.points { return {
|
||||
color: if app.state in [.over, .victory] {
|
||||
gx.white
|
||||
} else {
|
||||
app.theme.text_color
|
||||
}
|
||||
align: .left
|
||||
size: app.ui.font_size / 2
|
||||
}
|
||||
} .moves {
|
||||
return {
|
||||
color: if app.state in [.over, .victory] { gx.white } else { app.theme.text_color }
|
||||
} }
|
||||
.moves { return {
|
||||
color: if app.state in [.over, .victory] {
|
||||
gx.white
|
||||
} else {
|
||||
app.theme.text_color
|
||||
}
|
||||
align: .right
|
||||
size: app.ui.font_size / 2
|
||||
}
|
||||
} .tile {
|
||||
return {
|
||||
} }
|
||||
.tile { return {
|
||||
color: app.theme.text_color
|
||||
align: .center
|
||||
vertical_align: .middle
|
||||
size: app.ui.font_size
|
||||
}
|
||||
} .victory {
|
||||
return {
|
||||
} }
|
||||
.victory { return {
|
||||
color: app.theme.victory_color
|
||||
align: .center
|
||||
vertical_align: .middle
|
||||
size: app.ui.font_size * 2
|
||||
}
|
||||
} .game_over {
|
||||
return {
|
||||
} }
|
||||
.game_over { return {
|
||||
color: app.theme.game_over_color
|
||||
align: .center
|
||||
vertical_align: .middle
|
||||
size: app.ui.font_size * 2
|
||||
}
|
||||
} .score_end {
|
||||
return {
|
||||
} }
|
||||
.score_end { return {
|
||||
color: gx.white
|
||||
align: .center
|
||||
vertical_align: .middle
|
||||
size: app.ui.font_size * 3 / 4
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
@ -555,11 +584,12 @@ fn (mut app App) set_theme(idx int) {
|
||||
|
||||
fn (mut app App) resize() {
|
||||
mut s := sapp.dpi_scale()
|
||||
if s == 0.0 { s = 1.0 }
|
||||
if s == 0.0 {
|
||||
s = 1.0
|
||||
}
|
||||
w := int(sapp.width() / s)
|
||||
h := int(sapp.height() / s)
|
||||
m := f32(min(w, h))
|
||||
|
||||
app.ui.dpi_scale = s
|
||||
app.ui.window_width = w
|
||||
app.ui.window_height = h
|
||||
@ -568,7 +598,6 @@ fn (mut app App) resize() {
|
||||
app.ui.border_size = app.ui.padding_size * 2
|
||||
app.ui.tile_size = int((m - app.ui.padding_size * 5 - app.ui.border_size * 2) / 4)
|
||||
app.ui.font_size = int(m / 10)
|
||||
|
||||
// If the window's height is greater than its width, center the board vertically.
|
||||
// If not, center it horizontally
|
||||
if w > h {
|
||||
@ -587,29 +616,28 @@ fn (app &App) draw() {
|
||||
m := min(ww, wh)
|
||||
labelx := xpad + app.ui.border_size
|
||||
labely := ypad + app.ui.border_size / 2
|
||||
|
||||
app.draw_tiles()
|
||||
|
||||
// TODO: Make transparency work in `gg`
|
||||
if app.state == .over {
|
||||
app.gg.draw_rect(0, 0, ww, wh, gx.rgba(10, 0, 0, 180))
|
||||
app.gg.draw_text(ww / 2, (m * 4 / 10) + ypad, 'Game Over', app.label_format(.game_over))
|
||||
|
||||
f := app.label_format(.tile)
|
||||
msg := $if android { 'Tap to restart' } $else { 'Press `r` to restart' }
|
||||
app.gg.draw_text(ww / 2, (m * 6 / 10) + ypad, msg, { f | color: gx.white, size: f.size * 3 / 4 })
|
||||
app.gg.draw_text(ww / 2, (m * 6 / 10) + ypad, msg, {
|
||||
f |
|
||||
color: gx.white
|
||||
size: f.size * 3 / 4
|
||||
})
|
||||
}
|
||||
if app.state == .victory {
|
||||
app.gg.draw_rect(0, 0, ww, wh, gx.rgba(0, 10, 0, 180))
|
||||
app.gg.draw_text(ww / 2, (m * 4 / 10) + ypad, 'Victory!', app.label_format(.victory))
|
||||
|
||||
// f := app.label_format(.tile)
|
||||
msg1 := $if android { 'Tap to continue' } $else { 'Press `space` to continue' }
|
||||
msg2 := $if android { 'Tap to restart' } $else { 'Press `r` to restart' }
|
||||
app.gg.draw_text(ww / 2, (m * 6 / 10) + ypad, msg1, app.label_format(.score_end))
|
||||
app.gg.draw_text(ww / 2, (m * 8 / 10) + ypad, msg2, app.label_format(.score_end))
|
||||
}
|
||||
|
||||
// Draw at the end, so that it's on top of the victory / game over overlays
|
||||
app.gg.draw_text(labelx, labely, 'Points: $app.board.points', app.label_format(.points))
|
||||
app.gg.draw_text(ww - labelx, labely, 'Moves: $app.moves', app.label_format(.moves))
|
||||
@ -618,15 +646,13 @@ fn (app &App) draw() {
|
||||
fn (app &App) draw_tiles() {
|
||||
xstart := app.ui.x_padding + app.ui.border_size
|
||||
ystart := app.ui.y_padding + app.ui.border_size + app.ui.header_size
|
||||
|
||||
toffset := app.ui.tile_size + app.ui.padding_size
|
||||
tiles_size := min(app.ui.window_width, app.ui.window_height) - app.ui.border_size * 2
|
||||
|
||||
// Draw the padding around the tiles
|
||||
app.gg.draw_rect(xstart, ystart, tiles_size, tiles_size, app.theme.padding_color)
|
||||
// Draw the actual tiles
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
for y in 0 .. 4 {
|
||||
for x in 0 .. 4 {
|
||||
tidx := app.board.field[y][x]
|
||||
tile_color := if tidx < app.theme.tile_colors.len {
|
||||
app.theme.tile_colors[tidx]
|
||||
@ -640,27 +666,39 @@ fn (app &App) draw_tiles() {
|
||||
xoffset := xstart + app.ui.padding_size + x * toffset + (app.ui.tile_size - tw) / 2
|
||||
yoffset := ystart + app.ui.padding_size + y * toffset + (app.ui.tile_size - th) / 2
|
||||
app.gg.draw_rect(xoffset, yoffset, tw, th, tile_color)
|
||||
|
||||
if tidx != 0 { // 0 == blank spot
|
||||
xpos := xoffset + tw / 2
|
||||
ypos := yoffset + th / 2
|
||||
mut fmt := app.label_format(.tile)
|
||||
fmt = { fmt | size: int(f32(fmt.size - 1) / animation_length * anim_size) }
|
||||
|
||||
fmt = {
|
||||
fmt |
|
||||
size: int(f32(fmt.size - 1) / animation_length * anim_size)
|
||||
}
|
||||
match app.tile_format {
|
||||
.normal {
|
||||
app.gg.draw_text(xpos, ypos, '${1 << tidx}', fmt)
|
||||
} .log {
|
||||
app.gg.draw_text(xpos, ypos, '${1<<tidx}', fmt)
|
||||
}
|
||||
.log {
|
||||
app.gg.draw_text(xpos, ypos, '$tidx', fmt)
|
||||
} .exponent {
|
||||
}
|
||||
.exponent {
|
||||
app.gg.draw_text(xpos, ypos, '2', fmt)
|
||||
fs2 := int(f32(fmt.size) * 0.67)
|
||||
app.gg.draw_text(xpos + app.ui.tile_size / 10, ypos - app.ui.tile_size / 8,
|
||||
'$tidx', { fmt | size: fs2, align: gx.HorizontalAlign.left })
|
||||
} .shifts {
|
||||
'$tidx', {
|
||||
fmt |
|
||||
size: fs2
|
||||
align: gx.HorizontalAlign.left
|
||||
})
|
||||
}
|
||||
.shifts {
|
||||
fs2 := int(f32(fmt.size) * 0.6)
|
||||
app.gg.draw_text(xpos, ypos, '2<<${tidx - 1}', { fmt | size: fs2 })
|
||||
} .none_ {} // Don't draw any text here, colors only
|
||||
app.gg.draw_text(xpos, ypos, '2<<${tidx-1}', {
|
||||
fmt |
|
||||
size: fs2
|
||||
})
|
||||
}
|
||||
.none_ {} // Don't draw any text here, colors only
|
||||
.end_ {} // Should never get here
|
||||
}
|
||||
}
|
||||
@ -671,7 +709,6 @@ fn (app &App) draw_tiles() {
|
||||
fn (mut app App) handle_touches() {
|
||||
s, e := app.touch.start, app.touch.end
|
||||
adx, ady := abs(e.pos.x - s.pos.x), abs(e.pos.y - s.pos.y)
|
||||
|
||||
if max(adx, ady) < 10 {
|
||||
app.handle_tap()
|
||||
} else {
|
||||
@ -685,13 +722,15 @@ fn (mut app App) handle_tap() {
|
||||
m := min(w, h)
|
||||
s, e := app.touch.start, app.touch.end
|
||||
avgx, avgy := avg(s.pos.x, e.pos.x), avg(s.pos.y, e.pos.y)
|
||||
|
||||
// TODO: Replace "touch spots" with actual buttons
|
||||
// bottom left -> change theme
|
||||
if avgx < 200 && h - avgy < 200 { app.next_theme() }
|
||||
if avgx < 200 && h - avgy < 200 {
|
||||
app.next_theme()
|
||||
}
|
||||
// bottom right -> change tile format
|
||||
if w - avgx < 200 && h - avgy < 200 { app.next_tile_format() }
|
||||
|
||||
if w - avgx < 200 && h - avgy < 200 {
|
||||
app.next_tile_format()
|
||||
}
|
||||
if app.state == .victory {
|
||||
if avgy > (m / 2) + ypad {
|
||||
if avgy < (m * 7 / 10) + ypad {
|
||||
@ -710,10 +749,11 @@ fn (mut app App) handle_tap() {
|
||||
}
|
||||
|
||||
fn (mut app App) handle_swipe() {
|
||||
// Currently, swipes are only used to move the tiles.
|
||||
// Currently, swipes are only used to move the tiles.
|
||||
// If the user's not playing, exit early to avoid all the unnecessary calculations
|
||||
if app.state !in [.play, .freeplay] { return }
|
||||
|
||||
if app.state !in [.play, .freeplay] {
|
||||
return
|
||||
}
|
||||
s, e := app.touch.start, app.touch.end
|
||||
w, h := app.ui.window_width, app.ui.window_height
|
||||
dx, dy := e.pos.x - s.pos.x, e.pos.y - s.pos.y
|
||||
@ -721,23 +761,38 @@ fn (mut app App) handle_swipe() {
|
||||
dmin := if min(adx, ady) > 0 { min(adx, ady) } else { 1 }
|
||||
dmax := if max(adx, ady) > 0 { max(adx, ady) } else { 1 }
|
||||
tdiff := int(e.time.unix_time_milli() - s.time.unix_time_milli())
|
||||
|
||||
// TODO: make this calculation more accurate (don't use arbitrary numbers)
|
||||
min_swipe_distance := int(math.sqrt(min(w, h) * tdiff / 60)) + 20
|
||||
|
||||
if dmax < min_swipe_distance { return } // Swipe was too short
|
||||
if dmax / dmin < 2 { return } // Swiped diagonally
|
||||
|
||||
if dmax < min_swipe_distance {
|
||||
return
|
||||
}
|
||||
// Swipe was too short
|
||||
if dmax / dmin < 2 {
|
||||
return
|
||||
}
|
||||
// Swiped diagonally
|
||||
if adx > ady {
|
||||
if dx < 0 { app.move(.left) } else { app.move(.right) }
|
||||
if dx < 0 {
|
||||
app.move(.left)
|
||||
} else {
|
||||
app.move(.right)
|
||||
}
|
||||
} else {
|
||||
if dy < 0 { app.move(.up) } else { app.move(.down) }
|
||||
if dy < 0 {
|
||||
app.move(.up)
|
||||
} else {
|
||||
app.move(.down)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
fn (mut app App) next_theme() {
|
||||
app.set_theme(if app.theme_idx == themes.len - 1 { 0 } else { app.theme_idx + 1 })
|
||||
app.set_theme(if app.theme_idx == themes.len - 1 {
|
||||
0
|
||||
} else {
|
||||
app.theme_idx + 1
|
||||
})
|
||||
}
|
||||
|
||||
[inline]
|
||||
@ -758,39 +813,31 @@ fn (mut app App) undo() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn (mut app App) on_key_down(key sapp.KeyCode) {
|
||||
// these keys are independent from the game state:
|
||||
match key {
|
||||
.a {
|
||||
app.is_ai_mode = !app.is_ai_mode
|
||||
} .escape {
|
||||
exit(0)
|
||||
} .n, .r {
|
||||
app.new_game()
|
||||
} .backspace {
|
||||
app.undo()
|
||||
} .enter {
|
||||
app.next_tile_format()
|
||||
} .j {
|
||||
app.state = .over
|
||||
} .t {
|
||||
app.next_theme()
|
||||
} else {}
|
||||
.a { app.is_ai_mode = !app.is_ai_mode }
|
||||
.escape { exit(0) }
|
||||
.n, .r { app.new_game() }
|
||||
.backspace { app.undo() }
|
||||
.enter { app.next_tile_format() }
|
||||
.j { app.state = .over }
|
||||
.t { app.next_theme() }
|
||||
else {}
|
||||
}
|
||||
|
||||
if app.state in [.play, .freeplay] {
|
||||
match key {
|
||||
.w, .up { app.move(.up) }
|
||||
.a, .left { app.move(.left) }
|
||||
.s, .down { app.move(.down) }
|
||||
.w, .up { app.move(.up) }
|
||||
.a, .left { app.move(.left) }
|
||||
.s, .down { app.move(.down) }
|
||||
.d, .right { app.move(.right) }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
if app.state == .victory {
|
||||
if key == .space { app.state = .freeplay }
|
||||
if key == .space {
|
||||
app.state = .freeplay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -798,54 +845,62 @@ fn on_event(e &sapp.Event, mut app App) {
|
||||
match e.typ {
|
||||
.key_down {
|
||||
app.on_key_down(e.key_code)
|
||||
} .resized, .restored, .resumed {
|
||||
}
|
||||
.resized, .restored, .resumed {
|
||||
app.resize()
|
||||
} .touches_began {
|
||||
}
|
||||
.touches_began {
|
||||
if e.num_touches > 0 {
|
||||
t := e.touches[0]
|
||||
app.touch.start = {
|
||||
pos: {
|
||||
x: int(t.pos_x / app.ui.dpi_scale),
|
||||
x: int(t.pos_x / app.ui.dpi_scale)
|
||||
y: int(t.pos_y / app.ui.dpi_scale)
|
||||
},
|
||||
}
|
||||
time: time.now()
|
||||
}
|
||||
}
|
||||
} .touches_ended {
|
||||
}
|
||||
.touches_ended {
|
||||
if e.num_touches > 0 {
|
||||
t := e.touches[0]
|
||||
app.touch.end = {
|
||||
pos: {
|
||||
x: int(t.pos_x / app.ui.dpi_scale),
|
||||
x: int(t.pos_x / app.ui.dpi_scale)
|
||||
y: int(t.pos_y / app.ui.dpi_scale)
|
||||
},
|
||||
}
|
||||
time: time.now()
|
||||
}
|
||||
app.handle_touches()
|
||||
}
|
||||
} .mouse_down {
|
||||
}
|
||||
.mouse_down {
|
||||
app.touch.start = {
|
||||
pos: {
|
||||
x: int(e.mouse_x / app.ui.dpi_scale),
|
||||
x: int(e.mouse_x / app.ui.dpi_scale)
|
||||
y: int(e.mouse_y / app.ui.dpi_scale)
|
||||
},
|
||||
}
|
||||
time: time.now()
|
||||
}
|
||||
} .mouse_up {
|
||||
}
|
||||
.mouse_up {
|
||||
app.touch.end = {
|
||||
pos: {
|
||||
x: int(e.mouse_x / app.ui.dpi_scale),
|
||||
x: int(e.mouse_x / app.ui.dpi_scale)
|
||||
y: int(e.mouse_y / app.ui.dpi_scale)
|
||||
},
|
||||
}
|
||||
time: time.now()
|
||||
}
|
||||
app.handle_touches()
|
||||
} else {}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(mut app App) {
|
||||
$if showfps? { app.perf.frame_sw.restart() }
|
||||
$if showfps ? {
|
||||
app.perf.frame_sw.restart()
|
||||
}
|
||||
app.gg.begin()
|
||||
app.update_tickers()
|
||||
app.draw()
|
||||
@ -853,13 +908,15 @@ fn frame(mut app App) {
|
||||
if app.is_ai_mode && app.state in [.play, .freeplay] && app.perf.frame % frames_per_ai_move == 0 {
|
||||
app.ai_move()
|
||||
}
|
||||
$if showfps? { app.showfps() }
|
||||
$if showfps ? {
|
||||
app.showfps()
|
||||
}
|
||||
app.gg.end()
|
||||
}
|
||||
|
||||
fn init(mut app App) {
|
||||
app.resize()
|
||||
$if showfps? {
|
||||
$if showfps ? {
|
||||
app.perf.frame_sw.restart()
|
||||
app.perf.second_sw.restart()
|
||||
}
|
||||
@ -872,7 +929,7 @@ fn (mut app App) showfps() {
|
||||
last_frame_us := app.perf.frame_sw.elapsed().microseconds()
|
||||
ticks := f64(app.perf.second_sw.elapsed().milliseconds())
|
||||
fps := f64(app.perf.frame - app.perf.frame_old) * ticks / 1000 / 4.5
|
||||
last_fps := 128_000.0 / ticks
|
||||
last_fps := 128000.0 / ticks
|
||||
eprintln('frame ${f:-5} | avg. fps: ${fps:-5.1f} | avg. last 128 fps: ${last_fps:-5.1f} | last frame time: ${last_frame_us:-4}µs')
|
||||
app.perf.second_sw.restart()
|
||||
app.perf.frame_old = f
|
||||
@ -886,26 +943,21 @@ $if android {
|
||||
#define printf(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define fprintf(a, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
app.new_game()
|
||||
|
||||
mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf'))
|
||||
$if android {
|
||||
font_path = 'fonts/RobotoMono-Regular.ttf'
|
||||
}
|
||||
|
||||
mut window_title := 'V 2048'
|
||||
// TODO: Make emcc a real platform ifdef
|
||||
$if emscripten? {
|
||||
$if emscripten ? {
|
||||
// in emscripten, sokol uses `window_title` as the selector to the canvas it'll render to,
|
||||
// and since `document.querySelector('V 2048')` isn't valid JS, we use `canvas` instead
|
||||
window_title = 'canvas'
|
||||
}
|
||||
|
||||
app.perf = &Perf{}
|
||||
|
||||
app.gg = gg.new_context({
|
||||
bg_color: app.theme.bg_color
|
||||
width: default_window_width
|
||||
|
Loading…
Reference in New Issue
Block a user