✨ feat: 初步完成五子棋功能
This commit is contained in:
parent
b68d20d5ea
commit
d52aa2af21
@ -187,7 +187,7 @@ const loginFormRules = reactive<FormRules<typeof loginFormData>>({
|
||||
],
|
||||
verifyCode: [
|
||||
{ required: true, message: '请输入验证码' },
|
||||
{ pattern: /[0-9A-Za-z]{4}/, message: '验证码不符合格式' },
|
||||
{ pattern: /^[0-9A-Za-z]{4}$/, message: '验证码不符合格式' },
|
||||
],
|
||||
});
|
||||
const logining = ref(false);
|
||||
|
@ -34,7 +34,16 @@
|
||||
:disabled="(row as RoomRender).state === RoomState.GAMING"
|
||||
@click="onJoinButtonClick(row)"
|
||||
>
|
||||
<template v-if="(row as RoomRender).state === RoomState.GAMING">已满</template>
|
||||
<template
|
||||
v-if="
|
||||
arrayIncludes(
|
||||
[RoomState.GAMING, RoomState.FINISHED],
|
||||
(row as RoomRender).state,
|
||||
)
|
||||
"
|
||||
>
|
||||
已满
|
||||
</template>
|
||||
<template v-else>加入</template>
|
||||
</el-button>
|
||||
</template>
|
||||
@ -70,6 +79,8 @@ export enum RoomState {
|
||||
WAITING = 'WAITING',
|
||||
/** 正在游戏 */
|
||||
GAMING = 'GAMING',
|
||||
/** 正在游戏,但对局已经结束 */
|
||||
FINISHED = 'FINISHED',
|
||||
}
|
||||
/** 房间UUID */
|
||||
export type RoomId = string;
|
||||
@ -87,6 +98,9 @@ export type RoomDetail = {
|
||||
pieces: (-1 | 0 | 1)[][];
|
||||
whiteUser?: UserInfo;
|
||||
blackUser?: UserInfo;
|
||||
blackRequestRestart?: boolean;
|
||||
whiteRequestRestart?: boolean;
|
||||
canWhiteDown: boolean;
|
||||
};
|
||||
export enum WinFace {
|
||||
UP = 'UP',
|
||||
@ -115,10 +129,12 @@ export type Resp =
|
||||
| PayloadPart<
|
||||
'HasPlayerWin',
|
||||
{
|
||||
face: WinFace;
|
||||
isWhite: boolean;
|
||||
originalX: number;
|
||||
originalY: number;
|
||||
winInfo: {
|
||||
face: WinFace;
|
||||
isWhite: boolean;
|
||||
originalX: number;
|
||||
originalY: number;
|
||||
};
|
||||
}
|
||||
>
|
||||
| PayloadPart<'PlayerWin'>
|
||||
@ -128,17 +144,27 @@ const relations = {
|
||||
CreateRoom: 'RoomCreated',
|
||||
PlayerJoin: ['PlayerSideAllocation', 'RoomInfo'],
|
||||
PlaceChessPiece: ['RoomInfo'],
|
||||
ResetRoom: ['RoomInfo'],
|
||||
} as const satisfies Relations<Request['name'], Resp['name']>;
|
||||
const DEBUG = true;
|
||||
export function useGobangSocket(
|
||||
options: Omit<UseGameSocketOptions<Request, Resp, typeof relations>, 'url' | 'relations'>,
|
||||
) {
|
||||
return useGameSocket<Request, Resp>()(
|
||||
Object.assign(options, {
|
||||
url: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
|
||||
url: DEBUG
|
||||
? `ws://172.16.114.84:58080/chess/${useUserStore().token}`
|
||||
: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
|
||||
relations,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const playerCountMap: Record<RoomState, string> = {
|
||||
[RoomState.CREATED]: '0/2',
|
||||
[RoomState.WAITING]: '1/2',
|
||||
[RoomState.GAMING]: '2/2',
|
||||
[RoomState.FINISHED]: '2/2',
|
||||
};
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import CreateGobangRoomDialog from '@/components/gobang/CreateGobangRoomDialog.vue';
|
||||
@ -146,6 +172,7 @@ import GobangHeader from '@/components/gobang/GobangHeader.vue';
|
||||
import router from '@/router';
|
||||
import type { UserInfo } from '@/schemas';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { arrayIncludes } from '@/utils';
|
||||
import {
|
||||
useGameSocket,
|
||||
type PayloadPart,
|
||||
@ -190,14 +217,7 @@ onMounted(() => {
|
||||
refresh();
|
||||
});
|
||||
function getPlayerCount(row: RoomRender) {
|
||||
switch (row.state) {
|
||||
case RoomState.CREATED:
|
||||
return '0/2';
|
||||
case RoomState.WAITING:
|
||||
return '1/2';
|
||||
case RoomState.GAMING:
|
||||
return '2/2';
|
||||
}
|
||||
return playerCountMap[row.state];
|
||||
}
|
||||
function onJoinButtonClick(room: RoomRender) {
|
||||
play(room.id);
|
||||
|
@ -16,11 +16,10 @@
|
||||
</gobang-header>
|
||||
<el-container direction="horizontal">
|
||||
<el-main class="gobang-play-page-main !flex flex-col justify-center items-center">
|
||||
<div class="gobang__state">
|
||||
<!-- {{ stateDisplayMap[state] }} -->
|
||||
</div>
|
||||
<div
|
||||
v-loading="matchState === MatchState.FINISHED"
|
||||
class="gobang-chessboard"
|
||||
element-loading-svg-view-box="0 0 0 0"
|
||||
:class="{
|
||||
'gobang-chessboard--enabled': enabled,
|
||||
}"
|
||||
@ -70,10 +69,24 @@
|
||||
</div>
|
||||
<div class="flex flex-col items-center">
|
||||
<gobang-user v-if="userStore.userInfo" :user="userStore.userInfo" />
|
||||
<div v-if="otherUser" class="font-bold text-5">
|
||||
<div v-if="matchState === MatchState.GAMING" class="font-bold text-5">
|
||||
<template v-if="selfRound">请落子.</template>
|
||||
<template v-else>等待对方落子.</template>
|
||||
</div>
|
||||
<template v-else-if="matchState === MatchState.FINISHED">
|
||||
<div class="font-bold text-5">
|
||||
<template v-if="restartState === RestartState.WAITING">等待对方重新开始.</template>
|
||||
<template v-else>等待重新开始.</template>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="restartState === RestartState.RESTARTING"
|
||||
:disabled="restartState === RestartState.WAITING"
|
||||
@click="restart"
|
||||
>
|
||||
重新开始
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</el-aside>
|
||||
</el-container>
|
||||
@ -119,7 +132,9 @@
|
||||
@apply bg-black border-solid;
|
||||
}
|
||||
&--win-blink {
|
||||
box-shadow: 5px 5px 5px black;
|
||||
// box-shadow: 0 0 10px red;
|
||||
// 超过遮罩
|
||||
z-index: 2001;
|
||||
}
|
||||
}
|
||||
&--enabled {
|
||||
@ -159,9 +174,9 @@ import GobangUser from '@/components/gobang/GobangUser.vue';
|
||||
import router from '@/router';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { create2DArray } from '@/utils';
|
||||
import { arrayIncludes, create2DArray } from '@/utils';
|
||||
import { useGobangSocket, WinFace, type RoomDetail, type RoomId } from '@/views/GobangListPage.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElMessage, ElNotification } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onMounted, ref, watch, watchEffect, type CSSProperties } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
@ -232,9 +247,9 @@ function getCellStyle(chess: Chess | undefined, x: number, y: number) {
|
||||
}
|
||||
const firstLoading = ref(true);
|
||||
enum MatchState {
|
||||
WAITING,
|
||||
GAMING,
|
||||
FINISHED,
|
||||
WAITING = 'WAITING',
|
||||
GAMING = 'GAMING',
|
||||
FINISHED = 'FINISHED',
|
||||
}
|
||||
const matchState = ref<MatchState>(MatchState.WAITING);
|
||||
function updateMatchState(finished?: boolean) {
|
||||
@ -272,9 +287,13 @@ const otherUser = computed(() => {
|
||||
const { whiteUser, blackUser } = room.value;
|
||||
return [whiteUser, blackUser].find((v) => v && v.id !== userStore.userInfo!.id);
|
||||
});
|
||||
watch(otherUser, (v, old) => {
|
||||
updateMatchState();
|
||||
});
|
||||
watch(
|
||||
() => otherUser.value?.id,
|
||||
(v, old) => {
|
||||
updateMatchState();
|
||||
},
|
||||
);
|
||||
const isWhite = ref(false);
|
||||
const grid = computed<Grid | undefined>(() =>
|
||||
room.value?.pieces.map((row) => row.map((v) => (v !== -1 ? { isWhite: !!v } : undefined))),
|
||||
);
|
||||
@ -282,7 +301,26 @@ const blink = ref<boolean[][]>();
|
||||
function resetBlink() {
|
||||
blink.value = undefined;
|
||||
}
|
||||
const isWhite = ref(false);
|
||||
const win = ref(false);
|
||||
enum RestartState {
|
||||
NO = 'NO',
|
||||
RESTARTING = 'RESTARTING',
|
||||
WAITING = 'WAITING',
|
||||
}
|
||||
|
||||
const restartState = ref<RestartState>(RestartState.NO);
|
||||
function resetRestartState() {
|
||||
restartState.value = RestartState.NO;
|
||||
}
|
||||
const otherRequiresRestart = ref(false);
|
||||
function resetOtherRequiresRestart() {
|
||||
otherRequiresRestart.value = false;
|
||||
}
|
||||
function resetNecessaryStates() {
|
||||
resetSelfRound();
|
||||
resetBlink();
|
||||
resetRestartState();
|
||||
}
|
||||
function onCellClick(cell: Chess | undefined, x: number, y: number) {
|
||||
if (cell || !enabled.value) return;
|
||||
placing.value = true;
|
||||
@ -308,25 +346,57 @@ const { send } = useGobangSocket({
|
||||
},
|
||||
RoomInfo(p) {
|
||||
room.value = p;
|
||||
const { whiteRequestRestart, blackRequestRestart } = room.value;
|
||||
const selfRequiresRestart = isWhite.value ? whiteRequestRestart : blackRequestRestart;
|
||||
otherRequiresRestart.value =
|
||||
(isWhite.value ? blackRequestRestart : whiteRequestRestart) ?? false;
|
||||
if (
|
||||
arrayIncludes([RestartState.RESTARTING, RestartState.WAITING], restartState.value) &&
|
||||
!selfRequiresRestart &&
|
||||
!otherRequiresRestart.value
|
||||
) {
|
||||
resetRestartState();
|
||||
resetOtherRequiresRestart();
|
||||
resetNecessaryStates();
|
||||
updateMatchState();
|
||||
}
|
||||
if (matchState.value === MatchState.GAMING) {
|
||||
selfRound.value = !selfRound.value;
|
||||
selfRound.value = isWhite.value === p.canWhiteDown;
|
||||
}
|
||||
},
|
||||
PlayerLeave() {
|
||||
ElMessage.warning('对方已离开房间,对局自动结束');
|
||||
ElMessage.warning('对方已离开房间, 对局自动结束.');
|
||||
room.value = undefined;
|
||||
resetSelfRound();
|
||||
resetRoom();
|
||||
resetBlink();
|
||||
sendResetRoom();
|
||||
resetNecessaryStates();
|
||||
},
|
||||
HasPlayerWin(p) {
|
||||
const [deltaX, deltaY] = deltaMap[p.face];
|
||||
const { originalX, originalY } = p;
|
||||
blink.value = create2DArray(
|
||||
boardLength,
|
||||
boardLength,
|
||||
(x, y) => x === originalX + deltaX && y === originalY + deltaY,
|
||||
);
|
||||
HasPlayerWin({ winInfo }) {
|
||||
const [deltaX, deltaY] = deltaMap[winInfo.face];
|
||||
const { originalX, originalY } = winInfo;
|
||||
blink.value = create2DArray(boardLength, boardLength, false);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
blink.value[originalY + deltaY * i][originalX + deltaX * i] = true;
|
||||
}
|
||||
resetRestartState();
|
||||
updateMatchState(true);
|
||||
},
|
||||
PlayerWin() {
|
||||
win.value = true;
|
||||
ElNotification({
|
||||
title: '你赢了!',
|
||||
message: '🎉🥳🎊',
|
||||
type: 'success',
|
||||
position: mdLess.value ? 'bottom-right' : 'top-right',
|
||||
});
|
||||
},
|
||||
PlayerLose() {
|
||||
win.value = false;
|
||||
ElNotification({
|
||||
title: '你输了!',
|
||||
message: '🤣👉🤡',
|
||||
type: 'error',
|
||||
position: mdLess.value ? 'bottom-right' : 'top-right',
|
||||
});
|
||||
},
|
||||
},
|
||||
error: {
|
||||
@ -345,13 +415,20 @@ const { send } = useGobangSocket({
|
||||
PlaceChessPiece() {
|
||||
placing.value = false;
|
||||
},
|
||||
ResetRoom() {
|
||||
restartState.value = RestartState.WAITING;
|
||||
},
|
||||
},
|
||||
});
|
||||
function resetRoom() {
|
||||
function sendResetRoom() {
|
||||
send({
|
||||
name: 'ResetRoom',
|
||||
});
|
||||
}
|
||||
function restart() {
|
||||
sendResetRoom();
|
||||
restartState.value = RestartState.RESTARTING;
|
||||
}
|
||||
onMounted(() => {
|
||||
if (!roomId) {
|
||||
matchState.value = MatchState.GAMING;
|
||||
|
Loading…
x
Reference in New Issue
Block a user