增加战绩显示功能
This commit is contained in:
parent
c48a15552e
commit
728a3f4d6e
97
src/App.vue
97
src/App.vue
@ -84,6 +84,7 @@
|
||||
{{ getPlayerName(winner) }}胜利!<br />
|
||||
第 {{ moveCount }} 手获胜
|
||||
</p>
|
||||
<GameStatistics v-if="gameMode === 'pve'" :stats="statistics" />
|
||||
<p
|
||||
v-if="gameMode === 'pve' && winner !== (isPlayerBlack ? 1 : 2)"
|
||||
class="taunt-message"
|
||||
@ -119,10 +120,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GameStatistics from './components/GameStatistics.vue'
|
||||
|
||||
const TURN_TIME = 30 // 每回合30秒
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
GameStatistics,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
board: Array(15)
|
||||
@ -168,6 +174,20 @@ export default {
|
||||
'看起来很激烈?其实我一直在掌控局面哦!',
|
||||
],
|
||||
},
|
||||
statistics: {
|
||||
total: 0,
|
||||
playerWins: 0,
|
||||
computerWins: 0,
|
||||
timeoutWins: 0,
|
||||
avgMoves: 0,
|
||||
totalMoves: 0,
|
||||
winningStreaks: {
|
||||
current: 0,
|
||||
max: 0,
|
||||
player: 0,
|
||||
computer: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@ -227,6 +247,9 @@ export default {
|
||||
this.lastWinner = this.currentPlayer
|
||||
setTimeout(() => {
|
||||
this.winner = this.currentPlayer
|
||||
if (this.gameMode === 'pve') {
|
||||
this.updateStatistics(this.currentPlayer)
|
||||
}
|
||||
}, 2000)
|
||||
this.player1Time = TURN_TIME
|
||||
this.player2Time = TURN_TIME
|
||||
@ -276,12 +299,18 @@ export default {
|
||||
this.player1Skips++
|
||||
if (this.player1Skips >= 3) {
|
||||
this.winner = 'timeout'
|
||||
if (this.gameMode === 'pve') {
|
||||
this.updateStatistics('timeout')
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
this.player2Skips++
|
||||
if (this.player2Skips >= 3) {
|
||||
this.winner = 'timeout'
|
||||
if (this.gameMode === 'pve') {
|
||||
this.updateStatistics('timeout')
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -418,8 +447,9 @@ export default {
|
||||
this.lastWinner = this.currentPlayer
|
||||
setTimeout(() => {
|
||||
this.winner = this.currentPlayer
|
||||
this.player1Time = TURN_TIME
|
||||
this.player2Time = TURN_TIME
|
||||
if (this.gameMode === 'pve') {
|
||||
this.updateStatistics(this.currentPlayer)
|
||||
}
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
@ -936,10 +966,73 @@ export default {
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
updateStatistics(winner) {
|
||||
const stats = this.statistics
|
||||
stats.total++
|
||||
stats.totalMoves += this.moveCount
|
||||
stats.avgMoves = Math.round(stats.totalMoves / stats.total)
|
||||
|
||||
if (winner === 'timeout') {
|
||||
stats.timeoutWins++
|
||||
if (this.currentPlayer === (this.isPlayerBlack ? 1 : 2)) {
|
||||
stats.computerWins++
|
||||
stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1
|
||||
} else {
|
||||
stats.playerWins++
|
||||
stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1
|
||||
}
|
||||
} else if (winner === (this.isPlayerBlack ? 1 : 2)) {
|
||||
stats.playerWins++
|
||||
stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1
|
||||
stats.winningStreaks.player = Math.max(
|
||||
stats.winningStreaks.player,
|
||||
stats.winningStreaks.current,
|
||||
)
|
||||
} else {
|
||||
stats.computerWins++
|
||||
stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1
|
||||
stats.winningStreaks.computer = Math.max(
|
||||
stats.winningStreaks.computer,
|
||||
Math.abs(stats.winningStreaks.current),
|
||||
)
|
||||
}
|
||||
|
||||
stats.winningStreaks.max = Math.max(
|
||||
stats.winningStreaks.player,
|
||||
stats.winningStreaks.computer,
|
||||
)
|
||||
|
||||
// 保存到本地存储
|
||||
this.saveStatistics()
|
||||
},
|
||||
|
||||
saveStatistics() {
|
||||
localStorage.setItem('gameStatistics', JSON.stringify(this.statistics))
|
||||
},
|
||||
|
||||
loadStatistics() {
|
||||
const saved = localStorage.getItem('gameStatistics')
|
||||
if (saved) {
|
||||
this.statistics = JSON.parse(saved)
|
||||
}
|
||||
},
|
||||
|
||||
handleGameEnd() {
|
||||
if (this.gameMode === 'pve') {
|
||||
this.updateStatistics(this.winner)
|
||||
}
|
||||
// ... 其他游戏结束逻辑
|
||||
},
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.stopTimer()
|
||||
this.saveStatistics()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadStatistics()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
235
src/components/GameStatistics.vue
Normal file
235
src/components/GameStatistics.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="statistics-panel">
|
||||
<h3>战绩统计</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">总对局</div>
|
||||
<div class="stat-value">{{ totalGames }}</div>
|
||||
<div class="stat-unit">局</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">胜率</div>
|
||||
<div
|
||||
class="stat-value"
|
||||
:class="{ 'win-rate': true, positive: winRate > 50, negative: winRate < 50 }"
|
||||
>
|
||||
{{ formatPercent(winRate) }}
|
||||
</div>
|
||||
<div class="stat-unit">%</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">玩家胜场</div>
|
||||
<div class="stat-value">{{ playerWins }}</div>
|
||||
<div class="stat-unit">胜</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">电脑胜场</div>
|
||||
<div class="stat-value">{{ computerWins }}</div>
|
||||
<div class="stat-unit">胜</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">最大连胜</div>
|
||||
<div class="stat-value">{{ maxStreak }}</div>
|
||||
<div class="stat-unit">连胜</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">平均步数</div>
|
||||
<div class="stat-value">{{ avgMoves }}</div>
|
||||
<div class="stat-unit">步</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-chart">
|
||||
<div class="chart-label">胜率分布</div>
|
||||
<div class="win-bars">
|
||||
<div class="win-bar player" :style="{ width: playerBarWidth }">
|
||||
<span class="bar-label">玩家</span>
|
||||
<span class="bar-value">{{ formatPercent(playerWinRate) }}%</span>
|
||||
</div>
|
||||
<div class="win-bar computer" :style="{ width: computerBarWidth }">
|
||||
<span class="bar-label">电脑</span>
|
||||
<span class="bar-value">{{ formatPercent(computerWinRate) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GameStatistics',
|
||||
props: {
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
totalGames() {
|
||||
return this.stats?.total || 0
|
||||
},
|
||||
playerWins() {
|
||||
return this.stats?.playerWins || 0
|
||||
},
|
||||
computerWins() {
|
||||
return this.stats?.computerWins || 0
|
||||
},
|
||||
maxStreak() {
|
||||
return this.stats?.winningStreaks?.max || 0
|
||||
},
|
||||
avgMoves() {
|
||||
return this.stats?.avgMoves || 0
|
||||
},
|
||||
winRate() {
|
||||
if (!this.totalGames) return 0
|
||||
return (this.playerWins / this.totalGames) * 100
|
||||
},
|
||||
playerWinRate() {
|
||||
if (!this.totalGames) return 0
|
||||
return (this.playerWins / this.totalGames) * 100
|
||||
},
|
||||
computerWinRate() {
|
||||
if (!this.totalGames) return 0
|
||||
return (this.computerWins / this.totalGames) * 100
|
||||
},
|
||||
playerBarWidth() {
|
||||
return `${Math.max(this.playerWinRate, 20)}%`
|
||||
},
|
||||
computerBarWidth() {
|
||||
return `${Math.max(this.computerWinRate, 20)}%`
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatPercent(value) {
|
||||
return value.toFixed(1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.statistics-panel {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.statistics-panel h3 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 15px 10px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
transition: color 0.3s ease;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.win-rate {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.win-rate.positive {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.win-rate.negative {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.stats-chart {
|
||||
margin-top: 20px;
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.chart-label {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.win-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.win-bar {
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
color: white;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 120px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar-label {
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bar-value {
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.win-bar.player {
|
||||
background: linear-gradient(90deg, #4caf50 0%, #45a049 100%);
|
||||
}
|
||||
|
||||
.win-bar.computer {
|
||||
background: linear-gradient(90deg, #f44336 0%, #e53935 100%);
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user