@vnejs/plugins.views.screens.checkers 0.2.2 → 0.2.3
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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +21 -12
- package/dist/modules/board.d.ts +9 -0
- package/dist/modules/board.js +16 -0
- package/dist/modules/bridge.d.ts +8 -0
- package/dist/modules/bridge.js +10 -0
- package/dist/modules/click.d.ts +8 -0
- package/dist/modules/click.js +14 -0
- package/dist/modules/controller.accept.d.ts +7 -25
- package/dist/modules/controller.accept.js +12 -159
- package/dist/modules/controller.d.ts +4 -6
- package/dist/modules/controller.grid.d.ts +11 -11
- package/dist/modules/controller.grid.js +21 -25
- package/dist/modules/controller.js +8 -26
- package/dist/modules/player.interact.d.ts +9 -0
- package/dist/modules/player.interact.js +33 -0
- package/dist/modules/player.select.d.ts +9 -0
- package/dist/modules/player.select.js +29 -0
- package/dist/modules/view.d.ts +5 -5
- package/dist/modules/view.js +4 -4
- package/dist/types.d.ts +7 -5
- package/dist/utils/checkers.d.ts +19 -9
- package/dist/utils/checkers.js +30 -16
- package/dist/view/index.d.ts +2 -2
- package/dist/view/index.js +12 -12
- package/package.json +8 -2
- package/src/index.ts +21 -12
- package/src/modules/board.ts +29 -0
- package/src/modules/bridge.ts +20 -0
- package/src/modules/click.ts +28 -0
- package/src/modules/controller.accept.ts +20 -189
- package/src/modules/controller.grid.ts +23 -26
- package/src/modules/controller.ts +16 -36
- package/src/modules/player.interact.ts +48 -0
- package/src/modules/player.select.ts +42 -0
- package/src/modules/view.ts +12 -6
- package/src/tests/player.interact.test.ts +56 -0
- package/src/tests/setup.ts +21 -10
- package/src/types.ts +19 -6
- package/src/utils/checkers.ts +51 -25
- package/src/view/index.tsx +22 -23
- package/dist/modules/checkers.d.ts +0 -9
- package/dist/modules/checkers.js +0 -37
- package/dist/utils/ai.d.ts +0 -4
- package/dist/utils/ai.js +0 -27
- package/dist/utils/board.d.ts +0 -32
- package/dist/utils/board.js +0 -199
- package/src/modules/checkers.ts +0 -52
- package/src/tests/ai.test.ts +0 -61
- package/src/tests/board.test.ts +0 -43
- package/src/tests/checkers.test.ts +0 -58
- package/src/utils/ai.ts +0 -31
- package/src/utils/board.ts +0 -252
|
@@ -1,50 +1,30 @@
|
|
|
1
1
|
import { ModuleController } from "@vnejs/module.components";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export class
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 = "
|
|
13
|
+
name = "checkers_view.controller";
|
|
14
14
|
|
|
15
|
-
EVENT_UPDATE_VIEW = this.EVENTS.
|
|
15
|
+
EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS_VIEW.UPDATE;
|
|
16
16
|
|
|
17
17
|
CONTROLS = {};
|
|
18
|
-
CONTROLS_INDEX = this.PARAMS.
|
|
18
|
+
CONTROLS_INDEX = this.PARAMS.CHECKERS_VIEW.ZINDEX;
|
|
19
19
|
|
|
20
20
|
subscribe = () => {
|
|
21
|
-
this.on(this.EVENTS.
|
|
22
|
-
this.on(this.EVENTS.
|
|
23
|
-
this.on(this.EVENTS.
|
|
24
|
-
this.on(this.EVENTS.
|
|
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
|
+
}
|
package/src/modules/view.ts
CHANGED
|
@@ -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 {
|
|
5
|
-
import type {
|
|
4
|
+
import type { CheckersViewPluginConstants, CheckersViewPluginEvents, CheckersViewPluginParams, CheckersViewPluginSettings } from "../types.js";
|
|
5
|
+
import type { CheckersViewState } from "../utils/checkers.js";
|
|
6
6
|
|
|
7
|
-
export class
|
|
8
|
-
|
|
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.
|
|
16
|
+
EVENT_UPDATE_VIEW = this.EVENTS.CHECKERS_VIEW.UPDATE;
|
|
11
17
|
|
|
12
18
|
LOC_LABEL = null;
|
|
13
19
|
|
|
14
|
-
TRANSITION = this.PARAMS.
|
|
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
|
+
});
|
package/src/tests/setup.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
|
+
import { CONSTANTS as AI_CONST, PARAMS as AI_PARAMS, PLUGIN_NAME as AI, SUBSCRIBE_EVENTS as AI_EVENTS } from "@vnejs/contracts.ai";
|
|
1
2
|
import { CONSTANTS as CONTROLS_CONST, PLUGIN_NAME as CONTROLS, SUBSCRIBE_EVENTS as CONTROLS_EVENTS } from "@vnejs/contracts.controls";
|
|
2
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CONSTANTS as CHECKERS_CONST,
|
|
5
|
+
PARAMS as CHECKERS_PARAMS,
|
|
6
|
+
PLUGIN_NAME as CHECKERS,
|
|
7
|
+
SUBSCRIBE_EVENTS as CHECKERS_EVENTS,
|
|
8
|
+
} from "@vnejs/contracts.game.checkers";
|
|
9
|
+
import { PARAMS, PLUGIN_NAME, SUBSCRIBE_EVENTS } from "@vnejs/contracts.views.screens.checkers";
|
|
10
|
+
import { SourcesSet } from "@vnejs/sources-set";
|
|
3
11
|
import { createTestModule as baseCreateTestModule, registerCoreVne, registerVnePlugin, stubSilentEvent } from "@vnejs/test-utils";
|
|
4
12
|
|
|
5
|
-
export const
|
|
13
|
+
export const registerCheckersViewPluginVne = () => {
|
|
6
14
|
registerCoreVne();
|
|
7
15
|
registerVnePlugin(CONTROLS, { constants: CONTROLS_CONST, events: CONTROLS_EVENTS });
|
|
8
|
-
registerVnePlugin(
|
|
16
|
+
registerVnePlugin(AI, { constants: AI_CONST, events: AI_EVENTS, params: AI_PARAMS });
|
|
17
|
+
registerVnePlugin(CHECKERS, { constants: CHECKERS_CONST, events: CHECKERS_EVENTS, params: CHECKERS_PARAMS });
|
|
18
|
+
registerVnePlugin(PLUGIN_NAME, { events: SUBSCRIBE_EVENTS, params: PARAMS });
|
|
9
19
|
};
|
|
10
20
|
|
|
11
|
-
export const
|
|
21
|
+
export const createCheckersViewTestModule = <T extends Parameters<typeof baseCreateTestModule>[0]>(
|
|
12
22
|
ModuleClass: T,
|
|
13
23
|
options: Parameters<typeof baseCreateTestModule>[1] = {},
|
|
14
24
|
) => {
|
|
15
25
|
const result = baseCreateTestModule(ModuleClass, {
|
|
16
|
-
state: {
|
|
17
|
-
checkers: { isShow: false },
|
|
18
|
-
...(options.state ?? {}),
|
|
19
|
-
},
|
|
20
26
|
...options,
|
|
27
|
+
shared: {
|
|
28
|
+
checkersInteractDisableSources: new SourcesSet(),
|
|
29
|
+
...(options.shared ?? {}),
|
|
30
|
+
},
|
|
21
31
|
});
|
|
22
32
|
|
|
23
33
|
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.SHOW);
|
|
@@ -26,9 +36,10 @@ export const createCheckersTestModule = <T extends Parameters<typeof baseCreateT
|
|
|
26
36
|
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.UPDATE_FAST);
|
|
27
37
|
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.STATE_UPDATE);
|
|
28
38
|
stubSilentEvent(result.observer, SUBSCRIBE_EVENTS.GRID_UPDATE_ITEMS);
|
|
29
|
-
stubSilentEvent(result.observer,
|
|
39
|
+
stubSilentEvent(result.observer, CHECKERS_EVENTS.BOARD_CHANGE);
|
|
40
|
+
stubSilentEvent(result.observer, CHECKERS_EVENTS.FINISH);
|
|
30
41
|
|
|
31
42
|
return result;
|
|
32
43
|
};
|
|
33
44
|
|
|
34
|
-
export { SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME, CONTROLS_EVENTS };
|
|
45
|
+
export { SUBSCRIBE_EVENTS, PARAMS, PLUGIN_NAME, CONTROLS_EVENTS, CHECKERS_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,30 @@ 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 {
|
|
10
|
+
import type {
|
|
11
|
+
Constants as CheckersConstants,
|
|
12
|
+
Params as CheckersParams,
|
|
13
|
+
PluginName as CheckersPluginName,
|
|
14
|
+
SubscribeEvents as CheckersSubscribeEvents,
|
|
15
|
+
} from "@vnejs/contracts.game.checkers";
|
|
16
|
+
import type { Params, PluginName, SubscribeEvents } from "@vnejs/contracts.views.screens.checkers";
|
|
10
17
|
|
|
11
|
-
export type
|
|
18
|
+
export type CheckersViewPluginEvents = ModuleComponentsEvents &
|
|
12
19
|
Record<PluginName, SubscribeEvents> &
|
|
20
|
+
Record<CheckersPluginName, CheckersSubscribeEvents> &
|
|
21
|
+
Record<AiPluginName, AiSubscribeEvents> &
|
|
13
22
|
Record<ScenarioPluginName, ScenarioSubscribeEvents> &
|
|
14
23
|
Record<LogsPluginName, LogsSubscribeEvents>;
|
|
15
24
|
|
|
16
|
-
export type
|
|
17
|
-
Record<
|
|
25
|
+
export type CheckersViewPluginConstants = ModuleComponentsConstants &
|
|
26
|
+
Record<CheckersPluginName, CheckersConstants> &
|
|
27
|
+
Record<AiPluginName, AiConstants> &
|
|
18
28
|
Record<ControlsPluginName, ControlsConstants> &
|
|
19
29
|
Record<ScenarioPluginName, ScenarioConstants>;
|
|
20
30
|
|
|
21
|
-
export type
|
|
31
|
+
export type CheckersViewPluginSettings = ModuleComponentsSettings;
|
|
22
32
|
|
|
23
|
-
export type
|
|
33
|
+
export type CheckersViewPluginParams = ModuleComponentsParams &
|
|
34
|
+
Record<PluginName, Params> &
|
|
35
|
+
Record<CheckersPluginName, CheckersParams> &
|
|
36
|
+
Record<AiPluginName, AiParams>;
|
package/src/utils/checkers.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { CheckersBoardChangedPayload, CheckersMovePayload, CheckersPosPayload } from "@vnejs/contracts.game.checkers";
|
|
2
|
+
|
|
3
|
+
export const BOARD_SIZE = 8;
|
|
4
|
+
|
|
5
|
+
export type CheckersColor = "white" | "black";
|
|
6
|
+
|
|
7
|
+
export type CheckersPiece = {
|
|
8
|
+
id: string;
|
|
9
|
+
color: CheckersColor;
|
|
10
|
+
king: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type CheckersPos = CheckersPosPayload;
|
|
14
|
+
|
|
15
|
+
export type CheckersMove = CheckersMovePayload;
|
|
16
|
+
|
|
17
|
+
export type CheckersBoard = (CheckersPiece | null)[][];
|
|
3
18
|
|
|
4
19
|
export type CheckersGridItem = {
|
|
5
20
|
row: number;
|
|
@@ -11,7 +26,7 @@ export type CheckersGridItem = {
|
|
|
11
26
|
isFocus?: boolean;
|
|
12
27
|
};
|
|
13
28
|
|
|
14
|
-
export type
|
|
29
|
+
export type CheckersViewState = {
|
|
15
30
|
isShow: boolean;
|
|
16
31
|
isForce: boolean;
|
|
17
32
|
board?: CheckersBoard;
|
|
@@ -25,21 +40,16 @@ export type CheckersPluginState = {
|
|
|
25
40
|
gridItems?: CheckersGridItem[];
|
|
26
41
|
gridIndex?: number;
|
|
27
42
|
gridsCount?: number;
|
|
28
|
-
ai?: {
|
|
29
|
-
white: string;
|
|
30
|
-
black: string;
|
|
31
|
-
};
|
|
32
43
|
};
|
|
33
44
|
|
|
34
|
-
export type
|
|
45
|
+
export type CheckersClickPayload = {
|
|
35
46
|
row?: number;
|
|
36
47
|
column?: number;
|
|
37
48
|
};
|
|
38
49
|
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
50
|
+
export const isDarkSquare = (row: number, column: number) => (row + column) % 2 === 1;
|
|
51
|
+
|
|
52
|
+
export const samePos = (a: CheckersPos, b: CheckersPos) => a.row === b.row && a.column === b.column;
|
|
43
53
|
|
|
44
54
|
export const buildGridItems = ({
|
|
45
55
|
board,
|
|
@@ -74,23 +84,39 @@ export const buildGridItems = ({
|
|
|
74
84
|
return items;
|
|
75
85
|
};
|
|
76
86
|
|
|
77
|
-
export const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
export const listBoardPieces = (board: CheckersBoard) => {
|
|
88
|
+
const pieces: Array<CheckersPiece & CheckersPos> = [];
|
|
89
|
+
|
|
90
|
+
for (let row = 0; row < BOARD_SIZE; row++) {
|
|
91
|
+
for (let column = 0; column < BOARD_SIZE; column++) {
|
|
92
|
+
const piece = board[row]?.[column];
|
|
93
|
+
if (!piece) continue;
|
|
94
|
+
pieces.push({ ...piece, row, column });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return pieces;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const emptyBoard = (): CheckersBoard => Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => null));
|
|
102
|
+
|
|
103
|
+
export const statusForTurn = (turn?: CheckersColor, winner: CheckersColor | null = null) => {
|
|
104
|
+
if (winner) return winner === "white" ? "White wins" : "Black wins";
|
|
105
|
+
if (!turn) return "";
|
|
106
|
+
return turn === "white" ? "White to move" : "Black to move";
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const fromBoardChanged = (payload: CheckersBoardChangedPayload = {}): Partial<CheckersViewState> => {
|
|
110
|
+
const turn = payload.turn as CheckersColor | undefined;
|
|
111
|
+
const winner = (payload.winner ?? null) as CheckersColor | null;
|
|
84
112
|
|
|
85
113
|
return {
|
|
86
|
-
board,
|
|
114
|
+
board: payload.board as CheckersBoard | undefined,
|
|
87
115
|
turn,
|
|
116
|
+
winner,
|
|
117
|
+
statusText: statusForTurn(turn, winner),
|
|
118
|
+
// selection is view-only — reset on every game model change
|
|
88
119
|
selected: null,
|
|
89
120
|
legalMoves: [],
|
|
90
|
-
winner: null,
|
|
91
|
-
statusText: statusForTurn(turn),
|
|
92
|
-
gridItems: buildGridItems({ board, legalMoves: [] }),
|
|
93
|
-
gridIndex: 0,
|
|
94
|
-
gridsCount: 1,
|
|
95
121
|
};
|
|
96
122
|
};
|
package/src/view/index.tsx
CHANGED
|
@@ -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 {
|
|
8
|
-
import type { CheckersColor, CheckersPiece } from "../utils/
|
|
9
|
-
import { listBoardPieces } from "../utils/
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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<
|
|
76
|
-
|
|
77
|
-
const transition = PARAMS.
|
|
78
|
-
const propsByView = PARAMS.
|
|
79
|
-
const rows = PARAMS.
|
|
80
|
-
const columns = PARAMS.
|
|
81
|
-
const cellSize = PARAMS.
|
|
82
|
-
const cellGap = PARAMS.
|
|
83
|
-
const graveyardGap = PARAMS.
|
|
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
|
|
246
|
+
const onClick = useCallback(
|
|
248
247
|
(row: number, column: number) => {
|
|
249
|
-
void emit(EVENTS.
|
|
248
|
+
void emit(EVENTS.CHECKERS_VIEW.CLICK, { row, column });
|
|
250
249
|
},
|
|
251
|
-
[emit, EVENTS.
|
|
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={() =>
|
|
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<
|
|
308
|
+
export const render: ViewRenderFunc<CheckersViewState> = createRenderFunc(CheckersScreen);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ModuleCore } from "@vnejs/module.core";
|
|
2
|
-
import type { LineExecHandlerArg } from "@vnejs/module.core";
|
|
3
|
-
import type { CheckersPluginConstants, CheckersPluginEvents, CheckersPluginParams, CheckersPluginSettings } from "../types.js";
|
|
4
|
-
export declare class Checkers extends ModuleCore<CheckersPluginEvents, CheckersPluginConstants, CheckersPluginSettings, CheckersPluginParams> {
|
|
5
|
-
name: string;
|
|
6
|
-
postinject: () => void;
|
|
7
|
-
init: () => Promise<unknown[]> | undefined;
|
|
8
|
-
onLineExec: ({ line }?: LineExecHandlerArg) => Promise<unknown[]> | undefined;
|
|
9
|
-
}
|
package/dist/modules/checkers.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { tokenizeExecLine } from "@vnejs/helpers";
|
|
2
|
-
import { ModuleCore } from "@vnejs/module.core";
|
|
3
|
-
const AI_COLOR_VALUES = new Set(["white", "black"]);
|
|
4
|
-
const AI_TYPE_VALUES = new Set(["player", "sacrifice", "random", "strong"]);
|
|
5
|
-
export class Checkers extends ModuleCore {
|
|
6
|
-
name = "checkers";
|
|
7
|
-
postinject = () => {
|
|
8
|
-
this.shared.checkersAi ??= {
|
|
9
|
-
white: this.CONST.CHECKERS.AI_TYPES.PLAYER,
|
|
10
|
-
black: this.CONST.CHECKERS.AI_TYPES.PLAYER,
|
|
11
|
-
};
|
|
12
|
-
this.shared.checkersAiBusy ??= false;
|
|
13
|
-
};
|
|
14
|
-
init = () => this.emit(this.EVENTS.SCENARIO.LINE_EXEC_REG, { module: this.name, handler: this.onLineExec });
|
|
15
|
-
onLineExec = ({ line = "" } = {}) => {
|
|
16
|
-
const [action = "", colorRaw = "", typeRaw = ""] = tokenizeExecLine(line).map(String);
|
|
17
|
-
if (action === this.CONST.CHECKERS.EXEC_ACTIONS.SHOW) {
|
|
18
|
-
this.emitLog({ action });
|
|
19
|
-
return this.emit(this.EVENTS.CHECKERS.SHOW);
|
|
20
|
-
}
|
|
21
|
-
if (action !== this.CONST.CHECKERS.EXEC_ACTIONS.AI)
|
|
22
|
-
return;
|
|
23
|
-
if (!AI_COLOR_VALUES.has(colorRaw) || !AI_TYPE_VALUES.has(typeRaw)) {
|
|
24
|
-
this.emitLog({ action, color: colorRaw, type: typeRaw, error: "invalid_args" });
|
|
25
|
-
this.emitNext();
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const color = colorRaw;
|
|
29
|
-
const type = typeRaw;
|
|
30
|
-
this.shared.checkersAi = {
|
|
31
|
-
...this.shared.checkersAi,
|
|
32
|
-
[color]: type,
|
|
33
|
-
};
|
|
34
|
-
this.emitLog({ action, color, type });
|
|
35
|
-
this.emitNext();
|
|
36
|
-
};
|
|
37
|
-
}
|
package/dist/utils/ai.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { type CheckersBoard, type CheckersColor, type CheckersMove } from "./board.js";
|
|
2
|
-
export type CheckersAiType = "player" | "sacrifice" | "random" | "strong";
|
|
3
|
-
export declare const isHangingMove: (board: CheckersBoard, move: CheckersMove, color: CheckersColor) => boolean;
|
|
4
|
-
export declare const pickMove: (board: CheckersBoard, color: CheckersColor, type: CheckersAiType) => CheckersMove | null;
|
package/dist/utils/ai.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { applyMove, getAllMoves, oppositeColor } from "./board.js";
|
|
2
|
-
export const isHangingMove = (board, move, color) => {
|
|
3
|
-
const nextBoard = applyMove(board, move);
|
|
4
|
-
const opponentMoves = getAllMoves(nextBoard, oppositeColor(color));
|
|
5
|
-
return opponentMoves.some((candidate) => candidate.captures.length > 0);
|
|
6
|
-
};
|
|
7
|
-
const pickRandom = (items) => {
|
|
8
|
-
if (items.length === 0)
|
|
9
|
-
return null;
|
|
10
|
-
return items[Math.floor(Math.random() * items.length)] ?? null;
|
|
11
|
-
};
|
|
12
|
-
export const pickMove = (board, color, type) => {
|
|
13
|
-
if (type === "player")
|
|
14
|
-
return null;
|
|
15
|
-
const moves = getAllMoves(board, color);
|
|
16
|
-
if (moves.length === 0)
|
|
17
|
-
return null;
|
|
18
|
-
if (type === "random")
|
|
19
|
-
return pickRandom(moves);
|
|
20
|
-
const hanging = moves.filter((move) => isHangingMove(board, move, color));
|
|
21
|
-
const safe = moves.filter((move) => !isHangingMove(board, move, color));
|
|
22
|
-
if (type === "sacrifice")
|
|
23
|
-
return pickRandom(hanging.length > 0 ? hanging : moves);
|
|
24
|
-
if (type === "strong")
|
|
25
|
-
return pickRandom(safe.length > 0 ? safe : hanging.length > 0 ? hanging : moves);
|
|
26
|
-
return pickRandom(moves);
|
|
27
|
-
};
|
package/dist/utils/board.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export type CheckersColor = "white" | "black";
|
|
2
|
-
export type CheckersPiece = {
|
|
3
|
-
id: string;
|
|
4
|
-
color: CheckersColor;
|
|
5
|
-
king: boolean;
|
|
6
|
-
};
|
|
7
|
-
export type CheckersPos = {
|
|
8
|
-
row: number;
|
|
9
|
-
column: number;
|
|
10
|
-
};
|
|
11
|
-
export type CheckersMove = {
|
|
12
|
-
from: CheckersPos;
|
|
13
|
-
to: CheckersPos;
|
|
14
|
-
captures: CheckersPos[];
|
|
15
|
-
};
|
|
16
|
-
export type CheckersBoard = (CheckersPiece | null)[][];
|
|
17
|
-
export declare const BOARD_SIZE = 8;
|
|
18
|
-
export declare const oppositeColor: (color: CheckersColor) => CheckersColor;
|
|
19
|
-
export declare const isDarkSquare: (row: number, column: number) => boolean;
|
|
20
|
-
export declare const inBounds: (row: number, column: number) => boolean;
|
|
21
|
-
export declare const cloneBoard: (board: CheckersBoard) => CheckersBoard;
|
|
22
|
-
export declare const createInitialBoard: () => CheckersBoard;
|
|
23
|
-
export declare const samePos: (a: CheckersPos, b: CheckersPos) => boolean;
|
|
24
|
-
export declare const getMovesFrom: (board: CheckersBoard, from: CheckersPos) => CheckersMove[];
|
|
25
|
-
export declare const getAllMoves: (board: CheckersBoard, color: CheckersColor, onlyFrom?: CheckersPos) => CheckersMove[];
|
|
26
|
-
export declare const applyMove: (board: CheckersBoard, move: CheckersMove) => CheckersBoard;
|
|
27
|
-
/** Apply one hop of a multi-capture chain (first capture only). */
|
|
28
|
-
export declare const applyCaptureStep: (board: CheckersBoard, from: CheckersPos, to: CheckersPos, capture: CheckersPos) => CheckersBoard;
|
|
29
|
-
export declare const listBoardPieces: (board: CheckersBoard) => (CheckersPiece & CheckersPos)[];
|
|
30
|
-
export declare const countPieces: (board: CheckersBoard, color: CheckersColor) => number;
|
|
31
|
-
export declare const getWinner: (board: CheckersBoard, colorToMove: CheckersColor) => CheckersColor | null;
|
|
32
|
-
export declare const findMove: (moves: CheckersMove[], from: CheckersPos, to: CheckersPos) => CheckersMove | undefined;
|