├── .gitignore
├── .vscode
└── extensions.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── auto-imports.d.ts
├── docs
├── README_CN.md
├── README_JP.md
└── README_KR.md
├── eslint.config.ts
├── gameImg
├── changeBackground.png
├── choose.png
├── drop.png
├── explain.png
├── final.png
├── gameinfo.png
├── lose.png
├── menu.png
├── producer.png
├── role.png
└── win.png
├── index.html
├── niva.json
├── package.json
├── pnpm-lock.yaml
├── public
├── king.png
└── king.svg
├── src
├── App.vue
├── assets
│ ├── bg-1.jpg
│ ├── bg-2.jpg
│ ├── bg-3.jpg
│ ├── bg-4.jpg
│ ├── card-bg.jpg
│ ├── citizen.jpg
│ ├── emperor.jpg
│ ├── music
│ │ ├── 发牌.wav
│ │ ├── 抽牌.wav
│ │ └── 洗牌.wav
│ └── slave.jpg
├── i18n
│ ├── cn.ts
│ ├── en.ts
│ ├── index.ts
│ ├── jp.ts
│ └── kr.ts
├── main.ts
├── router
│ └── index.ts
├── store
│ └── index.ts
├── styles
│ └── base.css
├── utils
│ ├── game.util.ts
│ └── index.ts
├── views
│ ├── Component
│ │ ├── Card.vue
│ │ ├── CheckCard.vue
│ │ ├── ComputedCard.vue
│ │ ├── DropCard.vue
│ │ ├── GameExplain.vue
│ │ ├── GameInformation.vue
│ │ ├── GameMenu.vue
│ │ ├── MessageInfo.vue
│ │ ├── PlayerCard.vue
│ │ └── ProducerList.vue
│ ├── Game
│ │ ├── GameMain.vue
│ │ └── Home.vue
│ ├── Layout
│ │ ├── GameContent.vue
│ │ └── GameNav.vue
│ ├── NavButton
│ │ ├── ChangeBackImage.vue
│ │ ├── ChangeGameStatus.vue
│ │ ├── ChangeLanguage.vue
│ │ ├── ColorSchemeToggle.vue
│ │ ├── FullScreenToggle.vue
│ │ ├── GameInfoToggle.vue
│ │ └── GitHubButton.vue
│ ├── Others
│ │ └── 404.vue
│ └── Type
│ │ ├── cardType.ts
│ │ ├── gameType.ts
│ │ ├── groupType.ts
│ │ ├── index.ts
│ │ ├── logType.ts
│ │ ├── roleType.ts
│ │ └── stateType.ts
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── uno.config.ts
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json*
8 | yarn-lock.json*
9 | pnpm-debug.log*
10 | lerna-debug.log*
11 |
12 | node_modules
13 | dist
14 | dist-ssr
15 | *.local
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
28 | # Build
29 | ECard.zip
30 | ECard.app
31 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | // my extensions, ofc :P
4 | "antfu.browse-lite",
5 | "antfu.iconify",
6 | "antfu.slidev",
7 | "antfu.unocss",
8 | "antfu.vite",
9 | "antfu.where-am-i",
10 | "antfu.open-in-github-button",
11 | "lokalise.i18n-ally",
12 |
13 | // themes & icons
14 | "antfu.icons-carbon",
15 | "antfu.theme-vitesse",
16 | "file-icons.file-icons",
17 | "sainnhe.gruvbox-material",
18 |
19 | // life savers!
20 | "dbaeumer.vscode-eslint",
21 | "Vue.volar",
22 | "GitHub.copilot",
23 | "usernamehw.errorlens",
24 | "streetsidesoftware.code-spell-checker",
25 |
26 | // up to you
27 | "eamodio.gitlens",
28 | "EditorConfig.EditorConfig",
29 | "github.vscode-github-actions",
30 | "GitHub.vscode-pull-request-github",
31 | "johnsoncodehk.vscode-tsconfig-helper",
32 | "mpontus.tab-cycle",
33 | "naumovs.color-highlight",
34 | "WakaTime.vscode-wakatime",
35 | "znck.grammarly"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG.md
2 |
3 | ## 1.0.0 Release
4 |
5 | ### Features
6 |
7 | - 布局和基础功能按钮开发
8 | - 手牌区域组件开发
9 | - 检查区域组件开发
10 | - 对局信息组件开发
11 | - 对局开始组件,开始随机从国王和奴隶中随机取 `1` 个作为玩家的开始角色,之后角色采用轮换制,共 `21` 局
12 | - 弃牌区域组件开发,无牌虚线+文字显示,有牌按照 `0° - 30°` 随机角度旋转堆叠显示
13 | - 手牌选中卡牌后, 显示 `check` 按钮
14 | - 点击 check 按钮后,卡牌中删除该卡牌,检查区域增加该卡牌
15 | - 卡牌实现 `3D` 效果,`check` 后翻转在检查区域显示,过 `2` 秒后检查区域卡牌翻转至正面比对,比对后移至弃牌区域
16 | - 如果国王牌或者奴隶牌打出,则比赛结束
17 | - 进行对局信息结算,进行下一局游戏
18 | - 游戏结束,显示对局信息,并可重新开始游戏
19 | - 菜单增加游戏规则和制作人名单按钮,并增加相对应内容
20 |
21 | ## 1.1.0 Release
22 |
23 | ### Features
24 |
25 | - 新增多语言配置
26 |
27 | ## 1.2.0 Release
28 |
29 | ### Features
30 |
31 | - 新增 electron 版本
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 SmallTeddy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # An E-Card mini-game imitating "Gambling Apocalypse"
3 |
4 | [Chinese Doc](./docs/README_CN.md) | **English Doc** | [Japanese Doc](./docs/README_JP.md) | [Korean Doc](./docs/README_KR.md)
5 |
6 | ## develop tools
7 |
8 | | Tools | Instructions | Official website |
9 | |--------|-----------------| - |
10 | | icon | icon | [https://icones.js.org/collection/all](https://icones.js.org/collection/all) |
11 | | vueuse | tools function | [https://vueuse.org/functions.html](https://vueuse.org/functions.html) |
12 | | unocss | atomization css | [https://unocss.dev/interactive/](https://unocss.dev/interactive/) |
13 | | grid | grid layout | [https://cssgrid-generator.netlify.app/](https://cssgrid-generator.netlify.app/) |
14 |
15 | ## todo list
16 |
17 | - [x] Layout and basic function button development
18 | - [x] Hand card area component development
19 | - [x] Check area component development
20 | - [x] Game information component development
21 | - [x] Game start component, randomly select `1` from King and Slave to start the player's role, then roles rotate, with a total of `21` rounds
22 | - [x] Discard area component development, with dashed lines and text display for no cards, and cards are stacked and displayed at random angles from `0° - 30°` when there are cards
23 | - [x] After selecting a card from the hand, the `check` button is displayed
24 | - [x] After clicking the `check` button, the card is removed from the hand and added to the check area
25 | - [x] Cards implement `3D` effects, flip after `check` to display in the check area, flip to the front for comparison after `2` seconds, and then move to the discard area after comparison
26 | - [ ] Animation effects for cards moving from the hand area to the check area, and from the check area to the discard area across components
27 | - [x] If the King card or Slave card is played, the game ends
28 | - [x] Game information is settled, and the next round of the game begins
29 | - [ ] After the player has played `5` rounds, the computer will analyze the player's card-playing strategy based on the data from the previous `5` rounds and each subsequent round to decide its own card-playing strategy
30 | - [x] Game ends, display game information, and allow for a new game to start
31 | - [x] Menu adds a game rules and credits button, with corresponding content
32 | - [x] Support for multiple languages
33 | - [ ] `web` end adaptation
34 | - [ ] Add appropriate background music and playing music
35 | - [ ] You can choose the game difficulty, the default is medium
36 |
37 | ## game preview
38 |
39 | 
40 |
41 | 
42 |
43 | 
44 |
45 | 
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 | 
54 |
55 | 
56 |
57 | 
58 |
59 | 
60 |
--------------------------------------------------------------------------------
/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // noinspection JSUnusedGlobalSymbols
5 | // Generated by unplugin-auto-import
6 | export {}
7 | declare global {
8 | const EffectScope: typeof import('vue')['EffectScope']
9 | const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
10 | const computed: typeof import('vue')['computed']
11 | const createApp: typeof import('vue')['createApp']
12 | const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
13 | const createI18n: typeof import('vue-i18n')['createI18n']
14 | const createPinia: typeof import('pinia')['createPinia']
15 | const createRouter: typeof import('vue-router')['createRouter']
16 | const createWebHistory: typeof import('vue-router')['createWebHistory']
17 | const customRef: typeof import('vue')['customRef']
18 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
19 | const defineComponent: typeof import('vue')['defineComponent']
20 | const defineStore: typeof import('pinia')['defineStore']
21 | const effectScope: typeof import('vue')['effectScope']
22 | const getActivePinia: typeof import('pinia')['getActivePinia']
23 | const getCurrentInstance: typeof import('vue')['getCurrentInstance']
24 | const getCurrentScope: typeof import('vue')['getCurrentScope']
25 | const h: typeof import('vue')['h']
26 | const inject: typeof import('vue')['inject']
27 | const isProxy: typeof import('vue')['isProxy']
28 | const isReactive: typeof import('vue')['isReactive']
29 | const isReadonly: typeof import('vue')['isReadonly']
30 | const isRef: typeof import('vue')['isRef']
31 | const mapActions: typeof import('pinia')['mapActions']
32 | const mapGetters: typeof import('pinia')['mapGetters']
33 | const mapState: typeof import('pinia')['mapState']
34 | const mapStores: typeof import('pinia')['mapStores']
35 | const mapWritableState: typeof import('pinia')['mapWritableState']
36 | const markRaw: typeof import('vue')['markRaw']
37 | const nextTick: typeof import('vue')['nextTick']
38 | const onActivated: typeof import('vue')['onActivated']
39 | const onBeforeMount: typeof import('vue')['onBeforeMount']
40 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
41 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
42 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
43 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
44 | const onDeactivated: typeof import('vue')['onDeactivated']
45 | const onErrorCaptured: typeof import('vue')['onErrorCaptured']
46 | const onMounted: typeof import('vue')['onMounted']
47 | const onRenderTracked: typeof import('vue')['onRenderTracked']
48 | const onRenderTriggered: typeof import('vue')['onRenderTriggered']
49 | const onScopeDispose: typeof import('vue')['onScopeDispose']
50 | const onServerPrefetch: typeof import('vue')['onServerPrefetch']
51 | const onUnmounted: typeof import('vue')['onUnmounted']
52 | const onUpdated: typeof import('vue')['onUpdated']
53 | const provide: typeof import('vue')['provide']
54 | const reactive: typeof import('vue')['reactive']
55 | const readonly: typeof import('vue')['readonly']
56 | const ref: typeof import('vue')['ref']
57 | const resolveComponent: typeof import('vue')['resolveComponent']
58 | const setActivePinia: typeof import('pinia')['setActivePinia']
59 | const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
60 | const shallowReactive: typeof import('vue')['shallowReactive']
61 | const shallowReadonly: typeof import('vue')['shallowReadonly']
62 | const shallowRef: typeof import('vue')['shallowRef']
63 | const storeToRefs: typeof import('pinia')['storeToRefs']
64 | const toRaw: typeof import('vue')['toRaw']
65 | const toRef: typeof import('vue')['toRef']
66 | const toRefs: typeof import('vue')['toRefs']
67 | const toValue: typeof import('vue')['toValue']
68 | const triggerRef: typeof import('vue')['triggerRef']
69 | const unref: typeof import('vue')['unref']
70 | const useAttrs: typeof import('vue')['useAttrs']
71 | const useColorMode: typeof import('@vueuse/core')['useColorMode']
72 | const useCssModule: typeof import('vue')['useCssModule']
73 | const useCssVars: typeof import('vue')['useCssVars']
74 | const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
75 | const useGlobalState: typeof import('@/store')['useGlobalState']
76 | const useI18n: typeof import('vue-i18n')['useI18n']
77 | const useLink: typeof import('vue-router')['useLink']
78 | const useRoute: typeof import('vue-router')['useRoute']
79 | const useRouter: typeof import('vue-router')['useRouter']
80 | const useSlots: typeof import('vue')['useSlots']
81 | const useStorage: typeof import('@vueuse/core')['useStorage']
82 | const watch: typeof import('vue')['watch']
83 | const watchEffect: typeof import('vue')['watchEffect']
84 | const watchPostEffect: typeof import('vue')['watchPostEffect']
85 | const watchSyncEffect: typeof import('vue')['watchSyncEffect']
86 | }
87 | // for type re-export
88 | declare global {
89 | // @ts-ignore
90 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
91 | import('vue')
92 | }
93 |
--------------------------------------------------------------------------------
/docs/README_CN.md:
--------------------------------------------------------------------------------
1 |
2 | # 仿照《赌博默示录》的 E-Card小游戏
3 |
4 | **中文文档** | [英文文档](../README.md) | [日文文档](./README_JP.md) | [韩文文档](./README_KR.md)
5 |
6 | ## 开发工具
7 |
8 | | 工具 | 说明 | 官网 |
9 | | --- |-------| --- |
10 | | icon | 图标 | [https://icones.js.org/collection/all](https://icones.js.org/collection/all) |
11 | | vueuse | 工具函数 | [https://vueuse.org/functions.html](https://vueuse.org/functions.html) |
12 | | unocss | 原子化样式 | [https://unocss.dev/interactive/](https://unocss.dev/interactive/) |
13 | | grid | 网格布局 | [https://cssgrid-generator.netlify.app/](https://cssgrid-generator.netlify.app/) |
14 |
15 | ## 待办列表
16 |
17 | - [x] 布局和基础功能按钮开发
18 | - [x] 手牌区域组件开发
19 | - [x] 检查区域组件开发
20 | - [x] 对局信息组件开发
21 | - [x] 对局开始组件,开始随机从国王和奴隶中随机取 `1` 个作为玩家的开始角色,之后角色采用轮换制,共 `21` 局
22 | - [x] 弃牌区域组件开发,无牌虚线+文字显示,有牌按照 `0° - 30°` 随机角度旋转堆叠显示
23 | - [x] 手牌选中卡牌后, 显示 `check` 按钮
24 | - [x] 点击 check 按钮后,卡牌中删除该卡牌,检查区域增加该卡牌
25 | - [x] 卡牌实现 `3D` 效果,`check` 后翻转在检查区域显示,过 `2` 秒后检查区域卡牌翻转至正面比对,比对后移至弃牌区域
26 | - [ ] 卡牌从手牌到检查区域,从检查区域到弃牌区域跨组件动画效果
27 | - [x] 如果国王牌或者奴隶牌打出,则比赛结束
28 | - [x] 进行对局信息结算,进行下一局游戏
29 | - [ ] 玩家玩过 `5` 局之后,电脑会根据前 `5` 局的数据,和之后每 `1` 局的数据,对玩家进行出牌分析,来决定自己每次出牌的策略
30 | - [x] 游戏结束,显示对局信息,并可重新开始游戏
31 | - [x] 菜单增加游戏规则和制作人名单按钮,并增加相对应内容
32 | - [x] 支持多语言
33 | - [ ] `web` 端适配
34 | - [ ] 添加合适的背景音乐和出牌音乐
35 | - [ ] 可以选择游戏难度,默认为中等
36 | - [ ] 优化动画
37 |
38 | ## 游戏预览
39 |
40 | 
41 |
42 | 
43 |
44 | 
45 |
46 | 
47 |
48 | 
49 |
50 | 
51 |
52 | 
53 |
54 | 
55 |
56 | 
57 |
58 | 
59 |
60 | 
61 |
--------------------------------------------------------------------------------
/docs/README_JP.md:
--------------------------------------------------------------------------------
1 |
2 | # 「賭博黙示録」を模したEカードミニゲーム
3 |
4 | [中国語文書](./README_CN.md) | [英語文書](../README.md) | **日本語文書** | [韓語文書](./README_KR.md)
5 |
6 | ## ツールを開発する
7 |
8 | | ツール | 説明する | 公式ウェブサイト |
9 | | --- |-----------| --- |
10 | | icon | アイコン | [https://icones.js.org/collection/all](https://icones.js.org/collection/all) |
11 | | vueuse | 効用関数 | [https://vueuse.org/functions.html](https://vueuse.org/functions.html) |
12 | | unocss | 霧化スタイル | [https://unocss.dev/interactive/](https://unocss.dev/interactive/) |
13 | | grid | グリッドレイアウト | [https://cssgrid-generator.netlify.app/](https://cssgrid-generator.netlify.app/) |
14 |
15 | ## すべてのリスト
16 |
17 | - [x] レイアウトと基本機能ボタンの開発
18 | - [x] 手札エリアコンポーネントの開発
19 | - [x] チェックエリアコンポーネントの開発
20 | - [x] 対局情報コンポーネントの開発
21 | - [x] 対局開始コンポーネント、最初の役割は「王」または「奴隷」からランダムに1つ選択し、その後は21局で役割がローテーションします。
22 | - [x] 捨て札エリアコンポーネントの開発、札がない場合は点線とテキストを表示し、札がある場合は0°から30°のランダムな角度で積み重ねて表示します。
23 | - [x] 手札からカードを選択すると、`check`ボタンが表示されます。
24 | - [x] `check`ボタンをクリックすると、カードは手札から削除され、チェックエリアに追加されます。
25 | - [x] カードは`3D`効果を実装し、`check`後にチェックエリアに表示されるように反転します。2秒後にカードは正面に反転して比較し、その後捨て札エリアに移動します。
26 | - [ ] 手札エリアからチェックエリア、チェックエリアから捨て札エリアへのアニメーション効果
27 | - [x] 王のカードまたは奴隷のカードが出たら、ゲームが終了します。
28 | - [x] 対局情報を決算し、次のラウンドを開始します。
29 | - [ ] プレイヤーが5ラウンドプレイした後、コンピュータは前の5ラウンドのデータを基に、その後の各ラウンドのデータを使用してプレイヤーの出札戦略を分析し、自分の出札戦略を決定します。
30 | - [x] ゲームが終了し、対局情報を表示し、新しいゲームを開始できます。
31 | - [x] メニューにはゲームルールと制作者リストボタンが追加され、それに対応する内容があります。
32 | - [x] 複数の言語をサポート
33 | - [ ] `Web`端の適応
34 | - [ ] 適切なBGMと再生音楽を追加する
35 | - [ ] ゲームの難易度を選択できます。デフォルトは中です
36 |
37 | ## ゲームのプレビュー
38 |
39 | 
40 |
41 | 
42 |
43 | 
44 |
45 | 
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 | 
54 |
55 | 
56 |
57 | 
58 |
59 | 
60 |
--------------------------------------------------------------------------------
/docs/README_KR.md:
--------------------------------------------------------------------------------
1 |
2 | # '도박의 종말'을 모방한 E-Card 미니게임
3 |
4 | [중국어 문서](./README_CN.md) | [영문 문서](../README.md) | [일본어 문서](./README_JP.md) | **한국어 문서**
5 |
6 | ## 개발 도구
7 |
8 | | 도구 | 설명하다 | 공식 웹 사이트 |
9 | | --- |-------| --- |
10 | | icon | 상 | [https://icones.js.org/collection/all](https://icones.js.org/collection/all) |
11 | | vueuse | 유틸리티 기능 | [https://vueuse.org/functions.html](https://vueuse.org/functions.html) |
12 | | unocss | 원자화된 스타일 | [https://unocss.dev/interactive/](https://unocss.dev/interactive/) |
13 | | grid | 그리드 레이아웃 | [https://cssgrid-generator.netlify.app/](https://cssgrid-generator.netlify.app/) |
14 |
15 | ## 할 일 목록
16 |
17 | - [x] 레이아웃 및 기본 기능 버튼 개발
18 | - [x] 손패 영역 구성 요소 개발
19 | - [x] 검사 영역 구성 요소 개발
20 | - [x] 대결 정보 구성 요소 개발
21 | - [x] 대결 시작 구성 요소, 킹과 노예 중에서 무작위로 `1`개를 선택하여 플레이어의 시작 역할을 지정하고, 이후 역할은 21회 동안 롤링됩니다.
22 | - [x] 버려진 카드 영역 구성 요소 개발, 카드가 없으면 점선 + 텍스트를 표시하고, 카드가 있을 경우 0° - 30°의 무작위 각도로 회전하여 쌓여서 표시됩니다.
23 | - [x] 손패에서 카드를 선택하면 `check` 버튼이 표시됩니다.
24 | - [x] `check` 버튼을 클릭하면 해당 카드가 손패에서 제거되고 검사 영역에 추가됩니다.
25 | - [x] 카드는 `3D` 효과를 구현하여 `check` 후 뒤집어 검사 영역에 표시되며, 2초 후 검사 영역의 카드는 정면으로 뒤집어져 비교하고, 비교 후 버려진 카드 영역으로 이동합니다.
26 | - [ ] 손패에서 검사 영역으로, 검사 영역에서 버려진 카드 영역으로의 구성 요소 간 애니메이션 효과
27 | - [x] 킹 카드나 노예 카드가 낸 경우 대결이 종료됩니다.
28 | - [x] 대결 정보를 정산하고 다음 대결을 시작합니다.
29 | - [ ] 플레이어가 5회를 플레이한 후, 컴퓨터는 이전 5회 데이터와 이후 각 회차 데이터를 기반으로 플레이어의 카드 낸 전략을 분석하여 자신의 카드 낸 전략을 결정합니다.
30 | - [x] 게임이 종료되면 대결 정보를 표시하고 게임을 다시 시작할 수 있습니다.
31 | - [x] 메뉴에 게임 규칙 및 제작자 명단 버튼을 추가하고 해당 내용을 추가합니다.
32 | - [x] 여러 언어 지원
33 | - [ ] `web` 엔드 어댑트
34 | - [ ] 적절한 배경 음악 추가 및 음악 재생
35 | - [ ] 게임 난이도를 선택할 수 있으며 기본값은 중간입니다.
36 |
37 | ## 게임 미리보기
38 |
39 | 
40 |
41 | 
42 |
43 | 
44 |
45 | 
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 | 
54 |
55 | 
56 |
57 | 
58 |
59 | 
60 |
--------------------------------------------------------------------------------
/eslint.config.ts:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default await antfu()
4 |
--------------------------------------------------------------------------------
/gameImg/changeBackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/changeBackground.png
--------------------------------------------------------------------------------
/gameImg/choose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/choose.png
--------------------------------------------------------------------------------
/gameImg/drop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/drop.png
--------------------------------------------------------------------------------
/gameImg/explain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/explain.png
--------------------------------------------------------------------------------
/gameImg/final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/final.png
--------------------------------------------------------------------------------
/gameImg/gameinfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/gameinfo.png
--------------------------------------------------------------------------------
/gameImg/lose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/lose.png
--------------------------------------------------------------------------------
/gameImg/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/menu.png
--------------------------------------------------------------------------------
/gameImg/producer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/producer.png
--------------------------------------------------------------------------------
/gameImg/role.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/role.png
--------------------------------------------------------------------------------
/gameImg/win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/gameImg/win.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | E-Card
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/niva.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-card",
3 | "uuid": "7bf88399-7470-4f4a-82bb-107c03fcee50",
4 | "icon": "./king.png",
5 | "debug": {
6 | "entry": "http://localhost:5173",
7 | "resource": "public"
8 | },
9 | "build": {
10 | "resource": "dist"
11 | },
12 | "window": {
13 | "size": {
14 | "width": 1280,
15 | "height": 960
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-card",
3 | "private": true,
4 | "version": "1.2.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vue-tsc && vite build",
9 | "predeploy": "pnpm run build",
10 | "deploy": "gh-pages -d dist"
11 | },
12 | "dependencies": {
13 | "@unocss/reset": "^0.58.0",
14 | "@vueuse/core": "^10.7.0",
15 | "vue": "^3.3.11",
16 | "vue-i18n": "^9.9.1",
17 | "vue-router": "^4.2.5"
18 | },
19 | "devDependencies": {
20 | "@antfu/eslint-config": "^2.1.1",
21 | "@iconify-json/logos": "^1.1.39",
22 | "@iconify/json": "^2.2.147",
23 | "@types/node": "^20.10.4",
24 | "@vitejs/plugin-vue": "^4.5.0",
25 | "eslint": "^8.54.0",
26 | "gh-pages": "^6.1.0",
27 | "typescript": "^5.2.2",
28 | "unocss": "^0.58.0",
29 | "unplugin-auto-import": "^0.17.2",
30 | "vite": "^5.0.0",
31 | "vue-tsc": "^1.8.22"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/king.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/public/king.png
--------------------------------------------------------------------------------
/public/king.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/bg-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/bg-1.jpg
--------------------------------------------------------------------------------
/src/assets/bg-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/bg-2.jpg
--------------------------------------------------------------------------------
/src/assets/bg-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/bg-3.jpg
--------------------------------------------------------------------------------
/src/assets/bg-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/bg-4.jpg
--------------------------------------------------------------------------------
/src/assets/card-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/card-bg.jpg
--------------------------------------------------------------------------------
/src/assets/citizen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/citizen.jpg
--------------------------------------------------------------------------------
/src/assets/emperor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/emperor.jpg
--------------------------------------------------------------------------------
/src/assets/music/发牌.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/music/发牌.wav
--------------------------------------------------------------------------------
/src/assets/music/抽牌.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/music/抽牌.wav
--------------------------------------------------------------------------------
/src/assets/music/洗牌.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/music/洗牌.wav
--------------------------------------------------------------------------------
/src/assets/slave.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SmallTeddyGames/e-card/0e76c3105e07a011e488db275e6b1d0c2a97c878/src/assets/slave.jpg
--------------------------------------------------------------------------------
/src/i18n/cn.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu': {
3 | 'start': '开始游戏',
4 | 'continue': '游戏继续',
5 | 'win': '您赢了',
6 | 'lose': '您输了',
7 | 'reStart': '重新开始',
8 | 'explain': '游戏说明',
9 | 'producer': '制作人员'
10 | },
11 | 'game': {
12 | 'emperor': '国王',
13 | 'slave': '奴隶',
14 | 'no': '第',
15 | 'round': '局',
16 | 'role': '角色',
17 | 'checkArea': '检查区域',
18 | 'dropArea': '弃牌区域'
19 | },
20 | 'info': {
21 | 'round': '局数',
22 | 'role': '角色',
23 | 'result': '结果',
24 | 'player': '玩家',
25 | 'computer': '电脑',
26 | 'score': '比分'
27 | },
28 | 'explain': {
29 | 'explain': '游戏说明',
30 | 'explain1': '游戏共分为两种角色,皇帝和奴隶,每个角色拥有5张牌',
31 | 'explain2': '皇帝方:持1张皇帝牌(Emperor),4张市民牌(Citizen)',
32 | 'explain3': '奴隶方:持1张奴隶牌(Slave),4张市民牌(Citizen)',
33 | 'explain4': '游戏开始时随机分配角色,之后每一局角色轮换,共进行21局',
34 | 'explain5': '玩家选择一张牌后,点击出牌,进行比较',
35 | 'explain6': '皇帝赢市民,市民赢奴隶,奴隶赢皇帝',
36 | 'explain7': '市民对市民互相抵消,各自弃牌进入下一轮,抵消牌不再交还各方',
37 | 'explain8': '皇帝或奴隶被杀,游戏宣告结束,被杀方告负',
38 | 'explain9': '赢则积1分,数则积0分,先赢到11分则游戏结束'
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/i18n/en.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu': {
3 | 'start': 'Start Game',
4 | 'continue': 'Continue Game',
5 | 'win': 'You Win',
6 | 'lose': 'You Lose',
7 | 'reStart': 'ReStart Game',
8 | 'explain': 'Game Explain',
9 | 'producer': 'Producer List',
10 | },
11 | 'game': {
12 | 'emperor': 'Emperor',
13 | 'slave': 'Slave',
14 | 'no': 'No',
15 | 'round': 'Round',
16 | 'role': 'Role',
17 | 'checkArea': 'Check Area',
18 | 'dropArea': 'Drop Area'
19 | },
20 | 'info': {
21 | 'round': 'Round',
22 | 'role': 'Role',
23 | 'result': 'Result',
24 | 'player': 'Player',
25 | 'computer': 'Computer',
26 | 'score': 'Score',
27 | },
28 | 'explain': {
29 | 'explain': 'Game Explain',
30 | 'explain1': 'The game is divided into two roles: Emperor and Slave, each with 5 cards.',
31 | 'explain2': `Emperor's side: Holds 1 Emperor card (Emperor) and 4 Citizen cards (Citizen).`,
32 | 'explain3': `Slave's side: Holds 1 Slave card (Slave) and 4 Citizen cards (Citizen).`,
33 | 'explain4': 'At the start of the game, roles are assigned randomly, and then the roles rotate after each round, for a total of 21 rounds. ',
34 | 'explain5': 'After a player selects a card, they click to play it for comparison. ',
35 | 'explain6': 'The Emperor wins against the Citizen, the Citizen wins against the Slave, and the Slave wins against the Emperor. ',
36 | 'explain7': 'When Citizens play against each other, they cancel each other out, discard their cards, and proceed to the next round without returning the canceled cards. ',
37 | 'explain8': 'If the Emperor or Slave is killed, the game ends, and the killed party loses. ',
38 | 'explain9': 'A win accumulates 1 point, a draw accumulates 0 points, and the game ends when a player reaches 11 points first. '
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import EN from './en'
2 | import CN from './cn'
3 | import JP from './jp'
4 | import KR from './kr'
5 |
6 | const state = useGlobalState()
7 | const message = {
8 | cn: CN,
9 | en: EN,
10 | jp: JP,
11 | kr: KR
12 | }
13 |
14 | const i18n = createI18n({
15 | locale: state.value.language, // 设置语言类型
16 | legacy: false, // 如果要支持compositionAPI,此项必须设置为false;
17 | globalInjection: true, // 全局注册$t方法
18 | messages: message,
19 | })
20 |
21 | export default i18n
22 |
--------------------------------------------------------------------------------
/src/i18n/jp.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu': {
3 | 'start': 'ケジュール',
4 | 'continue': 'ケジュール再開',
5 | 'win': 'ケジュール',
6 | 'lose': 'ケジュール',
7 | 'reStart': 'ケジュール再開',
8 | 'explain': 'ケジュール説明',
9 | 'producer': 'ケジュール制作者',
10 | },
11 | 'game': {
12 | 'emperor': '国王',
13 | 'slave': '奴隶',
14 | 'no': '第',
15 | 'round': '局',
16 | 'role': '役割',
17 | 'checkArea': 'チェックエリア',
18 | 'dropArea': 'ドロップエリア'
19 | },
20 | 'info': {
21 | 'round': '局数',
22 | 'role': '役割',
23 | 'result': '結果',
24 | 'player': 'プレイヤー',
25 | 'computer': 'コンピューター',
26 | 'score': 'スコア',
27 | },
28 | 'explain': {
29 | 'explain': 'ケジュール説明',
30 | 'explain1': 'このゲームは2つの役割に分けられています:皇帝と奴隷。それぞれが5枚のカードを持っています。 ',
31 | 'explain2': '皇帝側:1枚の皇帝カード(Emperor)と4枚の市民カード(Citizen)を所持しています。 ',
32 | 'explain3': '奴隷側:1枚の奴隷カード(Slave)と4枚の市民カード(Citizen)を所持しています。 ',
33 | 'explain4': 'ゲームの開始時には、役割がランダムに割り当てられ、その後の各ラウンドで役割が交代し、合計で21ラウンドを行います。 ',
34 | 'explain5': 'プレイヤーがカードを選んでから、出札をクリックして比較を行います。 ',
35 | 'explain6': '皇帝は市民に勝ち、市民は奴隷に勝ち、奴隷は皇帝に勝ちます。 ',
36 | 'explain7': '市民同士が対決した場合は互いに打ち消し、カードを捨てて次のラウンドに進み、打ち消されたカードは返されません。 ',
37 | 'explain8': '皇帝か奴隷が倒された場合、ゲームは終了し、倒された側が敗北します。 ',
38 | 'explain9': '勝利すれば1点、引き分けは0点が積み上がり、どちらかが11点を最初に達するとゲームは終了します。 '
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/i18n/kr.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu': {
3 | 'start': '게임 시작',
4 | 'continue': '게임 기준',
5 | 'win': '네가 이겼다',
6 | 'lose': '당신은 잃습니다',
7 | 'reStart': '게임 기준',
8 | 'explain': '게임 속성',
9 | 'producer': '기산자',
10 | },
11 | 'game': {
12 | 'emperor': '국사',
13 | 'slave': '사항',
14 | 'no': '안녕',
15 | 'round': '게임',
16 | 'role': '사항',
17 | 'checkArea': '선택 영역',
18 | 'dropArea': '추가 영역'
19 | },
20 | 'info': {
21 | 'round': '게임',
22 | 'role': '사항',
23 | 'result': '비슷',
24 | 'player': '플랫티',
25 | 'computer': '비슷자',
26 | 'score': '점수',
27 | },
28 | 'explain': {
29 | 'explain': '게임 속성',
30 | 'explain1': '이 게임은 두 가지 역할로 나누어져 있습니다: 황제와 노예. 각각 5장의 카드를 가지고 있습니다.',
31 | 'explain2': '황제 측: 1장의 황제 카드 (Emperor)와 4장의 시민 카드 (Citizen)를 보유합니다.',
32 | 'explain3': '노예 측: 1장의 노예 카드 (Slave)와 4장의 시민 카드 (Citizen)를 보유합니다.',
33 | 'explain4': '게임 시작 시 역할이 무작위로 배정되며, 이후 각 라운드마다 역할이 교체되며 총 21라운드를 진행합니다.',
34 | 'explain5': '플레이어는 카드를 선택한 후, 카드를 낼 때 비교를 수행합니다.',
35 | 'explain6': '황제는 시민에게 이길 수 있고, 시민은 노예에게 이길 수 있고, 노예는 황제에게 이길 수 있습니다.',
36 | 'explain7': '시민이 서로 대결하면 서로 상쇄하며 카드를 버리고 다음 라운드로 넘어가고, 상쇄된 카드는 돌려주지 않습니다.',
37 | 'explain8': '황제나 노예가 죽으면 게임이 종료되고, 죽은 쪽이 패배합니다.',
38 | 'explain9': '승리하면 1점, 무승부는 0점이 쌓이며, 누가 먼저 11점을 얻으면 게임이 종료됩니다.'
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import App from './App.vue'
2 | import router from './router'
3 | import { createApp } from 'vue'
4 | import i18n from './i18n'
5 | import '@unocss/reset/tailwind.css'
6 | import 'virtual:uno.css'
7 | import './styles/base.css'
8 |
9 | const app = createApp(App)
10 | app.use(router)
11 |
12 | app.use(i18n).mount('#app')
13 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | const router = createRouter({
2 | history: createWebHistory('/e-card'),
3 | routes: [
4 | {
5 | path: '/',
6 | name: 'home',
7 | component: () => import('../views/Game/Home.vue')
8 | },
9 | {
10 | path: '/:pathMatch(.*)*',
11 | name: 'NotFound',
12 | component: () => import('../views/Others/404.vue')
13 | }
14 | ]
15 | })
16 |
17 | export default router
18 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalState, useStorage } from '@vueuse/core'
2 | import { Ref } from 'vue'
3 | import { GameStateType } from '@/views/Type'
4 |
5 | export const useGlobalState: () => Ref = createGlobalState(
6 | () => useStorage('global-state', {
7 | // 最开始未确定玩家角色
8 | playerRole: null,
9 | // 语言设置
10 | language: 'cn',
11 | // 人机对战
12 | isAiBattle: true,
13 | // 游戏难度
14 | difficulty: 'middle',
15 | // 当前局此
16 | rounds: 0,
17 | // 游戏状态
18 | gameState: 'init',
19 | // 玩家当前持有卡牌
20 | playerCardItems: [],
21 | // 电脑当前持有卡牌
22 | computerCardItems: [],
23 | // 背景图片
24 | bgImage: 1,
25 | // 是否显示游戏日志
26 | isShowGameInfo: true,
27 | // 游戏日志
28 | gameLogItems: [],
29 | // 丢弃的卡牌
30 | dropedCardItems: []
31 | })
32 | )
33 |
--------------------------------------------------------------------------------
/src/styles/base.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100vh;
4 | overflow: hidden;
5 | --uno: bg-base font-ping;
6 | }
7 |
8 | html {
9 | font-size: 14px;
10 | }
11 |
12 | body {
13 | font-size: 16px;
14 | }
15 |
16 | .dark {
17 | color-scheme: dark;
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/game.util.ts:
--------------------------------------------------------------------------------
1 | import { useGlobalState } from "@/store";
2 | import { getRandomNumber } from './index';
3 | import { CardItem, GroupEn, Role, GameStateType } from "@/views/Type";
4 |
5 | const state: { value: GameStateType } = useGlobalState()
6 |
7 | /**
8 | * 创建卡片
9 | * @param type 传入类型
10 | * @param sort 序号
11 | * @param group 分组
12 | * @returns
13 | */
14 | export const createCard = (role: Role, sort: number, group: GroupEn): CardItem => {
15 | return { role: role, img: `${role}.jpg`, isClick: false, isBack: false, sort, group };
16 | }
17 |
18 | /**
19 | * 初始化阵营卡片
20 | * @param group 分组
21 | * @returns
22 | */
23 | export const initRoleItems = (group: GroupEn, isPlayer: boolean = true): CardItem[] => {
24 | const items: CardItem[] = Array(5).fill(0).map((_, idx) => createCard('citizen', idx + 1, group));
25 | const sort = getRandomNumber(5);
26 | items[sort] = (createCard(group, sort + 1, group));
27 | items.map(card => card.isBack = !isPlayer)
28 | return items;
29 | }
30 |
31 | /**
32 | * 获取对立角色
33 | * @param group 分组
34 | * @returns
35 | */
36 | export const getReverseRole = (group: GroupEn): GroupEn => group == "emperor" ? "slave" : "emperor"
37 |
38 | /**
39 | * 初始化轮次
40 | * @param playerRole 玩家角色
41 | * @param rounds 回合数
42 | */
43 | export const initRounds = (playerRole: GroupEn, rounds: number): void => {
44 | // 初始化
45 | state.value = {
46 | playerRole,
47 | rounds,
48 | language: 'cn',
49 | gameState: "init",
50 | difficulty: 'middle',
51 | isAiBattle: true,
52 | playerCardItems: initRoleItems(playerRole, true),
53 | computerCardItems: initRoleItems(getReverseRole(playerRole), false),
54 | bgImage: 1,
55 | isShowGameInfo: true,
56 | gameLogItems: [],
57 | dropedCardItems: []
58 | }
59 | }
60 |
61 | /**
62 | * 进行下一轮
63 | */
64 | export const nextRounds = (): void => {
65 | const nextRole = getReverseRole(state.value.playerRole);
66 | const nextRound = state.value.rounds + 1;
67 | state.value.playerRole = nextRole;
68 | state.value.rounds = nextRound;
69 | state.value.playerCardItems = initRoleItems(nextRole, true);
70 | state.value.computerCardItems = initRoleItems(getReverseRole(nextRole), false);
71 | state.value.dropedCardItems = [];
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./game.util"
2 |
3 | /**
4 | * 获取assets静态资源,路径为assets
5 | * @param url 图片名称
6 | * @returns
7 | */
8 | export const getAssetsFile = (url: string): string => {
9 | return new URL(`../assets/${url}`, import.meta.url).href
10 | }
11 |
12 | // 根据最大数字生成随机数 取下标 0 - max-1
13 | export const getRandomNumber = (max: number): number => {
14 | return (Math.floor(Math.random() * max) + 1) % max
15 | }
16 |
17 | /**
18 | * 深拷贝
19 | * @param obj 要拷贝的对象
20 | * @param map 已经拷贝的对象
21 | * @returns
22 | */
23 | export const deepClone = (obj, map = new WeakMap()) => {
24 | // 如果是基本数据类型,则直接返回
25 | if (obj === null || typeof obj !== 'object') {
26 | return obj;
27 | }
28 |
29 | // 如果已经拷贝过该对象,则直接返回拷贝后的对象
30 | if (map.has(obj)) {
31 | return map.get(obj)!;
32 | }
33 |
34 | // 根据对象的类型进行深拷贝
35 | const clone = Array.isArray(obj) ? [] : {};
36 |
37 | // 将当前对象和对应的拷贝存储到map中
38 | map.set(obj, clone);
39 |
40 | for (let key in obj) {
41 | if (obj.hasOwnProperty(key)) {
42 | clone[key] = deepClone(obj[key], map);
43 | }
44 | }
45 |
46 | return clone;
47 | }
48 |
49 | /**
50 | * 防抖函数
51 | */
52 | let debounceTimer;
53 | export const debounce = (fn: Function, delay: number = 500) => {
54 | if (debounceTimer) clearInterval(debounceTimer)
55 | debounceTimer = window.setTimeout(() => {
56 | fn()
57 | }, delay);
58 | }
59 |
60 | /**
61 | * 节流函数 点击之后一段时间不会再触发
62 | * @param fn
63 | * @param still 持续间隔时间
64 | */
65 | let throttleTimer;
66 | export const throttle = (fn:Function, still = 300) => {
67 | if (!throttleTimer) {
68 | fn();
69 | throttleTimer = setTimeout(() => {
70 | if (throttleTimer) clearTimeout(throttleTimer)
71 | throttleTimer = null;
72 | }, still);
73 | }
74 | }
75 |
76 | /**
77 | * 强制清除节流
78 | */
79 | export const clearAllThrottle = ()=>{
80 | if(throttleTimer)clearTimeout(throttleTimer);
81 | }
82 |
--------------------------------------------------------------------------------
/src/views/Component/Card.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
29 |
30 |
31 |
55 |
--------------------------------------------------------------------------------
/src/views/Component/CheckCard.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ t('game.checkArea') }}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/views/Component/ComputedCard.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/Component/DropCard.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
![]()
14 |
15 |
16 |
17 | {{ t('game.dropArea') }}
18 |
19 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/views/Component/GameExplain.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/views/Component/GameInformation.vue:
--------------------------------------------------------------------------------
1 |
44 |
45 |
46 |
47 |
48 | {{ t('info.round') }} |
49 | {{ t('info.role') }} |
50 | {{ t('info.result') }} |
51 | {{ t('info.player') }} |
52 | {{ t('info.computer') }} |
53 |
54 |
56 | {{ item.round }} |
57 | {{ item.role }} |
58 | {{ item.result }} |
59 | {{ item.playerScore }} |
60 | {{ item.computerScore }} |
61 |
62 |
63 | {{ t('info.score') }} |
64 | |
65 | |
66 | {{ playerFinalScore }} |
67 | {{ computerFinalScore }} |
68 |
69 |
70 |
71 |
72 |
83 |
--------------------------------------------------------------------------------
/src/views/Component/GameMenu.vue:
--------------------------------------------------------------------------------
1 |
147 |
148 |
149 |
150 |
151 |
152 | {{ $t('game.no') }} {{ info?.rounds }} {{ $t('game.round') }}
153 |
154 |
155 | {{ $t('game.role') }} : {{ t(`game.${name}`) }}
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/src/views/Component/MessageInfo.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
{{ title }}
20 |
21 |
22 | {{ text }}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
40 |
--------------------------------------------------------------------------------
/src/views/Component/PlayerCard.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
35 |
36 |
--------------------------------------------------------------------------------
/src/views/Component/ProducerList.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/Game/GameMain.vue:
--------------------------------------------------------------------------------
1 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/views/Game/Home.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/views/Layout/GameContent.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/views/Layout/GameNav.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
30 |
31 |
--------------------------------------------------------------------------------
/src/views/NavButton/ChangeBackImage.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/src/views/NavButton/ChangeGameStatus.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/NavButton/ChangeLanguage.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
33 |
34 |
35 |
37 |
--------------------------------------------------------------------------------
/src/views/NavButton/ColorSchemeToggle.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/NavButton/FullScreenToggle.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/NavButton/GameInfoToggle.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/NavButton/GitHubButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/Others/404.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/Type/cardType.ts:
--------------------------------------------------------------------------------
1 | import { GroupEn } from "./groupType";
2 | import { Role } from "./roleType";
3 |
4 | export type CardItem = {
5 | role: Role;
6 | img: string; // 'emperor.jpg' | 'citizen.jpg' | 'slave.jpg'|
7 | isClick?: boolean;
8 | isBack?: boolean;
9 | sort?: number;
10 | group?: GroupEn;
11 | }
12 |
--------------------------------------------------------------------------------
/src/views/Type/gameType.ts:
--------------------------------------------------------------------------------
1 | export type GameStatus = 'init' |'start' | 'pause' | 'win' | 'lose'
2 |
--------------------------------------------------------------------------------
/src/views/Type/groupType.ts:
--------------------------------------------------------------------------------
1 | export type GroupEn = 'emperor' | 'slave';
2 |
--------------------------------------------------------------------------------
/src/views/Type/index.ts:
--------------------------------------------------------------------------------
1 | import { CardItem } from './cardType'
2 | import { GroupEn } from './groupType'
3 | import { LogItem } from './logType'
4 | import { Role } from './roleType'
5 | import { GameStatus } from './gameType'
6 | import { GameStateType } from './stateType'
7 |
8 | export type {
9 | CardItem,
10 | GroupEn,
11 | LogItem,
12 | Role,
13 | GameStatus,
14 | GameStateType
15 | }
16 |
--------------------------------------------------------------------------------
/src/views/Type/logType.ts:
--------------------------------------------------------------------------------
1 | import { Role } from "./roleType";
2 |
3 | export type LogItem = {
4 | round: number;
5 | role: Role;
6 | result: 'win' | 'lose';
7 | sort?: number;
8 | playerScore?: number;
9 | computerScore?: number;
10 | }
11 |
--------------------------------------------------------------------------------
/src/views/Type/roleType.ts:
--------------------------------------------------------------------------------
1 | export type Role = 'emperor' | 'citizen' | 'slave';
2 |
--------------------------------------------------------------------------------
/src/views/Type/stateType.ts:
--------------------------------------------------------------------------------
1 | import { CardItem } from "./cardType";
2 | import { GameStatus } from "./gameType";
3 | import { GroupEn } from "./groupType";
4 | import { LogItem } from "./logType";
5 |
6 | export type GameStateType = {
7 | // 最开始未确定玩家角色
8 | playerRole: GroupEn;
9 | // 语言设置
10 | language: 'cn' | 'en' | 'jp' | 'kr';
11 | // 人机对战
12 | isAiBattle: boolean;
13 | // 游戏难度
14 | difficulty: 'easy' | 'middle' | 'hard';
15 | // 当前局此
16 | rounds: number;
17 | // 游戏状态
18 | gameState: GameStatus;
19 | // 玩家当前持有卡牌
20 | playerCardItems: CardItem[];
21 | // 电脑当前持有卡牌
22 | computerCardItems: CardItem[];
23 | // 背景图片
24 | bgImage: number;
25 | // 是否显示游戏日志
26 | isShowGameInfo: boolean;
27 | // 游戏日志
28 | gameLogItems: LogItem[];
29 | // 丢弃的卡牌
30 | dropedCardItems: CardItem[]
31 | }
32 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 |
4 | const vueComponent: DefineComponent<{}, {}, any>
5 |
6 | export default vueComponent
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "jsx": "preserve",
5 | "lib": ["ESNext", "DOM"],
6 | "useDefineForClassFields": true,
7 | "baseUrl": "./",
8 | "module": "ESNext",
9 | "moduleResolution": "Node",
10 | "paths": {
11 | "~": ["./"],
12 | "@/*": ["src/*"]
13 | },
14 | "resolveJsonModule": true,
15 | "types": ["vite/client"],
16 | "allowJs": true,
17 | "strict": false,
18 | "noEmit": true,
19 | "esModuleInterop": true,
20 | "isolatedModules": true,
21 | "skipLibCheck": true
22 | },
23 | "references": [{ "path": "./tsconfig.node.json" }],
24 | "include": [
25 | "src/**/*.ts",
26 | "src/**/*.d.ts",
27 | "src/**/*.tsx",
28 | "src/**/*.vue",
29 | "src/**/*.lrc",
30 | "src/**/*.mp3",
31 | "auto-imports.d.ts"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | import {
2 | defineConfig,
3 | presetAttributify,
4 | presetIcons,
5 | presetTypography,
6 | presetUno,
7 | presetWebFonts,
8 | transformerDirectives,
9 | } from "unocss";
10 | export default defineConfig({
11 | shortcuts: {
12 | "border-base": "border-gray-200 dark:border-gray-500",
13 | "bg-active": "bg-gray:10",
14 | "flex-center": "flex items-center justify-center",
15 | "card-size": "w-120px h-164px",
16 | "bg-Mask": "bg-white-80 dark:bg-black-30",
17 | },
18 | rules: [
19 | [/^t-a-(\d+)$/, ([, d]) => ({ transition: `all 0.${d}s linear` })],
20 | [
21 | /^bg-white-(\d+)$/,
22 | ([, d]) => ({ "background-color": `rgb(255 255 255 / ${d}%)` }),
23 | ],
24 | [
25 | /^bg-black-(\d+)$/,
26 | ([, d]) => ({ "background-color": `rgb(0 0 0 / ${d}%)` }),
27 | ],
28 | [/^l-s-(\d+)$/, ([, d]) => ({ "letter-spacing": `${d}px` })],
29 | ],
30 | theme: {
31 | colors: {
32 | primary: {
33 | DEFAULT: "#00DC82",
34 | },
35 | },
36 | },
37 | presets: [
38 | presetUno(),
39 | presetIcons(),
40 | presetAttributify(),
41 | presetWebFonts({
42 | provider: "bunny",
43 | fonts: {
44 | sans: "DM Sans",
45 | mono: "DM Mono",
46 | ping: "PingFangMedium"
47 | },
48 | }),
49 | presetTypography(),
50 | ],
51 | transformers: [transformerDirectives()],
52 | });
53 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import UnoCSS from 'unocss/vite'
4 | import AutoImport from 'unplugin-auto-import/vite'
5 | import path from 'path'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | base: './',
10 | plugins: [
11 | vue(),
12 | UnoCSS(),
13 | AutoImport({
14 | // targets to transform
15 | include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/],
16 |
17 | // global imports to register
18 | imports: [
19 | // vue auto import
20 | 'vue',
21 | // vue-router auto import
22 | {
23 | 'vue-router': [
24 | 'createRouter',
25 | 'createWebHistory'
26 | ]
27 | },
28 | // @vueuse/core auto import
29 | {
30 | '@vueuse/core': [
31 | 'createGlobalState',
32 | 'useStorage',
33 | 'useColorMode',
34 | 'useFullscreen'
35 | ]
36 | },
37 | // @/store auto import
38 | {
39 | '@/store': [
40 | 'useGlobalState'
41 | ]
42 | },
43 | // i18n
44 | {
45 | 'vue-i18n': [
46 | 'useI18n',
47 | 'createI18n'
48 | ]
49 | }
50 | ]
51 | }),
52 | ],
53 | resolve: {
54 | alias: {
55 | '@': path.resolve(__dirname, 'src')
56 | },
57 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
58 | },
59 | server: {
60 | port: 5173,
61 | host: true,
62 | open: true
63 | }
64 | })
65 |
--------------------------------------------------------------------------------