1134 lines
41 KiB
HTML
1134 lines
41 KiB
HTML
<!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;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
color: white;
|
|
overflow-x: hidden;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 600px;
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 3rem;
|
|
margin-bottom: 10px;
|
|
text-shadow: 0 0 10px #ff00ff, 0 0 20px #ff00ff;
|
|
letter-spacing: 2px;
|
|
background: linear-gradient(to right, #ff00ff, #00ffff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 1.1rem;
|
|
margin-bottom: 15px;
|
|
color: #e0e0ff;
|
|
}
|
|
|
|
.game-info {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin: 15px 0;
|
|
padding: 12px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border-radius: 10px;
|
|
border: 1px solid #ff00ff;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.info-item {
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
text-shadow: 0 0 5px #ff00ff;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
.game-container {
|
|
position: relative;
|
|
margin: 15px auto;
|
|
border: 3px solid #ff00ff;
|
|
border-radius: 10px;
|
|
box-shadow: 0 0 20px #ff00ff, inset 0 0 20px rgba(255, 0, 255, 0.1);
|
|
overflow: visible; /* 改为visible让连击提示可以显示在外面 */
|
|
max-width: 500px;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
padding: 15px;
|
|
}
|
|
|
|
.grid-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(8, 1fr);
|
|
grid-template-rows: repeat(8, 1fr);
|
|
gap: 4px;
|
|
width: 100%;
|
|
aspect-ratio: 1/1;
|
|
background: rgba(0, 0, 0, 0.2);
|
|
border-radius: 6px;
|
|
padding: 8px;
|
|
position: relative;
|
|
}
|
|
|
|
.grid-cell {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.grid-cell:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.gem {
|
|
position: absolute;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
z-index: 10;
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
|
user-select: none;
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.gem.selected {
|
|
transform: scale(0.9);
|
|
box-shadow: 0 0 15px #ffff00, 0 2px 5px rgba(0, 0, 0, 0.3);
|
|
border: 2px solid #ffff00;
|
|
}
|
|
|
|
/* 重新设计颜色,确保有明显区分 */
|
|
.gem.red {
|
|
background: linear-gradient(135deg, #ff416c, #ff4b2b);
|
|
color: white;
|
|
}
|
|
.gem.blue {
|
|
background: linear-gradient(135deg, #2196f3, #21cbf3);
|
|
color: white;
|
|
}
|
|
.gem.green {
|
|
background: linear-gradient(135deg, #00c853, #64dd17);
|
|
color: white;
|
|
}
|
|
.gem.yellow {
|
|
background: linear-gradient(135deg, #ffd600, #ffab00);
|
|
color: #333;
|
|
}
|
|
.gem.purple {
|
|
background: linear-gradient(135deg, #7b1fa2, #ba68c8);
|
|
color: white;
|
|
}
|
|
.gem.pink {
|
|
background: linear-gradient(135deg, #ec407a, #f48fb1);
|
|
color: white;
|
|
}
|
|
.gem.cyan {
|
|
background: linear-gradient(135deg, #00bcd4, #80deea);
|
|
color: #333;
|
|
}
|
|
|
|
.gem.matched {
|
|
animation: matchAnimation 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes matchAnimation {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.2); opacity: 0.7; }
|
|
100% { transform: scale(0); opacity: 0; }
|
|
}
|
|
|
|
.gem.swapping {
|
|
animation: swapAnimation 0.3s ease;
|
|
}
|
|
|
|
@keyframes swapAnimation {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.1); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
margin: 15px 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
button {
|
|
background: linear-gradient(135deg, #ff00ff, #00ffff);
|
|
border: none;
|
|
padding: 10px 20px;
|
|
font-size: 1rem;
|
|
border-radius: 50px;
|
|
cursor: pointer;
|
|
color: #000;
|
|
font-weight: bold;
|
|
box-shadow: 0 0 15px rgba(255, 0, 255, 0.7);
|
|
transition: all 0.3s ease;
|
|
min-width: 120px;
|
|
}
|
|
|
|
button:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 0 20px rgba(255, 0, 255, 0.9);
|
|
}
|
|
|
|
button:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
button:disabled {
|
|
background: #555;
|
|
color: #888;
|
|
box-shadow: none;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.instructions {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border-radius: 10px;
|
|
border: 1px solid #ff00ff;
|
|
max-width: 500px;
|
|
}
|
|
|
|
.instructions h3 {
|
|
color: #ff00ff;
|
|
margin-bottom: 10px;
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.instructions p {
|
|
margin: 8px 0;
|
|
color: #e0e0ff;
|
|
font-size: 0.95rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.game-over {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0, 0, 0, 0.9);
|
|
padding: 25px;
|
|
border-radius: 15px;
|
|
border: 2px solid #ff0000;
|
|
text-align: center;
|
|
box-shadow: 0 0 30px #ff0000;
|
|
z-index: 20;
|
|
display: none;
|
|
width: 85%;
|
|
max-width: 350px;
|
|
}
|
|
|
|
.game-over h2 {
|
|
font-size: 1.8rem;
|
|
color: #ff0000;
|
|
margin-bottom: 12px;
|
|
text-shadow: 0 0 10px #ff0000;
|
|
}
|
|
|
|
.game-over p {
|
|
font-size: 1.1rem;
|
|
margin: 8px 0;
|
|
}
|
|
|
|
.level-complete {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0, 0, 0, 0.9);
|
|
padding: 25px;
|
|
border-radius: 15px;
|
|
border: 2px solid #00ff00;
|
|
text-align: center;
|
|
box-shadow: 0 0 30px #00ff00;
|
|
z-index: 20;
|
|
display: none;
|
|
width: 85%;
|
|
max-width: 350px;
|
|
}
|
|
|
|
.level-complete h2 {
|
|
font-size: 1.8rem;
|
|
color: #00ff00;
|
|
margin-bottom: 12px;
|
|
text-shadow: 0 0 10px #00ff00;
|
|
}
|
|
|
|
.level-complete p {
|
|
font-size: 1.1rem;
|
|
margin: 8px 0;
|
|
}
|
|
|
|
.combo-display {
|
|
position: absolute;
|
|
top: -50px; /* 移到游戏区域上方 */
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
color: #ffff00;
|
|
text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
|
|
display: none;
|
|
animation: comboPulse 0.5s infinite alternate;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
padding: 8px 16px;
|
|
border-radius: 25px;
|
|
border: 2px solid #ffff00;
|
|
z-index: 25;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@keyframes comboPulse {
|
|
from { transform: translateX(-50%) scale(1); }
|
|
to { transform: translateX(-50%) scale(1.1); }
|
|
}
|
|
|
|
.score-popup {
|
|
position: absolute;
|
|
font-size: 1.2rem;
|
|
font-weight: bold;
|
|
color: #ffff00;
|
|
text-shadow: 0 0 3px #000;
|
|
animation: scoreFloat 1s ease-out forwards;
|
|
pointer-events: none;
|
|
z-index: 15;
|
|
}
|
|
|
|
@keyframes scoreFloat {
|
|
0% { transform: translateY(0); opacity: 1; }
|
|
100% { transform: translateY(-30px); opacity: 0; }
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
h1 {
|
|
font-size: 2.2rem;
|
|
}
|
|
|
|
.game-info {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.controls {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
button {
|
|
width: 100%;
|
|
max-width: 220px;
|
|
padding: 8px 15px;
|
|
}
|
|
|
|
.instructions {
|
|
margin-top: 15px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.gem {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.grid-container {
|
|
grid-template-columns: repeat(8, 1fr);
|
|
grid-template-rows: repeat(8, 1fr);
|
|
gap: 3px;
|
|
padding: 6px;
|
|
}
|
|
|
|
.combo-display {
|
|
font-size: 1.3rem;
|
|
top: -45px;
|
|
padding: 6px 12px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 400px) {
|
|
body {
|
|
padding: 15px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.game-container {
|
|
margin: 10px auto;
|
|
padding: 10px;
|
|
}
|
|
|
|
.controls {
|
|
margin: 10px 0;
|
|
}
|
|
|
|
button {
|
|
font-size: 0.9rem;
|
|
padding: 7px 12px;
|
|
}
|
|
|
|
.gem {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.combo-display {
|
|
font-size: 1.1rem;
|
|
top: -40px;
|
|
padding: 5px 10px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>宝石迷阵</h1>
|
|
<p class="subtitle">交换宝石,三个或以上相同宝石连成一线即可消除!</p>
|
|
|
|
<div class="game-info">
|
|
<div class="info-item">分数: <span id="score">0</span></div>
|
|
<div class="info-item">目标: <span id="target">1000</span></div>
|
|
<div class="info-item">步数: <span id="moves">15</span></div>
|
|
<div class="info-item">连击: <span id="combo">0</span></div>
|
|
</div>
|
|
|
|
<div class="game-container">
|
|
<div class="combo-display" id="comboDisplay">连击 x<span id="comboCount">1</span>!</div>
|
|
|
|
<div class="grid-container" id="grid">
|
|
<!-- 网格将由JavaScript生成 -->
|
|
</div>
|
|
|
|
<div class="game-over" id="gameOver">
|
|
<h2>游戏结束</h2>
|
|
<p>你的最终分数: <span id="finalScore">0</span></p>
|
|
<p id="gameMessage">再试一次,创造新纪录!</p>
|
|
<button id="restartBtn">重新开始</button>
|
|
</div>
|
|
|
|
<div class="level-complete" id="levelComplete">
|
|
<h2>关卡完成!</h2>
|
|
<p>恭喜你完成了目标!</p>
|
|
<p>分数: <span id="levelScore">0</span></p>
|
|
<button id="nextLevelBtn">下一关</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="controls">
|
|
<button id="startBtn">开始游戏</button>
|
|
<button id="hintBtn">提示</button>
|
|
<button id="shuffleBtn">重排</button>
|
|
</div>
|
|
|
|
<div class="instructions">
|
|
<h3>游戏说明</h3>
|
|
<p>点击选择一颗宝石,然后点击相邻的宝石进行交换</p>
|
|
<p>三个或更多相同颜色的宝石连成一线即可消除</p>
|
|
<p>连续消除可以产生连击,获得额外分数</p>
|
|
<p>在步数用完前达到目标分数即可过关</p>
|
|
<p>特殊组合可以产生爆炸效果,消除更多宝石!</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const gridContainer = document.getElementById('grid');
|
|
const startBtn = document.getElementById('startBtn');
|
|
const hintBtn = document.getElementById('hintBtn');
|
|
const shuffleBtn = document.getElementById('shuffleBtn');
|
|
const restartBtn = document.getElementById('restartBtn');
|
|
const nextLevelBtn = document.getElementById('nextLevelBtn');
|
|
const scoreEl = document.getElementById('score');
|
|
const targetEl = document.getElementById('target');
|
|
const movesEl = document.getElementById('moves');
|
|
const comboEl = document.getElementById('combo');
|
|
const gameOverEl = document.getElementById('gameOver');
|
|
const levelCompleteEl = document.getElementById('levelComplete');
|
|
const finalScoreEl = document.getElementById('finalScore');
|
|
const levelScoreEl = document.getElementById('levelScore');
|
|
const gameMessageEl = document.getElementById('gameMessage');
|
|
const comboDisplay = document.getElementById('comboDisplay');
|
|
const comboCount = document.getElementById('comboCount');
|
|
|
|
// 游戏配置 - 使用明显区分的颜色
|
|
const GRID_SIZE = 8;
|
|
const GEM_TYPES = ['red', 'blue', 'green', 'yellow', 'purple', 'pink', 'cyan'];
|
|
|
|
// 游戏状态
|
|
let game = {
|
|
grid: [],
|
|
score: 0,
|
|
target: 1000,
|
|
moves: 15,
|
|
level: 1,
|
|
combo: 0,
|
|
selectedGem: null,
|
|
isSwapping: false,
|
|
gameOver: false,
|
|
levelComplete: false
|
|
};
|
|
|
|
// 初始化游戏
|
|
function initGame() {
|
|
// 创建网格
|
|
game.grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(null));
|
|
game.score = 0;
|
|
game.moves = 15;
|
|
game.level = 1;
|
|
game.combo = 0;
|
|
game.selectedGem = null;
|
|
game.isSwapping = false;
|
|
game.gameOver = false;
|
|
game.levelComplete = false;
|
|
game.target = 1000;
|
|
|
|
// 生成网格HTML
|
|
createGridCells();
|
|
|
|
// 填充宝石
|
|
fillGrid();
|
|
|
|
updateUI();
|
|
gameOverEl.style.display = 'none';
|
|
levelCompleteEl.style.display = 'none';
|
|
comboDisplay.style.display = 'none';
|
|
|
|
// 渲染宝石
|
|
renderGems();
|
|
}
|
|
|
|
// 创建网格单元格
|
|
function createGridCells() {
|
|
gridContainer.innerHTML = '';
|
|
const containerWidth = gridContainer.offsetWidth;
|
|
const cellSize = (containerWidth - 24) / GRID_SIZE;
|
|
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
const cell = document.createElement('div');
|
|
cell.className = 'grid-cell';
|
|
cell.dataset.row = i;
|
|
cell.dataset.col = j;
|
|
cell.style.width = `${cellSize}px`;
|
|
cell.style.height = `${cellSize}px`;
|
|
|
|
// 添加点击事件到网格单元格
|
|
cell.addEventListener('click', handleCellClick);
|
|
gridContainer.appendChild(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 填充网格
|
|
function fillGrid() {
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
// 随机选择宝石类型,避免初始就有匹配
|
|
let gemType;
|
|
let attempts = 0;
|
|
do {
|
|
gemType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
|
|
attempts++;
|
|
if (attempts > 20) break; // 防止无限循环
|
|
} while (hasInitialMatch(i, j, gemType));
|
|
|
|
game.grid[i][j] = {
|
|
type: gemType,
|
|
row: i,
|
|
col: j
|
|
};
|
|
}
|
|
}
|
|
|
|
// 检查并修复初始匹配
|
|
let matches = findMatches();
|
|
let attempts = 0;
|
|
while (matches.length > 0 && attempts < 50) {
|
|
// 重新填充有匹配的区域
|
|
matches.forEach(match => {
|
|
if (match.horizontal) {
|
|
for (let k = 0; k < 3; k++) {
|
|
const newType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
|
|
game.grid[match.row][match.col + k].type = newType;
|
|
}
|
|
} else {
|
|
for (let k = 0; k < 3; k++) {
|
|
const newType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
|
|
game.grid[match.row + k][match.col].type = newType;
|
|
}
|
|
}
|
|
});
|
|
matches = findMatches();
|
|
attempts++;
|
|
}
|
|
}
|
|
|
|
// 检查初始匹配
|
|
function hasInitialMatch(row, col, gemType) {
|
|
// 检查水平匹配
|
|
if (col >= 2 &&
|
|
game.grid[row][col-1]?.type === gemType &&
|
|
game.grid[row][col-2]?.type === gemType) {
|
|
return true;
|
|
}
|
|
|
|
// 检查垂直匹配
|
|
if (row >= 2 &&
|
|
game.grid[row-1][col]?.type === gemType &&
|
|
game.grid[row-2][col]?.type === gemType) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// 渲染宝石
|
|
function renderGems() {
|
|
// 移除所有现有的宝石
|
|
const existingGems = document.querySelectorAll('.gem');
|
|
existingGems.forEach(gem => gem.remove());
|
|
|
|
// 获取网格容器的实际尺寸
|
|
const containerWidth = gridContainer.offsetWidth;
|
|
const containerHeight = gridContainer.offsetHeight;
|
|
|
|
// 计算每个格子的实际尺寸
|
|
const cellWidth = (containerWidth - 24) / GRID_SIZE;
|
|
const cellHeight = (containerHeight - 24) / GRID_SIZE;
|
|
|
|
// 添加新的宝石
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
const gem = game.grid[i][j];
|
|
if (gem) {
|
|
const gemElement = document.createElement('div');
|
|
gemElement.className = `gem ${gem.type}`;
|
|
gemElement.dataset.row = i;
|
|
gemElement.dataset.col = j;
|
|
|
|
// 使用像素精确定位
|
|
const left = 4 + j * (cellWidth + 4);
|
|
const top = 4 + i * (cellHeight + 4);
|
|
|
|
gemElement.style.left = `${left}px`;
|
|
gemElement.style.top = `${top}px`;
|
|
gemElement.style.width = `${cellWidth}px`;
|
|
gemElement.style.height = `${cellHeight}px`;
|
|
|
|
// 添加点击事件到宝石
|
|
gemElement.addEventListener('click', handleGemClick);
|
|
|
|
gridContainer.appendChild(gemElement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 处理单元格点击
|
|
function handleCellClick(e) {
|
|
if (game.gameOver || game.levelComplete || game.isSwapping) return;
|
|
|
|
const row = parseInt(e.target.dataset.row);
|
|
const col = parseInt(e.target.dataset.col);
|
|
|
|
// 找到对应的宝石元素
|
|
const gemElement = document.querySelector(`.gem[data-row="${row}"][data-col="${col}"]`);
|
|
if (gemElement) {
|
|
handleGemSelection(row, col, gemElement);
|
|
}
|
|
}
|
|
|
|
// 处理宝石点击
|
|
function handleGemClick(e) {
|
|
if (game.gameOver || game.levelComplete || game.isSwapping) return;
|
|
|
|
const row = parseInt(e.target.dataset.row);
|
|
const col = parseInt(e.target.dataset.col);
|
|
|
|
handleGemSelection(row, col, e.target);
|
|
}
|
|
|
|
// 处理宝石选择
|
|
function handleGemSelection(row, col, gemElement) {
|
|
// 如果没有选中的宝石,选中当前宝石
|
|
if (!game.selectedGem) {
|
|
game.selectedGem = { row, col };
|
|
gemElement.classList.add('selected');
|
|
return;
|
|
}
|
|
|
|
// 如果点击的是已选中的宝石,取消选择
|
|
if (game.selectedGem.row === row && game.selectedGem.col === col) {
|
|
game.selectedGem = null;
|
|
gemElement.classList.remove('selected');
|
|
return;
|
|
}
|
|
|
|
// 检查是否相邻
|
|
const rowDiff = Math.abs(game.selectedGem.row - row);
|
|
const colDiff = Math.abs(game.selectedGem.col - col);
|
|
|
|
if ((rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1)) {
|
|
// 交换宝石
|
|
swapGems(game.selectedGem.row, game.selectedGem.col, row, col);
|
|
} else {
|
|
// 选择新的宝石
|
|
document.querySelector('.gem.selected')?.classList.remove('selected');
|
|
game.selectedGem = { row, col };
|
|
gemElement.classList.add('selected');
|
|
}
|
|
}
|
|
|
|
// 交换宝石
|
|
function swapGems(row1, col1, row2, col2) {
|
|
game.isSwapping = true;
|
|
|
|
// 添加交换动画
|
|
const gem1 = document.querySelector(`.gem[data-row="${row1}"][data-col="${col1}"]`);
|
|
const gem2 = document.querySelector(`.gem[data-row="${row2}"][data-col="${col2}"]`);
|
|
|
|
if (gem1) gem1.classList.add('swapping');
|
|
if (gem2) gem2.classList.add('swapping');
|
|
|
|
// 交换网格中的数据
|
|
const temp = game.grid[row1][col1];
|
|
game.grid[row1][col1] = game.grid[row2][col2];
|
|
game.grid[row2][col2] = temp;
|
|
|
|
// 更新宝石位置
|
|
game.grid[row1][col1].row = row1;
|
|
game.grid[row1][col1].col = col1;
|
|
game.grid[row2][col2].row = row2;
|
|
game.grid[row2][col2].col = col2;
|
|
|
|
// 重新渲染
|
|
setTimeout(() => {
|
|
renderGems();
|
|
|
|
// 检查匹配
|
|
setTimeout(() => {
|
|
const matches = findMatches();
|
|
|
|
if (matches.length > 0) {
|
|
// 有匹配,处理消除
|
|
processMatches(matches);
|
|
} else {
|
|
// 没有匹配,交换回来
|
|
const temp = game.grid[row1][col1];
|
|
game.grid[row1][col1] = game.grid[row2][col2];
|
|
game.grid[row2][col2] = temp;
|
|
|
|
game.grid[row1][col1].row = row1;
|
|
game.grid[row1][col1].col = col1;
|
|
game.grid[row2][col2].row = row2;
|
|
game.grid[row2][col2].col = col2;
|
|
|
|
renderGems();
|
|
game.isSwapping = false;
|
|
}
|
|
|
|
// 清除选择
|
|
document.querySelector('.gem.selected')?.classList.remove('selected');
|
|
game.selectedGem = null;
|
|
}, 100);
|
|
}, 300);
|
|
}
|
|
|
|
// 查找匹配
|
|
function findMatches() {
|
|
const matches = [];
|
|
|
|
// 检查水平匹配
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE - 2; j++) {
|
|
const gem1 = game.grid[i][j];
|
|
const gem2 = game.grid[i][j+1];
|
|
const gem3 = game.grid[i][j+2];
|
|
|
|
if (gem1 && gem2 && gem3 &&
|
|
gem1.type === gem2.type && gem2.type === gem3.type) {
|
|
// 检查是否已经包含在匹配中
|
|
const isNewMatch = !matches.some(match =>
|
|
match.horizontal && match.row === i &&
|
|
(match.col === j || match.col === j-1 || match.col === j-2)
|
|
);
|
|
|
|
if (isNewMatch) {
|
|
matches.push({row: i, col: j, horizontal: true});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查垂直匹配
|
|
for (let i = 0; i < GRID_SIZE - 2; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
const gem1 = game.grid[i][j];
|
|
const gem2 = game.grid[i+1][j];
|
|
const gem3 = game.grid[i+2][j];
|
|
|
|
if (gem1 && gem2 && gem3 &&
|
|
gem1.type === gem2.type && gem2.type === gem3.type) {
|
|
// 检查是否已经包含在匹配中
|
|
const isNewMatch = !matches.some(match =>
|
|
!match.horizontal && match.col === j &&
|
|
(match.row === i || match.row === i-1 || match.row === i-2)
|
|
);
|
|
|
|
if (isNewMatch) {
|
|
matches.push({row: i, col: j, horizontal: false});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
// 处理匹配
|
|
function processMatches(matches) {
|
|
// 计算分数
|
|
const matchCount = matches.length;
|
|
const baseScore = matchCount * 100;
|
|
const comboBonus = game.combo * 50;
|
|
const totalScore = baseScore + comboBonus;
|
|
|
|
game.score += totalScore;
|
|
game.combo++;
|
|
game.moves--;
|
|
|
|
// 显示分数弹出效果
|
|
showScorePopup(totalScore);
|
|
|
|
// 显示连击 - 现在应该能正常显示了
|
|
if (game.combo > 1) {
|
|
comboCount.textContent = game.combo;
|
|
comboDisplay.style.display = 'block';
|
|
setTimeout(() => {
|
|
comboDisplay.style.display = 'none';
|
|
}, 1500); // 延长显示时间
|
|
}
|
|
|
|
// 标记匹配的宝石
|
|
const matchedGems = new Set();
|
|
|
|
matches.forEach(match => {
|
|
if (match.horizontal) {
|
|
for (let k = 0; k < 3; k++) {
|
|
matchedGems.add(`${match.row},${match.col + k}`);
|
|
}
|
|
} else {
|
|
for (let k = 0; k < 3; k++) {
|
|
matchedGems.add(`${match.row + k},${match.col}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 添加匹配动画
|
|
matchedGems.forEach(pos => {
|
|
const [row, col] = pos.split(',').map(Number);
|
|
const gemElement = document.querySelector(`.gem[data-row="${row}"][data-col="${col}"]`);
|
|
if (gemElement) {
|
|
gemElement.classList.add('matched');
|
|
}
|
|
});
|
|
|
|
// 移除匹配的宝石并填充新宝石
|
|
setTimeout(() => {
|
|
// 移除匹配的宝石
|
|
matchedGems.forEach(pos => {
|
|
const [row, col] = pos.split(',').map(Number);
|
|
game.grid[row][col] = null;
|
|
});
|
|
|
|
// 下落宝石
|
|
dropGems();
|
|
|
|
// 填充空缺
|
|
fillEmptySpaces();
|
|
|
|
// 重新渲染
|
|
renderGems();
|
|
|
|
updateUI();
|
|
|
|
// 检查是否还有匹配
|
|
setTimeout(() => {
|
|
const newMatches = findMatches();
|
|
if (newMatches.length > 0) {
|
|
// 继续处理匹配
|
|
processMatches(newMatches);
|
|
} else {
|
|
game.isSwapping = false;
|
|
|
|
// 检查游戏状态
|
|
checkGameState();
|
|
}
|
|
}, 500);
|
|
}, 500);
|
|
}
|
|
|
|
// 显示分数弹出效果
|
|
function showScorePopup(score) {
|
|
const popup = document.createElement('div');
|
|
popup.className = 'score-popup';
|
|
popup.textContent = `+${score}`;
|
|
popup.style.left = '50%';
|
|
popup.style.top = '50%';
|
|
gridContainer.appendChild(popup);
|
|
|
|
setTimeout(() => {
|
|
popup.remove();
|
|
}, 1000);
|
|
}
|
|
|
|
// 宝石下落
|
|
function dropGems() {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
let emptySpaces = 0;
|
|
|
|
for (let i = GRID_SIZE - 1; i >= 0; i--) {
|
|
if (game.grid[i][j] === null) {
|
|
emptySpaces++;
|
|
} else if (emptySpaces > 0) {
|
|
game.grid[i + emptySpaces][j] = game.grid[i][j];
|
|
game.grid[i][j] = null;
|
|
game.grid[i + emptySpaces][j].row = i + emptySpaces;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 填充空缺
|
|
function fillEmptySpaces() {
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
if (game.grid[i][j] === null) {
|
|
const gemType = GEM_TYPES[Math.floor(Math.random() * GEM_TYPES.length)];
|
|
game.grid[i][j] = {
|
|
type: gemType,
|
|
row: i,
|
|
col: j
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查游戏状态
|
|
function checkGameState() {
|
|
if (game.moves <= 0) {
|
|
if (game.score >= game.target) {
|
|
// 关卡完成
|
|
game.levelComplete = true;
|
|
levelScoreEl.textContent = game.score;
|
|
levelCompleteEl.style.display = 'block';
|
|
} else {
|
|
// 游戏结束
|
|
game.gameOver = true;
|
|
finalScoreEl.textContent = game.score;
|
|
gameMessageEl.textContent = "步数用完了!再试一次吧!";
|
|
gameOverEl.style.display = 'block';
|
|
}
|
|
} else if (game.score >= game.target) {
|
|
// 关卡完成
|
|
game.levelComplete = true;
|
|
levelScoreEl.textContent = game.score;
|
|
levelCompleteEl.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// 提示功能
|
|
function showHint() {
|
|
// 找到所有可能的移动
|
|
const possibleMoves = [];
|
|
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
// 检查与右边宝石交换
|
|
if (j < GRID_SIZE - 1) {
|
|
// 交换
|
|
const temp = game.grid[i][j];
|
|
game.grid[i][j] = game.grid[i][j+1];
|
|
game.grid[i][j+1] = temp;
|
|
|
|
// 检查是否有匹配
|
|
const matches = findMatches();
|
|
|
|
// 交换回来
|
|
game.grid[i][j+1] = game.grid[i][j];
|
|
game.grid[i][j] = temp;
|
|
|
|
if (matches.length > 0) {
|
|
possibleMoves.push({row: i, col: j, direction: 'right'});
|
|
}
|
|
}
|
|
|
|
// 检查与下面宝石交换
|
|
if (i < GRID_SIZE - 1) {
|
|
// 交换
|
|
const temp = game.grid[i][j];
|
|
game.grid[i][j] = game.grid[i+1][j];
|
|
game.grid[i+1][j] = temp;
|
|
|
|
// 检查是否有匹配
|
|
const matches = findMatches();
|
|
|
|
// 交换回来
|
|
game.grid[i+1][j] = game.grid[i][j];
|
|
game.grid[i][j] = temp;
|
|
|
|
if (matches.length > 0) {
|
|
possibleMoves.push({row: i, col: j, direction: 'down'});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (possibleMoves.length > 0) {
|
|
// 随机选择一个可能的移动
|
|
const move = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
|
|
const gem1 = document.querySelector(`.gem[data-row="${move.row}"][data-col="${move.col}"]`);
|
|
let gem2;
|
|
|
|
if (move.direction === 'right') {
|
|
gem2 = document.querySelector(`.gem[data-row="${move.row}"][data-col="${move.col + 1}"]`);
|
|
} else {
|
|
gem2 = document.querySelector(`.gem[data-row="${move.row + 1}"][data-col="${move.col}"]`);
|
|
}
|
|
|
|
if (gem1 && gem2) {
|
|
gem1.style.boxShadow = '0 0 15px #ffff00';
|
|
gem2.style.boxShadow = '0 0 15px #ffff00';
|
|
|
|
setTimeout(() => {
|
|
gem1.style.boxShadow = '';
|
|
gem2.style.boxShadow = '';
|
|
}, 2000);
|
|
}
|
|
} else {
|
|
alert('没有找到可匹配的移动!尝试重排宝石吧!');
|
|
}
|
|
}
|
|
|
|
// 重排宝石
|
|
function shuffleGems() {
|
|
if (game.moves <= 0) return;
|
|
|
|
// 收集所有宝石
|
|
const allGems = [];
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
allGems.push(game.grid[i][j]);
|
|
}
|
|
}
|
|
|
|
// 随机重排
|
|
for (let i = allGems.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[allGems[i], allGems[j]] = [allGems[j], allGems[i]];
|
|
}
|
|
|
|
// 重新分配位置
|
|
let index = 0;
|
|
for (let i = 0; i < GRID_SIZE; i++) {
|
|
for (let j = 0; j < GRID_SIZE; j++) {
|
|
game.grid[i][j] = allGems[index];
|
|
game.grid[i][j].row = i;
|
|
game.grid[i][j].col = j;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// 重新渲染
|
|
renderGems();
|
|
|
|
// 消耗一步
|
|
game.moves--;
|
|
updateUI();
|
|
|
|
// 检查游戏状态
|
|
checkGameState();
|
|
}
|
|
|
|
// 下一关
|
|
function nextLevel() {
|
|
game.level++;
|
|
game.score = 0;
|
|
game.moves = 15 + game.level * 2;
|
|
game.target = 1000 + game.level * 500;
|
|
game.combo = 0;
|
|
|
|
// 重新填充网格
|
|
fillGrid();
|
|
renderGems();
|
|
updateUI();
|
|
|
|
levelCompleteEl.style.display = 'none';
|
|
}
|
|
|
|
// 更新UI
|
|
function updateUI() {
|
|
scoreEl.textContent = game.score;
|
|
targetEl.textContent = game.target;
|
|
movesEl.textContent = game.moves;
|
|
comboEl.textContent = game.combo;
|
|
}
|
|
|
|
// 事件监听
|
|
startBtn.addEventListener('click', initGame);
|
|
hintBtn.addEventListener('click', showHint);
|
|
shuffleBtn.addEventListener('click', shuffleGems);
|
|
restartBtn.addEventListener('click', initGame);
|
|
nextLevelBtn.addEventListener('click', nextLevel);
|
|
|
|
// 窗口大小改变时重新渲染
|
|
window.addEventListener('resize', () => {
|
|
if (game.grid && game.grid.length > 0) {
|
|
createGridCells();
|
|
renderGems();
|
|
}
|
|
});
|
|
|
|
// 初始化游戏
|
|
initGame();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |