chessground12
Version:
Extended lishuuro.org Chess UI
208 lines • 9.73 kB
JavaScript
import { dragNewPiece } from './drag';
import { setDropMode, cancelDropMode } from './drop';
import { predrop } from './predrop';
import { eventsClicking, eventsDragging, } from './types';
import { createEl, letterOf, opposite, pieceClasses, roleOf } from './util';
/**
* Logically maybe belongs to fen.ts, but put here to avoid merge conflicts from upsteam
* Analogous to fen.ts->read(), but for pocket part of FEN
* TODO: See todo in fen.ts->read() as well. Not sure if pocket parsing belongs there unless return
* type is extended to contain pocket state.
* */
export function readPockets(fen, pocketRoles) {
const placement = fen.split(' ')[0];
const bracketPos = placement.indexOf('[');
const placementPockets = bracketPos !== -1 ? placement.slice(bracketPos) : '';
const pockets = {};
const rWhite = pocketRoles('white');
const rBlack = pocketRoles('black');
if (rWhite) {
pockets.white = {};
for (const r of rWhite) {
pockets.white[roleOf(r)] = lc(placementPockets, r, 'upper');
}
}
if (rBlack) {
pockets.black = {};
for (const r of rBlack)
pockets.black[roleOf(r)] = lc(placementPockets, r, 'lower');
}
return pockets;
}
function lc(str, letter, letterCase) {
if (letterCase === 'upper')
letter = letter.toUpperCase();
else if (letterCase === 'lower')
letter = letter.toLowerCase();
let letterCount = 0;
for (let position = 0; position < str.length; position++)
if (str.charAt(position) === letter)
letterCount += 1;
return letterCount;
}
function renderPiece(el, state) {
const role = el.getAttribute('data-role');
const color = el.getAttribute('data-color');
el.setAttribute('data-nb', '' + (state.pockets[color][role] ?? 0));
const dropMode = state.dropmode;
const dropPiece = state.dropmode.piece;
const selectedSquare = dropMode.active && dropPiece?.role === role && dropPiece.color === color;
const preDropRole = state.predroppable.current?.role;
const activeColor = color === state.movable.color;
if (activeColor && preDropRole === role) {
el.classList.add('premove');
}
else {
el.classList.remove('premove');
}
if (selectedSquare) {
el.classList.add('selected-square');
}
else {
el.classList.remove('selected-square');
}
}
export function renderPocketsInitial(state, elements, pocketTop, pocketBottom) {
function pocketView(pocketEl, position) {
if (!state.pockets) {
return;
}
const color = position === 'top' ? opposite(state.orientation) : state.orientation;
const pocket = state.pockets[color];
if (!pocket)
return;
const roles = Object.keys(pocket); // contains the list of possible pieces/roles (i.e. for crazyhouse p-piece, n-piece, b-piece, r-piece, q-piece) in the order they will be displayed in the pocket
const pl = String(roles.length);
const files = String(state.dimensions.width);
const ranks = String(state.dimensions.height);
// const pocketEl = createEl('div','pocket ' + position + ' usable');
pocketEl.setAttribute('style', `--pocketLength: ${pl}; --files: ${files}; --ranks: ${ranks}`);
pocketEl.classList.add('pocket', position, 'usable');
roles.forEach((role) => {
const pieceName = pieceClasses({ role: role, color: color, promoted: false }, state.orientation);
const p = createEl('piece', pieceName);
// todo: next 2 attributes already exist as classes, but need inverse function for util.ts->pieceClasses()
p.setAttribute('data-color', color);
p.setAttribute('data-role', role);
renderPiece(p, state);
// TODO: i wonder if events.ts->bindBoard() or something similar is a better place similarly to main board?
// todo: in spectators mode movable.color is never set (except in goPly to undefined). Simultaneously
// state.ts->default is "both" and here as well. Effect is that dragging and clicking is disabled, which is
// great, but feels more like an accidental side effect than intention (effectively 'both' means 'none').
// Maybe state.movable.color should be set to undef in roundCtrl ALWAYS when in spectotor mode instead of
// left unset (and with its default). Then below we can handle 'both' properly for sake of clarity
eventsDragging.forEach(name => p.addEventListener(name, (e) => {
if (state.movable.free || state.movable.color === color)
drag(state, e);
}));
eventsClicking.forEach(name => p.addEventListener(name, (e) => {
// movable.free is synonymous with editor mode, and right now click-drop not supported for pocket pieces
if ( /*state.movable.free ||*/state.movable.color === color)
click(state, e);
}));
pocketEl.appendChild(p);
});
}
//
if (pocketTop) {
pocketTop.innerHTML = '';
elements.pocketTop = pocketTop;
pocketView(elements.pocketTop, 'top');
}
if (pocketBottom) {
pocketBottom.innerHTML = '';
elements.pocketBottom = pocketBottom;
pocketView(elements.pocketBottom, 'bottom');
}
}
export function click(state, e) {
if (e.button !== undefined && e.button !== 0)
return; // only touch or left click
const el = e.target, role = el.getAttribute('data-role'), color = el.getAttribute('data-color'), number = el.getAttribute('data-nb');
if (number === '0')
return;
const dropMode = state.dropmode;
const dropPiece = state.dropmode.piece;
const canceledDropMode = el.getAttribute('canceledDropMode');
el.setAttribute('canceledDropMode', '');
if ((!dropMode.active || dropPiece?.role !== role) && canceledDropMode !== 'true') {
setDropMode(state, { color, role });
}
else {
cancelDropMode(state);
}
e.stopPropagation();
e.preventDefault();
}
export function drag(state, e) {
if (e.button !== undefined && e.button !== 0)
return; // only touch or left click
const el = e.target, role = el.getAttribute('data-role'), color = el.getAttribute('data-color'), n = Number(el.getAttribute('data-nb'));
el.setAttribute('canceledDropMode', ''); // We want to know if later in this method cancelDropMode was called,
// so right after mouse button is up and dragging is over if a click event is triggered
// (which annoyingly does happen if mouse is still over same pocket element)
// then we know not to call setDropMode selecting the piece we have just unselected.
// Alternatively we might not cancelDropMode on drag of same piece but then after drag is over
// the selected piece remains selected which is not how board pieces behave and more importantly is counter intuitive
if (n === 0)
return;
state.events.pocketSelect({ role, color });
// always cancel drop mode if it is active
if (state.dropmode.active) {
cancelDropMode(state);
if (state.dropmode.piece?.role === role) {
// we mark it with this only if we are cancelling the same piece we "drag"
el.setAttribute('canceledDropMode', 'true');
}
}
if (state.movable.dests) {
const dropDests = new Map([[role, state.movable.dests.get((letterOf(role, true) + '@'))]]);
state.dropmode.dropDests = dropDests;
}
e.stopPropagation();
e.preventDefault();
dragNewPiece(state, { color, role }, e);
}
/**
* updates each piece element attributes based on state
* */
export function renderPockets(state) {
function renderPocket(pocketEl) {
let el = pocketEl?.firstChild;
while (el) {
renderPiece(el, state);
el = el.nextSibling;
}
}
renderPocket(state.dom.elements.pocketBottom);
renderPocket(state.dom.elements.pocketTop);
}
function pocket2str(pocket) {
const letters = [];
for (const role in pocket) {
letters.push(letterOf(role, true).repeat(pocket[role] || 0));
}
return letters.join('');
}
export function pockets2str(pockets) {
return '[' + pocket2str(pockets['white']) + pocket2str(pockets['black']).toLowerCase() + ']';
}
/**
* todo: Ideally this whole method should disappear. It is legacy solution from when pocket was outside CG for the case
* when dragging started while another premove/predrop was set. After that premove/drop executes and turn is again
* opp's, we are again in predrop state and need to set those again
* Maybe predroppable should be initialized in board.ts->setSelected() and implemented similarly as premove dests
* Could happen together with further refactoring to make pocket more of a first class citizen and enable other
* stuff like highlighting last move etc. maybe.
* Even if not made part of the setSelected infrastructure, i am pretty sure this is not needed if we track and
* check better what is dragged/clicked and with proper combination of if-s in render.ts and clean-up-to-undef logic
* */
export function setPredropDests(state) {
const piece = state.draggable.current?.piece;
if (piece && piece.color !== state.turnColor) {
//it is opponents turn, but we are dragging a pocket piece at the same time
const dropDests = predrop(state.pieces, piece, state.geometry, state.variant);
state.predroppable.dropDests = dropDests;
}
}
//# sourceMappingURL=pocket.js.map