Compare commits

..

No commits in common. 'main' and 'v1.0.2' have entirely different histories.
main ... v1.0.2

  1. 261
      README.md
  2. 185
      internal/game/game.go
  3. 3
      internal/i18n/i18n.go

@ -1,237 +1,80 @@
# Tetris Game | 俄罗斯方块 # Tetris Game
一个使用 Go 语言和 Ebitengine 游戏引擎实现的经典俄罗斯方块游戏,支持中英文双语界面。 A classic Tetris game implemented in Go using the Ebitengine game engine.
A classic Tetris game implemented in Go using the Ebitengine game engine with bilingual support (Chinese/English). ## Features
## ✨ 特性 | Features - Classic Tetris gameplay
- Ghost piece preview
- Next piece preview
- Line clearing with scoring
- Progressive difficulty levels
- Smooth controls with wall kicks
### 🎮 游戏特性 | Game Features ## Project Structure
- ✅ 经典俄罗斯方块玩法 | Classic Tetris gameplay
- 👻 半透明幽灵方块预览 | Transparent ghost piece preview
- 🔮 下一个方块预览 | Next piece preview
- 💥 消除行数计分系统 | Line clearing with scoring system
- 📈 渐进式难度等级 | Progressive difficulty levels
- 🎯 平滑控制与踢墙系统 | Smooth controls with wall kicks
- ⚡ 硬降落特效 | Hard drop visual effects
- 🎨 彩色方块显示 | Colorful block rendering
### 🌍 多语言支持 | Multi-language Support
- 🇺🇸 **English** - 完整英文界面 | Full English interface
- 🇨🇳 **中文** - 完整中文界面 | Full Chinese interface
- 🔄 实时语言切换 | Real-time language switching (Press **L** key)
### 🖥 平台支持 | Platform Support
- 🪟 **Windows** (64-bit) - 包含安装包和启动脚本
- 🍎 **macOS** (10.15+) - 原生 .app 应用包
- 🐧 **Linux** (64-bit) - 支持 X11/Wayland 显示系统
## 🎯 游戏控制 | Game Controls
| 键位 | 功能 | Key | Function |
|------|------|-----|----------|
| ← → | 左右移动方块 | ← → | Move piece horizontally |
| ↑ | 顺时针旋转方块 | ↑ | Rotate piece clockwise |
| ↓ | 软降落 (加速下降) | ↓ | Soft drop (faster falling) |
| 空格 | 硬降落 (瞬间落底) | Space | Hard drop (instant drop) |
| L | 切换中英文界面 | L | Switch language |
| R | 重新开始游戏 | R | Restart game (when game over) |
| Esc | 退出游戏 | Esc | Exit game |
## 🏗 项目结构 | Project Structure
``` ```
tetris/ tetris/
├── main.go # 应用程序入口 | Application entry point ├── main.go # Application entry point
├── go.mod # Go 模块定义 | Go module definition ├── go.mod # Go module definition
├── go.sum # Go 模块校验和 | Go module checksums ├── go.sum # Go module checksums
├── Makefile # 构建系统 | Build system ├── assets/ # Game assets (future use)
├── BUILD.md # 构建说明文档 | Build documentation ├── internal/ # Internal packages
├── build.sh # 构建脚本 | Build script │ ├── game/ # Core game logic
├── assets/ # 游戏资源文件 | Game assets │ │ └── game.go # Game state and rendering
│ ├── fonts/ # 字体文件 | Font files │ ├── tetromino/ # Tetromino (falling pieces) logic
│ │ └── HYSongYunLangHeiW-1.ttf # 中文字体 │ │ └── tetromino.go # Piece shapes, rotation, movement
│ └── icons/ # 图标文件 | Icon files │ └── types/ # Shared type definitions
├── internal/ # 内部包 | Internal packages │ └── types.go # Block types and colors
│ ├── game/ # 核心游戏逻辑 | Core game logic └── pkg/ # Public packages
│ │ └── game.go # 游戏状态和渲染 | Game state and rendering └── config/ # Configuration constants
│ ├── tetromino/ # 俄罗斯方块逻辑 | Tetromino logic └── config.go # Game configuration and constants
│ │ └── tetromino.go # 方块形状、旋转、移动 | Piece shapes, rotation, movement
│ ├── types/ # 共享类型定义 | Shared type definitions
│ │ └── types.go # 方块类型和颜色 | Block types and colors
│ ├── font/ # 字体渲染系统 | Font rendering system
│ │ └── font.go # 字体渲染器 | Font renderer
│ └── i18n/ # 国际化支持 | Internationalization
│ └── i18n.go # 多语言文本 | Multi-language text
├── pkg/ # 公共包 | Public packages
│ └── config/ # 配置常量 | Configuration constants
│ └── config.go # 游戏配置和常量 | Game configuration
└── dist/ # 分发文件 | Distribution files (generated)
├── TetrisGame-Windows-v1.0.zip # Windows 分发包
├── TetrisGame-macOS-v1.0.dmg # macOS 分发包
└── TetrisGame-Linux-v1.0.tar.gz # Linux 分发包
``` ```
## 🏛 架构设计 | Architecture ## Architecture
项目遵循 Go 语言最佳实践,采用清晰的包结构设计:
The project follows Go best practices with a clean package structure: The project follows Go best practices with a clean package structure:
- **`main.go`**: 应用程序入口,初始化游戏 | Entry point that initializes the game - **`main.go`**: Entry point that initializes the game
- **`pkg/config`**: 包含所有游戏配置常量 | Contains all game configuration constants - **`pkg/config`**: Contains all game configuration constants
- **`internal/types`**: 定义核心数据类型和枚举 | Defines core data types and enums - **`internal/types`**: Defines core data types and enums
- **`internal/tetromino`**: 处理俄罗斯方块逻辑 | Handles tetromino piece logic - **`internal/tetromino`**: Handles tetromino piece logic (shapes, rotation, movement)
- **`internal/game`**: 核心游戏机制 | Core game mechanics (board state, input handling, rendering) - **`internal/game`**: Core game mechanics (board state, input handling, rendering)
- **`internal/font`**: 字体渲染系统 | Font rendering system for multi-language support
- **`internal/i18n`**: 国际化和本地化支持 | Internationalization and localization support
## 🚀 快速开始 | Quick Start
### 开发环境运行 | Development
```bash
# 克隆项目 | Clone the repository
git clone <repository-url>
cd tetris
# 下载依赖 | Download dependencies
go mod download
# 运行游戏 | Run the game
go run .
# 测试字体渲染 | Test font rendering
go run . --test-font
```
### 构建分发版本 | Build Distribution
```bash
# 构建所有平台版本 | Build for all platforms
make all
# 构建特定平台 | Build for specific platform
make windows # Windows 版本
make macos # macOS 版本
make linux # Linux 版本
# 查看构建帮助 | View build help ## Controls
make help
# 清理构建文件 | Clean build files - **Left/Right Arrow**: Move piece horizontally
make clean - **Up Arrow**: Rotate piece clockwise
``` - **Down Arrow**: Soft drop (faster falling)
- **Space**: Hard drop (instant drop)
构建完成后,分发文件将保存在 `dist/` 目录中。 - **L**: Switch language (English ⇄ 中文)
- **R** (when game over): Restart game
After building, distribution files will be saved in the `dist/` directory.
## 📦 系统要求 | System Requirements ## 中文支持 / Chinese Support
### 开发环境 | Development Environment 游戏支持中文界面!启动游戏后按 **L 键** 切换到中文模式。
- **Go**: 1.19 或更高版本 | 1.19 or higher
- **操作系统**: Windows 10+, macOS 10.15+, Linux (64-bit)
### 运行环境 | Runtime Environment The game supports Chinese interface! Press **L key** after starting the game to switch to Chinese mode.
- **Windows**: Windows 10/11 (64-bit)
- **macOS**: macOS 10.15 Catalina 或更高版本 | or higher
- **Linux**: 64-bit Linux 发行版,支持 X11 或 Wayland | distributions with X11/Wayland support
## 🛠 主要依赖 | Dependencies
```go
require (
github.com/hajimehoshi/ebiten/v2 v2.8.8 // 2D 游戏引擎 | 2D game engine
golang.org/x/image v0.20.0 // 图像处理库 | Image processing
)
```
- **[Ebitengine](https://ebitengine.org/)**: 现代 Go 语言 2D 游戏引擎 | Modern 2D game engine for Go 参见 [中文使用指南](HOW_TO_USE_CHINESE.md) 了解详细说明。
- **golang.org/x/image**: Go 官方图像处理扩展库 | Official Go image processing extensions
## 🎨 特色功能 | Special Features See [Chinese Usage Guide](HOW_TO_USE_CHINESE.md) for detailed instructions.
### 🌟 视觉效果 | Visual Effects ## Building and Running
- **幽灵方块**: 半透明预览方块将要落下的位置 | Transparent preview of where piece will land
- **硬降落特效**: 硬降落时的闪烁动画效果 | Flashing animation during hard drop
- **网格线**: 清晰的游戏区域网格显示 | Clear game area grid display
- **渐变背景**: 美观的背景色彩搭配 | Beautiful background color scheme
### 💯 计分系统 | Scoring System
- **基础分数**: 根据消除行数计算 | Based on lines cleared
- **等级系统**: 随着分数增加难度递增 | Progressive difficulty with score
- **实时显示**: 分数和等级实时更新 | Real-time score and level updates
### 🎯 游戏机制 | Game Mechanics
- **7种标准方块**: I、O、T、S、Z、J、L 形方块 | Standard 7 tetromino pieces
- **踢墙算法**: 智能旋转碰撞检测 | Smart rotation collision detection
- **行消除**: 满行自动消除和分数计算 | Automatic line clearing and scoring
- **渐进速度**: 等级越高下降速度越快 | Progressive drop speed increase
## 🎮 游戏截图 | Screenshots
*(游戏支持实时中英文切换,界面简洁美观)*
*(The game supports real-time Chinese/English switching with a clean and beautiful interface)*
## 🔧 故障排除 | Troubleshooting
### 常见问题 | Common Issues
**中文字体显示异常 | Chinese Font Display Issues**
```bash
# 检查字体文件是否存在 | Check if font file exists
ls -la assets/fonts/HYSongYunLangHeiW-1.ttf
# 重新运行字体测试 | Re-run font test
go run . --test-font
```
**构建失败 | Build Failures**
```bash ```bash
# 检查 Go 环境 | Check Go environment # Run the game
go version go run .
# 清理并重新构建 | Clean and rebuild
make clean && make all
# 检查依赖 | Check dependencies # Build executable
go mod verify go build -o tetris
./tetris
``` ```
**运行时错误 | Runtime Errors** ## Dependencies
- 确保在支持的操作系统上运行 | Ensure running on supported OS
- 检查显卡驱动是否支持 OpenGL | Check if graphics drivers support OpenGL
- 确保有足够的系统资源 | Ensure sufficient system resources
## 🤝 贡献指南 | Contributing
欢迎提交 Issue 和 Pull Request!
Welcome to submit Issues and Pull Requests!
1. Fork 本项目 | Fork the project
2. 创建特性分支 | Create a feature branch
3. 提交更改 | Commit your changes
4. 推送到分支 | Push to the branch
5. 创建 Pull Request | Create a Pull Request
## 📄 许可证 | License
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 📞 联系方式 | Contact
如有问题或建议,请通过以下方式联系:
For questions or suggestions, please contact via:
- 📧 提交 Issue | Submit an Issue - [Ebitengine](https://ebitengine.org/) v2.8.8 - 2D game engine for Go
- 🐙 GitHub Discussions
- 📝 Pull Request
--- ## License
**享受游戏!| Enjoy the game!** 🎮✨ This project is open source and available under the MIT License.

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"image/color" "image/color"
"math/rand" "math/rand"
"os"
"time" "time"
"tetris/internal/font" "tetris/internal/font"
@ -20,33 +19,27 @@ import (
// Game represents the main game state // Game represents the main game state
type Game struct { type Game struct {
board [][]types.BlockType board [][]types.BlockType
currentPiece *tetromino.Tetromino currentPiece *tetromino.Tetromino
nextPiece *tetromino.Tetromino nextPiece *tetromino.Tetromino
dropCounter int dropCounter int
dropInterval int dropInterval int
score int score int
level int level int
gameOver bool gameOver bool
lastMoveDown time.Time lastMoveDown time.Time
lastRotate time.Time lastRotate time.Time
lastMoveLeft time.Time fontRenderer *font.FontRenderer
lastMoveRight time.Time
fontRenderer *font.FontRenderer
hardDropping bool
hardDropCounter int
} }
// New creates a new game instance // New creates a new game instance
func New() *Game { func New() *Game {
g := &Game{ g := &Game{
board: make([][]types.BlockType, config.BoardHeight), board: make([][]types.BlockType, config.BoardHeight),
dropInterval: config.InitialDropInterval, dropInterval: config.InitialDropInterval,
lastMoveDown: time.Now(), lastMoveDown: time.Now(),
lastRotate: time.Now(), lastRotate: time.Now(),
lastMoveLeft: time.Now(), fontRenderer: font.NewFontRenderer(),
lastMoveRight: time.Now(),
fontRenderer: font.NewFontRenderer(),
} }
for i := range g.board { for i := range g.board {
@ -60,7 +53,7 @@ func New() *Game {
// Update handles game logic updates // Update handles game logic updates
func (g *Game) Update() error { func (g *Game) Update() error {
if g.gameOver { if g.gameOver {
if inpututil.IsKeyJustPressed(ebiten.KeyR) { if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
*g = *New() *g = *New()
return nil return nil
} }
@ -121,14 +114,10 @@ func (g *Game) Draw(screen *ebiten.Image) {
} }
} }
// Draw actual piece with special effect during hard drop // Draw actual piece
for _, pos := range g.currentPiece.GetAbsolutePositions() { for _, pos := range g.currentPiece.GetAbsolutePositions() {
if pos[1] >= 0 { if pos[1] >= 0 {
if g.hardDropping { g.drawBlock(screen, pos[0], pos[1], g.currentPiece.BlockType)
g.drawHardDropBlock(screen, pos[0], pos[1], g.currentPiece.BlockType)
} else {
g.drawBlock(screen, pos[0], pos[1], g.currentPiece.BlockType)
}
} }
} }
} }
@ -162,8 +151,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
if g.gameOver { if g.gameOver {
text := i18n.GetText() text := i18n.GetText()
g.fontRenderer.DrawText(screen, text.GameOver, float64(config.ScreenWidth/2-20), float64(config.ScreenHeight/2-100), color.RGBA{255, 255, 255, 255}) g.fontRenderer.DrawText(screen, text.GameOver, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight/2), color.RGBA{255, 255, 255, 255})
g.fontRenderer.DrawText(screen, text.Restart, float64(config.ScreenWidth/2-20), float64(config.ScreenHeight/2-80), color.RGBA{255, 0, 0, 255}) g.fontRenderer.DrawText(screen, text.Restart, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight/2+20), color.RGBA{255, 255, 255, 255})
} }
} }
@ -179,8 +168,8 @@ func (g *Game) handleInput() {
i18n.SwitchLanguage() i18n.SwitchLanguage()
} }
// Restart game anytime // Restart game when game over
if inpututil.IsKeyJustPressed(ebiten.KeyR) { if g.gameOver && inpututil.IsKeyJustPressed(ebiten.KeyR) {
g.restart() g.restart()
return return
} }
@ -190,37 +179,12 @@ func (g *Game) handleInput() {
return return
} }
// Hard drop // Move left/right
if inpututil.IsKeyJustPressed(ebiten.KeySpace) { if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
g.hardDrop() g.tryMove(-1, 0)
} }
if inpututil.IsKeyJustPressed(ebiten.KeyRight) {
// Skip movement inputs during hard drop animation g.tryMove(1, 0)
if g.hardDropping {
return
}
// Move left/right - support continuous movement when holding keys
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
// First press moves immediately, then add delay for continuous movement
if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
g.tryMove(-1, 0)
g.lastMoveLeft = time.Now()
} else if time.Since(g.lastMoveLeft) > time.Millisecond*120 {
g.tryMove(-1, 0)
g.lastMoveLeft = time.Now()
}
}
if ebiten.IsKeyPressed(ebiten.KeyRight) {
// First press moves immediately, then add delay for continuous movement
if inpututil.IsKeyJustPressed(ebiten.KeyRight) {
g.tryMove(1, 0)
g.lastMoveRight = time.Now()
} else if time.Since(g.lastMoveRight) > time.Millisecond*120 {
g.tryMove(1, 0)
g.lastMoveRight = time.Now()
}
} }
// Rotate // Rotate
@ -238,32 +202,20 @@ func (g *Game) handleInput() {
g.lastMoveDown = time.Now() g.lastMoveDown = time.Now()
} }
} }
// Exit game
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { // Hard drop
os.Exit(0) if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
g.hardDrop()
} }
} }
// updateDropCounter handles automatic piece dropping and hard drop animation // updateDropCounter handles automatic piece dropping
func (g *Game) updateDropCounter() { func (g *Game) updateDropCounter() {
if g.hardDropping { g.dropCounter++
// Hard drop animation - move faster if g.dropCounter >= g.dropInterval {
g.hardDropCounter++ g.dropCounter = 0
if g.hardDropCounter >= 2 { // Move every 2 frames for visible animation if !g.tryMove(0, 1) {
g.hardDropCounter = 0 g.lockPiece()
if !g.tryMove(0, 1) {
g.hardDropping = false
g.lockPiece()
}
}
} else {
// Normal drop
g.dropCounter++
if g.dropCounter >= g.dropInterval {
g.dropCounter = 0
if !g.tryMove(0, 1) {
g.lockPiece()
}
} }
} }
} }
@ -323,14 +275,24 @@ func (g *Game) tryRotate() bool {
return false return false
} }
// hardDrop starts the hard drop animation // hardDrop drops the piece instantly to the bottom
func (g *Game) hardDrop() { func (g *Game) hardDrop() {
if g.currentPiece == nil || g.hardDropping { if g.currentPiece == nil {
return return
} }
g.hardDropping = true dropDistance := 0
g.hardDropCounter = 0 for {
if !g.tryMove(0, 1) {
break
}
dropDistance++
}
// Award extra points for hard drop
g.score += dropDistance * 2
g.lockPiece()
} }
// isColliding checks if the current piece is colliding with the board or boundaries // isColliding checks if the current piece is colliding with the board or boundaries
@ -492,40 +454,6 @@ func (g *Game) drawBlock(screen *ebiten.Image, x, y int, blockType types.BlockTy
borderColor, false) borderColor, false)
} }
// drawHardDropBlock draws a block with special hard drop effect
func (g *Game) drawHardDropBlock(screen *ebiten.Image, x, y int, blockType types.BlockType) {
// Get original color and make it brighter for hard drop effect
originalColor := types.BlockColors[blockType].(color.RGBA)
hardDropColor := color.RGBA{
min(255, originalColor.R+50),
min(255, originalColor.G+50),
min(255, originalColor.B+50),
255,
}
vector.DrawFilledRect(screen,
float32(x*config.BlockSize),
float32(y*config.BlockSize),
float32(config.BlockSize-1),
float32(config.BlockSize-1),
hardDropColor, false)
// Add bright white border for hard drop effect
borderColor := color.RGBA{255, 255, 255, 200}
vector.DrawFilledRect(screen,
float32(x*config.BlockSize),
float32(y*config.BlockSize),
float32(config.BlockSize-1),
3,
borderColor, false)
vector.DrawFilledRect(screen,
float32(x*config.BlockSize),
float32(y*config.BlockSize),
3,
float32(config.BlockSize-1),
borderColor, false)
}
// max returns the maximum of two integers // max returns the maximum of two integers
func max(a, b int) int { func max(a, b int) int {
if a > b { if a > b {
@ -534,14 +462,6 @@ func max(a, b int) int {
return b return b
} }
// min returns the minimum of two uint8 values
func min(a, b uint8) uint8 {
if a < b {
return a
}
return b
}
// drawControls renders the control instructions on the right side of the screen // drawControls renders the control instructions on the right side of the screen
func (g *Game) drawControls(screen *ebiten.Image) { func (g *Game) drawControls(screen *ebiten.Image) {
// Starting position for controls display // Starting position for controls display
@ -560,7 +480,6 @@ func (g *Game) drawControls(screen *ebiten.Image) {
text.Rotate, text.Rotate,
text.SoftDrop, text.SoftDrop,
text.HardDrop, text.HardDrop,
text.PressE,
"", "",
text.Language, text.Language,
text.PressL, text.PressL,

@ -22,7 +22,6 @@ type Localization struct {
Restart string Restart string
Language string Language string
PressL string PressL string
PressE string
} }
// currentLanguage holds the current language setting // currentLanguage holds the current language setting
@ -43,7 +42,6 @@ var localizations = map[Language]Localization{
Restart: "Press R to restart", Restart: "Press R to restart",
Language: "Language:", Language: "Language:",
PressL: "Press L to switch", PressL: "Press L to switch",
PressE: "Press Esc to exit",
}, },
Chinese: { Chinese: {
Next: "下一个:", Next: "下一个:",
@ -58,7 +56,6 @@ var localizations = map[Language]Localization{
Restart: "按 R 重新开始", Restart: "按 R 重新开始",
Language: "语言:", Language: "语言:",
PressL: "按 L 切换", PressL: "按 L 切换",
PressE: "按 Esc 退出",
}, },
} }

Loading…
Cancel
Save