aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/examples/flappylearning
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/examples/flappylearning')
-rw-r--r--v_windows/v/examples/flappylearning/.gitignore1
-rw-r--r--v_windows/v/examples/flappylearning/LICENSE21
-rw-r--r--v_windows/v/examples/flappylearning/README.md16
-rw-r--r--v_windows/v/examples/flappylearning/assets/img/background.pngbin0 -> 3175 bytes
-rw-r--r--v_windows/v/examples/flappylearning/assets/img/bird.pngbin0 -> 382 bytes
-rw-r--r--v_windows/v/examples/flappylearning/assets/img/flappy.pngbin0 -> 17036 bytes
-rw-r--r--v_windows/v/examples/flappylearning/assets/img/pipebottom.pngbin0 -> 1241 bytes
-rw-r--r--v_windows/v/examples/flappylearning/assets/img/pipetop.pngbin0 -> 1191 bytes
-rw-r--r--v_windows/v/examples/flappylearning/game.v291
-rw-r--r--v_windows/v/examples/flappylearning/modules/neuroevolution/neuronevolution.v287
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
new file mode 100644
index 0000000..22b3070
--- /dev/null
+++ b/v_windows/v/examples/flappylearning/assets/img/background.png
Binary files differ
diff --git a/v_windows/v/examples/flappylearning/assets/img/bird.png b/v_windows/v/examples/flappylearning/assets/img/bird.png
new file mode 100644
index 0000000..ca3a159
--- /dev/null
+++ b/v_windows/v/examples/flappylearning/assets/img/bird.png
Binary files differ
diff --git a/v_windows/v/examples/flappylearning/assets/img/flappy.png b/v_windows/v/examples/flappylearning/assets/img/flappy.png
new file mode 100644
index 0000000..c7085aa
--- /dev/null
+++ b/v_windows/v/examples/flappylearning/assets/img/flappy.png
Binary files differ
diff --git a/v_windows/v/examples/flappylearning/assets/img/pipebottom.png b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png
new file mode 100644
index 0000000..99d27b2
--- /dev/null
+++ b/v_windows/v/examples/flappylearning/assets/img/pipebottom.png
Binary files differ
diff --git a/v_windows/v/examples/flappylearning/assets/img/pipetop.png b/v_windows/v/examples/flappylearning/assets/img/pipetop.png
new file mode 100644
index 0000000..1fcd52b
--- /dev/null
+++ b/v_windows/v/examples/flappylearning/assets/img/pipetop.png
Binary files differ
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()
+ })
+}