examples/2048: run `v -w fmt 2048.v`
							parent
							
								
									f9ce7f3542
								
							
						
					
					
						commit
						71e1ca72ec
					
				|  | @ -48,67 +48,67 @@ struct Theme { | |||
| 
 | ||||
| const ( | ||||
| 	themes                = [ | ||||
| 		&Theme { | ||||
| 		&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), | ||||
| 			] | ||||
| 		}, | ||||
| 	] | ||||
|  | @ -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] | ||||
| 		} | ||||
| 	} | ||||
|  | @ -312,8 +330,8 @@ fn (b Board) move(d Direction) (Board, bool) { | |||
| 		.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() | ||||
| } | ||||
| 
 | ||||
|  | @ -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 { | ||||
|  | @ -712,8 +751,9 @@ fn (mut app App) handle_tap() { | |||
| fn (mut app App) handle_swipe() { | ||||
| 	// 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 { | ||||
| 		if dy < 0 { app.move(.up) } else { app.move(.down) } | ||||
| 			app.move(.right) | ||||
| 		} | ||||
| 	} else { | ||||
| 		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,27 +813,18 @@ 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) } | ||||
|  | @ -788,9 +834,10 @@ fn (mut app App) on_key_down(key sapp.KeyCode) { | |||
| 			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