good version
This commit is contained in:
parent
687a83922f
commit
11c01fb1ea
|
|
@ -0,0 +1,617 @@
|
|||
<template>
|
||||
<div class="game-overlay" @click.self="handleClose">
|
||||
<div class="game-container">
|
||||
<h2 class="game-title">猜拳遊戲</h2>
|
||||
|
||||
<div v-if="!gameStarted" class="game-intro">
|
||||
<p>3回合制</p>
|
||||
</div>
|
||||
|
||||
<div v-if="gameStarted" class="game-status">
|
||||
<div class="score">
|
||||
<span>你: {{ playerScore }}</span>
|
||||
<span>第{{ currentRound }}/3局</span>
|
||||
<span>電腦: {{ cpuScore }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="result" class="result-display" ref="resultDisplayRef">
|
||||
<div class="hands" :class="{ 'shake': waiting }">
|
||||
<div class="hand player-hand" :class="{ 'slide-in-left': !waiting }">
|
||||
<div class="hand-icon" :class="`icon-${playerChoice}`"></div>
|
||||
</div>
|
||||
<div class="vs">VS</div>
|
||||
<div class="hand cpu-hand" :class="{ 'slide-in-right': !waiting }">
|
||||
<div class="hand-icon" :class="`icon-${cpuChoice}`"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-text" :class="[result, { 'bounce-in': !waiting }]">{{ resultText }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!gameOver" class="choices">
|
||||
<button
|
||||
v-for="choice in choices"
|
||||
:key="choice.id"
|
||||
class="choice-btn"
|
||||
@click="play(choice.id)"
|
||||
:disabled="waiting"
|
||||
>
|
||||
<div class="choice-icon" :class="`icon-${choice.id}`"></div>
|
||||
<span>{{ choice.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="gameOver" class="game-over">
|
||||
<div class="final-result" :class="finalResult">
|
||||
<div class="result-icon" :class="`icon-${finalResult}`"></div>
|
||||
<h3>{{ finalResultText }}</h3>
|
||||
</div>
|
||||
<button class="action-btn" @click="playAgain">再玩一次</button>
|
||||
</div>
|
||||
|
||||
<button class="close-btn" @click="handleClose">
|
||||
{{ gameOver ? '完成' : '取消' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const emit = defineEmits(['close', 'complete']);
|
||||
|
||||
const choices = [
|
||||
{ id: 'rock', name: '石頭' },
|
||||
{ id: 'paper', name: '布' },
|
||||
{ id: 'scissors', name: '剪刀' }
|
||||
];
|
||||
|
||||
const gameStarted = ref(false);
|
||||
const gameOver = ref(false);
|
||||
const currentRound = ref(1);
|
||||
const playerScore = ref(0);
|
||||
const cpuScore = ref(0);
|
||||
const playerChoice = ref('');
|
||||
const cpuChoice = ref('');
|
||||
const result = ref('');
|
||||
const waiting = ref(false);
|
||||
|
||||
const resultText = computed(() => {
|
||||
if (result.value === 'win') return '你贏了!';
|
||||
if (result.value === 'lose') return '你輸了!';
|
||||
return '平手!';
|
||||
});
|
||||
|
||||
const finalResult = computed(() => {
|
||||
if (playerScore.value > cpuScore.value) return 'win';
|
||||
if (playerScore.value < cpuScore.value) return 'lose';
|
||||
return 'draw';
|
||||
});
|
||||
|
||||
const finalResultText = computed(() => {
|
||||
if (finalResult.value === 'win') return '勝利!';
|
||||
if (finalResult.value === 'lose') return '失敗...';
|
||||
return '平手';
|
||||
});
|
||||
|
||||
function play(choice) {
|
||||
if (waiting.value || gameOver.value) return;
|
||||
|
||||
gameStarted.value = true;
|
||||
waiting.value = true;
|
||||
playerChoice.value = choice;
|
||||
|
||||
// CPU makes a random choice
|
||||
const cpuIndex = Math.floor(Math.random() * 3);
|
||||
cpuChoice.value = choices[cpuIndex].id;
|
||||
|
||||
// Determine winner
|
||||
setTimeout(() => {
|
||||
const outcome = determineWinner(choice, cpuChoice.value);
|
||||
result.value = outcome;
|
||||
|
||||
if (outcome === 'win') {
|
||||
playerScore.value++;
|
||||
} else if (outcome === 'lose') {
|
||||
cpuScore.value++;
|
||||
}
|
||||
|
||||
// Check if game is over
|
||||
setTimeout(() => {
|
||||
if (currentRound.value >= 3) {
|
||||
gameOver.value = true;
|
||||
} else {
|
||||
currentRound.value++;
|
||||
result.value = '';
|
||||
waiting.value = false;
|
||||
}
|
||||
}, 1500);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function determineWinner(player, cpu) {
|
||||
if (player === cpu) return 'draw';
|
||||
if (
|
||||
(player === 'rock' && cpu === 'scissors') ||
|
||||
(player === 'paper' && cpu === 'rock') ||
|
||||
(player === 'scissors' && cpu === 'paper')
|
||||
) {
|
||||
return 'win';
|
||||
}
|
||||
return 'lose';
|
||||
}
|
||||
|
||||
function playAgain() {
|
||||
gameStarted.value = false;
|
||||
gameOver.value = false;
|
||||
currentRound.value = 1;
|
||||
playerScore.value = 0;
|
||||
cpuScore.value = 0;
|
||||
result.value = '';
|
||||
waiting.value = false;
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
if (gameOver.value && finalResult.value === 'win') {
|
||||
emit('complete', true); // Won the game
|
||||
} else {
|
||||
emit('complete', false); // Didn't win
|
||||
}
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.game-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 150;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
background: #f5f5dc;
|
||||
border: 4px solid #8b4513;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
width: 90%;
|
||||
max-width: 280px;
|
||||
max-height: 90vh;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
animation: slide-up 0.3s ease-out;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.game-title {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
margin: 0 0 6px 0;
|
||||
color: #8b4513;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.game-intro {
|
||||
text-align: center;
|
||||
margin-bottom: 6px;
|
||||
color: #8b4513;
|
||||
font-size: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.game-status {
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.score {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: #8b4513;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.result-display {
|
||||
margin: 6px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hands {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.hands.shake {
|
||||
animation: shake-hands 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes shake-hands {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px) rotate(-5deg); }
|
||||
75% { transform: translateX(5px) rotate(5deg); }
|
||||
}
|
||||
|
||||
.hand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.hand.slide-in-left {
|
||||
animation: slide-in-left 0.5s ease-out;
|
||||
}
|
||||
|
||||
.hand.slide-in-right {
|
||||
animation: slide-in-right 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slide-in-left {
|
||||
from {
|
||||
transform: translateX(-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-in-right {
|
||||
from {
|
||||
transform: translateX(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.hand span {
|
||||
font-size: 10px;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.hand-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vs {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result-text.bounce-in {
|
||||
animation: bounce-in 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
@keyframes bounce-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.result-text.win {
|
||||
background: #90EE90;
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.result-text.lose {
|
||||
background: #FFB6C1;
|
||||
color: #8b0000;
|
||||
}
|
||||
|
||||
.result-text.draw {
|
||||
background: #FFE4B5;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.choices {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-bottom: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.choice-btn {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border: 3px solid #8b4513;
|
||||
border-radius: 5px;
|
||||
padding: 6px 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
}
|
||||
|
||||
.choice-btn:hover:not(:disabled) {
|
||||
background: #fffacd;
|
||||
transform: scale(1.05);
|
||||
animation: pulse 0.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1.05); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.choice-btn:active:not(:disabled) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.choice-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.choice-btn span {
|
||||
font-size: 9px;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.choice-icon, .hand-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Pixel Art Icons */
|
||||
.icon-rock::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
-2px -2px 0 #888, -1px -2px 0 #888, 0 -2px 0 #888, 1px -2px 0 #888,
|
||||
-3px -1px 0 #888, -2px -1px 0 #aaa, -1px -1px 0 #aaa, 0 -1px 0 #aaa, 1px -1px 0 #aaa, 2px -1px 0 #888,
|
||||
-3px 0 0 #888, -2px 0 0 #aaa, -1px 0 0 #666, 0 0 0 #666, 1px 0 0 #aaa, 2px 0 0 #888,
|
||||
-2px 1px 0 #888, -1px 1px 0 #aaa, 0 1px 0 #aaa, 1px 1px 0 #888,
|
||||
-1px 2px 0 #888, 0 2px 0 #888;
|
||||
}
|
||||
|
||||
.icon-paper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
-2px -3px 0 #ffcc99, -1px -3px 0 #ffcc99, 0 -3px 0 #ffcc99, 1px -3px 0 #ffcc99, 2px -3px 0 #ffcc99,
|
||||
-3px -2px 0 #ffcc99, -2px -2px 0 #ffcc99, -1px -2px 0 #ffcc99, 0 -2px 0 #ffcc99, 1px -2px 0 #ffcc99, 2px -2px 0 #ffcc99, 3px -2px 0 #ffcc99,
|
||||
-3px -1px 0 #ffcc99, -2px -1px 0 #ffcc99, -1px -1px 0 #ffcc99, 0 -1px 0 #ffcc99, 1px -1px 0 #ffcc99, 2px -1px 0 #ffcc99, 3px -1px 0 #ffcc99,
|
||||
-3px 0 0 #ffcc99, -2px 0 0 #ffcc99, -1px 0 0 #ffaa77, 0 0 0 #ffaa77, 1px 0 0 #ffaa77, 2px 0 0 #ffcc99, 3px 0 0 #ffcc99,
|
||||
-3px 1px 0 #ffcc99, -2px 1px 0 #ffcc99, -1px 1px 0 #ffcc99, 0 1px 0 #ffcc99, 1px 1px 0 #ffcc99, 2px 1px 0 #ffcc99, 3px 1px 0 #ffcc99;
|
||||
}
|
||||
|
||||
.icon-scissors::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
-3px -2px 0 #888, -2px -2px 0 #888,
|
||||
-3px -1px 0 #888, -2px -1px 0 #888,
|
||||
-2px 0 0 #888, -1px 0 0 #888, 0 0 0 #888, 1px 0 0 #888, 2px 0 0 #888,
|
||||
-1px 1px 0 #888, 0 1px 0 #888, 1px 1px 0 #888,
|
||||
2px -2px 0 #888, 3px -2px 0 #888,
|
||||
2px -1px 0 #888, 3px -1px 0 #888;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
text-align: center;
|
||||
margin-bottom: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.final-result {
|
||||
font-size: 13px;
|
||||
margin: 6px 0;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Trophy icon for win */
|
||||
.icon-win::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Cup top */
|
||||
-3px -4px 0 #ffd700, -2px -4px 0 #ffd700, -1px -4px 0 #ffd700, 0 -4px 0 #ffd700, 1px -4px 0 #ffd700, 2px -4px 0 #ffd700, 3px -4px 0 #ffd700,
|
||||
/* Cup body */
|
||||
-2px -3px 0 #ffd700, -1px -3px 0 #ffed4e, 0 -3px 0 #ffed4e, 1px -3px 0 #ffed4e, 2px -3px 0 #ffd700,
|
||||
-2px -2px 0 #ffd700, -1px -2px 0 #ffed4e, 0 -2px 0 #ffed4e, 1px -2px 0 #ffed4e, 2px -2px 0 #ffd700,
|
||||
-2px -1px 0 #ffd700, -1px -1px 0 #ffed4e, 0 -1px 0 #ffed4e, 1px -1px 0 #ffed4e, 2px -1px 0 #ffd700,
|
||||
-2px 0 0 #ffd700, -1px 0 0 #ffd700, 0 0 0 #ffd700, 1px 0 0 #ffd700, 2px 0 0 #ffd700,
|
||||
/* Base */
|
||||
-1px 1px 0 #b8860b, 0 1px 0 #b8860b, 1px 1px 0 #b8860b,
|
||||
-2px 2px 0 #b8860b, -1px 2px 0 #b8860b, 0 2px 0 #b8860b, 1px 2px 0 #b8860b, 2px 2px 0 #b8860b;
|
||||
}
|
||||
|
||||
/* Broken heart icon for lose */
|
||||
.icon-lose::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Left side of heart */
|
||||
-3px -2px 0 #ff6b6b, -2px -2px 0 #ff6b6b,
|
||||
-4px -1px 0 #ff6b6b, -3px -1px 0 #ff6b6b, -2px -1px 0 #ff6b6b, -1px -1px 0 #ff6b6b,
|
||||
-4px 0 0 #ff6b6b, -3px 0 0 #ff6b6b, -2px 0 0 #ff6b6b, -1px 0 0 #ff6b6b,
|
||||
/* Right side of heart */
|
||||
1px -2px 0 #ff6b6b, 2px -2px 0 #ff6b6b,
|
||||
0 -1px 0 #ff6b6b, 1px -1px 0 #ff6b6b, 2px -1px 0 #ff6b6b, 3px -1px 0 #ff6b6b,
|
||||
0 0 0 #ff6b6b, 1px 0 0 #ff6b6b, 2px 0 0 #ff6b6b, 3px 0 0 #ff6b6b,
|
||||
/* Bottom crack (broken) */
|
||||
-3px 1px 0 #ff6b6b, -1px 1px 0 #ff6b6b, 1px 1px 0 #ff6b6b,
|
||||
-2px 2px 0 #ff6b6b, 0 2px 0 #ff6b6b,
|
||||
-1px 3px 0 #ff6b6b;
|
||||
}
|
||||
|
||||
/* Handshake icon for draw */
|
||||
.icon-draw::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Left hand */
|
||||
-4px -1px 0 #ffcc99, -3px -1px 0 #ffcc99, -2px -1px 0 #ffcc99,
|
||||
-4px 0 0 #ffcc99, -3px 0 0 #ffcc99, -2px 0 0 #ffcc99, -1px 0 0 #ffcc99,
|
||||
-4px 1px 0 #ffcc99, -3px 1px 0 #ffcc99, -2px 1px 0 #ffcc99,
|
||||
/* Right hand */
|
||||
1px -1px 0 #ffcc99, 2px -1px 0 #ffcc99, 3px -1px 0 #ffcc99,
|
||||
0 0 0 #ffcc99, 1px 0 0 #ffcc99, 2px 0 0 #ffcc99, 3px 0 0 #ffcc99,
|
||||
1px 1px 0 #ffcc99, 2px 1px 0 #ffcc99, 3px 1px 0 #ffcc99;
|
||||
}
|
||||
|
||||
.final-result h3 {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.final-result.win {
|
||||
background: #90EE90;
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.final-result.lose {
|
||||
background: #FFB6C1;
|
||||
color: #8b0000;
|
||||
}
|
||||
|
||||
.final-result.draw {
|
||||
background: #FFE4B5;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
background: #4CAF50;
|
||||
border: 3px solid #2e7d32;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
background: #cd853f;
|
||||
border: 3px solid #8b4513;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #d2691e;
|
||||
}
|
||||
|
||||
.close-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
:disabled="stage === 'egg'"
|
||||
@info="showPetInfo = !showPetInfo"
|
||||
@feed="$emit('action', 'feed')"
|
||||
@play="$emit('action', 'play')"
|
||||
@playMenu="showPlayMenu = true"
|
||||
@sleep="$emit('action', 'sleep')"
|
||||
/>
|
||||
|
||||
|
|
@ -199,6 +199,20 @@
|
|||
@close="showPetInfo = false"
|
||||
/>
|
||||
|
||||
<!-- Play Menu -->
|
||||
<PlayMenu
|
||||
v-if="showPlayMenu"
|
||||
@select="handlePlaySelect"
|
||||
@close="showPlayMenu = false"
|
||||
/>
|
||||
|
||||
<!-- Mini Games -->
|
||||
<GuessingGame
|
||||
v-if="currentGame === 'guessing'"
|
||||
@close="currentGame = ''"
|
||||
@complete="handleGameComplete"
|
||||
/>
|
||||
|
||||
<!-- Inventory Screen -->
|
||||
<InventoryScreen
|
||||
v-if="showInventory"
|
||||
|
|
@ -239,6 +253,8 @@ import FortuneStickAnimation from './FortuneStickAnimation.vue';
|
|||
import FortuneResult from './FortuneResult.vue';
|
||||
import PetInfoScreen from './PetInfoScreen.vue';
|
||||
import InventoryScreen from './InventoryScreen.vue';
|
||||
import PlayMenu from './PlayMenu.vue';
|
||||
import GuessingGame from './GuessingGame.vue';
|
||||
import guanyinLots from '../assets/guanyin_100_lots.json';
|
||||
|
||||
const props = defineProps({
|
||||
|
|
@ -285,6 +301,8 @@ const currentLotNumber = ref(null);
|
|||
const consecutiveSaintCount = ref(0);
|
||||
const showPetInfo = ref(false);
|
||||
const showInventory = ref(false);
|
||||
const showPlayMenu = ref(false);
|
||||
const currentGame = ref(''); // '', 'training', 'guessing', 'ball'
|
||||
const inventory = ref(new Array(16).fill(null));
|
||||
// Initialize some items
|
||||
inventory.value[0] = { id: 'cookie', name: '幸運餅乾', description: '增加一點快樂值', count: 5, iconClass: 'icon-cookie' };
|
||||
|
|
@ -343,6 +361,25 @@ function handleJiaobeiClose() {
|
|||
consecutiveSaintCount.value = 0;
|
||||
}
|
||||
|
||||
// --- Play Game Selection ---
|
||||
function handlePlaySelect(gameType) {
|
||||
showPlayMenu.value = false;
|
||||
console.log('Selected game:', gameType);
|
||||
|
||||
// Show the selected game
|
||||
currentGame.value = gameType;
|
||||
}
|
||||
|
||||
function handleGameComplete(won) {
|
||||
console.log('Game completed, won:', won);
|
||||
currentGame.value = '';
|
||||
|
||||
if (won) {
|
||||
// Reward: increase happiness
|
||||
emit('action', 'play');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 求籤流程函數 ---
|
||||
|
||||
function handleStickComplete(number) {
|
||||
|
|
@ -914,58 +951,22 @@ async function startFeeding(type) {
|
|||
foodVisible.value = true;
|
||||
console.log('Food visible:', foodVisible.value);
|
||||
|
||||
// Calculate food position: in front of pet, at mouth height
|
||||
// Food drops in front (not directly at mouth)
|
||||
// Calculate food position: directly above pet's mouth
|
||||
const foodSize = 10 * pixelSize;
|
||||
const frontOffsetX = isFacingRight.value ? -foodSize - 5 : width + 5; // In front of pet
|
||||
const mouthY = 8.5; // Mouth is at row 8-9
|
||||
|
||||
// Set horizontal position (in front of pet)
|
||||
let targetFoodX = petX.value + frontOffsetX;
|
||||
|
||||
// Only avoid poop areas if there's actual poop
|
||||
const areas = getPoopAreas();
|
||||
if (areas.length > 0) {
|
||||
const maxPoopRight = Math.max(...areas.map(a => a.right));
|
||||
if (targetFoodX < maxPoopRight + 10) {
|
||||
// Move food to the right of poop areas
|
||||
targetFoodX = maxPoopRight + 15;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure food stays within bounds
|
||||
const cw = containerRef.value?.clientWidth || 300;
|
||||
targetFoodX = Math.max(10, Math.min(cw - 40, targetFoodX));
|
||||
|
||||
foodX.value = targetFoodX;
|
||||
foodX.value = targetFoodX;
|
||||
// Food drops right in front of pet's mouth (don't move the pet)
|
||||
// Position food based on which way pet is facing
|
||||
const mouthOffsetX = isFacingRight.value ? -foodSize - 2 : width + 2;
|
||||
foodX.value = petX.value + mouthOffsetX;
|
||||
foodY.value = 0; // Start from top of screen
|
||||
|
||||
// Calculate target Y (at mouth level)
|
||||
const targetY = petY.value + (mouthY * pixelSize) - (foodSize / 2);
|
||||
|
||||
// Only avoid poop areas vertically if there's actual poop
|
||||
let safeTargetY = targetY;
|
||||
if (areas.length > 0) {
|
||||
const maxPoopBottom = Math.max(...areas.map(a => a.bottom));
|
||||
safeTargetY = Math.min(targetY, maxPoopBottom - foodSize - 5);
|
||||
}
|
||||
console.log('Food dropping to:', foodX.value, targetY, 'Pet at:', petX.value, petY.value);
|
||||
|
||||
// Move pet to food
|
||||
const targetPetX = isFacingRight.value ? targetFoodX - width - 5 : targetFoodX + (10 * pixelSize) + 5;
|
||||
|
||||
// Force pet to be at the correct position to eat
|
||||
// This ensures that even if the food was moved due to bounds/poop, the pet is there
|
||||
petX.value = targetPetX;
|
||||
|
||||
// Simple animation sequence
|
||||
// 1. Drop food
|
||||
// 2. Pet moves to food
|
||||
// 3. Pet eats (mouth open/close)
|
||||
|
||||
// ... (Existing feeding logic would go here, but we rely on state='eating' triggers from parent)
|
||||
|
||||
// Animate falling to front of pet
|
||||
// Animate falling to pet's mouth
|
||||
const duration = 800;
|
||||
const startTime = performance.now();
|
||||
|
||||
|
|
@ -975,7 +976,7 @@ async function startFeeding(type) {
|
|||
const progress = Math.min(elapsed / duration, 1);
|
||||
// Ease out for smoother landing
|
||||
const eased = 1 - Math.pow(1 - progress, 3);
|
||||
foodY.value = eased * safeTargetY;
|
||||
foodY.value = eased * targetY;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animateFall);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
<template>
|
||||
<div class="play-menu-overlay" @click.self="$emit('close')">
|
||||
<div class="play-menu-container">
|
||||
<h2 class="menu-title">選擇遊戲</h2>
|
||||
|
||||
<div class="game-options">
|
||||
<button
|
||||
class="game-option"
|
||||
@click="selectGame('training')"
|
||||
>
|
||||
<div class="option-icon icon-training"></div>
|
||||
<div class="option-name">訓練</div>
|
||||
<div class="option-desc">攻擊訓練</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="game-option"
|
||||
@click="selectGame('guessing')"
|
||||
>
|
||||
<div class="option-icon icon-rps"></div>
|
||||
<div class="option-name">猜拳</div>
|
||||
<div class="option-desc">剪刀石頭布</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="game-option"
|
||||
@click="selectGame('ball')"
|
||||
>
|
||||
<div class="option-icon icon-ball"></div>
|
||||
<div class="option-name">接球</div>
|
||||
<div class="option-desc">反應小遊戲</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="close-btn" @click="$emit('close')">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['close', 'select']);
|
||||
|
||||
function selectGame(gameType) {
|
||||
emit('select', gameType);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.play-menu-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.play-menu-container {
|
||||
background: #f5f5dc;
|
||||
border: 4px solid #8b4513;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
min-width: 200px;
|
||||
max-width: 90%;
|
||||
max-height: 85%;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
overflow-y: auto;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
margin: 0 0 12px 0;
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.game-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.game-option {
|
||||
background: #fff;
|
||||
border: 3px solid #8b4513;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
}
|
||||
|
||||
.game-option:hover {
|
||||
background: #fffacd;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.game-option:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 auto 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Pixel Art Icons */
|
||||
.icon-training::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Explosion/Impact shape */
|
||||
0 0 0 #ff0000,
|
||||
-2px -2px 0 #ff4400, 2px -2px 0 #ff4400,
|
||||
-3px 0 0 #ff6600, -2px 0 0 #ff4400, 0 0 0 #ff0000, 2px 0 0 #ff4400, 3px 0 0 #ff6600,
|
||||
-2px 2px 0 #ff4400, 2px 2px 0 #ff4400,
|
||||
/* Outer glow */
|
||||
-4px -1px 0 #ffaa00, 4px -1px 0 #ffaa00,
|
||||
-4px 1px 0 #ffaa00, 4px 1px 0 #ffaa00,
|
||||
-1px -4px 0 #ffaa00, 1px -4px 0 #ffaa00,
|
||||
-1px 4px 0 #ffaa00, 1px 4px 0 #ffaa00;
|
||||
}
|
||||
|
||||
.icon-rps::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Fist shape */
|
||||
-1px -3px 0 #ffcc99, 0 -3px 0 #ffcc99, 1px -3px 0 #ffcc99,
|
||||
-2px -2px 0 #ffcc99, -1px -2px 0 #ffcc99, 0 -2px 0 #ffcc99, 1px -2px 0 #ffcc99, 2px -2px 0 #ffcc99,
|
||||
-2px -1px 0 #ffcc99, -1px -1px 0 #ffcc99, 0 -1px 0 #ffcc99, 1px -1px 0 #ffcc99, 2px -1px 0 #ffcc99,
|
||||
-2px 0 0 #ffcc99, -1px 0 0 #ffcc99, 0 0 0 #ffaa77, 1px 0 0 #ffcc99, 2px 0 0 #ffcc99,
|
||||
-2px 1px 0 #ffcc99, -1px 1px 0 #ffcc99, 0 1px 0 #ffcc99, 1px 1px 0 #ffcc99, 2px 1px 0 #ffcc99,
|
||||
-1px 2px 0 #ffcc99, 0 2px 0 #ffcc99, 1px 2px 0 #ffcc99;
|
||||
}
|
||||
|
||||
.icon-ball::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(2);
|
||||
box-shadow:
|
||||
/* Ball shape */
|
||||
-1px -3px 0 #000, 0 -3px 0 #000, 1px -3px 0 #000,
|
||||
-2px -2px 0 #000, -1px -2px 0 #fff, 0 -2px 0 #fff, 1px -2px 0 #fff, 2px -2px 0 #000,
|
||||
-3px -1px 0 #000, -2px -1px 0 #fff, -1px -1px 0 #fff, 0 -1px 0 #000, 1px -1px 0 #fff, 2px -1px 0 #fff, 3px -1px 0 #000,
|
||||
-3px 0 0 #000, -2px 0 0 #fff, -1px 0 0 #000, 0 0 0 #000, 1px 0 0 #000, 2px 0 0 #fff, 3px 0 0 #000,
|
||||
-3px 1px 0 #000, -2px 1px 0 #fff, -1px 1px 0 #fff, 0 1px 0 #000, 1px 1px 0 #fff, 2px 1px 0 #fff, 3px 1px 0 #000,
|
||||
-2px 2px 0 #000, -1px 2px 0 #fff, 0 2px 0 #fff, 1px 2px 0 #fff, 2px 2px 0 #000,
|
||||
-1px 3px 0 #000, 0 3px 0 #000, 1px 3px 0 #000;
|
||||
}
|
||||
|
||||
.option-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #8b4513;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.option-desc {
|
||||
font-size: 10px;
|
||||
color: #a0522d;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: #cd853f;
|
||||
border: 3px solid #8b4513;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
font-family: 'DotGothic16', sans-serif;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #d2691e;
|
||||
}
|
||||
|
||||
.close-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="top-menu">
|
||||
<button class="icon-btn icon-stats" @click="$emit('info')" title="Status"></button>
|
||||
<button class="icon-btn icon-feed" @click="$emit('feed')" :disabled="disabled" title="Feed"></button>
|
||||
<button class="icon-btn icon-play" @click="$emit('play')" :disabled="disabled" title="Play"></button>
|
||||
<button class="icon-btn icon-play" @click="$emit('playMenu')" :disabled="disabled" title="Play"></button>
|
||||
<button class="icon-btn icon-sleep" @click="$emit('sleep')" :disabled="disabled" title="Sleep"></button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -15,7 +15,7 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
defineEmits(['info', 'feed', 'play', 'sleep']);
|
||||
defineEmits(['info', 'feed', 'playMenu', 'sleep']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
Loading…
Reference in New Issue