Skip to content

Commit

Permalink
Merge branch 'main' of github.com:libondev/memory-block into dev-d
Browse files Browse the repository at this point in the history
  • Loading branch information
humandetail committed Apr 8, 2024
2 parents 4548790 + 12aa534 commit 977851d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 33 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
- [x] 练习模式
- [x] 历史最高分
- [ ] 游玩热力图
- [ ] 本地缓存
- [x] 本地缓存
- [ ] 彩色方块
- [x] 游戏音效
- [x] 游戏界面增加键盘操作
- [x] i18n
- [ ] 积分商店,积分收集、兑换
- [ ] 道具系统
Expand Down
2 changes: 1 addition & 1 deletion src/components/ToggleDark.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useDark, useToggle } from '@vueuse/core'
import { name } from '../../package.json'
import { name } from '@/../package.json'
import Button from '@/components/Button.vue'
// 如果用户没有设置过主题选项(包含第一次进入系统),那么就使用系统的明暗主题设置
Expand Down
18 changes: 18 additions & 0 deletions src/composables/use-game-score.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { GameLevel, LEVEL_GRIDS } from '@/config/game'
import { getHighestScoreInHistory } from '@/composables/use-local-cache'

export function useGameScore(
{ rate: multiplier }: typeof LEVEL_GRIDS[GameLevel],
Expand All @@ -11,6 +12,9 @@ export function useGameScore(
const deltaScore = shallowRef(0)
const showDeltaScore = shallowRef(false)

const highestScore = shallowRef(0)
const showScoreBadge = shallowRef(false)

let lastTimestamp = 0
let timestampId = -1

Expand Down Expand Up @@ -53,14 +57,28 @@ export function useGameScore(
showDeltaScore.value = false
}

// 更新历史最高分状态
function updateHighestScoreStatus(level: GameLevel) {
showScoreBadge.value = false

getHighestScoreInHistory(level).then((v) => {
// 更新历史最高分
highestScore.value = v || 0
})
}

return {
timestamp,
gameScore,
deltaScore,
highestScore,
showDeltaScore,
showScoreBadge,

setGameScore,
stopRecording,
startRecording,
onEndHideDeltaScore,
updateHighestScoreStatus,
}
}
2 changes: 1 addition & 1 deletion src/composables/use-game-sounds.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useStorage, useToggle } from '@vueuse/core'
import { name } from '../../package.json'
import { name } from '@/../package.json'

interface GameSounds {
enableSounds: Ref<boolean>
Expand Down
19 changes: 0 additions & 19 deletions src/composables/use-game-status.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { type GameLevel, LEVEL_GRIDS } from '@/config/game'

import { getHighestScoreInHistory } from '@/composables/use-local-cache'

// 游戏核心状态管理,分数、生命值、目标方块等
export function useGameStatus() {
const route = useRoute()
Expand Down Expand Up @@ -43,19 +41,6 @@ export function useGameStatus() {
const gameHealth = shallowRef(levelConfig.health)
const gameHealthList = computed(() => Array.from({ length: levelConfig.health + 1 }, (_, i) => i))

const highestScore = shallowRef(0)
const showHighestScoreBadge = shallowRef(false)

// 更新历史最高分状态
function updateHighestScoreStatus() {
showHighestScoreBadge.value = false

getHighestScoreInHistory(level).then((v) => {
// 更新历史最高分
highestScore.value = v || 0
})
}

// 生成目标方块
function generateRandomTargetBlock() {
const { min, max, grid } = levelConfig
Expand Down Expand Up @@ -90,11 +75,7 @@ export function useGameStatus() {
gameHealth,
levelConfig,
targetBlocks,
highestScore,
gameHealthList,
showHighestScoreBadge,

updateHighestScoreStatus,
generateRandomTargetBlock,
}
}
2 changes: 1 addition & 1 deletion src/composables/use-local-cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import localforage from 'localforage'
import { name } from '../../package.json'
import { name } from '@/../package.json'
import type { GameLevel } from '@/config/game'

localforage.config({
Expand Down
91 changes: 81 additions & 10 deletions src/views/game/[level].vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts" setup>
import confetti from 'canvas-confetti'
import { name } from '@/../package.json'
import { useGameStatus } from '@/composables/use-game-status'
import { useGameScore } from '@/composables/use-game-score'
import { useCheckedBlocks } from '@/composables/use-checked-blocks'
Expand Down Expand Up @@ -30,11 +32,7 @@ const {
gameHealth,
levelConfig,
targetBlocks,
highestScore,
gameHealthList,
showHighestScoreBadge,
updateHighestScoreStatus,
generateRandomTargetBlock,
} = useGameStatus()
Expand All @@ -49,11 +47,14 @@ const {
timestamp,
gameScore,
deltaScore,
highestScore,
showDeltaScore,
showScoreBadge,
setGameScore,
stopRecording,
startRecording,
onEndHideDeltaScore,
updateHighestScoreStatus,
} = useGameScore(levelConfig, targetBlocks)
const {
Expand Down Expand Up @@ -88,8 +89,8 @@ function onFinishedPreviewCountdown() {
}
async function startGame() {
updateHighestScoreStatus()
generateRandomTargetBlock()
updateHighestScoreStatus(level)
// 重置所有错误块的选中
uncheckAllBlocks()
Expand All @@ -106,6 +107,10 @@ async function startGame() {
}
function onCheckResult() {
if (!gameStatus.value.playing) {
return
}
const { matched, blocks } = getAllCheckedResult()
if (!matched && !blocks.length) {
Expand All @@ -115,14 +120,14 @@ function onCheckResult() {
// 如果匹配成功
if (matched) {
playSuccessSound()
setGameScore()
startGame()
setGameScore()
playSuccessSound()
return
}
// 如果上一步是选错的
// 如果上一步是选错的, 那么游戏状态会被设置为暂停
if (gameStatus.value.pause) {
startGame()
return
Expand Down Expand Up @@ -161,7 +166,7 @@ function gameOver() {
// 如果分数比历史最高分高, 更新历史最高分, 并播放纸屑
if (gameScore.value > highestScore.value) {
highestScore.value = gameScore.value
showHighestScoreBadge.value = true
showScoreBadge.value = true
playOverSound()
confetti({ spread: 120, particleCount: 300 })
Expand Down Expand Up @@ -205,14 +210,80 @@ function onGameTabVisibilityChange() {
startPreviewCountdown()
}
// 当在当前页面刷新或退出的时候保存本地状态以便于恢复上次的状态
// 但是只保存游戏的分数、生命值和选中的方块的状态,重新进入后会重新进入预览模式
const savedLocalStatusKey = `${name}.fe.game-status.${level}`
// 从本地恢复游戏状态
function onRestoreLocalStatus() {
const localCache = localStorage.getItem(savedLocalStatusKey)
if (!localCache) {
return
}
try {
const { _score, _health, _blocks } = JSON.parse(localCache)
gameScore.value = _score
gameHealth.value = _health
targetBlocks.value = new Set(_blocks)
uncheckAllBlocks()
checkedTargetBlock()
// 恢复后进入预览模式, 重新开始读秒,主要是需要恢复分数和血量
setGameStatus('previewing')
resetPrewiewCountdown()
startPreviewCountdown()
// 恢复后删除本地缓存
localStorage.removeItem(savedLocalStatusKey)
} catch (e) { }
}
// 保存游戏状态到本地
function onPageHideSaveLocalStatus() {
// 保存游戏状态
localStorage.setItem(savedLocalStatusKey, JSON.stringify({
_score: gameScore.value,
_health: gameHealth.value,
_blocks: [...targetBlocks.value],
}))
}
// PC 端监听键盘按下事件
function onBoardKeyDown({ code }: KeyboardEvent) {
const KEY_EVENTS_MAP = {
Enter: onCheckResult,
Delete: onResetBlocks,
} as const
KEY_EVENTS_MAP[code as keyof typeof KEY_EVENTS_MAP]?.()
}
onMounted(() => {
startGame()
onRestoreLocalStatus()
// 非移动端增加键盘事件
if (!window.ontouchstart) {
document.addEventListener('keydown', onBoardKeyDown)
}
document.addEventListener('visibilitychange', onGameTabVisibilityChange)
window.addEventListener('pagehide', onPageHideSaveLocalStatus, { once: true })
})
onBeforeUnmount(() => {
stopRecording()
if (!window.ontouchstart) {
document.removeEventListener('keydown', onBoardKeyDown)
}
// 如果是退出了当前页面再刷新的则不保存状态
window.removeEventListener('pagehide', onPageHideSaveLocalStatus)
document.removeEventListener('visibilitychange', onGameTabVisibilityChange)
})
</script>
Expand Down Expand Up @@ -266,7 +337,7 @@ onBeforeUnmount(() => {
<div class="relative mb-6 text-5xl text-center">
<span class="z-10 font-mono font-medium">{{ formatScore(gameScore) }}</span>

<span v-if="showHighestScoreBadge" class="absolute -translate-x-2 text-xs rotate-45 inline-block font-bold px-2 rounded-full border-2 border-red-500 text-red-500">BEST</span>
<span v-if="showScoreBadge" class="absolute -translate-x-2 text-xs rotate-45 inline-block font-bold px-2 rounded-full border-2 border-red-500 text-red-500">BEST</span>

<Transition name="increase-score">
<span
Expand Down

0 comments on commit 977851d

Please sign in to comment.