| 1 | module main |
| 2 | |
| 3 | import gg |
| 4 | import time |
| 5 | import math |
| 6 | import rand |
| 7 | import neuroevolution |
| 8 | import os.asset |
| 9 | |
| 10 | const win_width = 500 |
| 11 | const win_height = 512 |
| 12 | |
| 13 | const bg_color = gg.Color{0x96, 0xE2, 0x82, 0xFF} |
| 14 | |
| 15 | struct Bird { |
| 16 | mut: |
| 17 | x f64 = 80 |
| 18 | y f64 = 250 |
| 19 | width f64 = 40 |
| 20 | height f64 = 30 |
| 21 | alive bool = true |
| 22 | gravity f64 |
| 23 | velocity f64 = 0.3 |
| 24 | jump f64 = -6 |
| 25 | } |
| 26 | |
| 27 | fn (mut b Bird) flap() { |
| 28 | b.gravity = b.jump |
| 29 | } |
| 30 | |
| 31 | fn (mut b Bird) update() { |
| 32 | b.gravity += b.velocity |
| 33 | b.y += b.gravity |
| 34 | } |
| 35 | |
| 36 | fn (b Bird) is_dead(height f64, pipes []Pipe) bool { |
| 37 | if b.y >= height || b.y + b.height <= 0 { |
| 38 | return true |
| 39 | } |
| 40 | for pipe in pipes { |
| 41 | if !(b.x > pipe.x + pipe.width || b.x + b.width < pipe.x || b.y > pipe.y + pipe.height |
| 42 | || b.y + b.height < pipe.y) { |
| 43 | return true |
| 44 | } |
| 45 | } |
| 46 | return false |
| 47 | } |
| 48 | |
| 49 | struct Pipe { |
| 50 | mut: |
| 51 | x f64 = 80 |
| 52 | y f64 = 250 |
| 53 | width f64 = 40 |
| 54 | height f64 = 30 |
| 55 | speed f64 = 3 |
| 56 | } |
| 57 | |
| 58 | fn (mut p Pipe) update() { |
| 59 | p.x -= p.speed |
| 60 | } |
| 61 | |
| 62 | fn (p Pipe) is_out() bool { |
| 63 | return p.x + p.width < 0 |
| 64 | } |
| 65 | |
| 66 | struct App { |
| 67 | mut: |
| 68 | gg &gg.Context = unsafe { nil } |
| 69 | background gg.Image |
| 70 | bird gg.Image |
| 71 | pipetop gg.Image |
| 72 | pipebottom gg.Image |
| 73 | pipes []Pipe |
| 74 | birds []Bird |
| 75 | score int |
| 76 | max_score int |
| 77 | width f64 = win_width |
| 78 | height f64 = win_height |
| 79 | spawn_interval f64 = 90 |
| 80 | interval f64 |
| 81 | nv neuroevolution.Generations |
| 82 | gen []neuroevolution.Network |
| 83 | alives int |
| 84 | generation int |
| 85 | background_speed f64 = 0.5 |
| 86 | background_x f64 |
| 87 | timer_period_ms int = 24 |
| 88 | } |
| 89 | |
| 90 | fn (mut app App) start() { |
| 91 | app.interval = 0 |
| 92 | app.score = 0 |
| 93 | app.pipes = [] |
| 94 | app.birds = [] |
| 95 | app.gen = app.nv.generate() |
| 96 | for _ in 0 .. app.gen.len { |
| 97 | app.birds << Bird{} |
| 98 | } |
| 99 | app.generation++ |
| 100 | app.alives = app.birds.len |
| 101 | } |
| 102 | |
| 103 | fn (app &App) is_it_end() bool { |
| 104 | for i in 0 .. app.birds.len { |
| 105 | if app.birds[i].alive { |
| 106 | return false |
| 107 | } |
| 108 | } |
| 109 | return true |
| 110 | } |
| 111 | |
| 112 | fn (mut app App) update() { |
| 113 | app.background_x += app.background_speed |
| 114 | mut next_holl := f64(0) |
| 115 | if app.birds.len > 0 { |
| 116 | for i := 0; i < app.pipes.len; i += 2 { |
| 117 | if app.pipes[i].x + app.pipes[i].width > app.birds[0].x { |
| 118 | next_holl = app.pipes[i].height / app.height |
| 119 | break |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | for j, mut bird in app.birds { |
| 124 | if bird.alive { |
| 125 | inputs := [ |
| 126 | bird.y / app.height, |
| 127 | next_holl, |
| 128 | ] |
| 129 | res := app.gen[j].compute(inputs) |
| 130 | if res[0] > 0.5 { |
| 131 | bird.flap() |
| 132 | } |
| 133 | bird.update() |
| 134 | if bird.is_dead(app.height, app.pipes) { |
| 135 | bird.alive = false |
| 136 | app.alives-- |
| 137 | app.nv.network_score(app.gen[j], app.score) |
| 138 | if app.is_it_end() { |
| 139 | app.start() |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | for k := 0; k < app.pipes.len; k++ { |
| 145 | app.pipes[k].update() |
| 146 | if app.pipes[k].is_out() { |
| 147 | app.pipes.delete(k) |
| 148 | k-- |
| 149 | } |
| 150 | } |
| 151 | if app.interval == 0 { |
| 152 | delta_bord := f64(50) |
| 153 | pipe_holl := f64(120) |
| 154 | holl_position := math.round(rand.f64() * (app.height - delta_bord * 2.0 - pipe_holl)) + |
| 155 | delta_bord |
| 156 | app.pipes << Pipe{ |
| 157 | x: app.width |
| 158 | y: 0 |
| 159 | height: holl_position |
| 160 | } |
| 161 | app.pipes << Pipe{ |
| 162 | x: app.width |
| 163 | y: holl_position + pipe_holl |
| 164 | height: app.height |
| 165 | } |
| 166 | } |
| 167 | app.interval++ |
| 168 | if app.interval == app.spawn_interval { |
| 169 | app.interval = 0 |
| 170 | } |
| 171 | app.score++ |
| 172 | app.max_score = if app.score > app.max_score { app.score } else { app.max_score } |
| 173 | } |
| 174 | |
| 175 | fn main() { |
| 176 | mut app := &App{} |
| 177 | app.gg = gg.new_context( |
| 178 | bg_color: bg_color |
| 179 | width: win_width |
| 180 | height: win_height |
| 181 | create_window: true |
| 182 | window_title: 'flappylearning-v' |
| 183 | frame_fn: frame |
| 184 | event_fn: on_event |
| 185 | user_data: app |
| 186 | init_fn: app.init_images_wrapper |
| 187 | font_path: asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf') |
| 188 | ) |
| 189 | app.nv = neuroevolution.Generations{ |
| 190 | population: 50 |
| 191 | network: [2, 2, 1] |
| 192 | } |
| 193 | app.start() |
| 194 | spawn app.run() |
| 195 | app.gg.run() |
| 196 | } |
| 197 | |
| 198 | fn (mut app App) run() { |
| 199 | for { |
| 200 | app.update() |
| 201 | time.sleep(app.timer_period_ms * time.millisecond) |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | fn (mut app App) init_images_wrapper() { |
| 206 | app.init_images() or { panic(err) } |
| 207 | } |
| 208 | |
| 209 | fn (mut app App) init_images() ! { |
| 210 | app.bird = app.gg.create_image_from_byte_array(asset.read_bytes('assets', 'img/bird.png')!)! |
| 211 | app.pipetop = |
| 212 | app.gg.create_image_from_byte_array(asset.read_bytes('assets', 'img/pipetop.png')!)! |
| 213 | app.pipebottom = app.gg.create_image_from_byte_array(asset.read_bytes('assets', |
| 214 | 'img/pipebottom.png')!)! |
| 215 | app.background = app.gg.create_image_from_byte_array(asset.read_bytes('assets', |
| 216 | 'img/background.png')!)! |
| 217 | } |
| 218 | |
| 219 | fn frame(app &App) { |
| 220 | app.gg.begin() |
| 221 | app.display() |
| 222 | app.gg.end() |
| 223 | } |
| 224 | |
| 225 | fn (app &App) display() { |
| 226 | for i := 0; i < int(math.ceil(app.width / app.background.width) + 1.0); i++ { |
| 227 | background_x := i * app.background.width - math.floor(int(app.background_x) % int(app.background.width)) |
| 228 | app.gg.draw_image(f32(background_x), 0, app.background.width, app.background.height, |
| 229 | app.background) |
| 230 | } |
| 231 | for i, pipe in app.pipes { |
| 232 | if i % 2 == 0 { |
| 233 | app.gg.draw_image(f32(pipe.x), f32(pipe.y + pipe.height - app.pipetop.height), |
| 234 | app.pipetop.width, app.pipetop.height, app.pipetop) |
| 235 | } else { |
| 236 | app.gg.draw_image(f32(pipe.x), f32(pipe.y), app.pipebottom.width, |
| 237 | app.pipebottom.height, app.pipebottom) |
| 238 | } |
| 239 | } |
| 240 | for bird in app.birds { |
| 241 | if bird.alive { |
| 242 | app.gg.draw_image(f32(bird.x), f32(bird.y), app.bird.width, app.bird.height, app.bird) |
| 243 | } |
| 244 | } |
| 245 | app.gg.draw_rect_filled(0, 510, app.background.width * 3, 5, gg.Color{0x21, 0x19, 0x28, 255}) |
| 246 | app.gg.draw_rect_filled(0, 513, app.background.width * 3, app.background.height, bg_color) |
| 247 | app.gg.draw_rect_filled(550, 0, app.background.width + 50, app.background.height + 20, bg_color) |
| 248 | app.gg.draw_text_def(10, 25, 'Score: ${app.score}') |
| 249 | app.gg.draw_text_def(10, 50, 'Max Score: ${app.max_score}') |
| 250 | app.gg.draw_text_def(10, 75, 'Generation: ${app.generation}') |
| 251 | app.gg.draw_text_def(10, 100, 'Alive: ${app.alives} / ${app.nv.population}') |
| 252 | } |
| 253 | |
| 254 | fn on_event(e &gg.Event, mut app App) { |
| 255 | if e.typ == .key_down { |
| 256 | app.key_down(e.key_code) |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | fn (mut app App) key_down(key gg.KeyCode) { |
| 261 | // global keys |
| 262 | match key { |
| 263 | .escape { |
| 264 | app.gg.quit() |
| 265 | } |
| 266 | ._0 { |
| 267 | app.timer_period_ms = 0 |
| 268 | } |
| 269 | .space { |
| 270 | if app.timer_period_ms == 24 { |
| 271 | app.timer_period_ms = 4 |
| 272 | } else { |
| 273 | app.timer_period_ms = 24 |
| 274 | } |
| 275 | } |
| 276 | else {} |
| 277 | } |
| 278 | } |
| 279 | |