@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,43 +1,40 @@
1
1
  import { ModuleControllerArrowsGrid } from "@vnejs/module.controller.arrows.grid";
2
2
 
3
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
4
- import { buildGridItems, type CheckersPluginState } from "../utils/checkers.js";
5
- import { createInitialBoard } from "../utils/board.js";
3
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
4
+ import { buildGridItems, emptyBoard, type CheckersViewState } from "../utils/checkers.js";
6
5
 
7
- export class CheckersControllerGrid extends ModuleControllerArrowsGrid<
8
- CheckersPluginEvents,
9
- CheckersPluginConstants,
10
- CheckersPluginSettings,
11
- CheckersPluginParams,
12
- CheckersPluginState
6
+ export class CheckersViewControllerGrid extends ModuleControllerArrowsGrid<
7
+ CheckersViewPluginEvents,
8
+ CheckersViewPluginConstants,
9
+ CheckersViewPluginSettings,
10
+ CheckersViewPluginParams,
11
+ CheckersViewState
13
12
  > {
14
- name = "checkers.controller.grid";
13
+ name = "checkers_view.controller.grid";
15
14
 
16
- EVENT_SHOW = this.EVENTS.CHECKERS.SHOW;
17
- EVENT_HIDE = this.EVENTS.CHECKERS.HIDE;
18
- EVENT_STATE_UPDATE = this.EVENTS.CHECKERS.STATE_UPDATE;
19
- EVENT_UPDATE_FAST = this.EVENTS.CHECKERS.UPDATE_FAST;
20
- EVENT_GRID_NEXT = this.EVENTS.CHECKERS.GRID_NEXT;
21
- EVENT_GRID_PREV = this.EVENTS.CHECKERS.GRID_PREV;
22
- EVENT_GRID_CLEAR = this.EVENTS.CHECKERS.GRID_CLEAR;
23
- EVENT_GRID_UPDATE_ITEMS = this.EVENTS.CHECKERS.GRID_UPDATE_ITEMS;
15
+ EVENT_SHOW = this.EVENTS.CHECKERS_VIEW.SHOW;
16
+ EVENT_HIDE = this.EVENTS.CHECKERS_VIEW.HIDE;
17
+ EVENT_STATE_UPDATE = this.EVENTS.CHECKERS_VIEW.STATE_UPDATE;
18
+ EVENT_UPDATE_FAST = this.EVENTS.CHECKERS_VIEW.UPDATE_FAST;
19
+ EVENT_GRID_NEXT = this.EVENTS.CHECKERS_VIEW.GRID_NEXT;
20
+ EVENT_GRID_PREV = this.EVENTS.CHECKERS_VIEW.GRID_PREV;
21
+ EVENT_GRID_CLEAR = this.EVENTS.CHECKERS_VIEW.GRID_CLEAR;
22
+ EVENT_GRID_UPDATE_ITEMS = this.EVENTS.CHECKERS_VIEW.GRID_UPDATE_ITEMS;
24
23
 
25
- CONTROLS_ZINDEX = this.PARAMS.CHECKERS.ZINDEX;
24
+ CONTROLS_ZINDEX = this.PARAMS.CHECKERS_VIEW.ZINDEX;
26
25
 
27
26
  isDisableGridChangeHorizontal = true;
28
27
  isDisableGridChangeVertical = true;
29
28
 
30
- getMaxRows = () => this.PARAMS.CHECKERS.ROWS;
31
- getMaxColumns = () => this.PARAMS.CHECKERS.COLUMNS;
29
+ getMaxRows = () => this.PARAMS.CHECKERS_VIEW.ROWS;
30
+ getMaxColumns = () => this.PARAMS.CHECKERS_VIEW.COLUMNS;
32
31
 
33
- getGridItems = async () => {
34
- const board = this.state.board ?? createInitialBoard();
35
- return buildGridItems({
36
- board,
32
+ getGridItems = async () =>
33
+ buildGridItems({
34
+ board: this.state.board ?? emptyBoard(),
37
35
  selected: this.state.selected ?? null,
38
36
  legalMoves: this.state.legalMoves ?? [],
39
37
  gridRow: this.state.gridRow ?? null,
40
38
  gridColumn: this.state.gridColumn ?? null,
41
39
  });
42
- };
43
40
  }
@@ -1,50 +1,30 @@
1
1
  import { ModuleController } from "@vnejs/module.components";
2
2
 
3
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
4
- import { createInitialCheckersState, type CheckersPluginState } from "../utils/checkers.js";
5
-
6
- export class CheckersController extends ModuleController<
7
- CheckersPluginEvents,
8
- CheckersPluginConstants,
9
- CheckersPluginSettings,
10
- CheckersPluginParams,
11
- CheckersPluginState
3
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
4
+ import type { CheckersViewState } from "../utils/checkers.js";
5
+
6
+ export class CheckersViewController extends ModuleController<
7
+ CheckersViewPluginEvents,
8
+ CheckersViewPluginConstants,
9
+ CheckersViewPluginSettings,
10
+ CheckersViewPluginParams,
11
+ CheckersViewState
12
12
  > {
13
- name = "checkers.controller";
13
+ name = "checkers_view.controller";
14
14
 
15
- EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS.UPDATE;
15
+ EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS_VIEW.UPDATE;
16
16
 
17
17
  CONTROLS = {};
18
- CONTROLS_INDEX = this.PARAMS.CHECKERS.ZINDEX;
18
+ CONTROLS_INDEX = this.PARAMS.CHECKERS_VIEW.ZINDEX;
19
19
 
20
20
  subscribe = () => {
21
- this.on(this.EVENTS.CHECKERS.SHOW, this.onShow);
22
- this.on(this.EVENTS.CHECKERS.HIDE, this.onHide);
23
- this.on(this.EVENTS.CHECKERS.STATE_UPDATE, this.updateState);
24
- this.on(this.EVENTS.CHECKERS.UPDATE_FAST, this.updateViewFast);
25
- this.on(this.EVENTS.CHECKERS.FINISH, this.onFinish);
26
- };
27
-
28
- beforeShow = async () => {
29
- this.shared.checkersAiBusy = false;
30
- const initial = createInitialCheckersState();
31
- const ai = {
32
- white: this.shared.checkersAi?.white ?? this.CONST.CHECKERS.AI_TYPES.PLAYER,
33
- black: this.shared.checkersAi?.black ?? this.CONST.CHECKERS.AI_TYPES.PLAYER,
34
- };
35
-
36
- await this.emit(this.EVENTS.CHECKERS.STATE_UPDATE, { ...initial, ai } satisfies Partial<CheckersPluginState>);
37
- await this.emit(this.EVENTS.CHECKERS.GRID_UPDATE_ITEMS);
21
+ this.on(this.EVENTS.CHECKERS_VIEW.SHOW, this.onShow);
22
+ this.on(this.EVENTS.CHECKERS_VIEW.HIDE, this.onHide);
23
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
24
+ this.on(this.EVENTS.CHECKERS_VIEW.UPDATE_FAST, this.updateViewFast);
38
25
  };
39
26
 
40
27
  afterHide = () => {
41
- this.shared.checkersAiBusy = false;
42
28
  this.setDefaultState();
43
29
  };
44
-
45
- onFinish = async () => {
46
- this.shared.checkersAiBusy = false;
47
- await this.emit(this.EVENTS.CHECKERS.HIDE);
48
- this.emitNext();
49
- };
50
30
  }
@@ -0,0 +1,48 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { CheckersMovePayload, CheckersMovePlayPayload, CheckersMovesGetResult } from "@vnejs/contracts.game.checkers";
3
+ import type { CheckersPlayerInteractPayload, CheckersPlayerSelectPayload } from "@vnejs/contracts.views.screens.checkers";
4
+
5
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
6
+ import { samePos, type CheckersPos, type CheckersViewState } from "../utils/checkers.js";
7
+
8
+ export class CheckersViewPlayerInteract extends ModuleCore<
9
+ CheckersViewPluginEvents,
10
+ CheckersViewPluginConstants,
11
+ CheckersViewPluginSettings,
12
+ CheckersViewPluginParams,
13
+ CheckersViewState
14
+ > {
15
+ name = "checkers_view.player.interact";
16
+
17
+ subscribe = () => {
18
+ this.on(this.EVENTS.CHECKERS_VIEW.PLAYER_INTERACT, this.onPlayerInteract);
19
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
20
+ };
21
+
22
+ onPlayerInteract = async ({ row, column }: CheckersPlayerInteractPayload = {}) => {
23
+ const { board, turn, selected } = this.state;
24
+ if (row == null || column == null || !board || !turn) return;
25
+
26
+ const focus: CheckersPos = { row, column };
27
+ const piece = board[focus.row]?.[focus.column] ?? null;
28
+ const legalFromState = this.state.legalMoves ?? [];
29
+ const move = selected && legalFromState.find((m) => samePos(m.from, selected) && samePos(m.to, focus));
30
+
31
+ if (move) return void this.emitOne(this.EVENTS.CHECKERS.MOVE_PLAY, { move, controllerType: this.CONST.AI.PLAYER_TYPE } satisfies CheckersMovePlayPayload);
32
+
33
+ if (piece && piece.color === turn) {
34
+ const result = (await this.emitOne(this.EVENTS.CHECKERS.MOVES_GET, { board, turn })) as CheckersMovesGetResult<CheckersMovePayload> | null;
35
+ const legalMoves = (result?.moves ?? []).filter((m) => samePos(m.from, focus));
36
+ if (legalMoves.length === 0) return;
37
+
38
+ const nextSelected = selected && samePos(selected, focus) ? null : focus;
39
+
40
+ return void this.emit(this.EVENTS.CHECKERS_VIEW.PLAYER_SELECT, {
41
+ selected: nextSelected,
42
+ legalMoves: nextSelected ? legalMoves : [],
43
+ } satisfies CheckersPlayerSelectPayload);
44
+ }
45
+
46
+ if (selected) return void this.emit(this.EVENTS.CHECKERS_VIEW.PLAYER_SELECT, { selected: null, legalMoves: [] } satisfies CheckersPlayerSelectPayload);
47
+ };
48
+ }
@@ -0,0 +1,42 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { CheckersPlayerSelectPayload } from "@vnejs/contracts.views.screens.checkers";
3
+
4
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
5
+ import { buildGridItems, type CheckersMove, type CheckersPos, type CheckersViewState } from "../utils/checkers.js";
6
+
7
+ export class CheckersViewPlayerSelect extends ModuleCore<
8
+ CheckersViewPluginEvents,
9
+ CheckersViewPluginConstants,
10
+ CheckersViewPluginSettings,
11
+ CheckersViewPluginParams,
12
+ CheckersViewState
13
+ > {
14
+ name = "checkers_view.player.select";
15
+
16
+ subscribe = () => {
17
+ this.on(this.EVENTS.CHECKERS_VIEW.PLAYER_SELECT, this.onPlayerSelect);
18
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
19
+ };
20
+
21
+ onPlayerSelect = async ({ selected = null, legalMoves = [] }: CheckersPlayerSelectPayload = {}) => {
22
+ const board = this.state.board;
23
+ if (!board) return;
24
+
25
+ const nextSelected = selected as CheckersPos | null;
26
+ const nextLegalMoves = legalMoves as CheckersMove[];
27
+ const gridItems = buildGridItems({
28
+ board,
29
+ selected: nextSelected,
30
+ legalMoves: nextLegalMoves,
31
+ gridRow: this.state.gridRow ?? null,
32
+ gridColumn: this.state.gridColumn ?? null,
33
+ });
34
+
35
+ await this.emit(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, {
36
+ selected: nextSelected,
37
+ legalMoves: nextLegalMoves,
38
+ gridItems,
39
+ } satisfies Partial<CheckersViewState>);
40
+ await this.emit(this.EVENTS.CHECKERS_VIEW.UPDATE_FAST);
41
+ };
42
+ }
@@ -0,0 +1,41 @@
1
+ import { ModuleCore } from "@vnejs/module.core";
2
+ import type { TurnsChangedPayload, TurnsUnsetPayload } from "@vnejs/contracts.common.turn";
3
+ import type { CheckersFinishPayload } from "@vnejs/contracts.game.checkers";
4
+
5
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
6
+ import { statusForTurn, type CheckersColor, type CheckersViewState } from "../utils/checkers.js";
7
+
8
+ export class CheckersViewStatus extends ModuleCore<
9
+ CheckersViewPluginEvents,
10
+ CheckersViewPluginConstants,
11
+ CheckersViewPluginSettings,
12
+ CheckersViewPluginParams,
13
+ CheckersViewState
14
+ > {
15
+ name = "checkers_view.status";
16
+
17
+ subscribe = () => {
18
+ this.on(this.EVENTS.TURNS.CHANGED, this.onTurnsChanged);
19
+ this.on(this.EVENTS.TURNS.UNSET, this.onTurnsUnset);
20
+ this.on(this.EVENTS.CHECKERS.FINISH, this.onFinish);
21
+ this.on(this.EVENTS.CHECKERS.SHOW, this.onShow);
22
+ this.on(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, this.updateState);
23
+ };
24
+
25
+ applyStatus = async ({ turn, winner, clearSelection = false }: { turn?: CheckersColor; winner: CheckersColor | null; clearSelection?: boolean }) => {
26
+ const next: Partial<CheckersViewState> = { turn, winner, statusText: statusForTurn(turn, winner) };
27
+
28
+ if (clearSelection) Object.assign(next, { selected: null, legalMoves: [] });
29
+
30
+ await this.emit(this.EVENTS.CHECKERS_VIEW.STATE_UPDATE, next);
31
+ await this.emit(this.EVENTS.CHECKERS_VIEW.UPDATE_FAST);
32
+ };
33
+
34
+ onTurnsChanged = ({ id, owner }: TurnsChangedPayload = {}) =>
35
+ id === this.CONST.CHECKERS.TURN_ID &&
36
+ this.applyStatus({ turn: owner as CheckersColor | undefined, winner: this.state.winner ?? null, clearSelection: true });
37
+ onTurnsUnset = ({ id }: TurnsUnsetPayload = {}) =>
38
+ id === this.CONST.CHECKERS.TURN_ID && this.applyStatus({ turn: undefined, winner: this.state.winner ?? null, clearSelection: true });
39
+ onFinish = ({ winner }: CheckersFinishPayload = {}) => this.applyStatus({ turn: this.state.turn, winner: (winner ?? null) as CheckersColor | null });
40
+ onShow = () => this.applyStatus({ turn: this.state.turn, winner: null });
41
+ }
@@ -1,17 +1,23 @@
1
1
  import { ModuleView } from "@vnejs/module.components";
2
2
 
3
3
  import { render } from "../view/index.js";
4
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
5
- import type { CheckersPluginState } from "../utils/checkers.js";
4
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
5
+ import type { CheckersViewState } from "../utils/checkers.js";
6
6
 
7
- export class CheckersView extends ModuleView<CheckersPluginEvents, CheckersPluginConstants, CheckersPluginSettings, CheckersPluginParams, CheckersPluginState> {
8
- name = "checkers.view";
7
+ export class CheckersViewView extends ModuleView<
8
+ CheckersViewPluginEvents,
9
+ CheckersViewPluginConstants,
10
+ CheckersViewPluginSettings,
11
+ CheckersViewPluginParams,
12
+ CheckersViewState
13
+ > {
14
+ name = "checkers_view.view";
9
15
 
10
- EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS.UPDATE;
16
+ EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS_VIEW.UPDATE;
11
17
 
12
18
  LOC_LABEL = null;
13
19
 
14
- TRANSITION = this.PARAMS.CHECKERS.TRANSITION;
20
+ TRANSITION = this.PARAMS.CHECKERS_VIEW.TRANSITION;
15
21
 
16
22
  renderFunc = render;
17
23
  }
@@ -0,0 +1,56 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { SUBSCRIBE_EVENTS as CHECKERS_EVENTS } from "@vnejs/contracts.game.checkers";
4
+ import { SUBSCRIBE_EVENTS as CHECKERS_VIEW_EVENTS } from "@vnejs/contracts.views.screens.checkers";
5
+ import { stubEvent } from "@vnejs/test-utils";
6
+
7
+ import { CheckersViewPlayerInteract } from "../modules/player.interact.js";
8
+ import { BOARD_SIZE, type CheckersBoard, type CheckersPiece } from "../utils/checkers.js";
9
+ import { createCheckersViewTestModule, registerCheckersViewPluginVne } from "./setup.js";
10
+
11
+ const emptyBoard = (): CheckersBoard => Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => null));
12
+
13
+ const piece = (id: string, color: CheckersPiece["color"]): CheckersPiece => ({ id, color, king: false });
14
+
15
+ /** White at (5,2): (4,1) hangs to black at (3,0); (4,3) is safe. */
16
+ const hangingScenarioBoard = (): CheckersBoard => {
17
+ const board = emptyBoard();
18
+ board[5]![2] = piece("w1", "white");
19
+ board[3]![0] = piece("b1", "black");
20
+ return board;
21
+ };
22
+
23
+ describe("checkers_view player.interact", () => {
24
+ beforeEach(() => {
25
+ registerCheckersViewPluginVne();
26
+ });
27
+
28
+ afterEach(() => {
29
+ vi.restoreAllMocks();
30
+ });
31
+
32
+ it("PLAYER_INTERACT selects piece and emits PLAYER_SELECT", async () => {
33
+ const board = hangingScenarioBoard();
34
+ const { module, observer } = createCheckersViewTestModule(CheckersViewPlayerInteract, { subscribe: false });
35
+ const interact = module as CheckersViewPlayerInteract;
36
+ interact.updateState({ board, turn: "white", selected: null, legalMoves: [] });
37
+
38
+ const playerSelect = vi.fn(() => null);
39
+ stubEvent(observer, CHECKERS_VIEW_EVENTS.PLAYER_SELECT, playerSelect);
40
+ stubEvent(observer, CHECKERS_EVENTS.MOVES_GET, () => ({
41
+ moves: [
42
+ { from: { row: 5, column: 2 }, to: { row: 4, column: 1 }, captures: [] },
43
+ { from: { row: 5, column: 2 }, to: { row: 4, column: 3 }, captures: [] },
44
+ ],
45
+ hanging: [],
46
+ safe: [],
47
+ }));
48
+
49
+ await interact.onPlayerInteract({ row: 5, column: 2 });
50
+
51
+ expect(playerSelect).toHaveBeenCalledOnce();
52
+ const args = playerSelect.mock.calls[0]?.[0] as { selected: { row: number; column: number }; legalMoves: unknown[] };
53
+ expect(args.selected).toEqual({ row: 5, column: 2 });
54
+ expect(args.legalMoves).toHaveLength(2);
55
+ });
56
+ });
@@ -1,23 +1,35 @@
1
+ import { CONSTANTS as AI_CONST, PARAMS as AI_PARAMS, PLUGIN_NAME as AI, SUBSCRIBE_EVENTS as AI_EVENTS } from "@vnejs/contracts.ai";
2
+ import { PLUGIN_NAME as GRID, SUBSCRIBE_EVENTS as GRID_EVENTS } from "@vnejs/contracts.common.grid";
1
3
  import { CONSTANTS as CONTROLS_CONST, PLUGIN_NAME as CONTROLS, SUBSCRIBE_EVENTS as CONTROLS_EVENTS } from "@vnejs/contracts.controls";
2
- import { CONSTANTS, PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.screens.checkers";
4
+ import {
5
+ CONSTANTS as CHECKERS_CONST,
6
+ PARAMS as CHECKERS_PARAMS,
7
+ PLUGIN_NAME as CHECKERS,
8
+ SUBSCRIBE_EVENTS as CHECKERS_EVENTS,
9
+ } from "@vnejs/contracts.game.checkers";
10
+ import { PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.screens.checkers";
11
+ import { SourcesSet } from "@vnejs/sources-set";
3
12
  import { createTestModule as baseCreateTestModule, registerCoreVne, registerVnePlugin, stubSilentEvent } from "@vnejs/test-utils";
4
13
 
5
- export const registerCheckersPluginVne = () => {
14
+ export const registerCheckersViewPluginVne = () => {
6
15
  registerCoreVne();
7
16
  registerVnePlugin(CONTROLS, { constants: CONTROLS_CONST, events: CONTROLS_EVENTS });
8
- registerVnePlugin(PLUGIN_NAME, { constants: CONSTANTS, events: SUBSCRIBE_EVENTS, params: PARAMS });
17
+ registerVnePlugin(AI, { constants: AI_CONST, events: AI_EVENTS, params: AI_PARAMS });
18
+ registerVnePlugin(CHECKERS, { constants: CHECKERS_CONST, events: CHECKERS_EVENTS, params: CHECKERS_PARAMS });
19
+ registerVnePlugin(GRID, { events: GRID_EVENTS });
20
+ registerVnePlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, params: PARAMS });
9
21
  };
10
22
 
11
- export const createCheckersTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
23
+ export const createCheckersViewTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
12
24
  ModuleClass: T,
13
25
  options: Parameters<typeof baseCreateTestModule>[1] = {},
14
26
  ) => {
15
27
  const result = baseCreateTestModule(ModuleClass, {
16
- state: {
17
- checkers: { isShow: false },
18
- ...(options.state ?? {}),
19
- },
20
28
  ...options,
29
+ shared: {
30
+ checkersInteractDisableSources: new SourcesSet(),
31
+ ...(options.shared ?? {}),
32
+ },
21
33
  });
22
34
 
23
35
  stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.SHOW);
@@ -26,9 +38,11 @@ export const createCheckersTestModule = <T extends Parameters<typeof baseCreateT
26
38
  stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.UPDATE_FAST);
27
39
  stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
28
40
  stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.GRID_UPDATE_ITEMS);
29
- stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.FINISH);
41
+ stubSilentEvent(result.observer, GRID_EVENTS.SET);
42
+ stubSilentEvent(result.observer, GRID_EVENTS.CHANGED);
43
+ stubSilentEvent(result.observer, CHECKERS_EVENTS.FINISH);
30
44
 
31
45
  return result;
32
46
  };
33
47
 
34
- export { SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME, CONTROLS_EVENTS };
48
+ export { SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME, CONTROLS_EVENTS, CHECKERS_EVENTS, GRID_EVENTS };
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
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 {
4
5
  Constants as ScenarioConstants,
@@ -6,18 +7,34 @@ import type {
6
7
  SubscribeEvents as ScenarioSubscribeEvents,
7
8
  } from "@vnejs/contracts.core.scenario";
8
9
  import type { PluginName as LogsPluginName, SubscribeEvents as LogsSubscribeEvents } from "@vnejs/contracts.core.logs";
9
- import type { Constants, Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.screens.checkers";
10
+ import type { PluginName as GridPluginName, SubscribeEvents as GridSubscribeEvents } from "@vnejs/contracts.common.grid";
11
+ import type { PluginName as TurnsPluginName, SubscribeEvents as TurnsSubscribeEvents } from "@vnejs/contracts.common.turn";
12
+ import type {
13
+ Constants as CheckersConstants,
14
+ Params as CheckersParams,
15
+ PluginName as CheckersPluginName,
16
+ SubscribeEvents as CheckersSubscribeEvents,
17
+ } from "@vnejs/contracts.game.checkers";
18
+ import type { Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.screens.checkers";
10
19
 
11
- export type CheckersPluginEvents = ModuleComponentsEvents &
20
+ export type CheckersViewPluginEvents = ModuleComponentsEvents &
12
21
  Record<PluginName, SubscribeEvents> &
22
+ Record<CheckersPluginName, CheckersSubscribeEvents> &
23
+ Record<GridPluginName, GridSubscribeEvents> &
24
+ Record<TurnsPluginName, TurnsSubscribeEvents> &
25
+ Record<AiPluginName, AiSubscribeEvents> &
13
26
  Record<ScenarioPluginName, ScenarioSubscribeEvents> &
14
27
  Record<LogsPluginName, LogsSubscribeEvents>;
15
28
 
16
- export type CheckersPluginConstants = ModuleComponentsConstants &
17
- Record<PluginName, Constants> &
29
+ export type CheckersViewPluginConstants = ModuleComponentsConstants &
30
+ Record<CheckersPluginName, CheckersConstants> &
31
+ Record<AiPluginName, AiConstants> &
18
32
  Record<ControlsPluginName, ControlsConstants> &
19
33
  Record<ScenarioPluginName, ScenarioConstants>;
20
34
 
21
- export type CheckersPluginSettings = ModuleComponentsSettings;
35
+ export type CheckersViewPluginSettings = ModuleComponentsSettings;
22
36
 
23
- export type CheckersPluginParams = ModuleComponentsParams & Record<PluginName, Params>;
37
+ export type CheckersViewPluginParams = ModuleComponentsParams &
38
+ Record<PluginName, Params> &
39
+ Record<CheckersPluginName, CheckersParams> &
40
+ Record<AiPluginName, AiParams>;
@@ -1,5 +1,21 @@
1
- import type { CheckersBoard, CheckersColor, CheckersMove, CheckersPiece, CheckersPos } from "./board.js";
2
- import { BOARD_SIZE, createInitialBoard, getAllMoves, isDarkSquare } from "./board.js";
1
+ import type { GridChangedPayload } from "@vnejs/contracts.common.grid";
2
+ import type { CheckersMovePayload, CheckersPosPayload } from "@vnejs/contracts.game.checkers";
3
+
4
+ export const BOARD_SIZE = 8;
5
+
6
+ export type CheckersColor = "white" | "black";
7
+
8
+ export type CheckersPiece = {
9
+ id: string;
10
+ color: CheckersColor;
11
+ king: boolean;
12
+ };
13
+
14
+ export type CheckersPos = CheckersPosPayload;
15
+
16
+ export type CheckersMove = CheckersMovePayload;
17
+
18
+ export type CheckersBoard = (CheckersPiece | null)[][];
3
19
 
4
20
  export type CheckersGridItem = {
5
21
  row: number;
@@ -11,7 +27,7 @@ export type CheckersGridItem = {
11
27
  isFocus?: boolean;
12
28
  };
13
29
 
14
- export type CheckersPluginState = {
30
+ export type CheckersViewState = {
15
31
  isShow: boolean;
16
32
  isForce: boolean;
17
33
  board?: CheckersBoard;
@@ -25,21 +41,16 @@ export type CheckersPluginState = {
25
41
  gridItems?: CheckersGridItem[];
26
42
  gridIndex?: number;
27
43
  gridsCount?: number;
28
- ai?: {
29
- white: string;
30
- black: string;
31
- };
32
44
  };
33
45
 
34
- export type CheckersCellClickPayload = {
46
+ export type CheckersClickPayload = {
35
47
  row?: number;
36
48
  column?: number;
37
49
  };
38
50
 
39
- export const statusForTurn = (turn: CheckersColor, winner: CheckersColor | null = null) => {
40
- if (winner) return winner === "white" ? "White wins" : "Black wins";
41
- return turn === "white" ? "White to move" : "Black to move";
42
- };
51
+ export const isDarkSquare = (row: number, column: number) => (row + column) % 2 === 1;
52
+
53
+ export const samePos = (a: CheckersPos, b: CheckersPos) => a.row === b.row && a.column === b.column;
43
54
 
44
55
  export const buildGridItems = ({
45
56
  board,
@@ -74,23 +85,31 @@ export const buildGridItems = ({
74
85
  return items;
75
86
  };
76
87
 
77
- export const createInitialCheckersState = (): Pick<
78
- CheckersPluginState,
79
- "board" | "turn" | "selected" | "legalMoves" | "winner" | "statusText" | "gridItems" | "gridIndex" | "gridsCount"
80
- > => {
81
- const board = createInitialBoard();
82
- const turn: CheckersColor = "white";
83
- const legalMoves = getAllMoves(board, turn);
84
-
85
- return {
86
- board,
87
- turn,
88
- selected: null,
89
- legalMoves: [],
90
- winner: null,
91
- statusText: statusForTurn(turn),
92
- gridItems: buildGridItems({ board, legalMoves: [] }),
93
- gridIndex: 0,
94
- gridsCount: 1,
95
- };
88
+ export const listBoardPieces = (board: CheckersBoard) => {
89
+ const pieces: Array<CheckersPiece & CheckersPos> = [];
90
+
91
+ for (let row = 0; row < BOARD_SIZE; row++) {
92
+ for (let column = 0; column < BOARD_SIZE; column++) {
93
+ const piece = board[row]?.[column];
94
+ if (!piece) continue;
95
+ pieces.push({ ...piece, row, column });
96
+ }
97
+ }
98
+
99
+ return pieces;
96
100
  };
101
+
102
+ export const emptyBoard = (): CheckersBoard => Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => null));
103
+
104
+ export const statusForTurn = (turn?: CheckersColor, winner: CheckersColor | null = null) => {
105
+ if (winner) return winner === "white" ? "White wins" : "Black wins";
106
+ if (!turn) return "";
107
+ return turn === "white" ? "White to move" : "Black to move";
108
+ };
109
+
110
+ export const fromGridChanged = (payload: GridChangedPayload = {}): Partial<CheckersViewState> => ({
111
+ board: payload.cells as CheckersBoard | undefined,
112
+ // selection is view-only — reset on every game model change
113
+ selected: null,
114
+ legalMoves: [],
115
+ });
@@ -4,17 +4,16 @@ import type { ReactComponentProps } from "@vnejs/uis.react";
4
4
  import { Flex, PositionBox, Screen, Text, createRenderFunc, useCallback, useEffect, useMemo, useState, useStoreState } from "@vnejs/uis.react";
5
5
  import { getVneLength } from "@vnejs/uis.utils";
6
6
 
7
- import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
8
- import type { CheckersColor, CheckersPiece } from "../utils/board.js";
9
- import { listBoardPieces } from "../utils/board.js";
10
- import type { CheckersGridItem, CheckersPluginState } from "../utils/checkers.js";
7
+ import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
8
+ import type { CheckersColor, CheckersGridItem, CheckersPiece, CheckersViewState } from "../utils/checkers.js";
9
+ import { listBoardPieces } from "../utils/checkers.js";
11
10
 
12
11
  type CheckersComponentProps = ReactComponentProps<
13
- CheckersPluginEvents,
14
- CheckersPluginConstants,
15
- CheckersPluginSettings,
16
- CheckersPluginParams,
17
- CheckersPluginState
12
+ CheckersViewPluginEvents,
13
+ CheckersViewPluginConstants,
14
+ CheckersViewPluginSettings,
15
+ CheckersViewPluginParams,
16
+ CheckersViewState
18
17
  >;
19
18
 
20
19
  type VisualPiece = {
@@ -72,15 +71,15 @@ const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }: CheckersCompon
72
71
  gridColumn = null,
73
72
  selected = null,
74
73
  statusText = "",
75
- } = useStoreState<CheckersPluginState>(store, onMount);
76
-
77
- const transition = PARAMS.CHECKERS.TRANSITION;
78
- const propsByView = PARAMS.CHECKERS.VIEW_PROPS;
79
- const rows = PARAMS.CHECKERS.ROWS;
80
- const columns = PARAMS.CHECKERS.COLUMNS;
81
- const cellSize = PARAMS.CHECKERS.CELL_SIZE;
82
- const cellGap = PARAMS.CHECKERS.CELL_GAP;
83
- const graveyardGap = PARAMS.CHECKERS.GRAVEYARD_GAP;
74
+ } = useStoreState<CheckersViewState>(store, onMount);
75
+
76
+ const transition = PARAMS.CHECKERS_VIEW.TRANSITION;
77
+ const propsByView = PARAMS.CHECKERS_VIEW.VIEW_PROPS;
78
+ const rows = PARAMS.CHECKERS_VIEW.ROWS;
79
+ const columns = PARAMS.CHECKERS_VIEW.COLUMNS;
80
+ const cellSize = PARAMS.CHECKERS_VIEW.CELL_SIZE;
81
+ const cellGap = PARAMS.CHECKERS_VIEW.CELL_GAP;
82
+ const graveyardGap = PARAMS.CHECKERS_VIEW.GRAVEYARD_GAP;
84
83
  const boardSize = columns * cellSize + (columns - 1) * cellGap;
85
84
  const playfieldWidth = cellSize + graveyardGap + boardSize + graveyardGap + cellSize;
86
85
 
@@ -244,11 +243,11 @@ const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }: CheckersCompon
244
243
  [blackGraveyardX, boardOffsetX, cellLen, stepLen],
245
244
  );
246
245
 
247
- const onCellClick = useCallback(
246
+ const onClick = useCallback(
248
247
  (row: number, column: number) => {
249
- void emit(EVENTS.CHECKERS.CELL_CLICK, { row, column });
248
+ void emit(EVENTS.CHECKERS_VIEW.CLICK, { row, column });
250
249
  },
251
- [emit, EVENTS.CHECKERS.CELL_CLICK],
250
+ [emit, EVENTS.CHECKERS_VIEW.CLICK],
252
251
  );
253
252
 
254
253
  return (
@@ -266,7 +265,7 @@ const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }: CheckersCompon
266
265
  return (
267
266
  <div
268
267
  key={`${item.row}-${item.column}`}
269
- onClick={() => onCellClick(item.row, item.column)}
268
+ onClick={() => onClick(item.row, item.column)}
270
269
  vne-cursor-type="pointer"
271
270
  style={{
272
271
  ...cellBaseStyle,
@@ -306,4 +305,4 @@ const CheckersScreen = ({ store, onMount, PARAMS, emit, EVENTS }: CheckersCompon
306
305
  );
307
306
  };
308
307
 
309
- export const render: ViewRenderFunc<CheckersPluginState> = createRenderFunc(CheckersScreen);
308
+ export const render: ViewRenderFunc<CheckersViewState> = createRenderFunc(CheckersScreen);