Skip to content

Commit

Permalink
Merge pull request #32 from libondev/dev
Browse files Browse the repository at this point in the history
feat: 道具系统
  • Loading branch information
humandetail authored May 1, 2024
2 parents 265f55e + ebbc2d3 commit 108adc7
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 104 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
- [x] i18n
- [x] 积分系统
- [x] 道具兑换
- [ ] 道具系统
- [x] 道具系统

### 后续功能
一些想做但不确定会不会做的功能
Expand Down
30 changes: 13 additions & 17 deletions src/components/Button.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
<script lang="ts" setup>
import { VARIANT } from '@/config/theme'
const props = defineProps({
as: {
type: String as PropType<string | Component>,
default: 'button',
const props = withDefaults(
defineProps<{
as?: string | Component
type?: keyof typeof VARIANT
disabled?: boolean
btnClassName?: string
}>(),
{
as: 'button',
type: 'default',
disabled: false,
btnClassName: '',
},
type: {
type: String as PropType<keyof typeof VARIANT>,
default: 'default',
},
disabled: {
type: Boolean,
default: false,
},
btnClassName: {
type: String,
default: '',
},
})
)
const emits = defineEmits(['click'])
Expand Down
2 changes: 1 addition & 1 deletion src/components/Grid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function handleChange(e: Event) {
</script>

<template>
<div class="game-grid w-max mx-auto dark:border-gray-600 border-t">
<div class="game-grid dark:border-gray-600 border-t">
<div v-for="_row, rowIndex of config.grid" :key="_row" class="flex border-inherit border-b">
<div v-for="_col, colIndex of config.grid" :key="_col" class="border-inherit border-l [&:last-child]:border-r p-1">
<GridItem
Expand Down
77 changes: 77 additions & 0 deletions src/components/PropsBag.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script setup lang="ts">
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
import type { GameGood } from '@/config/goods'
import { getGameGoods, setGameGoods } from '@/composables/use-local-cache'
defineProps<{
disabled?: boolean
}>()
const emits = defineEmits<{
(eventName: 'use-props', good: GameGood): void
}>()
const popoverTriggerRef = shallowRef<typeof PopoverButton>()
const goods = ref<GameGood[]>([])
function onClickGood(good: GameGood) {
if (good.count <= 0) {
return
}
good.count -= 1
emits('use-props', good)
setGameGoods(goods.value)
popoverTriggerRef.value!.el.click()
}
onMounted(async () => {
goods.value = await getGameGoods() || []
})
</script>

<template>
<Popover class="relative" :class="{ 'opacity-40 pointer-events-none': disabled }">
<PopoverButton
ref="popoverTriggerRef"
:disabled
class="inline-block px-3 py-1.5 select-none rounded-lg text-sm shadow-sm dark:bg-gray-700 border-[rgba(0,0,0,.2)] border-x-[1.5px] border-t-[1.5px] border-b-4 active:border-b-[1.5px] active:translate-y-1 active:shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
>
道具栏
</PopoverButton>

<Transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="translate-y-1 opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-1 opacity-0"
>
<PopoverPanel class="absolute right-0 bottom-full mb-2 z-10 p-1 border rounded-md bg-[hsl(var(--background))] shadow-sm w-max select-none" as="ul">
<li
v-for="good of goods"
:key="good.id"
class="relative flex items-center pl-2 pr-2 py-1 rounded hover:bg-secondary cursor-pointer"
@click="onClickGood(good)"
>
<i :class="[good.icon, good.color]" />

&nbsp;&nbsp;{{ good.name }}

<span class="text-white bg-red-500 rounded-e-full rounded-s-full text-xs px-1 absolute right-0 -top-0.5">
{{ good.count }}
</span>
</li>
<li v-if="goods.length === 0" class="text-center text-sm px-1 text-gray-500">
暂无道具,<RouterLink to="/store" class="text-primary font-medium">
去购买
</RouterLink>
</li>
</PopoverPanel>
</Transition>
</Popover>
</template>
21 changes: 15 additions & 6 deletions src/components/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,34 @@ interface OptionsItem {
value: string
}
const props = defineProps < {
const props = defineProps<{
options: OptionsItem[]
} > ()
}>()
const optionsMap = computed(() => props.options.reduce((acc, item) => {
acc[item.value] = item.label
return acc
}, {} as Record<string, string>))
const modelValue = defineModel < string > ()
const modelValue = defineModel<string>()
</script>

<template>
<Listbox v-model="modelValue" as="div" class="relative h-10">
<ListboxButton class="inline-flex truncate items-center justify-center px-2 h-[31px] select-none rounded-lg text-sm shadow-sm dark:bg-gray-700 border-[rgba(0,0,0,.2)] border-x-[1.5px] border-t-[1.5px] border-b-4 active:border-b-[1.5px] active:h-[26px] active:translate-y-1 active:shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1">
<ListboxButton
class="inline-flex truncate items-center justify-center px-2 h-[31px] select-none rounded-lg text-sm shadow-sm dark:bg-gray-700 border-[rgba(0,0,0,.2)] border-x-[1.5px] border-t-[1.5px] border-b-4 active:border-b-[1.5px] active:h-[26px] active:translate-y-1 active:shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
>
{{ optionsMap[modelValue!] }}
</ListboxButton>

<transition leave-active-class="transition duration-100 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0">
<Transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="translate-y-1 opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-1 opacity-0"
>
<ListboxOptions
class="absolute top-9 right-0 max-h-60 min-w-20 w-full overflow-auto rounded-md bg-[hsl(var(--background))] border-input py-1 text-base shadow-lg ring-1 ring-black/5 dark:ring-white/10 focus:outline-none sm:text-sm"
>
Expand All @@ -45,6 +54,6 @@ const modelValue = defineModel < string > ()
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</Transition>
</Listbox>
</template>
12 changes: 11 additions & 1 deletion src/composables/use-checked-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,19 @@ export function useCheckedBlocks(blocks: Ref<Set<string>>) {
/**
* 匹配所有选中的是否达成胜利条件
*/
function getAllCheckedResult() {
function getAllCheckedResult(ignoreErrorProp: Ref<number>) {
const checkedBlocks = getAllCheckedBlocks()

// 如果道具的数量
if (ignoreErrorProp.value >= 0) {
ignoreErrorProp.value -= 1

return {
matched: true,
blocks: checkedBlocks,
}
}

// 如果选中的数量和生成的数量不相等
if (checkedBlocks.length !== blocks.value.size) {
return {
Expand Down
2 changes: 1 addition & 1 deletion src/composables/use-game-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function useGameStatus() {

// 当前生命值
const gameHealth = shallowRef(levelConfig.health)
const gameHealthList = computed(() => Array.from({ length: levelConfig.health + 1 }, (_, i) => i))
const gameHealthList = computed(() => Array.from({ length: levelConfig.health + gameHealth.value }, (_, i) => i))

// 生成目标方块
function generateRandomTargetBlock() {
Expand Down
14 changes: 7 additions & 7 deletions src/config/goods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

export const GAME_GOODS = [
{
id: 'regenerate',
name: '再来一次',
id: 'REGENERATE',
name: '重新开始',
icon: 'i-game-icons-perspective-dice-six-faces-random',
description: '说不上来为什么,但总感觉哪里不对劲(重新生成方块数量及位置)',
color: 'text-orange-600',
price: 300,
count: 0,
},
{
id: 'ignoreError',
id: 'IGNORE_ERROR',
name: '走个后门',
icon: 'i-game-icons-broken-shield',
description: '尽情犯错吧,但这可不是长久之计(可以选择任意方块作为结果)',
Expand All @@ -20,16 +20,16 @@ export const GAME_GOODS = [
count: 0,
},
{
id: 'lookAgain',
name: '再看一遍',
id: 'LOOK_AGAIN',
name: '完好如初',
icon: 'i-game-icons-brain',
description: '刚刚发生了什么?(增加 5 秒预览时间【仅在预览模式可用】)',
description: '刚刚发生了什么?(预览极端重新开始本回合)',
color: 'text-indigo-600',
price: 500,
count: 0,
},
{
id: 'restoreLife',
id: 'RESTORE_LIFE',
name: '打个补丁',
icon: 'i-game-icons-arm-bandage',
description: '休息一下,做些你想做的事(回复 1 点生命值)',
Expand Down
9 changes: 9 additions & 0 deletions src/utils/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ export const prefersDarkColorScheme = () => window && window.matchMedia && windo
* @param object 要检测的对象
*/
export const isEmptyObject = (object: object) => Reflect.ownKeys(object).length === 0

// 千分位格式化分数
export function formatScore(score: number) {
const numStr = score.toString()
const reg = /\B(?=(\d{3})+(?!\d))/g
return numStr.replace(reg, ',')
}

export const isMobile = 'ontouchstart' in window
Loading

0 comments on commit 108adc7

Please sign in to comment.