Show placed cards before drawing
This commit is contained in:
+12
-4
@@ -9,6 +9,7 @@ import {
|
||||
encodeAction,
|
||||
legalActionMask,
|
||||
newGame,
|
||||
previewPlacement,
|
||||
step,
|
||||
} from "./game/engine";
|
||||
import { DISCARD, DRAW_DECK, N_CARDS, PLAY, type GameState, type PlaceType } from "./game/types";
|
||||
@@ -27,10 +28,18 @@ function App() {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const generation = useRef(0);
|
||||
|
||||
const scores = useMemo(() => boardScore(state), [state]);
|
||||
const humanHand = useMemo(() => currentHandSorted(state, 0), [state]);
|
||||
const opponentHand = useMemo(() => currentHandSorted(state, 1), [state]);
|
||||
const legal = useMemo(() => legalActionMask(state), [state]);
|
||||
const selectedCard = selection.handSlot === null ? null : humanHand[selection.handSlot];
|
||||
const selectedColor = selectedCard === null ? null : cardColor(selectedCard);
|
||||
const displayState = useMemo(
|
||||
() => selection.handSlot === null || selection.placeType === null
|
||||
? state
|
||||
: previewPlacement(state, selection.handSlot, selection.placeType),
|
||||
[selection, state],
|
||||
);
|
||||
const scores = useMemo(() => boardScore(displayState), [displayState]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -106,8 +115,6 @@ function App() {
|
||||
setSelection(EMPTY_SELECTION);
|
||||
}
|
||||
|
||||
const selectedCard = selection.handSlot === null ? null : humanHand[selection.handSlot];
|
||||
const selectedColor = selectedCard === null ? null : cardColor(selectedCard);
|
||||
const canPlaySelected = selection.handSlot !== null && Array.from(
|
||||
{ length: 6 },
|
||||
(_, draw) => legal[encodeAction(selection.handSlot!, PLAY, draw)],
|
||||
@@ -142,7 +149,7 @@ function App() {
|
||||
|
||||
<div className="table-center">
|
||||
<Board
|
||||
state={state}
|
||||
state={displayState}
|
||||
selectedColor={selectedColor}
|
||||
selectedPlace={selection.placeType}
|
||||
canPlay={canPlaySelected}
|
||||
@@ -168,6 +175,7 @@ function App() {
|
||||
|
||||
<section className="human-hand" aria-label="Your hand">
|
||||
{humanHand.map((card, index) => (
|
||||
selection.placeType !== null && selection.handSlot === index ? null :
|
||||
<Card
|
||||
card={card}
|
||||
key={card}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { boardScore, currentHandSorted, encodeAction, legalActionMask, resetFromOrder, step } from "./engine";
|
||||
import { DISCARD, DRAW_DECK, N_CARDS } from "./types";
|
||||
import {
|
||||
boardScore,
|
||||
cardsOnBoard,
|
||||
currentHandSorted,
|
||||
encodeAction,
|
||||
legalActionMask,
|
||||
previewPlacement,
|
||||
resetFromOrder,
|
||||
step,
|
||||
} from "./engine";
|
||||
import { DISCARD, DRAW_DECK, N_CARDS, PLAY } from "./types";
|
||||
import { cardColor } from "./cards";
|
||||
import { observation } from "./observation";
|
||||
import fixture from "./parity-fixture.json";
|
||||
@@ -24,6 +33,19 @@ describe("JAX-compatible game engine", () => {
|
||||
expect(mask[encodeAction(0, DISCARD, DRAW_DECK)]).toBe(true);
|
||||
});
|
||||
|
||||
test("previews a placed card without ending the turn", () => {
|
||||
const state = resetFromOrder(orderedDeck);
|
||||
const card = currentHandSorted(state)[3];
|
||||
const preview = previewPlacement(state, 3, PLAY);
|
||||
|
||||
expect(currentHandSorted(preview, 0)).not.toContain(card);
|
||||
expect(cardsOnBoard(preview, 0, cardColor(card))).toContain(card);
|
||||
expect(preview.toMove).toBe(0);
|
||||
expect(preview.drawPtr).toBe(state.drawPtr);
|
||||
expect(preview.stepCount).toBe(state.stepCount);
|
||||
expect(currentHandSorted(state, 0)).toContain(card);
|
||||
});
|
||||
|
||||
test("deck-only games terminate after exactly 44 plies", () => {
|
||||
let state = resetFromOrder(orderedDeck);
|
||||
let plies = 0;
|
||||
|
||||
+28
-11
@@ -114,18 +114,8 @@ export function legalActionMask(state: GameState): boolean[] {
|
||||
return mask;
|
||||
}
|
||||
|
||||
export function step(state: GameState, action: number): GameState {
|
||||
const mask = legalActionMask(state);
|
||||
if (!Number.isInteger(action) || action < 0 || action >= N_ACTIONS || !mask[action]) {
|
||||
return state;
|
||||
}
|
||||
const next = cloneState(state);
|
||||
const { handSlot, placeType, drawSource } = decodeAction(action);
|
||||
const player = state.toMove;
|
||||
const hand = currentHandSorted(state, player);
|
||||
const card = hand[handSlot];
|
||||
function placeCard(next: GameState, player: number, card: number, placeType: PlaceType): void {
|
||||
const color = cardColor(card);
|
||||
|
||||
next.handPublic[card] = false;
|
||||
if (placeType === PLAY) {
|
||||
next.cardLoc[card] = LOC_P0_BOARD + player;
|
||||
@@ -136,6 +126,33 @@ export function step(state: GameState, action: number): GameState {
|
||||
next.cardLoc[card] = LOC_DISCARD;
|
||||
next.piles[color].push(card);
|
||||
}
|
||||
}
|
||||
|
||||
export function previewPlacement(state: GameState, handSlot: number, placeType: PlaceType): GameState {
|
||||
const mask = legalActionMask(state);
|
||||
const hasLegalDraw = Array.from(
|
||||
{ length: N_COLORS + 1 },
|
||||
(_, drawSource) => mask[encodeAction(handSlot, placeType, drawSource)],
|
||||
).some(Boolean);
|
||||
if (!hasLegalDraw) return state;
|
||||
|
||||
const next = cloneState(state);
|
||||
const card = currentHandSorted(state)[handSlot];
|
||||
placeCard(next, state.toMove, card, placeType);
|
||||
return next;
|
||||
}
|
||||
|
||||
export function step(state: GameState, action: number): GameState {
|
||||
const mask = legalActionMask(state);
|
||||
if (!Number.isInteger(action) || action < 0 || action >= N_ACTIONS || !mask[action]) {
|
||||
return state;
|
||||
}
|
||||
const next = cloneState(state);
|
||||
const { handSlot, placeType, drawSource } = decodeAction(action);
|
||||
const player = state.toMove;
|
||||
const hand = currentHandSorted(state, player);
|
||||
const card = hand[handSlot];
|
||||
placeCard(next, player, card, placeType);
|
||||
|
||||
let drawnCard: number;
|
||||
if (drawSource === DRAW_DECK) {
|
||||
|
||||
Reference in New Issue
Block a user