v / examples / flappylearning / game.v
278 lines · 256 sloc · 6.09 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module main
2
3import gg
4import time
5import math
6import rand
7import neuroevolution
8import os.asset
9
10const win_width = 500
11const win_height = 512
12
13const bg_color = gg.Color{0x96, 0xE2, 0x82, 0xFF}
14
15struct Bird {
16mut:
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
27fn (mut b Bird) flap() {
28 b.gravity = b.jump
29}
30
31fn (mut b Bird) update() {
32 b.gravity += b.velocity
33 b.y += b.gravity
34}
35
36fn (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
49struct Pipe {
50mut:
51 x f64 = 80
52 y f64 = 250
53 width f64 = 40
54 height f64 = 30
55 speed f64 = 3
56}
57
58fn (mut p Pipe) update() {
59 p.x -= p.speed
60}
61
62fn (p Pipe) is_out() bool {
63 return p.x + p.width < 0
64}
65
66struct App {
67mut:
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
90fn (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
103fn (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
112fn (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
175fn 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
198fn (mut app App) run() {
199 for {
200 app.update()
201 time.sleep(app.timer_period_ms * time.millisecond)
202 }
203}
204
205fn (mut app App) init_images_wrapper() {
206 app.init_images() or { panic(err) }
207}
208
209fn (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
219fn frame(app &App) {
220 app.gg.begin()
221 app.display()
222 app.gg.end()
223}
224
225fn (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
254fn on_event(e &gg.Event, mut app App) {
255 if e.typ == .key_down {
256 app.key_down(e.key_code)
257 }
258}
259
260fn (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