增加战绩显示功能

This commit is contained in:
icezhb 2024-12-29 15:35:15 +08:00
parent c48a15552e
commit 728a3f4d6e
2 changed files with 330 additions and 2 deletions

View File

@ -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>

View 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>