增加战绩显示功能
This commit is contained in:
parent
c48a15552e
commit
728a3f4d6e
97
src/App.vue
97
src/App.vue
@ -84,6 +84,7 @@
|
|||||||
{{ getPlayerName(winner) }}胜利!<br />
|
{{ getPlayerName(winner) }}胜利!<br />
|
||||||
第 {{ moveCount }} 手获胜
|
第 {{ moveCount }} 手获胜
|
||||||
</p>
|
</p>
|
||||||
|
<GameStatistics v-if="gameMode === 'pve'" :stats="statistics" />
|
||||||
<p
|
<p
|
||||||
v-if="gameMode === 'pve' && winner !== (isPlayerBlack ? 1 : 2)"
|
v-if="gameMode === 'pve' && winner !== (isPlayerBlack ? 1 : 2)"
|
||||||
class="taunt-message"
|
class="taunt-message"
|
||||||
@ -119,10 +120,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import GameStatistics from './components/GameStatistics.vue'
|
||||||
|
|
||||||
const TURN_TIME = 30 // 每回合30秒
|
const TURN_TIME = 30 // 每回合30秒
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
GameStatistics,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
board: Array(15)
|
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
|
this.lastWinner = this.currentPlayer
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.winner = this.currentPlayer
|
this.winner = this.currentPlayer
|
||||||
|
if (this.gameMode === 'pve') {
|
||||||
|
this.updateStatistics(this.currentPlayer)
|
||||||
|
}
|
||||||
}, 2000)
|
}, 2000)
|
||||||
this.player1Time = TURN_TIME
|
this.player1Time = TURN_TIME
|
||||||
this.player2Time = TURN_TIME
|
this.player2Time = TURN_TIME
|
||||||
@ -276,12 +299,18 @@ export default {
|
|||||||
this.player1Skips++
|
this.player1Skips++
|
||||||
if (this.player1Skips >= 3) {
|
if (this.player1Skips >= 3) {
|
||||||
this.winner = 'timeout'
|
this.winner = 'timeout'
|
||||||
|
if (this.gameMode === 'pve') {
|
||||||
|
this.updateStatistics('timeout')
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.player2Skips++
|
this.player2Skips++
|
||||||
if (this.player2Skips >= 3) {
|
if (this.player2Skips >= 3) {
|
||||||
this.winner = 'timeout'
|
this.winner = 'timeout'
|
||||||
|
if (this.gameMode === 'pve') {
|
||||||
|
this.updateStatistics('timeout')
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,8 +447,9 @@ export default {
|
|||||||
this.lastWinner = this.currentPlayer
|
this.lastWinner = this.currentPlayer
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.winner = this.currentPlayer
|
this.winner = this.currentPlayer
|
||||||
this.player1Time = TURN_TIME
|
if (this.gameMode === 'pve') {
|
||||||
this.player2Time = TURN_TIME
|
this.updateStatistics(this.currentPlayer)
|
||||||
|
}
|
||||||
}, 2000)
|
}, 2000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -936,10 +966,73 @@ export default {
|
|||||||
}
|
}
|
||||||
return false
|
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() {
|
beforeUnmount() {
|
||||||
this.stopTimer()
|
this.stopTimer()
|
||||||
|
this.saveStatistics()
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadStatistics()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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