From c1ca21bdba9759c58053ca8268c84d4956d1bf69 Mon Sep 17 00:00:00 2001 From: huyinsong Date: Mon, 9 Jun 2025 00:27:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0windows=E5=92=8Cmacos?= =?UTF-8?q?=E5=88=86=E5=8F=91=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BUILD.md | 143 ++++++ Makefile | 526 ++++++++++++-------- assets/icons/README.md | 89 ++++ assets/icons/app.icns | Bin 0 -> 98396 bytes assets/icons/icon_128.png | Bin 0 -> 2053 bytes assets/icons/icon_16.png | Bin 0 -> 179 bytes assets/icons/icon_256.png | Bin 0 -> 6548 bytes assets/icons/icon_32.png | Bin 0 -> 271 bytes assets/icons/icon_64.png | Bin 0 -> 743 bytes build.sh | 267 ++++++++++ internal/font/font.go | 44 +- patch/0001-fix-IBlock-disappear-issue.patch | 151 ------ 12 files changed, 852 insertions(+), 368 deletions(-) create mode 100644 BUILD.md create mode 100644 assets/icons/README.md create mode 100644 assets/icons/app.icns create mode 100644 assets/icons/icon_128.png create mode 100644 assets/icons/icon_16.png create mode 100644 assets/icons/icon_256.png create mode 100644 assets/icons/icon_32.png create mode 100644 assets/icons/icon_64.png create mode 100755 build.sh delete mode 100755 patch/0001-fix-IBlock-disappear-issue.patch diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..f05bc7d --- /dev/null +++ b/BUILD.md @@ -0,0 +1,143 @@ +# Tetris Game 构建系统 + +这是一个统一的构建系统,支持 Windows、macOS 和 Linux 平台的应用打包,所有分发文件统一存放在 `dist/` 目录中。 + +## 🚀 快速开始 + +### 构建所有平台 +```bash +make all +``` + +### 构建特定平台 +```bash +make windows # 构建 Windows 版本 +make macos # 构建 macOS 版本 +make linux # 构建 Linux 版本 +``` + +### 查看帮助 +```bash +make help +``` + +## 📁 目录结构 + +构建完成后,`dist/` 目录结构如下: + +``` +dist/ +├── TetrisGame-Windows-v1.0.zip # Windows 分发包 +├── TetrisGame-macOS-v1.0.dmg # macOS 分发包 +├── TetrisGame-Linux-v1.0.tar.gz # Linux 分发包 +├── windows/ # Windows 构建临时目录 +├── macos/ # macOS 构建临时目录 +└── linux/ # Linux 构建临时目录 +``` + +## 🛠️ 可用命令 + +### 基础命令 +- `make help` - 显示帮助信息 +- `make info` - 显示构建信息 +- `make check-env` - 检查构建环境 +- `make clean` - 清理所有构建文件 + +### 构建命令 +- `make windows` - 构建 Windows 版本 +- `make macos` - 构建 macOS 版本 +- `make linux` - 构建 Linux 版本 +- `make all` - 构建所有平台版本 + +### 验证命令 +- `make verify` - 验证构建结果 +- `make run` - 运行游戏(开发模式) +- `make test` - 运行测试 + +### 依赖管理 +- `make deps` - 下载依赖 + +### 迁移命令 +- `make migrate` - 迁移旧版本文件到 dist 目录 + +## 📦 分发包内容 + +### Windows 版本 (TetrisGame-Windows-v1.0.zip) +``` +TetrisGame-Windows-v1.0/ +├── TetrisGame.exe # 主程序 +├── 启动游戏.bat # 中文启动脚本 +├── README.txt # 英文说明 +├── 安装说明.txt # 中文说明 +└── assets/ # 资源文件 + └── fonts/ # 字体文件 + ├── HYSongYunLangHeiW-1.ttf + └── README.md +``` + +### macOS 版本 (TetrisGame-macOS-v1.0.dmg) +- 包含完整的 TetrisGame.app 应用包 +- 支持拖拽到 Applications 文件夹安装 +- 包含应用图标和中文显示名称 + +### Linux 版本 (TetrisGame-Linux-v1.0.tar.gz) +``` +TetrisGame-Linux-v1.0/ +├── tetris # 主程序 +├── start_game.sh # 启动脚本 +├── README.txt # 说明文档 +└── assets/ # 资源文件 + └── fonts/ # 字体文件 +``` + +## 🔧 系统要求 + +### 构建环境 +- Go 1.19 或更高版本 +- macOS (用于构建 macOS 版本的 DMG) +- zip 命令 (用于创建 Windows 压缩包) +- tar 命令 (用于创建 Linux 压缩包) + +### 运行环境 +- **Windows**: Windows 10/11 (64-bit) +- **macOS**: macOS 10.15 或更高版本 +- **Linux**: 64-bit Linux 发行版,支持 X11 或 Wayland + +## 🎮 游戏控制 + +- **←→** 方向键:左右移动方块 +- **↑** 方向键:旋转方块 +- **↓** 方向键:快速下降 +- **空格键**:瞬间下降到底部 +- **L 键**:切换中英文界面 +- **R 键**:游戏结束后重新开始 + +## 🌍 多语言支持 + +游戏支持中英文双语界面,按 L 键可以实时切换语言。 + +## 📝 版本信息 + +- **版本**: v1.0 +- **构建时间**: 自动生成 +- **Git 提交**: 自动检测 + +## 🔍 故障排除 + +### 构建失败 +1. 检查 Go 环境:`go version` +2. 检查依赖:`make deps` +3. 清理重建:`make clean && make all` + +### 字体问题 +- 确保 `assets/fonts/` 目录存在 +- 确保字体文件 `HYSongYunLangHeiW-1.ttf` 存在 + +### macOS DMG 创建失败 +- 确保在 macOS 系统上运行 +- 确保有足够的磁盘空间 +- 检查 hdiutil 命令是否可用 + +## 📄 许可证 + +© 2025 Tetris Game. All rights reserved. \ No newline at end of file diff --git a/Makefile b/Makefile index 938c8d0..dd8b6d5 100644 --- a/Makefile +++ b/Makefile @@ -1,223 +1,331 @@ -# Tetris Game Makefile -# Supports cross-compilation for Windows, macOS, and Linux - -# Variables -BINARY_NAME=tetris -MAIN_PATH=. -BUILD_DIR=build -VERSION?=dev -LDFLAGS=-ldflags "-s -w" - -# Platform specific settings -WINDOWS_BINARY=$(BINARY_NAME).exe -MACOS_BINARY=$(BINARY_NAME)_macos -LINUX_BINARY=$(BINARY_NAME)_linux - -# Default target +# Tetris Game - 统一构建系统 +# 支持 Windows 和 macOS 应用打包,统一使用 dist 目录 + +# 基础配置 +BINARY_NAME := tetris +APP_NAME := TetrisGame +VERSION := 1.0 +BUILD_TIME := $(shell date '+%Y-%m-%d %H:%M:%S') +GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") + +# 目录配置 +DIST_DIR := dist +ASSETS_DIR := assets +BUILD_TEMP := .build_temp + +# Go 构建标志 +GO_LDFLAGS := -ldflags "-s -w" +GO_LDFLAGS_GUI := -ldflags "-s -w -H windowsgui" + +# 平台特定配置 +WINDOWS_DIR := $(DIST_DIR)/windows +MACOS_DIR := $(DIST_DIR)/macos +LINUX_DIR := $(DIST_DIR)/linux + +# 版本文件名 +WINDOWS_ZIP := $(APP_NAME)-Windows-v$(VERSION).zip +MACOS_DMG := $(APP_NAME)-macOS-v$(VERSION).dmg +LINUX_TAR := $(APP_NAME)-Linux-v$(VERSION).tar.gz + +# 颜色输出 +BLUE := \033[34m +GREEN := \033[32m +YELLOW := \033[33m +RED := \033[31m +RESET := \033[0m + +.DEFAULT_GOAL := help + +# 帮助信息 .PHONY: help -help: ## Show this help message - @echo "Available targets:" - @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) - -# Build targets -.PHONY: build -build: ## Build for current platform - @echo "Building $(BINARY_NAME) for current platform..." - go build $(LDFLAGS) -o $(BINARY_NAME) $(MAIN_PATH) - @echo "Build complete: $(BINARY_NAME)" - -.PHONY: build-windows -build-windows: ## Build for Windows (64-bit) - requires Windows or cross-compilation setup - @echo "Building $(BINARY_NAME) for Windows..." - @echo "Note: Cross-compilation for Ebitengine requires proper CGO setup" - @mkdir -p $(BUILD_DIR) - CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(WINDOWS_BINARY) $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(WINDOWS_BINARY)" - -.PHONY: build-windows-native -build-windows-native: ## Build for Windows using native Go (may have limitations) - @echo "Building $(BINARY_NAME) for Windows (native Go)..." - @mkdir -p $(BUILD_DIR) - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -tags ebitengine -o $(BUILD_DIR)/$(WINDOWS_BINARY) $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(WINDOWS_BINARY)" - -.PHONY: build-macos -build-macos: ## Build for macOS (Intel) - requires macOS or cross-compilation setup - @echo "Building $(BINARY_NAME) for macOS (Intel)..." - @echo "Note: Cross-compilation for Ebitengine requires proper CGO setup" - @mkdir -p $(BUILD_DIR) - CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(MACOS_BINARY)_amd64 $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(MACOS_BINARY)_amd64" - -.PHONY: build-macos-arm -build-macos-arm: ## Build for macOS (Apple Silicon) - requires macOS or cross-compilation setup - @echo "Building $(BINARY_NAME) for macOS (Apple Silicon)..." - @echo "Note: Cross-compilation for Ebitengine requires proper CGO setup" - @mkdir -p $(BUILD_DIR) - CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(MACOS_BINARY)_arm64 $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(MACOS_BINARY)_arm64" - -.PHONY: build-macos-current -build-macos-current: ## Build for current macOS architecture - @echo "Building $(BINARY_NAME) for current macOS architecture..." - @mkdir -p $(BUILD_DIR) - go build $(LDFLAGS) -o $(BUILD_DIR)/$(MACOS_BINARY)_$$(go env GOARCH) $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(MACOS_BINARY)_$$(go env GOARCH)" - -.PHONY: build-linux -build-linux: ## Build for Linux (64-bit) - requires Linux or cross-compilation setup - @echo "Building $(BINARY_NAME) for Linux..." - @echo "Note: Cross-compilation for Ebitengine requires proper CGO setup" - @mkdir -p $(BUILD_DIR) - CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BUILD_DIR)/$(LINUX_BINARY) $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(LINUX_BINARY)" - -.PHONY: build-current-platform -build-current-platform: ## Build for current platform (recommended) - @echo "Building $(BINARY_NAME) for current platform: $$(go env GOOS)/$$(go env GOARCH)" - @mkdir -p $(BUILD_DIR) - go build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)_$$(go env GOOS)_$$(go env GOARCH) $(MAIN_PATH) - @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)_$$(go env GOOS)_$$(go env GOARCH)" - -.PHONY: build-all -build-all: ## Build for all platforms (requires proper cross-compilation setup) - @echo "Attempting to build for all platforms..." - @echo "Note: This may fail without proper cross-compilation environment" - $(MAKE) build-current-platform - -$(MAKE) build-windows-native - @echo "Build summary:" - @ls -la $(BUILD_DIR)/ 2>/dev/null || echo "No builds completed successfully" - -# Development targets -.PHONY: run -run: ## Run the game - @echo "Starting Tetris game..." - go run $(MAIN_PATH) +help: ## 显示帮助信息 + @echo "$(BLUE)🎮 Tetris Game 构建系统$(RESET)" + @echo "" + @echo "$(GREEN)基础命令:$(RESET)" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-20s$(RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "$(GREEN)示例:$(RESET)" + @echo " make all # 构建所有平台" + @echo " make windows # 仅构建 Windows 版本" + @echo " make macos # 仅构建 macOS 版本" + @echo " make clean # 清理所有构建文件" -.PHONY: dev -dev: clean build run ## Clean, build and run for development +# 信息显示 +.PHONY: info +info: ## 显示构建信息 + @echo "$(BLUE)📋 构建信息$(RESET)" + @echo " 应用名称: $(APP_NAME)" + @echo " 版本: v$(VERSION)" + @echo " 构建时间: $(BUILD_TIME)" + @echo " Git 提交: $(GIT_COMMIT)" + @echo " Go 版本: $(shell go version)" + @echo " 当前平台: $(shell go env GOOS)/$(shell go env GOARCH)" + @echo " 分发目录: $(DIST_DIR)/" + +# 环境检查 +.PHONY: check-env +check-env: ## 检查构建环境 + @echo "$(BLUE)🔍 环境检查$(RESET)" + @go version >/dev/null 2>&1 || (echo "$(RED)❌ Go 未安装$(RESET)" && exit 1) + @echo "$(GREEN)✅ Go 环境正常$(RESET)" + @[ -d "$(ASSETS_DIR)" ] || (echo "$(RED)❌ assets 目录缺失$(RESET)" && exit 1) + @echo "$(GREEN)✅ 资源目录存在$(RESET)" + @[ -f "main.go" ] || (echo "$(RED)❌ main.go 文件缺失$(RESET)" && exit 1) + @echo "$(GREEN)✅ 源代码文件存在$(RESET)" + +# 准备目录 +.PHONY: prepare +prepare: ## 准备构建目录 + @echo "$(BLUE)📁 准备构建目录$(RESET)" + @mkdir -p $(DIST_DIR) + @mkdir -p $(WINDOWS_DIR) + @mkdir -p $(MACOS_DIR) + @mkdir -p $(LINUX_DIR) + @mkdir -p $(BUILD_TEMP) + +# 清理 +.PHONY: clean +clean: ## 清理构建文件 + @echo "$(BLUE)🧹 清理构建文件$(RESET)" + @rm -rf $(DIST_DIR) + @rm -rf $(BUILD_TEMP) + @rm -f *.zip *.dmg *.tar.gz + @rm -f $(BINARY_NAME) $(BINARY_NAME).exe + @rm -rf TetrisGame.app + @echo "$(GREEN)✅ 清理完成$(RESET)" + +# Windows 构建 +.PHONY: build-windows-binary +build-windows-binary: check-env prepare + @echo "$(BLUE)🪟 构建 Windows 二进制文件$(RESET)" + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(GO_LDFLAGS_GUI) -o $(BUILD_TEMP)/$(APP_NAME).exe . + @echo "$(GREEN)✅ Windows 二进制文件构建完成$(RESET)" + +.PHONY: create-windows-startup-script +create-windows-startup-script: + @echo "创建 Windows 启动脚本..." + @echo '@echo off' > $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'chcp 65001 >nul' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo 🎮 正在启动 Tetris Game...' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo.' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo 🎯 游戏控制:' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo ←→ 左右移动' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo ↑ 旋转方块' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo ↓ 快速下降' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo 空格 瞬间下降' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo L 切换中英文' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo 'echo.' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + @echo '"%~dp0$(APP_NAME).exe"' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/启动游戏.bat + +.PHONY: create-windows-docs +create-windows-docs: + @echo "创建 Windows 说明文档..." + @echo '🎮 Tetris Game - Windows Version v1.0' > $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '📥 Installation: This is a portable version, no installation required.' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '🚀 How to Start: Double-click TetrisGame.exe or 启动游戏.bat' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '🎯 Game Controls: ←→ Move, ↑ Rotate, ↓ Soft drop, Space Hard drop, L Language' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '🔧 System Requirements: Windows 10/11 (64-bit)' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + @echo '© 2025 Tetris Game. All rights reserved.' >> $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/README.txt + +.PHONY: package-windows +package-windows: build-windows-binary + @echo "$(BLUE)📦 打包 Windows 应用$(RESET)" + + # 创建 Windows 应用目录 + @rm -rf $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION) + @mkdir -p $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION) + + # 复制主程序文件 + @cp $(BUILD_TEMP)/$(APP_NAME).exe $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/ + + # 复制资源文件 + @cp -r $(ASSETS_DIR) $(WINDOWS_DIR)/$(APP_NAME)-Windows-v$(VERSION)/ + + # 创建启动脚本和文档 + @$(MAKE) create-windows-startup-script + @$(MAKE) create-windows-docs + + # 创建压缩包 + @cd $(WINDOWS_DIR) && zip -r ../$(WINDOWS_ZIP) $(APP_NAME)-Windows-v$(VERSION)/ + + @echo "$(GREEN)✅ Windows 应用打包完成: $(DIST_DIR)/$(WINDOWS_ZIP)$(RESET)" + +# macOS 构建 +.PHONY: build-macos-binary +build-macos-binary: check-env prepare + @echo "$(BLUE)🍎 构建 macOS 二进制文件$(RESET)" + go build $(GO_LDFLAGS) -o $(BUILD_TEMP)/$(BINARY_NAME) . + @echo "$(GREEN)✅ macOS 二进制文件构建完成$(RESET)" + +.PHONY: create-macos-plist +create-macos-plist: + @echo "创建 macOS Info.plist..." + @echo '' > $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo '' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo '' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo '' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleDisplayName' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' 俄罗斯方块' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleExecutable' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' $(BINARY_NAME)' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleIdentifier' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' com.tetrisgame.app' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleName' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' $(APP_NAME)' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleShortVersionString' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' $(VERSION)' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' CFBundleIconFile' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' app' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' LSApplicationCategoryType' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo ' public.app-category.games' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo '' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + @echo '' >> $(MACOS_DIR)/$(APP_NAME).app/Contents/Info.plist + +.PHONY: create-dmg +create-dmg: + @echo "创建 DMG 文件..." + @DMG_SIZE="50m" && \ + TEMP_DMG="$(BUILD_TEMP)/temp_$(MACOS_DMG)" && \ + MOUNT_POINT="$(BUILD_TEMP)/dmg_mount" && \ + rm -rf $$MOUNT_POINT && \ + mkdir -p $$MOUNT_POINT && \ + hdiutil create -size $$DMG_SIZE -fs HFS+ -volname "Tetris Game" $$TEMP_DMG && \ + hdiutil attach $$TEMP_DMG -mountpoint $$MOUNT_POINT && \ + cp -R $(MACOS_DIR)/$(APP_NAME).app $$MOUNT_POINT/ && \ + ln -s /Applications $$MOUNT_POINT/Applications && \ + hdiutil detach $$MOUNT_POINT && \ + hdiutil convert $$TEMP_DMG -format UDZO -o $(DIST_DIR)/$(MACOS_DMG) && \ + rm -f $$TEMP_DMG + +.PHONY: package-macos +package-macos: build-macos-binary + @echo "$(BLUE)📦 打包 macOS 应用$(RESET)" + + # 创建 .app 包结构 + @rm -rf $(MACOS_DIR)/$(APP_NAME).app + @mkdir -p $(MACOS_DIR)/$(APP_NAME).app/Contents/MacOS + @mkdir -p $(MACOS_DIR)/$(APP_NAME).app/Contents/Resources + + # 复制可执行文件 + @cp $(BUILD_TEMP)/$(BINARY_NAME) $(MACOS_DIR)/$(APP_NAME).app/Contents/MacOS/ + @chmod +x $(MACOS_DIR)/$(APP_NAME).app/Contents/MacOS/$(BINARY_NAME) + + # 复制资源文件 + @cp -r $(ASSETS_DIR) $(MACOS_DIR)/$(APP_NAME).app/Contents/Resources/ + + # 创建 Info.plist + @$(MAKE) create-macos-plist + + # 复制应用图标 + @if [ -f "$(ASSETS_DIR)/icons/app.icns" ]; then \ + cp "$(ASSETS_DIR)/icons/app.icns" $(MACOS_DIR)/$(APP_NAME).app/Contents/Resources/; \ + echo "$(GREEN)✅ 已复制应用图标 app.icns$(RESET)"; \ + else \ + echo "$(YELLOW)⚠️ 图标文件 $(ASSETS_DIR)/icons/app.icns 不存在$(RESET)"; \ + fi + + # 创建 DMG + @$(MAKE) create-dmg + + @echo "$(GREEN)✅ macOS 应用打包完成: $(DIST_DIR)/$(MACOS_DMG)$(RESET)" + +# Linux 构建 +.PHONY: build-linux-binary +build-linux-binary: check-env prepare + @echo "$(BLUE)🐧 构建 Linux 二进制文件$(RESET)" + @echo "$(YELLOW)⚠️ 注意: Linux 交叉编译需要 CGO 支持,建议在 Linux 系统上构建$(RESET)" + @if [ "$$(go env GOOS)" = "linux" ]; then \ + go build $(GO_LDFLAGS) -o $(BUILD_TEMP)/$(BINARY_NAME)_linux .; \ + else \ + echo "$(YELLOW)⚠️ 当前不在 Linux 系统,跳过 Linux 构建$(RESET)"; \ + mkdir -p $(BUILD_TEMP); \ + echo "# Linux 构建需要在 Linux 系统上进行" > $(BUILD_TEMP)/$(BINARY_NAME)_linux.txt; \ + fi + @echo "$(GREEN)✅ Linux 二进制文件构建完成$(RESET)" + +.PHONY: package-linux +package-linux: build-linux-binary + @echo "$(BLUE)📦 打包 Linux 应用$(RESET)" + + @if [ "$$(go env GOOS)" = "linux" ] && [ -f "$(BUILD_TEMP)/$(BINARY_NAME)_linux" ]; then \ + echo "正在打包 Linux 应用..."; \ + rm -rf $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION); \ + mkdir -p $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION); \ + cp $(BUILD_TEMP)/$(BINARY_NAME)_linux $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/$(BINARY_NAME); \ + chmod +x $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/$(BINARY_NAME); \ + cp -r $(ASSETS_DIR) $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/; \ + echo '#!/bin/bash' > $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/start_game.sh; \ + echo 'echo "🎮 Starting Tetris Game..."' >> $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/start_game.sh; \ + echo 'cd "$$(dirname "$$0")"' >> $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/start_game.sh; \ + echo './$(BINARY_NAME)' >> $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/start_game.sh; \ + chmod +x $(LINUX_DIR)/$(APP_NAME)-Linux-v$(VERSION)/start_game.sh; \ + cd $(LINUX_DIR) && tar -czf ../$(LINUX_TAR) $(APP_NAME)-Linux-v$(VERSION)/; \ + echo "$(GREEN)✅ Linux 应用打包完成: $(DIST_DIR)/$(LINUX_TAR)$(RESET)"; \ + else \ + echo "$(YELLOW)⚠️ 跳过 Linux 打包(需要在 Linux 系统上构建)$(RESET)"; \ + mkdir -p $(LINUX_DIR); \ + echo "# Linux 版本需要在 Linux 系统上构建" > $(LINUX_DIR)/README.txt; \ + echo "$(YELLOW)⚠️ Linux 版本跳过$(RESET)"; \ + fi -.PHONY: install -install: ## Install the game to GOPATH/bin - @echo "Installing $(BINARY_NAME)..." - go install $(LDFLAGS) $(MAIN_PATH) - @echo "Installation complete!" +# 主要构建目标 +.PHONY: windows +windows: package-windows ## 构建 Windows 版本 -# Quality assurance targets -.PHONY: test -test: ## Run tests - @echo "Running tests..." - go test -v ./... +.PHONY: macos +macos: package-macos ## 构建 macOS 版本 -.PHONY: test-coverage -test-coverage: ## Run tests with coverage - @echo "Running tests with coverage..." - go test -v -coverprofile=coverage.out ./... - go tool cover -html=coverage.out -o coverage.html - @echo "Coverage report generated: coverage.html" +.PHONY: linux +linux: package-linux ## 构建 Linux 版本 -.PHONY: fmt -fmt: ## Format code - @echo "Formatting code..." - go fmt ./... +.PHONY: all +all: clean windows macos ## 构建所有支持的平台版本 + @if [ "$$(go env GOOS)" = "linux" ]; then \ + $(MAKE) linux; \ + else \ + echo "$(YELLOW)⚠️ 当前平台不支持 Linux 交叉编译,跳过 Linux 构建$(RESET)"; \ + fi + @echo "" + @echo "$(GREEN)🎉 所有平台构建完成!$(RESET)" + @echo "" + @echo "$(BLUE)📋 构建结果:$(RESET)" + @ls -lh $(DIST_DIR)/*.zip $(DIST_DIR)/*.dmg $(DIST_DIR)/*.tar.gz 2>/dev/null || true -.PHONY: vet -vet: ## Run go vet - @echo "Running go vet..." - go vet ./... +# 验证和测试 +.PHONY: verify +verify: ## 验证构建结果 + @echo "$(BLUE)🔍 验证构建结果$(RESET)" + @echo "" + @echo "$(GREEN)构建文件:$(RESET)" + @ls -lh $(DIST_DIR)/ 2>/dev/null || echo "无构建文件" + @echo "" + @if [ -f "$(DIST_DIR)/$(WINDOWS_ZIP)" ]; then \ + echo "$(GREEN)✅ Windows 版本: $(WINDOWS_ZIP)$(RESET)"; \ + unzip -l $(DIST_DIR)/$(WINDOWS_ZIP) | head -10; \ + fi + @if [ -f "$(DIST_DIR)/$(MACOS_DMG)" ]; then \ + echo "$(GREEN)✅ macOS 版本: $(MACOS_DMG)$(RESET)"; \ + fi + @if [ -f "$(DIST_DIR)/$(LINUX_TAR)" ]; then \ + echo "$(GREEN)✅ Linux 版本: $(LINUX_TAR)$(RESET)"; \ + fi -.PHONY: lint -lint: ## Run golangci-lint (requires golangci-lint to be installed) - @echo "Running golangci-lint..." - golangci-lint run +# 开发相关 +.PHONY: run +run: ## 运行游戏(开发模式) + @echo "$(BLUE)🎮 启动游戏$(RESET)" + go run . -.PHONY: check -check: fmt vet test ## Run format, vet, and tests +.PHONY: test +test: ## 运行测试 + @echo "$(BLUE)🧪 运行测试$(RESET)" + go test -v ./... -# Dependency management .PHONY: deps -deps: ## Download dependencies - @echo "Downloading dependencies..." +deps: ## 下载依赖 + @echo "$(BLUE)📦 下载依赖$(RESET)" go mod download - -.PHONY: deps-update -deps-update: ## Update dependencies - @echo "Updating dependencies..." - go mod tidy - go get -u ./... go mod tidy -.PHONY: deps-vendor -deps-vendor: ## Vendor dependencies - @echo "Vendoring dependencies..." - go mod vendor - -# Cleanup targets -.PHONY: clean -clean: ## Clean build artifacts - @echo "Cleaning build artifacts..." - @rm -f $(BINARY_NAME) - @rm -rf $(BUILD_DIR) - @rm -f coverage.out coverage.html - @echo "Clean complete!" - -.PHONY: clean-all -clean-all: clean ## Clean everything including vendor - @echo "Cleaning vendor directory..." - @rm -rf vendor/ - -# Release targets -.PHONY: release -release: clean build-current-platform ## Create release build for current platform - @echo "Creating release package for current platform..." - @mkdir -p $(BUILD_DIR)/releases - @CURRENT_OS=$$(go env GOOS); \ - CURRENT_ARCH=$$(go env GOARCH); \ - if [ "$$CURRENT_OS" = "windows" ]; then \ - cd $(BUILD_DIR) && zip releases/$(BINARY_NAME)-$$CURRENT_OS-$$CURRENT_ARCH.zip $(BINARY_NAME)_$$CURRENT_OS_$$CURRENT_ARCH; \ - else \ - cd $(BUILD_DIR) && tar -czf releases/$(BINARY_NAME)-$$CURRENT_OS-$$CURRENT_ARCH.tar.gz $(BINARY_NAME)_$$CURRENT_OS_$$CURRENT_ARCH; \ - fi - @echo "Release package created in $(BUILD_DIR)/releases/" - @ls -la $(BUILD_DIR)/releases/ - -# Docker targets (optional) -.PHONY: docker-build -docker-build: ## Build Docker image - @echo "Building Docker image..." - docker build -t $(BINARY_NAME):$(VERSION) . - -.PHONY: docker-run -docker-run: ## Run in Docker container - @echo "Running in Docker container..." - docker run --rm -it $(BINARY_NAME):$(VERSION) - -# Info targets -.PHONY: info -info: ## Show build information - @echo "=== Build Information ===" - @echo "Binary name: $(BINARY_NAME)" - @echo "Version: $(VERSION)" - @echo "Go version: $$(go version)" - @echo "Build directory: $(BUILD_DIR)" - @echo "Current platform: $$(go env GOOS)/$$(go env GOARCH)" - @echo "CGO enabled: $$(go env CGO_ENABLED)" - -.PHONY: env -env: ## Show Go environment - @echo "=== Go Environment ===" - @go env - -.PHONY: cross-compile-info -cross-compile-info: ## Show cross-compilation information - @echo "=== Cross-Compilation Information ===" - @echo "Ebitengine requires CGO for cross-compilation." - @echo "To build for other platforms, you need:" - @echo "1. C compiler for target platform" - @echo "2. Platform-specific libraries" - @echo "3. Proper CGO_ENABLED=1 setting" - @echo "" - @echo "Recommended approach:" - @echo "- Build on the target platform directly" - @echo "- Use GitHub Actions or CI/CD for multi-platform builds" - @echo "- Use Docker with multi-stage builds" \ No newline at end of file diff --git a/assets/icons/README.md b/assets/icons/README.md new file mode 100644 index 0000000..3d0cb97 --- /dev/null +++ b/assets/icons/README.md @@ -0,0 +1,89 @@ +# 应用图标目录 + +本目录包含 Tetris 游戏的应用图标文件。 + +## 文件说明 + +### 主要图标文件 +- `app.icns` - macOS 应用图标文件 (96K) + - 格式: Mac OS X icon, "ic12" type + - 包含所有标准尺寸 (16x16 到 1024x1024) + - 支持 Retina 显示屏 (@2x 版本) + +### PNG 图标文件 +- `icon_16.png` - 16x16 像素图标 +- `icon_32.png` - 32x32 像素图标 +- `icon_64.png` - 64x64 像素图标 +- `icon_128.png` - 128x128 像素图标 +- `icon_256.png` - 256x256 像素图标 + +## 图标设计 + +### 设计元素 +- **主题**: 俄罗斯方块彩色块图案 +- **背景**: 深蓝色 (#2c3e50) 圆角矩形 +- **方块颜色**: + - 红色 (#e74c3c) + - 蓝色 (#3498db) + - 橙色 (#f39c12) + - 绿色 (#2ecc71) + - 紫色 (#9b59b6) + - 深橙色 (#e67e22) + - 青绿色 (#1abc9c) + - 深灰色 (#34495e) + +### 技术特性 +- **多分辨率支持**: 适配不同显示密度 +- **圆角设计**: 符合 macOS 设计规范 +- **高光效果**: 增加视觉层次感 +- **白色边框**: 增强方块定义 + +## 构建集成 + +在 macOS 构建过程中,`app.icns` 文件会自动复制到应用包中: + +```bash +# 构建 macOS 版本 +make macos + +# 图标将自动复制到: +# dist/macos/TetrisGame.app/Contents/Resources/app.icns +``` + +## 图标使用 + +### macOS 应用 +- 文件位置: `TetrisGame.app/Contents/Resources/app.icns` +- Info.plist 配置: `CFBundleIconFile = app` +- 显示位置: Finder、Dock、应用启动台 + +### 其他平台 +- Windows: 可使用 PNG 文件转换为 .ico 格式 +- Linux: 直接使用 PNG 文件 + +## 维护说明 + +### 更新图标 +如需更新应用图标: + +1. 替换 `app.icns` 文件 +2. 可选:更新对应的 PNG 文件 +3. 重新构建应用: `make clean && make macos` + +### 创建新图标 +如需从零创建新图标: + +1. 创建 1024x1024 的高分辨率设计 +2. 生成多个尺寸的 PNG 文件 +3. 使用 macOS `iconutil` 工具创建 .icns 文件: + ```bash + iconutil -c icns icon.iconset -o app.icns + ``` + +## 文件完整性 + +所有图标文件的校验和: +- `app.icns`: 96K (Mac OS X icon format) +- 总体设计一致性: ✅ +- 多分辨率覆盖: ✅ +- macOS 兼容性: ✅ \ No newline at end of file diff --git a/assets/icons/app.icns b/assets/icons/app.icns new file mode 100644 index 0000000000000000000000000000000000000000..31ec7690626381f9013bc0e4465439e622ce367e GIT binary patch literal 98396 zcmeFa2T)UA+b_BaK|oXxQADZ&BGROZ^jK*EiV7$tNG}4?O9%lK5s@ZUs!9`SA|;T} zY=E@TLkH;)AV46L0J$6S|GwXx^PM~A%zXFWbMEL2WoNJQtY`h6z82b9IJp5d0mgO~ zl2QNwp@iJhQ9FF_)Ik6M4r{1i(*u9P_P!VnfZspbxeS9pXx#MFt^ftCT(jWAH7i36 zYi(`dJoq~UKoe{SK=+z}U#Gw?0HA$K1JHs$Y4$#Q3;EYu*jw6v|GwAo#_cPgKnhGW zu3f(CO|uZM>fCvY?MFn~PGaJ!ZlZ#Q(LI^`C$_UIlD!F=_QPQO!W>N2CK5nUbHV+$Vx)_}r!#>1c<<_bjZZTiwpQMB&vpzg| zrs+X`%+f^r)tD6XxaFKQNnQGQ9(K{u=hDF*HXPF;vdxOeyF$4i#RfQ?Oz$Hfy*{13 zkmCMH-{lk1%3-d>s^aZRztD$CwnZ$Z+=|ooF7a(?MJS?M!u!+a!2z8Y1w`F>K7O(L z;!A}#c?G<+X_CvO)lZA{IMz1gVl-K1&OcZdHBZRdG#g9RczrWjV&!xY??$EN=#B{! zeo&=A)r;ovcTTm2!{P0e(#KIdJ8O&SK*{F|%$z=MCw$wVMEN`PYR%9)o|D#ox_pX` zh3?c-BByQe39_&Ag>>1T@FrOOHMZ%K1N1j&R<(m@5dl}A+mCz`nzChV*+oogF}KEf zYx^G38kB|zAV-h0KRT)TE9iDWX+nK=Cl3{h}YH4_tghe}l{oH)0vs2YPqp;L(*n!cfNjiY#lt>6m z?MIy_9H;-k|NRX$H9&P-BfLipnGV|noW^yXYXw)#p4eGjkOQ&H@t;6H0BjcU8~Wc= z9yWmw|0U25_z%$En*mS{hk5}3J>n1aQ*jX z>a!P`0>jTjzjzlhGBV0(yb_YEi#ApI_2ZfZPnWGZaFq@6K7_UH1AW;0EP*y^Oxb{w zmzVW+b>^LnAurEe`)F0!SJge3Xll!(sNDQ`?Lk|Uxr=tE(z+S?0pZMd2pop0#| zIgt`j?R%7D&F(t}HuTEFlnV=k)nQ!qIS&fEZADK2OWi+lw> z=qYf@@H)c#zGtdLfhh9u(!~ohV?@;2h2zFvf^`v=&1z*T>dssWEFqrxAqr`{su~r> z)mdahJyXqV<+S_we&aio<3Cz2O^a_|aXQSsb6K68I!g~+K6x6~I{D?H^bR5vlcy!u zUe;oAApD2pWgDSxb^g2T{X#=5+1ET1lp;_eZyGV~TiYXnc@3nE?-?(2@KIayv@o?|PG>9pOcW>f-p6H~E(-a|{JbtoU zt~q)5iS2MNGN1`N^M@*~(#}_x2PtV!DxYdxT4!1zXrH#O|Cz>N=~0=L+IIih(1naZ ztjyuQiSS3R<;5tRG>{OSbBOoVbhN`FUHQ}-qnANThxvGzL%4%UUuMm#ZbHJv+;e`Q zuOH?+*Zn20_($oGarG|VnRV0pP#XPDxrJnAYumgNntbjD&hhM=mJXn+<=OE(F?wR4 zUQVqB`6-kJkOq=;G7e91ydj z`=o|?!%@3+&dZ@UoPAZYgQOad>3&2p7fu5ZtcJ<2*^_CZQLU;;bddggj2bgLn4FiYmDNnPE zHaIU+q*7wGU0>@@IwuQ>mMCDQ)rxj;4Z=$b_(ZhC;Gr6cI}lO8Q!`Z-Pc(3yVzG&P zB46Z2a!EY(Q%ZSWtLDwHoAiYC^Ff9O$6SMI&!=BL6IlL?L1y(V0Ic@te3u7ULT(o_ z0BuH!N2dXpz>TUi0L*#eT5@rGVA$cX5_Cau!4IiK6;Zf1k zlta$5VNT5NxDo-T03=?*X81ctvvx7WX>e7NimH34Qg@?Ig@+U_fTcP0 zopp5DspvG{-4;e5z`meW%9|EwVsVvarU5=yC^U^821KtmbSpuD4^wlZPvyu`7vg_* zxcMhOAA~xjr;BS0Ab8&wx$NG#n&X~Wu)6rxxIrj1Yk(+avHkrI-Iu6Cbe|(WO}ZP7 z8xOP}%RWC7G-kk}Ct$L~%A%3ydyOU_^}+QqHsBg-F3)Y?ZMJ^YB|0GL`$si4U?%6D zfDR2zplI$W9pGz!Jme1WKkAZyA2`4LbUNzUM-7>3OwR4-k(X6HLea<9tD+U_4#$VcUckuRe1U>eKmgbbI`Iwbc`IF?_Nu2RadbMg`9Mmr@WA} zbJvm0;V6K97pTo96i}h(vMi8C3xu#8cL0DNKcYV!0R;5!=FtFb?7CkSfJpV*MF)Ue z#!8F?0Cwj3k5d5bZ-@UKxJ0FCaaUHMkzN(vGspjS3?!uifaAk|?-=a4w=}?S$H3V2 zgZ19ie}PX-1N_@D*qZ?WZ01gBfD|13$1y;~nB4QIU$6Ahm@@D`bd_O(Uf}hmqpW8n zC)EV5=kdLT1wLqEHo|_=aG2 zOc<12Ug=2HuA;9CL8QdZfO%m-8CFGqL=R4tRPgr;IB_CSLzs2xw2bg@_nnxx?}0r3sd)Dh+7Xg2tH=|WCS&E= zcE#$ZZsDO}V;Mtms#wJ9i$pE5MpAv} zaHFzD(z+leqh-8cz_D=tP-^UKjq^mct`DNKeN+tz-=2FaGqd4{b-f*bk-g`;Ws*^! zkfXfQR;OP>)u)#HJ_C6pa;6W_g)Fq*coD5JKcsX4zFJ1-z7`uVbadFK`3|(U@=Nx5 z?dfTz(|ioc;?Uj6U(~mu>|zdKL=zG#PTp)lxWm~@~8E$wO;aaRaA z6^(i)-*N#h30VpwD(R{BR?=yW-zFHfyeE$;n0EPwg=1Us9+fxN*=0vOstd+Lw$n^% zqApT>ImM2s+{)NIrve>ZLK)doTJZ;olii7uOwo3kCeGDT&r1kl@vBO2(n|;?26d61 zRm-nah1_C1s|bxJI|}4dWr-s^l$`E#V#&tAQHrWxrMK>^)-jGi*cLYN$(qX$-t$&_>Zx=j8kQpuhpK-n9huh7+8b`wG zT`DF^zg}0vPpZhHJ1*7#iksG7LD%<17*%AI_zXH_MwImFk~s!;M>dLn{|avFg8r6z z(vD?X(ZaWlH{W7wxFg109eSjQxjrfzKja@o3Ft>hEYq(}Kw-6#f$SIQ z*AqJUshUgas)M_lxlc=^E}Km+@|@z~(ccWcx`Orf4V4g+zRZ~f&{#pAKff{4(5{lG zVDNaG%o=lgVvtyAwEA(XGK@ha-jKTTs@ z{^BtmN3jyA@15$|h%L5Cdv&}X-fER(q^O_lP3GbVb#!tZv0tXwO%{qni34eOKz_d=nr|C_J=Yq8R>;QYmFQh{1DcvN} z5scfU&8*qd^zdVu8>16#k1c3e6H^Mw-6NBU`C?-3wj}FD!62-`$_%e z;>S4y6NXu`YU#_R^&<0n_?jQOv~gOY&PSZWB1)QCa&jUH-JHJB*}uHd^Je_RhA4#fPNY?_O4=Vucs`y=c(M<>c4B^mvz>vvP$RN%Jkdx48Lq+BH|q zwAsk>jm`Qan7z5)*E(A(#((eUMu)M#3MBJfA@a**9hgLvgGdL?DAy?0>~etL-98f> z;Hu>EK}I!eUbogVyC&|iINp{MNzV^R(8VSAM>a{W9Z^M?>?Dt%3qeS2}Vwx zXJ&R%)2%l)R;m-DH0XyHwdAi_VEYZfWG6g$eBLqqR*n_iDGUy3^S+`9!=D9l#atF3 z4t*1DSG2aPq}DseP47onmjM$KVkFTk^6MA&M&2#O=X>Qa||bpF_+KVYwFh@xsa(q z2(3w`Q6rkLyXExz$jxmHDh)hIL5@c&_vKbdHa_jITBU zTk>r{r@oXegVmarrtNkF^z(AUV|1TeTK$1NCcK3Vsnmke@<2Ot)+38b-8&-Y(mY-G>y_d*jG(dB~TGXep;uX~?tbU#el<8H1R$wXkPI+7FG)T294llePYw*mk?_MpZZ=6q3l&M*l{;*2 zH;$`TcFDe#L>tcry?FlWh84^}FOV-NF@u)n^W&SfL>rps*t_LKF6VUe|LcQo(bO3E9q}~#c>pBzaYn1Frd z%9Ws_i0TD+)0*{E|ML8%=k^EE2k+bCx0mZJDznl+)7+Y`QJ!p4Aki~*L!xBqO1?}W z@?a=?W#ifpj>3`~LH^Ym6^G+lst0lT%HqbkNv^78*&wtA9$zj}Sa)-X7%1Xzttc2g z@in$*+}~+o`Nh%?2aQ(rbc1ABMJayoBNs)_F8TUjJ>m5`gZ;lJR)VityS=%+tRQ?! zJRQ$2qJDjtbB)eLE*Dm}of{f;UVZT~zaFf1;A-vr^Y#_v%`Js9tPF1LF>U8K@5qS> z-nm=Qobj5MyxM+e_tN9r40YV^Dh8S(D(aM@E(Aj2`8e({>&k}oS8VQ%bhY4RS!0>& zhK@0eFj%ss-b)?0q*Ww=LmM4?ioBBa^mF<`?Re~iBPYE4pQ(;eKuBdyF8Ip_TQu}+ z6ockijm)9AHEQd^e+{&o2Dpa0CaYgP&DMM7w6OYtu>Rm?rKceE!}0^tFOTwAMEFZt z({YPxx=7gA+3#+RnLe z6gl=JI1B~Kd~B#N<>ThcNwjO~32YWG?ZwybN3XQo3M7VB1ce$mp#?IH#6Oi>IHi4^ ziJ$$R+N&by6JFXl@v5S$PPnh34aU9xaiQ^*pN(JQXxJ{FpJZbeuI#vO){F-H^$c9` zbmo>)w^x6Z1;FpdROkU05otYA5g7s!43LS0OakbbLxyxzoP-b#+fh=80<6+L%`Sfe1a@X z*8#|As7wzHO%T}rVCa(=SS@>CqW@|2bd6GXkM{41i}o_FqBl1yraLQJHCEfLQgp^- zPffgZ8?bxqEG>)kW1q~DaF9uk-t3&JRDMDF5N*itJ~}{-<|NOl_u-qQ*`lBH_aY64 z`?N!IV$W7)@TZYZxVIg#&x$G#neENR<(kR(nA6{*gw>vZeU0_-(WSFM#gw&gmJQPm ziG~|~dogRhLlJHaBh(a^o(?ED%R4ln>lk_<$GIDCJ31*_@bw~Fc8%l3N!JbWqSG%L zh0-yNOwU$fd>Rmz0D;*v7a7h80W`Nv`7-Ds8ms|;fChWfWtcp;{1wP)Wft|<0F+My z^Wre1kr4*OurtgC!d`=n(CE1Sh~<_J$hb%HEY7p#Yv3g2j9*PXM=ns#5gnz+@! z3d8@LV2bN(Xy6|&Rq9!T)g}S+bF-Pe`TmLGNh%}X%HGG@&L+WPmU^UzxsNUzROF;HquayfflJ1SLpQxD;%D8DprU`o}-%m$YQK zAtE|-@p<>6aPJq=)$!R4fGo2LZjNi%{evQ zLv3?cu|vf7;V;;A-{`1qQS26;YkM?%s#>)+b9`x9IC@InH9H)W zsF}zxdfF`UW03LXT1gNr7H=8oA$Dg#6x=C|6k)h@7NB{2i0v1QPaE7-yN@Sa>1gV~ z-@*gWa4}?rfSA(mzEZ$9ngBp+C2u{W4U_>TFC*tc!*M7Vh?YwSUOwiFJPRUJ<{neU zlK=}4RQ0fpt4XX}@y$%p#-rF(4XKk?{EPJ&Cba6U0YFGd3s}YumE#;IHQ_2ca-N z6_9~``b*HjW^iLH3o?3H7`}sg;sx$tDqo~LxG~MKY@Cq54RBAF?|(T4l~MyYrsB2v zRX&z9aCcQiyr00Pz>SeI(0jxH9Rv6DyA7Q#O9K};?v&=s!+|hAaFC6>QAWrcet@RI zSfl7P%L`oq!jg7e=`x=iIPMz};fqknGPtK>#ZQD7EW^QZ-2{I;|4ZEe3!HOpX=w}C zC@?A_zF$Jg@=$K|E9I^csl(9frnTx=u(}_r%XP2>2LGZrl3~+l@8rCIWG`@5yHDQ z&iSF#>h)YnDCB}iqvh5Ty|Z45=xf@*5|&G(SFNM>)+k-sMr=UztOzX8o>94t|663;`OyRz{U+ zcq_(xmVX||eFAG_yh)tPK>C#r4>idCr-DZ5R}!XiLjQF>Ka z_3jT`dnIYf?u+Ya8$IM^?cnxQUmv2-SI8?f%kdOrxP4!>&jtf>yA%FayJlFw<*)_c z;|Hu4Bq%KgyE4PuS!PiXv*d7Y-nTf^tpC>XG2O&FTzmk8hK5v&^TVZ;#qWIfE`kOK zDQ!hlGZ93J^*M&Y;1f_CHOQ12a>@>s$g>ffdq*2$23cKHRbTg?G%A_P@t7QY#|)Ly zuC4rrtNbY>raB#Q{zeO=mIhVo!i3y;r)IlT*D@G%0xG3d>-TB;DsOq9Q_UJO$2%B4 zc%@4Ws0{GAwDFWz^|C5u^MOJ?9yerrneQ01d3v)&&9^0cs|58an`iVe6Cj~*;qjBy zp(RK@!vEOL+-A#aV=mU#);1xy{R!+mLqg%|)VihVXLrdrBNOSH@jm6i+)ZHK@_BIZ zESM`&;M!L8Hjg}@o0<_F$aihKtNqARHx~6*0h?|nQxfC^)&BMq+d+9bUnG9CZw(-# zpF}1A04$?`hokAsG=J+ff9o`V z>okAsG=J+ff9o`V>okAsH2+Bv>2ICpZ=L3U%L>=uI?dlY&EGoB-#X3zSrw%JVVy<> zEZdy93|=3nQYlp6J(ape1+ssC|FriVyoC<7P|f)7Z`?q@j*$HuH>=)VTj0}w#f=-t zf4Fh8Hv<4LZIlLq6dd?-<7S94MnKJHba^Hk_W!^^XeqS^h`x?bnJW)G>aA}vGUSfXs+%eco1eT8@lD;36+;K1F?Szf zy^{W>{n2%O)DZl-ji=>Z%e!ipJ4_FbzB#&leL})sp{8K5Ahl1~JAp@eesa)%dT1(J zZb;!AQ3^fel3g)gfM`aptC}sMM!(Ei)Q1PRgmYt?TTQUd{8--E_HH*}2EG6Q;z(z0 zW8E_N@t*aXJ*B8_zIffVyIx|x7%gu$-o3^qN3+C=?C)yF=@onYaF{!y;iY=> z-5ULy@r%6okX)4S-&zI-jc>E-UxR#d!1GV7seS-`*qQMI`p1ec9BcF`_-EP4ne4F=Gp)h-)_ zU#!_qwn?;zQ22`X7+bh62#i$BpC|pqnb`VojHl^vJz+cwHifdNgBL~WmFp@e=x~xk zTwaep3O^aQjspDFKN6bjRd;l!=;gKrHXtW|(-8m@h6FSFhmK}S_@?@-6zLi-3l`c! z9(RrkZQypk+q7ifS<$09NpHk{{X%_Y$E$~)lu;`8C&N=oO4QAD30#of&h~nY5Vu9E z(oWD+W4a)J-O8_q?wqUeiR8^k?PfkX&nn!~QCxs`?|R46)WN z-|>>m(9uSRYG;+lyCaE42ULIIXn!v}(2m_ubwfL_7`Gu?yg9=!q$ybhHLDL#nc_#S z`;dZAoBg9HYtQkvBQRXgWY}uPaMxnv#uj#AaieW>$a~RSt{O4VNpy$^n-2HFgYd#H z)_FxB$B_#~%Y)0tsNR~IwjGzMe?FL3rAXl9DT9$1u?7CsO-Ziq?U9SiR1(Vf`DQ*l zb!F5sM2PhK$}tT9#QF(RD|525|8{3ml85c(J;uY#8tHh}6An*#*reUAT6k>#a)Pl| zP8`!!xlZ*Te1!5=n{zm=vIiRg@`EYQq_}^35#^I3KbeuQQ7CvHvedvIkw8-2lqXe4 z_{+}Hv2g7(LFHiOXa!Ej2Ha6=&x z*${OkiaVjBtyrR@v;P34S=YI`wDUUaJ^%ylPIxyzf7+5XvT?qcn!ZkW^{yB?S_a+% zMy>RCB^eRRbBli*W%x}l4fHvLp7&Ghb{}tL9hdtkMn_#?^CyH7OXZ~=m!s-02#6C>`OqjIC`zbqS!gF9QuuXXd-leuL-e=8ki-Xk5dx_ zm=F8+QzeF%zswv;Url_)d$x~$ne8LX5?Sk+f@7?qRak}Vxm4Y3=8euU_3R^G55)LmA!FEYx9|j_zf!n z5&}IkRMNiPl2(0sMwgMk3i5cEE^Tru&6dOv`nySOIfTpvo=`pFQ=$Wi*K8rC+}QmE z1ui20Vc-=Ana7dX?Z}&7P4{W%S8$8NZaq9P$r+pOXuP!#bnxgI-9;|<0H1a0*41Lq zFlr%vnJ?Ywc-pR-)9$h1Ygt!+)5~|a*8iOovColb7-}{!DnUV#d20sGo78xNA4`xPA5L?1cuzo|fYQK8}frp^KUyrOlHxR=nTES_`S6@0b{}`$IC||ox zp)wC|;dYM(Wb%4uthRg1=HmJ=#M4Fn;p=PbrNISiJ%X9s5^giTcMvmv3f4o0Z?JYM zaD>TCWGlA>Nqx2ew0vcRUXQi5@OTeH^Oww{gb8upKgZ_*zhc=I)0tdUGU&r->@m?bHcE#5J}PY<44ELs%t5FV=bJ&Y-! z;Jeo*QmA3d4wMRGh zW}ok!ucsXyDcI1JJz-rokGML`v_LHQEv$8+%12TpN@}pbrJPfOe965Ul=Q7VDR-*u z9dFybLctP#OZopoMCsoJr-=1R5$ah4^ zJ$J4w913Gca4EoTQC`UEAY6l4XOgFcX65yb$v6u91XK_5D)&?yq!Vm5|3jC-DhbVwLl|sDt}6FvLih5f{ijg}nTXwfO2a zBF1q-h`OfB7w-P@Qk$elm2k#lIzfBK0!NT{SH4*A3NPtl(m$lP-tK{h8`S$AF@fi7 zfan|U_8q(gK6z2X5P24JzIT*{)bL4L^{IGJ(jOUf6k5z-O0b#WSWYzs;cHx&YQ5#P zQ|?u{YvnGhT$n{N=el8i*IJf0CZa%YEke0)x^1nuT*|b*%frq~3mgkPkIIc*-w1N1 znIEj^^dl+8iw~{}zPM=_0Sd@!h_Hufj@Es41GI)oBWAZ~+Wk?E76<1*IKC(Y#J%6p zSr1_XNKI!EC99mdn9#b<`H-iI`wgwn4+QuZ*HwUA;dfC#eXh9&C7Ee z&FR(Em5uKY4*xG4yav%aJWd^G9l7A<_RWa;RRoBKBU3i}o-$YL$lv&*XL%TU5tUW= zCX3TFH8X%f~k-0uRo;M;ALdFqbd5PDU5euu|P~F71V=XNT=<#@wT;c5~U7H zSYGoQSSZ?k44U^~9xSP1*=D z+q}2>*9_$r%j*@g3bd4vI{1MyEmdK`x*zdkyMZ-tzWkgkpuWUc{T%e}6zhm*{i8K} z&wiSXT${Q|-0qzF_|X7Z#87ygc9jP##p(B37=VV#nYc;5y(3V255Hr!ntjyrdpQaT zs|${cPQ{_CH41GyNG>}~OgDX1&nl0s7^rTGn33tOV8tnsTT9o!{i+bBHmymV()~Js z_KF^k(7?(4z=I=Ff92>(c8dT8rU;~R(DqeyW&wUZTs0Sj-A2Rs^)`pLwdMDlYP0h0 zL#k5R%%C>al^&RJd&ElGEVx>D8IK&Va|EHiRlaOqKPwADd@ySngunA#+E%6M(}L_o z1Kc}8&J^iKH>l=E%3TIP$g6+~%>|Sd9!|7fW$IN>Jk*lo5&mljYfJGXwFv5UGby&; z%MR274eW@D^#Tsqoe>()ye~(%_6HISGzc7eeYelZxu$7*``yCPcPC%|(IlB5TB@to zaXTAzNgr>%jR`8&*>@IMUL5ugMz%qtekBDDugye$1OrHDAP`rdsfYJ%DZ+U~B*Lt))nHQ{2Q#;E&$Vt99pzBHPIs_9+I5Q*y_ zFEfH}v4W@~?KOgAK2+^WT)yXOw;vDiT{vS#+$mlkrWQv z)4)4}9u9LgQy(l^guEU}@m6gQXNe~>v-P?z@k}Kswn{1%fOq0DNbVyOcG(<({!Gne z%1XJy9|(Uqmf+wU{}$6)rD!t(TvqY*auIRAOscu{IcY?6)|+-JEtgv@W%w~UM8P|5 zf7xSxO^k7#ijmLMvm4InG z0lsgc?4!p6#?X;W`@Q&#&BUh=rP9`Q{_KuRBX8`jbI@TNio;cdZQkDuR1d)EUs0qQ zM)5-*i^@uvIn`PZ<3y?OSw%3!_66gqjYXFszcOa{2aMkB`l~t=IBy=3r(cDHs}I6B z50*EDrbu{JViyCv$s_h_)Mdvjsw1K%j)T*0 zz5d4-9uEJXk3+<)X8fdd8rzl^p9qAU45M{ByL0K>}4C?UXjZ>rGy;+-u zJ4?wgs;?ylhF6|r;f?~jrDL+~>UE(MCAw0JyI+qB)QSLfjwiH!Cz)VxMQB(93Isx) z?qmBh#I19CZvAU0s`60p<{i&3Wu%7AQ*AHb_YW=Ns2|uehN4(=E(Jyz027plWnF7y z1@?n(dsX>Hk6^^9ls2rf?Akty-(>FfX zcUWstKEb^kD}KFcr)mylY(=!HRIJ@2>BEDJ?(Zb5lC*G+1W)D92hc4%S_9k9t$W}y z*_)&^zwm;Qs&+4o8)af+Q)$BY3Vx)X04L`MO!7XD5f4yy>dd0dU^sRAGm@`u&$+$E zMh1=l&^^kO9d|ba_xkx4M!V<^a=VAus}hYj7St?R>@>ZB0*XUV-ymJ0h2zXlMsrb3 z@kEe=JC0DAjY<76{rX0?hu6K1vsR^==xBvFf4z@cLH0=(LDR$jq}d=tTNCjBk0nT? zS!B5LGu!aA4rS>MEIXRds$dpdPdn<($zqWnMmJG%1>xOYAZflKJ4rqBspxvwe7!lh ziq&Z&z(eti{5~#y#oK1nXpbN@dP>8%iFG6%1IFD^)h#U<;#(_)@20T%4Xe0|QD?Ec zQH8ytpAb$JvFamb5*!lj4ubvWc(|(nhR27h)F1tt#wn=Qtu-{dEUAqWR%HvG$(~-}R^iBBL+`+l zYk9Fq!t@bVF%2;vN+iQKH~9j@C*pD(w+-NA6>jCpUppv5<1P$l~ zK}gE(DYG}$bHxF7GfUX-8WbvKQ|3H*U+#s*gu1!k9>R#1P6QYTdps*TVG5lhQff}Y>5?PP%YCu`n&?)|IOGYh?Dh0g0cln( zt5Hk^)t(_s!lD6i;^c>YqkO>x(#=fM+Kc8pr8Lj{z1O#sxlY~a4hlV#Ij>O7iAw>q z`1s<9UVqs$&h;f3E|0JDPgQ-4e>j=EAOXfW7Vg|5VbiT(zMe3PK^@1XST0}kl&_xL z>Ss5Q%JUf7dnvcLX5cba)5}|ZR&skCV;Eo8=G8&K)Nne7)Q&z|bs3m1D(UM`nfLf9 zj}TpR8E`!0bEsv)?U}NOhf$BRUmASSQ|<>pZl z|2@fc0p1Zz5AIkD5T-bHWnpX2(9su_L8%-l+)El1^_R@5>~YCgOnX7A=ZwVCoI}r= zAnlZ+dm09#f@k}t>>L4_jjiN@hyf>ync(kH9c8na#oPh+v_#Ckj%<&FEE3HxHFJWu zUc6p?mqR3GHCn2y=6jUhRJ+x0EkOT~^e!V$KksXkkMNLZcj-q>RbdN?7MJkQrrvkF z8|ctYPDhXI+U;zb`%5Qofvp@0{oSvsVHz$8=XU4rx|%k&k)wVdpl4OnlryzOh45!8 zs$BO|j_=BRm4Y*=`5xO5CNZ7q4lZ)YlS3$T0N$#_(Qhg@a+i9_jLuxu-~-B1oVETP zg!b0Xrkwkwop^?Zu!FsVGUI6psPH-6tZ{k^0f^PXC27EFku!HcTI16UfbzYJ__FV{ z#Ry;6I01XZM6~SgE*+rWl)fVLn*Z|o+v;jJ$CwYiy|WV`Yx-E`$=Wmbu4n0Xds1UJ z;8MW8@BIe?jDRlfAG>P`3d!-9FyJ^}9h7dT0XPf|T`nBfwce|z;NV>51g5nD8{&3B?kbC{bmaiA-X z-KqOmKq$WOE;Ib}nw5D2lNe}Rt)4?=XA>~umsjOY;>m4#^`Q5DY2SNSkwBPfnZ(1u zoz_Cw{*EEJ{^LZik~tS^$EIm!yQ*V~z@f4j{_XW+zPC@ioAh7R;s%}kv6+86`K)Sb zogQ|~VjhP~oL{E@JMhrrLh8(E4Kvp- zb__Dz`8(HkZa?P`4CWj%#52S0f%!U2DlRO08l=N%#3g($r(ieU4d(3Ho{lC<4u$>Gxbeco zpe@c*O{&bXvTFyK%hO!AXic^gFdE~$j~BNXP)X5SFEmP5VOOZ(F*1nv4t1=awmCql zSxQFdlJmWm8|NI3mGk(#)^AE#I##y(o*mbCtTv8(+QD&aI~hZLe7uEvIHg9uD;bKA)4TAVN6}Z(GY}U3MeOL%9;#?9kC#gko8jfnsB9Ap-k|v@2$%CiT2!BH0v0| z#9Y$r55x>BwE3xVh-6Bnu10?*>{hQaPnS|-#UeZ8J{OqOY1)B)hT@I>jCo8AXaht^ znzKnG+(aJ2rBFv-XLj5XhmNzE%DIH1-ojLWg?2KGtEpEc4(Ai9 zCo2}D^c(HBg61)}rjgK~;L&eO?G>>&Ut$$o8QLV%~8oob$ z>osU7V@izjj_*rwI8+@_cp`9Ue<(r5o9l`+N8m0?g)VUSfHJM?&DC-5wXo}e9F0~#yy3x zLVk68KYC;l`!rujvo&nk;OJ#w0XL3RaY|B-y9lcM!DS~Dx=OrmW%>Y937kQu>fl&X zo1>QyN$i4q<-iF0h>7k@k+TYVD8$)rvT86nYwlM6sj`~>Q?nNI>Ly#1F?iB=B#I|61Xr9#^%Nky9^Cf8%4HBy&{}};TMYX-=iBm>SdDu7%_y>k=Wq}$VUGGx4@%iu7Vj#D zweHgxm5X7GlNztbWZz2`wD?DLS*qM9@40$qy~blyRH4i})%r74r0%%;&bN4~!I)2C zWA+FRRdWi@>q#ws-{j9!tD9=Tyjd@rX_8VfUD^-Ljk!^I5uEQbDv{vVp|hrqC}2BuV5-SbdpXEPtP6F+b@pO3DVf@TBz8FY-$pK~M9 zOyHqinaOnoFhp6#37^?_9B(B9j4&6Yx+nXYxfkqwG%w@?Ap5%G2V*3_hPDztxz9~+ zFjDru`v3EeffXApR%*SJOq(~dMrCoxqa6AEEmPyo&dfcen8OI`l8>L`UAIruHibFM zM3uw!bty2#ID-*n-x+MtKj3MaL6oU)d0c9j&dcn-`sTOR^0v zZ8r7q%Hz(UDOx$I8$!}oT;%cB;oziu zRP*M{G>m_rzf2i>@8ea4V%!3+EVJL5$pU?QaZv5n3FOQZ7>aI#BVcz?%wRwD21^Gw zmjCeKeUEI_AOQNd2GJC|U#e|82ZOY!U!~pO=AGkwG&96uSF8QDG6y38nU1zU$vzaF zI#dGf0hp0qQQ#|<6Ca8#zekSuGEt9G%o-$p7w;^O7o9gIlj(PtHSqfbYs z*LDd3#EoB>Q`~aKiQ~g;6?)ktv_dG&gNNLm7 zVxB&t6-xG>y0e}JAMQ?sZ?1G=4_@bjvYb;N-4Ouj7EWks zpT4lh2s?P!d_Q|0i3LeY z&(ZN2?HztI!CE_(F~r3iTXN&x6{Vg~;YzCBaR z7Y2(3@&=qoWZFaiIeB;gU@w9?|NJR)gR6_^!QTd%2HF80$D_{I9j4C?(YgB%eR_NJ z$rT9EG`M;hvhB|ivPU0z>8hOIL0!Xt8s6S($b295)~Cx9F;%Y8^X$)QE^em%Q+}<@ z%?bQR5RdAAF$Vv6B@dDT9so2zG1@k@t4)>P_z#^LlxsABGADV=_|mieg#dwEbQ7tn zX#iL3_^JEH{;bzs^pi)^QYu^!*t_7yMeYsb*1de`AF4EJhY1Vd$;6M)T74bmO>d*6 ze@-H&9-Ki`fisX-T`NTYH2(z2PObDVgsTHON>0Unl-r2Y|Exk8Jg4T3D2%Va0{P^- zW>3nyqZ>zx`DY{eX%G?UbzdJ*0f7A7EzRR)a`r#xA9)zd*Egb|qjo}EHe@&&9w8pD zwSUbBpg}mJ*S&X6Y5-GH`fu2kiG7%qKj)|!*u?*J$~nMsBNl#!rVZA$pKm>9@^*2! zT7agv#O0|^-JLYOtMECgP>pS2_ptj~2$X-o!BmOI*uR4ZmOXJ>o;HSs<11M@-05N#8}+U0|v( zT;G=>Bz}0{qHW(Ki$+7V8FXys+;oOLn;n+{7-Dvf>90sL&v@0tCN1TiGc=sMM#q@+ zTY&Ksd51;B-j`MF-MIw0vw9L!jobKJ92rVg4L%kqcwZwU^-bk2KPb8SCv1@9F}#K< zx#{IP`J^=+xAEtSJa>n-bWv`Ri*h@si3DjvwZa^t1>WkH#$yN7_IjIrRylv<@c315 z!rLWJ;TK9}t-E%UATLk0p%oduwI1r*&5<=HPM!Oh8EvIu(DzQ`RCQa^FT#)Kjt`hROioZ)v8=Tb^(&-l<5HWs`JYov{H>!u0K^$_VRpYjoLd$-5* zU#?HiDP9QPa)YWsU8)>2v8ed=uAOXoMX5B!VI(nz2BlkZeTPd*kJ3L9k-nWaHcv8$ z;n&h5RfTBlpeNHe=JqEcRHd@ZMHE_-WNt_Nhr?1crwNf)6e0*}N7?k4#Z2=zRG)^i zk8z}#_yCIJ1uV0C(c#HJ=dOY{WfpEsUy&^0sBRlNU7B8DKJY#v>UtDzI97Cts`$Qd zN&h6_YNKEVPE^GpseTLTLx`6e9z8reZq4u=x7>9hDxM^5X%Txx=2w$%<+@k=j-{-d zHfmK?E!ewh51v&nfM8JiC)!VP@cbFQWDaOmB5?@9PFh^qHhlR+Y1#)dy};lx{*I*K zatbUq*m{*|QFFE*8NEUjX}vXUb{yt?QF}~k31FH*VXW(`b7d5ns$=L3Y+UAi(&obC zt2FZVCpk*ix$1H5M*$Uh18|m1V~vmKwc*)K!c#^EW2bej;B&~WZ4os?jqR|e$eNs^ zt#w5QXi@DLVhNX{$VwT~4}q0i3wP!RaCZi%1DES-o-Dq9!DIZ95OG_&XZ}$-;HWYO zftk>lpsX)js^?S@+!p${xmeXPfR$-fc3N6m|H5}_=xglxWYV{yECRl!IUVN|Gz~POmXz5IQml@{V9(A6i0uG zqd&#bpW^8MU%=6~29*69oA$VVul~D#1Zv%cn@$~RYTbYW+g9+g&Ss6~qocJ|{&yAC zNorlxjh&sKjF|ib2Yk4=I%z&SIUL1bYGRUndO+FLqyz84$3Bl;nvb6M@BdQsc!1?P z2!xt+cGphVd7mx$49s%g)*oE*XfX1GrVSVCx2An8oesIB`^R@p#d`LD2?Vkc zvejkNzG$66@|=R4A>GgCHBo*|E;!WJ z_x8RCJEQVYb!f!)0))%u12>KZZQQ+SzCpv5b)Qe4F+IscxahNUuAAa#>=QmJP2mIy ztQcuXfaP!GctdP)YWW2`kqa8mUqp-ew^ z*qOZ+R#I3t6aeF$B(KF`W6#1%6Jd%bTA4k+xWTvd3#xfksyV_d-OP`l>qCB$A0_FG ztk7%HvG<1##+f|U1%DLShQ9CkK1z5asMMa_e`9lblW%!(qM?O3-4OhbsKw@NBxlH+ z&E}(7e11~2q>PO^E0B3c37ohb$pAl`go%ivh%#Dd-XQPSGnVSc<)EF;)%Z|i?_t^% zA$*RQFNqYO1v}W#ld7zqr7Hc{zH59l7`d>i>$k}X{Ao7+flw!&l_6|q8;?+U1GQn#fQ25$O2tC4(MMkbO?LJ<#8=)^Jvx4&5elb{I-Sv}X4 zNDZcph_mHsoP!j4NCvBg6ziBmX0$1IxKjLjgbaN^xjkeJf?Grn8uFkJXdEP8iGs7? zH$lBD+z79gZD#U+`__qgTUQ0?m|K_f z7Dg67 zGI!j--lf+E4E}KgHpLI2I_lv=^`)ffH>nW_@k0FpQ{m%;2qHFx(Z)F<`OyjwSy#V6 z_P$D=i)qgCYgINA5iD|;kSbLi`h6e&F-a7Z5-YTsrmAK+lw-QhC{N00oeA^gmSoMd ze(xUQJ6(9(PP@ec{zh>GH z)>}mqnW_V-f+EM9^^;%w&;L8!{>344n?ZT9DRqyd&xfg;dSs?mXl?7^fZI|sr z>#Pk+sj@X4Ah~4VZcG+n@TY!b{ zpF8wHx%_Ff_)cyinVz z#&lHZ!qqx#{a8sd>=Q)}S9M4N#gDFH-*Zd7Mu0^m!A6Rt)I?{~ zjxB2(_l2~|N<;d0rninMhI8Y^xKb*!5W#36p&DBwDf}$SThY^YS4pFyU**NZIR;Ug z`!y{{99Kpw4#xHnWGaGu72<5Amk0R-mq$Uv977b?2n3nPC!vN|`C~!8;-?uT#Q=7+ zd`R*Z`|l4B^&Tkj5XJqFK8nJga<8lg8C&{}Q6=*M4@g9ejPQ>$V*MnSyhH2_-Je(- z*K+<=1KrDy;!y-;RIoAbTCnrD?hi>wEnPSp7mKRwQ7M=~ zJR3_ZiVcVY^59It;CDy?91^Xt4;dtk5(yk~m#?6?f7R=n)<2sulRK@#K`-__7k^Un zLft218#)fR#LNsnJHWv~LVI3c-n)42OBEM}5IuME$16oFb)GtSv${nOP-_M9q zjp#o@4&J3{+i0ft^I=8~D$5kU>RFVNybu2!r;x|*hC+z)h9Nc;GqShL!$%_b6}2i_ zwCqzo`{6=;{4ojK={>Z}T~WYRltAnyIu!}GJqR6>&^!r!dm+yPf`oDsBGn_4kt+|QnCO(_A|c) z+7^N|k38hD9?M?Z{!S^S%99t<|Q!_)#!msm!yIvIvBHRW_l2GhF?d(|3hM5i<>!v=AHJZlu@9 z^X-C!C!0LX!V8Kb=)FksuU~SXsIuu^@mFC;Rh9A3+Km0~ihT6e%i*5*1IV?UweoG$ z>vzfbH2tQ2XPXKp!2B$Ge1piM_gF}OHu~@Sed{5g=ptX{N&f9s37A*@0skIa8s5?s z-J$18$FYvk|^&cx=62M46NKiKce)^D4ez$P9IOQZSl6tB8>epw{q{% zt2j*;++f@Ri{q!vd5nSVg$HF}y@=YkoOhn$f;|ZwPkHYRzN@-l5(Pvsf-Ej#%k?3C(#}*A zb$B&3X7o6VX+JPSi-PU&v7N8}B@QN|wbx_o+tJ+z+A&#hh?HztUaYv6=@^tXZ}ik( z3^>i+d>*gnD=1ltry(kD@1SZXP#M|nm`#!(s<=GInx54B*9{zJvI|O4!(uTK-}bS( z8=P~Z1E3X;s>HBElziC9UpDzY*J7RxZcNfm!PMR@Q;!LhHfz@|eSeaTpV8sTz{IzY zhjc%b%-6Cg*I&eIEEC`IBKA|-0W4&=yh<{zZn09bR7FFZJFy=<**_T@x9Z}fLzW7{ ziu&`cVUY6SDm+t3RmJ05M_Y!?wXDIq=kd`gO9iN+p1g&ies>T4t#aH#-TO4U(@{(J z6?)q{HucS*cKXVIyHkfaDIrT{8nRX z{~uc5DW-7q+FP)Ub_Fy0#5?&GV3}y#T$OkMG4@Mh#P)$3Ra!Spa_BOr`BD#!$Ta}g z>#VC!ndh-{Dt5r69cfDIg!OK5>v|*?y3a$*NHn7Kp z?!g0oga9peRHT0v*laI_ezqK|kj}hI`VfuX^p*{@!}>57~3^eQnu(Twu5 zw84)DKCX91UrW?b1v(@WmXdTCtU5oR7b zRxyZDc#AEemhwE^fClSt6D*_upXoYbl!obz7C1-(n6PQlq6F0=1q?TyS(YLrsTfKJ zFKr?rvMIid(_>YNSBI#NL=iFAvEZWXLm?2Kl2b>7&T7JdjXK{EwMffeipPd6QzeL^ z^-To#xait3or-*d+w7-YO>o?rl{EW+b+Gd@w@ z0_?kYLIU~%ts4db7+{G)pE}C(b;0^LZg=@H+aW#eg^`%~a!Il`irFLDUTy8zyDnFl z?+~<(F2Das^8@z(*FS`sujRO@6|XS^UV=B{-SFwdX$C<~Wy0^DWSM|I=W!!=Y68%HzQ z%SW!&D!jRLpfF7Yd6}`>!OocDH=4=jaiq+zqw=q5RcGP6jpeipl9s)WUjomeizxk3 z(tsT2@3Z%$X;DSs$qsIqFDUGClzfqrv^TL*qHG^|>wWW{o}ON5mIo=)pi9R%>}5c( z`r_i+G&JWRIZqi|v^;tsCO}+0re9(eexG1-AS4*xeqt73ve=nloBoh{zHw{TP7gCb947{!KJayDzP5JprMg0RF%2Wg zOW6$$lBCool2ptW2SkNZwN=-#f77+GTyjCu5nvjGhsNp+{bqAzcF&s^B`?E5a`a4G zMwwDCsQxA7x4$a^`ReG7bu&}Kf0zG{ou&r;XUl6_NqrKn?=*_+-JImaR4tx#@!i#J z7LN!Ft+v}>#df{%8kq7ne>XxgT-d;pFNJvI`t%dVYPmVqZ+3j55gKTJVYEAi9+0CC zQFR|74RFK+Nsbl6@e>Vhd~9%6rW;q2M0AoCAeAb+{5GuyNrhf=#4^rh8b-u)6#<|E z-A{}Xko%Q2wo=(lsppJKF1ZD>0-a?m&r{mkQ>Fhyo(Vb92!2)T@){2~e76M>l?UiqLy(M0&>H7lk$<|HfwIL_qy>v)UeF%_Eu)b^vTx&0xK z@`1vT;hX9&F*~&AY9Kerx;AT639{%E&d4!2q@#!^`DxLKa4l}1282&DK88(k0>QVa zauJ)Ig;4jVF`s6CLR0|H#Wh7T;0!wrY*(g$9QxpX%qQOROygrQBwW}_r=nQtT{d1; z?_rL6u5MgD+Pnb@h1Qs?s5Tqhbn@wK@4qFN$yGR&QZ9k3RdOy#Esc7Idt=-V}^7EwY1_GQ2u zD1?E3>y~gV?Gt-DxGDa&pt=5o8z{iGL)Vv6Xj+}-$GXnIq#6`*95KJabvyJ`B4R`m zH5Acc+qbCwG8N#>LdZ-6@0&ZZIYHEs!d&HRR_PdWoQZ2>se%b0w8-1b0|*OBbzf31 z0|Cans3C?lNBM}PY<2)KNoFR~!_kp9zC(~H0+C5qUJFs^t=NxHV79LmMXcug(Pnw*45*Bg0O+u#`=7E`zX#1A*sg}ufhr!L>&miuXhLW7F1)3gpZJQSTv5F6+P?3%ziE48y0wn zC=N=<@Nq_&TdH}+v5{xm&59;dC_f_89_0=+Y(F1YRCXYBK)EQ+kUF>@Cqfj`wS@v6 z>$bkHFo|k=TZNHQT-8tre(W{HkEPxGieDSnQS}NV9e_f>AuyY;iq@8#VQKZE{GoF2 zdWVI6l(7c@4*Au?hvi%HL@tB~_f8wWJZ-+)PY)WUds)EUiF}^CTya}}x8nBxEM9Ul z9A$!8KA>vG^*J}FUYgOOuR1F2f#>wfwwlLuK8^oHFXHwq18e@FY_ObmLE-amqz5?c zH?d1}Fw^Kz{bB1nPyc2zalWy6Z{zLL zFRi9&v^E4`7U=eFxxOub^Q5Ep|N3yD4xO&!RTx>zUAZ$vok<*rm$In+7C~6LRlV+4v+r(!j!3|B+y5`UuJueybj(M%9)iIAO&Vv9q*Ile(#d<6*k;1XRS!HLt# z>$>x0F21X^)IGNI@{O_PPaG~CXs08#ya?aqOdYIznh8+M)qYC(g6XhNOoV;v)+k5S z0ELi7p^!3hHriXOv(dS+pR7Eq4{;iQ>s_|=WGtybc1)Z9S)n)htFDma#OV@sRYD;w zs?4I~sJ7q}c(TCrM^x%h*Ej`@&+1@=SGFTLET=MH@Y!rQqW}F24WRo%~B0! z%U}N?)(Qy#SUag?D!##Mf(9%_!Tk0xJTCG){3$(u+d*+2^zdlhi)eN!A2NgzKhfkG4be;!fw974=F1#T3-^hh z$gZD$X}`{|h)~B}oSbNYVVc{6%NbF25()$|7Ftq@d1 zv(f8`$aRE3zx4o&i-G=+O`#ZNN@c<2iy6<=9e>i)3kN>=~X;`Dm^ z{*siWCEziN?{REkK+l~HbMxi)rxgZT_g)G8+M~5-{oQJd3D{q=izr*c!LB>`IpS zPZK-C-#f6jClI~ToIAP`&F{K%2E4124<&U8dMg1JcMoPRu5PxF_UmkNVYNwwY1y!eNHi;N^z7kwcs!=7ZOHt(0DG71S75`}O-h zxlcn*Y&nm32bM$mr-|KGTtT~)-0(YFV@tkFP!vB{4t9h009K^Y+D1rmG%1Dlwp|ks zZ$F=YSG$e>7^*jnQT9L~sph^Q1&<&Zde#`L-4u$108dUC&48tV>VlGfrR(gA#-DIA z*pbUP<1~yadJP*~J6RDIx#07WbLOI$FS{N}txdW$p960mZYI2n z{sy-L8UsEb*+m2b2;u+u<{2-kUBlk0RNxMti6SFktz8@MGe6G2mUf+YNq|{rXq4AL zv-Yq299|Pbv{WR%HaXSn=U>@p@qW*R5+6pSzxleu z4^x9G5?A7`b!iILP87iG5em1}h8jg;W{Sf=ut^=fBqStC9*&nT~waY|D_ zo-7F92El`L&Ay3xnTClsxJ&)n&9?WHV{3d#MdCfbTphD(QqK#Tf})86>|pE=@`S%j z&sau#UNl2}KE-Z)DMWx#LlwojpYX!$2Ou1g1pwmi>jSUZ1Zzrus_i7j`$2~Zc#s2A zr-IyKtu@qoE`a4>V?yClhaYM(b7jM>QKL)zTD2>mzVKxn5e=hG5~wQ@c8=fDJ!!XL z!$BtSqj61;Q?)lgbH?lyJt5otW7bKOt2&$JCmhyz*9oj+V=bPUdP6(NoF^uKsjA?q z?-NsYFhrtuLB+&39+j+?wU3JO`eTF~^$LpNtq$nm<{1MHilFQ>fp&Os=9aHjVkz?WT$1Hb_MAwu#}48iyCA ziQ$6Nn(rc|!ldGlA`lH0vIskdKp90X48Q2;5#AtlSVmXI`s;PJiN0k#hIY0oCcek2 zanRvEiaN>aU-a1{?{S7cS`rvE(dfvQ_-FzCJ+sy6X?^POh<5y9BNbSW>gTRb=pcB_ zeyyfxq<L10e!l;8N{H2usRUxsA9d@nAPpqW7u{0fn zkBoYM7X<28v4qy2@DemOz502MWBasSfv4!&4@%Qo=*c7y6;s@ED=QSAJ)w{PVUyt{ z%OBDMlI6Lslm!88NdQJ}7r%@;oQw&(U^!4`D%+VHWVuX`wJ^d-US7wp3?k41<08NdC`UU*qd+{peyzm}_i{PO%8LxIx5$1C=;PsJb}Q-O+C&xslwErJXR zE_dD3Dii@ksX0-4>UnVw#|tiK6^A(h#Z@56yWaKW_|V->RLA3`Qd;oQi~i@kRubzGZ1P}+ssJpaG9>wcoM5itA%ZQ18iuIw!(r{s?6JXi2ht)b z$G)S&7p9Q%J34MD3R!JWbX(C?qJattw#TuQ6g!+6i4ebmqKc?|E+cCmMWVMMG&4c< zLljDwe~U0O+TI+0f_!Ik(%bth8ncr0A@{#1VDfE;^V(z35@9cJVXgvHOVGRs2*u5Y+_Znr)ht2$uKouVVH8*xP`^3ga zm<&T93VE&EPpcey36fkl9yQYHVKjL_&Ha4G|8`xf;|=oM4x*>)r+jEG0uwJy z*dT>=z1d4|CmnqE9(WysCYE=XS;_M?iOEkZ9pGC>ZVsZX}BC!#3dZ$TKFYM zk&v*hPUDi`uRXk;T5IM72ZP7ZD4Zp*7?(z8)7`v#X=`o!R!rD|*N1dX?~Gh>4bDj& zJMrp}`Qe8Wm!V=+8J^uvQ#47L9H0z9?Ga{LcGbq=x?^bkJJXg7Fg!}G7EI2;c01#i*PFt348 zSfN@J_p2LSt6*ue(ol5>j<+e=h03qGhJ7LU45@e0B^F|NMeIVCdDzz>InL0${UL+6 z>q!qpw1F(Y+D&>^ws)Q$Hh~*<95ZonX>?V6WlBe=<_%QHS0{EDTN`Zry;;fu@8=J> zDUA&NX^h#?*qju$ez*aE#>v-D8y_|8y|4{3$FEk8>AsE8ZJ9guZ!U zU_JU3wNTHb@_-}fJSJfvKH=q(M|Fiu;3FNDen(Qso2grVk80*N=F9q=9nir`)aye1;S^)OJ021wR z6LUNBlC5T_2>@bsbYH++J?=D~F4>yGeC9mYF?8R}AZsUd0{8~yl?y+tz}J7QfTv&>2{GQA*B#btbo)%SkL?z480H=T zyNz~^Hp`)MIhw{^y$^WI1t~~NL<}edN1+hCqfE2;6*0YMTizRUlwf{`nTkX^n&K_He*}|W2_o62_dZYGYXVNlBfp$fnYCV!rN+Hz^!0E@ZOh+g zJCs%D;kPvlpb(ZH2k4SseL+H2XWHo>NeI1Gw0ao0Qsf)Z-3E&9&h#CJM;H{+ zZzQJykIaV{IXV1AWdW=-(F?hwx)ungTzAcq+J~d@p{s^C#tmkD* z5pc(N^|``Ju7mYPPl#jAhP!#$udd60XCyw(lD;$>JMIt1&)N8DrFPVKFipd9Vi0yA zv?gL~d_l0qVi zbtmCVDLq*-TAG<#6?HWBTyf@XidB~*nLpxX7`EHzS4Kr*$6$DO*)%hLE7?OJT!eq~ z>?XO`e|GrE_U&&J!@j`vElWXCR}=%O=K7+tfI zb-!AfKaZNRB|U8L{=Kds`H0$`b6%aTn_S$#ka5_weW8(X%~u3FJ$l4>K6t#fTe!C zyUejxn~7V|yaCXX=+#l~T0NY{yMbMhF$|40F*aUSF^q2B{`Id~;bC;GO zKqm8?)hi!1>QB4DhaRXBnZoxS6OY5XF2j#KpzH&syw^7$CbKO-zVgkIBiPqX%0Q)X zn-}>^XHDb#)YKkE)?A18sgLE2Gxt5NDOG?RXl%u~{5{otkY+9Rw6NJVUg=S~p<|`v zZ?LvR;Tu&pWx%eioiqb;}U?e@2*WGdezJWv9&0!uHO_okZYHb0do zglC_f`C+Q7M)Fhh3=i`(@jP6&L7|gk#$}y9Pr#jdlblSR?=5lu&~dZH^|=*(i8&KN z!Arnwc)}Sx^5^Fwg>S;osj$l}(R!{cF4%(Y6gfCsRI1emWrkJ2AlY3vFj{@G!Rxw) zDY!Sg)|j??pMib1w{%t@unvbja6!JvAJIAEd1yP57#G0hIj}SL!zl=T_;I(w&SbC3 zcg~8d?Ov?KX@0Bm*#vqJxxGnd=Pr3QnvcGpiDIv+2OI(FhOU*cl^u!qT*pShpATbm zcuqji0mdxrH7qMrr*&Yx}PWOyVcmK@1OEnEnW@FzeQ}^j}KHGbYy>=xn?qH1SX2O z9k3Ks4d9_li30pMDRKBqkxf<>buX|p`f>KsaT0Ep|C$TXfbRLbom~iKz&<+d0U))Y zX{Qq&pgWfTl<)uueqbfI49~yBeF;=r$5jC5-KgkT{>U^sv19pX>aR>f+CG@2FT=Y5 z);0{_?SLKr!}Is+JAh`udb-2BeR`JZiVKjbhXAe+)%YI5o zbZ`ZlY9g9g_N`HVxM(shNrs?k% z7fhOm55&vSKsPpyUUKa=ZR}~;7u&aNB>A@Js>i!;dZG{H_|+56;D0+ceUZpWe=(xV z#ndY?J-{amngHuqzU91Sc*z9Jzc1^4X8ErN03050a>q^EWN%sD=I)GrNkO$9reIY) zPlstbI;U^q!r6)pKQDXTH4!SvZ47GJD+D%yeSdK7pUn!)HyRXuRYsk4@9Go)GHMdn zNXOl)5Pec_NW+grtzvjkL!9>4C+_X$!H-GYKuqrliqyD6?7Z7S|xB;^< z3kb9Q)T=~sfJJ3Tz|5}8E50LqybHj~S1g{J*oChy!0s%G)K+P!78jMcv>o90*^>{yrE=TuB zS0NDInNbH{5SL%^vOApwPdS!s8u}quTT_@T&l%5#X?DfR(8eEmGYs%^FGE_vrhNO?*(7UVIPYFO{gV zdfCdHJr|H+58#?imLbCK4X#nw`2}$ueW;gTwkQ z>5L|3b7srz@Z9y#n2)nD?qweSFqx9c$zbFB+KGlci&*N}aeAGB_X0HDny;Oep;4HL za*qXfn|)|52!VClDTLX}mG?X+M@~3qmHD%6rW}0~Anl=U*TXeCN;LmO@0E4jeFz2s z)aRZABmez_0^Ix=c+f)4CU7oD(^$Y*M=+3O@ygng?Tq$mxAbIM!|ML}8e{__{Y}12 zt5(riIJYiH|8s5Qlah#Bk}Z*OR=0+WlZ`5@Lw6EJGRlvZ+oe*+zh%GT3d+}zV`EH} zy@tG)7!JtB5(-q&zsMQ*GU;H<1PWOZ4IQ_ERAY{54{mu20KK_Mp80Z)R`A70Q*0!- zy-=%C44$|zfJaKD^NKMwJ)Kb`=-+wWNi& z0LM7ZFGzim!r#lxa!5m$Y)GAPAdKD>4%|+(p~J7I9zhb>70~(hI|N{=;58^m4~63KKb~>7zwc9KRk-yuGXo1tgu;m*k{FO&M?gZ zce~*nSbHcN{b2bP@CsiiEDSTRi-=I_@SeI(AlwNJLcSw0breLxN~ zoydxCp0`_k$<=rdYq2ICdt#O=rYqUWg`V>wLGRAM@|1%&ZkK4pMN5{b@PboiGJbs8 zmn&6=XC@bau_m-+XU~d35ZOKW`DBKL^E}Ie$+(l9^F++JFcmr0SWL_tyLIM){H2-R zL*87+{A&$6uk18VJXiyrVeLY{RB14M0w&UX2s`Tm3HloRm_v*C;k<3~etl~IPMC)M z0GOZapQ>HxJF1oGA_VN06LYXP3pt4Rot&xb~4iHm?qbiEF~pBA(dXw&cFUKsh% zXV6XGJenvm65YNu^xS7O=toWVIZ)C5MW5(u=#1d-y@s~~)-j>GSw>yYUL81;JJ3-J z6d!v1g0e=rHxHx(nB@CjPr`j9Ev57&x#nna50_&W33H`8xSOD14{3HSkN?(jwYKsz zI%sq7i?BhbH=Fj6C(`rk@LbdCzV6@g7iV|G^i*pjpfU8(iOmB%s*ey*CciF^KtFmsiG-px7R8OoBjG2T)+n9~4;XsYf;B zS_|O@7H#vqOUQM-c0sA@!1$h*H6~5R@<1}!$EIrUi1>{z*=r9jd0vuDy+<$IJ80!% z(duqJnl3JC2x`f`ZS1I-3W%Ri=zE|Bok!EMS`y$97uDsV=T@B6$cQX)AMxA^-(vHT z%nLw$8sA~~EOea*@K;Up1^}9_9u3X?ZU1XwwpRw~KR;PbB8g=$`uI%E^1i$QAUS4^ z@akP8Ggp(jlD>D|2Yu}G`k^^mxfZe#nXcuw6~NO&YA^>!%=cImtq9Ia=z6&hL>2hG zOZB~~F-saqn@)*Z%RVm5wx~B)oLa27-UTojTG=&zWzY%rpSU_^Cj%4m(_a8Erft-(#^z8GJfWkm>C0Zx8vn{2wQ%0z3Fw7$ zVu45ly)?&;U$X5w5uPD{Bp26V+zXcitS0Qy;?~=H%NBCu4T=IpF-08!9-`aF*f&go zHSBfwPs$ml4*Eu6`9<)X$0z!0K9;|Cva~z+{sfeB^lshG9?|&Kxp&!9x-;x>kh($fz%R0zlek*-YAff`KoK4#D?`+*`gn4=4#3}ldMZmD(a(=;a|z#>_z zF{fz^@ectx?%L6~Jlz_iPUuxCbf`G;Gm|yAV>?34azz_MPWas?DPJ=NO7j8wfXv7g z52n}Ej20qfp1zW@b0JMI>9_^{;Qmlntg0S%>K^=y{)XoX`FW&~JntNUX81Df+imSv%lqPC7(a`p0YCqrs z&!qv4tQw=gpB-{G|Jd&cowluU#&`k z+suvWBifu9UFWKfeGY8Xi87wUr-!pgioV4H9OwW`d4+@mZW}NxgFh|_@joyy5BH2} zM4g9?OWIJ75`!f_Sbhq&PJ^fbp?6yEFk+suOQ^x6kh7YSYY`eOWE^;2Tf;;3NDC{U zop%}J0R_-wt_Xy0&zF`wki2=(wZHngty5D?}W`WvXpAP!NIJ?;s#rx^E?A&Z>9M?q}zf(#)+@^;L-Dbd!@mbQh{#FCmrZ|I@<8vcQ9n%im|Z3 zSdbZ3JGu=;-vqrB7Op%u^*)J1<5r(WX6oMnTg5u)n{ipTn^ho_?QcrPkA_?`GNTN zKM429nR&Y2LlD)YNSEruducCkJN-TtM9MUT63E@ak>#YTC5TZg)U1gU37!6x{Su6A9#b2rx$K`xT4m}^#kpGe2ca*%hAqF(vO8j?*zF{x4ffV#<<+1{s z1*VbA%4k#U9Ib+ztSR=x_-NIp|GM!S0M~|Kv(7=GS;XCjmZo|3r?P5s>I-E%Bh@|O zFzV3jq-6wYlX!SW=+99{)OjR^2!nTcAw@c+Pv_ht9SZQ%xvUn!kEU7mvBwl$)}`Z1 ztZ7N7TIXF?Sie`m@NegkT(LSnW*hTB-w8 zm4cWPKskeG8;4i(+$%R52v;0LgcaF^g;a_n)m3h);n~hUc8|T?J0G0T{D#7p^9@pq zkgK=Elqu_qZSDxD^3;woc=&=7iK|TCTEvOO(<($4qUi*@&r)H!ue$HWl6JL}_cbOg zo$dZ}*$TN>V4)uOI>_BG2+=u6vzGkn0+c^2mnKV1`2B+8AwQNj{IkMNv-&8X@Dy!9 zu6UDhf0-2|*Ay2be%6_gHRFPje#dR`ARq8CV}UkgDjpI5gG_Vf54f_!Q+y?bD<1Ag zD1*sT!axh7X{o}h4@8M_4PVj$?ANxi-8FX{1b}%lCzZhf!w{qnk>qH)JV@-EahH`f zaDeXYr|$T9dT3>IAH3ZY+uR`X1~@F{x1^Pgd-w2^``iJUjR7tP@*odp1K>*uf)7Y# zUnA{`96)D4kri;<#RUklI9+pBQH^FyHbRzzYGeWSXR|{NP1za0L4pI5S4M#0iHOE} zz^n_`K)n|Bz;{F8L0}w=5Y7XXI1ZW`jQ?{fR^5nU4=*kO*8RppZE7qYP40 zR;*jW1z6^@e#|X>0IGpdZ9g;c&89WN;t7Rj-m|UchBK_QLGIj}(K$@q8*kt-$Fyh} zh+YL8U94e&X#zf;g@j-94xa@A-z7s6A8?bWe+Uktm`i@Tefr=4$Z5Gl2a$E&N-X_D zE4!e16;kfpyoTpM&@@WBj%YGYwi3fX#GU9~&g&xPyE$rdSb#$oNdAps`ym8|%vo+^ zmw3+<$gTlRJj3rD*Z4#9DxW!ju1Vz{eIPwhnn;61f4JUFaS@O=-)x#Q9 zIAQaBNJ`hLN=;dYCL=A5xhoSDKqKb-I~o!Gx+>^9zx%5Y?AV!G90D^8hJk+;HRK;xWlLe43IWUVDM65Ex|yImgO|B9>7+PEIZbimQXuUe~qa?t3f znApNcTF+dL280Z5tbcz*tJ_!GRZ%5fybcOo9oB)un{V&iX!b???bJr`xz=iyax{JD zW}~;bdL&gjp8sfccD+usZJGZ859Nt29u7?(s{`@6o4tS@gp||_SUc7!f}u|v;(Mq( zJvFxU-fHw(`_Rz^`-P8yJ|vWsRfY4;8NNego9}H+1qpaH##~)Cy?uhizFxKPz%hs zk5{c|j}ObCd$qm<50{KtK4Jp2k~KLx<5bE>>{H#=4v5osaS&=LVV>Zk-DC8#k(#Sv zE#}ilS8OQ(Ap9jS(`U~69EWuD!vaA--l})Sgks=4Dmt$DlmCm67{safC1HwwL9|>7 zZ(48((6bI8b53-F)v6}Xm+66o`2*w~B9Is#W>NV7WN&49wb`#~*SH;={}GJ&F`%6# zw9mo4F3B-I z{6Iy=5tPaG3@b9otc4%2;DO5`Z0nbLvt$7p-d<{km1BcGsuOd2`1HONGeDnSh$AU@ zhBSajyL{$$p6CgVy<#HcbC`4wfVVne0KXSY!%;N~+rNP@*pB;qy$j0PmgBFDkZ{;h&ksiCmS@nywi zC_b}C$K(V5rm&Ph%@lC({EBi#PXIDAK`-Qikbdq!bQE=i&WhY?8?J2vDx6k(XqKHO zXB6ai7RBiu&N2lS0l0|+*|+SG=}2M@DnnnB8wo53D!7vafZ>j>bvyNQ+y)Lkxw88F zO#Mqh+jv{l4b~i*WFVH(&uDma#%Qdd4E+ze zvtT29z?B{-?fyvTZC*SHUhP((oT3w7gDkzm$yL(eS9ivEvA-v~Y91jtLu2l+WFXCf z4dSNe|08kJgW$Sal+f{zaJ6aTy|O#&uOQxCw_=sHw~gBhyxyH8>wnjY`Fb|Ia zL060E|7!0$pqk3Mg)az#G765Ps3^g~j(~`YAdol~Mrn?yAT1~YA|-T4Ap!f03OY)Y z5*!7Tj)+J}f(;l!Kw78?N(nJSq=Wz=$vZ(uNxtv>@2&OL``7ya71n~>+XWxW;(c#Y$xP4y1O=#iN`-bG7FLqW;< zCrotOn%%`raPV<7+x~?|zIC~yGNo%PvNTdNbh&aZt=Tj&B@SIGYLLefY@7-#-%{Ky zGt*>P)&0_mPT;iY;I3BLQB>hWZm(>Ln7YEml;Y860$w_|eY|}7t!<1YyO4<{6Dac% z@ur{;;Y1df6jrb1*rSKE;knoO3$KOGD_o`X#|m8KNw788A*v~x_+C?1GfWsigF3gi zcCWG5yzd2Lk5!=wL=qY{b8?x#p-!iNr4jgwh1Kp&!aBRJ`F2e+;?`p-Tl@oG`22Z1|J|>4h7Km|#!#Wz8 zs`Xc1s4`3zxzt0THY6bj^GuY?XoRfK;avv@Dk*ahuHKGhw_(+%r}Ow@F&x(C>v_So z2OL-WWoP1YVEQ$iSm3=!Ihf}RB!*%E&YLj=H`s}xlgpL~EwXb%Mp>H9+^Zm9n8gY+ zaSh2Kc!z%iIYHH6Q!0xk`nMa%R(Oc|@V6Ttyy%?hratwKh2U z(6u@ zh3V(D?s3Ok26ppeLf&`9ql=td;gQj+7SngDCu0$y%Zkm(Jw*?UiF zbM(f}kz5C;JE$b2r)u4e1Y4FHAMA^s8%&r@5BgH43s9<=7Dc!lzUDiLN{mG?;y57J zDD$2N$Y+}Wwd}UG++LR@icEjswXN;P?E2qJCyEFEYXAqg+h6Gy_3a2bQ2Cb_RX)%4 z6WDNn_$4&}6#FeQbB@$&?Ew{jUrrPSMT6;@?1~jwKhXZmUOT80Yy!tBTsN6L$j&TD z9y$>P>X#i>S9k@jv|hc~0PO8|$j852ffJP!&S^S(%qgfMWP+0cD%C;hVCk0}22hO{ z1dd^F1%>cm54-~yp^Sj7RiN;#%mQF!um+q-(01X!7=fi3EmCjdzZii@G$@h0TA5^g z{W+%M>b)Z`lA1OH{pe_B&B-v03(@Ki3(aMjf;*rp!_k)XqU4tIk=<4Wz#Yy9<%m^> zwO#?z3?4s5_!#I}d^9SQQ93M&-n6>Z2Dc>gY72ofG^=!^@~6+e;j}MKDwwyC04Y0{ zUoCJ1G^ktg-+LK^Z$MQ{NiOCJL-Wm&hbj(D*$UTJ*aIR<7J2ml4e;N32qdC+fvT?R zcvdM4=;o6b^r**C!}|5}5B9BD8R*3Ql0G_6`tfb}Kzqg2a!uD<7XI7tZ!?4x5>_Vm z$yTw||| zG@yrXvUZE#J-g2(q^TUGYWFmoyT+bWKV}H)@13F=Pt^Je_Y7lRl2|YDR)tb}bBjO! z{Z#O#gl(Jb;5%i0Q_pxRNU*MU~M8x}$`J{#?*`FEMQFkNXq6SMc>%*jphXrNqy406x*C>BDC~ zg!oO@%bW>(e}d^SzRC%eJ?8t7A6!dF*<>^vQsu<{-HL_VA$pQm*KB=kjR-7ve@3wB zEV8y9A2()3mC^mF*uR>LU^hfpS$e_jE0C%G9ihjOx~YUI7~H?#4;Dj>9ec>RjHO;e z9-rRI_>HF>{07~OQ6hS`*~SvoHZe+~u7r=A$km#??SQ^kV0vs$JJ&TKTCWci`;E_0 z(yR=l?NM6V(IfXp2^syy)S;QyJ!IQQHg)$~S(6jVy1L;26hGDoG$z%}3&!nb7ABbu z7yq<{aZ4#=^+Q&FT77_e5&dXJl35NlI3<@FXIg`9#+xUDyUtX!ku$ObAlUf@!8KJ( z99q=DP;v8S#&4`(^@qJma}dev-Tj|H{O5($8DnA)(Wz^ldmNz4h=2v+BoZGaBUAAJ zbwnsmW-f~0w#u(MYrj*Th|+HBH1O6&Bx2y>>;l-)W2Py*_kN_mM|=AVjL%LzGlqZs z+?z`;qNTDFOyJBh)H{N}J!bT_*68j2ULv0#&x=&)FVJ=_k5D+ATLRq#!G* z6G>;fIAyJBLHtyPfttGvF(?~8*f~-rsD8ees*J(QEJaQH=}-Oo0^K`a9=CmX+KIKO z6yGq`2}3u~TW3DLqn)bZ{?p{~1>flFWq2y)LN06^SL|6_jsXy;T0*NhtQIVeKuMe+ z2v_c)r!tDWJc`XKohV);5h!x!69;FLZ+$yhH4okT2AI8TDAqRmpI&ETmj+{J@vfoP z^HnNFm1nvHt){dN*Y1Pe(=BPfhFprU6@(O^I;ICvK$dbCAf7v13;`HQKg@^)W0$J(?KDdQ&2t9t|_(!r^(%=S{Z;1EQM-#H^fYo2$hT zuxfhy!9Twc;O^XS)@C?*396jD;sDd6V){!1hACuY=Q@6U=n$%gW!@j4&RKIOeMOS^(+iz^^BY_q?*SqeCf57_kXxt-ZSeE7-yjarNnQ?e zdhKZaO$p3iY&e2|wndsZZ5M|)2sFXqmH~{o-q;o?D;u%E@oua*M1(ESCboLirE@e) z1Qfa8Cl$z|r}2q}IK)zbUg;ktDdGdSNR5D8WaHA^#342uM?ek7YdPWrcaa5SS^8{; zwgkjnfL`ut=~Vwi57MdrA5nE=d#wHg5(nc+ zAxW7fKj=Hjn&oxsb>mnjp8c1p8U5IlZB8!}+%x53*)2RCx|3tG^Y$r``Q;J@*!c4a z8*D!@n_t&l=brz!>a(z>^e5i!kDoA`LM;W`HpJUUT6)07C%xfZ%$(U8O|!a)=#*vIlnV3c zG3tYSf0eTiF7;14o7RY?103=cN|Tk~MnHt`iYYX5J!jQ`4Z#Jk@m3+6bBc%%ayImtC3@Ks!KWThw-cNcC zL8dK-_U}C=wVl6u8Bz~rPGtZ8Bd5ycY4VAn$3Mxc=3(C*EzjYu+xDcQU=J**v3(zM z_!n98pZ&($6*3Z0{9ihl@_F`ua=GbpmzTx!_Ds`QS4dsKBJ$YgCR-D=o0)BCv+K6M zkzzR`**4@^N1EtSzIJ3BY5I>o;-k*SkuY=FDR$-skyt#vaB^WpJeHU^{ijmBWtDpH z{^^%_^dIe>59TP9Ped&8Y=7*=>}Ct;ImMZj3RzvaFnc^D)X|Vz-^_zgrd0fR5ar~; zG1oa9=N#l*I$f$A-wciABIO6JIh|LcTN;k_qk@XTg8XKc^q zv@i$uA(yj}z0_RuD$02Fy}Z!1k!dPnXf}KBZ83gr4%ksQADN)tu4s@AB$&~${03^- zO_AG}Ztl{)WMguXXX(N1ma38FI>yB{{kiLbVG!w~=-5>DXtciBrzh;$=E#}Qmc1hT zSr6ob=y83|edh_FI2AYactCOv&-}&QS&j4vNMnFzY5Cpm1|2*HUh?zhzK&uYT-~ez zT`dW<5!?7iYy&=W{w1|IF$ecFNN71d+}-?5UyCkOED(^2_xnl_=HFH&G9Gx-@mPbM z-|mPmHL@dCoq4L}8YwhTa ztA&XYWtbP2v<1f{V`LC)!fG*Ch=_{ug`XOE(L&h>W_RA-a~%`#ED0fq7V4J`Kh|F? z4h-S)hdh`r3&D0sK`i(SlGI4Dfppn?o=r$HZGq)M@hMS!0Pr7sLYhF##=7U=Vo`t~ zbBQO58N)Wlf4vjO1DYN6%gp1<&0f@o?;9jvV$tbGH0WatFw@4JO%u3}KSiBgCxL8g z*BE>`G-(J44t`IY7IhQ(=%-C^9!dvl!zRPzcE>pE`blgw7gaZLX98s^o-2G(Q7B)#!2l>4<%kjnQ3;yp#5&m*oZe-;5E92~o2BfOGaO*< zssqI*JEbh{La{yXzT5gQ-H@sE+?lC(C1_Nk-&}UI>R*;(RDOWYf-iwqKNI#@yUU7$ zLG<`X?i=W4!PTMF=(gw4$ewu;4P3sw51!)(_mbF3pD#cg38W1Y)9g8{4yUu?dPg9a zmdqID=Y61RJ6UWuLR2EVyqY^X9#<(B zogOTKPO9QKy_YKwk!4d6hGNtYMIKt-_PO>gd&EEqYRzq5zb2@HeA}2v6GsCP7KuX< z*&_^CqC7P9`%+@y_zWXlqC8O1BEF(^c&JzrO4$g-=NFw5-vd$f*2yoz^0Wb#W~Q1$ z&w3XpQW`zPmnNcOKVaf%9EwixUIJyuCtFsn2|ptYHkBximfL{Ppf@qFJb~*qNCWBj z!5_aE0l!l(j&;R4MQ>ILK1-%TDGDC)qkE&K7$vG`G;GYz!A|GQhpfAOe2g$!blnEq zrVI&`5c>d8nbMn3Lv81vo4UsQJ4=g0NL7;TbWr|js4nF#)boS^BgSewv=08-@$=lB z9RPbksSi7e)1~CNCdyR!dQl&EtO=fc>df$$T(7l}IAtQczc|=iAF9@Eqs1-u&PuHk z9~~6=T6IN`GNl{pIiS1SG{>>z^hnu6lmzx*_iT!0LVt;3xA{W(HFbN^Y^;rZv3cCp z5_6INYEHwO$jKVY=fF-Zi6J@8^~+Zb(866HzfYGn8_8I9(X0DmSPA@Hykw_ROz36G zM)T)G;Qz*XB<*?_#gIgQy~>J-l<SEr|VNg{vX$H9O+(;&!iaIeCH z-YCO>TL(Yyk_0&+H)?_*+rlPR7(vzYy2kA6Q>@Y`2}$*EQv*kxWCQuVC^UF*LAk5= zH1DuDXUWK30Jx*cX6%5Vg;2nIrFzbB$SK0fTwHutYWuxIQ<_W8Frc36O;(3_L!{g> zHMESx@@y+bkT(jaY>3p7BMis|%?|y+8S}0rnO?0VImmbNqb6vw0UHyv7#dYe!oT_5 zlP}-}h3iUG-^<8q0%jY*S(9(N?v<&it+l}P*Em1E$sJoIxt@`L1&rypb^GkvvM}tR zkB6!_czTaO32mJxxe!J`Lx0BUk{eu~%Tzpet7-JoCA-{Xvu3lc=kNnU0nm5~`?V@& z>u#tT6*f~~md1byvoDd0sY@gTejgM(-$elRg<7=X}TA!^4dE+)I+Cd_c=# z9NXlhUI3I&H)S%WPDV& z->co2qY_Oos#bH`rogfCRc*2nT@8tNR^yX%{crp^>5IizU2*eGcFvf|#O~;V`4DfI zWLrA^*H~pc(O#TnHIfkI;yP|yuW{unsIL;wNkUp!!zUyBCmt@B=uq<}`_UMQ^DMeR z0V*u4X=0NFF`gk)qyF4Xb@8V0=OS^sCZX2dWyR`Ty$MLeAgpaIt0_%)EC=d!j>U+f zu&o;qh!Nd}x;s-2DtU**GOso1nW>++@3B*Sg({jC^kD~ax=yTz%c0uZ+6M+m{K*ba zP8c3}N*fB6XnYjNO9oCknVsgfhO>ohMDvR7Zeq_2tS`dYN+=hLO9*mk9k*d;bfyf# zcV^ti%EBWBAMkc%vxH5$RO==?VT=?wx35axU`G(l*h$DCC8Eb@&Y3U7z6d4xD3dk)S{x$gzEpd;2= z)s}M5$`7wSlJ3$bGm)t0`<2ByMYx=a#6>5vgB=X-E`YXI1IcpBnXg7^{NskH_{>+F z2ZG;+T;;<&y9KJ=M!^f(nae2CY=`ZQN)oW`dp;=DC6pDfnU=p6^!~9W$@La3tEqEl zM8Wh=+SD*N6m#R6BvUNx6OIyvFGH@G70Mtgrz^~Se?_6Gu;Rh=jpDSMpMVgg2wTaY z2>1(@3V<)8X`e^N)8WsIc1~YfIHoOOz{$vu=1teYhbU>V$>O4&J77>>I%?*XC9^mW zW7Yvq5p+m=?kX`QhveMjbgfb3naA$<7W#zBhM%G8c{4Ayi_@r%BRr-+0bfo+8=Hx% zyyXX6GFq$;qbQ?`m|z`CHn9ypCR$hk!YB3Cp)$;!32vo~GP+u@Z=QtRr?h;Q)OMms zKj+X1M0u*+CsnIs&`p0bfgR3-lSeYgU*~ldw4I#`gt{ohvU zFKzM3YySb)U53|9#=R)qm=nzZaV}d{Tm-V`9nRXw(?FOI{sCCdGfsa~mi2US!*@H4 z(OCS;5$8XMQ}uDVQR`_+-;2N|kvbWzx*cZW4IRlBPUsWOQRSmU%qz+>5C4S~DmUbh zecYH)>^O3Fyu3GRrNi5_O*=+p5C?e^-Sk6oP68e?E3~WRr^_^P4?6u=&rJ?5Bh-;| z`AL1L$wvH(M~cws@>y|~S);b2xC2xnU1hmnE~gE#x? z#0e94Xc;gZV!E43*}f*Edb$dhSgD+D3RRnTJ{!3;4u{XEGC#6R@2KMj?|F9JPY?r~ zdcPtwMyqMPYD%KcZf@<4J!R#+SsU4-Dl&+ZHO0fF8%a!xbFwd)qa-;`ikoK5;#rN( zreQB7m>z?UJe_{0W||`6<_s*Gw-;S4?!L(?W;+#{V>qap!q#+K}x4<+$5M#~oOmy4I~f;J)O zGZWoRksf{I4g#PD($UIeh=3GPJNg10}#=I*Zum z$P#|t=uLewfn||jaZh20=!_kaXHL3C-|g94NiBt3Qu@;^6C11GoP=>R_Eb|)RWdz= zdtF&faJd|nYh_ccMJDE-Rwm46q7!Wnd6r>_vJp8d@ae((OhAFnyrM{I28tHUJ(2ra zMGW+l4f%8$t1(SAInfH<|7wT6ev>`c9Pqiz9<8vxTV{=mV7z0c!}|!azQW4lr-5l1 zVr%O1NN%x_3q9x^+9OThYR_hEBpLUp{X$QF#Wj??fUY|VjMUv zcOpC9GQ&hz!i*!lXEYY5MGazGGrpYl^)!&Lj1MD}j@%~12_AD9g+$@$H}qS#0eZV6oOtZ=;I1G^u{Vv4mXfu^*OdvfPR0e z{Li^~$jC*Rj;;)(kl9aerzD18-{i_{aGqHr4_$K+=+Y?$d`6oc09qs*Bn0e zx3%hfCz9CdlO4<2{HxY9`UBN(Wjs7Me!e%_QslXpy|p^zAscbPx$mViJjs`zp_@VB zUvX%(B*BXby$;v9B8xTWm}nJ^imO4CgZYZFIyr^PE4;@=&e+!0=~!y+r5}g#UtObc z$*@{^**mOaB3Na27Jz6>GnwSEqx ztcXSkJoyU4#i2%L_yXq5d0cBV^$1sO(LsJ} z^XV~5As(A&4jcl*ojRaQ6u0+62akC{IlYWBMk+z{U`cf2n}lWWDg2%*OfsxX*hZu= z;N`-|@t%FkT=E|p!uEJ|+N2+5@E(p9uO2@BZZGORdX5H3-|yn??mo;Lw#`i%O16D4 zgB-Taw!3vA$MIGO%`@1(i5G+Q3PFFW&n^38%+;C!}c*>J+3r$L)eY@s<<-|>{Y`{XoVwOd=^Wc6gvyC&f_;)V0Xm`j&( z7oVC@mJ2Fc@^<@%9j4tS_ZP2N6!}i&Km7wijpG{IBOUavixB%ZMZWGRFb>nxe%_ve zLEE-9%gD}?lb^4!&~9CbTR@;E%J<~DQvt4LJ;AqQ>w-{zcI!~5&$+n<1^x)hTuw!* zC|lW>yP@6A?ETv4o#KLH0Vq^ZgiMCg52`Cxu2Mg~&NtZGyJ^v4WtFANZLMr{yxh=% z_TcktM<>>~dY=jm{A{0R7im zO*<)8P)E?Kp1^LB$n7QfjzoI2}=J%8!S@7JSl{u!5FR8AsSyl!Y85Rn4-kr6Gt N=x5!Rk8giR{{!#&WjFu; literal 0 HcmV?d00001 diff --git a/assets/icons/icon_128.png b/assets/icons/icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..c003c1cb368b13c9e6523e7c29530810ce4b68b7 GIT binary patch literal 2053 zcmeHIX*e5r8vlnVhP07!ObtP$RYpb8Wn77hBf81d(b878t)>`3*4>UO>T0T@j;^CL zZj&~sGU8a*HbJU{c0!#oT2~Qfl4>;xtIxB~?x+2}ANIrVeSh!qd!F}sKfEvXG|>(A zmG)Nv02tmK=e6gPzCdZ;o_+3kWpK|yqP*Nrg8E^dmA$~`njb!pKmaCtTnRujegx1j zEql=30|0Vma{taGmC65ye<_k(j}QP*jK|}ge6B;*OMeJezxGX^VCLnkFqT3~;?Zgk z#@!=@ckp#=OzIsE)NE>I@AXDyWo2vpJ)`4&d4IA0je|oYe+>=*&gya%RK&>7idhvk zsFB_LmZ|W#xIk%FwRhD_Tx|J9v&4*df2c*2AiBx3-=K7r7F}#VE-w4J$%wESVgS4b?MEm*L{a?8M9>RAHu|wy^inK{Coq*Op^y#wWPO#o2NDs z#dt<+%<5Qucvakfy^68LX4{SLuGsv^+-iO!@q@XBf861|Y|=}&7DsoAjxC%TTSv8} z>tXP1{#`X&tNkk7xeoMm_lqOi}vWxQb96vK=g@Sb%$7&X*01F z%1u~X5mpuSqjTxJ2cfL*BrIXIp-}Th8uruqNAEj|lo?niDfA#^v6^$5Q~lO% z-3d=KA;1zmBv!!|b;`s!H)Mx;k34$h6tE!_uvBhc{rnX**ubLQku9qrK%!L9EB46XP4ck&E!4zuo@6F-jCs ztj)D>SUc5SG#h34j*vdBbpGqev>ua6r$foDsY+JjBLIlUi0tnHoa))21V%2{sVxC4 z%C++lz*5$6B~1m%S+J~TR((c2$6^gz3e$auc~DMDUkc`bMzt|yK83$gpml_a?1)`1 zEhT*!aT!uL@NW6}D5O|v8#m&JCJOZIoL*Z>YZLYhCtcTrXgPUH5>?7dR``K(onoMp zlrWACnq?n+NobM_pB5kAjSnV;mTX+boFnP~(dRnhh!oLK9T06cVs0tC(Lmp4NEsxB z)DK%+mj{FJ$P))3;BlMn;2aE?IuG#eq2TAm_ogYflM%s7K0La?aD_i75@rw|RQH~4Ro zx^uupnzEFW`WSE3)z0wD6Wr|_Gs@HW*qLY7XB|gja%B_>nZcFi&N4m;Yw7u8{t=y@ zNxCzM8#Njq%X_iXv?Y2uA&xRK_e(%_f@JG{8aTBLou}e%&&Eq)Lhud7f(uUBL2>r3 z3Fvjtxn^6_;l!X34N=hEA^NCPDl?E?z0AK7->4GLo-!0@Vvr3JnGZR3UF;Jg0JTkd zUMVivewOw-IWOP(lKz>Y%+6n%f+SlHosg{!-@e`nQzxqdR(Uc(-6*NeE8=$)Qyx&& zHOK&PIC)R^1C*B!69Uw=e)xwixbN=SxDTB6w^v>T*h4NHZGip1;D2FDyO5!12SFMGaVj-9uv8Cp4y%&SB whcPp2LnqVTgtd#=3=DeO4rWL+7%?9OT4!qPbghwpW)(=Yr>mdKI;Vst0ILEdXaE2J literal 0 HcmV?d00001 diff --git a/assets/icons/icon_256.png b/assets/icons/icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..7c36796b48e440791daf1f7829fbd7d9b69b1815 GIT binary patch literal 6548 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 SG1{;PGt`T14NGY z&;L7X)))U-R-VzJCTp8A+Ic(t@^3eiKTRP&ys8cFKn*7oDff zO`7&`<$Obb_UUrsIuF{6+e_0WpKF_*u@=43Et@m7Ax~oQ&2NIP&lkU)Te2ana6@fs z_N%k0I~RPqrM}{+$h+OYcce;ZguGv{b!T_=RsD($Ke=xEb*y{u|3*PsH&XAz`t&vH zr^X4GQuRqMEcnQ4Fiu$ONG-Kt@gh%r{C;80L|D@O||6@&5W#90A!JeyO?A!cNidBGB zs`c2oT?e8mz!jWYsWF~3V1Ak z;ASFo_|{I9+6SWIITBtETLT-m&N#mOvfO`$J^8*o-+$baw>S8=Mxy$3iZ9cSJ^N;9 zuKc+0l2QMEMrZ$H{M)DQ`TapKamqiIlnCd~ySL{b+H{8J@@4y^CF{4=+{@;yJZ;<{ qAf??YaA1{RlceInvuFOF*I@tHv+tU2?P3Q|g7b9sb6Mw<&;$Ty&n?mb literal 0 HcmV?d00001 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d2b73fc --- /dev/null +++ b/build.sh @@ -0,0 +1,267 @@ +#!/bin/bash + +# Tetris Game 自动化构建脚本 +# 支持 Windows、macOS 和 Linux 平台 + +set -e # 遇到错误立即退出 + +# 颜色定义 +BLUE='\033[34m' +GREEN='\033[32m' +YELLOW='\033[33m' +RED='\033[31m' +RESET='\033[0m' + +# 配置 +VERSION="1.0" +BUILD_TIME=$(date '+%Y-%m-%d %H:%M:%S') +GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + +# 函数:打印带颜色的消息 +print_info() { + echo -e "${BLUE}ℹ️ $1${RESET}" +} + +print_success() { + echo -e "${GREEN}✅ $1${RESET}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${RESET}" +} + +print_error() { + echo -e "${RED}❌ $1${RESET}" +} + +# 函数:显示帮助信息 +show_help() { + echo -e "${BLUE}🎮 Tetris Game 自动化构建脚本${RESET}" + echo "" + echo "用法: $0 [选项] [平台]" + echo "" + echo "平台:" + echo " windows 构建 Windows 版本" + echo " macos 构建 macOS 版本" + echo " linux 构建 Linux 版本" + echo " all 构建所有平台 (默认)" + echo "" + echo "选项:" + echo " -h, --help 显示帮助信息" + echo " -c, --clean 构建前清理" + echo " -v, --verify 构建后验证" + echo " -i, --info 显示构建信息" + echo " --no-color 禁用颜色输出" + echo "" + echo "示例:" + echo " $0 # 构建所有平台" + echo " $0 windows # 仅构建 Windows" + echo " $0 -c all # 清理后构建所有平台" + echo " $0 -cv macos # 清理、构建 macOS 并验证" +} + +# 函数:显示构建信息 +show_info() { + print_info "构建信息" + echo " 版本: v${VERSION}" + echo " 构建时间: ${BUILD_TIME}" + echo " Git 提交: ${GIT_COMMIT}" + echo " Go 版本: $(go version 2>/dev/null || echo '未安装')" + echo " 当前平台: $(go env GOOS 2>/dev/null || echo 'unknown')/$(go env GOARCH 2>/dev/null || echo 'unknown')" + echo " 工作目录: $(pwd)" +} + +# 函数:检查环境 +check_environment() { + print_info "检查构建环境..." + + # 检查 Go + if ! command -v go &> /dev/null; then + print_error "Go 未安装或不在 PATH 中" + exit 1 + fi + print_success "Go 环境正常" + + # 检查必要文件 + if [ ! -f "main.go" ]; then + print_error "main.go 文件不存在" + exit 1 + fi + + if [ ! -d "assets" ]; then + print_error "assets 目录不存在" + exit 1 + fi + + print_success "源代码文件检查通过" +} + +# 函数:清理构建文件 +clean_build() { + print_info "清理构建文件..." + make clean + print_success "清理完成" +} + +# 函数:构建指定平台 +build_platform() { + local platform=$1 + print_info "构建 ${platform} 版本..." + + case $platform in + windows) + make windows + ;; + macos) + make macos + ;; + linux) + make linux + ;; + all) + make all + ;; + *) + print_error "不支持的平台: $platform" + exit 1 + ;; + esac + + print_success "${platform} 构建完成" +} + +# 函数:验证构建结果 +verify_build() { + print_info "验证构建结果..." + make verify + print_success "验证完成" +} + +# 函数:显示构建结果摘要 +show_summary() { + echo "" + print_success "🎉 构建完成!" + echo "" + print_info "构建结果摘要:" + + if [ -d "dist" ]; then + echo " 分发目录: dist/" + + # 显示文件大小 + if [ -f "dist/TetrisGame-Windows-v1.0.zip" ]; then + local size=$(ls -lh dist/TetrisGame-Windows-v1.0.zip | awk '{print $5}') + echo " Windows 版本: TetrisGame-Windows-v1.0.zip (${size})" + fi + + if [ -f "dist/TetrisGame-macOS-v1.0.dmg" ]; then + local size=$(ls -lh dist/TetrisGame-macOS-v1.0.dmg | awk '{print $5}') + echo " macOS 版本: TetrisGame-macOS-v1.0.dmg (${size})" + fi + + if [ -f "dist/TetrisGame-Linux-v1.0.tar.gz" ]; then + local size=$(ls -lh dist/TetrisGame-Linux-v1.0.tar.gz | awk '{print $5}') + echo " Linux 版本: TetrisGame-Linux-v1.0.tar.gz (${size})" + fi + + echo "" + print_info "总计文件数: $(find dist/ -type f -name '*.zip' -o -name '*.dmg' -o -name '*.tar.gz' | wc -l | tr -d ' ')" + + # 计算总大小 + local total_size=$(find dist/ -type f -name '*.zip' -o -name '*.dmg' -o -name '*.tar.gz' -exec ls -l {} \; | awk '{sum += $5} END {print sum}') + if [ -n "$total_size" ] && [ "$total_size" -gt 0 ]; then + local total_mb=$((total_size / 1024 / 1024)) + echo " 总大小: ${total_mb}MB" + fi + else + print_warning "dist 目录不存在" + fi + + echo "" + print_info "使用说明:" + echo " - 查看详细文档: cat BUILD.md" + echo " - 验证构建: make verify" + echo " - 运行游戏: make run" +} + +# 主函数 +main() { + local platform="all" + local clean_first=false + local verify_after=false + local show_info_only=false + + # 解析命令行参数 + while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_help + exit 0 + ;; + -c|--clean) + clean_first=true + shift + ;; + -v|--verify) + verify_after=true + shift + ;; + -i|--info) + show_info_only=true + shift + ;; + --no-color) + # 禁用颜色 + BLUE='' + GREEN='' + YELLOW='' + RED='' + RESET='' + shift + ;; + windows|macos|linux|all) + platform=$1 + shift + ;; + *) + print_error "未知参数: $1" + echo "使用 $0 --help 查看帮助" + exit 1 + ;; + esac + done + + # 显示标题 + echo -e "${BLUE}🎮 Tetris Game 自动化构建脚本 v${VERSION}${RESET}" + echo "" + + # 如果只是显示信息 + if [ "$show_info_only" = true ]; then + show_info + exit 0 + fi + + # 检查环境 + check_environment + + # 清理(如果需要) + if [ "$clean_first" = true ]; then + clean_build + fi + + # 构建 + build_platform "$platform" + + # 验证(如果需要) + if [ "$verify_after" = true ]; then + verify_build + fi + + # 显示摘要 + show_summary +} + +# 错误处理 +trap 'print_error "构建过程中发生错误,退出码: $?"' ERR + +# 运行主函数 +main "$@" \ No newline at end of file diff --git a/internal/font/font.go b/internal/font/font.go index 515c457..a52bfa1 100644 --- a/internal/font/font.go +++ b/internal/font/font.go @@ -62,11 +62,29 @@ func NewFontRenderer() *FontRenderer { // loadFromAssetsDirectory 专门扫描 assets/fonts 目录并加载字体文件 func loadFromAssetsDirectory() ([]byte, string) { - assetsPath := "assets/fonts" + // 尝试多个可能的路径 + possiblePaths := []string{ + "assets/fonts", // 开发环境 + "Contents/Resources/assets/fonts", // macOS 应用包内部 + "../Resources/assets/fonts", // macOS 应用包相对路径 + filepath.Join(getExecutableDir(), "assets/fonts"), // 可执行文件同目录 + } + + var assetsPath string + var found bool + + // 查找存在的资源路径 + for _, path := range possiblePaths { + if _, err := os.Stat(path); err == nil { + assetsPath = path + found = true + log.Printf("📁 找到字体目录: %s", path) + break + } + } - // 检查目录是否存在 - if _, err := os.Stat(assetsPath); os.IsNotExist(err) { - log.Printf("📁 assets/fonts 目录不存在,请创建并放入字体文件") + if !found { + log.Printf("📁 未找到 assets/fonts 目录,已尝试路径: %v", possiblePaths) return nil, "" } @@ -87,16 +105,16 @@ func loadFromAssetsDirectory() ([]byte, string) { }) if err != nil { - log.Printf("❌ 扫描 assets/fonts 目录失败: %v", err) + log.Printf("❌ 扫描字体目录失败: %v", err) return nil, "" } if len(fontFiles) == 0 { - log.Printf("📂 assets/fonts 目录为空,请添加字体文件 (.ttf, .ttc, .otf)") + log.Printf("📂 字体目录为空,请添加字体文件 (.ttf, .ttc, .otf)") return nil, "" } - log.Printf("🔍 在 assets/fonts 目录找到 %d 个字体文件", len(fontFiles)) + log.Printf("🔍 在 %s 找到 %d 个字体文件", assetsPath, len(fontFiles)) // 字体优先级:微软雅黑 > 其他中文字体 > 任意字体 fontPriorities := [][]string{ @@ -133,10 +151,20 @@ func loadFromAssetsDirectory() ([]byte, string) { } } - log.Printf("❌ assets/fonts 目录中没有可用的字体文件") + log.Printf("❌ 字体目录中没有可用的字体文件") return nil, "" } +// getExecutableDir 获取可执行文件所在目录 +func getExecutableDir() string { + execPath, err := os.Executable() + if err != nil { + log.Printf("⚠️ 获取可执行文件路径失败: %v", err) + return "." + } + return filepath.Dir(execPath) +} + // tryLoadFont 尝试加载字体文件并验证格式 func tryLoadFont(fontPath string) ([]byte, error) { data, err := os.ReadFile(fontPath) diff --git a/patch/0001-fix-IBlock-disappear-issue.patch b/patch/0001-fix-IBlock-disappear-issue.patch deleted file mode 100755 index 6f8a33c..0000000 --- a/patch/0001-fix-IBlock-disappear-issue.patch +++ /dev/null @@ -1,151 +0,0 @@ -From ffe9242a73abfb3e8ee9b6effd57e64155717bfb Mon Sep 17 00:00:00 2001 -From: yinqiang -Date: Sat, 7 Jun 2025 22:50:07 +0800 -Subject: [PATCH] fix IBlock disappear issue - ---- - block.go | 21 +++++++++++---------- - game.go | 26 +++++++++++++++----------- - 2 files changed, 26 insertions(+), 21 deletions(-) - -diff --git a/block.go b/block.go -index ad276ea..0b3aaee 100644 ---- a/block.go -+++ b/block.go -@@ -14,7 +14,7 @@ type Block struct { - type BlockType int - - const ( -- IBlock BlockType = iota -+ IBlock BlockType = iota + 1 - JBlock - LBlock - OBlock -@@ -32,13 +32,14 @@ type Tetromino struct { - - // 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 -+ EmptyBlock: color.RGBA{0, 0, 0, 0}, // Black -+ 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 -@@ -102,8 +103,8 @@ 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++ { -+ for i := range shape { -+ for j := range shape[i] { - if shape[i][j] { - blocks = append(blocks, Block{ - X: j, -diff --git a/game.go b/game.go -index aa9896f..2aa98e9 100644 ---- a/game.go -+++ b/game.go -@@ -24,6 +24,7 @@ const ( - // Game constants - InitialDropInterval = 60 - MinDropInterval = 5 -+ EmptyBlock = 0 - ) - - // Game represents the main game state -@@ -90,9 +91,9 @@ func (g *Game) Draw(screen *ebiten.Image) { - } - - // Draw placed blocks -- for y := 0; y < BoardHeight; y++ { -- for x := 0; x < BoardWidth; x++ { -- if g.board[y][x] != 0 { -+ for y := range BoardHeight { -+ for x := range BoardWidth { -+ if g.board[y][x] != EmptyBlock { - g.drawBlock(screen, x, y, g.board[y][x]) - } - } -@@ -311,7 +312,7 @@ func (g *Game) isColliding() bool { - } - // Check collision with other pieces - if y >= 0 && x >= 0 && x < BoardWidth && y < BoardHeight { -- if g.board[y][x] != 0 { -+ if g.board[y][x] != EmptyBlock { - return true - } - } -@@ -324,6 +325,9 @@ func (g *Game) lockPiece() { - if g.currentPiece == nil { - return - } -+ // if g.currentPiece.BlockType == IBlock { -+ // g.currentPiece.BlockType = IBlock -+ // } - - positions := g.currentPiece.GetAbsolutePositions() - for _, pos := range positions { -@@ -378,8 +382,8 @@ func (g *Game) clearLines() { - - // 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 { -+ for x := range BoardWidth { -+ if g.board[y][x] == EmptyBlock { - return false - } - } -@@ -391,15 +395,15 @@ 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 -+ for x := range BoardWidth { -+ g.board[0][x] = EmptyBlock - } - } - - // 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.nextPiece = NewTetromino(BlockType(rand.Intn(7)+1), 0, 0) - } - - g.currentPiece = g.nextPiece -@@ -409,7 +413,7 @@ func (g *Game) spawnNewPiece() { - } - g.currentPiece.Y = 0 - -- g.nextPiece = NewTetromino(BlockType(rand.Intn(7)), 0, 0) -+ g.nextPiece = NewTetromino(BlockType(rand.Intn(7)+1), 0, 0) - - if g.isColliding() { - g.gameOver = true -@@ -423,7 +427,7 @@ func (g *Game) wouldCollide(piece *Tetromino) bool { - if x < 0 || x >= BoardWidth || y >= BoardHeight { - return true - } -- if y >= 0 && y < BoardHeight && x >= 0 && x < BoardWidth && g.board[y][x] != 0 { -+ if y >= 0 && y < BoardHeight && x >= 0 && x < BoardWidth && g.board[y][x] != EmptyBlock { - return true - } - } --- -2.39.5 (Apple Git-154) -