feat: 增加五子棋高亮功能

This commit is contained in:
Litrix 2024-12-20 13:03:07 +08:00
parent 84992372a4
commit 864b585c2e
3 changed files with 96 additions and 20 deletions

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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() {