Initial commit: Add basic tic-tac-toe game structure and documentation
This commit is contained in:
57
PROJECT_PLAN.md
Normal file
57
PROJECT_PLAN.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# 井字过三关游戏项目计划
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
开发一个基于Web的井字过三关游戏,提供流畅的游戏体验和直观的用户界面。
|
||||||
|
|
||||||
|
## 项目目标
|
||||||
|
- 实现经典的井字棋游戏逻辑
|
||||||
|
- 提供良好的用户体验
|
||||||
|
- 支持多平台访问
|
||||||
|
- 包含游戏统计功能
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
### 前端技术栈
|
||||||
|
- HTML5: 结构化页面内容
|
||||||
|
- CSS3: 样式设计和动画效果
|
||||||
|
- JavaScript ES6+: 游戏逻辑和交互功能
|
||||||
|
- LocalStorage: 存储游戏统计数据
|
||||||
|
|
||||||
|
### 设计原则
|
||||||
|
- 响应式设计
|
||||||
|
- 用户友好界面
|
||||||
|
- 高性能
|
||||||
|
- 可维护性
|
||||||
|
|
||||||
|
## 开发阶段
|
||||||
|
|
||||||
|
### 阶段一:基础框架搭建
|
||||||
|
- [ ] 创建HTML页面结构
|
||||||
|
- [ ] 设计CSS样式
|
||||||
|
- [ ] 初始化JavaScript文件
|
||||||
|
|
||||||
|
### 阶段二:核心游戏逻辑
|
||||||
|
- [ ] 实现游戏棋盘
|
||||||
|
- [ ] 实现玩家轮流下棋
|
||||||
|
- [ ] 实现胜利判定算法
|
||||||
|
- [ ] 实现平局判定
|
||||||
|
|
||||||
|
### 阶段三:用户界面优化
|
||||||
|
- [ ] 添加游戏状态提示
|
||||||
|
- [ ] 实现游戏重置功能
|
||||||
|
- [ ] 添加动画效果
|
||||||
|
|
||||||
|
### 阶段四:功能增强
|
||||||
|
- [ ] 实现游戏统计功能
|
||||||
|
- [ ] 添加本地存储
|
||||||
|
- [ ] 实现响应式设计
|
||||||
|
|
||||||
|
### 阶段五:测试与优化
|
||||||
|
- [ ] 功能测试
|
||||||
|
- [ ] 兼容性测试
|
||||||
|
- [ ] 性能优化
|
||||||
|
- [ ] 用户体验优化
|
||||||
|
|
||||||
|
## 交付成果
|
||||||
|
- 完整的井字过三关游戏Web应用
|
||||||
|
- 项目文档
|
||||||
|
- 部署指南
|
||||||
37
README.md
37
README.md
@@ -1,3 +1,36 @@
|
|||||||
# tic-tac-toe-game
|
# 网页版井字过三关游戏
|
||||||
|
|
||||||
网页版井字过三关游戏
|
## 项目介绍
|
||||||
|
这是一个基于Web的井字过三关游戏(Tic-Tac-Toe),玩家可以在浏览器中享受经典的井字棋游戏。该游戏支持双人对战模式,具有直观的用户界面和流畅的游戏体验。
|
||||||
|
|
||||||
|
## 游戏玩法
|
||||||
|
1. 游戏在一个3x3的网格上进行
|
||||||
|
2. 两名玩家轮流放置自己的标记(X或O)
|
||||||
|
3. 首先在横、竖或对角线上连成一线的玩家获胜
|
||||||
|
4. 若9个格子填满仍未分出胜负,则为平局
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
- 前端:HTML5, CSS3, JavaScript (ES6+)
|
||||||
|
- 无需后端:纯前端实现
|
||||||
|
- 响应式设计:支持桌面和移动设备
|
||||||
|
- 本地存储:保存游戏统计数据
|
||||||
|
|
||||||
|
## 项目特性
|
||||||
|
- 直观的用户界面
|
||||||
|
- 实时游戏状态显示
|
||||||
|
- 游戏统计跟踪
|
||||||
|
- 响应式设计
|
||||||
|
- 无刷新页面交互
|
||||||
|
|
||||||
|
## 安装与运行
|
||||||
|
1. 克隆项目
|
||||||
|
2. 直接在浏览器中打开index.html
|
||||||
|
3. 开始游戏
|
||||||
|
|
||||||
|
## 项目计划
|
||||||
|
1. 设计游戏界面
|
||||||
|
2. 实现核心游戏逻辑
|
||||||
|
3. 添加游戏统计功能
|
||||||
|
4. 实现响应式设计
|
||||||
|
5. 添加音效和动画效果
|
||||||
|
6. 测试和优化
|
||||||
37
index.html
Normal file
37
index.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>井字过三关游戏</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>井字过三关游戏</h1>
|
||||||
|
<div class="game-info">
|
||||||
|
<div class="current-player">当前玩家: <span id="current-player">X</span></div>
|
||||||
|
<div class="game-status" id="game-status">游戏中</div>
|
||||||
|
</div>
|
||||||
|
<div class="score-board">
|
||||||
|
<div class="player-score">
|
||||||
|
玩家 X: <span id="score-x">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="player-score">
|
||||||
|
玩家 O: <span id="score-o">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="draws">
|
||||||
|
平局: <span id="score-draw">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="game-board" id="game-board">
|
||||||
|
<!-- Game board will be generated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
<div class="game-controls">
|
||||||
|
<button id="reset-btn" class="btn">新游戏</button>
|
||||||
|
<button id="reset-scores-btn" class="btn">重置分数</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
142
script.js
Normal file
142
script.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
class TicTacToeGame {
|
||||||
|
constructor() {
|
||||||
|
this.board = ['', '', '', '', '', '', '', '', ''];
|
||||||
|
this.currentPlayer = 'X';
|
||||||
|
this.gameActive = true;
|
||||||
|
this.scores = {
|
||||||
|
X: parseInt(localStorage.getItem('scoreX') || '0'),
|
||||||
|
O: parseInt(localStorage.getItem('scoreO') || '0'),
|
||||||
|
draw: parseInt(localStorage.getItem('scoreDraw') || '0')
|
||||||
|
};
|
||||||
|
|
||||||
|
this.initializeGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeGame() {
|
||||||
|
this.renderBoard();
|
||||||
|
this.updateScores();
|
||||||
|
this.updateCurrentPlayer();
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
document.getElementById('reset-btn').addEventListener('click', () => this.resetGame());
|
||||||
|
document.getElementById('reset-scores-btn').addEventListener('click', () => this.resetScores());
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBoard() {
|
||||||
|
const gameBoard = document.getElementById('game-board');
|
||||||
|
gameBoard.innerHTML = '';
|
||||||
|
|
||||||
|
this.board.forEach((cell, index) => {
|
||||||
|
const cellElement = document.createElement('div');
|
||||||
|
cellElement.classList.add('cell');
|
||||||
|
cellElement.textContent = cell;
|
||||||
|
|
||||||
|
if (cell === 'X') cellElement.classList.add('x');
|
||||||
|
if (cell === 'O') cellElement.classList.add('o');
|
||||||
|
|
||||||
|
cellElement.addEventListener('click', () => this.makeMove(index));
|
||||||
|
gameBoard.appendChild(cellElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMove(index) {
|
||||||
|
if (this.board[index] !== '' || !this.gameActive) return;
|
||||||
|
|
||||||
|
this.board[index] = this.currentPlayer;
|
||||||
|
this.renderBoard();
|
||||||
|
|
||||||
|
const winResult = this.checkWinner();
|
||||||
|
if (winResult.isWin) {
|
||||||
|
this.handleWin(winResult.winner, winResult.winningCells);
|
||||||
|
} else if (this.isBoardFull()) {
|
||||||
|
this.handleDraw();
|
||||||
|
} else {
|
||||||
|
this.switchPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWinner() {
|
||||||
|
const winPatterns = [
|
||||||
|
[0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
|
||||||
|
[0, 3, 6], [1, 4, 7], [2, 5, 8], // Columns
|
||||||
|
[0, 4, 8], [2, 4, 6] // Diagonals
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of winPatterns) {
|
||||||
|
const [a, b, c] = pattern;
|
||||||
|
if (this.board[a] && this.board[a] === this.board[b] && this.board[a] === this.board[c]) {
|
||||||
|
return { isWin: true, winner: this.board[a], winningCells: pattern };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isWin: false, winner: null, winningCells: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
isBoardFull() {
|
||||||
|
return this.board.every(cell => cell !== '');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWin(winner, winningCells) {
|
||||||
|
this.gameActive = false;
|
||||||
|
this.scores[winner]++;
|
||||||
|
localStorage.setItem(`score${winner}`, this.scores[winner].toString());
|
||||||
|
|
||||||
|
// Highlight winning cells
|
||||||
|
const cells = document.querySelectorAll('.cell');
|
||||||
|
winningCells.forEach(index => {
|
||||||
|
cells[index].classList.add('winning-cell');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('game-status').textContent = `玩家 ${winner} 获胜!`;
|
||||||
|
this.updateScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDraw() {
|
||||||
|
this.gameActive = false;
|
||||||
|
this.scores.draw++;
|
||||||
|
localStorage.setItem('scoreDraw', this.scores.draw.toString());
|
||||||
|
|
||||||
|
document.getElementById('game-status').textContent = '平局!';
|
||||||
|
this.updateScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPlayer() {
|
||||||
|
this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X';
|
||||||
|
this.updateCurrentPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentPlayer() {
|
||||||
|
document.getElementById('current-player').textContent = this.currentPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetGame() {
|
||||||
|
this.board = ['', '', '', '', '', '', '', '', ''];
|
||||||
|
this.currentPlayer = 'X';
|
||||||
|
this.gameActive = true;
|
||||||
|
|
||||||
|
document.getElementById('game-status').textContent = '游戏中';
|
||||||
|
this.renderBoard();
|
||||||
|
this.updateCurrentPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScores() {
|
||||||
|
this.scores = { X: 0, O: 0, draw: 0 };
|
||||||
|
localStorage.setItem('scoreX', '0');
|
||||||
|
localStorage.setItem('scoreO', '0');
|
||||||
|
localStorage.setItem('scoreDraw', '0');
|
||||||
|
|
||||||
|
this.updateScores();
|
||||||
|
this.resetGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScores() {
|
||||||
|
document.getElementById('score-x').textContent = this.scores.X;
|
||||||
|
document.getElementById('score-o').textContent = this.scores.O;
|
||||||
|
document.getElementById('score-draw').textContent = this.scores.draw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the game when the page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new TicTacToeGame();
|
||||||
|
});
|
||||||
155
styles.css
Normal file
155
styles.css
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-player {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-status {
|
||||||
|
color: #764ba2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-board {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-score, .draws {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.x {
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.o {
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.winning-cell {
|
||||||
|
background-color: #d4edda;
|
||||||
|
animation: pulse 0.6s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: #5a6fd8;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user