From e69e5c5b91ffbb7126d6c79be559ecb521e5f6b3 Mon Sep 17 00:00:00 2001 From: uxnow Date: Sun, 27 Dec 2020 18:19:32 +0800 Subject: [PATCH] examples: add flappylearning to examples (#7605) --- examples/flappylearning/.gitignore | 1 + examples/flappylearning/LICENSE | 21 ++ examples/flappylearning/README.md | 16 + examples/flappylearning/game.v | 275 +++++++++++++++ examples/flappylearning/img/background.png | Bin 0 -> 3175 bytes examples/flappylearning/img/bird.png | Bin 0 -> 382 bytes examples/flappylearning/img/flappy.png | Bin 0 -> 17036 bytes examples/flappylearning/img/pipebottom.png | Bin 0 -> 1241 bytes examples/flappylearning/img/pipetop.png | Bin 0 -> 1191 bytes .../modules/neuroevolution/neuronevolution.v | 329 ++++++++++++++++++ 10 files changed, 642 insertions(+) create mode 100644 examples/flappylearning/.gitignore create mode 100644 examples/flappylearning/LICENSE create mode 100644 examples/flappylearning/README.md create mode 100644 examples/flappylearning/game.v create mode 100644 examples/flappylearning/img/background.png create mode 100644 examples/flappylearning/img/bird.png create mode 100644 examples/flappylearning/img/flappy.png create mode 100644 examples/flappylearning/img/pipebottom.png create mode 100644 examples/flappylearning/img/pipetop.png create mode 100644 examples/flappylearning/modules/neuroevolution/neuronevolution.v diff --git a/examples/flappylearning/.gitignore b/examples/flappylearning/.gitignore new file mode 100644 index 0000000000..dc22e61c94 --- /dev/null +++ b/examples/flappylearning/.gitignore @@ -0,0 +1 @@ +game diff --git a/examples/flappylearning/LICENSE b/examples/flappylearning/LICENSE new file mode 100644 index 0000000000..87cfb6f0d1 --- /dev/null +++ b/examples/flappylearning/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 uxnow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/flappylearning/README.md b/examples/flappylearning/README.md new file mode 100644 index 0000000000..57e288910f --- /dev/null +++ b/examples/flappylearning/README.md @@ -0,0 +1,16 @@ +# flappylearning-v +flappy learning implemented by vlang + +## get started + +```sh +v run game.v +``` + +![flappy.png](img/flappy.png) + +## thanks +https://github.com/xviniette/FlappyLearning + +## license +MIT diff --git a/examples/flappylearning/game.v b/examples/flappylearning/game.v new file mode 100644 index 0000000000..65c10625de --- /dev/null +++ b/examples/flappylearning/game.v @@ -0,0 +1,275 @@ + +module main + +import gg +import gx +import os +import time +import math +import rand + +import neuroevolution + +const ( + win_width = 500 + win_height = 512 + timer_period = 24 // ms +) + +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 +} + +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 mut j, 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 + } + 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 + user_data: app + init_fn: init_images + font_path: os.resource_abs_path('../assets/fonts/RobotoMono-Regular.ttf') + }) + 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.sleep_ms(timer_period) + } +} + +fn init_images(mut app App) { + app.background = app.gg.create_image(os.resource_abs_path('./img/background.png')) + app.bird = app.gg.create_image(os.resource_abs_path('./img/bird.png')) + app.pipetop = app.gg.create_image(os.resource_abs_path('./img/pipetop.png')) + app.pipebottom = app.gg.create_image(os.resource_abs_path('./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() +} diff --git a/examples/flappylearning/img/background.png b/examples/flappylearning/img/background.png new file mode 100644 index 0000000000000000000000000000000000000000..22b307092c207584a424fc9f7d851d9d6af56e4e GIT binary patch literal 3175 zcmeH}dsNbA7{`AWE8AY(gcfA;78+3`8&JnYyg=`)Wz&o*_IE7gyOnqH2z{P|XHFu$-}nU8q~{@)HHJ-iCq zI$Urp@|!dO__*os1GX}Ri8hF0hVN&_GLA4=hfFSNuUEz<-#}k98aENT z1pu7ec7|d1exG3I|1m?oc}duwn)Jg~>vH9iaJMYGLBcpStujCyoD@^HIQVG?NH9(V*$g_J&%zmL)5nzG%iTLYnm*Y;U~NJ2+VlRk~!)=vS5)Yie1P2f8LNj^a2OEdGU>op(o4#b|lE z`!{|!Bq)ZDIub0Ok*^o~#$o3Cc#p`~?7xC)Y=P$7Qr89Jb0gHYV+S}I=GqSJR zgPfi&{_fuw4dKzRJ15!G_b&t-kKjrqn=15uU*2zCsUc$C;#jHfa;-@>R65Hrv1U0f z7_0dil?fY)o1y^kLcRkbWr2v9*k00*Q#UvZE8g>m!H~ZWN7)Mxib<6XbO^`RfLwds z$!nC!CBXnjf*TwzBPS&T0z#!Q1Nvyvf~u2y9yLgU#Pln z#PD8R47OAh=Vt1BGY)Vo;nH8)*svM}AoSOWI4$@FKR>|=!Im%TUOWhtRw;*>cJfQ) zJ2x!jRdW}Ezu>>>tVD0C$CDGvemAT0ZWeW$6)2HSdj``@G@Ux4Td$tuKg`@vz^^d* zwa;wPTbWU0`aV+{i{zf5a|(J$3fW?&zh@~%hc4Rgu-@fH3~+-o<%K&jLkpAmz4 z_d2>|rfLn0usv?m4vPoxSXp$wDE)o5)ueDE^ANgBYo1ZuSTr^~_mf5& z?w?jg7uH`!Cf{0L#b^%}(HU9PgGPfYG$+D49<-O!*y}k&qNXpu=G!-;nl_=z?GLtA znifFso$D&r_xde=?vv9w(M6**Wo39rao)^^Z974Uq^8u|5LcR!rm{(Ru&A*AQ;O#! zVgSF7xb6LRP)^)UD=r|J_=dEnGv8#*o}DvChty8ja+%qn9`rfi8v+_wFP$dl%GUvX z%UYR2-I$at7?|e0iprTt4|*`@r%cuTD#?8-0BQVPCr#_G;~KTx(#UwPsRbK_hn;r- jNuNBP-*q3}bru)4+t--L)pyB%fBANXM~8_+51#%DNn{1`ISV`@iy0XB4ude`@%$AjKtZVz*NBqf{Irtt#G+J&fW*wa5h zJyShHL-)^4=0H_%JzX3_JiM)yUF3!lt_)+21i0yUUqfzn#?$Z;p9cp zcqg{g_Z^a0PqMx!w%NCz--}aj`a7M8TzcnED+~CZUjIhpYjgbjxNBUy3Z+uwcI=bZ zkY!=`^;UlCi9I^%e8p+*Enj61V2;(j>Td>(^)9s1-nH!y> z=N1|tvif8!@a5hu%db-^f}gb38`|D_Wg<|+yyl+!T#0q+ol{=-m@bR+Rub^d&MOGZ zpTAuxo7I-#Mo!MX#LEtA*f%@Gb!WI=a{MCFIEOXa*y;bqm#mi^Og=u$_ljQBa-Z!d Z1J|R;Ez<7tM}Qt@@O1TaS?83{1OU^3k4OLj literal 0 HcmV?d00001 diff --git a/examples/flappylearning/img/flappy.png b/examples/flappylearning/img/flappy.png new file mode 100644 index 0000000000000000000000000000000000000000..c7085aa8ad0592108c2cac3cd9a6465c13064096 GIT binary patch literal 17036 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 BNs|Bo literal 0 HcmV?d00001 diff --git a/examples/flappylearning/img/pipebottom.png b/examples/flappylearning/img/pipebottom.png new file mode 100644 index 0000000000000000000000000000000000000000..99d27b2679d6fab3955868760586e40d2095398e GIT binary patch literal 1241 zcmeAS@N?(olHy`uVBq!ia0vp^8Vn3f3><7g)`xj|@<58QILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFKt5-IM`SSr1K(i~W;~w1A_XWYRpJ^^5}cn_Ql40p$`Fv4nOCCc=Nh6= zW~^tbXK3jD*~uJel82{@V@O5Z+q>T0QxZkkKm249{K3c;wWAfEj&YlU6PfRrU!OHK~ z{P|PGhuZW1|2#{2>G8gC%fxWa{C6(#`MV4*&2(G-eaWk{%`gAWZ7hxK?l(8vyWXO1 zb?M8f@{RrflIO0>ug?$Mk<|S2+^ZPPy+3x@p8abvXWxIR_~~1JRIK0kR;TZN*4dgD z8s7>x1$q5FZY^#$qkh$&1A8|1+n@h$dE&8u>`~^d`P;>`@5jA2wFvfNzIv>%UEahj zy*Yd3Ca!f$qw0UhiH01kSy(2UQ`lHO>CfcJPu>KS?}~cuu|2+Q>B{HPUAHTHZEl8q zIj-$*E)FuLv})D8YIW)3w#CVrTT*vu+Us{SE9@%~D{p}a8gI4W{|8e*9_p|vMHd;!@JXBiS zbdh@t*Q$iiQZI}i8yUaXjJmt$vu)GvC1sgJW-r>*A^6l-Bkbc_&yQPcJ` zpBA>9cjBWGLf9c;|t<$_j5B^=pwr^u5Jz2ss%HT_#~+r zPM+a;MkP(ixO1k;Bu>>=9$(L>*iBja@!{60jd5$lUmu;Aw@BQ5)p8)Ks!)3UF=b^mU^R?VXW# zQFoW?_7`Gz7e$wJ=edHNAqV2D?p3^(dMx!AR5$NCw({oP2k$hS$XyQiUnMs9PLi9|5T8F(p)ov|IEVr{OAYSW- za1b(`QHv02z`_%KGy>gM9Y@tIxz$A%P3tR&Ba`7aKn#sqG!0E_sfw`Im5HwmL9Rep z?4}#&ji%KK5^WO5&IW3jwn7z6t1Ki$fnNLX@Ag-E@6Bm?5?+r%g%^XTtDnm{r-UW| D4<|^f literal 0 HcmV?d00001 diff --git a/examples/flappylearning/img/pipetop.png b/examples/flappylearning/img/pipetop.png new file mode 100644 index 0000000000000000000000000000000000000000..1fcd52b3c4d210188e85e6e96bbd8f7ea6def600 GIT binary patch literal 1191 zcmeAS@N?(olHy`uVBq!ia0vp^8Vn3f3><7g)`xj|@<58QILO_JVcj{Imp~3nx}&cn z1H;CC?mvmFKt5-IM`SSr1K(i~W;~w1A_XWYRpJ^^5}cn_Ql40p$`Fv4nOCCc=Nh6= zW~^tbXK3jD*~uJel9;E9V@O5Z+dJOgQvxN}KYW*VdB84_@$gXZx>Ji@Y|>cKs&s>O zleU`M>K+qbH%Da+on1RJ5)He%mbh-^{=>93YIlL)(_^WpAAFf->>FnwYaygp$IQQH z@wtMEFMHnq|9Q5@HSpdpYyUes+I#-)vp=I>-nuk?apJq<2TS$`Zavy(U>M(Wvvu2t zU$^g-%n44O-&L?Ls^#H#*A;c^?bA z&ASiYeUMjRzC-#B?>n~g=4wB+DJzP1r9(yTb-v3zWta2dvOsj*HJ3X=+Zogju*_f( zPGItB;51-NgQ#XVU_=)F>#f)swJ7aM&#XY>EVr{OBuy8mU9p@Qu;|=NIUox~SZJ#Y zQ~^x5*l3~4+{?BweK29wtWIR(fWlD!z!ZRO0dl;r2!ZTF5v=ypw z*FjXzoy@8FYR)@DG+|w!nMlR~&9+h(_PSzuHyKShYSA=^XW^<#Apvw8O?az|E>h5d zRKJ?zqY?PLq#sQ!~4nVRb8e)!~2y(2c8lmGtK)|2MdmkQS`oWA?egUw2JsIwC%q7T^zN-Ur(pSq&(i9 z(Rj8jntk@$EBovG*h>xOt(ePhF8L^T(*I4K>v*>=%TC-QcV54IY4)eGE3cl= 2 + input := network[0] + hiddens := network.slice(1, network.len - 1) + output := network[network.len - 1] + + mut index := 0 + mut previous_neurons := 0 + mut input_layer := Layer{ + id: index + } + input_layer.populate(input, previous_neurons) + n.layers << input_layer + + previous_neurons = input + index++ + for hidden in hiddens { + mut hidden_layer := Layer{ + id: index + } + hidden_layer.populate(hidden, previous_neurons) + previous_neurons = hidden + n.layers << hidden_layer + index++ + } + + mut output_layer := Layer{ + id: index + } + output_layer.populate(output, previous_neurons) + n.layers << output_layer +} + +fn (n Network) get_save() Save { + + mut save := Save{} + for layer in n.layers { + save.neurons << layer.neurons.len + for neuron in layer.neurons { + for weight in neuron.weights { + save.weights << weight + } + } + } + return save +} + +fn (mut n Network) set_save(save Save) { + + mut previous_neurons := 0 + mut index := 0 + mut index_weights := 0 + + n.layers = [] + for save_neuron in save.neurons { + mut layer := Layer{ + id: index + } + layer.populate(save_neuron, previous_neurons) + for mut neuron in layer.neurons { + for i in 0 .. neuron.weights.len { + neuron.weights[i] = save.weights[index_weights] + index_weights++ + } + } + previous_neurons = save_neuron + index++ + n.layers << layer + } +} + +pub fn (mut n Network) compute(inputs []f64) []f64 { + assert n.layers.len > 0 + assert inputs.len == n.layers[0].neurons.len + + for i, input in inputs { + n.layers[0].neurons[i].value = input + } + + mut prev_layer := n.layers[0] + + for i in 1 .. n.layers.len { + for j, neuron in n.layers[i].neurons { + mut sum := f64(0) + for k, prev_layer_neuron in prev_layer.neurons { + sum += prev_layer_neuron.value * neuron.weights[k] + } + n.layers[i].neurons[j].value = activation(sum) + } + prev_layer = n.layers[i] + } + + mut outputs := []f64{} + mut last_layer := n.layers[n.layers.len - 1] + for neuron in last_layer.neurons { + outputs << neuron.value + } + + return outputs +} + +struct Save { +mut: + neurons []int + weights []f64 +} + +fn (s Save) clone() Save { + mut save := Save{} + save.neurons << s.neurons + save.weights << s.weights + return save +} + +struct Genome { + score int + network Save +} + +struct Generation { +mut: + genomes []Genome +} + +fn (mut g Generation) add_genome(genome Genome) { + + mut i := 0 + + for gg in g.genomes { + if genome.score > gg.score { + break + } + + i++ + } + + g.genomes.insert(i, genome) +} + +fn (g1 Genome) breed(g2 Genome, nb_child int) []Save { + mut datas := []Save{} + + for _ in 0 .. nb_child { + + mut data := g1.network.clone() + + for i, weight in g2.network.weights { + if rand.f64() <= 0.5 { + data.weights[i] = weight + } + } + + for i, _ in data.weights { + if rand.f64() <= 0.1 { + data.weights[i] += (rand.f64() * 2 - 1) * 0.5 + } + } + + datas << data + } + + return datas +} + +fn (g Generation) next(population int) []Save { + + mut nexts := []Save{} + + if population == 0 { + return nexts + } + + keep := round(population, 0.2) + + for i in 0 .. keep { + if nexts.len < population { + nexts << g.genomes[i].network.clone() + } + } + + random := round(population, 0.2) + + for _ in 0 .. random { + + if nexts.len < population { + mut n := g.genomes[0].network.clone() + for k, _ in n.weights { + n.weights[k] = random_clamped() + } + nexts << n + } + } + + mut max := 0 + out: for { + for i in 0 .. max { + mut childs := g.genomes[i].breed(g.genomes[max], 1) + for c in childs { + nexts << c + if nexts.len >= population { + break out + } + } + } + max++ + if max >= g.genomes.len - 1 { + max = 0 + } + } + + return nexts +} + +pub struct Generations { +pub: + population int + network []int +mut: + generations []Generation +} + +fn (mut gs Generations) first() []Save { + mut out := []Save{} + for _ in 0 .. gs.population { + mut nn := Network{} + nn.populate(gs.network) + out << nn.get_save() + } + + gs.generations << Generation{} + return out +} + +fn (mut gs Generations) next() []Save { + assert gs.generations.len > 0 + gen := gs.generations[gs.generations.len - 1].next(gs.population) + gs.generations << Generation{} + return gen +} + +fn (mut gs Generations) add_genome(genome Genome) { + assert gs.generations.len > 0 + gs.generations[gs.generations.len - 1].add_genome(genome) +} + +fn (mut gs Generations) restart() { + gs.generations = [] +} + +pub fn (mut gs Generations) generate() []Network { + + saves := if gs.generations.len == 0 { + gs.first() + } else { + gs.next() + } + + mut nns := []Network{} + for save in saves { + mut nn := Network{} + nn.set_save(save) + nns << nn + } + + if gs.generations.len >= 2 { + gs.generations.delete(0) + } + + return nns +} + +pub fn (mut gs Generations) network_score(network Network, score int) { + gs.add_genome(Genome{ + score: score + network: network.get_save() + }) +} +