✨ feat: 添加落子功能
This commit is contained in:
parent
e3440c72e2
commit
8d5a19248c
@ -37,6 +37,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.17.9",
|
||||
"@unocss/transformer-directives": "^0.65.1",
|
||||
"@vitejs/plugin-legacy": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-container class="app__container">
|
||||
<el-header class="app-header flex items-center">
|
||||
<el-header class="app-header flex items-center bg-white">
|
||||
<el-icon v-show="mdLess" size="30" @click="showVerticalHeaderMenu = true">
|
||||
<icon-cs-menu />
|
||||
</el-icon>
|
||||
@ -85,7 +85,6 @@
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
// 侧边栏需要居中
|
||||
.app-header-user {
|
||||
|
@ -2,7 +2,11 @@
|
||||
<transition appear name="game-container" @after-enter="onContainerEnterComplete">
|
||||
<div :style="containerSizeStyle" class="game-container">
|
||||
<transition name="mask">
|
||||
<div v-show="gameStatus !== 'playing' && !hideMask" class="mask" @click="onMaskClick">
|
||||
<div
|
||||
v-show="gameStatus !== 'playing' && !hideMask"
|
||||
class="mask bg-white bg-op-50"
|
||||
@click="onMaskClick"
|
||||
>
|
||||
<div v-if="gameStatus === 'succeed'">
|
||||
<b>你赢了!</b>
|
||||
<div>🎉🥳🎊</div>
|
||||
@ -120,7 +124,6 @@
|
||||
align-items: center;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
background-color: rgb(white, 0.5);
|
||||
color: rgb(119, 110, 101);
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-header class="gobang-header flex items-center">
|
||||
<el-header class="gobang-header flex items-center bg-white">
|
||||
<router-link custom :to="{ name: 'GobangList' }" v-slot="{ navigate }">
|
||||
<div class="gobang-header__left flex items-center" @click="navigate">
|
||||
<el-icon :size="30"><icon-cs-gobang /></el-icon>
|
||||
@ -12,7 +12,6 @@
|
||||
<style lang="scss" scoped>
|
||||
.gobang-header {
|
||||
--el-header-height: var(--header-height);
|
||||
background-color: white;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
|
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="gobang-play-page-aside-user flex justify-center items-center" :class="{ 'flex-col': !footer }">
|
||||
<div
|
||||
class="gobang-play-page-aside-user flex justify-center items-center"
|
||||
:class="{ 'flex-col': !footer }"
|
||||
>
|
||||
<el-avatar :size="40" :icon="user.avatar ?? undefined" />
|
||||
<div class="gobang-play-page-aside-user__name">{{ user.name }}</div>
|
||||
</div>
|
||||
@ -11,5 +14,7 @@
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import type { UserInfo } from '@/schemas';
|
||||
const props = defineProps<{ user: UserInfo; footer: boolean }>();
|
||||
withDefaults(defineProps<{ user: UserInfo; footer?: boolean }>(), {
|
||||
footer: false,
|
||||
});
|
||||
</script>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import router from '@/router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useWebSocket } from '@vueuse/core';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { ValueOf } from 'element-plus/es/components/table/src/table-column/defaults.mjs';
|
||||
@ -51,7 +50,6 @@ export function useGameSocket<TRequest extends SimplePart<string>, TResp extends
|
||||
(reversedRelations[v] ??= new Set()).add(k);
|
||||
}
|
||||
}
|
||||
const userStore = useUserStore();
|
||||
const relationMap = new Map<keyof TRelations, [TRequest, Set<string>]>();
|
||||
const ws = useWebSocket<string>(options.url, {
|
||||
// autoReconnect: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-main class="gobang-list-page__wrapper !flex">
|
||||
<el-main class="gobang-list-page__wrapper !flex bg-white">
|
||||
<el-container>
|
||||
<gobang-header class="justify-between">
|
||||
<div class="flex items-center">
|
||||
@ -44,9 +44,6 @@
|
||||
</el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.gobang-list-page__wrapper {
|
||||
background-color: white;
|
||||
}
|
||||
.gobang-list-page-header__title {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
@ -92,7 +89,8 @@ export type Request =
|
||||
| SimplePart<'RoomList'>
|
||||
| SimplePart<'CreateRoom'>
|
||||
| PayloadPart<'PlayerJoin', { roomId: RoomId }>
|
||||
| SimplePart<'ResetRoom'>;
|
||||
| SimplePart<'ResetRoom'>
|
||||
| PayloadPart<'PlaceChessPiece', { x: number; y: number }>;
|
||||
export type Resp =
|
||||
| PayloadPart<'UserInfo', UserInfo>
|
||||
| PayloadPart<'RoomList', { rooms: Room[] }>
|
||||
@ -105,6 +103,7 @@ export const relations = {
|
||||
RoomList: 'RoomList',
|
||||
CreateRoom: 'RoomCreated',
|
||||
PlayerJoin: ['PlayerSideAllocation', 'RoomInfo'],
|
||||
PlaceChessPiece: ['RoomInfo'],
|
||||
} as const;
|
||||
export function useGobangSocket(
|
||||
options: Omit<UseGameSocketOptions<Request, Resp, typeof relations>, 'url' | 'relations'>,
|
||||
|
@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<el-main
|
||||
v-loading="state !== 'GAMING' && state !== 'WAITING'"
|
||||
class="gobang-play-page__wrapper !flex"
|
||||
>
|
||||
<el-container direction="vertical" v-if="state !== 'FIRST_LOADING'">
|
||||
<el-main v-loading="firstLoading || placing" class="gobang-play-page__wrapper !flex bg-white">
|
||||
<el-container direction="vertical">
|
||||
<gobang-header class="justify-between">
|
||||
<div class="flex items-center">
|
||||
<template v-if="roomId">
|
||||
@ -22,17 +19,18 @@
|
||||
<div class="gobang__state">
|
||||
<!-- {{ stateDisplayMap[state] }} -->
|
||||
</div>
|
||||
<div class="gobang-user">
|
||||
<div class="gobang-user__name">{{}}</div>
|
||||
</div>
|
||||
<div class="gobang-chessboard">
|
||||
<div
|
||||
class="gobang-chessboard"
|
||||
:class="{
|
||||
'gobang-chessboard--enabled': enabled,
|
||||
}"
|
||||
>
|
||||
<canvas class="gobang-chessboard__background" ref="canvas"></canvas>
|
||||
<template v-for="(row, y) of grid">
|
||||
<template v-for="(cell, x) of row">
|
||||
<!-- eslint-disable-next-line vue/require-v-for-key -->
|
||||
<div
|
||||
v-if="state !== 'WAITING'"
|
||||
class="gobang-chessboard__cell"
|
||||
class="gobang-chessboard__cell absolute border border-black rounded-full"
|
||||
:class="[
|
||||
cell
|
||||
? cell.isWhite
|
||||
@ -41,6 +39,7 @@
|
||||
: undefined,
|
||||
]"
|
||||
:style="getCellStyle(cell, x, y)"
|
||||
@click="onCellClick(cell, x, y)"
|
||||
></div>
|
||||
</template>
|
||||
</template>
|
||||
@ -50,8 +49,28 @@
|
||||
v-if="!mdLess"
|
||||
class="gobang-play-page-aside !flex flex-col items-center justify-between"
|
||||
>
|
||||
<gobang-user v-if="otherUser" :footer="false" :user="otherUser" />
|
||||
<div class="gobang-play-page-aside__vs absolute-self-center">VS</div>
|
||||
<div><gobang-user v-if="otherUser" :footer="false" :user="otherUser" /></div>
|
||||
<div>
|
||||
<div
|
||||
v-if="otherUser"
|
||||
class="absolute-self-center font-bold text-12.5 flex flex-col items-center select-none"
|
||||
>
|
||||
VS
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="absolute-self-center text-center text-5 font-bold w-100% select-none"
|
||||
>
|
||||
等待玩家加入.
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center">
|
||||
<gobang-user v-if="userStore.userInfo" :user="userStore.userInfo" />
|
||||
<div class="font-bold text-5">
|
||||
<template v-if="selfRound">请落子.</template>
|
||||
<template v-else>等待对方落子.</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-aside>
|
||||
</el-container>
|
||||
<el-footer v-if="mdLess" class="gobang-play-page-footer flex items-center">
|
||||
@ -62,9 +81,6 @@
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.gobang-play-page {
|
||||
&__wrapper {
|
||||
background-color: white;
|
||||
}
|
||||
&-main {
|
||||
--el-main-padding: 0;
|
||||
margin-top: var(--header-height);
|
||||
@ -76,10 +92,6 @@
|
||||
border-left: 1px solid var(--el-border-color);
|
||||
width: 200px;
|
||||
padding: 80px 0;
|
||||
&__vs {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
&-footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
@ -92,39 +104,37 @@
|
||||
zoom: v-bind(boardZoom);
|
||||
&__cell {
|
||||
--size: 24px;
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 50%;
|
||||
user-select: none;
|
||||
@mixin border {
|
||||
border: 1px solid black;
|
||||
}
|
||||
&--white {
|
||||
@include border;
|
||||
background-color: white;
|
||||
@apply bg-white;
|
||||
}
|
||||
&--black {
|
||||
@include border;
|
||||
background-color: black;
|
||||
@apply bg-black;
|
||||
}
|
||||
&:not(&--white, &--black):hover {
|
||||
background-color: red;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&--enabled {
|
||||
.gobang-chessboard__cell {
|
||||
&:not(&--white, &--black):hover {
|
||||
background-color: rgb(red, 0.5);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import GobangUser from '@/components/gobang/GobangUser.vue';
|
||||
import GobangHeader from '@/components/gobang/GobangHeader.vue';
|
||||
import GobangUser from '@/components/gobang/GobangUser.vue';
|
||||
import router from '@/router';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useGobangSocket, type RoomDetail, type RoomId } from '@/views/GobangListPage.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onMounted, ref, watchEffect, type CSSProperties } from 'vue';
|
||||
import { computed, onMounted, ref, watch, watchEffect, type CSSProperties } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
const userStore = useUserStore();
|
||||
const { smLess, mdLess } = storeToRefs(useMediaStore());
|
||||
@ -200,20 +210,62 @@ function getCellStyle(chess: Chess | undefined, x: number, y: number) {
|
||||
};
|
||||
return res;
|
||||
}
|
||||
type State = 'FIRST_LOADING' | 'LOADING' | 'GAMING' | 'WAITING';
|
||||
const firstLoading = ref(true);
|
||||
enum MatchState {
|
||||
WAITING,
|
||||
GAMING,
|
||||
FINISHED,
|
||||
}
|
||||
const matchState = ref<MatchState>(MatchState.WAITING);
|
||||
function updateMatchState(finished?: boolean) {
|
||||
if (finished) {
|
||||
matchState.value = MatchState.FINISHED;
|
||||
return;
|
||||
}
|
||||
matchState.value = otherUser.value ? MatchState.GAMING : MatchState.WAITING;
|
||||
}
|
||||
watch(matchState, (v) => {
|
||||
console.log(v);
|
||||
switch (v) {
|
||||
case MatchState.GAMING:
|
||||
ElMessage('对局开始');
|
||||
break;
|
||||
case MatchState.FINISHED:
|
||||
resetLoadingStates();
|
||||
break;
|
||||
}
|
||||
});
|
||||
const placing = ref(false);
|
||||
const selfRound = ref(false);
|
||||
const enabled = computed(
|
||||
() => matchState.value === MatchState.GAMING && selfRound.value && !placing.value,
|
||||
);
|
||||
function resetLoadingStates() {
|
||||
placing.value = false;
|
||||
}
|
||||
const room = ref<RoomDetail>();
|
||||
const otherUser = computed(() => {
|
||||
if (!room.value) return;
|
||||
const { whiteUser, blackUser } = room.value;
|
||||
return [whiteUser, blackUser].find((v) => v && v.id !== userStore.userInfo!.id);
|
||||
});
|
||||
watch(otherUser, (v, old) => {
|
||||
updateMatchState();
|
||||
});
|
||||
const grid = computed<Grid | undefined>(() =>
|
||||
room.value?.pieces.map((row) =>
|
||||
row.map((v) => (v === 0 ? { isWhite: false } : v === 1 ? { isWhite: true } : undefined)),
|
||||
),
|
||||
);
|
||||
const state = ref<State>('FIRST_LOADING');
|
||||
const isWhite = ref(false);
|
||||
function onCellClick(cell: Chess | undefined, x: number, y: number) {
|
||||
if (cell || !enabled.value) return;
|
||||
placing.value = true;
|
||||
send({
|
||||
name: 'PlaceChessPiece',
|
||||
payload: { x, y },
|
||||
});
|
||||
}
|
||||
const { send } = useGobangSocket({
|
||||
succeed: {
|
||||
UserInfo() {
|
||||
@ -226,29 +278,41 @@ const { send } = useGobangSocket({
|
||||
});
|
||||
},
|
||||
PlayerSideAllocation(p) {
|
||||
isWhite.value = p.isWhite;
|
||||
console.log(p.isWhite);
|
||||
selfRound.value = !(isWhite.value = p.isWhite);
|
||||
},
|
||||
RoomInfo(p) {
|
||||
room.value = p;
|
||||
if (matchState.value === MatchState.GAMING) {
|
||||
selfRound.value = !selfRound.value;
|
||||
}
|
||||
},
|
||||
PlayerLeave() {
|
||||
ElMessage.warning('对方已离开房间,对局自动结束');
|
||||
room.value = undefined;
|
||||
},
|
||||
},
|
||||
error: {
|
||||
PlayerJoin() {
|
||||
router.back();
|
||||
queueMicrotask(() => router.back());
|
||||
return false;
|
||||
},
|
||||
},
|
||||
finally: {
|
||||
PlayerJoin(error) {
|
||||
if (!error) {
|
||||
state.value = 'GAMING';
|
||||
firstLoading.value = false;
|
||||
updateMatchState();
|
||||
}
|
||||
},
|
||||
PlaceChessPiece() {
|
||||
placing.value = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
onMounted(() => {
|
||||
if (!roomId) {
|
||||
state.value = 'GAMING';
|
||||
matchState.value = MatchState.GAMING;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { defineConfig } from 'unocss';
|
||||
import { transformerDirectives } from 'unocss';
|
||||
export default defineConfig({
|
||||
rules: [
|
||||
[
|
||||
@ -11,4 +12,5 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
],
|
||||
transformers: [transformerDirectives()],
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user