konva实战-摇钱树-翻牌
1、摇钱树
先用千问大模型生成示例代码
https://bailian.console.aliyun.com/cn-beijing/?tab=demohouse#/experience/llm

如何不合适可以手动修改代码

最终代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Konva.js 摇钱树 - 礼物掉落弹窗版</title>
<script src="https://unpkg.com/konva@9.2.0/konva.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #87CEEB;
font-family: 'Microsoft YaHei', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
/* 按钮样式 */
#ui-layer {
position: absolute;
top: 20px;
z-index: 10;
text-align: center;
}
button {
padding: 15px 40px;
font-size: 20px;
background: linear-gradient(to bottom, #ff7675, #d63031);
color: white;
border: 2px solid #fff;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
transition: all 0.2s;
font-weight: bold;
outline: none;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.4);
}
button:active:not(:disabled) {
transform: translateY(1px);
}
button:disabled {
background: #b2bec3;
cursor: not-allowed;
opacity: 0.7;
}
/* 画布容器 */
#container {
margin-top: 60px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.modal-overlay.active {
opacity: 1;
pointer-events: all;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 15px;
text-align: center;
width: 300px;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
transform: scale(0.8);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.modal-overlay.active .modal-content {
transform: scale(1);
}
.gift-icon {
font-size: 60px;
margin-bottom: 10px;
display: block;
}
.gift-title {
font-size: 24px;
color: #2d3436;
margin: 10px 0;
}
.gift-desc {
color: #636e72;
margin-bottom: 20px;
}
.close-btn {
background-color: #0984e3;
padding: 10px 30px;
font-size: 16px;
border-radius: 20px;
border: none;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<div id="ui-layer">
<button id="shakeBtn">🎄 摇动摇钱树</button>
</div>
<div id="container"></div>
<!-- 弹窗结构 -->
<div class="modal-overlay" id="giftModal">
<div class="modal-content">
<span class="gift-icon" id="modalIcon">🎁</span>
<h2 class="gift-title" id="modalTitle">获得礼物</h2>
<p class="gift-desc" id="modalDesc">恭喜获得神秘大奖!</p>
<button class="close-btn" id="closeModalBtn">收下礼物</button>
</div>
</div>
<script>
// --- 1. 初始化 Konva ---
const width = 800;
const height = 600;
const groundY = height - 50;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
// 地面
const ground = new Konva.Rect({
x: 0, y: groundY, width: width, height: 50,
fill: '#55efc4', listening: false
});
layer.add(ground);
// --- 2. 创建树 ---
const treeGroup = new Konva.Group({
x: width / 2,
y: groundY,
offset: { x: 0, y: 0 } // 旋转轴在底部
});
// 树干
const trunk = new Konva.Rect({
x: -30, y: -300, width: 60, height: 300,
fill: '#8B4513', cornerRadius: 5
});
// 树冠
const foliageGroup = new Konva.Group();
for(let i=0; i<50; i++) {
const angle = Math.random() * Math.PI;
const r = Math.random() * 120 + 40;
const fx = Math.cos(angle) * r * (Math.random() > 0.5 ? 1 : -1);
const fy = -Math.sin(angle) * r - 210;
foliageGroup.add(new Konva.Circle({
x: fx, y: fy,
radius: Math.random() * 25 + 15,
fill: i % 2 === 0 ? '#00b894' : '#55efc4',
opacity: 0.9
}));
}
treeGroup.add(trunk);
treeGroup.add(foliageGroup);
layer.add(treeGroup);
// --- 3. 礼物系统 ---
const giftTypes = [
{ icon: '💰', name: '金币袋', desc: '财富滚滚来!' },
{ icon: '💎', name: '大钻石', desc: '闪耀夺目!' },
{ icon: '📱', name: '新手机', desc: '最新款旗舰机!' },
{ icon: '🚗', name: '跑车模型', desc: '速度与激情!' },
{ icon: '🧧', name: '大红包', desc: '恭喜发财!' }
];
let treeGifts = []; // 存储在树上的礼物对象
let currentFallingGift = null; // 当前正在掉落的礼物
let isShaking = false;
let isModalOpen = false;
// 初始化树上的礼物
function initTreeGifts() {
// 清除旧礼物
treeGifts.forEach(g => g.group.destroy());
treeGifts = [];
// 生成 5-8 个新礼物
const count = Math.floor(Math.random() * 4) + 5;
for (let i = 0; i < count; i++) {
createHangingGift();
}
}
function createHangingGift() {
// 随机位置 (树冠范围内)
const angle = Math.random() * Math.PI;
const r = Math.random() * 100 + 60;
const gx = Math.cos(angle) * r * (Math.random() > 0.5 ? 1 : -1);
const gy = -Math.sin(angle) * r - 210;
const giftGroup = new Konva.Group({
x: gx,
y: gy,
rotation: Math.random() * 20 - 10 // 随机轻微旋转
});
// 礼物盒主体
const box = new Konva.Rect({
x: -15, y: -15, width: 30, height: 30,
fill: '#ff7675', stroke: '#d63031', strokeWidth: 2,
cornerRadius: 3
});
// 丝带
const ribbon = new Konva.Rect({ x: -3, y: -15, width: 6, height: 30, fill: '#ffeaa7' });
giftGroup.add(box);
giftGroup.add(ribbon);
// 将礼物添加到树组中
foliageGroup.add(giftGroup);
// 记录数据
treeGifts.push({
group: giftGroup,
type: giftTypes[Math.floor(Math.random() * giftTypes.length)],
isOnTree: true
});
}
// --- 4. 动画循环 (处理掉落物理) ---
function animate() {
if (currentFallingGift) {
const gift = currentFallingGift;
// 重力加速
gift.vy += 0.8;
gift.group.y(gift.group.y() + gift.vy);
// 旋转下落
gift.group.rotation(gift.group.rotation() + 5);
// 落地检测
// 注意:gift.group 现在在 layer 上,y 是绝对坐标
if (gift.group.y() >= groundY - 20) {
gift.group.y(groundY - 20);
onGiftLand(gift);
currentFallingGift = null;
return;
}
}
// layer.batchDraw();
requestAnimationFrame(animate);
}
// --- 5. 交互逻辑 ---
// 礼物落地处理
function onGiftLand(giftObj) {
// 简单的弹跳效果
giftObj.group.to({
scaleY: 0.8,
scaleX: 1.2,
duration: 0.1,
onFinish: () => {
giftObj.group.to({
scaleY: 1,
scaleX: 1,
duration: 0.1
});
// 落地后显示弹窗
showGiftModal(giftObj.type);
}
});
}
// 显示弹窗
function showGiftModal(typeData) {
isModalOpen = true;
document.getElementById('modalIcon').innerText = typeData.icon;
document.getElementById('modalTitle').innerText = typeData.name;
document.getElementById('modalDesc').innerText = typeData.desc;
const modal = document.getElementById('giftModal');
modal.classList.add('active');
}
// 关闭弹窗
document.getElementById('closeModalBtn').addEventListener('click', () => {
const modal = document.getElementById('giftModal');
modal.classList.remove('active');
isModalOpen = false;
// 重置按钮状态
const btn = document.getElementById('shakeBtn');
btn.disabled = false;
btn.innerText = "🎄 继续摇树";
// 可选:落地后过一会让礼物消失,或者保留在地上
// 这里我们让地上的礼物停留 2 秒后消失,保持画面整洁
if(currentFallingGift === null && treeGifts.length > 0) {
// 这里的逻辑稍微复杂,因为 currentFallingGift 已经置空了
// 我们简单处理:不自动消失,或者你可以添加逻辑让它淡出
}
});
// 摇树按钮点击
document.getElementById('shakeBtn').addEventListener('click', () => {
if (treeGifts.length === 0) {
finishShake();
return;
}
if (isShaking || isModalOpen || treeGifts.length === 0) return;
isShaking = true;
const btn = document.getElementById('shakeBtn');
btn.disabled = true;
btn.innerText = "摇晃中...";
// 1. 执行摇晃动画 (使用 Tween 链式调用)
// 左 -> 右 -> 左 -> 右 -> 回正
const shakeSequence = [
{ rot: -20, dur: 0.1 },
{ rot: 20, dur: 0.1 },
{ rot: -20, dur: 0.1 },
{ rot: 20, dur: 0.1 },
{ rot: 0, dur: 0.2 }
];
let currentIndex = 0;
function playNextShake() {
if (currentIndex >= shakeSequence.length) {
animate();
finishShake();
return;
}
const step = shakeSequence[currentIndex];
new Konva.Tween({
node: treeGroup,
rotation: step.rot,
duration: step.dur,
easing: Konva.Easings.EaseInOut,
onFinish: () => {
currentIndex++;
playNextShake();
}
}).play();
}
playNextShake();
});
// 摇晃结束后的逻辑
function finishShake() {
isShaking = false;
// 2. 选择一个礼物掉落
if (treeGifts.length > 0) {
// 随机选一个
const randomIndex = Math.floor(Math.random() * treeGifts.length);
const selectedGiftData = treeGifts[randomIndex];
// 从数组移除
treeGifts.splice(randomIndex, 1);
selectedGiftData.isOnTree = false;
// 关键:将礼物从树组 (treeGroup) 移动到主图层 (layer)
// 这样它就不再跟随树旋转,而是独立掉落
const globalPos = selectedGiftData.group.getAbsolutePosition();
// 从父节点移除
selectedGiftData.group.moveTo(layer);
// 设置绝对位置
selectedGiftData.group.position(globalPos);
// 重置旋转偏移,确保视觉连续
selectedGiftData.group.rotation(selectedGiftData.group.rotation() + treeGroup.rotation());
// 设置物理属性并开始掉落
currentFallingGift = {
group: selectedGiftData.group,
type: selectedGiftData.type,
vy: 2 // 初始向下速度
};
} else {
// 如果没有礼物了
const btn = document.getElementById('shakeBtn');
btn.innerText = "🎄 树上没礼物了 (刷新重置)";
// 这里可以添加逻辑重新长礼物,或者提示刷新
setTimeout(() => {
btn.innerText = "🎄 摇动摇钱树";
btn.disabled = false;
initTreeGifts(); // 重新长满
// alert("树上长满了新礼物!");
}, 1000);
}
}
// 初始化
initTreeGifts();
</script>
</body>
</html>
2、翻牌

最终代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Konva 3D翻牌匹配游戏</title>
<script src="https://unpkg.com/konva@8.4.0/konva.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
color: white;
}
.game-header {
text-align: center;
margin-bottom: 20px;
}
.game-title {
font-size: 2.2rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
.score-container {
background: rgba(255,255,255,0.1);
padding: 12px 25px;
border-radius: 30px;
font-size: 1.3rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
}
#container {
border-radius: 15px;
overflow: hidden;
box-shadow: 0 20px 50px rgba(0,0,0,0.4);
background: rgba(255,255,255,0.05);
}
.instructions {
text-align: center;
margin-top: 20px;
max-width: 800px;
line-height: 1.6;
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 15px;
backdrop-filter: blur(5px);
}
</style>
</head>
<body>
<div class="game-header">
<h1 class="game-title">✨ 3D翻牌匹配游戏 ✨</h1>
<div class="score-container">得分: <span id="score">0</span></div>
</div>
<div id="container"></div>
<div class="instructions">
<p>点击卡牌进行3D翻转!找到相同的卡牌匹配成功时,它们会飞向中心碰撞并获得奖励 🎯</p>
</div>
<script>
// 游戏配置
const CONFIG = {
cardWidth: 90,
cardHeight: 130,
cardSpacing: 25,
columns: 4,
rows: 4,
symbols: ['🌟', '⭐', '💎', '🌈', '🔥', '⚡', '🎯', '🎮']
};
// 计算画布尺寸
const canvasWidth = CONFIG.columns * (CONFIG.cardWidth + CONFIG.cardSpacing) + CONFIG.cardSpacing;
const canvasHeight = CONFIG.rows * (CONFIG.cardHeight + CONFIG.cardSpacing) + CONFIG.cardSpacing;
// 初始化Konva舞台
const stage = new Konva.Stage({
container: 'container',
width: canvasWidth,
height: canvasHeight
});
// 主图层
const mainLayer = new Konva.Layer();
stage.add(mainLayer);
// 动画图层(用于飞行卡牌)
const animationLayer = new Konva.Layer();
stage.add(animationLayer);
// 游戏状态
let cards = [];
let flippedCards = [];
let matchedPairs = 0;
let score = 0;
let canFlip = true;
// 卡牌类
class Card {
constructor(symbol, x, y, index) {
this.symbol = symbol;
this.x = x;
this.y = y;
this.index = index;
this.isFlipped = false;
this.isMatched = false;
this.group = null;
this.createCard();
}
createCard() {
// 创建卡牌组
this.group = new Konva.Group({
x: this.x,
y: this.y,
offsetX: CONFIG.cardWidth / 2,
offsetY: CONFIG.cardHeight / 2,
listening: true
});
// 背面矩形
this.backRect = new Konva.Rect({
width: CONFIG.cardWidth,
height: CONFIG.cardHeight,
fill: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
cornerRadius: 12,
stroke: '#fff',
strokeWidth: 2,
shadowColor: 'rgba(0,0,0,0.3)',
shadowBlur: 10,
shadowOffset: { x: 0, y: 4 }
});
// 背面图标
this.backIcon = new Konva.Text({
text: '❓',
fontSize: 45,
fontFamily: 'Arial',
fill: 'white',
width: CONFIG.cardWidth,
height: CONFIG.cardHeight,
align: 'center',
verticalAlign: 'middle',
fontWeight: 'bold'
});
// 正面矩形
this.frontRect = new Konva.Rect({
width: CONFIG.cardWidth,
height: CONFIG.cardHeight,
fill: 'white',
cornerRadius: 12,
stroke: '#333',
strokeWidth: 2,
shadowColor: 'rgba(0,0,0,0.3)',
shadowBlur: 10,
shadowOffset: { x: 0, y: 4 }
});
// 正面符号
this.frontSymbol = new Konva.Text({
text: this.symbol,
fontSize: 45,
fontFamily: 'Arial',
fill: '#333',
width: CONFIG.cardWidth,
height: CONFIG.cardHeight,
align: 'center',
verticalAlign: 'middle'
});
// 添加到组
this.group.add(this.backRect);
this.group.add(this.backIcon);
this.group.add(this.frontRect);
this.group.add(this.frontSymbol);
// 初始状态:显示背面,隐藏正面
this.frontRect.visible(false);
this.frontSymbol.visible(false);
// 添加点击事件
this.group.on('click tap', () => {
this.flipCard();
});
mainLayer.add(this.group);
}
flipCard() {
// this.showWinMessage();
// return;
if (!canFlip || this.isFlipped || this.isMatched) {
return;
}
// 3D翻转动画
this.isFlipped = true;
console.log("this.group-scaleX", this.group.scaleX());
// 第一阶段:旋转到90度(消失)
this.group.to({
scaleX: 0,
duration: 0.2,
easing: Konva.Easings.EaseInOut,
onFinish: () => {
// 切换显示内容
this.backRect.visible(false);
this.backIcon.visible(false);
this.frontRect.visible(true);
this.frontSymbol.visible(true);
// 第二阶段:从90度旋转到0度(显示正面)
this.group.to({
scaleX: 1,
duration: 0.2,
easing: Konva.Easings.EaseInOut
});
}
});
flippedCards.push(this);
if (flippedCards.length === 2) {
canFlip = false;
setTimeout(() => {
this.checkMatch();
}, 500);
}
}
checkMatch() {
const [card1, card2] = flippedCards;
if (card1.symbol === card2.symbol) {
// 匹配成功 - 执行飞行动画
this.flyToCenter(card1, card2);
} else {
// 匹配失败 - 翻回背面
setTimeout(() => {
card1.flipBack();
card2.flipBack();
flippedCards = [];
canFlip = true;
}, 1000);
}
}
randomNeonColor() {
return ['#00FFE0', '#0066FF', '#CC00FF', '#FF0099', '#FF6600', '#FFFF00'][Math.floor(Math.random() * 6)];
}
flyToCenter(card1, card2) {
const centerX = canvasWidth / 2;
const centerY = canvasHeight / 2;
// 将卡牌移动到动画图层以便独立控制
// card1.group.getLayer().remove(card1.group);
// card2.group.getLayer().remove(card2.group);
animationLayer.add(card1.group);
animationLayer.add(card2.group);
// 飞行动画
card1.group.to({
x: centerX,
y: centerY,
duration: 0.8,
easing: Konva.Easings.EaseOut,
onFinish: () => {
// 碰撞效果
this.createCollisionEffect(centerX, centerY);
this.showReward(centerX, centerY);
// 淡出卡牌
card1.group.to({
opacity: 0,
duration: 0.3
});
card2.group.to({
opacity: 0,
duration: 0.3,
onFinish: () => {
// 更新游戏状态
card1.isMatched = true;
card2.isMatched = true;
matchedPairs++;
score += 10;
document.getElementById('score').textContent = score;
flippedCards = [];
canFlip = true;
// 检查游戏完成
if (matchedPairs === CONFIG.symbols.length) {
setTimeout(() => {
this.showWinMessage();
}, 1000);
}
}
});
}
});
card2.group.to({
x: centerX,
y: centerY,
duration: 0.8,
easing: Konva.Easings.EaseOut
});
}
createCollisionEffect(x, y) {
// 创建碰撞粒子效果
this.flyoffEffect(x, y);
// 粒子爆炸
// this.bombEffect(x, y);
// 波纹扩散
// this.waveEffect(x, y);
// 星光闪烁
// this.twinkleEffect(x, y);
// 中心闪光
this.centerScaleEffect(x, y);
}
flyoffEffect(x, y) {
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const particle = new Konva.Circle({
x: x,
y: y,
radius: 3,
fill: '#FFD700',
opacity: 1
});
animationLayer.add(particle);
// 粒子飞散动画
particle.to({
x: x + Math.cos(angle) * 50,
y: y + Math.sin(angle) * 50,
radius: 0,
opacity: 0,
duration: 0.6,
onFinish: () => {
particle.destroy();
}
});
}
}
bombEffect(x, y) {
const colors = ['#FFD700', '#FFA500', '#FF4500', '#FF1493', '#00BFFF'];
const particles = [];
// 创建粒子
for (let i = 0; i < 150; i++) {
const size = Math.random() * 2;
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 5 + 2;
const particle = new Konva.Circle({
x: x,
y: y,
radius: size,
fill: colors[Math.floor(Math.random() * colors.length)],
opacity: 0.8,
shadowColor: 'white',
shadowBlur: 5
});
animationLayer.add(particle);
particles.push(particle);
// 动画
const tween = new Konva.Tween({
node: particle,
x: x + Math.cos(angle) * speed * 30,
y: y + Math.sin(angle) * speed * 30,
opacity: 0,
duration: 1.2,
easing: Konva.Easings.EaseOut,
onFinish: function() {
particle.destroy();
}
});
// 延迟开始
setTimeout(() => {
tween.play();
}, i * 1.5);
}
}
waveEffect(x, y) {
for (let i = 0; i < 6; i++) {
const ripple = new Konva.Circle({
x: x,
y: y,
radius: 0,
stroke: "white",
strokeWidth: 6 - i * 0.8,
opacity: 0.7 - i * 0.12,
});
animationLayer.add(ripple);
ripple.to({
radius: 140 + i * 35,
opacity: 0,
duration: 0.9 + i * 0.15,
easing: Konva.Easings.EaseOut,
onFinish: () => {
ripple.destroy();
}
});
}
}
twinkleEffect(x, y) {
// 星光闪烁
for (let i = 0; i < 60; i++) {
setTimeout(() => {
const star = new Konva.Star({
x: x + (Math.random() - 0.5) * 400,
y: y + (Math.random() - 0.6) * 400,
numPoints: 5,
innerRadius: 3,
outerRadius: 10,
fill: "#fff100",
opacity: 0.9,
});
animationLayer.add(star);
star.to({ opacity: 0.6, duration: 0.1, onFinish: () => {
star.to({ opacity: 0, duration: 0.7, delay: 0.5, onFinish: () => star.destroy() });
} });
}, i * 2);
}
}
centerScaleEffect(x, y) {
// 中心闪光
// 中心闪光
const flash = new Konva.Circle({
x: x,
y: y,
radius: 10,
fill: 'white',
opacity: 0.8,
shadowColor: '#FFD700',
shadowBlur: 30
});
animationLayer.add(flash);
const flashTween = new Konva.Tween({
node: flash,
radius: 100,
opacity: 0,
duration: 0.8,
easing: Konva.Easings.EaseOut,
onFinish: function() {
flash.destroy();
}
});
flashTween.play();
animationLayer.draw();
}
showReward(x, y) {
// 奖励文本
const rewardText = new Konva.Text({
x: x,
y: y - 60,
text: '+10 分!',
fontSize: 32,
fontFamily: 'Arial',
fill: '#FFD700',
fontWeight: 'bold',
align: 'center',
width: 200,
shadowColor: 'rgba(255,215,0,0.5)',
shadowBlur: 15,
shadowOffset: { x: 0, y: 0 },
opacity: 0
});
animationLayer.add(rewardText);
// 弹跳动画
rewardText.to({
y: y - 80,
opacity: 1,
scale: { x: 1.2, y: 1.2 },
duration: 0.3,
easing: Konva.Easings.ElasticEaseOut,
onFinish: () => {
rewardText.to({
scale: { x: 1, y: 1 },
duration: 0.2
});
// 淡出
setTimeout(() => {
rewardText.to({
opacity: 0,
y: y - 120,
duration: 1,
onFinish: () => {
rewardText.destroy();
}
});
}, 1000);
}
});
}
showWinMessage() {
const winGroup = new Konva.Group({
x: canvasWidth / 2,
y: canvasHeight / 2,
offsetX: 0,
offsetY: 0
});
// 背景遮罩
const overlay = new Konva.Rect({
x: 0,
y: 0,
offsetX: canvasWidth/2,
offsetY: canvasHeight/2,
width: canvasWidth,
height: canvasHeight,
fill: 'rgba(0,0,0,0.8)',
cornerRadius: 12,
listening: false
});
// 胜利文本
const winText = new Konva.Text({
x: -190,
y: -140,
text: '🎉 恭喜通关! 🎉',
fontSize: 36,
fontFamily: 'Arial',
fill: '#FFD700',
align: 'center',
width: 400,
fontWeight: 'bold'
});
// 最终得分
const scoreText = new Konva.Text({
x: -200,
y: -60,
text: `最终得分: ${score}`,
fontSize: 24,
fontFamily: 'Arial',
fill: 'white',
align: 'center',
width: 400
});
// 重新开始按钮
const restartBtn = new Konva.Rect({
x: -80,
y: 0,
width: 160,
height: 50,
fill: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
cornerRadius: 25,
stroke: 'white',
strokeWidth: 2,
});
const restartText = new Konva.Text({
x: -80,
y: 18,
text: '重新开始',
fontSize: 20,
fontFamily: 'Arial',
fill: 'white',
align: 'center',
width: 160,
listening: false
});
winGroup.add(overlay, winText, scoreText, restartBtn, restartText);
animationLayer.add(winGroup);
// restartText.on('click tap', () => {
// winGroup.destroy();
// initGame();
// });
restartBtn.on('click tap', () => {
winGroup.destroy();
initGame();
});
overlay.on('click tap', (event) => {
event.evt.stopPropagation(); // 阻止事件冒泡
});
}
flipBack() {
this.isFlipped = false;
// 翻回背面的3D动画
this.group.to({
scaleX: 0,
duration: 0.2,
easing: Konva.Easings.EaseInOut,
onFinish: () => {
// 切换回背面
this.backRect.visible(true);
this.backIcon.visible(true);
this.frontRect.visible(false);
this.frontSymbol.visible(false);
this.group.to({
scaleX: 1,
duration: 0.2,
easing: Konva.Easings.EaseInOut
});
}
});
}
}
function initGame() {
// 重置游戏状态
cards = [];
flippedCards = [];
matchedPairs = 0;
score = 0;
canFlip = true;
document.getElementById('score').textContent = score;
// 清空所有图层
mainLayer.destroyChildren();
animationLayer.destroyChildren();
// 创建卡牌数据(每种符号两个)
let cardData = [...CONFIG.symbols, ...CONFIG.symbols];
// 随机打乱数组
// for (let i = cardData.length - 1; i > 0; i--) {
// const j = Math.floor(Math.random() * (i + 1));
// [cardData[i], cardData[j]] = [cardData[j], cardData[i]];
// }
// 创建卡牌
let index = 0;
for (let row = 0; row < CONFIG.rows; row++) {
for (let col = 0; col < CONFIG.columns; col++) {
if (index >= cardData.length) break;
const x = CONFIG.cardSpacing + col * (CONFIG.cardWidth + CONFIG.cardSpacing) + CONFIG.cardWidth / 2;
const y = CONFIG.cardSpacing + row * (CONFIG.cardHeight + CONFIG.cardSpacing) + CONFIG.cardHeight / 2;
const card = new Card(cardData[index], x, y, index);
cards.push(card);
index++;
}
}
mainLayer.draw();
animationLayer.draw();
}
// 初始化游戏
initGame();
</script>
</body>
</html>
浙公网安备 33010602011771号