├── .gitattributes ├── mjs ├── json_type.js ├── package.json ├── util.js └── Kakomimasu.js ├── .gitignore ├── cjs ├── package.json ├── json_type.js ├── util.js └── Kakomimasu.js ├── types ├── util.d.ts ├── json_type.d.ts └── Kakomimasu.d.ts ├── mod.ts ├── src ├── test │ ├── deps.ts │ ├── sample_test.ts │ ├── nornd.js │ ├── player_check_test.ts │ ├── mtrnd.js │ ├── field_test.ts │ ├── flow_test.ts │ ├── fillBase1_test.ts │ ├── fillBase3_test.ts │ ├── fillBase4_test.ts │ ├── game_check_test.ts │ ├── 2d-array_test.ts │ ├── action_check_test.ts │ ├── revert1_test.ts │ ├── fillBase2_test.ts │ ├── conflict4_test.ts │ ├── restore_test.ts │ ├── conflict2_test.ts │ ├── conflict5_test.ts │ ├── conflict3_test.ts │ ├── random_test.ts │ ├── conflict1_test.ts │ └── unit_test.ts ├── util.ts ├── json_type.ts └── Kakomimasu.ts ├── .vscode └── settings.json ├── .github └── workflows │ └── test.yml ├── deno.json ├── package.json ├── scripts └── dnt.ts ├── sample └── main.js ├── LICENSE ├── CHANGELOG.md ├── README.md └── deno.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * eol=lf -------------------------------------------------------------------------------- /mjs/json_type.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /mjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module"}" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | coverage 4 | coverage-html -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs"}" 3 | } 4 | -------------------------------------------------------------------------------- /types/util.d.ts: -------------------------------------------------------------------------------- 1 | export declare function flat(arr: T[][]): T[]; 2 | -------------------------------------------------------------------------------- /mjs/util.js: -------------------------------------------------------------------------------- 1 | export function flat(arr) { 2 | return [].concat(...arr); 3 | } 4 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/Kakomimasu.ts"; 2 | export * from "./src/json_type.ts"; 3 | -------------------------------------------------------------------------------- /src/test/deps.ts: -------------------------------------------------------------------------------- 1 | export * from "https://deno.land/std@0.111.0/testing/asserts.ts"; 2 | -------------------------------------------------------------------------------- /cjs/json_type.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export function flat(arr: T[][]) { 2 | return ([] as T[]).concat(...arr); 3 | } 4 | -------------------------------------------------------------------------------- /src/test/sample_test.ts: -------------------------------------------------------------------------------- 1 | Deno.test("sample/main.js", async () => { 2 | await import("../../sample/main.js"); 3 | }); 4 | -------------------------------------------------------------------------------- /cjs/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.flat = void 0; 4 | function flat(arr) { 5 | return [].concat(...arr); 6 | } 7 | exports.flat = flat; 8 | -------------------------------------------------------------------------------- /src/test/nornd.js: -------------------------------------------------------------------------------- 1 | const util = {}; 2 | 3 | let seed = 3293485835; 4 | util.rnd = (n) => { 5 | seed = (seed * 5 + 380317) & 0xfffffff; 6 | return seed % n; 7 | }; 8 | 9 | export default util; 10 | -------------------------------------------------------------------------------- /src/test/player_check_test.ts: -------------------------------------------------------------------------------- 1 | import { Player } from "../Kakomimasu.ts"; 2 | import { assertThrows } from "./deps.ts"; 3 | 4 | Deno.test("Player: setActions before game attached", () => { 5 | const p1 = new Player("test1"); 6 | const fn = () => p1.setActions([]); 7 | assertThrows(fn, Error, "game is null"); 8 | }); 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": false, 5 | "deno.codeLens.referencesAllFunctions": true, 6 | "deno.codeLens.implementations": true, 7 | "deno.codeLens.references": true, 8 | "editor.defaultFormatter": "denoland.vscode-deno", 9 | "search.exclude": { 10 | "cjs": true, 11 | "mjs": true, 12 | "types": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/mtrnd.js: -------------------------------------------------------------------------------- 1 | import { MersenneTwister } from "https://code4sabae.github.io/js/MersenneTwister.js"; 2 | //import { MersenneTwister } from "http://127.0.0.1:8080/MersenneTwister.js"; 3 | 4 | const mt = new MersenneTwister(0); 5 | // console.log(mt.nextInt()); 6 | // Deno.exit(0); 7 | 8 | const util = {}; 9 | 10 | /* 11 | let seed = 3293485835; 12 | util.rnd = (n) => { 13 | seed = (seed * 5 + 380317) & 0xfffffff; 14 | return seed % n;; 15 | }; 16 | */ 17 | 18 | util.rnd = (n) => { 19 | return mt.nextInt() % n; 20 | }; 21 | 22 | export default util; 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: denoland/setup-deno@v1 14 | with: 15 | deno-version: v1.x 16 | - name: Run fmt 17 | run: | 18 | deno fmt -c deno.json --check 19 | - name: Run lint 20 | run: | 21 | deno lint -c deno.json 22 | - name: Run test 23 | run: | 24 | deno test -A 25 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "fmt": { 3 | "files": { 4 | "exclude": ["cjs", "mjs", "types", "package.json"] 5 | } 6 | }, 7 | "lint": { 8 | "files": { 9 | "exclude": ["cjs", "mjs", "types"] 10 | } 11 | }, 12 | "tasks": { 13 | "dnt": "deno run -A scripts/dnt.ts", 14 | "test": "rm -rf coverage && deno test --allow-read --coverage=coverage --parallel", 15 | "cov": "deno coverage ./coverage --lcov --output=coverage/coverage.lcov", 16 | "cov:report": "genhtml -o coverage-html coverage/coverage.lcov", 17 | "test-report": "deno task test ; deno task cov && deno task cov:report" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/field_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Field } from "../Kakomimasu.ts"; 2 | import { assertThrows } from "./deps.ts"; 3 | 4 | Deno.test("Field: set invalid playerid", () => { 5 | const [width, height] = [3, 1]; 6 | const board: Board = { width, height, points: new Array(width * height) }; 7 | 8 | const field = new Field(board); 9 | 10 | assertThrows(() => { 11 | field.set(0, 0, Field.WALL, -1); 12 | }); 13 | }); 14 | 15 | Deno.test("Field: invalid points.length", () => { 16 | const fn = () => { 17 | new Field({ 18 | width: 3, 19 | height: 3, 20 | points: new Array(2).fill(0), 21 | }); 22 | }; 23 | assertThrows(fn, Error, "points.length must be 9"); 24 | }); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kakomimasu/core", 3 | "version": "v2.0.0", 4 | "description": "Kakomimasu core module for js/ts(browser/deno/node)", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/codeforkosen/Kakomimasu" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/codeforkosen/Kakomimasu/issues" 12 | }, 13 | "dependencies": {}, 14 | "main": "./cjs/Kakomimasu.js", 15 | "module": "./mjs/Kakomimasu.js", 16 | "types": "./types/Kakomimasu.d.ts", 17 | "exports": { 18 | ".": { 19 | "import": "./mjs/Kakomimasu.js", 20 | "require": "./cjs/Kakomimasu.js", 21 | "types": "./types/Kakomimasu.d.ts" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /scripts/dnt.ts: -------------------------------------------------------------------------------- 1 | // Deno to Node transform program 2 | import { build } from "https://deno.land/x/dnt@0.0.9/mod.ts"; 3 | 4 | const version = Deno.args[0]; 5 | if (!version) { 6 | throw Error("Version args need."); 7 | } 8 | 9 | await build({ 10 | entryPoint: "./src/Kakomimasu.ts", 11 | outDir: ".", 12 | typeCheck: true, 13 | package: { 14 | // package.json properties 15 | name: "@kakomimasu/core", 16 | version, 17 | description: "Kakomimasu core module for js/ts(browser/deno/node)", 18 | license: "MIT", 19 | repository: { 20 | type: "git", 21 | url: "git+https://github.com/codeforkosen/Kakomimasu", 22 | }, 23 | bugs: { 24 | url: "https://github.com/codeforkosen/Kakomimasu/issues", 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /sample/main.js: -------------------------------------------------------------------------------- 1 | import { Action, Game, Player } from "../mod.ts"; 2 | 3 | const width = 8; 4 | const height = 8; 5 | const points = []; 6 | for (let i = 0; i < width * height; i++) { 7 | points[i] = i; 8 | } 9 | const nAgent = 6; 10 | const totalTurn = 10; 11 | const board = { width, height, points, nAgent, totalTurn }; 12 | 13 | const game = new Game(board); 14 | const p1 = new Player("test1"); 15 | const p2 = new Player("test2"); 16 | game.attachPlayer(p1); 17 | game.attachPlayer(p2); 18 | game.start(); 19 | for (;;) { 20 | p1.setActions(Action.fromArray([ 21 | [0, Action.PUT, 1, 1], 22 | [0, Action.MOVE, 2, 2], 23 | ])); 24 | p2.setActions(Action.fromArray([ 25 | [0, Action.PUT, 1, 1], 26 | [1, Action.PUT, 1, 2], 27 | ])); 28 | if (!game.nextTurn()) { 29 | break; 30 | } 31 | } 32 | console.log(game); 33 | -------------------------------------------------------------------------------- /src/json_type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ActionRes, 3 | ActionType, 4 | FieldInit, 5 | FieldTile, 6 | Point, 7 | } from "./Kakomimasu.ts"; 8 | 9 | export interface AgentJson { 10 | x: number; 11 | y: number; 12 | } 13 | 14 | export interface ActionJson { 15 | agentId: number; 16 | type: ActionType; 17 | x: number; 18 | y: number; 19 | res: ActionRes; 20 | } 21 | 22 | export type FieldJson = Required & { 23 | tiles: FieldTile[]; 24 | }; 25 | 26 | export interface PlayerJson { 27 | id: string; 28 | spec: string; 29 | actions: ActionJson[]; 30 | index: number; 31 | agents: AgentJson[]; 32 | } 33 | 34 | export interface GameJson { 35 | turn: number; 36 | totalTurn: number; 37 | field: FieldJson; 38 | players: PlayerJson[]; 39 | log: { 40 | players: { 41 | point: Point; 42 | actions: ActionJson[]; 43 | }[]; 44 | }[]; 45 | } 46 | -------------------------------------------------------------------------------- /types/json_type.d.ts: -------------------------------------------------------------------------------- 1 | import type { ActionRes, ActionType, FieldInit, FieldTile, Point } from "./Kakomimasu.js"; 2 | export interface AgentJson { 3 | x: number; 4 | y: number; 5 | } 6 | export interface ActionJson { 7 | agentId: number; 8 | type: ActionType; 9 | x: number; 10 | y: number; 11 | res: ActionRes; 12 | } 13 | export declare type FieldJson = Required & { 14 | tiles: FieldTile[]; 15 | }; 16 | export interface PlayerJson { 17 | id: string; 18 | spec: string; 19 | actions: ActionJson[]; 20 | index: number; 21 | agents: AgentJson[]; 22 | } 23 | export interface GameJson { 24 | turn: number; 25 | totalTurn: number; 26 | field: FieldJson; 27 | players: PlayerJson[]; 28 | log: { 29 | players: { 30 | point: Point; 31 | actions: ActionJson[]; 32 | }[]; 33 | }[]; 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Taisuke Fukuno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/flow_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assertEquals } from "./deps.ts"; 3 | //import util from "../util.mjs"; 4 | 5 | const cl = (...a: Parameters) => { 6 | a; 7 | }; //console.log(...a); 8 | 9 | Deno.test("flow", () => { 10 | //test("flow", () => { 11 | const width = 8; 12 | const height = 8; 13 | const points = []; 14 | for (let i = 0; i < width * height; i++) { 15 | points[i] = i; 16 | // points[i] = i % (16 * 2 + 1) - 16; 17 | // util.rnd(16 * 2 + 1) - 16; 18 | } 19 | const nAgent = 6; 20 | const totalTurn = 10; 21 | const board: Board = { width, height, points, nAgent, totalTurn }; 22 | 23 | const game = new Game(board); 24 | const p1 = new Player("test1"); 25 | const p2 = new Player("test2"); 26 | game.attachPlayer(p1); 27 | game.attachPlayer(p2); 28 | game.start(); 29 | for (;;) { 30 | const _st = game; 31 | // console.log(st); 32 | p1.setActions(Action.fromArray([ 33 | [0, Action.PUT, 1, 1], 34 | [0, Action.MOVE, 2, 2], 35 | ])); 36 | p2.setActions(Action.fromArray([ 37 | [0, Action.PUT, 1, 1], // point 9 38 | [1, Action.PUT, 1, 2], // point 17 39 | [2, Action.PUT, 1, 3], // point 25 40 | [10, Action.PUT, 2, 2], // point 18(failed) 41 | //total 9+17+25=51 42 | ])); 43 | if (!game.nextTurn()) { 44 | break; 45 | } 46 | } 47 | assertEquals(game.log.length, totalTurn); 48 | cl(game.field.points); 49 | assertEquals(game.log.at(-1)?.players.map((p) => p.point), [{ 50 | areaPoint: 0, 51 | wallPoint: 0, 52 | }, { 53 | areaPoint: 0, 54 | wallPoint: 51, 55 | }]); 56 | // util.p(game.getStatusJSON()); 57 | }); 58 | -------------------------------------------------------------------------------- /src/test/fillBase1_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Field } from "../Kakomimasu.ts"; 2 | import { AssertionError } from "./deps.ts"; 3 | 4 | const cl = (...a: Parameters) => { 5 | a; 6 | }; //console.log(...a); 7 | 8 | Deno.test("fill1", () => { 9 | const nAgent = 6; 10 | const [width, height] = [3, 3]; 11 | const board: Board = { 12 | width, 13 | height, 14 | points: new Array(width * height), 15 | nAgent, 16 | }; 17 | const field = new Field(board); 18 | 19 | const p = () => { 20 | for (let i = 0; i < height; i++) { 21 | const s = []; 22 | for (let j = 0; j < width; j++) { 23 | const n = field.tiles[j + i * width]; 24 | s.push( 25 | "_W".charAt(n.type) + (n.player === null ? "." : n.player).toString(), 26 | ); 27 | } 28 | cl(s.join(" ")); 29 | } 30 | cl(); 31 | }; 32 | const set = (s: string) => { 33 | s = s.replace(/\n/g, ""); 34 | for (let i = 0; i < s.length; i++) { 35 | const c = s.charAt(i); 36 | if (c === "0") { 37 | field.tiles[i] = { type: Field.WALL, player: 0 }; 38 | } else if (c === "1") { 39 | field.tiles[i] = { type: Field.WALL, player: 1 }; 40 | } 41 | } 42 | }; 43 | const chk = (s: string) => { 44 | s = s.replace(/\n/g, ""); 45 | for (let i = 0; i < s.length; i++) { 46 | const c = s.charAt(i); 47 | if (c !== ".") { 48 | const n = parseInt(c); 49 | const f = field.tiles[i]; 50 | if (f.type !== Field.AREA || f.player !== n) { 51 | throw new AssertionError(""); 52 | } 53 | } 54 | } 55 | }; 56 | 57 | p(); 58 | 59 | set(` 60 | 000 61 | 0.0 62 | 000 63 | `); 64 | 65 | p(); 66 | 67 | field.fillArea(); 68 | 69 | p(); 70 | 71 | chk(` 72 | ... 73 | .0. 74 | ... 75 | `); 76 | }); 77 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to 7 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [v2.0.0-beta.0] 10 | 11 | ### Added 12 | 13 | - Node.jsで使用できるようにcjs,mjsのファイル、`package.json`を追加 14 | 15 | ### Changed 16 | 17 | - Node.jsモジュール化によるDeno向けエントリーポイントの変更(`./Kakomimasu.js`->`./mod.ts`) 18 | - Boardクラスのコンストラクタ引数をオブジェクトのみ受け取るように変更 19 | - Gameクラス内の2次元配列を削除(型定義を以下のように変更) 20 | 21 | ```diff 22 | Field.fieldの型を以下のように変更 23 | - type FieldType = 0 | 1; 24 | + type FieldType = typeof Field.BASE | typeof Field.WALL; 25 | 26 | - type FieldCell = [FieldType, number]; 27 | + type FieldCell = { type: FieldType; player: null | number }; 28 | ``` 29 | 30 | ```diff 31 | Game.logの型を以下のように変更 32 | - public log: { 33 | - point: { basepoint: number; wallpoint: number }; 34 | - actions: ReturnType[]; 35 | - }[][]; 36 | 37 | + public log: { 38 | + players: { 39 | + point: { basepoint: number; wallpoint: number }; 40 | + actions: ReturnType[]; 41 | + }[]; 42 | + }[]; 43 | ``` 44 | 45 | ```diff 46 | Game.agentsをPlayers.agentsに移行 47 | - game.agents[i] 48 | + game.players[i].agents 49 | ``` 50 | 51 | ### Deprecated 52 | 53 | - Kakomimasuクラスの`createGame`,`createPlayer`を非推奨関数に変更(クラス継承時の型定義が上手くいかないため) 54 | 55 | ```diff 56 | - const game = kkmm.createGame(board); 57 | + const game = new Game(board); 58 | + kkmm.addGame(board); 59 | ``` 60 | 61 | ```diff 62 | - const player = kkmm.createPlayer(...param); 63 | + const player = new Player(...param); 64 | 65 | // createPlayerはKakomimasuクラスとは独立していたため`addPlayer`は無しでよい。 66 | ``` 67 | 68 | ## [1.0.0] - 2021-09-27 69 | 70 | ### Added 71 | 72 | - v1.0.0 Release 73 | -------------------------------------------------------------------------------- /src/test/fillBase3_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Field } from "../Kakomimasu.ts"; 2 | import { AssertionError } from "./deps.ts"; 3 | 4 | const cl = (...a: Parameters) => { 5 | a; 6 | }; //console.log(...a); 7 | 8 | Deno.test("fill2", () => { 9 | const nAgent = 6; 10 | const [width, height] = [3, 3]; 11 | const board: Board = { 12 | width, 13 | height, 14 | points: new Array(width * height), 15 | nAgent, 16 | }; 17 | const field = new Field(board); 18 | 19 | const p = () => { 20 | for (let i = 0; i < height; i++) { 21 | const s = []; 22 | for (let j = 0; j < width; j++) { 23 | const n = field.tiles[j + i * width]; 24 | s.push( 25 | "_W".charAt(n.type) + (n.player === null ? "." : n.player).toString(), 26 | ); 27 | } 28 | cl(s.join(" ")); 29 | } 30 | cl(); 31 | }; 32 | const set = (s: string) => { 33 | s = s.replace(/\n/g, ""); 34 | for (let i = 0; i < s.length; i++) { 35 | const c = s.charAt(i); 36 | if (c === "0") { 37 | field.tiles[i] = { type: Field.WALL, player: 0 }; 38 | } else if (c === "1") { 39 | field.tiles[i] = { type: Field.WALL, player: 1 }; 40 | } 41 | } 42 | }; 43 | const chk = (s: string) => { 44 | s = s.replace(/\n/g, ""); 45 | for (let i = 0; i < s.length; i += 2) { 46 | const c = s.charAt(i) === "W" ? Field.WALL : Field.AREA; 47 | const n = s.charAt(i + 1) === "." ? null : parseInt(s.charAt(i + 1)); 48 | const f = field.tiles[i / 2]; 49 | if (f.type !== c || f.player !== n) { 50 | throw new AssertionError(""); 51 | } 52 | } 53 | }; 54 | 55 | p(); 56 | 57 | set(` 58 | 000 59 | 0.0 60 | 00. 61 | `); 62 | 63 | p(); 64 | 65 | field.fillArea(); 66 | 67 | p(); 68 | 69 | chk(` 70 | W0W0W0 71 | W0_.W0 72 | W0W0_. 73 | `); 74 | }); 75 | -------------------------------------------------------------------------------- /src/test/fillBase4_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Field } from "../Kakomimasu.ts"; 2 | import { AssertionError } from "./deps.ts"; 3 | 4 | const cl = (...a: Parameters) => { 5 | console.log(...a); //a; 6 | }; //console.log(...a); 7 | 8 | Deno.test("fill1", () => { 9 | const nAgent = 6; 10 | const [width, height] = [3, 4]; 11 | const board: Board = { 12 | width, 13 | height, 14 | points: new Array(width * height), 15 | nAgent, 16 | }; 17 | const field = new Field(board); 18 | 19 | const p = () => { 20 | for (let i = 0; i < height; i++) { 21 | const s = []; 22 | for (let j = 0; j < width; j++) { 23 | const n = field.tiles[j + i * width]; 24 | s.push( 25 | "_W".charAt(n.type) + (n.player === null ? "." : n.player).toString(), 26 | ); 27 | } 28 | cl(s.join(" ")); 29 | } 30 | cl(); 31 | }; 32 | const set = (s: string) => { 33 | s = s.replace(/\n/g, ""); 34 | for (let i = 0; i < s.length; i++) { 35 | const c = s.charAt(i); 36 | if (c === "0") { 37 | field.tiles[i] = { type: Field.WALL, player: 0 }; 38 | } else if (c === "1") { 39 | field.tiles[i] = { type: Field.WALL, player: 1 }; 40 | } 41 | } 42 | }; 43 | const chk = (s: string) => { 44 | s = s.replace(/\n/g, ""); 45 | for (let i = 0; i < s.length; i++) { 46 | const c = s.charAt(i); 47 | if (c !== ".") { 48 | const n = parseInt(c); 49 | const f = field.tiles[i]; 50 | if (f.type !== Field.AREA || f.player !== n) { 51 | throw new AssertionError(""); 52 | } 53 | } 54 | } 55 | }; 56 | 57 | p(); 58 | 59 | set(` 60 | 000 61 | 0.0 62 | 000 63 | ... 64 | `); 65 | 66 | p(); 67 | 68 | field.fillArea(); 69 | 70 | p(); 71 | 72 | chk(` 73 | ... 74 | .0. 75 | ... 76 | ... 77 | `); 78 | }); 79 | -------------------------------------------------------------------------------- /src/test/game_check_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assertEquals } from "./deps.ts"; 3 | 4 | Deno.test("Game: too much attach", () => { 5 | const [width, height] = [3, 1]; 6 | const board: Board = { width, height, points: new Array(width * height) }; 7 | 8 | const game = new Game(board); 9 | 10 | assertEquals(game.attachPlayer(new Player("test1")), true); 11 | assertEquals(game.attachPlayer(new Player("test2")), true); 12 | assertEquals(game.attachPlayer(new Player("test3")), false); 13 | }); 14 | 15 | Deno.test("Game: attach same player", () => { 16 | const [width, height] = [3, 1]; 17 | const board: Board = { width, height, points: new Array(width * height) }; 18 | 19 | const game = new Game(board); 20 | 21 | const p = new Player("test1"); 22 | 23 | assertEquals(game.attachPlayer(p), true); 24 | assertEquals(game.attachPlayer(p), false); 25 | }); 26 | 27 | Deno.test("Game: status check", () => { 28 | const [width, height] = [3, 1]; 29 | const board: Board = { width, height, points: new Array(width * height) }; 30 | 31 | const game = new Game(board); 32 | 33 | const check = ( 34 | isFree: boolean, 35 | isReady: boolean, 36 | isGaming: boolean, 37 | isEnded: boolean, 38 | ) => { 39 | assertEquals(game.isFree(), isFree); 40 | assertEquals(game.isReady(), isReady); 41 | assertEquals(game.isGaming(), isGaming); 42 | assertEquals(game.isEnded(), isEnded); 43 | }; 44 | 45 | check(true, false, false, false); 46 | game.attachPlayer(new Player("test1")); 47 | check(true, false, false, false); 48 | game.attachPlayer(new Player("test2")); 49 | check(false, true, false, false); 50 | game.start(); 51 | check(false, false, true, false); 52 | while (game.nextTurn()) { 53 | check(false, false, true, false); 54 | } 55 | check(false, false, false, true); 56 | }); 57 | -------------------------------------------------------------------------------- /src/test/2d-array_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assertEquals } from "./deps.ts"; 3 | 4 | const cl = (...a: Parameters) => { 5 | a; 6 | }; //console.log(...a); 7 | 8 | Deno.test("two-dimensional array check", () => { 9 | const width = 8; 10 | const height = 8; 11 | const points = []; 12 | for (let i = 0; i < width * height; i++) points[i] = i; 13 | const nAgent = 6; 14 | const totalTurn = 10; 15 | const board: Board = { width, height, points, nAgent, totalTurn }; 16 | 17 | const game = new Game(board); 18 | const p1 = new Player("test1"); 19 | const p2 = new Player("test2"); 20 | game.attachPlayer(p1); 21 | game.attachPlayer(p2); 22 | game.start(); 23 | for (;;) { 24 | const _st = game; 25 | // console.log(st); 26 | p1.setActions(Action.fromArray([ 27 | [0, Action.PUT, 1, 1], 28 | [0, Action.MOVE, 2, 2], 29 | ])); 30 | p2.setActions(Action.fromArray([ 31 | [0, Action.PUT, 1, 1], // point 9 32 | [1, Action.PUT, 1, 2], // point 17 33 | [2, Action.PUT, 1, 3], // point 25 34 | [10, Action.PUT, 2, 2], // point 18(failed) 35 | //total 9+17+25=51 36 | ])); 37 | if (!game.nextTurn()) { 38 | break; 39 | } 40 | } 41 | assertEquals(game.log.length, totalTurn); 42 | cl(game.field.points); 43 | assertEquals(game.log.at(-1)?.players.map((p) => p.point), [{ 44 | areaPoint: 0, 45 | wallPoint: 0, 46 | }, { 47 | areaPoint: 0, 48 | wallPoint: 51, 49 | }]); 50 | // util.p(game.getStatusJSON()); 51 | 52 | const log = JSON.parse(JSON.stringify(game)); 53 | test2dArray(log); 54 | }); 55 | 56 | function test2dArray( 57 | obj: unknown, 58 | [prevKey, prevValue]: [string, unknown] = ["", {}], 59 | ) { 60 | if (typeof obj !== "object") return; 61 | if (!obj) return; 62 | Object.entries(obj).forEach(([k, v]) => { 63 | if (typeof v === "object") { 64 | if (Array.isArray(v)) { 65 | if (Array.isArray(prevValue)) { 66 | throw Error(`Found 2d-array : ${prevKey}`); 67 | } else test2dArray(v, [k, v]); 68 | } else test2dArray(v); 69 | } 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /src/test/action_check_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | const tos = (game: Game) => { 5 | const isOnAgent = (p: number, x: number, y: number) => { 6 | let cnt = 0; 7 | for (const a of game.players[p].agents) { 8 | if (a.x === x && a.y === y) { 9 | cnt++; 10 | } 11 | } 12 | if (cnt === 1) { 13 | return true; 14 | } 15 | if (cnt === 0) { 16 | return false; 17 | } 18 | throw new AssertionError("agent conflict!! cnt:" + cnt); 19 | }; 20 | 21 | const { height, width } = game.field; 22 | const res = []; 23 | 24 | for (let i = 0; i < height; i++) { 25 | const s = []; 26 | for (let j = 0; j < width; j++) { 27 | const n = game.field.tiles[j + i * width]; 28 | const a0 = isOnAgent(0, j, i); 29 | const a1 = isOnAgent(1, j, i); 30 | if (a0 && a1) { 31 | throw new AssertionError("agent conflict!!"); 32 | } 33 | const a = a0 ? "0" : (a1 ? "1" : "."); 34 | s.push( 35 | "_W".charAt(n.type) + 36 | (n.player === null ? "." : n.player).toString() + 37 | a, 38 | ); 39 | } 40 | res.push(s.join(" ")); 41 | } 42 | return res.join("\n"); 43 | }; 44 | 45 | Deno.test("Action: NONE", () => { 46 | const [width, height] = [3, 1]; 47 | const board: Board = { width, height, points: new Array(width * height) }; 48 | 49 | const game = new Game(board); 50 | 51 | const p1 = new Player("test1"); 52 | const p2 = new Player("test2"); 53 | game.attachPlayer(p1); 54 | game.attachPlayer(p2); 55 | game.start(); 56 | 57 | p1.setActions(Action.fromArray([ 58 | [0, Action.NONE, 0, 0], 59 | ])); 60 | assert(game.nextTurn()); 61 | assertEquals("_.. _.. _..", tos(game)); 62 | }); 63 | 64 | Deno.test("Action: REMOVE checkOnBoard", () => { 65 | const [width, height] = [3, 1]; 66 | const board: Board = { width, height, points: new Array(width * height) }; 67 | 68 | const game = new Game(board); 69 | 70 | const p1 = new Player("test1"); 71 | const p2 = new Player("test2"); 72 | game.attachPlayer(p1); 73 | game.attachPlayer(p2); 74 | game.start(); 75 | 76 | p1.setActions(Action.fromArray([[0, Action.PUT, 2, 0]])); 77 | assert(game.nextTurn()); 78 | assertEquals("_.. _.. W00", tos(game)); 79 | 80 | const action = new Action(0, Action.REMOVE, 3, 0); 81 | p1.setActions([action]); 82 | assert(game.nextTurn()); 83 | assertEquals(action.res, Action.ERR_ILLEGAL_ACTION); 84 | assertEquals("_.. _.. W00", tos(game)); 85 | }); 86 | -------------------------------------------------------------------------------- /src/test/revert1_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | const cl = (...a: Parameters) => { 5 | a; 6 | }; //console.log(...a); 7 | 8 | Deno.test("revert1", () => { 9 | const nAgent = 6; 10 | const [width, height] = [3, 1]; 11 | const totalTurn = 20; 12 | const board: Board = { 13 | width, 14 | height, 15 | points: new Array(width * height), 16 | nAgent, 17 | totalTurn, 18 | }; 19 | 20 | const game = new Game(board); 21 | 22 | const field = game.field; 23 | 24 | const p1 = new Player("test1"); 25 | const p2 = new Player("test2"); 26 | game.attachPlayer(p1); 27 | game.attachPlayer(p2); 28 | game.start(); 29 | 30 | const isOnAgent = (p: number, x: number, y: number) => { 31 | let cnt = 0; 32 | for (const a of game.players[p].agents) { 33 | if (a.x === x && a.y === y) { 34 | cnt++; 35 | } 36 | } 37 | if (cnt === 1) { 38 | return true; 39 | } 40 | if (cnt === 0) { 41 | return false; 42 | } 43 | throw new AssertionError("agent conflict!! cnt:" + cnt); 44 | }; 45 | 46 | const tos = () => { 47 | const res = []; 48 | for (let i = 0; i < height; i++) { 49 | const s = []; 50 | for (let j = 0; j < width; j++) { 51 | const n = field.tiles[j + i * width]; 52 | const a0 = isOnAgent(0, j, i); 53 | const a1 = isOnAgent(1, j, i); 54 | if (a0 && a1) { 55 | throw new AssertionError("agent conflict!!"); 56 | } 57 | const a = a0 ? "0" : (a1 ? "1" : "."); 58 | s.push( 59 | "_W".charAt(n.type) + 60 | (n.player === null ? "." : n.player).toString() + 61 | a, 62 | ); 63 | } 64 | res.push(s.join(" ")); 65 | } 66 | return res.join("\n"); 67 | }; 68 | const p = () => cl(tos()); 69 | const chk = (s: string) => assertEquals(s.trim(), tos()); 70 | 71 | // put 72 | p1.setActions(Action.fromArray([ 73 | [0, Action.PUT, 0, 0], 74 | [1, Action.PUT, 1, 0], 75 | ])); 76 | assert(game.nextTurn()); 77 | p(); 78 | chk("W00 W00 _.."); 79 | 80 | // move x2 81 | p1.setActions(Action.fromArray([ 82 | [0, Action.MOVE, 1, 0], 83 | [1, Action.MOVE, 2, 0], 84 | ])); 85 | assert(game.nextTurn()); 86 | p(); 87 | chk("W0. W00 W00"); 88 | 89 | // move x2 revert 90 | p1.setActions(Action.fromArray([ 91 | [0, Action.MOVE, 2, 0], 92 | [1, Action.MOVE, 3, 0], 93 | ])); 94 | assert(game.nextTurn()); 95 | p(); 96 | chk("W0. W00 W00"); 97 | 98 | // finish 99 | while (game.nextTurn()); 100 | }); 101 | -------------------------------------------------------------------------------- /src/test/fillBase2_test.ts: -------------------------------------------------------------------------------- 1 | import { Board, Field } from "../Kakomimasu.ts"; 2 | 3 | const cl = (...a: Parameters) => { 4 | a; 5 | }; //console.log(...a); 6 | 7 | Deno.test("fill2", () => { 8 | const nAgent = 6; 9 | const [width, height] = [8, 8]; 10 | const board: Board = { 11 | width, 12 | height, 13 | points: new Array(width * height), 14 | nAgent, 15 | }; 16 | const field = new Field(board); 17 | 18 | const p = () => { 19 | for (let i = 0; i < height; i++) { 20 | const s = []; 21 | for (let j = 0; j < width; j++) { 22 | const n = field.tiles[j + i * width]; 23 | s.push( 24 | "_W".charAt(n.type) + (n.player === null ? "." : n.player).toString(), 25 | ); 26 | } 27 | cl(s.join(" ")); 28 | } 29 | cl(); 30 | }; 31 | const set = (s: string) => { 32 | s = s.replace(/\n/g, ""); 33 | for (let i = 0; i < s.length; i++) { 34 | const c = s.charAt(i); 35 | if (c === "0") { 36 | field.tiles[i] = { type: Field.WALL, player: 0 }; 37 | } else if (c === "1") { 38 | field.tiles[i] = { type: Field.WALL, player: 1 }; 39 | } else { 40 | field.tiles[i] = { type: Field.AREA, player: null }; 41 | } 42 | } 43 | }; 44 | const chk = (s: string) => { 45 | s = s.replace(/\n/g, ""); 46 | for (let i = 0; i < s.length; i++) { 47 | const c = s.charAt(i); 48 | if (c !== ".") { 49 | const n = parseInt(c); 50 | const f = field.tiles[i]; 51 | if (f.type !== Field.AREA || f.player !== n) { 52 | throw new Error(); 53 | } 54 | } 55 | } 56 | }; 57 | 58 | p(); 59 | 60 | // test 1 61 | set(` 62 | .00000.. 63 | 00...00. 64 | 0.1..00. 65 | 0.....00 66 | 0.1...0. 67 | 0.....0. 68 | 00..000. 69 | .00000.. 70 | `); 71 | 72 | p(); 73 | field.fillArea(); 74 | 75 | p(); 76 | 77 | chk(` 78 | ........ 79 | ..000... 80 | .0.00... 81 | .00000.. 82 | .0.000.. 83 | .00000.. 84 | ..00.... 85 | ........ 86 | `); 87 | 88 | // test2 89 | 90 | set(` 91 | .00000.. 92 | 00...00. 93 | 0.1..00. 94 | 0......0 95 | 0.1...0. 96 | 0.....0. 97 | 00..000. 98 | .00000.. 99 | `); 100 | 101 | p(); 102 | field.fillArea(); 103 | p(); 104 | 105 | chk(` 106 | ........ 107 | ........ 108 | ........ 109 | ........ 110 | ........ 111 | ........ 112 | ........ 113 | ........ 114 | `); 115 | 116 | // test3 117 | 118 | set(` 119 | .00000.. 120 | 00...00. 121 | 0.11100. 122 | 0.1.1.00 123 | 0.1.1.0. 124 | 0.111.0. 125 | 00..000. 126 | .00000.. 127 | `); 128 | 129 | p(); 130 | field.fillArea(); 131 | p(); 132 | 133 | chk(` 134 | ........ 135 | ..000... 136 | .0...... 137 | .0.1.0.. 138 | .0.1.0.. 139 | .0...0.. 140 | ..00.... 141 | ........ 142 | `); 143 | }); 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kakomimasu 2 | 3 | #procon31 競技部門 コアモジュール for 4 | [Deno](https://deno.land/)/[Node.js](https://nodejs.org/ja/)/web\ 5 | 6 | 7 | ![.github/workflows/test.yml](https://github.com/codeforkosen/Kakomimasu/workflows/.github/workflows/test.yml/badge.svg) 8 | [![deno](https://img.shields.io/static/v1?logo=deno&label=Deno&message=1.13.2)](https://deno.land/) 9 | 10 | ## 競技部門ルール 11 | 12 | [#procon31の競技ルール](http://www.procon.gr.jp/?p=77044)を元に作成しました。下記URLからご覧ください。 13 | 14 | https://hackmd.io/cBAzsJkoSp6c6N5Vggo7VA 15 | 16 | ## 関連ツール・リポジトリ 17 | 18 | ### kakomimasu client for Deno 19 | 20 | [囲みマス サーバ](#kakomimasu-server)に参戦するためのDeno用クライアントです。 21 | 22 | [kakomimasu/client-deno - Github](https://github.com/kakomimasu/client-deno) 23 | 24 | ### kakomimasu client for C++ 25 | 26 | [囲みマス サーバ](#kakomimasu-server)に参戦するためのC++用クライアントです。 27 | 28 | [kakomimasu/client-cpp - Github](https://github.com/kakomimasu/client-cpp) 29 | 30 | ### kakomimasu server 31 | 32 | オンラインで対戦可能なサーバのコードです。 33 | 34 | [kakomimasu/server - Github](https://github.com/kakomimasu/server) 35 | 36 | ### kakomimasu viewer 37 | 38 | [囲みマスビューア](https://kakomimasu.com)のコードです。 39 | 40 | [kakomimasu/viewer - Github](https://github.com/kakomimasu/viewer) 41 | 42 | ## サポート Discord 43 | 44 | 囲みマス 公式Discord 招待リンク\ 45 | https://discord.gg/283ZvKPcUD 46 | 47 | ## 人vs人で遊んでみる 48 | 49 | http://2ndpinew.site/d/test/kakomimasu/local/v0/?w=10&h=10&nAgent=6&endTurn=10&positiveRatio=80&min=-16&max=9 50 | 51 | ## コア利用方法(コアのみ使用する) 52 | 53 | ### for Deno 54 | 55 | ```typescript 56 | import { 57 | Action, 58 | Board, 59 | Game, 60 | Player, 61 | } from "https://raw.githubusercontent.com/codeforkosen/Kakomimasu/${version}/mod.ts"; 62 | const board: Board = { 63 | width: 10, 64 | height: 10, 65 | points: new Array(width * height), 66 | }; 67 | const game = new Game(board); 68 | ``` 69 | 70 | ## コア利用方法(リポジトリを取得し、ローカルで使用する) 71 | 72 | ### for Deno 73 | 74 | ```console 75 | $ git clone https://github.com/codeforkosen/Kakomimasu.git 76 | ``` 77 | 78 | `sample/main.js` を編集(そのままでも動きます) 79 | 80 | コンソールにて 81 | 82 | ```console 83 | $ deno run sample/main.js 84 | ``` 85 | 86 | ## コアテスト 87 | 88 | ```console 89 | $ deno task test 90 | ``` 91 | 92 | ## その他 93 | 94 | ### Online版 95 | 96 | Kakomimasuコアを利用したAPIを通してリアルタイム対戦可能なオンラインサーバが公開されています。 97 | 98 | 詳しくはこちら:[kakomimasu.com](https://kakomimasu.com) 99 | 100 | ### スマホアプリデザイン案 101 | 102 | Kakomimasu – Figma\ 103 | https://www.figma.com/file/oWmSSWHCkRUS3a4h1URvx3/Kakomimasu 104 | 105 | ## 出典 106 | 107 | 高専プロコン第31回苫小牧大会\ 108 | http://www.procon.gr.jp/ 109 | 110 | ## 関連記事 111 | 112 | 2020-06-02 中止になった高専プロコン競技部門はオンラインで遊ぼう! 113 | 競技システムのDeno/Node.js用コアモジュールのオープンソース公開\ 114 | https://fukuno.jig.jp/2869 115 | 116 | 2020-06-09 117 | 高専プロコン競技部門を勝手に開催する会オンラインハックデーの進捗、プロトコル、デザイン、Denoで30行のAPIサーバーのモック\ 118 | https://fukuno.jig.jp/2876 119 | 120 | 遊んでくれる人、協力者募集! 121 | -------------------------------------------------------------------------------- /src/test/conflict4_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | Deno.test("conflict4", () => { 5 | const nAgent = 2; 6 | const totalTurn = 30; 7 | const [width, height] = [3, 2]; 8 | const board: Board = { 9 | width, 10 | height, 11 | points: new Array(width * height), 12 | nAgent, 13 | totalTurn, 14 | }; 15 | 16 | const game = new Game(board); 17 | 18 | const field = game.field; 19 | 20 | const p1 = new Player("test1"); 21 | const p2 = new Player("test2"); 22 | game.attachPlayer(p1); 23 | game.attachPlayer(p2); 24 | game.start(); 25 | 26 | const cl = (...a: Parameters) => { 27 | a; 28 | }; //console.log(...a); 29 | 30 | const _showAgents = () => { 31 | let i = 0; 32 | for (const player of game.players) { 33 | let j = 0; 34 | for (const a of player.agents) { 35 | console.log("pid", i, "aid", j, a.x, a.y); 36 | j++; 37 | } 38 | i++; 39 | } 40 | }; 41 | 42 | const isOnAgent = (p: number, x: number, y: number) => { 43 | let cnt = 0; 44 | for (const a of game.players[p].agents) { 45 | if (a.x === x && a.y === y) { 46 | cnt++; 47 | } 48 | } 49 | if (cnt === 1) { 50 | return true; 51 | } 52 | if (cnt === 0) { 53 | return false; 54 | } 55 | throw new AssertionError("agent conflict!! cnt:" + cnt); 56 | }; 57 | 58 | const tos = () => { 59 | const res = []; 60 | for (let i = 0; i < height; i++) { 61 | const s = []; 62 | for (let j = 0; j < width; j++) { 63 | const n = field.tiles[j + i * width]; 64 | const a0 = isOnAgent(0, j, i); 65 | const a1 = isOnAgent(1, j, i); 66 | if (a0 && a1) { 67 | throw new AssertionError("agent conflict!!"); 68 | } 69 | const a = a0 ? "0" : (a1 ? "1" : "."); 70 | s.push( 71 | "_W".charAt(n.type) + 72 | (n.player === null ? "." : n.player).toString() + 73 | a, 74 | ); 75 | } 76 | res.push(s.join(" ")); 77 | } 78 | return res.join("\n"); 79 | }; 80 | 81 | const p = () => cl(tos()); 82 | const chk = (s: string) => assertEquals(s.trim(), tos()); 83 | 84 | cl("put"); 85 | p1.setActions(Action.fromArray([ 86 | [0, Action.PUT, 0, 0], 87 | [1, Action.PUT, 1, 1], 88 | ])); 89 | p2.setActions(Action.fromArray([ 90 | [0, Action.PUT, 1, 0], 91 | ])); 92 | assert(game.nextTurn()); 93 | p(); 94 | chk(` 95 | W00 W11 _.. 96 | _.. W00 _.. 97 | `); 98 | 99 | cl("move"); 100 | p2.setActions(Action.fromArray([ 101 | [0, Action.MOVE, 2, 0], 102 | ])); 103 | assert(game.nextTurn()); 104 | p(); 105 | chk(` 106 | W00 W1. W11 107 | _.. W00 _.. 108 | `); 109 | 110 | cl("remove move conflict"); 111 | p1.setActions(Action.fromArray([ 112 | [0, Action.REMOVE, 1, 0], 113 | [1, Action.MOVE, 1, 0], 114 | ])); 115 | assert(game.nextTurn()); 116 | p(); 117 | chk(` 118 | W00 _.. W11 119 | _.. W00 _.. 120 | `); 121 | 122 | cl("move"); 123 | p1.setActions(Action.fromArray([ 124 | [0, Action.MOVE, 1, 0], 125 | ])); 126 | assert(game.nextTurn()); 127 | p(); 128 | chk(` 129 | W0. W00 W11 130 | _.. W00 _.. 131 | `); 132 | 133 | cl("move remove conflict myself"); 134 | p1.setActions(Action.fromArray([ 135 | [0, Action.MOVE, 0, 0], 136 | [0, Action.REMOVE, 0, 0], 137 | ])); 138 | assert(game.nextTurn()); 139 | p(); 140 | chk(` 141 | W0. W00 W11 142 | _.. W00 _.. 143 | `); 144 | 145 | // finish 146 | for (let i = 0;; i++) { 147 | //console.log("turn", i); 148 | // showAgents(); 149 | if (!game.nextTurn()) break; 150 | } 151 | console.log("finish"); 152 | }); 153 | -------------------------------------------------------------------------------- /src/test/restore_test.ts: -------------------------------------------------------------------------------- 1 | import { assert, assertEquals } from "./deps.ts"; 2 | import { Action, Agent, Board, Field, Game, Player } from "../Kakomimasu.ts"; 3 | 4 | const boardObj: Board = { 5 | width: 2, 6 | height: 2, 7 | points: [1, 2, 3, 4], 8 | nAgent: 1, 9 | nPlayer: 2, 10 | totalTurn: 10, 11 | }; 12 | 13 | function assertAgent(a: Agent, b: Agent) { 14 | assertEquals(a, b); 15 | assert(a.field === b.field); // check same instance 16 | } 17 | 18 | function assertAction(a: Action, b: Action) { 19 | assertEquals(a, b); 20 | } 21 | 22 | function assertField(a: Field, b: Field) { 23 | assertEquals(a, b); 24 | } 25 | 26 | function assertGame(a: Game, b: Game) { 27 | // Player.gameが循環参照になるので分けて比較 28 | const { players: aPlayers, ...aOther } = a; 29 | const { players: bPlayers, ...bOther } = b; 30 | assertEquals(aOther, bOther); 31 | assertEquals(aPlayers.length, bPlayers.length); 32 | for (let i = 0; i < aPlayers.length; i++) { 33 | const { game: aGame, ...aOther } = aPlayers[i]; 34 | const { game: bGame, ...bOther } = bPlayers[i]; 35 | if (aGame) aGame.turn = 1; 36 | assert(aGame === a); // check same instance 37 | assert(bGame === b); // check same instance 38 | assertEquals(aOther, bOther); 39 | } 40 | } 41 | 42 | function assertPlayer(a: Player, b: Player) { 43 | assertEquals(a, b); 44 | assert(a.game === b.game); // check same instance 45 | } 46 | 47 | // Agent test 48 | // lastactionを変えてテスト 49 | Deno.test("fromJSON Agent class with lastaction is null", () => { 50 | const field = new Field(boardObj); 51 | const agent = new Agent(field, 1); 52 | const restoredAgent = Agent.fromJSON( 53 | JSON.parse(JSON.stringify(agent)), 54 | 1, 55 | field, 56 | ); 57 | 58 | assertAgent(agent, restoredAgent); 59 | }); 60 | 61 | // Action test 62 | Deno.test("fromJSON Action class", () => { 63 | const action = new Action(0, Action.NONE, 0, 0); 64 | const restoredAction = Action.fromJSON(JSON.parse(JSON.stringify(action))); 65 | 66 | assertAction(action, restoredAction); 67 | }); 68 | 69 | // Field test 70 | Deno.test("fromJSON Field class", () => { 71 | const field = new Field(boardObj); 72 | const restoredField = Field.fromJSON(JSON.parse(JSON.stringify(field))); 73 | 74 | assertField(field, restoredField); 75 | }); 76 | 77 | // Game test 78 | Deno.test("fromJSON Game class", () => { 79 | const game = new Game(boardObj); 80 | const restoredGame = Game.fromJSON(JSON.parse(JSON.stringify(game))); 81 | 82 | assertGame(game, restoredGame); 83 | }); 84 | Deno.test("fromJSON Game class with custom", () => { 85 | const game = new Game({ ...boardObj, nPlayer: 3, totalTurn: 20 }); 86 | const restoredGame = Game.fromJSON(JSON.parse(JSON.stringify(game))); 87 | 88 | assertGame(game, restoredGame); 89 | }); 90 | Deno.test("fromJSON Game class with players", () => { 91 | const game = new Game(boardObj); 92 | game.attachPlayer(new Player("abcd", "spec")); 93 | game.attachPlayer(new Player("efgh", "spec")); 94 | const restoredGame = Game.fromJSON(JSON.parse(JSON.stringify(game))); 95 | 96 | assertGame(game, restoredGame); 97 | }); 98 | 99 | // Player test 100 | // game有り無しでテスト 101 | Deno.test("fromJSON Player class with game is null", () => { 102 | const player = new Player("abcd", "spec"); 103 | const restoredPlayer = Player.fromJSON(JSON.parse(JSON.stringify(player))); 104 | 105 | assertPlayer(player, restoredPlayer); 106 | }); 107 | 108 | Deno.test("fromJSON Player class with game is class", () => { 109 | const board: Board = boardObj; 110 | const game = new Game(board); 111 | const player = new Player("abcd", "spec"); 112 | player.setGame(game); 113 | const restoredPlayer = Player.fromJSON( 114 | JSON.parse(JSON.stringify(player)), 115 | game, 116 | ); 117 | 118 | assertPlayer(player, restoredPlayer); 119 | }); 120 | -------------------------------------------------------------------------------- /types/Kakomimasu.d.ts: -------------------------------------------------------------------------------- 1 | import type { ActionJson, AgentJson, FieldJson, GameJson, PlayerJson } from "./json_type.js"; 2 | export declare type Point = { 3 | areaPoint: number; 4 | wallPoint: number; 5 | }; 6 | export interface Board { 7 | width: number; 8 | height: number; 9 | points: number[]; 10 | nAgent?: number; 11 | nPlayer?: number; 12 | totalTurn?: number; 13 | } 14 | declare class Agent { 15 | #private; 16 | field: Field; 17 | playerIdx: number; 18 | x: number; 19 | y: number; 20 | bkx: number; 21 | bky: number; 22 | constructor(field: Field, playeridx: number); 23 | static fromJSON(data: AgentJson, playerIdx: number, field: Field): Agent; 24 | toJSON(): AgentJson; 25 | check(act: Action): boolean; 26 | isValidAction(): Action | undefined; 27 | putOrMove(): boolean; 28 | remove(): boolean; 29 | commit(): void; 30 | revert(): void; 31 | } 32 | export declare type ActionType = 1 | 2 | 3 | 4; 33 | export declare type ActionRes = 0 | 1 | 2 | 3 | 4 | 5; 34 | export declare type ActionArray = [number, ActionType, number, number]; 35 | declare class Action { 36 | agentId: number; 37 | type: ActionType; 38 | x: number; 39 | y: number; 40 | res: ActionRes; 41 | static readonly PUT = 1; 42 | static readonly NONE = 2; 43 | static readonly MOVE = 3; 44 | static readonly REMOVE = 4; 45 | static readonly SUCCESS = 0; 46 | static readonly CONFLICT = 1; 47 | static readonly REVERT = 2; 48 | static readonly ERR_ONLY_ONE_TURN = 3; 49 | static readonly ERR_ILLEGAL_AGENT = 4; 50 | static readonly ERR_ILLEGAL_ACTION = 5; 51 | constructor(agentid: number, type: ActionType, x: number, y: number); 52 | static fromJSON(data: ActionJson): Action; 53 | static getMessage(res: ActionRes): string; 54 | static fromArray: (array: ActionArray[]) => Action[]; 55 | } 56 | declare type FieldType = typeof Field.AREA | typeof Field.WALL; 57 | export declare type FieldTile = { 58 | type: FieldType; 59 | player: null | number; 60 | }; 61 | export declare type FieldInit = Omit; 62 | declare class Field { 63 | width: number; 64 | height: number; 65 | nAgent: number; 66 | nPlayer: number; 67 | points: number[]; 68 | tiles: FieldTile[]; 69 | static readonly AREA = 0; 70 | static readonly WALL = 1; 71 | constructor({ width, height, points, nAgent, nPlayer }: FieldInit); 72 | static fromJSON(data: FieldJson): Field; 73 | set(x: number, y: number, att: FieldType, playerid: number | null): void; 74 | get(x: number, y: number): FieldTile; 75 | setAgent(playerid: number, x: number, y: number): boolean; 76 | fillArea(): void; 77 | getPoints(): Point[]; 78 | } 79 | export declare type GameInit = Board; 80 | declare class Game { 81 | #private; 82 | totalTurn: number; 83 | players: Player[]; 84 | field: Field; 85 | log: { 86 | players: { 87 | point: Point; 88 | actions: Action[]; 89 | }[]; 90 | }[]; 91 | turn: number; 92 | constructor(gameInit: GameInit); 93 | static fromJSON(data: GameJson): Game; 94 | attachPlayer(player: Player): boolean; 95 | getStatus(): "ended" | "free" | "ready" | "gaming"; 96 | isFree(): boolean; 97 | isReady(): boolean; 98 | isGaming(): boolean; 99 | isEnded(): boolean; 100 | start(): void; 101 | nextTurn(): boolean; 102 | } 103 | declare class Player { 104 | id: string; 105 | spec: string; 106 | game: T | null; 107 | actions: Action[]; 108 | index: number; 109 | agents: Agent[]; 110 | constructor(id: string, spec?: string); 111 | static fromJSON(data: PlayerJson, game?: Game): Player; 112 | toJSON(): PlayerJson; 113 | setGame(game: T): void; 114 | setActions(actions: Action[]): typeof Game.prototype.turn; 115 | getActions(): typeof Player.prototype.actions; 116 | clearActions(): void; 117 | } 118 | export { Action, Agent, Field, Game, Player }; 119 | -------------------------------------------------------------------------------- /src/test/conflict2_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | Deno.test("conflict2", () => { 5 | const nAgent = 6; 6 | const [width, height] = [3, 3]; 7 | const totalTurn = 20; 8 | const board: Board = { 9 | width, 10 | height, 11 | points: new Array(width * height), 12 | nAgent, 13 | totalTurn, 14 | }; 15 | 16 | const game = new Game(board); 17 | 18 | const field = game.field; 19 | 20 | const p1 = new Player("test1"); 21 | const p2 = new Player("test2"); 22 | game.attachPlayer(p1); 23 | game.attachPlayer(p2); 24 | game.start(); 25 | 26 | const isOnAgent = (p: number, x: number, y: number) => { 27 | let cnt = 0; 28 | for (const a of game.players[p].agents) { 29 | if (a.x === x && a.y === y) { 30 | cnt++; 31 | } 32 | } 33 | if (cnt === 1) { 34 | return true; 35 | } 36 | if (cnt === 0) { 37 | return false; 38 | } 39 | throw new AssertionError("agent conflict!! cnt:" + cnt); 40 | }; 41 | 42 | const tos = () => { 43 | const res = []; 44 | for (let i = 0; i < height; i++) { 45 | const s = []; 46 | for (let j = 0; j < width; j++) { 47 | const n = field.tiles[j + i * width]; 48 | const a0 = isOnAgent(0, j, i); 49 | const a1 = isOnAgent(1, j, i); 50 | if (a0 && a1) { 51 | throw new AssertionError("agent conflict!!"); 52 | } 53 | const a = a0 ? "0" : (a1 ? "1" : "."); 54 | s.push( 55 | "_W".charAt(n.type) + 56 | (n.player === null ? "." : n.player).toString() + 57 | a, 58 | ); 59 | } 60 | res.push(s.join(" ")); 61 | } 62 | return res.join("\n"); 63 | }; 64 | 65 | const cl = (...a: Parameters) => a; //console.log(...a); 66 | const p = () => cl(tos()); 67 | const chk = (s: string) => assertEquals(s.trim(), tos()); 68 | 69 | // put 70 | p1.setActions(Action.fromArray([ 71 | [0, Action.PUT, 0, 0], 72 | [1, Action.PUT, 0, 1], 73 | [2, Action.PUT, 0, 2], 74 | ])); 75 | p2.setActions(Action.fromArray([ 76 | [0, Action.PUT, 2, 0], 77 | [1, Action.PUT, 2, 1], 78 | [2, Action.PUT, 2, 2], 79 | ])); 80 | assert(game.nextTurn()); 81 | p(); 82 | chk(` 83 | W00 _.. W11 84 | W00 _.. W11 85 | W00 _.. W11 86 | `); 87 | 88 | // move conflict 89 | p1.setActions(Action.fromArray([ 90 | [0, Action.MOVE, 1, 1], 91 | [1, Action.MOVE, 1, 1], 92 | [2, Action.MOVE, 1, 1], 93 | ])); 94 | p2.setActions(Action.fromArray([ 95 | [0, Action.MOVE, 1, 1], 96 | [1, Action.MOVE, 1, 1], 97 | [2, Action.MOVE, 1, 1], 98 | ])); 99 | assert(game.nextTurn()); 100 | p(); 101 | chk(` 102 | W00 _.. W11 103 | W00 _.. W11 104 | W00 _.. W11 105 | `); 106 | 107 | // move remove put conflict 108 | p1.setActions(Action.fromArray([ 109 | [0, Action.MOVE, 1, 1], 110 | [1, Action.REMOVE, 1, 1], 111 | [2, Action.REMOVE, 1, 1], 112 | [3, Action.PUT, 1, 1], 113 | ])); 114 | p2.setActions(Action.fromArray([ 115 | [0, Action.MOVE, 1, 1], 116 | [1, Action.REMOVE, 1, 1], 117 | [2, Action.MOVE, 1, 1], 118 | [3, Action.PUT, 1, 1], 119 | ])); 120 | assert(game.nextTurn()); 121 | p(); 122 | chk(` 123 | W00 _.. W11 124 | W00 _.. W11 125 | W00 _.. W11 126 | `); 127 | 128 | // move no conflict 129 | p1.setActions(Action.fromArray([ 130 | [0, Action.MOVE, 1, 0], 131 | [1, Action.MOVE, 1, 1], 132 | [2, Action.MOVE, 1, 2], 133 | ])); 134 | p2.setActions(Action.fromArray([])); 135 | assert(game.nextTurn()); 136 | p(); 137 | chk(` 138 | W0. W00 W11 139 | W0. W00 W11 140 | W0. W00 W11 141 | `); 142 | 143 | // move no other wall and agent 144 | p1.setActions(Action.fromArray([ 145 | [0, Action.MOVE, 2, 0], 146 | [1, Action.MOVE, 2, 1], 147 | [2, Action.MOVE, 2, 2], 148 | ])); 149 | p2.setActions(Action.fromArray([])); 150 | assert(game.nextTurn()); 151 | p(); 152 | chk(` 153 | W0. W00 W11 154 | W0. W00 W11 155 | W0. W00 W11 156 | `); 157 | 158 | // finish 159 | while (game.nextTurn()); 160 | }); 161 | -------------------------------------------------------------------------------- /src/test/conflict5_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | Deno.test("conflict5 test", () => { 5 | const nAgent = 2; 6 | const [width, height] = [3, 1]; 7 | const points = [1, 1, 1]; 8 | const totalTurn = 20; 9 | const board: Board = { width, height, points, nAgent, totalTurn }; 10 | 11 | const game = new Game(board); 12 | 13 | const field = game.field; 14 | 15 | const p1 = new Player("test1"); 16 | const p2 = new Player("test2"); 17 | game.attachPlayer(p1); 18 | game.attachPlayer(p2); 19 | game.start(); 20 | 21 | const cl = (...a: Parameters) => { 22 | a; 23 | }; //console.log(...a); 24 | 25 | const _showAgents = () => { 26 | let i = 0; 27 | for (const player of game.players) { 28 | let j = 0; 29 | for (const a of player.agents) { 30 | console.log("pid", i, "aid", j, a.x, a.y); 31 | j++; 32 | } 33 | i++; 34 | } 35 | }; 36 | 37 | const isOnAgent = (p: number, x: number, y: number) => { 38 | let cnt = 0; 39 | for (const a of game.players[p].agents) { 40 | if (a.x === x && a.y === y) { 41 | cnt++; 42 | } 43 | } 44 | if (cnt === 1) { 45 | return true; 46 | } 47 | if (cnt === 0) { 48 | return false; 49 | } 50 | throw new AssertionError("agent conflict!! cnt:" + cnt); 51 | }; 52 | 53 | const tos = () => { 54 | const res = []; 55 | for (let i = 0; i < height; i++) { 56 | const s = []; 57 | for (let j = 0; j < width; j++) { 58 | const n = field.tiles[j + i * width]; 59 | const a0 = isOnAgent(0, j, i); 60 | const a1 = isOnAgent(1, j, i); 61 | if (a0 && a1) { 62 | throw new AssertionError("agent conflict!!"); 63 | } 64 | const a = a0 ? "0" : (a1 ? "1" : "."); 65 | s.push( 66 | "_W".charAt(n.type) + 67 | (n.player === null ? "." : n.player).toString() + 68 | a, 69 | ); 70 | } 71 | res.push(s.join(" ")); 72 | } 73 | return res.join("\n"); 74 | }; 75 | const p = () => cl(tos()); 76 | const chk = (s: string) => assertEquals(s.trim(), tos()); 77 | 78 | //put 2,2 79 | //kc.setActions([ new Action(0, "MOVE", 4, 2),new Action(0, "MOVE", 3, 2)]); // こっちだとAgentが[3,2]に移動する。 80 | //kc.setActions([ new Action(0, "MOVE", 3, 2),new Action(0, "MOVE", 4, 2)]); // こっちだとAgentは移動しない。 81 | cl("put"); 82 | p1.setActions(Action.fromArray([ 83 | [0, Action.PUT, 0, 0], 84 | ])); 85 | assert(game.nextTurn()); 86 | p(); 87 | chk("W00 _.. _.."); 88 | //console.log(game.log); 89 | 90 | let actions; 91 | 92 | cl("conflict1"); 93 | p1.setActions(Action.fromArray([ 94 | [0, Action.MOVE, 1, 0], 95 | [0, Action.MOVE, 2, 0], 96 | ])); 97 | assert(game.nextTurn()); 98 | p(); 99 | actions = game.log[game.log.length - 1].players[0].actions; 100 | //console.log("log", actions); 101 | assertEquals(actions.map((a) => a.res), [ 102 | Action.ERR_ONLY_ONE_TURN, 103 | Action.ERR_ONLY_ONE_TURN, 104 | ]); 105 | chk("W00 _.. _.."); 106 | 107 | cl("conflict2"); 108 | p1.setActions(Action.fromArray([ 109 | [0, Action.MOVE, 2, 0], 110 | [0, Action.MOVE, 1, 0], 111 | ])); 112 | assert(game.nextTurn()); 113 | p(); 114 | actions = game.log[game.log.length - 1].players[0].actions; 115 | assertEquals(actions.map((a) => a.res), [ 116 | Action.ERR_ONLY_ONE_TURN, 117 | Action.ERR_ONLY_ONE_TURN, 118 | ]); 119 | chk("W00 _.. _.."); 120 | //console.log(game.log); 121 | 122 | cl("conflict3"); 123 | p1.setActions(Action.fromArray([ 124 | [0, Action.MOVE, 2, 0], 125 | [0, Action.MOVE, 1, 0], 126 | [0, Action.MOVE, 3, 0], 127 | ])); 128 | assert(game.nextTurn()); 129 | p(); 130 | actions = game.log[game.log.length - 1].players[0].actions; 131 | //console.log("log", actions); 132 | assertEquals(actions.map((a) => a.res), [ 133 | Action.ERR_ONLY_ONE_TURN, 134 | Action.ERR_ONLY_ONE_TURN, 135 | Action.ERR_ONLY_ONE_TURN, 136 | ]); 137 | chk("W00 _.. _.."); 138 | //console.log(game.log); 139 | 140 | // finish 141 | for (let i = 0;; i++) { 142 | //console.log("turn", i); 143 | // showAgents(); 144 | if (!game.nextTurn()) break; 145 | } 146 | console.log("finish"); 147 | //game.dispose(); 148 | }); 149 | -------------------------------------------------------------------------------- /src/test/conflict3_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | Deno.test("conflict3", () => { 5 | const nAgent = 2; 6 | const totalTurn = 20; 7 | const [width, height] = [3, 1]; 8 | const board: Board = { 9 | width, 10 | height, 11 | points: new Array(width * height), 12 | nAgent, 13 | totalTurn, 14 | }; 15 | 16 | const game = new Game(board); 17 | 18 | const field = game.field; 19 | 20 | const p1 = new Player("test1"); 21 | const p2 = new Player("test2"); 22 | game.attachPlayer(p1); 23 | game.attachPlayer(p2); 24 | game.start(); 25 | 26 | const cl = (...a: Parameters) => a; //console.log(...a); 27 | 28 | const showAgents = () => { 29 | let i = 0; 30 | for (const player of game.players) { 31 | let j = 0; 32 | for (const a of player.agents) { 33 | cl("pid", i, "aid", j, a.x, a.y); 34 | j++; 35 | } 36 | i++; 37 | } 38 | }; 39 | 40 | const isOnAgent = (p: number, x: number, y: number) => { 41 | let cnt = 0; 42 | for (const a of game.players[p].agents) { 43 | if (a.x === x && a.y === y) { 44 | cnt++; 45 | } 46 | } 47 | if (cnt === 1) { 48 | return true; 49 | } 50 | if (cnt === 0) { 51 | return false; 52 | } 53 | throw new AssertionError("agent conflict!! cnt:" + cnt); 54 | }; 55 | 56 | const tos = () => { 57 | const res = []; 58 | for (let i = 0; i < height; i++) { 59 | const s = []; 60 | for (let j = 0; j < width; j++) { 61 | const n = field.tiles[j + i * width]; 62 | const a0 = isOnAgent(0, j, i); 63 | const a1 = isOnAgent(1, j, i); 64 | if (a0 && a1) { 65 | throw new AssertionError("agent conflict!!"); 66 | } 67 | const a = a0 ? "0" : (a1 ? "1" : "."); 68 | s.push( 69 | "_W".charAt(n.type) + 70 | (n.player === null ? "." : n.player).toString() + 71 | a, 72 | ); 73 | } 74 | res.push(s.join(" ")); 75 | } 76 | return res.join("\n"); 77 | }; 78 | 79 | const p = () => cl(tos()); 80 | const chk = (s: string) => assertEquals(s.trim(), tos()); 81 | 82 | cl("put"); 83 | p1.setActions(Action.fromArray([ 84 | [0, Action.PUT, 0, 0], 85 | ])); 86 | assert(game.nextTurn()); 87 | p(); 88 | chk("W00 _.. _.."); 89 | 90 | cl("move"); 91 | p1.setActions(Action.fromArray([ 92 | [0, Action.MOVE, 1, 0], 93 | ])); 94 | assert(game.nextTurn()); 95 | p(); 96 | chk("W0. W00 _.."); 97 | 98 | cl("put conflict myself"); 99 | p1.setActions(Action.fromArray([ 100 | [1, Action.PUT, 1, 0], 101 | ])); 102 | p2.setActions(Action.fromArray([])); 103 | assert(game.nextTurn()); 104 | p(); 105 | chk("W0. W00 _.."); 106 | showAgents(); 107 | 108 | cl("put conflict"); 109 | p1.setActions(Action.fromArray([])); 110 | p2.setActions(Action.fromArray([ 111 | [0, Action.PUT, 0, 0], 112 | ])); 113 | assert(game.nextTurn()); 114 | p(); 115 | chk("W0. W00 _.."); 116 | showAgents(); 117 | 118 | cl("move put conflict myself"); 119 | p1.setActions(Action.fromArray([ 120 | [0, Action.MOVE, 2, 0], 121 | [1, Action.PUT, 2, 0], 122 | ])); 123 | p2.setActions(Action.fromArray([])); 124 | assert(game.nextTurn()); 125 | p(); 126 | chk("W0. W00 _.."); 127 | showAgents(); 128 | 129 | cl("move put conflict"); 130 | p1.setActions(Action.fromArray([ 131 | [0, Action.MOVE, 2, 0], 132 | ])); 133 | p2.setActions(Action.fromArray([ 134 | [0, Action.PUT, 2, 0], 135 | ])); 136 | assert(game.nextTurn()); 137 | p(); 138 | chk("W0. W00 _.."); 139 | showAgents(); 140 | 141 | cl("put no conflict"); 142 | p1.setActions(Action.fromArray([])); 143 | p2.setActions(Action.fromArray([ 144 | [0, Action.PUT, 2, 0], 145 | ])); 146 | assert(game.nextTurn()); 147 | p(); 148 | chk("W0. W00 W11"); 149 | showAgents(); 150 | 151 | cl("remove"); 152 | p1.setActions(Action.fromArray([ 153 | [0, Action.REMOVE, 0, 0], 154 | ])); 155 | p2.setActions(Action.fromArray([])); 156 | assert(game.nextTurn()); 157 | p(); 158 | chk("_.. W00 W11"); 159 | showAgents(); 160 | 161 | cl("put"); 162 | p1.setActions(Action.fromArray([ 163 | [1, Action.PUT, 0, 0], 164 | ])); 165 | p2.setActions(Action.fromArray([])); 166 | assert(game.nextTurn()); 167 | p(); 168 | chk("W00 W00 W11"); 169 | showAgents(); 170 | 171 | cl("remove move conflict"); 172 | p1.setActions(Action.fromArray([ 173 | [0, Action.MOVE, 0, 0], 174 | [1, Action.REMOVE, 1, 0], 175 | ])); 176 | p2.setActions(Action.fromArray([])); 177 | assert(game.nextTurn()); 178 | p(); 179 | chk("W00 W00 W11"); 180 | showAgents(); 181 | 182 | // finish 183 | for (let i = 0;; i++) { 184 | //console.log("turn", i); 185 | // showAgents(); 186 | if (!game.nextTurn()) break; 187 | } 188 | console.log("finish"); 189 | }); 190 | -------------------------------------------------------------------------------- /src/test/random_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Action, 3 | ActionArray, 4 | ActionType, 5 | Board, 6 | Game, 7 | Player, 8 | } from "../Kakomimasu.ts"; 9 | import { assert, AssertionError } from "./deps.ts"; 10 | //import util from "../util.js"; 11 | //import util from "./nornd.js"; 12 | import util from "./mtrnd.js"; 13 | 14 | const cl = (...a: Parameters) => { 15 | a; 16 | }; //console.log(...a); 17 | 18 | Deno.test("random", () => { 19 | const nAgent = 6; 20 | const [width, height] = [nAgent, nAgent]; 21 | const totalTurn = 10000; 22 | const board: Board = { 23 | width, 24 | height, 25 | points: new Array(width * height), 26 | nAgent, 27 | totalTurn, 28 | }; 29 | 30 | const initialput = false; 31 | 32 | const game = new Game(board); 33 | 34 | const field = game.field; 35 | 36 | const nplayers = 2; 37 | const p1 = new Player("test1"); 38 | const p2 = new Player("test2"); 39 | game.attachPlayer(p1); 40 | game.attachPlayer(p2); 41 | game.start(); 42 | 43 | const showAgents = () => { 44 | let i = 0; 45 | for (const players of game.players) { 46 | let j = 0; 47 | for (const a of players.agents) { 48 | cl("pid", i, "aid", j, a.x, a.y); 49 | j++; 50 | } 51 | i++; 52 | } 53 | }; 54 | 55 | const isOnAgent = (p: number, x: number, y: number) => { 56 | let cnt = 0; 57 | for (const a of game.players[p].agents) { 58 | if (a.x === x && a.y === y) { 59 | cnt++; 60 | } 61 | } 62 | if (cnt === 1) { 63 | return true; 64 | } 65 | if (cnt === 0) { 66 | return false; 67 | } 68 | throw new AssertionError("agent conflict!! cnt:" + cnt); 69 | }; 70 | 71 | const tos = () => { 72 | const res = []; 73 | let fillfld = 0; 74 | for (let i = 0; i < height; i++) { 75 | const s = []; 76 | for (let j = 0; j < width; j++) { 77 | const n = field.tiles[j + i * width]; 78 | const a0 = isOnAgent(0, j, i); 79 | const a1 = isOnAgent(1, j, i); 80 | if (a0 && a1) { 81 | throw new AssertionError("agent conflict!!"); 82 | } 83 | const a = a0 ? "0" : (a1 ? "1" : "."); 84 | s.push( 85 | "_W".charAt(n.type) + 86 | (n.player === null ? "." : n.player).toString() + 87 | a, 88 | ); 89 | fillfld += (n.type === 0 && n.player !== null) ? 1 : 0; 90 | } 91 | res.push(s.join(" ")); 92 | } 93 | //console.log("fillfld", fillfld); 94 | if (fillfld >= 3) { 95 | console.log("fillfld over 3!\n", res.join("\n")); 96 | // Deno.exit(0); 97 | } 98 | return res.join("\n"); 99 | }; 100 | const checkAgent = () => { 101 | for (let i = 0; i < height; i++) { 102 | for (let j = 0; j < width; j++) { 103 | const n = field.tiles[j + i * width]; 104 | const a0 = isOnAgent(0, j, i); 105 | const a1 = isOnAgent(1, j, i); 106 | if (a0 && a1) { 107 | throw new AssertionError("agent conflict!!"); 108 | } 109 | const a = a0 ? "0" : (a1 ? "1" : "."); 110 | if (a !== "." && n.player !== null && n.player != parseInt(a)) { 111 | throw new AssertionError( 112 | `illegal field!! ${j}x${i} ${n.player} must be ${a}`, 113 | ); 114 | } 115 | } 116 | } 117 | }; 118 | const p = () => { 119 | const _ret = tos(); 120 | //console.log(ret); 121 | }; 122 | const chk = () => { 123 | p(); 124 | checkAgent(); 125 | // assertEquals(s.trim(), tos()) 126 | }; 127 | 128 | // put 129 | let actions: ActionType[]; 130 | const getPutAction = (x: number) => { 131 | const act: ActionArray[] = []; 132 | for (let i = 0; i < nAgent; i++) { 133 | act.push([i, Action.PUT, x, i]); 134 | } 135 | return act; 136 | }; 137 | game.nextTurn(); 138 | chk(); 139 | if (initialput) { 140 | p1.setActions(Action.fromArray(getPutAction(0))); 141 | p2.setActions(Action.fromArray(getPutAction(nAgent - 1))); 142 | assert(game.nextTurn()); 143 | chk(); 144 | actions = [Action.MOVE, Action.REMOVE]; 145 | } else { 146 | actions = [Action.PUT, Action.MOVE, Action.REMOVE]; 147 | } 148 | //console.log("actions", actions); 149 | const getRandomAction = (n: number): ActionArray => [ 150 | n, 151 | actions[util.rnd(actions.length)], 152 | util.rnd(nAgent), 153 | util.rnd(nAgent), 154 | ]; 155 | for (let i = 1; i <= totalTurn; i++) { 156 | const act: ActionArray[][] = []; 157 | for (let k = 0; k < nplayers; k++) { 158 | const act2: ActionArray[] = []; 159 | for (let j = 0; j < nAgent; j++) { 160 | act2.push(getRandomAction(j)); 161 | } 162 | act.push(act2); 163 | } 164 | //console.log("act", act); 165 | p1.setActions(Action.fromArray(act[0])); 166 | p2.setActions(Action.fromArray(act[1])); 167 | if (i === 9) { 168 | showAgents(); 169 | } 170 | game.nextTurn(); 171 | cl("turn", i); 172 | chk(); 173 | } 174 | }); 175 | -------------------------------------------------------------------------------- /src/test/conflict1_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Game, Player } from "../Kakomimasu.ts"; 2 | import { assert, assertEquals, AssertionError } from "./deps.ts"; 3 | 4 | Deno.test("conflict1", () => { 5 | const nAgent = 6; 6 | const [width, height] = [3, 1]; 7 | const totalTurn = 20; 8 | const board: Board = { 9 | width, 10 | height, 11 | points: new Array(width * height), 12 | nAgent, 13 | totalTurn, 14 | }; 15 | 16 | const game = new Game(board); 17 | 18 | const field = game.field; 19 | 20 | const p1 = new Player("test1"); 21 | const p2 = new Player("test2"); 22 | game.attachPlayer(p1); 23 | game.attachPlayer(p2); 24 | game.start(); 25 | 26 | const isOnAgent = (p: number, x: number, y: number) => { 27 | let cnt = 0; 28 | for (const a of game.players[p].agents) { 29 | if (a.x === x && a.y === y) { 30 | cnt++; 31 | } 32 | } 33 | if (cnt === 1) { 34 | return true; 35 | } 36 | if (cnt === 0) { 37 | return false; 38 | } 39 | throw new AssertionError("agent conflict!! cnt:" + cnt); 40 | }; 41 | 42 | const tos = () => { 43 | const res = []; 44 | for (let i = 0; i < height; i++) { 45 | const s = []; 46 | for (let j = 0; j < width; j++) { 47 | const n = field.tiles[j + i * width]; 48 | const a0 = isOnAgent(0, j, i); 49 | const a1 = isOnAgent(1, j, i); 50 | if (a0 && a1) { 51 | throw new AssertionError("agent conflict!!"); 52 | } 53 | const a = a0 ? "0" : (a1 ? "1" : "."); 54 | s.push( 55 | "_W".charAt(n.type) + 56 | (n.player === null ? "." : n.player).toString() + 57 | a, 58 | ); 59 | } 60 | res.push(s.join(" ")); 61 | } 62 | return res.join("\n"); 63 | }; 64 | 65 | const chk = (s: string) => assertEquals(s.trim(), tos()); 66 | 67 | const cl = (...a: Parameters) => a; //console.log(...a); 68 | const p = () => cl(tos()); 69 | 70 | // put 71 | cl("put"); 72 | p1.setActions(Action.fromArray([ 73 | [0, Action.PUT, 0, 0], 74 | ])); 75 | p2.setActions(Action.fromArray([ 76 | [0, Action.PUT, 2, 0], 77 | ])); 78 | assert(game.nextTurn()); 79 | p(); 80 | chk("W00 _.. W11"); 81 | 82 | cl("move conflict"); 83 | p1.setActions(Action.fromArray([ 84 | [0, Action.MOVE, 1, 0], 85 | ])); 86 | p2.setActions(Action.fromArray([ 87 | [0, Action.MOVE, 1, 0], 88 | ])); 89 | assert(game.nextTurn()); 90 | p(); 91 | chk("W00 _.. W11"); 92 | 93 | // move conflict 94 | p1.setActions(Action.fromArray([ 95 | [0, Action.MOVE, 1, 0], 96 | ])); 97 | p2.setActions(Action.fromArray([ 98 | [0, Action.MOVE, 1, 0], 99 | ])); 100 | assert(game.nextTurn()); 101 | p(); 102 | chk("W00 _.. W11"); 103 | 104 | // put move conflict 105 | p1.setActions(Action.fromArray([ 106 | [1, Action.PUT, 1, 0], 107 | ])); 108 | p2.setActions(Action.fromArray([ 109 | [0, Action.MOVE, 1, 0], 110 | ])); 111 | assert(game.nextTurn()); 112 | p(); 113 | chk("W00 _.. W11"); 114 | 115 | // move no conflict 116 | p1.setActions(Action.fromArray([ 117 | [0, Action.MOVE, 1, 0], 118 | ])); 119 | p2.setActions(Action.fromArray([])); 120 | assert(game.nextTurn()); 121 | p(); 122 | chk("W0. W00 W11"); 123 | 124 | // move no conflict 125 | p1.setActions(Action.fromArray([ 126 | [0, Action.MOVE, 0, 0], 127 | ])); 128 | p2.setActions(Action.fromArray([])); 129 | assert(game.nextTurn()); 130 | p(); 131 | chk("W00 W0. W11"); 132 | 133 | // move remove conflict 134 | p1.setActions(Action.fromArray([ 135 | [0, Action.MOVE, 1, 0], 136 | ])); 137 | p2.setActions(Action.fromArray([ 138 | [0, Action.REMOVE, 1, 0], 139 | ])); 140 | assert(game.nextTurn()); 141 | p(); 142 | chk("W00 W0. W11"); 143 | 144 | // remove no conflict 145 | p1.setActions(Action.fromArray([])); 146 | p2.setActions(Action.fromArray([ 147 | [0, Action.REMOVE, 1, 0], 148 | ])); 149 | assert(game.nextTurn()); 150 | p(); 151 | chk("W00 _.. W11"); 152 | 153 | cl("move no conflict"); 154 | p1.setActions(Action.fromArray([])); 155 | p2.setActions(Action.fromArray([ 156 | [0, Action.MOVE, 1, 0], 157 | ])); 158 | assert(game.nextTurn()); 159 | p(); 160 | chk("W00 W11 W1."); 161 | 162 | cl("remove failed"); 163 | p1.setActions(Action.fromArray([])); 164 | p2.setActions(Action.fromArray([ 165 | [0, Action.REMOVE, 0, 0], 166 | ])); 167 | assert(game.nextTurn()); 168 | p(); 169 | chk("W00 W11 W1."); 170 | 171 | cl("move failed"); 172 | p1.setActions(Action.fromArray([])); 173 | p2.setActions(Action.fromArray([ 174 | [0, Action.MOVE, 0, 0], 175 | ])); 176 | assert(game.nextTurn()); 177 | p(); 178 | chk("W00 W11 W1."); 179 | 180 | cl("cross move failed"); 181 | p1.setActions(Action.fromArray([ 182 | [0, Action.MOVE, 1, 0], 183 | ])); 184 | p2.setActions(Action.fromArray([ 185 | [0, Action.MOVE, 0, 0], 186 | ])); 187 | assert(game.nextTurn()); 188 | p(); 189 | chk("W00 W11 W1."); 190 | 191 | // finish 192 | while (game.nextTurn()); 193 | }); 194 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2", 3 | "remote": { 4 | "https://code4sabae.github.io/js/MersenneTwister.js": "a47fdcce5a6d1849cb0840efebd1846304eade3de1b556a18f6611aba78d81fd", 5 | "https://deno.land/std@0.106.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", 6 | "https://deno.land/std@0.106.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", 7 | "https://deno.land/std@0.106.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", 8 | "https://deno.land/std@0.106.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", 9 | "https://deno.land/std@0.106.0/fs/expand_glob.ts": "73e7b13f01097b04ed782b3d63863379b718417417758ba622e282b1e5300b91", 10 | "https://deno.land/std@0.106.0/fs/walk.ts": "b91c655c60d048035f9cae0e6177991ab3245e786e3ab7d20a5b60012edf2126", 11 | "https://deno.land/std@0.106.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", 12 | "https://deno.land/std@0.106.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", 13 | "https://deno.land/std@0.106.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", 14 | "https://deno.land/std@0.106.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a", 15 | "https://deno.land/std@0.106.0/path/glob.ts": "3b84af55c53febacf6afe214c095624b22a56b6f57d7312157479cc783a0de65", 16 | "https://deno.land/std@0.106.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", 17 | "https://deno.land/std@0.106.0/path/posix.ts": "b81974c768d298f8dcd2c720229639b3803ca4a241fa9a355c762fa2bc5ef0c1", 18 | "https://deno.land/std@0.106.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", 19 | "https://deno.land/std@0.106.0/path/win32.ts": "f4a3d4a3f2c9fe894da046d5eac48b5e789a0ebec5152b2c0985efe96a9f7ae1", 20 | "https://deno.land/std@0.109.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", 21 | "https://deno.land/std@0.109.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", 22 | "https://deno.land/std@0.109.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", 23 | "https://deno.land/std@0.109.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", 24 | "https://deno.land/std@0.109.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", 25 | "https://deno.land/std@0.109.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", 26 | "https://deno.land/std@0.109.0/path/glob.ts": "46708a3249cb5dc4a116cae3055114d6339bd5f0c1f412db6a4e0cb44c828a7d", 27 | "https://deno.land/std@0.109.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", 28 | "https://deno.land/std@0.109.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", 29 | "https://deno.land/std@0.109.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", 30 | "https://deno.land/std@0.109.0/path/win32.ts": "2edb2f71f10578ee1168de01a8cbd3c65483e45a46bc2fa3156a0c6bfbd2720d", 31 | "https://deno.land/std@0.111.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621", 32 | "https://deno.land/std@0.111.0/testing/_diff.ts": "ccd6c3af6e44c74bf1591acb1361995f5f50df64323a6e7fb3f16c8ea792c940", 33 | "https://deno.land/std@0.111.0/testing/asserts.ts": "6b0d6ba564bdff807bd0f0e93e02c48aa3177acf19416bf84a7f420191ef74cd", 34 | "https://deno.land/x/dnt@0.0.9/lib/compiler.ts": "32ec582f5c20c82372d53550381eb9d44b6a8d9dbabffe7b4807557ebfbdd616", 35 | "https://deno.land/x/dnt@0.0.9/lib/mod.deps.ts": "82e53a28f5768b72244f46bcc821a8405c9103c7c00c34c94563595d0ffd1f4b", 36 | "https://deno.land/x/dnt@0.0.9/lib/pkg/dnt_wasm.js": "52305bf88fc54e8a251a92b7f1a6b695803d4f83b193f61d308438f7effce596", 37 | "https://deno.land/x/dnt@0.0.9/lib/pkg/dnt_wasm_bg.ts": "8d542f8255053e1c39b98e7a583600d148f8584a6fbf1ed3435e53f856435aff", 38 | "https://deno.land/x/dnt@0.0.9/lib/pkg/snippets/dnt-wasm-2cfe58fb53074cc7/helpers.js": "deaf53dd2583afdfa97d108504ede8e1e0f16ac73a17ce004bb92c429285a104", 39 | "https://deno.land/x/dnt@0.0.9/lib/transform.deps.ts": "07a8a9e98567f6a06788549783d19f66942992695cfdb08464b617a56e12963e", 40 | "https://deno.land/x/dnt@0.0.9/lib/types.ts": "b31f4c9bac4e97ed4ef95b2173566db850cac65bbb4c277b51fbf000c72692f8", 41 | "https://deno.land/x/dnt@0.0.9/mod.ts": "d45a352f2e426871594aa70651ba242cf29bda4f768ce987bb22d29921f16740", 42 | "https://deno.land/x/dnt@0.0.9/transform.ts": "68bb48e4180039680191b73d4f028d39f2ab980695a0cc48ea1f76861a0ce8ee", 43 | "https://deno.land/x/ts_morph@12.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", 44 | "https://deno.land/x/ts_morph@12.0.0/bootstrap/ts_morph_bootstrap.d.ts": "7c8b47adc3cbbcb41238a89bf3e466c422d38e128aeb8cf7a7af4069f143d864", 45 | "https://deno.land/x/ts_morph@12.0.0/bootstrap/ts_morph_bootstrap.js": "124c16b4f53ae28bbe80c289f2cc90400a405b70c38a18b9ebf6ebebf5dcda2c", 46 | "https://deno.land/x/ts_morph@12.0.0/common/DenoRuntime.ts": "e3fa796be7de190f24f91ee6e04e9c78dff1952455b00fb1ff328a42faffc834", 47 | "https://deno.land/x/ts_morph@12.0.0/common/data/libFiles.js": "d64cb559483d01728cb1c51e8768d4f53d4c5bda9dc5c8becaace2878a3d01a9", 48 | "https://deno.land/x/ts_morph@12.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", 49 | "https://deno.land/x/ts_morph@12.0.0/common/ts_morph_common.d.ts": "0b9286871b6216853e75fa7ea196a49abdb933c600c36a63aa868845bbd50b8f", 50 | "https://deno.land/x/ts_morph@12.0.0/common/ts_morph_common.js": "0bbde4726e6110c4fa7e54121362cc5d9f27371ae2bdc59d10f9b71fbb8a56ef", 51 | "https://deno.land/x/ts_morph@12.0.0/common/typescript.d.ts": "693bfecb024a122139eccd43e82633076d9bd7ee453600eb273fb521fe842d96", 52 | "https://deno.land/x/ts_morph@12.0.0/common/typescript.js": "d78f7584a6bb4709a9fb4f65e79bb13a5c621e15737560db4cfc3fc558e2db95" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/unit_test.ts: -------------------------------------------------------------------------------- 1 | import { Action, Board, Field, Game, Player } from "../Kakomimasu.ts"; 2 | import { assertEquals } from "./deps.ts"; 3 | // import util from "../util.mjs"; 4 | 5 | const prepare = () => { 6 | const width = 3; 7 | const height = 3; 8 | const points = []; 9 | for (let i = 0; i < width * height; i++) { 10 | points[i] = i; 11 | // points[i] = i % (16 * 2 + 1) - 16; 12 | // util.rnd(16 * 2 + 1) - 16; 13 | } 14 | const nAgent = 9; 15 | const board: Board = { width, height, points, nAgent, totalTurn: 30 }; 16 | 17 | const game = new Game(board); 18 | const p1 = new Player("test1"); 19 | const p2 = new Player("test2"); 20 | game.attachPlayer(p1); 21 | game.attachPlayer(p2); 22 | game.start(); 23 | return { game, p1, p2 }; 24 | }; 25 | 26 | Deno.test("action put", () => { 27 | const { game, p1 } = prepare(); 28 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 29 | game.nextTurn(); 30 | assertEquals(game.field.tiles[0], { type: Field.WALL, player: 0 }); 31 | }); 32 | 33 | Deno.test("action can't put", () => { 34 | const { game, p1 } = prepare(); 35 | p1.setActions(Action.fromArray([[0, Action.PUT, 1000, 0]])); 36 | game.nextTurn(); 37 | assertEquals(game.field.tiles[0], { type: Field.AREA, player: null }); 38 | assertEquals( 39 | game.log[0].players[0].actions[0].res, 40 | Action.ERR_ILLEGAL_ACTION, 41 | ); 42 | }); 43 | 44 | Deno.test("action move", () => { 45 | const { game, p1 } = prepare(); 46 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 47 | game.nextTurn(); 48 | p1.setActions(Action.fromArray([[0, Action.MOVE, 1, 0]])); 49 | assertEquals(game.field.tiles[0], { type: Field.WALL, player: 0 }); 50 | }); 51 | 52 | Deno.test("action move series", () => { 53 | //Deno.stdout.writeSync(new TextEncoder().encode("連なり移動")); 54 | console.log("連なり移動"); 55 | const { game, p1 } = prepare(); 56 | p1.setActions(Action.fromArray([ 57 | [0, Action.PUT, 0, 0], 58 | [1, Action.PUT, 1, 0], 59 | ])); 60 | game.nextTurn(); 61 | p1.setActions(Action.fromArray([ 62 | [0, Action.MOVE, 1, 0], 63 | [1, Action.MOVE, 2, 0], 64 | ])); 65 | game.nextTurn(); 66 | assertEquals(game.field.tiles[2], { type: Field.WALL, player: 0 }); 67 | }); 68 | 69 | Deno.test("action cant't move series", () => { 70 | console.log("連なり移動失敗"); 71 | const { game, p1, p2 } = prepare(); 72 | p1.setActions(Action.fromArray([ 73 | [0, Action.PUT, 0, 0], 74 | [1, Action.PUT, 1, 0], 75 | ])); 76 | game.nextTurn(); 77 | p1.setActions(Action.fromArray([ 78 | [0, Action.MOVE, 1, 0], 79 | [1, Action.MOVE, 2, 0], 80 | ])); 81 | p2.setActions(Action.fromArray([ 82 | [0, Action.PUT, 2, 0], 83 | ])); 84 | game.nextTurn(); 85 | assertEquals(game.field.tiles[2], { 86 | type: Field.AREA, 87 | player: null, 88 | }); 89 | }); 90 | 91 | Deno.test("action can't move", () => { 92 | const { game, p1 } = prepare(); 93 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 94 | game.nextTurn(); 95 | p1.setActions(Action.fromArray([[0, Action.MOVE, 2, 0]])); 96 | game.nextTurn(); 97 | assertEquals(game.field.tiles[2], { type: Field.AREA, player: null }); 98 | assertEquals( 99 | game.log[1].players[0].actions[0].res, 100 | Action.ERR_ILLEGAL_ACTION, 101 | ); 102 | }); 103 | 104 | Deno.test("fill", () => { 105 | const { game, p1 } = prepare(); 106 | p1.setActions(Action.fromArray([ 107 | [0, Action.PUT, 0, 0], 108 | [1, Action.PUT, 1, 0], 109 | [2, Action.PUT, 2, 0], 110 | [3, Action.PUT, 0, 1], 111 | [4, Action.PUT, 2, 1], 112 | [5, Action.PUT, 0, 2], 113 | [6, Action.PUT, 1, 2], 114 | [7, Action.PUT, 2, 2], 115 | ])); 116 | game.nextTurn(); 117 | assertEquals(game.field.tiles[4], { type: Field.AREA, player: 0 }); 118 | }); 119 | 120 | Deno.test("action remove", () => { 121 | const { game, p1 } = prepare(); 122 | p1.setActions(Action.fromArray([ 123 | [0, Action.PUT, 0, 0], 124 | ])); 125 | game.nextTurn(); 126 | assertEquals(game.field.tiles[0], { type: Field.WALL, player: 0 }); 127 | p1.setActions(Action.fromArray([ 128 | [0, Action.MOVE, 1, 0], 129 | ])); 130 | game.nextTurn(); 131 | p1.setActions(Action.fromArray([ 132 | [0, Action.REMOVE, 0, 0], 133 | ])); 134 | game.nextTurn(); 135 | assertEquals(game.field.tiles[0], { 136 | type: Field.AREA, 137 | player: null, 138 | }); 139 | }); 140 | 141 | Deno.test("action can't remove", () => { 142 | const { game, p1 } = prepare(); 143 | p1.setActions(Action.fromArray([ 144 | [0, Action.PUT, 0, 0], 145 | ])); 146 | game.nextTurn(); 147 | p1.setActions(Action.fromArray([ 148 | [0, Action.REMOVE, 1, 0], 149 | ])); 150 | game.nextTurn(); 151 | assertEquals(game.field.tiles[1], { 152 | type: Field.AREA, 153 | player: null, 154 | }); 155 | assertEquals( 156 | game.log[1].players[0].actions[0].res, 157 | Action.ERR_ILLEGAL_ACTION, 158 | ); 159 | }); 160 | 161 | Deno.test("wall point", () => { 162 | const { game, p1 } = prepare(); 163 | p1.setActions(Action.fromArray([ 164 | [0, Action.PUT, 1, 0], 165 | [1, Action.PUT, 2, 0], 166 | ])); 167 | game.nextTurn(); 168 | assertEquals(game.log[0].players[0].point, { 169 | areaPoint: 0, 170 | wallPoint: 1 + 2, 171 | }); 172 | }); 173 | 174 | Deno.test("AREA point", () => { 175 | const { game, p1 } = prepare(); 176 | p1.setActions(Action.fromArray([ 177 | [0, Action.PUT, 0, 0], 178 | [1, Action.PUT, 1, 0], 179 | [2, Action.PUT, 2, 0], 180 | [3, Action.PUT, 0, 1], 181 | [4, Action.PUT, 2, 1], 182 | [5, Action.PUT, 0, 2], 183 | [6, Action.PUT, 1, 2], 184 | [7, Action.PUT, 2, 2], 185 | ])); 186 | game.nextTurn(); 187 | const status = game; 188 | assertEquals(status.field.tiles[4], { type: Field.AREA, player: 0 }); 189 | assertEquals(game.log[0].players[0].point, { 190 | areaPoint: 4, 191 | wallPoint: 0 + 1 + 2 + 3 + 5 + 6 + 7 + 8, 192 | }); 193 | }); 194 | 195 | Deno.test("remove on agent", () => { 196 | console.log("エージェントがいるマスの壁はREMOVE不可"); 197 | const { game, p1, p2 } = prepare(); 198 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 199 | p2.setActions(Action.fromArray([[0, Action.PUT, 1, 0]])); 200 | game.nextTurn(); 201 | p1.setActions(Action.fromArray([[0, Action.REMOVE, 1, 0]])); 202 | game.nextTurn(); 203 | assertEquals(game.field.tiles[1], { type: Field.WALL, player: 1 }); 204 | assertEquals( 205 | game.log[1].players[0].actions[0].res, 206 | Action.REVERT, 207 | ); 208 | }); 209 | 210 | Deno.test("conflict put", () => { 211 | const { game, p1, p2 } = prepare(); 212 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 213 | p2.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 214 | game.nextTurn(); 215 | assertEquals(game.field.tiles[0], { type: Field.AREA, player: null }); 216 | assertEquals(game.log[0].players[0].actions[0].res, Action.CONFLICT); 217 | assertEquals(game.log[0].players[1].actions[0].res, Action.CONFLICT); 218 | }); 219 | 220 | Deno.test("conflict move", () => { 221 | const { game, p1, p2 } = prepare(); 222 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 223 | p2.setActions(Action.fromArray([[0, Action.PUT, 2, 0]])); 224 | game.nextTurn(); 225 | p1.setActions(Action.fromArray([[0, Action.MOVE, 1, 0]])); 226 | p2.setActions(Action.fromArray([[0, Action.MOVE, 1, 0]])); 227 | game.nextTurn(); 228 | // util.p(status.agents); 229 | assertEquals(game.field.tiles[1], { type: Field.AREA, player: null }); 230 | assertEquals(game.log[1].players[0].actions[0].res, Action.CONFLICT); 231 | assertEquals(game.log[1].players[1].actions[0].res, Action.CONFLICT); 232 | }); 233 | 234 | Deno.test("conflict remove", () => { 235 | const { game, p1, p2 } = prepare(); 236 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 237 | p2.setActions(Action.fromArray([ 238 | [0, Action.PUT, 2, 0], 239 | [1, Action.PUT, 1, 0], 240 | ])); 241 | game.nextTurn(); 242 | p2.setActions(Action.fromArray([[1, Action.MOVE, 1, 1]])); 243 | assertEquals(game.field.tiles[1], { type: Field.WALL, player: 1 }); 244 | game.nextTurn(); 245 | p1.setActions(Action.fromArray([[0, Action.REMOVE, 1, 0]])); 246 | p2.setActions(Action.fromArray([[0, Action.REMOVE, 1, 0]])); 247 | game.nextTurn(); 248 | assertEquals(game.field.tiles[1], { type: Field.WALL, player: 1 }); 249 | assertEquals( 250 | game.log[2].players[0].actions[0].res, 251 | Action.CONFLICT, 252 | ); 253 | assertEquals( 254 | game.log[2].players[1].actions[0].res, 255 | Action.CONFLICT, 256 | ); 257 | }); 258 | 259 | Deno.test("conflict remove & move", () => { 260 | console.log("壁がないところの先読みREMOVEは不可、移動が成功する"); 261 | const { game, p1, p2 } = prepare(); 262 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 263 | p2.setActions(Action.fromArray([[0, Action.PUT, 2, 0]])); 264 | game.nextTurn(); 265 | p1.setActions(Action.fromArray([[0, Action.REMOVE, 1, 0]])); 266 | p2.setActions(Action.fromArray([[0, Action.MOVE, 1, 0]])); 267 | game.nextTurn(); 268 | assertEquals(game.field.tiles[1], { type: Field.WALL, player: 1 }); 269 | assertEquals( 270 | game.log[1].players[0].actions[0].res, 271 | Action.ERR_ILLEGAL_ACTION, 272 | ); 273 | assertEquals( 274 | game.log[1].players[1].actions[0].res, 275 | Action.SUCCESS, 276 | ); 277 | }); 278 | 279 | Deno.test("conflict remove & move", () => { 280 | console.log("壁がないところの先読みREMOVEは不可、PUTが成功する"); 281 | const { game, p1, p2 } = prepare(); 282 | p1.setActions(Action.fromArray([[0, Action.PUT, 0, 0]])); 283 | game.nextTurn(); 284 | p1.setActions(Action.fromArray([[0, Action.REMOVE, 1, 0]])); 285 | p2.setActions(Action.fromArray([[0, Action.PUT, 1, 0]])); 286 | game.nextTurn(); 287 | assertEquals(game.field.tiles[1], { type: Field.WALL, player: 1 }); 288 | assertEquals( 289 | game.log[1].players[0].actions[0].res, 290 | Action.ERR_ILLEGAL_ACTION, 291 | ); 292 | assertEquals( 293 | game.log[1].players[1].actions[0].res, 294 | Action.SUCCESS, 295 | ); 296 | }); 297 | -------------------------------------------------------------------------------- /src/Kakomimasu.ts: -------------------------------------------------------------------------------- 1 | import { flat } from "./util.ts"; 2 | import type { 3 | ActionJson, 4 | AgentJson, 5 | FieldJson, 6 | GameJson, 7 | PlayerJson, 8 | } from "./json_type.ts"; 9 | 10 | export type Point = { 11 | areaPoint: number; 12 | wallPoint: number; 13 | }; 14 | 15 | export interface Board { 16 | width: number; 17 | height: number; 18 | points: number[]; 19 | nAgent?: number; 20 | nPlayer?: number; 21 | totalTurn?: number; 22 | } 23 | 24 | class Agent { 25 | field: Field; 26 | playerIdx: number; 27 | x: number; 28 | y: number; 29 | bkx: number; 30 | bky: number; 31 | #lastaction: Action | null; 32 | 33 | constructor(field: Field, playeridx: number) { 34 | this.field = field; 35 | this.playerIdx = playeridx; 36 | this.x = -1; 37 | this.y = -1; 38 | this.bkx = -1; 39 | this.bky = -1; 40 | this.#lastaction = null; 41 | } 42 | 43 | static fromJSON(data: AgentJson, playerIdx: number, field: Field): Agent { 44 | const agent = new Agent(field, playerIdx); 45 | agent.x = data.x; 46 | agent.y = data.y; 47 | return agent; 48 | } 49 | toJSON(): AgentJson { 50 | return { x: this.x, y: this.y }; 51 | } 52 | 53 | #isOnBoard(): boolean { 54 | return this.x !== -1; 55 | } 56 | 57 | #checkOnBoard(x: number, y: number): boolean { 58 | return x >= 0 && x < this.field.width && y >= 0 && 59 | y < this.field.height; 60 | } 61 | 62 | #checkDir(x: number, y: number): boolean { 63 | if (this.x === x && this.y === y) return false; 64 | return Math.abs(this.x - x) <= 1 && Math.abs(this.y - y) <= 1; 65 | } 66 | 67 | check(act: Action): boolean { 68 | this.#lastaction = act; 69 | this.bkx = this.x; 70 | this.bky = this.y; 71 | const x = act.x; 72 | const y = act.y; 73 | const t = act.type; 74 | if (t === Action.PUT) return this.#checkPut(x, y); 75 | else if (t === Action.MOVE) return this.#checkMove(x, y); 76 | else if (t === Action.REMOVE) return this.#checkRemove(x, y); 77 | else return false; 78 | } 79 | 80 | #checkPut(x: number, y: number): boolean { 81 | if (this.#isOnBoard()) return false; 82 | if (!this.#checkOnBoard(x, y)) return false; 83 | return true; 84 | } 85 | 86 | #checkMove(x: number, y: number) { 87 | if (!this.#isOnBoard()) return false; 88 | if (!this.#checkOnBoard(x, y)) return false; 89 | if (!this.#checkDir(x, y)) return false; 90 | return true; 91 | } 92 | 93 | #checkRemove(x: number, y: number) { 94 | if (!this.#isOnBoard()) return false; 95 | if (!this.#checkOnBoard(x, y)) return false; 96 | if (!this.#checkDir(x, y)) return false; 97 | if (this.field.get(x, y).type !== Field.WALL) return false; 98 | return true; 99 | } 100 | 101 | isValidAction(): Action | undefined { 102 | if (!this.#lastaction) return; 103 | if (this.#lastaction.res !== Action.SUCCESS) return; 104 | return this.#lastaction; 105 | } 106 | 107 | putOrMove(): boolean { 108 | //console.log("putormove", this); 109 | if (this.#lastaction == null) throw new Error("putOrMove before check"); 110 | if (this.#lastaction.res !== Action.SUCCESS) return false; 111 | const act = this.#lastaction; 112 | const x = act.x; 113 | const y = act.y; 114 | const t = act.type; 115 | if (t === Action.PUT) return this.#put(x, y); 116 | if (t === Action.MOVE) return this.#move(x, y); 117 | return true; 118 | } 119 | 120 | #put(x: number, y: number): boolean { 121 | if (!this.#checkPut(x, y)) return false; 122 | if (!this.field.setAgent(this.playerIdx, x, y)) { 123 | return false; // throw new Error("can't enter the wall"); 124 | } 125 | this.x = x; 126 | this.y = y; 127 | return true; 128 | } 129 | 130 | #move(x: number, y: number): boolean { 131 | if (!this.#checkMove(x, y)) return false; 132 | if (!this.field.setAgent(this.playerIdx, x, y)) { 133 | return false; // throw new Error("can't enter the wall"); 134 | } 135 | this.x = x; 136 | this.y = y; 137 | return true; 138 | } 139 | 140 | remove(): boolean { 141 | if (this.#lastaction == null) throw new Error("remove before check"); 142 | const { x, y } = this.#lastaction; 143 | if (!this.#checkRemove(x, y)) return false; 144 | this.field.set(x, y, Field.AREA, null); 145 | return true; 146 | } 147 | 148 | commit(): void { 149 | this.#lastaction = null; 150 | } 151 | 152 | revert(): void { 153 | const act = this.#lastaction; 154 | if ( 155 | act && (act.type === Action.MOVE || act.type === Action.PUT) && 156 | act.res === Action.SUCCESS 157 | ) { 158 | act.res = Action.REVERT; 159 | this.x = this.bkx; 160 | this.y = this.bky; 161 | } 162 | } 163 | } 164 | 165 | export type ActionType = 1 | 2 | 3 | 4; 166 | export type ActionRes = 0 | 1 | 2 | 3 | 4 | 5; 167 | export type ActionArray = [number, ActionType, number, number]; 168 | 169 | class Action { 170 | agentId: number; 171 | type: ActionType; 172 | x: number; 173 | y: number; 174 | res: ActionRes; 175 | 176 | // Action Type 177 | static readonly PUT = 1; 178 | static readonly NONE = 2; 179 | static readonly MOVE = 3; 180 | static readonly REMOVE = 4; 181 | 182 | // Action Res 183 | static readonly SUCCESS = 0; 184 | static readonly CONFLICT = 1; 185 | static readonly REVERT = 2; 186 | static readonly ERR_ONLY_ONE_TURN = 3; 187 | static readonly ERR_ILLEGAL_AGENT = 4; 188 | static readonly ERR_ILLEGAL_ACTION = 5; 189 | 190 | constructor(agentid: number, type: ActionType, x: number, y: number) { 191 | this.agentId = agentid; 192 | this.type = type; 193 | this.x = x; 194 | this.y = y; 195 | this.res = Action.SUCCESS; 196 | } 197 | 198 | static fromJSON(data: ActionJson) { 199 | const action = new Action(data.agentId, data.type, data.x, data.y); 200 | action.res = data.res; 201 | return action; 202 | } 203 | 204 | static getMessage(res: ActionRes): string { 205 | return [ 206 | "success", 207 | "conflict", 208 | "revert", 209 | "err: only 1 turn", 210 | "err: illegal agent", 211 | "err: illegal action", 212 | ][res]; 213 | } 214 | 215 | static fromArray = (array: ActionArray[]) => 216 | array.map((a) => new Action(a[0], a[1], a[2], a[3])); 217 | } 218 | 219 | type FieldType = typeof Field.AREA | typeof Field.WALL; 220 | export type FieldTile = { type: FieldType; player: null | number }; 221 | 222 | export type FieldInit = Omit; 223 | 224 | class Field { 225 | width: number; 226 | height: number; 227 | nAgent: number; 228 | nPlayer: number; 229 | points: number[]; 230 | tiles: FieldTile[]; 231 | 232 | static readonly AREA = 0; 233 | static readonly WALL = 1; 234 | 235 | constructor({ width, height, points, nAgent = 4, nPlayer = 2 }: FieldInit) { 236 | if (points.length !== width * height) { 237 | throw Error("points.length must be " + width * height); 238 | } 239 | 240 | this.width = width; 241 | this.height = height; 242 | this.nAgent = nAgent; 243 | this.nPlayer = nPlayer; 244 | this.points = points; 245 | this.tiles = new Array(width * height); 246 | // fillで初期化すると参照が同じになってしまうので、一つずつ初期化 247 | for (let i = 0; i < width * height; i++) { 248 | this.tiles[i] = { type: Field.AREA, player: null }; 249 | } 250 | } 251 | 252 | static fromJSON(data: FieldJson) { 253 | const { tiles, ...init } = data; 254 | const field = new Field(init); 255 | field.tiles = tiles; 256 | return field; 257 | } 258 | 259 | set(x: number, y: number, att: FieldType, playerid: number | null): void { 260 | if (playerid !== null && playerid < 0) { 261 | throw Error("playerid must be 0 or more"); 262 | } 263 | this.tiles[x + y * this.width] = { type: att, player: playerid }; 264 | } 265 | 266 | get(x: number, y: number): FieldTile { 267 | return this.tiles[x + y * this.width]; 268 | } 269 | 270 | setAgent(playerid: number, x: number, y: number): boolean { 271 | const { type: att, player: pid } = this.get(x, y); 272 | if (att === Field.WALL && pid !== playerid) return false; 273 | this.set(x, y, Field.WALL, playerid); 274 | return true; 275 | } 276 | 277 | fillArea(): void { 278 | // プレイヤーごとに入れ子関係なく囲まれている所にフラグを立て、まとめる。 279 | // (bitごと 例:010だったら、1番目のプレイヤーの領地or城壁であるという意味) 280 | // 各マスの立っているbitが一つだけだったらそのプレイヤーの領地or城壁で確定。 281 | // 2つ以上bitが立っていたら入れ子になっているので、その部分だけmaskをかけ、もう一度最初からやり直す。 282 | // (whileするたびに入れ子が一個ずつ解消されていくイメージ) 283 | // 説明難しい… 284 | 285 | const w = this.width; 286 | const h = this.height; 287 | const field: FieldTile[] = []; 288 | 289 | // 外側に空白のマスを作る 290 | for (let y = -1; y < h + 1; y++) { 291 | for (let x = -1; x < w + 1; x++) { 292 | if (x < 0 || x >= w || y < 0 || y >= h) { 293 | field.push({ type: Field.AREA, player: null }); 294 | } else field.push({ ...this.tiles[x + y * w] }); 295 | } 296 | } 297 | 298 | const mask = new Array(field.length); 299 | for (let i = 0; i < mask.length; i++) mask[i] = 1; 300 | 301 | // reduceはmaskの中身が全部0になるまで続けている 302 | while (mask.reduce((s, c) => s + c)) { 303 | const area = new Array(field.length); 304 | for (let pid = 0; pid < this.nPlayer; pid++) { 305 | for (let i = 0; i < field.length; i++) { 306 | area[i] |= 1 << pid; // とりあえず全部自分の領地としてマーク? 307 | } 308 | // 外側の囲まれていないところを判定 309 | // 0,0(上で余白のマスをつけているため必ず中立マス)から探索を初め壁にぶつかるまで探索することで囲まれていない部分を判定している 310 | const chk = (x: number, y: number) => { 311 | const n = x + y * (w + 2); 312 | if (x < 0 || x >= w + 2 || y < 0 || y >= h + 2) return; // ボードの外を判定しようとしていたらreturn 313 | else if ((area[n] & (1 << pid)) === 0) return; // すでに囲まれていないと判定されたところはスキップ 314 | else if ( 315 | mask[n] !== 0 && field[n].type === Field.WALL && 316 | field[n].player === pid 317 | ) { 318 | return; 319 | } else { 320 | area[n] &= ~(1 << pid); // 探索しているマスを「囲まれていない」と判定(ビットを下げる) 321 | chk(x - 1, y); // 左のマスを探索 322 | chk(x + 1, y); // 右のマスを探索 323 | chk(x - 1, y - 1); // 左上のマスを探索 324 | chk(x, y - 1); // 上のマスを探索 325 | chk(x + 1, y - 1); // 右上のマスを探索 326 | chk(x - 1, y + 1); // 左下のマスを探索 327 | chk(x, y + 1); // 下のマスを探索 328 | chk(x + 1, y + 1); // 右下のマスを探索 329 | } 330 | }; 331 | chk(0, 0); 332 | //console.log(mask, narea, pid); 333 | } 334 | 335 | //console.log("mamamama"); 336 | //console.log(mask, area, "mask"); 337 | for (let i = 0; i < field.length; i++) { 338 | if (area[i] === 0) { 339 | mask[i] = 0; // どのプレイヤーの領地でもないと判定されたところはスキップするようにmaskを0に設定 340 | } else if ((area[i] & (area[i] - 1)) === 0) { // 2のべき乗かを判定(Trueならそのプレイヤーの領地として確定) 341 | field[i].player = Math.log2(area[i]); 342 | mask[i] = 0; 343 | } 344 | } 345 | } 346 | 347 | for (let i = 0; i < w; i++) { 348 | for (let j = 0; j < h; j++) { 349 | const n = i + j * w; 350 | const nexp = (i + 1) + (j + 1) * (w + 2); 351 | if (this.tiles[n].type !== Field.WALL) { 352 | this.tiles[n] = field[nexp]; 353 | } 354 | } 355 | } 356 | } 357 | 358 | getPoints(): Point[] { 359 | const points: ReturnType = []; 360 | for (let i = 0; i < this.nPlayer; i++) { 361 | points[i] = { areaPoint: 0, wallPoint: 0 }; 362 | } 363 | this.tiles.forEach(({ type: att, player: pid }, idx) => { 364 | if (pid === null) return; 365 | const p = points[pid]; 366 | const pnt = this.points[idx]; 367 | if (att === Field.WALL) { 368 | p.wallPoint += pnt; 369 | } else if (att === Field.AREA) { 370 | p.areaPoint += Math.abs(pnt); 371 | } 372 | }); 373 | return points; 374 | } 375 | } 376 | 377 | export type GameInit = Board; 378 | 379 | class Game { 380 | totalTurn: number; 381 | players: Player[]; 382 | field: Field; 383 | log: { 384 | players: { 385 | point: Point; 386 | actions: Action[]; 387 | }[]; 388 | }[]; 389 | turn: number; 390 | 391 | constructor(gameInit: GameInit) { 392 | const { totalTurn = 30, ...fieldInit } = gameInit; 393 | 394 | this.totalTurn = totalTurn; 395 | this.players = []; 396 | this.field = new Field(fieldInit); 397 | this.log = []; 398 | this.turn = 0; 399 | } 400 | 401 | static fromJSON(data: GameJson): Game { 402 | const board: GameInit = { 403 | width: data.field.width, 404 | height: data.field.height, 405 | points: data.field.points, 406 | nAgent: data.field.nAgent, 407 | nPlayer: data.field.nPlayer, 408 | totalTurn: data.totalTurn, 409 | }; 410 | const game = new Game(board); 411 | game.players = data.players.map((p) => Player.fromJSON(p, game)); 412 | game.field.tiles = data.field.tiles; 413 | game.log = data.log; 414 | game.turn = data.turn; 415 | return game; 416 | } 417 | 418 | attachPlayer(player: Player): boolean { 419 | if (!this.isFree()) return false; 420 | if (this.players.indexOf(player) >= 0) return false; 421 | 422 | player.index = this.players.push(player) - 1; 423 | player.setGame(this); 424 | return true; 425 | } 426 | 427 | // status : free -> ready -> gaming -> ended 428 | getStatus() { 429 | if (this.turn === 0) { 430 | if (this.players.length < this.field.nPlayer) return "free"; 431 | else return "ready"; 432 | } else if (this.log.length !== this.totalTurn) { 433 | return "gaming"; 434 | } else { 435 | return "ended"; 436 | } 437 | } 438 | isFree() { 439 | return this.getStatus() === "free"; 440 | } 441 | isReady() { 442 | return this.getStatus() === "ready"; 443 | } 444 | isGaming() { 445 | return this.getStatus() === "gaming"; 446 | } 447 | isEnded() { 448 | return this.getStatus() === "ended"; 449 | } 450 | 451 | start(): void { 452 | this.turn = 1; 453 | } 454 | 455 | nextTurn(): boolean { 456 | const actions: Action[][] = []; 457 | this.players.forEach((p, idx) => actions[idx] = p.getActions()); 458 | // console.log("actions", actions); 459 | 460 | this.#checkActions(actions); // 同じエージェントの2回移動、画面外など無効な操作をチェック 461 | this.#revertNotOwnerWall(); // PUT, MOVE先が敵陣壁ではないか?チェックし無効化 462 | this.#checkConflict(actions); // 同じマスを差しているものはすべて無効 // 壁remove & move は、removeが有効 463 | this.#revertOverlap(); // 仮に配置または動かし、かぶったところをrevert 464 | this.#putOrMove(); // 配置または動かし、フィールド更新 465 | this.#removeOrNot(); // AgentがいるところをREMOVEしているものはrevert 466 | 467 | this.#commit(); 468 | 469 | this.field.fillArea(); 470 | 471 | this.log.push({ 472 | players: actions.map((ar, idx) => { 473 | return { 474 | point: this.field.getPoints()[idx], 475 | actions: ar, 476 | }; 477 | }), 478 | }); 479 | 480 | this.players.forEach((p) => p.clearActions()); 481 | if (this.turn < this.totalTurn) { 482 | this.turn++; 483 | return true; 484 | } else { 485 | return false; 486 | } 487 | } 488 | 489 | #checkActions(actions: Action[][]): void { 490 | const nplayer = actions.length; 491 | // 範囲外と、かぶりチェック 492 | for (let playerid = 0; playerid < nplayer; playerid++) { 493 | const done: Record = {}; 494 | actions[playerid].forEach((a) => { 495 | const aid = a.agentId; 496 | const agents = this.players[playerid].agents; 497 | if (aid < 0 || aid >= agents.length) { 498 | a.res = Action.ERR_ILLEGAL_AGENT; 499 | return; 500 | } 501 | const doneAgent = done[aid]; 502 | if (doneAgent) { 503 | a.res = Action.ERR_ONLY_ONE_TURN; 504 | doneAgent.res = Action.ERR_ONLY_ONE_TURN; 505 | return; 506 | } 507 | done[aid] = a; 508 | }); 509 | } 510 | // 変な動きチェック 511 | for (let playerid = 0; playerid < nplayer; playerid++) { 512 | actions[playerid].filter((a) => a.res === Action.SUCCESS).forEach((a) => { 513 | const aid = a.agentId; 514 | const agents = this.players[playerid].agents; 515 | const agent = agents[aid]; 516 | if (!agent.check(a)) { 517 | a.res = Action.ERR_ILLEGAL_ACTION; 518 | return; 519 | } 520 | }); 521 | } 522 | } 523 | 524 | #checkConflict(actions: Action[][]): void { 525 | //console.log("Actions", actions); 526 | const chkfield: Action[][] = new Array(this.field.tiles.length); 527 | for (let i = 0; i < chkfield.length; i++) { 528 | chkfield[i] = []; 529 | } 530 | const nplayer = actions.length; 531 | for (let playerid = 0; playerid < nplayer; playerid++) { 532 | actions[playerid].forEach((a) => { 533 | if (a.res !== Action.SUCCESS) return false; 534 | const n = a.x + a.y * this.field.width; 535 | if (n >= 0 && n < chkfield.length) { 536 | chkfield[n].push(a); 537 | } 538 | }); 539 | } 540 | // PUT/MOVE/REMOVE、競合はすべて無効 541 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 542 | // console.log("conflict", a); 543 | a.forEach((action) => action.res = Action.CONFLICT); 544 | }); 545 | } 546 | 547 | #putOrMove(): void { 548 | flat(this.players.map((p) => p.agents)).forEach((agent) => { 549 | if (!agent.isValidAction()) return; 550 | if (!agent.putOrMove()) { 551 | // throw new Error("illegal action!") 552 | // console.log(`throw new Error("illegal action!")`); 553 | return; 554 | } 555 | }); 556 | } 557 | 558 | #revertOverlap(): void { 559 | let reverts = false; 560 | const chkfield: Agent[][] = new Array(this.field.tiles.length); 561 | do { 562 | for (let i = 0; i < chkfield.length; i++) { 563 | chkfield[i] = []; 564 | } 565 | flat(this.players.map((p) => p.agents)).forEach((agent) => { 566 | const act = agent.isValidAction(); 567 | if ( 568 | act && 569 | (act.type === Action.MOVE || act.type === Action.PUT) 570 | ) { 571 | const n = act.x + act.y * this.field.width; 572 | //console.log("act", n); 573 | chkfield[n].push(agent); 574 | } else { 575 | if (agent.x === -1) return; 576 | const n = agent.x + agent.y * this.field.width; 577 | //console.log("agent", n); 578 | chkfield[n].push(agent); 579 | } 580 | }); 581 | reverts = false; 582 | //console.log("chkfield", chkfield); 583 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 584 | // console.log("**\nreverts", a); 585 | a.forEach((agent) => agent.revert()); 586 | reverts = true; 587 | }); 588 | //console.log(reverts); 589 | } while (reverts); // revertがあったら再度全件チェック 590 | } 591 | 592 | #removeOrNot(): void { 593 | const agents = flat(this.players.map((p) => p.agents)); 594 | agents.forEach((agent) => { 595 | if (agent.x === -1) return; 596 | const act = agent.isValidAction(); 597 | if (!act) return; 598 | if (act.type !== Action.REMOVE) return; 599 | if (agents.find((a) => a.x === act.x && a.y === act.y)) { 600 | act.res = Action.REVERT; 601 | } else { 602 | agent.remove(); 603 | } 604 | }); 605 | } 606 | 607 | #revertNotOwnerWall(): void { 608 | const agents = flat(this.players.map((p) => p.agents)); 609 | const fld = this.field.tiles; 610 | const w = this.field.width; 611 | agents.forEach((agent) => { 612 | if (agent.x === -1) return; 613 | const act = agent.isValidAction(); 614 | if (!act) return; 615 | if (act.type !== Action.MOVE && act.type !== Action.PUT) return; 616 | // only PUT & MOVE 617 | const n = act.x + act.y * w; 618 | const f = fld[n]; 619 | const iswall = f.type === Field.WALL; 620 | const owner = f.player; 621 | if (iswall && owner !== agent.playerIdx && owner !== -1) { 622 | agent.revert(); 623 | } 624 | }); 625 | } 626 | 627 | #commit(): void { 628 | const agents = flat(this.players.map((p) => p.agents)); 629 | agents.forEach((agent) => { 630 | // if (agent.x === -1) return; 631 | // if (!agent.isValidAction()) return; 632 | agent.commit(); 633 | }); 634 | } 635 | } 636 | 637 | class Player { 638 | id: string; 639 | spec: string; 640 | game: T | null; 641 | actions: Action[]; 642 | index: number; 643 | agents: Agent[]; 644 | 645 | constructor(id: string, spec = "") { 646 | this.id = id; 647 | this.spec = spec; 648 | this.game = null; 649 | this.actions = []; 650 | this.index = -1; 651 | this.agents = []; 652 | } 653 | 654 | static fromJSON(data: PlayerJson, game?: Game): Player { 655 | const player = new Player(data.id, data.spec); 656 | player.index = data.index; 657 | if (game) { 658 | player.game = game; 659 | player.agents = data.agents.map((a) => { 660 | return Agent.fromJSON(a, player.index, game.field); 661 | }); 662 | } 663 | 664 | return player; 665 | } 666 | 667 | toJSON(): PlayerJson { 668 | return { 669 | id: this.id, 670 | spec: this.spec, 671 | index: this.index, 672 | actions: this.actions, 673 | agents: this.agents, 674 | }; 675 | } 676 | 677 | setGame(game: T): void { 678 | this.game = game; 679 | for (let j = 0; j < game.field.nAgent; j++) { 680 | this.agents.push(new Agent(game.field, this.index)); 681 | } 682 | } 683 | 684 | setActions(actions: Action[]): typeof Game.prototype.turn { 685 | if (this.game === null) throw new Error("game is null"); 686 | this.actions = actions; 687 | return this.game.turn; 688 | } 689 | 690 | getActions(): typeof Player.prototype.actions { 691 | return this.actions; 692 | } 693 | 694 | clearActions(): void { 695 | this.actions = []; 696 | } 697 | } 698 | 699 | export { Action, Agent, Field, Game, Player }; 700 | -------------------------------------------------------------------------------- /mjs/Kakomimasu.js: -------------------------------------------------------------------------------- 1 | var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { 2 | if (kind === "m") throw new TypeError("Private method is not writable"); 3 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); 4 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); 5 | return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; 6 | }; 7 | var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { 8 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); 9 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); 10 | return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); 11 | }; 12 | var __rest = (this && this.__rest) || function (s, e) { 13 | var t = {}; 14 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 15 | t[p] = s[p]; 16 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 17 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 18 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 19 | t[p[i]] = s[p[i]]; 20 | } 21 | return t; 22 | }; 23 | var _Agent_instances, _Agent_lastaction, _Agent_isOnBoard, _Agent_checkOnBoard, _Agent_checkDir, _Agent_checkPut, _Agent_checkMove, _Agent_checkRemove, _Agent_put, _Agent_move, _Game_instances, _Game_checkActions, _Game_checkConflict, _Game_putOrMove, _Game_revertOverlap, _Game_removeOrNot, _Game_revertNotOwnerWall, _Game_commit; 24 | import { flat } from "./util.js"; 25 | class Agent { 26 | constructor(field, playeridx) { 27 | _Agent_instances.add(this); 28 | Object.defineProperty(this, "field", { 29 | enumerable: true, 30 | configurable: true, 31 | writable: true, 32 | value: void 0 33 | }); 34 | Object.defineProperty(this, "playerIdx", { 35 | enumerable: true, 36 | configurable: true, 37 | writable: true, 38 | value: void 0 39 | }); 40 | Object.defineProperty(this, "x", { 41 | enumerable: true, 42 | configurable: true, 43 | writable: true, 44 | value: void 0 45 | }); 46 | Object.defineProperty(this, "y", { 47 | enumerable: true, 48 | configurable: true, 49 | writable: true, 50 | value: void 0 51 | }); 52 | Object.defineProperty(this, "bkx", { 53 | enumerable: true, 54 | configurable: true, 55 | writable: true, 56 | value: void 0 57 | }); 58 | Object.defineProperty(this, "bky", { 59 | enumerable: true, 60 | configurable: true, 61 | writable: true, 62 | value: void 0 63 | }); 64 | _Agent_lastaction.set(this, void 0); 65 | this.field = field; 66 | this.playerIdx = playeridx; 67 | this.x = -1; 68 | this.y = -1; 69 | this.bkx = -1; 70 | this.bky = -1; 71 | __classPrivateFieldSet(this, _Agent_lastaction, null, "f"); 72 | } 73 | static fromJSON(data, playerIdx, field) { 74 | const agent = new Agent(field, playerIdx); 75 | agent.x = data.x; 76 | agent.y = data.y; 77 | return agent; 78 | } 79 | toJSON() { 80 | return { x: this.x, y: this.y }; 81 | } 82 | check(act) { 83 | __classPrivateFieldSet(this, _Agent_lastaction, act, "f"); 84 | this.bkx = this.x; 85 | this.bky = this.y; 86 | const x = act.x; 87 | const y = act.y; 88 | const t = act.type; 89 | if (t === Action.PUT) 90 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkPut).call(this, x, y); 91 | else if (t === Action.MOVE) 92 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkMove).call(this, x, y); 93 | else if (t === Action.REMOVE) 94 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkRemove).call(this, x, y); 95 | else 96 | return false; 97 | } 98 | isValidAction() { 99 | if (!__classPrivateFieldGet(this, _Agent_lastaction, "f")) 100 | return; 101 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f").res !== Action.SUCCESS) 102 | return; 103 | return __classPrivateFieldGet(this, _Agent_lastaction, "f"); 104 | } 105 | putOrMove() { 106 | //console.log("putormove", this); 107 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f") == null) 108 | throw new Error("putOrMove before check"); 109 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f").res !== Action.SUCCESS) 110 | return false; 111 | const act = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 112 | const x = act.x; 113 | const y = act.y; 114 | const t = act.type; 115 | if (t === Action.PUT) 116 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_put).call(this, x, y); 117 | if (t === Action.MOVE) 118 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_move).call(this, x, y); 119 | return true; 120 | } 121 | remove() { 122 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f") == null) 123 | throw new Error("remove before check"); 124 | const { x, y } = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 125 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkRemove).call(this, x, y)) 126 | return false; 127 | this.field.set(x, y, Field.AREA, null); 128 | return true; 129 | } 130 | commit() { 131 | __classPrivateFieldSet(this, _Agent_lastaction, null, "f"); 132 | } 133 | revert() { 134 | const act = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 135 | if (act && (act.type === Action.MOVE || act.type === Action.PUT) && 136 | act.res === Action.SUCCESS) { 137 | act.res = Action.REVERT; 138 | this.x = this.bkx; 139 | this.y = this.bky; 140 | } 141 | } 142 | } 143 | _Agent_lastaction = new WeakMap(), _Agent_instances = new WeakSet(), _Agent_isOnBoard = function _Agent_isOnBoard() { 144 | return this.x !== -1; 145 | }, _Agent_checkOnBoard = function _Agent_checkOnBoard(x, y) { 146 | return x >= 0 && x < this.field.width && y >= 0 && 147 | y < this.field.height; 148 | }, _Agent_checkDir = function _Agent_checkDir(x, y) { 149 | if (this.x === x && this.y === y) 150 | return false; 151 | return Math.abs(this.x - x) <= 1 && Math.abs(this.y - y) <= 1; 152 | }, _Agent_checkPut = function _Agent_checkPut(x, y) { 153 | if (__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 154 | return false; 155 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 156 | return false; 157 | return true; 158 | }, _Agent_checkMove = function _Agent_checkMove(x, y) { 159 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 160 | return false; 161 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 162 | return false; 163 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkDir).call(this, x, y)) 164 | return false; 165 | return true; 166 | }, _Agent_checkRemove = function _Agent_checkRemove(x, y) { 167 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 168 | return false; 169 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 170 | return false; 171 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkDir).call(this, x, y)) 172 | return false; 173 | if (this.field.get(x, y).type !== Field.WALL) 174 | return false; 175 | return true; 176 | }, _Agent_put = function _Agent_put(x, y) { 177 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkPut).call(this, x, y)) 178 | return false; 179 | if (!this.field.setAgent(this.playerIdx, x, y)) { 180 | return false; // throw new Error("can't enter the wall"); 181 | } 182 | this.x = x; 183 | this.y = y; 184 | return true; 185 | }, _Agent_move = function _Agent_move(x, y) { 186 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkMove).call(this, x, y)) 187 | return false; 188 | if (!this.field.setAgent(this.playerIdx, x, y)) { 189 | return false; // throw new Error("can't enter the wall"); 190 | } 191 | this.x = x; 192 | this.y = y; 193 | return true; 194 | }; 195 | class Action { 196 | constructor(agentid, type, x, y) { 197 | Object.defineProperty(this, "agentId", { 198 | enumerable: true, 199 | configurable: true, 200 | writable: true, 201 | value: void 0 202 | }); 203 | Object.defineProperty(this, "type", { 204 | enumerable: true, 205 | configurable: true, 206 | writable: true, 207 | value: void 0 208 | }); 209 | Object.defineProperty(this, "x", { 210 | enumerable: true, 211 | configurable: true, 212 | writable: true, 213 | value: void 0 214 | }); 215 | Object.defineProperty(this, "y", { 216 | enumerable: true, 217 | configurable: true, 218 | writable: true, 219 | value: void 0 220 | }); 221 | Object.defineProperty(this, "res", { 222 | enumerable: true, 223 | configurable: true, 224 | writable: true, 225 | value: void 0 226 | }); 227 | this.agentId = agentid; 228 | this.type = type; 229 | this.x = x; 230 | this.y = y; 231 | this.res = Action.SUCCESS; 232 | } 233 | static fromJSON(data) { 234 | const action = new Action(data.agentId, data.type, data.x, data.y); 235 | action.res = data.res; 236 | return action; 237 | } 238 | static getMessage(res) { 239 | return [ 240 | "success", 241 | "conflict", 242 | "revert", 243 | "err: only 1 turn", 244 | "err: illegal agent", 245 | "err: illegal action", 246 | ][res]; 247 | } 248 | } 249 | // Action Type 250 | Object.defineProperty(Action, "PUT", { 251 | enumerable: true, 252 | configurable: true, 253 | writable: true, 254 | value: 1 255 | }); 256 | Object.defineProperty(Action, "NONE", { 257 | enumerable: true, 258 | configurable: true, 259 | writable: true, 260 | value: 2 261 | }); 262 | Object.defineProperty(Action, "MOVE", { 263 | enumerable: true, 264 | configurable: true, 265 | writable: true, 266 | value: 3 267 | }); 268 | Object.defineProperty(Action, "REMOVE", { 269 | enumerable: true, 270 | configurable: true, 271 | writable: true, 272 | value: 4 273 | }); 274 | // Action Res 275 | Object.defineProperty(Action, "SUCCESS", { 276 | enumerable: true, 277 | configurable: true, 278 | writable: true, 279 | value: 0 280 | }); 281 | Object.defineProperty(Action, "CONFLICT", { 282 | enumerable: true, 283 | configurable: true, 284 | writable: true, 285 | value: 1 286 | }); 287 | Object.defineProperty(Action, "REVERT", { 288 | enumerable: true, 289 | configurable: true, 290 | writable: true, 291 | value: 2 292 | }); 293 | Object.defineProperty(Action, "ERR_ONLY_ONE_TURN", { 294 | enumerable: true, 295 | configurable: true, 296 | writable: true, 297 | value: 3 298 | }); 299 | Object.defineProperty(Action, "ERR_ILLEGAL_AGENT", { 300 | enumerable: true, 301 | configurable: true, 302 | writable: true, 303 | value: 4 304 | }); 305 | Object.defineProperty(Action, "ERR_ILLEGAL_ACTION", { 306 | enumerable: true, 307 | configurable: true, 308 | writable: true, 309 | value: 5 310 | }); 311 | Object.defineProperty(Action, "fromArray", { 312 | enumerable: true, 313 | configurable: true, 314 | writable: true, 315 | value: (array) => array.map((a) => new Action(a[0], a[1], a[2], a[3])) 316 | }); 317 | class Field { 318 | constructor({ width, height, points, nAgent = 4, nPlayer = 2 }) { 319 | Object.defineProperty(this, "width", { 320 | enumerable: true, 321 | configurable: true, 322 | writable: true, 323 | value: void 0 324 | }); 325 | Object.defineProperty(this, "height", { 326 | enumerable: true, 327 | configurable: true, 328 | writable: true, 329 | value: void 0 330 | }); 331 | Object.defineProperty(this, "nAgent", { 332 | enumerable: true, 333 | configurable: true, 334 | writable: true, 335 | value: void 0 336 | }); 337 | Object.defineProperty(this, "nPlayer", { 338 | enumerable: true, 339 | configurable: true, 340 | writable: true, 341 | value: void 0 342 | }); 343 | Object.defineProperty(this, "points", { 344 | enumerable: true, 345 | configurable: true, 346 | writable: true, 347 | value: void 0 348 | }); 349 | Object.defineProperty(this, "tiles", { 350 | enumerable: true, 351 | configurable: true, 352 | writable: true, 353 | value: void 0 354 | }); 355 | if (points.length !== width * height) { 356 | throw Error("points.length must be " + width * height); 357 | } 358 | this.width = width; 359 | this.height = height; 360 | this.nAgent = nAgent; 361 | this.nPlayer = nPlayer; 362 | this.points = points; 363 | this.tiles = new Array(width * height).fill({ 364 | type: Field.AREA, 365 | player: null, 366 | }); 367 | } 368 | static fromJSON(data) { 369 | const { tiles } = data, init = __rest(data, ["tiles"]); 370 | const field = new Field(init); 371 | field.tiles = tiles; 372 | return field; 373 | } 374 | set(x, y, att, playerid) { 375 | if (playerid !== null && playerid < 0) { 376 | throw Error("playerid must be 0 or more"); 377 | } 378 | this.tiles[x + y * this.width] = { type: att, player: playerid }; 379 | } 380 | get(x, y) { 381 | return this.tiles[x + y * this.width]; 382 | } 383 | setAgent(playerid, x, y) { 384 | const { type: att, player: pid } = this.get(x, y); 385 | if (att === Field.WALL && pid !== playerid) 386 | return false; 387 | this.set(x, y, Field.WALL, playerid); 388 | return true; 389 | } 390 | fillArea() { 391 | // プレイヤーごとに入れ子関係なく囲まれている所にフラグを立て、まとめる。 392 | // (bitごと 例:010だったら、1番目のプレイヤーの領地or城壁であるという意味) 393 | // 各マスの立っているbitが一つだけだったらそのプレイヤーの領地or城壁で確定。 394 | // 2つ以上bitが立っていたら入れ子になっているので、その部分だけmaskをかけ、もう一度最初からやり直す。 395 | // (whileするたびに入れ子が一個ずつ解消されていくイメージ) 396 | // 説明難しい… 397 | const w = this.width; 398 | const h = this.height; 399 | const field = []; 400 | // 外側に空白のマスを作る 401 | for (let y = -1; y < h + 1; y++) { 402 | for (let x = -1; x < w + 1; x++) { 403 | if (x < 0 || x >= w || y < 0 || y >= h) { 404 | field.push({ type: Field.AREA, player: null }); 405 | } 406 | else 407 | field.push(Object.assign({}, this.tiles[x + y * w])); 408 | } 409 | } 410 | const mask = new Array(field.length); 411 | for (let i = 0; i < mask.length; i++) 412 | mask[i] = 1; 413 | while (mask.reduce((s, c) => s + c)) { 414 | const area = new Array(field.length); 415 | for (let pid = 0; pid < this.nPlayer; pid++) { 416 | for (let i = 0; i < field.length; i++) { 417 | area[i] |= 1 << pid; 418 | } 419 | // 外側の囲まれていないところを判定 420 | const chk = (x, y) => { 421 | const n = x + y * (w + 2); 422 | if (x < 0 || x >= w + 2 || y < 0 || y >= h + 2) 423 | return; 424 | else if ((area[n] & (1 << pid)) === 0) 425 | return; 426 | else if (mask[n] !== 0 && field[n].type === Field.WALL && 427 | field[n].player === pid) { 428 | return; 429 | } 430 | else { 431 | area[n] &= ~(1 << pid); 432 | chk(x - 1, y); 433 | chk(x + 1, y); 434 | chk(x - 1, y - 1); 435 | chk(x, y - 1); 436 | chk(x + 1, y - 1); 437 | chk(x - 1, y + 1); 438 | chk(x, y + 1); 439 | chk(x + 1, y + 1); 440 | } 441 | }; 442 | chk(0, 0); 443 | //console.log(mask, narea, pid); 444 | } 445 | //console.log(area); 446 | //console.log("mamamama"); 447 | //console.log(mask, area, "mask"); 448 | for (let i = 0; i < field.length; i++) { 449 | if (area[i] === 0) { 450 | mask[i] = 0; 451 | } 452 | else if ((area[i] & (area[i] - 1)) === 0) { // 2のべき乗かを判定 453 | field[i].player = Math.log2(area[i]); 454 | mask[i] = 0; 455 | } 456 | } 457 | } 458 | for (let i = 0; i < w; i++) { 459 | for (let j = 0; j < h; j++) { 460 | const n = i + j * w; 461 | const nexp = (i + 1) + (j + 1) * (w + 2); 462 | if (this.tiles[n].type !== Field.WALL) { 463 | this.tiles[n].player = field[nexp].player; 464 | } 465 | } 466 | } 467 | } 468 | getPoints() { 469 | const points = []; 470 | for (let i = 0; i < this.nPlayer; i++) { 471 | points[i] = { areaPoint: 0, wallPoint: 0 }; 472 | } 473 | this.tiles.forEach(({ type: att, player: pid }, idx) => { 474 | if (pid === null) 475 | return; 476 | const p = points[pid]; 477 | const pnt = this.points[idx]; 478 | if (att === Field.WALL) { 479 | p.wallPoint += pnt; 480 | } 481 | else if (att === Field.AREA) { 482 | p.areaPoint += Math.abs(pnt); 483 | } 484 | }); 485 | return points; 486 | } 487 | } 488 | Object.defineProperty(Field, "AREA", { 489 | enumerable: true, 490 | configurable: true, 491 | writable: true, 492 | value: 0 493 | }); 494 | Object.defineProperty(Field, "WALL", { 495 | enumerable: true, 496 | configurable: true, 497 | writable: true, 498 | value: 1 499 | }); 500 | class Game { 501 | constructor(gameInit) { 502 | _Game_instances.add(this); 503 | Object.defineProperty(this, "totalTurn", { 504 | enumerable: true, 505 | configurable: true, 506 | writable: true, 507 | value: void 0 508 | }); 509 | Object.defineProperty(this, "players", { 510 | enumerable: true, 511 | configurable: true, 512 | writable: true, 513 | value: void 0 514 | }); 515 | Object.defineProperty(this, "field", { 516 | enumerable: true, 517 | configurable: true, 518 | writable: true, 519 | value: void 0 520 | }); 521 | Object.defineProperty(this, "log", { 522 | enumerable: true, 523 | configurable: true, 524 | writable: true, 525 | value: void 0 526 | }); 527 | Object.defineProperty(this, "turn", { 528 | enumerable: true, 529 | configurable: true, 530 | writable: true, 531 | value: void 0 532 | }); 533 | const { totalTurn = 30 } = gameInit, fieldInit = __rest(gameInit, ["totalTurn"]); 534 | this.totalTurn = totalTurn; 535 | this.players = []; 536 | this.field = new Field(fieldInit); 537 | this.log = []; 538 | this.turn = 0; 539 | } 540 | static fromJSON(data) { 541 | const board = { 542 | width: data.field.width, 543 | height: data.field.height, 544 | points: data.field.points, 545 | nAgent: data.field.nAgent, 546 | nPlayer: data.field.nPlayer, 547 | totalTurn: data.totalTurn, 548 | }; 549 | const game = new Game(board); 550 | game.players = data.players.map((p) => Player.fromJSON(p, game)); 551 | game.field.tiles = data.field.tiles; 552 | game.log = data.log; 553 | game.turn = data.turn; 554 | return game; 555 | } 556 | attachPlayer(player) { 557 | if (!this.isFree()) 558 | return false; 559 | if (this.players.indexOf(player) >= 0) 560 | return false; 561 | player.index = this.players.push(player) - 1; 562 | player.setGame(this); 563 | return true; 564 | } 565 | // status : free -> ready -> gaming -> ended 566 | getStatus() { 567 | if (this.turn === 0) { 568 | if (this.players.length < this.field.nPlayer) 569 | return "free"; 570 | else 571 | return "ready"; 572 | } 573 | else if (this.log.length !== this.totalTurn) { 574 | return "gaming"; 575 | } 576 | else { 577 | return "ended"; 578 | } 579 | } 580 | isFree() { 581 | return this.getStatus() === "free"; 582 | } 583 | isReady() { 584 | return this.getStatus() === "ready"; 585 | } 586 | isGaming() { 587 | return this.getStatus() === "gaming"; 588 | } 589 | isEnded() { 590 | return this.getStatus() === "ended"; 591 | } 592 | start() { 593 | this.turn = 1; 594 | } 595 | nextTurn() { 596 | const actions = []; 597 | this.players.forEach((p, idx) => actions[idx] = p.getActions()); 598 | // console.log("actions", actions); 599 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_checkActions).call(this, actions); // 同じエージェントの2回移動、画面外など無効な操作をチェック 600 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_revertNotOwnerWall).call(this); // PUT, MOVE先が敵陣壁ではないか?チェックし無効化 601 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_checkConflict).call(this, actions); // 同じマスを差しているものはすべて無効 // 壁remove & move は、removeが有効 602 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_revertOverlap).call(this); // 仮に配置または動かし、かぶったところをrevert 603 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_putOrMove).call(this); // 配置または動かし、フィールド更新 604 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_removeOrNot).call(this); // AgentがいるところをREMOVEしているものはrevert 605 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_commit).call(this); 606 | this.field.fillArea(); 607 | this.log.push({ 608 | players: actions.map((ar, idx) => { 609 | return { 610 | point: this.field.getPoints()[idx], 611 | actions: ar, 612 | }; 613 | }), 614 | }); 615 | this.players.forEach((p) => p.clearActions()); 616 | if (this.turn < this.totalTurn) { 617 | this.turn++; 618 | return true; 619 | } 620 | else { 621 | return false; 622 | } 623 | } 624 | } 625 | _Game_instances = new WeakSet(), _Game_checkActions = function _Game_checkActions(actions) { 626 | const nplayer = actions.length; 627 | // 範囲外と、かぶりチェック 628 | for (let playerid = 0; playerid < nplayer; playerid++) { 629 | const done = {}; 630 | actions[playerid].forEach((a) => { 631 | const aid = a.agentId; 632 | const agents = this.players[playerid].agents; 633 | if (aid < 0 || aid >= agents.length) { 634 | a.res = Action.ERR_ILLEGAL_AGENT; 635 | return; 636 | } 637 | const doneAgent = done[aid]; 638 | if (doneAgent) { 639 | a.res = Action.ERR_ONLY_ONE_TURN; 640 | doneAgent.res = Action.ERR_ONLY_ONE_TURN; 641 | return; 642 | } 643 | done[aid] = a; 644 | }); 645 | } 646 | // 変な動きチェック 647 | for (let playerid = 0; playerid < nplayer; playerid++) { 648 | actions[playerid].filter((a) => a.res === Action.SUCCESS).forEach((a) => { 649 | const aid = a.agentId; 650 | const agents = this.players[playerid].agents; 651 | const agent = agents[aid]; 652 | if (!agent.check(a)) { 653 | a.res = Action.ERR_ILLEGAL_ACTION; 654 | return; 655 | } 656 | }); 657 | } 658 | }, _Game_checkConflict = function _Game_checkConflict(actions) { 659 | //console.log("Actions", actions); 660 | const chkfield = new Array(this.field.tiles.length); 661 | for (let i = 0; i < chkfield.length; i++) { 662 | chkfield[i] = []; 663 | } 664 | const nplayer = actions.length; 665 | for (let playerid = 0; playerid < nplayer; playerid++) { 666 | actions[playerid].forEach((a) => { 667 | if (a.res !== Action.SUCCESS) 668 | return false; 669 | const n = a.x + a.y * this.field.width; 670 | if (n >= 0 && n < chkfield.length) { 671 | chkfield[n].push(a); 672 | } 673 | }); 674 | } 675 | // PUT/MOVE/REMOVE、競合はすべて無効 676 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 677 | // console.log("conflict", a); 678 | a.forEach((action) => action.res = Action.CONFLICT); 679 | }); 680 | }, _Game_putOrMove = function _Game_putOrMove() { 681 | flat(this.players.map((p) => p.agents)).forEach((agent) => { 682 | if (!agent.isValidAction()) 683 | return; 684 | if (!agent.putOrMove()) { 685 | // throw new Error("illegal action!") 686 | // console.log(`throw new Error("illegal action!")`); 687 | return; 688 | } 689 | }); 690 | }, _Game_revertOverlap = function _Game_revertOverlap() { 691 | let reverts = false; 692 | const chkfield = new Array(this.field.tiles.length); 693 | do { 694 | for (let i = 0; i < chkfield.length; i++) { 695 | chkfield[i] = []; 696 | } 697 | flat(this.players.map((p) => p.agents)).forEach((agent) => { 698 | const act = agent.isValidAction(); 699 | if (act && 700 | (act.type === Action.MOVE || act.type === Action.PUT)) { 701 | const n = act.x + act.y * this.field.width; 702 | //console.log("act", n); 703 | chkfield[n].push(agent); 704 | } 705 | else { 706 | if (agent.x === -1) 707 | return; 708 | const n = agent.x + agent.y * this.field.width; 709 | //console.log("agent", n); 710 | chkfield[n].push(agent); 711 | } 712 | }); 713 | reverts = false; 714 | //console.log("chkfield", chkfield); 715 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 716 | // console.log("**\nreverts", a); 717 | a.forEach((agent) => agent.revert()); 718 | reverts = true; 719 | }); 720 | //console.log(reverts); 721 | } while (reverts); // revertがあったら再度全件チェック 722 | }, _Game_removeOrNot = function _Game_removeOrNot() { 723 | const agents = flat(this.players.map((p) => p.agents)); 724 | agents.forEach((agent) => { 725 | if (agent.x === -1) 726 | return; 727 | const act = agent.isValidAction(); 728 | if (!act) 729 | return; 730 | if (act.type !== Action.REMOVE) 731 | return; 732 | if (agents.find((a) => a.x === act.x && a.y === act.y)) { 733 | act.res = Action.REVERT; 734 | } 735 | else { 736 | agent.remove(); 737 | } 738 | }); 739 | }, _Game_revertNotOwnerWall = function _Game_revertNotOwnerWall() { 740 | const agents = flat(this.players.map((p) => p.agents)); 741 | const fld = this.field.tiles; 742 | const w = this.field.width; 743 | agents.forEach((agent) => { 744 | if (agent.x === -1) 745 | return; 746 | const act = agent.isValidAction(); 747 | if (!act) 748 | return; 749 | if (act.type !== Action.MOVE && act.type !== Action.PUT) 750 | return; 751 | // only PUT & MOVE 752 | const n = act.x + act.y * w; 753 | const f = fld[n]; 754 | const iswall = f.type === Field.WALL; 755 | const owner = f.player; 756 | if (iswall && owner !== agent.playerIdx && owner !== -1) { 757 | agent.revert(); 758 | } 759 | }); 760 | }, _Game_commit = function _Game_commit() { 761 | const agents = flat(this.players.map((p) => p.agents)); 762 | agents.forEach((agent) => { 763 | // if (agent.x === -1) return; 764 | // if (!agent.isValidAction()) return; 765 | agent.commit(); 766 | }); 767 | }; 768 | class Player { 769 | constructor(id, spec = "") { 770 | Object.defineProperty(this, "id", { 771 | enumerable: true, 772 | configurable: true, 773 | writable: true, 774 | value: void 0 775 | }); 776 | Object.defineProperty(this, "spec", { 777 | enumerable: true, 778 | configurable: true, 779 | writable: true, 780 | value: void 0 781 | }); 782 | Object.defineProperty(this, "game", { 783 | enumerable: true, 784 | configurable: true, 785 | writable: true, 786 | value: void 0 787 | }); 788 | Object.defineProperty(this, "actions", { 789 | enumerable: true, 790 | configurable: true, 791 | writable: true, 792 | value: void 0 793 | }); 794 | Object.defineProperty(this, "index", { 795 | enumerable: true, 796 | configurable: true, 797 | writable: true, 798 | value: void 0 799 | }); 800 | Object.defineProperty(this, "agents", { 801 | enumerable: true, 802 | configurable: true, 803 | writable: true, 804 | value: void 0 805 | }); 806 | this.id = id; 807 | this.spec = spec; 808 | this.game = null; 809 | this.actions = []; 810 | this.index = -1; 811 | this.agents = []; 812 | } 813 | static fromJSON(data, game) { 814 | const player = new Player(data.id, data.spec); 815 | player.index = data.index; 816 | if (game) { 817 | player.game = game; 818 | player.agents = data.agents.map((a) => { 819 | return Agent.fromJSON(a, player.index, game.field); 820 | }); 821 | } 822 | return player; 823 | } 824 | toJSON() { 825 | return { 826 | id: this.id, 827 | spec: this.spec, 828 | index: this.index, 829 | actions: this.actions, 830 | agents: this.agents, 831 | }; 832 | } 833 | setGame(game) { 834 | this.game = game; 835 | for (let j = 0; j < game.field.nAgent; j++) { 836 | this.agents.push(new Agent(game.field, this.index)); 837 | } 838 | } 839 | setActions(actions) { 840 | if (this.game === null) 841 | throw new Error("game is null"); 842 | this.actions = actions; 843 | return this.game.turn; 844 | } 845 | getActions() { 846 | return this.actions; 847 | } 848 | clearActions() { 849 | this.actions = []; 850 | } 851 | } 852 | export { Action, Agent, Field, Game, Player }; 853 | -------------------------------------------------------------------------------- /cjs/Kakomimasu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { 3 | if (kind === "m") throw new TypeError("Private method is not writable"); 4 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); 5 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); 6 | return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; 7 | }; 8 | var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { 9 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); 10 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); 11 | return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); 12 | }; 13 | var __rest = (this && this.__rest) || function (s, e) { 14 | var t = {}; 15 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 16 | t[p] = s[p]; 17 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 18 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 19 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 20 | t[p[i]] = s[p[i]]; 21 | } 22 | return t; 23 | }; 24 | var _Agent_instances, _Agent_lastaction, _Agent_isOnBoard, _Agent_checkOnBoard, _Agent_checkDir, _Agent_checkPut, _Agent_checkMove, _Agent_checkRemove, _Agent_put, _Agent_move, _Game_instances, _Game_checkActions, _Game_checkConflict, _Game_putOrMove, _Game_revertOverlap, _Game_removeOrNot, _Game_revertNotOwnerWall, _Game_commit; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.Player = exports.Game = exports.Field = exports.Agent = exports.Action = void 0; 27 | const util_js_1 = require("./util.js"); 28 | class Agent { 29 | constructor(field, playeridx) { 30 | _Agent_instances.add(this); 31 | Object.defineProperty(this, "field", { 32 | enumerable: true, 33 | configurable: true, 34 | writable: true, 35 | value: void 0 36 | }); 37 | Object.defineProperty(this, "playerIdx", { 38 | enumerable: true, 39 | configurable: true, 40 | writable: true, 41 | value: void 0 42 | }); 43 | Object.defineProperty(this, "x", { 44 | enumerable: true, 45 | configurable: true, 46 | writable: true, 47 | value: void 0 48 | }); 49 | Object.defineProperty(this, "y", { 50 | enumerable: true, 51 | configurable: true, 52 | writable: true, 53 | value: void 0 54 | }); 55 | Object.defineProperty(this, "bkx", { 56 | enumerable: true, 57 | configurable: true, 58 | writable: true, 59 | value: void 0 60 | }); 61 | Object.defineProperty(this, "bky", { 62 | enumerable: true, 63 | configurable: true, 64 | writable: true, 65 | value: void 0 66 | }); 67 | _Agent_lastaction.set(this, void 0); 68 | this.field = field; 69 | this.playerIdx = playeridx; 70 | this.x = -1; 71 | this.y = -1; 72 | this.bkx = -1; 73 | this.bky = -1; 74 | __classPrivateFieldSet(this, _Agent_lastaction, null, "f"); 75 | } 76 | static fromJSON(data, playerIdx, field) { 77 | const agent = new Agent(field, playerIdx); 78 | agent.x = data.x; 79 | agent.y = data.y; 80 | return agent; 81 | } 82 | toJSON() { 83 | return { x: this.x, y: this.y }; 84 | } 85 | check(act) { 86 | __classPrivateFieldSet(this, _Agent_lastaction, act, "f"); 87 | this.bkx = this.x; 88 | this.bky = this.y; 89 | const x = act.x; 90 | const y = act.y; 91 | const t = act.type; 92 | if (t === Action.PUT) 93 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkPut).call(this, x, y); 94 | else if (t === Action.MOVE) 95 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkMove).call(this, x, y); 96 | else if (t === Action.REMOVE) 97 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkRemove).call(this, x, y); 98 | else 99 | return false; 100 | } 101 | isValidAction() { 102 | if (!__classPrivateFieldGet(this, _Agent_lastaction, "f")) 103 | return; 104 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f").res !== Action.SUCCESS) 105 | return; 106 | return __classPrivateFieldGet(this, _Agent_lastaction, "f"); 107 | } 108 | putOrMove() { 109 | //console.log("putormove", this); 110 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f") == null) 111 | throw new Error("putOrMove before check"); 112 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f").res !== Action.SUCCESS) 113 | return false; 114 | const act = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 115 | const x = act.x; 116 | const y = act.y; 117 | const t = act.type; 118 | if (t === Action.PUT) 119 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_put).call(this, x, y); 120 | if (t === Action.MOVE) 121 | return __classPrivateFieldGet(this, _Agent_instances, "m", _Agent_move).call(this, x, y); 122 | return true; 123 | } 124 | remove() { 125 | if (__classPrivateFieldGet(this, _Agent_lastaction, "f") == null) 126 | throw new Error("remove before check"); 127 | const { x, y } = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 128 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkRemove).call(this, x, y)) 129 | return false; 130 | this.field.set(x, y, Field.AREA, null); 131 | return true; 132 | } 133 | commit() { 134 | __classPrivateFieldSet(this, _Agent_lastaction, null, "f"); 135 | } 136 | revert() { 137 | const act = __classPrivateFieldGet(this, _Agent_lastaction, "f"); 138 | if (act && (act.type === Action.MOVE || act.type === Action.PUT) && 139 | act.res === Action.SUCCESS) { 140 | act.res = Action.REVERT; 141 | this.x = this.bkx; 142 | this.y = this.bky; 143 | } 144 | } 145 | } 146 | exports.Agent = Agent; 147 | _Agent_lastaction = new WeakMap(), _Agent_instances = new WeakSet(), _Agent_isOnBoard = function _Agent_isOnBoard() { 148 | return this.x !== -1; 149 | }, _Agent_checkOnBoard = function _Agent_checkOnBoard(x, y) { 150 | return x >= 0 && x < this.field.width && y >= 0 && 151 | y < this.field.height; 152 | }, _Agent_checkDir = function _Agent_checkDir(x, y) { 153 | if (this.x === x && this.y === y) 154 | return false; 155 | return Math.abs(this.x - x) <= 1 && Math.abs(this.y - y) <= 1; 156 | }, _Agent_checkPut = function _Agent_checkPut(x, y) { 157 | if (__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 158 | return false; 159 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 160 | return false; 161 | return true; 162 | }, _Agent_checkMove = function _Agent_checkMove(x, y) { 163 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 164 | return false; 165 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 166 | return false; 167 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkDir).call(this, x, y)) 168 | return false; 169 | return true; 170 | }, _Agent_checkRemove = function _Agent_checkRemove(x, y) { 171 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_isOnBoard).call(this)) 172 | return false; 173 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkOnBoard).call(this, x, y)) 174 | return false; 175 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkDir).call(this, x, y)) 176 | return false; 177 | if (this.field.get(x, y).type !== Field.WALL) 178 | return false; 179 | return true; 180 | }, _Agent_put = function _Agent_put(x, y) { 181 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkPut).call(this, x, y)) 182 | return false; 183 | if (!this.field.setAgent(this.playerIdx, x, y)) { 184 | return false; // throw new Error("can't enter the wall"); 185 | } 186 | this.x = x; 187 | this.y = y; 188 | return true; 189 | }, _Agent_move = function _Agent_move(x, y) { 190 | if (!__classPrivateFieldGet(this, _Agent_instances, "m", _Agent_checkMove).call(this, x, y)) 191 | return false; 192 | if (!this.field.setAgent(this.playerIdx, x, y)) { 193 | return false; // throw new Error("can't enter the wall"); 194 | } 195 | this.x = x; 196 | this.y = y; 197 | return true; 198 | }; 199 | class Action { 200 | constructor(agentid, type, x, y) { 201 | Object.defineProperty(this, "agentId", { 202 | enumerable: true, 203 | configurable: true, 204 | writable: true, 205 | value: void 0 206 | }); 207 | Object.defineProperty(this, "type", { 208 | enumerable: true, 209 | configurable: true, 210 | writable: true, 211 | value: void 0 212 | }); 213 | Object.defineProperty(this, "x", { 214 | enumerable: true, 215 | configurable: true, 216 | writable: true, 217 | value: void 0 218 | }); 219 | Object.defineProperty(this, "y", { 220 | enumerable: true, 221 | configurable: true, 222 | writable: true, 223 | value: void 0 224 | }); 225 | Object.defineProperty(this, "res", { 226 | enumerable: true, 227 | configurable: true, 228 | writable: true, 229 | value: void 0 230 | }); 231 | this.agentId = agentid; 232 | this.type = type; 233 | this.x = x; 234 | this.y = y; 235 | this.res = Action.SUCCESS; 236 | } 237 | static fromJSON(data) { 238 | const action = new Action(data.agentId, data.type, data.x, data.y); 239 | action.res = data.res; 240 | return action; 241 | } 242 | static getMessage(res) { 243 | return [ 244 | "success", 245 | "conflict", 246 | "revert", 247 | "err: only 1 turn", 248 | "err: illegal agent", 249 | "err: illegal action", 250 | ][res]; 251 | } 252 | } 253 | exports.Action = Action; 254 | // Action Type 255 | Object.defineProperty(Action, "PUT", { 256 | enumerable: true, 257 | configurable: true, 258 | writable: true, 259 | value: 1 260 | }); 261 | Object.defineProperty(Action, "NONE", { 262 | enumerable: true, 263 | configurable: true, 264 | writable: true, 265 | value: 2 266 | }); 267 | Object.defineProperty(Action, "MOVE", { 268 | enumerable: true, 269 | configurable: true, 270 | writable: true, 271 | value: 3 272 | }); 273 | Object.defineProperty(Action, "REMOVE", { 274 | enumerable: true, 275 | configurable: true, 276 | writable: true, 277 | value: 4 278 | }); 279 | // Action Res 280 | Object.defineProperty(Action, "SUCCESS", { 281 | enumerable: true, 282 | configurable: true, 283 | writable: true, 284 | value: 0 285 | }); 286 | Object.defineProperty(Action, "CONFLICT", { 287 | enumerable: true, 288 | configurable: true, 289 | writable: true, 290 | value: 1 291 | }); 292 | Object.defineProperty(Action, "REVERT", { 293 | enumerable: true, 294 | configurable: true, 295 | writable: true, 296 | value: 2 297 | }); 298 | Object.defineProperty(Action, "ERR_ONLY_ONE_TURN", { 299 | enumerable: true, 300 | configurable: true, 301 | writable: true, 302 | value: 3 303 | }); 304 | Object.defineProperty(Action, "ERR_ILLEGAL_AGENT", { 305 | enumerable: true, 306 | configurable: true, 307 | writable: true, 308 | value: 4 309 | }); 310 | Object.defineProperty(Action, "ERR_ILLEGAL_ACTION", { 311 | enumerable: true, 312 | configurable: true, 313 | writable: true, 314 | value: 5 315 | }); 316 | Object.defineProperty(Action, "fromArray", { 317 | enumerable: true, 318 | configurable: true, 319 | writable: true, 320 | value: (array) => array.map((a) => new Action(a[0], a[1], a[2], a[3])) 321 | }); 322 | class Field { 323 | constructor({ width, height, points, nAgent = 4, nPlayer = 2 }) { 324 | Object.defineProperty(this, "width", { 325 | enumerable: true, 326 | configurable: true, 327 | writable: true, 328 | value: void 0 329 | }); 330 | Object.defineProperty(this, "height", { 331 | enumerable: true, 332 | configurable: true, 333 | writable: true, 334 | value: void 0 335 | }); 336 | Object.defineProperty(this, "nAgent", { 337 | enumerable: true, 338 | configurable: true, 339 | writable: true, 340 | value: void 0 341 | }); 342 | Object.defineProperty(this, "nPlayer", { 343 | enumerable: true, 344 | configurable: true, 345 | writable: true, 346 | value: void 0 347 | }); 348 | Object.defineProperty(this, "points", { 349 | enumerable: true, 350 | configurable: true, 351 | writable: true, 352 | value: void 0 353 | }); 354 | Object.defineProperty(this, "tiles", { 355 | enumerable: true, 356 | configurable: true, 357 | writable: true, 358 | value: void 0 359 | }); 360 | if (points.length !== width * height) { 361 | throw Error("points.length must be " + width * height); 362 | } 363 | this.width = width; 364 | this.height = height; 365 | this.nAgent = nAgent; 366 | this.nPlayer = nPlayer; 367 | this.points = points; 368 | this.tiles = new Array(width * height).fill({ 369 | type: Field.AREA, 370 | player: null, 371 | }); 372 | } 373 | static fromJSON(data) { 374 | const { tiles } = data, init = __rest(data, ["tiles"]); 375 | const field = new Field(init); 376 | field.tiles = tiles; 377 | return field; 378 | } 379 | set(x, y, att, playerid) { 380 | if (playerid !== null && playerid < 0) { 381 | throw Error("playerid must be 0 or more"); 382 | } 383 | this.tiles[x + y * this.width] = { type: att, player: playerid }; 384 | } 385 | get(x, y) { 386 | return this.tiles[x + y * this.width]; 387 | } 388 | setAgent(playerid, x, y) { 389 | const { type: att, player: pid } = this.get(x, y); 390 | if (att === Field.WALL && pid !== playerid) 391 | return false; 392 | this.set(x, y, Field.WALL, playerid); 393 | return true; 394 | } 395 | fillArea() { 396 | // プレイヤーごとに入れ子関係なく囲まれている所にフラグを立て、まとめる。 397 | // (bitごと 例:010だったら、1番目のプレイヤーの領地or城壁であるという意味) 398 | // 各マスの立っているbitが一つだけだったらそのプレイヤーの領地or城壁で確定。 399 | // 2つ以上bitが立っていたら入れ子になっているので、その部分だけmaskをかけ、もう一度最初からやり直す。 400 | // (whileするたびに入れ子が一個ずつ解消されていくイメージ) 401 | // 説明難しい… 402 | const w = this.width; 403 | const h = this.height; 404 | const field = []; 405 | // 外側に空白のマスを作る 406 | for (let y = -1; y < h + 1; y++) { 407 | for (let x = -1; x < w + 1; x++) { 408 | if (x < 0 || x >= w || y < 0 || y >= h) { 409 | field.push({ type: Field.AREA, player: null }); 410 | } 411 | else 412 | field.push(Object.assign({}, this.tiles[x + y * w])); 413 | } 414 | } 415 | const mask = new Array(field.length); 416 | for (let i = 0; i < mask.length; i++) 417 | mask[i] = 1; 418 | while (mask.reduce((s, c) => s + c)) { 419 | const area = new Array(field.length); 420 | for (let pid = 0; pid < this.nPlayer; pid++) { 421 | for (let i = 0; i < field.length; i++) { 422 | area[i] |= 1 << pid; 423 | } 424 | // 外側の囲まれていないところを判定 425 | const chk = (x, y) => { 426 | const n = x + y * (w + 2); 427 | if (x < 0 || x >= w + 2 || y < 0 || y >= h + 2) 428 | return; 429 | else if ((area[n] & (1 << pid)) === 0) 430 | return; 431 | else if (mask[n] !== 0 && field[n].type === Field.WALL && 432 | field[n].player === pid) { 433 | return; 434 | } 435 | else { 436 | area[n] &= ~(1 << pid); 437 | chk(x - 1, y); 438 | chk(x + 1, y); 439 | chk(x - 1, y - 1); 440 | chk(x, y - 1); 441 | chk(x + 1, y - 1); 442 | chk(x - 1, y + 1); 443 | chk(x, y + 1); 444 | chk(x + 1, y + 1); 445 | } 446 | }; 447 | chk(0, 0); 448 | //console.log(mask, narea, pid); 449 | } 450 | //console.log(area); 451 | //console.log("mamamama"); 452 | //console.log(mask, area, "mask"); 453 | for (let i = 0; i < field.length; i++) { 454 | if (area[i] === 0) { 455 | mask[i] = 0; 456 | } 457 | else if ((area[i] & (area[i] - 1)) === 0) { // 2のべき乗かを判定 458 | field[i].player = Math.log2(area[i]); 459 | mask[i] = 0; 460 | } 461 | } 462 | } 463 | for (let i = 0; i < w; i++) { 464 | for (let j = 0; j < h; j++) { 465 | const n = i + j * w; 466 | const nexp = (i + 1) + (j + 1) * (w + 2); 467 | if (this.tiles[n].type !== Field.WALL) { 468 | this.tiles[n].player = field[nexp].player; 469 | } 470 | } 471 | } 472 | } 473 | getPoints() { 474 | const points = []; 475 | for (let i = 0; i < this.nPlayer; i++) { 476 | points[i] = { areaPoint: 0, wallPoint: 0 }; 477 | } 478 | this.tiles.forEach(({ type: att, player: pid }, idx) => { 479 | if (pid === null) 480 | return; 481 | const p = points[pid]; 482 | const pnt = this.points[idx]; 483 | if (att === Field.WALL) { 484 | p.wallPoint += pnt; 485 | } 486 | else if (att === Field.AREA) { 487 | p.areaPoint += Math.abs(pnt); 488 | } 489 | }); 490 | return points; 491 | } 492 | } 493 | exports.Field = Field; 494 | Object.defineProperty(Field, "AREA", { 495 | enumerable: true, 496 | configurable: true, 497 | writable: true, 498 | value: 0 499 | }); 500 | Object.defineProperty(Field, "WALL", { 501 | enumerable: true, 502 | configurable: true, 503 | writable: true, 504 | value: 1 505 | }); 506 | class Game { 507 | constructor(gameInit) { 508 | _Game_instances.add(this); 509 | Object.defineProperty(this, "totalTurn", { 510 | enumerable: true, 511 | configurable: true, 512 | writable: true, 513 | value: void 0 514 | }); 515 | Object.defineProperty(this, "players", { 516 | enumerable: true, 517 | configurable: true, 518 | writable: true, 519 | value: void 0 520 | }); 521 | Object.defineProperty(this, "field", { 522 | enumerable: true, 523 | configurable: true, 524 | writable: true, 525 | value: void 0 526 | }); 527 | Object.defineProperty(this, "log", { 528 | enumerable: true, 529 | configurable: true, 530 | writable: true, 531 | value: void 0 532 | }); 533 | Object.defineProperty(this, "turn", { 534 | enumerable: true, 535 | configurable: true, 536 | writable: true, 537 | value: void 0 538 | }); 539 | const { totalTurn = 30 } = gameInit, fieldInit = __rest(gameInit, ["totalTurn"]); 540 | this.totalTurn = totalTurn; 541 | this.players = []; 542 | this.field = new Field(fieldInit); 543 | this.log = []; 544 | this.turn = 0; 545 | } 546 | static fromJSON(data) { 547 | const board = { 548 | width: data.field.width, 549 | height: data.field.height, 550 | points: data.field.points, 551 | nAgent: data.field.nAgent, 552 | nPlayer: data.field.nPlayer, 553 | totalTurn: data.totalTurn, 554 | }; 555 | const game = new Game(board); 556 | game.players = data.players.map((p) => Player.fromJSON(p, game)); 557 | game.field.tiles = data.field.tiles; 558 | game.log = data.log; 559 | game.turn = data.turn; 560 | return game; 561 | } 562 | attachPlayer(player) { 563 | if (!this.isFree()) 564 | return false; 565 | if (this.players.indexOf(player) >= 0) 566 | return false; 567 | player.index = this.players.push(player) - 1; 568 | player.setGame(this); 569 | return true; 570 | } 571 | // status : free -> ready -> gaming -> ended 572 | getStatus() { 573 | if (this.turn === 0) { 574 | if (this.players.length < this.field.nPlayer) 575 | return "free"; 576 | else 577 | return "ready"; 578 | } 579 | else if (this.log.length !== this.totalTurn) { 580 | return "gaming"; 581 | } 582 | else { 583 | return "ended"; 584 | } 585 | } 586 | isFree() { 587 | return this.getStatus() === "free"; 588 | } 589 | isReady() { 590 | return this.getStatus() === "ready"; 591 | } 592 | isGaming() { 593 | return this.getStatus() === "gaming"; 594 | } 595 | isEnded() { 596 | return this.getStatus() === "ended"; 597 | } 598 | start() { 599 | this.turn = 1; 600 | } 601 | nextTurn() { 602 | const actions = []; 603 | this.players.forEach((p, idx) => actions[idx] = p.getActions()); 604 | // console.log("actions", actions); 605 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_checkActions).call(this, actions); // 同じエージェントの2回移動、画面外など無効な操作をチェック 606 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_revertNotOwnerWall).call(this); // PUT, MOVE先が敵陣壁ではないか?チェックし無効化 607 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_checkConflict).call(this, actions); // 同じマスを差しているものはすべて無効 // 壁remove & move は、removeが有効 608 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_revertOverlap).call(this); // 仮に配置または動かし、かぶったところをrevert 609 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_putOrMove).call(this); // 配置または動かし、フィールド更新 610 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_removeOrNot).call(this); // AgentがいるところをREMOVEしているものはrevert 611 | __classPrivateFieldGet(this, _Game_instances, "m", _Game_commit).call(this); 612 | this.field.fillArea(); 613 | this.log.push({ 614 | players: actions.map((ar, idx) => { 615 | return { 616 | point: this.field.getPoints()[idx], 617 | actions: ar, 618 | }; 619 | }), 620 | }); 621 | this.players.forEach((p) => p.clearActions()); 622 | if (this.turn < this.totalTurn) { 623 | this.turn++; 624 | return true; 625 | } 626 | else { 627 | return false; 628 | } 629 | } 630 | } 631 | exports.Game = Game; 632 | _Game_instances = new WeakSet(), _Game_checkActions = function _Game_checkActions(actions) { 633 | const nplayer = actions.length; 634 | // 範囲外と、かぶりチェック 635 | for (let playerid = 0; playerid < nplayer; playerid++) { 636 | const done = {}; 637 | actions[playerid].forEach((a) => { 638 | const aid = a.agentId; 639 | const agents = this.players[playerid].agents; 640 | if (aid < 0 || aid >= agents.length) { 641 | a.res = Action.ERR_ILLEGAL_AGENT; 642 | return; 643 | } 644 | const doneAgent = done[aid]; 645 | if (doneAgent) { 646 | a.res = Action.ERR_ONLY_ONE_TURN; 647 | doneAgent.res = Action.ERR_ONLY_ONE_TURN; 648 | return; 649 | } 650 | done[aid] = a; 651 | }); 652 | } 653 | // 変な動きチェック 654 | for (let playerid = 0; playerid < nplayer; playerid++) { 655 | actions[playerid].filter((a) => a.res === Action.SUCCESS).forEach((a) => { 656 | const aid = a.agentId; 657 | const agents = this.players[playerid].agents; 658 | const agent = agents[aid]; 659 | if (!agent.check(a)) { 660 | a.res = Action.ERR_ILLEGAL_ACTION; 661 | return; 662 | } 663 | }); 664 | } 665 | }, _Game_checkConflict = function _Game_checkConflict(actions) { 666 | //console.log("Actions", actions); 667 | const chkfield = new Array(this.field.tiles.length); 668 | for (let i = 0; i < chkfield.length; i++) { 669 | chkfield[i] = []; 670 | } 671 | const nplayer = actions.length; 672 | for (let playerid = 0; playerid < nplayer; playerid++) { 673 | actions[playerid].forEach((a) => { 674 | if (a.res !== Action.SUCCESS) 675 | return false; 676 | const n = a.x + a.y * this.field.width; 677 | if (n >= 0 && n < chkfield.length) { 678 | chkfield[n].push(a); 679 | } 680 | }); 681 | } 682 | // PUT/MOVE/REMOVE、競合はすべて無効 683 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 684 | // console.log("conflict", a); 685 | a.forEach((action) => action.res = Action.CONFLICT); 686 | }); 687 | }, _Game_putOrMove = function _Game_putOrMove() { 688 | (0, util_js_1.flat)(this.players.map((p) => p.agents)).forEach((agent) => { 689 | if (!agent.isValidAction()) 690 | return; 691 | if (!agent.putOrMove()) { 692 | // throw new Error("illegal action!") 693 | // console.log(`throw new Error("illegal action!")`); 694 | return; 695 | } 696 | }); 697 | }, _Game_revertOverlap = function _Game_revertOverlap() { 698 | let reverts = false; 699 | const chkfield = new Array(this.field.tiles.length); 700 | do { 701 | for (let i = 0; i < chkfield.length; i++) { 702 | chkfield[i] = []; 703 | } 704 | (0, util_js_1.flat)(this.players.map((p) => p.agents)).forEach((agent) => { 705 | const act = agent.isValidAction(); 706 | if (act && 707 | (act.type === Action.MOVE || act.type === Action.PUT)) { 708 | const n = act.x + act.y * this.field.width; 709 | //console.log("act", n); 710 | chkfield[n].push(agent); 711 | } 712 | else { 713 | if (agent.x === -1) 714 | return; 715 | const n = agent.x + agent.y * this.field.width; 716 | //console.log("agent", n); 717 | chkfield[n].push(agent); 718 | } 719 | }); 720 | reverts = false; 721 | //console.log("chkfield", chkfield); 722 | chkfield.filter((a) => a.length >= 2).forEach((a) => { 723 | // console.log("**\nreverts", a); 724 | a.forEach((agent) => agent.revert()); 725 | reverts = true; 726 | }); 727 | //console.log(reverts); 728 | } while (reverts); // revertがあったら再度全件チェック 729 | }, _Game_removeOrNot = function _Game_removeOrNot() { 730 | const agents = (0, util_js_1.flat)(this.players.map((p) => p.agents)); 731 | agents.forEach((agent) => { 732 | if (agent.x === -1) 733 | return; 734 | const act = agent.isValidAction(); 735 | if (!act) 736 | return; 737 | if (act.type !== Action.REMOVE) 738 | return; 739 | if (agents.find((a) => a.x === act.x && a.y === act.y)) { 740 | act.res = Action.REVERT; 741 | } 742 | else { 743 | agent.remove(); 744 | } 745 | }); 746 | }, _Game_revertNotOwnerWall = function _Game_revertNotOwnerWall() { 747 | const agents = (0, util_js_1.flat)(this.players.map((p) => p.agents)); 748 | const fld = this.field.tiles; 749 | const w = this.field.width; 750 | agents.forEach((agent) => { 751 | if (agent.x === -1) 752 | return; 753 | const act = agent.isValidAction(); 754 | if (!act) 755 | return; 756 | if (act.type !== Action.MOVE && act.type !== Action.PUT) 757 | return; 758 | // only PUT & MOVE 759 | const n = act.x + act.y * w; 760 | const f = fld[n]; 761 | const iswall = f.type === Field.WALL; 762 | const owner = f.player; 763 | if (iswall && owner !== agent.playerIdx && owner !== -1) { 764 | agent.revert(); 765 | } 766 | }); 767 | }, _Game_commit = function _Game_commit() { 768 | const agents = (0, util_js_1.flat)(this.players.map((p) => p.agents)); 769 | agents.forEach((agent) => { 770 | // if (agent.x === -1) return; 771 | // if (!agent.isValidAction()) return; 772 | agent.commit(); 773 | }); 774 | }; 775 | class Player { 776 | constructor(id, spec = "") { 777 | Object.defineProperty(this, "id", { 778 | enumerable: true, 779 | configurable: true, 780 | writable: true, 781 | value: void 0 782 | }); 783 | Object.defineProperty(this, "spec", { 784 | enumerable: true, 785 | configurable: true, 786 | writable: true, 787 | value: void 0 788 | }); 789 | Object.defineProperty(this, "game", { 790 | enumerable: true, 791 | configurable: true, 792 | writable: true, 793 | value: void 0 794 | }); 795 | Object.defineProperty(this, "actions", { 796 | enumerable: true, 797 | configurable: true, 798 | writable: true, 799 | value: void 0 800 | }); 801 | Object.defineProperty(this, "index", { 802 | enumerable: true, 803 | configurable: true, 804 | writable: true, 805 | value: void 0 806 | }); 807 | Object.defineProperty(this, "agents", { 808 | enumerable: true, 809 | configurable: true, 810 | writable: true, 811 | value: void 0 812 | }); 813 | this.id = id; 814 | this.spec = spec; 815 | this.game = null; 816 | this.actions = []; 817 | this.index = -1; 818 | this.agents = []; 819 | } 820 | static fromJSON(data, game) { 821 | const player = new Player(data.id, data.spec); 822 | player.index = data.index; 823 | if (game) { 824 | player.game = game; 825 | player.agents = data.agents.map((a) => { 826 | return Agent.fromJSON(a, player.index, game.field); 827 | }); 828 | } 829 | return player; 830 | } 831 | toJSON() { 832 | return { 833 | id: this.id, 834 | spec: this.spec, 835 | index: this.index, 836 | actions: this.actions, 837 | agents: this.agents, 838 | }; 839 | } 840 | setGame(game) { 841 | this.game = game; 842 | for (let j = 0; j < game.field.nAgent; j++) { 843 | this.agents.push(new Agent(game.field, this.index)); 844 | } 845 | } 846 | setActions(actions) { 847 | if (this.game === null) 848 | throw new Error("game is null"); 849 | this.actions = actions; 850 | return this.game.turn; 851 | } 852 | getActions() { 853 | return this.actions; 854 | } 855 | clearActions() { 856 | this.actions = []; 857 | } 858 | } 859 | exports.Player = Player; 860 | --------------------------------------------------------------------------------