724 lines
16 KiB
Go
724 lines
16 KiB
Go
/*
|
|
Package skulls
|
|
A simple strategy game about skulls
|
|
Copyright (C) 2021 rootVIII
|
|
|
|
colleyloyejames@gmail.com
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
package skulls
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
|
|
|
// Required for Ebiten.
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
|
|
"github.com/goki/freetype/truetype"
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/audio"
|
|
"github.com/hajimehoshi/ebiten/v2/audio/mp3"
|
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
"github.com/hajimehoshi/ebiten/v2/text"
|
|
"github.com/rootVIII/skulls/assets"
|
|
"golang.org/x/image/font"
|
|
)
|
|
|
|
const (
|
|
screenW = 640
|
|
screenH = 960
|
|
frame = 64
|
|
rowMax = 22
|
|
colMax = 15
|
|
hotspotUpX = 123.0
|
|
hotspotUpY = 788.0
|
|
hotspotLeftX = 39.0
|
|
hotspotLeftY = 862.0
|
|
hotspotRightX = 210.0
|
|
hotspotRightY = 865.0
|
|
hotspotButtonX = 373.0
|
|
hotspotButtonY = 818.0
|
|
)
|
|
|
|
// Clock is the global game clock.
|
|
var Clock float64
|
|
|
|
// Game controls overall gameplay.
|
|
type Game struct {
|
|
background, explosion, intro *ebiten.Image
|
|
skulls map[string]*ebiten.Image
|
|
planchette, onDeck []string
|
|
skullColors []string
|
|
skullCollector [][]string
|
|
skullCoords [][][]int
|
|
empties [][2]int
|
|
searchHead [2]int
|
|
beep, clear, track *audio.Player
|
|
fontFace font.Face
|
|
green color.Color
|
|
isMovingL, isMovingR, isMovingU bool
|
|
havePlanchette, isPlaying bool
|
|
wonGame, lostGame bool
|
|
rollCount, moveCount, explosionCount int
|
|
jumpCount, jumpMax, loseCount int
|
|
score, level, best, matchMin int
|
|
}
|
|
|
|
/* - - - - - - - - U P D A T E M E T H O D S - - - - - - - - */
|
|
|
|
func (g *Game) rollBones() {
|
|
|
|
g.planchette = append(g.planchette[len(g.planchette)-1:], g.planchette[:len(g.planchette)-1]...)
|
|
|
|
row, col := g.searchHead[0], g.searchHead[1]
|
|
for _, skull := range g.planchette {
|
|
g.skullCollector[row][col] = skull
|
|
row++
|
|
}
|
|
}
|
|
|
|
func (g *Game) removeCurrentPos() {
|
|
row, col := g.searchHead[0], g.searchHead[1]
|
|
for range g.planchette {
|
|
g.skullCollector[row][col] = ""
|
|
row++
|
|
}
|
|
}
|
|
|
|
func (g *Game) shiftPlanchette() {
|
|
row, col := g.searchHead[0], g.searchHead[1]
|
|
for _, skull := range g.planchette {
|
|
g.skullCollector[row][col] = skull
|
|
row++
|
|
}
|
|
}
|
|
|
|
func (g *Game) matchSkulls() [][2]int {
|
|
return append(g.checkCols(), g.checkRows()...)
|
|
}
|
|
|
|
func (g *Game) removeEmpties() {
|
|
for _, coords := range g.empties {
|
|
g.skullCollector[coords[0]][coords[1]] = ""
|
|
}
|
|
}
|
|
|
|
func (g *Game) checkRows() [][2]int {
|
|
|
|
var matchesIndex = make([][2]int, 0)
|
|
var matchColor string
|
|
var matchCount int
|
|
|
|
for x := 0; x < len(g.skullCollector[0]); x++ {
|
|
|
|
matchColor = ""
|
|
matchCount = 0
|
|
|
|
for y := 0; y < len(g.skullCollector); y++ {
|
|
if g.skullCollector[y][x] == matchColor {
|
|
matchCount++
|
|
} else {
|
|
matchCount = 0
|
|
}
|
|
var last = (y == len(g.skullCollector)-1) || (g.skullCollector[y+1][x] != g.skullCollector[y][x])
|
|
if matchCount > 2 && last && len(matchColor) > 0 {
|
|
for index := y - matchCount; index <= y; index++ {
|
|
matchesIndex = append(matchesIndex, [2]int{index, x})
|
|
}
|
|
}
|
|
matchColor = g.skullCollector[y][x]
|
|
}
|
|
}
|
|
|
|
return matchesIndex
|
|
}
|
|
|
|
func (g *Game) checkCols() [][2]int {
|
|
|
|
var matchesIndex = make([][2]int, 0)
|
|
var matchColor string
|
|
var matchCount int
|
|
|
|
for ri, row := range g.skullCollector {
|
|
matchColor = ""
|
|
matchCount = 0
|
|
for ci, col := range row {
|
|
if col == matchColor {
|
|
matchCount++
|
|
} else {
|
|
matchCount = 0
|
|
}
|
|
if matchCount > 2 && (ci == len(row)-1 || g.skullCollector[ri][ci+1] != col) && len(matchColor) > 1 {
|
|
for index := ci - matchCount; index <= ci; index++ {
|
|
matchesIndex = append(matchesIndex, [2]int{ri, index})
|
|
}
|
|
}
|
|
matchColor = col
|
|
}
|
|
}
|
|
return matchesIndex
|
|
}
|
|
|
|
func (g *Game) bubbleSortSkulls() {
|
|
var length = len(g.skullCollector)
|
|
|
|
for x := 0; x < len(g.skullCollector[0]); x++ {
|
|
for {
|
|
var madeChange = false
|
|
for y := 0; y < length; y++ {
|
|
for i := 0; i < length-y-1; i++ {
|
|
|
|
if len(g.skullCollector[y][x]) < 1 && len(g.skullCollector[y+1][x]) > 1 {
|
|
g.skullCollector[y][x], g.skullCollector[y+1][x] = g.skullCollector[y+1][x], g.skullCollector[y][x]
|
|
madeChange = true
|
|
}
|
|
}
|
|
}
|
|
if !madeChange {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func (g *Game) isValidMove(direction byte) bool {
|
|
|
|
row, col := g.searchHead[0], g.searchHead[1]
|
|
switch direction {
|
|
|
|
case 0x4C:
|
|
if col < 1 || len(g.skullCollector[row][col-1]) > 0 {
|
|
return false
|
|
}
|
|
case 0x52:
|
|
if col > 13 || len(g.skullCollector[row][col+1]) > 0 {
|
|
return false
|
|
}
|
|
case 0x55:
|
|
if row < 1 || len(g.skullCollector[row-1][col]) > 0 {
|
|
|
|
for {
|
|
var combined = g.matchSkulls()
|
|
if len(combined) > 0 {
|
|
g.clear.Play()
|
|
g.score += len(combined)
|
|
g.best = g.score
|
|
g.empties = combined
|
|
g.explosionCount = 30
|
|
g.removeEmpties()
|
|
g.bubbleSortSkulls()
|
|
g.clear.Rewind()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
g.reset()
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (g Game) inHotSpot(courseX, courseY float64) bool {
|
|
|
|
touchX, touchY := ebiten.TouchPosition(0)
|
|
|
|
if float64(touchX) < courseX || float64(touchX) > courseX+frame {
|
|
return false
|
|
}
|
|
if float64(touchY) < courseY || float64(touchY) > courseY+frame {
|
|
return false
|
|
}
|
|
return true
|
|
|
|
}
|
|
|
|
func (g Game) hotspotClickedLeft() bool {
|
|
return g.inHotSpot(hotspotLeftX, hotspotLeftY)
|
|
}
|
|
|
|
func (g Game) hotspotClickedRight() bool {
|
|
return g.inHotSpot(hotspotRightX, hotspotRightY)
|
|
}
|
|
|
|
func (g Game) hotspotClickedUp() bool {
|
|
return g.inHotSpot(hotspotUpX, hotspotUpY)
|
|
}
|
|
|
|
func (g Game) hotspotClickedButton() bool {
|
|
return g.inHotSpot(hotspotButtonX, hotspotButtonY)
|
|
}
|
|
|
|
func (g *Game) updatePlanchette() {
|
|
|
|
if !g.lostGame && g.hotspotClickedLeft() {
|
|
g.isMovingL = true
|
|
g.isMovingR = false
|
|
g.isMovingU = false
|
|
}
|
|
|
|
if !g.lostGame && g.hotspotClickedRight() {
|
|
g.isMovingR = true
|
|
g.isMovingL = false
|
|
g.isMovingU = false
|
|
}
|
|
|
|
if g.hotspotClickedUp() {
|
|
g.isMovingR = false
|
|
g.isMovingL = false
|
|
g.isMovingU = true
|
|
}
|
|
|
|
if g.isMovingL && g.moveCount < 1 && g.isValidMove('L') {
|
|
|
|
g.removeCurrentPos()
|
|
|
|
g.searchHead[1] -= 1
|
|
g.shiftPlanchette()
|
|
}
|
|
|
|
if g.isMovingR && g.moveCount < 1 && g.isValidMove('R') {
|
|
|
|
g.removeCurrentPos()
|
|
|
|
g.searchHead[1] += 1
|
|
g.shiftPlanchette()
|
|
}
|
|
|
|
if (g.isMovingU && g.moveCount < 1 || g.jumpCount == 0) && g.isValidMove('U') {
|
|
|
|
g.removeCurrentPos()
|
|
|
|
g.searchHead[0] -= 1
|
|
g.shiftPlanchette()
|
|
}
|
|
|
|
if inpututil.IsTouchJustReleased(0) && (g.isMovingL || g.isMovingR || g.isMovingU) {
|
|
g.isMovingL, g.isMovingR, g.isMovingU = false, false, false
|
|
}
|
|
|
|
if g.hotspotClickedButton() {
|
|
if g.rollCount < 1 {
|
|
g.beep.Play()
|
|
g.rollBones()
|
|
g.beep.Rewind()
|
|
}
|
|
g.rollCount++
|
|
}
|
|
|
|
if g.isMovingL || g.isMovingR || g.isMovingU {
|
|
g.moveCount++
|
|
}
|
|
|
|
if g.rollCount > 9 {
|
|
g.rollCount = 0
|
|
}
|
|
|
|
if g.moveCount > 7 {
|
|
g.moveCount = 0
|
|
}
|
|
|
|
if g.jumpCount > g.jumpMax {
|
|
g.jumpCount = 0
|
|
} else {
|
|
g.jumpCount++
|
|
}
|
|
|
|
}
|
|
|
|
func (g *Game) spawn() {
|
|
g.onDeck = nil
|
|
maxLen := randNo(1, 5)
|
|
|
|
for i := 0; i < maxLen; i++ {
|
|
g.onDeck = append(g.onDeck, g.skullColors[randNo(0, 4)])
|
|
}
|
|
|
|
}
|
|
|
|
func (g *Game) deepCopyPlanchette() {
|
|
g.planchette = nil
|
|
g.planchette = append(g.planchette, g.onDeck...)
|
|
}
|
|
|
|
func (g *Game) insertPlanchette() {
|
|
row, col := 21, 8
|
|
for i := len(g.planchette) - 1; i >= 0; i-- {
|
|
if len(g.skullCollector[row][col]) > 0 {
|
|
g.lostGame = true
|
|
g.isPlaying = false
|
|
}
|
|
g.skullCollector[row][col] = g.planchette[i]
|
|
row--
|
|
}
|
|
|
|
g.searchHead[0], g.searchHead[1] = row+1, col
|
|
}
|
|
|
|
func (g *Game) initSkullCollector() {
|
|
g.skullCollector = make([][]string, rowMax)
|
|
for row := 0; row < rowMax; row++ {
|
|
cols := make([]string, colMax)
|
|
for col := 0; col < colMax; col++ {
|
|
cols[col] = ""
|
|
}
|
|
g.skullCollector[row] = cols
|
|
}
|
|
}
|
|
|
|
func (g *Game) initSkullCoords() {
|
|
g.skullCoords = make([][][]int, rowMax)
|
|
|
|
for outer, y := 0, 60; y < (rowMax+1)*32; outer, y = outer+1, y+32 {
|
|
g.skullCoords[outer] = make([][]int, colMax)
|
|
for inner, x := 0, 22; x < colMax*32; inner, x = inner+1, x+32 {
|
|
g.skullCoords[outer][inner] = make([]int, 2)
|
|
g.skullCoords[outer][inner] = []int{x, y}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *Game) initPlanchettes() {
|
|
g.planchette = make([]string, 0)
|
|
g.onDeck = make([]string, 0)
|
|
}
|
|
|
|
func (g *Game) reset() {
|
|
g.havePlanchette = false
|
|
g.isMovingL = false
|
|
g.isMovingR = false
|
|
g.isMovingU = false
|
|
g.rollCount = 1
|
|
g.moveCount = 0
|
|
g.jumpCount = 1
|
|
|
|
switch score := g.score; {
|
|
|
|
case score > 99999:
|
|
g.wonGame = true
|
|
case score > 250:
|
|
g.level = 7
|
|
g.jumpMax = 14
|
|
case score > 100:
|
|
g.level = 6
|
|
g.jumpMax = 16
|
|
case score > 50:
|
|
g.level = 5
|
|
g.jumpMax = 18
|
|
case score > 40:
|
|
g.level = 4
|
|
g.jumpMax = 20
|
|
case score > 30:
|
|
g.level = 3
|
|
g.jumpMax = 25
|
|
case score > 20:
|
|
g.level = 2
|
|
g.jumpMax = 30
|
|
case score > 10:
|
|
g.level = 1
|
|
g.jumpMax = 35
|
|
}
|
|
|
|
}
|
|
|
|
func (g *Game) resetGame() {
|
|
g.score = 0
|
|
g.reset()
|
|
g.loseCount = 300
|
|
g.lostGame = false
|
|
g.isPlaying = false
|
|
g.wonGame = false
|
|
}
|
|
|
|
func (g *Game) checkTrackPlaying() {
|
|
if !g.track.IsPlaying() {
|
|
g.track.Rewind()
|
|
g.track.Play()
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - D R A W M E T H O D S - - - - - - - - */
|
|
|
|
func (g Game) drawBackground(screen *ebiten.Image) {
|
|
opts := &ebiten.DrawImageOptions{}
|
|
opts.GeoM.Translate(0, 0)
|
|
screen.DrawImage(g.background, opts)
|
|
}
|
|
|
|
func (g Game) drawAllGameText(screen *ebiten.Image) {
|
|
text.Draw(screen, fmt.Sprintf("%05d", g.score), g.fontFace, 528, 610, g.green)
|
|
text.Draw(screen, fmt.Sprintf("%05d", g.best), g.fontFace, 528, 788, g.green)
|
|
text.Draw(screen, fmt.Sprintf("%02d", g.level), g.fontFace, 556, 926, g.green)
|
|
}
|
|
|
|
func (g Game) drawOnDeck(screen *ebiten.Image) {
|
|
var opts *ebiten.DrawImageOptions
|
|
|
|
var odY float64
|
|
|
|
switch len(g.onDeck) {
|
|
case 4:
|
|
odY = 186.00
|
|
case 3:
|
|
odY = 209.00
|
|
case 2:
|
|
odY = 222.00
|
|
case 1:
|
|
odY = 244.00
|
|
}
|
|
|
|
for _, skull := range g.onDeck {
|
|
opts = &ebiten.DrawImageOptions{}
|
|
opts.GeoM.Translate(561.00, odY)
|
|
screen.DrawImage(g.skulls[skull], opts)
|
|
odY += 32.00
|
|
}
|
|
}
|
|
|
|
func (g Game) drawSkullCollector(screen *ebiten.Image) {
|
|
|
|
var opts *ebiten.DrawImageOptions
|
|
|
|
for i, row := range g.skullCollector {
|
|
for j, col := range row {
|
|
if len(col) > 0 {
|
|
opts = &ebiten.DrawImageOptions{}
|
|
opts.GeoM.Translate(float64(g.skullCoords[i][j][0]), float64(g.skullCoords[i][j][1]))
|
|
screen.DrawImage(g.skulls[col], opts)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *Game) drawExplosions(screen *ebiten.Image) {
|
|
var opts *ebiten.DrawImageOptions
|
|
var width = frame / 2
|
|
if g.explosionCount > 0 {
|
|
for _, coords := range g.empties {
|
|
opts = &ebiten.DrawImageOptions{}
|
|
opts.GeoM.Translate(float64(g.skullCoords[coords[0]][coords[1]][0]), float64(g.skullCoords[coords[0]][coords[1]][1]))
|
|
i := int(Clock/5) % 8
|
|
sX, sY := i*width, 0
|
|
screen.DrawImage(g.explosion.SubImage(image.Rect(sX, sY, sX+width, sY+width)).(*ebiten.Image), opts)
|
|
g.explosionCount--
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g Game) drawIntro(screen *ebiten.Image) {
|
|
opts := &ebiten.DrawImageOptions{}
|
|
opts.GeoM.Translate(0, 0)
|
|
screen.DrawImage(g.intro, opts)
|
|
|
|
if int(Clock)%50 < 40 {
|
|
text.Draw(screen, "TOUCH TO BEGIN", g.fontFace, 200, 890, color.White)
|
|
}
|
|
}
|
|
|
|
func (g *Game) drawLostGame(screen *ebiten.Image) {
|
|
if g.loseCount > 0 {
|
|
text.Draw(screen, "Game Over", g.fontFace, 200, 400, color.White)
|
|
g.loseCount--
|
|
}
|
|
}
|
|
|
|
func (g Game) drawWonGame(screen *ebiten.Image) {
|
|
text.Draw(screen, " You Win ", g.fontFace, 200, 400, color.White)
|
|
}
|
|
|
|
/* - - - - - - - - E B I T E N M E T H O D S - - - - - - - - */
|
|
|
|
// Update proceeds the game state every tick (1/60 [s] by default).
|
|
func (g *Game) Update() error {
|
|
Clock++
|
|
|
|
if g.isPlaying {
|
|
if !g.havePlanchette {
|
|
g.deepCopyPlanchette()
|
|
g.insertPlanchette()
|
|
g.spawn()
|
|
g.havePlanchette = true
|
|
}
|
|
g.updatePlanchette()
|
|
} else if g.lostGame && g.loseCount < 1 {
|
|
g.resetGame()
|
|
g.initSkullCollector()
|
|
g.initPlanchettes()
|
|
g.spawn()
|
|
} else if g.wonGame {
|
|
g.isPlaying = false
|
|
} else {
|
|
if inpututil.IsTouchJustReleased(0) {
|
|
g.isPlaying = true
|
|
Clock = 0
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Layout takes the outside/window size and returns the (logical) screen size.
|
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|
return screenW, screenH
|
|
}
|
|
|
|
// Draw the screen every frame (typically 1/60[s] for 60Hz display).
|
|
func (g *Game) Draw(screen *ebiten.Image) {
|
|
g.checkTrackPlaying()
|
|
if g.lostGame {
|
|
g.drawBackground(screen)
|
|
g.drawSkullCollector(screen)
|
|
g.drawAllGameText(screen)
|
|
g.drawLostGame(screen)
|
|
} else if g.wonGame {
|
|
g.drawBackground(screen)
|
|
g.drawAllGameText(screen)
|
|
g.drawWonGame(screen)
|
|
} else if !g.isPlaying {
|
|
g.drawIntro(screen)
|
|
} else {
|
|
g.drawBackground(screen)
|
|
g.drawOnDeck(screen)
|
|
g.drawSkullCollector(screen)
|
|
g.drawExplosions(screen)
|
|
g.drawAllGameText(screen)
|
|
}
|
|
|
|
ebitenutil.DebugPrint(screen, "")
|
|
}
|
|
|
|
/* - - - - - - - - U T I L I T Y F U N C T I O N S - - - - - - - - */
|
|
|
|
func randNo(min, max int) int {
|
|
rand.Seed(time.Now().UnixNano())
|
|
return rand.Intn(max-min) + min
|
|
}
|
|
|
|
func readRawIMG(asset []byte) (*ebiten.Image, error) {
|
|
rawIMG, _, err := image.Decode(bytes.NewReader(asset))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newImage := ebiten.NewImageFromImage(rawIMG)
|
|
return newImage, nil
|
|
}
|
|
|
|
func readAudio(context *audio.Context, asset []byte) (*audio.Player, error) {
|
|
mp3Decoded, err := mp3.Decode(context, bytes.NewReader(asset))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
player, err := audio.NewPlayer(context, mp3Decoded)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return player, nil
|
|
}
|
|
|
|
// Load is the entry point to the game.
|
|
func Load() (*Game, error) {
|
|
|
|
audioContext := audio.NewContext(44100)
|
|
|
|
radioLand, err := truetype.Parse(assets.RadioLandTTF)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
PNGs := [][]byte{
|
|
assets.BackgroundPNG,
|
|
assets.ExplosionPNG,
|
|
assets.IntroPNG,
|
|
assets.GreenskullPNG,
|
|
assets.RedskullPNG,
|
|
assets.PurpleskullPNG,
|
|
assets.BlueskullPNG,
|
|
}
|
|
|
|
var images []*ebiten.Image
|
|
|
|
for _, png := range PNGs {
|
|
ebImage, err := readRawIMG(png)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
images = append(images, ebImage)
|
|
}
|
|
|
|
beepSound, err := readAudio(audioContext, assets.BeepMP3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clearSound, err := readAudio(audioContext, assets.ClearMP3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
themeSong, err := readAudio(audioContext, assets.ThemeMP3)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var game = &Game{
|
|
background: images[0],
|
|
explosion: images[1],
|
|
intro: images[2],
|
|
beep: beepSound,
|
|
clear: clearSound,
|
|
track: themeSong,
|
|
fontFace: truetype.NewFace(radioLand, &truetype.Options{Size: 28, DPI: 72, Hinting: font.HintingFull}),
|
|
green: color.RGBA{R: 0x7C, G: 0xFC, B: 0x00, A: 0xFF},
|
|
skullColors: []string{"purple", "blue", "red", "green"},
|
|
jumpCount: 1,
|
|
jumpMax: 35,
|
|
matchMin: 3,
|
|
loseCount: 300,
|
|
}
|
|
|
|
game.skulls = map[string]*ebiten.Image{
|
|
"green": images[3],
|
|
"red": images[4],
|
|
"purple": images[5],
|
|
"blue": images[6],
|
|
}
|
|
|
|
game.initSkullCollector()
|
|
game.initSkullCoords()
|
|
game.initPlanchettes()
|
|
|
|
game.track.SetVolume(.70)
|
|
game.clear.SetVolume(.50)
|
|
game.beep.SetVolume(.50)
|
|
|
|
game.spawn()
|
|
|
|
ebiten.SetWindowSize(screenW, screenH)
|
|
ebiten.SetWindowTitle("💀")
|
|
|
|
return game, nil
|
|
}
|