commit
95d3f05079
@ -0,0 +1,38 @@ |
||||
# Tetris Game in Go |
||||
|
||||
A simple Tetris game implementation using Go and the Ebiten game engine. |
||||
|
||||
## Requirements |
||||
|
||||
- Go 1.16 or later |
||||
- Ebiten v2 |
||||
|
||||
## Installation |
||||
|
||||
1. Clone the repository |
||||
2. Install dependencies: |
||||
```bash |
||||
go mod tidy |
||||
``` |
||||
|
||||
## Running the Game |
||||
|
||||
```bash |
||||
go run . |
||||
``` |
||||
|
||||
## Controls |
||||
|
||||
- Left Arrow: Move piece left |
||||
- Right Arrow: Move piece right |
||||
- Down Arrow: Move piece down faster |
||||
- Up Arrow: Rotate piece |
||||
- Space: Drop piece instantly |
||||
- ESC: Quit game |
||||
|
||||
## Features |
||||
|
||||
- Classic Tetris gameplay |
||||
- Score tracking |
||||
- Next piece preview |
||||
- Level system |
@ -0,0 +1,190 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"image/color" |
||||
) |
||||
|
||||
// Block represents a single block in the game
|
||||
type Block struct { |
||||
X, Y int |
||||
Type BlockType |
||||
} |
||||
|
||||
// BlockType represents different types of tetrominos
|
||||
type BlockType int |
||||
|
||||
const ( |
||||
IBlock BlockType = iota |
||||
JBlock |
||||
LBlock |
||||
OBlock |
||||
SBlock |
||||
TBlock |
||||
ZBlock |
||||
) |
||||
|
||||
// Tetromino represents a complete tetris piece
|
||||
type Tetromino struct { |
||||
Blocks []Block |
||||
BlockType BlockType |
||||
X, Y int |
||||
} |
||||
|
||||
// Colors for different block types
|
||||
var BlockColors = map[BlockType]color.Color{ |
||||
IBlock: color.RGBA{0, 255, 255, 255}, // Cyan
|
||||
JBlock: color.RGBA{0, 0, 255, 255}, // Blue
|
||||
LBlock: color.RGBA{255, 165, 0, 255}, // Orange
|
||||
OBlock: color.RGBA{255, 255, 0, 255}, // Yellow
|
||||
SBlock: color.RGBA{0, 255, 0, 255}, // Green
|
||||
TBlock: color.RGBA{128, 0, 128, 255}, // Purple
|
||||
ZBlock: color.RGBA{255, 0, 0, 255}, // Red
|
||||
} |
||||
|
||||
// TetrominoShapes defines the shape of each tetromino type
|
||||
var TetrominoShapes = map[BlockType][][]bool{ |
||||
IBlock: { |
||||
{false, false, false, false}, |
||||
{false, false, false, false}, |
||||
{true, true, true, true}, |
||||
{false, false, false, false}, |
||||
}, |
||||
JBlock: { |
||||
{true, false, false}, |
||||
{true, true, true}, |
||||
{false, false, false}, |
||||
}, |
||||
LBlock: { |
||||
{false, false, true}, |
||||
{true, true, true}, |
||||
{false, false, false}, |
||||
}, |
||||
OBlock: { |
||||
{true, true}, |
||||
{true, true}, |
||||
}, |
||||
SBlock: { |
||||
{false, true, true}, |
||||
{true, true, false}, |
||||
{false, false, false}, |
||||
}, |
||||
TBlock: { |
||||
{false, true, false}, |
||||
{true, true, true}, |
||||
{false, false, false}, |
||||
}, |
||||
ZBlock: { |
||||
{true, true, false}, |
||||
{false, true, true}, |
||||
{false, false, false}, |
||||
}, |
||||
} |
||||
|
||||
// Rotation offsets for I piece wall kicks
|
||||
var IKicks = [][2]int{ |
||||
{0, 0}, |
||||
{-2, 0}, |
||||
{1, 0}, |
||||
{-2, -1}, |
||||
{1, 2}, |
||||
} |
||||
|
||||
// Rotation offsets for other pieces wall kicks
|
||||
var NormalKicks = [][2]int{ |
||||
{0, 0}, |
||||
{-1, 0}, |
||||
{1, 0}, |
||||
{0, -1}, |
||||
} |
||||
|
||||
// NewTetromino creates a new tetromino of the specified type
|
||||
func NewTetromino(blockType BlockType, x, y int) *Tetromino { |
||||
shape := TetrominoShapes[blockType] |
||||
blocks := make([]Block, 0) |
||||
|
||||
for i := 0; i < len(shape); i++ { |
||||
for j := 0; j < len(shape[i]); j++ { |
||||
if shape[i][j] { |
||||
blocks = append(blocks, Block{ |
||||
X: j, |
||||
Y: i, |
||||
Type: blockType, |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Adjust initial position for I piece
|
||||
if blockType == IBlock { |
||||
y-- // Start one row higher
|
||||
} |
||||
|
||||
return &Tetromino{ |
||||
Blocks: blocks, |
||||
BlockType: blockType, |
||||
X: x, |
||||
Y: y, |
||||
} |
||||
} |
||||
|
||||
// Move moves the tetromino by the specified delta
|
||||
func (t *Tetromino) Move(dx, dy int) { |
||||
t.X += dx |
||||
t.Y += dy |
||||
} |
||||
|
||||
// Rotate rotates the tetromino clockwise
|
||||
func (t *Tetromino) Rotate() { |
||||
if t.BlockType == OBlock { |
||||
return // O block doesn't need rotation
|
||||
} |
||||
|
||||
// Get the size of the shape matrix
|
||||
size := 3 |
||||
if t.BlockType == IBlock { |
||||
size = 4 |
||||
} |
||||
|
||||
// Create a temporary matrix for rotation
|
||||
matrix := make([][]bool, size) |
||||
for i := range matrix { |
||||
matrix[i] = make([]bool, size) |
||||
} |
||||
|
||||
// Fill the matrix with current block positions
|
||||
for _, block := range t.Blocks { |
||||
if block.Y >= 0 && block.Y < size && block.X >= 0 && block.X < size { |
||||
matrix[block.Y][block.X] = true |
||||
} |
||||
} |
||||
|
||||
// Create a new rotated shape
|
||||
newBlocks := make([]Block, 0) |
||||
|
||||
// Perform rotation
|
||||
for y := 0; y < size; y++ { |
||||
for x := 0; x < size; x++ { |
||||
if matrix[y][x] { |
||||
// Rotate coordinates 90 degrees clockwise
|
||||
newX := size - 1 - y |
||||
newY := x |
||||
newBlocks = append(newBlocks, Block{ |
||||
X: newX, |
||||
Y: newY, |
||||
Type: t.BlockType, |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
t.Blocks = newBlocks |
||||
} |
||||
|
||||
// GetAbsolutePositions returns the absolute positions of all blocks in the tetromino
|
||||
func (t *Tetromino) GetAbsolutePositions() [][2]int { |
||||
positions := make([][2]int, len(t.Blocks)) |
||||
for i, block := range t.Blocks { |
||||
positions[i] = [2]int{t.X + block.X, t.Y + block.Y} |
||||
} |
||||
return positions |
||||
} |
@ -0,0 +1,468 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"image/color" |
||||
"math/rand" |
||||
"time" |
||||
|
||||
"github.com/hajimehoshi/ebiten/v2" |
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil" |
||||
"github.com/hajimehoshi/ebiten/v2/inpututil" |
||||
) |
||||
|
||||
const ( |
||||
// Size constants
|
||||
BlockSize = 30 |
||||
BoardWidth = 10 |
||||
BoardHeight = 20 |
||||
ScreenWidth = BlockSize * (BoardWidth + 6) // Extra space for next piece preview
|
||||
ScreenHeight = BlockSize * BoardHeight |
||||
PreviewX = BlockSize * (BoardWidth + 1) |
||||
PreviewY = BlockSize * 2 |
||||
|
||||
// Game constants
|
||||
InitialDropInterval = 60 |
||||
MinDropInterval = 5 |
||||
) |
||||
|
||||
// Game represents the main game state
|
||||
type Game struct { |
||||
board [][]BlockType |
||||
currentPiece *Tetromino |
||||
nextPiece *Tetromino |
||||
dropCounter int |
||||
dropInterval int |
||||
score int |
||||
level int |
||||
gameOver bool |
||||
lastMoveDown time.Time |
||||
lastRotate time.Time |
||||
} |
||||
|
||||
// NewGame creates a new game instance
|
||||
func NewGame() *Game { |
||||
g := &Game{ |
||||
board: make([][]BlockType, BoardHeight), |
||||
dropInterval: InitialDropInterval, |
||||
lastMoveDown: time.Now(), |
||||
lastRotate: time.Now(), |
||||
} |
||||
|
||||
for i := range g.board { |
||||
g.board[i] = make([]BlockType, BoardWidth) |
||||
} |
||||
|
||||
g.spawnNewPiece() |
||||
return g |
||||
} |
||||
|
||||
// Update handles game logic updates
|
||||
func (g *Game) Update() error { |
||||
if g.gameOver { |
||||
if inpututil.IsKeyJustPressed(ebiten.KeySpace) { |
||||
*g = *NewGame() |
||||
return nil |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
g.handleInput() |
||||
g.updateDropCounter() |
||||
return nil |
||||
} |
||||
|
||||
// Draw renders the game state
|
||||
func (g *Game) Draw(screen *ebiten.Image) { |
||||
// Draw board background
|
||||
ebitenutil.DrawRect(screen, 0, 0, float64(BoardWidth*BlockSize), float64(BoardHeight*BlockSize), color.RGBA{40, 40, 40, 255}) |
||||
|
||||
// Draw preview area background
|
||||
ebitenutil.DrawRect(screen, float64(PreviewX-BlockSize), 0, float64(6*BlockSize), float64(ScreenHeight), color.RGBA{30, 30, 30, 255}) |
||||
|
||||
// Draw grid lines
|
||||
gridColor := color.RGBA{60, 60, 60, 255} |
||||
for x := 0; x <= BoardWidth; x++ { |
||||
ebitenutil.DrawLine(screen, float64(x*BlockSize), 0, float64(x*BlockSize), float64(BoardHeight*BlockSize), gridColor) |
||||
} |
||||
for y := 0; y <= BoardHeight; y++ { |
||||
ebitenutil.DrawLine(screen, 0, float64(y*BlockSize), float64(BoardWidth*BlockSize), float64(y*BlockSize), gridColor) |
||||
} |
||||
|
||||
// Draw placed blocks
|
||||
for y := 0; y < BoardHeight; y++ { |
||||
for x := 0; x < BoardWidth; x++ { |
||||
if g.board[y][x] != 0 { |
||||
g.drawBlock(screen, x, y, g.board[y][x]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Draw current piece and its ghost
|
||||
if g.currentPiece != nil { |
||||
// Draw ghost piece
|
||||
ghostPiece := *g.currentPiece |
||||
for { |
||||
ghostPiece.Move(0, 1) |
||||
if g.wouldCollide(&ghostPiece) { |
||||
ghostPiece.Move(0, -1) |
||||
break |
||||
} |
||||
} |
||||
for _, pos := range ghostPiece.GetAbsolutePositions() { |
||||
if pos[1] >= 0 { |
||||
ghostColor := color.RGBA{128, 128, 128, 128} |
||||
ebitenutil.DrawRect(screen, |
||||
float64(pos[0]*BlockSize), |
||||
float64(pos[1]*BlockSize), |
||||
float64(BlockSize-1), |
||||
float64(BlockSize-1), |
||||
ghostColor) |
||||
} |
||||
} |
||||
|
||||
// Draw actual piece
|
||||
for _, pos := range g.currentPiece.GetAbsolutePositions() { |
||||
if pos[1] >= 0 { |
||||
g.drawBlock(screen, pos[0], pos[1], g.currentPiece.BlockType) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Draw next piece preview
|
||||
if g.nextPiece != nil { |
||||
previewLabel := "NEXT:" |
||||
ebitenutil.DebugPrintAt(screen, previewLabel, PreviewX-BlockSize, BlockSize) |
||||
|
||||
for _, block := range g.nextPiece.Blocks { |
||||
x := PreviewX + block.X*BlockSize |
||||
y := PreviewY + block.Y*BlockSize |
||||
ebitenutil.DrawRect(screen, |
||||
float64(x), |
||||
float64(y), |
||||
float64(BlockSize-1), |
||||
float64(BlockSize-1), |
||||
BlockColors[g.nextPiece.BlockType]) |
||||
} |
||||
} |
||||
|
||||
// Draw score and level with larger font
|
||||
scoreStr := fmt.Sprintf("Score: %d", g.score) |
||||
levelStr := fmt.Sprintf("Level: %d", g.level) |
||||
ebitenutil.DebugPrintAt(screen, scoreStr, PreviewX-BlockSize, ScreenHeight-4*BlockSize) |
||||
ebitenutil.DebugPrintAt(screen, levelStr, PreviewX-BlockSize, ScreenHeight-3*BlockSize) |
||||
|
||||
if g.gameOver { |
||||
gameOverStr := "Game Over!" |
||||
restartStr := "Press SPACE to restart" |
||||
ebitenutil.DebugPrintAt(screen, gameOverStr, PreviewX-BlockSize, ScreenHeight/2) |
||||
ebitenutil.DebugPrintAt(screen, restartStr, PreviewX-BlockSize, ScreenHeight/2+20) |
||||
} |
||||
} |
||||
|
||||
// Layout implements ebiten.Game interface
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { |
||||
return ScreenWidth, ScreenHeight |
||||
} |
||||
|
||||
// handleInput processes player input
|
||||
func (g *Game) handleInput() { |
||||
// Move left/right
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyLeft) { |
||||
g.tryMove(-1, 0) |
||||
} |
||||
if inpututil.IsKeyJustPressed(ebiten.KeyRight) { |
||||
g.tryMove(1, 0) |
||||
} |
||||
|
||||
// Rotate
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyUp) { |
||||
if time.Since(g.lastRotate) > time.Millisecond*100 { |
||||
g.tryRotate() |
||||
g.lastRotate = time.Now() |
||||
} |
||||
} |
||||
|
||||
// Soft drop
|
||||
if ebiten.IsKeyPressed(ebiten.KeyDown) { |
||||
if time.Since(g.lastMoveDown) > time.Millisecond*50 { |
||||
g.tryMove(0, 1) |
||||
g.lastMoveDown = time.Now() |
||||
} |
||||
} |
||||
|
||||
// Hard drop
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeySpace) { |
||||
g.hardDrop() |
||||
} |
||||
} |
||||
|
||||
// updateDropCounter handles automatic piece dropping
|
||||
func (g *Game) updateDropCounter() { |
||||
if g.currentPiece == nil { |
||||
return |
||||
} |
||||
|
||||
g.dropCounter++ |
||||
if g.dropCounter >= g.dropInterval { |
||||
g.dropCounter = 0 |
||||
|
||||
// Try to move down
|
||||
g.currentPiece.Move(0, 1) |
||||
if g.isColliding() { |
||||
// Move back up and lock the piece
|
||||
g.currentPiece.Move(0, -1) |
||||
g.lockPiece() |
||||
if !g.gameOver { |
||||
g.clearLines() |
||||
g.spawnNewPiece() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// tryMove attempts to move the current piece
|
||||
func (g *Game) tryMove(dx, dy int) bool { |
||||
if g.currentPiece == nil { |
||||
return false |
||||
} |
||||
|
||||
g.currentPiece.Move(dx, dy) |
||||
if g.isColliding() { |
||||
g.currentPiece.Move(-dx, -dy) |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// tryRotate attempts to rotate the current piece
|
||||
func (g *Game) tryRotate() bool { |
||||
if g.currentPiece == nil { |
||||
return false |
||||
} |
||||
|
||||
g.currentPiece.Rotate() |
||||
if g.isColliding() { |
||||
// Try wall kicks
|
||||
kicks := NormalKicks |
||||
if g.currentPiece.BlockType == IBlock { |
||||
kicks = IKicks |
||||
} |
||||
|
||||
success := false |
||||
for _, kick := range kicks { |
||||
g.currentPiece.Move(kick[0], kick[1]) |
||||
if !g.isColliding() { |
||||
success = true |
||||
break |
||||
} |
||||
g.currentPiece.Move(-kick[0], -kick[1]) |
||||
} |
||||
|
||||
if !success { |
||||
// Rotate back to original position
|
||||
g.currentPiece.Rotate() |
||||
g.currentPiece.Rotate() |
||||
g.currentPiece.Rotate() |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// hardDrop drops the piece to the bottom instantly
|
||||
func (g *Game) hardDrop() { |
||||
if g.currentPiece == nil { |
||||
return |
||||
} |
||||
|
||||
// Move down until collision
|
||||
dropDistance := 0 |
||||
for { |
||||
g.currentPiece.Move(0, 1) |
||||
if g.isColliding() { |
||||
g.currentPiece.Move(0, -1) |
||||
break |
||||
} |
||||
dropDistance++ |
||||
} |
||||
|
||||
// Add bonus points for hard drop
|
||||
g.score += dropDistance * 2 |
||||
|
||||
g.lockPiece() |
||||
if !g.gameOver { |
||||
g.clearLines() |
||||
g.spawnNewPiece() |
||||
} |
||||
} |
||||
|
||||
// isColliding checks if the current piece collides with the board or boundaries
|
||||
func (g *Game) isColliding() bool { |
||||
if g.currentPiece == nil { |
||||
return false |
||||
} |
||||
|
||||
for _, pos := range g.currentPiece.GetAbsolutePositions() { |
||||
x, y := pos[0], pos[1] |
||||
// Check boundaries
|
||||
if x < 0 || x >= BoardWidth || y >= BoardHeight { |
||||
return true |
||||
} |
||||
// Check collision with other pieces
|
||||
if y >= 0 && x >= 0 && x < BoardWidth && y < BoardHeight { |
||||
if g.board[y][x] != 0 { |
||||
return true |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// lockPiece fixes the current piece to the board
|
||||
func (g *Game) lockPiece() { |
||||
if g.currentPiece == nil { |
||||
return |
||||
} |
||||
|
||||
positions := g.currentPiece.GetAbsolutePositions() |
||||
for _, pos := range positions { |
||||
x, y := pos[0], pos[1] |
||||
if y < 0 { |
||||
g.gameOver = true |
||||
return |
||||
} |
||||
if x >= 0 && x < BoardWidth && y >= 0 && y < BoardHeight { |
||||
g.board[y][x] = g.currentPiece.BlockType |
||||
} |
||||
} |
||||
} |
||||
|
||||
// clearLines removes completed lines and updates the score
|
||||
func (g *Game) clearLines() { |
||||
linesCleared := 0 |
||||
for y := BoardHeight - 1; y >= 0; y-- { |
||||
if g.isLineFull(y) { |
||||
g.removeLine(y) |
||||
linesCleared++ |
||||
y++ // Check the same line again after shifting
|
||||
} |
||||
} |
||||
|
||||
// Update score and level
|
||||
if linesCleared > 0 { |
||||
// 计分规则:一次消除的行数越多,得分越高
|
||||
baseScore := 0 |
||||
switch linesCleared { |
||||
case 1: |
||||
baseScore = 100 |
||||
case 2: |
||||
baseScore = 300 |
||||
case 3: |
||||
baseScore = 500 |
||||
case 4: |
||||
baseScore = 800 |
||||
} |
||||
|
||||
// 根据当前等级增加得分
|
||||
levelMultiplier := g.level + 1 |
||||
g.score += baseScore * levelMultiplier |
||||
|
||||
// 更新等级
|
||||
g.level = g.score / 1000 |
||||
|
||||
// 随等级提高,方块下落速度加快
|
||||
g.dropInterval = max(MinDropInterval, InitialDropInterval-g.level*5) |
||||
} |
||||
} |
||||
|
||||
// isLineFull checks if a line is completely filled
|
||||
func (g *Game) isLineFull(y int) bool { |
||||
for x := 0; x < BoardWidth; x++ { |
||||
if g.board[y][x] == 0 { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// removeLine removes a line and shifts everything above down
|
||||
func (g *Game) removeLine(y int) { |
||||
for i := y; i > 0; i-- { |
||||
copy(g.board[i], g.board[i-1]) |
||||
} |
||||
for x := 0; x < BoardWidth; x++ { |
||||
g.board[0][x] = 0 |
||||
} |
||||
} |
||||
|
||||
// spawnNewPiece creates a new piece at the top of the board
|
||||
func (g *Game) spawnNewPiece() { |
||||
if g.nextPiece == nil { |
||||
g.nextPiece = NewTetromino(BlockType(rand.Intn(7)), 0, 0) |
||||
} |
||||
|
||||
g.currentPiece = g.nextPiece |
||||
g.currentPiece.X = BoardWidth/2 - 2 |
||||
if g.currentPiece.BlockType != IBlock { |
||||
g.currentPiece.X++ // Adjust for non-I pieces
|
||||
} |
||||
g.currentPiece.Y = 0 |
||||
|
||||
g.nextPiece = NewTetromino(BlockType(rand.Intn(7)), 0, 0) |
||||
|
||||
if g.isColliding() { |
||||
g.gameOver = true |
||||
} |
||||
} |
||||
|
||||
// wouldCollide checks if a piece would collide in its current position
|
||||
func (g *Game) wouldCollide(piece *Tetromino) bool { |
||||
for _, pos := range piece.GetAbsolutePositions() { |
||||
x, y := pos[0], pos[1] |
||||
if x < 0 || x >= BoardWidth || y >= BoardHeight { |
||||
return true |
||||
} |
||||
if y >= 0 && y < BoardHeight && x >= 0 && x < BoardWidth && g.board[y][x] != 0 { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// drawBlock draws a single block at the specified position
|
||||
func (g *Game) drawBlock(screen *ebiten.Image, x, y int, blockType BlockType) { |
||||
if y < 0 { |
||||
return |
||||
} |
||||
|
||||
// Draw block background
|
||||
ebitenutil.DrawRect(screen, |
||||
float64(x*BlockSize), |
||||
float64(y*BlockSize), |
||||
float64(BlockSize-1), |
||||
float64(BlockSize-1), |
||||
BlockColors[blockType]) |
||||
|
||||
// Draw highlight (3D effect)
|
||||
highlightColor := color.RGBA{255, 255, 255, 64} |
||||
ebitenutil.DrawLine(screen, |
||||
float64(x*BlockSize), |
||||
float64(y*BlockSize), |
||||
float64(x*BlockSize+BlockSize-1), |
||||
float64(y*BlockSize), |
||||
highlightColor) |
||||
ebitenutil.DrawLine(screen, |
||||
float64(x*BlockSize), |
||||
float64(y*BlockSize), |
||||
float64(x*BlockSize), |
||||
float64(y*BlockSize+BlockSize-1), |
||||
highlightColor) |
||||
} |
||||
|
||||
func max(a, b int) int { |
||||
if a > b { |
||||
return a |
||||
} |
||||
return b |
||||
} |
@ -0,0 +1,14 @@ |
||||
module tetris |
||||
|
||||
go 1.24.2 |
||||
|
||||
require github.com/hajimehoshi/ebiten/v2 v2.8.8 |
||||
|
||||
require ( |
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 // indirect |
||||
github.com/ebitengine/hideconsole v1.0.0 // indirect |
||||
github.com/ebitengine/purego v0.8.0 // indirect |
||||
github.com/jezek/xgb v1.1.1 // indirect |
||||
golang.org/x/sync v0.8.0 // indirect |
||||
golang.org/x/sys v0.25.0 // indirect |
||||
) |
@ -0,0 +1,16 @@ |
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 h1:Gk1XUEttOk0/hb6Tq3WkmutWa0ZLhNn/6fc6XZpM7tM= |
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325/go.mod h1:ulhSQcbPioQrallSuIzF8l1NKQoD7xmMZc5NxzibUMY= |
||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= |
||||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= |
||||
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= |
||||
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= |
||||
github.com/hajimehoshi/ebiten/v2 v2.8.8 h1:xyMxOAn52T1tQ+j3vdieZ7auDBOXmvjUprSrxaIbsi8= |
||||
github.com/hajimehoshi/ebiten/v2 v2.8.8/go.mod h1:durJ05+OYnio9b8q0sEtOgaNeBEQG7Yr7lRviAciYbs= |
||||
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= |
||||
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= |
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= |
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= |
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= |
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= |
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= |
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
@ -0,0 +1,21 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"math/rand" |
||||
"time" |
||||
|
||||
"github.com/hajimehoshi/ebiten/v2" |
||||
) |
||||
|
||||
func main() { |
||||
rand.Seed(time.Now().UnixNano()) |
||||
|
||||
ebiten.SetWindowSize(ScreenWidth, ScreenHeight) |
||||
ebiten.SetWindowTitle("Tetris") |
||||
|
||||
game := NewGame() |
||||
if err := ebiten.RunGame(game); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
Loading…
Reference in new issue