diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/examples/flappylearning | |
download | cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.gz cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.tar.bz2 cli-tools-windows-f5c4671bfbad96bf346bd7e9a21fc4317b4959df.zip |
Diffstat (limited to 'v_windows/v/examples/flappylearning')
-rw-r--r-- | v_windows/v/examples/flappylearning/.gitignore | 1 | ||||
-rw-r--r-- | v_windows/v/examples/flappylearning/LICENSE | 21 | ||||
-rw-r--r-- | v_windows/v/examples/flappylearning/README.md | 16 | ||||
-rw-r--r-- | v_windows/v/examples/flappylearning/assets/img/background.png | bin | 0 -> 3175 bytes | |||
-rw-r--r-- | v_windows/v/examples/flappylearning/assets/img/bird.png | bin | 0 -> 382 bytes | |||
-rw-r--r-- | v_windows/v/examples/flappylearning/assets/img/flappy.png | bin | 0 -> 17036 bytes | |||
-rw-r--r-- | v_windows/v/examples/flappylearning/assets/img/pipebottom.png | bin | 0 -> 1241 bytes | |||
-rw-r--r-- | v_windows/v/examples/flappylearning/assets/img/pipetop.png | bin | 0 -> 1191 bytes | |||
-rw-r--r-- | v_windows/v/examples/flappylearning/game.v | 291 | ||||
-rw-r--r-- | v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v | 287 |
10 files changed, 616 insertions, 0 deletions
diff --git a/v_windows/v/examples/flappylearning/.gitignore b/v_windows/v/examples/flappylearning/.gitignore new file mode 100644 index 0000000..dc22e61 --- /dev/null +++ b/v_windows/v/examples/flappylearning/.gitignore @@ -0,0 +1 @@ +game diff --git a/v_windows/v/examples/flappylearning/LICENSE b/v_windows/v/examples/flappylearning/LICENSE new file mode 100644 index 0000000..87cfb6f --- /dev/null +++ b/v_windows/v/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/v_windows/v/examples/flappylearning/README.md b/v_windows/v/examples/flappylearning/README.md new file mode 100644 index 0000000..57e2889 --- /dev/null +++ b/v_windows/v/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/v_windows/v/examples/flappylearning/assets/img/background.png b/v_windows/v/examples/flappylearning/assets/img/background.png Binary files differnew file mode 100644 index 0000000..22b3070 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/background.png diff --git a/v_windows/v/examples/flappylearning/assets/img/bird.png b/v_windows/v/examples/flappylearning/assets/img/bird.png Binary files differnew file mode 100644 index 0000000..ca3a159 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/bird.png diff --git a/v_windows/v/examples/flappylearning/assets/img/flappy.png b/v_windows/v/examples/flappylearning/assets/img/flappy.png Binary files differnew file mode 100644 index 0000000..c7085aa --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/flappy.png diff --git a/v_windows/v/examples/flappylearning/assets/img/pipebottom.png b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png Binary files differnew file mode 100644 index 0000000..99d27b2 --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png diff --git a/v_windows/v/examples/flappylearning/assets/img/pipetop.png b/v_windows/v/examples/flappylearning/assets/img/pipetop.png Binary files differnew file mode 100644 index 0000000..1fcd52b --- /dev/null +++ b/v_windows/v/examples/flappylearning/assets/img/pipetop.png diff --git a/v_windows/v/examples/flappylearning/game.v b/v_windows/v/examples/flappylearning/game.v new file mode 100644 index 0000000..15cdc28 --- /dev/null +++ b/v_windows/v/examples/flappylearning/game.v @@ -0,0 +1,291 @@ +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 + 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.sleep(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 {} + } +} diff --git a/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v b/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v new file mode 100644 index 0000000..f3839ee --- /dev/null +++ b/v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v @@ -0,0 +1,287 @@ +module neuroevolution + +import rand +import math + +fn random_clamped() f64 { + return rand.f64() * 2 - 1 +} + +pub fn activation(a f64) f64 { + ap := (-a) / 1 + return (1 / (1 + math.exp(ap))) +} + +fn round(a int, b f64) int { + return int(math.round(f64(a) * b)) +} + +struct Neuron { +mut: + value f64 + weights []f64 +} + +fn (mut n Neuron) populate(nb int) { + for _ in 0 .. nb { + n.weights << random_clamped() + } +} + +struct Layer { + id int +mut: + neurons []Neuron +} + +fn (mut l Layer) populate(nb_neurons int, nb_inputs int) { + for _ in 0 .. nb_neurons { + mut n := Neuron{} + n.populate(nb_inputs) + l.neurons << n + } +} + +struct Network { +mut: + layers []Layer +} + +fn (mut n Network) populate(network []int) { + assert network.len >= 2 + input := network[0] + hiddens := network[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() + }) +} |