This commit is contained in:
rootVIII 2021-05-24 23:21:01 -04:00
commit a0ff055379
17 changed files with 937 additions and 0 deletions

79
README.md Normal file
View file

@ -0,0 +1,79 @@
### Skulls is simple Columns-like strategy game developed in Golang with the Ebiten library (for Android)
All development/debugging was done with the <b>gomobile</b> tool and <b>adb</b>.
###### DEVELOPMENT: Local testing environment
<pre>
<code>
// Navigate to skulls/ and generate a <code>.apk</code> with skullsgomobile/:
gomobile build -target=android github.com/rootVIII/skulls/skullsgomobile
// Install the newly created .apk into an already running Android Emulator (from Android Studio):
adb -s emulator-5554 install skullsgomobile.apk
// view logging output from the game:
adb logcat
// Note that I use a pixel4 emulator. I have an alias stored in my profile to open it easily via terminal:
alias pixel4='$ANDROID_HOME/emulator/emulator -avd "Pixel_4_API_30"'
</code>
</pre>
###### PRODUCTION: Build for Android Studio
<pre>
<code>
// Navigate to skulls/ and generate a .aar with skullsebitenbind/
ebitenmobile bind -target android -javapkg com.solsticenet.skulls -o skulls.aar github.com/rootVIII/skulls/skullsebitenbind
// Create a new project in Android Studio named SkullsMobile.
// Click File, New Module, import .jar/.aar, and locate the newly created skulls.aar file.
// Add the following line into dependencies{} within app/build.gradle and then clean the project:
compile project(':skulls')
// Place the following code inside of MainActivity.java:
package com.solsticenet.skullsmobile;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import go.Seq;
import com.solsticenet.skulls.skullsebitenbind.EbitenView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Seq.setContext(getApplicationContext());
}
private EbitenView getEbitenView() {
return (EbitenView)this.findViewById(R.id.ebitenview);
}
@Override
protected void onPause() {
super.onPause();
this.getEbitenView().suspendGame();
}
@Override
protected void onResume() {
super.onResume();
this.getEbitenView().resumeGame();
}
}
</code>
</pre>
This was developed on macOS Big Sur.
<hr>
<b>Author: rootVIII 2021</b>
<br><br>

5
assets/background.go Normal file

File diff suppressed because one or more lines are too long

4
assets/beep.go Normal file

File diff suppressed because one or more lines are too long

4
assets/blueskull.go Normal file

File diff suppressed because one or more lines are too long

4
assets/clear.go Normal file

File diff suppressed because one or more lines are too long

4
assets/explosion.go Normal file

File diff suppressed because one or more lines are too long

4
assets/greenskull.go Normal file

File diff suppressed because one or more lines are too long

5
assets/intro.go Normal file

File diff suppressed because one or more lines are too long

4
assets/purpleskull.go Normal file

File diff suppressed because one or more lines are too long

4
assets/radioland.go Normal file

File diff suppressed because one or more lines are too long

4
assets/redskull.go Normal file

File diff suppressed because one or more lines are too long

4
assets/themesong.go Normal file

File diff suppressed because one or more lines are too long

9
go.mod Normal file
View file

@ -0,0 +1,9 @@
module github.com/rootVIII/skulls
go 1.16
require (
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff
github.com/hajimehoshi/ebiten/v2 v2.0.6
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
)

67
go.sum Normal file
View file

@ -0,0 +1,67 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2 h1:Ac1OEHHkbAZ6EUnJahF0GKcU0FjPc/V8F1DvjhKngFE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
github.com/hajimehoshi/ebiten/v2 v2.0.6 h1:sHNymgI+q80xasP69oFyrpup6r2qCNsKxqwsGEh6PWE=
github.com/hajimehoshi/ebiten/v2 v2.0.6/go.mod h1:uS3OjMW3f2DRDMtWoIF7yMMmrMkv+fZ6pXcwR1pfA0Y=
github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao=
github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.6.8 h1:yRb3EJQ4lAkBgZYheqmdH6Lr77RV9nSWFsK/jwWdTNY=
github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f h1:aEcjdTsycgPqO/caTgnxfR9xwWOltP/21vtJyFztEy0=
golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201009162240-fcf82128ed91/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

715
skulls.go Normal file
View file

@ -0,0 +1,715 @@
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
}
// Play is the entry point to the game.
func Play() (*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)
// for _, item := range game.skullCollector {
// fmt.Printf("%v\n", item)
// }
// for _, item := range game.skullCoords {
// fmt.Printf("%v\n", item)
// }
game.spawn()
ebiten.SetWindowSize(screenW, screenH)
ebiten.SetWindowTitle("💀")
if err := ebiten.RunGame(game); err != nil {
return nil, err
}
return game, nil
}

1
skullsgomobile/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.log

20
skullsgomobile/main.go Normal file
View file

@ -0,0 +1,20 @@
package main
import (
_ "image/jpeg"
_ "image/png"
"log"
"os"
"github.com/rootVIII/skulls"
)
func main() {
_, err := skulls.Play()
if err != nil {
logf, _ := os.OpenFile("SKULLS-ERROR.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
log.SetOutput(logf)
log.Println("** An error occurred during startup **")
log.Fatal(err)
}
}