1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

examples: add flappylearning to examples (#7605)

This commit is contained in:
uxnow 2020-12-27 18:19:32 +08:00 committed by GitHub
parent d563261e58
commit e69e5c5b91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 642 additions and 0 deletions

1
examples/flappylearning/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
game

View File

@ -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.

View File

@ -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

View File

@ -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()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,329 @@
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.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()
})
}