14 Commits
v1.1 ... rabbit

Author SHA1 Message Date
a3b7941e2b [add] rabbit 2022-09-30 09:55:16 +08:00
0f19b5b19b [add] first 2022-09-30 09:48:41 +08:00
yupi
e8f95a4d4d add 添加统计 2022-09-18 00:47:09 +08:00
yupi
02e38aca6a update README.md 2022-09-18 00:42:16 +08:00
yupi
ce8bd53827 add 新增圣光、透视技能 2022-09-18 00:41:14 +08:00
yupi
0cf6b3a9c2 add 支持保存自定义配置
add 新增 2 种难度
2022-09-18 00:20:52 +08:00
yupi
e57adbc824 fix 修复移出道具 bug 2022-09-18 00:09:22 +08:00
yupi
06bc94fa1b update 补充 README.md 文档 2022-09-17 23:59:15 +08:00
yupi
9b91330a83 update 补充 README.md 文档 2022-09-17 23:37:55 +08:00
yupi
fc74efac2d add 支持自定义动物图案、随机区情况 2022-09-17 23:32:26 +08:00
yupi
75d97b11c3 bug fix 2022-09-17 17:30:42 +08:00
yupi
9b8077c536 add 添加 4 种道具 2022-09-17 12:09:46 +08:00
yupi
200b2a47d0 add 添加游戏胜利标识 2022-09-16 22:41:26 +08:00
yupi
7d73c64d62 add 添加自定义游戏配置 2022-09-16 22:17:31 +08:00
110 changed files with 13470 additions and 2956 deletions

3
.eslintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "@antfu"
}

View File

@@ -1,17 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ["plugin:vue/vue3-recommended", "plugin:prettier/recommended"],
parserOptions: {
ecmaVersion: "latest",
parser: "@typescript-eslint/parser",
sourceType: "module",
},
plugins: ["vue", "@typescript-eslint", "prettier"],
rules: {
"prettier/prettier": "error",
},
};

3
.gitignore vendored
View File

@@ -8,7 +8,7 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
dist
# dist
dist-ssr
*.local
@@ -22,4 +22,3 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.vercel

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 chenxch<https://github.com/chenxch>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,12 +1,87 @@
# 鱼了个鱼
<img src="https://cdn.staticaly.com/gh/chenxch/pic-image@master/20220929/image-31.5wzs9gnp33k.webp" />
> 被羊了个羊虐了百遍后,我自己做了一个!
# xlegex / x了个X
在线体验https://yulegeyu.cn
This is a match-3 game, a simplified version of the sheep, currently based on rabbits, you can customize your own game based on this.
玩法:
这是一个三消类的游戏,简化版的羊了个羊,目前是以兔子为素材,你可以基于这个定制你自己的游戏。
1. 支持选关
2. 支持自定义难度(待更新)
3. 无限道具(待更新)
[Online Demo / 在线demo](https://chenxch.github.io/xlegex/)
## Game screenshot / 游戏截图
![QQ浏览器截图20220922214942](https://cdn.staticaly.com/gh/chenxch/pic-image@master/20220929/tutu.4jhzwxilnfs0.gif)
## Core Code / 核心代码
```ts
// useGame.ts
useGame(config: GameConfig): Game{
...
}
```
###
```ts
// type.d.ts
interface Game {
nodes: Ref<CardNode[]>
selectedNodes: Ref<CardNode[]>
removeList: Ref<CardNode[]>
removeFlag: Ref<boolean>
backFlag: Ref<boolean>
handleSelect: (node: CardNode) => void
handleSelectRemove: (node: CardNode) => void
handleBack: () => void
handleRemove: () => void
initData: (config?: GameConfig) => void
}
interface GameConfig {
container?: Ref<HTMLElement | undefined> // cardNode容器
cardNum: number // card类型数量
layerNum: number // card层数
trap?: boolean // 是否开启陷阱
delNode?: boolean // 是否从nodes中剔除已选节点
events?: GameEvents // 游戏事件
}
interface GameEvents {
clickCallback?: () => void
dropCallback?: () => void
winCallback?: () => void
loseCallback?: () => void
}
```
## Application / 应用
```ts
const {
nodes,
selectedNodes,
handleSelect,
handleBack,
backFlag,
handleRemove,
removeFlag,
removeList,
handleSelectRemove,
initData,
} = useGame({
container: containerRef,
cardNum: 4,
layerNum: 2,
trap: false,
events: {
clickCallback: handleClickCard,
dropCallback: handleDropCard,
winCallback: handleWin,
loseCallback: handleLose,
},
})
initData()
```
## Related Articles / 相关文章
[juejin/掘金](https://juejin.cn/post/7147245442172977189)

BIN
dist/assets/1.d7c29e40.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
dist/assets/10.503e2578.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
dist/assets/11.d4eec1ed.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
dist/assets/12.32050800.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/assets/13.4bda2068.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
dist/assets/14.f7b0a8f2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/assets/15.9468171d.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/assets/2.e06d2ca4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
dist/assets/3.8a4cfab6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
dist/assets/4.458f7dfb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
dist/assets/5.22ddcfaa.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
dist/assets/6.42bf09bd.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
dist/assets/7.0d186733.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
dist/assets/8.75c96fe8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
dist/assets/9.8870e803.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

1
dist/assets/index.338b321b.css vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/assets/index.ded8c5da.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/audio/click.mp3 vendored Normal file

Binary file not shown.

BIN
dist/audio/drop.mp3 vendored Normal file

Binary file not shown.

BIN
dist/audio/lose.mp3 vendored Normal file

Binary file not shown.

BIN
dist/audio/win.mp3 vendored Normal file

Binary file not shown.

27
dist/index.html vendored Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="https://chenxch.github.io/xlegex/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>兔了个兔</title>
<script>
var _hmt = _hmt || [];
(function () {
const hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?1b051845f9998479adf57914a7ef51d1'
const s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
</script>
<script type="module" crossorigin src="https://chenxch.github.io/xlegex/assets/index.ded8c5da.js"></script>
<link rel="stylesheet" href="https://chenxch.github.io/xlegex/assets/index.338b321b.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

1
dist/vite.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,13 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>了个</title>
</head>
<body>
<title>了个</title>
<script>
var _hmt = _hmt || [];
(function () {
const hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?1b051845f9998479adf57914a7ef51d1'
const s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</body>
</html>

9215
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,31 @@
{
"name": "yulegeyu",
"name": "xlegex",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"ant-design-vue": "^3.2.11",
"lodash": "^4.17.21",
"pinia": "^2.0.19",
"pinia-plugin-persistedstate": "^2.1.1",
"vue": "^3.2.37",
"vue-router": "4"
"canvas-confetti": "^1.5.1",
"lodash-es": "^4.17.21",
"vue": "^3.2.37"
},
"devDependencies": {
"@types/lodash": "^4.14.185",
"@typescript-eslint/eslint-plugin": "^5.23.0",
"@typescript-eslint/parser": "^5.23.0",
"@vitejs/plugin-vue": "^3.0.3",
"eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.2.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^8.7.1",
"prettier": "^2.7.1",
"@antfu/eslint-config": "^0.26.3",
"@iconify-json/carbon": "^1.1.8",
"@types/canvas-confetti": "^1.4.3",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.7.18",
"@vitejs/plugin-vue": "^3.1.0",
"eslint": "^8.23.1",
"typescript": "^4.6.4",
"vite": "^3.0.7",
"vue-tsc": "^0.39.5"
"unocss": "^0.45.21",
"vite": "^3.1.0",
"vue-tsc": "^0.40.4"
}
}

3260
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

BIN
public/audio/click.mp3 Normal file

Binary file not shown.

BIN
public/audio/drop.mp3 Normal file

Binary file not shown.

BIN
public/audio/lose.mp3 Normal file

Binary file not shown.

BIN
public/audio/win.mp3 Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,41 +1,259 @@
<template>
<div id="app">
<div class="content">
<router-view />
</div>
<div class="footer">
鱼了个鱼 ©2022 by
<a href="https://github.com/liyupi" target="_blank" style="color: #fff">
程序员鱼皮
</a>
|
<a
href="https://github.com/liyupi/yulegeyu"
target="_blank"
style="color: #fff"
>
代码开源
</a>
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped>
#app {
padding: 16px 16px 50px;
background: url("assets/bg.jpeg");
height: 100vh;
background-size: 100% 100%;
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import Card from './components/card.vue'
import { useGame } from './core/useGame'
import { basicCannon, schoolPride } from './core/utils'
const containerRef = ref<HTMLElement | undefined>()
const clickAudioRef = ref<HTMLAudioElement | undefined>()
const dropAudioRef = ref<HTMLAudioElement | undefined>()
const winAudioRef = ref<HTMLAudioElement | undefined>()
const loseAudioRef = ref<HTMLAudioElement | undefined>()
const curLevel = ref(1)
const showTip = ref(false)
const LevelConfig = [
{ cardNum: 4, layerNum: 2, trap: false },
{ cardNum: 9, layerNum: 3, trap: false },
{ cardNum: 15, layerNum: 6, trap: false },
]
const isWin = ref(false)
const {
nodes,
selectedNodes,
handleSelect,
handleBack,
backFlag,
handleRemove,
removeFlag,
removeList,
handleSelectRemove,
initData,
} = useGame({
container: containerRef,
cardNum: 4,
layerNum: 2,
trap: false,
events: {
clickCallback: handleClickCard,
dropCallback: handleDropCard,
winCallback: handleWin,
loseCallback: handleLose,
},
})
function handleClickCard() {
if (clickAudioRef.value?.paused) {
clickAudioRef.value.play()
}
else if (clickAudioRef.value) {
clickAudioRef.value.load()
clickAudioRef.value.play()
}
}
.footer {
background: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 12px;
text-align: center;
position: fixed;
bottom: 0;
left: 0;
right: 0;
function handleDropCard() {
dropAudioRef.value?.play()
}
function handleWin() {
winAudioRef.value?.play()
// fireworks()
if (curLevel.value < LevelConfig.length) {
basicCannon()
showTip.value = true
setTimeout(() => {
showTip.value = false
}, 1500)
setTimeout(() => {
initData(LevelConfig[curLevel.value])
curLevel.value++
}, 2000)
}
else {
isWin.value = true
schoolPride()
}
}
function handleLose() {
loseAudioRef.value?.play()
setTimeout(() => {
alert('槽位已满,再接再厉~')
// window.location.reload()
nodes.value = []
removeList.value = []
selectedNodes.value = []
curLevel.value = 0
showTip.value = true
setTimeout(() => {
showTip.value = false
}, 1500)
setTimeout(() => {
initData(LevelConfig[curLevel.value])
curLevel.value++
}, 2000)
}, 500)
}
onMounted(() => {
initData()
})
</script>
<template>
<div flex flex-col w-full h-full>
<div text-44px text-center w-full color="#000" fw-600 h-60px flex items-center justify-center mt-10px>
兔了个兔
</div>
<div ref="containerRef" flex-1 flex>
<div w-full relative flex-1>
<template v-for="item in nodes" :key="item.id">
<transition name="slide-fade">
<Card
v-if="[0, 1].includes(item.state)"
:node="item"
@click-card="handleSelect"
/>
</transition>
</template>
</div>
<transition name="bounce">
<div v-if="isWin" color="#000" flex items-center justify-center w-full text-28px fw-bold>
成功加入兔圈~
</div>
</transition>
<transition name="bounce">
<div v-if="showTip" color="#000" flex items-center justify-center w-full text-28px fw-bold>
{{ curLevel + 1 }}
</div>
</transition>
</div>
<div text-center h-50px flex items-center justify-center>
<Card
v-for="item in removeList" :key="item.id" :node="item"
is-dock
@click-card="handleSelectRemove"
/>
</div>
<div w-full flex items-center justify-center>
<div border="~ 4px dashed #000" w-295px h-44px flex>
<template v-for="item in selectedNodes" :key="item.id">
<transition name="bounce">
<Card
v-if="item.state === 2"
:node="item"
is-dock
/>
</transition>
</template>
</div>
</div>
<div h-50px flex items-center w-full justify-center>
<button :disabled="removeFlag" mr-10px @click="handleRemove">
移出前三个
</button>
<button :disabled="backFlag" @click="handleBack">
回退
</button>
</div>
<div w-full color="#000" fw-600 text-center pb-10px>
<span mr-20px>designer: Teacher Face</span>
by: Xc
<a
class="icon-btn"
color="#000"
i-carbon-logo-github
rel="noreferrer"
href="https://github.com/chenxch"
target="_blank"
title="GitHub"
/>
<span
text-12px
color="#00000018"
>
<span
class="icon-btn"
text-2
i-carbon:arrow-up-left
/>
star buff</span>
</div>
<audio
ref="clickAudioRef"
style="display: none;"
controls
src="./audio/click.mp3"
/>
<audio
ref="dropAudioRef"
style="display: none;"
controls
src="./audio/drop.mp3"
/>
<audio
ref="winAudioRef"
style="display: none;"
controls
src="./audio/win.mp3"
/>
<audio
ref="loseAudioRef"
style="display: none;"
controls
src="./audio/lose.mp3"
/>
</div>
</template>
<style>
body{
background-color: #c3fe8b;
}
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
.slide-fade-enter-active {
transition: all 0.2s ease-out;
}
.slide-fade-leave-active {
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateY(25vh);
opacity: 0;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>

BIN
src/assets/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
src/assets/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
src/assets/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/11.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
src/assets/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
src/assets/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
src/assets/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
src/assets/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
src/assets/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
src/assets/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
src/assets/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
src/assets/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

BIN
src/assets/tutu/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
src/assets/tutu/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/assets/tutu/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/tutu/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/tutu/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/tutu/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
src/assets/tutu/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/assets/tutu/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
src/assets/tutu/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
src/assets/tutu/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
src/assets/tutu/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
src/assets/tutu/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
src/assets/tutu/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
src/assets/tutu2/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
src/assets/tutu2/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
src/assets/tutu2/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/assets/tutu2/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/tutu2/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/assets/tutu2/14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/tutu2/15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/assets/tutu2/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
src/assets/tutu2/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src/assets/tutu2/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
src/assets/tutu2/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
src/assets/tutu2/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
src/assets/tutu2/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
src/assets/tutu2/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src/assets/tutu2/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

75
src/components/card.vue Normal file
View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<Props>()
const emit = defineEmits(['clickCard'])
// 加载图片资源
const modules = import.meta.glob('../assets/tutu2/*.png', {
as: 'url',
import: 'default',
eager: true,
})
const IMG_MAP = Object.keys(modules).reduce((acc, cur) => {
const key = cur.replace('../assets/tutu2/', '').replace('.png', '')
acc[key] = modules[cur]
return acc
}, {} as Record<string, string>)
interface Props {
node: CardNode
isDock?: boolean
}
const isFreeze = computed(() => {
return props.node.parents.length > 0 ? props.node.parents.some(o => o.state < 2) : false
},
)
function handleClick() {
if (!isFreeze.value)
emit('clickCard', props.node)
}
</script>
<template>
<div
class="card"
:style="isDock ? {} : { position: 'absolute', zIndex: node.zIndex, top: `${node.top}px`, left: `${node.left}px` }"
@click="handleClick"
>
<!-- {{ node.zIndex }}-{{ node.type }} -->
<!-- {{ node.id }} -->
<img :src="IMG_MAP[node.type]" width="40" height="40" :alt="`${node.type}`">
<div v-if="isFreeze" class="mask" />
</div>
</template>
<style scoped>
.card{
width: 40px;
height: 40px;
/* border: 1px solid red; */
background: #f9f7e1;
color:#000;
display: flex;
align-items: center;
justify-content: center;
position: relative;
border-radius: 4px;
border: 1px solid #000;
box-shadow: 1px 5px 5px -1px #000;
cursor: pointer;
}
img{
border-radius: 4px;
}
.mask {
position: absolute;
z-index: 1;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.55);
width: 40px;
height: 40px;
pointer-events: none;
}
</style>

View File

@@ -1,14 +0,0 @@
import { RouteRecordRaw } from "vue-router";
import IndexPage from "../pages/IndexPage.vue";
import GamePage from "../pages/GamePage.vue";
export default [
{
path: "/",
component: IndexPage,
},
{
path: "/game",
component: GamePage,
},
] as RouteRecordRaw[];

View File

@@ -1,368 +0,0 @@
/**
* 游戏逻辑 V2不固定 level
*
* @author yupi https://github.com/liyupi
*/
import { useGlobalStore } from "./globalStore";
// @ts-ignore
import _ from "lodash";
import { nextTick, ref } from "vue";
const useGame = () => {
const { gameConfig } = useGlobalStore();
// 游戏状态0 - 初始化, 1 - 进行中, 2 - 结束
const gameStatus = ref(0);
// 各层块
const levelBlocksVal = ref<BlockType[]>([]);
// 随机区块
const randomBlocksVal = ref<BlockType[][]>([]);
// 插槽区
const slotAreaVal = ref<BlockType[]>([]);
// 当前槽占用数
const currSlotNum = ref(0);
// 保存所有块(包括随机块)
const blockData: Record<number, BlockType> = {};
// 总共划分 24 x 24 的格子,每个块占 3 x 3 的格子,生成的起始 x 和 y 坐标范围均为 0 ~ 21
const boxWidthNum = 24;
const boxHeightNum = 24;
// 每个格子的宽高
const widthUnit = 14;
const heightUnit = 14;
// 保存整个 "棋盘" 的每个格子状态(下标为格子起始点横纵坐标)
let chessBoard: ChessBoardUnitType[][] = [];
/**
* 初始化指定大小的棋盘
* @param width
* @param height
*/
const initChessBoard = (width: number, height: number) => {
chessBoard = new Array(width);
for (let i = 0; i < width; i++) {
chessBoard[i] = new Array(height);
for (let j = 0; j < height; j++) {
chessBoard[i][j] = {
blocks: [],
};
}
}
};
// 初始化棋盘
initChessBoard(boxWidthNum, boxHeightNum);
/**
* 游戏初始化
*/
const initGame = () => {
console.log("initGame", gameConfig);
// 0. 设置父容器宽高
const levelBoardDom: any = document.getElementsByClassName("level-board");
levelBoardDom[0].style.width = widthUnit * boxWidthNum + "px";
levelBoardDom[0].style.height = heightUnit * boxHeightNum + "px";
// 1. 规划块数
// 块数单位(总块数必须是该值的倍数)
const blockNumUnit = gameConfig.composeNum * gameConfig.typeNum;
console.log("块数单位", blockNumUnit);
// 随机生成的总块数
const totalRandomBlockNum = gameConfig.randomBlocks.reduce((pre, curr) => {
return pre + curr;
}, 0);
console.log("随机生成的总块数", totalRandomBlockNum);
// 需要的最小块数
const minBlockNum =
gameConfig.levelNum * gameConfig.levelBlockNum + totalRandomBlockNum;
console.log("需要的最小块数", minBlockNum);
// 补齐到 blockNumUnit 的倍数
// e.g. minBlockNum = 14, blockNumUnit = 6, 补到 18
let totalBlockNum = minBlockNum;
if (totalBlockNum % blockNumUnit !== 0) {
totalBlockNum =
(Math.floor(minBlockNum / blockNumUnit) + 1) * blockNumUnit;
}
console.log("总块数", totalBlockNum);
// 2. 初始化块,随机生成块的内容
// 保存所有块的数组
const animalBlocks: string[] = [];
// 需要用到的动物数组
const needAnimals = gameConfig.animals.slice(0, gameConfig.typeNum);
// 依次把块塞到数组里
for (let i = 0; i < totalBlockNum; i++) {
animalBlocks.push(needAnimals[i % gameConfig.typeNum]);
}
// 打乱数组
const randomAnimalBlocks = _.shuffle(animalBlocks);
// 初始化
const allBlocks: BlockType[] = [];
for (let i = 0; i < totalBlockNum; i++) {
const newBlock = {
id: i,
status: 0,
level: 0,
type: randomAnimalBlocks[i],
higherThanBlocks: [] as BlockType[],
lowerThanBlocks: [] as BlockType[],
} as BlockType;
allBlocks.push(newBlock);
}
// 下一个要塞入的块
let pos = 0;
// 3. 计算随机生成的块
const randomBlocks: BlockType[][] = [];
gameConfig.randomBlocks.forEach((randomBlock, idx) => {
randomBlocks[idx] = [];
for (let i = 0; i < randomBlock; i++) {
randomBlocks[idx].push(allBlocks[pos]);
blockData[pos] = allBlocks[pos];
pos++;
}
});
// 剩余块数
let leftBlockNum = totalBlockNum - totalRandomBlockNum;
// 4. 计算有层级关系的块
const levelBlocks: BlockType[] = [];
let minX = 0;
let maxX = 22;
let minY = 0;
let maxY = 22;
// 分为 gameConfig.levelNum 批,依次生成,每批的边界不同
for (let i = 0; i < gameConfig.levelNum; i++) {
let nextBlockNum = Math.min(gameConfig.levelBlockNum, leftBlockNum);
// 最后一批,分配所有 leftBlockNum
if (i == gameConfig.levelNum - 1) {
nextBlockNum = leftBlockNum;
}
// 边界收缩
if (gameConfig.borderStep > 0) {
const dir = i % 4;
if (i > 0) {
if (dir === 0) {
minX += gameConfig.borderStep;
} else if (dir === 1) {
maxY -= gameConfig.borderStep;
} else if (dir === 2) {
minY += gameConfig.borderStep;
} else {
maxX -= gameConfig.borderStep;
}
}
}
const nextGenBlocks = allBlocks.slice(pos, pos + nextBlockNum);
levelBlocks.push(...nextGenBlocks);
pos = pos + nextBlockNum;
// 生成块的坐标
genLevelBlockPos(nextGenBlocks, minX, minY, maxX, maxY);
leftBlockNum -= nextBlockNum;
if (leftBlockNum <= 0) {
break;
}
}
console.log("最终剩余块数", leftBlockNum);
// 4. 初始化空插槽
const slotArea: BlockType[] = new Array(gameConfig.slotNum).fill(null);
console.log("随机块情况", randomBlocks);
return {
levelBlocks,
randomBlocks,
slotArea,
};
};
/**
* 生成一批层级块(坐标、层级关系)
* @param blocks
* @param minX
* @param minY
* @param maxX
* @param maxY
*/
const genLevelBlockPos = (
blocks: BlockType[],
minX: number,
minY: number,
maxX: number,
maxY: number
) => {
// 记录这批块的坐标,用于保证同批次元素不能完全重叠
const currentPosSet = new Set<string>();
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
// 随机生成坐标
let newPosX;
let newPosY;
let key;
while (true) {
newPosX = Math.floor(Math.random() * maxX + minX);
newPosY = Math.floor(Math.random() * maxY + minY);
key = newPosX + "," + newPosY;
// 同批次元素不能完全重叠
if (!currentPosSet.has(key)) {
break;
}
}
chessBoard[newPosX][newPosY].blocks.push(block);
currentPosSet.add(key);
block.x = newPosX;
block.y = newPosY;
// 填充层级关系
genLevelRelation(block);
}
};
/**
* 给块绑定层级关系(用于确认哪些元素是当前可点击的)
* 核心逻辑:每个块压住和其坐标有交集棋盘格内所有 level 大于它的点,双向建立联系
* @param block
*/
const genLevelRelation = (block: BlockType) => {
// 确定该块附近的格子坐标范围
const minX = Math.max(block.x - 2, 0);
const minY = Math.max(block.y - 2, 0);
const maxX = Math.min(block.x + 3, boxWidthNum - 2);
const maxY = Math.min(block.y + 3, boxWidthNum - 2);
// 遍历该块附近的格子
let maxLevel = 0;
for (let i = minX; i < maxX; i++) {
for (let j = minY; j < maxY; j++) {
const relationBlocks = chessBoard[i][j].blocks;
if (relationBlocks.length > 0) {
// 取当前位置最高层的块
const maxLevelRelationBlock =
relationBlocks[relationBlocks.length - 1];
// 排除自己
if (maxLevelRelationBlock.id === block.id) {
continue;
}
maxLevel = Math.max(maxLevel, maxLevelRelationBlock.level);
block.higherThanBlocks.push(maxLevelRelationBlock);
maxLevelRelationBlock.lowerThanBlocks.push(block);
}
}
}
// 比最高层的块再高一层(初始为 1
block.level = maxLevel + 1;
};
/**
* 点击块事件
* @param block
* @param e
* @param randomIdx 随机区域下标,>= 0 表示点击的是随机块
*/
const doClickBlock = (block: BlockType, e: Event, randomIdx = -1) => {
// 已经输了 / 已经被点击 / 有上层块,不能再点击
if (
currSlotNum.value >= gameConfig.slotNum ||
block.status !== 0 ||
block.lowerThanBlocks.length > 0
) {
return;
}
// 修改元素状态为已点击
block.status = 1;
// 移除当前元素
if (randomIdx >= 0) {
// 移除所点击的随机区域的第一个元素
randomBlocksVal.value[randomIdx] = randomBlocksVal.value[randomIdx].slice(
1,
randomBlocksVal.value[randomIdx].length
);
} else {
// 删除节点
// @ts-ignore
e.target.remove();
// 移除覆盖关系
block.higherThanBlocks.forEach((higherThanBlock) => {
_.remove(higherThanBlock.lowerThanBlocks, (lowerThanBlock) => {
return lowerThanBlock.id === block.id;
});
});
}
// 新元素加入插槽
let tempSlotNum = currSlotNum.value;
slotAreaVal.value[tempSlotNum] = block;
// 检查是否有可消除的
// block => 出现次数
const map: Record<string, number> = {};
// 去除空槽
const tempSlotAreaVal = slotAreaVal.value.filter(
(slotBlock) => !!slotBlock
);
tempSlotAreaVal.forEach((slotBlock) => {
const type = slotBlock.type;
if (!map[type]) {
map[type] = 1;
} else {
map[type]++;
}
});
console.log("tempSlotAreaVal", tempSlotAreaVal);
console.log("map", map);
// 得到新数组
const newSlotAreaVal = new Array(gameConfig.slotNum).fill(null);
tempSlotNum = 0;
tempSlotAreaVal.forEach((slotBlock) => {
// 成功消除(不添加到新数组中)
if (map[slotBlock.type] >= gameConfig.composeNum) {
// 块状态改为已消除
slotBlock.status = 2;
return;
}
newSlotAreaVal[tempSlotNum++] = slotBlock;
});
slotAreaVal.value = newSlotAreaVal;
currSlotNum.value = tempSlotNum;
// 游戏结束
if (tempSlotNum >= gameConfig.slotNum) {
gameStatus.value = 2;
setTimeout(() => {
alert("你输了");
}, 2000);
}
};
/**
* 开始游戏
*/
const doStart = () => {
gameStatus.value = 0;
const { levelBlocks, randomBlocks, slotArea } = initGame();
console.log(levelBlocks, randomBlocks, slotArea);
levelBlocksVal.value = levelBlocks;
randomBlocksVal.value = randomBlocks;
slotAreaVal.value = slotArea;
gameStatus.value = 1;
};
return {
gameStatus,
levelBlocksVal,
randomBlocksVal,
slotAreaVal,
widthUnit,
heightUnit,
doClickBlock,
doStart,
};
};
export default useGame;

View File

@@ -1,131 +0,0 @@
// 动物数组
const animals = [
"🐔",
"🐟",
"🦆",
"🐶",
"🐱",
"🐴",
"🐑",
"🐦",
"🐧",
"🐊",
"🐺",
"🐒",
"🐳",
"🐬",
"🐢",
"🦖",
"🦒",
"🦁",
"🐍",
"🐭",
"🐂",
];
export const defaultGameConfig: GameConfig = {
// 槽容量
slotNum: 7,
// 需要多少个一样块的才能合成
composeNum: 3,
// 动物类别数
typeNum: 12,
// 每层块数(大致)
levelBlockNum: 24,
// 边界收缩步长
borderStep: 1,
// 总层数(最小为 2
levelNum: 6,
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: [8, 8],
// 动物数组
animals,
};
/**
* 简单难度
*/
export const easyGameConfig: GameConfig = {
// 槽容量
slotNum: 7,
// 需要多少个一样块的才能合成
composeNum: 3,
// 动物类别数
typeNum: 8,
// 每层块数(大致)
levelBlockNum: 10,
// 边界收缩步长
borderStep: 1,
// 总层数(最小为 2
levelNum: 6,
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: [4, 4],
// 动物数组
animals,
};
/**
* 中等难度
*/
export const middleGameConfig: GameConfig = {
// 槽容量
slotNum: 7,
// 需要多少个一样块的才能合成
composeNum: 3,
// 动物类别数
typeNum: 10,
// 每层块数(大致)
levelBlockNum: 12,
// 边界收缩步长
borderStep: 1,
// 总层数(最小为 2
levelNum: 7,
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: [5, 5],
// 动物数组
animals,
};
/**
* 困难难度
*/
export const hardGameConfig: GameConfig = {
// 槽容量
slotNum: 7,
// 需要多少个一样块的才能合成
composeNum: 3,
// 动物类别数
typeNum: 12,
// 每层块数(大致)
levelBlockNum: 16,
// 边界收缩步长
borderStep: 1,
// 总层数(最小为 2
levelNum: 8,
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: [6, 6],
// 动物数组
animals,
};
/**
* 地狱难度
*/
export const lunaticGameConfig: GameConfig = {
// 槽容量
slotNum: 7,
// 需要多少个一样块的才能合成
composeNum: 3,
// 动物类别数
typeNum: 14,
// 每层块数(大致)
levelBlockNum: 20,
// 边界收缩步长
borderStep: 2,
// 总层数(最小为 2
levelNum: 10,
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: [8, 8],
// 动物数组
animals,
};

View File

@@ -1,37 +0,0 @@
import { defineStore } from "pinia";
import { defaultGameConfig } from "./gameConfig";
/**
* 全局状态存储
*
* @author yupi
*/
export const useGlobalStore = defineStore("global", {
state: () => ({
customConfig: { ...defaultGameConfig },
gameConfig: { ...defaultGameConfig },
}),
getters: {},
// 持久化
persist: {
key: "global",
storage: window.localStorage,
beforeRestore: (context) => {
console.log("load globalStore data start");
},
afterRestore: (context) => {
console.log("load globalStore data end");
},
},
actions: {
setGameConfig(gameConfig: GameConfig) {
this.gameConfig = gameConfig;
},
setCustomConfig(customConfig: GameConfig) {
this.customConfig = customConfig;
},
reset() {
this.$reset();
},
},
});

50
src/core/type.d.ts vendored
View File

@@ -1,50 +0,0 @@
/**
* 块类型
*/
interface BlockType {
id: number;
x: number;
y: number;
level: number;
type: string;
// 0 - 正常, 1 - 已点击, 2 - 已消除
status: 0 | 1 | 2;
// 压住的其他块
higherThanBlocks: BlockType[];
// 被哪些块压住(为空表示可点击)
lowerThanBlocks: BlockType[];
}
/**
* 每个格子单元类型
*/
interface ChessBoardUnitType {
// 放到当前格子里的块(层级越高下标越大)
blocks: BlockType[];
}
/**
* 游戏配置
*/
interface GameConfig {
// 槽容量
slotNum: number;
// 需要多少个一样块的才能合成
composeNum: number;
// 动物类别数
typeNum: number;
// 每层块数(大致)
levelBlockNum: number;
// 边界收缩步长
borderStep: number;
// 总层数(最小为 2
levelNum: number;
// 随机区块数(数组长度代表随机区数量,值表示每个随机区生产多少块)
randomBlocks: number[];
// 动物数组
animals: string[];
// 最上层块数(已废弃)
// topBlockNum: 40,
// 最下层块数最小值(已废弃)
// minBottomBlockNum: 20,
}

Some files were not shown because too many files have changed in this diff Show More