commit a875a4ed574e80ecd5e79906a60b592ee5e19bf3 Author: MrDonuts Date: Sun Nov 19 20:53:35 2023 -0500 everything I got diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f52d4e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}" + } + ] +} diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..0050115 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/audio.go b/assets/audio.go new file mode 100644 index 0000000..b2a06de --- /dev/null +++ b/assets/audio.go @@ -0,0 +1,71 @@ +package assets + +import ( + "bytes" + "embed" + "fmt" + "io/fs" + "log" + + "github.com/hajimehoshi/ebiten/v2/audio" + "github.com/hajimehoshi/ebiten/v2/audio/wav" +) + +//go:embed * +//go:embed */* +var embeddedFiles embed.FS +var sampleRate = 44100 +var audioContext = audio.NewContext(sampleRate) +var songChoice = 2 +var SongTracks = loadTracksForSong(songChoice) + +func loadTracksForSong(sng int) map[int]*audio.Player { + tempMap := make(map[int]*audio.Player) + for i := 1; i <= 10; i++ { + tempMap[i] = LoadWavAudioFile(fmt.Sprintf("audio/song0%d/track%02d.wav", sng, i), true) + } + + return tempMap +} + +var SFXLibrary = map[string]*audio.Player{ + "newLevel": LoadWavAudioFile("audio/portal2.wav", false), + "colourMatch": LoadWavAudioFile("audio/portal.wav", false), + "gameOver": LoadWavAudioFile("audio/gameOver.wav", false), +} + +func LoadWavAudioFile(filePath string, loop bool) *audio.Player { + + audioData, err := fs.ReadFile(embeddedFiles, filePath) + if err != nil { + log.Fatal(err) + } + + stream, err := wav.DecodeWithSampleRate(sampleRate, bytes.NewReader(audioData)) + if err != nil { + log.Fatalf("failed to decode audio: %v", err) + } + + var player *audio.Player + + if loop { + lengthOfAudioData := int64(len(audioData)) + loopingStream := audio.NewInfiniteLoop(stream, lengthOfAudioData) + player, err = audioContext.NewPlayer(loopingStream) + + } else { + player, err = audioContext.NewPlayer(stream) + } + + if err != nil { + log.Fatal(err) + } + return player + +} + +func SilenceSongTracks() { + for _, song := range SongTracks { + song.SetVolume(0) + } +} diff --git a/assets/audio/.DS_Store b/assets/audio/.DS_Store new file mode 100644 index 0000000..c1e01e1 Binary files /dev/null and b/assets/audio/.DS_Store differ diff --git a/assets/audio/gameOver.wav b/assets/audio/gameOver.wav new file mode 100644 index 0000000..3b22b26 Binary files /dev/null and b/assets/audio/gameOver.wav differ diff --git a/assets/audio/portal.wav b/assets/audio/portal.wav new file mode 100644 index 0000000..5097c74 Binary files /dev/null and b/assets/audio/portal.wav differ diff --git a/assets/audio/portal2.wav b/assets/audio/portal2.wav new file mode 100644 index 0000000..84c03b6 Binary files /dev/null and b/assets/audio/portal2.wav differ diff --git a/assets/audio/song01/track01.wav b/assets/audio/song01/track01.wav new file mode 100644 index 0000000..63b37c1 Binary files /dev/null and b/assets/audio/song01/track01.wav differ diff --git a/assets/audio/song01/track02.wav b/assets/audio/song01/track02.wav new file mode 100644 index 0000000..3460d73 Binary files /dev/null and b/assets/audio/song01/track02.wav differ diff --git a/assets/audio/song01/track03.wav b/assets/audio/song01/track03.wav new file mode 100644 index 0000000..7d1429f Binary files /dev/null and b/assets/audio/song01/track03.wav differ diff --git a/assets/audio/song01/track04.wav b/assets/audio/song01/track04.wav new file mode 100644 index 0000000..0e51613 Binary files /dev/null and b/assets/audio/song01/track04.wav differ diff --git a/assets/audio/song01/track05.wav b/assets/audio/song01/track05.wav new file mode 100644 index 0000000..76c248b Binary files /dev/null and b/assets/audio/song01/track05.wav differ diff --git a/assets/audio/song01/track06.wav b/assets/audio/song01/track06.wav new file mode 100644 index 0000000..38ce3eb Binary files /dev/null and b/assets/audio/song01/track06.wav differ diff --git a/assets/audio/song01/track07.wav b/assets/audio/song01/track07.wav new file mode 100644 index 0000000..c354c9d Binary files /dev/null and b/assets/audio/song01/track07.wav differ diff --git a/assets/audio/song01/track08.wav b/assets/audio/song01/track08.wav new file mode 100644 index 0000000..0a2677d Binary files /dev/null and b/assets/audio/song01/track08.wav differ diff --git a/assets/audio/song01/track09.wav b/assets/audio/song01/track09.wav new file mode 100644 index 0000000..5a4ae16 Binary files /dev/null and b/assets/audio/song01/track09.wav differ diff --git a/assets/audio/song01/track10.wav b/assets/audio/song01/track10.wav new file mode 100644 index 0000000..8ccb4db Binary files /dev/null and b/assets/audio/song01/track10.wav differ diff --git a/assets/audio/song01/track11.wav b/assets/audio/song01/track11.wav new file mode 100644 index 0000000..c434d8e Binary files /dev/null and b/assets/audio/song01/track11.wav differ diff --git a/assets/audio/song01/track12.wav b/assets/audio/song01/track12.wav new file mode 100644 index 0000000..06a99e5 Binary files /dev/null and b/assets/audio/song01/track12.wav differ diff --git a/assets/audio/song01/track13.wav b/assets/audio/song01/track13.wav new file mode 100644 index 0000000..d51008b Binary files /dev/null and b/assets/audio/song01/track13.wav differ diff --git a/assets/audio/song02/.DS_Store b/assets/audio/song02/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/assets/audio/song02/.DS_Store differ diff --git a/assets/audio/song02/track01.wav b/assets/audio/song02/track01.wav new file mode 100644 index 0000000..a46a6de Binary files /dev/null and b/assets/audio/song02/track01.wav differ diff --git a/assets/audio/song02/track02.wav b/assets/audio/song02/track02.wav new file mode 100644 index 0000000..0337a8d Binary files /dev/null and b/assets/audio/song02/track02.wav differ diff --git a/assets/audio/song02/track03.wav b/assets/audio/song02/track03.wav new file mode 100644 index 0000000..6927455 Binary files /dev/null and b/assets/audio/song02/track03.wav differ diff --git a/assets/audio/song02/track04.wav b/assets/audio/song02/track04.wav new file mode 100644 index 0000000..7a66b29 Binary files /dev/null and b/assets/audio/song02/track04.wav differ diff --git a/assets/audio/song02/track05.wav b/assets/audio/song02/track05.wav new file mode 100644 index 0000000..2c55e4f Binary files /dev/null and b/assets/audio/song02/track05.wav differ diff --git a/assets/audio/song02/track06.wav b/assets/audio/song02/track06.wav new file mode 100644 index 0000000..92bd35c Binary files /dev/null and b/assets/audio/song02/track06.wav differ diff --git a/assets/audio/song02/track07.wav b/assets/audio/song02/track07.wav new file mode 100644 index 0000000..fa664f4 Binary files /dev/null and b/assets/audio/song02/track07.wav differ diff --git a/assets/audio/song02/track08.wav b/assets/audio/song02/track08.wav new file mode 100644 index 0000000..12b2b66 Binary files /dev/null and b/assets/audio/song02/track08.wav differ diff --git a/assets/audio/song02/track09.wav b/assets/audio/song02/track09.wav new file mode 100644 index 0000000..df16823 Binary files /dev/null and b/assets/audio/song02/track09.wav differ diff --git a/assets/audio/song02/track10.wav b/assets/audio/song02/track10.wav new file mode 100644 index 0000000..2fecaa5 Binary files /dev/null and b/assets/audio/song02/track10.wav differ diff --git a/assets/fonts.go b/assets/fonts.go new file mode 100644 index 0000000..6e3e77a --- /dev/null +++ b/assets/fonts.go @@ -0,0 +1,37 @@ +package assets + +import ( + "embed" + "log" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" +) + +//go:embed * +var FontFiles embed.FS +var fontFace font.Face + +func LoadFontFace(fileName string, size float64) font.Face { + fontBytes, err := FontFiles.ReadFile(fileName) + if err != nil { + log.Fatal(err) + } + + tt, err := opentype.Parse(fontBytes) + if err != nil { + log.Fatal(err) + } + + const dpi = 144 + fontFace, err = opentype.NewFace(tt, &opentype.FaceOptions{ + Size: size, + DPI: dpi, + Hinting: font.HintingNone, + }) + if err != nil { + log.Fatal(err) + } + + return fontFace +} diff --git a/assets/fonts/mechanical.otf b/assets/fonts/mechanical.otf new file mode 100644 index 0000000..6428023 Binary files /dev/null and b/assets/fonts/mechanical.otf differ diff --git a/assets/fonts/robot.otf b/assets/fonts/robot.otf new file mode 100644 index 0000000..b505ecf Binary files /dev/null and b/assets/fonts/robot.otf differ diff --git a/geometry/grid.go b/geometry/grid.go new file mode 100644 index 0000000..ce92cb7 --- /dev/null +++ b/geometry/grid.go @@ -0,0 +1,182 @@ +package geom + +import ( + //"fmt" + "image/color" + "math/rand" + + "github.com/hajimehoshi/ebiten/v2" +) + +var defaultSquareSpacing = 5 +var minBorder = 20 +var ScoreOffset = 50 +var HighScoreOffset = 10 +var GameGrid Grid +var MaxGridDimension = 20 + +type Grid struct { + squareSpacing int + squares [][]Square + startX int + startY int + squareSize int + coloredSquaresRemaining int +} + +// used for comparing two squares positions +type Pair struct { + rowNum int + colNum int +} + +func (pr Pair) equal(pr2 Pair) bool { + return pr.rowNum == pr2.rowNum && pr.colNum == pr2.colNum + +} + +func pairAlreadyExists(pr Pair, pairs []Pair) bool { + result := false + for _, nextPair := range pairs { + if pr.equal(nextPair) { + return true + } + } + + return result +} + +func (grd *Grid) NumSquares() int { + size := -1 + if len(grd.squares[0]) != 0 { + size = len(grd.squares) * len(grd.squares[0]) + } + return size +} + +// returns num rows by num columns +func (grd *Grid) Size() (int, int) { + if len(grd.squares[0]) != 0 { + return len(grd.squares), len(grd.squares[0]) + } + return 0, 0 +} + +func BuildGrid(numRows int, numColumns int) { + grd := Grid{squareSpacing: defaultSquareSpacing} + + winWidth, winHeight := ebiten.WindowSize() + sqSize := min((winWidth-2*minBorder-defaultSquareSpacing*numColumns)/numColumns, (winHeight-2*minBorder-ScoreOffset-3*HighScoreOffset-defaultSquareSpacing*numRows)/numRows) + sqSize = max(sqSize, 1) //ensure a nonzero, nonnegative square size + gridWidth := numColumns*(sqSize+grd.squareSpacing) - grd.squareSpacing + gridHeight := numRows*(sqSize+grd.squareSpacing) - grd.squareSpacing + + currentX := (winWidth - gridWidth) / 2 + currentY := ScoreOffset + (winHeight-gridHeight-ScoreOffset-HighScoreOffset)/2 + grd.startX = currentX + grd.startY = currentY + grd.squareSize = sqSize + + table := make([][]Square, numRows) + + for i := 0; i < numRows; i++ { + row := make([]Square, numColumns) + for j := 0; j < numColumns; j++ { + sq := MakeSquare(sqSize) + sq.SetPosition(currentX, currentY) + row[j] = sq + currentX += sqSize + grd.squareSpacing + } + table[i] = row + currentY += sqSize + grd.squareSpacing + currentX = grd.startX + } + + grd.squares = table + + //assign random colours to a subset of all squares + squaresToColour := min(numColumns, numRows) + + usedPositions := make([]Pair, squaresToColour) + coloredSoFar := 0 + for i := 0; i < squaresToColour; i++ { + colourIndex := rand.Intn(len(LightRGBMap)) + row := rand.Intn(numRows) + 1 + col := rand.Intn(numColumns) + 1 + //prevent a colour on the starting square + if row == numRows && col == 1 { + col = 2 + } + + //make sure we don't colour the same square twice + nextPair := Pair{rowNum: row, colNum: col} + if coloredSoFar == 0 { + usedPositions[0] = nextPair + coloredSoFar = 1 + } else { + for pairAlreadyExists(nextPair, usedPositions) { + row = rand.Intn(numRows) + 1 + col = rand.Intn(numColumns) + 1 + //prevent a colour on the starting square + if row == numRows && col == 1 { + col = 2 + } + nextPair = Pair{rowNum: row, colNum: col} + } + usedPositions[coloredSoFar] = nextPair + coloredSoFar += 1 + + } + + grd.ChangeColour(row, col, LightRGBMap[colourIndex]) + grd.GetSquareAt(row, col).RGBColourIndex = colourIndex + } + grd.coloredSquaresRemaining = squaresToColour + + GameGrid = grd +} + +func (grd *Grid) Draw(screen *ebiten.Image) { + opts := &ebiten.DrawImageOptions{} + + for _, rowOfSquares := range grd.squares { + for _, sq := range rowOfSquares { + xPos := float64(sq.X()) + yPos := float64(sq.Y()) + opts.GeoM.Translate(xPos, yPos) + screen.DrawImage(sq.Img, opts) + opts.GeoM.Reset() + } + } +} + +func (grd *Grid) DrawUpTo(finalSquareCount int, screen *ebiten.Image) { + opts := &ebiten.DrawImageOptions{} + curr := 1 + for _, rowOfSquares := range grd.squares { + for _, sq := range rowOfSquares { + xPos := float64(sq.X()) + yPos := float64(sq.Y()) + opts.GeoM.Translate(xPos, yPos) + screen.DrawImage(sq.Img, opts) + opts.GeoM.Reset() + if curr == finalSquareCount { + return + } + curr++ + } + } +} + +func (grd *Grid) ChangeColour(row int, column int, clr color.Color) { + sq := grd.squares[row-1][column-1] + sq.ChangeColour(clr) +} + +func (grd *Grid) GetSquareAt(row int, column int) *Square { + return &grd.squares[row-1][column-1] +} + +func (grd *Grid) ColouredSquaresRemaining() int { + return grd.coloredSquaresRemaining +} diff --git a/geometry/player.go b/geometry/player.go new file mode 100644 index 0000000..bd0445a --- /dev/null +++ b/geometry/player.go @@ -0,0 +1,131 @@ +package geom + +import ( + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "src.robn.tv/MrDonuts/RGB/assets" +) + +var MainPlayer *Player +var totalColors = 3 +var colorMap = map[int]color.Color{ + 0: RedFill, + 1: GreenFill, + 2: BlueFill, +} +var defaultStartingEnergy = 50 +var nextLevelEnergyJump = 30 +var CurrentEnergy = defaultStartingEnergy +var CurrentLevel = 1 + +type Player struct { + square Square + row int + col int + currColorIndex int +} + +func SetupPlayer() { + rows, _ := GameGrid.Size() + + //build default square + sq := MakeSquare(GameGrid.squareSize) + + //adjust to red as starting value + sq.ChangeColour(RedFill) + + //position on bottom left + gridSq := GameGrid.GetSquareAt(rows, 1) + sq.SetPosition(gridSq.x, gridSq.y) + + //setup struct for passing around + ply := Player{square: sq, row: rows, col: 1, currColorIndex: 0} + MainPlayer = &ply +} + +func (p *Player) Update() { + + moveOccurs := false + + if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) { + rows, _ := GameGrid.Size() + if p.row < rows { + p.row += 1 + p.square.moveDownOnGrid() + moveOccurs = true + } + + } + + if inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) { + _, cols := GameGrid.Size() + if p.col < cols { + p.col += 1 + p.square.moveRightOnGrid() + moveOccurs = true + } + + } + + if inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) { + if p.col > 1 { + p.col -= 1 + p.square.moveLeftOnGrid() + moveOccurs = true + } + } + + if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) { + if p.row > 1 { + p.row -= 1 + p.square.moveUpOnGrid() + moveOccurs = true + } + } + + if moveOccurs { + moveOccurs = false + + p.NextColor() + + // check if player is on same colour square + gridSquare := GameGrid.GetSquareAt(p.row, p.col) + if p.currColorIndex == gridSquare.RGBColourIndex { + GameGrid.coloredSquaresRemaining -= 1 + gridSquare.RGBColourIndex = -1 + gridSquare.ChangeColour(GrayFill) + if GameGrid.coloredSquaresRemaining != 0 { + assets.SFXLibrary["colourMatch"].Rewind() + assets.SFXLibrary["colourMatch"].Play() + } + + } + + // reduce energy + CurrentEnergy -= 1 + } + +} + +func (p *Player) Draw(screen *ebiten.Image) { + opts := &ebiten.DrawImageOptions{} + opts.GeoM.Translate(float64(p.square.x), float64(p.square.y)) + screen.DrawImage(p.square.Img, opts) +} + +func (p *Player) NextColor() { + p.currColorIndex = (p.currColorIndex + 1) % totalColors + p.square.ChangeColour(colorMap[p.currColorIndex]) +} + +func ResetPlayerValues() { + CurrentEnergy = defaultStartingEnergy + CurrentLevel = 1 +} + +func PrepareForNextLevel() { + CurrentEnergy += nextLevelEnergyJump + CurrentLevel += 1 +} diff --git a/geometry/square.go b/geometry/square.go new file mode 100644 index 0000000..f390f53 --- /dev/null +++ b/geometry/square.go @@ -0,0 +1,93 @@ +package geom + +import ( + "image/color" + + "github.com/hajimehoshi/ebiten/v2" +) + +var GrayFill = color.RGBA{R: 128, G: 128, B: 128, A: 255} +var RedFill = color.RGBA{R: 200, G: 0, B: 0, A: 255} +var GreenFill = color.RGBA{R: 0, G: 200, B: 0, A: 255} +var BlueFill = color.RGBA{R: 0, G: 0, B: 200, A: 255} +var LightRedFill = color.RGBA{R: 255, G: 104, B: 103, A: 255} +var LightGreenFill = color.RGBA{R: 144, G: 255, B: 143, A: 255} +var LightBlueFill = color.RGBA{R: 150, G: 150, B: 255, A: 255} + +var RGBColourMap = map[int]color.Color{ + 0: RedFill, + 1: GreenFill, + 2: BlueFill, +} + +var AllColourMap = map[int]color.Color{ + 0: GrayFill, + 1: RedFill, + 2: GreenFill, + 3: BlueFill, + 4: LightRedFill, + 5: LightBlueFill, + 6: LightGreenFill, +} + +var LightRGBMap = map[int]color.Color{ + 0: LightRedFill, + 1: LightGreenFill, + 2: LightBlueFill, +} + +type Square struct { + x int + y int + clr color.Color + width int + height int + Img *ebiten.Image + RGBColourIndex int +} + +func MakeSquare(size int) Square { + image := ebiten.NewImage(size, size) + defaultColor := color.RGBA{R: 128, G: 128, B: 128, A: 255} + image.Fill(defaultColor) + sq := Square{clr: defaultColor, Img: image, width: size, height: size, x: 0, y: 0, RGBColourIndex: -1} + return sq +} + +func (sq *Square) ChangeColour(newColor color.Color) { + sq.clr = newColor + sq.Img.Fill(newColor) +} + +func (sq *Square) SetPosition(x int, y int) { + sq.x = x + sq.y = y +} + +func (sq *Square) X() int { + return sq.x +} + +func (sq *Square) Y() int { + return sq.y +} + +func (sq *Square) moveRightOnGrid() { + newX := sq.x + GameGrid.squareSize + GameGrid.squareSpacing + sq.x = newX +} + +func (sq *Square) moveLeftOnGrid() { + newX := sq.x - GameGrid.squareSize - GameGrid.squareSpacing + sq.x = newX +} + +func (sq *Square) moveDownOnGrid() { + newY := sq.y + GameGrid.squareSize + GameGrid.squareSpacing + sq.y = newY +} + +func (sq *Square) moveUpOnGrid() { + newY := sq.y - GameGrid.squareSize - GameGrid.squareSpacing + sq.y = newY +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..27d9081 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module src.robn.tv/MrDonuts/RGB + +go 1.21.3 + +require github.com/hajimehoshi/ebiten/v2 v2.6.2 + +require ( + github.com/ebitengine/oto/v3 v3.1.0 // indirect + github.com/ebitengine/purego v0.5.0 // indirect + github.com/jezek/xgb v1.1.0 // indirect + golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/image v0.14.0 + golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..45fbaa3 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/ebitengine/oto/v3 v3.1.0 h1:9tChG6rizyeR2w3vsygTTTVVJ9QMMyu00m2yBOCch6U= +github.com/ebitengine/oto/v3 v3.1.0/go.mod h1:IK1QTnlfZK2GIB6ziyECm433hAdTaPpOsGMLhEyEGTg= +github.com/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo= +github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4= +github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA= +github.com/hajimehoshi/ebiten/v2 v2.6.2 h1:tVa3ZJbp4Uz/VSjmpgtQIOvwd7aQH290XehHBLr2iWk= +github.com/hajimehoshi/ebiten/v2 v2.6.2/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI= +github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= +github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg= +golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 h1:Q6NT8ckDYNcwmi/bmxe+XbiDMXqMRW1xFBtJ+bIpie4= +golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/main.go b/main.go new file mode 100644 index 0000000..852f4d0 --- /dev/null +++ b/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + + "github.com/hajimehoshi/ebiten/v2" + + "src.robn.tv/MrDonuts/RGB/assets" + geom "src.robn.tv/MrDonuts/RGB/geometry" + "src.robn.tv/MrDonuts/RGB/states" + score "src.robn.tv/MrDonuts/RGB/tools" +) + +func init() { + + numRows := 3 + numColumns := 6 + geom.BuildGrid(numRows, numColumns) + + states.SetupStateMachine() + + //some audio testing + for i, song := range assets.SongTracks { + song.Play() + if i != 1 { + song.SetVolume(0) + } else { + song.SetVolume(0.7) + } + } + +} + +type Game struct{} + +func (g *Game) Update() error { + + states.SM.Update() + score.ScoreMngr.Update() + + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + + states.SM.Draw(screen) + +} + +func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { + return 640, 480 +} + +func main() { + ebiten.SetWindowSize(640, 480) + ebiten.SetWindowTitle("RGB") + if err := ebiten.RunGame(&Game{}); err != nil { + log.Fatal(err) + } +} diff --git a/states/gameOverState.go b/states/gameOverState.go new file mode 100644 index 0000000..7f1098f --- /dev/null +++ b/states/gameOverState.go @@ -0,0 +1,57 @@ +package states + +import ( + "fmt" + "image/color" + "math/rand" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/hajimehoshi/ebiten/v2/text" + "golang.org/x/image/font" + "src.robn.tv/MrDonuts/RGB/assets" + geom "src.robn.tv/MrDonuts/RGB/geometry" +) + +type GameOverState struct { +} + +func (st *GameOverState) Enter() { + fmt.Println("Entering Game Over State") + assets.SilenceSongTracks() + assets.SFXLibrary["gameOver"].Rewind() + assets.SFXLibrary["gameOver"].Play() + +} + +func (st *GameOverState) Exit() { + fmt.Println("Exiting Game OverState") + +} + +func (st *GameOverState) Update() { + if inpututil.IsKeyJustPressed(ebiten.KeyN) { + geom.ResetPlayerValues() + geom.BuildGrid(rand.Intn(5+geom.CurrentLevel)+2, rand.Intn(5+geom.CurrentLevel)+2) + CurrTrack = 1 + assets.SongTracks[CurrTrack].SetVolume(0.7) //turn up track 1 + SM.Transition("load") + + } + +} + +func (st *GameOverState) Draw(screen *ebiten.Image) { + w, h := ebiten.WindowSize() + + gameOverStr := "Game Over" + face := assets.LoadFontFace("fonts/robot.otf", 30) + stringWidth := font.MeasureString(face, gameOverStr).Ceil() + text.Draw(screen, gameOverStr, face, w/2-stringWidth/2, h/2, color.White) + + newGameStr := "(press N for new game)" + face = assets.LoadFontFace("fonts/robot.otf", 12) + stringWidth = font.MeasureString(face, newGameStr).Ceil() + text.Draw(screen, newGameStr, face, w/2-stringWidth/2, h-int(0.1*float64(h)), color.White) + +} diff --git a/states/loadLevelState.go b/states/loadLevelState.go new file mode 100644 index 0000000..2d23c87 --- /dev/null +++ b/states/loadLevelState.go @@ -0,0 +1,52 @@ +package states + +import ( + "fmt" + "math" + + "github.com/hajimehoshi/ebiten/v2" + geom "src.robn.tv/MrDonuts/RGB/geometry" + score "src.robn.tv/MrDonuts/RGB/tools" +) + +var currentEnd = 1 //used for animating squares +var secondsPerUpdate = 0.02 +var ticksPerSecond = 60.0 +var deltaSeconds = 1.0 / ticksPerSecond +var secondsCounter = 0.0 + +type LoadLevelState struct { +} + +func (s *LoadLevelState) Enter() { + fmt.Println("Entering Loading State") + currentEnd = 1 +} + +func (s *LoadLevelState) Exit() { + fmt.Println("Exiting Loading State") +} + +func (s *LoadLevelState) Update() { + secondsCounter += deltaSeconds + + //determine how much to jump by on each draw + totalSquares := geom.GameGrid.NumSquares() + jumpAmount := math.Ceil(float64(totalSquares) / ticksPerSecond) + if secondsCounter >= float64(secondsPerUpdate) { + secondsCounter = 0 + currentEnd += int(jumpAmount) + } + + if currentEnd > geom.GameGrid.NumSquares() { + SM.Transition("play") + + } + +} + +func (s *LoadLevelState) Draw(screen *ebiten.Image) { + + geom.GameGrid.DrawUpTo(currentEnd, screen) + score.ScoreMngr.Draw(screen) +} diff --git a/states/playLevelState.go b/states/playLevelState.go new file mode 100644 index 0000000..884f79f --- /dev/null +++ b/states/playLevelState.go @@ -0,0 +1,71 @@ +package states + +import ( + "fmt" + "math/rand" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "src.robn.tv/MrDonuts/RGB/assets" + geom "src.robn.tv/MrDonuts/RGB/geometry" + score "src.robn.tv/MrDonuts/RGB/tools" +) + +var CurrTrack = 1 + +type PlayLevelState struct { +} + +func (s *PlayLevelState) Enter() { + fmt.Println("Entering Play State") + geom.SetupPlayer() + fmt.Println(geom.GameGrid.ColouredSquaresRemaining()) + +} + +func (s *PlayLevelState) Exit() { + fmt.Println("Exiting Play State") + + geom.PrepareForNextLevel() + + if CurrTrack < len(assets.SongTracks) { + if geom.CurrentLevel%3 == 0 { + CurrTrack++ + assets.SongTracks[CurrTrack].SetVolume(0.7) + } + } + +} + +func (s *PlayLevelState) Update() { + + if inpututil.IsKeyJustPressed(ebiten.KeyN) { + newRowSize := min(rand.Intn(5+geom.CurrentLevel)+2, geom.MaxGridDimension) + newColSize := min(rand.Intn(5+geom.CurrentLevel)+2, geom.MaxGridDimension) + geom.BuildGrid(newRowSize, newColSize) + SM.Transition("load") + assets.SFXLibrary["newLevel"].Rewind() + assets.SFXLibrary["newLevel"].Play() + } + + geom.MainPlayer.Update() + if geom.CurrentEnergy == 0 { + SM.Transition("game over") + } + if geom.GameGrid.ColouredSquaresRemaining() == 0 { + //go to next level + newRowSize := min(rand.Intn(5+geom.CurrentLevel)+2, geom.MaxGridDimension) + newColSize := min(rand.Intn(5+geom.CurrentLevel)+2, geom.MaxGridDimension) + geom.BuildGrid(newRowSize, newColSize) + SM.Transition("load") + assets.SFXLibrary["newLevel"].Rewind() + assets.SFXLibrary["newLevel"].Play() + } + +} + +func (s *PlayLevelState) Draw(screen *ebiten.Image) { + geom.GameGrid.Draw(screen) + geom.MainPlayer.Draw(screen) + score.ScoreMngr.Draw(screen) +} diff --git a/states/statemachine.go b/states/statemachine.go new file mode 100644 index 0000000..faa9452 --- /dev/null +++ b/states/statemachine.go @@ -0,0 +1,54 @@ +package states + +import ( + "github.com/hajimehoshi/ebiten/v2" +) + +var SM StateMachine +var StateMap map[string]State + +type State interface { + Enter() + Exit() + Update() + Draw(screen *ebiten.Image) +} + +type StateMachine struct { + CurrentState State +} + +func NewStateMachine(initialState State) *StateMachine { + sm := &StateMachine{CurrentState: initialState} + sm.CurrentState.Enter() + return sm +} + +func (sm *StateMachine) Transition(newState string) { + sm.CurrentState.Exit() + sm.CurrentState = StateMap[newState] + sm.CurrentState.Enter() +} + +func (sm *StateMachine) Update() { + sm.CurrentState.Update() +} + +func (sm *StateMachine) Draw(screen *ebiten.Image) { + sm.CurrentState.Draw(screen) +} + +func SetupStateMachine() { + + loadState := &LoadLevelState{} + playState := &PlayLevelState{} + gameOverState := &GameOverState{} + + StateMap = make(map[string]State) + StateMap["load"] = loadState + StateMap["play"] = playState + StateMap["game over"] = gameOverState + + SM = *NewStateMachine(loadState) + +} diff --git a/tools/scoreManager.go b/tools/scoreManager.go new file mode 100644 index 0000000..f5a566f --- /dev/null +++ b/tools/scoreManager.go @@ -0,0 +1,45 @@ +package score + +import ( + "fmt" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/text" + "golang.org/x/image/font" + + //"golang.org/x/image/font/gofont/gobold" + "src.robn.tv/MrDonuts/RGB/assets" + geom "src.robn.tv/MrDonuts/RGB/geometry" +) + +var ScoreMngr ScoreManager + +type ScoreManager struct{} + +func (sm *ScoreManager) Update() { +} + +func (sm *ScoreManager) Draw(screen *ebiten.Image) { + + w, h := ebiten.WindowSize() + + //highScoreStr := fmt.Sprintf("High Score: %v", highScore) + levelStr := fmt.Sprintf("Level: %v", geom.CurrentLevel) + face := assets.LoadFontFace("fonts/robot.otf", 12) + + //draw high score (i.e. best level) + //stringWidth := font.MeasureString(face, highScoreStr).Ceil() + //text.Draw(screen, highScoreStr, face, w-stringWidth-geom.HighScoreOffset, h-geom.HighScoreOffset, color.White) + + //draw current level + //stringWidth = font.MeasureString(face, levelStr).Ceil() + text.Draw(screen, levelStr, face, geom.HighScoreOffset, h-geom.HighScoreOffset, color.White) + + //draw energy at the top + energyStr := fmt.Sprintf("Energy: %v", geom.CurrentEnergy) + face = assets.LoadFontFace("fonts/robot.otf", 20) + stringWidth := font.MeasureString(face, energyStr).Ceil() + text.Draw(screen, energyStr, face, w/2-stringWidth/2, geom.ScoreOffset, color.White) + +}