|
@@ -5,37 +5,27 @@
|
|
<reload-outlined class="restart" @click="restart" />
|
|
<reload-outlined class="restart" @click="restart" />
|
|
</div>
|
|
</div>
|
|
<div class="tip">{{
|
|
<div class="tip">{{
|
|
- isGaming ? '请选中的玩家描述所见的词' : '请玩家依次传手机查看自己的词'
|
|
|
|
|
|
+ isPlaying ? '请选中的玩家描述所见的词' : '请玩家依次传手机查看自己的词'
|
|
}}</div>
|
|
}}</div>
|
|
- <template v-if="!isGaming">
|
|
|
|
- <div class="content">
|
|
|
|
|
|
+ <template v-if="!isPlaying">
|
|
|
|
+ <ReadWordsComponent :read-words="readWords"></ReadWordsComponent>
|
|
|
|
+ <!-- <div class="content">
|
|
<div class="cards">
|
|
<div class="cards">
|
|
<template v-for="(word, key) in readWords" :key="`${key}-${word.num}`">
|
|
<template v-for="(word, key) in readWords" :key="`${key}-${word.num}`">
|
|
- <div
|
|
|
|
- class="card"
|
|
|
|
- :class="{ reverse: word.status === 1 }"
|
|
|
|
|
|
+ <Card
|
|
|
|
+ v-model:status="word.status"
|
|
|
|
+ :num="key + 1"
|
|
|
|
+ :text="word.text"
|
|
|
|
+ @remember="rememberClick"
|
|
:style="{ left: `${key * 3}px`, top: `${key * 10}px` }"
|
|
:style="{ left: `${key * 3}px`, top: `${key * 10}px` }"
|
|
- @click="() => cardClick(key)"
|
|
|
|
- >
|
|
|
|
- <div class="front">
|
|
|
|
- {{ key + 1 }}
|
|
|
|
- </div>
|
|
|
|
- <div class="back">
|
|
|
|
- <div class="number">{{ key + 1 }}</div>
|
|
|
|
- <div class="word">{{ word.text }}</div>
|
|
|
|
- <div class="confirm">
|
|
|
|
- <a-button class="btn" type="primary" @click.stop="rememberClick"
|
|
|
|
- >我记住啦</a-button
|
|
|
|
- >
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
|
|
+ ></Card>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
|
|
+ </div> -->
|
|
</template>
|
|
</template>
|
|
<template v-else>
|
|
<template v-else>
|
|
- <div ref="gaming" class="gaming">
|
|
|
|
|
|
+ <PlayComponent :players="players" @over="init"></PlayComponent>
|
|
|
|
+ <!-- <div ref="gaming" class="gaming">
|
|
<template v-for="(player, key) in players" :key="key">
|
|
<template v-for="(player, key) in players" :key="key">
|
|
<div
|
|
<div
|
|
class="player"
|
|
class="player"
|
|
@@ -58,195 +48,86 @@
|
|
<div class="number">{{ currentPlayer.num }}</div>
|
|
<div class="number">{{ currentPlayer.num }}</div>
|
|
<div class="word">{{ currentPlayer.text }}</div>
|
|
<div class="word">{{ currentPlayer.text }}</div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
|
|
+ </div> -->
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
- import { session, initSession } from '@/utils/storage';
|
|
|
|
import { Modal } from 'ant-design-vue/es';
|
|
import { Modal } from 'ant-design-vue/es';
|
|
- import 'ant-design-vue/es/modal/style';
|
|
|
|
- import { wordsList } from '@/utils/words';
|
|
|
|
- interface InterfacePlayer {
|
|
|
|
- num: number;
|
|
|
|
- text: string;
|
|
|
|
- status: number;
|
|
|
|
- identity: number;
|
|
|
|
- }
|
|
|
|
- enum EnumStatus {
|
|
|
|
- init = 0,
|
|
|
|
- finish = 1
|
|
|
|
|
|
+ import { useGetSession, useWords, usePlayer } from './composables';
|
|
|
|
+ import { randomPlayer } from '@/utils/tools';
|
|
|
|
+ import ReadWordsComponent from './components/ReadWords.vue';
|
|
|
|
+ import PlayComponent from './components/Playing.vue';
|
|
|
|
+ enum EnumGameStatus {
|
|
|
|
+ word = 1,
|
|
|
|
+ game = 2
|
|
}
|
|
}
|
|
- enum EnumIdentity {
|
|
|
|
- civilian = 1,
|
|
|
|
- spy = 2,
|
|
|
|
- white = 3
|
|
|
|
- }
|
|
|
|
- // 取缓存词组
|
|
|
|
- let civilianWord = session.value.civilianWord || '';
|
|
|
|
- let spyWord = session.value.spyWord || '';
|
|
|
|
- // 生成随机组词并放入缓存
|
|
|
|
- if (!civilianWord || !spyWord) {
|
|
|
|
- const wordsListNum = wordsList.length;
|
|
|
|
- const selectWordKey = Math.floor(Math.random() * wordsListNum);
|
|
|
|
- const selectWord = wordsList[selectWordKey];
|
|
|
|
- [civilianWord, spyWord] = selectWord.split(',');
|
|
|
|
- session.value.civilianWord = civilianWord;
|
|
|
|
- session.value.spyWord = spyWord;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
- let playerNums = session.value.player;
|
|
|
|
- if (!playerNums) {
|
|
|
|
- playerNums = 4;
|
|
|
|
- router.replace('/login');
|
|
|
|
|
|
+ let {
|
|
|
|
+ civilianWord: cacheCivilianWord,
|
|
|
|
+ spyWord: cacheSpyWord,
|
|
|
|
+ playerNum,
|
|
|
|
+ spyNum,
|
|
|
|
+ whiteNum,
|
|
|
|
+ spyKeys,
|
|
|
|
+ whiteKeys,
|
|
|
|
+ selectKey,
|
|
|
|
+ currentStatus: cacheCurrentStatus,
|
|
|
|
+ setInitSession,
|
|
|
|
+ setSession
|
|
|
|
+ } = useGetSession();
|
|
|
|
+ const { civilianWord, spyWord, isSetWordStorage } = useWords(cacheCivilianWord, cacheSpyWord);
|
|
|
|
+ // 更新缓存词组
|
|
|
|
+ if (isSetWordStorage) {
|
|
|
|
+ setSession('civilianWord', civilianWord);
|
|
|
|
+ setSession('spyWord', spyWord);
|
|
}
|
|
}
|
|
- const list = new Array(playerNums).fill(1).map((el, i) => {
|
|
|
|
- return {
|
|
|
|
- num: ++i,
|
|
|
|
- text: civilianWord,
|
|
|
|
- status: 0,
|
|
|
|
- identity: EnumIdentity.civilian
|
|
|
|
- };
|
|
|
|
- });
|
|
|
|
- // 生成随机数组
|
|
|
|
- const randomPlayer = (sum: number, num: number, exist: number[] = []): number[] => {
|
|
|
|
- const arr: number[] = [];
|
|
|
|
- for (let i = 0; i < num; i++) {
|
|
|
|
- const r = Math.floor(Math.random() * sum);
|
|
|
|
- if (arr.includes(r) || exist.includes(r)) {
|
|
|
|
- i--;
|
|
|
|
- } else {
|
|
|
|
- arr.push(r);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return arr;
|
|
|
|
- };
|
|
|
|
- // 对应的key组
|
|
|
|
- let spyKeys = session.value.spyKeys;
|
|
|
|
- let whiteKeys = session.value.whiteKeys;
|
|
|
|
|
|
+ // 玩家获取异常处理
|
|
|
|
+ if (!playerNum) {
|
|
|
|
+ playerNum = 4;
|
|
|
|
+ router.replace('login');
|
|
|
|
+ }
|
|
|
|
+ // 生成随机key
|
|
if (!spyKeys) {
|
|
if (!spyKeys) {
|
|
- spyKeys = randomPlayer(playerNums, session.value.spy);
|
|
|
|
- session.value.spyKeys = spyKeys;
|
|
|
|
|
|
+ const keys = randomPlayer(playerNum, spyNum);
|
|
|
|
+ spyKeys = keys;
|
|
|
|
+ setSession('spyKeys', keys);
|
|
}
|
|
}
|
|
|
|
+ // 生成随机key
|
|
if (!whiteKeys) {
|
|
if (!whiteKeys) {
|
|
- whiteKeys = randomPlayer(playerNums, session.value.white, spyKeys);
|
|
|
|
- session.value.whiteKeys = whiteKeys;
|
|
|
|
|
|
+ const keys = randomPlayer(playerNum, whiteNum, spyKeys);
|
|
|
|
+ whiteKeys = keys;
|
|
|
|
+ setSession('whiteKeys', keys);
|
|
}
|
|
}
|
|
- // 设置
|
|
|
|
- spyKeys.forEach(key => {
|
|
|
|
- list[key].identity = EnumIdentity.spy;
|
|
|
|
- list[key].text = spyWord;
|
|
|
|
- });
|
|
|
|
- whiteKeys.forEach(key => {
|
|
|
|
- list[key].identity = EnumIdentity.white;
|
|
|
|
- list[key].text = '';
|
|
|
|
- });
|
|
|
|
- const readWords = reactive(list.map(el => ({ ...el })));
|
|
|
|
- const players = reactive(list.map(el => ({ ...el })));
|
|
|
|
// 选中玩家的key
|
|
// 选中玩家的key
|
|
- const selectKey =
|
|
|
|
- session.value.selectKey === undefined
|
|
|
|
- ? Math.floor(Math.random() * playerNums)
|
|
|
|
- : session.value.selectKey;
|
|
|
|
- players[selectKey].status = 1;
|
|
|
|
- session.value.selectKey = selectKey;
|
|
|
|
- // 当前环节
|
|
|
|
- const currentStatus = ref(session.value.currentStatus);
|
|
|
|
- // 当前哪个玩家被选中
|
|
|
|
- const currentFocus = ref(-1);
|
|
|
|
- const gaming = ref(null);
|
|
|
|
- const isDisabled = ref(false);
|
|
|
|
- // 是否是游戏进行环节
|
|
|
|
- const isGaming = computed(() => currentStatus.value === EnumStatus.finish);
|
|
|
|
- // 是否要进行看词或者投票
|
|
|
|
- const isVote = computed(() => currentFocus.value !== -1);
|
|
|
|
- // 当前操作的玩家
|
|
|
|
- const currentPlayer = ref<InterfacePlayer | null>(null);
|
|
|
|
- // 查看词
|
|
|
|
- const isForget = ref(false);
|
|
|
|
- // 选中状态切换
|
|
|
|
- onClickOutside(gaming, () => {
|
|
|
|
- if (currentFocus.value !== -1) {
|
|
|
|
- currentFocus.value = -1;
|
|
|
|
- isDisabled.value = false;
|
|
|
|
- }
|
|
|
|
|
|
+ if (selectKey === undefined) {
|
|
|
|
+ selectKey = Math.floor(Math.random() * playerNum);
|
|
|
|
+ setSession('selectKey', selectKey);
|
|
|
|
+ }
|
|
|
|
+ const { readWords, players } = usePlayer({
|
|
|
|
+ playerNum,
|
|
|
|
+ civilianWord,
|
|
|
|
+ spyWord,
|
|
|
|
+ spyKeys,
|
|
|
|
+ whiteKeys,
|
|
|
|
+ selectKey
|
|
});
|
|
});
|
|
|
|
+
|
|
|
|
+ // 当前环节
|
|
|
|
+ const currentStatus = ref(cacheCurrentStatus);
|
|
|
|
+ const isPlaying = computed(() => currentStatus.value === EnumGameStatus.game);
|
|
|
|
+
|
|
// 玩家查看词的情况
|
|
// 玩家查看词的情况
|
|
watch(readWords, p => {
|
|
watch(readWords, p => {
|
|
if (!p.length) {
|
|
if (!p.length) {
|
|
- const status = EnumStatus.finish;
|
|
|
|
|
|
+ const status = EnumGameStatus.game;
|
|
currentStatus.value = status;
|
|
currentStatus.value = status;
|
|
- session.value.currentStatus = status;
|
|
|
|
|
|
+ setSession('currentStatus', status);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
-
|
|
|
|
- // 卡片点击查看
|
|
|
|
- const cardClick = (key: number) => {
|
|
|
|
- readWords[key].status = 1;
|
|
|
|
- };
|
|
|
|
- // 我记住啦
|
|
|
|
- const rememberClick = () => {
|
|
|
|
- readWords.pop();
|
|
|
|
- };
|
|
|
|
- // 选中玩家
|
|
|
|
- const focusClick = (key: number) => {
|
|
|
|
- const player = players[key];
|
|
|
|
- // 踢出了,就没有操作了
|
|
|
|
- if (player.status === 2) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- isDisabled.value = true;
|
|
|
|
- currentFocus.value = key;
|
|
|
|
- currentPlayer.value = player;
|
|
|
|
- };
|
|
|
|
- // 忘词
|
|
|
|
- const forget = () => {
|
|
|
|
- isForget.value = true;
|
|
|
|
- };
|
|
|
|
- // 关闭忘词
|
|
|
|
- const closeForget = () => {
|
|
|
|
- isForget.value = false;
|
|
|
|
- currentPlayer.value = null;
|
|
|
|
- };
|
|
|
|
- // 投票/结算
|
|
|
|
- const vote = () => {
|
|
|
|
- const status = currentPlayer.value?.status;
|
|
|
|
- // 出局
|
|
|
|
- if (currentPlayer.value) {
|
|
|
|
- currentPlayer.value.status = 2;
|
|
|
|
- }
|
|
|
|
- const filterPlayer = players.filter(el => el.status !== 2);
|
|
|
|
- const filterPlayerNum = filterPlayer.length;
|
|
|
|
- const filterSpy = filterPlayer.filter(el => el.identity === EnumIdentity.spy);
|
|
|
|
- const filterSpyNum = filterSpy.length;
|
|
|
|
- if (!filterSpyNum) {
|
|
|
|
- alert('游戏结束,平民胜利');
|
|
|
|
- init();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- const filterCivilian = filterPlayer.filter(el => el.identity === EnumIdentity.civilian);
|
|
|
|
- const filterCivilianNum = filterCivilian.length;
|
|
|
|
- if (filterSpyNum >= filterCivilianNum) {
|
|
|
|
- alert('游戏结束,卧底胜利');
|
|
|
|
- init();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- // 如果当前是选中的第一个玩家,则自动延伸到下一个玩家
|
|
|
|
- if (status === 1) {
|
|
|
|
- const findIndex = players.findIndex(el => el.num === currentPlayer.value?.num);
|
|
|
|
- let index = -1;
|
|
|
|
- if (findIndex === players.length - 1) {
|
|
|
|
- index = 0;
|
|
|
|
- } else {
|
|
|
|
- index = findIndex + 1;
|
|
|
|
- }
|
|
|
|
- players[index].status = 1;
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
// 初始化
|
|
// 初始化
|
|
function init() {
|
|
function init() {
|
|
- session.value = initSession;
|
|
|
|
|
|
+ setInitSession();
|
|
router.replace('/login');
|
|
router.replace('/login');
|
|
}
|
|
}
|
|
// 重新开始
|
|
// 重新开始
|
|
@@ -263,10 +144,8 @@
|
|
};
|
|
};
|
|
</script>
|
|
</script>
|
|
<style lang="less" scoped>
|
|
<style lang="less" scoped>
|
|
- @yellow: var(--color-yellow);
|
|
|
|
@blue: var(--color-blue);
|
|
@blue: var(--color-blue);
|
|
@text: var(--color-text);
|
|
@text: var(--color-text);
|
|
- @red: var(--color-red);
|
|
|
|
.game {
|
|
.game {
|
|
height: 100%;
|
|
height: 100%;
|
|
background-color: @blue;
|
|
background-color: @blue;
|
|
@@ -283,165 +162,5 @@
|
|
color: @text;
|
|
color: @text;
|
|
padding-top: 30px;
|
|
padding-top: 30px;
|
|
}
|
|
}
|
|
- .content {
|
|
|
|
- padding-top: 40px;
|
|
|
|
- .cards {
|
|
|
|
- position: relative;
|
|
|
|
- margin-left: 70px;
|
|
|
|
- .card {
|
|
|
|
- position: absolute;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- flex-direction: column;
|
|
|
|
- width: 200px;
|
|
|
|
- height: 300px;
|
|
|
|
- top: 0;
|
|
|
|
- left: 0;
|
|
|
|
- text-align: center;
|
|
|
|
- &.reverse {
|
|
|
|
- z-index: 10;
|
|
|
|
- .front {
|
|
|
|
- transform: rotateY(180deg);
|
|
|
|
- }
|
|
|
|
- .back {
|
|
|
|
- transform: rotateY(360deg);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- .front,
|
|
|
|
- .back {
|
|
|
|
- position: absolute;
|
|
|
|
- width: 100%;
|
|
|
|
- height: 100%;
|
|
|
|
- transition: transform 1s ease;
|
|
|
|
- backface-visibility: hidden;
|
|
|
|
- -webkit-backface-visibility: hidden;
|
|
|
|
- background-color: @yellow;
|
|
|
|
- box-shadow: 2px 2px 10px 1px rgba(0, 0, 0, 0.2);
|
|
|
|
- }
|
|
|
|
- .front {
|
|
|
|
- transform: rotateY(0);
|
|
|
|
- z-index: 2;
|
|
|
|
- line-height: 300px;
|
|
|
|
- font-size: 46px;
|
|
|
|
- }
|
|
|
|
- .back {
|
|
|
|
- transform: rotateY(180deg);
|
|
|
|
- z-index: 1;
|
|
|
|
- }
|
|
|
|
- .number {
|
|
|
|
- text-align: center;
|
|
|
|
- font-size: 30px;
|
|
|
|
- padding-top: 30px;
|
|
|
|
- }
|
|
|
|
- .word {
|
|
|
|
- padding-top: 30px;
|
|
|
|
- font-size: 36px;
|
|
|
|
- }
|
|
|
|
- .confirm {
|
|
|
|
- padding-top: 20px;
|
|
|
|
- .btn {
|
|
|
|
- background-color: #141e30;
|
|
|
|
- border: none;
|
|
|
|
- color: @yellow;
|
|
|
|
- width: 160px;
|
|
|
|
- height: 40px;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- .gaming {
|
|
|
|
- display: flex;
|
|
|
|
- text-align: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
- padding-top: 40px;
|
|
|
|
- .player {
|
|
|
|
- width: 60px;
|
|
|
|
- height: 60px;
|
|
|
|
- border: 1px solid @yellow;
|
|
|
|
- border-radius: 50%;
|
|
|
|
- margin: 20px 10px;
|
|
|
|
- line-height: 60px;
|
|
|
|
- font-size: 30px;
|
|
|
|
- color: @yellow;
|
|
|
|
- font-weight: 600;
|
|
|
|
- &.select {
|
|
|
|
- background-color: @yellow;
|
|
|
|
- color: @blue;
|
|
|
|
- }
|
|
|
|
- &.disabled {
|
|
|
|
- border-color: rgba(255, 255, 255, 0.2);
|
|
|
|
- color: rgba(255, 255, 255, 0.2);
|
|
|
|
- background-color: transparent;
|
|
|
|
- }
|
|
|
|
- &.out {
|
|
|
|
- position: relative;
|
|
|
|
- &::before {
|
|
|
|
- content: ' ';
|
|
|
|
- position: absolute;
|
|
|
|
- width: 8px;
|
|
|
|
- height: 100%;
|
|
|
|
- background-color: @red;
|
|
|
|
- transform-origin: 50% 58%;
|
|
|
|
- transform: rotateZ(45deg);
|
|
|
|
- }
|
|
|
|
- &::after {
|
|
|
|
- content: ' ';
|
|
|
|
- position: absolute;
|
|
|
|
- width: 8px;
|
|
|
|
- height: 100%;
|
|
|
|
- background-color: @red;
|
|
|
|
- transform-origin: 0 70%;
|
|
|
|
- transform: rotateZ(-45deg);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- .operate {
|
|
|
|
- display: flex;
|
|
|
|
- justify-content: space-around;
|
|
|
|
- padding-top: 40px;
|
|
|
|
- .btn {
|
|
|
|
- width: 140px;
|
|
|
|
- height: 50px;
|
|
|
|
- font-size: 26px;
|
|
|
|
- border-radius: 6px;
|
|
|
|
- border: none;
|
|
|
|
- }
|
|
|
|
- .forget {
|
|
|
|
- color: @text;
|
|
|
|
- background-color: @yellow;
|
|
|
|
- }
|
|
|
|
- .vote {
|
|
|
|
- color: @text;
|
|
|
|
- background-color: @red;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- .mask {
|
|
|
|
- position: fixed;
|
|
|
|
- top: 0;
|
|
|
|
- left: 0;
|
|
|
|
- width: 100vw;
|
|
|
|
- height: 100vh;
|
|
|
|
- display: flex;
|
|
|
|
- justify-content: center;
|
|
|
|
- align-items: center;
|
|
|
|
- background-color: rgba(0, 0, 0, 0.6);
|
|
|
|
- .forget-cards {
|
|
|
|
- width: 200px;
|
|
|
|
- height: 300px;
|
|
|
|
- background-color: @yellow;
|
|
|
|
- text-align: center;
|
|
|
|
- .number {
|
|
|
|
- font-size: 32px;
|
|
|
|
- padding-top: 50px;
|
|
|
|
- }
|
|
|
|
- .word {
|
|
|
|
- font-size: 36px;
|
|
|
|
- padding-top: 40px;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|