添加中文支持

main v1.0.1
huyinsong 1 week ago
parent fa81ccec6e
commit fdc0c294f3
  1. 112
      DEPRECATED_METHODS_FIXED.md
  2. 13
      README.md
  3. BIN
      assets/fonts/HYSongYunLangHeiW-1.ttf
  4. 158
      assets/fonts/README.md
  5. 7
      go.mod
  6. 10
      go.sum
  7. 194
      internal/font/font.go
  8. 78
      internal/game/game.go
  9. 85
      internal/i18n/i18n.go
  10. 53
      main.go

@ -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 版本更新以享受最新优化

@ -47,7 +47,18 @@ The project follows Go best practices with a clean package structure:
- **Up Arrow**: Rotate piece clockwise - **Up Arrow**: Rotate piece clockwise
- **Down Arrow**: Soft drop (faster falling) - **Down Arrow**: Soft drop (faster falling)
- **Space**: Hard drop (instant drop) - **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 ## Building and Running

@ -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)
- 阿里巴巴普惠体 (免费商用)
---
**🎉 字体系统已优化完成!享受一致的中文显示效果!**

@ -2,13 +2,18 @@ module tetris
go 1.24.2 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 ( require (
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 // indirect github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/purego v0.8.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 github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
) )

@ -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/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 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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 h1:xyMxOAn52T1tQ+j3vdieZ7auDBOXmvjUprSrxaIbsi8=
github.com/hajimehoshi/ebiten/v2 v2.8.8/go.mod h1:durJ05+OYnio9b8q0sEtOgaNeBEQG7Yr7lRviAciYbs= 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 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 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 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= 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 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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=

@ -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)
}

@ -6,12 +6,13 @@ import (
"math/rand" "math/rand"
"time" "time"
"tetris/internal/font"
"tetris/internal/i18n"
"tetris/internal/tetromino" "tetris/internal/tetromino"
"tetris/internal/types" "tetris/internal/types"
"tetris/pkg/config" "tetris/pkg/config"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
@ -28,6 +29,7 @@ type Game struct {
gameOver bool gameOver bool
lastMoveDown time.Time lastMoveDown time.Time
lastRotate time.Time lastRotate time.Time
fontRenderer *font.FontRenderer
} }
// New creates a new game instance // New creates a new game instance
@ -37,6 +39,7 @@ func New() *Game {
dropInterval: config.InitialDropInterval, dropInterval: config.InitialDropInterval,
lastMoveDown: time.Now(), lastMoveDown: time.Now(),
lastRotate: time.Now(), lastRotate: time.Now(),
fontRenderer: font.NewFontRenderer(),
} }
for i := range g.board { for i := range g.board {
@ -121,8 +124,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Draw next piece preview // Draw next piece preview
if g.nextPiece != nil { if g.nextPiece != nil {
previewLabel := "NEXT:" text := i18n.GetText()
ebitenutil.DebugPrintAt(screen, previewLabel, config.PreviewX-config.BlockSize, config.BlockSize) 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 { for _, block := range g.nextPiece.Blocks {
x := config.PreviewX + block.X*config.BlockSize 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 // Draw score and level with larger font
scoreStr := fmt.Sprintf("Score: %d", g.score) text := i18n.GetText()
levelStr := fmt.Sprintf("Level: %d", g.level) scoreStr := fmt.Sprintf(text.Score, g.score)
ebitenutil.DebugPrintAt(screen, scoreStr, config.PreviewX-config.BlockSize, config.ScreenHeight-4*config.BlockSize) levelStr := fmt.Sprintf(text.Level, g.level)
ebitenutil.DebugPrintAt(screen, levelStr, config.PreviewX-config.BlockSize, config.ScreenHeight-3*config.BlockSize) 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 { if g.gameOver {
gameOverStr := "Game Over!" text := i18n.GetText()
restartStr := "Press SPACE to restart" g.fontRenderer.DrawText(screen, text.GameOver, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight/2), color.RGBA{255, 255, 255, 255})
ebitenutil.DebugPrintAt(screen, gameOverStr, config.PreviewX-config.BlockSize, config.ScreenHeight/2) g.fontRenderer.DrawText(screen, text.Restart, float64(config.PreviewX-config.BlockSize), float64(config.ScreenHeight/2+20), color.RGBA{255, 255, 255, 255})
ebitenutil.DebugPrintAt(screen, restartStr, config.PreviewX-config.BlockSize, config.ScreenHeight/2+20)
} }
} }
@ -157,6 +163,22 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeigh
// handleInput processes player input // handleInput processes player input
func (g *Game) handleInput() { 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 // Move left/right
if inpututil.IsKeyJustPressed(ebiten.KeyLeft) { if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
g.tryMove(-1, 0) g.tryMove(-1, 0)
@ -439,3 +461,37 @@ func max(a, b int) int {
} }
return b 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()
}

@ -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
}

@ -1,15 +1,68 @@
package main package main
import ( import (
"image/color"
"log" "log"
"os"
"tetris/internal/font"
"tetris/internal/game" "tetris/internal/game"
"tetris/pkg/config" "tetris/pkg/config"
"github.com/hajimehoshi/ebiten/v2" "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() { 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.SetWindowSize(config.ScreenWidth, config.ScreenHeight)
ebiten.SetWindowTitle("Tetris") ebiten.SetWindowTitle("Tetris")

Loading…
Cancel
Save