commit d22fa6e1896f6a55b93d985c16e6700035541955 Author: TheEdgeOfRage Date: Sun Jan 28 00:36:09 2024 +0100 Initial commit diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..097c831 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/gen2brain/raylib-go/raylib" + + "gitea.theedgeofrage.com/theedgeofrage/yeetris/game" +) + +func main() { + game := game.Game{} + game.Init() + + rl.SetTargetFPS(60) + + for !rl.WindowShouldClose() { + game.Update() + game.Draw() + } + + rl.CloseWindow() +} diff --git a/elements/block.go b/elements/block.go new file mode 100644 index 0000000..f0f30cd --- /dev/null +++ b/elements/block.go @@ -0,0 +1,18 @@ +package elements + +import "github.com/gen2brain/raylib-go/raylib" + +type Block struct { + Position rl.Vector2 + Color rl.Color + LongBoi bool +} + +func (b *Block) Draw(piecePosition rl.Vector2, scale rl.Vector2, offset rl.Vector2) { + basePos := rl.Vector2Add(b.Position, piecePosition) + if basePos.Y < 0 { + return + } + pos := rl.Vector2Add(rl.Vector2Multiply(basePos, scale), offset) + rl.DrawRectangleV(pos, scale, b.Color) +} diff --git a/elements/colors.go b/elements/colors.go new file mode 100644 index 0000000..6a62802 --- /dev/null +++ b/elements/colors.go @@ -0,0 +1,17 @@ +package elements + +import "github.com/gen2brain/raylib-go/raylib" + +var ( + Red = rl.GetColor(0xcc241dff) + Blue = rl.GetColor(0x458588ff) + Green = rl.GetColor(0x98971aff) + Yellow = rl.GetColor(0xfabd2fff) + Purple = rl.GetColor(0xb16286ff) + Orange = rl.GetColor(0xd65d0eff) + Cyan = rl.GetColor(0x689d6aff) + Black = rl.GetColor(0x282828ff) + Gray = rl.GetColor(0x928374ff) + LightGray = rl.GetColor(0xa89984ff) + White = rl.GetColor(0xebdbb2ff) +) diff --git a/elements/pieces.go b/elements/pieces.go new file mode 100644 index 0000000..9589e46 --- /dev/null +++ b/elements/pieces.go @@ -0,0 +1,74 @@ +package elements + +import ( + "github.com/gen2brain/raylib-go/raylib" +) + +type Piece struct { + Blocks []*Block +} + +var Pieces = []*Piece{ + { + Blocks: []*Block{ + {rl.NewVector2(-1, 0), Cyan, false}, + {rl.NewVector2(0, 0), Cyan, false}, + {rl.NewVector2(1, 0), Cyan, true}, + {rl.NewVector2(2, 0), Cyan, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(-1, 0), Blue, false}, + {rl.NewVector2(-1, -1), Blue, false}, + {rl.NewVector2(0, 0), Blue, false}, + {rl.NewVector2(1, 0), Blue, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(-1, 0), Orange, false}, + {rl.NewVector2(0, 0), Orange, false}, + {rl.NewVector2(1, 0), Orange, false}, + {rl.NewVector2(1, -1), Orange, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(-1, 0), Purple, false}, + {rl.NewVector2(0, 0), Purple, false}, + {rl.NewVector2(1, 0), Purple, false}, + {rl.NewVector2(0, -1), Purple, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(-1, -1), Red, false}, + {rl.NewVector2(0, -1), Red, false}, + {rl.NewVector2(0, 0), Red, false}, + {rl.NewVector2(1, 0), Red, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(-1, 0), Green, false}, + {rl.NewVector2(0, 0), Green, false}, + {rl.NewVector2(0, -1), Green, false}, + {rl.NewVector2(1, -1), Green, false}, + }, + }, + { + Blocks: []*Block{ + {rl.NewVector2(0, -1), Yellow, false}, + {rl.NewVector2(1, -1), Yellow, false}, + {rl.NewVector2(0, 0), Yellow, false}, + {rl.NewVector2(1, 0), Yellow, false}, + }, + }, +} + +func (p *Piece) Draw(pos rl.Vector2, scale rl.Vector2, offset rl.Vector2) { + for _, block := range p.Blocks { + block.Draw(pos, scale, offset) + } +} diff --git a/game/board.go b/game/board.go new file mode 100644 index 0000000..6d4768b --- /dev/null +++ b/game/board.go @@ -0,0 +1,262 @@ +package game + +import ( + "math" + + "gitea.theedgeofrage.com/theedgeofrage/yeetris/elements" + "github.com/gen2brain/raylib-go/raylib" +) + +type Board interface { + DescendActivePiece() bool + DropActivePiece() bool + MoveActivePiece(direction int) + RotateActivePiece(clockwise bool) + ClearLines() + Draw(offset, scale rl.Vector2) +} + +type board struct { + Board [10][20]*elements.Block + + ActivePiece *elements.Piece + ActivePiecePos rl.Vector2 + NextPiece *elements.Piece +} + +var _ Board = (*board)(nil) + +func InitBoard() *board { + board := &board{} + board.newActivePiece() + + return board +} + +func (b *board) newActivePiece() bool { + b.ActivePiecePos = rl.NewVector2(5, 0) + b.ActivePiece = elements.Pieces[rl.GetRandomValue(0, int32(len(elements.Pieces)-1))] + for _, block := range b.ActivePiece.Blocks { + newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y + if newY >= 0 && b.Board[int(newX)][int(newY)] != nil { + return true + } + } + return false +} + +func (b *board) setBlocksFromActivePiece() { + for _, block := range b.ActivePiece.Blocks { + newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y + b.Board[int(newX)][int(newY)] = &elements.Block{ + Position: rl.NewVector2(newX, newY), + Color: b.ActivePiece.Blocks[0].Color, + } + } +} + +func (b *board) checkHorizontalCollision(left bool) bool { + for _, activeBlock := range b.ActivePiece.Blocks { + xPos := int(b.ActivePiecePos.X + activeBlock.Position.X) + yPos := int(b.ActivePiecePos.Y + activeBlock.Position.Y) + if left { + if xPos == 0 { + return true + } + if yPos >= 0 && b.Board[xPos-1][yPos] != nil { + return true + } + } else { + if xPos == 9 { + return true + } + if yPos >= 0 && b.Board[xPos+1][yPos] != nil { + return true + } + } + } + return false +} + +func (b *board) descendActivePiece() bool { + b.ActivePiecePos.Y += 1 + for _, activeBlock := range b.ActivePiece.Blocks { + if b.ActivePiecePos.Y+activeBlock.Position.Y == 20 { + b.ActivePiecePos.Y -= 1 + b.setBlocksFromActivePiece() + return true + } + + if b.Board[int(b.ActivePiecePos.X+activeBlock.Position.X)][int(b.ActivePiecePos.Y+activeBlock.Position.Y)] != nil { + b.ActivePiecePos.Y -= 1 + b.setBlocksFromActivePiece() + return true + } + } + return false +} + +// DescendActivePiece moves the piece down by one block. If the piece cannot move down, it will be placed on the board. +// After that, a new piece will be generated. If the new piece cannot be placed, true is returned. +func (b *board) DescendActivePiece() bool { + if b.descendActivePiece() { + return b.newActivePiece() + } + return false +} + +// DropActivePiece moves the piece down until it cannot move down anymore. After that, a new piece will be generated. +// If the new piece cannot be placed, true is returned. +func (b *board) DropActivePiece() bool { + for !b.descendActivePiece() { + } + return b.newActivePiece() +} + +// MoveActivePiece moves the piece left or right. If the piece cannot move in the specified direction, nothing happens. +func (b *board) MoveActivePiece(direction int) { + if !b.checkHorizontalCollision(direction == -1) { + b.ActivePiecePos.X += float32(direction) + } +} + +func getCollisionDepth(collisionDepth int, longBoi bool) int { + if longBoi || collisionDepth == 2 { + return 2 + } else { + return 1 + } +} + +// RotateActivePiece rotates the piece clockwise or counter-clockwise. If the piece cannot rotate in the specified +// direction, nothing happens. +func (b *board) RotateActivePiece(clockwise bool) { + if b.ActivePiece.Blocks[0].Color == elements.Yellow { + return + } + + angle := rl.Deg2rad * 90 + if !clockwise { + angle = -angle + } + + tmpPiece := &elements.Piece{ + Blocks: make([]*elements.Block, 4), + } + for i := 0; i < 4; i++ { + newPos := rl.Vector2Rotate(b.ActivePiece.Blocks[i].Position, float32(angle)) + newPos.X = float32(math.Round(float64(newPos.X))) + newPos.Y = float32(math.Round(float64(newPos.Y))) + tmpPiece.Blocks[i] = &elements.Block{ + Position: newPos, + Color: b.ActivePiece.Blocks[i].Color, + LongBoi: b.ActivePiece.Blocks[i].LongBoi, + } + } + collisionLeft := 0 + collisionRight := 0 + collisionBottom := 0 + for _, block := range tmpPiece.Blocks { + newX, newY := b.ActivePiecePos.X+block.Position.X, b.ActivePiecePos.Y+block.Position.Y + if newY < 0 { + continue + } + if newX < 0 && collisionLeft == 0 { + collisionLeft = getCollisionDepth(collisionLeft, block.LongBoi) + } + if newX > 9 && collisionRight == 0 { + collisionRight = getCollisionDepth(collisionRight, block.LongBoi) + } + if newY > 19 && collisionBottom == 0 { + collisionBottom = getCollisionDepth(collisionBottom, block.LongBoi) + } + if collisionLeft != 0 || collisionRight != 0 || collisionBottom != 0 { + continue + } + if b.Board[int(newX)][int(newY)] != nil { + if newX < b.ActivePiecePos.X && collisionLeft == 0 { + collisionLeft = getCollisionDepth(collisionLeft, block.LongBoi) + } + if newX > b.ActivePiecePos.X && collisionRight == 0 { + collisionRight = getCollisionDepth(collisionRight, block.LongBoi) + } + if newY > b.ActivePiecePos.Y && collisionBottom == 0 { + collisionBottom = getCollisionDepth(collisionBottom, block.LongBoi) + } + } + } + if collisionLeft != 0 && collisionRight != 0 { + return + } + + b.ActivePiecePos.Y -= float32(collisionBottom) + b.ActivePiecePos.X += float32(collisionLeft) + b.ActivePiecePos.X -= float32(collisionRight) + b.ActivePiece = tmpPiece +} + +// ClearLines clears all lines that are completely filled. +func (g *board) ClearLines() { + for y := 19; y >= 0; { + skip := false + for x := 0; x < 10; x++ { + if g.Board[x][y] == nil { + skip = true + break + } + } + if skip { + y-- + continue + } + for ty := y; ty > 0; ty-- { + for x := 0; x < 10; x++ { + if g.Board[x][ty-1] != nil { + g.Board[x][ty] = g.Board[x][ty-1] + g.Board[x][ty].Position.Y++ + } else { + g.Board[x][ty] = nil + } + } + } + for x := 0; x < 10; x++ { + g.Board[x][0] = nil + } + } +} + +func (b *board) drawGrid(offset, scale rl.Vector2) { + for i := 0; i < 10; i++ { + rl.DrawLine( + int32(scale.X*float32(i)+offset.X), + int32(offset.Y), + int32(scale.X*float32(i)+offset.X), + int32(20*scale.Y+offset.Y), + elements.Gray, + ) + } + for i := 0; i < 20; i++ { + rl.DrawLine( + int32(offset.X), + int32(scale.Y*float32(i)+offset.Y), + int32(10*scale.X+offset.X), + int32(scale.Y*float32(i)+offset.Y), + elements.Gray, + ) + } +} + +// Draw draws the board with placed blocks and the active piece. +func (b *board) Draw(offset rl.Vector2, scale rl.Vector2) { + b.drawGrid(offset, scale) + for i := 0; i < 20; i++ { + for j := 0; j < 10; j++ { + if b.Board[j][i] == nil { + continue + } + + b.Board[j][i].Draw(rl.NewVector2(0, 0), scale, offset) + } + } + b.ActivePiece.Draw(b.ActivePiecePos, scale, offset) +} diff --git a/game/game.go b/game/game.go new file mode 100644 index 0000000..ece776e --- /dev/null +++ b/game/game.go @@ -0,0 +1,92 @@ +package game + +import ( + "gitea.theedgeofrage.com/theedgeofrage/yeetris/elements" + "github.com/gen2brain/raylib-go/raylib" +) + +const DEBUG = false + +// Game type +type Game struct { + FramesCounter int32 + TickRate int32 + GameOver bool + Pause bool + + Scale rl.Vector2 + + Board Board + UI *UI +} + +// Init - Initialize game +func (g *Game) Init() { + g.Scale = rl.NewVector2(40, 40) + + g.FramesCounter = 0 + g.TickRate = 30 + g.GameOver = false + g.Pause = false + g.Board = InitBoard() + g.UI = InitUI(g.Scale) +} + +func (g *Game) Update() { + defer g.Board.ClearLines() + if g.GameOver { + if rl.IsKeyPressed(rl.KeyEnter) { + g.Init() + g.GameOver = false + } + return + } + if rl.IsKeyPressed(rl.KeyP) { + g.Pause = !g.Pause + } + + if g.Pause { + return + } + + if rl.IsKeyPressed(rl.KeyI) { + g.Board.MoveActivePiece(1) + } + if rl.IsKeyPressed(rl.KeyH) { + g.Board.MoveActivePiece(-1) + } + if rl.IsKeyPressed(rl.KeyE) { + g.Board.RotateActivePiece(true) + } + if rl.IsKeyPressed(rl.KeyN) { + g.Board.DescendActivePiece() + } + if rl.IsKeyPressed(rl.KeySpace) { + g.Board.DropActivePiece() + } + if g.FramesCounter%g.TickRate == 0 && !DEBUG { + g.Board.DescendActivePiece() + } + g.FramesCounter++ +} + +func (g *Game) Draw() { + rl.BeginDrawing() + rl.ClearBackground(elements.Black) + + g.UI.Draw(g) + + if !g.GameOver { + g.Board.Draw(g.UI.BoardOffset, g.Scale) + } else { + rl.DrawText( + "PRESS [ENTER] TO PLAY AGAIN", + int32(rl.GetScreenWidth())/2-rl.MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, + int32(rl.GetScreenHeight())/2-50, + 20, + elements.LightGray, + ) + } + + rl.EndDrawing() +} diff --git a/game/ui.go b/game/ui.go new file mode 100644 index 0000000..f8add3a --- /dev/null +++ b/game/ui.go @@ -0,0 +1,56 @@ +package game + +import ( + // "fmt" + + "gitea.theedgeofrage.com/theedgeofrage/yeetris/elements" + "github.com/gen2brain/raylib-go/raylib" +) + +type UI struct { + ScreenWidth int32 + ScreenHeight int32 + BoardWidth int32 + BoardHeight int32 + BoardOffset rl.Vector2 + UIOffsetWidth int32 + + Score int +} + +func InitUI(scale rl.Vector2) *UI { + boardWidth := 10 * int32(scale.X) + boardHeight := 20 * int32(scale.Y) + screenWidth := boardWidth + 40 + 300 + screenHeight := boardHeight + 40 + rl.InitWindow(screenWidth, screenHeight, "Yeetris") + + return &UI{ + ScreenWidth: screenWidth, + ScreenHeight: screenHeight, + BoardWidth: boardWidth, + BoardHeight: boardHeight, + BoardOffset: rl.NewVector2(20, 20), + UIOffsetWidth: boardWidth + 40 + 20, + } +} + +func drawRectangleBorder(x, y, width, height, border int32, color rl.Color) { + rl.DrawRectangle(x-border, y-border, width+2*border, height+2*border, color) + rl.DrawRectangle(x, y, width, height, elements.Black) +} + +func (u *UI) Draw(g *Game) { + drawRectangleBorder(int32(u.BoardOffset.X), int32(u.BoardOffset.Y), u.BoardWidth, u.BoardHeight, 20, elements.Gray) + rl.DrawText("Yeetris", u.UIOffsetWidth, 20, 40, rl.White) + + if g.Pause { + rl.DrawText( + "GAME PAUSED", + u.ScreenWidth/2-rl.MeasureText("GAME PAUSED", 40)/2, + u.ScreenHeight/2-40, + 40, + elements.LightGray, + ) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..129be15 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module gitea.theedgeofrage.com/theedgeofrage/yeetris + +go 1.21.6 + +require github.com/gen2brain/raylib-go/raylib v0.0.0-20240125111008-83d871a38f28 + +require ( + github.com/ebitengine/purego v0.6.0-alpha.1.0.20231122024802-192c5e846faa // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b957b9c --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/ebitengine/purego v0.6.0-alpha.1.0.20231122024802-192c5e846faa h1:Ik7QikRgeH+bFOfAcMpttCbs6XxWXxCLXMm4awxtOXk= +github.com/ebitengine/purego v0.6.0-alpha.1.0.20231122024802-192c5e846faa/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/gen2brain/raylib-go/raylib v0.0.0-20240125111008-83d871a38f28 h1:e0HOaKlvsdC0ZtfWbEaaWAxYTSxUZoCAFS36jLfsVRA= +github.com/gen2brain/raylib-go/raylib v0.0.0-20240125111008-83d871a38f28/go.mod h1:P/hDjVwz/9fhR0ww3+umzDpDA7Bf7Tce4xNChHIEFqE= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=