336 lines
7.2 KiB
Go
336 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"image/color"
|
|
"math/rand"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"tinygo.org/x/tinyfont"
|
|
"tinygo.org/x/tinyfont/freesans"
|
|
"tinygo.org/x/tinyfont/proggy"
|
|
)
|
|
|
|
const (
|
|
GameSplash = iota
|
|
GameStart
|
|
GamePlay
|
|
GameOver
|
|
GameQuit
|
|
)
|
|
|
|
const (
|
|
SnakeUp = iota
|
|
SnakeDown
|
|
SnakeLeft
|
|
SnakeRight
|
|
)
|
|
|
|
var (
|
|
// Those variable are there for a more easy reading of the apple shape.
|
|
re = colorRed // red
|
|
bk = colorBlack // background
|
|
gr = colorMellonGreen // green
|
|
|
|
// The array is split for a visual purpose too.
|
|
appleBuf = []color.RGBA{
|
|
bk, bk, bk, bk, bk, gr, gr, gr, bk, bk,
|
|
bk, bk, bk, bk, gr, gr, gr, bk, bk, bk,
|
|
bk, bk, bk, re, gr, gr, re, bk, bk, bk,
|
|
bk, bk, re, re, re, re, re, re, bk, bk,
|
|
bk, re, re, re, re, re, re, re, re, bk,
|
|
bk, re, re, re, re, re, re, re, re, bk,
|
|
bk, re, re, re, re, re, re, re, re, bk,
|
|
bk, bk, re, re, re, re, re, re, bk, bk,
|
|
bk, bk, bk, re, re, re, re, bk, bk, bk,
|
|
bk, bk, bk, bk, bk, bk, bk, bk, bk, bk,
|
|
}
|
|
)
|
|
|
|
type Snake struct {
|
|
body [snakeWidthBLOCKS * snakeHeightBLOCKS][2]int16
|
|
length int16
|
|
direction int16
|
|
}
|
|
|
|
type SnakeGame struct {
|
|
snake Snake
|
|
appleX, appleY int16
|
|
status uint8
|
|
score int
|
|
frame, delay int
|
|
}
|
|
|
|
var splashed = false
|
|
var scoreStr string
|
|
|
|
func NewSnakeGame() *SnakeGame {
|
|
return &SnakeGame{
|
|
snake: Snake{
|
|
body: [snakeWidthBLOCKS * snakeHeightBLOCKS][2]int16{
|
|
{0, 3},
|
|
{0, 2},
|
|
{0, 1},
|
|
},
|
|
length: 3,
|
|
direction: SnakeLeft,
|
|
},
|
|
appleX: 5,
|
|
appleY: 5,
|
|
status: GameSplash,
|
|
delay: 120,
|
|
}
|
|
}
|
|
|
|
func (g *SnakeGame) Splash() {
|
|
if !splashed {
|
|
g.splash()
|
|
splashed = true
|
|
}
|
|
}
|
|
|
|
func (g *SnakeGame) Start() {
|
|
display.FillScreen(bk)
|
|
|
|
g.initSnake()
|
|
g.drawSnake()
|
|
g.createApple()
|
|
|
|
g.status = GamePlay
|
|
}
|
|
|
|
func (g *SnakeGame) Play(direction int) {
|
|
if direction != -1 && ((g.snake.direction == SnakeUp && direction != SnakeDown) ||
|
|
(g.snake.direction == SnakeDown && direction != SnakeUp) ||
|
|
(g.snake.direction == SnakeLeft && direction != SnakeRight) ||
|
|
(g.snake.direction == SnakeRight && direction != SnakeLeft)) {
|
|
g.snake.direction = int16(direction)
|
|
}
|
|
|
|
g.moveSnake()
|
|
}
|
|
|
|
func (g *SnakeGame) Over() {
|
|
display.FillScreen(bk)
|
|
splashed = false
|
|
|
|
g.status = GameOver
|
|
}
|
|
|
|
func (g *SnakeGame) splash() {
|
|
display.FillScreen(bk)
|
|
|
|
if displayWidth < 300 {
|
|
logoSmall := `
|
|
____ _ _ _ _ _____
|
|
/ ___|| \| | /_\ | |/ / __|
|
|
\___ \| \ |/ _ \| ' < | _|
|
|
|___/ |_|\_/_/ \_\_|\_\___|`
|
|
for i, line := range strings.Split(strings.TrimSuffix(logoSmall, "\n"), "\n") {
|
|
tinyfont.WriteLine(&display, &proggy.TinySZ8pt7b, 6, int16(14+i*11), line+"\n", gr)
|
|
}
|
|
tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 10, 95, "Press A to start", colorRed)
|
|
if g.score > 0 {
|
|
scoreStr = strconv.Itoa(g.score)
|
|
tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 126, "SCORE: "+scoreStr, colorText)
|
|
}
|
|
} else {
|
|
logo := `
|
|
___ ___ ___
|
|
/ /\ / /\ / /\
|
|
/ /::\ / /::| / /::\
|
|
/__/:/\:\ / /:|:| / /:/\:\
|
|
_\_ \:\ \:\ / /:/|:|__ / /::\ \:\
|
|
/__/\ \:\ \:\ /__/:/ |:| /\ /__/:/\:\_\:\
|
|
\ \:\ \:\_\/ \__\/ |:|/:/ \__\/ \:\/:/
|
|
\ \:\_\:\ | |:/:/ \__\::/
|
|
\ \:\/:/ |__|::/ / /:/
|
|
\ \::/ /__/:/ /__/:/
|
|
\__\/ \__\/ \__\/
|
|
___ ___
|
|
/ /\ / /\
|
|
/ /:/ / /::\
|
|
/ /:/ / /:/\:\
|
|
/ /::\____ / /::\ \:\
|
|
/__/:/\:::::\ /__/:/\:\ \:\
|
|
\__\/~|:|~~~~ \ \:\ \:\_\/
|
|
| |:| \ \:\ \:\
|
|
| |:| \ \:\_\/
|
|
|__|:| \ \:\
|
|
\__\| \__\/
|
|
`
|
|
for i, line := range strings.Split(strings.TrimSuffix(logo, "\n"), "\n") {
|
|
tinyfont.WriteLine(&display, &proggy.TinySZ8pt7b, 0, int16(-6+i*11), line+"\n", gr)
|
|
}
|
|
tinyfont.WriteLine(&display, &freesans.Regular18pt7b, 30, 130, "Press A to start", colorRed)
|
|
if g.score > 0 {
|
|
scoreStr = strconv.Itoa(g.score)
|
|
tinyfont.WriteLineRotated(&display, &freesans.Regular12pt7b, 300, 200, "SCORE: "+scoreStr, colorText, tinyfont.ROTATION_270)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *SnakeGame) initSnake() {
|
|
g.snake.body[0][0] = 0
|
|
g.snake.body[0][1] = 3
|
|
g.snake.body[1][0] = 0
|
|
g.snake.body[1][1] = 2
|
|
g.snake.body[2][0] = 0
|
|
g.snake.body[2][1] = 1
|
|
|
|
g.snake.length = 3
|
|
g.snake.direction = SnakeRight
|
|
}
|
|
|
|
func (g *SnakeGame) collisionWithSnake(x, y int16) bool {
|
|
for i := int16(0); i < g.snake.length; i++ {
|
|
if x == g.snake.body[i][0] && y == g.snake.body[i][1] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (g *SnakeGame) createApple() {
|
|
g.appleX = int16(rand.Int31n(snakeWidthBLOCKS))
|
|
g.appleY = int16(rand.Int31n(snakeHeightBLOCKS))
|
|
for g.collisionWithSnake(g.appleX, g.appleY) {
|
|
g.appleX = int16(rand.Int31n(snakeWidthBLOCKS))
|
|
g.appleY = int16(rand.Int31n(snakeHeightBLOCKS))
|
|
}
|
|
g.drawApple(g.appleX, g.appleY)
|
|
}
|
|
|
|
func (g *SnakeGame) moveSnake() {
|
|
x := g.snake.body[0][0]
|
|
y := g.snake.body[0][1]
|
|
|
|
switch g.snake.direction {
|
|
case SnakeLeft:
|
|
x--
|
|
break
|
|
case SnakeUp:
|
|
y--
|
|
break
|
|
case SnakeDown:
|
|
y++
|
|
break
|
|
case SnakeRight:
|
|
x++
|
|
break
|
|
}
|
|
if x >= snakeWidthBLOCKS {
|
|
x = 0
|
|
}
|
|
if x < 0 {
|
|
x = snakeWidthBLOCKS - 1
|
|
}
|
|
if y >= snakeHeightBLOCKS {
|
|
y = 0
|
|
}
|
|
if y < 0 {
|
|
y = snakeHeightBLOCKS - 1
|
|
}
|
|
|
|
if g.collisionWithSnake(x, y) {
|
|
g.score = int(g.snake.length - 3)
|
|
g.Over()
|
|
|
|
return
|
|
}
|
|
|
|
// draw head
|
|
g.drawSnakePartial(x, y, colorMellonGreen)
|
|
if x == g.appleX && y == g.appleY {
|
|
g.snake.length++
|
|
g.createApple()
|
|
} else {
|
|
// remove tail
|
|
g.drawSnakePartial(g.snake.body[g.snake.length-1][0], g.snake.body[g.snake.length-1][1], colorBlack)
|
|
}
|
|
for i := g.snake.length - 1; i > 0; i-- {
|
|
g.snake.body[i][0] = g.snake.body[i-1][0]
|
|
g.snake.body[i][1] = g.snake.body[i-1][1]
|
|
}
|
|
g.snake.body[0][0] = x
|
|
g.snake.body[0][1] = y
|
|
}
|
|
|
|
func (g *SnakeGame) drawApple(x, y int16) {
|
|
display.FillRectangleWithBuffer(10*x, 10*y, 10, 10, appleBuf)
|
|
}
|
|
|
|
func (g *SnakeGame) drawSnake() {
|
|
for i := int16(0); i < g.snake.length; i++ {
|
|
g.drawSnakePartial(g.snake.body[i][0], g.snake.body[i][1], colorMellonGreen)
|
|
}
|
|
}
|
|
|
|
func (g *SnakeGame) drawSnakePartial(x, y int16, c color.RGBA) {
|
|
display.FillRectangle(10*x, 10*y, 9, 9, c)
|
|
}
|
|
|
|
func (g *SnakeGame) Loop() {
|
|
g.status = GameSplash
|
|
splashed = false
|
|
for {
|
|
g.update()
|
|
if g.status == GameQuit {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func (g *SnakeGame) update() {
|
|
getInput()
|
|
switch g.status {
|
|
case GameSplash:
|
|
g.Splash()
|
|
if !buttonsOldState[buttonA] && buttonsState[buttonA] {
|
|
g.Start()
|
|
}
|
|
if goBack() {
|
|
g.status = GameOver
|
|
}
|
|
break
|
|
|
|
case GamePlay:
|
|
switch {
|
|
case (goBack()):
|
|
g.Over()
|
|
break
|
|
case (!buttonsOldState[buttonRight] && buttonsState[buttonRight]):
|
|
g.Play(SnakeRight)
|
|
break
|
|
|
|
case (!buttonsOldState[buttonLeft] && buttonsState[buttonLeft]):
|
|
g.Play(SnakeLeft)
|
|
break
|
|
|
|
case (!buttonsOldState[buttonDown] && buttonsState[buttonDown]):
|
|
g.Play(SnakeDown)
|
|
break
|
|
|
|
case (!buttonsOldState[buttonUp] && buttonsState[buttonUp]):
|
|
g.Play(SnakeUp)
|
|
break
|
|
|
|
default:
|
|
g.Play(-1)
|
|
break
|
|
}
|
|
break
|
|
case GameQuit:
|
|
case GameOver:
|
|
g.Splash()
|
|
|
|
if !buttonsOldState[buttonA] && buttonsState[buttonA] {
|
|
g.Start()
|
|
}
|
|
if goBack() {
|
|
g.status = GameQuit
|
|
}
|
|
}
|
|
}
|