移除mobile_*系列移动端原型文件及文档
This commit is contained in:
1683
mobile_battle_1.html
1683
mobile_battle_1.html
File diff suppressed because it is too large
Load Diff
@@ -1,209 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<title>深空战机 - 移动端原型入口</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'PingFang SC', 'Helvetica Neue', 'Arial', sans-serif;
|
||||
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
|
||||
color: #ffffff;
|
||||
min-height: 100vh;
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #40e0d0;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 0 20px rgba(64, 224, 208, 0.5);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.prototype-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.prototype-item {
|
||||
background: rgba(22, 33, 62, 0.8);
|
||||
border: 1px solid rgba(64, 224, 208, 0.3);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.prototype-item:active {
|
||||
transform: scale(0.98);
|
||||
background: rgba(64, 224, 208, 0.1);
|
||||
border-color: #40e0d0;
|
||||
}
|
||||
|
||||
.prototype-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #40e0d0;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.prototype-desc {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.prototype-features {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(40, 167, 69, 0.9);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="status-indicator">
|
||||
✅ 4个原型已完成
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="title">深空战机</h1>
|
||||
<p class="subtitle">移动端高可用性原型演示</p>
|
||||
|
||||
<div class="prototype-list">
|
||||
<a href="mobile_main_menu_1.html" class="prototype-item">
|
||||
<div class="prototype-title">
|
||||
🏠 主菜单界面
|
||||
</div>
|
||||
<div class="prototype-desc">
|
||||
游戏主界面,包含完整的导航菜单和设置选项
|
||||
</div>
|
||||
<div class="prototype-features">
|
||||
PWA支持 • 星空动画 • 音效控制 • 安全区适配
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="mobile_plane_placement_1.html" class="prototype-item">
|
||||
<div class="prototype-title">
|
||||
✈️ 飞机放置界面
|
||||
</div>
|
||||
<div class="prototype-desc">
|
||||
十字形战机布置界面,支持拖拽放置和旋转操作
|
||||
</div>
|
||||
<div class="prototype-features">
|
||||
触屏拖拽 • 碰撞检测 • 自动布置 • 实时验证
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="mobile_battle_1.html" class="prototype-item">
|
||||
<div class="prototype-title">
|
||||
⚔️ 战斗界面
|
||||
</div>
|
||||
<div class="prototype-desc">
|
||||
双棋盘战斗系统,包含计时器和AI对手
|
||||
</div>
|
||||
<div class="prototype-features">
|
||||
回合制战斗 • 攻击动画 • 战斗统计 • 触觉反馈
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="mobile_leaderboard_1.html" class="prototype-item">
|
||||
<div class="prototype-title">
|
||||
🏆 排行榜界面
|
||||
</div>
|
||||
<div class="prototype-desc">
|
||||
多维度排行榜展示,包含个人统计信息
|
||||
</div>
|
||||
<div class="prototype-features">
|
||||
三级榜单 • 数据动画 • 下拉刷新 • 个人卡片
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>移动端原型 v1.0 | 支持PWA离线访问</p>
|
||||
<p>建议在移动设备上体验完整功能</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 防止双指缩放
|
||||
document.addEventListener('touchmove', function(event) {
|
||||
if (event.scale !== 1) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
document.addEventListener('gesturestart', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// 简单的点击反馈
|
||||
document.querySelectorAll('.prototype-item').forEach(item => {
|
||||
item.addEventListener('touchstart', function() {
|
||||
if ('vibrate' in navigator) {
|
||||
navigator.vibrate(10);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,209 +0,0 @@
|
||||
# 深空战机移动端原型整合文档
|
||||
|
||||
## 📱 移动端高可用性原型系列
|
||||
|
||||
本文档描述了为深空战机游戏创建的完整移动端原型系列,包括四个核心界面的高可用性移动端优化版本。
|
||||
|
||||
## 🎯 项目概述
|
||||
|
||||
**项目名称**: 深空战机移动端原型
|
||||
**设计理念**: 高可用性、PWA支持、移动优先
|
||||
**技术栈**: HTML5 + CSS3 + JavaScript (原生)
|
||||
**兼容性**: iOS Safari 12+, Android Chrome 70+
|
||||
|
||||
## 📋 原型清单
|
||||
|
||||
### 1. 主菜单界面 - [`mobile_main_menu_1.html`](mobile_main_menu_1.html)
|
||||
**功能特性**:
|
||||
- 🌟 动态星空背景动画
|
||||
- 🔌 PWA离线支持
|
||||
- 📶 网络状态监控
|
||||
- 🔊 音效切换控制
|
||||
- 📱 安全区域适配(刘海屏)
|
||||
- ⚡ 触觉反馈支持
|
||||
|
||||
**核心导航**:
|
||||
- 开始游戏 → 飞机放置界面
|
||||
- 排行榜 → 排行榜界面
|
||||
- 设置面板(音效/教程)
|
||||
- 关于信息
|
||||
|
||||
### 2. 飞机放置界面 - [`mobile_plane_placement_1.html`](mobile_plane_placement_1.html)
|
||||
**功能特性**:
|
||||
- ✈️ 十字形飞机拖拽放置
|
||||
- 🔄 四方向旋转功能
|
||||
- ✅ 实时碰撞检测
|
||||
- 🎯 智能自动布置
|
||||
- 📏 10x10坐标网格
|
||||
- 🖱️ 触屏优化交互
|
||||
|
||||
**游戏机制**:
|
||||
- 支持放置3架十字形战机
|
||||
- 每架战机包含11个格子(头1+翼4+身4+尾2)
|
||||
- 完整的边界检查和重叠验证
|
||||
- 数据本地存储用于战斗界面调用
|
||||
|
||||
### 3. 战斗界面 - [`mobile_battle_1.html`](mobile_battle_1.html)
|
||||
**功能特性**:
|
||||
- ⚔️ 双棋盘战斗系统
|
||||
- ⏱️ 30秒回合计时器
|
||||
- 🎯 攻击动画效果
|
||||
- 📊 实时战斗统计
|
||||
- 🤖 AI对手模拟
|
||||
- 💥 击中机头摧毁整机
|
||||
|
||||
**移动优化**:
|
||||
- 垂直布局适配小屏幕
|
||||
- 触屏优化的攻击交互
|
||||
- 视觉/触觉双重反馈
|
||||
- 游戏结束弹窗及统计
|
||||
|
||||
### 4. 排行榜界面 - [`mobile_leaderboard_1.html`](mobile_leaderboard_1.html)
|
||||
**功能特性**:
|
||||
- 🏆 三级排行榜(总/周/日)
|
||||
- 👤 个人排名卡片
|
||||
- 🥇 前三名特殊样式
|
||||
- 🔄 下拉刷新数据
|
||||
- 📈 数字跳动动画
|
||||
- 🎖️ 奖杯图标系统
|
||||
|
||||
**数据展示**:
|
||||
- 玩家头像、昵称、等级
|
||||
- 胜率、总局数统计
|
||||
- 排名变化动画效果
|
||||
- 空状态和加载状态
|
||||
|
||||
## 🔧 技术架构特性
|
||||
|
||||
### PWA支持
|
||||
- **离线缓存**: Service Worker自动缓存
|
||||
- **安装支持**: Add to Home Screen
|
||||
- **网络监控**: 实时在线/离线状态
|
||||
- **数据持久**: LocalStorage游戏数据
|
||||
|
||||
### 移动端优化
|
||||
- **响应式设计**: 适配各种屏幕尺寸
|
||||
- **触屏交互**: 44px最小触控区域
|
||||
- **手势支持**: 防双指缩放和误触
|
||||
- **性能优化**: 硬件加速动画
|
||||
|
||||
### 高可用性特性
|
||||
- **安全区域**: 支持刘海屏和圆角屏
|
||||
- **网络容错**: 离线模式和错误处理
|
||||
- **触觉反馈**: 原生震动API支持
|
||||
- **可访问性**: 语义化HTML结构
|
||||
|
||||
### 视觉设计系统
|
||||
- **深空主题**: 星空背景+科技蓝色调
|
||||
- **渐变设计**: 高质量CSS渐变效果
|
||||
- **动画系统**: 流畅的页面转场和交互
|
||||
- **一致性**: 统一的组件和交互模式
|
||||
|
||||
## 🔗 页面导航流程
|
||||
|
||||
```
|
||||
主菜单 (mobile_main_menu_1.html)
|
||||
├── 开始游戏 → 飞机放置 (mobile_plane_placement_1.html)
|
||||
│ └── 开始战斗 → 战斗界面 (mobile_battle_1.html)
|
||||
│ └── 游戏结束 → 返回主菜单 or 再玩一局
|
||||
├── 排行榜 → 排行榜界面 (mobile_leaderboard_1.html)
|
||||
│ └── 返回 → 主菜单
|
||||
├── 设置面板 → 音效/教程控制
|
||||
└── 关于信息 → 游戏说明
|
||||
```
|
||||
|
||||
## 📊 数据流管理
|
||||
|
||||
### LocalStorage数据结构
|
||||
```javascript
|
||||
// 玩家飞机布置数据
|
||||
playerPlanes: [
|
||||
{
|
||||
id: 1,
|
||||
center: [row, col],
|
||||
direction: 'UP|DOWN|LEFT|RIGHT',
|
||||
positions: [[r1,c1], [r2,c2], ...],
|
||||
headPosition: [row, col],
|
||||
isDestroyed: false
|
||||
},
|
||||
// ... 更多飞机
|
||||
]
|
||||
|
||||
// 游戏设置
|
||||
gameSettings: {
|
||||
soundEnabled: true,
|
||||
musicEnabled: true,
|
||||
hapticEnabled: true
|
||||
}
|
||||
|
||||
// 玩家统计
|
||||
playerStats: {
|
||||
totalGames: 156,
|
||||
wins: 106,
|
||||
winRate: 68,
|
||||
level: 15
|
||||
}
|
||||
```
|
||||
|
||||
## 🎮 游戏规则实现
|
||||
|
||||
### 十字形飞机结构
|
||||
- **机头**(1格): 被击中时整机摧毁
|
||||
- **机翼**(4格): 左右各2格,呈十字形
|
||||
- **机身**(4格): 中心轴线垂直分布
|
||||
- **机尾**(2格): 尾部扇形分布
|
||||
|
||||
### 战斗机制
|
||||
- **攻击规则**: 每回合攻击一个格子
|
||||
- **胜利条件**: 率先摧毁对方3架飞机
|
||||
- **摧毁判定**: 击中机头即摧毁整机
|
||||
- **时间限制**: 30秒回合计时
|
||||
|
||||
## 🔍 质量保证检查清单
|
||||
|
||||
### 功能测试
|
||||
- [x] 所有页面JavaScript无语法错误
|
||||
- [x] 页面间导航流程完整
|
||||
- [x] 数据存储和读取正常
|
||||
- [x] 触屏交互响应灵敏
|
||||
- [x] 网络状态监控有效
|
||||
|
||||
### 兼容性测试
|
||||
- [x] iOS Safari刘海屏适配
|
||||
- [x] Android Chrome手势支持
|
||||
- [x] 横竖屏切换适配
|
||||
- [x] 不同分辨率响应式
|
||||
- [x] PWA功能离线可用
|
||||
|
||||
### 性能优化
|
||||
- [x] CSS动画硬件加速
|
||||
- [x] JavaScript代码优化
|
||||
- [x] 图片和资源压缩
|
||||
- [x] 首屏加载时间<3秒
|
||||
- [x] 交互延迟<100ms
|
||||
|
||||
## 🚀 部署建议
|
||||
|
||||
### 生产环境优化
|
||||
1. **资源压缩**: HTML/CSS/JS代码压缩
|
||||
2. **图片优化**: WebP格式+响应式图片
|
||||
3. **CDN加速**: 静态资源CDN部署
|
||||
4. **HTTPS部署**: PWA功能需要HTTPS
|
||||
5. **缓存策略**: 合理的Cache-Control设置
|
||||
|
||||
### 监控指标
|
||||
- **页面加载时间**: 目标<3秒
|
||||
- **首次内容绘制**: 目标<1.5秒
|
||||
- **累计布局偏移**: 目标<0.1
|
||||
- **首次输入延迟**: 目标<100ms
|
||||
- **离线可用率**: 目标>95%
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
本原型系列为深空战机移动端游戏的完整高保真原型,支持:
|
||||
- 完整的游戏流程演示
|
||||
- 移动设备原生体验
|
||||
- PWA离线功能验证
|
||||
- 高可用性特性展示
|
||||
|
||||
所有原型均经过移动端实机测试,确保在主流移动设备上具备良好的用户体验和稳定性。
|
||||
630
准备页面-终稿.html
630
准备页面-终稿.html
@@ -1,630 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>飞机布置 - 新版</title>
|
||||
<meta name="theme-color" content="#0f1419">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #6366f1;
|
||||
--primary-light: #8b5cf6;
|
||||
--accent-color: #f59e0b;
|
||||
--danger-color: #ef4444;
|
||||
--success-color: #10b981;
|
||||
--bg-primary: #0f1419;
|
||||
--bg-secondary: #1a1d29;
|
||||
--bg-tertiary: #252837;
|
||||
--border-primary: #3d4159;
|
||||
--border-secondary: #4a5073;
|
||||
--border-highlight: #6366f1;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #b4b7c9;
|
||||
--text-tertiary: #9ca3af;
|
||||
--text-disabled: #6b7280;
|
||||
--cell-size: min(8.5vw, 38px);
|
||||
--safe-area-padding: 16px;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { height: 100%; overflow: hidden; -webkit-user-select: none; user-select: none; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Microsoft YaHei UI", sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
touch-action: none;
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.app-container { display: flex; flex-direction: column; height: 100%; }
|
||||
|
||||
.top-nav {
|
||||
padding: 12px var(--safe-area-padding);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
background: rgba(15, 20, 25, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: var(--safe-area-padding);
|
||||
}
|
||||
|
||||
.board-wrapper { position: relative; }
|
||||
.game-board {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, var(--cell-size));
|
||||
grid-template-rows: repeat(10, var(--cell-size));
|
||||
gap: 1px;
|
||||
background: var(--border-primary);
|
||||
border: 1px solid var(--border-primary);
|
||||
}
|
||||
.board-cell {
|
||||
background: var(--bg-secondary);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.board-cell:active { background-color: var(--border-highlight); }
|
||||
.board-cell.plane-part { background-color: var(--primary-color); }
|
||||
.board-cell.plane-head { background-color: var(--accent-color); }
|
||||
.board-cell.plane-body { background-color: var(--primary-light); }
|
||||
.board-cell.plane-wing { background-color: var(--primary-light); }
|
||||
.board-cell.plane-tail { background-color: var(--primary-color); }
|
||||
.board-cell.selected { box-shadow: inset 0 0 0 2px var(--success-color); z-index: 1; }
|
||||
|
||||
.col-label, .row-label {
|
||||
position: absolute;
|
||||
font-size: 10px;
|
||||
color: var(--text-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.col-label { top: -20px; height: 15px; width: var(--cell-size); }
|
||||
.row-label { left: -20px; width: 15px; height: var(--cell-size); }
|
||||
|
||||
.controls-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: var(--safe-area-padding);
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
.main-controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.direction-control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.direction-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 44px);
|
||||
grid-template-rows: repeat(3, 44px);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.direction-grid button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.plane-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.confirmation-group {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.plane-btn.selected {
|
||||
background-color: var(--success-color) !important;
|
||||
border-color: var(--success-color) !important;
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.status-display {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
}
|
||||
.btn:disabled {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-disabled);
|
||||
border-color: var(--border-primary);
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.btn:active:not(:disabled) { transform: scale(0.95); }
|
||||
|
||||
.plane-btn.placed { background-color: var(--primary-color); border-color: var(--primary-light); }
|
||||
.plane-btn.selected { background-color: var(--success-color); border-color: var(--success-color); }
|
||||
|
||||
.confirmation-group .btn-primary { background-color: var(--primary-color); }
|
||||
.confirmation-group .btn-danger { background-color: var(--danger-color); }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header class="top-nav">准备页面</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="board-wrapper" id="boardWrapper">
|
||||
<div class="game-board" id="gameBoard"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="controls-area">
|
||||
<div class="status-display" id="statusDisplay">请点击棋盘放置飞机</div>
|
||||
|
||||
<div class="main-controls">
|
||||
<div class="direction-control">
|
||||
<div class="direction-grid">
|
||||
<div></div>
|
||||
<button class="btn" id="moveUpBtn">↑</button>
|
||||
<button class="btn" id="deleteBtn">🗑️</button>
|
||||
|
||||
<button class="btn" id="moveLeftBtn">←</button>
|
||||
<button class="btn" id="rotateBtn">↻</button>
|
||||
<button class="btn" id="moveRightBtn">→</button>
|
||||
|
||||
<div></div>
|
||||
<button class="btn" id="moveDownBtn">↓</button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plane-selection">
|
||||
<button class="btn plane-btn" id="planeBtn1" data-plane-id="1">飞机1</button>
|
||||
<button class="btn plane-btn" id="planeBtn2" data-plane-id="2">飞机2</button>
|
||||
<button class="btn plane-btn" id="planeBtn3" data-plane-id="3">飞机3</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="confirmation-group">
|
||||
<button class="btn" id="randomBtn">🎲 随机</button>
|
||||
<button class="btn btn-danger" id="resetBtn">🔄 重置</button>
|
||||
<button class="btn btn-primary" id="doneBtn">✓ 完成</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class MobilePlacementGame {
|
||||
constructor() {
|
||||
this.BOARD_SIZE = 10;
|
||||
this.PLANE_COUNT = 3;
|
||||
this.boardState = Array(this.BOARD_SIZE).fill(null).map(() => Array(this.BOARD_SIZE).fill(0));
|
||||
this.planes = [];
|
||||
this.selectedPlaneId = null;
|
||||
|
||||
this.dom = {
|
||||
board: document.getElementById('gameBoard'),
|
||||
boardWrapper: document.getElementById('boardWrapper'),
|
||||
statusDisplay: document.getElementById('statusDisplay'),
|
||||
planeBtns: [
|
||||
document.getElementById('planeBtn1'),
|
||||
document.getElementById('planeBtn2'),
|
||||
document.getElementById('planeBtn3'),
|
||||
],
|
||||
moveUpBtn: document.getElementById('moveUpBtn'),
|
||||
moveDownBtn: document.getElementById('moveDownBtn'),
|
||||
moveLeftBtn: document.getElementById('moveLeftBtn'),
|
||||
moveRightBtn: document.getElementById('moveRightBtn'),
|
||||
rotateBtn: document.getElementById('rotateBtn'),
|
||||
deleteBtn: document.getElementById('deleteBtn'),
|
||||
randomBtn: document.getElementById('randomBtn'),
|
||||
resetBtn: document.getElementById('resetBtn'),
|
||||
doneBtn: document.getElementById('doneBtn'),
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createBoard();
|
||||
this.createPlanes();
|
||||
this.bindEvents();
|
||||
this.updateUI();
|
||||
console.log("游戏已初始化");
|
||||
}
|
||||
|
||||
createBoard() {
|
||||
this.dom.board.innerHTML = '';
|
||||
for (let r = 0; r < this.BOARD_SIZE; r++) {
|
||||
for (let c = 0; c < this.BOARD_SIZE; c++) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'board-cell';
|
||||
cell.dataset.row = r;
|
||||
cell.dataset.col = c;
|
||||
this.dom.board.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
const labelsWrapper = document.createElement('div');
|
||||
labelsWrapper.className = 'labels';
|
||||
for (let i = 0; i < this.BOARD_SIZE; i++) {
|
||||
const colLabel = document.createElement('div');
|
||||
colLabel.className = 'col-label';
|
||||
colLabel.textContent = String.fromCharCode(65 + i);
|
||||
colLabel.style.left = `calc(${(i + 0.5)} * var(--cell-size))`;
|
||||
labelsWrapper.appendChild(colLabel);
|
||||
|
||||
const rowLabel = document.createElement('div');
|
||||
rowLabel.className = 'row-label';
|
||||
rowLabel.textContent = i + 1;
|
||||
rowLabel.style.top = `calc(${(i + 0.5)} * var(--cell-size))`;
|
||||
labelsWrapper.appendChild(rowLabel);
|
||||
}
|
||||
this.dom.boardWrapper.appendChild(labelsWrapper);
|
||||
}
|
||||
|
||||
createPlanes() {
|
||||
this.planes = [];
|
||||
for(let i = 1; i <= this.PLANE_COUNT; i++) {
|
||||
this.planes.push({
|
||||
id: i,
|
||||
center: null,
|
||||
direction: 'up',
|
||||
isPlaced: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.dom.board.addEventListener('click', this.handleBoardClick.bind(this));
|
||||
this.dom.planeBtns.forEach(btn => btn.addEventListener('click', this.handlePlaneBtnClick.bind(this)));
|
||||
this.dom.moveUpBtn.addEventListener('click', () => this.moveSelectedPlane(0, -1));
|
||||
this.dom.moveDownBtn.addEventListener('click', () => this.moveSelectedPlane(0, 1));
|
||||
this.dom.moveLeftBtn.addEventListener('click', () => this.moveSelectedPlane(-1, 0));
|
||||
this.dom.moveRightBtn.addEventListener('click', () => this.moveSelectedPlane(1, 0));
|
||||
this.dom.rotateBtn.addEventListener('click', this.rotateSelectedPlane.bind(this));
|
||||
this.dom.deleteBtn.addEventListener('click', this.deleteSelectedPlane.bind(this));
|
||||
this.dom.randomBtn.addEventListener('click', this.randomPlacePlane.bind(this));
|
||||
this.dom.resetBtn.addEventListener('click', this.resetGame.bind(this));
|
||||
this.dom.doneBtn.addEventListener('click', this.completePlacement.bind(this));
|
||||
}
|
||||
|
||||
handleBoardClick(e) {
|
||||
const cell = e.target.closest('.board-cell');
|
||||
if (!cell) return;
|
||||
|
||||
const row = parseInt(cell.dataset.row);
|
||||
const col = parseInt(cell.dataset.col);
|
||||
const cellContent = this.boardState[row][col];
|
||||
|
||||
if (cellContent > 0) { // Clicked on an existing plane
|
||||
this.selectedPlaneId = cellContent;
|
||||
} else { // Clicked on an empty cell
|
||||
const unplacedPlane = this.planes.find(p => !p.isPlaced);
|
||||
if (unplacedPlane) {
|
||||
this.tryPlacePlane(unplacedPlane.id, {x: col, y: row}, 'up');
|
||||
} else {
|
||||
this.updateStatus("所有飞机都已放置。");
|
||||
}
|
||||
}
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
handlePlaneBtnClick(e) {
|
||||
const planeId = parseInt(e.target.dataset.planeId);
|
||||
const plane = this.planes.find(p => p.id === planeId);
|
||||
if (plane && plane.isPlaced) {
|
||||
this.selectedPlaneId = (this.selectedPlaneId === planeId) ? null : planeId;
|
||||
this.updateUI();
|
||||
} else {
|
||||
this.updateStatus(`飞机 ${planeId} 尚未放置。`);
|
||||
}
|
||||
}
|
||||
|
||||
tryPlacePlane(planeId, center, direction) {
|
||||
const plane = this.planes.find(p => p.id === planeId);
|
||||
if (!plane) return;
|
||||
|
||||
const directions = ['up', 'right', 'down', 'left'];
|
||||
const startIndex = directions.indexOf(direction);
|
||||
let placedSuccessfully = false;
|
||||
let usedDirection = direction;
|
||||
|
||||
// 尝试所有方向,从指定方向开始
|
||||
for (let i = 0; i < directions.length; i++) {
|
||||
const currentDirection = directions[(startIndex + i) % 4];
|
||||
const positions = this.getPlanePositions(center, currentDirection);
|
||||
|
||||
if (this.isPlacementValid(positions, planeId)) {
|
||||
// Clear old position if any
|
||||
this.clearPlaneFromBoard(planeId);
|
||||
|
||||
plane.center = center;
|
||||
plane.direction = currentDirection;
|
||||
plane.isPlaced = true;
|
||||
|
||||
positions.forEach(p => { this.boardState[p.y][p.x] = planeId; });
|
||||
this.selectedPlaneId = planeId;
|
||||
placedSuccessfully = true;
|
||||
usedDirection = currentDirection;
|
||||
|
||||
// 如果使用了不同于初始请求的方向,显示特殊提示
|
||||
if (currentDirection !== direction) {
|
||||
this.updateStatus(`飞机 ${planeId} 已放置,已自动旋转为 ${this.getDirectionName(currentDirection)} 方向。`);
|
||||
} else {
|
||||
this.updateStatus(`飞机 ${planeId} 操作成功。`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!placedSuccessfully) {
|
||||
this.updateStatus(`操作无效: 所有方向都存在位置冲突或越界。`);
|
||||
}
|
||||
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
moveSelectedPlane(dx, dy) {
|
||||
if (!this.selectedPlaneId) return;
|
||||
const plane = this.planes.find(p => p.id === this.selectedPlaneId);
|
||||
const newCenter = { x: plane.center.x + dx, y: plane.center.y + dy };
|
||||
this.tryPlacePlane(plane.id, newCenter, plane.direction);
|
||||
}
|
||||
|
||||
rotateSelectedPlane() {
|
||||
if (!this.selectedPlaneId) return;
|
||||
const plane = this.planes.find(p => p.id === this.selectedPlaneId);
|
||||
const directions = ['up', 'right', 'down', 'left'];
|
||||
const currentIndex = directions.indexOf(plane.direction);
|
||||
const nextDirection = directions[(currentIndex + 1) % 4];
|
||||
this.tryPlacePlane(plane.id, plane.center, nextDirection);
|
||||
}
|
||||
|
||||
deleteSelectedPlane() {
|
||||
if (!this.selectedPlaneId) return;
|
||||
const planeId = this.selectedPlaneId;
|
||||
this.clearPlaneFromBoard(planeId);
|
||||
const plane = this.planes.find(p => p.id === planeId);
|
||||
plane.isPlaced = false;
|
||||
plane.center = null;
|
||||
this.selectedPlaneId = null;
|
||||
this.updateStatus(`飞机 ${planeId} 已移除。`);
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
randomPlacePlane() {
|
||||
const unplacedPlane = this.planes.find(p => !p.isPlaced);
|
||||
if (!unplacedPlane) {
|
||||
this.updateStatus("所有飞机都已放置。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试随机位置和方向
|
||||
const directions = ['up', 'right', 'down', 'left'];
|
||||
const maxAttempts = 100; // 防止无限循环
|
||||
let placed = false;
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts && !placed; attempt++) {
|
||||
const randomX = Math.floor(Math.random() * this.BOARD_SIZE);
|
||||
const randomY = Math.floor(Math.random() * this.BOARD_SIZE);
|
||||
const randomDirection = directions[Math.floor(Math.random() * directions.length)];
|
||||
|
||||
const center = { x: randomX, y: randomY };
|
||||
const positions = this.getPlanePositions(center, randomDirection);
|
||||
|
||||
if (this.isPlacementValid(positions, unplacedPlane.id)) {
|
||||
this.clearPlaneFromBoard(unplacedPlane.id);
|
||||
|
||||
unplacedPlane.center = center;
|
||||
unplacedPlane.direction = randomDirection;
|
||||
unplacedPlane.isPlaced = true;
|
||||
|
||||
positions.forEach(p => { this.boardState[p.y][p.x] = unplacedPlane.id; });
|
||||
this.selectedPlaneId = unplacedPlane.id;
|
||||
placed = true;
|
||||
|
||||
this.updateStatus(`飞机 ${unplacedPlane.id} 已随机放置在 ${String.fromCharCode(65 + randomX)}${randomY + 1},方向为${this.getDirectionName(randomDirection)}。`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!placed) {
|
||||
this.updateStatus(`无法为飞机 ${unplacedPlane.id} 找到合适的随机位置。`);
|
||||
}
|
||||
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
resetGame() {
|
||||
this.boardState = Array(this.BOARD_SIZE).fill(null).map(() => Array(this.BOARD_SIZE).fill(0));
|
||||
this.createPlanes();
|
||||
this.selectedPlaneId = null;
|
||||
this.updateStatus("已重置棋盘,请重新放置。");
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
completePlacement() {
|
||||
if(this.planes.every(p => p.isPlaced)) {
|
||||
this.updateStatus("准备就绪,等待对手...");
|
||||
localStorage.setItem('playerPlanes', JSON.stringify(this.planes));
|
||||
// 禁用所有按钮
|
||||
Object.values(this.dom).flat().forEach(el => {
|
||||
if(el.tagName === 'BUTTON') el.disabled = true;
|
||||
});
|
||||
} else {
|
||||
this.updateStatus("请先放置所有飞机。");
|
||||
}
|
||||
}
|
||||
|
||||
clearPlaneFromBoard(planeId) {
|
||||
for (let r = 0; r < this.BOARD_SIZE; r++) {
|
||||
for (let c = 0; c < this.BOARD_SIZE; c++) {
|
||||
if (this.boardState[r][c] === planeId) {
|
||||
this.boardState[r][c] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPlanePositions(center, direction) {
|
||||
const geometry = {
|
||||
up: [
|
||||
{ x: 0, y: -2, type: 'head' },
|
||||
{ x: -2, y: -1, type: 'wing' }, { x: -1, y: -1, type: 'wing' }, { x: 0, y: -1, type: 'wing' }, { x: 1, y: -1, type: 'wing' }, { x: 2, y: -1, type: 'wing' },
|
||||
{ x: 0, y: 0, type: 'body' }, { x: 0, y: 1, type: 'body' },
|
||||
{ x: -1, y: 2, type: 'tail' }, { x: 0, y: 2, type: 'tail' }, { x: 1, y: 2, type: 'tail' }
|
||||
],
|
||||
down: [
|
||||
{ x: 0, y: 2, type: 'head' },
|
||||
{ x: -2, y: 1, type: 'wing' }, { x: -1, y: 1, type: 'wing' }, { x: 0, y: 1, type: 'wing' }, { x: 1, y: 1, type: 'wing' }, { x: 2, y: 1, type: 'wing' },
|
||||
{ x: 0, y: 0, type: 'body' }, { x: 0, y: -1, type: 'body' },
|
||||
{ x: -1, y: -2, type: 'tail' }, { x: 0, y: -2, type: 'tail' }, { x: 1, y: -2, type: 'tail' }
|
||||
],
|
||||
left: [
|
||||
{ x: -2, y: 0, type: 'head' },
|
||||
{ x: -1, y: -2, type: 'wing' }, { x: -1, y: -1, type: 'wing' }, { x: -1, y: 0, type: 'wing' }, { x: -1, y: 1, type: 'wing' }, { x: -1, y: 2, type: 'wing' },
|
||||
{ x: 0, y: 0, type: 'body' }, { x: 1, y: 0, type: 'body' },
|
||||
{ x: 2, y: -1, type: 'tail' }, { x: 2, y: 0, type: 'tail' }, { x: 2, y: 1, type: 'tail' }
|
||||
],
|
||||
right: [
|
||||
{ x: 2, y: 0, type: 'head' },
|
||||
{ x: 1, y: -2, type: 'wing' }, { x: 1, y: -1, type: 'wing' }, { x: 1, y: 0, type: 'wing' }, { x: 1, y: 1, type: 'wing' }, { x: 1, y: 2, type: 'wing' },
|
||||
{ x: 0, y: 0, type: 'body' }, { x: -1, y: 0, type: 'body' },
|
||||
{ x: -2, y: -1, type: 'tail' }, { x: -2, y: 0, type: 'tail' }, { x: -2, y: 1, type: 'tail' }
|
||||
]
|
||||
};
|
||||
|
||||
const offsets = geometry[direction];
|
||||
if (!offsets) return [];
|
||||
|
||||
return offsets.map(o => ({ x: center.x + o.x, y: center.y + o.y, type: o.type }));
|
||||
}
|
||||
|
||||
isPlacementValid(positions, planeId) {
|
||||
for (const pos of positions) {
|
||||
if (pos.x < 0 || pos.x >= this.BOARD_SIZE || pos.y < 0 || pos.y >= this.BOARD_SIZE) return false; // Out of bounds
|
||||
const occupyingId = this.boardState[pos.y][pos.x];
|
||||
if (occupyingId !== 0 && occupyingId !== planeId) return false; // Collision
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getDirectionName(direction) {
|
||||
const directionNames = {
|
||||
'up': '向上',
|
||||
'right': '向右',
|
||||
'down': '向下',
|
||||
'left': '向左'
|
||||
};
|
||||
return directionNames[direction] || direction;
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
// Update board
|
||||
for (let r = 0; r < this.BOARD_SIZE; r++) {
|
||||
for (let c = 0; c < this.BOARD_SIZE; c++) {
|
||||
const cell = this.dom.board.children[r * this.BOARD_SIZE + c];
|
||||
cell.className = 'board-cell';
|
||||
const planeId = this.boardState[r][c];
|
||||
if (planeId > 0) {
|
||||
cell.classList.add('plane-part');
|
||||
const plane = this.planes.find(p => p.id === planeId);
|
||||
|
||||
if (plane && plane.isPlaced && plane.center) {
|
||||
const planePositions = this.getPlanePositions(plane.center, plane.direction);
|
||||
const part = planePositions.find(p => p.x === c && p.y === r);
|
||||
|
||||
if (part && part.type) {
|
||||
cell.classList.add(`plane-${part.type}`); // Adds plane-head, plane-body etc.
|
||||
}
|
||||
if (planeId === this.selectedPlaneId) {
|
||||
cell.classList.add('selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update plane buttons
|
||||
this.dom.planeBtns.forEach(btn => {
|
||||
const planeId = parseInt(btn.dataset.planeId);
|
||||
const plane = this.planes.find(p => p.id === planeId);
|
||||
btn.classList.remove('placed', 'selected');
|
||||
if (plane.isPlaced) btn.classList.add('placed');
|
||||
if (planeId === this.selectedPlaneId) btn.classList.add('selected');
|
||||
});
|
||||
|
||||
// Update manipulation buttons
|
||||
const isPlaneSelected = this.selectedPlaneId !== null;
|
||||
const hasUnplacedPlane = this.planes.some(p => !p.isPlaced);
|
||||
this.dom.moveUpBtn.disabled = !isPlaneSelected;
|
||||
this.dom.moveDownBtn.disabled = !isPlaneSelected;
|
||||
this.dom.moveLeftBtn.disabled = !isPlaneSelected;
|
||||
this.dom.moveRightBtn.disabled = !isPlaneSelected;
|
||||
this.dom.rotateBtn.disabled = !isPlaneSelected;
|
||||
this.dom.deleteBtn.disabled = !isPlaneSelected;
|
||||
this.dom.randomBtn.disabled = !hasUnplacedPlane;
|
||||
|
||||
// Update confirmation buttons
|
||||
this.dom.doneBtn.disabled = !this.planes.every(p => p.isPlaced);
|
||||
}
|
||||
|
||||
updateStatus(message) {
|
||||
this.dom.statusDisplay.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new MobilePlacementGame());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user