<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>井字棋游戏</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
color: white;
overflow: hidden;
}
.container {
text-align: center;
}
h1 {
font-size: 2.8em;
margin-bottom: 10px;
text-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
letter-spacing: 4px;
}
.status {
font-size: 1.4em;
margin-bottom: 25px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.status .player-icon {
display: inline-block;
font-weight: bold;
font-size: 1.2em;
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 6px;
text-align: center;
}
.player-x {
color: #ff6b9d;
text-shadow: 0 0 10px rgba(255, 107, 157, 0.5);
}
.player-o {
color: #4ecdc4;
text-shadow: 0 0 10px rgba(78, 205, 196, 0.5);
}
.board {
display: grid;
grid-template-columns: repeat(3, 120px);
grid-template-rows: repeat(3, 120px);
gap: 8px;
margin: 0 auto 30px;
perspective: 800px;
}
.cell {
width: 120px;
height: 120px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
position: relative;
overflow: hidden;
}
.cell::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.1), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.cell:hover:not(.taken) {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
box-shadow: 0 0 25px rgba(255, 255, 255, 0.1);
}
.cell:hover:not(.taken)::before {
opacity: 1;
}
.cell.taken {
cursor: default;
}
.cell.x-cell {
color: #ff6b9d;
text-shadow: 0 0 15px rgba(255, 107, 157, 0.6);
animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.cell.o-cell {
color: #4ecdc4;
text-shadow: 0 0 15px rgba(78, 205, 196, 0.6);
animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.cell.win-cell {
animation: winPulse 0.6s ease-in-out infinite alternate;
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
}
@keyframes popIn {
0% {
transform: scale(0) rotate(-180deg);
opacity: 0;
}
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
@keyframes winPulse {
0% {
transform: scale(1);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.2);
}
100% {
transform: scale(1.08);
box-shadow: 0 0 30px rgba(255, 255, 255, 0.4);
}
}
.score-board {
display: flex;
justify-content: center;
gap: 40px;
margin-bottom: 25px;
}
.score-item {
text-align: center;
}
.score-label {
font-size: 0.9em;
opacity: 0.7;
margin-bottom: 4px;
}
.score-value {
font-size: 2em;
font-weight: bold;
}
.score-x .score-value {
color: #ff6b9d;
}
.score-o .score-value {
color: #4ecdc4;
}
.score-draw .score-value {
color: #a0a0a0;
}
.btn-restart {
padding: 14px 40px;
font-size: 1.1em;
font-weight: 600;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50px;
background: rgba(255, 255, 255, 0.08);
color: white;
cursor: pointer;
transition: all 0.3s ease;
letter-spacing: 2px;
backdrop-filter: blur(10px);
}
.btn-restart:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.6);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.btn-restart:active {
transform: translateY(0);
}
.winner-overlay {
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5px);
opacity: 0;
pointer-events: none;
transition: opacity 0.4s ease;
z-index: 100;
}
.winner-overlay.show {
opacity: 1;
pointer-events: all;
}
.winner-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 24px;
padding: 50px 60px;
text-align: center;
transform: scale(0.8);
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
backdrop-filter: blur(20px);
}
.winner-overlay.show .winner-card {
transform: scale(1);
}
.winner-emoji {
font-size: 4em;
margin-bottom: 15px;
}
.winner-text {
font-size: 2em;
font-weight: bold;
margin-bottom: 8px;
}
.winner-sub {
font-size: 1.1em;
opacity: 0.7;
margin-bottom: 30px;
}
.btn-play-again {
padding: 12px 36px;
font-size: 1em;
font-weight: 600;
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 50px;
background: rgba(255, 255, 255, 0.15);
color: white;
cursor: pointer;
transition: all 0.3s ease;
letter-spacing: 1px;
}
.btn-play-again:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
}
@media (max-width: 480px) {
.board {
grid-template-columns: repeat(3, 90px);
grid-template-rows: repeat(3, 90px);
gap: 6px;
}
.cell {
width: 90px;
height: 90px;
font-size: 2.2em;
border-radius: 12px;
}
h1 {
font-size: 2em;
}
.winner-card {
padding: 35px 40px;
margin: 0 20px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>✦ 井字棋 ✦</h1>
<div class="score-board">
<div class="score-item score-x">
<div class="score-label">玩家 X</div>
<div class="score-value" id="scoreX">0</div>
</div>
<div class="score-item score-draw">
<div class="score-label">平局</div>
<div class="score-value" id="scoreDraw">0</div>
</div>
<div class="score-item score-o">
<div class="score-label">玩家 O</div>
<div class="score-value" id="scoreO">0</div>
</div>
</div>
<div class="status" id="status">
轮到 <span class="player-icon player-x">X</span> 下棋
</div>
<div class="board" id="board"></div>
<button class="btn-restart" id="btnRestart">重新开始</button>
</div>
<div class="winner-overlay" id="winnerOverlay">
<div class="winner-card">
<div class="winner-emoji" id="winnerEmoji">🎉</div>
<div class="winner-text" id="winnerText">玩家 X 获胜!</div>
<div class="winner-sub" id="winnerSub">太棒了!再来一局?</div>
<button class="btn-play-again" id="btnPlayAgain">再来一局</button>
</div>
</div>
<script>
const board = document.getElementById('board');
const statusEl = document.getElementById('status');
const btnRestart = document.getElementById('btnRestart');
const winnerOverlay = document.getElementById('winnerOverlay');
const winnerEmoji = document.getElementById('winnerEmoji');
const winnerText = document.getElementById('winnerText');
const winnerSub = document.getElementById('winnerSub');
const btnPlayAgain = document.getElementById('btnPlayAgain');
const scoreXEl = document.getElementById('scoreX');
const scoreOEl = document.getElementById('scoreO');
const scoreDrawEl = document.getElementById('scoreDraw');
let cells = Array(9).fill('');
let currentPlayer = 'X';
let gameActive = true;
let scores = { X: 0, O: 0, draw: 0 };
const winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
function createBoard() {
board.innerHTML = '';
for (let i = 0; i < 9; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.index = i;
cell.addEventListener('click', () => handleCellClick(i));
board.appendChild(cell);
}
}
function handleCellClick(index) {
if (!gameActive || cells[index] !== '') return;
cells[index] = currentPlayer;
const cell = board.children[index];
cell.textContent = currentPlayer;
cell.classList.add('taken', currentPlayer === 'X' ? 'x-cell' : 'o-cell');
const winResult = checkWin();
if (winResult) {
gameActive = false;
highlightWin(winResult);
scores[currentPlayer]++;
updateScores();
setTimeout(() => showWinner(currentPlayer), 600);
return;
}
if (cells.every(c => c !== '')) {
gameActive = false;
scores.draw++;
updateScores();
setTimeout(() => showWinner('draw'), 400);
return;
}
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
updateStatus();
}
function checkWin() {
for (const pattern of winPatterns) {
const [a, b, c] = pattern;
if (cells[a] && cells[a] === cells[b] && cells[a] === cells[c]) {
return pattern;
}
}
return null;
}
function highlightWin(pattern) {
pattern.forEach(i => {
board.children[i].classList.add('win-cell');
});
}
function updateStatus() {
const playerClass = currentPlayer === 'X' ? 'player-x' : 'player-o';
statusEl.innerHTML = `轮到 <span class="player-icon ${playerClass}">${currentPlayer}</span> 下棋`;
}
function updateScores() {
scoreXEl.textContent = scores.X;
scoreOEl.textContent = scores.O;
scoreDrawEl.textContent = scores.draw;
}
function showWinner(result) {
if (result === 'draw') {
winnerEmoji.textContent = '🤝';
winnerText.textContent = '平局!';
winnerText.style.color = '#a0a0a0';
winnerSub.textContent = '势均力敌,再来一局?';
} else {
winnerEmoji.textContent = '🏆';
winnerText.textContent = `玩家 ${result} 获胜!`;
winnerText.style.color = result === 'X' ? '#ff6b9d' : '#4ecdc4';
winnerSub.textContent = '太厉害了!再来一局?';
}
winnerOverlay.classList.add('show');
}
function resetGame() {
cells = Array(9).fill('');
currentPlayer = 'X';
gameActive = true;
winnerOverlay.classList.remove('show');
updateStatus();
createBoard();
}
btnRestart.addEventListener('click', resetGame);
btnPlayAgain.addEventListener('click', resetGame);
createBoard();
</script>
</body>
</html>
index.html
index.html