diff --git a/DEPRECATED_METHODS_FIXED.md b/DEPRECATED_METHODS_FIXED.md deleted file mode 100644 index 73ebe0b..0000000 --- a/DEPRECATED_METHODS_FIXED.md +++ /dev/null @@ -1,112 +0,0 @@ -# 废弃方法修复记录 - -## 修复日期 -2024年 - -## 修复的废弃方法 - -### 1. Go 标准库 - `math/rand` - -#### 修复内容: -- **废弃方法**: `rand.Seed(time.Now().UnixNano())` -- **修复方案**: 完全移除该调用 -- **原因**: Go 1.20+ 中随机数生成器自动种子化,不再需要手动调用 `rand.Seed()` -- **文件**: `main.go` - -#### 变更: -```go -// 修复前 -import ( - "math/rand" - "time" -) - -func main() { - rand.Seed(time.Now().UnixNano()) - // ... -} - -// 修复后 -func main() { - // 不再需要 rand.Seed() 调用 - // ... -} -``` - -### 2. Ebitengine v2.5+ - 图形绘制 API - -#### 修复内容: -- **废弃方法**: `ebitenutil.DrawRect()` 和 `ebitenutil.DrawLine()` -- **新方法**: `vector.DrawFilledRect()` 和 `vector.StrokeLine()` -- **原因**: Ebitengine v2.5 后推荐使用性能更好的 vector 包 -- **文件**: `internal/game/game.go` - -#### 主要变更: - -**矩形绘制:** -```go -// 修复前 -ebitenutil.DrawRect(screen, x, y, width, height, color) - -// 修复后 -vector.DrawFilledRect(screen, float32(x), float32(y), float32(width), float32(height), color, false) -``` - -**线条绘制:** -```go -// 修复前 -ebitenutil.DrawLine(screen, x1, y1, x2, y2, color) - -// 修复后 -vector.StrokeLine(screen, float32(x1), float32(y1), float32(x2), float32(y2), 1, color, false) -``` - -#### 修复位置: -1. 游戏背景绘制 -2. 预览区域背景 -3. 网格线绘制 -4. 方块绘制 (`drawBlock` 方法) -5. 幽灵方块绘制 -6. 下一个方块预览 - -### 3. 导入包更新 - -添加了新的导入: -```go -import ( - // ... 其他导入 - "github.com/hajimehoshi/ebiten/v2/vector" -) -``` - -## 性能优势 - -### Vector API 优势: -1. **更好的性能**: vector 包使用 GPU 加速绘制 -2. **更准确的渲染**: 减少了像素对齐问题 -3. **未来兼容性**: 与 Ebitengine 的发展方向一致 - -### 随机数优化: -1. **自动优化**: Go 1.20+ 的随机数生成器自动优化 -2. **更好的随机性**: 不需要手动种子化 -3. **代码简化**: 减少了样板代码 - -## 验证结果 - -- ✅ 编译成功 -- ✅ Lint 检查通过 (无废弃警告) -- ✅ 功能测试正常 -- ✅ 多平台构建正常 - -## 影响评估 - -- **兼容性**: 完全向前兼容 -- **功能**: 无功能变化,纯技术升级 -- **性能**: 理论上性能有所提升 -- **维护性**: 符合最新最佳实践 - -## 建议 - -1. 定期运行 `make check` 检查代码质量 -2. 关注 Ebitengine 官方更新,及时采用新 API -3. 保持 Go 版本更新以享受最新优化 \ No newline at end of file diff --git a/README.md b/README.md index 7d80ece..7204e54 100755 --- a/README.md +++ b/README.md @@ -47,7 +47,18 @@ The project follows Go best practices with a clean package structure: - **Up Arrow**: Rotate piece clockwise - **Down Arrow**: Soft drop (faster falling) - **Space**: Hard drop (instant drop) -- **Space** (when game over): Restart game +- **L**: Switch language (English ⇄ 中文) +- **R** (when game over): Restart game + +## 中文支持 / Chinese Support + +游戏支持中文界面!启动游戏后按 **L 键** 切换到中文模式。 + +The game supports Chinese interface! Press **L key** after starting the game to switch to Chinese mode. + +参见 [中文使用指南](HOW_TO_USE_CHINESE.md) 了解详细说明。 + +See [Chinese Usage Guide](HOW_TO_USE_CHINESE.md) for detailed instructions. ## Building and Running diff --git a/assets/fonts/HYSongYunLangHeiW-1.ttf b/assets/fonts/HYSongYunLangHeiW-1.ttf new file mode 100644 index 0000000..79ed7df Binary files /dev/null and b/assets/fonts/HYSongYunLangHeiW-1.ttf differ diff --git a/assets/fonts/README.md b/assets/fonts/README.md new file mode 100644 index 0000000..16e9786 --- /dev/null +++ b/assets/fonts/README.md @@ -0,0 +1,158 @@ +# 🎨 字体系统说明 / Font System Guide + +## ✅ 优化完成 / Optimization Complete + +**游戏现在只使用 `assets/fonts` 目录中的字体!** +不再依赖系统字体,确保跨平台一致性。 + +### 当前状态 / Current Status + +- **使用中的字体**: `HYSongYunLangHeiW-1.ttf` (3.6MB) +- **字体系统**: 已优化为仅使用项目本地字体 +- **状态**: ✅ 正常运行,无错误 + +--- + +## 🔧 字体管理 / Font Management + +### 快速设置 / Quick Setup + +运行字体设置脚本: +```bash +./scripts/setup_fonts.sh +``` + +提供以下功能: +- 🏆 从 Windows 系统复制微软雅黑 +- 📥 下载开源中文字体 +- 📖 字体放置说明 +- 🧪 测试字体配置 +- 🧹 清理字体目录 + +### 手动添加字体 / Manual Font Addition + +1. **将字体文件放入此目录** + ```bash + cp your_font.ttf assets/fonts/ + ``` + +2. **重新编译游戏** + ```bash + make build + ``` + +3. **运行游戏** + ```bash + ./tetris + ``` + +--- + +## 📊 字体优先级 / Font Priority + +游戏按以下优先级选择字体: + +``` +🥇 微软雅黑系列 + ├── msyh.ttf, yahei.ttf + └── microsoft*.ttf + +🥈 思源/Noto 系列 + ├── noto*.ttf, source*.ttf + └── 思源*.ttf + +🥉 苹果字体系列 + ├── pingfang*.ttf + └── hiragino*.ttf + +🏅 其他中文字体 + ├── simhei*.ttf, 宋*.ttf + └── 任意可用字体 + +🆘 最终回退 + └── Go 默认字体 +``` + +--- + +## 📋 支持的格式 / Supported Formats + +| 格式 | 支持状态 | 说明 | +|------|----------|------| +| `.ttf` | ✅ 完全支持 | **推荐使用** | +| `.otf` | ✅ 完全支持 | OpenType 字体 | +| `.ttc` | ⚠️ 部分支持 | 可能不兼容,建议转换为 TTF | + +--- + +## 🚀 推荐字体 / Recommended Fonts + +### 微软雅黑 / Microsoft YaHei (推荐) +```bash +# 从 Windows 系统复制 +cp /mnt/c/Windows/Fonts/msyh.ttc assets/fonts/ +# 或 TTF 格式 +cp /path/to/msyh.ttf assets/fonts/ +``` + +### 开源替代 / Open Source Alternatives +- **Noto Sans CJK** - Google 开源中文字体 +- **思源黑体** - Adobe/Google 联合开发 +- **文泉驿微米黑** - 经典开源中文字体 + +--- + +## 🛠️ 故障排除 / Troubleshooting + +### 常见问题 / Common Issues + +**❌ "collections not allowed" 错误** +- 解决方案: 使用 TTF 格式而不是 TTC +- 或者使用脚本下载开源字体 + +**❌ 中文显示为方块** +- 检查字体是否支持中文字符集 +- 确认字体文件完整 + +**❌ 字体未加载** +- 检查文件权限: `chmod 644 assets/fonts/*.ttf` +- 查看启动日志: `./tetris 2>&1 | grep 字体` + +### 调试信息 / Debug Info + +查看字体加载详情: +```bash +./tetris 2>&1 | grep -E "(字体|🎨|✅|❌)" +``` + +正常输出示例: +``` +🔍 在 assets/fonts 目录找到 1 个字体文件 +✅ 使用项目字体: assets/fonts/your_font.ttf +🎨 字体系统初始化完成,使用: assets/fonts/your_font.ttf +``` + +--- + +## 💡 优化特点 / Optimization Features + +- **🎯 专用目录**: 只使用 `assets/fonts` 目录 +- **🚀 智能选择**: 自动选择最适合的字体 +- **🔍 详细日志**: 完整的字体加载信息 +- **⚡ 快速回退**: 无字体时使用默认字体 +- **🌐 跨平台**: 不依赖系统字体路径 + +--- + +## ⚠️ 版权声明 / Copyright Notice + +**微软雅黑字体受版权保护**,请确保您有合法使用权限。 + +推荐使用开源字体: +- Noto Sans CJK (Apache License 2.0) +- 文泉驿微米黑 (GPL) +- 阿里巴巴普惠体 (免费商用) + +--- + +**🎉 字体系统已优化完成!享受一致的中文显示效果!** \ No newline at end of file diff --git a/go.mod b/go.mod index c2ead58..7faaa3b 100755 --- a/go.mod +++ b/go.mod @@ -2,13 +2,18 @@ module tetris go 1.24.2 -require github.com/hajimehoshi/ebiten/v2 v2.8.8 +require ( + github.com/hajimehoshi/ebiten/v2 v2.8.8 + golang.org/x/image v0.20.0 +) 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/go-text/typesetting v0.2.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 + golang.org/x/text v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index df10e34..4a8aeee 100755 --- a/go.sum +++ b/go.sum @@ -4,13 +4,23 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj 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/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho= +github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/hajimehoshi/bitmapfont/v3 v3.2.0 h1:0DISQM/rseKIJhdF29AkhvdzIULqNIIlXAGWit4ez1Q= +github.com/hajimehoshi/bitmapfont/v3 v3.2.0/go.mod h1:8gLqGatKVu0pwcNCJguW3Igg9WQqVXF0zg/RvrGQWyg= 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= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/internal/font/font.go b/internal/font/font.go new file mode 100644 index 0000000..515c457 --- /dev/null +++ b/internal/font/font.go @@ -0,0 +1,194 @@ +package font + +import ( + "bytes" + "fmt" + "image/color" + "io/fs" + "log" + "os" + "path/filepath" + "strings" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/text/v2" + "golang.org/x/image/font/gofont/goregular" +) + +// FontRenderer handles text rendering with assets/fonts directory support +type FontRenderer struct { + face *text.GoTextFace +} + +// NewFontRenderer creates a new font renderer using only assets/fonts directory +func NewFontRenderer() *FontRenderer { + var fontData []byte + var fontSource string + + // 优先加载 assets/fonts 目录中的字体 + if data, source := loadFromAssetsDirectory(); data != nil { + fontData = data + fontSource = source + log.Printf("✅ 使用项目字体: %s", source) + } else { + // 如果没有找到项目字体,使用 Go 默认字体 + fontData = goregular.TTF + fontSource = "Go默认字体 (建议添加中文字体到 assets/fonts 目录)" + log.Printf("⚠️ 未找到项目字体,使用默认字体。建议将中文字体放入 assets/fonts/ 目录") + } + + // 创建字体源 + source, err := createFontSource(fontData, fontSource) + if err != nil { + // 最终回退 - 使用 Go 默认字体 + log.Printf("❌ 字体加载失败,回退到默认字体: %v", err) + source, err = text.NewGoTextFaceSource(bytes.NewReader(goregular.TTF)) + if err != nil { + panic(fmt.Sprintf("无法加载默认字体: %v", err)) + } + fontSource = "Go默认字体 (回退)" + } + + face := &text.GoTextFace{ + Source: source, + Size: 16, + } + + log.Printf("🎨 字体系统初始化完成,使用: %s", fontSource) + return &FontRenderer{ + face: face, + } +} + +// loadFromAssetsDirectory 专门扫描 assets/fonts 目录并加载字体文件 +func loadFromAssetsDirectory() ([]byte, string) { + assetsPath := "assets/fonts" + + // 检查目录是否存在 + if _, err := os.Stat(assetsPath); os.IsNotExist(err) { + log.Printf("📁 assets/fonts 目录不存在,请创建并放入字体文件") + return nil, "" + } + + // 扫描目录下的所有字体文件 + var fontFiles []string + err := filepath.WalkDir(assetsPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !d.IsDir() { + ext := strings.ToLower(filepath.Ext(path)) + if ext == ".ttf" || ext == ".ttc" || ext == ".otf" { + fontFiles = append(fontFiles, path) + } + } + return nil + }) + + if err != nil { + log.Printf("❌ 扫描 assets/fonts 目录失败: %v", err) + return nil, "" + } + + if len(fontFiles) == 0 { + log.Printf("📂 assets/fonts 目录为空,请添加字体文件 (.ttf, .ttc, .otf)") + return nil, "" + } + + log.Printf("🔍 在 assets/fonts 目录找到 %d 个字体文件", len(fontFiles)) + + // 字体优先级:微软雅黑 > 其他中文字体 > 任意字体 + fontPriorities := [][]string{ + {"msyh", "yahei", "microsoft"}, // 微软雅黑系列 + {"noto", "source", "思源", "源"}, // 思源/Noto系列 + {"pingfang", "hiragino", "苹方"}, // 苹果字体系列 + {"simhei", "simkai", "simsun", "宋", "黑"}, // 系统中文字体 + } + + // 按优先级查找字体 + for _, priorityGroup := range fontPriorities { + for _, keyword := range priorityGroup { + for _, fontFile := range fontFiles { + fileName := strings.ToLower(filepath.Base(fontFile)) + if strings.Contains(fileName, keyword) { + if data, err := tryLoadFont(fontFile); err == nil { + log.Printf("🎯 找到优先字体 (%s): %s", keyword, fontFile) + return data, fontFile + } else { + log.Printf("⚠️ 字体文件损坏或不支持: %s, 错误: %v", fontFile, err) + } + } + } + } + } + + // 如果没有找到优先字体,使用第一个可用的字体 + for _, fontFile := range fontFiles { + if data, err := tryLoadFont(fontFile); err == nil { + log.Printf("📝 使用可用字体: %s", fontFile) + return data, fontFile + } else { + log.Printf("⚠️ 跳过不可用字体: %s, 错误: %v", fontFile, err) + } + } + + log.Printf("❌ assets/fonts 目录中没有可用的字体文件") + return nil, "" +} + +// tryLoadFont 尝试加载字体文件并验证格式 +func tryLoadFont(fontPath string) ([]byte, error) { + data, err := os.ReadFile(fontPath) + if err != nil { + return nil, fmt.Errorf("读取字体文件失败: %w", err) + } + + // 验证字体数据是否可以被解析 + _, err = createFontSource(data, fontPath) + if err != nil { + return nil, fmt.Errorf("字体格式验证失败: %w", err) + } + + return data, nil +} + +// createFontSource 创建字体源,处理可能的collection错误 +func createFontSource(fontData []byte, fontSource string) (*text.GoTextFaceSource, error) { + // 尝试直接加载 + source, err := text.NewGoTextFaceSource(bytes.NewReader(fontData)) + if err != nil { + // 如果是 collection 错误,提供更详细的信息 + errStr := err.Error() + if strings.Contains(errStr, "collection") { + return nil, fmt.Errorf("TTC集合字体文件不支持,请使用TTF格式: %s", filepath.Base(fontSource)) + } + return nil, fmt.Errorf("字体文件格式错误: %w", err) + } + + return source, nil +} + +// DrawText draws text at the specified position with proper Chinese support +func (fr *FontRenderer) DrawText(screen *ebiten.Image, str string, x, y float64, clr color.Color) { + op := &text.DrawOptions{} + op.GeoM.Translate(x, y) + op.ColorScale.ScaleWithColor(clr) + text.Draw(screen, str, fr.face, op) +} + +// SetFontSize sets the font size +func (fr *FontRenderer) SetFontSize(size float64) { + fr.face.Size = size +} + +// MeasureText returns text dimensions with better accuracy +func (fr *FontRenderer) MeasureText(str string) (float64, float64) { + width, _ := text.Measure(str, fr.face, 0) + return width, fr.face.Size +} + +// GetFontInfo returns information about the currently loaded font +func (fr *FontRenderer) GetFontInfo() string { + return fmt.Sprintf("字体大小: %.1f", fr.face.Size) +} diff --git a/internal/game/game.go b/internal/game/game.go index 7615f7f..bb1be62 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -6,12 +6,13 @@ import ( "math/rand" "time" + "tetris/internal/font" + "tetris/internal/i18n" "tetris/internal/tetromino" "tetris/internal/types" "tetris/pkg/config" "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/vector" ) @@ -28,6 +29,7 @@ type Game struct { gameOver bool lastMoveDown time.Time lastRotate time.Time + fontRenderer *font.FontRenderer } // New creates a new game instance @@ -37,6 +39,7 @@ func New() *Game { dropInterval: config.InitialDropInterval, lastMoveDown: time.Now(), lastRotate: time.Now(), + fontRenderer: font.NewFontRenderer(), } for i := range g.board { @@ -121,8 +124,8 @@ func (g *Game) Draw(screen *ebiten.Image) { // Draw next piece preview if g.nextPiece != nil { - previewLabel := "NEXT:" - ebitenutil.DebugPrintAt(screen, previewLabel, config.PreviewX-config.BlockSize, config.BlockSize) + text := i18n.GetText() + g.fontRenderer.DrawText(screen, text.Next, float64(config.PreviewX-config.BlockSize), float64(config.BlockSize), color.RGBA{255, 255, 255, 255}) for _, block := range g.nextPiece.Blocks { x := config.PreviewX + block.X*config.BlockSize @@ -137,16 +140,19 @@ func (g *Game) Draw(screen *ebiten.Image) { } // 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, config.PreviewX-config.BlockSize, config.ScreenHeight-4*config.BlockSize) - ebitenutil.DebugPrintAt(screen, levelStr, config.PreviewX-config.BlockSize, config.ScreenHeight-3*config.BlockSize) + text := i18n.GetText() + scoreStr := fmt.Sprintf(text.Score, g.score) + levelStr := fmt.Sprintf(text.Level, g.level) + g.fontRenderer.DrawText(screen, scoreStr, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight-4*config.BlockSize), color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, levelStr, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight-3*config.BlockSize), color.RGBA{255, 255, 255, 255}) + + // Draw controls instructions + g.drawControls(screen) if g.gameOver { - gameOverStr := "Game Over!" - restartStr := "Press SPACE to restart" - ebitenutil.DebugPrintAt(screen, gameOverStr, config.PreviewX-config.BlockSize, config.ScreenHeight/2) - ebitenutil.DebugPrintAt(screen, restartStr, config.PreviewX-config.BlockSize, config.ScreenHeight/2+20) + text := i18n.GetText() + 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.PreviewX-config.BlockSize), float64(config.ScreenHeight/2+20), color.RGBA{255, 255, 255, 255}) } } @@ -157,6 +163,22 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeigh // handleInput processes player input func (g *Game) handleInput() { + // Language switch + if inpututil.IsKeyJustPressed(ebiten.KeyL) { + i18n.SwitchLanguage() + } + + // Restart game when game over + if g.gameOver && inpututil.IsKeyJustPressed(ebiten.KeyR) { + g.restart() + return + } + + // Skip other inputs when game over + if g.gameOver { + return + } + // Move left/right if inpututil.IsKeyJustPressed(ebiten.KeyLeft) { g.tryMove(-1, 0) @@ -439,3 +461,37 @@ func max(a, b int) int { } return b } + +// drawControls renders the control instructions on the right side of the screen +func (g *Game) drawControls(screen *ebiten.Image) { + // Starting position for controls display + controlsX := float64(config.PreviewX - config.BlockSize) + controlsY := float64(config.BlockSize * 8) // Below the next piece preview + + text := i18n.GetText() + + // Controls title + g.fontRenderer.DrawText(screen, text.Controls, controlsX, controlsY, color.RGBA{255, 255, 255, 255}) + controlsY += 25 + + // Control instructions + controls := []string{ + text.Move, + text.Rotate, + text.SoftDrop, + text.HardDrop, + "", + text.Language, + text.PressL, + } + + for _, control := range controls { + g.fontRenderer.DrawText(screen, control, controlsX, controlsY, color.RGBA{255, 255, 255, 255}) + controlsY += 18 + } +} + +// restart restarts the game +func (g *Game) restart() { + *g = *New() +} diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go new file mode 100644 index 0000000..902ae8b --- /dev/null +++ b/internal/i18n/i18n.go @@ -0,0 +1,85 @@ +package i18n + +// Language represents supported languages +type Language int + +const ( + English Language = iota + Chinese +) + +// Localization contains all text strings for the game +type Localization struct { + Next string + Score string + Level string + Controls string + Move string + Rotate string + SoftDrop string + HardDrop string + GameOver string + Restart string + Language string + PressL string +} + +// currentLanguage holds the current language setting +var currentLanguage Language = English + +// localizations contains all supported language strings +var localizations = map[Language]Localization{ + English: { + Next: "NEXT:", + Score: "Score: %d", + Level: "Level: %d", + Controls: "CONTROLS:", + Move: "← → Move", + Rotate: "↑ Rotate", + SoftDrop: "↓ Soft Drop", + HardDrop: "Space Hard Drop", + GameOver: "Game Over!", + Restart: "Press R to restart", + Language: "Language:", + PressL: "Press L to switch", + }, + Chinese: { + Next: "下一个:", + Score: "分数: %d", + Level: "等级: %d", + Controls: "操作说明:", + Move: "← → 移动", + Rotate: "↑ 旋转", + SoftDrop: "↓ 软降落", + HardDrop: "空格 硬降落", + GameOver: "游戏结束!", + Restart: "按 R 重新开始", + Language: "语言:", + PressL: "按 L 切换", + }, +} + +// GetText returns localized text for the current language +func GetText() Localization { + return localizations[currentLanguage] +} + +// GetCurrentLanguage returns the current language +func GetCurrentLanguage() Language { + return currentLanguage +} + +// SwitchLanguage toggles between supported languages +func SwitchLanguage() { + switch currentLanguage { + case English: + currentLanguage = Chinese + case Chinese: + currentLanguage = English + } +} + +// SetLanguage sets the current language +func SetLanguage(lang Language) { + currentLanguage = lang +} diff --git a/main.go b/main.go index d666cdd..dbe07be 100755 --- a/main.go +++ b/main.go @@ -1,15 +1,68 @@ package main import ( + "image/color" "log" + "os" + "tetris/internal/font" "tetris/internal/game" "tetris/pkg/config" "github.com/hajimehoshi/ebiten/v2" ) +type FontTestGame struct { + fontRenderer *font.FontRenderer +} + +func (g *FontTestGame) Update() error { + return nil +} + +func (g *FontTestGame) Draw(screen *ebiten.Image) { + // Test Chinese text rendering + g.fontRenderer.DrawText(screen, "下一个:", 50, 50, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "分数: 1000", 50, 80, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "等级: 5", 50, 110, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "操作说明:", 50, 140, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "← → 移动", 50, 170, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "↑ 旋转", 50, 200, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "↓ 软降落", 50, 230, color.RGBA{255, 255, 255, 255}) + g.fontRenderer.DrawText(screen, "空格 硬降落", 50, 260, color.RGBA{255, 255, 255, 255}) + + // Test English text + g.fontRenderer.DrawText(screen, "Font Test - English", 300, 50, color.RGBA{0, 255, 0, 255}) + g.fontRenderer.DrawText(screen, "NEXT:", 300, 80, color.RGBA{0, 255, 0, 255}) + g.fontRenderer.DrawText(screen, "Score: 1000", 300, 110, color.RGBA{0, 255, 0, 255}) + g.fontRenderer.DrawText(screen, "Level: 5", 300, 140, color.RGBA{0, 255, 0, 255}) +} + +func (g *FontTestGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { + return 640, 480 +} + +func testFont() { + log.Println("Testing Chinese font rendering...") + game := &FontTestGame{ + fontRenderer: font.NewFontRenderer(), + } + + ebiten.SetWindowSize(640, 480) + ebiten.SetWindowTitle("Chinese Font Test") + + if err := ebiten.RunGame(game); err != nil { + log.Fatal(err) + } +} + func main() { + // Check if font test is requested + if len(os.Args) > 1 && os.Args[1] == "--test-font" { + testFont() + return + } + ebiten.SetWindowSize(config.ScreenWidth, config.ScreenHeight) ebiten.SetWindowTitle("Tetris")