✨ feat: 增加五子棋高亮功能
This commit is contained in:
parent
84992372a4
commit
864b585c2e
@ -9,7 +9,29 @@ export function create2DArray<T>(
|
||||
Array.from({ length: width }, (_, x) => (cell instanceof Function ? cell(x, y) : cell)),
|
||||
);
|
||||
}
|
||||
|
||||
export function* iter2DArray<T>(grid: T[][]): Generator<[number, number, T], void, unknown> {
|
||||
for (let y = 0; y < grid.length; y++) {
|
||||
for (let x = 0; x < grid[y].length; x++) {
|
||||
yield [x, y, grid[y][x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
export type Zip<T extends Iterable<unknown>[]> = T extends [
|
||||
infer R,
|
||||
...infer S extends Iterable<unknown>[],
|
||||
]
|
||||
? R extends Iterable<infer U>
|
||||
? [U, ...Zip<S>]
|
||||
: []
|
||||
: [];
|
||||
export function* zip<T extends Iterable<unknown>[]>(...its: T): Generator<Zip<T>, void, unknown> {
|
||||
const iterators = its.map((it) => it[Symbol.iterator]());
|
||||
while (true) {
|
||||
const nextValues = iterators.map((it) => it.next());
|
||||
if (nextValues.some(({ done }) => done)) break;
|
||||
yield nextValues.map(({ value }) => value) as any;
|
||||
}
|
||||
}
|
||||
export function get2DArrayItem<T>(
|
||||
grid: T[][],
|
||||
first: number,
|
||||
|
@ -28,6 +28,10 @@ export interface UseGameSocketOptions<
|
||||
error?: {
|
||||
[P in TRequest['name']]?: (reason: string) => boolean | void;
|
||||
};
|
||||
pre?: {
|
||||
// 只有拥有依赖关系的请求才可以有pre
|
||||
[P in keyof TRelations]?: (error: boolean) => void;
|
||||
};
|
||||
finally?: {
|
||||
// 只有拥有依赖关系的请求才可以有finally
|
||||
[P in keyof TRelations]?: (error: boolean) => void;
|
||||
@ -79,6 +83,7 @@ export function useGameSocket<TRequest extends SimplePart<string>, TResp extends
|
||||
error = true;
|
||||
if (relations[originalPacketName]) {
|
||||
reqName = originalPacketName;
|
||||
options.pre?.[reqName]?.(true);
|
||||
relationMap.delete(originalPacketName);
|
||||
}
|
||||
const errorHandler = options.error?.[originalPacketName];
|
||||
@ -99,6 +104,7 @@ export function useGameSocket<TRequest extends SimplePart<string>, TResp extends
|
||||
if (set.size) continue;
|
||||
relationMap.delete(k);
|
||||
reqName = k;
|
||||
options.pre?.[reqName]?.(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
,
|
||||
{
|
||||
'gobang-chessboard__cell--win-blink': blink?.[y][x],
|
||||
'gobang-chessboard__cell--last':
|
||||
lastChess && lastChess[0] === x && lastChess[1] === y,
|
||||
},
|
||||
]"
|
||||
:style="getCellStyle(cell, x, y)"
|
||||
@ -76,6 +78,7 @@
|
||||
<template v-else-if="matchState === MatchState.FINISHED">
|
||||
<div class="font-bold text-5">
|
||||
<template v-if="restartState === RestartState.WAITING">等待对方重新开始.</template>
|
||||
<template v-else-if="otherRequiresRestart">对方已重新开始.</template>
|
||||
<template v-else>等待重新开始.</template>
|
||||
</div>
|
||||
<el-button
|
||||
@ -126,10 +129,29 @@
|
||||
height: var(--size);
|
||||
user-select: none;
|
||||
&--white {
|
||||
@apply bg-white border-solid;
|
||||
@apply bg-white border-solid border-inset;
|
||||
}
|
||||
&--black {
|
||||
@apply bg-black border-solid;
|
||||
@apply bg-black border-solid border-inset;
|
||||
}
|
||||
&--last {
|
||||
&::after {
|
||||
@apply absolute inset--1;
|
||||
content: '';
|
||||
border: 2px solid red;
|
||||
animation: blink 1s ease-in-out infinite;
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&--win-blink {
|
||||
// box-shadow: 0 0 10px red;
|
||||
@ -174,9 +196,9 @@ import GobangUser from '@/components/gobang/GobangUser.vue';
|
||||
import router from '@/router';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { arrayIncludes, create2DArray } from '@/utils';
|
||||
import { arrayIncludes, create2DArray, iter2DArray, zip } from '@/utils';
|
||||
import { useGobangSocket, WinFace, type RoomDetail, type RoomId } from '@/views/GobangListPage.vue';
|
||||
import { ElMessage, ElNotification } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onMounted, ref, watch, watchEffect, type CSSProperties } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
@ -289,7 +311,7 @@ const otherUser = computed(() => {
|
||||
});
|
||||
watch(
|
||||
() => otherUser.value?.id,
|
||||
(v, old) => {
|
||||
() => {
|
||||
updateMatchState();
|
||||
},
|
||||
);
|
||||
@ -297,7 +319,26 @@ const isWhite = ref(false);
|
||||
const grid = computed<Grid | undefined>(() =>
|
||||
room.value?.pieces.map((row) => row.map((v) => (v !== -1 ? { isWhite: !!v } : undefined))),
|
||||
);
|
||||
watch(
|
||||
grid,
|
||||
(v, old) => {
|
||||
if (!v || !old) return;
|
||||
let changed = false;
|
||||
for (const [[x, y, newItem], [, , oldItem]] of zip(iter2DArray(v), iter2DArray(old))) {
|
||||
if (newItem && !oldItem && !selfRound.value) {
|
||||
lastChess.value = [x, y];
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
lastChess.value = undefined;
|
||||
}
|
||||
},
|
||||
{ flush: 'sync' },
|
||||
);
|
||||
const blink = ref<boolean[][]>();
|
||||
const lastChess = ref<[number, number]>();
|
||||
function resetBlink() {
|
||||
blink.value = undefined;
|
||||
}
|
||||
@ -320,6 +361,7 @@ function resetNecessaryStates() {
|
||||
resetSelfRound();
|
||||
resetBlink();
|
||||
resetRestartState();
|
||||
lastChess.value = undefined;
|
||||
}
|
||||
function onCellClick(cell: Chess | undefined, x: number, y: number) {
|
||||
if (cell || !enabled.value) return;
|
||||
@ -350,22 +392,25 @@ const { send } = useGobangSocket({
|
||||
const selfRequiresRestart = isWhite.value ? whiteRequestRestart : blackRequestRestart;
|
||||
otherRequiresRestart.value =
|
||||
(isWhite.value ? blackRequestRestart : whiteRequestRestart) ?? false;
|
||||
if (matchState.value === MatchState.GAMING) {
|
||||
selfRound.value = isWhite.value === p.canWhiteDown;
|
||||
}
|
||||
if (
|
||||
arrayIncludes([RestartState.RESTARTING, RestartState.WAITING], restartState.value) &&
|
||||
restartState.value === RestartState.WAITING &&
|
||||
!selfRequiresRestart &&
|
||||
!otherRequiresRestart.value
|
||||
) {
|
||||
resetRestartState();
|
||||
resetOtherRequiresRestart();
|
||||
resetNecessaryStates();
|
||||
updateMatchState();
|
||||
}
|
||||
if (matchState.value === MatchState.GAMING) {
|
||||
selfRound.value = isWhite.value === p.canWhiteDown;
|
||||
}
|
||||
},
|
||||
PlayerLeave() {
|
||||
ElMessage.warning('对方已离开房间, 对局自动结束.');
|
||||
if (matchState.value === MatchState.FINISHED) {
|
||||
ElMessage.warning('对方已离开房间.');
|
||||
} else {
|
||||
ElMessage.warning('对方已离开房间, 对局自动结束.');
|
||||
}
|
||||
room.value = undefined;
|
||||
sendResetRoom();
|
||||
resetNecessaryStates();
|
||||
@ -377,25 +422,26 @@ const { send } = useGobangSocket({
|
||||
for (let i = 0; i < 5; i++) {
|
||||
blink.value[originalY + deltaY * i][originalX + deltaX * i] = true;
|
||||
}
|
||||
resetRestartState();
|
||||
updateMatchState(true);
|
||||
},
|
||||
PlayerWin() {
|
||||
win.value = true;
|
||||
ElNotification({
|
||||
ElMessageBox({
|
||||
title: '你赢了!',
|
||||
message: '🎉🥳🎊',
|
||||
type: 'success',
|
||||
position: mdLess.value ? 'bottom-right' : 'top-right',
|
||||
center: true,
|
||||
confirmButtonText: '点按空白处关闭',
|
||||
});
|
||||
},
|
||||
PlayerLose() {
|
||||
win.value = false;
|
||||
ElNotification({
|
||||
ElMessageBox({
|
||||
title: '你输了!',
|
||||
message: '🤣👉🤡',
|
||||
type: 'error',
|
||||
position: mdLess.value ? 'bottom-right' : 'top-right',
|
||||
center: true,
|
||||
confirmButtonText: '点按空白处关闭',
|
||||
});
|
||||
},
|
||||
},
|
||||
@ -405,6 +451,11 @@ const { send } = useGobangSocket({
|
||||
return false;
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
ResetRoom() {
|
||||
restartState.value = RestartState.WAITING;
|
||||
},
|
||||
},
|
||||
finally: {
|
||||
PlayerJoin(error) {
|
||||
if (!error) {
|
||||
@ -415,9 +466,6 @@ const { send } = useGobangSocket({
|
||||
PlaceChessPiece() {
|
||||
placing.value = false;
|
||||
},
|
||||
ResetRoom() {
|
||||
restartState.value = RestartState.WAITING;
|
||||
},
|
||||
},
|
||||
});
|
||||
function sendResetRoom() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user