Documentação/Visão geral
Xadrez
Um tabuleiro de xadrez — os lances legais se acendem, a peça na mão diz por que uma casa é recusada, um peão pergunta em que vai se transformar, e um bot responde com arrastes de verdade.
Esta página foi traduzida por IA e pode conter imprecisões. Ler o original em inglês
Carregando a demo…
<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>O que observar
Square.vue: cada uma das 64 casas é um alvo de drop cujoaccepté uma função. Ela é consultada enquanto uma peça é arrastada, então a casa aceita só os lances legais daquela peça; um drop em qualquer outro lugar é recusado e a peça volta ao lugar. As casas legais se acendem a partir deisAccepting: nenhum estado do próprio jogo acompanha o arraste.Piece.vue: um draggable simples,disabledquando não é a vez do seu lado.keyboardMode: 'droppables'faz as setas pularem entre as casas que aceitam a peça: os lances legais dela, nada mais.snapCenterToCursormantém a peça centralizada sob o ponteiro, e um toque arrasta na hora (activation).canDragimpede que você mexa nas peças do bot e no tabuleiro enquanto o código joga, sem desativar nada: arrastes iniciados por código não têm evento de entrada, entãocanDragnão é consultado para eles.chess.tsegame.ts: as regras e o jogo, sem nenhum arrastar e soltar neles: lances legais com xeque, roque, en passant, promoção, empates por falta de material, a notação, desfazer. Um lance noonDropcria uma nova posição, e o overlay pousa na peça movida.
A peça na mão
PieceOverlay.vue é o componente de overlay das peças. Ele lê as props que todo overlay
recebe e diz o que um drop na casa sob ele faria: sobre uma casa recusada (over definido,
accepted falso) a peça fica vermelha e dá o motivo; sobre uma captura, ela mostra um alvo e o que
captura. Quando o drop está decidido (status: 'dropping'), a dica some e a peça se assenta.
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 desenha uma seta da casa de onde a peça veio até a casa sob ela, a partir do active e
do over de useDnD(). Os haptics do provider vibram um arraste por toque sobre uma casa legal
e quando um drop é recusado; os announcements em index.vue falam os mesmos motivos: "f7,
pinned".
Promoção: um drop que espera uma resposta
Sobre a última fileira, um peão na mão mostra a peça em que vai se transformar: o overOverlay da
própria casa (PromotionOverlay.vue) toma o lugar do da peça. Ao soltar, o onBeforeDrop da casa
retorna uma promise, e o overlay espera sobre a casa enquanto PromotionPicker.vue, um
elemento simples da página ao lado dela, pergunta qual peça. Uma peça responde 'accept', Escape
ou Cancel 'reject' (o peão volta ao lugar), e Keep dragging 'continue': o peão volta a seguir
o ponteiro, e o próximo clique o solta e pergunta de novo.
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),
});
O onBeforeDrop de um alvo de drop recebe o evento de arraste simples e só é consultado quando o
accept dele aceitou o arraste; o de uma lista ordenável recebe o SortableDropEvent da lista,
com o result do drop. O overOverlay do alvo é lido mais uma vez depois do onDrop, para o pouso:
ele verifica o peão como ele foi pego (active.data), não o jogo, onde a essa altura ele já é uma
dama.
O bot, os replays e o roque: arrastes executados por código
autoplay.ts joga lances com @vue-dnd-kit/actions: os do bot (bot.ts, um guloso), os do
Replay, e o passo da torre em todo roque, incluindo os seus. Cada um é um arraste de verdade:
pego, deslizado sobre o tabuleiro um quadro por vez, solto através do accept e do onDrop da
casa, anunciado e animado como os seus. Uma promoção roteirizada já traz a sua peça, então o
seletor não abre; Escape cancela um arraste roteirizado como qualquer outro.
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
O roque é jogado em dois movimentos, como à mão: o rei anda duas colunas, depois a torre dele é arrastada por cima dele, então a torre nunca pula.
Capturas, desfazer e um novo jogo
As peças capturadas vão para o Tray.vue do seu lado: uma lista ordenável usada só pelo
animateChanges (disabled, nada é arrastado ali). O enter dela é uma função: uma nova peça voa para
dentro a partir da casa em que foi capturada, e as outras abrem espaço.
makeSortableList(tray, {
items: () => props.pieces,
getKey: (piece) => `taken-${piece.id}`,
disabled: true,
animateChanges: { enter: flyIn, leave: false },
});
Undo e New game mudam a posição sem arraste, então glide.ts os anima à mão (FLIP): cada peça
voa de onde estava, em uma casa ou em uma bandeja, para onde está agora. Contra o bot, Undo desfaz
o seu lance junto com a resposta dele.
Leia mais: Decidindo um drop ·
O overlay ·
Arrastes programáticos ·
Mudanças nos dados: animateChanges ·
Acessibilidade