@vnejs/plugins.views.screens.checkers 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.js +25 -12
  3. package/dist/modules/board.d.ts +10 -0
  4. package/dist/modules/board.js +32 -0
  5. package/dist/modules/bridge.d.ts +8 -0
  6. package/dist/modules/bridge.js +10 -0
  7. package/dist/modules/click.d.ts +8 -0
  8. package/dist/modules/click.js +14 -0
  9. package/dist/modules/controller.accept.d.ts +7 -25
  10. package/dist/modules/controller.accept.js +12 -159
  11. package/dist/modules/controller.d.ts +4 -6
  12. package/dist/modules/controller.grid.d.ts +11 -11
  13. package/dist/modules/controller.grid.js +21 -25
  14. package/dist/modules/controller.js +8 -26
  15. package/dist/modules/player.interact.d.ts +9 -0
  16. package/dist/modules/player.interact.js +33 -0
  17. package/dist/modules/player.select.d.ts +9 -0
  18. package/dist/modules/player.select.js +29 -0
  19. package/dist/modules/status.d.ts +18 -0
  20. package/dist/modules/status.js +24 -0
  21. package/dist/modules/view.d.ts +5 -5
  22. package/dist/modules/view.js +4 -4
  23. package/dist/types.d.ts +9 -5
  24. package/dist/utils/checkers.d.ts +20 -9
  25. package/dist/utils/checkers.js +28 -21
  26. package/dist/view/index.d.ts +2 -2
  27. package/dist/view/index.js +12 -12
  28. package/package.json +12 -2
  29. package/src/index.ts +25 -12
  30. package/src/modules/board.ts +47 -0
  31. package/src/modules/bridge.ts +20 -0
  32. package/src/modules/click.ts +28 -0
  33. package/src/modules/controller.accept.ts +20 -189
  34. package/src/modules/controller.grid.ts +23 -26
  35. package/src/modules/controller.ts +16 -36
  36. package/src/modules/player.interact.ts +48 -0
  37. package/src/modules/player.select.ts +42 -0
  38. package/src/modules/status.ts +41 -0
  39. package/src/modules/view.ts +12 -6
  40. package/src/tests/player.interact.test.ts +56 -0
  41. package/src/tests/setup.ts +24 -10
  42. package/src/types.ts +23 -6
  43. package/src/utils/checkers.ts +50 -31
  44. package/src/view/index.tsx +22 -23
  45. package/dist/modules/checkers.d.ts +0 -9
  46. package/dist/modules/checkers.js +0 -37
  47. package/dist/utils/ai.d.ts +0 -4
  48. package/dist/utils/ai.js +0 -27
  49. package/dist/utils/board.d.ts +0 -32
  50. package/dist/utils/board.js +0 -199
  51. package/src/modules/checkers.ts +0 -52
  52. package/src/tests/ai.test.ts +0 -61
  53. package/src/tests/board.test.ts +0 -43
  54. package/src/tests/checkers.test.ts +0 -58
  55. package/src/utils/ai.ts +0 -31
  56. package/src/utils/board.ts +0 -252
@@ -1,10 +1,10 @@
1
1
  import { ModuleView } from "@vnejs/module.components";
2
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
3
- import type { CheckersPluginState } from "../utils/checkers.js";
4
- export declare class CheckersView extends ModuleView<CheckersPluginEvents, CheckersPluginConstants, CheckersPluginSettings, CheckersPluginParams, CheckersPluginState> {
2
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
3
+ import type { CheckersViewState } from "../utils/checkers.js";
4
+ export declare class CheckersViewView extends ModuleView<CheckersViewPluginEvents, CheckersViewPluginConstants, CheckersViewPluginSettings, CheckersViewPluginParams, CheckersViewState> {
5
5
  name: string;
6
- EVENT_UPDATE_VIEW: "vne:checkers:update";
6
+ EVENT_UPDATE_VIEW: "vne:checkers_view:update";
7
7
  LOC_LABEL: null;
8
8
  TRANSITION: number;
9
- renderFunc: import("@vnejs/module.components").ViewRenderFunc<CheckersPluginState>;
9
+ renderFunc: import("@vnejs/module.components").ViewRenderFunc<CheckersViewState>;
10
10
  }
@@ -1,9 +1,9 @@
1
1
  import { ModuleView } from "@vnejs/module.components";
2
2
  import { render } from "../view/index.js";
3
- export class CheckersView extends ModuleView {
4
- name = "checkers.view";
5
- EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS.UPDATE;
3
+ export class CheckersViewView extends ModuleView {
4
+ name = "checkers_view.view";
5
+ EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS_VIEW.UPDATE;
6
6
  LOC_LABEL = null;
7
- TRANSITION = this.PARAMS.CHECKERS.TRANSITION;
7
+ TRANSITION = this.PARAMS.CHECKERS_VIEW.TRANSITION;
8
8
  renderFunc = render;
9
9
  }
package/dist/types.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import type { ModuleComponentsConstants, ModuleComponentsEvents, ModuleComponentsParams, ModuleComponentsSettings } from "@vnejs/module.components";
2
+ import type { Constants as AiConstants, Params as AiParams, PluginName as AiPluginName, SubscribeEvents as AiSubscribeEvents } from "@vnejs/contracts.ai";
2
3
  import type { Constants as ControlsConstants, PluginName as ControlsPluginName } from "@vnejs/contracts.controls";
3
4
  import type { Constants as ScenarioConstants, PluginName as ScenarioPluginName, SubscribeEvents as ScenarioSubscribeEvents } from "@vnejs/contracts.core.scenario";
4
5
  import type { PluginName as LogsPluginName, SubscribeEvents as LogsSubscribeEvents } from "@vnejs/contracts.core.logs";
5
- import type { Constants, Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.screens.checkers";
6
- export type CheckersPluginEvents = ModuleComponentsEvents & Record<PluginName, SubscribeEvents> & Record<ScenarioPluginName, ScenarioSubscribeEvents> & Record<LogsPluginName, LogsSubscribeEvents>;
7
- export type CheckersPluginConstants = ModuleComponentsConstants & Record<PluginName, Constants> & Record<ControlsPluginName, ControlsConstants> & Record<ScenarioPluginName, ScenarioConstants>;
8
- export type CheckersPluginSettings = ModuleComponentsSettings;
9
- export type CheckersPluginParams = ModuleComponentsParams & Record<PluginName, Params>;
6
+ import type { PluginName as GridPluginName, SubscribeEvents as GridSubscribeEvents } from "@vnejs/contracts.common.grid";
7
+ import type { PluginName as TurnsPluginName, SubscribeEvents as TurnsSubscribeEvents } from "@vnejs/contracts.common.turn";
8
+ import type { Constants as CheckersConstants, Params as CheckersParams, PluginName as CheckersPluginName, SubscribeEvents as CheckersSubscribeEvents } from "@vnejs/contracts.game.checkers";
9
+ import type { Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.screens.checkers";
10
+ export type CheckersViewPluginEvents = ModuleComponentsEvents & Record<PluginName, SubscribeEvents> & Record<CheckersPluginName, CheckersSubscribeEvents> & Record<GridPluginName, GridSubscribeEvents> & Record<TurnsPluginName, TurnsSubscribeEvents> & Record<AiPluginName, AiSubscribeEvents> & Record<ScenarioPluginName, ScenarioSubscribeEvents> & Record<LogsPluginName, LogsSubscribeEvents>;
11
+ export type CheckersViewPluginConstants = ModuleComponentsConstants & Record<CheckersPluginName, CheckersConstants> & Record<AiPluginName, AiConstants> & Record<ControlsPluginName, ControlsConstants> & Record<ScenarioPluginName, ScenarioConstants>;
12
+ export type CheckersViewPluginSettings = ModuleComponentsSettings;
13
+ export type CheckersViewPluginParams = ModuleComponentsParams & Record<PluginName, Params> & Record<CheckersPluginName, CheckersParams> & Record<AiPluginName, AiParams>;
@@ -1,4 +1,15 @@
1
- import type { CheckersBoard, CheckersColor, CheckersMove, CheckersPiece, CheckersPos } from "./board.js";
1
+ import type { GridChangedPayload } from "@vnejs/contracts.common.grid";
2
+ import type { CheckersMovePayload, CheckersPosPayload } from "@vnejs/contracts.game.checkers";
3
+ export declare const BOARD_SIZE = 8;
4
+ export type CheckersColor = "white" | "black";
5
+ export type CheckersPiece = {
6
+ id: string;
7
+ color: CheckersColor;
8
+ king: boolean;
9
+ };
10
+ export type CheckersPos = CheckersPosPayload;
11
+ export type CheckersMove = CheckersMovePayload;
12
+ export type CheckersBoard = (CheckersPiece | null)[][];
2
13
  export type CheckersGridItem = {
3
14
  row: number;
4
15
  column: number;
@@ -8,7 +19,7 @@ export type CheckersGridItem = {
8
19
  isLegalTarget?: boolean;
9
20
  isFocus?: boolean;
10
21
  };
11
- export type CheckersPluginState = {
22
+ export type CheckersViewState = {
12
23
  isShow: boolean;
13
24
  isForce: boolean;
14
25
  board?: CheckersBoard;
@@ -22,16 +33,13 @@ export type CheckersPluginState = {
22
33
  gridItems?: CheckersGridItem[];
23
34
  gridIndex?: number;
24
35
  gridsCount?: number;
25
- ai?: {
26
- white: string;
27
- black: string;
28
- };
29
36
  };
30
- export type CheckersCellClickPayload = {
37
+ export type CheckersClickPayload = {
31
38
  row?: number;
32
39
  column?: number;
33
40
  };
34
- export declare const statusForTurn: (turn: CheckersColor, winner?: CheckersColor | null) => "Black to move" | "Black wins" | "White to move" | "White wins";
41
+ export declare const isDarkSquare: (row: number, column: number) => boolean;
42
+ export declare const samePos: (a: CheckersPos, b: CheckersPos) => boolean;
35
43
  export declare const buildGridItems: ({ board, selected, legalMoves, gridRow, gridColumn, }: {
36
44
  board: CheckersBoard;
37
45
  selected?: CheckersPos | null;
@@ -39,4 +47,7 @@ export declare const buildGridItems: ({ board, selected, legalMoves, gridRow, gr
39
47
  gridRow?: number | null;
40
48
  gridColumn?: number | null;
41
49
  }) => CheckersGridItem[];
42
- export declare const createInitialCheckersState: () => Pick<CheckersPluginState, "board" | "turn" | "selected" | "legalMoves" | "winner" | "statusText" | "gridItems" | "gridIndex" | "gridsCount">;
50
+ export declare const listBoardPieces: (board: CheckersBoard) => (CheckersPiece & CheckersPosPayload)[];
51
+ export declare const emptyBoard: () => CheckersBoard;
52
+ export declare const statusForTurn: (turn?: CheckersColor, winner?: CheckersColor | null) => "" | "Black to move" | "Black wins" | "White to move" | "White wins";
53
+ export declare const fromGridChanged: (payload?: GridChangedPayload) => Partial<CheckersViewState>;
@@ -1,9 +1,6 @@
1
- import { BOARD_SIZE, createInitialBoard, getAllMoves, isDarkSquare } from "./board.js";
2
- export const statusForTurn = (turn, winner = null) => {
3
- if (winner)
4
- return winner === "white" ? "White wins" : "Black wins";
5
- return turn === "white" ? "White to move" : "Black to move";
6
- };
1
+ export const BOARD_SIZE = 8;
2
+ export const isDarkSquare = (row, column) => (row + column) % 2 === 1;
3
+ export const samePos = (a, b) => a.row === b.row && a.column === b.column;
7
4
  export const buildGridItems = ({ board, selected = null, legalMoves = [], gridRow = null, gridColumn = null, }) => {
8
5
  const legalKeys = new Set(legalMoves.map((move) => `${move.to.row}:${move.to.column}`));
9
6
  const items = [];
@@ -22,19 +19,29 @@ export const buildGridItems = ({ board, selected = null, legalMoves = [], gridRo
22
19
  }
23
20
  return items;
24
21
  };
25
- export const createInitialCheckersState = () => {
26
- const board = createInitialBoard();
27
- const turn = "white";
28
- const legalMoves = getAllMoves(board, turn);
29
- return {
30
- board,
31
- turn,
32
- selected: null,
33
- legalMoves: [],
34
- winner: null,
35
- statusText: statusForTurn(turn),
36
- gridItems: buildGridItems({ board, legalMoves: [] }),
37
- gridIndex: 0,
38
- gridsCount: 1,
39
- };
22
+ export const listBoardPieces = (board) => {
23
+ const pieces = [];
24
+ for (let row = 0; row < BOARD_SIZE; row++) {
25
+ for (let column = 0; column < BOARD_SIZE; column++) {
26
+ const piece = board[row]?.[column];
27
+ if (!piece)
28
+ continue;
29
+ pieces.push({ ...piece, row, column });
30
+ }
31
+ }
32
+ return pieces;
33
+ };
34
+ export const emptyBoard = () => Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => null));
35
+ export const statusForTurn = (turn, winner = null) => {
36
+ if (winner)
37
+ return winner === "white" ? "White wins" : "Black wins";
38
+ if (!turn)
39
+ return "";
40
+ return turn === "white" ? "White to move" : "Black to move";
40
41
  };
42
+ export const fromGridChanged = (payload = {}) => ({
43
+ board: payload.cells,
44
+ // selection is view-only — reset on every game model change
45
+ selected: null,
46
+ legalMoves: [],
47
+ });
@@ -1,3 +1,3 @@
1
1
  import type { ViewRenderFunc } from "@vnejs/module.components";
2
- import type { CheckersPluginState } from "../utils/checkers.js";
3
- export declare const render: ViewRenderFunc<CheckersPluginState>;
2
+ import type { CheckersViewState } from "../utils/checkers.js";
3
+ export declare const render: ViewRenderFunc<CheckersViewState>;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Flex, PositionBox, Screen, Text, createRenderFunc, useCallback, useEffect, useMemo, useState, useStoreState } from "@vnejs/uis.react";
3
3
  import { getVneLength } from "@vnejs/uis.utils";
4
- import { listBoardPieces } from "../utils/board.js";
4
+ import { listBoardPieces } from "../utils/checkers.js";
5
5
  const SELECTED_OPACITY = 0.7;
6
6
  const GRAVEYARD_STACK = 0.55;
7
7
  const cellBackground = (item) => {
@@ -37,13 +37,13 @@ const kingMarkLook = () => ({
37
37
  });
38
38
  const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }) => {
39
39
  const { isShow = false, isForce = false, board, gridItems = [], gridRow = null, gridColumn = null, selected = null, statusText = "", } = useStoreState(store, onMount);
40
- const transition = PARAMS.CHECKERS.TRANSITION;
41
- const propsByView = PARAMS.CHECKERS.VIEW_PROPS;
42
- const rows = PARAMS.CHECKERS.ROWS;
43
- const columns = PARAMS.CHECKERS.COLUMNS;
44
- const cellSize = PARAMS.CHECKERS.CELL_SIZE;
45
- const cellGap = PARAMS.CHECKERS.CELL_GAP;
46
- const graveyardGap = PARAMS.CHECKERS.GRAVEYARD_GAP;
40
+ const transition = PARAMS.CHECKERS_VIEW.TRANSITION;
41
+ const propsByView = PARAMS.CHECKERS_VIEW.VIEW_PROPS;
42
+ const rows = PARAMS.CHECKERS_VIEW.ROWS;
43
+ const columns = PARAMS.CHECKERS_VIEW.COLUMNS;
44
+ const cellSize = PARAMS.CHECKERS_VIEW.CELL_SIZE;
45
+ const cellGap = PARAMS.CHECKERS_VIEW.CELL_GAP;
46
+ const graveyardGap = PARAMS.CHECKERS_VIEW.GRAVEYARD_GAP;
47
47
  const boardSize = columns * cellSize + (columns - 1) * cellGap;
48
48
  const playfieldWidth = cellSize + graveyardGap + boardSize + graveyardGap + cellSize;
49
49
  const boardPieces = useMemo(() => (board ? listBoardPieces(board) : []), [board]);
@@ -169,12 +169,12 @@ const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }) => {
169
169
  }
170
170
  return `translate(calc(${boardOffsetX} + ${piece.column} * ${stepLen}), calc(${piece.row} * ${stepLen}))`;
171
171
  }, [blackGraveyardX, boardOffsetX, cellLen, stepLen]);
172
- const onCellClick = useCallback((row, column) => {
173
- void emit(EVENTS.CHECKERS.CELL_CLICK, { row, column });
174
- }, [emit, EVENTS.CHECKERS.CELL_CLICK]);
172
+ const onClick = useCallback((row, column) => {
173
+ void emit(EVENTS.CHECKERS_VIEW.CLICK, { row, column });
174
+ }, [emit, EVENTS.CHECKERS_VIEW.CLICK]);
175
175
  return (_jsx(Screen, { ...propsScreen, children: _jsx(PositionBox, { ...propsByView.position, children: _jsxs(Flex, { ...propsByView.flex, children: [_jsx(Text, { ...propsStatus }), _jsxs("div", { style: playfieldStyle, children: [_jsx("div", { style: graveyardStyle("left") }), _jsx("div", { style: graveyardStyle("right") }), _jsx("div", { style: boardStyle, children: gridItems.map((item) => {
176
176
  const isFocus = gridRow === item.row && gridColumn === item.column;
177
- return (_jsx("div", { onClick: () => onCellClick(item.row, item.column), "vne-cursor-type": "pointer", style: {
177
+ return (_jsx("div", { onClick: () => onClick(item.row, item.column), "vne-cursor-type": "pointer", style: {
178
178
  ...cellBaseStyle,
179
179
  background: cellBackground({ ...item, isFocus }),
180
180
  } }, `${item.row}-${item.column}`));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vnejs/plugins.views.screens.checkers",
3
- "version": "0.2.2",
4
- "description": "",
3
+ "version": "0.2.4",
4
+ "description": "Checkers screen view (CHECKERS_VIEW)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -30,6 +30,10 @@
30
30
  "author": "",
31
31
  "license": "ISC",
32
32
  "dependencies": {
33
+ "@vnejs/contracts.ai": "~0.2.0",
34
+ "@vnejs/contracts.common.grid": "~0.2.0",
35
+ "@vnejs/contracts.common.turn": "~0.2.0",
36
+ "@vnejs/contracts.game.checkers": "~0.2.0",
33
37
  "@vnejs/contracts.views.screens.checkers": "~0.2.0"
34
38
  },
35
39
  "peerDependencies": {
@@ -40,13 +44,18 @@
40
44
  "@vnejs/module.controller.arrows.grid": "~0.2.0",
41
45
  "@vnejs/contracts.controls": "~0.2.0",
42
46
  "@vnejs/shared": "~0.2.0",
47
+ "@vnejs/sources-set": "~0.2.0",
43
48
  "@vnejs/uis.react": "~0.2.0"
44
49
  },
45
50
  "devDependencies": {
46
51
  "@vnejs/configs.ts-common": "~0.2.0",
47
52
  "@vnejs/configs.vitest": "~0.2.0",
48
53
  "@vnejs/test-utils": "~0.2.0",
54
+ "@vnejs/contracts.ai": "~0.2.0",
55
+ "@vnejs/contracts.common.grid": "~0.2.0",
56
+ "@vnejs/contracts.common.turn": "~0.2.0",
49
57
  "@vnejs/contracts.controls": "~0.2.0",
58
+ "@vnejs/contracts.game.checkers": "~0.2.0",
50
59
  "@vnejs/contracts.views.screens.checkers": "~0.2.0",
51
60
  "@vnejs/helpers": "~0.2.0",
52
61
  "@vnejs/module.core": "~0.2.0",
@@ -54,6 +63,7 @@
54
63
  "@vnejs/module.controller.accept": "~0.2.0",
55
64
  "@vnejs/module.controller.arrows.grid": "~0.2.0",
56
65
  "@vnejs/shared": "~0.2.0",
66
+ "@vnejs/sources-set": "~0.2.0",
57
67
  "@vnejs/uis.react": "~0.2.0"
58
68
  }
59
69
  }
package/src/index.ts CHANGED
@@ -1,18 +1,31 @@
1
+ import "@vnejs/contracts.common.grid";
2
+ import "@vnejs/contracts.common.turn";
3
+ import "@vnejs/contracts.game.checkers";
1
4
  import "@vnejs/contracts.views.screens.checkers";
2
5
 
3
6
  import { regPlugin } from "@vnejs/shared";
4
- import { CONSTANTS, PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.screens.checkers";
7
+ import { PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.screens.checkers";
5
8
 
6
- import { CheckersController } from "./modules/controller.js";
7
- import { CheckersControllerAccept } from "./modules/controller.accept.js";
8
- import { CheckersControllerGrid } from "./modules/controller.grid.js";
9
- import { Checkers } from "./modules/checkers.js";
10
- import { CheckersView } from "./modules/view.js";
9
+ import { CheckersViewBoard } from "./modules/board.js";
10
+ import { CheckersViewBridge } from "./modules/bridge.js";
11
+ import { CheckersViewClick } from "./modules/click.js";
12
+ import { CheckersViewController } from "./modules/controller.js";
13
+ import { CheckersViewControllerAccept } from "./modules/controller.accept.js";
14
+ import { CheckersViewControllerGrid } from "./modules/controller.grid.js";
15
+ import { CheckersViewPlayerInteract } from "./modules/player.interact.js";
16
+ import { CheckersViewPlayerSelect } from "./modules/player.select.js";
17
+ import { CheckersViewStatus } from "./modules/status.js";
18
+ import { CheckersViewView } from "./modules/view.js";
11
19
 
12
- regPlugin(PLUGIN_NAME, { constants: CONSTANTS, events: SUBSCRIBE_EVENTS, params: PARAMS }, [
13
- CheckersController,
14
- CheckersControllerGrid,
15
- CheckersControllerAccept,
16
- Checkers,
17
- CheckersView,
20
+ regPlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, params: PARAMS }, [
21
+ CheckersViewBridge,
22
+ CheckersViewController,
23
+ CheckersViewControllerGrid,
24
+ CheckersViewControllerAccept,
25
+ CheckersViewPlayerInteract,
26
+ CheckersViewPlayerSelect,
27
+ CheckersViewClick,
28
+ CheckersViewBoard,
29
+ CheckersViewStatus,
30
+ CheckersViewView,
18
31
  ]);
@@ -0,0 +1,47 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { GridChangedPayload, GridUnsetPayload } from "@vnejs/contracts.common.grid";
3
+
4
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
5
+ import { buildGridItems, emptyBoard, fromGridChanged, type CheckersViewState } from "../utils/checkers.js";
6
+
7
+ export class CheckersViewBoard extends ModuleCore<
8
+ CheckersViewPluginEvents,
9
+ CheckersViewPluginConstants,
10
+ CheckersViewPluginSettings,
11
+ CheckersViewPluginParams,
12
+ CheckersViewState
13
+ > {
14
+ name = "checkers_view.board";
15
+
16
+ subscribe = () => {
17
+ this.on(this.EVENTS.GRID.CHANGED, this.onGridChanged);
18
+ this.on(this.EVENTS.GRID.UNSET, this.onGridUnset);
19
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
20
+ };
21
+
22
+ onGridChanged = async (payload: GridChangedPayload = {}) => {
23
+ if (payload.id !== this.CONST.CHECKERS.GRID_ID) return;
24
+
25
+ const mirror = fromGridChanged(payload);
26
+ const board = mirror.board ?? this.state.board ?? emptyBoard();
27
+ const gridItems = buildGridItems({ board, gridRow: this.state.gridRow ?? null, gridColumn: this.state.gridColumn ?? null });
28
+
29
+ await this.emit(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, { ...mirror, board, gridItems } satisfies Partial<CheckersViewState>);
30
+ await this.emit(this.EVENTS.CHECKERS_VIEW.UPDATE_FAST);
31
+ };
32
+
33
+ onGridUnset = async ({ id }: GridUnsetPayload = {}) => {
34
+ if (id !== this.CONST.CHECKERS.GRID_ID) return;
35
+
36
+ const board = emptyBoard();
37
+ const gridItems = buildGridItems({ board, gridRow: this.state.gridRow ?? null, gridColumn: this.state.gridColumn ?? null });
38
+
39
+ await this.emit(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, {
40
+ board,
41
+ selected: null,
42
+ legalMoves: [],
43
+ gridItems,
44
+ } satisfies Partial<CheckersViewState>);
45
+ await this.emit(this.EVENTS.CHECKERS_VIEW.UPDATE_FAST);
46
+ };
47
+ }
@@ -0,0 +1,20 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+
3
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
4
+
5
+ export class CheckersViewBridge extends ModuleCore<
6
+ CheckersViewPluginEvents,
7
+ CheckersViewPluginConstants,
8
+ CheckersViewPluginSettings,
9
+ CheckersViewPluginParams
10
+ > {
11
+ name = "checkers_view.bridge";
12
+
13
+ subscribe = () => {
14
+ this.on(this.EVENTS.CHECKERS.SHOW, this.onCheckersShow);
15
+ this.on(this.EVENTS.CHECKERS.HIDE, this.onCheckersHide);
16
+ };
17
+
18
+ onCheckersShow = () => this.emit(this.EVENTS.CHECKERS_VIEW.SHOW);
19
+ onCheckersHide = () => this.emit(this.EVENTS.CHECKERS_VIEW.HIDE);
20
+ }
@@ -0,0 +1,28 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { CheckersPlayerInteractPayload } from "@vnejs/contracts.views.screens.checkers";
3
+
4
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
5
+ import type { CheckersClickPayload, CheckersViewState } from "../utils/checkers.js";
6
+
7
+ export class CheckersViewClick extends ModuleCore<
8
+ CheckersViewPluginEvents,
9
+ CheckersViewPluginConstants,
10
+ CheckersViewPluginSettings,
11
+ CheckersViewPluginParams,
12
+ CheckersViewState
13
+ > {
14
+ name = "checkers_view.click";
15
+
16
+ subscribe = () => {
17
+ this.on(this.EVENTS.CHECKERS_VIEW.CLICK, this.onClick);
18
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
19
+ };
20
+
21
+ onClick = async ({ row, column }: CheckersClickPayload = {}) => {
22
+ if (this.shared.checkersInteractDisableSources.isNotEmpty() || row == null || column == null) return;
23
+
24
+ await this.emit(this.EVENTS.CHECKERS_VIEW.GRID_CLEAR);
25
+
26
+ this.emit(this.EVENTS.CHECKERS_VIEW.PLAYER_INTERACT, { row, column } satisfies CheckersPlayerInteractPayload);
27
+ };
28
+ }
@@ -1,202 +1,33 @@
1
1
  import { ModuleControllerAccept } from "@vnejs/module.controller.accept";
2
- import type { ControlsPopPayload, ControlsPushPayload } from "@vnejs/contracts.controls";
2
+ import type { CheckersPlayerInteractPayload } from "@vnejs/contracts.views.screens.checkers";
3
3
 
4
- import type { CheckersAiType } from "@vnejs/contracts.views.screens.checkers";
4
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
5
+ import type { CheckersViewState } from "../utils/checkers.js";
5
6
 
6
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
7
- import { applyMove, findMove, getAllMoves, getWinner, oppositeColor, samePos, type CheckersPos } from "../utils/board.js";
8
- import { pickMove } from "../utils/ai.js";
9
- import { buildGridItems, statusForTurn, type CheckersCellClickPayload, type CheckersPluginState } from "../utils/checkers.js";
10
-
11
- export class CheckersControllerAccept extends ModuleControllerAccept<
12
- CheckersPluginEvents,
13
- CheckersPluginConstants,
14
- CheckersPluginSettings,
15
- CheckersPluginParams,
16
- CheckersPluginState
7
+ export class CheckersViewControllerAccept extends ModuleControllerAccept<
8
+ CheckersViewPluginEvents,
9
+ CheckersViewPluginConstants,
10
+ CheckersViewPluginSettings,
11
+ CheckersViewPluginParams,
12
+ CheckersViewState
17
13
  > {
18
- name = "checkers.controller.accept";
19
-
20
- EVENT_SHOW = this.EVENTS.CHECKERS.SHOW;
21
- EVENT_HIDE = this.EVENTS.CHECKERS.HIDE;
22
- EVENT_ACCEPT = this.EVENTS.CHECKERS.ACCEPT;
23
- EVENT_STATE_UPDATE = this.EVENTS.CHECKERS.STATE_UPDATE;
24
-
25
- CONTROLS_ZINDEX = this.PARAMS.CHECKERS.ZINDEX;
26
-
27
- subscribe = () => {
28
- this.on(this.EVENT_SHOW, this.onShow);
29
- this.on(this.EVENT_HIDE, this.onHide);
30
- this.on(this.EVENT_ACCEPT, this.onAccept);
31
- if (this.EVENT_STATE_UPDATE) this.on(this.EVENT_STATE_UPDATE, this.updateState);
32
- this.on(this.EVENTS.CHECKERS.CELL_CLICK, this.onCellClick);
33
- this.on(this.EVENTS.CHECKERS.AI_TURN, this.onAiTurn);
34
- };
35
-
36
- onShow = () => void this.onShowAndMaybeAi();
37
-
38
- onShowAndMaybeAi = async () => {
39
- await this.emit(this.EVENTS.CONTROLS.PUSH, {
40
- key: this.name,
41
- controls: this.CONTROLS,
42
- index: this.CONTROLS_ZINDEX + 1,
43
- checkNext: true,
44
- } satisfies ControlsPushPayload);
45
-
46
- // SHOW handlers run in parallel with controller.beforeShow — wait for board.
47
- await this.waitCheck(() => Boolean(this.state.board && this.state.turn));
48
- await this.onAiTurn();
49
- };
50
-
51
- onHide = () => {
52
- this.shared.checkersAiBusy = false;
53
- this.setDefaultState();
14
+ name = "checkers_view.controller.accept";
54
15
 
55
- return this.emit(this.EVENTS.CONTROLS.POP, { key: this.name } satisfies ControlsPopPayload);
56
- };
57
-
58
- controllerFor = (turn: NonNullable<CheckersPluginState["turn"]>) =>
59
- (this.state.ai?.[turn] ?? this.shared.checkersAi?.[turn] ?? this.CONST.CHECKERS.AI_TYPES.PLAYER) as CheckersAiType;
60
-
61
- isHumanTurn = () => {
62
- const turn = this.state.turn;
63
- if (!turn) return false;
64
- return this.controllerFor(turn) === this.CONST.CHECKERS.AI_TYPES.PLAYER;
65
- };
66
-
67
- onCellClick = async ({ row, column }: CheckersCellClickPayload = {}) => {
68
- if (this.shared.checkersAiBusy || !this.isHumanTurn()) return;
69
- if (row == null || column == null) return;
16
+ EVENT_SHOW = this.EVENTS.CHECKERS_VIEW.SHOW;
17
+ EVENT_HIDE = this.EVENTS.CHECKERS_VIEW.HIDE;
18
+ EVENT_ACCEPT = this.EVENTS.CHECKERS_VIEW.ACCEPT;
19
+ EVENT_STATE_UPDATE = this.EVENTS.CHECKERS_VIEW.STATE_UPDATE;
70
20
 
71
- await this.emit(this.EVENTS.CHECKERS.STATE_UPDATE, {
72
- gridRow: row,
73
- gridColumn: column,
74
- } satisfies Partial<CheckersPluginState>);
75
- await this.emit(this.EVENTS.CHECKERS.UPDATE_FAST);
76
- await this.onAccept();
77
- };
21
+ CONTROLS_ZINDEX = this.PARAMS.CHECKERS_VIEW.ZINDEX;
78
22
 
79
23
  onAccept = async () => {
80
- if (this.shared.checkersAiBusy) return;
81
-
82
- if (this.state.winner) {
83
- await this.emit(this.EVENTS.CHECKERS.FINISH);
84
- return;
85
- }
86
-
87
- if (!this.isHumanTurn()) return;
88
-
89
- const { gridRow, gridColumn, board, turn, selected } = this.state;
90
- if (gridRow == null || gridColumn == null || !board || !turn) return;
91
-
92
- const focus: CheckersPos = { row: gridRow, column: gridColumn };
93
- const piece = board[focus.row]?.[focus.column] ?? null;
94
-
95
- if (selected && findMove(this.state.legalMoves ?? [], selected, focus)) {
96
- await this.playMove(selected, focus);
97
- return;
98
- }
99
-
100
- if (piece && piece.color === turn) {
101
- const all = getAllMoves(board, turn);
102
- const legalMoves = all.filter((move) => samePos(move.from, focus));
103
- if (legalMoves.length === 0) return;
104
-
105
- const nextSelected = selected && samePos(selected, focus) ? null : focus;
106
- const nextLegal = nextSelected ? legalMoves : [];
107
-
108
- await this.emitBoardState({
109
- selected: nextSelected,
110
- legalMoves: nextLegal,
111
- });
112
- return;
113
- }
114
-
115
- if (selected) {
116
- await this.emitBoardState({ selected: null, legalMoves: [] });
117
- }
118
- };
119
-
120
- onAiTurn = async () => {
121
- if (this.state.winner || !this.state.board || !this.state.turn) return;
122
- if (this.isHumanTurn()) return;
123
- if (this.shared.checkersAiBusy) return;
124
-
125
- this.shared.checkersAiBusy = true;
126
-
127
- try {
128
- while (!this.state.winner && this.state.board && this.state.turn && !this.isHumanTurn()) {
129
- await this.waitTimeout(this.PARAMS.CHECKERS.AI_DELAY);
130
-
131
- const { board, turn } = this.state;
132
- if (!board || !turn || this.state.winner) break;
133
-
134
- const move = pickMove(board, turn, this.controllerFor(turn));
135
- if (!move) break;
136
-
137
- const played = await this.playMove(move.from, move.to, { scheduleAi: false });
138
- if (!played) break;
139
- }
140
- } finally {
141
- this.shared.checkersAiBusy = false;
142
- }
143
- };
144
-
145
- playMove = async (from: CheckersPos, to: CheckersPos, { scheduleAi = true }: { scheduleAi?: boolean } = {}) => {
146
- const { board, turn } = this.state;
147
- if (!board || !turn) return false;
148
-
149
- // Always resolve against current legal moves — `legalMoves: []` after a move is truthy and must not win over getAllMoves.
150
- const move = findMove(getAllMoves(board, turn), from, to);
151
- if (!move) return false;
152
-
153
- const nextBoard = applyMove(board, move);
154
- const nextTurn = oppositeColor(turn);
155
- const winner = getWinner(nextBoard, nextTurn);
156
-
157
- await this.emitBoardState({
158
- board: nextBoard,
159
- turn: nextTurn,
160
- selected: null,
161
- legalMoves: [],
162
- winner,
163
- statusText: statusForTurn(nextTurn, winner),
164
- });
165
-
166
- if (winner) {
167
- await this.waitTimeout(this.PARAMS.CHECKERS.TRANSITION);
168
- await this.emit(this.EVENTS.CHECKERS.FINISH);
169
- return true;
170
- }
171
-
172
- if (scheduleAi) await this.onAiTurn();
173
- return true;
174
- };
24
+ if (this.shared.checkersInteractDisableSources.isNotEmpty()) return;
175
25
 
176
- emitBoardState = async (
177
- patch: Partial<CheckersPluginState> & {
178
- board?: CheckersPluginState["board"];
179
- selected?: CheckersPluginState["selected"];
180
- legalMoves?: CheckersPluginState["legalMoves"];
181
- },
182
- ) => {
183
- const board = patch.board ?? this.state.board;
184
- if (!board) return;
26
+ const { gridRow, gridColumn } = this.state;
27
+ if (gridRow == null || gridColumn == null) return;
185
28
 
186
- const selected = patch.selected !== undefined ? patch.selected : this.state.selected;
187
- const legalMoves = patch.legalMoves !== undefined ? patch.legalMoves : this.state.legalMoves;
188
- const gridItems = buildGridItems({
189
- board,
190
- selected: selected ?? null,
191
- legalMoves: legalMoves ?? [],
192
- gridRow: this.state.gridRow ?? null,
193
- gridColumn: this.state.gridColumn ?? null,
194
- });
29
+ await this.emit(this.EVENTS.CHECKERS_VIEW.GRID_CLEAR);
195
30
 
196
- await this.emit(this.EVENTS.CHECKERS.STATE_UPDATE, {
197
- ...patch,
198
- gridItems,
199
- } satisfies Partial<CheckersPluginState>);
200
- await this.emit(this.EVENTS.CHECKERS.UPDATE_FAST);
31
+ this.emit(this.EVENTS.CHECKERS_VIEW.PLAYER_INTERACT, { row: gridRow, column: gridColumn } satisfies CheckersPlayerInteractPayload);
201
32
  };
202
33
  }