├── .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 | ![menu](/gameImg/menu.png) 40 | 41 | ![explain](/gameImg/explain.png) 42 | 43 | ![role](/gameImg/role.png) 44 | 45 | ![gameInfo](/gameImg/gameinfo.png) 46 | 47 | ![choose](/gameImg/choose.png) 48 | 49 | ![changeBackGround](/gameImg/changeBackground.png) 50 | 51 | ![drop](/gameImg/drop.png) 52 | 53 | ![final](/gameImg/final.png) 54 | 55 | ![win](/gameImg/win.png) 56 | 57 | ![lose](/gameImg/lose.png) 58 | 59 | ![producer](/gameImg/producer.png) 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 | ![menu](/gameImg/menu.png) 41 | 42 | ![explain](/gameImg/explain.png) 43 | 44 | ![role](/gameImg/role.png) 45 | 46 | ![gameInfo](/gameImg/gameinfo.png) 47 | 48 | ![choose](/gameImg/choose.png) 49 | 50 | ![changeBackGround](/gameImg/changeBackground.png) 51 | 52 | ![drop](/gameImg/drop.png) 53 | 54 | ![final](/gameImg/final.png) 55 | 56 | ![win](/gameImg/win.png) 57 | 58 | ![lose](/gameImg/lose.png) 59 | 60 | ![producer](/gameImg/producer.png) 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 | ![menu](/gameImg/menu.png) 40 | 41 | ![explain](/gameImg/explain.png) 42 | 43 | ![role](/gameImg/role.png) 44 | 45 | ![gameInfo](/gameImg/gameinfo.png) 46 | 47 | ![choose](/gameImg/choose.png) 48 | 49 | ![changeBackGround](/gameImg/changeBackground.png) 50 | 51 | ![drop](/gameImg/drop.png) 52 | 53 | ![final](/gameImg/final.png) 54 | 55 | ![win](/gameImg/win.png) 56 | 57 | ![lose](/gameImg/lose.png) 58 | 59 | ![producer](/gameImg/producer.png) 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 | ![menu](/gameImg/menu.png) 40 | 41 | ![explain](/gameImg/explain.png) 42 | 43 | ![role](/gameImg/role.png) 44 | 45 | ![gameInfo](/gameImg/gameinfo.png) 46 | 47 | ![choose](/gameImg/choose.png) 48 | 49 | ![changeBackGround](/gameImg/changeBackground.png) 50 | 51 | ![drop](/gameImg/drop.png) 52 | 53 | ![final](/gameImg/final.png) 54 | 55 | ![win](/gameImg/win.png) 56 | 57 | ![lose](/gameImg/lose.png) 58 | 59 | ![producer](/gameImg/producer.png) 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 | 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 | 30 | 31 | 55 | -------------------------------------------------------------------------------- /src/views/Component/CheckCard.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /src/views/Component/ComputedCard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/views/Component/DropCard.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /src/views/Component/GameExplain.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/Component/GameInformation.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 71 | 72 | 83 | -------------------------------------------------------------------------------- /src/views/Component/GameMenu.vue: -------------------------------------------------------------------------------- 1 | 147 | 148 | 178 | 179 | -------------------------------------------------------------------------------- /src/views/Component/MessageInfo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /src/views/Component/PlayerCard.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 36 | -------------------------------------------------------------------------------- /src/views/Component/ProducerList.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/Game/GameMain.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 149 | 150 | -------------------------------------------------------------------------------- /src/views/Game/Home.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /src/views/Layout/GameContent.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /src/views/Layout/GameNav.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/views/NavButton/ChangeBackImage.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /src/views/NavButton/ChangeGameStatus.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /src/views/NavButton/ChangeLanguage.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /src/views/NavButton/ColorSchemeToggle.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /src/views/NavButton/FullScreenToggle.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /src/views/NavButton/GameInfoToggle.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | -------------------------------------------------------------------------------- /src/views/NavButton/GitHubButton.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Others/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 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 | --------------------------------------------------------------------------------