2020-12-27 13:19:32 +03:00
|
|
|
module main
|
|
|
|
|
|
|
|
import gg
|
2020-12-31 18:46:37 +03:00
|
|
|
import sokol.sapp
|
2020-12-27 13:19:32 +03:00
|
|
|
import gx
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
import math
|
|
|
|
import rand
|
|
|
|
import neuroevolution
|
|
|
|
|
|
|
|
const (
|
2020-12-31 18:46:37 +03:00
|
|
|
win_width = 500
|
|
|
|
win_height = 512
|
2020-12-27 13:19:32 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
struct Bird {
|
|
|
|
mut:
|
2021-02-07 13:40:12 +03:00
|
|
|
x f64 = 80
|
|
|
|
y f64 = 250
|
|
|
|
width f64 = 40
|
|
|
|
height f64 = 30
|
2020-12-27 14:02:01 +03:00
|
|
|
alive bool = true
|
|
|
|
gravity f64
|
2020-12-27 13:19:32 +03:00
|
|
|
velocity f64 = 0.3
|
2020-12-27 14:02:01 +03:00
|
|
|
jump f64 = -6
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2021-02-07 13:40:12 +03:00
|
|
|
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) {
|
2020-12-27 13:19:32 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Pipe {
|
|
|
|
mut:
|
2020-12-27 14:02:01 +03:00
|
|
|
x f64 = 80
|
|
|
|
y f64 = 250
|
|
|
|
width f64 = 40
|
2020-12-27 13:19:32 +03:00
|
|
|
height f64 = 30
|
2020-12-27 14:02:01 +03:00
|
|
|
speed f64 = 3
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn (mut p Pipe) update() {
|
|
|
|
p.x -= p.speed
|
|
|
|
}
|
|
|
|
|
|
|
|
fn (p Pipe) is_out() bool {
|
|
|
|
return p.x + p.width < 0
|
|
|
|
}
|
|
|
|
|
|
|
|
struct App {
|
|
|
|
mut:
|
2020-12-27 14:02:01 +03:00
|
|
|
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
|
2020-12-27 13:19:32 +03:00
|
|
|
background_speed f64 = 0.5
|
2020-12-27 14:02:01 +03:00
|
|
|
background_x f64
|
2020-12-31 18:46:37 +03:00
|
|
|
timer_period_ms int = 24
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-27 14:02:01 +03:00
|
|
|
for j, mut bird in app.birds {
|
2020-12-27 13:19:32 +03:00
|
|
|
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)
|
2021-02-07 13:40:12 +03:00
|
|
|
holl_position := math.round(rand.f64() * (app.height - delta_bord * 2.0 - pipe_holl)) +
|
|
|
|
delta_bord
|
2020-12-27 13:19:32 +03:00
|
|
|
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++
|
2020-12-27 14:02:01 +03:00
|
|
|
app.max_score = if app.score > app.max_score { app.score } else { app.max_score }
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
mut app := &App{
|
|
|
|
gg: 0
|
|
|
|
}
|
2021-02-07 13:40:12 +03:00
|
|
|
mut font_path := os.resource_abs_path(os.join_path('../assets/fonts/', 'RobotoMono-Regular.ttf'))
|
|
|
|
$if android {
|
|
|
|
font_path = 'fonts/RobotoMono-Regular.ttf'
|
|
|
|
}
|
2020-12-27 14:02:01 +03:00
|
|
|
app.gg = gg.new_context(
|
2020-12-27 13:19:32 +03:00
|
|
|
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
|
2020-12-31 18:46:37 +03:00
|
|
|
event_fn: on_event
|
2020-12-27 13:19:32 +03:00
|
|
|
user_data: app
|
|
|
|
init_fn: init_images
|
2021-02-07 13:40:12 +03:00
|
|
|
font_path: font_path
|
2020-12-27 14:02:01 +03:00
|
|
|
)
|
2020-12-27 13:19:32 +03:00
|
|
|
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()
|
2020-12-31 18:46:37 +03:00
|
|
|
time.sleep_ms(app.timer_period_ms)
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init_images(mut app App) {
|
2021-02-07 13:40:12 +03:00
|
|
|
$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'))
|
|
|
|
}
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
2020-12-27 14:02:01 +03:00
|
|
|
app.gg.draw_image(f32(background_x), 0, app.background.width, app.background.height,
|
|
|
|
app.background)
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
for i, pipe in app.pipes {
|
|
|
|
if i % 2 == 0 {
|
2020-12-27 14:02:01 +03:00
|
|
|
app.gg.draw_image(f32(pipe.x), f32(pipe.y + pipe.height - app.pipetop.height),
|
|
|
|
app.pipetop.width, app.pipetop.height, app.pipetop)
|
2020-12-27 13:19:32 +03:00
|
|
|
} else {
|
2020-12-27 14:02:01 +03:00
|
|
|
app.gg.draw_image(f32(pipe.x), f32(pipe.y), app.pipebottom.width, app.pipebottom.height,
|
|
|
|
app.pipebottom)
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for bird in app.birds {
|
|
|
|
if bird.alive {
|
2020-12-27 14:02:01 +03:00
|
|
|
app.gg.draw_image(f32(bird.x), f32(bird.y), app.bird.width, app.bird.height,
|
|
|
|
app.bird)
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
}
|
2020-12-27 14:02:01 +03:00
|
|
|
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')
|
2020-12-27 13:19:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
fn (app &App) draw() {
|
|
|
|
app.display()
|
|
|
|
}
|
2020-12-31 18:46:37 +03:00
|
|
|
|
|
|
|
fn on_event(e &sapp.Event, mut app App) {
|
|
|
|
if e.typ == .key_down {
|
|
|
|
app.key_down(e.key_code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn (mut app App) key_down(key sapp.KeyCode) {
|
|
|
|
// global keys
|
|
|
|
match key {
|
|
|
|
.escape {
|
|
|
|
exit(0)
|
|
|
|
}
|
2020-12-31 18:51:52 +03:00
|
|
|
._0 {
|
|
|
|
app.timer_period_ms = 0
|
|
|
|
}
|
2020-12-31 18:46:37 +03:00
|
|
|
.space {
|
|
|
|
if app.timer_period_ms == 24 {
|
2020-12-31 18:51:52 +03:00
|
|
|
app.timer_period_ms = 4
|
2020-12-31 18:46:37 +03:00
|
|
|
} else {
|
|
|
|
app.timer_period_ms = 24
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {}
|
|
|
|
}
|
|
|
|
}
|