├── .eslintrc.js ├── .github ├── release.yml └── workflows │ ├── bump.yml │ ├── gh-pages.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .travis.yml ├── LICENSE.txt ├── README.md ├── example ├── csa-player.html ├── jkf-player.html ├── ki2-player.html └── kif-player.html ├── jest.config.js ├── package-lock.json ├── package.json ├── specification ├── files │ ├── ryuou201409020101.jkf.json │ └── same_move_minimal.jkf.json └── json-kifu-format.schema.json ├── src ├── Formats.ts ├── __tests__ │ ├── __snapshots__ │ │ ├── fileTest.ts.snap │ │ ├── jkfplayerTest.ts.snap │ │ └── normalizerTest.ts.snap │ ├── fileTest.ts │ ├── jkfplayerTest.ts │ ├── normalizerTest-relative.ts │ └── normalizerTest.ts ├── jkfplayer.ts ├── main.ts ├── normalizer.ts └── peg │ ├── __tests__ │ ├── __snapshots__ │ │ ├── csaParserTest.ts.snap │ │ └── kifParserTest.ts.snap │ ├── csaParserTest.ts │ ├── ki2ParserTest.ts │ └── kifParserTest.ts │ ├── ambient.ts │ ├── csa-parser.pegjs │ ├── ki2-parser.pegjs │ ├── kif-parser.pegjs │ └── parsers.ts ├── test ├── board-serializer.js ├── debug-comment-content.html ├── files │ ├── csa │ │ ├── 2005_GPS_K-SHOGI.csa │ │ ├── 2005_YAMADA_GPS.csa │ │ ├── 8mai.csa │ │ ├── 8mai_hirate.csa │ │ ├── 9fu.csa │ │ ├── 9fu_komabetsu.csa │ │ ├── chudan.csa │ │ ├── dr1test1test0+tu-1_aobazero_viper-300-5F+aobazero+viper+20200719201040.csa │ │ ├── example.csa │ │ ├── formal.csa │ │ ├── illegal_lose.csa │ │ ├── illegal_win.csa │ │ ├── noeol.csa │ │ └── v2.csa │ ├── jkf │ │ ├── ryuou201409020101.jkf │ │ └── same_move_minimal.jkf │ ├── ki2 │ │ ├── 20091203.ki2 │ │ ├── 7mai.ki2u │ │ ├── 8mai.ki2 │ │ ├── 8mai.ki2u │ │ ├── 9fu.ki2 │ │ ├── chudan.ki2 │ │ ├── denou3-1.ki2 │ │ ├── fork.ki2 │ │ ├── illegal.ki2 │ │ ├── kobayashi_kinsho196702.ki2 │ │ └── noeol.ki2 │ └── kif │ │ ├── 20081220kyoochi.kif │ │ ├── 8mai.kif │ │ ├── 9fu.kif │ │ ├── chudan.kif │ │ ├── doh_branch.kifu │ │ ├── fork-notime.kif │ │ ├── fork-test.kif │ │ ├── fork.kif │ │ ├── henka.kif │ │ ├── illegal.kif │ │ ├── jt201409130101.kif │ │ ├── kifu_for_iphone.kif │ │ ├── kiou201403160101.kif │ │ ├── last-fork.kifu │ │ ├── last_comment.kif │ │ ├── meijinsen_20180508_M7_10034.kif │ │ ├── no_henka.kif │ │ ├── no_henka.kifu │ │ ├── noeol.kif │ │ ├── oui201407080101.kif │ │ ├── ouza201410070101.kif │ │ ├── ryuou201409020101.kif │ │ ├── ryuou4.kif │ │ ├── shogidb2.kifu │ │ ├── shogidokoro.kifu │ │ └── taichitsume.kif └── pegjs-jest.js ├── tsconfig.json ├── tsconfig.typecheck.json └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:jest/recommended", 10 | "prettier", 11 | ], 12 | parser: "@typescript-eslint/parser", 13 | parserOptions: { 14 | ecmaVersion: 13, 15 | sourceType: "module", 16 | }, 17 | plugins: ["@typescript-eslint", "jest"], 18 | ignorePatterns: [ 19 | ".eslintrc.js", 20 | "jest.config.js", 21 | "pegjs-jest.js", 22 | "webpack.config.js", 23 | "docs/", 24 | "bundle/", 25 | "dist/", 26 | ], 27 | rules: {}, 28 | }; 29 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: Documentation Changes 4 | labels: 5 | - documentation 6 | - title: New Features 7 | labels: 8 | - enhancement 9 | - title: Bug Fixes 10 | labels: 11 | - bug 12 | - title: Other Changes 13 | labels: 14 | - "*" -------------------------------------------------------------------------------- /.github/workflows/bump.yml: -------------------------------------------------------------------------------- 1 | name: Bump version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Type of version (major / minor / patch)' 8 | required: true 9 | 10 | jobs: 11 | bump-version: 12 | name: Bump version 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out source 16 | uses: actions/checkout@v3 17 | with: 18 | ssh-key: ${{ secrets.DEPLOY_KEY }} 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '16' 23 | cache: 'npm' 24 | - name: Install dependencies 25 | uses: bahmutov/npm-install@v1 26 | - name: Setup Git 27 | run: | 28 | git config user.name '${{ secrets.GIT_USER_NAME }}' 29 | git config user.email '${{ secrets.GIT_USER_EMAIL }}' 30 | - name: bump version 31 | run: npm version ${{ github.event.inputs.version }} 32 | 33 | - name: Push latest version 34 | run: git push origin master --follow-tags 35 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Setup Node 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: '16.x' 18 | 19 | - name: Cache dependencies 20 | uses: actions/cache@v2 21 | with: 22 | path: ~/.npm 23 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 24 | restore-keys: | 25 | ${{ runner.os }}-node- 26 | - run: npm ci 27 | - run: npm run deploy:ghpages 28 | 29 | - name: Deploy 30 | uses: peaceiris/actions-gh-pages@v3 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | publish_dir: ./public 34 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repo 11 | uses: actions/checkout@v3 12 | 13 | - name: Use Node 16 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 16 17 | 18 | - name: Install dependencies 19 | uses: bahmutov/npm-install@v1 20 | 21 | - name: Build 22 | run: npm run build 23 | 24 | typecheck: 25 | name: Typechecker 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout repo 29 | uses: actions/checkout@v3 30 | 31 | - name: Use Node 16 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: 16 35 | 36 | - name: Install dependencies 37 | uses: bahmutov/npm-install@v1 38 | 39 | - name: Typecheck 40 | run: npm run typecheck 41 | 42 | test: 43 | name: Unit and Integration Tests 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout repo 47 | uses: actions/checkout@v3 48 | 49 | - name: Use Node 16 50 | uses: actions/setup-node@v3 51 | with: 52 | node-version: 16 53 | 54 | - name: Install dependencies 55 | uses: bahmutov/npm-install@v1 56 | 57 | - name: Test 58 | run: npm run test -- --ci --coverage --maxWorkers=2 59 | 60 | lint: 61 | name: Linter 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout repo 65 | uses: actions/checkout@v3 66 | 67 | - name: Use Node 16 68 | uses: actions/setup-node@v3 69 | with: 70 | node-version: 16 71 | 72 | - name: Install dependencies 73 | uses: bahmutov/npm-install@v1 74 | 75 | - name: Build 76 | run: npm run build 77 | 78 | - name: Lint 79 | run: npm run lint 80 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: 16 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm install 17 | - run: npm run build 18 | - run: npm publish --access public 19 | env: 20 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bundle 2 | /coverage 3 | /dist 4 | /docs 5 | /node_modules 6 | /.idea 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /**/__tests__/ 2 | /.github/ 3 | /.idea/ 4 | /coverage/ 5 | /test/ 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/gallium 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.github/ 2 | /bundle/ 3 | /coverage/ 4 | /dist/ 5 | /docs/ 6 | /node_modules/ 7 | /public/ 8 | /src/__tests__/jkfplayerTest.ts 9 | README.md 10 | package-lock.json 11 | package.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4, 4 | "bracketSpacing": false 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 16 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 na2hiro (https://github.com/na2hiro) 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (Moved) `json-kifu-format`はmonorepoへ移行しました 2 | 3 | https://github.com/na2hiro/Kifu-for-JS/tree/master/packages/json-kifu-format 4 | 5 | # JSON棋譜フォーマット(JKF) ![Build Status](https://github.com/na2hiro/json-kifu-format/actions/workflows/main.yml/badge.svg?branch=master) 6 | JSONで将棋の棋譜を取り扱う標準形式JKFを定義しています.またJKFを既存のKIF, KI2, CSA等から変換するライブラリ及びJKFを用いて盤面再生を行うライブラリを提供します. 7 | 8 | ## 概要 9 | 10 | * 現状の問題として,既存のKIF, KI2, CSA形式が持つ情報はバラバラであり,アプリケーション側の対応に手間がかかる. 11 | * 上記形式の情報に加え,棋譜再生や表示に必要な情報を持った形式をJSONで表し,これを流通用の **JSON棋譜フォーマット(JKF)** とする. 12 | * JKFが一般的になれば,棋譜再生を行うアプリケーションの作成が容易になる. 13 | * 一例として,JKFを元に棋譜再生を行うJKFPlayerクラス(JavaScript)を提供する. 14 | * KIF, KI2, CSA各形式のパーサ(JavaScript)を用意し,JKFへ変換できるようにする. 15 | * これはパーサジェネレータによるパーサである.つまり, **KIF, KI2, CSA形式のEBNF表現** を提供している.これは将棋界において画期的である. 16 | 17 | ### 棋譜形式の比較 18 | 19 | | フォーマット | KIF | KI2 | CSA | JKF | 20 | | --- | --- | --- | --- | --- | 21 | | 0) 1) 元座標(from) | ○ | △(要相対逆算) | ○ | ○ | 22 | | 1) 取った駒(capture) | × | × | × | ○ | 23 | | 1) 2) 成り(promote) | ○(不成なし) | ○ | △ | ○ | 24 | | 2) 相対情報(relative) | △ | ○ | △ | ○ | 25 | | 2) 同〜(same) | ○ | ○ | × | ○ | 26 | | 手番(color) | × | ○ | ○ | ○ | 27 | | 消費時間(time) | ○ | × | ○ | ○ | 28 | 29 | ○=棋譜だけで可能 △=現在の局面を見れば可能 ×=不可能か,以前の局面を見れば可能 30 | 31 | * 0: 局面を1手進めるために必要な情報 32 | * 1: 局面を1手戻すために必要な情報 33 | * 2: 可読棋譜にするために必要な情報 34 | * 相対情報: 上下左右など,同じ座標に複数の駒が移動できる場合に駒を区別するための情報. 35 | * 相対逆算: 局面と座標と相対情報から,その位置に移動する駒を特定すること. 36 | 37 | ## 形式の定義 38 | 以下で定義される形式をVersion 1.0とします.1.x台では後方互換性を保つ変更のみを採用します. ([TypeDoc](https://apps.81.la/json-kifu-format/docs/modules.html) も参照) 39 | 40 | ### JSONの形式 (Version 1.0) 41 | [./src/Formats.ts](./src/Formats.ts)にある内容です."?"はない場合があるという意味です.小文字で始まる`型名`は組み込み型です. 42 | 43 | * JKFが持つフィールドの定義 44 | * header `string=>string` ヘッダ情報.キーはKI2,KIF等の日本語のものに準ずる.(例: "場所", "先手") 45 | * initial? 初期局面(なければ平手) 46 | * preset `InitialPresetString` 手合名 47 | * data? 初期局面データ(手合名がOTHERの時に使用) 48 | * color: `Color` 初手の手番 49 | * board: `以下[][]` board[x-1][y-1]に(x,y)の駒情報.駒がない場合は空オブジェクト`{}` 50 | * color?: `Color` 先手/後手 51 | * kind?: `string` 駒の種類 52 | * hands: `(string=>number)[]` 駒種がkey, 枚数がvalueの連想配列.0番目が先手,1番目が後手の持駒 53 | * moves `MoveFormat[]` n番目はn手目の棋譜(0番目は初期局面のコメント用) 54 | 55 | 上の定義で使うオブジェクトの補助定義は次の通り. 56 | 57 | * `MoveFormat` 指し手を表す 58 | * comments? `string[]` コメント 59 | * move? 駒の動き 60 | * color `Color` 先手/後手 61 | * from? `PlaceFormat` 移動元 打った場合はなし 62 | * to `PlaceFormat` 移動先 63 | * piece `string` 駒の種類(`FU` `KY`等のCSA形式) 64 | * same? `boolean` 直前と同じ場合 65 | * promote? `Bool` 成るかどうか true:成, false:不成, 無いかnull:どちらでもない 66 | * capture? `string` 取った駒の種類 67 | * relative? `RelativeString` 相対情報 68 | * time? 消費時間 69 | * now `TimeFormat` 1手 70 | * total `TimeFormat` 合計 71 | * special? `string` 特殊棋譜(CSAのTORYO, CHUDAN等) 72 | * forks? `MoveFormat[][]` 任意の長さの分岐を任意個格納する.分岐の初手はこのforksを持つ棋譜の代替の手とする(次の手ではなく) 73 | * `TimeFormat` 時間を表す 74 | * h? `Integer` 時 75 | * m `Integer` 分 76 | * s `Integer` 秒 77 | * `PlaceFormat` 座標を表す 78 | * x `Integer` 1から9 79 | * y `Integer` 一から九 80 | 81 | エイリアスは次の通り. 82 | 83 | * `Color = number` 陣営を表す. 先手: 0, 後手: 1 84 | * `RelativeString = string` 以下の文字列を連結したもの 85 | * 左, 直, 右: それぞれL, C, R(Left, Center/Choku, Right) 86 | * 上, 寄, 引: それぞれU, M, D(Up, Middle, Down) 87 | * 打: H(Hit (本来はDrop)) 88 | * `InitialPresetString = string` 平手,香落ち等KIFでサポートされている手合情報 89 | * HIRATE: 平手 90 | * KY: 香落ち 91 | * KY_R: 右香落ち 92 | * KA: 角落ち 93 | * HI: 飛車落ち 94 | * HIKY: 飛香落ち 95 | * 2: 二枚落ち 96 | * 3: 三枚落ち 97 | * 4: 四枚落ち 98 | * 5: 五枚落ち 99 | * 5_L: 左五枚落ち 100 | * 6: 六枚落ち 101 | * 7_L: 左七枚落ち 102 | * 7_R: 右七枚落ち 103 | * 8: 八枚落ち 104 | * 10: 十枚落ち 105 | * OTHER: その他 106 | 107 | ### 文字コード 108 | JSONで一般的なUTF-8を使用するものとします. 109 | 110 | ### JSON Schemaによる定義 111 | [JSON Schema](https://json-schema.org/) のバージョン 2020-12 による定義が [./specification/json-kifu-format.schema.json](./specification/json-kifu-format.schema.json) にあります。 112 | また、 `npm run schema:compile` によりこのJSON Schemaファイル自体の検証を、 `npm run schema:validate` により [specification/files/](specification/files/) 以下にあるJKFファイルのJSON Schemaファイルに対する検証を行います。 113 | 114 | ## JKFの例 115 | [.test/](./test/) 以下にも例が載っています. 116 | 117 | ### 通常 118 | ```json 119 | { 120 | "header": { 121 | "先手": "na2hiro", 122 | "後手": "うひょ" 123 | }, 124 | "moves": [ 125 | {}, 126 | {"move":{"from":{"x":7,"y":7},"to":{"x":7,"y":6},"color":0,"piece":"FU"}}, 127 | {"move":{"from":{"x":3,"y":3},"to":{"x":3,"y":4},"color":1,"piece":"FU"}}, 128 | {"move":{"from":{"x":8,"y":8},"to":{"x":2,"y":2},"color":0,"piece":"KA","capture":"KA","promote":false}}, 129 | {"move":{"from":{"x":3,"y":1},"to":{"x":2,"y":2},"color":1,"piece":"GI","capture":"KA","same":true}}, 130 | {"move":{"to":{"x":4,"y":5},"color":0,"piece":"KA"}}, 131 | 132 | {"special": "CHUDAN"} 133 | ] 134 | } 135 | ``` 136 | 137 | ### 分岐 138 | ```json 139 | { 140 | "header": {}, 141 | "moves": [ 142 | {"comments":["分岐の例"]}, 143 | {"move":{"from":{"x":7,"y":7},"to":{"x":7,"y":6},"color":0,"piece":"FU"}}, 144 | {"move":{"from":{"x":3,"y":3},"to":{"x":3,"y":4},"color":1,"piece":"FU"}, "comments":["次の手で二種類が考えられる:7七桂か2二角成である.","2二角成を選ぶと筋違い角となる."]}, 145 | {"move":{"from":{"x":8,"y":9},"to":{"x":7,"y":7},"color":0,"piece":"KE"}, "forks":[ 146 | [ 147 | {"move":{"from":{"x":8,"y":8},"to":{"x":2,"y":2},"color":0,"piece":"KA","capture":"KA","promote":false}}, 148 | {"move":{"from":{"x":3,"y":1},"to":{"x":2,"y":2},"color":1,"piece":"GI","capture":"KA","same":true}}, 149 | {"move":{"to":{"x":4,"y":5},"color":0,"piece":"KA"}} 150 | ] 151 | ]}, 152 | {"move":{"from":{"x":2,"y":2},"to":{"x":7,"y":7},"color":1,"piece":"KA","capture":"KE","promote":true,"same":true}}, 153 | {"move":{"from":{"x":8,"y":8},"to":{"x":7,"y":7},"color":0,"piece":"KA","capture":"UM","same":true}}, 154 | {"move":{"to":{"x":3,"y":3},"color":1,"piece":"KE","relative":"H"}} 155 | ] 156 | } 157 | ``` 158 | 159 | ### 駒落ち 160 | ```json 161 | { 162 | "header": {}, 163 | "initial": {"preset": "6"}, 164 | "moves": [ 165 | {}, 166 | {"move":{"from":{"x":5,"y":1},"to":{"x":4,"y":2},"color":1,"piece":"OU"}}, 167 | {"move":{"from":{"x":7,"y":7},"to":{"x":7,"y":6},"color":0,"piece":"FU"}}, 168 | {"move":{"from":{"x":6,"y":1},"to":{"x":7,"y":2},"color":1,"piece":"KI"}} 169 | ] 170 | } 171 | ``` 172 | ### 初形変則 173 | ```json 174 | { 175 | "header": {}, 176 | "initial": { 177 | "preset": "OTHER", 178 | "data": { 179 | "board": [ 180 | [{"color":1, "kind":"KY"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"KY"}], 181 | [{"color":1, "kind":"KE"}, {"color":1, "kind":"KA"},{"color":1, "kind":"FU"}, {}, {}, {}, { }, {"color":0, "kind":"HI"}, {"color":0, "kind":"KE"}], 182 | [{"color":1, "kind":"GI"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"GI"}], 183 | [{"color":1, "kind":"KI"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"KI"}], 184 | [{"color":1, "kind":"OU"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"OU"}], 185 | [{"color":1, "kind":"KI"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"KI"}], 186 | [{"color":1, "kind":"GI"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, { }, { }, {"color":0, "kind":"GI"}], 187 | [{"color":1, "kind":"KE"}, {"color":1, "kind":"HI"},{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, {"color":0, "kind":"KA"}, {"color":0, "kind":"KE"}], 188 | [{"color":1, "kind":"KY"}, { },{"color":1, "kind":"FU"}, {}, {}, {}, {"color":0, "kind":"FU"}, { }, {"color":0, "kind":"KY"}] 189 | ], 190 | "color": 0, 191 | "hands":[ 192 | {"FU":0,"KY":0,"KE":0,"GI":0,"KI":0,"KA":0,"HI":0}, 193 | {"FU":0,"KY":0,"KE":0,"GI":0,"KI":0,"KA":0,"HI":0} 194 | ] 195 | } 196 | }, 197 | "moves": [ 198 | {"comments": ["飛車角先落ち."]}, 199 | {"move":{"from":{"x":2,"y":8},"to":{"x":2,"y":3},"color":0,"piece":"HI","promote":true,"capture":"FU"}} 200 | ] 201 | } 202 | ``` 203 | 204 | ## プログラム 205 | ### ブラウザ向け 206 | 207 | [Releases](https://github.com/na2hiro/json-kifu-format/releases) からどうぞ.`json-kifu-format-*.*.*.min.js`を読み込むと,`JSONKifuFormat` が使えるようになります. 208 | 209 | ### node.js用 210 | ```shell 211 | $ npm install json-kifu-format 212 | ``` 213 | 214 | exportされているクラス群は次の通りです 215 | 216 | * `Parsers` 217 | * `parseKIF`: KIFをJSON形式に一対一変換するパーサ 218 | * `parseKI2`: KI2をJSON形式に一対一変換するパーサ 219 | * `parseCSA`: CSA(V1, V2, V2.1, V2.2)をJSON形式に一対一変換するパーサ 220 | * `Normalizer`: {KIF/KI2/CSA}と同等の情報しか持たないJKFを完全なJKFに変換するプログラム 221 | * `JKFPlayer`: JKFを扱う棋譜再生盤の例 222 | 223 | ## 開発環境 224 | ```shell 225 | $ nvm use && nvm i && npm i 226 | ``` 227 | 228 | 上記コマンドを実行することで開発に必要なパッケージをインストールできます. 229 | 230 | * [na2hiro/Shogi.js](https://github.com/na2hiro/Shogi.js): 将棋の盤駒を扱うライブラリ 231 | * [PEG.js](http://pegjs.majda.cz/): パーサジェネレータ 232 | * [TypeScript](https://www.typescriptlang.org/) 233 | * [Webpack](https://webpack.js.org/) 234 | * [Jest](https://jestjs.io/) 235 | * [ESLint](https://eslint.org/) 236 | 237 | ### コマンド 238 | 239 | ```shell 240 | $ npm run build 241 | $ npm run build:watch 242 | $ npm run build:analyze 243 | ``` 244 | 245 | ビルドが走ります.`build:watch`の場合,変更されるたびにビルドが走ります.`build:analyze`の場合,バンドルの大きさの可視化ができます. 246 | 247 | ```shell 248 | $ npm run test:watch 249 | ``` 250 | 251 | コンソールでテスト結果が表示されます.コードの変更が保存されるたびに必要なテストが再実行されるため,実装が既存の有効なテストを壊してないか簡単に確認できます. 252 | 253 | ```shell 254 | $ npm run test 255 | ``` 256 | 257 | 全てのテストが走るとともにカバレッジレポートが表示されます.`coverage/lcov-report/index.html`では,行ごとのカバレッジを確認できます.追加されたコードのブランチカバレッジが100%になるようにしてください.push時にチェックされ満たしていなければ却下されるはずです. 258 | 259 | ```shell 260 | $ npm run lint 261 | ``` 262 | 263 | コードの品質が検査されます.エラーがあればそれに従い直してください.push前にもチェックされます. 264 | 265 | ```shell 266 | $ npm run lint:fix 267 | ``` 268 | 269 | 自動的に修正可能な問題(インデント等)を直してくれます. 270 | 271 | ## バグ報告、機能要望等 272 | 273 | [issues](https://github.com/na2hiro/json-kifu-format/issues) 参照. 274 | 読み込みエラーとなる棋譜があったら教えて下さい. 275 | 276 | ## 参考文献 277 | 278 | * [CSA標準棋譜ファイル形式](http://www.computer-shogi.org/protocol/record_v22.html) 279 | * [shogi-format](https://code.google.com/p/shogi-format/): こちらは昔自ら提案したもの.大風呂敷だったため挫折しました.今回はより小さく洗練された形式を目指しており,また実装を用意し実用第一で進めていきます. 280 | * [棋譜の表記方法](http://www.shogi.or.jp/faq/kihuhyouki.html): 相対情報の書き方 281 | * [棋譜の形式について - 将棋の棋譜でーたべーす](http://wiki.optus.nu/shogi/index.php?post=%B4%FD%C9%E8%A4%CE%B7%C1%BC%B0%A4%CB%A4%C4%A4%A4%A4%C6): 出回っている棋譜形式のまとめ 282 | 283 | ## ライセンス 284 | 285 | MIT License (see [./LICENSE.txt](./LICENSE.txt)) 286 | 287 | ## Workflows (it's for me) 288 | 289 | ### Publish 290 | 1. Bump version using `bump` action 291 | 2. Create a new release with the new version 292 | 3. Check an email about publish result 293 | -------------------------------------------------------------------------------- /example/csa-player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | -------------------------------------------------------------------------------- /example/jkf-player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | -------------------------------------------------------------------------------- /example/ki2-player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 47 | -------------------------------------------------------------------------------- /example/kif-player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.tsx?$": "ts-jest", 4 | "^.+\\.pegjs$": "/test/pegjs-jest.js", 5 | }, 6 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 7 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 8 | coverageThreshold: { 9 | global: { 10 | branches: -6, 11 | functions: -1, 12 | lines: -3, 13 | statements: -5, 14 | }, 15 | }, 16 | collectCoverage: true, 17 | collectCoverageFrom: [ 18 | "src/**", 19 | "!**/__tests__/**", 20 | "!**/*.d.ts", 21 | "!src/main.ts", 22 | "!src/peg/**", 23 | ], 24 | snapshotSerializers: ["./test/board-serializer.js"], 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-kifu-format", 3 | "version": "1.3.1", 4 | "description": "JSON棋譜フォーマット(JKF)の定義とその関連ライブラリ", 5 | "main": "dist/json-kifu-format.js", 6 | "types": "dist/src/main.d.ts", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "build": "webpack --mode=production", 12 | "build:analyze": "npm run build -- --env analyze", 13 | "build:watch": "webpack --watch", 14 | "lint": "eslint ./ && prettier --check .", 15 | "lint:fix": "eslint ./ --fix && prettier --write .", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "typecheck": "tsc --project tsconfig.typecheck.json --noEmit", 19 | "prepublishOnly": "npm run build", 20 | "docs": "typedoc --exclude '**/__tests__/**/*' src/main.ts", 21 | "deploy:ghpages": "rm -rf ./public && mkdir -p ./public && npm run docs && mv ./docs ./public/", 22 | "schema:compile": "ajv compile --spec=draft2020 --strict-tuples=false -s specification/json-kifu-format.schema.json", 23 | "schema:validate": "ajv validate --spec=draft2020 --strict-tuples=false -s specification/json-kifu-format.schema.json -d 'specification/files/*.json'" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/na2hiro/json-kifu-format.git" 28 | }, 29 | "keywords": [ 30 | "shogi", 31 | "json", 32 | "kifu", 33 | "format", 34 | "jkf" 35 | ], 36 | "author": "na2hiro", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/na2hiro/json-kifu-format/issues" 40 | }, 41 | "homepage": "https://github.com/na2hiro/json-kifu-format#readme", 42 | "devDependencies": { 43 | "@types/jest": "^28.1.4", 44 | "@types/node": "^13.1.1", 45 | "@typescript-eslint/eslint-plugin": "^5.28.0", 46 | "@typescript-eslint/parser": "^5.28.0", 47 | "ajv-cli": "^5.0.0", 48 | "clean-webpack-plugin": "^4.0.0", 49 | "eslint": "^8.17.0", 50 | "eslint-config-prettier": "^8.5.0", 51 | "eslint-plugin-jest": "^26.5.3", 52 | "iconv-lite": "^0.6.3", 53 | "jest": "^28.1.2", 54 | "jschardet": "^1.3.0", 55 | "pegjs": "^0.10.0", 56 | "pegjs-loader": "^0.5.6", 57 | "pre-push": "^0.1.1", 58 | "prettier": "^2.7.1", 59 | "ts-jest": "^28.0.5", 60 | "ts-loader": "^4.5.0", 61 | "typedoc": "^0.22.11", 62 | "typescript": "^4.0.0", 63 | "webpack": "^5.65.0", 64 | "webpack-bundle-analyzer": "^4.5.0", 65 | "webpack-cli": "^4.9.1", 66 | "webpack-merge": "^5.8.0" 67 | }, 68 | "dependencies": { 69 | "shogi.js": "^2.0.5" 70 | }, 71 | "pre-push": [ 72 | "lint", 73 | "test" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /specification/files/ryuou201409020101.jkf.json: -------------------------------------------------------------------------------- 1 | ../../test/files/jkf/ryuou201409020101.jkf -------------------------------------------------------------------------------- /specification/files/same_move_minimal.jkf.json: -------------------------------------------------------------------------------- 1 | ../../test/files/jkf/same_move_minimal.jkf -------------------------------------------------------------------------------- /specification/json-kifu-format.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://github.com/na2hiro/json-kifu-format", 4 | "$ref": "#/$defs/record", 5 | "$defs": { 6 | "record": { 7 | "title": "json-kifu-format", 8 | "type": "object", 9 | "properties": { 10 | "header": { 11 | "description": "ヘッダ情報。キーはKI2,KIF等の日本語のものに準ずる.(例: \"場所\", \"先手\")", 12 | "$ref": "#/$defs/header" 13 | }, 14 | "initial": { 15 | "description": "初期局面。nullの場合は平手を表す", 16 | "$ref": "#/$defs/initial" 17 | }, 18 | "moves": { 19 | "description": "n番目はn手目の棋譜(0番目は初期局面のコメント用)。", 20 | "$ref": "#/$defs/moves" 21 | } 22 | }, 23 | "required": ["header", "moves"], 24 | "additionalProperties": false 25 | }, 26 | "initial": { 27 | "title": "初期状態", 28 | "type": ["object", "null"], 29 | "properties": { 30 | "preset": { 31 | "description": "手合名", 32 | "$ref": "#/$defs/preset" 33 | }, 34 | "data": { 35 | "description": "初期局面データ。手合名がOTHERの時に使用する", 36 | "$ref": "#/$defs/initialData" 37 | } 38 | }, 39 | "required": ["preset"], 40 | "additionalProperties": false 41 | }, 42 | "header": { 43 | "title": "ヘッダ情報", 44 | "type": "object", 45 | "additionalProperties": { 46 | "type": "string" 47 | } 48 | }, 49 | "preset": { 50 | "title": "手合情報", 51 | "description": "KIFでサポートされている手合情報。順に、平手、香落ち、右香落ち、角落ち、飛車落ち、飛香落ち、二枚落ち、三枚落ち、四枚落ち、五枚落ち、左五枚落ち、六枚落ち、左七枚落ち、右七枚落ち、八枚落ち、十枚落ち、その他、を表す。", 52 | "enum": [ 53 | "HIRATE", 54 | "KY", 55 | "KY_R", 56 | "KA", 57 | "HI", 58 | "HIKY", 59 | "2", 60 | "3", 61 | "4", 62 | "5", 63 | "5_L", 64 | "6", 65 | "7_L", 66 | "7_R", 67 | "8", 68 | "10", 69 | "OTHER" 70 | ] 71 | }, 72 | "initialData": { 73 | "title": "初期局面のデータ", 74 | "type": ["object", "null"], 75 | "properties": { 76 | "color": { 77 | "description": "初手の手番", 78 | "$ref": "#/$defs/color" 79 | }, 80 | "board": { 81 | "description": "盤上の駒の配置", 82 | "$ref": "#/$defs/initialBoard" 83 | }, 84 | "hands": { 85 | "description": "0番目が先手,1番目が後手の持駒", 86 | "$ref": "#/$defs/hands" 87 | } 88 | }, 89 | "required": ["color", "board", "hands"] 90 | }, 91 | "initialBoard": { 92 | "title": "初期状態の盤面", 93 | "oneOf": [ 94 | { 95 | "description": "駒がない場合", 96 | "type": "object", 97 | "additionalProperties": false 98 | }, 99 | { 100 | "description": "盤上の駒。board[x-1][y-1]に(x,y)の駒情報", 101 | "$ref": "#/$defs/board" 102 | } 103 | ] 104 | }, 105 | "board": { 106 | "title": "盤", 107 | "type": "array", 108 | "items": { 109 | "$ref": "#/$defs/column" 110 | } 111 | }, 112 | "column": { 113 | "title": "盤の列", 114 | "type": "array", 115 | "items": { 116 | "$ref": "#/$defs/square" 117 | } 118 | }, 119 | "square": { 120 | "title": "盤の升", 121 | "type": "object", 122 | "properties": { 123 | "color": { 124 | "description": "先手/後手", 125 | "oneOf": [ 126 | { 127 | "$ref": "#/$defs/color" 128 | }, 129 | { 130 | "type": "null" 131 | } 132 | ] 133 | }, 134 | "kind": { 135 | "description": "駒の種類", 136 | "oneOf": [ 137 | { 138 | "$ref": "#/$defs/kind" 139 | }, 140 | { 141 | "type": "null" 142 | } 143 | ] 144 | } 145 | }, 146 | "additionalProperties": false 147 | }, 148 | "moves": { 149 | "title": "指し手のリスト", 150 | "type": "array", 151 | "prefixItems": [ 152 | { 153 | "$ref": "#/$defs/initialComment" 154 | } 155 | ], 156 | "items": { 157 | "$ref": "#/$defs/moveWithForksAndInfo" 158 | } 159 | }, 160 | "initialComment": { 161 | "title": "初期局面のコメント", 162 | "type": "object", 163 | "properties": { 164 | "comment": { 165 | "type": "string" 166 | } 167 | } 168 | }, 169 | "hands": { 170 | "title": "先後双方の持駒", 171 | "type": "array", 172 | "minItems": 2, 173 | "maxItems": 2, 174 | "items": { 175 | "description": "駒種がkey, 枚数がvalueの連想配列", 176 | "$ref": "#/$defs/hand" 177 | } 178 | }, 179 | "hand": { 180 | "title": "持駒", 181 | "type": "object", 182 | "additionalProperties": false, 183 | "properties": { 184 | "FU": { 185 | "type": "integer" 186 | }, 187 | "KY": { 188 | "type": "integer" 189 | }, 190 | "KE": { 191 | "type": "integer" 192 | }, 193 | "GI": { 194 | "type": "integer" 195 | }, 196 | "KI": { 197 | "type": "integer" 198 | }, 199 | "KA": { 200 | "type": "integer" 201 | }, 202 | "HI": { 203 | "type": "integer" 204 | }, 205 | "OU": { 206 | "type": "integer" 207 | }, 208 | "TO": { 209 | "type": "integer" 210 | }, 211 | "NY": { 212 | "type": "integer" 213 | }, 214 | "NK": { 215 | "type": "integer" 216 | }, 217 | "NG": { 218 | "type": "integer" 219 | }, 220 | "UM": { 221 | "type": "integer" 222 | }, 223 | "RY": { 224 | "type": "integer" 225 | } 226 | } 227 | }, 228 | "color": { 229 | "title": "陣営", 230 | "description": "先手:0、後手:1", 231 | "enum": [0, 1] 232 | }, 233 | "moveWithForksAndInfo": { 234 | "title": "指し手", 235 | "type": "object", 236 | "properties": { 237 | "comments": { 238 | "description": "コメント", 239 | "$ref": "#/$defs/comments" 240 | }, 241 | "move": { 242 | "description": "駒の動き", 243 | "$ref": "#/$defs/move" 244 | }, 245 | "time": { 246 | "description": "消費時間", 247 | "$ref": "#/$defs/consumption" 248 | }, 249 | "special": { 250 | "description": "特殊棋譜。それぞれの意味はCSA標準棋譜ファイル形式 (V2.2) に準拠する。", 251 | "$ref": "#/$defs/special" 252 | }, 253 | "forks": { 254 | "description": "分岐。任意の長さの分岐を任意個格納する.分岐の初手はこのforksを持つ棋譜の代替の手とする(次の手ではなく)", 255 | "$ref": "#/$defs/forks" 256 | } 257 | }, 258 | "additionalProperties": false 259 | }, 260 | "special": { 261 | "title": "特殊棋譜", 262 | "enum": [ 263 | "TORYO", 264 | "CHUDAN", 265 | "SENNICHITE", 266 | "TIME_UP", 267 | "ILLEGAL_MOVE", 268 | "+ILLEGAL_ACTION", 269 | "-ILLEGAL_ACTION", 270 | "JISHOGI", 271 | "KACHI", 272 | "HIKIWAKE", 273 | "MATTA", 274 | "TSUMI", 275 | "FUZUMI", 276 | "ERROR", 277 | null 278 | ] 279 | }, 280 | "forks": { 281 | "title": "分岐群", 282 | "type": ["array", "null"], 283 | "items": { 284 | "$ref": "#/$defs/fork" 285 | } 286 | }, 287 | "fork": { 288 | "title": "分岐", 289 | "type": "array", 290 | "items": { 291 | "$ref": "#/$defs/moveWithForksAndInfo" 292 | } 293 | }, 294 | "consumption": { 295 | "title": "消費時間", 296 | "type": ["object", "null"], 297 | "properties": { 298 | "now": { 299 | "description": "1手", 300 | "$ref": "#/$defs/time" 301 | }, 302 | "total": { 303 | "description": "合計", 304 | "$ref": "#/$defs/time" 305 | } 306 | }, 307 | "required": ["now", "total"], 308 | "additionalProperties": false 309 | }, 310 | "move": { 311 | "title": "指し手", 312 | "type": ["object", "null"], 313 | "properties": { 314 | "color": { 315 | "description": "先手/後手", 316 | "$ref": "#/$defs/color" 317 | }, 318 | "from": { 319 | "description": "移動元。打った場合はなし", 320 | "oneOf": [ 321 | { 322 | "$ref": "#/$defs/placeFormat" 323 | }, 324 | { 325 | "type": "null" 326 | } 327 | ] 328 | }, 329 | "to": { 330 | "description": "移動先", 331 | "$ref": "#/$defs/placeFormat" 332 | }, 333 | "piece": { 334 | "description": "駒の種類", 335 | "$ref": "#/$defs/kind" 336 | }, 337 | "same": { 338 | "description": "直前と同じ場合", 339 | "type": ["boolean", "null"] 340 | }, 341 | "promote": { 342 | "description": "成るかどうか。true:成, false:不成, 無いかnull:どちらでもない", 343 | "type": ["boolean", "null"] 344 | }, 345 | "capture": { 346 | "description": "取った駒の種類", 347 | "oneOf": [ 348 | { 349 | "$ref": "#/$defs/capturablekind" 350 | }, 351 | { 352 | "type": "null" 353 | } 354 | ] 355 | }, 356 | "relative": { 357 | "description": "相対情報", 358 | "oneOf": [ 359 | { 360 | "$ref": "#/$defs/relative" 361 | }, 362 | { 363 | "type": "null" 364 | } 365 | ] 366 | } 367 | }, 368 | "required": ["color", "to", "piece"], 369 | "additionalProperties": false 370 | }, 371 | "comments": { 372 | "title": "コメント", 373 | "oneOf": [ 374 | { 375 | "type": "null" 376 | }, 377 | { 378 | "type": "array", 379 | "items": { 380 | "type": "string" 381 | } 382 | } 383 | ] 384 | }, 385 | "relative": { 386 | "title": "相対情報", 387 | "type": "string", 388 | "pattern": "[LCR]|[UMD]|[LCR][UD]|[LR]M|H" 389 | }, 390 | "time": { 391 | "title": "時間", 392 | "type": "object", 393 | "properties": { 394 | "h": { 395 | "description": "時", 396 | "type": ["integer", "null"] 397 | }, 398 | "m": { 399 | "description": "分", 400 | "type": "integer" 401 | }, 402 | "s": { 403 | "description": "秒", 404 | "type": "integer" 405 | } 406 | }, 407 | "required": ["m", "s"], 408 | "additionalProperties": false 409 | }, 410 | "placeFormat": { 411 | "title": "座標", 412 | "type": "object", 413 | "properties": { 414 | "x": { 415 | "description": "1から9", 416 | "type": "integer" 417 | }, 418 | "y": { 419 | "description": "一から九", 420 | "type": "integer" 421 | } 422 | }, 423 | "required": ["x", "y"], 424 | "additionalProperties": false 425 | }, 426 | "kind": { 427 | "title": "駒の種類", 428 | "description": "CSA標準棋譜ファイル形式 (V2.2) の表記を使用している。", 429 | "enum": [ 430 | "FU", 431 | "KY", 432 | "KE", 433 | "GI", 434 | "KI", 435 | "KA", 436 | "HI", 437 | "OU", 438 | "TO", 439 | "NY", 440 | "NK", 441 | "NG", 442 | "UM", 443 | "RY" 444 | ] 445 | }, 446 | "capturablekind": { 447 | "title": "持駒に加えることができる駒の種類", 448 | "description": "kindの部分集合である。", 449 | "enum": ["FU", "KY", "KE", "GI", "KI", "KA", "HI", "TO", "NY", "NK", "NG", "UM", "RY"] 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/Formats.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * JSON Kifu Format 3 | * Copyright (c) 2014 na2hiro (https://github.com/na2hiro) 4 | * This software is released under the MIT License. 5 | * http://opensource.org/licenses/mit-license.php 6 | */ 7 | import {Color} from "shogi.js"; 8 | import {Kind} from "shogi.js/dist/src/Kind"; 9 | 10 | /** 11 | * Kifu format 12 | */ 13 | export interface IJSONKifuFormat { 14 | header: {[index: string]: string}; 15 | initial?: { 16 | preset: string; 17 | data?: IStateFormat; 18 | }; 19 | moves: IMoveFormat[]; 20 | } 21 | 22 | /** 23 | * Game state 24 | */ 25 | export interface IStateFormat { 26 | color: Color; 27 | board: IPiece[][]; 28 | hands: Array<{ 29 | [index in Extract]: number; 30 | }>; 31 | } 32 | 33 | /** 34 | * Piece 35 | * TODO: Make color and kind nonnull 36 | */ 37 | export interface IPiece { 38 | color?: Color; 39 | kind?: Kind; 40 | } 41 | 42 | /** 43 | * Abstract Move 44 | */ 45 | export interface IMoveMoveFormat { 46 | color: Color; 47 | from?: IPlaceFormat; 48 | to?: IPlaceFormat; 49 | piece: Kind; 50 | same?: boolean; 51 | promote?: boolean; 52 | capture?: Kind; 53 | relative?: string; 54 | } 55 | 56 | /** 57 | * Move 58 | */ 59 | export interface IMoveFormat { 60 | comments?: string[]; 61 | move?: IMoveMoveFormat; 62 | time?: { 63 | now: ITimeFormat; 64 | total: ITimeFormat; 65 | }; 66 | special?: string; 67 | forks?: IMoveFormat[][]; 68 | } 69 | 70 | /** 71 | * Elapsed Time 72 | */ 73 | export interface ITimeFormat { 74 | h?: number; 75 | m: number; 76 | s: number; 77 | } 78 | 79 | /** 80 | * Position on boards 81 | */ 82 | export interface IPlaceFormat { 83 | x: number; 84 | y: number; 85 | } 86 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/normalizerTest.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`module Normalizer normalizeKIF same at first fork 1`] = `{"header":{},"moves":[{},{"move":{"from":{"x":7,"y":7},"piece":"FU","to":{"x":7,"y":6},"color":0}},{"move":{"from":{"x":3,"y":3},"piece":"FU","to":{"x":3,"y":4},"color":1}},{"move":{"from":{"x":2,"y":7},"piece":"FU","to":{"x":2,"y":6},"color":0},"forks":[[{"move":{"from":{"x":8,"y":8},"piece":"KA","promote":true,"to":{"x":2,"y":2},"color":0,"capture":"KA"}},{"move":{"from":{"x":3,"y":1},"piece":"GI","same":true,"color":1,"to":{"x":2,"y":2},"capture":"UM"},"forks":[[{"move":{"from":{"x":8,"y":2},"piece":"HI","same":true,"color":1,"to":{"x":2,"y":2},"capture":"UM"}},{"special":"JISHOGI"}]]},{"special":"CHUDAN"}]]},{"special":"TORYO"}]}`; 4 | -------------------------------------------------------------------------------- /src/__tests__/fileTest.ts: -------------------------------------------------------------------------------- 1 | import {readdirSync, readFile} from "fs"; 2 | import {decode} from "iconv-lite"; 3 | import {detect} from "jschardet"; 4 | import JKFPlayer from "../jkfplayer"; 5 | 6 | const SJIS = "cp932"; 7 | const FILES_DIR = __dirname + "/../../test/files"; 8 | 9 | makeTest("kif", (filename) => (filename.match(/u$/) ? loadUTF : loadSJIS)); 10 | makeTest("ki2", (filename) => (filename.match(/u$/) ? loadUTF : loadSJIS)); 11 | makeTest("csa", () => loadAuto); 12 | makeTest("jkf", () => loadUTF); 13 | 14 | function makeTest(ext, fileNameToLoadFunc) { 15 | describe(ext + " file", () => { 16 | const files = readdirSync(FILES_DIR + "/" + ext); 17 | for (const file of files) { 18 | ((filename) => { 19 | if (!filename.match(new RegExp("\\." + ext + "u?$"))) { 20 | return; 21 | } 22 | // eslint-disable-next-line jest/valid-title 23 | it(filename, () => { 24 | return new Promise((done) => { 25 | try { 26 | fileNameToLoadFunc(filename)( 27 | FILES_DIR + "/" + ext + "/" + filename, 28 | (err, data) => { 29 | if (err) { 30 | done(err); 31 | return; 32 | } 33 | data = data.replace(/^\ufeff/, ""); // delete BOM 34 | try { 35 | const player: JKFPlayer = 36 | JKFPlayer["parse" + ext.toUpperCase()](data); 37 | player.goto(Infinity); 38 | player.goto(0); 39 | expect(player.kifu).toMatchSnapshot(); 40 | done(); 41 | } catch (e) { 42 | done(e); 43 | } 44 | } 45 | ); 46 | } catch (e) { 47 | done(e); 48 | } 49 | }); 50 | }); 51 | })(file); 52 | } 53 | }); 54 | } 55 | function loadUTF(filename, cb) { 56 | readFile(filename, {encoding: "utf-8"}, cb); 57 | } 58 | function loadSJIS(filename, cb) { 59 | readFile(filename, (err, data) => { 60 | if (err) { 61 | cb(err); 62 | return; 63 | } 64 | cb(null, decode(data, SJIS)); 65 | }); 66 | } 67 | function loadAuto(filename, cb) { 68 | readFile(filename, (err, data) => { 69 | if (err) { 70 | cb(err); 71 | return; 72 | } 73 | const {encoding} = detect(data); 74 | cb(null, decode(data, encoding)); 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /src/__tests__/normalizerTest-relative.ts: -------------------------------------------------------------------------------- 1 | import {Color, Piece, Shogi} from "shogi.js"; 2 | import {IMoveMoveFormat} from "../Formats"; 3 | import {JKFPlayer} from "../main"; 4 | import {addRelativeInformation, filterMovesByRelatives} from "../normalizer"; 5 | 6 | const emptyLine = [{}, {}, {}, {}, {}, {}, {}, {}, {}]; 7 | describe("module Normalizer (relative information test from https://www.shogi.or.jp/faq/kihuhyouki.html)", () => { 8 | describe("上寄引", () => { 9 | run("KI", [93, 72], 82, ["上", "寄"]); 10 | run("KI", [43, 31], 32, ["上", "引"]); 11 | run("KI", [56, 45], 55, ["上", "寄"]); 12 | run("GI", [89, 77], 88, ["上", "引"]); 13 | run("GI", [49, 27], 38, ["上", "引"]); 14 | }); 15 | describe("左右", () => { 16 | run("KI", [92, 72], 81, ["左", "右"]); 17 | run("KI", [32, 12], 22, ["左", "右"]); 18 | run("GI", [65, 45], 56, ["左", "右"]); 19 | run("KI", [89, 79], 78, ["左", "直"]); 20 | run("GI", [39, 29], 38, ["直", "右"]); 21 | }); 22 | describe("3枚", () => { 23 | run("KI", [63, 53, 43], 52, ["左", "直", "右"]); 24 | run("TO", [79, 89, 99, 98, 87], 88, ["右", "直", "左上", "寄", "引"]); 25 | run("GI", [29, 17, 39, 37], 28, ["直", "右", "左上", "左引"]); 26 | }); 27 | describe("竜", () => { 28 | run("RY", [91, 84], 82, ["引", "上"]); 29 | run("RY", [23, 52], 43, ["寄", "引"]); 30 | run("RY", [55, 15], 35, ["左", "右"]); 31 | run("RY", [99, 89], 88, ["左", "右"]); 32 | run("RY", [28, 19], 17, ["左", "右"]); 33 | }); 34 | describe("馬", () => { 35 | run("UM", [91, 81], 82, ["左", "右"]); 36 | run("UM", [95, 63], 85, ["寄", "引"]); 37 | run("UM", [11, 34], 12, ["引", "上"]); 38 | run("UM", [99, 59], 77, ["左", "右"]); 39 | run("UM", [47, 18], 29, ["左", "右"]); 40 | }); 41 | // eslint-disable-next-line jest/no-identical-title 42 | describe("馬", () => { 43 | run("UM", [93, 74], 92, ["左", "右"]); 44 | }); 45 | }); 46 | 47 | function run(piece, coords, toCoord, expected) { 48 | describe(`${coords.join(", ")} ${piece} to ${toCoord}`, () => { 49 | const shogi = new Shogi({ 50 | preset: "OTHER", 51 | data: { 52 | board: [ 53 | emptyLine, 54 | emptyLine, 55 | emptyLine, 56 | emptyLine, 57 | emptyLine, 58 | emptyLine, 59 | emptyLine, 60 | emptyLine, 61 | emptyLine, 62 | ], 63 | color: 0, 64 | hands: [ 65 | {FU: 18, KY: 4, KE: 4, GI: 4, KI: 4, KA: 2, HI: 2}, 66 | {FU: 0, KY: 0, KE: 0, GI: 0, KI: 0, KA: 0, HI: 0}, 67 | ], 68 | }, 69 | }); 70 | shogi.editMode(true); 71 | coords.forEach((coord) => { 72 | const {x, y} = coordToXY(coord); 73 | if (Piece.isPromoted(piece)) { 74 | shogi.drop(x, y, Piece.unpromote(piece), Color.Black); 75 | shogi.flip(x, y); 76 | } else { 77 | shogi.drop(x, y, piece, Color.Black); 78 | } 79 | }); 80 | shogi.editMode(false); 81 | const to = coordToXY(toCoord); 82 | const movesTo = shogi.getMovesTo(to.x, to.y, piece, Color.Black); 83 | coords.forEach((coord, i) => { 84 | it(`${coord}`, () => { 85 | const from = coordToXY(coord); 86 | const move: IMoveMoveFormat = { 87 | from, 88 | to, 89 | color: Color.Black, 90 | piece, 91 | }; 92 | addRelativeInformation(shogi, move); 93 | expect((move.relative || "").split("").map(JKFPlayer.relativeToKan).join("")).toBe( 94 | expected[i] 95 | ); 96 | 97 | const found = filterMovesByRelatives(move.relative, Color.Black, movesTo); 98 | expect(found).toHaveLength(1); 99 | expect(found[0].from).toEqual(from); 100 | }); 101 | }); 102 | }); 103 | } 104 | function coordToXY(coord: number) { 105 | return { 106 | x: Math.floor(coord / 10), 107 | y: coord % 10, 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /src/jkfplayer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-empty */ 2 | /** @license 3 | * JSON Kifu Format 4 | * Copyright (c) 2014 na2hiro (https://github.com/na2hiro) 5 | * This software is released under the MIT License. 6 | * http://opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | import {Color, Piece, Shogi} from "shogi.js"; 10 | import {IJSONKifuFormat, IMoveFormat, IMoveMoveFormat, IStateFormat} from "./Formats"; 11 | import {canPromote, normalizeCSA, normalizeKI2, normalizeKIF, normalizeMinimal} from "./normalizer"; 12 | import {parseCSA, parseKI2, parseKIF} from "./peg/parsers"; 13 | 14 | export default class JKFPlayer { 15 | public static debug = false; 16 | public static logs = []; 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | public static log(...lg: any[]) { 19 | if (JKFPlayer.debug) { 20 | console.log(lg); 21 | } else { 22 | JKFPlayer.logs.push(lg); 23 | } 24 | } 25 | public static parse(kifu: string, filename?: string) { 26 | if (filename) { 27 | const tmp = filename.split("."); 28 | const ext = tmp[tmp.length - 1].toLowerCase(); 29 | switch (ext) { 30 | case "jkf": 31 | return JKFPlayer.parseJKF(kifu); 32 | case "kif": 33 | case "kifu": 34 | return JKFPlayer.parseKIF(kifu); 35 | case "ki2": 36 | case "ki2u": 37 | return JKFPlayer.parseKI2(kifu); 38 | case "csa": 39 | return JKFPlayer.parseCSA(kifu); 40 | } 41 | } 42 | // 不明 43 | try { 44 | return JKFPlayer.parseJKF(kifu); 45 | } catch (e) { 46 | JKFPlayer.log("failed to parse as jkf", e); 47 | } 48 | try { 49 | return JKFPlayer.parseKIF(kifu); 50 | } catch (e) { 51 | JKFPlayer.log("failed to parse as kif", e); 52 | } 53 | try { 54 | return JKFPlayer.parseKI2(kifu); 55 | } catch (e) { 56 | JKFPlayer.log("failed to parse as ki2", e); 57 | } 58 | try { 59 | return JKFPlayer.parseCSA(kifu); 60 | } catch (e) { 61 | JKFPlayer.log("failed to parse as csa", e); 62 | } 63 | throw new Error("JKF, KIF, KI2, CSAいずれの形式でも失敗しました"); 64 | } 65 | public static parseJKF(kifu: string) { 66 | JKFPlayer.log("parseJKF", kifu); 67 | return new JKFPlayer(JSON.parse(kifu)); 68 | } 69 | public static parseKIF(kifu: string) { 70 | JKFPlayer.log("parseKIF", kifu); 71 | return new JKFPlayer(normalizeKIF(parseKIF(JKFPlayer.addLastNewLine(kifu)))); 72 | } 73 | public static parseKI2(kifu: string) { 74 | JKFPlayer.log("parseKI2", kifu); 75 | return new JKFPlayer(normalizeKI2(parseKI2(JKFPlayer.addLastNewLine(kifu)))); 76 | } 77 | public static parseCSA(kifu: string) { 78 | JKFPlayer.log("parseCSA", kifu); 79 | return new JKFPlayer(normalizeCSA(parseCSA(JKFPlayer.addLastNewLine(kifu)))); 80 | } 81 | public static addLastNewLine(kifu: string) { 82 | if (kifu.substr(kifu.length - 1) === "\n") { 83 | return kifu; 84 | } 85 | return kifu + "\n"; 86 | } 87 | public static numToZen(n: number) { 88 | return "0123456789"[n]; 89 | } 90 | public static numToKan(n: number) { 91 | return "〇一二三四五六七八九"[n]; 92 | } 93 | public static kindToKan(kind: string): string { 94 | return { 95 | FU: "歩", 96 | KY: "香", 97 | KE: "桂", 98 | GI: "銀", 99 | KI: "金", 100 | KA: "角", 101 | HI: "飛", 102 | OU: "玉", 103 | TO: "と", 104 | NY: "成香", 105 | NK: "成桂", 106 | NG: "成銀", 107 | UM: "馬", 108 | RY: "龍", 109 | }[kind]; 110 | } 111 | public static relativeToKan(relative: string) { 112 | return { 113 | L: "左", 114 | C: "直", 115 | R: "右", 116 | U: "上", 117 | M: "寄", 118 | D: "引", 119 | H: "打", 120 | }[relative]; 121 | } 122 | public static specialToKan(special: string) { 123 | return ( 124 | { 125 | TORYO: "投了", 126 | CHUDAN: "中断", 127 | SENNICHITE: "千日手", 128 | TIME_UP: "時間切れ", 129 | ILLEGAL_MOVE: "反則負け", 130 | "+ILLEGAL_ACTION": "先手反則負け", 131 | "-ILLEGAL_ACTION": "後手反則負け", 132 | JISHOGI: "持将棋", 133 | KACHI: "勝ち宣言", 134 | HIKIWAKE: "引き分け宣言", 135 | MATTA: "待った", 136 | TSUMI: "詰", 137 | FUZUMI: "不詰", 138 | ERROR: "エラー", 139 | }[special] || special 140 | ); 141 | } 142 | public static moveToReadableKifu(mv: IMoveFormat): string { 143 | if (mv.special) { 144 | return JKFPlayer.specialToKan(mv.special); 145 | } 146 | const move = mv.move; 147 | let ret = move.color === Color.Black ? "☗" : "☖"; 148 | if (move.same) { 149 | ret += "同 "; 150 | } else { 151 | ret += JKFPlayer.numToZen(move.to.x) + JKFPlayer.numToKan(move.to.y); 152 | } 153 | ret += JKFPlayer.kindToKan(move.piece); 154 | if (move.relative) { 155 | ret += move.relative.split("").map(JKFPlayer.relativeToKan).join(""); 156 | } 157 | if (move.promote != null) { 158 | ret += move.promote ? "成" : "不成"; 159 | } 160 | return ret; 161 | } 162 | public static doMove(shogi: Shogi, move: IMoveMoveFormat) { 163 | if (!move) { 164 | return; 165 | } 166 | if (move.from) { 167 | shogi.move(move.from.x, move.from.y, move.to.x, move.to.y, move.promote); 168 | } else { 169 | shogi.drop( 170 | move.to.x, 171 | move.to.y, 172 | move.piece, 173 | typeof move.color !== "undefined" ? move.color : void 0 174 | ); 175 | } 176 | } 177 | public static undoMove(shogi: Shogi, move: IMoveMoveFormat) { 178 | if (!move) { 179 | return; 180 | } 181 | if (move.from) { 182 | shogi.unmove( 183 | move.from.x, 184 | move.from.y, 185 | move.to.x, 186 | move.to.y, 187 | move.promote, 188 | move.capture 189 | ); 190 | } else { 191 | shogi.undrop(move.to.x, move.to.y); 192 | } 193 | } 194 | public static getState(shogi: Shogi): IStateFormat { 195 | return { 196 | board: JKFPlayer.getBoardState(shogi), 197 | color: shogi.turn, 198 | hands: JKFPlayer.getHandsState(shogi), 199 | }; 200 | } 201 | 202 | private static sameMoveMinimal(move1: IMoveMoveFormat, move2: IMoveMoveFormat) { 203 | return ( 204 | move1.to.x === move2.to.x && 205 | move1.to.y === move2.to.y && 206 | (move1.from && move2.from 207 | ? move1.from.x === move2.from.x && 208 | move1.from.y === move2.from.y && 209 | move1.promote === move2.promote 210 | : move1.piece === move2.piece) 211 | ); 212 | } 213 | private static getBoardState(shogi: Shogi) { 214 | const ret = []; 215 | for (let i = 1; i <= 9; i++) { 216 | const arr = []; 217 | for (let j = 1; j <= 9; j++) { 218 | const piece = shogi.get(i, j); 219 | arr.push(piece ? {color: piece.color, kind: piece.kind} : {}); 220 | } 221 | ret.push(arr); 222 | } 223 | return ret; 224 | } 225 | private static getHandsState(shogi: Shogi) { 226 | return [shogi.getHandsSummary(Color.Black), shogi.getHandsSummary(Color.White)]; 227 | } 228 | 229 | public shogi: Shogi; 230 | public kifu: IJSONKifuFormat; 231 | public tesuu: number; 232 | public forkPointers: Array<{te: number; forkIndex: number}> = []; 233 | private forks_ = null; 234 | private currentStream_: IMoveFormat[] = null; 235 | get forks(): Array<{te: number; moves: IMoveFormat[]}> { 236 | if (this.forks_ === null) { 237 | this.updateForksAndCurrentStream(); 238 | } 239 | return this.forks_; 240 | } 241 | get currentStream(): IMoveFormat[] { 242 | if (this.currentStream_ === null) { 243 | this.updateForksAndCurrentStream(); 244 | } 245 | return this.currentStream_; 246 | } 247 | 248 | constructor(kifu: IJSONKifuFormat) { 249 | this.shogi = new Shogi(kifu.initial || undefined); 250 | this.initialize(kifu); 251 | } 252 | public initialize(kifu: IJSONKifuFormat) { 253 | this.kifu = kifu; 254 | this.tesuu = 0; 255 | this.forkPointers = []; 256 | } 257 | // 1手進める 258 | public forward() { 259 | const nextMove = this.getMoveFormat(this.tesuu + 1); 260 | if (!nextMove) { 261 | return false; 262 | } 263 | this.tesuu++; 264 | const move = nextMove.move; 265 | if (!move) { 266 | return true; 267 | } 268 | JKFPlayer.log("forward", this.tesuu, move); 269 | this.doMove(move); 270 | return true; 271 | } 272 | // 1手戻す 273 | public backward() { 274 | if (this.tesuu <= 0) { 275 | return false; 276 | } 277 | const move = this.getMoveFormat(this.tesuu).move; 278 | JKFPlayer.log("backward", this.tesuu - 1, move); 279 | this.undoMove(move); 280 | this.tesuu--; 281 | this.forkPointers = this.forkPointers.filter((fork) => fork.te <= this.tesuu); 282 | this.updateForksAndCurrentStream(); 283 | return true; 284 | } 285 | // tesuu手目へ行く 286 | public goto(tesuu: number | string) { 287 | if (typeof tesuu === "string") { 288 | const commaPos = tesuu.indexOf(","); 289 | if (commaPos > 0) { 290 | // Specify tesuu pointer 291 | const te = Number(tesuu.slice(0, commaPos)); 292 | const destPointers = JSON.parse(tesuu.slice(commaPos + 1)); 293 | let matchingSoFar = true; 294 | for (let i = 0; i < destPointers.length; i++) { 295 | const dest = destPointers[i]; 296 | const current = this.forkPointers[i]; 297 | if ( 298 | matchingSoFar && 299 | current && 300 | current.te === dest.te && 301 | current.forkIndex === dest.forkIndex 302 | ) { 303 | // We don't need to repeat the same forking as currently while it's matching 304 | continue; 305 | } 306 | matchingSoFar = false; 307 | this.goto(dest.te - 1); 308 | this.forkAndForward(dest.forkIndex); 309 | } 310 | // Rewind if current fork is deeper than the destination 311 | if (matchingSoFar && destPointers.length < this.forkPointers.length) { 312 | this.goto(this.forkPointers[destPointers.length].te - 1); 313 | } 314 | this.goto(te); 315 | return; 316 | } 317 | tesuu = Number(tesuu); 318 | } 319 | if (isNaN(tesuu)) { 320 | return; 321 | } 322 | let limit = 10000; // for safe 323 | if (this.tesuu < tesuu) { 324 | while (this.tesuu !== tesuu && this.forward() && limit-- > 0) {} 325 | } else { 326 | while (this.tesuu !== tesuu && this.backward() && limit-- > 0) {} 327 | } 328 | if (limit === 0) { 329 | throw new Error("tesuu overflows"); 330 | } 331 | } 332 | // tesuu手前後に移動する 333 | public go(tesuu: number | string) { 334 | if (typeof tesuu === "string") { 335 | tesuu = Number(tesuu); 336 | } 337 | if (isNaN(tesuu)) { 338 | return; 339 | } 340 | this.goto(this.tesuu + tesuu); 341 | } 342 | // 現在の局面から別れた分岐のうちnum番目の変化へ1つ進む 343 | public forkAndForward(num: number | string): boolean { 344 | if (typeof num === "string") { 345 | num = parseInt(num, 10); 346 | } 347 | const forks = this.getMoveFormat(this.tesuu + 1).forks; 348 | if (!forks || forks.length <= num) { 349 | return false; 350 | } 351 | this.forkPointers.push({te: this.tesuu + 1, forkIndex: num}); 352 | this.updateForksAndCurrentStream(); 353 | return this.forward(); 354 | } 355 | 356 | /** 357 | * Return a tesuu pointer string which can be used to call goto() 358 | */ 359 | public getTesuuPointer(tesuu?: number): string { 360 | if (isNaN(tesuu)) { 361 | tesuu = this.tesuu; 362 | } 363 | return `${tesuu},${JSON.stringify(this.forkPointers.filter((p) => p.te <= tesuu))}`; 364 | } 365 | // TODO: Distinguish minimal move and full move 366 | // 現在の局面から新しいかもしれない手を1手動かす. 367 | // 必要フィールドは,指し: from, to, promote(成れる場合のみ).打ち: to, piece 368 | // 新しい手の場合,最終手であれば手を追加,そうでなければ分岐を追加 369 | // もしpromoteの可能性があればfalseを返して何もしない 370 | // 成功すればその局面に移動してtrueを返す. 371 | public inputMove(move: IMoveMoveFormat) { 372 | if (this.getMoveFormat().special) { 373 | throw new Error("終了局面へ棋譜を追加することは出来ません"); 374 | } 375 | if (move.from != null && move.promote == null) { 376 | const piece = this.shogi.get(move.from.x, move.from.y); 377 | if ( 378 | !Piece.isPromoted(piece.kind) && 379 | Piece.canPromote(piece.kind) && 380 | (canPromote(move.from, piece.color) || canPromote(move.to, piece.color)) 381 | ) { 382 | return false; 383 | } 384 | } 385 | const nextMove = this.getMoveFormat(this.tesuu + 1); 386 | if (nextMove) { 387 | if (nextMove.move && JKFPlayer.sameMoveMinimal(nextMove.move, move)) { 388 | // 次の一手と一致 389 | this.forward(); 390 | return true; 391 | } 392 | if (nextMove.forks) { 393 | for (let i = 0; i < nextMove.forks.length; i++) { 394 | const forkCand = nextMove.forks[i][0]; 395 | if ( 396 | forkCand && 397 | forkCand.move && 398 | JKFPlayer.sameMoveMinimal(forkCand.move, move) 399 | ) { 400 | // 分岐と一致 401 | this.forkAndForward(i); 402 | return true; 403 | } 404 | } 405 | } 406 | } 407 | this.doMove(move); // 動かしてみる(throwされうる) 408 | const newMove = {move}; 409 | const addToFork = this.tesuu < this.getMaxTesuu(); 410 | let next; 411 | if (addToFork) { 412 | // 最終手でなければ分岐に追加 413 | next = this.getMoveFormat(this.tesuu + 1); 414 | if (!next.forks) { 415 | next.forks = []; 416 | } 417 | next.forks.push([newMove]); 418 | } else { 419 | // 最終手に追加 420 | this.forks[this.forks.length - 1].moves.push(newMove); 421 | } 422 | this.updateForksAndCurrentStream(); 423 | normalizeMinimal(this.kifu); // 復元 424 | this.undoMove(move); 425 | // 考え改めて再生 426 | if (addToFork) { 427 | this.forkAndForward(next.forks.length - 1); 428 | } else { 429 | this.forward(); 430 | } 431 | return true; 432 | } 433 | // wrapper 434 | public getBoard(x: number, y: number): Piece { 435 | return this.shogi.get(x, y); 436 | } 437 | public getComments(tesuu: number = this.tesuu) { 438 | return this.getMoveFormat(tesuu).comments || []; 439 | } 440 | public getMove(tesuu: number = this.tesuu) { 441 | return this.getMoveFormat(tesuu).move; 442 | } 443 | public getReadableKifu(tesuu: number = this.tesuu): string { 444 | if (tesuu === 0) { 445 | return "開始局面"; 446 | } 447 | return JKFPlayer.moveToReadableKifu(this.getMoveFormat(tesuu)); 448 | } 449 | public getReadableForkKifu(tesuu: number = this.tesuu): string[] { 450 | return this.getNextFork(tesuu).map((fork) => JKFPlayer.moveToReadableKifu(fork[0])); 451 | } 452 | public getMaxTesuu() { 453 | return this.currentStream.length - 1; 454 | } 455 | public toJKF() { 456 | return JSON.stringify(this.kifu); 457 | } 458 | // jkf.initial.dataの形式を得る 459 | public getState() { 460 | return JKFPlayer.getState(this.shogi); 461 | } 462 | public getReadableKifuState(): Array<{kifu: string; forks: string[]; comments: string[]}> { 463 | const ret = []; 464 | for (let i = 0; i <= this.getMaxTesuu(); i++) { 465 | ret.push({ 466 | comments: this.getComments(i), 467 | forks: this.getReadableForkKifu(i - 1), 468 | kifu: this.getReadableKifu(i), 469 | }); 470 | } 471 | return ret; 472 | } 473 | 474 | private updateForksAndCurrentStream() { 475 | const forks: Array<{te: number; moves: IMoveFormat[]}> = []; 476 | let currentStream: IMoveFormat[] = []; 477 | let currentFork: IMoveFormat[] = this.kifu.moves; 478 | let tesuuOffset = 0; 479 | for (const pointer of this.forkPointers) { 480 | forks.push({te: tesuuOffset, moves: currentFork}); 481 | currentStream = currentStream.concat(currentFork.slice(0, pointer.te - tesuuOffset)); 482 | currentFork = currentFork[pointer.te - tesuuOffset].forks[pointer.forkIndex]; 483 | tesuuOffset = pointer.te; 484 | } 485 | forks.push({te: tesuuOffset, moves: currentFork}); 486 | this.forks_ = forks; 487 | this.currentStream_ = currentStream.concat(currentFork.slice()); 488 | } 489 | 490 | // 現在の局面から分岐を遡った初手から,現在の局面からの本譜の中から棋譜を得る 491 | private getMoveFormat(tesuu: number = this.tesuu): IMoveFormat { 492 | return this.currentStream[tesuu]; 493 | } 494 | private getNextFork(tesuu: number = this.tesuu) { 495 | const next = this.getMoveFormat(tesuu + 1); 496 | return next && next.forks ? next.forks : []; 497 | } 498 | private doMove(move: IMoveMoveFormat) { 499 | JKFPlayer.doMove(this.shogi, move); 500 | } 501 | private undoMove(move: IMoveMoveFormat) { 502 | JKFPlayer.undoMove(this.shogi, move); 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as Shogi from "shogi.js"; 2 | import * as Formats from "./Formats"; 3 | import JKFPlayer from "./jkfplayer"; 4 | import * as Normalizer from "./normalizer"; 5 | import * as Parsers from "./peg/parsers"; 6 | 7 | export {Normalizer, JKFPlayer, Parsers, Formats, Shogi}; 8 | -------------------------------------------------------------------------------- /src/normalizer.ts: -------------------------------------------------------------------------------- 1 | /** @license 2 | * JSON Kifu Format 3 | * Copyright (c) 2014 na2hiro (https://github.com/na2hiro) 4 | * This software is released under the MIT License. 5 | * http://opensource.org/licenses/mit-license.php 6 | */ 7 | import {Color, IMove, Piece, Shogi} from "shogi.js"; 8 | import {IJSONKifuFormat, IMoveFormat, IMoveMoveFormat, IPlaceFormat, ITimeFormat} from "./Formats"; 9 | 10 | export function canPromote(place: IPlaceFormat, color: Color) { 11 | return color === Color.Black ? place.y <= 3 : place.y >= 7; 12 | } 13 | 14 | // 最小形式の棋譜をnormalizeする 15 | // 最小形式: (指し) from, to, promote; (打ち) piece, to 16 | export function normalizeMinimal(obj: IJSONKifuFormat): IJSONKifuFormat { 17 | const shogi = new Shogi(obj.initial); 18 | normalizeMinimalMoves(shogi, obj.moves); 19 | return obj; 20 | } 21 | function normalizeMinimalMoves(shogi: Shogi, moves: IMoveFormat[], lastMove?: IMoveFormat) { 22 | for (let i = 0; i < moves.length; i++) { 23 | const last = i === 0 ? lastMove : moves[i - 1]; 24 | const move = moves[i].move; 25 | if (!move) { 26 | continue; 27 | } 28 | // 手番 29 | move.color = shogi.turn; 30 | if (move.from) { 31 | // move 32 | 33 | // toからsame復元 34 | if (last && last.move && move.to.x === last.move.to.x && move.to.y === last.move.to.y) { 35 | move.same = true; 36 | } 37 | 38 | // capture復元 39 | addCaptureInformation(shogi, move); 40 | 41 | // piece復元 42 | if (!move.piece) { 43 | move.piece = shogi.get(move.from.x, move.from.y).kind; 44 | } 45 | 46 | // 不成復元 47 | if (!move.promote && !Piece.isPromoted(move.piece) && Piece.canPromote(move.piece)) { 48 | // 成ってない 49 | if (canPromote(move.to, shogi.turn) || canPromote(move.from, shogi.turn)) { 50 | move.promote = false; 51 | } 52 | } 53 | // relative復元 54 | addRelativeInformation(shogi, move); 55 | 56 | try { 57 | shogi.move(move.from.x, move.from.y, move.to.x, move.to.y, move.promote); 58 | } catch (e) { 59 | throw new Error(i + "手目で失敗しました: " + e); 60 | } 61 | } else { 62 | // drop 63 | if (shogi.getMovesTo(move.to.x, move.to.y, move.piece).length > 0) { 64 | move.relative = "H"; 65 | } 66 | shogi.drop(move.to.x, move.to.y, move.piece); 67 | } 68 | } 69 | restoreColorOfIllegalAction(moves, shogi); 70 | for (let i = moves.length - 1; i >= 0; i--) { 71 | const move = moves[i].move; 72 | if (move) { 73 | if (move.from) { 74 | shogi.unmove( 75 | move.from.x, 76 | move.from.y, 77 | move.to.x, 78 | move.to.y, 79 | move.promote, 80 | move.capture 81 | ); 82 | } else { 83 | shogi.undrop(move.to.x, move.to.y); 84 | } 85 | } 86 | const last = i <= 1 ? lastMove : moves[i - 1]; 87 | if (moves[i].forks) { 88 | for (const fork of moves[i].forks) { 89 | normalizeMinimalMoves(shogi, fork, last); 90 | } 91 | } 92 | } 93 | } 94 | /** 95 | * Normalize JKF 96 | * @param obj JKF. TODO: Introduce a type which is not a normalized KIF 97 | */ 98 | export function normalizeKIF(obj: IJSONKifuFormat): IJSONKifuFormat { 99 | // Kifu for iPhone bug 100 | if (obj.initial && obj.initial.preset === "HIRATE" && obj.initial.data) { 101 | obj.initial.preset = "OTHER"; 102 | } 103 | const shogi = new Shogi(obj.initial || undefined); 104 | normalizeKIFMoves(shogi, obj.moves); 105 | return obj; 106 | } 107 | function normalizeKIFMoves(shogi: Shogi, moves: IMoveFormat[], lastMove?: IMoveFormat) { 108 | for (let i = 0; i < moves.length; i++) { 109 | const last = i === 0 ? lastMove : moves[i - 1]; 110 | const move = moves[i].move; 111 | if (!move) { 112 | continue; 113 | } 114 | // 手番 115 | move.color = shogi.turn; 116 | if (move.from) { 117 | // move 118 | 119 | // sameからto復元 120 | if (move.same) { 121 | move.to = last.move.to; 122 | } 123 | 124 | // capture復元 125 | addCaptureInformation(shogi, move); 126 | 127 | // 不成復元 128 | if (!move.promote && !Piece.isPromoted(move.piece) && Piece.canPromote(move.piece)) { 129 | // 成ってない 130 | if (canPromote(move.to, shogi.turn) || canPromote(move.from, shogi.turn)) { 131 | move.promote = false; 132 | } 133 | } 134 | // relative復元 135 | addRelativeInformation(shogi, move); 136 | 137 | try { 138 | shogi.move(move.from.x, move.from.y, move.to.x, move.to.y, move.promote); 139 | } catch (e) { 140 | throw new Error(i + "手目で失敗しました: " + e); 141 | } 142 | } else { 143 | // drop 144 | if (shogi.getMovesTo(move.to.x, move.to.y, move.piece).length > 0) { 145 | move.relative = "H"; 146 | } 147 | shogi.drop(move.to.x, move.to.y, move.piece); 148 | } 149 | } 150 | restoreColorOfIllegalAction(moves, shogi); 151 | for (let i = moves.length - 1; i >= 0; i--) { 152 | const move = moves[i].move; 153 | if (move) { 154 | if (move.from) { 155 | shogi.unmove( 156 | move.from.x, 157 | move.from.y, 158 | move.to.x, 159 | move.to.y, 160 | move.promote, 161 | move.capture 162 | ); 163 | } else { 164 | shogi.undrop(move.to.x, move.to.y); 165 | } 166 | } 167 | const last = i === 0 ? lastMove : moves[i - 1]; // When first fork has fork, use lastMove of this fork 168 | if (moves[i].forks) { 169 | for (const fork of moves[i].forks) { 170 | normalizeKIFMoves(shogi, fork, last); 171 | } 172 | } 173 | } 174 | } 175 | export function normalizeKI2(obj: IJSONKifuFormat): IJSONKifuFormat { 176 | const shogi = new Shogi(obj.initial || undefined); 177 | normalizeKI2Moves(shogi, obj.moves); 178 | return obj; 179 | } 180 | function normalizeKI2Moves(shogi: Shogi, moves: IMoveFormat[], lastMove?: IMoveFormat) { 181 | for (let i = 0; i < moves.length; i++) { 182 | const last = i === 0 ? lastMove : moves[i - 1]; 183 | const move = moves[i].move; 184 | if (!move) { 185 | continue; 186 | } 187 | 188 | // 手番 189 | move.color = shogi.turn; 190 | // 同からto復元 191 | if (move.same) { 192 | move.to = last.move.to; 193 | } 194 | 195 | // from復元 196 | const candMoves = shogi.getMovesTo(move.to.x, move.to.y, move.piece); 197 | if (move.relative === "H" || candMoves.length === 0) { 198 | // ok 199 | } else if (candMoves.length === 1) { 200 | move.from = candMoves[0].from; 201 | } else { 202 | // 相対逆算 203 | const moveAns = filterMovesByRelatives(move.relative, shogi.turn, candMoves); 204 | if (moveAns.length !== 1) { 205 | throw new Error("相対情報が不完全で複数の候補があります"); 206 | } 207 | move.from = moveAns[0].from; 208 | } 209 | 210 | if (move.from) { 211 | // move 212 | 213 | // capture復元 214 | addCaptureInformation(shogi, move); 215 | 216 | try { 217 | shogi.move(move.from.x, move.from.y, move.to.x, move.to.y, move.promote); 218 | } catch (e) { 219 | throw new Error(i + "手目で失敗しました: " + e); 220 | } 221 | } else { 222 | // drop 223 | shogi.drop(move.to.x, move.to.y, move.piece); 224 | } 225 | } 226 | restoreColorOfIllegalAction(moves, shogi); 227 | for (let i = moves.length - 1; i >= 0; i--) { 228 | const move = moves[i].move; 229 | if (!move) { 230 | continue; 231 | } 232 | if (move.from) { 233 | shogi.unmove( 234 | move.from.x, 235 | move.from.y, 236 | move.to.x, 237 | move.to.y, 238 | move.promote, 239 | move.capture 240 | ); 241 | } else { 242 | shogi.undrop(move.to.x, move.to.y); 243 | } 244 | const last = i <= 1 ? lastMove : moves[i - 1]; 245 | if (moves[i].forks) { 246 | for (const fork of moves[i].forks) { 247 | normalizeKI2Moves(shogi, fork, last); 248 | } 249 | } 250 | } 251 | } 252 | export function normalizeCSA(obj: IJSONKifuFormat): IJSONKifuFormat { 253 | restorePreset(obj); 254 | const shogi = new Shogi(obj.initial || undefined); 255 | for (let i = 0; i < obj.moves.length; i++) { 256 | restoreTotalTime(obj.moves[i].time, i >= 2 ? obj.moves[i - 2].time : void 0); 257 | const move = obj.moves[i].move; 258 | if (!move) { 259 | continue; 260 | } 261 | // 手番 262 | move.color = shogi.turn; 263 | if (move.from) { 264 | // move 265 | 266 | // same復元 267 | if ( 268 | i > 0 && 269 | obj.moves[i - 1].move && 270 | obj.moves[i - 1].move.to.x === move.to.x && 271 | obj.moves[i - 1].move.to.y === move.to.y 272 | ) { 273 | move.same = true; 274 | } 275 | 276 | // capture復元 277 | addCaptureInformation(shogi, move); 278 | if (Piece.isPromoted(move.piece)) { 279 | // 成かも 280 | const from = shogi.get(move.from.x, move.from.y); 281 | if (from.kind !== move.piece) { 282 | move.piece = from.kind; 283 | move.promote = true; 284 | } 285 | } else if (Piece.canPromote(move.piece)) { 286 | // 不成かも 287 | if (canPromote(move.to, shogi.turn) || canPromote(move.from, shogi.turn)) { 288 | move.promote = false; 289 | } 290 | } 291 | // relative復元 292 | addRelativeInformation(shogi, move); 293 | 294 | try { 295 | shogi.move(move.from.x, move.from.y, move.to.x, move.to.y, move.promote); 296 | } catch (e) { 297 | throw new Error(i + "手目で失敗しました: " + e); 298 | } 299 | } else { 300 | // drop 301 | if (shogi.getMovesTo(move.to.x, move.to.y, move.piece).length > 0) { 302 | move.relative = "H"; 303 | } 304 | shogi.drop(move.to.x, move.to.y, move.piece); 305 | } 306 | } 307 | return obj; 308 | } 309 | // export for testing 310 | export function addRelativeInformation(shogi: Shogi, move: IMoveMoveFormat) { 311 | const moveVectors = shogi 312 | .getMovesTo(move.to.x, move.to.y, move.piece) 313 | .map((mv) => flipVector(shogi.turn, spaceshipVector(mv.to, mv.from))); 314 | if (moveVectors.length >= 2) { 315 | const realVector = flipVector(shogi.turn, spaceshipVector(move.to, move.from)); 316 | move.relative = (() => { 317 | // 上下方向唯一 318 | if (moveVectors.filter((mv) => mv.y === realVector.y).length === 1) { 319 | return YToUMD(realVector.y); 320 | } 321 | // 左右方向唯一 322 | if (moveVectors.filter((mv) => mv.x === realVector.x).length === 1) { 323 | if ((move.piece === "UM" || move.piece === "RY") && realVector.x === 0) { 324 | // 直はだめ 325 | return XToLCR(moveVectors.filter((mv) => mv.x < 0).length === 0 ? -1 : 1); 326 | } else { 327 | return XToLCR(realVector.x); 328 | } 329 | } 330 | // 上下も左右も他の駒がいる 331 | if (realVector.x === 0) { 332 | // 直は上下とは使わない 333 | return XToLCR(realVector.x); 334 | } 335 | return XToLCR(realVector.x) + YToUMD(realVector.y); 336 | })(); 337 | } 338 | } 339 | function addCaptureInformation(shogi: Shogi, move: IMoveMoveFormat) { 340 | const to = shogi.get(move.to.x, move.to.y); 341 | if (to) { 342 | move.capture = to.kind; 343 | } 344 | } 345 | 346 | function flipVector(color: Color, vector: {x: number; y: number}) { 347 | return color === Color.Black ? vector : {x: -vector.x, y: -vector.y}; 348 | } 349 | function spaceship(a: number, b: number) { 350 | return a === b ? 0 : a > b ? 1 : -1; 351 | } 352 | function spaceshipVector(a: {x: number; y: number}, b: {x: number; y: number}) { 353 | return {x: spaceship(a.x, b.x), y: spaceship(a.y, b.y)}; 354 | } 355 | // yの段から移動した場合の相対情報 356 | function YToUMD(y: number) { 357 | return y === 0 ? "M" : y > 0 ? "D" : "U"; 358 | } 359 | // xの行から移動した場合の相対情報 360 | function XToLCR(x: number) { 361 | return x === 0 ? "C" : x > 0 ? "R" : "L"; 362 | } 363 | export function filterMovesByRelatives(relative: string, color: Color, moves: IMove[]): IMove[] { 364 | let candidates = moves 365 | .map((move) => ({ 366 | move, 367 | vec: flipVector(color, {x: move.to.x - move.from.x, y: move.to.y - move.from.y}), 368 | })) 369 | .filter(({vec}) => 370 | relative.split("").every((rel) => moveSatisfiesRelative(rel, color, vec)) 371 | ); 372 | if (relative.indexOf("C") >= 0) { 373 | // 直は上がる時だけ 374 | candidates = candidates.filter(({vec}) => vec.y < 0); 375 | } 376 | if (relative.indexOf("L") >= 0) { 377 | let min = Infinity; 378 | candidates.forEach(({vec}) => (min = Math.min(min, vec.x))); 379 | return candidates.filter(({vec}) => vec.x === min).map(({move}) => move); 380 | } 381 | if (relative.indexOf("R") >= 0) { 382 | let max = -Infinity; 383 | candidates.forEach(({vec}) => (max = Math.max(max, vec.x))); 384 | return candidates.filter(({vec}) => vec.x === max).map(({move}) => move); 385 | } 386 | return candidates.map(({move}) => move); 387 | } 388 | function moveSatisfiesRelative( 389 | relative: string, 390 | color: Color, 391 | vec: {x: number; y: number} 392 | ): boolean { 393 | switch (relative) { 394 | case "U": 395 | return vec.y < 0; 396 | case "M": 397 | return vec.y === 0; 398 | case "D": 399 | return vec.y > 0; 400 | case "C": 401 | return vec.x === 0; 402 | case "L": 403 | case "R": 404 | // 移動先ではなく他の駒との相対的な位置関係のため後で判定 405 | return true; 406 | } 407 | } 408 | // CSA等で盤面みたままで表現されているものをpresetに戻せれば戻す 409 | function restorePreset(obj: IJSONKifuFormat) { 410 | if (!obj.initial || obj.initial.preset !== "OTHER") { 411 | return; 412 | } 413 | const kinds = ["FU", "KY", "KE", "GI", "KI", "KA", "HI"]; 414 | for (let i = 0; i < 2; i++) { 415 | for (const kind of kinds) { 416 | if (obj.initial.data.hands[i][kind] !== 0) { 417 | return; 418 | } 419 | } 420 | } 421 | const hirate = [ 422 | [ 423 | {color: Color.White, kind: "KY"}, 424 | {}, 425 | {color: Color.White, kind: "FU"}, 426 | {}, 427 | {}, 428 | {}, 429 | {color: Color.Black, kind: "FU"}, 430 | {}, 431 | {color: Color.Black, kind: "KY"}, 432 | ], 433 | [ 434 | {color: Color.White, kind: "KE"}, 435 | {color: Color.White, kind: "KA"}, 436 | {color: Color.White, kind: "FU"}, 437 | {}, 438 | {}, 439 | {}, 440 | {color: Color.Black, kind: "FU"}, 441 | {color: Color.Black, kind: "HI"}, 442 | {color: Color.Black, kind: "KE"}, 443 | ], 444 | [ 445 | {color: Color.White, kind: "GI"}, 446 | {}, 447 | {color: Color.White, kind: "FU"}, 448 | {}, 449 | {}, 450 | {}, 451 | {color: Color.Black, kind: "FU"}, 452 | {}, 453 | {color: Color.Black, kind: "GI"}, 454 | ], 455 | [ 456 | {color: Color.White, kind: "KI"}, 457 | {}, 458 | {color: Color.White, kind: "FU"}, 459 | {}, 460 | {}, 461 | {}, 462 | {color: Color.Black, kind: "FU"}, 463 | {}, 464 | {color: Color.Black, kind: "KI"}, 465 | ], 466 | [ 467 | {color: Color.White, kind: "OU"}, 468 | {}, 469 | {color: Color.White, kind: "FU"}, 470 | {}, 471 | {}, 472 | {}, 473 | {color: Color.Black, kind: "FU"}, 474 | {}, 475 | {color: Color.Black, kind: "OU"}, 476 | ], 477 | [ 478 | {color: Color.White, kind: "KI"}, 479 | {}, 480 | {color: Color.White, kind: "FU"}, 481 | {}, 482 | {}, 483 | {}, 484 | {color: Color.Black, kind: "FU"}, 485 | {}, 486 | {color: Color.Black, kind: "KI"}, 487 | ], 488 | [ 489 | {color: Color.White, kind: "GI"}, 490 | {}, 491 | {color: Color.White, kind: "FU"}, 492 | {}, 493 | {}, 494 | {}, 495 | {color: Color.Black, kind: "FU"}, 496 | {}, 497 | {color: Color.Black, kind: "GI"}, 498 | ], 499 | [ 500 | {color: Color.White, kind: "KE"}, 501 | {color: Color.White, kind: "HI"}, 502 | {color: Color.White, kind: "FU"}, 503 | {}, 504 | {}, 505 | {}, 506 | {color: Color.Black, kind: "FU"}, 507 | {color: Color.Black, kind: "KA"}, 508 | {color: Color.Black, kind: "KE"}, 509 | ], 510 | [ 511 | {color: Color.White, kind: "KY"}, 512 | {}, 513 | {color: Color.White, kind: "FU"}, 514 | {}, 515 | {}, 516 | {}, 517 | {color: Color.Black, kind: "FU"}, 518 | {}, 519 | {color: Color.Black, kind: "KY"}, 520 | ], 521 | ]; 522 | const diff = []; 523 | for (let i = 0; i < 9; i++) { 524 | for (let j = 0; j < 9; j++) { 525 | if (!samePiece(obj.initial.data.board[i][j], hirate[i][j])) { 526 | diff.push("" + (i + 1) + (j + 1)); 527 | } 528 | } 529 | } 530 | 531 | const presets = {}; 532 | presets[""] = "HIRATE"; 533 | presets["11"] = "KY"; 534 | presets["91"] = "KY_R"; 535 | presets["22"] = "KA"; 536 | presets["82"] = "HI"; 537 | presets["1182"] = "HIKY"; 538 | presets["2282"] = "2"; 539 | presets["228291"] = "3"; 540 | presets["11228291"] = "4"; 541 | presets["1122818291"] = "5"; 542 | presets["1121228291"] = "5_L"; 543 | presets["112122818291"] = "6"; 544 | presets["11212231818291"] = "7_L"; 545 | presets["11212271818291"] = "7_R"; 546 | presets["1121223171818291"] = "8"; 547 | presets["11212231416171818291"] = "10"; 548 | 549 | const preset = presets[diff.sort().join("")]; 550 | if (preset === "HIRATE") { 551 | if (obj.initial.data.color === Color.Black) { 552 | obj.initial.preset = "HIRATE"; 553 | delete obj.initial.data; 554 | } 555 | } else if (preset && obj.initial.data.color === Color.White) { 556 | obj.initial.preset = preset; 557 | delete obj.initial.data; 558 | } 559 | } 560 | function samePiece(p1, p2) { 561 | return ( 562 | (typeof p1.color === "undefined" && typeof p2.color === "undefined") || 563 | (typeof p1.color !== "undefined" && 564 | typeof p2.color !== "undefined" && 565 | p1.color === p2.color && 566 | p1.kind === p2.kind) 567 | ); 568 | } 569 | function restoreColorOfIllegalAction(moves: IMoveFormat[], shogi: Shogi) { 570 | if (moves.length >= 1 && moves[moves.length - 1].special === "ILLEGAL_ACTION") { 571 | moves[moves.length - 1].special = (shogi.turn ? "+" : "-") + "ILLEGAL_ACTION"; 572 | } 573 | } 574 | function restoreTotalTime( 575 | time: {now: ITimeFormat; total: ITimeFormat}, 576 | lastTime: {now: ITimeFormat; total: ITimeFormat} = { 577 | now: {m: 0, s: 0}, 578 | total: {h: 0, m: 0, s: 0}, 579 | } 580 | ) { 581 | if (!time) { 582 | return; 583 | } 584 | time.total = { 585 | h: (time.now.h || 0) + lastTime.total.h, 586 | m: time.now.m + lastTime.total.m, 587 | s: time.now.s + lastTime.total.s, 588 | }; 589 | time.total.m += Math.floor(time.total.s / 60); 590 | time.total.s = time.total.s % 60; 591 | time.total.h += Math.floor(time.total.m / 60); 592 | time.total.m = time.total.m % 60; 593 | } 594 | -------------------------------------------------------------------------------- /src/peg/__tests__/__snapshots__/kifParserTest.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`kif-parser same at first fork 1`] = `{"header":{},"moves":[{},{"move":{"piece":"FU","to":{"x":7,"y":6},"from":{"x":7,"y":7}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}},{"move":{"piece":"FU","to":{"x":3,"y":4},"from":{"x":3,"y":3}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}},{"move":{"piece":"FU","to":{"x":2,"y":6},"from":{"x":2,"y":7}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}},"forks":[[{"move":{"piece":"KA","to":{"x":2,"y":2},"promote":true,"from":{"x":8,"y":8}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}},{"move":{"piece":"GI","same":true,"from":{"x":3,"y":1}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}},"forks":[[{"move":{"piece":"HI","same":true,"from":{"x":8,"y":2}},"time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}},{"special":"CHUDAN","time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}}]]},{"special":"CHUDAN","time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}}]]},{"special":"CHUDAN","time":{"now":{"m":0,"s":0},"total":{"h":0,"m":0,"s":0}}}]}`; 4 | -------------------------------------------------------------------------------- /src/peg/__tests__/csaParserTest.ts: -------------------------------------------------------------------------------- 1 | import {parseCSA as parse} from "../parsers"; 2 | 3 | describe("V2 parser for V2 formats", () => { 4 | it("supports header with $ markups", () => { 5 | expect( 6 | parse( 7 | `V2.2 8 | N+sente 9 | N-gote 10 | $SITE:将棋会館 11 | $START_TIME:2015/08/04 13:00:00 12 | PI 13 | + 14 | +7776FU 15 | -3334FU 16 | +7978GI 17 | -2288UM 18 | %TORYO 19 | ` 20 | ).header 21 | ).toMatchInlineSnapshot(` 22 | Object { 23 | "先手": "sente", 24 | "場所": "将棋会館", 25 | "後手": "gote", 26 | "開始日時": "2015/08/04 13:00:00", 27 | } 28 | `); 29 | }); 30 | it.todo("separator -- csa file can contain multiple kifus"); 31 | }); 32 | 33 | describe.each([ 34 | {v: "V1", prefix: ""}, 35 | {v: "V2", prefix: "V2.2\n"}, 36 | ])("$v parser for V1 formats", ({prefix}) => { 37 | it("matches snapshot for simple case", () => { 38 | expect( 39 | parse(`${prefix}PI 40 | + 41 | +7776FU 42 | -3334FU 43 | +8822UM 44 | -3122GI 45 | +0045KA 46 | `) 47 | ).toMatchSnapshot(); 48 | }); 49 | it("supports comments", () => { 50 | expect( 51 | parse( 52 | `${prefix}PI 53 | + 54 | '開始時コメント 55 | +7776FU 56 | '初手コメント 57 | '初手コメント2 58 | -3334FU 59 | +8822UM 60 | ` 61 | ).moves 62 | ).toMatchSnapshot(); 63 | }); 64 | it("supports special moves", () => { 65 | expect( 66 | parse(`${prefix}PI 67 | + 68 | +7776FU 69 | -3334FU 70 | +7978GI 71 | -2288UM 72 | %TORYO 73 | `).moves 74 | ).toMatchSnapshot(); 75 | }); 76 | it("supports multiple statements with commas", () => { 77 | expect( 78 | parse( 79 | `${prefix}PI 80 | + 81 | +7776FU,T12,-3334FU,T2 82 | +8822UM,T100 83 | -3122GI,T1 84 | +0045KA,T0 85 | ` 86 | ).moves 87 | ).toMatchSnapshot(); 88 | }); 89 | it("supports time", () => { 90 | expect( 91 | parse( 92 | `${prefix}PI 93 | + 94 | +7776FU 95 | T12 96 | -3334FU 97 | T2 98 | +8822UM 99 | T100 100 | -3122GI 101 | T1 102 | +0045KA 103 | T0 104 | ` 105 | ).moves 106 | ).toMatchSnapshot(); 107 | }); 108 | it("supports header", () => { 109 | expect( 110 | parse(`${prefix}N+sente 111 | N-gote 112 | PI 113 | + 114 | +7776FU 115 | -3334FU 116 | +7978GI 117 | -2288UM 118 | %TORYO 119 | `).header 120 | ).toMatchSnapshot(); 121 | }); 122 | describe("initial field", () => { 123 | it("supports 平手初期局面 (PI)", () => { 124 | expect( 125 | parse( 126 | `${prefix}PI82HI22KA91KY81KE21KE11KY 127 | - 128 | -5142OU 129 | +7776FU 130 | -3122GI 131 | +8866KA 132 | -7182GI 133 | ` 134 | ).initial 135 | ).toMatchSnapshot(); 136 | }); 137 | it("supports 一括表現", () => { 138 | expect( 139 | parse( 140 | `${prefix}P1 * * -GI-KI-OU-KI-GI * * 141 | P2 * * * * * * * * * 142 | P3-FU-FU-FU-FU-FU-FU-FU-FU-FU 143 | P4 * * * * * * * * * 144 | P5 * * * * * * * * * 145 | P6 * * * * * * * * * 146 | P7+FU+FU+FU+FU+FU+FU+FU+FU+FU 147 | P8 * +KA * * * * * +HI * 148 | P9+KY+KE+GI+KI+OU+KI+GI+KE+KY 149 | - 150 | -5142OU 151 | +7776FU 152 | -3122GI 153 | +8866KA 154 | -7182GI 155 | ` 156 | ).initial 157 | ).toMatchSnapshot(); 158 | }); 159 | it("supports 駒別単独表現", () => { 160 | expect( 161 | parse( 162 | `${prefix}P-11OU21FU22FU23FU24FU25FU26FU27FU28FU29FU 163 | P+00HI00HI00KY00KY00KY00KY 164 | P-00GI00GI00GI00GI00KE00KE00KE00KE 165 | + 166 | +0013KY 167 | -0012KE 168 | +1312NY 169 | ` 170 | ).initial 171 | ).toMatchSnapshot(); 172 | }); 173 | describe("00AL stands for the rest", () => { 174 | it("works with 駒別単独", () => { 175 | expect( 176 | parse(`${prefix}P+23TO 177 | P-11OU21KE 178 | P+00KI 179 | P-00AL 180 | + 181 | +0022KI 182 | %TSUMI 183 | `).initial 184 | ).toMatchSnapshot(); 185 | }); 186 | it("works with 一括表現", () => { 187 | expect( 188 | parse(`${prefix}P1 * * * * * * * * * 189 | P2 * * * * * * * * * 190 | P3 * * * -FU-FU * * * * 191 | P4-RY * * -OU * -FU * * * 192 | P5 * * +KY * -TO * * * * 193 | P6 * * +RY * * * * * * 194 | P7+KA * * * * * * * * 195 | P8+KA * * * * * * * * 196 | P9 * * * * * * * * * 197 | P-00AL 198 | + 199 | `).initial 200 | ).toMatchSnapshot(); 201 | }); 202 | }); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /src/peg/__tests__/ki2ParserTest.ts: -------------------------------------------------------------------------------- 1 | import {parseKI2 as parse} from "../parsers"; 2 | 3 | function p(x, y) { 4 | return {x, y}; 5 | } 6 | describe("ki2-parser", () => { 7 | it("simple", () => { 8 | expect(parse("▲7六歩 △3四歩 ▲2二角成 △同 銀 ▲4五角")).toEqual({ 9 | header: {}, 10 | moves: [ 11 | {}, 12 | {move: {to: p(7, 6), piece: "FU"}}, 13 | {move: {to: p(3, 4), piece: "FU"}}, 14 | {move: {to: p(2, 2), piece: "KA", promote: true}}, 15 | {move: {same: true, piece: "GI"}}, 16 | {move: {to: p(4, 5), piece: "KA"}}, 17 | ], 18 | }); 19 | }); 20 | it("comment", () => { 21 | expect( 22 | parse("*開始時コメント\n▲7六歩\n*初手コメント\n*初手コメント2\n△3四歩 ▲2二角成") 23 | ).toEqual({ 24 | header: {}, 25 | moves: [ 26 | {comments: ["開始時コメント"]}, 27 | {move: {to: p(7, 6), piece: "FU"}, comments: ["初手コメント", "初手コメント2"]}, 28 | {move: {to: p(3, 4), piece: "FU"}}, 29 | {move: {to: p(2, 2), piece: "KA", promote: true}}, 30 | ], 31 | }); 32 | }); 33 | it("special", () => { 34 | expect(parse("▲7六歩 △3四歩 ▲7八銀 △8八角成\nまで4手で後手の勝ち\n")).toEqual({ 35 | header: {}, 36 | moves: [ 37 | {}, 38 | {move: {to: p(7, 6), piece: "FU"}}, 39 | {move: {to: p(3, 4), piece: "FU"}}, 40 | {move: {to: p(7, 8), piece: "GI"}}, 41 | {move: {to: p(8, 8), piece: "KA", promote: true}}, 42 | {special: "TORYO"}, 43 | ], 44 | }); 45 | }); 46 | describe("header", () => { 47 | it("手合割", () => { 48 | expect(parse("手合割:平手\n▲7六歩 △3四歩 ▲2二角成 △同 銀 ▲4五角")).toEqual({ 49 | header: { 50 | 手合割: "平手", 51 | }, 52 | initial: {preset: "HIRATE"}, 53 | moves: [ 54 | {}, 55 | {move: {to: p(7, 6), piece: "FU"}}, 56 | {move: {to: p(3, 4), piece: "FU"}}, 57 | {move: {to: p(2, 2), piece: "KA", promote: true}}, 58 | {move: {same: true, piece: "GI"}}, 59 | {move: {to: p(4, 5), piece: "KA"}}, 60 | ], 61 | }); 62 | expect(parse("手合割:六枚落ち\n△4二玉 ▲7六歩 △2二銀 ▲6六角 △8二銀")).toEqual({ 63 | header: { 64 | 手合割: "六枚落ち", 65 | }, 66 | initial: {preset: "6"}, 67 | moves: [ 68 | {}, 69 | {move: {to: p(4, 2), piece: "OU"}}, 70 | {move: {to: p(7, 6), piece: "FU"}}, 71 | {move: {to: p(2, 2), piece: "GI"}}, 72 | {move: {to: p(6, 6), piece: "KA"}}, 73 | {move: {to: p(8, 2), piece: "GI"}}, 74 | ], 75 | }); 76 | }); 77 | }); 78 | describe("initial", () => { 79 | it("simple", () => { 80 | expect( 81 | parse( 82 | "\ 83 | 手合割:その他 \n\ 84 | 上手の持駒:銀四 桂四 \n\ 85 | 9 8 7 6 5 4 3 2 1\n\ 86 | +---------------------------+\n\ 87 | | ・ ・ ・ ・ ・ ・ ・v歩v玉|一\n\ 88 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|二\n\ 89 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|三\n\ 90 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|四\n\ 91 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|五\n\ 92 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|六\n\ 93 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|七\n\ 94 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|八\n\ 95 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|九\n\ 96 | +---------------------------+\n\ 97 | 下手の持駒:飛二 香四 \n\ 98 | 下手番\n\ 99 | 下手:shitate\n\ 100 | 上手:uwate\n\ 101 | ▲1三香 △1二桂 ▲同香成" 102 | ) 103 | ).toEqual({ 104 | header: { 105 | 手合割: "その他 ", 106 | 上手: "uwate", 107 | 下手: "shitate", 108 | }, 109 | initial: { 110 | preset: "OTHER", 111 | data: { 112 | board: [ 113 | [{color: 1, kind: "OU"}, {}, {}, {}, {}, {}, {}, {}, {}], 114 | [ 115 | {color: 1, kind: "FU"}, 116 | {color: 1, kind: "FU"}, 117 | {color: 1, kind: "FU"}, 118 | {color: 1, kind: "FU"}, 119 | {color: 1, kind: "FU"}, 120 | {color: 1, kind: "FU"}, 121 | {color: 1, kind: "FU"}, 122 | {color: 1, kind: "FU"}, 123 | {color: 1, kind: "FU"}, 124 | ], 125 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 126 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 127 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 128 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 129 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 130 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 131 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 132 | ], 133 | color: 0, 134 | hands: [ 135 | {FU: 0, KY: 4, KE: 0, GI: 0, KI: 0, KA: 0, HI: 2}, 136 | {FU: 0, KY: 0, KE: 4, GI: 4, KI: 0, KA: 0, HI: 0}, 137 | ], 138 | }, 139 | }, 140 | moves: [ 141 | {}, 142 | {move: {to: p(1, 3), piece: "KY"}}, 143 | {move: {to: p(1, 2), piece: "KE"}}, 144 | {move: {same: true, piece: "KY", promote: true}}, 145 | ], 146 | }); 147 | }); 148 | }); 149 | describe("fork", () => { 150 | it("normal", () => { 151 | expect( 152 | parse( 153 | "\ 154 | 手合割:平手\n\ 155 | ▲7六歩 △3四歩 ▲2二角成 △同 銀 ▲4五角\n\ 156 | まで5手で中断\n\ 157 | \n\ 158 | 変化:3手\n\ 159 | ▲6六歩 △8四歩\ 160 | " 161 | ) 162 | ).toEqual({ 163 | header: { 164 | 手合割: "平手", 165 | }, 166 | initial: {preset: "HIRATE"}, 167 | moves: [ 168 | {}, 169 | {move: {to: p(7, 6), piece: "FU"}}, 170 | {move: {to: p(3, 4), piece: "FU"}}, 171 | { 172 | move: {to: p(2, 2), piece: "KA", promote: true}, 173 | forks: [ 174 | [ 175 | // {}, 176 | {move: {to: p(6, 6), piece: "FU"}}, 177 | {move: {to: p(8, 4), piece: "FU"}}, 178 | ], 179 | ], 180 | }, 181 | {move: {same: true, piece: "GI"}}, 182 | {move: {to: p(4, 5), piece: "KA"}}, 183 | {special: "CHUDAN"}, 184 | ], 185 | }); 186 | }); 187 | 188 | it("Multiple forks", () => { 189 | expect( 190 | parse( 191 | "▲7六歩 △3四歩\n" + 192 | "\n" + 193 | "変化:2手\n" + 194 | "△8四歩\n" + 195 | "\n" + 196 | "変化:2手\n" + 197 | "△4四歩\n" + 198 | "\n" + 199 | "変化:1手\n" + 200 | "▲2六歩\n" + 201 | "\n" + 202 | "変化:1手\n" + 203 | "▲8六歩\n" + 204 | "\n" + 205 | "変化:1手\n" + 206 | "▲7八金" 207 | ) 208 | ).toEqual({ 209 | header: {}, 210 | moves: [ 211 | {}, 212 | { 213 | move: {to: p(7, 6), piece: "FU"}, 214 | forks: [ 215 | [{move: {to: p(2, 6), piece: "FU"}}], 216 | [{move: {to: p(8, 6), piece: "FU"}}], 217 | [{move: {to: p(7, 8), piece: "KI"}}], 218 | ], 219 | }, 220 | { 221 | move: {to: p(3, 4), piece: "FU"}, 222 | forks: [ 223 | [{move: {to: p(8, 4), piece: "FU"}}], 224 | [{move: {to: p(4, 4), piece: "FU"}}], 225 | ], 226 | }, 227 | ], 228 | }); 229 | }); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /src/peg/__tests__/kifParserTest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-irregular-whitespace */ 2 | import {parseKIF as parse} from "../parsers"; 3 | 4 | function p(x, y) { 5 | return {x, y}; 6 | } 7 | describe("kif-parser", () => { 8 | it("simple", () => { 9 | expect( 10 | parse("1 7六歩(77)\n2 3四歩(33)\n3 2二角成(88)\n 4 同 銀(31)\n5 4五角打\n") 11 | ).toEqual({ 12 | header: {}, 13 | moves: [ 14 | {}, 15 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 16 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 17 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 18 | {move: {from: p(3, 1), same: true, piece: "GI"}}, 19 | {move: {to: p(4, 5), piece: "KA"}}, 20 | ], 21 | }); 22 | }); 23 | it("comment", () => { 24 | expect( 25 | parse( 26 | "*開始時コメント\n1 7六歩(77)\n*初手コメント\n*初手コメント2\n2 3四歩(33)\n3 2二角成(88)\n" 27 | ) 28 | ).toEqual({ 29 | header: {}, 30 | moves: [ 31 | {comments: ["開始時コメント"]}, 32 | { 33 | move: {from: p(7, 7), to: p(7, 6), piece: "FU"}, 34 | comments: ["初手コメント", "初手コメント2"], 35 | }, 36 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 37 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 38 | ], 39 | }); 40 | }); 41 | it("time", () => { 42 | expect( 43 | parse( 44 | "1 7六歩(77) (0:01/00:00:01)\n2 3四歩(33) (0:02/00:00:02)\n3 2二角成(88) (0:20/00:00:21)\n 4 同 銀(31) (0:03/00:00:05)\n5 4五角打 (0:39/00:01:00)\n" 45 | ) 46 | ).toEqual({ 47 | header: {}, 48 | moves: [ 49 | {}, 50 | { 51 | move: {from: p(7, 7), to: p(7, 6), piece: "FU"}, 52 | time: {now: {m: 0, s: 1}, total: {h: 0, m: 0, s: 1}}, 53 | }, 54 | { 55 | move: {from: p(3, 3), to: p(3, 4), piece: "FU"}, 56 | time: {now: {m: 0, s: 2}, total: {h: 0, m: 0, s: 2}}, 57 | }, 58 | { 59 | move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}, 60 | time: {now: {m: 0, s: 20}, total: {h: 0, m: 0, s: 21}}, 61 | }, 62 | { 63 | move: {from: p(3, 1), same: true, piece: "GI"}, 64 | time: {now: {m: 0, s: 3}, total: {h: 0, m: 0, s: 5}}, 65 | }, 66 | { 67 | move: {to: p(4, 5), piece: "KA"}, 68 | time: {now: {m: 0, s: 39}, total: {h: 0, m: 1, s: 0}}, 69 | }, 70 | ], 71 | }); 72 | }); 73 | it("special", () => { 74 | expect( 75 | parse( 76 | "1 7六歩(77)\n2 3四歩(33)\n3 7八銀(79)\n 4 8八角成(22)\n5 投了\nまで4手で後手の勝ち\n" 77 | ) 78 | ).toEqual({ 79 | header: {}, 80 | moves: [ 81 | {}, 82 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 83 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 84 | {move: {from: p(7, 9), to: p(7, 8), piece: "GI"}}, 85 | {move: {from: p(2, 2), to: p(8, 8), piece: "KA", promote: true}}, 86 | {special: "TORYO"}, 87 | ], 88 | }); 89 | }); 90 | describe("header", () => { 91 | it("手合割", () => { 92 | expect( 93 | parse( 94 | "手合割:平手\n1 7六歩(77)\n2 3四歩(33)\n3 2二角成(88)\n 4 同 銀(31)\n5 4五角打\n" 95 | ) 96 | ).toEqual({ 97 | header: { 98 | 手合割: "平手", 99 | }, 100 | initial: {preset: "HIRATE"}, 101 | moves: [ 102 | {}, 103 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 104 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 105 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 106 | {move: {from: p(3, 1), same: true, piece: "GI"}}, 107 | {move: {to: p(4, 5), piece: "KA"}}, 108 | ], 109 | }); 110 | expect( 111 | parse( 112 | "手合割:六枚落ち\n1 4二玉(51)\n2 7六歩(77)\n3 2二銀(31)\n 4 6六角(88)\n5 8二銀(71)\n" 113 | ) 114 | ).toEqual({ 115 | header: { 116 | 手合割: "六枚落ち", 117 | }, 118 | initial: {preset: "6"}, 119 | moves: [ 120 | {}, 121 | {move: {from: p(5, 1), to: p(4, 2), piece: "OU"}}, 122 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 123 | {move: {from: p(3, 1), to: p(2, 2), piece: "GI"}}, 124 | {move: {from: p(8, 8), to: p(6, 6), piece: "KA"}}, 125 | {move: {from: p(7, 1), to: p(8, 2), piece: "GI"}}, 126 | ], 127 | }); 128 | }); 129 | }); 130 | describe("initial", () => { 131 | it("simple", () => { 132 | expect( 133 | parse( 134 | "\ 135 | 手合割:その他 \n\ 136 | 上手の持駒:銀四 桂四 \n\ 137 | 9 8 7 6 5 4 3 2 1\n\ 138 | +---------------------------+\n\ 139 | | ・ ・ ・ ・ ・ ・ ・v歩v玉|一\n\ 140 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|二\n\ 141 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|三\n\ 142 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|四\n\ 143 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|五\n\ 144 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|六\n\ 145 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|七\n\ 146 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|八\n\ 147 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|九\n\ 148 | +---------------------------+\n\ 149 | 下手の持駒:飛二 香四 \n\ 150 | 下手番\n\ 151 | 下手:shitate\n\ 152 | 上手:uwate\n\ 153 | 1 1三香打\n2 1二桂打\n3 同 香成(13)\n" 154 | ) 155 | ).toEqual({ 156 | header: { 157 | 手合割: "その他 ", 158 | 上手: "uwate", 159 | 下手: "shitate", 160 | }, 161 | initial: { 162 | preset: "OTHER", 163 | data: { 164 | board: [ 165 | [{color: 1, kind: "OU"}, {}, {}, {}, {}, {}, {}, {}, {}], 166 | [ 167 | {color: 1, kind: "FU"}, 168 | {color: 1, kind: "FU"}, 169 | {color: 1, kind: "FU"}, 170 | {color: 1, kind: "FU"}, 171 | {color: 1, kind: "FU"}, 172 | {color: 1, kind: "FU"}, 173 | {color: 1, kind: "FU"}, 174 | {color: 1, kind: "FU"}, 175 | {color: 1, kind: "FU"}, 176 | ], 177 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 178 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 179 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 180 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 181 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 182 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 183 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 184 | ], 185 | color: 0, 186 | hands: [ 187 | {FU: 0, KY: 4, KE: 0, GI: 0, KI: 0, KA: 0, HI: 2}, 188 | {FU: 0, KY: 0, KE: 4, GI: 4, KI: 0, KA: 0, HI: 0}, 189 | ], 190 | }, 191 | }, 192 | moves: [ 193 | {}, 194 | {move: {to: p(1, 3), piece: "KY"}}, 195 | {move: {to: p(1, 2), piece: "KE"}}, 196 | {move: {from: p(1, 3), same: true, piece: "KY", promote: true}}, 197 | ], 198 | }); 199 | }); 200 | it("Kifu for iPhone dialect", () => { 201 | // 持ち駒が半角スペース区切り 202 | // 手合割が平手 203 | expect( 204 | parse( 205 | "\ 206 | 手合割:平手\n\ 207 | 上手の持駒:銀四 桂四 \n\ 208 | 9 8 7 6 5 4 3 2 1\n\ 209 | +---------------------------+\n\ 210 | | ・ ・ ・ ・ ・ ・ ・v歩v玉|一\n\ 211 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|二\n\ 212 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|三\n\ 213 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|四\n\ 214 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|五\n\ 215 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|六\n\ 216 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|七\n\ 217 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|八\n\ 218 | | ・ ・ ・ ・ ・ ・ ・v歩 ・|九\n\ 219 | +---------------------------+\n\ 220 | 下手の持駒:飛二 香四 \n\ 221 | 下手番\n\ 222 | 下手:shitate\n\ 223 | 上手:uwate\n\ 224 | 1 1三香打\n2 1二桂打\n3 同 香成(13)\n" 225 | ) 226 | ).toEqual({ 227 | header: { 228 | 手合割: "平手", 229 | 上手: "uwate", 230 | 下手: "shitate", 231 | }, 232 | initial: { 233 | preset: "OTHER", // 上書き 234 | data: { 235 | board: [ 236 | [{color: 1, kind: "OU"}, {}, {}, {}, {}, {}, {}, {}, {}], 237 | [ 238 | {color: 1, kind: "FU"}, 239 | {color: 1, kind: "FU"}, 240 | {color: 1, kind: "FU"}, 241 | {color: 1, kind: "FU"}, 242 | {color: 1, kind: "FU"}, 243 | {color: 1, kind: "FU"}, 244 | {color: 1, kind: "FU"}, 245 | {color: 1, kind: "FU"}, 246 | {color: 1, kind: "FU"}, 247 | ], 248 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 249 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 250 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 251 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 252 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 253 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 254 | [{}, {}, {}, {}, {}, {}, {}, {}, {}], 255 | ], 256 | color: 0, 257 | hands: [ 258 | {FU: 0, KY: 4, KE: 0, GI: 0, KI: 0, KA: 0, HI: 2}, 259 | {FU: 0, KY: 0, KE: 4, GI: 4, KI: 0, KA: 0, HI: 0}, 260 | ], 261 | }, 262 | }, 263 | moves: [ 264 | {}, 265 | {move: {to: p(1, 3), piece: "KY"}}, 266 | {move: {to: p(1, 2), piece: "KE"}}, 267 | {move: {from: p(1, 3), same: true, piece: "KY", promote: true}}, 268 | ], 269 | }); 270 | }); 271 | }); 272 | describe("fork", () => { 273 | it("normal", () => { 274 | expect( 275 | parse( 276 | "\ 277 | 手合割:平手\n\ 278 | 1 7六歩(77)\n\ 279 | 2 3四歩(33)\n\ 280 | 3 2二角成(88)+\n\ 281 | 4 同 銀(31)\n\ 282 | 5 4五角打\n\ 283 | 6 中断\n\ 284 | \n\ 285 | 変化:3手\n\ 286 | 3 6六歩(67)\n\ 287 | 4 8四歩(83)\n\ 288 | " 289 | ) 290 | ).toEqual({ 291 | header: { 292 | 手合割: "平手", 293 | }, 294 | initial: {preset: "HIRATE"}, 295 | moves: [ 296 | {}, 297 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 298 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 299 | { 300 | move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}, 301 | forks: [ 302 | [ 303 | // {}, 304 | {move: {from: p(6, 7), to: p(6, 6), piece: "FU"}}, 305 | {move: {from: p(8, 3), to: p(8, 4), piece: "FU"}}, 306 | ], 307 | ], 308 | }, 309 | {move: {from: p(3, 1), same: true, piece: "GI"}}, 310 | {move: {to: p(4, 5), piece: "KA"}}, 311 | {special: "CHUDAN"}, 312 | ], 313 | }); 314 | }); 315 | 316 | it("Multiple forks", () => { 317 | expect( 318 | parse( 319 | "1 7六歩(77)+\n" + 320 | "2 3四歩(33)+\n" + 321 | "\n" + 322 | "変化:2手\n" + 323 | "2 8四歩(83)+\n" + 324 | "\n" + 325 | "変化:2手\n" + 326 | "2 4四歩(43)\n" + 327 | "\n" + 328 | "変化:1手\n" + 329 | "1 2六歩(27)+\n" + 330 | "\n" + 331 | "変化:1手\n" + 332 | "1 8六歩(87)+\n" + 333 | "\n" + 334 | "変化:1手\n" + 335 | "1 7八金(69)\n" 336 | ) 337 | ).toEqual({ 338 | header: {}, 339 | moves: [ 340 | {}, 341 | { 342 | move: {from: p(7, 7), to: p(7, 6), piece: "FU"}, 343 | forks: [ 344 | [{move: {from: p(2, 7), to: p(2, 6), piece: "FU"}}], 345 | [{move: {from: p(8, 7), to: p(8, 6), piece: "FU"}}], 346 | [{move: {from: p(6, 9), to: p(7, 8), piece: "KI"}}], 347 | ], 348 | }, 349 | { 350 | move: {from: p(3, 3), to: p(3, 4), piece: "FU"}, 351 | forks: [ 352 | [{move: {from: p(8, 3), to: p(8, 4), piece: "FU"}}], 353 | [{move: {from: p(4, 3), to: p(4, 4), piece: "FU"}}], 354 | ], 355 | }, 356 | ], 357 | }); 358 | }); 359 | }); 360 | describe("split", () => { 361 | it("normal", () => { 362 | expect( 363 | parse( 364 | "\ 365 | 手合割:平手\n\ 366 | 手数----指手--\n\ 367 | *開始コメント\n\ 368 | 1 7六歩(77)\n\ 369 | *初手コメント\n\ 370 | 2 3四歩(33)\n\ 371 | 3 2二角成(88)+\n\ 372 | 4 中断\n\ 373 | " 374 | ) 375 | ).toEqual({ 376 | header: { 377 | 手合割: "平手", 378 | }, 379 | initial: {preset: "HIRATE"}, 380 | moves: [ 381 | {comments: ["開始コメント"]}, 382 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}, comments: ["初手コメント"]}, 383 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 384 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 385 | {special: "CHUDAN"}, 386 | ], 387 | }); 388 | }); 389 | it("after initial comment", () => { 390 | expect( 391 | parse( 392 | "\ 393 | 手合割:平手\n\ 394 | *開始コメント\n\ 395 | 手数----指手--\n\ 396 | 1 7六歩(77)\n\ 397 | *初手コメント\n\ 398 | 2 3四歩(33)\n\ 399 | 3 2二角成(88)+\n\ 400 | 4 中断\n\ 401 | " 402 | ) 403 | ).toEqual({ 404 | header: { 405 | 手合割: "平手", 406 | }, 407 | initial: {preset: "HIRATE"}, 408 | moves: [ 409 | {comments: ["開始コメント"]}, 410 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}, comments: ["初手コメント"]}, 411 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 412 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 413 | {special: "CHUDAN"}, 414 | ], 415 | }); 416 | }); 417 | }); 418 | describe("unsupported annotations", () => { 419 | it("盤面回転", () => { 420 | expect( 421 | parse( 422 | "盤面回転\n1 7六歩(77)\n2 3四歩(33)\n3 2二角成(88)\n 4 同 銀(31)\n5 4五角打\n" 423 | ) 424 | ).toEqual({ 425 | header: {}, 426 | moves: [ 427 | {}, 428 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 429 | {move: {from: p(3, 3), to: p(3, 4), piece: "FU"}}, 430 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 431 | {move: {from: p(3, 1), same: true, piece: "GI"}}, 432 | {move: {to: p(4, 5), piece: "KA"}}, 433 | ], 434 | }); 435 | }); 436 | it("&読み込み時表示", () => { 437 | expect( 438 | parse( 439 | "1 7六歩(77)\n2 3四歩(33)\n&読み込み時表示\n3 2二角成(88)\n 4 同 銀(31)\n5 4五角打\n" 440 | ) 441 | ).toEqual({ 442 | header: {}, 443 | moves: [ 444 | {}, 445 | {move: {from: p(7, 7), to: p(7, 6), piece: "FU"}}, 446 | { 447 | move: {from: p(3, 3), to: p(3, 4), piece: "FU"}, 448 | comments: ["&読み込み時表示"], 449 | }, 450 | {move: {from: p(8, 8), to: p(2, 2), piece: "KA", promote: true}}, 451 | {move: {from: p(3, 1), same: true, piece: "GI"}}, 452 | {move: {to: p(4, 5), piece: "KA"}}, 453 | ], 454 | }); 455 | }); 456 | }); 457 | it("same at first fork", () => { 458 | expect( 459 | parse(` 1 7六歩(77) ( 0:00/00:00:00) 460 | 2 3四歩(33) ( 0:00/00:00:00) 461 | 3 2六歩(27) ( 0:00/00:00:00) 462 | 4 中断 ( 0:00/00:00:00) 463 | まで3手で中断 464 | 465 | 変化:3手 466 | 3 2二角成(88) ( 0:00/00:00:00) 467 | 4 同 銀(31) ( 0:00/00:00:00)+ 468 | 5 中断 ( 0:00/00:00:00) 469 | まで4手で中断 470 | 471 | 変化:4手 472 | 4 同 飛(82) ( 0:00/00:00:00) 473 | 5 中断 ( 0:00/00:00:00) 474 | まで4手で中断 475 | `) 476 | ).toMatchSnapshot(); 477 | }); 478 | }); 479 | -------------------------------------------------------------------------------- /src/peg/ambient.ts: -------------------------------------------------------------------------------- 1 | declare module "*.pegjs" { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | export function parse(kifuString: string): any; 4 | } 5 | -------------------------------------------------------------------------------- /src/peg/csa-parser.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * CSA V1 http://www2.computer-shogi.org/wcsc12/record.html 4 | * CSA V2.2 http://www2.computer-shogi.org/protocol/record_v22.html 5 | */ 6 | function secToTime(sec){ 7 | var remain, h, m, s = sec%60; 8 | m = (sec-s)/60; 9 | return {m:m, s:s}; 10 | } 11 | function getHirate(){ 12 | return [ 13 | [{color:1,kind:"KY"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"KY"},], 14 | [{color:1,kind:"KE"},{color:1,kind:"KA"},{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{color:0,kind:"HI"},{color:0,kind:"KE"},], 15 | [{color:1,kind:"GI"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"GI"},], 16 | [{color:1,kind:"KI"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"KI"},], 17 | [{color:1,kind:"OU"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"OU"},], 18 | [{color:1,kind:"KI"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"KI"},], 19 | [{color:1,kind:"GI"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"GI"},], 20 | [{color:1,kind:"KE"},{color:1,kind:"HI"},{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{color:0,kind:"KA"},{color:0,kind:"KE"},], 21 | [{color:1,kind:"KY"},{ },{color:1,kind:"FU"},{},{},{},{color:0,kind:"FU"},{ },{color:0,kind:"KY"},], 22 | ]; 23 | } 24 | function normalizeHeaderKey(key){ 25 | return { 26 | "EVENT": "棋戦", 27 | "SITE": "場所", 28 | "START_TIME": "開始日時", 29 | "END_TIME": "終了日時", 30 | "TIME_LIMIT": "持ち時間", 31 | }[key] || key; 32 | } 33 | function unpromote(kind) { 34 | return { 35 | TO: "FU", 36 | NY: "KY", 37 | NK: "KE", 38 | NG: "GI", 39 | UM: "KA", 40 | RY: "HI", 41 | }[kind] || kind; 42 | } 43 | } 44 | kifu = csa2 / csa1 45 | 46 | csa2 = version20to22 i:information? ini:initialboard ms:moves? { 47 | var ret = {header:i.header, initial:ini, moves:ms} 48 | if(i && i.players){ 49 | if(i.players[0]) ret.header["先手"]=i.players[0]; 50 | if(i.players[1]) ret.header["後手"]=i.players[1]; 51 | } 52 | return ret; 53 | } 54 | 55 | version20to22 = comment* "V2" (".1" / ".2")? nl 56 | 57 | information = players:players? header:headers {return {players:players, header:header}} 58 | 59 | headers = header:header* { 60 | var ret = {}; 61 | for(var i=0; i=0) { 87 | var rest = {FU:18,KY:4,KE:4,GI:4,KI:4,KA:2,HI:2}; 88 | for(var piece in data.data.hands[1-alColor]) { 89 | rest[piece] -= data.data.hands[1-alColor][piece]; 90 | } 91 | for(var x=0; x0?{comments:c}:{}} 162 | 163 | move = move:(normalmove / specialmove) comments1:comment* time:time? comments2:comment* { 164 | var ret = {}; 165 | const comments = comments1.concat(comments2); 166 | if(comments.length>0){ret.comments=comments;} 167 | if(time){ret.time=time;} 168 | if(move.special){ret.special=move.special}else{ret.move=move}; 169 | return ret; 170 | } 171 | normalmove = teban from:xy to:xy piece:piece nl { 172 | var ret = {to:to, piece:piece} 173 | if(from.x!=0) ret.from = from; 174 | return ret; 175 | } 176 | specialmove = "%" m:[-+_A-Z]+ nl { return {special: m.join("")}; } 177 | 178 | teban = "+"{return 0}/"-"{return 1} 179 | 180 | comment = "'" c:nonl* nl { return c.join(""); } 181 | time = "T" t:[0-9]* nl { return {now: secToTime(parseInt(t.join("")))}; } 182 | 183 | xy = x:[0-9] y:[0-9] { return {x:parseInt(x), y:parseInt(y)}; } 184 | piece = a:[A-Z] b:[A-Z] { return a+b; } 185 | xypiece = xy:xy piece:piece {return {xy:xy, piece:piece}} 186 | 187 | nl = ("\r"? "\n") / " "* "," 188 | nonl = [^\r\n] 189 | -------------------------------------------------------------------------------- /src/peg/ki2-parser.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | function toN(ss){ 3 | return parseInt(ss.join(""), 10); 4 | } 5 | function zenToN(s){ 6 | return "0123456789".indexOf(s); 7 | } 8 | function kanToN(s){ 9 | return "〇一二三四五六七八九".indexOf(s); 10 | } 11 | function kanToN2(s){ 12 | switch(s.length){ 13 | case 1: 14 | return "〇一二三四五六七八九十".indexOf(s); 15 | case 2: 16 | return "〇一二三四五六七八九十".indexOf(s[1])+10; 17 | default: 18 | throw "21以上の数値に対応していません"; 19 | } 20 | } 21 | function kindToCSA(kind){ 22 | if(kind[0]=="成"){ 23 | return { 24 | "香": "NY", 25 | "桂": "NK", 26 | "銀": "NG" 27 | }[kind[1]]; 28 | } 29 | return { 30 | "歩": "FU", 31 | "香": "KY", 32 | "桂": "KE", 33 | "銀": "GI", 34 | "金": "KI", 35 | "角": "KA", 36 | "飛": "HI", 37 | "玉": "OU", 38 | "王": "OU", 39 | "と": "TO", 40 | "杏": "NY", 41 | "圭": "NK", 42 | "全": "NG", 43 | "馬": "UM", 44 | "竜": "RY", 45 | "龍": "RY" 46 | }[kind]; 47 | } 48 | function soutaiToRelative(str){ 49 | return { 50 | "左": "L", 51 | "直": "C", 52 | "右": "R", 53 | }[str] || ""; 54 | } 55 | function dousaToRelative(str){ 56 | return { 57 | "上": "U", 58 | "寄": "M", 59 | "引": "D", 60 | }[str] || ""; 61 | } 62 | function presetToString(preset){ 63 | return { 64 | "平手": "HIRATE", 65 | "香落ち": "KY", 66 | "右香落ち": "KY_R", 67 | "角落ち": "KA", 68 | "飛車落ち": "HI", 69 | "飛香落ち": "HIKY", 70 | "二枚落ち": "2", 71 | "三枚落ち": "3", 72 | "四枚落ち": "4", 73 | "五枚落ち": "5", 74 | "左五枚落ち": "5_L", 75 | "六枚落ち": "6", 76 | "左七枚落ち": "7_L", 77 | "右七枚落ち": "7_R", 78 | "八枚落ち": "8", 79 | "十枚落ち": "10", 80 | "その他": "OTHER", 81 | }[preset.replace(/\s/g, "")]; 82 | } 83 | function makeHand(str){ 84 | var kinds = str.replace(/ $/, "").split(" "); 85 | var ret = {FU:0,KY:0,KE:0,GI:0,KI:0,KA:0,HI:0}; 86 | if(str=="") return ret; 87 | for(var i=0; i=0 ? 0 : 1; 112 | delete ret.header["手番"]; 113 | }else{ 114 | ret.initial.data.color = 0; 115 | } 116 | ret.initial.data.hands = [ 117 | makeHand(ret.header["先手の持駒"] || ret.header["下手の持駒"]), 118 | makeHand(ret.header["後手の持駒"] || ret.header["上手の持駒"]) 119 | ]; 120 | delete ret.header["先手の持駒"]; 121 | delete ret.header["下手の持駒"]; 122 | delete ret.header["後手の持駒"]; 123 | delete ret.header["上手の持駒"]; 124 | } 125 | var forkStack = [{te:0, moves:moves}]; 126 | for(var i=0; i=nowFork.te){fork = forkStack.pop();} 130 | var move = fork.moves[nowFork.te-fork.te]; 131 | move.forks = move.forks || []; 132 | move.forks.push(nowFork.moves); 133 | forkStack.push(fork); 134 | forkStack.push(nowFork); 135 | } 136 | return ret; 137 | } 138 | 139 | header 140 | = key:[^:\r\n]+ ":" value:nonl* nl+ {return {k:key.join(""), v:value.join("")}} / te:[先後上下] "手番" nl {return {k:"手番",v:te}} 141 | initialboard = (" " nonl* nl)? ("+" nonl* nl)? lines:ikkatsuline+ ("+" nonl* nl)? { 142 | var ret = []; 143 | for(var i=0; i<9; i++){ 144 | var line = []; 145 | for(var j=0; j<9; j++){ 146 | line.push(lines[j][8-i]); 147 | } 148 | ret.push(line); 149 | } 150 | return {preset: "OTHER", data: {board:ret}}; 151 | } 152 | ikkatsuline = "|" masu:masu+ "|" nonl+ nl { return masu; } 153 | masu = c:teban k:piece {return {color:c, kind:k}} / " ・" { return {} } 154 | teban = (" "/"+"/"^"){return 0} / ("v"/"V"){return 1} 155 | 156 | moves = hd:firstboard tl:move* res:result? { 157 | tl.unshift(hd); 158 | if(res && !tl[tl.length-1].special){ 159 | tl.push({special: res}); 160 | } 161 | return tl; 162 | } 163 | 164 | firstboard = c:comment* pointer? {return c.length==0 ? {} : {comments:c}} 165 | move = line:line c:comment* pointer? (nl / " ")* { 166 | var ret = {move: line}; 167 | if(c.length>0) ret.comments=c; 168 | return ret; 169 | } 170 | 171 | pointer = "&" nonl* nl 172 | 173 | line = [▲△] f:fugou (nl / " ")* {return f} 174 | fugou = pl:place pi:piece sou:soutai? dou:dousa? pro:("成"/"不成")? da:"打"? { 175 | var ret = {piece: pi}; 176 | if(pl.same){ 177 | ret.same = true; 178 | }else{ 179 | ret.to = pl; 180 | } 181 | if(pro)ret.promote=pro=="成"; 182 | if(da){ 183 | ret.relative = "H"; 184 | }else{ 185 | var rel = soutaiToRelative(sou)+dousaToRelative(dou); 186 | if(rel!="") ret.relative=rel; 187 | } 188 | return ret; 189 | } 190 | place = x:num y:numkan {return {x:x,y:y}} / "同" " "? {return {same:true}} 191 | piece = pro:"成"? p:[歩香桂銀金角飛王玉と杏圭全馬竜龍] {return kindToCSA((pro||"")+p)} 192 | soutai = [左直右] 193 | dousa = [上寄引] 194 | 195 | num = n:[123456789] {return zenToN(n);} 196 | numkan = n:[一二三四五六七八九] {return kanToN(n);} 197 | 198 | comment = "*" comm:nonl* nl {return comm.join("")} 199 | 200 | result = "まで" [0-9]+ "手" res:( 201 | "で" win:turn "手の" res:("勝ち" {return "TORYO"} 202 | / "反則" res:("勝ち" {return "ILLEGAL_ACTION"} 203 | / "負け" {return "ILLEGAL_MOVE"}) {return res}) {return res} 204 | / "で時間切れにより" win:turn "手の勝ち" {return "TIME_UP"} 205 | / "で中断" {return "CHUDAN"} 206 | / "で持将棋" {return "JISHOGI"} 207 | / "で千日手" {return "SENNICHITE"} 208 | / "で"? "詰" "み"? {return "TSUMI"} 209 | / "で不詰" {return "FUZUMI"} 210 | ) nl {return res} 211 | 212 | fork = "変化:" " "* te:[0-9]+ "手" nl moves:moves {return {te:parseInt(te.join("")), moves:moves.slice(1)}} 213 | 214 | turn = [先後上下] 215 | nl = newline+ skipline* 216 | skipline = ("#" nonl* newline) 217 | whitespace = " " / "\t" 218 | newline = whitespace* ("\n" / "\r" "\n"?) 219 | nonl = [^\r\n] 220 | -------------------------------------------------------------------------------- /src/peg/kif-parser.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | function toN(ss){ 3 | return parseInt(ss.join(""), 10); 4 | } 5 | function zenToN(s){ 6 | return "0123456789".indexOf(s); 7 | } 8 | function kanToN(s){ 9 | return "〇一二三四五六七八九".indexOf(s); 10 | } 11 | function kanToN2(s){ 12 | switch(s.length){ 13 | case 1: 14 | return "〇一二三四五六七八九十".indexOf(s); 15 | case 2: 16 | return "〇一二三四五六七八九十".indexOf(s[1])+10; 17 | default: 18 | throw "21以上の数値に対応していません"; 19 | } 20 | } 21 | function kindToCSA(kind){ 22 | if(kind[0]=="成"){ 23 | return { 24 | "香": "NY", 25 | "桂": "NK", 26 | "銀": "NG" 27 | }[kind[1]]; 28 | } 29 | return { 30 | "歩": "FU", 31 | "香": "KY", 32 | "桂": "KE", 33 | "銀": "GI", 34 | "金": "KI", 35 | "角": "KA", 36 | "飛": "HI", 37 | "玉": "OU", 38 | "王": "OU", 39 | "と": "TO", 40 | "杏": "NY", 41 | "圭": "NK", 42 | "全": "NG", 43 | "馬": "UM", 44 | "竜": "RY", 45 | "龍": "RY" 46 | }[kind]; 47 | } 48 | function specialToCSA(str){ 49 | return { 50 | "中断": "CHUDAN", 51 | "封じ手": "CHUDAN", // とりあえず 52 | "投了": "TORYO", 53 | "持将棋": "JISHOGI", 54 | "千日手": "SENNICHITE", 55 | "詰み": "TSUMI", 56 | "不詰": "FUZUMI", 57 | "切れ負け": "TIME_UP", 58 | "反則勝ち": "ILLEGAL_ACTION", // 直前の手が反則(先頭に+か-で反則した側の情報を含める必要が有る) 59 | "反則負け": "ILLEGAL_MOVE" // ここで手番側が反則,反則の内容はコメントで表現 60 | }[str]; 61 | } 62 | function presetToString(preset){ 63 | return { 64 | "平手": "HIRATE", 65 | "香落ち": "KY", 66 | "右香落ち": "KY_R", 67 | "角落ち": "KA", 68 | "飛車落ち": "HI", 69 | "飛香落ち": "HIKY", 70 | "二枚落ち": "2", 71 | "三枚落ち": "3", 72 | "四枚落ち": "4", 73 | "五枚落ち": "5", 74 | "左五枚落ち": "5_L", 75 | "六枚落ち": "6", 76 | "左七枚落ち": "7_L", 77 | "右七枚落ち": "7_R", 78 | "八枚落ち": "8", 79 | "十枚落ち": "10", 80 | "その他": "OTHER", 81 | }[preset.replace(/\s/g, "")]; 82 | } 83 | function makeHand(str){ 84 | // Kifu for iPhoneは半角スペース区切り 85 | var kinds = str.split(/[  ]/); 86 | var ret = {FU:0,KY:0,KE:0,GI:0,KI:0,KA:0,HI:0}; 87 | if(str=="") return ret; 88 | for(var i=0; i=0 ? 0 : 1 ; 114 | delete ret.header["手番"]; 115 | }else{ 116 | ret.initial.data.color = 0; 117 | } 118 | ret.initial.data.hands = [ 119 | makeHand(ret.header["先手の持駒"] || ret.header["下手の持駒"]), 120 | makeHand(ret.header["後手の持駒"] || ret.header["上手の持駒"]) 121 | ]; 122 | delete ret.header["先手の持駒"]; 123 | delete ret.header["下手の持駒"]; 124 | delete ret.header["後手の持駒"]; 125 | delete ret.header["上手の持駒"]; 126 | } 127 | var forkStack = [{te:0, moves:moves}]; 128 | for(var i=0; i=nowFork.te){fork = forkStack.pop();} 132 | var move = fork.moves[nowFork.te-fork.te]; 133 | move.forks = move.forks || []; 134 | move.forks.push(nowFork.moves); 135 | forkStack.push(fork); 136 | forkStack.push(nowFork); 137 | } 138 | return ret; 139 | } 140 | 141 | // ヘッダ 142 | header = key:[^:\r\n]+ ":" value:nonl* nl {return {k:key.join(""), v:value.join("")}} 143 | / te:turn "手番" nl {return {k:"手番",v:te}} 144 | / "盤面回転" nl {return null} 145 | 146 | turn = [先後上下] 147 | 148 | initialboard = (" " nonl* nl)? ("+" nonl* nl)? lines:ikkatsuline+ ("+" nonl* nl)? { 149 | var ret = []; 150 | for(var i=0; i<9; i++){ 151 | var line = []; 152 | for(var j=0; j<9; j++){ 153 | line.push(lines[j][8-i]); 154 | } 155 | ret.push(line); 156 | } 157 | return {preset: "OTHER", data: {board:ret}}; 158 | } 159 | ikkatsuline = "|" masu:masu+ "|" nonl+ nl { return masu; } 160 | masu = c:teban k:piece {return {color:c, kind:k}} / " ・" { return {} } 161 | teban = (" "/"+"/"^"){return 0} / ("v"/"V"){return 1} 162 | 163 | split = "手数----指手--" "-------消費時間--"? nl 164 | 165 | // 棋譜部分 166 | moves = hd:firstboard split? tl:move* result? {tl.unshift(hd); return tl;} 167 | 168 | firstboard = c:comment* pointer? {return c.length==0 ? {} : {comments:c}} 169 | move = line:line c:comment* pointer? { 170 | var ret = {}; 171 | if(c.length>0) ret.comments = c; 172 | if(typeof line.move=="object"){ 173 | ret.move=line.move; 174 | }else{ 175 | ret.special=specialToCSA(line.move) 176 | } 177 | if(line.time) ret.time=line.time; 178 | return ret; 179 | } 180 | 181 | pointer = "&" nonl* nl 182 | 183 | line = " "* te " "* move:(fugou:fugou from:from { 184 | var ret = {piece: fugou.piece}; 185 | if(fugou.to){ret.to=fugou.to}else{ret.same=true}; 186 | if(fugou.promote) ret.promote=true; 187 | if(from) ret.from=from; 188 | return ret; 189 | } / spe:[^\r\n ]* {return spe.join("")}) " "* time:time? "+"? nl {return {move: move, time: time}} 190 | 191 | te = [0-9]+ 192 | fugou = pl:place pi:piece pro:"成"? {return {to:pl, piece: pi,promote:!!pro};} 193 | place = x:num y:numkan {return {x:x,y:y}} / "同" " "? {return null} 194 | 195 | num = n:[123456789] {return zenToN(n);} 196 | numkan = n:[一二三四五六七八九] {return kanToN(n);} 197 | 198 | piece = pro:"成"? p:[歩香桂銀金角飛王玉と杏圭全馬竜龍] {return kindToCSA((pro||"")+p);} 199 | 200 | from = "打" {return null} / "(" x:[1-9] y:[1-9] ")" {return {x:parseInt(x),y:parseInt(y)}} 201 | 202 | time = "(" " "* now:ms " "* "/" " "* total:hms ")" {return {now: now, total: total}} 203 | 204 | hms = h:[0-9]+ ":" m:[0-9]+ ":" s:[0-9]+ {return {h:toN(h),m:toN(m),s:toN(s)}} 205 | ms = m:[0-9]+ ":" s:[0-9]+ {return {m:toN(m),s:toN(s)}} 206 | 207 | comment = "*" comm:nonl* nl {return comm.join("")} 208 | / "&" annotation:nonl* nl {return "&"+annotation.join("")} 209 | 210 | result = "まで" [0-9]+ "手" res:( 211 | "で" win:turn "手の" res:("勝ち" {return "TORYO"} 212 | / "反則" res:("勝ち" {return "ILLEGAL_ACTION"} 213 | / "負け" {return "ILLEGAL_MOVE"}) {return res}) {return res} 214 | / "で時間切れにより" win:turn "手の勝ち" {return "TIME_UP"} 215 | / "で中断" {return "CHUDAN"} 216 | / "で封じ手" {return "CHUDAN"} // とりあえず 217 | / "で持将棋" {return "JISHOGI"} 218 | / "で千日手" {return "SENNICHITE"} 219 | / "で"? "詰" "み"? {return "TSUMI"} 220 | / "で不詰" {return "FUZUMI"} 221 | ) nl {return res} 222 | 223 | fork = "変化:" " "* te:[0-9]+ "手" nl moves:moves {return {te:parseInt(te.join("")), moves:moves.slice(1)}} 224 | 225 | 226 | nl = newline+ skipline* 227 | skipline = ("#" nonl* newline) 228 | whitespace = " " / "\t" 229 | newline = whitespace* ("\n" / "\r" "\n"?) 230 | nonl = [^\r\n] 231 | -------------------------------------------------------------------------------- /src/peg/parsers.ts: -------------------------------------------------------------------------------- 1 | import {IJSONKifuFormat} from "../Formats"; 2 | import "./ambient"; 3 | import * as CSAParser from "./csa-parser.pegjs"; 4 | import * as KI2Parser from "./ki2-parser.pegjs"; 5 | import * as KIFParser from "./kif-parser.pegjs"; 6 | 7 | export function parseCSA(csaString: string): IJSONKifuFormat { 8 | return CSAParser.parse(csaString); 9 | } 10 | 11 | export function parseKIF(kifString: string): IJSONKifuFormat { 12 | return KIFParser.parse(kifString); 13 | } 14 | 15 | export function parseKI2(ki2String: string): IJSONKifuFormat { 16 | return KI2Parser.parse(ki2String); 17 | } 18 | -------------------------------------------------------------------------------- /test/board-serializer.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 4 | serialize(val, config, indentation, depth, refs, printer) { 5 | return JSON.stringify(val); 6 | }, 7 | test(val) { 8 | return val && Object.prototype.hasOwnProperty.call(val, "moves"); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /test/debug-comment-content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 31 | -------------------------------------------------------------------------------- /test/files/csa/2005_GPS_K-SHOGI.csa: -------------------------------------------------------------------------------- 1 | N+sente 2 | N-gote 3 | P1-KY-KE-GI-KI-OU-KI-GI-KE-KY 4 | P2 * -HI * * * * * -KA * 5 | P3-FU-FU-FU-FU-FU-FU-FU-FU-FU 6 | P4 * * * * * * * * * 7 | P5 * * * * * * * * * 8 | P6 * * * * * * * * * 9 | P7+FU+FU+FU+FU+FU+FU+FU+FU+FU 10 | P8 * +KA * * * * * +HI * 11 | P9+KY+KE+GI+KI+OU+KI+GI+KE+KY 12 | + 13 | 'Tue May 3 13:50:01 2005 14 | +7776FU 15 | T12 16 | 'time left 1488 1500 17 | -3334FU 18 | T1 19 | 'time left 1488 1499 20 | +2726FU 21 | T1 22 | 'time left 1487 1499 23 | -4344FU 24 | T1 25 | 'time left 1487 1498 26 | +3948GI 27 | T1 28 | 'time left 1486 1498 29 | -8242HI 30 | T1 31 | 'time left 1486 1497 32 | +5968OU 33 | T1 34 | 'time left 1485 1497 35 | -5162OU 36 | T1 37 | 'time left 1485 1496 38 | +6878OU 39 | T1 40 | 'time left 1484 1496 41 | -6272OU 42 | T1 43 | 'time left 1484 1495 44 | +5756FU 45 | T1 46 | 'time left 1483 1495 47 | -7282OU 48 | T1 49 | 'time left 1483 1494 50 | +4958KI 51 | T1 52 | 'time left 1482 1494 53 | -7172GI 54 | T1 55 | 'time left 1482 1493 56 | +9796FU 57 | T1 58 | 'time left 1481 1493 59 | -9394FU 60 | T1 61 | 'time left 1481 1492 62 | +7968GI 63 | T1 64 | 'time left 1480 1492 65 | -3435FU 66 | T1 67 | 'time left 1480 1491 68 | +6766FU 69 | T12 70 | 'time left 1468 1491 71 | -4232HI 72 | T18 73 | 'time left 1468 1473 74 | +6857GI 75 | T15 76 | 'time left 1453 1473 77 | -3142GI 78 | T33 79 | 'time left 1453 1440 80 | +2625FU 81 | T9 82 | 'time left 1444 1440 83 | -3234HI 84 | T8 85 | 'time left 1444 1432 86 | +6665FU 87 | T7 88 | 'time left 1437 1432 89 | -1314FU 90 | T5 91 | 'time left 1437 1427 92 | +8866KA 93 | T12 94 | 'time left 1425 1427 95 | -2133KE 96 | T5 97 | 'time left 1425 1422 98 | +4746FU 99 | T14 100 | 'time left 1411 1422 101 | -7374FU 102 | T8 103 | 'time left 1411 1414 104 | +6655KA 105 | T10 106 | 'time left 1401 1414 107 | -7273GI 108 | T17 109 | 'time left 1401 1397 110 | +2524FU 111 | T8 112 | 'time left 1393 1397 113 | -3424HI 114 | T2 115 | 'time left 1393 1395 116 | +2824HI 117 | T9 118 | 'time left 1384 1395 119 | -2324FU 120 | T8 121 | 'time left 1384 1387 122 | +0023HI 123 | T15 124 | 'time left 1369 1387 125 | -4132KI 126 | T16 127 | 'time left 1369 1371 128 | +2324RY 129 | T40 130 | 'time left 1329 1371 131 | -4243GI 132 | T1 133 | 'time left 1329 1370 134 | +2427RY 135 | T14 136 | 'time left 1315 1370 137 | -0025FU 138 | T46 139 | 'time left 1315 1324 140 | +3736FU 141 | T16 142 | 'time left 1299 1324 143 | -9495FU 144 | T47 145 | 'time left 1299 1277 146 | +9695FU 147 | T10 148 | 'time left 1289 1277 149 | -9195KY 150 | T12 151 | 'time left 1289 1265 152 | +0096FU 153 | T1 154 | 'time left 1288 1265 155 | -9596KY 156 | T21 157 | 'time left 1288 1244 158 | +0097FU 159 | T1 160 | 'time left 1287 1244 161 | -0024HI 162 | T12 163 | 'time left 1287 1232 164 | +4837GI 165 | T24 166 | 'time left 1263 1232 167 | -3536FU 168 | T12 169 | 'time left 1263 1220 170 | +2736RY 171 | T22 172 | 'time left 1241 1220 173 | -2526FU 174 | T28 175 | 'time left 1241 1192 176 | +3726GI 177 | T5 178 | 'time left 1236 1192 179 | -5354FU 180 | T28 181 | 'time left 1236 1164 182 | +5566KA 183 | T21 184 | 'time left 1215 1164 185 | -0028FU 186 | T21 187 | 'time left 1215 1143 188 | +2635GI 189 | T1 190 | 'time left 1214 1143 191 | -2423HI 192 | T11 193 | 'time left 1214 1132 194 | +9796FU 195 | T9 196 | 'time left 1205 1132 197 | -2231KA 198 | T13 199 | 'time left 1205 1119 200 | +2937KE 201 | T4 202 | 'time left 1201 1119 203 | -0098FU 204 | T16 205 | 'time left 1201 1103 206 | +0024FU 207 | T24 208 | 'time left 1177 1103 209 | -2321HI 210 | T42 211 | 'time left 1177 1061 212 | +9998KY 213 | T22 214 | 'time left 1155 1061 215 | -2829TO 216 | T3 217 | 'time left 1155 1058 218 | +0034FU 219 | T18 220 | 'time left 1137 1058 221 | -2919TO 222 | T26 223 | 'time left 1137 1032 224 | +3433TO 225 | T1 226 | 'time left 1136 1032 227 | -3233KI 228 | T45 229 | 'time left 1136 987 230 | +3745KE 231 | T14 232 | 'time left 1122 987 233 | -3332KI 234 | T41 235 | 'time left 1122 946 236 | +0023KY 237 | T15 238 | 'time left 1107 946 239 | -0022FU 240 | T46 241 | 'time left 1107 900 242 | +0033KE 243 | T17 244 | 'time left 1090 900 245 | -0034FU 246 | T45 247 | 'time left 1090 855 248 | +3534GI 249 | T19 250 | 'time left 1071 855 251 | -3197UM 252 | T19 253 | 'time left 1071 836 254 | +8997KE 255 | T30 256 | 'time left 1041 836 257 | -2151HI 258 | T30 259 | 'time left 1041 806 260 | +2322NY 261 | T2 262 | 'time left 1039 806 263 | -3242KI 264 | T32 265 | 'time left 1039 774 266 | +0086KA 267 | T1 268 | 'time left 1038 774 269 | -6152KI 270 | T20 271 | 'time left 1038 754 272 | +9785KE 273 | T34 274 | 'time left 1004 754 275 | -4445FU 276 | T30 277 | 'time left 1004 724 278 | +8573NK 279 | T40 280 | 'time left 964 724 281 | -8273OU 282 | T14 283 | 'time left 964 710 284 | +0031GI 285 | T16 286 | 'time left 948 710 287 | -0053KE 288 | T25 289 | 'time left 948 685 290 | +8695KA 291 | T1 292 | 'time left 947 685 293 | -0084KY 294 | T21 295 | 'time left 947 664 296 | +3142GI 297 | T20 298 | 'time left 927 664 299 | -5242KI 300 | T25 301 | 'time left 927 639 302 | +3443NG 303 | T1 304 | 'time left 926 639 305 | -4243KI 306 | T26 307 | 'time left 926 613 308 | +0085GI 309 | T1 310 | 'time left 925 613 311 | -7372OU 312 | T21 313 | 'time left 925 592 314 | +8584GI 315 | T23 316 | 'time left 902 592 317 | -8384FU 318 | T21 319 | 'time left 902 571 320 | +0062KI 321 | T22 322 | 'time left 880 571 323 | -7262OU 324 | T20 325 | 'time left 880 551 326 | +6684KA 327 | T21 328 | 'time left 859 551 329 | -0073GI 330 | T25 331 | 'time left 859 526 332 | +3341NK 333 | T21 334 | 'time left 838 526 335 | -5141HI 336 | T25 337 | 'time left 838 501 338 | +3632RY 339 | T18 340 | 'time left 820 501 341 | -4342KI 342 | T11 343 | 'time left 820 490 344 | +3241RY 345 | T7 346 | 'time left 813 490 347 | -4241KI 348 | T21 349 | 'time left 813 469 350 | +8473UM 351 | T1 352 | 'time left 812 469 353 | -8173KE 354 | T15 355 | 'time left 812 454 356 | +0092HI 357 | T1 358 | 'time left 811 454 359 | -0072HI 360 | T15 361 | 'time left 811 439 362 | +9573UM 363 | T22 364 | 'time left 789 439 365 | -6273OU 366 | T9 367 | 'time left 789 430 368 | +0085KE 369 | T11 370 | 'time left 778 430 371 | -7362OU 372 | T20 373 | 'time left 778 410 374 | +0073GI 375 | T1 376 | 'time left 777 410 377 | -6252OU 378 | T16 379 | 'time left 777 394 380 | +9272RY 381 | T26 382 | 'time left 751 394 383 | -5243OU 384 | T20 385 | 'time left 751 374 386 | +0023HI 387 | T2 388 | 'time left 749 374 389 | -0033GI 390 | T16 391 | 'time left 749 358 392 | +2333RY 393 | T25 394 | 'time left 724 358 395 | -4333OU 396 | T1 397 | 'time left 724 357 398 | +2423TO 399 | T25 400 | 'time left 699 357 401 | -3334OU 402 | T15 403 | 'time left 699 342 404 | +0038KY 405 | T16 406 | 'time left 683 342 407 | -0036KE 408 | T11 409 | 'time left 683 331 410 | +3836KY 411 | T18 412 | 'time left 665 331 413 | -3425OU 414 | T15 415 | 'time left 665 316 416 | +0027GI 417 | T2 418 | 'time left 663 316 419 | -0026KI 420 | T11 421 | 'time left 663 305 422 | +0037KE 423 | T19 424 | 'time left 644 305 425 | -2515OU 426 | T10 427 | 'time left 644 295 428 | +0028FU 429 | T1 430 | 'time left 643 295 431 | -2637KI 432 | T15 433 | 'time left 643 280 434 | +2211NY 435 | T1 436 | 'time left 642 280 437 | -0077GI 438 | T15 439 | 'time left 642 265 440 | +7877OU 441 | T21 442 | 'time left 621 265 443 | -0044KA 444 | T8 445 | 'time left 621 257 446 | +7767OU 447 | T5 448 | 'time left 616 257 449 | -0089KA 450 | T15 451 | 'time left 616 242 452 | +0078GI 453 | T33 454 | 'time left 583 242 455 | -5365KE 456 | T15 457 | 'time left 583 227 458 | +1716FU 459 | T9 460 | 'time left 574 227 461 | -1525OU 462 | T1 463 | 'time left 574 226 464 | +0026KY 465 | T27 466 | 'time left 547 226 467 | -4426KA 468 | T1 469 | 'time left 547 225 470 | +2324TO 471 | T20 472 | 'time left 527 225 473 | -2524OU 474 | T1 475 | 'time left 527 224 476 | +7222RY 477 | T20 478 | 'time left 507 224 479 | -0023FU 480 | T4 481 | 'time left 507 220 482 | +2233RY 483 | T14 484 | 'time left 493 220 485 | -2413OU 486 | T3 487 | 'time left 493 217 488 | +2726GI 489 | T15 490 | 'time left 478 217 491 | -6557NK 492 | T1 493 | 'time left 478 216 494 | +5857KI 495 | T60 496 | 'time left 418 216 497 | -0075KE 498 | T7 499 | 'time left 418 209 500 | +6777OU 501 | T9 502 | 'time left 409 209 503 | -7587NK 504 | T4 505 | 'time left 409 205 506 | +7787OU 507 | T19 508 | 'time left 390 205 509 | -0086FU 510 | T1 511 | 'time left 390 204 512 | +8786OU 513 | T6 514 | 'time left 384 204 515 | -0088HI 516 | T1 517 | 'time left 384 203 518 | +0087FU 519 | T1 520 | 'time left 383 203 521 | -0094KE 522 | T4 523 | 'time left 383 199 524 | +8695OU 525 | T1 526 | 'time left 382 199 527 | -0084GI 528 | T2 529 | 'time left 382 197 530 | +7384NG 531 | T1 532 | 'time left 381 197 533 | %TORYO 534 | '+ win 535 | 'Tue May 3 14:34:13 2005 536 | 'game end 537 | -------------------------------------------------------------------------------- /test/files/csa/2005_YAMADA_GPS.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/2005_YAMADA_GPS.csa -------------------------------------------------------------------------------- /test/files/csa/8mai.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/8mai.csa -------------------------------------------------------------------------------- /test/files/csa/8mai_hirate.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/8mai_hirate.csa -------------------------------------------------------------------------------- /test/files/csa/9fu.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/9fu.csa -------------------------------------------------------------------------------- /test/files/csa/9fu_komabetsu.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/9fu_komabetsu.csa -------------------------------------------------------------------------------- /test/files/csa/chudan.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/chudan.csa -------------------------------------------------------------------------------- /test/files/csa/dr1test1test0+tu-1_aobazero_viper-300-5F+aobazero+viper+20200719201040.csa: -------------------------------------------------------------------------------- 1 | V2 2 | N+aobazero 3 | N-viper 4 | 'Max_Moves:512 5 | 'Least_Time_Per_Move:0 6 | 'Increment:5 7 | $EVENT:dr1test1test0+tu-1_aobazero_viper-300-5F+aobazero+viper+20200719201040 8 | $START_TIME:2020/07/19 20:10:37 9 | P1-KY-KE-GI-KI-OU-KI-GI-KE-KY 10 | P2 * -HI * * * * * -KA * 11 | P3-FU-FU-FU-FU-FU-FU-FU-FU-FU 12 | P4 * * * * * * * * * 13 | P5 * * * * * * * * * 14 | P6 * * * * * * * * * 15 | P7+FU+FU+FU+FU+FU+FU+FU+FU+FU 16 | P8 * +KA * * * * * +HI * 17 | P9+KY+KE+GI+KI+OU+KI+GI+KE+KY 18 | + 19 | +7776FU 20 | T10 21 | '** 130 7g7f 8c8d 7i6h 3c3d 22 | -4132KI 23 | T0 24 | '** 50 +2726FU -8384FU +7968GI -3334FU +6877GI -3142GI +2625FU 25 | +2726FU 26 | T10 27 | '** 106 2g2f 8c8d 7i6h 3c3d 28 | -8384FU 29 | T0 30 | '** 50 +7978GI -3334FU +7877GI -3142GI +2625FU -4233GI +5756FU 31 | +7968GI 32 | T10 33 | '** 104 7i6h 3c3d 6h7g 7c7d 34 | -3334FU 35 | T0 36 | '** 46 +6877GI -3142GI +4958KI -8485FU +6978KI -5354FU +2625FU 37 | +6877GI 38 | T9 39 | '** 98 6h7g 7c7d 2f2e 7a6b 40 | -3142GI 41 | T0 42 | '** 46 +4958KI -8485FU +6978KI -5354FU +2625FU -4233GI +3948GI 43 | +2625FU 44 | T10 45 | '** 144 2f2e 4b3c 3i4h 8d8e 46 | -4233GI 47 | T0 48 | '** 46 +5756FU -8485FU +3948GI -5354FU +3736FU -7374FU +6978KI 49 | +3948GI 50 | T20 51 | '** 144 3i4h 8d8e 6i7h 7a6b 52 | -1314FU 53 | T0 54 | '** 44 +8879KA -5141OU +5756FU -7162GI +3736FU -5354FU +6978KI 55 | +3736FU 56 | T20 57 | '** 161 3g3f 8d8e 6i7h 7a6b 58 | -7162GI 59 | T0 60 | '** 44 +6978KI -8485FU +5969OU -5141OU +4958KI -7374FU +5756FU 61 | +6978KI 62 | T20 63 | '** 163 6i7h 8d8e 5g5f 7c7d 64 | -8485FU 65 | T0 66 | '** 44 +5969OU -5141OU +4958KI -7374FU +5756FU -5354FU +8879KA 67 | +5756FU 68 | T20 69 | '** 161 5g5f 7c7d 5i6i 5a4a 70 | -2231KA 71 | T0 72 | '** 16 +4958KI -5354FU 73 | +5969OU 74 | T20 75 | '** 177 5i6i 7c7d 8h7i 5c5d 76 | -6152KI 77 | T0 78 | '** 6 +4837GI 79 | +8879KA 80 | T20 81 | '** 196 8h7i 5c5d 6g6f 7c7d 82 | -5354FU 83 | T5 84 | '** 20 +7946KA -3164KA +4958KI -7374FU +9796FU -4344FU +6979OU -5141OU +9695FU -8173KE +6766FU -4131OU +7988OU -3122OU +1716FU -6446KA +4746FU -6364FU +2937KE -6465FU +4645FU -6566FU +4544FU -3344GI +2524FU -2324FU +0067FU -8586FU +7786GI -0069KA 85 | +6766FU 86 | T15 87 | '** 193 6g6f 7c7d 4i5h 5a4a 88 | -3164KA 89 | T16 90 | '** 54 +7946KA -5141OU +4958KI -4344FU +6979OU -9394FU +9796FU -4131OU +1716FU -6253GI +4837GI -7374FU +3726GI -8173KE +3635FU -3435FU +2635GI -0034FU +2524FU -2324FU +3524GI -3324GI +2824HI -6446KA +4746FU -0023FU +2428HI 91 | +2937KE 92 | T20 93 | '** 206 2i3g 5a4a 4i5h 7c7d 94 | -5141OU 95 | T9 96 | '** 43 +7946KA 97 | +4958KI 98 | T10 99 | '** 174 4i5h 7c7d 7h6g 9c9d 100 | -7374FU 101 | T0 102 | '** 88 +7867KI -4131OU +6978OU -4344FU +4746FU -5243KI +1716FU 103 | +7867KI 104 | T20 105 | '** 165 7h6g 9c9d 9g9f 4c4d 106 | -4131OU 107 | T0 108 | '** 88 +6978OU -4344FU +4746FU -9394FU +9796FU -5243KI +1716FU 109 | +6978OU 110 | T20 111 | '** 174 6i7h 9c9d 9g9f 6b7c 112 | -4344FU 113 | T0 114 | '** 88 +4746FU -9394FU +9796FU -5243KI +1716FU -6473KA +4847GI 115 | +4746FU 116 | T20 117 | '** 184 4g4f 5b4c 4h4g 6b7c 118 | -9394FU 119 | T0 120 | '** 88 +9796FU -5243KI +1716FU -6473KA +4847GI 121 | +9796FU 122 | T20 123 | '** 222 9g9f 8a7c 4h4g 3b4c 124 | -5243KI 125 | T0 126 | '** 88 +1716FU -6473KA +4847GI 127 | +4847GI 128 | T20 129 | '** 291 4h4g 6b7c 2h2i 7c8d 130 | -1415FU 131 | T0 132 | '** 53 +1918KY -3122OU 133 | +2829HI 134 | T20 135 | '** 282 2h2i 6b7c 6f6e 6d4b 136 | -3122OU 137 | T0 138 | '** -11 +1918KY 139 | +1918KY 140 | T20 141 | '** 425 1i1h 8a7c 2i1i 8b8a 142 | -6273GI 143 | T3 144 | '** 56 145 | +1716FU 146 | T20 147 | '** 430 1g1f 1e1f 2i1i 7c8d 148 | -1516FU 149 | T3 150 | '** 137 151 | +2919HI 152 | T11 153 | '** 418 2i1i 7c8d 1h1f P*1d 154 | -7475FU 155 | T11 156 | '** 60 +7675FU -7384GI +1816KY -1116KY +1916HI -8475GI +6665FU -6442KA +7957KA -0014FU +0076FU -8586FU +8786FU -7586GI +7786GI -8286HI +0087KY -0069GI +7888OU -0084KY +8786KY -4286KA +0087FU -6958GI +4758GI -8659UM +5784KA -5958UM 157 | +7675FU 158 | T10 159 | '** 503 7f7e 7c8d 6f6e 6d4b 160 | -7384GI 161 | T3 162 | '** 60 +6665FU -6442KA +1816KY -0014FU +4645FU -4445FU +3745KE -3344GI +2524FU -2324FU +0025FU -8173KE +0013FU -1113KY +7924KA -7365KE +7766GI -4445GI +2442UM -4342KI +0046FU -0037KA +1949HI -3726UM +4645FU -2616UM +4919HI 163 | +6665FU 164 | T6 165 | '** 521 6f6e 6d4b 4f4e 4d4e 166 | -6442KA 167 | T6 168 | '** 61 +4645FU -4445FU +1816KY -1116KY +1916HI -0015FU +1615HI -0013FU +1518HI -8173KE +3745KE -7365KE +0016KY -8475GI +1613NY -2231OU +0076FU -8586FU +7786GI -7586GI +8786FU -4286KA +0083FU -8283HI +0084FU -8384HI 169 | +4645FU 170 | T3 171 | '** 506 4f4e 4d4e 1h1f P*1d 172 | -4445FU 173 | T3 174 | '** 74 +1816KY -0014FU +3745KE -3344GI +2524FU -2324FU +0025FU -8173KE +7924KA -7365KE +2442UM -3242KI +0012FU -6577NK +6777KI -1112KY +2524FU -4445GI +1614KY -1214KY +1914HI -0013KY +0011KA -2231OU +0044FU -1314KY +4443TO -4243KI +2423TO -0065KE 175 | +1816KY 176 | T6 177 | '** 480 1h1f P*1d 3g4e 3c4d 178 | -0014FU 179 | T9 180 | '** 196 +3745KE 181 | +3745KE 182 | T0 183 | '** 460 3g4e 3c4d 1f1d 1a1d 184 | -3344GI 185 | T5 186 | '** 192 +1614KY 187 | +1614KY 188 | T4 189 | '** 444 1f1d 1a1d 1i1d L*1a 190 | -0012FU 191 | T19 192 | '** 214 +2524FU 193 | +2524FU 194 | T10 195 | '** 697 2e2d 2c2d P*2e 2d2e 196 | -4224KA 197 | T3 198 | '** 173 199 | +7924KA 200 | T10 201 | '** 608 7i2d 2c2d 1i2i B*3g 202 | -2324FU 203 | T3 204 | '** 264 205 | +1929HI 206 | T6 207 | '** 591 1i2i B*3g B*5a 1b1c 208 | -0037KA 209 | T3 210 | '** 261 +0042FU 211 | +0051KA 212 | T0 213 | '** 575 B*5a 1b1c 5a2d+ P*2c 214 | -3242KI 215 | T14 216 | '** 72 +3635FU -4445GI +0046FU -4546GI +0044FU -4344KI +4746GI -3746UM +0083GI -8252HI +5184UM -4636UM +5868KI -3614UM +6564FU -5253HI +8462UM -0065KY +0025FU -2425FU +0036GI -5343HI +3625GI -6567NY +7867OU -1425UM +2925HI -0024FU +2524HI -0023FU 217 | +0016FU 218 | T10 219 | '** 540 P*1f 1b1c 6g5g 4d4e 220 | -1213FU 221 | T5 222 | '** 211 +3635FU 223 | +6757KI 224 | T0 225 | '** 550 6g5g 4d4e P*4f 8d7e 226 | -4445GI 227 | T20 228 | '** 237 +0046FU -8475GI +0076FU -8586FU +8786FU -7586GI +7786GI -8286HI +0077GI -8682HI +4645FU -0028GI +4544FU -4344KI +2949HI -3726UM +0071GI -8252HI +7162NG -5251HI +6251NG -0045KE +5746KI -4232KI +0082HI -2223OU +0025FU -2616UM +4979HI -1625UM +8281RY -2314OU 229 | +0046FU 230 | T0 231 | '** 512 P*4f 8d7e P*7f 8e8f 232 | -8475GI 233 | T5 234 | '** 206 +0076FU -8586FU +8786FU -7586GI +7786GI -8286HI +0087FU -8676HI +0077FU -0066KE +5766KI -7666HI +4645FU -0086FU +0067GI -8687TO +7887OU -6667RY +5867KI -1314FU +0072HI -0086FU +8786OU -0085FU +8685OU -0041FU +4544FU -0061KI +4443TO -6172KI +4342TO 235 | +0076FU 236 | T4 237 | '** 484 P*7f 8e8f 8g8f 7e8f 238 | -8586FU 239 | T3 240 | '** 240 +8786FU 241 | +8786FU 242 | T6 243 | '** 470 8g8f 7e8f 7g8f 8b8f 244 | -7586GI 245 | T3 246 | '** 305 +7786GI 247 | +7786GI 248 | T6 249 | '** 464 7g8f 8b8f S*7g 8f8b 250 | -8286HI 251 | T3 252 | '** 355 +0077GI -8682HI +4645FU -0028GI +4544FU -2829NG +4443TO -4243KI +0044FU -4344KI +5124UM -0023FU +2451UM -0086KE +7867OU -0069HI +5868KI -6989RY +0083FU -8272HI +0024FU -0085KE +2423TO -2223OU +7786GI -8986RY +0015KE -2332OU +0041GI -3243OU +5161UM -4333OU +0023KI -3342OU +6172UM 253 | +0077GI 254 | T6 255 | '** 484 S*7g 8f8b 4f4e S*2h 256 | -8682HI 257 | T3 258 | '** 245 +4645FU 259 | +4645FU 260 | T6 261 | '** 481 4f4e S*2h 2i4i P*8f 262 | -0028GI 263 | T25 264 | '** 392 +2949HI -3726UM +0083FU -8272HI +5184UM -0088FU +0082GI -2133KE +4746GI -8889TO +4989HI -1314FU +8291GI -7252HI +8382TO -2837GI +8281TO -3746NG +5746KI -0083FU +8466UM -0074KE +6675UM -0084KY +0085FU -8485KY +8985HI 265 | +2949HI 266 | T0 267 | '** 465 2i4i P*8f P*8c 8b5b 268 | -0086FU 269 | T17 270 | '** 296 +0083FU 271 | +0083FU 272 | T0 273 | '** 463 P*8c 8b5b 5a8d+ 1c1d 274 | -8272HI 275 | T3 276 | '** 264 +4544FU 277 | +4544FU 278 | T15 279 | '** 564 4e4d 4c3c 3f3e 3g2f+ 280 | -4333KI 281 | T3 282 | '** 362 +3635FU 283 | +3635FU 284 | T6 285 | '** 538 3f3e 1c1d 4g4f 3g2f+ 286 | -1314FU 287 | T3 288 | '** 337 289 | +7786GI 290 | T6 291 | '** 523 7g8f 3g2f+ 4g4f 2f1f 292 | -3726UM 293 | T7 294 | '** 220 +4746GI -0041KY +5184UM -3435FU +0034FU -3332KI +8451UM -2616UM +4947HI -1638UM +4443TO -3243KI +8677GI -0086FU +5161UM -7273HI +4635GI -3847UM +5747KI 295 | +4746GI 296 | T2 297 | '** 487 4g4f 2f1f 4i6i 3d3e 298 | -0041KY 299 | T4 300 | '** 202 +5184UM 301 | +5184UM 302 | T10 303 | '** 658 5a8d+ 3d3e 8d5a 2f1f 304 | -3435FU 305 | T3 306 | '** 257 307 | +8451UM 308 | T6 309 | '** 701 8d5a 2f1f 4i6i 1f2e 310 | -2616UM 311 | T3 312 | '** 288 +4969HI 313 | +4969HI 314 | T6 315 | '** 741 4i6i 1f2e 6e6d 7b7f 316 | -5455FU 317 | T11 318 | '** 243 +4655GI -1625UM +4443TO -3343KI +8677GI -0045KE +0026FU -4557KE +2625FU -5769NK +2524FU -0038HI +7867OU -0071FU +0061KA -0057FU +6172UM -7172FU +0023HI -2232OU +0022GI -3858RY +6766OU -0067KI +6675OU 319 | +4655GI 320 | T10 321 | '** 605 4f5e 1f3d 6e6d N*4e 322 | -1625UM 323 | T3 324 | '** 213 +4443TO 325 | +6968HI 326 | T4 327 | '** 651 6i6h 2h3g P*3h P*5d 328 | -2837NG 329 | T28 330 | '** 62 +5867KI -8193KE +0045GI -7252HI +0026FU -5251HI +2625FU -0088FU +7888OU -0059KA +0034FU -3332KI +5758KI -5968UM +5868KI -0087FU +8887OU -0049HI +0073KA -4989RY +0088KA -0085FU 331 | +6564FU 332 | T4 333 | '** 447 6e6d N*4e P*2f 4e5g+ 334 | -0047FU 335 | T27 336 | '** 45 +5867KI -7271HI +5162UM -7161HI +0026FU -2534UM +6272UM -6151HI +0043GI -4243KI +4443TO -3443UM +7262UM -5155HI +5655FU -0085FU +8677GI -0059GI +0082HI -8586FU +7786GI -5968NG +6768KI -0059GI +6251UM -0052FU +5141UM -5968NG +7868OU 337 | +6463TO 338 | T4 339 | '** 698 6d6c+ 7b7f S*7g 7f7a 340 | -7271HI 341 | T3 342 | '** -43 343 | +5162UM 344 | T4 345 | '** 523 5a6b 7a7f 8f7g 7f7g+ 346 | -7176HI 347 | T7 348 | '** -64 +8677GI 349 | +8677GI 350 | T0 351 | '** 479 8f7g 7f7g+ 7h7g 4g4h+ 352 | -7677RY 353 | T13 354 | '** 130 +7877OU -4748TO +5848KI -3748NG +6284UM -4847NG +0034FU -3332KI +5767KI -2534UM +0071HI -0058GI +0065GI -0054FU +7181RY -0092KE +0026KE -3445UM +8466UM -5455FU +8191RY -4563UM +9192RY -2213OU +0016KY -3223KI +4443TO -4243KI +6776KI -0085GI +8382TO 355 | +7877OU 356 | T0 357 | '** 534 7h7g 4g4h+ 5h4h 3g4h 358 | -4748TO 359 | T11 360 | '** 10 +5848KI -3748NG +0071HI -0045KE +5767KI -4858NG +6888HI -0087FU +7787OU -4557NK +6776KI -5756NK +5564GI -0084FU +8382TO -0067GI +8786OU -0061FU +6251UM -6776NG +8676OU 361 | +5867KI 362 | T0 363 | '** 581 5h6g N*6e 7g8f 6e5g+ 364 | -0065KE 365 | T5 366 | '** -92 +7766OU 367 | +7786OU 368 | T5 369 | '** 511 7g8f 6e5g+ 6g5g S*7d 370 | -6557NK 371 | T5 372 | '** -284 +6757KI -0074KI +0034FU -0085FU +8697OU -2534UM +0026KE -7475KI +2634KE -3334KI +0054KA -7586KI +9798OU -0075KE +0076GI -8676KI +5476KA -0086GI +0088KI -3747NG +0023FU -2223OU +7621UM -4757NG +0022HI -2313OU 373 | +6757KI 374 | T4 375 | '** 443 6g5g S*7d P*3d 2e3d 376 | -0074GI 377 | T37 378 | '** -48 +0034FU -0085FU +8677OU -0086KI +7788OU -2534UM +0026KE -3423UM +0034FU -3332KI +6353TO -3747NG +5747KI -4847TO +5342TO -4142KY +0041HI -0087KI +8879OU -0077FU +4443TO -4243KY +3433TO -2133KE +0034KI -7778TO +6878HI -8778KI +7978OU -0028HI +0048FU -2826RY +3423KI -2223OU +4121RY -2334OU +2132RY 379 | +0034FU 380 | T0 381 | '** 463 P*3d 2e3d N*2f P*8e 382 | -2534UM 383 | T6 384 | '** -138 +0026KE 385 | +0026KE 386 | T9 387 | '** 422 N*2f P*8e 8f7g P*7f 388 | -0085FU 389 | T12 390 | '** 75 +8677OU -0076FU +7788OU -3423UM +0034FU -3332KI +6352TO -7677TO +8877OU -3747NG +5747KI -4847TO +5242TO -4142KY +0041HI -0086KI +7788OU -0077FU +4443TO -4243KY +3433TO -2333UM +0034GI -0087KI +8879OU -8778KI +6878HI -7778TO +7978OU -3334UM +2634KE -2213OU +6235UM 391 | +8677OU 392 | T2 393 | '** 440 8f7g P*7f 7g8h 3d2c 394 | -0086KI 395 | T33 396 | '** 71 +7788OU -3423UM +6352TO -3747NG +5747KI -4847TO +5242TO -4142KY +0071HI -2356UM +0031GI -2212OU +7151RY -0067FU +6867HI -0032KI +0034FU -3323KI +3142NG -5667UM +5121RY -1213OU +2111RY -0012HI +1112RY -1312OU +0013FU -1213OU +0011HI -0012HI 397 | +7788OU 398 | T15 399 | '** 556 7g8h 3d2c 6c5c 3g4g 400 | -3423UM 401 | T4 402 | '** -118 403 | +6353TO 404 | T6 405 | '** 490 6c5c 3g4g 5g4g 4h4g 406 | -3747NG 407 | T5 408 | '** -107 409 | +5747KI 410 | T4 411 | '** 497 5g4g 4h4g 5c4b 4a4b 412 | -4847TO 413 | T7 414 | '** 90 +5342TO -4142KY +0071HI -2356UM +0031GI -2212OU +0013FU -1223OU +3142GI -5655UM +0077FU -0087GI +8879OU -0078KI +6878HI -8778NG +7978OU -0028HI +0068KY -2826RY +6252UM -0043FU +4443TO -2334OU +7174RY -3425OU +4333TO -2536OU +5263UM -3637OU +7475RY -0066KE +6866KY -5566UM +7566RY -2666RY +0059KA -0048HI +5948KA -3748OU 415 | +5342TO 416 | T2 417 | '** 398 5c4b 4a4b R*5b P*4a 418 | -4142KY 419 | T3 420 | '** -7 +0071HI 421 | +0052HI 422 | T6 423 | '** 369 R*5b P*4a P*3d P*6g 424 | -0041FU 425 | T34 426 | '** 186 +0034FU 427 | +0034FU 428 | T0 429 | '** 346 P*3d P*6g 6h1h G*8g 430 | -3332KI 431 | T4 432 | '** 137 +4443TO -3243KI +6244UM -4344KI +5544GI -0032KI +0043FU -0076KA +0098GI -3243KI +4435GI -0067FU +6818HI -2231OU +0044FU -4344KI +3544GI -4244KY +0022KI -2322UM +5222RY -3122OU +0033KI -2133KE +3433TO -2233OU 433 | +4443TO 434 | T15 435 | '** 657 4d4c+ 3b4c 5e4d P*6g 436 | -4243KY 437 | T3 438 | '** 463 +6251UM 439 | +0044FU 440 | T10 441 | '** 1016 P*4d 2b1c P*1e P*7g 442 | -0078FU 443 | T28 444 | '** 1073 +6867HI -7463GI +6263UM -0087KI +6787HI -8687KI +8887OU -0067HI +0077KI -6763RY +5282RY -0073KA +0054GI -7382KA +8382TO -6373RY +4443TO -3243KI +5443GI -7343RY +0076KI -0086GI +7786KI -8586FU +7686KI -0085FU +8685KI -0084FU +3433TO -4333RY 445 | +6867HI 446 | T10 447 | '** 1494 6h6g 2b1c 4d4c+ 7h7i+ 448 | -7463GI 449 | T6 450 | '** 1277 +6263UM -0087KI +6787HI -8687KI +8887OU -0067HI +0077KI -6763RY +5282RY -7879TO +0064KI -6364RY +5564GI -7989TO +4443TO -0065KA +0076GI -6543KA +0044GI -0088KI +8797OU -4376KA +7776KI -0078GI +0077KI -0074KE +0055KA -2213OU +0028HI -0066FU +7778KI -8878KI +2878HI -0086KI +7686KI -8586FU 451 | +6263UM 452 | T4 453 | '** 2271 6b6c 7h7i+ 8h7i P*7g 454 | -0087KI 455 | T3 456 | '** 1286 +6787HI 457 | +6787HI 458 | T4 459 | '** 2058 6g8g 8f8g 8h8g R*6g 460 | -8687KI 461 | T4 462 | '** 1341 +8887OU -0067HI +0077KI -6763RY +5282RY -2213OU +4443TO -6343RY +0015FU -2425FU +0044GI -4373RY +4435GI -0079KA +3433TO -7935UM +3323TO -3223KI +1514FU -2314KI +0022GI -1324OU +0051KA -0033FU +5173UM -8173KE +8273RY 463 | +8887OU 464 | T0 465 | '** 1984 8h8g R*6g G*7g 6g6c+ 466 | -0067HI 467 | T6 468 | '** 1278 +0077KI 469 | +0077KI 470 | T4 471 | '** 1959 G*7g 6g6c+ 5b8b+ B*7c 472 | -6763RY 473 | T3 474 | '** 1295 +5282RY 475 | +5282RY 476 | T6 477 | '** 2028 5b8b+ B*7c P*6d 7c8b 478 | -0073KA 479 | T28 480 | '** 1338 +0054GI -7382KA +8382TO -6374RY +4443TO -0086HI +8778OU -3243KI +0076KY -8676HI +5443NG -2213OU +0044KA -7444RY +4344NG -7656HI +0022GI -2322UM +0053HI -0023GI +3433TO -2133KE +4433NG -5658RY +0068KE 481 | +0064FU 482 | T0 483 | '** 1912 P*6d 7c8b 6d6c+ 8b5e 484 | -7382KA 485 | T6 486 | '** 1343 +6463TO 487 | +6463TO 488 | T4 489 | '** 1929 6d6c+ 8b5e 5f5e R*6i 490 | -8255KA 491 | T3 492 | '** 1326 +5655FU 493 | +5655FU 494 | T6 495 | '** 1938 5f5e R*6i 4d4c+ S*8f 496 | -0046HI 497 | T30 498 | '** 1536 +0066GI 499 | +0066GI 500 | T10 501 | '** 2160 S*6f S*8f 8g7f 8f7g 502 | -0057GI 503 | T7 504 | '** 1563 +8776OU 505 | +8776OU 506 | T10 507 | '** 2374 8g7f 5g6f 7g6f S*5g 508 | -5766GI 509 | T3 510 | '** 1603 +7766KI 511 | +7766KI 512 | T0 513 | '** 2304 7g6f S*5g G*5f 5g6f+ 514 | -4344KY 515 | T6 516 | '** 1595 +0072HI -0057GI +0077GI -4626HI +0053KA -2646HI +7675OU -5766NG +7766GI -0052FU +5364UM -2334UM +0065KI -3456UM +0076KI -0053KE +7252RY -5365KE +0043GI -0084KI +7584OU 517 | +0072HI 518 | T10 519 | '** 2068 R*7b 4f2f S*4c S*6a 520 | -0057GI 521 | T10 522 | '** 1600 +0077GI -0052FU +7675OU -5766GI +7766GI -4626HI +0065KI -2334UM +0082GI -4757TO +8281NG -5767TO +6677GI -0053KE +8191NG -5365KE +7584OU -6777TO +8977KE -3456UM +8382TO -7879TO +0074KI 523 | +0056KI 524 | T4 525 | '** 1988 G*5f 4f2f S*4c 5g6f+ 526 | -5766NG 527 | T13 528 | '** 1652 +5666KI -0052FU +7252RY -4626HI +0053KA -2212OU +5364UM -4757TO +0077KI -5756TO +6675KI -5667TO +7665OU -6777TO +8977KE -2627RY +0066GI -3242KI +5272RY -0062FU +7262RY -0061FU +6272RY -7879TO +3433TO -2133KE +8382TO 529 | +5666KI 530 | T4 531 | '** 1834 5f6f 4f2f S*4c P*6b 532 | -0052FU 533 | T3 534 | '** 1647 +7252RY 535 | +0075GI 536 | T10 537 | '** 2456 S*7e 4f2f 7f8e 2c3d 538 | -4626HI 539 | T14 540 | '** 1646 +7252RY -0067KI +0053KA -2213OU +5272RY -6766KI +7566GI -7879TO +0065KI -0073FU +0074FU -0052FU +5364UM -0062FU +7262RY -4757TO +7473TO -5756TO +0075GI -5666TO +6566KI -0057GI 541 | +7685OU 542 | T0 543 | '** 2451 7f8e 2c3d 8e7d G*6a 544 | -0051KE 545 | T12 546 | '** 1537 +0064GI -5163KE +6463NG -7879TO +8382TO -2334UM +0065KI -0067KI +6667KI -3467UM +8574OU -0061KI +8281TO -0062FU +6362NG -6172KI +6272NG -6789UM 547 | +0064FU 548 | T10 549 | '** 2574 P*6d 2c3d S*8b G*6a 550 | -2334UM 551 | T17 552 | '** 1646 +0077KI 553 | +0082GI 554 | T0 555 | '** 2919 S*8b P*6e 6f6e G*6a 556 | -2425FU 557 | T27 558 | '** 1937 +8281NG 559 | +8281NG 560 | T10 561 | '** 3474 8b8a+ G*6a 7b7c+ P*6e 562 | -2223OU 563 | T10 564 | '** 1983 +6352TO -3536FU +5251TO -0033KI +0022FU -2113KE +2221TO -2324OU +2122TO -3243KI +2232TO -3323KI +8382TO -2628RY +6463TO -3637TO +8191NG -7879TO +3222TO -2333KI +8594OU -3489UM +2211TO -2526FU +9483OU -2627TO +7222RY -2435OU 565 | +8191NG 566 | T4 567 | '** 3142 8a9a 2c2d 6c5b 2f2i+ 568 | -3536FU 569 | T11 570 | '** 1998 +6352TO 571 | +6352TO 572 | T4 573 | '** 3202 6c5b 3f3g+ 5b5a 2f2i+ 574 | -3233KI 575 | T18 576 | '** 2037 +5251TO -3637TO +8594OU -2324OU +9493OU -2629RY +0063KA -0036FU +8977KE -4757TO +9392OU -2435OU +0022GI -2999RY +2211NG -5756TO +6665KI -9997RY +5554FU -9777RY +5453TO -4447NY +1121NG -7776RY +0054GI -3526OU +8382TO -7879TO +6341UM -2627OU +0039KE -2718OU +3947KE 577 | +5251TO 578 | T4 579 | '** 2659 5b5a 3f3g+ 5a4a 2c2d 580 | -3637TO 581 | T3 582 | '** 2057 +8594OU 583 | +5141TO 584 | T0 585 | '** 2551 5a4a 2c2d 7b2b+ 2d3e 586 | -2324OU 587 | T3 588 | '** 1814 589 | +7222RY 590 | T6 591 | '** 2609 7b2b+ 2d3e 2b2a 2f2i+ 592 | -2435OU 593 | T3 594 | '** 1763 +8382TO 595 | +2221RY 596 | T6 597 | '** 2461 2b2a 2f2i+ 4a4b 2i8i 598 | -3412UM 599 | T12 600 | '** 1585 +2131RY -0032KI +0013FU -1222UM +3122RY -3222KI +8594OU -0087HI +9493OU -8789RY +0081KA -0045KE +6463TO -2628RY +9695FU -8999RY +9594FU -2526FU +0061KA -2627TO +7584GI -1113KY 601 | +2131RY 602 | T10 603 | '** 2381 2a3a G*3b P*1c 1b2b 604 | -0032KI 605 | T3 606 | '** 1694 607 | +0013FU 608 | T0 609 | '** 2536 P*1c 1b2b 3a2b 3b2b 610 | -1222UM 611 | T3 612 | '** 1692 +3122RY -3222KI +8594OU -2629RY +0063KA -0036FU +8977KE -1113KY +8382TO -3526OU +0071KA -1415FU +9483OU -1516FU +0045KY -0043FU +5554FU -2999RY +8392OU -1617TO +5453TO -4445KY +5343TO -2627OU +4333TO -2233KI 613 | +3122RY 614 | T6 615 | '** 2227 3a2b 3b2b 8e9d 2f2i+ 616 | -3222KI 617 | T3 618 | '** 1704 +8594OU 619 | +8594OU 620 | T6 621 | '** 2228 8e9d 2f2i+ 8i7g 2i9i 622 | -2629RY 623 | T14 624 | '** 1702 625 | +0054KA 626 | T0 627 | '** 2336 B*5d P*3f 4a4b 3e2f 628 | -0036FU 629 | T4 630 | '** 1574 +8382TO -3526OU +8977KE -1415FU +0071KA -1113KY +0045KY -0043FU +9483OU -1516FU +5463UM -4746TO +4142TO -4645TO +6345UM -7879TO +9695FU -1617TO +6463TO 631 | +8977KE 632 | T6 633 | '** 1994 8i7g 634 | -2999RY 635 | T3 636 | '** 1562 +5463UM 637 | +5463UM 638 | T10 639 | '** 2114 5d6c+ 3e2f 5e5d L*7a 640 | -3526OU 641 | T8 642 | '** 1598 +8382TO 643 | +5554FU 644 | T0 645 | '** 2094 5e5d L*7a P*7c 7a7c 646 | -0097HI 647 | T6 648 | '** 1534 +7765KE -9796RY +6396UM -9996RY +0095KY -4738TO +0072HI -2223KI +5453TO -9698RY +0086KI -1113KY +9493OU -4447NY +9382OU -2616OU +6573NK -9899RY +6463TO -7879TO +9592NY 649 | +0086GI 650 | T10 651 | '** 2117 S*8f 9g9f+ P*9e 9f9h 652 | -9796RY 653 | T3 654 | '** 1490 655 | +0095FU 656 | T0 657 | '** 2120 P*9e 9f9h 5d5c+ 7h7i+ 658 | -9698RY 659 | T3 660 | '** 1431 +5453TO -9889RY +8382TO -1415FU +7785KE -1516FU +6354UM -0074KY +0077KA -9996RY +7574GI -1617TO +5343TO -1113KY +4333TO -2233KI +0022GI -3334KI +2213NG -4446KY +9483OU -8986RY 661 | +5453TO 662 | T6 663 | '** 1998 5d5c+ 2f2g 6c5d L*7a 664 | -2627OU 665 | T8 666 | '** 1452 667 | +6354UM 668 | T1 669 | '** 1907 6c5d 9h8i 5c4c L*7c 670 | -9889RY 671 | T3 672 | '** 1483 +5343TO 673 | +5343TO 674 | T6 675 | '** 1889 5c4c L*7c 4c3c 2b3c 676 | -0071KY 677 | T7 678 | '** 1541 +4344TO 679 | +4333TO 680 | T10 681 | '** 2003 4c3c 7a7e 8f7e 2b3c 682 | -7175KY 683 | T5 684 | '** 1633 +8675GI 685 | +8675GI 686 | T4 687 | '** 1937 8f7e 2b3c L*3e 3c2d 688 | -2233KI 689 | T4 690 | '** 1614 691 | +7785KE 692 | T6 693 | '** 1916 7g8e 1a1c B*2b S*2d 694 | -4446KY 695 | T6 696 | '** 1518 +5455UM 697 | +0022KA 698 | T10 699 | '** 2218 B*2b 3c2c 2b1a+ 2c1c 700 | -8959RY 701 | T5 702 | '** 1681 703 | +5476UM 704 | T4 705 | '** 2416 5d7f 3c2c 2b1a+ 2c1c 706 | -3323KI 707 | T3 708 | '** 1736 709 | +2211UM 710 | T0 711 | '** 2448 2b1a+ 2c1c 1a2b 1c2d 712 | -2313KI 713 | T3 714 | '** 1758 +7677UM 715 | +1122UM 716 | T6 717 | '** 2353 1a2b 1c2d 2b3c 2d1e 718 | -1324KI 719 | T3 720 | '** 1701 +7584GI 721 | +2233UM 722 | T6 723 | '** 2290 2b3c 2d1e S*2d 5i1i 724 | -2435KI 725 | T9 726 | '** 1730 +6463TO 727 | +3324UM 728 | T4 729 | '** 2428 3c2d 3e2f 2d1d 5i1i 730 | -3526KI 731 | T5 732 | '** 1647 +6463TO 733 | +2414UM 734 | T4 735 | '** 2532 2d1d 5i1i 1d3b P*9f 736 | -4757TO 737 | T7 738 | '** 1687 +1423UM 739 | +1423UM 740 | T10 741 | '** 2780 1d2c 4f4g+ 7f4c 7h7i+ 742 | -0067FU 743 | T5 744 | '** 1836 +8382TO -3728TO +6463TO -6768TO +8573NK -0067GI +7677UM -9989RY +9493OU -6756GI +6656KI -5756TO +7722UM -5957RY +9392OU -3637TO +0084GI -7879TO 745 | +7643UM 746 | T4 747 | '** 2745 7f4c 6g6h+ 8c8b+ 6h6g 748 | -4647NY 749 | T5 750 | '** 1717 +6463TO 751 | +6463TO 752 | T4 753 | '** 2955 6d6c+ 6g6h+ 8e7c+ 6h6g 754 | -0096FU 755 | T5 756 | '** 1736 757 | +8573NK 758 | T4 759 | '** 2980 8e7c+ 9f9g+ 9d9c 9g9f 760 | -9697TO 761 | T5 762 | '** 1943 +9493OU 763 | +9493OU 764 | T0 765 | '** 3180 9d9c 9g9f 9e9d 9f9e 766 | -9796TO 767 | T6 768 | '** 2047 +9382OU -9695TO +6667KI -5767TO +2367UM -9989RY +6712UM -0054GI +4342UM -5463GI +7363NK -9585TO +7564GI -8584TO +0073KI -5919RY +0023KI -8483TO +7383KI -1959RY +9192NG -7879TO 769 | +9594FU 770 | T4 771 | '** 3073 9e9d 9f9e G*8d S*8e 772 | -9695TO 773 | T3 774 | '** 1994 +9382OU 775 | +0084KI 776 | T6 777 | '** 2933 G*8d S*8e 9c8b 9e9d 778 | -0085GI 779 | T3 780 | '** 1763 +8485KI 781 | +0098KY 782 | T6 783 | '** 3160 L*9h P*9f 9h9f 9i9f 784 | -9586TO 785 | T8 786 | '** 2020 +9392OU -5969RY +9493TO -7879TO +0022GI -6768TO +7564GI -7978TO +8382TO -7877TO +6675KI -8576GI +9894KY -6878TO +8281TO -6989RY +4151TO -7687GI +2332UM 787 | +0087KY 788 | T10 789 | '** 3679 L*8g 8f8g 4c8g 5i8i 790 | -9989RY 791 | T5 792 | '** 1892 +8485KI 793 | +8786KY 794 | T4 795 | '** 3430 8g8f 8e8f 7e8f 8i8f 796 | -8586GI 797 | T3 798 | '** 1854 799 | +7586GI 800 | T0 801 | '** 3590 7e8f 8i8f S*7e 8f8i 802 | -8986RY 803 | T6 804 | '** 1897 +0076GI 805 | +6676KI 806 | T3 807 | '** 3838 6f7f 8f8i 9h9e 6g6h+ 808 | -8689RY 809 | T6 810 | '** 1841 +7685KI 811 | +9895KY 812 | T3 813 | '** 3643 9h9e 6g6h+ 7f8e S*9f 814 | -6768TO 815 | T6 816 | '** 1825 +8382TO 817 | +7685KI 818 | T4 819 | '** 3618 7f8e S*9f G*8f 9f8e 820 | -5767TO 821 | T4 822 | '** 1814 +0022GI 823 | +8574KI 824 | T10 825 | '** 3461 8e7d S*8f G*8e 8f9e 826 | -8986RY 827 | T5 828 | '** 1646 +0085KI 829 | +0085KI 830 | T10 831 | '** 3604 G*8e 8f4f 4c3b 4f3e 832 | -8646RY 833 | T3 834 | '** 1686 +4361UM 835 | +4332UM 836 | T0 837 | '** 3420 4c3b 4f3e 3b4b 3e4f 838 | -5955RY 839 | T7 840 | '** 1654 841 | +8382TO 842 | T10 843 | '** 3281 8c8b+ S*9f 7d7e 9f8e 844 | -3728TO 845 | T5 846 | '** 1635 +7362NK 847 | +7483KI 848 | T4 849 | '** 2823 7d8c S*9f S*7d 9f8e 850 | -0086GI 851 | T6 852 | '** 1597 +0065GI 853 | +8586KI 854 | T10 855 | '** 2761 8e8f 4f8f 9c9b 8f9e 856 | -4686RY 857 | T3 858 | '** 1937 +9392OU 859 | +9392OU 860 | T0 861 | '** 2516 9c9b 8f9e 9d9c+ 5e3e 862 | -8695RY 863 | T6 864 | '** 2051 +9493TO 865 | +9493TO 866 | T3 867 | '** 2461 9d9c+ 5e3e 3b4b 3f3g+ 868 | -5535RY 869 | T5 870 | '** 2354 +3222UM 871 | +7362NK 872 | T4 873 | '** 2766 7c6b 3f3g+ 8d7c 874 | -0012KI 875 | T5 876 | '** 2280 877 | +2333UM 878 | T10 879 | '** 2883 2c3c 9e4e 8d7c 880 | -0046KY 881 | T5 882 | '** 2573 883 | +8473KI 884 | T4 885 | '** 5000 8d7c 886 | -3533RY 887 | T4 888 | '** 5475 +3233UM 889 | +3233UM 890 | T10 891 | '** 3892 3b3c 9e3e 3c4b 1b2b 892 | -9535RY 893 | T6 894 | '** 5555 +3342UM 895 | +3342UM 896 | T0 897 | '** 4461 3c4b 1b2b P*5b 898 | -0061KY 899 | T6 900 | '** 5784 +6261NK -0024KA +4252UM -0081FU +8281TO -0051FU +6151NK -2451KA +5251UM -0082FU +8182TO -0061KE +5161UM -0081FU +0051HI -8182FU +0053KA -3533RY +0021GI -8283FU 901 | +6261NK 902 | T4 903 | '** 4097 6b6a 3e6e 6a6b 6e4e 904 | -0033KA 905 | T4 906 | '** 5842 +4252UM 907 | +4253UM 908 | T10 909 | '** 3918 4b5c 3c4d 5c4d 3e4d 910 | -3555RY 911 | T5 912 | '** 5793 +0072HI 913 | +5362UM 914 | T4 915 | '** 4363 5c6b 5e8e P*7b 916 | -3344KA 917 | T5 918 | '** 5835 +0053KE -5553RY +6353TO -0081FU +8281TO -0071KE +0031HI -4453KA +6253UM -0082FU +3136RY -2636KI +0017HI -2738OU +8171TO 919 | +0053GI 920 | T4 921 | '** 5000 S*5c 922 | -4453KA 923 | T4 924 | '** 5781 925 | +6353TO 926 | T10 927 | '** 4229 6c5c 5e6d 5c6c 6d1d 928 | -0081FU 929 | T3 930 | '** 5821 +0051HI 931 | +8281TO 932 | T4 933 | '** 4239 8b8a P*8b 8a8b 1b1a 934 | -0064GI 935 | T8 936 | '** 5843 +0042GI 937 | +5363TO 938 | T4 939 | '** 3898 5c6c 6d7c 6c7c 5e8e 940 | -6473GI 941 | T4 942 | '** 5803 +8373KI 943 | +6373TO 944 | T0 945 | '** 3974 6c7c 5e8e P*7b 8e5e 946 | -0084KI 947 | T7 948 | '** 5747 +8384KI -5564RY +0063KY -6484RY +0083KI -0094KI +0042HI 949 | +0072FU 950 | T4 951 | '** 4316 P*7b 8d8c 7c8c 5e8e 952 | -8483KI 953 | T4 954 | '** 5756 +7383TO -0052KI +6271UM -5251KI +6151NK -5551RY +4151TO -0082FU +0032HI -1222KI +3231RY -8283FU +3122RY -2738OU +0043KA -3637TO 955 | +7383TO 956 | T5 957 | '** 4257 7c8c 5e8e P*5b 8e5e 958 | -0084KI 959 | T5 960 | '** 5808 +0042HI 961 | +8384TO 962 | T10 963 | '** 3735 8c8d 5e8e 8d8e P*1d 964 | -5545RY 965 | T5 966 | '** 5844 +0031HI 967 | +8485TO 968 | T4 969 | '** 3596 8d8e 4e8e G*8d 8e8d 970 | -4541RY 971 | T5 972 | '** 5743 +0052KI 973 | +8594TO 974 | T4 975 | '** 3454 8e9d 4a4e 9d8c 4e4a 976 | -0088FU 977 | T5 978 | '** 5733 +0052KI 979 | +9483TO 980 | T4 981 | '** 3308 9d8c 8h8i+ 8c8d 4a4e 982 | -2637KI 983 | T5 984 | '** 5705 +0052KI -4152RY +6252UM -2738OU +0042HI -0051FU +5253UM -0011FU +0032KI -3727KI +0041KA -3637TO 985 | +8384TO 986 | T4 987 | '** 3240 8c8d 4a4e 8d8e 4e8e 988 | -4111RY 989 | T5 990 | '** 5695 +0052KI -2738OU +0041GI -1141RY +5241KI -0032GI +4142KI -3233GI +7271TO -1222KI +0032HI -2232KI +0016KA -3727KI +4232KI -3637TO +3233KI 991 | +8485TO 992 | T4 993 | '** 3183 8d8e 1b2b 8e9d 1a1h 994 | -2526FU 995 | T5 996 | '** 5632 +0052KI -0051FU +0041HI -1141RY +5241KI -1222KI +0063KA -2738OU +7271TO -0065HI +0042KI -6563HI +6263UM 997 | +8594TO 998 | T10 999 | '** 3324 8e9d 1b2b 9d8c 1a1h 1000 | -7879TO 1001 | T5 1002 | '** 5705 +0071KA -1121RY +0042GI -1222KI +0041GI -2141RY +4241NG -2738OU +0082HI -0077GI +0052HI -2627TO 1003 | +9483TO 1004 | T4 1005 | '** 3049 9d8c 1b2b 8c8d 8h8i+ 1006 | -1122RY 1007 | T5 1008 | '** 5740 +0052KI 1009 | +0052FU 1010 | T4 1011 | '** 3900 P*5b 8h8i+ P*4b 2b4b 1012 | -0051FU 1013 | T5 1014 | '** 5883 +0043KI 1015 | +6151NK 1016 | T4 1017 | '** 3669 6a5a 8h8i+ P*4b 2b4b 1018 | -0071FU 1019 | T5 1020 | '** 6006 +0061KA 1021 | +0042FU 1022 | T4 1023 | '** 3842 P*4b 2b4b G*4a 4b4e 1024 | -0061FU 1025 | T5 1026 | '** 6636 +6253UM 1027 | +5161NK 1028 | T4 1029 | '** 4313 5a6a 8h8i+ P*3b 1030 | -2242RY 1031 | T5 1032 | '** 6002 +0051GI 1033 | +0051GI 1034 | T10 1035 | '** 4387 S*5a 4b4e P*4b 1036 | -4251RY 1037 | T3 1038 | '** 6344 +5251TO 1039 | +5251TO 1040 | T4 1041 | '** 3554 5b5a+ 8h8i+ 5a5b 8i8h 1042 | -0063GI 1043 | T5 1044 | '** 5961 +0049KA 1045 | +5152TO 1046 | T4 1047 | '** 3439 5a5b 6c7b 5b4b 7b8c 1048 | -6757TO 1049 | T8 1050 | '** 5805 +0049KI 1051 | +0042FU 1052 | T4 1053 | '** 5000 P*4b 1b1a P*3b 1054 | -6352GI 1055 | T4 1056 | '** 6416 +6252UM 1057 | +0032FU 1058 | T4 1059 | '** 4292 P*3b 5b6a 6b6a N*2c 1060 | -5261GI 1061 | T5 1062 | '** 6030 +6261UM 1063 | +6261UM 1064 | T0 1065 | '** 4267 6b6a N*2c P*5b 8h8i+ 1066 | -7172FU 1067 | T6 1068 | '** 6293 +0071KA 1069 | +0052FU 1070 | T10 1071 | '** 3678 P*5b N*2c G*6b 6h5h 1072 | -0087KE 1073 | T4 1074 | '** 6108 +0082GI 1075 | +0062KI 1076 | T4 1077 | '** 3293 G*6b 7i7h 6b5a 7h7i 1078 | -1222KI 1079 | T0 1080 | '** 100000 +0011HI -2232KI +0049KA -3738KI +0017HI 1081 | +6271KI 1082 | T10 1083 | '** 4052 6b7a 2b2a 7a7b 2a3b 1084 | -2232KI 1085 | T0 1086 | '** 100000 +0012HI -2738OU +1232RY -0041FU +0062HI -4142FU +0031GI -2627TO 1087 | +7172KI 1088 | T4 1089 | '** 3251 7a7b 7i7h 7b6b 7h7i 1090 | -3242KI 1091 | T6 1092 | '** 100000 +0012HI -3748KI +0031GI -4252KI +0016KA -2737OU +1652UM -0051FU +0013HI -5152FU +0021GI -2627TO 1093 | +7262KI 1094 | T4 1095 | '** 3267 7b6b 4b5b 6b5b P*1g 1096 | -8889TO 1097 | T14 1098 | '** 5870 +0012HI 1099 | +0072FU 1100 | T4 1101 | '** 3033 P*7b 8i8h G*6c 8h9h 1102 | -0017FU 1103 | T5 1104 | '** 6003 +0021HI 1105 | +0063KI 1106 | T4 1107 | '** 3005 G*6c 2h1h 6c5c 4b5c 1108 | -4252KI 1109 | T3 1110 | '** 100000 +6252KI -0051FU +0032KI -5152FU +0051HI -0084KI +0012HI -8483KI +9383TO -0071FU +0043KA -7172FU +0031GI -1718TO 1111 | +6352KI 1112 | T4 1113 | '** 3111 6c5b 2h1h 5b4a 1h2h 1114 | -0051FU 1115 | T2 1116 | '** 100000 +0032KI -5152FU +0071KA -0094KI +0012HI -9493KI +8393TO -1718TO +0082GI -0013FU 1117 | +5251KI 1118 | T10 1119 | '** 3431 5b5a 2h1h 5a5b 1h2h 1120 | -0082FU 1121 | T0 1122 | '** 100000 +0021HI -0071FU +0032KI -7172FU +0063KA -8283FU +0013HI -1718TO 1123 | +5152KI 1124 | T4 1125 | '** 3437 5a5b 8b8c P*4b 8c8d 1126 | -0051FU 1127 | T3 1128 | '** 100000 +0013HI -5152FU +0071KA -0084KI +8384TO -1718TO +0032KI -0012FU +0021HI -1213FU +0031GI -2738OU 1129 | +5242KI 1130 | T4 1131 | '** 3229 5b4b 5a5b 4b5a 8b8c 1132 | -0041FU 1133 | T2 1134 | '** 100000 +4232KI -0071FU +0021HI -7969TO +0013HI -8283FU +0031GI -7172FU +0071KA -1718TO 1135 | +4251KI 1136 | T4 1137 | '** 3208 4b5a 4a4b 5a5b 4b4c 1138 | -0071FU 1139 | T1 1140 | '** 6056 1141 | +5152KI 1142 | T4 1143 | '** 3320 5a5b 4a4b 5b4b 6h5h 1144 | -0051FU 1145 | T5 1146 | '** 100000 +0012KI -5152FU +0021HI -7172FU +0071KA -8283FU +0051GI -0063KI +6263KI -0062FU +0032KI -6263FU +0082GI -0022KI 1147 | +5251KI 1148 | T4 1149 | '** 3361 5b5a 4a4b 5a5b 6h5h 1150 | -7172FU 1151 | T1 1152 | '** 100000 +0021HI -8283FU +0063KA -1718TO +0013HI -1829TO +6116UM -2718OU +1626UM -0015FU +1315RY -0017FU +1517RY 1153 | +5152KI 1154 | T10 1155 | '** 3434 5a5b 4a4b 5b4b P*4a 1156 | -0051FU 1157 | T9 1158 | '** 100000 +6251KI -6869TO +0032KI -8283FU +0013HI -5758TO +0021HI -1718TO +0082GI -4142FU 1159 | +5251KI 1160 | T4 1161 | '** 3144 5b5a 4a4b 5a5b 6h5h 1162 | -8283FU 1163 | T3 1164 | '** 100000 +0021HI -5756TO +0082GI -0057FU +0032KI -5758TO +0071KA -7273FU 1165 | +5152KI 1166 | T4 1167 | '** 3205 5a5b 4a4b 5b4b P*4a 1168 | -3748KI 1169 | T25 1170 | '** 6153 1171 | +5241KI 1172 | T4 1173 | '** 3142 5b4a 4h5i P*5b 5i4i 1174 | -2737OU 1175 | T6 1176 | '** 6351 +0032KI 1177 | +0052FU 1178 | T4 1179 | '** 3123 P*5b 2f2g+ 4a4b 4h5i 1180 | -0051FU 1181 | T5 1182 | '** 100000 1183 | +5251TO 1184 | T4 1185 | '** 3278 5b5a+ 6h5h 5a5b 4h5i 1186 | -6867TO 1187 | T0 1188 | '** 100000 +0032KI -2839TO +0013HI -8799NK +0021HI -9998NK 1189 | +5152TO 1190 | T10 1191 | '** 3475 5a5b 2f2g+ 5b4b 2g2f 1192 | -2627TO 1193 | T0 1194 | '** 6041 1195 | +5242TO 1196 | T3 1197 | '** 3265 5b4b 2g2f 4b5a 4h5i 1198 | -5758TO 1199 | T1 1200 | '** 100000 +0032KI -6778TO +0021HI -2829TO +0013HI -1718TO 1201 | +0052FU 1202 | T4 1203 | '** 4549 P*5b 4h3h P*3b 4g5g 1204 | -3726OU 1205 | T0 1206 | '** 100000 +0012KI -1718TO +0021HI -2635OU 1207 | +0032FU 1208 | T4 1209 | '** 4601 P*3b 2f3g P*2b 1210 | -2635OU 1211 | T0 1212 | '** 5944 +0023HI 1213 | +0022FU 1214 | T4 1215 | '** 5000 P*2b 1216 | -3637TO 1217 | T0 1218 | '** 100000 1219 | %KACHI,'* -5000 win 1220 | T10 1221 | %KACHI 1222 | 'P1+NG+TO * +UM * +KI * * * 1223 | 'P2+OU * -FU+KI+FU+TO+FU+FU * 1224 | 'P3+TO-FU * * * * * * * 1225 | 'P4 * * * * * * * * * 1226 | 'P5 * * * * * * -OU * * 1227 | 'P6 * * * * * -KY * * * 1228 | 'P7 * -KE * -TO * -NY-TO-TO-FU 1229 | 'P8 * * * * -TO-KI * -TO * 1230 | 'P9 * -TO-TO * * * * * * 1231 | 'P+00FU00FU00GI00GI00GI00HI00HI00KA00KE00KE00KE00KI00KY00KY 1232 | '+ 1233 | 'summary:kachi:aobazero win:viper lose 1234 | '$END_TIME:2020/07/19 20:56:29 1235 | -------------------------------------------------------------------------------- /test/files/csa/example.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/example.csa -------------------------------------------------------------------------------- /test/files/csa/formal.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/formal.csa -------------------------------------------------------------------------------- /test/files/csa/illegal_lose.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/illegal_lose.csa -------------------------------------------------------------------------------- /test/files/csa/illegal_win.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/illegal_win.csa -------------------------------------------------------------------------------- /test/files/csa/noeol.csa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/csa/noeol.csa -------------------------------------------------------------------------------- /test/files/jkf/same_move_minimal.jkf: -------------------------------------------------------------------------------- 1 | { 2 | "header": {}, 3 | "moves": [ 4 | {}, 5 | { 6 | "move": { 7 | "from": { 8 | "x": 7, 9 | "y": 7 10 | }, 11 | "to": { 12 | "x": 7, 13 | "y": 6 14 | }, 15 | "color": 0, 16 | "piece": "FU" 17 | } 18 | }, 19 | { 20 | "move": { 21 | "from": { 22 | "x": 3, 23 | "y": 3 24 | }, 25 | "to": { 26 | "x": 3, 27 | "y": 4 28 | }, 29 | "color": 1, 30 | "piece": "FU" 31 | } 32 | }, 33 | { 34 | "move": { 35 | "from": { 36 | "x": 8, 37 | "y": 8 38 | }, 39 | "to": { 40 | "x": 2, 41 | "y": 2 42 | }, 43 | "promote": true, 44 | "color": 0, 45 | "capture": "KA", 46 | "piece": "KA" 47 | } 48 | }, 49 | { 50 | "move": { 51 | "from": { 52 | "x": 3, 53 | "y": 1 54 | }, 55 | "to": { 56 | "x": 2, 57 | "y": 2 58 | }, 59 | "color": 1, 60 | "same": true, 61 | "capture": "UM", 62 | "piece": "GI" 63 | } 64 | }, 65 | { 66 | "move": { 67 | "from": { 68 | "x": 2, 69 | "y": 7 70 | }, 71 | "to": { 72 | "x": 2, 73 | "y": 6 74 | }, 75 | "color": 0, 76 | "piece": "FU" 77 | } 78 | } 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /test/files/ki2/20091203.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/20091203.ki2 -------------------------------------------------------------------------------- /test/files/ki2/7mai.ki2u: -------------------------------------------------------------------------------- 1 | 開始日時:2022/02/06 14:51:20 2 | 終了日時:2022/02/06 14:53:37 3 | 手合割:左七枚落ち 4 | 下手:shitate 5 | 上手:uwate 6 | 7 | △8二銀 ▲7六歩 8 | まで2手で中断 9 | -------------------------------------------------------------------------------- /test/files/ki2/8mai.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/8mai.ki2 -------------------------------------------------------------------------------- /test/files/ki2/8mai.ki2u: -------------------------------------------------------------------------------- 1 | 開始日時:2014/10/13 18:51:20 2 | 終了日時:2014/10/13 18:53:37 3 | 手合割:八枚落ち 4 | 下手:shitate 5 | 上手:uwate 6 | 7 | △7二金 ▲7六歩 △3二金 ▲2六歩 △6四歩 ▲2五歩 8 | △6五歩 ▲2四歩 △同 歩 ▲同 飛 △2三歩 ▲2八飛 9 | △6三金 ▲3八銀 △5四金 ▲2七銀 △4二玉 ▲2六銀 10 | △4四歩 ▲2五銀 △4三玉 ▲2四歩 △同 歩 ▲同 銀 11 | △3四玉 12 | まで25手で中断 13 | -------------------------------------------------------------------------------- /test/files/ki2/9fu.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/9fu.ki2 -------------------------------------------------------------------------------- /test/files/ki2/chudan.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/chudan.ki2 -------------------------------------------------------------------------------- /test/files/ki2/denou3-1.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/denou3-1.ki2 -------------------------------------------------------------------------------- /test/files/ki2/fork.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/fork.ki2 -------------------------------------------------------------------------------- /test/files/ki2/illegal.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/illegal.ki2 -------------------------------------------------------------------------------- /test/files/ki2/kobayashi_kinsho196702.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/kobayashi_kinsho196702.ki2 -------------------------------------------------------------------------------- /test/files/ki2/noeol.ki2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/ki2/noeol.ki2 -------------------------------------------------------------------------------- /test/files/kif/20081220kyoochi.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/20081220kyoochi.kif -------------------------------------------------------------------------------- /test/files/kif/8mai.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/8mai.kif -------------------------------------------------------------------------------- /test/files/kif/9fu.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/9fu.kif -------------------------------------------------------------------------------- /test/files/kif/chudan.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/chudan.kif -------------------------------------------------------------------------------- /test/files/kif/doh_branch.kifu: -------------------------------------------------------------------------------- 1 | #KIF version=2.0 encoding=UTF-8 2 | # ---- Kifu for Windows V7 V7.31 棋譜ファイル ---- 3 | 開始日時:2017/10/03 01:12:24 4 | 終了日時:2017/10/03 01:12:54 5 | 手合割:平手   6 | 先手: 7 | 後手: 8 | 手数----指手---------消費時間-- 9 | 1 7六歩(77) ( 0:00/00:00:00) 10 | 2 3四歩(33) ( 0:00/00:00:00) 11 | 3 2六歩(27) ( 0:00/00:00:00)+ 12 | 4 中断 ( 0:00/00:00:00) 13 | まで3手で中断 14 | 15 | 変化:3手 16 | 3 2二角成(88) ( 0:00/00:00:00) 17 | 4 同 銀(31) ( 0:00/00:00:00)+ 18 | 5 中断 ( 0:00/00:00:00) 19 | まで4手で中断 20 | 21 | 変化:4手 22 | 4 同 飛(82) ( 0:00/00:00:00) 23 | 5 中断 ( 0:00/00:00:00) 24 | まで4手で中断 25 | -------------------------------------------------------------------------------- /test/files/kif/fork-notime.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/fork-notime.kif -------------------------------------------------------------------------------- /test/files/kif/fork-test.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/fork-test.kif -------------------------------------------------------------------------------- /test/files/kif/fork.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/fork.kif -------------------------------------------------------------------------------- /test/files/kif/henka.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/henka.kif -------------------------------------------------------------------------------- /test/files/kif/illegal.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/illegal.kif -------------------------------------------------------------------------------- /test/files/kif/jt201409130101.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/jt201409130101.kif -------------------------------------------------------------------------------- /test/files/kif/kifu_for_iphone.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/kifu_for_iphone.kif -------------------------------------------------------------------------------- /test/files/kif/kiou201403160101.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/kiou201403160101.kif -------------------------------------------------------------------------------- /test/files/kif/last-fork.kifu: -------------------------------------------------------------------------------- 1 | #KIF version=2.0 encoding=UTF-8 2 | # ---- Kifu for Windows V7 V7.11 棋譜ファイル ---- 3 | 開始日時:2015/08/17 00:32:18 4 | 終了日時:2015/08/17 00:32:35 5 | 手合割:平手   6 | 先手: 7 | 後手: 8 | 手数----指手---------消費時間-- 9 | 1 2六歩(27) ( 0:01/00:00:01) 10 | 2 8四歩(83) ( 0:01/00:00:01) 11 | 3 2五歩(26) ( 0:01/00:00:02) 12 | 4 8五歩(84) ( 0:00/00:00:01) 13 | 5 7八金(69) ( 0:01/00:00:03) 14 | 6 3二金(41) ( 0:01/00:00:02) 15 | 7 2四歩(25) ( 0:01/00:00:04) 16 | 8 同 歩(23) ( 0:00/00:00:02) 17 | 9 同 飛(28) ( 0:01/00:00:05) 18 | 10 2三歩打 ( 0:05/00:00:07)+ 19 | 11 中断 ( 0:01/00:00:06) 20 | まで10手で中断 21 | 22 | 変化:10手 23 | 10 1四歩(13) ( 0:01/00:00:03) 24 | 11 中断 ( 0:01/00:00:06) 25 | まで10手で中断 26 | -------------------------------------------------------------------------------- /test/files/kif/last_comment.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/last_comment.kif -------------------------------------------------------------------------------- /test/files/kif/meijinsen_20180508_M7_10034.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/meijinsen_20180508_M7_10034.kif -------------------------------------------------------------------------------- /test/files/kif/no_henka.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/no_henka.kif -------------------------------------------------------------------------------- /test/files/kif/no_henka.kifu: -------------------------------------------------------------------------------- 1 | #KIF version=2.0 encoding=UTF-8 2 | # ---- Kifu for Windows V7 V7.00β 棋譜ファイル ---- 3 | 開始日時:2005/08/23 10:12 4 | 終了日時:2005/08/23(火) 22:50 5 | 棋戦:第64期順位戦C級2組2回戦 6 | 持ち時間:6時間 7 | 消費時間:120▲359△329 8 | 場所:東京将棋会館 9 | 手合割:平手   10 | 先手:先手56789012全3456789 11 | 後手:後手567890全123456789 12 | 手数----指手---------消費時間-- 13 | *Unicodeのコメント 14 | *森雞二九段 15 | 1 7六歩(77) ( 0:00/00:00:00) 16 | *初手のコメント 17 | *2 18 | *3 19 | *4 20 | *5 21 | *6 22 | *7 23 | *8 24 | *9 25 | *10 26 | *11 27 | *12 28 | 2 8四歩(83) ( 0:00/00:00:00) 29 | *2手目のコメント 30 | 3 2六歩(27) ( 0:00/00:00:00) 31 | 4 8五歩(84) ( 0:00/00:00:00) 32 | 5 7七角(88) ( 0:00/00:00:00) 33 | 6 3四歩(33) ( 0:00/00:00:00) 34 | 7 8八銀(79) ( 0:00/00:00:00) 35 | 8 3二金(41) ( 0:00/00:00:00) 36 | 9 7八金(69) ( 0:00/00:00:00) 37 | 10 7七角(22) ( 0:00/00:00:00) 38 | 11 同 銀(88) ( 0:00/00:00:00) 39 | &読み込み時表示 40 | 41 | -------------------------------------------------------------------------------- /test/files/kif/noeol.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/noeol.kif -------------------------------------------------------------------------------- /test/files/kif/oui201407080101.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/oui201407080101.kif -------------------------------------------------------------------------------- /test/files/kif/ouza201410070101.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/ouza201410070101.kif -------------------------------------------------------------------------------- /test/files/kif/ryuou201409020101.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/ryuou201409020101.kif -------------------------------------------------------------------------------- /test/files/kif/ryuou4.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/ryuou4.kif -------------------------------------------------------------------------------- /test/files/kif/shogidb2.kifu: -------------------------------------------------------------------------------- 1 | 開始日時:2016/12/24 9:00:00 2 | 終了日時:2016/12/24 20:43:00 3 | 棋戦:竜王戦 4 | 手合割:平手 5 | 先手:加藤一二三 6 | 後手:藤井聡太 7 | 手数----指手---------消費時間-- 8 | 1 7六歩(77) (00:00/00:00:00) 9 | 2 8四歩(83) (00:00/00:00:00) 10 | 3 6八銀(79) (00:00/00:00:00) 11 | 4 3四歩(33) (00:00/00:00:00) 12 | 5 7七銀(68) (00:00/00:00:00) 13 | 6 6二銀(71) (00:00/00:00:00) 14 | 7 2六歩(27) (00:00/00:00:00) 15 | 8 4二銀(31) (00:00/00:00:00) 16 | 9 4八銀(39) (00:00/00:00:00) 17 | 10 5四歩(53) (00:00/00:00:00) 18 | 11 7八金(69) (00:00/00:00:00) 19 | 12 3二金(41) (00:00/00:00:00) 20 | 13 5六歩(57) (00:00/00:00:00) 21 | 14 4一玉(51) (00:00/00:00:00) 22 | 15 6九玉(59) (00:00/00:00:00) 23 | 16 5二金(61) (00:00/00:00:00) 24 | 17 3六歩(37) (00:00/00:00:00) 25 | 18 3三銀(42) (00:00/00:00:00) 26 | 19 5八金(49) (00:00/00:00:00) 27 | 20 3一角(22) (00:00/00:00:00) 28 | 21 7九角(88) (00:00/00:00:00) 29 | 22 4四歩(43) (00:00/00:00:00) 30 | 23 6六歩(67) (00:00/00:00:00) 31 | 24 7四歩(73) (00:00/00:00:00) 32 | 25 6七金(58) (00:00/00:00:00) 33 | 26 6四角(31) (00:00/00:00:00) 34 | 27 3七銀(48) (00:00/00:00:00) 35 | 28 3一玉(41) (00:00/00:00:00) 36 | 29 6八角(79) (00:00/00:00:00) 37 | 30 4三金(52) (00:00/00:00:00) 38 | 31 7九玉(69) (00:00/00:00:00) 39 | 32 2二玉(31) (00:00/00:00:00) 40 | 33 8八玉(79) (00:00/00:00:00) 41 | 34 9四歩(93) (00:00/00:00:00) 42 | 35 1六歩(17) (00:00/00:00:00) 43 | 36 9五歩(94) (00:00/00:00:00) 44 | 37 1五歩(16) (00:00/00:00:00) 45 | 38 7三桂(81) (00:00/00:00:00) 46 | 39 6五歩(66) (00:00/00:00:00) 47 | 40 5三角(64) (00:00/00:00:00) 48 | 41 4六銀(37) (00:00/00:00:00) 49 | 42 6四歩(63) (00:00/00:00:00) 50 | 43 5五歩(56) (00:00/00:00:00) 51 | 44 同歩(54) (00:00/00:00:00) 52 | 45 同銀(46) (00:00/00:00:00) 53 | 46 6五桂(73) (00:00/00:00:00) 54 | 47 6六銀(77) (00:00/00:00:00) 55 | 48 6三銀(62) (00:00/00:00:00) 56 | 49 3七桂(29) (00:00/00:00:00) 57 | 50 8五歩(84) (00:00/00:00:00) 58 | 51 5四歩打 (00:00/00:00:00) 59 | 52 同銀(63) (00:00/00:00:00) 60 | 53 同銀(55) (00:00/00:00:00) 61 | 54 同金(43) (00:00/00:00:00) 62 | 55 6三銀打 (00:00/00:00:00) 63 | 56 4三銀打 (00:00/00:00:00) 64 | 57 2五桂(37) (00:00/00:00:00) 65 | 58 2四銀(33) (00:00/00:00:00) 66 | 59 1三桂成(25) (00:00/00:00:00) 67 | 60 同銀(24) (00:00/00:00:00) 68 | 61 1四歩(15) (00:00/00:00:00) 69 | 62 同銀(13) (00:00/00:00:00) 70 | 63 5四銀成(63) (00:00/00:00:00) 71 | 64 同銀(43) (00:00/00:00:00) 72 | 65 5五歩打 (00:00/00:00:00) 73 | 66 4三銀(54) (00:00/00:00:00) 74 | 67 1四香(19) (00:00/00:00:00) 75 | 68 同香(11) (00:00/00:00:00) 76 | 69 5四銀打 (00:00/00:00:00) 77 | 70 同銀(43) (00:00/00:00:00) 78 | 71 同歩(55) (00:00/00:00:00) 79 | 72 3一角(53) (00:00/00:00:00) 80 | 73 6五銀(66) (00:00/00:00:00) 81 | 74 同歩(64) (00:00/00:00:00) 82 | 75 4六角(68) (00:00/00:00:00) 83 | 76 8三飛(82) (00:00/00:00:00) 84 | 77 2五桂打 (00:00/00:00:00) 85 | 78 1二歩打 (00:00/00:00:00) 86 | 79 4一金打 (00:00/00:00:00) 87 | 80 6九銀打 (00:00/00:00:00) 88 | 81 3一金(41) (00:00/00:00:00) 89 | 82 同金(32) (00:00/00:00:00) 90 | 83 6四角打 (00:00/00:00:00) 91 | 84 8六桂打 (00:00/00:00:00) 92 | 85 6八金(67) (00:00/00:00:00) 93 | 86 7八桂成(86) (00:00/00:00:00) 94 | 87 同金(68) (00:00/00:00:00) 95 | 88 同銀成(69) (00:00/00:00:00) 96 | 89 同飛(28) (00:00/00:00:00) 97 | 90 6七金打 (00:00/00:00:00) 98 | 91 6九銀打 (00:00/00:00:00) 99 | 92 8六銀打 (00:00/00:00:00) 100 | 93 3一角成(64) (00:00/00:00:00) 101 | 94 同玉(22) (00:00/00:00:00) 102 | 95 6四角(46) (00:00/00:00:00) 103 | 96 4二香打 (00:00/00:00:00) 104 | 97 7七銀打 (00:00/00:00:00) 105 | 98 8七銀成(86) (00:00/00:00:00) 106 | 99 同玉(88) (00:00/00:00:00) 107 | 100 8六銀打 (00:00/00:00:00) 108 | 101 同銀(77) (00:00/00:00:00) 109 | 102 同歩(85) (00:00/00:00:00) 110 | 103 同角(64) (00:00/00:00:00) 111 | 104 同飛(83) (00:00/00:00:00) 112 | 105 同玉(87) (00:00/00:00:00) 113 | 106 6四角打 (00:00/00:00:00) 114 | 107 7五桂打 (00:00/00:00:00) 115 | 108 8五歩打 (00:00/00:00:00) 116 | 109 同玉(86) (00:00/00:00:00) 117 | 110 9四角打 (00:00/00:00:00) 118 | 111 投了 (00:00/00:00:00) 119 | -------------------------------------------------------------------------------- /test/files/kif/shogidokoro.kifu: -------------------------------------------------------------------------------- 1 | # KIF形式棋譜ファイル 2 | # Generated by Shogidokoro 3 | 後手の持駒:飛 金三 銀三 桂二 香四 歩十三 4 | 9 8 7 6 5 4 3 2 1 5 | +---------------------------+ 6 | | ・ ・ ・ ・ 馬 ・ ・v桂 ・|一 7 | | ・ ・ ・ ・ ・ ・ ・v玉 ・|二 8 | | ・ ・ ・ ・ ・ ・v歩v歩 ・|三 9 | | ・ ・ ・ ・ ・ ・ ・v金v歩|四 10 | | ・ ・ ・ ・ 歩 ・ ・ ・ 歩|五 11 | | ・ ・ ・ ・ ・ ・ ・ ・ ・|六 12 | | ・ ・ ・ ・ ・ ・ ・ ・ ・|七 13 | | ・ ・ ・ ・ ・ ・ ・ ・ ・|八 14 | | ・ ・ ・ ・ ・ ・ ・ ・ ・|九 15 | +---------------------------+ 16 | 先手の持駒:飛 角 銀 桂 17 | 先手: 18 | 後手: 19 | 手数----指手---------消費時間-- 20 | 1 5二飛打 (00:31 / 00:00:31) 21 | 2 3二金打 (00:00 / 00:00:00) 22 | 3 3一角打 (00:00 / 00:00:31) 23 | 4 同 玉(22) (00:00 / 00:00:00) 24 | 5 4二馬(51) (00:00 / 00:00:31) 25 | 6 同 金(32) (00:00 / 00:00:00) 26 | 7 4三桂打 (00:00 / 00:00:31) 27 | 8 2二玉(31) (00:00 / 00:00:00) 28 | 9 4二飛成(52) (00:00 / 00:00:31) 29 | 10 3二飛打 (00:00 / 00:00:00) 30 | 11 3一銀打 (00:00 / 00:00:31) 31 | 12 1三玉(22) (00:00 / 00:00:00) 32 | 13 1二金打 (00:00 / 00:00:31) 33 | 14 同 飛(32) (00:00 / 00:00:00) 34 | 15 同 龍(42) (00:00 / 00:00:31) 35 | 16 同 玉(13) (00:00 / 00:00:00) 36 | 17 3二飛打 (00:00 / 00:00:31) 37 | 18 1三玉(12) (00:00 / 00:00:00) 38 | 19 2二飛成(32) (00:00 / 00:00:31) -------------------------------------------------------------------------------- /test/files/kif/taichitsume.kif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/na2hiro/json-kifu-format/0df0ed4740e2f3fd1ffc84403bde7926b50ba866/test/files/kif/taichitsume.kif -------------------------------------------------------------------------------- /test/pegjs-jest.js: -------------------------------------------------------------------------------- 1 | var peg = require("pegjs"); 2 | module.exports = { 3 | process(src, filename, config, options) { 4 | return { 5 | code: peg.generate(src, {output: "source", format: "commonjs"}), 6 | }; 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "exclude": ["**/__tests__/**/*"], 4 | "compilerOptions": { 5 | "declaration": true, 6 | "module": "commonjs", 7 | "noEmitOnError": true, 8 | "target": "es5" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.typecheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "compilerOptions": { 4 | "declaration": true, 5 | "module": "commonjs", 6 | "noEmitOnError": true, 7 | "target": "es5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const {merge} = require("webpack-merge"); 4 | const {CleanWebpackPlugin} = require("clean-webpack-plugin"); 5 | const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; 6 | const package = require("./package.json"); 7 | 8 | module.exports = (env) => { 9 | const IS_ANALYZE = env && env.analyze; 10 | const common = { 11 | entry: { 12 | filename: path.resolve(__dirname, "./src/main.ts"), 13 | //filename: path.resolve(__dirname, './src/tst.js'), 14 | //hoge: path.resolve(__dirname, './src/csa-parser.pegjs') 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.ts$/, 20 | use: "ts-loader", 21 | exclude: /node_modules/, 22 | }, 23 | { 24 | test: /\.pegjs$/, 25 | use: "pegjs-loader", 26 | }, 27 | ], 28 | }, 29 | resolve: { 30 | extensions: [".ts", ".js", ".pegjs"], 31 | }, 32 | }; 33 | 34 | // For browsers 35 | const BUNDLE_DIR = path.resolve(__dirname, "./bundle"); 36 | const bundle = merge(common, { 37 | output: { 38 | library: "JSONKifuFormat", 39 | filename: `json-kifu-format-${package.version}.min.js`, 40 | path: BUNDLE_DIR, 41 | }, 42 | plugins: [new CleanWebpackPlugin()], 43 | optimization: { 44 | minimize: true, 45 | }, 46 | }); 47 | if (IS_ANALYZE) { 48 | bundle.plugins.push(new BundleAnalyzerPlugin()); 49 | } 50 | 51 | // For npm module 52 | const DIST_DIR = path.resolve(__dirname, "./dist"); 53 | const dist = merge(common, { 54 | output: { 55 | libraryTarget: "commonjs2", 56 | filename: "json-kifu-format.js", 57 | path: DIST_DIR, 58 | }, 59 | plugins: [new CleanWebpackPlugin()], 60 | }); 61 | return [bundle, dist]; 62 | }; 63 | --------------------------------------------------------------------------------