Dokumentation/Übersicht
Schach
Ein Schachbrett – gültige Züge leuchten auf, die Figur in der Hand sagt, warum ein Feld abgelehnt wird, ein Bauer fragt, was er wird, und ein Bot spielt mit echten Drags zurück.
Diese Seite wurde von KI übersetzt und kann Ungenauigkeiten enthalten. Englisches Original lesen
Demo wird geladen…
<script setup lang="ts">
import { provide } from 'vue';
import { DnDProvider, type Announcements } from '@vue-dnd-kit/core';
import Board from './Board.vue';
import { NAMES, type Piece } from './chess';
import { GAME, createGame } from './game';
// Provided above the provider: the overlays it renders read the game too.
const game = createGame();
provide(GAME, game);
// Screen readers hear pieces and squares, not ids.
const name = (data: unknown) => {
const piece = data as Piece;
return `${piece.color === 'w' ? 'white' : 'black'} ${NAMES[piece.kind]}`;
};
const from = (data: unknown) => (data as Piece).square;
const stays = (data: unknown) => `The ${name(data)} stays on ${from(data)}.`;
const announcements: Partial<Announcements> = {
onDragStart: ({ active }) => {
const keys = active.sensor === 'keyboard' ? ' Arrow keys go through its moves.' : '';
return `Picked up the ${name(active.data)} on ${from(active.data)}.${keys}`;
},
// Over an illegal square, the reason the overlay shows: "f7, pinned".
onDragOver: ({ active, over }) =>
over ? `${over.id}${over.accepted ? '' : `, ${game.whyNot(active.id, String(over.id)).toLowerCase()}`}` : '',
onDragEnd: ({ active, over, dropped }) => {
if (!dropped || !over) return stays(active.data);
const turn = game.last.value;
const takes = turn?.captured ? `, takes the ${name(turn.captured)}` : '';
const becomes = turn?.promotion ? ` and becomes a ${NAMES[turn.promotion]}` : '';
return `The ${name(active.data)} moves to ${over.id}${takes}${becomes}.`;
},
onDragCancel: ({ active }) => stays(active.data),
onDragResume: ({ active }) => `Still holding the ${name(active.data)}.`,
};
</script>
<template>
<!-- Touch drags vibrate on a legal square, and when a drop is refused. -->
<DnDProvider :announcements="announcements" :haptics="{ enter: true, refuse: true }">
<Board />
</DnDProvider>
</template>Worauf du achten solltest
Square.vue: Jedes der 64 Felder ist ein Drop-Ziel, dessenaccepteine Funktion ist. Sie wird gefragt, während eine Figur gezogen wird, also nimmt das Feld nur die gültigen Züge dieser Figur an; ein Drop anderswo wird abgelehnt, und die Figur fliegt zurück. Die gültigen Felder leuchten überisAcceptingauf: Kein eigener Zustand des Spiels verfolgt den Drag.Piece.vue: ein einfaches ziehbares Element,disabled, wenn seine Seite nicht am Zug ist.keyboardMode: 'droppables'lässt die Pfeiltasten zwischen den Feldern springen, die die Figur annehmen: ihren gültigen Zügen, sonst nichts.snapCenterToCursorhält die Figur zentriert unter dem Zeiger, und ein Touch zieht sofort (activation).canDraghält deine Hände von den Figuren des Bots und vom Brett fern, solange Code spielt, ohne etwas abzuschalten: Von Code gestartete Drags haben kein Eingabe-Event, also wirdcanDragfür sie nicht gefragt.chess.tsundgame.ts: die Regeln und die Partie, ganz ohne Drag and Drop: gültige Züge mit Schach, Rochade, En passant, Umwandlung, Remis mangels Material, die Notation, Undo. Ein Zug inonDroperzeugt eine neue Stellung, und das Overlay landet auf der gezogenen Figur.
Die Figur in der Hand
PieceOverlay.vue ist die overlay-Komponente der Figuren. Sie liest die Props, die jedes
Overlay bekommt, und sagt, was ein Drop auf dem Feld darunter tun würde: Über einem abgelehnten
Feld (over gesetzt, accepted false) wird die Figur rot und nennt den Grund, über einem
Schlagzug zeigt sie eine Zielscheibe und was sie schlägt. Sobald der Drop entschieden ist
(status: 'dropping'), verschwindet der Hinweis, und die Figur setzt sich.
const props = defineProps<{
active: Active;
over: Over | null;
accepted: boolean;
status: DnDStatus;
}>();
const hint = computed(() => {
const square = props.over ? String(props.over.id) : null;
if (props.status !== 'dragging' || !square || square === piece.value.square) return null;
if (props.accepted) {
const taken = game.takes(piece.value.id, square);
return taken ? { type: 'capture', text: `Takes ${NAMES[taken.kind]}` } : null;
}
// "Pinned", "King in check", "Not a legal move"
return { type: 'refused', text: game.whyNot(piece.value.id, square) };
});
Board.vue zeichnet einen Pfeil von dem Feld, von dem die Figur kam, zu dem Feld darunter, aus
active und over von useDnD(). Die haptics des Providers lassen einen Touch-Drag auf einem
gültigen Feld vibrieren und wenn ein Drop abgelehnt wird; die announcements in index.vue
sprechen dieselben Gründe: „f7, pinned“.
Umwandlung: ein Drop, der auf eine Antwort wartet
Über der letzten Reihe zeigt ein Bauer in der Hand die Figur, die er wird: Das eigene overOverlay
des Feldes (PromotionOverlay.vue) tritt an die Stelle des Overlays der Figur. Beim Loslassen
gibt das onBeforeDrop des Feldes ein Promise zurück, und das Overlay wartet auf dem Feld,
während PromotionPicker.vue, ein einfaches Element der Seite daneben, fragt, welche Figur.
Eine Figur antwortet 'accept', Escape oder Cancel 'reject' (der Bauer fliegt zurück), und Keep
dragging 'continue': Der Bauer folgt wieder dem Zeiger, und der nächste Klick legt ihn ab und
fragt erneut.
makeDroppable(el, {
id: props.square,
accept: ({ active }) => game.canMove(active.id, props.square),
// Shown instead of the piece's overlay while a pawn is over the last rank.
overOverlay: computed(() =>
held.value?.kind === 'P' && isLastRank(props.square) ? PromotionOverlay : undefined,
),
// A promise: the drop waits on the square until the picker answers.
onBeforeDrop: ({ active }) =>
game.promotes(active.id, props.square) ? game.askPromotion(props.square) : 'accept',
onDrop: ({ active }) => game.move(String(active.id), props.square),
});
Das onBeforeDrop eines Drop-Ziels bekommt das einfache Drag-Event und wird nur gefragt, wenn
sein accept den Drag angenommen hat; das einer sortierbaren Liste bekommt das
SortableDropEvent der Liste, mit dem result des Drops. Das overOverlay des Ziels wird nach
onDrop noch einmal gelesen, für die Landung: Es prüft den Bauern, wie er aufgenommen wurde
(active.data), nicht die Partie, in der er dann schon eine Dame ist.
Der Bot, Wiederholungen und die Rochade: Drags, die Code spielt
autoplay.ts spielt Züge mit @vue-dnd-kit/actions: die des Bots (bot.ts, ein gieriger),
die von Replay und den Turmschritt jeder Rochade, auch deiner. Jeder ist ein echter Drag:
aufgenommen, Frame für Frame über das Brett geführt, über accept und onDrop des Feldes
abgelegt, angesagt und animiert wie deine. Eine geskriptete Umwandlung bringt ihre Figur mit, der
Auswahldialog öffnet sich also nicht; Escape bricht einen geskripteten Drag ab wie jeden anderen.
const { startDrag } = useDnDActions();
const drag = startDrag(id); // from the piece's center
for (let frame = 1; frame <= FRAMES; frame++) {
const t = easeOut(frame / FRAMES);
const point = { x: start.x + (end.x - start.x) * t, y: start.y + (end.y - start.y) * t };
if (!(await drag.moveTo(point))) return false; // cancelled meanwhile (Escape)
}
return drag.drop(); // true once it has landed, false if the square refused it
Die Rochade wird in zwei Zügen gespielt, wie von Hand: Der König geht zwei Linien weit, dann wird sein Turm über ihn gezogen, der Turm springt also nie.
Schlagen, Undo und eine neue Partie
Geschlagene Figuren kommen in das Tray.vue ihrer Seite: eine sortierbare Liste, die nur für
ihr animateChanges verwendet wird (disabled, dort wird nichts gezogen). Ihr enter ist eine
Funktion: Eine neue Figur fliegt von dem Feld herein, auf dem sie geschlagen wurde, und die
anderen machen Platz.
makeSortableList(tray, {
items: () => props.pieces,
getKey: (piece) => `taken-${piece.id}`,
disabled: true,
animateChanges: { enter: flyIn, leave: false },
});
Undo und New game ändern die Stellung ohne Drag, also animiert glide.ts sie von Hand
(FLIP): Jede Figur fliegt von dort, wo sie war, auf einem Feld oder in einer Ablage, dorthin, wo
sie jetzt ist. Gegen den Bot nimmt Undo deinen Zug zusammen mit seiner Antwort zurück.
Mehr dazu: Über einen Drop entscheiden ·
Das Overlay ·
Programmatisches Ziehen ·
Datenänderungen: animateChanges ·
Barrierefreiheit