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