examples/2048: add a simple Monte Carlo player on `a`
							parent
							
								
									624f22e27e
								
							
						
					
					
						commit
						46be0710ac
					
				| 
						 | 
					@ -19,6 +19,7 @@ mut:
 | 
				
			||||||
	tile_format   TileFormat = .normal
 | 
						tile_format   TileFormat = .normal
 | 
				
			||||||
	moves         int
 | 
						moves         int
 | 
				
			||||||
	perf          &Perf = 0
 | 
						perf          &Perf = 0
 | 
				
			||||||
 | 
						is_ai_mode    bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Ui {
 | 
					struct Ui {
 | 
				
			||||||
| 
						 | 
					@ -279,6 +280,44 @@ fn (b Board) to_left() Board {
 | 
				
			||||||
	return res
 | 
						return res
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn (b Board) move(d Direction) (Board, bool) {
 | 
				
			||||||
 | 
						new := match d {
 | 
				
			||||||
 | 
							.left  { b.to_left() }
 | 
				
			||||||
 | 
							.right { b.hmirror().to_left().hmirror() }
 | 
				
			||||||
 | 
							.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 {
 | 
				
			||||||
 | 
								if b.field[x][y] != new.field[x][y] {
 | 
				
			||||||
 | 
									return new, true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return new, false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn (mut b Board) is_game_over() bool {
 | 
				
			||||||
 | 
						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]) {
 | 
				
			||||||
 | 
									// there are remaining merges
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut app App) update_tickers() {
 | 
					fn (mut app App) update_tickers() {
 | 
				
			||||||
	for y in 0..4 {
 | 
						for y in 0..4 {
 | 
				
			||||||
		for x in 0..4 {
 | 
							for x in 0..4 {
 | 
				
			||||||
| 
						 | 
					@ -300,7 +339,7 @@ fn (mut app App) new_game() {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.state = .play
 | 
						app.state = .play
 | 
				
			||||||
	app.undo = []
 | 
						app.undo = []Board{cap:4096}
 | 
				
			||||||
	app.moves = 0
 | 
						app.moves = 0
 | 
				
			||||||
	app.new_random_tile()
 | 
						app.new_random_tile()
 | 
				
			||||||
	app.new_random_tile()
 | 
						app.new_random_tile()
 | 
				
			||||||
| 
						 | 
					@ -319,37 +358,19 @@ fn (mut app App) check_for_victory() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut app App) check_for_game_over() {
 | 
					fn (mut app App) check_for_game_over() {
 | 
				
			||||||
	mut zeros := 0
 | 
						if app.board.is_game_over() {
 | 
				
			||||||
	mut remaining_merges := 0
 | 
					 | 
				
			||||||
	for y in 0..4 {
 | 
					 | 
				
			||||||
		for x in 0..4 {
 | 
					 | 
				
			||||||
			fidx := app.board.field[y][x]
 | 
					 | 
				
			||||||
			if fidx == 0 {
 | 
					 | 
				
			||||||
				zeros++
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if (x > 0 && fidx == app.board.field[y][x - 1])
 | 
					 | 
				
			||||||
				|| (x < 4 - 1 && fidx == app.board.field[y][x + 1])
 | 
					 | 
				
			||||||
				|| (y > 0 && fidx == app.board.field[y - 1][x])
 | 
					 | 
				
			||||||
				|| (y < 4 - 1 && fidx == app.board.field[y + 1][x]) {
 | 
					 | 
				
			||||||
				remaining_merges++
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if remaining_merges == 0 && zeros == 0 {
 | 
					 | 
				
			||||||
		app.game_over()
 | 
							app.game_over()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut app App) new_random_tile() {
 | 
					fn (mut b Board) place_random_tile() (Pos, int) {
 | 
				
			||||||
	mut etiles := [16]Pos{}
 | 
						mut etiles := [16]Pos{}
 | 
				
			||||||
	mut empty_tiles_max := 0
 | 
						mut empty_tiles_max := 0
 | 
				
			||||||
	for y in 0..4 {
 | 
						for y in 0..4 {
 | 
				
			||||||
		for x in 0..4 {
 | 
							for x in 0..4 {
 | 
				
			||||||
			fidx := app.board.field[y][x]
 | 
								fidx := b.field[y][x]
 | 
				
			||||||
			if fidx == 0 {
 | 
								if fidx == 0 {
 | 
				
			||||||
				etiles[empty_tiles_max] = Pos{x, y}
 | 
									etiles[empty_tiles_max] = Pos{x, y}
 | 
				
			||||||
				app.atickers[y][x] = 0
 | 
					 | 
				
			||||||
				empty_tiles_max++
 | 
									empty_tiles_max++
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -357,9 +378,25 @@ fn (mut app App) new_random_tile() {
 | 
				
			||||||
	if empty_tiles_max > 0 {
 | 
						if empty_tiles_max > 0 {
 | 
				
			||||||
		new_random_tile_index := rand.intn(empty_tiles_max)
 | 
							new_random_tile_index := rand.intn(empty_tiles_max)
 | 
				
			||||||
		empty_pos := etiles[new_random_tile_index]
 | 
							empty_pos := etiles[new_random_tile_index]
 | 
				
			||||||
		// 1/8 chance of creating a `4` tile
 | 
							// 10% chance of getting a `4` tile
 | 
				
			||||||
		random_value := if rand.intn(8) == 0 { 2 } else { 1 }
 | 
							random_value := if rand.f64n(1.0) < 0.9 { 1 } else { 2 }
 | 
				
			||||||
		app.board.field[empty_pos.y][empty_pos.x] = random_value
 | 
							b.field[empty_pos.y][empty_pos.x] = random_value
 | 
				
			||||||
 | 
							return empty_pos, random_value
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return Pos{}, 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn (mut app App) new_random_tile() {
 | 
				
			||||||
 | 
						for y in 0..4 {
 | 
				
			||||||
 | 
							for x in 0..4 {
 | 
				
			||||||
 | 
								fidx := app.board.field[y][x]
 | 
				
			||||||
 | 
								if fidx == 0 {
 | 
				
			||||||
 | 
									app.atickers[y][x] = 0
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						empty_pos, random_value := app.board.place_random_tile()
 | 
				
			||||||
 | 
						if random_value > 0 {
 | 
				
			||||||
		app.atickers[empty_pos.y][empty_pos.x] = animation_length
 | 
							app.atickers[empty_pos.y][empty_pos.x] = animation_length
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.check_for_victory()
 | 
						app.check_for_victory()
 | 
				
			||||||
| 
						 | 
					@ -374,26 +411,82 @@ fn (mut app App) game_over() {
 | 
				
			||||||
	app.state = .over
 | 
						app.state = .over
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut app App) move(d Direction) {
 | 
					fn (mut app App) apply_new_board(new Board) {
 | 
				
			||||||
	old := app.board
 | 
						old := app.board
 | 
				
			||||||
	new := match d {
 | 
						app.moves++
 | 
				
			||||||
		.left  { old.to_left() }
 | 
						app.board = new
 | 
				
			||||||
		.right { old.hmirror().to_left().hmirror() }
 | 
						app.undo << old
 | 
				
			||||||
		.up    { old.transpose().to_left().transpose() }
 | 
						app.new_random_tile()
 | 
				
			||||||
		.down  { old.transpose().hmirror().to_left().hmirror().transpose() }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn (mut app App) move(d Direction) {
 | 
				
			||||||
 | 
						new, is_valid := app.board.move(d)
 | 
				
			||||||
 | 
						if !is_valid {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// If the board hasn't changed, it's an illegal move, don't allow it.
 | 
						app.apply_new_board(new)
 | 
				
			||||||
	for x in 0..4 {
 | 
					}
 | 
				
			||||||
		for y in 0..4 {
 | 
					
 | 
				
			||||||
			if old.field[x][y] != new.field[x][y] {
 | 
					const (
 | 
				
			||||||
				app.moves++
 | 
						possible_moves = [Direction.up, .right, .down, .left]
 | 
				
			||||||
				app.board = new
 | 
						predictions_per_move = 200
 | 
				
			||||||
				app.undo << old
 | 
						prediction_depth = 100
 | 
				
			||||||
				app.new_random_tile()
 | 
					)
 | 
				
			||||||
				return
 | 
					struct Prediction {
 | 
				
			||||||
 | 
					mut:
 | 
				
			||||||
 | 
						move Direction
 | 
				
			||||||
 | 
						mpoints f64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn (p Prediction) str() string {
 | 
				
			||||||
 | 
						return 'Prediction{move: ${p.move:5} | mpoints: ${p.mpoints:6.2f} }'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					fn (mut app App) ai_move() {
 | 
				
			||||||
 | 
						mut predictions := [4]Prediction{}
 | 
				
			||||||
 | 
						mut nboard := app.board
 | 
				
			||||||
 | 
						mut is_valid := false
 | 
				
			||||||
 | 
						think_watch := time.new_stopwatch({})
 | 
				
			||||||
 | 
						for move in possible_moves {
 | 
				
			||||||
 | 
							move_idx := int(move)
 | 
				
			||||||
 | 
							predictions[move_idx].move = move
 | 
				
			||||||
 | 
							mut mpoints := 0
 | 
				
			||||||
 | 
							mut mshifts := 0
 | 
				
			||||||
 | 
							for i := 0; i < predictions_per_move; i++ {
 | 
				
			||||||
 | 
								mut cboard := app.board
 | 
				
			||||||
 | 
								cboard, is_valid = cboard.move(move)
 | 
				
			||||||
 | 
								if !is_valid {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if 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)]
 | 
				
			||||||
 | 
									nboard, is_valid = cboard.move(nmove)
 | 
				
			||||||
 | 
									if !is_valid {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cboard = nboard
 | 
				
			||||||
 | 
									cboard.place_random_tile()
 | 
				
			||||||
 | 
									cmoves++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								mpoints += cboard.points
 | 
				
			||||||
 | 
								mshifts += cboard.shifts
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							predictions[move_idx].mpoints = f64(mpoints)/predictions_per_move
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						think_time := think_watch.elapsed().microseconds()
 | 
				
			||||||
 | 
						mut bestprediction := Prediction{mpoints:-1}
 | 
				
			||||||
 | 
						for move_idx in 0..possible_moves.len {
 | 
				
			||||||
 | 
							if bestprediction.mpoints < predictions[move_idx].mpoints {
 | 
				
			||||||
 | 
								bestprediction = predictions[move_idx]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						eprintln('Simulation time: ${think_time:6}µs |  best $bestprediction')
 | 
				
			||||||
 | 
						app.move( bestprediction.move )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (app &App) label_format(kind LabelKind) gx.TextCfg {
 | 
					fn (app &App) label_format(kind LabelKind) gx.TextCfg {
 | 
				
			||||||
| 
						 | 
					@ -572,7 +665,9 @@ fn (mut app App) handle_swipe(start, end Pos) {
 | 
				
			||||||
fn (mut app App) on_key_down(key sapp.KeyCode) {
 | 
					fn (mut app App) on_key_down(key sapp.KeyCode) {
 | 
				
			||||||
	// these keys are independent from the game state:
 | 
						// these keys are independent from the game state:
 | 
				
			||||||
	match key {
 | 
						match key {
 | 
				
			||||||
		.escape {
 | 
							.a {
 | 
				
			||||||
 | 
								app.is_ai_mode = !app.is_ai_mode
 | 
				
			||||||
 | 
							} .escape {
 | 
				
			||||||
			exit(0)
 | 
								exit(0)
 | 
				
			||||||
		} .n {
 | 
							} .n {
 | 
				
			||||||
			app.new_game()
 | 
								app.new_game()
 | 
				
			||||||
| 
						 | 
					@ -595,7 +690,7 @@ fn (mut app App) on_key_down(key sapp.KeyCode) {
 | 
				
			||||||
		} else {}
 | 
							} else {}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if app.state == .play {
 | 
						if app.state in [.play, .victory] {
 | 
				
			||||||
		match key {
 | 
							match key {
 | 
				
			||||||
			.w, .up    { app.move(.up) }
 | 
								.w, .up    { app.move(.up) }
 | 
				
			||||||
			.a, .left  { app.move(.left) }
 | 
								.a, .left  { app.move(.left) }
 | 
				
			||||||
| 
						 | 
					@ -633,8 +728,12 @@ fn frame(mut app App) {
 | 
				
			||||||
	app.gg.begin()
 | 
						app.gg.begin()
 | 
				
			||||||
	app.update_tickers()
 | 
						app.update_tickers()
 | 
				
			||||||
	app.draw()
 | 
						app.draw()
 | 
				
			||||||
	app.gg.end()
 | 
						app.perf.frame++
 | 
				
			||||||
 | 
						if app.is_ai_mode && app.perf.frame % 15 == 0 {
 | 
				
			||||||
 | 
							app.ai_move()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	$if showfps? { app.showfps() }
 | 
						$if showfps? { app.showfps() }
 | 
				
			||||||
 | 
						app.gg.end()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn init(mut app App) {
 | 
					fn init(mut app App) {
 | 
				
			||||||
| 
						 | 
					@ -647,7 +746,6 @@ fn init(mut app App) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut app App) showfps() {
 | 
					fn (mut app App) showfps() {
 | 
				
			||||||
	println(app.perf.frame_sw.elapsed().microseconds())
 | 
						println(app.perf.frame_sw.elapsed().microseconds())
 | 
				
			||||||
	app.perf.frame++
 | 
					 | 
				
			||||||
	f := app.perf.frame
 | 
						f := app.perf.frame
 | 
				
			||||||
	if (f & 127) == 0 {
 | 
						if (f & 127) == 0 {
 | 
				
			||||||
		last_frame_us := app.perf.frame_sw.elapsed().microseconds()
 | 
							last_frame_us := app.perf.frame_sw.elapsed().microseconds()
 | 
				
			||||||
| 
						 | 
					@ -685,9 +783,7 @@ fn main() {
 | 
				
			||||||
		window_title = 'canvas'
 | 
							window_title = 'canvas'
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	$if showfps? {
 | 
						app.perf = &Perf{}
 | 
				
			||||||
		app.perf = &Perf{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.gg = gg.new_context({
 | 
						app.gg = gg.new_context({
 | 
				
			||||||
		bg_color: app.theme.bg_color
 | 
							bg_color: app.theme.bg_color
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue