mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-06 05:23:56 -07:00
gameboard fixes
This commit is contained in:
parent
88489ea2eb
commit
f534cd79b7
86 changed files with 4249 additions and 2145 deletions
113
webclient/src/components/Game/Battlefield/gridMath.ts
Normal file
113
webclient/src/components/Game/Battlefield/gridMath.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Port of desktop Cockatrice's table-zone grid math (cockatrice/src/game/zones/table_zone.cpp).
|
||||
// The server wire protocol encodes table-zone card positions as integers:
|
||||
// x = stackColumn * MAX_SUBPOS + subPosition (subPosition ∈ {0, 1, 2})
|
||||
// y ∈ {0, 1, 2} (pre-inversion row index)
|
||||
// Desktop packs up to MAX_SUBPOS cards into a single stack column via small
|
||||
// horizontal offsets; once full, drops overflow to the next stack. We replicate
|
||||
// those semantics exactly so cards rendered side-by-side on desktop land on the
|
||||
// same grid points in the webclient.
|
||||
|
||||
import { Data } from '@app/types';
|
||||
|
||||
// Nominal pixel constants at the reference zoom (146x204 card). Cards scale
|
||||
// with lane height in CSS via `aspect-ratio: 146/204`; at runtime the drop
|
||||
// handler derives the effective card width from the row's measured height
|
||||
// (effectiveCardWidth = laneHeight × 146/204) and passes it to mapToGridX.
|
||||
// PADDING_X and MARGIN_LEFT match fixed CSS gap/padding so they don't scale.
|
||||
export const CARD_WIDTH_PX = 146; // desktop CardDimensions::WIDTH = 72
|
||||
export const CARD_HEIGHT_PX = 204;
|
||||
export const STACKED_CARD_OFFSET_X_PX = 49; // desktop WIDTH/3 = 24 → 146/3 ≈ 49 preserves ratio
|
||||
export const PADDING_X_PX = 16; // desktop PADDING_X = 35; smaller so rows fit more stacks at browser widths
|
||||
export const MARGIN_LEFT_PX = 8; // matches .battlefield__row horizontal padding
|
||||
|
||||
export const ROW_COUNT = 3;
|
||||
export const MAX_SUBPOS = 3; // cards per stack column before overflow
|
||||
|
||||
export function clampRow(y: number): number {
|
||||
if (y < 0) return 0;
|
||||
if (y >= ROW_COUNT) return ROW_COUNT - 1;
|
||||
return y;
|
||||
}
|
||||
|
||||
// Width a stack column occupies given how many cards it currently holds.
|
||||
// A 1-card stack is cardWidth wide; each additional sub-positioned card
|
||||
// extends the stack by offsetX (up to MAX_SUBPOS - 1 extra). Defaults to the
|
||||
// nominal 146/49 constants; callers that need to account for lane scaling
|
||||
// pass the effective width and offset derived from the row's measured height.
|
||||
export function stackColumnWidth(
|
||||
cardCount: number,
|
||||
cardWidth: number = CARD_WIDTH_PX,
|
||||
offsetX: number = STACKED_CARD_OFFSET_X_PX,
|
||||
): number {
|
||||
if (cardCount <= 1) return cardWidth;
|
||||
const extras = Math.min(cardCount - 1, MAX_SUBPOS - 1);
|
||||
return cardWidth + extras * offsetX;
|
||||
}
|
||||
|
||||
// Count cards per stack column in a row. `cards` must be the row's cards
|
||||
// (after filtering attached children, which do not occupy their own slot).
|
||||
export function stackCountsForRow(cards: Data.ServerInfo_Card[]): Map<number, number> {
|
||||
const counts = new Map<number, number>();
|
||||
for (const card of cards) {
|
||||
const col = Math.floor((card.x ?? 0) / MAX_SUBPOS);
|
||||
counts.set(col, (counts.get(col) ?? 0) + 1);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Port of table_zone.cpp:mapToGrid's x-axis walk (lines 336-363).
|
||||
// `pointerXInRow` is the drop pointer's x-coordinate relative to the row's
|
||||
// left content edge (i.e. already offset past any CSS padding). We mirror
|
||||
// desktop's "+ paddingX/2" rounding to snap to the nearest stack.
|
||||
export function mapToGridX(
|
||||
pointerXInRow: number,
|
||||
stackCounts: Map<number, number>,
|
||||
cardWidth: number = CARD_WIDTH_PX,
|
||||
offsetX: number = STACKED_CARD_OFFSET_X_PX,
|
||||
paddingX: number = PADDING_X_PX,
|
||||
): number {
|
||||
// Desktop shifts by paddingX/2 so a pointer near a stack boundary rounds
|
||||
// to the nearer stack. MARGIN_LEFT is the caller's responsibility.
|
||||
const x = pointerXInRow + paddingX / 2;
|
||||
|
||||
let xStack = 0;
|
||||
let xNextStack = 0;
|
||||
let nextStackCol = 0;
|
||||
while (xNextStack <= x) {
|
||||
xStack = xNextStack;
|
||||
const w = stackColumnWidth(stackCounts.get(nextStackCol) ?? 0, cardWidth, offsetX);
|
||||
xNextStack += w + paddingX;
|
||||
nextStackCol++;
|
||||
// Safety: the loop always terminates because x is finite and each iter
|
||||
// grows xNextStack by at least cardWidth + paddingX > 0.
|
||||
}
|
||||
const stackCol = Math.max(nextStackCol - 1, 0);
|
||||
const xDiff = Math.max(0, x - xStack);
|
||||
const subPos = Math.min(Math.floor(xDiff / offsetX), MAX_SUBPOS - 1);
|
||||
return stackCol * MAX_SUBPOS + subPos;
|
||||
}
|
||||
|
||||
// Port of table_zone.cpp:closestGridPoint (lines 366-375). Rounds x down to
|
||||
// its stack base, then bumps by +1 / +2 over occupied sub-slots. Returns null
|
||||
// when all MAX_SUBPOS slots in the stack are taken — desktop handles this by
|
||||
// dropping the card out of the drag list (card_drag_item.cpp:115), resulting
|
||||
// in a silent reject. Callers should skip dispatching moveCard on null.
|
||||
export function closestGridPoint(
|
||||
gridX: number,
|
||||
occupiedXs: ReadonlySet<number>,
|
||||
): number | null {
|
||||
const base = Math.floor(gridX / MAX_SUBPOS) * MAX_SUBPOS;
|
||||
for (let i = 0; i < MAX_SUBPOS; i++) {
|
||||
if (!occupiedXs.has(base + i)) return base + i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Port of table_zone.cpp:53-59. Inversion flips y at the grid-math boundary
|
||||
// only — rendering already reverses rowOrder in useBattlefield, so here we
|
||||
// invert when sending a move to a mirrored board (or when the user has the
|
||||
// invertVerticalCoordinate setting on and the board isn't already mirrored).
|
||||
export function applyInvertY(gridY: number, isInverted: boolean): number {
|
||||
const clamped = clampRow(gridY);
|
||||
return isInverted ? ROW_COUNT - 1 - clamped : clamped;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue