├── public
├── robots.txt
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── static
│ ├── images
│ │ ├── wolf.jpg
│ │ ├── hunter.jpg
│ │ ├── idiot.jpg
│ │ ├── knight.jpg
│ │ ├── witch.jpg
│ │ ├── predictor.jpg
│ │ └── villager.jpg
│ └── audio
│ │ ├── step_1.mp3
│ │ ├── step_10.mp3
│ │ ├── step_11.mp3
│ │ ├── step_12.mp3
│ │ ├── step_13.mp3
│ │ ├── step_14.mp3
│ │ ├── step_15.mp3
│ │ ├── step_16.mp3
│ │ ├── step_17.mp3
│ │ ├── step_18.mp3
│ │ ├── step_19.mp3
│ │ ├── step_2.mp3
│ │ ├── step_20.mp3
│ │ ├── step_21.mp3
│ │ ├── step_22.mp3
│ │ ├── step_3.mp3
│ │ ├── step_4.mp3
│ │ ├── step_5.mp3
│ │ ├── step_6.mp3
│ │ ├── step_7.mp3
│ │ ├── step_8.mp3
│ │ └── step_9.mp3
├── App.test.js
├── App.js
├── index.css
├── index.js
├── App.css
├── constants
│ └── Role.js
├── locales
│ ├── index.js
│ └── languages
│ │ ├── zh-TW.json
│ │ └── en-US.json
├── logo.svg
├── components
│ ├── CheckRole
│ │ └── CheckRole.js
│ ├── Setting
│ │ └── Setting.js
│ ├── Home
│ │ └── Home.js
│ └── Game
│ │ └── Game.js
└── serviceWorker.js
├── .gitignore
├── .vscode
└── settings.json
├── package.json
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/static/images/wolf.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/wolf.jpg
--------------------------------------------------------------------------------
/src/static/audio/step_1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_1.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_10.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_10.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_11.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_11.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_12.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_12.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_13.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_13.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_14.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_14.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_15.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_15.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_16.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_16.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_17.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_17.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_18.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_18.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_19.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_19.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_2.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_20.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_20.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_21.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_21.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_22.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_22.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_3.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_4.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_5.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_6.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_6.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_7.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_7.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_8.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_8.mp3
--------------------------------------------------------------------------------
/src/static/audio/step_9.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/audio/step_9.mp3
--------------------------------------------------------------------------------
/src/static/images/hunter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/hunter.jpg
--------------------------------------------------------------------------------
/src/static/images/idiot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/idiot.jpg
--------------------------------------------------------------------------------
/src/static/images/knight.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/knight.jpg
--------------------------------------------------------------------------------
/src/static/images/witch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/witch.jpg
--------------------------------------------------------------------------------
/src/static/images/predictor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/predictor.jpg
--------------------------------------------------------------------------------
/src/static/images/villager.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JaeyoLin/wolf_judge/HEAD/src/static/images/villager.jpg
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Home from './components/Home/Home';
4 | import './App.css';
5 |
6 | import './locales/index';
7 |
8 | function App() {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Wolf",
3 | "name": "Wolf",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100vh;
3 | }
4 | body {
5 | margin: 0;
6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
7 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
8 | sans-serif;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | height: 100vh;
12 | background-color: #f5f5f5;
13 | }
14 |
15 | code {
16 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
17 | monospace;
18 | }
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | // serviceWorker.unregister();
13 | serviceWorker.register();
14 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | height: 100vh;
4 | }
5 |
6 | .App-logo {
7 | animation: App-logo-spin infinite 20s linear;
8 | height: 40vmin;
9 | pointer-events: none;
10 | }
11 |
12 | .App-header {
13 | background-color: #282c34;
14 | min-height: 100vh;
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 | justify-content: center;
19 | font-size: calc(10px + 2vmin);
20 | color: white;
21 | }
22 |
23 | .App-link {
24 | color: #61dafb;
25 | }
26 |
27 | @keyframes App-logo-spin {
28 | from {
29 | transform: rotate(0deg);
30 | }
31 | to {
32 | transform: rotate(360deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "terminal.external.osxExec": "iTerm.app",
3 | "terminal.integrated.fontFamily": "Source Code Pro for Powerline",
4 | "terminal.integrated.fontSize": 14,
5 | "editor.tokenColorCustomizations": {
6 | "[Material Theme Darker High Contrast]": {
7 | "comments": "#229977",
8 | },
9 | },
10 | // "editor.fontFamily": "'Fira Code', 'Source Code Pro', Consolas, 'Microsoft JhengHei', 'Courier New', monospace",
11 | "editor.lineHeight": 24,
12 | "editor.fontLigatures": true,
13 | // "explorer.decorations.badges": false
14 | "workbench.sideBar.location": "right",
15 | "colorize.languages": [
16 | "javascript",
17 | ],
18 | "editor.minimap.enabled": false,
19 | "editor.fontSize": 14,
20 | "editor.renderWhitespace": "all"
21 | }
--------------------------------------------------------------------------------
/src/constants/Role.js:
--------------------------------------------------------------------------------
1 | export const WOLF = {
2 | key: 'wolf',
3 | isGood: false,
4 | isGod: false,
5 | src: 'wolf.jpg',
6 | };
7 |
8 | export const PREDICTOR = {
9 | key: 'predictor',
10 | isGood: true,
11 | isGod: true,
12 | src: 'predictor.jpg',
13 | };
14 |
15 | export const WITCH = {
16 | key: 'witch',
17 | isGood: true,
18 | isGod: true,
19 | src: 'witch.jpg',
20 | };
21 |
22 | export const HUNTER = {
23 | key: 'hunter',
24 | isGood: true,
25 | isGod: true,
26 | src: 'hunter.jpg',
27 | };
28 |
29 | export const KNIGHT = {
30 | key: 'knight',
31 | isGood: true,
32 | isGod: true,
33 | src: 'knight.jpg',
34 | };
35 |
36 | export const idiot = {
37 | key: 'idiot',
38 | isGood: true,
39 | isGod: true,
40 | src: 'idiot.jpg',
41 | };
42 |
43 | export const VILLAGER = {
44 | key: 'villager',
45 | isGood: true,
46 | isGod: false,
47 | src: 'villager.jpg',
48 | };
49 |
--------------------------------------------------------------------------------
/src/locales/index.js:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from "react-i18next";
3 |
4 | import enUS from './languages/en-US.json';
5 | import zhTW from './languages/zh-TW.json';
6 |
7 | // console.log('zhTW', zhTW);
8 |
9 | i18n
10 | .use(initReactI18next)
11 | .init({
12 | // we init with resources
13 | resources: {
14 | 'en-US': {
15 | translations: enUS,
16 | },
17 | 'zh-TW': {
18 | translations: zhTW,
19 | },
20 | },
21 | lng: localStorage.getItem('language') || 'zh-TW',
22 | fallbackLng: 'zh-TW',
23 | debug: true,
24 |
25 | // have a common namespace used around the full app
26 | ns: ['translations'],
27 | defaultNS: 'translations',
28 |
29 | // keySeparator: false, // we use content as keys
30 |
31 | interpolation: {
32 | escapeValue: false, // not needed for react!!
33 | formatSeparator: ',',
34 | },
35 |
36 | react: {
37 | wait: true,
38 | },
39 | });
40 |
41 | export default i18n;
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wolf_judge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://jaeyolin.github.io/wolf_judge/",
6 | "dependencies": {
7 | "react": "^16.9.0",
8 | "react-dom": "^16.9.0",
9 | "react-scripts": "3.1.1"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test",
15 | "eject": "react-scripts eject",
16 | "predeploy": "npm run build",
17 | "deploy": "gh-pages -d build"
18 | },
19 | "eslintConfig": {
20 | "extends": "react-app"
21 | },
22 | "browserslist": {
23 | "production": [
24 | ">0.2%",
25 | "not dead",
26 | "not op_mini all"
27 | ],
28 | "development": [
29 | "last 1 chrome version",
30 | "last 1 firefox version",
31 | "last 1 safari version"
32 | ]
33 | },
34 | "devDependencies": {
35 | "@material-ui/core": "^4.3.2",
36 | "@material-ui/icons": "^4.2.1",
37 | "gh-pages": "^2.1.1",
38 | "i18next": "^17.0.11",
39 | "react-i18next": "^10.12.2",
40 | "react-sound": "^1.2.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 狼人殺自動法官
2 |
3 | 你約了一些朋友想玩狼人殺嗎? 一個人要去當法官是不是又人不夠開局了.
4 |
5 | 這是一個沒有這麼多朋友, 也沒人想當法官, 又想玩狼人殺的法官輔助工具.
6 |
7 | ## 網頁
8 |
9 | - [請點我](https://jaeyolin.github.io/wolf_judge/)
10 |
11 | ## 功能特色
12 |
13 | - 不用實體卡片就可以使用, 工具內建隨機抽牌功能.
14 | - 自動主持人 (Google 小姐)
15 | - 第一次網頁載入後, 可離線使用.
16 |
17 | ## 角色
18 |
19 | - 預言家
20 | - 女巫
21 | - 獵人
22 | - 騎士
23 | - 白癡
24 | - 狼人
25 |
26 | >未來應該不會更新其他角色, 也不會加入警長競選規則, 我沒這麼多朋友可以玩到這麼多角色.
27 | >警長競選規則可以自己加進去玩, 跟自動法官沒什麼關係.
28 |
29 | ## 使用方式
30 |
31 | - 手機開啟此網頁.
32 | - 設定好角色.
33 | - 傳閱手機並查看自己的角色.
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 | ## 勝利判斷與條件
62 |
63 | - 白天睜眼, 白天投票, 獵人開槍, 騎士決鬥都會判斷遊戲是否結束
64 | - 正義聯盟獲勝: 所有狼人出局.
65 | - 邪惡陣營獲勝: 所有好人出局.
66 | - 有開啟屠邊局, 會判斷全部的神或全部的民出局, 狼人獲勝.
67 |
68 | ## 已實作功能
69 |
70 | - 預言家每晚都會睜眼查驗角色.
71 | - 女巫晚上不能一次使用兩種藥.
72 | - 獵人被毒殺不可開槍, 每晚都會睜眼看可不可以開槍.
73 | - 有使用的角色死掉時, 主持人會自動播放該角色相關台詞.
74 |
75 | ## 未實作功能
76 |
77 | - 女巫是否可以自救, 因為各玩法就不加入工具判斷了, 自己玩的時候講好就好, 自由心證.
78 | - 狼人是否可以連續兩天刀同一個人, 理由跟上面一樣, 自由心證.
79 | - 有使用騎士, 主持人每晚都會講一次台詞, 讓出局的玩家知道誰是騎士.
80 | - 有使用白癡, 主持人每晚都會講一次台詞, 讓出局的玩家知道誰是白癡.
81 | - 預言家可查驗自己, 你想查的話.
82 | - 女巫可毒自己, 你想毒的話.
83 | - 騎士可決鬥自己, 你想決鬥的話.
84 |
--------------------------------------------------------------------------------
/src/locales/languages/zh-TW.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "狼人殺法官",
3 | "gaming": "法官主持中...",
4 | "restart": "重新開始",
5 | "resetting": "重新設定",
6 | "player_number": "遊戲人數",
7 | "wolf_number": "狼人數量",
8 | "wolf": "狼人",
9 | "predictor": "預言家",
10 | "witch": "女巫",
11 | "hunter": "獵人",
12 | "villager": "村民",
13 | "knight": "騎士",
14 | "idiot": "白癡",
15 | "yes": "是",
16 | "no": "否",
17 | "finished": "完成",
18 | "start": "開始遊戲",
19 | "sit_number": "{{index}} 號玩家",
20 | "check_role": "請點選, 並確認身份.",
21 | "is_checked": "該玩家已確認身份.",
22 | "confirm": "確認",
23 | "your_role": "你的角色是...",
24 | "dead_person": "{{index}} 號玩家被殺死.",
25 | "is_use_save": "此回合已使用解藥, 不能使用毒藥",
26 | "wolf_kill": "狼人請殺人",
27 | "witch_save": "你要使用解藥嗎?",
28 | "witch_poison": "你要使用毒藥嗎?",
29 | "predictor_select": "你要查驗的對象是?",
30 | "role_result": "這位玩家的身份是",
31 | "is_wolf": "狼人",
32 | "not_wolf": "好人",
33 | "yesterday_dead": "昨夜的情況是",
34 | "christmas_eve": "平安夜",
35 | "dead_player": "{{ returnMessage }} 號玩家死亡",
36 | "dead_message": "遊戲訊息",
37 | "n_day": "第 {{day}} 天白天: ",
38 | "n_night" : "第 {{day}} 天夜晚: ",
39 | "start_vote": "開始放逐投票",
40 | "give_up": "放棄",
41 | "give_up_vote": "棄票",
42 | "save_used": "你已使用解藥",
43 | "poison_used": "你已使用毒藥",
44 | "game_over": "遊戲結束",
45 | "good_win": "正義聯盟獲勝",
46 | "bad_win": "邪惡陣營獲勝",
47 | "hunter_shoot": "獵人請發動技能",
48 | "could_shoot": "獵人是否可以開槍",
49 | "can_shoot": "可以",
50 | "cant_shoot": "不可以",
51 | "hunter_shoot_player": "獵人射殺: {{index}}",
52 | "last_words": "請發表遺言",
53 | "to_night": "天黑請閉眼",
54 | "is_kill_kind": "屠邊局",
55 | "bad_win_kind": "邪惡陣營獲勝, 屠邊成功.",
56 | "knight_fight": "騎士決鬥",
57 | "fight_result": "決鬥結果",
58 | "no_is_wolf": "{{index}} 是狼人",
59 | "no_is_not_wolf": "{{index}} 不是狼人, 騎士以死謝罪.",
60 | "idiot_result": "放逐無效",
61 | "no_is_idiot": "{{index}} 是白癡",
62 | "is_show_message": "是否顯示夜晚訊息",
63 | "is_mirror": "彈出視窗鏡像模式"
64 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 | Wolf Judge
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/locales/languages/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Wolf Judge",
3 | "gaming": "Judge hosting...",
4 | "restart": "Restart",
5 | "resetting": "Re Setting",
6 | "player_number": "Player Number",
7 | "wolf_number": "Wolf Number",
8 | "wolf": "Wolf",
9 | "predictor": "Predictor",
10 | "witch": "Witch",
11 | "hunter": "Hunter",
12 | "villager": "Villager",
13 | "knight": "Knight",
14 | "idiot": "idiot",
15 | "yes": "Yes",
16 | "no": "No",
17 | "finished": "Finished",
18 | "start": "Start Game",
19 | "sit_number": "Player No. {{index}}",
20 | "check_role": "Click and check the role.",
21 | "is_checked": "The player is checked.",
22 | "confirm": "Confirm",
23 | "your_role": "Your role is...",
24 | "dead_person": "Player No. {{index}} is dead.",
25 | "is_use_save": "Could't use two skill in the term.",
26 | "wolf_kill": "Kill someone",
27 | "witch_save": "Use Antidote?",
28 | "witch_poison": "Use Poison?",
29 | "predictor_select": "Check",
30 | "role_result": "The player is",
31 | "is_wolf": "Bad guy",
32 | "not_wolf": "Good guy",
33 | "yesterday_dead": "Last night is",
34 | "christmas_eve": "Safety night",
35 | "dead_player": "Player NO.{{ returnMessage }} was dead.",
36 | "dead_message": "Game Message",
37 | "n_day": "Night {{day}}: ",
38 | "n_night" : "Day {{day}}: ",
39 | "start_vote": "Start Voting",
40 | "give_up": "Give up",
41 | "give_up_vote": "Give up",
42 | "save_used": "You used it.",
43 | "poison_used": "You used it.",
44 | "game_over": "Game Over",
45 | "good_win": "Good guy win",
46 | "bad_win": "Bad guy win",
47 | "hunter_shoot": "Shoot",
48 | "could_shoot": "Can you shoot?",
49 | "can_shoot": "You can",
50 | "cant_shoot": "You can't",
51 | "hunter_shoot_player": "Hunter shooted: {{index}}",
52 | "last_words": "Publish last words",
53 | "to_night": "Close your eyes",
54 | "is_kill_kind": "Kill kind",
55 | "bad_win_kind": "Bad guy win",
56 | "knight_fight": "Kinght fight",
57 | "fight_result": "Fight Result",
58 | "no_is_wolf": "{{index}} is the wolf.",
59 | "no_is_not_wolf": "{{index}} is not the wolf. Knight is dead.",
60 | "idiot_result": "idiot Result",
61 | "no_is_idiot": "{{index}} is the idiot.",
62 | "is_show_message": "Is show messages?",
63 | "is_mirror": "Is mirror?"
64 | }
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/components/CheckRole/CheckRole.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/alt-text */
2 | import React, {
3 | useState,
4 | } from 'react';
5 | import { useTranslation } from "react-i18next";
6 | import { makeStyles } from '@material-ui/core/styles';
7 | import Paper from '@material-ui/core/Paper';
8 | import Typography from '@material-ui/core/Typography';
9 | import Dialog from '@material-ui/core/Dialog';
10 | import DialogActions from '@material-ui/core/DialogActions';
11 | import DialogContent from '@material-ui/core/DialogContent';
12 | import DialogContentText from '@material-ui/core/DialogContentText';
13 | import DialogTitle from '@material-ui/core/DialogTitle';
14 | import Button from '@material-ui/core/Button';
15 | import Divider from '@material-ui/core/Divider';
16 |
17 | import wolf from '../../static/images/wolf.jpg';
18 | import predictor from '../../static/images/predictor.jpg';
19 | import witch from '../../static/images/witch.jpg';
20 | import hunter from '../../static/images/hunter.jpg';
21 | import knight from '../../static/images/knight.jpg';
22 | import idiot from '../../static/images/idiot.jpg';
23 | import villager from '../../static/images/villager.jpg';
24 |
25 | const useStyles = makeStyles(theme => ({
26 | root: {
27 | padding: theme.spacing(3, 2),
28 | margin: '10px',
29 | },
30 | isChecked: {
31 | color: 'red',
32 | },
33 | role: {
34 | textAlign: 'center',
35 | fontSize: '30px',
36 | },
37 | }));
38 |
39 | const RoleCard = (props) => {
40 | const { sit } = props;
41 | const { t } = useTranslation();
42 | const classes = useStyles();
43 |
44 | const [isWatch, setIsWatch] = useState(false);
45 | const [isOpen, setIsOpen] = useState(false);
46 |
47 | const handleClick = () => {
48 | if (!isWatch) {
49 | setIsOpen(true);
50 | }
51 | }
52 |
53 | const handleClose = () => {
54 | setIsOpen(false);
55 | setIsWatch(true);
56 | }
57 |
58 | let src = null;
59 | switch(sit.role.key) {
60 | case 'wolf':
61 | src = wolf;
62 | break;
63 | case 'predictor':
64 | src = predictor;
65 | break;
66 | case 'witch':
67 | src = witch;
68 | break;
69 | case 'hunter':
70 | src = hunter;
71 | break;
72 | case 'knight':
73 | src = knight;
74 | break;
75 | case 'idiot':
76 | src = idiot;
77 | break;
78 | case 'villager':
79 | src = villager;
80 | break;
81 | default:
82 | break;
83 | };
84 |
85 | return (
86 | <>
87 |
88 |
89 | {
90 | t('sit_number', { index: sit.index })
91 | // sit.role.key
92 | }
93 |
94 |
95 | { (isWatch) ? (
96 |
97 | { t('is_checked') }
98 |
99 | ) : t('check_role') }
100 |
101 |
102 |
103 |
124 | >
125 | );
126 | };
127 |
128 | const CheckRole = (props) => {
129 | const { t } = useTranslation();
130 | const { list, handleStartGame } = props;
131 | // const classes = useStyles();
132 |
133 | return (
134 | <>
135 | {
136 | list.map((sit, index) => {
137 | return (
138 |
139 |
142 |
143 | )
144 | })
145 | }
146 |
147 |
148 | >
149 | );
150 | };
151 |
152 | export default CheckRole;
153 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/Setting/Setting.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | Fragment
3 | } from 'react';
4 | import { useTranslation } from "react-i18next";
5 | import { makeStyles } from '@material-ui/core/styles';
6 | import Button from '@material-ui/core/Button';
7 | import FormControl from '@material-ui/core/FormControl';
8 | import Divider from '@material-ui/core/Divider';
9 | import InputLabel from '@material-ui/core/InputLabel';
10 | import NativeSelect from '@material-ui/core/NativeSelect';
11 |
12 | const useStyles = makeStyles(theme => ({
13 | root: {
14 | display: 'flex',
15 | },
16 | group: {
17 | margin: theme.spacing(1, 0),
18 | },
19 | formControl: {
20 | margin: theme.spacing(1),
21 | minWidth: 200,
22 | },
23 | selectEmpty: {
24 | marginTop: theme.spacing(2),
25 | },
26 | }));
27 |
28 | const Setting = (props) => {
29 | const classes = useStyles();
30 | const { t } = useTranslation();
31 | const {
32 | playerNumber,
33 | setPlayerNumber,
34 | wolfNumber,
35 | setWolfNumber,
36 | isUsePredictor,
37 | setIsUsePredictor,
38 | isUseWitch,
39 | setIsUseWitch,
40 | isUseHunter,
41 | setIsUseHunter,
42 | handleStart,
43 | isKillKind,
44 | setIsKillKind,
45 | isUseKnight,
46 | setIsUseKnight,
47 | isUseidiot,
48 | setIsUseidiot,
49 | isMirror,
50 | setIsMirror,
51 | } = props;
52 |
53 | return (
54 |
55 |
56 |
57 | {t('player_number')}
58 | {setPlayerNumber(e.target.value * 1)}}
62 | >
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {t('wolf_number')}
77 | {setWolfNumber(e.target.value * 1)}}
81 | >
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | {t('is_kill_kind')}
93 | {setIsKillKind(e.target.value === 'true')}}
97 | >
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | {t('predictor')}
107 | {setIsUsePredictor(e.target.value === 'true')}}
111 | >
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | {t('witch')}
121 | {setIsUseWitch(e.target.value === 'true')}}
125 | >
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | {t('hunter')}
135 | {setIsUseHunter(e.target.value === 'true')}}
139 | >
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | {t('knight')}
149 | {setIsUseKnight(e.target.value === 'true')}}
153 | >
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | {t('idiot')}
163 | {setIsUseidiot(e.target.value === 'true')}}
167 | >
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | {t('is_mirror')}
177 | {setIsMirror(e.target.value === 'true')}}
181 | >
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | );
192 | };
193 |
194 | export default Setting;
195 |
--------------------------------------------------------------------------------
/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-extra-label */
2 | import React, {
3 | useState,
4 | } from 'react';
5 | import { useTranslation } from "react-i18next";
6 | import Typography from '@material-ui/core/Typography';
7 | import Container from '@material-ui/core/Container';
8 | import { makeStyles } from '@material-ui/core/styles';
9 | import AppBar from '@material-ui/core/AppBar';
10 | import Toolbar from '@material-ui/core/Toolbar';
11 | import IconButton from '@material-ui/core/IconButton';
12 | import MenuIcon from '@material-ui/icons/Menu';
13 | import Select from '@material-ui/core/Select';
14 | import Drawer from '@material-ui/core/Drawer';
15 | import Divider from '@material-ui/core/Divider';
16 | import List from '@material-ui/core/List';
17 | import ListItem from '@material-ui/core/ListItem';
18 | import ListItemIcon from '@material-ui/core/ListItemIcon';
19 | import ListItemText from '@material-ui/core/ListItemText';
20 | import CachedIcon from '@material-ui/icons/Cached';
21 |
22 | import {
23 | WOLF,
24 | PREDICTOR,
25 | WITCH,
26 | HUNTER,
27 | KNIGHT,
28 | idiot,
29 | VILLAGER,
30 | } from '../../constants/Role';
31 | import Setting from '../Setting/Setting';
32 | import CheckRole from '../CheckRole/CheckRole';
33 | import Game from '../Game/Game';
34 |
35 | const drawerWidth = 240;
36 |
37 | const createArray = (len, itm) => {
38 | let arr1 = [itm],
39 | arr2 = [];
40 | while (len > 0) {
41 | if (len & 1) arr2 = arr2.concat(arr1);
42 | arr1 = arr1.concat(arr1);
43 | len >>>= 1;
44 | }
45 | return arr2;
46 | }
47 |
48 | const useStyles = makeStyles(theme => ({
49 | container: {
50 | paddingLeft: '0px',
51 | paddingRight: '0px',
52 | height: '100vh',
53 | },
54 | toolbar: theme.mixins.toolbar,
55 | drawer: {
56 | width: drawerWidth,
57 | },
58 | drawerPaper: {
59 | width: drawerWidth,
60 | },
61 | root: {
62 | flexGrow: 1,
63 | },
64 | menuButton: {
65 | marginRight: theme.spacing(2),
66 | },
67 | title: {
68 | flexGrow: 1,
69 | textAlign: 'left',
70 | },
71 | list: {
72 | width: 250,
73 | },
74 | fullList: {
75 | width: 'auto',
76 | },
77 | }));
78 |
79 | const Home = (props) => {
80 | const [step, setStep] = useState(0);
81 | const [list, setList] = useState([]);
82 | const [playerNumber, setPlayerNumber] = useState(6);
83 | const [wolfNumber, setWolfNumber] = useState(2);
84 | const [isUsePredictor, setIsUsePredictor] = useState(false);
85 | const [isUseWitch, setIsUseWitch] = useState(true);
86 | const [isUseHunter, setIsUseHunter] = useState(false);
87 | const [isUseKnight, setIsUseKnight] = useState(false);
88 | const [isUseidiot, setIsUseidiot] = useState(false);
89 | const [isKillKind, setIsKillKind] = useState(false); // 屠邊局判斷
90 | const [isMirror, setIsMirror] = useState(true); // modal 是否開啟鏡像
91 |
92 | const [isOpenDrawer, setIsOpenDrawer] = useState(false);
93 | const { t, i18n } = useTranslation();
94 | const classes = useStyles();
95 |
96 | const handleChange = (value) => {
97 | i18n.changeLanguage(value);
98 | }
99 |
100 | const toggleDrawer = (isOpen) => event => {
101 | if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
102 | return;
103 | }
104 | setIsOpenDrawer(isOpen);
105 | };
106 |
107 | const handleRestart = () => {
108 | window.location.reload();
109 | }
110 |
111 | const getRaddomIndex = () => {
112 | return Math.floor(Math.random() * playerNumber);
113 | }
114 |
115 | const handleStart = () => {
116 | const list = createArray(playerNumber, null);
117 |
118 | // 狼人位置
119 | for (let i = 0 ; i < wolfNumber ; i += 1) {
120 | let index = null;
121 |
122 | beginning: while(true) {
123 | index = getRaddomIndex();
124 | if(list[index] !== null) {
125 | continue beginning;
126 | } else {
127 | break;
128 | }
129 | }
130 |
131 | list[index] = {
132 | index: index + 1,
133 | role: WOLF,
134 | };
135 | }
136 |
137 | // 預言家位置
138 | if (isUsePredictor) {
139 | let index = null;
140 |
141 | beginning: while(true) {
142 | index = getRaddomIndex();
143 | if(list[index] !== null) {
144 | continue beginning;
145 | } else {
146 | break;
147 | }
148 | }
149 |
150 | list[index] = {
151 | index: index + 1,
152 | role: PREDICTOR,
153 | };
154 | }
155 |
156 | // 女巫位置
157 | if (isUseWitch) {
158 | let index = null;
159 |
160 | beginning: while(true) {
161 | index = getRaddomIndex();
162 | if(list[index] !== null) {
163 | continue beginning;
164 | } else {
165 | break;
166 | }
167 | }
168 |
169 | list[index] = {
170 | index: index + 1,
171 | role: WITCH,
172 | };
173 | }
174 |
175 | // 獵人位置
176 | if (isUseHunter) {
177 | let index = null;
178 |
179 | beginning: while(true) {
180 | index = getRaddomIndex();
181 | if(list[index] !== null) {
182 | continue beginning;
183 | } else {
184 | break;
185 | }
186 | }
187 |
188 | list[index] = {
189 | index: index + 1,
190 | role: HUNTER,
191 | };
192 | }
193 |
194 | // 騎士位置
195 | if (isUseKnight) {
196 | let index = null;
197 |
198 | beginning: while(true) {
199 | index = getRaddomIndex();
200 | if(list[index] !== null) {
201 | continue beginning;
202 | } else {
203 | break;
204 | }
205 | }
206 |
207 | list[index] = {
208 | index: index + 1,
209 | role: KNIGHT,
210 | };
211 | }
212 |
213 | // 白癡位置
214 | if (isUseidiot) {
215 | let index = null;
216 |
217 | beginning: while(true) {
218 | index = getRaddomIndex();
219 | if(list[index] !== null) {
220 | continue beginning;
221 | } else {
222 | break;
223 | }
224 | }
225 |
226 | list[index] = {
227 | index: index + 1,
228 | role: idiot,
229 | };
230 | }
231 |
232 | // 村民位置
233 | list.forEach((sit, index) => {
234 | if (sit === null) {
235 | list[index] = {
236 | index: index + 1,
237 | role: VILLAGER,
238 | };
239 | }
240 | })
241 |
242 | setList(list);
243 | setStep(1);
244 | }
245 |
246 | const handleStartGame = () => {
247 | setStep(2);
248 | }
249 |
250 | return (
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 | { t('title') }
261 |
262 |
275 |
276 |
277 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 | {/*}
294 |
295 |
296 |
297 |
298 | {*/}
299 |
300 |
301 |
302 | {
303 | (step === 0) && (
304 |
325 | )
326 | }
327 | {
328 | (step === 1) && (
329 |
333 | )
334 | }
335 | {
336 | (step === 2) && (
337 |
351 | )
352 | }
353 |
354 |
355 |
356 | );
357 | }
358 |
359 | export default Home
360 |
--------------------------------------------------------------------------------
/src/components/Game/Game.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | useMemo,
4 | useCallback,
5 | } from 'react';
6 | import Sound from 'react-sound';
7 | import Dialog from '@material-ui/core/Dialog';
8 | import DialogActions from '@material-ui/core/DialogActions';
9 | import DialogContent from '@material-ui/core/DialogContent';
10 | import DialogContentText from '@material-ui/core/DialogContentText';
11 | import DialogTitle from '@material-ui/core/DialogTitle';
12 | import Button from '@material-ui/core/Button';
13 | import Avatar from '@material-ui/core/Avatar';
14 | import Grid from '@material-ui/core/Grid';
15 | import { makeStyles } from '@material-ui/core/styles';
16 | import {
17 | pink,
18 | } from '@material-ui/core/colors';
19 | import Divider from '@material-ui/core/Divider';
20 | import CheckIcon from '@material-ui/icons/Check';
21 | import CloseIcon from '@material-ui/icons/Close';
22 | import FormControlLabel from '@material-ui/core/FormControlLabel';
23 | import Switch from '@material-ui/core/Switch';
24 |
25 | import { useTranslation } from "react-i18next";
26 |
27 | import {
28 | WOLF,
29 | PREDICTOR,
30 | WITCH,
31 | HUNTER,
32 | KNIGHT,
33 | idiot,
34 | VILLAGER,
35 | } from '../../constants/Role';
36 |
37 | import step1 from '../../static/audio/step_1.mp3'; // 天黑請閉眼
38 | import step2 from '../../static/audio/step_2.mp3'; // 狼人現身請睜眼
39 | import step3 from '../../static/audio/step_3.mp3'; // 狼人確認彼此的身份
40 | import step4 from '../../static/audio/step_4.mp3'; // 狼人請殺人
41 | import step5 from '../../static/audio/step_5.mp3'; // 狼人請閉眼
42 | import step6 from '../../static/audio/step_6.mp3'; // 女巫請睜眼
43 | import step7 from '../../static/audio/step_7.mp3'; // 這位玩家被殺死了
44 | import step8 from '../../static/audio/step_8.mp3'; // 你要使用解藥嗎
45 | import step9 from '../../static/audio/step_9.mp3'; // 你要使用毒藥嗎
46 | import step10 from '../../static/audio/step_10.mp3'; // 你要毒誰呢
47 | import step11 from '../../static/audio/step_11.mp3'; // 女巫請閉眼
48 | import step12 from '../../static/audio/step_12.mp3'; // 預言家請睜眼
49 | import step13 from '../../static/audio/step_13.mp3'; // 你要查驗的對象是
50 | import step14 from '../../static/audio/step_14.mp3'; // 他的身份是
51 | import step15 from '../../static/audio/step_15.mp3'; // 預言家請閉眼
52 | import step16 from '../../static/audio/step_16.mp3'; // 獵人請睜眼
53 | import step17 from '../../static/audio/step_17.mp3'; // 獵人請閉眼
54 | import step18 from '../../static/audio/step_18.mp3'; // 騎士請睜眼
55 | import step19 from '../../static/audio/step_19.mp3'; // 騎士請閉眼
56 | import step20 from '../../static/audio/step_20.mp3'; // 白癡請睜眼
57 | import step21 from '../../static/audio/step_21.mp3'; // 白癡請閉眼
58 | import step22 from '../../static/audio/step_22.mp3'; // 天亮請睜眼
59 |
60 | /**
61 | * IS_DEBUG
62 | * 是否開啟 console.log 資訊
63 | *
64 | */
65 | const IS_DEBUG = true;
66 |
67 | /**
68 | * DAY_TYPE
69 | * 白天, 晚上
70 | *
71 | */
72 | const DAY_TYPE = {
73 | DAY: 'DAY',
74 | NIGHT: 'NIGHT',
75 | }
76 |
77 | const AudioSound = React.memo((props) => {
78 | const {
79 | url,
80 | onFinishedPlaying,
81 | } = props;
82 |
83 | return (
84 |
91 | );
92 | });
93 |
94 | const useStyles = makeStyles({
95 | avatar: {
96 | margin: 7,
97 | color: '#fff',
98 | backgroundColor: '#4DB6AC',
99 | width: '40px',
100 | height: '40px',
101 | },
102 | pinkAvatar: {
103 | margin: 7,
104 | color: '#fff',
105 | backgroundColor: pink[500],
106 | width: '40px',
107 | height: '40px',
108 | },
109 | dead: {
110 | margin: 7,
111 | color: '#fff',
112 | backgroundColor: '#9E9E9E',
113 | width: '40px',
114 | height: '40px',
115 | },
116 | number: {
117 | fontSize: '25px',
118 | },
119 | good: {
120 | fontSize: '30px',
121 | },
122 | bad: {
123 | fontSize: '30px',
124 | color: 'red',
125 | },
126 | // greenAvatar: {
127 | // margin: 10,
128 | // color: '#fff',
129 | // backgroundColor: green[500],
130 | // },
131 | });
132 |
133 | const Game = (props) => {
134 | const classes = useStyles();
135 | const {
136 | list,
137 | isUsePredictor,
138 | isUseWitch,
139 | isUseHunter,
140 | playerNumber,
141 | wolfNumber,
142 | isKillKind,
143 | isUseKnight,
144 | isUseidiot,
145 | isMirror,
146 | } = props;
147 |
148 | if (IS_DEBUG) {
149 | // console.log('list', list);
150 | console.log('===== Role List =====');
151 | list.forEach(tmp => { console.log(`${tmp.index} - ${tmp.role.key}`); });
152 | console.log('=====================');
153 | }
154 |
155 | const { t } = useTranslation();
156 | const [step, setStep] = useState(1);
157 | const [isOpenWolfKill, setIsOpenWolfKill] = useState(false);
158 | const [deadNumber, setDeadNumber] = useState(null); // 狼人晚上殺人的
159 | const [witchDeadNumber, setWitchDeadNumber] = useState(null); // 女巫毒的角色
160 | const [isOpenWitchSave, setIsOpenWitchSave] = useState(false); // 解藥詢問 視窗
161 | const [isOpenWitchPoison, setIsOpenWitchPoison] = useState(false); // 毒藥詢問 視窗
162 | const [isUse, setIsUse] = useState(false); // 女巫一晚只能使用一種藥
163 | const [isUseSave, setIsUseSave] = useState(false); // 是否已使用解藥
164 | const [isUsePoison, setIsUsePoison] = useState(false); // 是否已使用毒藥
165 | const [isOpenPredictor, setIsOpenPredictor] = useState(false); // 預言家選擇身份 視窗
166 | const [predictorSelect, setPredictorSelect] = useState(null); // 預言家選擇查驗的身份
167 | const [isOpenRole, setIsOpenRols] = useState(false); // 預言家查看身份 視窗
168 | const [isOpenResult, setIsOpenResult] = useState(false); // 晚上結果
169 | const [day, setDay] = useState(1); // 第幾天
170 | const [dayType, setDayType] = useState(DAY_TYPE.NIGHT); // 白天晚上
171 | const [messages, setMessages] = useState([]); // 遊戲訊息
172 | const [isOpenVote, setIsOpenVote] = useState(false); // 投票視窗
173 | const [selectVote, setSelectVote] = useState(null); // 選擇投票的人
174 | const [dead, setDead] = useState([]); // 死亡的人
175 | const [isOpenGameResult, setIsOpenGameResult] = useState(false); // 是否開啟遊戲結束畫面
176 | const [gameResultMessage, setGameResultMessage] = useState(''); // 遊戲結束訊息
177 | const [isUseHunterSkill, setIsUseHunterSkill] = useState(false); // 獵人是否已使用技能
178 | const [hunterSelect, setHunterSelect] = useState(null); // 獵人選擇
179 | const [isOpenHunter, setIsOpenHunter] = useState(false); // 是否開啟獵人視窗
180 | const [isKillByWitch, setIsKillByWitch] = useState(false); // 獵人是否被毒殺
181 | const [isOpenHunterShoot, setIsOpenHunterShoot] = useState(false); // 是否開啟獵人槍殺訊息
182 | const [isPredictorDead, setIsPredictorDead] = useState(false); // 預言家是否死亡
183 | const [isWitchDead, setIsWitchDead] = useState(false); // 女巫是否死亡
184 | const [isHunterDead, setIsHunterDead] = useState(false); // 獵人是否死亡
185 | const [isOpenLastWords, setIsOpenLastWords] = useState(false); // 遺言視窗
186 | const [isKnightDead, setIsKnightDead] = useState(false); // 騎士是否死亡
187 | const [isidiotDead, setIsidiotDead] = useState(false); // 白癡是否死亡
188 |
189 | const [isUseKnightSkill, setIsUseKnightSkill] = useState(false); // 騎士是否已實用技能
190 | const [isOpenKnight, setIsOpenKnight] = useState(false); // 開啟騎士選擇對象視窗
191 | const [knightSelect, setKnightSelect] = useState(null); // 騎士選擇
192 | const [isOpenKnightResult, setIsOpenKnightResult] = useState(false); // 騎士驗人結果
193 | const [isOpenidiotResult, setIsOpenidiotResult] = useState(false); // 放逐白癡結果
194 | const [isUseidiotSkill, setIsUseidiotSkill] = useState(false); // 白癡是否使用技能
195 |
196 | const [isShowMessage, setIsShowMessage] = useState(false); // 是否顯示夜晚訊息
197 |
198 | // console.log('isKillByWitch', isKillByWitch);
199 | // console.log('isUsePoison', isUsePoison);
200 |
201 | const handleSongFinishedPlaying = useCallback(() => {
202 | // setStep(step + 1);
203 | switch(step) {
204 | case 1:
205 | setStep(2);
206 | break;
207 | case 2:
208 | setStep(3);
209 | break;
210 | case 3:
211 | setStep(4);
212 | break;
213 | case 4:
214 | // setStep(2);
215 | setIsOpenWolfKill(true);
216 | break;
217 | case 5:
218 | // 是否有使用女巫
219 | if (isUseWitch) {
220 | setStep(6);
221 | } else {
222 | // 是否使用預言家
223 | if (isUsePredictor) {
224 | setStep(12);
225 | } else {
226 | if (isUseHunter) {
227 | // 是否使用獵人
228 | setStep(16);
229 | } else {
230 | if (isUseKnight) {
231 | setStep(18);
232 | } else {
233 | if (isUseidiot) {
234 | setStep(20);
235 | } else {
236 | setStep(22);
237 | }
238 | }
239 | }
240 | }
241 | }
242 | break;
243 | case 6:
244 | setStep(7);
245 | break;
246 | case 7:
247 | if (!isWitchDead) {
248 | setIsOpenWitchSave(true);
249 | }
250 | setStep(8);
251 | break;
252 | case 8:
253 | if (isWitchDead) {
254 | setTimeout(() => {
255 | setStep(9)
256 | }, 2000);
257 | }
258 | break;
259 | case 9:
260 | if (!isWitchDead) {
261 | setIsOpenWitchPoison(true);
262 | }
263 | setStep(10);
264 | break;
265 | case 10:
266 | if (isWitchDead) {
267 | setTimeout(() => {
268 | setStep(11)
269 | }, 2000);
270 | }
271 | break;
272 | case 11:
273 | // 是否使用預言家
274 | if (isUsePredictor) {
275 | setStep(12);
276 | } else {
277 | if (isUseHunter) {
278 | // 是否使用獵人
279 | setStep(16);
280 | } else {
281 | if (isUseKnight) {
282 | setStep(18);
283 | } else {
284 | if (isUseidiot) {
285 | setStep(20);
286 | } else {
287 | setStep(22);
288 | }
289 | }
290 | }
291 | }
292 | // setStep(12);
293 | break;
294 | case 12:
295 | setStep(13);
296 | break;
297 | case 13:
298 | if (!isPredictorDead) {
299 | setIsOpenPredictor(true);
300 | } else {
301 | setTimeout(() => {
302 | setStep(14)
303 | }, 2000);
304 | }
305 | break;
306 | case 14:
307 | // setStep(15);
308 | if (isPredictorDead) {
309 | setTimeout(() => {
310 | setStep(15)
311 | }, 2000);
312 | }
313 | break;
314 | case 15:
315 | if (isUseHunter) {
316 | // 是否使用獵人
317 | setStep(16);
318 | } else {
319 | if (isUseKnight) {
320 | setStep(18);
321 | } else {
322 | if (isUseidiot) {
323 | setStep(20);
324 | } else {
325 | setStep(22);
326 | }
327 | }
328 | }
329 | break;
330 | case 16:
331 | if (!isHunterDead) {
332 | setIsOpenHunterShoot(true);
333 | } else {
334 | setTimeout(() => {
335 | setStep(17)
336 | }, 2000);
337 | }
338 | break;
339 | case 17:
340 | if (isUseKnight) {
341 | setStep(18);
342 | } else {
343 | if (isUseidiot) {
344 | setStep(20);
345 | } else {
346 | setStep(22);
347 | }
348 | }
349 | break;
350 | case 18:
351 | setStep(19);
352 | break;
353 | case 19:
354 | if (isUseidiot) {
355 | setStep(20);
356 | } else {
357 | setStep(22);
358 | }
359 | break;
360 | case 20:
361 | setStep(21);
362 | break;
363 | case 21:
364 | setStep(22);
365 | break;
366 | case 22:
367 | setIsOpenResult(true);
368 | break;
369 | default:
370 | }
371 | }, [step]);
372 |
373 | /**
374 | * handleCloseWolfKill
375 | * 狼人殺人視窗
376 | *
377 | */
378 | const handleCloseWolfKill = () => {
379 | setIsOpenWolfKill(false);
380 | setStep(5);
381 | }
382 |
383 | /**
384 | * handleWitchSave
385 | * 女巫是否使用解藥
386 | *
387 | * @param {bool} isSave - true: 使用, false: 不使用
388 | */
389 | const handleWitchSave = (isSave) => {
390 | setIsOpenWitchSave(false);
391 | setStep(9);
392 |
393 | if (isSave) {
394 | // 使用解藥
395 | setTimeout(() => {
396 | setIsUse(true);
397 | setIsUseSave(true);
398 | setDeadNumber(null);
399 | }, 500);
400 | }
401 | }
402 |
403 | /**
404 | * handleWitchPoison
405 | * 關閉女巫是否使用毒藥
406 | *
407 | * @param {bool} isPoison - true: 使用, false: 不使用
408 | */
409 | const handleWitchPoison = (isPoison) => {
410 | setIsOpenWitchPoison(false);
411 | setStep(11);
412 |
413 | if (!isUsePoison) {
414 | setTimeout(() => {
415 | setIsUsePoison(isPoison);
416 | }, 500);
417 | }
418 |
419 | if (!isPoison) {
420 | setWitchDeadNumber(null);
421 | } else {
422 | // 使用毒藥, 判斷是不是毒到獵人
423 | if (IS_DEBUG) {
424 | console.log('witchDeadNumber.role.key', witchDeadNumber.role.key);
425 | }
426 | if (witchDeadNumber !== null && witchDeadNumber.role.key === HUNTER.key) {
427 | setIsKillByWitch(true);
428 | }
429 | }
430 | }
431 |
432 | /**
433 | * handlePredictor
434 | * 關閉預言家詢問視窗
435 | *
436 | */
437 | const handlePredictor = () => {
438 | setIsOpenPredictor(false);
439 | setIsOpenRols(true);
440 | setStep(14);
441 | }
442 |
443 | /**
444 | * handleCloseCheckRole
445 | * 關閉預言家查驗身份結果
446 | *
447 | */
448 | const handleCloseCheckRole = () => {
449 | setIsOpenRols(false);
450 | setStep(15);
451 | }
452 |
453 | /**
454 | * audioSrc
455 | * 取得音效檔
456 | *
457 | */
458 | const audioSrc = useMemo(() => {
459 | let returnSrc = null;
460 |
461 | switch(step) {
462 | case 1:
463 | returnSrc = step1;
464 | break;
465 | case 2:
466 | returnSrc = step2;
467 | break;
468 | case 3:
469 | returnSrc = step3;
470 | break;
471 | case 4:
472 | returnSrc = step4;
473 | break;
474 | case 5:
475 | returnSrc = step5;
476 | break;
477 | case 6:
478 | returnSrc = step6;
479 | break;
480 | case 7:
481 | returnSrc = step7;
482 | break;
483 | case 8:
484 | returnSrc = step8;
485 | break;
486 | case 9:
487 | returnSrc = step9;
488 | break;
489 | case 10:
490 | returnSrc = step10;
491 | break;
492 | case 11:
493 | returnSrc = step11;
494 | break;
495 | case 12:
496 | returnSrc = step12;
497 | break;
498 | case 13:
499 | returnSrc = step13;
500 | break;
501 | case 14:
502 | returnSrc = step14;
503 | break;
504 | case 15:
505 | returnSrc = step15;
506 | break;
507 | case 16:
508 | returnSrc = step16;
509 | break;
510 | case 17:
511 | returnSrc = step17;
512 | break;
513 | case 18:
514 | returnSrc = step18;
515 | break;
516 | case 19:
517 | returnSrc = step19;
518 | break;
519 | case 20:
520 | returnSrc = step20;
521 | break;
522 | case 21:
523 | returnSrc = step21;
524 | break;
525 | case 22:
526 | returnSrc = step22;
527 | break;
528 | default:
529 | break;
530 | }
531 |
532 | return returnSrc;
533 | }, [step]);
534 |
535 | /**
536 | * generateResultMessage
537 | * 組出當晚死亡訊息
538 | *
539 | */
540 | const generateResultMessage = () => {
541 | let returnMessage = null;
542 | if (deadNumber === null && witchDeadNumber === null) {
543 | returnMessage = t('christmas_eve');
544 | return returnMessage;
545 | } else {
546 | let tmp = [];
547 | if (deadNumber !== null) {
548 | tmp.push(deadNumber.index);
549 | }
550 | if (witchDeadNumber !== null && deadNumber !== null && witchDeadNumber.index !== deadNumber.index) {
551 | tmp.push(witchDeadNumber.index);
552 | }
553 |
554 | // 重新排序
555 | tmp.sort((a, b) => {
556 | return a - b;
557 | });
558 |
559 | // 每晚最多只會有兩位玩家死掉
560 | tmp.forEach((number, index) => {
561 | returnMessage += number;
562 | if (tmp.length === 2 && index !== tmp.length - 1) {
563 | returnMessage += ', ';
564 | }
565 | });
566 | }
567 |
568 | return t('dead_player', { returnMessage });
569 | }
570 |
571 | /**
572 | * handleCloseResult
573 | * 關閉晚上結果
574 | * 判斷是否結束遊戲
575 | * 判斷是否有獵人
576 | *
577 | */
578 | const handleCloseResult = () => {
579 | // 設定成白天
580 | // setDayType(DAY_TYPE.DAY);
581 |
582 | // 關閉晚上結果
583 | setIsOpenResult(false);
584 |
585 | // 更新遊戲訊息
586 | setMessages([
587 | ...messages,
588 | `${t('n_night', { day })}${generateResultMessage()}`
589 | ]);
590 |
591 | const tmpArray = [];
592 |
593 | // 狼人殺死的人
594 | if (deadNumber !== null) {
595 | tmpArray.push(deadNumber);
596 | // setDead([
597 | // ...dead,
598 | // deadNumber,
599 | // ]);
600 | }
601 |
602 | // 女巫毒的人
603 | if (isUseWitch && witchDeadNumber !== null && deadNumber.index !== witchDeadNumber.index) {
604 | tmpArray.push(witchDeadNumber);
605 | }
606 |
607 | // 更新已死亡的人
608 | const tmpDead = [
609 | ...dead,
610 | ...tmpArray,
611 | ];
612 | if (tmpArray.length > 0) {
613 | setDead(tmpDead);
614 | }
615 |
616 | const result = checkGameFinished(tmpDead);
617 | if (result.isFinished) {
618 | setIsOpenGameResult(true);
619 | setGameResultMessage(result.message);
620 | } else {
621 | // 檢查獵人是否死亡
622 | const isHunter = checkHunter(tmpDead);
623 |
624 | if (isHunter) {
625 | setIsOpenHunter(true);
626 | } else {
627 | setTimeout(() => {
628 | initSelect(false);
629 | }, 500);
630 | }
631 | }
632 | }
633 |
634 | /**
635 | * generateSelectPicker
636 | * 組出選擇頭像 component
637 | *
638 | * @param {string} role - 角色
639 | */
640 | const generateSelectPicker = (role) => {
641 | let returnComp = null;
642 | let selectValue = null;
643 | let selectFunc = null;
644 |
645 | switch(role) {
646 | // 狼人
647 | case WOLF.key:
648 | selectValue = deadNumber;
649 | selectFunc = setDeadNumber;
650 | break;
651 | // 女巫
652 | case WITCH.key:
653 | selectValue = witchDeadNumber;
654 | selectFunc = setWitchDeadNumber;
655 | break;
656 | // 預言家
657 | case PREDICTOR.key:
658 | selectValue = predictorSelect;
659 | selectFunc = setPredictorSelect;
660 | break;
661 | // 獵人
662 | case HUNTER.key:
663 | selectValue = hunterSelect;
664 | selectFunc = setHunterSelect;
665 | break;
666 | // 騎士
667 | case KNIGHT.key:
668 | selectValue = knightSelect;
669 | selectFunc = setKnightSelect;
670 | break;
671 | // 一般投票
672 | default:
673 | selectValue = selectVote;
674 | selectFunc = setSelectVote;
675 | }
676 |
677 | returnComp = (
678 |
679 | {
680 | list.map(sit => {
681 | let className = classes.avatar;
682 |
683 | if (selectValue) {
684 | if (selectValue.index === sit.index) {
685 | className = classes.pinkAvatar;
686 | };
687 | }
688 |
689 | // 判斷該玩家是否死亡
690 | const idDead = dead.some(tmp => tmp.index === sit.index);
691 |
692 | return (
693 | <>
694 | {
695 | (idDead) ? (
696 |
697 |
698 | { sit.index }
699 |
700 |
701 | ) : (
702 | {selectFunc(sit)}}>
703 |
704 | { sit.index }
705 |
706 |
707 | )
708 | }
709 | >
710 | );
711 | })
712 | }
713 |
714 | );
715 |
716 | return returnComp;
717 | }
718 |
719 | /**
720 | * handleVote
721 | * 投票結果
722 | *
723 | * @param {bool} isVote - 是否有投票, false: 放棄
724 | */
725 | const handleVote = (isVote) => {
726 | setDayType(DAY_TYPE.DAY);
727 |
728 | // 關閉投票視窗
729 | setIsOpenVote(false);
730 |
731 | if (isVote) {
732 | // 判斷被放逐的是不是白癡
733 | if (selectVote.role.key === idiot.key && isUseidiotSkill === false) {
734 | setMessages([
735 | ...messages,
736 | `${t('n_day', { day })}${t('no_is_idiot', { index: selectVote.index })}, ${t('idiot_result')}`,
737 | ]);
738 |
739 | setIsUseidiotSkill(true);
740 | setIsOpenidiotResult(true);
741 | } else {
742 | const tmpDead = [
743 | ...dead,
744 | selectVote,
745 | ]
746 | if (isVote) {
747 | setDead(tmpDead);
748 |
749 | setMessages([
750 | ...messages,
751 | `${t('n_day', { day })}${selectVote.index}`,
752 | ]);
753 | } else {
754 | setMessages([
755 | ...messages,
756 | `${t('n_day', { day })}${t('give_up_vote')}`,
757 | ]);
758 | }
759 |
760 | const result = checkGameFinished(tmpDead);
761 | if (result.isFinished) {
762 | setIsOpenGameResult(true);
763 | setGameResultMessage(result.message);
764 | } else {
765 | // 檢查獵人是否死亡
766 | const isHunter = checkHunter(tmpDead);
767 |
768 | if (isHunter) {
769 | setIsOpenHunter(true);
770 | } else {
771 | // initSelect(true);
772 | setIsOpenLastWords(true);
773 | }
774 | }
775 | }
776 | } else {
777 | // initSelect(true);
778 | setIsOpenLastWords(true);
779 | }
780 | }
781 |
782 | /**
783 | * checkHunter
784 | * 檢查獵人是否死亡
785 | *
786 | * @param {array} dead - 死掉的人
787 | */
788 | const checkHunter = (dead) => {
789 | let isHunter = false;
790 |
791 | // 有使用獵人並未發動技能
792 | // console.log('isUseHunter', isUseHunter);
793 | // console.log('isUseHunterSkill', isUseHunterSkill);
794 | // console.log('isKillByWitch', isKillByWitch);
795 | if (isUseHunter && !isUseHunterSkill && !isKillByWitch && !isHunterDead) {
796 | isHunter = dead.some(tmp => tmp.role.key === HUNTER.key);
797 | }
798 | // console.log('isHunter', isHunter);
799 | return isHunter;
800 | }
801 |
802 | /**
803 | * checkGameFinished
804 | * 檢查遊戲是否結束
805 | *
806 | * @param {array} - 死亡的人
807 | */
808 | const checkGameFinished = (dead) => {
809 | if (isUsePredictor) {
810 | if (dead.some(tmp => tmp.role.key === PREDICTOR.key)) {
811 | setIsPredictorDead(true);
812 | }
813 | }
814 |
815 | if (isUseWitch) {
816 | if (dead.some(tmp => tmp.role.key === WITCH.key)) {
817 | setIsWitchDead(true);
818 | }
819 | }
820 |
821 | if (isUseHunter) {
822 | if (dead.some(tmp => tmp.role.key === HUNTER.key)) {
823 | setIsHunterDead(true);
824 | }
825 | }
826 |
827 | if (isUseKnight) {
828 | if (dead.some(tmp => tmp.role.key === KNIGHT.key)) {
829 | setIsKnightDead(true);
830 | }
831 | }
832 |
833 | if (isUseidiot) {
834 | if (dead.some(tmp => tmp.role.key === idiot.key)) {
835 | setIsidiotDead(true);
836 | }
837 | }
838 |
839 | if (IS_DEBUG) {
840 | console.log('dead', dead);
841 | console.log('wolfNumber', wolfNumber);
842 | }
843 |
844 | // 判斷好人是否獲勝
845 | let deadWolf = 0;
846 | dead.forEach((dead) => {
847 | if (dead.role.key === WOLF.key) {
848 | deadWolf += 1;
849 | }
850 | });
851 |
852 | if (IS_DEBUG) {
853 | console.log('deadWolf', deadWolf);
854 | }
855 |
856 | if (deadWolf === wolfNumber) {
857 | return {
858 | isFinished: true,
859 | message: t('good_win'),
860 | }
861 | }
862 |
863 | // 判斷壞人是否獲勝
864 | if (IS_DEBUG) {
865 | console.log('playerNumber', playerNumber);
866 | }
867 |
868 | if ((playerNumber - dead.length) === (wolfNumber - deadWolf)) {
869 | return {
870 | isFinished: true,
871 | message: t('bad_win'),
872 | }
873 | }
874 |
875 | // if ((playerNumber - dead.length) <= (wolfNumber - deadWolf) * 2) {
876 | // return {
877 | // isFinished: true,
878 | // message: t('bad_win'),
879 | // }
880 | // }
881 |
882 | // 判斷是否有屠邊局, 壞人是否屠邊成功
883 | if (IS_DEBUG) {
884 | console.log('isKillKind', isKillKind);
885 | }
886 | if (isKillKind) {
887 | let gods = 0; // 神
888 | let villagers = 0; // 民
889 | let deadGods = 0; // 死掉的神
890 | let deadVillagers = 0; // 死掉的民
891 |
892 | // 玩家
893 | list.forEach((tmp) => {
894 | if (tmp.role.isGood) {
895 | if (tmp.role.isGod) {
896 | gods += 1;
897 | } else {
898 | villagers += 1;
899 | }
900 | }
901 | });
902 |
903 | // 死掉的人
904 | dead.forEach((tmp) => {
905 | if (tmp.role.isGood) {
906 | if (tmp.role.isGod) {
907 | deadGods += 1;
908 | } else {
909 | deadVillagers += 1;
910 | }
911 | }
912 | });
913 |
914 | if (IS_DEBUG) {
915 | console.log('gods', gods);
916 | console.log('villagers', villagers);
917 | console.log('deadGods', deadGods);
918 | console.log('deadVillagers', deadVillagers);
919 | }
920 |
921 | if ((deadGods !== 0 && gods === deadGods) || (deadVillagers !== 0 && villagers === deadVillagers)) {
922 | return {
923 | isFinished: true,
924 | message: t('bad_win_kind'),
925 | }
926 | }
927 | }
928 |
929 | return {
930 | isFinished: false,
931 | message: '',
932 | }
933 | }
934 |
935 | /**
936 | * handleShoot
937 | * 獵人射殺
938 | *
939 | * @param {bool} isShoot - 是否射殺
940 | */
941 | const handleShoot = (isShoot) => {
942 | setIsOpenHunter(false);
943 | setIsUseHunterSkill(true);
944 | // console.log('isShoot', isShoot);
945 | if (isShoot) {
946 | const tmpDead = [
947 | ...dead,
948 | hunterSelect,
949 | ];
950 |
951 | setDead(tmpDead);
952 | setMessages([
953 | ...messages,
954 | t('hunter_shoot_player', { index: hunterSelect.index })
955 | ]);
956 | const result = checkGameFinished(tmpDead);
957 |
958 | if (result.isFinished) {
959 | setIsOpenGameResult(true);
960 | setGameResultMessage(result.message);
961 | } else {
962 | // console.log('11', dayType);
963 | if (dayType === DAY_TYPE.DAY) {
964 | initSelect(true);
965 | } else {
966 | initSelect(false);
967 | }
968 | }
969 | } else {
970 | // console.log('22', dayType);
971 | if (dayType === DAY_TYPE.DAY) {
972 | initSelect(true);
973 | } else {
974 | initSelect(false);
975 | }
976 | }
977 | }
978 |
979 | /**
980 | * initSelect
981 | * 初始化選擇
982 | *
983 | * @param {bool} isNextDay - 是否進入下一天
984 | */
985 | const initSelect = (isNextDay) => {
986 | // 清空全部選擇
987 | setDeadNumber(null); // 狼人
988 | setWitchDeadNumber(null); // 女巫毒殺
989 | setPredictorSelect(null); // 預言家選擇
990 | setSelectVote(null); // 投票選擇
991 | setIsUse(false);
992 | setHunterSelect(null); // 獵人選擇
993 | setKnightSelect(null); // 騎士選擇
994 | setIsShowMessage(false);
995 |
996 | if (isNextDay) {
997 | // 進入下一天
998 | setDay(day + 1);
999 |
1000 | // 進入晚上
1001 | setDayType(DAY_TYPE.NIGHT);
1002 |
1003 | // 進入 Step 1
1004 | setStep(1);
1005 | } else {
1006 | setDayType(DAY_TYPE.DAY);
1007 | }
1008 | }
1009 |
1010 | /**
1011 | * handleGameOver
1012 | * 遊戲結束
1013 | *
1014 | */
1015 | const handleGameOver = () => {
1016 | // window.location.href = '/';
1017 | window.location.reload();
1018 | }
1019 |
1020 | /**
1021 | * handleCloseHunter
1022 | * 關閉獵人視窗
1023 | *
1024 | */
1025 | const handleCloseHunter = () => {
1026 | setIsOpenHunterShoot(false);
1027 | setStep(17);
1028 | }
1029 |
1030 | const getWolfs = () => {
1031 | //list.filter(role => role.role.key === WOLF.key)
1032 | let wolfsInfo = '';
1033 | const wolfs = list.filter(role => role.role.key === WOLF.key);
1034 | wolfs.forEach((tmp, index) => {
1035 | wolfsInfo += tmp.index;
1036 | if (wolfs.length - 1 !== index) {
1037 | wolfsInfo += ', ';
1038 | }
1039 | });
1040 | return wolfsInfo;
1041 | };
1042 |
1043 | const getVillages = () => {
1044 | let villagesInfo = '';
1045 | const villages = list.filter(role => role.role.key === VILLAGER.key);
1046 | villages.forEach((tmp, index) => {
1047 | villagesInfo += tmp.index;
1048 | if (villages.length - 1 !== index) {
1049 | villagesInfo += ', ';
1050 | }
1051 | });
1052 | return villagesInfo;
1053 | };
1054 |
1055 | /**
1056 | * handleCloseLastWords
1057 | * 關閉遺言視窗
1058 | *
1059 | */
1060 | const handleCloseLastWords = () => {
1061 | setIsOpenLastWords(false);
1062 | initSelect(true);
1063 | };
1064 |
1065 | /**
1066 | * handleFight
1067 | * 決鬥結果
1068 | *
1069 | */
1070 | const handleFight = () => {
1071 | setIsOpenKnight(false);
1072 | setIsOpenKnightResult(true);
1073 | }
1074 |
1075 | /**
1076 | * handleCloseFight
1077 | * 關閉決鬥結果
1078 | *
1079 | */
1080 | const handleCloseFight = () => {
1081 | setIsUseKnightSkill(true);
1082 | setIsOpenKnightResult(false);
1083 | let tmpDead = [];
1084 |
1085 | if (knightSelect !== null && knightSelect.role.key === WOLF.key) {
1086 | tmpDead = [
1087 | ...dead,
1088 | knightSelect,
1089 | ];
1090 |
1091 | setMessages([
1092 | ...messages,
1093 | t('no_is_wolf', { index: knightSelect.index })
1094 | ]);
1095 | } else {
1096 | tmpDead = [
1097 | ...dead,
1098 | list.find(tmp => tmp.role.key === KNIGHT.key),
1099 | ];
1100 |
1101 | setMessages([
1102 | ...messages,
1103 | t('no_is_not_wolf', { index: knightSelect.index })
1104 | ]);
1105 | }
1106 |
1107 | if (IS_DEBUG) {
1108 | console.log('handleCloseFight tmpDead', tmpDead);
1109 | }
1110 |
1111 | setDead(tmpDead);
1112 | const result = checkGameFinished(tmpDead);
1113 |
1114 | if (result.isFinished) {
1115 | setIsOpenGameResult(true);
1116 | setGameResultMessage(result.message);
1117 | } else {
1118 | initSelect(true);
1119 | }
1120 | }
1121 |
1122 | /**
1123 | * handleCloseidiot
1124 | * 關閉白癡結果
1125 | *
1126 | */
1127 | const handleCloseidiot =() => {
1128 | setIsOpenidiotResult(false);
1129 | initSelect(true);
1130 | }
1131 |
1132 | /**
1133 | * React - render
1134 | *
1135 | */
1136 | return (
1137 | <>
1138 |
1139 | { t('gaming') }
1140 |
1141 |
1142 |
1143 | { t('dead_message') }
1144 |
1145 |
1146 |
1147 |
1148 | {
1149 | messages.map(message => - { message }
)
1150 | }
1151 |
1152 |
1153 |
1154 |
1155 | {
1156 | (dayType === DAY_TYPE.DAY) && (
1157 | <>
1158 |
1159 |
1167 |
1168 |
1169 | >
1170 | )
1171 | }
1172 |
1173 |
1174 |
1178 |
1179 | { /* Wolf Kill Start */ }
1180 |
1220 | { /* Wolf Kill End */ }
1221 |
1222 | { /* Witch Save Start */ }
1223 |
1287 | { /* Witch Save End */ }
1288 |
1289 | {/* Witch Poison Start */}
1290 |
1361 | {/* Witch Poison End */}
1362 |
1363 | {/* Predictor Start */}
1364 |
1403 | {/* Predictor End */}
1404 |
1405 | {/* Check Role Start */}
1406 |
1457 | {/* Check Role End */}
1458 |
1459 | {/* Result Start*/}
1460 |
1533 | {/* Result End*/}
1534 |
1535 | { /* Vote Start */ }
1536 |
1583 | { /* Vote End */ }
1584 |
1585 | { /* Game Result Start */ }
1586 |
1667 | { /* Game Result End */ }
1668 |
1669 | { /* Hunter Select Start */ }
1670 |
1717 | { /* Hunter Select End */ }
1718 |
1719 | {/* Hunter Could Shoot Start */}
1720 |
1771 | {/* Hunter Could Shoot End */}
1772 |
1773 | {/* Last Words Start */}
1774 |
1811 | {/* Last Words End */}
1812 |
1813 | {/* Knight Start */}
1814 |
1859 | {/* Knight End */}
1860 |
1861 | {/* Knight Result Start */}
1862 |
1921 | {/* Knight Result End */}
1922 |
1923 | {/* idiot Result Start */}
1924 |
1975 | {/* idiot Result End */}
1976 | >
1977 | );
1978 | };
1979 |
1980 | export default Game;
1981 |
--------------------------------------------------------------------------------