mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Add new example: squirals (#952)
This commit is contained in:
parent
de7ae1aee8
commit
9082edf03f
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
*~
|
||||
*.aar
|
||||
*.apk
|
||||
.vscode
|
||||
|
351
examples/squiral/main.go
Normal file
351
examples/squiral/main.go
Normal file
@ -0,0 +1,351 @@
|
||||
// Copyright 2019 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build example jsgo
|
||||
|
||||
// This demo is inspired by the xscreensaver 'squirals'.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/inpututil"
|
||||
)
|
||||
|
||||
const (
|
||||
width = 800
|
||||
height = 600
|
||||
scale = 1
|
||||
numOfSquirals = width / 32
|
||||
)
|
||||
|
||||
type palette struct {
|
||||
name string
|
||||
colors []color.Color
|
||||
}
|
||||
|
||||
var (
|
||||
background = color.Black
|
||||
|
||||
palettes = []palette{
|
||||
{
|
||||
name: "sand dunes",
|
||||
colors: []color.Color{
|
||||
color.RGBA{0xF2, 0x74, 0x05, 0xFF}, // #F27405
|
||||
color.RGBA{0xD9, 0x52, 0x04, 0xFF}, // #D95204
|
||||
color.RGBA{0x40, 0x18, 0x01, 0xFF}, // #401801
|
||||
color.RGBA{0xA6, 0x2F, 0x03, 0xFF}, // #A62F03
|
||||
color.RGBA{0x73, 0x2A, 0x19, 0xFF}, // #732A19
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mono desert sand",
|
||||
colors: []color.Color{
|
||||
color.RGBA{0x7F, 0x6C, 0x52, 0xFF}, // #7F6C52
|
||||
color.RGBA{0xFF, 0xBA, 0x58, 0xFF}, // #FFBA58
|
||||
color.RGBA{0xFF, 0xD9, 0xA5, 0xFF}, // #FFD9A5
|
||||
color.RGBA{0x7F, 0x50, 0x0F, 0xFF}, // #7F500F
|
||||
color.RGBA{0xCC, 0xAE, 0x84, 0xFF}, // #CCAE84
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "land sea gradient",
|
||||
colors: []color.Color{
|
||||
color.RGBA{0x00, 0xA2, 0xE8, 0xFF}, // #00A2E8
|
||||
color.RGBA{0x67, 0xA3, 0xF5, 0xFF}, // #67A3F5
|
||||
color.RGBA{0xFF, 0xFF, 0xD5, 0xFF}, // #FFFFD5
|
||||
color.RGBA{0xDD, 0xE8, 0x0C, 0xFF}, // #DDE80C
|
||||
color.RGBA{0x74, 0x9A, 0x0D, 0xFF}, // #749A0D
|
||||
},
|
||||
},
|
||||
}
|
||||
selectedPalette = 0
|
||||
colorCycle = 0
|
||||
canvas *ebiten.Image
|
||||
auto *automaton
|
||||
// blocker is an arbitrary color used to prevent the
|
||||
// squirals from leaving the canvas.
|
||||
blocker = color.RGBA{0, 0, 0, 254}
|
||||
|
||||
// dirCycles defines by offset which direction a squiral
|
||||
// should try next for the two cases:
|
||||
// clockwise:
|
||||
// 1. try to turn right (index+1)
|
||||
// 2. try to go straight (index+0)
|
||||
// 3. try to turn left (index+3)
|
||||
// counter-clockwise:
|
||||
// 1. try to turn left (index+3)
|
||||
// 2. try to go straight (index+0)
|
||||
// 3. try to turn right (index+1)
|
||||
dirCycles = [2][3]int{
|
||||
[3]int{1, 0, 3}, // cw
|
||||
[3]int{3, 0, 1}, // ccw
|
||||
}
|
||||
|
||||
// dirs contains vectors for the directions: east, south, west, north
|
||||
// in the specified order.
|
||||
dirs = [4]vec2{vec2{1, 0}, vec2{0, 1}, vec2{-1, 0}, vec2{0, -1}}
|
||||
|
||||
// neighbors defines neighboring cells depending on the moving
|
||||
// direction of the squiral:
|
||||
// index of 0 -> squiral moves vertically,
|
||||
// index of 1 -> squiral moves horizontally.
|
||||
// These neighbors are tested for "collisions" during simulation.
|
||||
neighbors = [2][2]vec2{
|
||||
[2]vec2{vec2{0, 1}, vec2{0, -1}}, // east, west
|
||||
[2]vec2{vec2{1, 0}, vec2{-1, 0}}, // south, north
|
||||
}
|
||||
)
|
||||
|
||||
type vec2 struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
type squiral struct {
|
||||
speed int
|
||||
pos vec2
|
||||
dir int
|
||||
rot int
|
||||
col color.Color
|
||||
dead bool
|
||||
}
|
||||
|
||||
func (s *squiral) spawn() {
|
||||
s.dead = false
|
||||
|
||||
rx := rand.Intn(width-4) + 2
|
||||
ry := rand.Intn(height-4) + 2
|
||||
|
||||
for dx := -2; dx <= 2; dx++ {
|
||||
for dy := -2; dy <= 2; dy++ {
|
||||
tx, ty := rx+dx, ry+dy
|
||||
if auto.colorMap[tx][ty] != background {
|
||||
s.dead = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.speed = rand.Intn(5) + 1
|
||||
s.pos.x = rx
|
||||
s.pos.y = ry
|
||||
s.dir = rand.Intn(4)
|
||||
|
||||
colorCycle = (colorCycle + 1) % len(palettes[selectedPalette].colors)
|
||||
s.col = palettes[selectedPalette].colors[colorCycle]
|
||||
|
||||
s.rot = rand.Intn(2)
|
||||
}
|
||||
|
||||
func (s *squiral) step(debug int) {
|
||||
if s.dead {
|
||||
return
|
||||
}
|
||||
x, y := s.pos.x, s.pos.y // shorthands
|
||||
|
||||
change := rand.Intn(1000)
|
||||
if change < 2 {
|
||||
// On 0.2% of iterations, switch rotation direction.
|
||||
s.rot = (s.rot + 1) % 2
|
||||
}
|
||||
|
||||
// 1. try to advance the spiral in its rotation
|
||||
// direction (clockwise or counter-clockwise).
|
||||
for _, next := range dirCycles[s.rot] {
|
||||
dir := (s.dir + next) % 4
|
||||
off := dirs[dir]
|
||||
// Peek all targets by priority.
|
||||
target := vec2{
|
||||
x: x + off.x,
|
||||
y: y + off.y,
|
||||
}
|
||||
if auto.colorMap[target.x][target.y] == background {
|
||||
// If the target is free we need to also check the
|
||||
// surrounding cells.
|
||||
|
||||
// a. Test if next cell in direction dir does not have
|
||||
// the same color as this squiral.
|
||||
ntarg := vec2{
|
||||
x: target.x + off.x,
|
||||
y: target.y + off.y,
|
||||
}
|
||||
if auto.colorMap[ntarg.x][ntarg.y] == s.col {
|
||||
// If this has the same color, we cannot go into this direction,
|
||||
// to avoid ugly blocks of equal color.
|
||||
continue // try next direction
|
||||
}
|
||||
|
||||
// b. Test all outer fields for the color of the
|
||||
// squiral itself.
|
||||
horivert := dir % 2
|
||||
xtarg := vec2{}
|
||||
set := true
|
||||
for _, out := range neighbors[horivert] {
|
||||
xtarg.x = target.x + out.x
|
||||
xtarg.y = target.y + out.y
|
||||
|
||||
// If one of the outer targets equals the squiral's
|
||||
// color, again continue with next direction.
|
||||
if auto.colorMap[xtarg.x][xtarg.y] == s.col {
|
||||
// If this is not free we cannot go into this direction.
|
||||
set = false
|
||||
break // try next direction
|
||||
}
|
||||
|
||||
xtarg.x = ntarg.x + out.x
|
||||
xtarg.y = ntarg.y + out.y
|
||||
|
||||
// If one of the outer targets equals the squiral's
|
||||
// color, again continue with next direction.
|
||||
if auto.colorMap[xtarg.x][xtarg.y] == s.col {
|
||||
// If this is not free we cannot go into this direction.
|
||||
set = false
|
||||
break // try next direction
|
||||
}
|
||||
}
|
||||
|
||||
if set {
|
||||
s.pos = target
|
||||
s.dir = dir
|
||||
// 2. set the color of this squiral to its
|
||||
// current position.
|
||||
setpix(s.pos, s.col)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.dead = true
|
||||
}
|
||||
|
||||
type automaton struct {
|
||||
squirals [numOfSquirals]squiral
|
||||
colorMap [width][height]color.Color
|
||||
}
|
||||
|
||||
func (au *automaton) init() {
|
||||
// Init the test grid with color (0,0,0,0) and the borders of
|
||||
// it with color(0,0,0,254) as a blocker color, so the squirals
|
||||
// cannot escape the scene.
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
if x == 0 || x == width-1 || y == 0 || y == height-1 {
|
||||
auto.colorMap[x][y] = blocker
|
||||
} else {
|
||||
auto.colorMap[x][y] = background
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < numOfSquirals; i++ {
|
||||
auto.squirals[i].spawn()
|
||||
}
|
||||
}
|
||||
|
||||
func (au *automaton) step() {
|
||||
for i := 0; i < numOfSquirals; i++ {
|
||||
for s := 0; s < au.squirals[i].speed; s++ {
|
||||
au.squirals[i].step(i)
|
||||
if au.squirals[i].dead {
|
||||
au.squirals[i].spawn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setpix(xy vec2, col color.Color) {
|
||||
canvas.Set(xy.x, xy.y, col)
|
||||
auto.colorMap[xy.x][xy.y] = col
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
c, _ := ebiten.NewImage(width, height, ebiten.FilterDefault)
|
||||
canvas = c
|
||||
canvas.Fill(background)
|
||||
|
||||
auto = &automaton{}
|
||||
auto.init()
|
||||
}
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
reset := false
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyB) {
|
||||
if background == color.White {
|
||||
background = color.Black
|
||||
} else {
|
||||
background = color.White
|
||||
}
|
||||
reset = true
|
||||
} else if inpututil.IsKeyJustPressed(ebiten.KeyT) {
|
||||
selectedPalette = (selectedPalette + 1) % len(palettes)
|
||||
reset = true
|
||||
} else if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
||||
reset = true
|
||||
}
|
||||
|
||||
if reset {
|
||||
canvas.Fill(background)
|
||||
auto.init()
|
||||
}
|
||||
|
||||
auto.step()
|
||||
|
||||
if ebiten.IsDrawingSkipped() {
|
||||
return nil
|
||||
}
|
||||
|
||||
draw(screen)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func draw(screen *ebiten.Image) {
|
||||
screen.DrawImage(canvas, nil)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
fmt.Sprintf("TPS: %0.2f, FPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()),
|
||||
1, 0,
|
||||
)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
"[r]: respawn",
|
||||
1, 16,
|
||||
)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
"[b]: toggle background (white/black)",
|
||||
1, 32,
|
||||
)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
fmt.Sprintf("[t]: cycle theme (current: %s)", palettes[selectedPalette].name),
|
||||
1, 48,
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ebiten.SetMaxTPS(250)
|
||||
if err := ebiten.Run(update, width, height, scale, "Squirals"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user