Skip to content

Commit

Permalink
store
Browse files Browse the repository at this point in the history
  • Loading branch information
hsyhhssyy committed Jun 17, 2024
1 parent 72af767 commit a12076b
Show file tree
Hide file tree
Showing 13 changed files with 414 additions and 174 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@icon-park/vue-next": "^1.4.2",
"@microsoft/signalr": "^8.0.0",
"axios": "1.6.0",
"marked": "^13.0.0",
"pinia": "^2.0.36",
"vue": "^3.3.2",
"vue-router": "^4.2.0"
Expand Down
Binary file added public/rules/CypherChallenge-image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/rules/CypherChallenge-image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/rules/CypherChallenge-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions public/rules/CypherChallenge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 大帝的挑战

游玩一个根据线索猜测这是哪位干员的游戏。

## 如何游玩

整局游戏由10道题目组成,玩家们将会依次猜测这十位干员是谁。

游戏主面板中,由两部分组成,线索总览和表格面板。

移动版页面如下:
![alt text](/rules/CypherChallenge-image.png)
桌面版页面如下:
![alt text](/rules/CypherChallenge-image-1.png)

所有玩家可以随意打字说出某个方舟中干员的代号。

一开始没有任何线索,玩家可以任意发言,比如某位玩家此时发送**阿米娅**

![alt text](/rules/CypherChallenge-image-2.png)

游戏面板中会立刻将列出阿米娅的名字,并且在后面跟随一系列大拇指图标。这些图标可能是红色或者绿色。

绿色表示**该干员**,本例中是**阿米娅****题目的谜底**在这个词条上相同。
比如图中**性别****职业**两个词条下是绿色大拇指,就表示这一题的谜底干员,他的性别和阿米娅一样,都是女性干员。而他的职业和阿米娅一样,都是术师。

红色表示该干员在这个词条和谜底不一致。

游戏中的每一题,都会在**势力**, **职业**, **子职业**, **稀有度**, **性别**, **队伍**, **阵营**, **画师**中抽取6个词条(取决于游戏房间设置)。所有词条初始均显示为???。直到某位玩家发送的干员和谜底在该词条上一致,此时会解锁该词条并标记一个绿色大拇指。

每一题有10次回答干员名称的机会,若全部耗尽还未猜出谜底,会公布答案并进入下一题。

上一题的答案,会作为下一题的第一条线索。因此从第二题开始,玩家们只有9次机会猜测。

## 计分规则

每答出一个干员 +200分

每首次解锁一个线索 +50分

不设扣分项

## 生涯统计

一局游戏只有至少两人参与游戏并都有得分,才会计入玩家生涯统计数据。

只有当你发送的内容确实是一个干员代号时,才会参与计算正确率,你回答的干员是正确谜底,或者首次解锁了一个线索时,记为正确答案,否则记为错误。
Binary file added public/rules/SchulteGrid-image-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/rules/SchulteGrid-image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions public/rules/SchulteGrid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 技能方格

游玩一个在方格里找干员技能名字的游戏。

## 如何游玩

游戏主面板中,你将看到一个有100个字组成的10x10的方格。
方格中其实包含了多位干员的多个技能,如下图所示:

![技能方格总图](/rules/SchulteGrid-image-1.png)

你的任务,就是抢在其他玩家之前,找到某个技能,并回答**拥有该技能的干员的干员代号。**

比如你发现了**假日风暴**,并记起来他是**假日威龙陈**的技能。

![alt text](/rules/SchulteGrid-image-2.png)

你就可以在聊天框中打出**假日威龙陈**并发送,这样你就得到了200分。

一个技能的每个字都是按顺序连在一起的。

游戏将会一直进行到房主关闭游戏房间,或所有干员全部回答出来。
游戏房间关闭时,如果还有未答出的干员,兔兔会公布答案。

## 备注

1. 谜题的生成经过算法的控制,答案是确保唯一的。
2. 一个干员可能有不止一个技能在方格中,回答该干员可以多倍得分。

## 计分规则

每答出一个干员 +200分

不设扣分项

## 生涯统计

一局游戏只有至少两人参与游戏并都有得分,才会计入玩家生涯统计数据。

只有当你发送的内容确实是一个干员代号时,才会参与计算正确率,该干员在本题则记为正确答案,否则记为错误答案。
9 changes: 7 additions & 2 deletions src/mobile/views/game/CypherChallenge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
</div>

</n-flex>
<n-flex justify="center" style="margin-top: 20px;" align="center" v-if="!hasNextQuestion">
<n-flex justify="center" style="margin-top: 20px; margin-bottom: 10px;" align="center" v-if="!hasNextQuestion">
<div class="countdown">
游戏已结束
</div>
<icon-button :icon="Close" type="error" @click="closeResultPopup">关闭</icon-button>
</n-flex>
</div>
</n-modal>
Expand Down Expand Up @@ -83,7 +84,7 @@
import type { CSSProperties } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { Check, SendOne } from '@icon-park/vue-next'
import { Check, SendOne, Close } from '@icon-park/vue-next'
import { useGameHubStore } from '@/stores/gamehub'
import { listToDict } from '@/utils'
import type { SignalrResponse } from '@/api/signalr'
Expand Down Expand Up @@ -268,6 +269,10 @@ function getRallyPointData() {
return "PrepareNextQuestion:" + currentQuestionIndex.value;
}

function closeResultPopup(){
nextQuestionShown.value = false
}

function prepareNextQuestion() {
if(countDownActive.value==true){
return
Expand Down
211 changes: 40 additions & 171 deletions src/mobile/views/room/WaitingRoom.vue
Original file line number Diff line number Diff line change
@@ -1,196 +1,65 @@
<template>
<div class="waiting-room" v-if="gameRoomData">
<n-space vertical>
<game-info-card :room-data="gameRoomData">
<template #tags>
<n-tag type="info">等待中</n-tag>
<waiting-room-base :on-on-settings-loaded="onSettingsChange" :settings="roomSettings">
<div class="waiting-room">
<n-popover trigger="click" placement="bottom">
<template #trigger>
<n-button text>
<icon :icon="Help" />
</n-button>
</template>
<template #buttons>
<template v-if="isHost">
<icon-button :icon="Play" type="success" @click="startGame">开始游戏</icon-button>
<icon-button :icon="Close" type="error" @click="closeRoom">关闭房间</icon-button>
</template>
<template v-else>
<icon-button :icon="Logout" type="warning" @click="leaveRoom">退出房间</icon-button>
</template>
</template>
</game-info-card>
<n-card>
<template #header>
玩家列表
<n-tag type="default">
<n-space :size="0">
<icon :icon="Peoples" />
{{ Object.keys(players).length }}
</n-space>
</n-tag>
</template>
<template #header-extra>
<icon-button :icon="ShareOne" type="primary" @click="shareRoom">邀请玩家</icon-button>
</template>
<n-space :size="20" style="padding-top: 10px">
<n-space vertical align="center" v-for="(item, index) in players" :key="index" :size="0">
<n-popover :trigger="isHost ? 'hover' : 'manual'">
<template #trigger>
<n-badge
:type="item.id === hostId ? 'error' : 'info'"
:value="item.id === hostId ? '房主' : '玩家'"
>
<n-avatar size="large" :src="item.avatar" :img-props="{ referrerpolicy: 'no-referrer' }"/>
</n-badge>
</template>
<span>I wish they all could be California girls</span>
</n-popover>
<span>{{ item.name }}</span>
</n-space>
</n-space>
</n-card>
</n-space>
<div style="height: 100%">
<chat-board :players="players" :room-id="roomId" />
<span>私人房间只能通过房间号或邀请链接加入,<br/>不能从游戏大厅搜到。</span>
</n-popover>
<n-switch :rail-style="railStyle" v-model:value="isPrivateRoom">
<template #checked>私人房间</template>
<template #unchecked>公开房间</template>
</n-switch>
</div>
</div>
</waiting-room-base>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Close, Logout, Peoples, Play, ShareOne } from '@icon-park/vue-next'
import { useGameHubStore } from '@/stores/gamehub'
import { getData, removeData, toast } from '@/utils'
import type { GameTypes } from '@/def/games'
import { getGameTypeMap } from '@/def/games'
import type { GameRoom } from '@/api/game'
import { getGame, getShortenUrl } from '@/api/game'
import type { Player } from '@/def/players'
import type { SignalrResponse } from '@/api/signalr'
import IconButton from '@/universal/components/IconButton.vue'
import type { CSSProperties } from 'vue'
import { ref,watch } from 'vue'
import { Help } from '@icon-park/vue-next'
import WaitingRoomBase from '@/mobile/views/room/WaitingRoomBase.vue'
import Icon from '@/universal/components/Icon.vue'
import ChatBoard from '@/desktop/components/ChatBoard.vue' //注意这里是有意使用桌面端ChatBoard的
import GameInfoCard from '@/universal/components/GameInfoCard.vue'
const route = useRoute()
const router = useRouter()
const gameHub = useGameHubStore()
const userId = getData<string>('user-id')
const roomId = Array.isArray(route.params.roomId) ? route.params.roomId.join(',') : route.params.roomId
const isHost = ref(false)
const hostId = ref('')
const gameRoomData = ref<GameRoom>()
const gameTypeMap = ref<GameTypes>(getGameTypeMap())
const players = ref<Player[]>([])
let getGameInterval: any = null
async function gameInfoListener(response: SignalrResponse) {
const playerList = response.PlayerList
isHost.value = response.CreatorId == userId
hostId.value = response.CreatorId
players.value = playerList.map((p: SignalrResponse) => {
const avatar = p.UserAvatar ? p.UserAvatar : '/avatar.webp'
return {
id: p.UserId,
name: p.UserName,
avatar: avatar
}
})
const roomSettings = ref<any>()
const isPrivateRoom = ref(false)
if (response.GameStarted) {
await startGame()
watch(isPrivateRoom, (value) => {
roomSettings.value = {
IsPrivate: value
}
}
async function playerJoinedListener() {
gameHub.invokeGameHub('GetGame', roomId)
}
async function playerLeftListener(response: SignalrResponse) {
const playerId = response.LeavingPlayerId
const method = response.LeavingMethod
players.value = players.value.filter((p) => p.id !== playerId)
})
// 如果是自己被踢,弹出提示并返回首页
if (playerId == userId && method == 'Kicked') {
await toast('您已被房主踢出房间', 'warning')
function railStyle({ focused, checked }: { focused: boolean; checked: boolean }) {
const style: CSSProperties = {
margin: '0 10px'
}
}
async function gameStartedListener() {
if (gameRoomData.value?.gameType) {
const gameData = gameTypeMap.value[gameRoomData.value?.gameType]
await router.push(gameData.route + "game/" + roomId)
if (checked) {
style.background = '#7b40f2'
if (focused) {
style.boxShadow = '0 0 0 2px #d0305040'
}
} else {
style.background = '#18a058'
if (focused) {
style.boxShadow = '0 0 0 2px #2080f040'
}
}
return style
}
async function gameClosedListener() {
await toast('房间已关闭', 'warning')
await router.push('/regular-home')
}
async function startGame() {
gameHub.invokeGameHub('StartGame', roomId)
}
async function closeRoom() {
gameHub.invokeGameHub('CloseGame', roomId)
}
async function leaveRoom() {
removeData('current-game-id')
gameHub.invokeGameHub('LeaveGame', roomId)
await router.push('/regular-home')
}
async function shareRoom() {
const sUrl = await getShortenUrl(roomId)
await navigator.clipboard.writeText(`快来和大家一起玩游戏吧,点击链接: ${sUrl} 立刻加入房间。房间号[${gameRoomData.value?.joinCode}]。`)
await toast('已复制加入链接到剪贴板', 'success')
function onSettingsChange(Settings:any){
isPrivateRoom.value = Settings.isPrivateRoom
}
onMounted(async () => {
gameRoomData.value = await getGame(roomId)
gameHub.addGameHubListener('GameInfo', gameInfoListener)
gameHub.addGameHubListener('PlayerJoined', playerJoinedListener)
gameHub.addGameHubListener('PlayerLeft', playerLeftListener)
gameHub.addGameHubListener('PlayerKicked', playerLeftListener)
gameHub.addGameHubListener('GameClosed', gameClosedListener)
gameHub.addGameHubListener('GameStarted', gameStartedListener)
gameHub.invokeGameHub('GetGame', roomId)
// 间隔一段时间获取一次房间信息
getGameInterval = setInterval(() => {
gameHub.invokeGameHub('GetGame', roomId)
}, 4000)
})
onUnmounted(async () => {
gameHub.removeGameHubListener('GameInfo', gameInfoListener)
gameHub.removeGameHubListener('PlayerJoined', playerJoinedListener)
gameHub.removeGameHubListener('PlayerLeft', playerLeftListener)
gameHub.removeGameHubListener('PlayerKicked', playerLeftListener)
gameHub.removeGameHubListener('GameClosed', gameClosedListener)
gameHub.removeGameHubListener('GameStarted', gameStartedListener)
clearInterval(getGameInterval)
})
</script>

<style lang="scss" scoped>
.waiting-room {
height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
& > div {
margin-bottom: 10px;
}
}
</style>
Loading

0 comments on commit a12076b

Please sign in to comment.