module main import gg import gx import os import time import math import rand import neuroevolution const ( win_width = 500 win_height = 512 ) struct Bird { mut: x f64 = 80 y f64 = 250 width f64 = 40 height f64 = 30 alive bool = true gravity f64 velocity f64 = 0.3 jump f64 = -6 } fn (mut b Bird) flap() { b.gravity = b.jump } fn (mut b Bird) update() { b.gravity += b.velocity b.y += b.gravity } fn (b Bird) is_dead(height f64, pipes []Pipe) bool { if b.y >= height || b.y + b.height <= 0 { return true } for pipe in pipes { if !(b.x > pipe.x + pipe.width || b.x + b.width < pipe.x || b.y > pipe.y + pipe.height || b.y + b.height < pipe.y) { return true } } return false } struct Pipe { mut: x f64 = 80 y f64 = 250 width f64 = 40 height f64 = 30 speed f64 = 3 } fn (mut p Pipe) update() { p.x -= p.speed } fn (p Pipe) is_out() bool { return p.x + p.width < 0 } struct App { mut: gg &gg.Context background gg.Image bird gg.Image pipetop gg.Image pipebottom gg.Image pipes []Pipe birds []Bird score int max_score int width f64 = win_width height f64 = win_height spawn_interval f64 = 90 interval f64 nv neuroevolution.Generations gen []neuroevolution.Network alives int generation int background_speed f64 = 0.5 background_x f64 timer_period_ms int = 24 } fn (mut app App) start() { app.interval = 0 app.score = 0 app.pipes = [] app.birds = [] app.gen = app.nv.generate() for _ in 0 .. app.gen.len { app.birds << Bird{} } app.generation++ app.alives = app.birds.len } fn (app &App) is_it_end() bool { for i in 0 .. app.birds.len { if app.birds[i].alive { return false } } return true } fn (mut app App) update() { app.background_x += app.background_speed mut next_holl := f64(0) if app.birds.len > 0 { for i := 0; i < app.pipes.len; i += 2 { if app.pipes[i].x + app.pipes[i].width > app.birds[0].x { next_holl = app.pipes[i].height / app.height break } } } for j, mut bird in app.birds { if bird.alive { inputs := [ bird.y / app.height, next_holl, ] res := app.gen[j].compute(inputs) if res[0] > 0.5 { bird.flap() } bird.update() if bird.is_dead(app.height, app.pipes) { bird.alive = false app.alives-- app.nv.network_score(app.gen[j], app.score) if app.is_it_end() { app.start() } } } } for k := 0; k < app.pipes.len; k++ { app.pipes[k].update() if app.pipes[k].is_out() { app.pipes.delete(k) k-- } } if app.interval == 0 { delta_bord := f64(50) pipe_holl := f64(120) holl_position := math.round(rand.f64() * (app.height - delta_bord * 2.0 - pipe_holl)) + delta_bord app.pipes << Pipe{ x: app.width y: 0 height: holl_position } app.pipes << Pipe{ x: app.width y: holl_position + pipe_holl height: app.height } } app.interval++ if app.interval == app.spawn_interval { app.interval = 0 } app.score++ app.max_score = if app.score > app.max_score { app.score } else { app.max_score } } fn main() { mut app := &App{ gg: 0 } mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf')) $if android { font_path = 'fonts/RobotoMono-Regular.ttf' } app.gg = gg.new_context( bg_color: gx.white width: win_width height: win_height use_ortho: true // This is needed for 2D drawing create_window: true window_title: 'flappylearning-v' frame_fn: frame event_fn: on_event user_data: app init_fn: init_images font_path: font_path ) app.nv = neuroevolution.Generations{ population: 50 network: [2, 2, 1] } app.start() go app.run() app.gg.run() } fn (mut app App) run() { for { app.update() time.wait(app.timer_period_ms * time.millisecond) } } fn init_images(mut app App) { $if android { background := os.read_apk_asset('img/background.png') or { panic(err) } app.background = app.gg.create_image_from_byte_array(background) bird := os.read_apk_asset('img/bird.png') or { panic(err) } app.bird = app.gg.create_image_from_byte_array(bird) pipetop := os.read_apk_asset('img/pipetop.png') or { panic(err) } app.pipetop = app.gg.create_image_from_byte_array(pipetop) pipebottom := os.read_apk_asset('img/pipebottom.png') or { panic(err) } app.pipebottom = app.gg.create_image_from_byte_array(pipebottom) } $else { app.background = app.gg.create_image(os.resource_abs_path('assets/img/background.png')) app.bird = app.gg.create_image(os.resource_abs_path('assets/img/bird.png')) app.pipetop = app.gg.create_image(os.resource_abs_path('assets/img/pipetop.png')) app.pipebottom = app.gg.create_image(os.resource_abs_path('assets/img/pipebottom.png')) } } fn frame(app &App) { app.gg.begin() app.draw() app.gg.end() } fn (app &App) display() { for i := 0; i < int(math.ceil(app.width / app.background.width) + 1.0); i++ { background_x := i * app.background.width - math.floor(int(app.background_x) % int(app.background.width)) app.gg.draw_image(f32(background_x), 0, app.background.width, app.background.height, app.background) } for i, pipe in app.pipes { if i % 2 == 0 { app.gg.draw_image(f32(pipe.x), f32(pipe.y + pipe.height - app.pipetop.height), app.pipetop.width, app.pipetop.height, app.pipetop) } else { app.gg.draw_image(f32(pipe.x), f32(pipe.y), app.pipebottom.width, app.pipebottom.height, app.pipebottom) } } for bird in app.birds { if bird.alive { app.gg.draw_image(f32(bird.x), f32(bird.y), app.bird.width, app.bird.height, app.bird) } } app.gg.draw_text_def(10, 25, 'Score: $app.score') app.gg.draw_text_def(10, 50, 'Max Score: $app.max_score') app.gg.draw_text_def(10, 75, 'Generation: $app.generation') app.gg.draw_text_def(10, 100, 'Alive: $app.alives / $app.nv.population') } fn (app &App) draw() { app.display() } fn on_event(e &gg.Event, mut app App) { if e.typ == .key_down { app.key_down(e.key_code) } } fn (mut app App) key_down(key gg.KeyCode) { // global keys match key { .escape { exit(0) } ._0 { app.timer_period_ms = 0 } .space { if app.timer_period_ms == 24 { app.timer_period_ms = 4 } else { app.timer_period_ms = 24 } } else {} } }