UNPKG

shogi-player

Version:

Shogi board web components has functions for replaying, manipulating, and editing

238 lines (216 loc) 8.2 kB
import XRegExp from "xregexp" XRegExp.uninstall("namespacing") import assert from "minimalistic-assert" import { ParserBase } from "./parser_base.js" import { Board } from "./board.js" import { Piece } from "./piece.js" import { Place } from "./place.js" import { Location } from "./location.js" import { SfenParser } from "./sfen_parser.js" import { PresetInfo } from "./preset_info.js" import { Soldier } from "./soldier.js" import { KanjiNumber } from "./kanji_number.js" //////////////////////////////////////////////////////////////////////////////// const REGEXP = { header: "^(?!\\*)(?<key>.*):\s*(?<value>.*)", // key:val board: "\\|(?<board>.*)\\|", // | 香 桂 銀 金 玉 金 銀 桂 香|九 direct_location: "(?<direct_location>^[上下先後]手番)", // 上手番 latest_triangle: "^手数=.*(?<latest_triangle>[▲△])", // 手数=24 △5四歩 まで (TODO: 最終手を活用する) comment: "^\\*(?<comment>.*)", // *コメント hand: `^\\s*(?<number>\\d+)\\s+ # 1 ; 手数 (?<to>[1-91-9一二三四五六七八九]+)? # 76 ; 移動先 (?<same>同)?\\s* # 同 ; 座標を書いてくれ (?<piece>成[銀桂香]|[王玉金銀全桂圭香杏角馬飛龍竜歩と]) # 歩 ; 駒 (?<suffix>[左右直]?[寄引上]?) # 上 ; KI2っぽい表記は読み捨てる (?<motion>不?成|打|合|生)? # 打 ; 成や打 (\\((?<origin_place>\\d+)\\))? # (77) ; 移動元`, } // XRegExp.union はバグっていて ?<key> を外される const MERGED_REGEXP = XRegExp(Object.values(REGEXP).join("|"), "x") //////////////////////////////////////////////////////////////////////////////// export class KifParser extends ParserBase { reset() { super.reset() this.move_infos = [] this.comment_lines_hash = {} this.board_lines = [] // 盤面が1行ごと入る(内部用) this.direct_location = null // 手番の指定があればそのキー this.hold_pieces = this.hold_pieces_empty_hash() // 持駒個数 } get board() { if (this.board_lines.length >= 1) { return this.board_setup_from_board_lines() } else { // FIXME: KIFがSFENに依存してんのおかしいだろ const sfen_parser = new SfenParser() sfen_parser.raw_body = this.preset_info.sfen sfen_parser.parse() return sfen_parser.board } } get preset_key() { const v = this.header["手合割"] if (v === "その他") { return null } else { return v || "平手" } } get preset_info() { return PresetInfo.lookup(this.preset_key) } // 特別なメソッド get base_location() { if (this.direct_location) { // 「上手番」 return this.direct_location } else if (this.preset_key) { // 「手合割:角落ち」 return Location.fetch(this.preset_info.first_location_key) } else { // 「手合割:その他」 return Location.fetch("black") } } parse() { let before_place = null // 巨大な正規表現でまるごと切り分ける this.raw_body.split(/\n/).forEach(line => { const m = XRegExp.exec(line, MERGED_REGEXP) if (m) { if (m.key) { // ヘッダー部分 const value = m.value.trim() this.header[m.key] = value if (m.key.match(/手の持駒/)) { if (value === "なし") { } else { let s = KanjiNumber.kanji_to_number_string(value) s = s.replace(/\s+/g, "") XRegExp.forEach(s, XRegExp("(?<piece_char>\\D)(?<count>\\d*)"), (m2, x) => { const piece = Piece.lookup_by_name(m2.piece_char) let count = Number(m2.count || 1) const location_key = m.key.match(/[上後]/) ? "white" : "black" count += this.hold_pieces[location_key][piece.key] || 0 this.hold_pieces[location_key][piece.key] = count }) } } } else if (m["comment"]) { // コメント部分 const i = this.move_infos.length this.comment_lines_hash[i] ??= [] this.comment_lines_hash[i].push(m["comment"]) } else if (m["board"]) { // 盤 this.board_lines.push(m["board"]) } else if (m["direct_location"]) { // BODにある "○手番" this.direct_location = Location.fetch(m["direct_location"].match(/[上後]/) ? "white" : "black") } else if (m["latest_triangle"]) { // BODにある "手数=24 △5四歩 まで" this.direct_location = Location.fetch(m["latest_triangle"] === "▲" ? "white" : "black") } else if (m["number"]) { // 棋譜部分 const attrs = {} attrs["location"] = this.location_by_offset(Number(m["number"]) - 1) if (m["origin_place"]) { attrs["origin_place"] = Place.fetch(m["origin_place"]) } assert(m["to"] || m["same"]) if (m["to"]) { attrs["place"] = Place.fetch(m["to"]) before_place = attrs["place"] } else { assert(m["same"]) assert(before_place) attrs["place"] = before_place } if (m["motion"] === "成") { attrs["promoted_trigger"] = true } if (m["motion"] === "打") { attrs["drop_piece"] = Piece.lookup_by_name(m["piece"]) } this.move_infos.push(attrs) } else { // # で始まる行 } } }) } set move_infos(v) { this._move_infos = v } get move_infos() { return this._move_infos } set comment_lines_hash(v) { this._comment_lines_hash = v } get comment_lines_hash() { return this._comment_lines_hash } // private board_setup_from_board_lines() { assert(this.board_lines.length >= 1) const board = new Board() this.board_lines.forEach((e, y) => { XRegExp.forEach(e, XRegExp("(?<arrow>.)(?<piece>\\S)"), (m, x) => { if (m.piece === "・") { } else { const location_key = m.arrow === "v" ? "white" : "black" let piece = Piece.lookup_by_name(m.piece) let promoted = false if (!piece) { piece = Piece.lookup_by_promoted_name(m.piece) assert(piece) promoted = true } const soldier = new Soldier({ place: new Place([x, y]), piece: piece, promoted: promoted, location: Location.fetch(location_key), }) board.place_on(soldier) } }) }) return board } } if (typeof process !== "undefined" && process.argv[1] === __filename) { const instance = new KifParser() // instance.raw_body = ` // # ---- Kifu for Windows V6.26 棋譜ファイル ---- // key:value // 手数----指手---------消費時間-- // *コメント0a // *コメント0b // 1 7六歩(77) ( 0:00/00:00:00) // *コメント1a // *コメント1b // 2 3四歩(33) ( 0:00/00:00:00) // 3 投了 ( 0:00/00:00:00) // ` instance.raw_body = ` 1 2六歩(27) ( 0:16/00:00:16) 2 3四歩(33) ( 0:22/00:00:22) 3 7六歩(77) ( 0:06/00:00:22) 4 8四歩(83) ( 0:13/00:00:35) 5 2五歩(26) ( 0:09/00:00:31) 6 8五歩(84) ( 0:16/00:00:51) 7 7八金(69) ( 0:04/00:00:35) 8 3二金(41) ( 0:09/00:01:00) 9 2四歩(25) ( 0:04/00:00:39) 10 同 歩(23) ( 0:10/00:01:10) ` instance.parse() // console.log(instance.board) // console.log(instance.base_location) // console.log(instance.hold_pieces) console.log(instance.move_infos) console.log(instance.comment_lines_hash) console.log(instance.init_sfen === undefined) }