消消乐,是近年来最受欢迎的休闲类游戏之一。玩家只需要点击两个或以上相同颜色的方块,就能完成消除并获得分数,伴随着动画和音效,整个过程充满了成就感。玩法简单、上手快,却又富有挑战性,非常适合碎片时间娱乐。
传统开发一个消消乐,需要编写方块随机生成逻辑、消除算法、动画控制、分数统计等功能,甚至要处理各种连锁反应(例如一排消除后,方块下落再形成新的组合)。如果手动实现,既耗时又容易出错。而通过 Trae IDE,这些复杂逻辑都能自动生成,开发过程变得轻松有趣。
💡 我的需求其实很简单
我当时的设想非常清晰:
- 方块随机生成:不同颜色的方块填满整个棋盘。
- 点击消除:点击两个或以上相同颜色的方块就能完成消除。
- 得分机制:每次消除都会增加分数,消除的方块越多,得分越高。
- 流畅动画:点击后方块消失,剩余方块自动下落,填补空缺,动画自然不生硬。
这些功能看似不多,但如果靠手写代码,需要设计棋盘矩阵、实现连锁判断、处理动画逻辑,非常耗时。
✨ Trae 如何理解需求并生成代码?
我在 Trae 里输入了一句话:
"生成消消乐游戏,玩家点击相同颜色的方块消除,得分。"

Trae 立即生成了完整的消消乐代码,包含:
- 棋盘生成逻辑:自动创建一个网格布局,方块颜色随机分配,保证棋盘看起来色彩缤纷。
- 点击检测:识别玩家点击的方块,并判断周围是否存在相同颜色的方块。
- 消除 & 分数系统:找到可消除的方块后,触发动画,同时根据消除数量计算得分。
- 方块下落动画:消除后的空位会自动补齐,上方方块自然掉落,看起来顺畅自然。
- UI & 响应式:棋盘、分数显示区域和按钮都自动排版,不用手动调整。

几秒钟内,一个完整的消消乐框架就完成了,玩法和体验完全到位。
🧩 操作顺手,动画自然
Trae 生成的消消乐,点开来就能直接玩。
- 点击两个或以上相同颜色的方块时,会出现干净利落的消除动画,并伴随方块的下落效果。
- 得分会即时显示在屏幕上,玩家可以清楚看到每一次点击带来的收获。
- 如果棋盘没有可消除的组合,游戏还能提示"重新洗牌",继续保持趣味性。
这种顺畅的体验,不仅让玩家轻松上手,还能让人忍不住一直玩下去。
🛠 想扩展功能?一句话搞定
虽然 Trae 生成的消消乐已经很完整,但想要拓展玩法,也很简单,比如:
- 连击加成:一次性消除越多方块,分数倍率越高。
- 特殊方块:加入"炸弹方块""彩虹方块",触发大面积消除。
- 闯关模式:设定关卡目标,比如"消除指定颜色的方块 50 个"。
- 排行榜系统:记录最高分,玩家之间可以竞争。
只要告诉 Trae,比如输入"加一个炸弹方块,可以一次性炸掉周围 3×3 的方块",Trae 就会自动补齐代码,并且与现有逻辑无缝衔接。
🎮 这是开发消消乐的新方式
以前写消消乐,开发者需要:
- 设计矩阵坐标逻辑
- 手动写下落算法
- 调试无数次动画和分数计算
而现在,用 Trae IDE,只要说一句话,所有功能自动生成,甚至连 UI 和动画都已经安排好。这让游戏开发从"纯体力活"变成了真正的创意工作。
✅ 结语
如果你也想做一款消消乐游戏,真的可以直接试试 Trae IDE。只需要输入一句话:
"生成消消乐游戏,玩家点击相同颜色的方块消除,得分。"
几秒钟,一个完整的消消乐游戏就能跑起来,包含消除逻辑、分数计算、流畅动画,甚至还能随时扩展更多玩法。
从今天起,做游戏不再是长时间的苦力活,而是一个轻松、有趣、创意十足的过程。
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;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
.game-container {
position: relative;
width: 90%;
max-width: 500px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 20px;
overflow: hidden;
}
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.game-title {
font-size: 24px;
font-weight: bold;
color: #4a6fa5;
}
.game-info {
display: flex;
gap: 20px;
}
.score-container, .time-container, .level-container {
background-color: #4a6fa5;
color: white;
padding: 8px 15px;
border-radius: 10px;
font-weight: bold;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.game-board {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
gap: 4px;
margin: 0 auto;
width: 100%;
aspect-ratio: 1 / 1;
background-color: rgba(74, 111, 165, 0.1);
border-radius: 10px;
padding: 10px;
position: relative;
}
.tile {
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: white;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
box-shadow: inset 0 -4px 0 rgba(0, 0, 0, 0.2);
}
.tile:hover {
transform: scale(0.95);
box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.2);
}
.tile.selected {
transform: scale(0.9);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 20px rgba(255, 255, 255, 0.6);
}
.tile.matched {
animation: matched 0.5s forwards;
}
@keyframes matched {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.8;
}
100% {
transform: scale(0);
opacity: 0;
}
}
.game-controls {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
button {
background-color: #4a6fa5;
color: white;
border: none;
padding: 10px 20px;
border-radius: 10px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
button:hover {
background-color: #3a5a80;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal.active {
opacity: 1;
pointer-events: auto;
}
.modal-content {
background-color: white;
padding: 30px;
border-radius: 20px;
text-align: center;
max-width: 90%;
width: 400px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transform: scale(0.8);
transition: transform 0.3s ease;
}
.modal.active .modal-content {
transform: scale(1);
}
.modal h2 {
color: #4a6fa5;
margin-bottom: 15px;
}
.modal p {
margin-bottom: 20px;
color: #555;
}
.combo-indicator {
position: absolute;
font-size: 24px;
font-weight: bold;
color: gold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
pointer-events: none;
animation: comboFade 1.5s forwards;
}
@keyframes comboFade {
0% {
transform: scale(0.5);
opacity: 0;
}
20% {
transform: scale(1.2);
opacity: 1;
}
80% {
transform: translateY(-20px) scale(1);
opacity: 1;
}
100% {
transform: translateY(-40px) scale(0.8);
opacity: 0;
}
}
.progress-bar {
height: 10px;
background-color: #ddd;
border-radius: 5px;
margin-top: 10px;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #4CAF50;
width: 0%;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<div class="game-container">
<div class="game-header">
<div class="game-title">消消乐</div>
<div class="game-info">
<div class="score-container">分数: <span id="score">0</span></div>
<div class="time-container">时间: <span id="time">60</span>s</div>
<div class="level-container">关卡: <span id="level">1</span></div>
</div>
</div>
<div class="progress-bar">
<div class="progress" id="level-progress"></div>
</div>
<div class="game-board" id="game-board"></div>
<div class="game-controls">
<button id="hint-btn">提示</button>
<button id="shuffle-btn">重排</button>
<button id="restart-btn">重新开始</button>
</div>
</div>
<div class="modal" id="game-over-modal">
<div class="modal-content">
<h2>游戏结束</h2>
<p>你的最终得分: <span id="final-score">0</span></p>
<p>达到的关卡: <span id="final-level">1</span></p>
<button id="play-again-btn">再玩一次</button>
</div>
</div>
<div class="modal" id="level-up-modal">
<div class="modal-content">
<h2>恭喜过关!</h2>
<p>你已经进入关卡 <span id="new-level">2</span></p>
<p>奖励时间: +<span id="bonus-time">10</span>秒</p>
<button id="continue-btn">继续</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 游戏配置
const config = {
rows: 8,
cols: 8,
colors: ['#FF5252', '#FF9800', '#FFEB3B', '#4CAF50', '#2196F3', '#9C27B0'],
symbols: ['★', '●', '♦', '♠', '♥', '♣'],
initialTime: 60,
timeBonus: 10,
targetScore: 1000,
hintDuration: 1500,
shufflePenalty: 5
};
// 游戏状态
let gameState = {
board: [],
score: 0,
level: 1,
time: config.initialTime,
isPlaying: false,
selectedTile: null,
timer: null,
combo: 0,
targetScore: config.targetScore
};
// DOM 元素
const gameBoard = document.getElementById('game-board');
const scoreElement = document.getElementById('score');
const timeElement = document.getElementById('time');
const levelElement = document.getElementById('level');
const progressBar = document.getElementById('level-progress');
const hintBtn = document.getElementById('hint-btn');
const shuffleBtn = document.getElementById('shuffle-btn');
const restartBtn = document.getElementById('restart-btn');
const gameOverModal = document.getElementById('game-over-modal');
const levelUpModal = document.getElementById('level-up-modal');
const finalScoreElement = document.getElementById('final-score');
const finalLevelElement = document.getElementById('final-level');
const newLevelElement = document.getElementById('new-level');
const bonusTimeElement = document.getElementById('bonus-time');
const playAgainBtn = document.getElementById('play-again-btn');
const continueBtn = document.getElementById('continue-btn');
// 初始化游戏
function initGame() {
gameState = {
board: [],
score: 0,
level: 1,
time: config.initialTime,
isPlaying: true,
selectedTile: null,
timer: null,
combo: 0,
targetScore: config.targetScore
};
updateUI();
createBoard();
startTimer();
}
// 创建游戏板
function createBoard() {
gameBoard.innerHTML = '';
gameState.board = [];
// 根据关卡调整颜色数量
const colorCount = Math.min(config.colors.length, 3 + Math.floor(gameState.level / 2));
const availableColors = config.colors.slice(0, colorCount);
const availableSymbols = config.symbols.slice(0, colorCount);
// 创建初始板
for (let row = 0; row < config.rows; row++) {
gameState.board[row] = [];
for (let col = 0; col < config.cols; col++) {
const randomIndex = Math.floor(Math.random() * availableColors.length);
const tile = {
color: availableColors[randomIndex],
symbol: availableSymbols[randomIndex],
row,
col,
element: null
};
gameState.board[row][col] = tile;
const tileElement = document.createElement('div');
tileElement.className = 'tile';
tileElement.style.backgroundColor = tile.color;
tileElement.textContent = tile.symbol;
tileElement.dataset.row = row;
tileElement.dataset.col = col;
tileElement.addEventListener('click', () => handleTileClick(row, col));
gameBoard.appendChild(tileElement);
tile.element = tileElement;
}
}
// 确保有可消除的组合
if (!hasMatches() && !hasPotentialMatches()) {
shuffleBoard();
}
}
// 处理点击方块
function handleTileClick(row, col) {
if (!gameState.isPlaying) return;
const tile = gameState.board[row][col];
// 如果已经选中了一个方块
if (gameState.selectedTile) {
// 如果点击的是同一个方块,取消选择
if (gameState.selectedTile === tile) {
gameState.selectedTile.element.classList.remove('selected');
gameState.selectedTile = null;
return;
}
// 如果点击的是相同颜色的方块
if (gameState.selectedTile.color === tile.color) {
// 检查是否相邻或者可以通过直线连接
if (areAdjacent(gameState.selectedTile, tile) || canConnect(gameState.selectedTile, tile)) {
// 消除这两个方块
matchTiles(gameState.selectedTile, tile);
gameState.selectedTile = null;
// 检查是否还有可消除的组合
if (!hasMatches() && !hasPotentialMatches()) {
shuffleBoard();
}
return;
}
}
// 如果不能消除,选择新的方块
gameState.selectedTile.element.classList.remove('selected');
gameState.selectedTile = tile;
tile.element.classList.add('selected');
} else {
// 选择方块
gameState.selectedTile = tile;
tile.element.classList.add('selected');
}
}
// 检查两个方块是否相邻
function areAdjacent(tile1, tile2) {
const rowDiff = Math.abs(tile1.row - tile2.row);
const colDiff = Math.abs(tile1.col - tile2.col);
return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1);
}
// 检查两个方块是否可以通过直线连接(最多两条线)
function canConnect(tile1, tile2) {
// 检查一条直线连接
if (canConnectDirect(tile1, tile2)) {
return true;
}
// 检查两条直线连接(L形)
// 水平然后垂直
const corner1 = { row: tile1.row, col: tile2.col };
if (isEmpty(corner1.row, corner1.col) &&
canConnectDirect(tile1, corner1) &&
canConnectDirect(corner1, tile2)) {
return true;
}
// 垂直然后水平
const corner2 = { row: tile2.row, col: tile1.col };
if (isEmpty(corner2.row, corner2.col) &&
canConnectDirect(tile1, corner2) &&
canConnectDirect(corner2, tile2)) {
return true;
}
return false;
}
// 检查两个方块是否可以通过一条直线连接
function canConnectDirect(tile1, tile2) {
// 如果在同一行
if (tile1.row === tile2.row) {
const minCol = Math.min(tile1.col, tile2.col);
const maxCol = Math.max(tile1.col, tile2.col);
// 检查中间是否有障碍物
for (let col = minCol + 1; col < maxCol; col++) {
if (!isEmpty(tile1.row, col)) {
return false;
}
}
return true;
}
// 如果在同一列
if (tile1.col === tile2.col) {
const minRow = Math.min(tile1.row, tile2.row);
const maxRow = Math.max(tile1.row, tile2.row);
// 检查中间是否有障碍物
for (let row = minRow + 1; row < maxRow; row++) {
if (!isEmpty(row, tile1.col)) {
return false;
}
}
return true;
}
return false;
}
// 检查位置是否为空
function isEmpty(row, col) {
if (row < 0 || row >= config.rows || col < 0 || col >= config.cols) {
return true; // 边界外视为空
}
return !gameState.board[row][col] || gameState.board[row][col].matched;
}
// 消除两个方块
function matchTiles(tile1, tile2) {
// 增加连击计数
gameState.combo++;
// 计算得分(基础分 + 连击奖励)
const baseScore = 10 * gameState.level;
const comboBonus = gameState.combo > 1 ? gameState.combo * 5 : 0;
const scoreGain = baseScore + comboBonus;
// 更新分数
gameState.score += scoreGain;
// 标记为已匹配
tile1.matched = true;
tile2.matched = true;
// 添加动画
tile1.element.classList.add('matched');
tile2.element.classList.add('matched');
// 显示连击指示器
if (gameState.combo > 1) {
showComboIndicator(tile2, gameState.combo, scoreGain);
}
// 延迟后移除方块
setTimeout(() => {
// 移除方块
tile1.element.remove();
tile2.element.remove();
// 更新游戏板数据
gameState.board[tile1.row][tile1.col] = null;
gameState.board[tile2.row][tile2.col] = null;
// 更新UI
updateUI();
// 检查是否过关
checkLevelComplete();
}, 500);
// 重置连击计时器
clearTimeout(gameState.comboTimer);
gameState.comboTimer = setTimeout(() => {
gameState.combo = 0;
}, 3000);
}
// 显示连击指示器
function showComboIndicator(tile, combo, score) {
const indicator = document.createElement('div');
indicator.className = 'combo-indicator';
indicator.textContent = `${combo}连击! +${score}`;
// 设置位置
const rect = tile.element.getBoundingClientRect();
const boardRect = gameBoard.getBoundingClientRect();
indicator.style.left = `${rect.left - boardRect.left + rect.width / 2}px`;
indicator.style.top = `${rect.top - boardRect.top}px`;
gameBoard.appendChild(indicator);
// 动画结束后移除
setTimeout(() => {
indicator.remove();
}, 1500);
}
// 检查是否有可消除的组合
function hasMatches() {
for (let row = 0; row < config.rows; row++) {
for (let col = 0; col < config.cols; col++) {
const tile = gameState.board[row][col];
if (!tile || tile.matched) continue;
// 检查右侧
if (col < config.cols - 1) {
const rightTile = gameState.board[row][col + 1];
if (rightTile && !rightTile.matched && tile.color === rightTile.color) {
return true;
}
}
// 检查下方
if (row < config.rows - 1) {
const bottomTile = gameState.board[row + 1][col];
if (bottomTile && !bottomTile.matched && tile.color === bottomTile.color) {
return true;
}
}
}
}
return false;
}
// 检查是否有潜在的可消除组合
function hasPotentialMatches() {
const tiles = [];
// 收集所有未匹配的方块
for (let row = 0; row < config.rows; row++) {
for (let col = 0; col < config.cols; col++) {
const tile = gameState.board[row][col];
if (tile && !tile.matched) {
tiles.push(tile);
}
}
}
// 检查每对方块是否可以连接
for (let i = 0; i < tiles.length; i++) {
for (let j = i + 1; j < tiles.length; j++) {
if (tiles[i].color === tiles[j].color &&
(areAdjacent(tiles[i], tiles[j]) || canConnect(tiles[i], tiles[j]))) {
return true;
}
}
}
return false;
}
// 重排游戏板
function shuffleBoard() {
// 收集所有未匹配的方块
const tiles = [];
for (let row = 0; row < config.rows; row++) {
for (let col = 0; col < config.cols; col++) {
const tile = gameState.board[row][col];
if (tile && !tile.matched) {
tiles.push(tile);
}
}
}
// 随机打乱
for (let i = tiles.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[tiles[i], tiles[j]] = [tiles[j], tiles[i]];
}
// 重新分配位置
let index = 0;
for (let row = 0; row < config.rows; row++) {
for (let col = 0; col < config.cols; col++) {
if (index < tiles.length) {
const tile = tiles[index];
tile.row = row;
tile.col = col;
tile.element.dataset.row = row;
tile.element.dataset.col = col;
gameState.board[row][col] = tile;
index++;
} else {
gameState.board[row][col] = null;
}
}
}
// 重新排列DOM元素
gameBoard.innerHTML = '';
for (const tile of tiles) {
gameBoard.appendChild(tile.element);
}
// 重排惩罚
gameState.time = Math.max(1, gameState.time - config.shufflePenalty);
updateUI();
// 重置选中状态
if (gameState.selectedTile) {
gameState.selectedTile.element.classList.remove('selected');
gameState.selectedTile = null;
}
}
// 提供提示
function showHint() {
// 收集所有未匹配的方块
const tiles = [];
for (let row = 0; row < config.rows; row++) {
for (let col = 0; col < config.cols; col++) {
const tile = gameState.board[row][col];
if (tile && !tile.matched) {
tiles.push(tile);
}
}
}
// 查找可以连接的一对方块
for (let i = 0; i < tiles.length; i++) {
for (let j = i + 1; j < tiles.length; j++) {
if (tiles[i].color === tiles[j].color &&
(areAdjacent(tiles[i], tiles[j]) || canConnect(tiles[i], tiles[j]))) {
// 高亮显示这对方块
tiles[i].element.classList.add('selected');
tiles[j].element.classList.add('selected');
// 一段时间后取消高亮
setTimeout(() => {
tiles[i].element.classList.remove('selected');
tiles[j].element.classList.remove('selected');
// 如果当前有选中的方块,恢复其高亮
if (gameState.selectedTile) {
gameState.selectedTile.element.classList.add('selected');
}
}, config.hintDuration);
return;
}
}
}
}
// 开始计时器
function startTimer() {
if (gameState.timer) {
clearInterval(gameState.timer);
}
gameState.timer = setInterval(() => {
gameState.time--;
updateUI();
if (gameState.time <= 0) {
endGame();
}
}, 1000);
}
// 更新UI
function updateUI() {
scoreElement.textContent = gameState.score;
timeElement.textContent = gameState.time;
levelElement.textContent = gameState.level;
// 更新进度条
const progress = Math.min(100, (gameState.score / gameState.targetScore) * 100);
progressBar.style.width = `${progress}%`;
}
// 检查是否过关
function checkLevelComplete() {
if (gameState.score >= gameState.targetScore) {
// 停止计时器
clearInterval(gameState.timer);
// 更新关卡信息
gameState.level++;
gameState.targetScore = Math.floor(gameState.targetScore * 1.5);
gameState.time += config.timeBonus;
// 显示过关弹窗
newLevelElement.textContent = gameState.level;
bonusTimeElement.textContent = config.timeBonus;
levelUpModal.classList.add('active');
}
}
// 结束游戏
function endGame() {
gameState.isPlaying = false;
clearInterval(gameState.timer);
// 显示游戏结束弹窗
finalScoreElement.textContent = gameState.score;
finalLevelElement.textContent = gameState.level;
gameOverModal.classList.add('active');
}
// 事件监听器
hintBtn.addEventListener('click', showHint);
shuffleBtn.addEventListener('click', shuffleBoard);
restartBtn.addEventListener('click', initGame);
playAgainBtn.addEventListener('click', () => {
gameOverModal.classList.remove('active');
initGame();
});
continueBtn.addEventListener('click', () => {
levelUpModal.classList.remove('active');
createBoard();
startTimer();
});
// 初始化游戏
initGame();
});
</script>
</body>
</html>