Skip to content

Commit

Permalink
Merge pull request #9 from tsshogi/develop
Browse files Browse the repository at this point in the history
fix: tsume para parser
  • Loading branch information
tkgstrator authored Sep 12, 2024
2 parents 2b95de8 + c0bb0c1 commit 32693fb
Show file tree
Hide file tree
Showing 120 changed files with 326 additions and 257 deletions.
4 changes: 2 additions & 2 deletions .devcontainer/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ services:
volumes:
- node_modules:/home/bun/app/node_modules
- ../:/home/bun/app:cached
ports:
- 8787:8787
# ports:
# - 8787:8787
tty: true
stdin_open: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ jobs:
registry-url: https://npm.pkg.github.com
scope: '@tsshogi'
- name: Deploy
run: npm publish --access public
run: npm publish --access public --registry=https://npm.pkg.github.com
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
85 changes: 82 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,94 @@
## Shogi Kif Parser
## Kanna

独自形式で配布されている棋譜データをtsshogiのRecord型に変換するためのライブラリです.

### 対応データ

- [x] 詰将棋パラダイス

### 参考
### 導入

GitHub Package Registryを利用しているので`.npmrc`または`npm login`が必要になります

```zsh
# .npmrc
registry=https://npm.pkg.github.com/tsshogi
```

インストールは以下のコマンドをご利用ください

```zsh
# npm
npm install @tsshogi/kanna

# yarn
yarn add @tsshogi/kanna

# pnpm
pnpm install @tsshogi/kanna

# bun
bun add @tsshogi/kanna
```

### 使い方

```ts
import { importTCSV } from '@tsshogi/kanna'
import { Record } from 'tsshogi'

const text: string = "" // 詰将棋パラダイスのテキストデータ
const record: Record | Error = importTCSV(text)
if (record instanceof Error) return
exportKIF(record) // KIF形式の文字列
```

この結果、以下のようなレスポンスが得られます

```zsh
表題:チョコマカ銀
作者:須藤大輔
発表誌:詰将棋パラダイス
発表年月:2010/07/21
レベル:6
ポイント:20
ヒント:チョコマカ銀
手数:11
作品名:詰将棋パラダイス
作品番号:99
後手の持駒:歩十八 香四 桂二 銀二 金三 角 飛二 
9 8 7 6 5 4 3 2 1
+---------------------------+
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
| ・ ・ ・ ・ ・ ・ 馬 銀v玉|
| ・ ・ ・ ・ ・ ・v金 ・v銀|
| ・ ・ ・ ・ ・ ・ 桂 ・ ・|
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
| ・ ・ ・ ・ ・ ・ ・ ・ ・|
+---------------------------+
先手の持駒:桂 
先手番
手数----指手---------消費時間--
```

> 指し手も出力されますが、ネタバレ防止のためにここでは記載していません
複数の詰み手順がある場合、それら全てを出力します。

KIF形式以外の出力をした場合、カスタムメタデータは失われてしまいます。

### 既知の問題

- 盤面情報が正しくないデータに対してパースが失敗してしまう
- 問題番号58など

## 参考

- [tsshogi](https://github.com/sunfish-shogi/tsshogi)

### ライセンス
## ライセンス

[MIT License](https://github.com/tsshogi/kanna/blob/main/LICENSE)
Binary file modified bun.lockb
Binary file not shown.
15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
{
"name": "kif_parser",
"version": "0.0.1",
"name": "@tsshogi/kanna",
"version": "0.0.4",
"description": "棋譜データをパースするライブラリです",
"author": "tkgstrator",
"private": true,
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"repository": {
"type": "git",
"url": "https://github.com/tsshogi/kanna.git"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"config": {
"commitizen": {
"path": "@commitlint/cz-commitlint"
Expand All @@ -17,7 +23,7 @@
"scripts": {
"build": "bun build src/index.ts --sourcemap=external --minify --outdir dist",
"prebuild": "rimraf dist",
"postbuild": "bunx tsc",
"postbuild": "bunx tsc && tsc-alias",
"lint": "bunx @biomejs/biome lint --write src",
"format": "bunx @biomejs/biome format --write src",
"deploy": "wrangler deploy --minify src/index.ts",
Expand All @@ -37,6 +43,7 @@
"husky": "^9.1.3",
"lint-staged": "^15.2.10",
"rimraf": "^6.0.1",
"tsc-alias": "^1.8.10",
"zod": "^3.23.8"
}
}
68 changes: 66 additions & 2 deletions src/format/tsca.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import { machine } from 'node:os'
import { Color } from '@/enums/color'
import { MetadataKey } from '@/enums/metadata'
import { Board } from '@/models/board.dto'
import { Hand } from '@/models/hand.dto'
import { Metadata } from '@/models/metadata.dto'
import { Move } from '@/models/move.dto'
import { Record } from '@/models/record.dto'
import { Square } from '@/models/square.dto'
import dayjs from 'dayjs'
import { type Record as TSRecord, importCSA } from 'tsshogi'
import {
PieceType,
SpecialMoveType,
type Move as TSMove,
Node as TSNode,
type Record as TSRecord,
Square as TSSquare,
exportCSA,
exportKIF,
importCSA
} from 'tsshogi'
import { z } from 'zod'
import type { TCSV } from '..'

const chunk = <T>(arr: T[], size: number): T[][] => {
const results: T[][] = []
for (let i = 0; i < arr.length; i += size) {
results.push(arr.slice(i, i + size))
}
return results
}

/**
* 詰将棋パラダイス用のフォーマット
*/
Expand Down Expand Up @@ -59,6 +79,27 @@ export const TCSA = z.preprocess(
})
)
},
get answer(): TCSV.Move[][] {
// 手順分岐は全てが同じ長さなら区切り文字がないが、異なる長さが含まれる場合は`_@`で区切られている
const moves_list: string[][] = (
object.answercsv.includes('@')
? object.answercsv.split('_@')
: chunk(
object.answercsv.split('_').filter((value) => value.length > 0),
object.progresscnt
).map((value) => value.join('_'))
).map((value) => value.split('_').filter((value) => value.length > 0))
return moves_list.map((moves) =>
moves.map((move) => {
const [fromX, fromY, toX, toY, promote] = move.split('').map((value) => Number.parseInt(value))
return Move.parse({
from: { x: fromX, y: fromY },
to: { x: toX, y: toY },
promote: promote === 1
})
})
)
},
get board(): TCSV.Board {
return Board.parse({
pieces: [1, 2, 3, 4, 5, 6, 7, 8, 9].map((y) =>
Expand All @@ -81,14 +122,37 @@ export const TCSA = z.preprocess(
].map((metadata) => Metadata.parse(metadata))
},
get record(): TSRecord | Error {
return importCSA(
const record: TSRecord | Error = importCSA(
Record.parse({
metadata: (this as TCSA).metadata,
pieces: (this as TCSA).pieces,
hands: (this as TCSA).hands,
board: (this as TCSA).board
}).csa
)
if (record instanceof Error) {
return record
}
for (const moves of (this as TCSA).answer) {
// 初期局面に戻す
record.goto(0)
for (const move of moves) {
const m: TSMove | null = record.position.createMove(move.piece, new TSSquare(move.to.x, move.to.y))
// 不正な指し手でなければ追加する
if (m === null) {
console.error('[盤面]:', object.csv)
console.error('[盤面]:', (this as TCSA).pieces)
console.error('[解答]:', object.answercsv)
console.error('[棋譜]:', exportKIF(record))
throw new Error(`Invalid move: ${move}`)
}
m.promote = move.promote
record.append(m)
}
// 投了コマンドの追加
record.append(SpecialMoveType.RESIGN)
}
return record
}
}
})
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TCSA } from './format/tsca'
import type { Board } from './models/board.dto'
import type { Hand } from './models/hand.dto'
import type { Metadata } from './models/metadata.dto'
import type { Move } from './models/move.dto'
import type { Record } from './models/record.dto'
import type { Square } from './models/square.dto'

Expand All @@ -17,6 +18,7 @@ export namespace TCSV {
export type Square = z.infer<typeof Square>
export type Board = z.infer<typeof Board>
export type Metadata = z.infer<typeof Metadata>
export type Move = z.infer<typeof Move>
}

declare module '@/models/square.dto' {
Expand Down
11 changes: 11 additions & 0 deletions src/models/answer.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Square } from '@/models/square.dto'
import { z } from 'zod'

export const Answer = z.array(z.array(Square)).transform((object) => {
return {
...object,
get csa(): string {
return object[0].map((move) => move.csa).join('\n')
}
}
})
43 changes: 43 additions & 0 deletions src/models/move.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Color, PieceType, Square } from 'tsshogi'
import { z } from 'zod'

export const Move = z
.object({
from: z.object({
x: z.number().int().min(0).max(9),
y: z.number().int().min(0).max(9)
}),
to: z.object({
x: z.number().int().min(0).max(9),
y: z.number().int().min(0).max(9)
}),
promote: z.boolean()
})
.transform((object) => {
return {
...object,
get piece(): PieceType | Square {
if (object.from.x === 0) {
switch (object.from.y - 1) {
case 0:
return PieceType.PAWN
case 1:
return PieceType.LANCE
case 2:
return PieceType.KNIGHT
case 3:
return PieceType.SILVER
case 4:
return PieceType.GOLD
case 5:
return PieceType.ROOK
case 6:
return PieceType.BISHOP
default:
throw new Error(`Unexpected piece type: ${object.from.y}`)
}
}
return new Square(object.from.x, object.from.y)
}
}
})
1 change: 1 addition & 0 deletions src/models/record.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { Answer } from './answer.dto'
import { Board } from './board.dto'
import { Hand } from './hand.dto'
import { Metadata } from './metadata.dto'
Expand Down
1 change: 1 addition & 0 deletions src/tests/csv/0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=0&csv=0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_3401_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0001_2401_0011_3511_0011_0011_3211_&hint=チュートリアル&progresscnt=3&answercsv=24330_32410_05420_&title=チュートリアル&authorname=アライモン&workupdate=2000/8/4[14:41:00]&point=3&level=0&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=24230&marrcsv1=32231&mdescsv1=▽2三玉で失敗する。&manscsv2=34331&marrcsv2=35331&mdescsv2=▽同飛と取られてしまう。
1 change: 1 addition & 0 deletions src/tests/csv/1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=1&csv=9611_8611_7611_6611_5611_4611_3611_9901_8901_7901_6901_5901_3901_0011_0011_0011_0011_0011_0001_0011_0011_0011_0001_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0001_0011_0011_0011_9711_0011_&hint=歯が欠けているところを狙え&progresscnt=13&answercsv=06980_97870_98880_87770_88780_77670_78680_67770_79780_77870_03790_87970_68670_66671_02980_@06980_97870_98880_87770_88780_77670_78680_67770_79780_77870_03790_87970_02980_&title=よく噛んで食べよう&authorname=市原誠&workupdate=2008/11/03[00:00:00]&point=10&level=10&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3
1 change: 1 addition & 0 deletions src/tests/csv/10.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=10&csv=2311_1311_4401_3401_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0001_0011_0011_0011_1411_0011_0011_0011_5111_0001_0511_0011_4302_0011_0011_0011_2111_0011&hint=合駒問題です&progresscnt=9&answercsv=03330_21120_43320_05220_32220_12220_05210_22320_05430_&title=合駒問題です&authorname=アライモン&workupdate=2009/4/2[11:58:00]&point=30&level=9&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3
1 change: 1 addition & 0 deletions src/tests/csv/11.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=11&csv=2211_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_3601_0011_0011_0011_0011_0011_0011_0011_1111_1401_0011_0011_0011_0011_0011_0011_4202_0011_3502_4612_1211_0011&hint=香筋を通そう&progresscnt=9&answercsv=35340_12210_34430_21120_43210_12210_42320_21120_14231_&title=馬と玉のダンス&authorname=市原誠&workupdate=2008/11/29[16:24:00]&point=13&level=7&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3
1 change: 1 addition & 0 deletions src/tests/csv/12.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=12&csv=4301_3311_2311_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_2401_0011_0011_0011_2111_0011_0011_0011_0001_0001_0011_0011_0011_0011_0001_0011_2211_0011&hint=43歩に惑わされないで&progresscnt=5&answercsv=05120_21120_07310_22310_05320_&title=角と金の手筋&authorname=市原誠&workupdate=2008/11/29[16:29:00]&point=8&level=3&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=05320&marrcsv1=22131&mdescsv1=▽1三玉と逃げられて捕まらない。この退路を防ぐ手段は?&manscsv2=07310&marrcsv2=22311&mdescsv2=これは筋だが▽3一同玉で失敗。以下▲3二金▽同銀▲4二金▽2二玉で1三玉からの脱出を防げない。&manscsv3=05120_21120_05320&marrcsv3=22131&mdescsv3=▽1三玉で上空へ逃げ出される。上へ行かせてはならぬ。&manscsv4=07110&marrcsv4=22131&mdescsv4=▽1三玉で上空へ逃げ出される。上へ行かせてはならぬ。
1 change: 1 addition & 0 deletions src/tests/csv/13.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=13&csv=1211_2411_2501_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_1111_3601_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0001_0011_0011_0011_4102_0011_0011_0011_2211_0011&hint=25歩の意味を考えれば簡単&progresscnt=3&answercsv=41210_22210_05320_&title=香の力で決まる&authorname=市原誠&workupdate=2008/11/30[11:21:00]&point=3&level=2&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=41320&marrcsv1=22131&mdescsv1=▽1三玉で失敗。以下▲3三竜は▽2三香合。▲2三金は▽1四玉で不詰となる。玉を上空へ逃がすと詰ませにくくなる。&manscsv2=36321&marrcsv2=22131&mdescsv2=▽1三玉で失敗。玉を上空へ逃がすと詰ませにくくなる。玉は下段に落とすと詰ませやすい。&manscsv3=41310&marrcsv3=22131&mdescsv3=▽1三玉で失敗。以下▲3三竜は▽2三香合。▲2三金は▽1四玉で不詰となる。玉を上空へ逃がすと詰ませにくくなる。&manscsv4=05330&marrcsv4=22131&mdescsv4=▽1三玉で失敗。玉を上空へ逃がすと詰ませにくくなる。&manscsv5=05230&marrcsv5=22231&mdescsv5=▽2三同玉で失敗。玉を上空へ逃がすと詰ませにくくなる。以下▽2一竜なら▲2二香合で詰まない。&manscsv6=05330&marrcsv6=22131&mdescsv6=▽1三玉で失敗。玉を上空へ逃がすと詰ませにくくなる。
1 change: 1 addition & 0 deletions src/tests/csv/14.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=14&csv=3211_1411_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_1211_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0001_0011_0011_0011_4202_0011_4102_0011_1311_0011&hint=馬道を通せ&progresscnt=3&answercsv=42330_32330_05230_&title=犠牲心が大事&authorname=市原誠&workupdate=2008/11/30[11:28:00]&point=3&level=1&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=05230&marrcsv1=13231&mdescsv1=▽2三同玉に▲3二馬は▽2四玉▲3三竜▽2五玉と上空へ逃がしてしまう。▽2四玉を防ぐ一手は?。&manscsv2=41310&marrcsv2=13241&mdescsv2=▽2四玉で失敗する。玉を上へ逃がすと捕まりにくくなる。▽2四玉を防ぐ一手は?。
1 change: 1 addition & 0 deletions src/tests/csv/15.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=15&csv=2311_3411_2601_1601_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_1111_0011_0011_0011_0011_0011_0011_0011_2201_3601_0011_0011_0011_0011_0011_0011_0001_0011_3312_0011_2411_0011&hint=飛車は捨てる&progresscnt=3&answercsv=06140_11140_26250_&title=シャレた決め手&authorname=市原誠&workupdate=2008/11/30[12:1:00]&point=3&level=2&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=26250&marrcsv1=24141&mdescsv1=▽1四玉で1五には相手の馬が利いてくるため失敗。&manscsv2=06250&marrcsv2=24141&mdescsv2=▽1四玉と逃がし、次の▲1五歩は▽同馬とされて失敗する。飛車の打ち場所を深く考えてみよう。&manscsv3=36250&marrcsv3=24351&mdescsv3=▽3五玉で広いほうへ逃がしてしまう。相手を狭いほうへ誘ってみよう。&manscsv4=06140_11140_36250&marrcsv4=24351&mdescsv4=▽3五玉で広いほうへ逃がしてしまう。歩を「打って」詰ますのは反則だが、歩を動かして詰ますのは反則にならない。
1 change: 1 addition & 0 deletions src/tests/csv/16.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=16&csv=0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_6301_4301_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_5611_0001_0011_0011_0001_0011_6412_4412_5111_0011&hint=左右対称中央に手アリ&progresscnt=3&answercsv=06540_44540_05620_&title=馬の守備を回避&authorname=市原誠&workupdate=2008/11/30[12:14:00]&point=3&level=4&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=06410&marrcsv1=51521&mdescsv1=▽5二玉以下、▲4二飛成は▽6三玉で失敗。▲6二金は▽5三玉で失敗する。馬の守備をいなしてみよう。&manscsv2=05520&marrcsv2=51521&mdescsv2=▽5二同玉以下、▽4三玉または▽6三玉からの逃れを同時に防げない。▲6二金もしくは▲4二金を狙うためには...?&manscsv3=06530&marrcsv3=44531&mdescsv3=狙いは良いが▽5三同馬で守備力が変わってない。&manscsv4=05420&marrcsv4=64421&mdescsv4=馬の利きを生かされて詰まない。この馬をいなす手順がある。&manscsv5=06520&marrcsv5=51521&mdescsv5=▽5二同玉以下、▽4三玉または▽6三玉からの逃れを同時に防げない。▲6二金もしくは▲4二金を狙うためには...?&manscsv6=05410&marrcsv6=51521&mdescsv6=▽5二玉で失敗。以下▲5一飛は▽6三玉で詰まないし、▲5一飛以外は全て▽5三玉から逃げられる。
1 change: 1 addition & 0 deletions src/tests/csv/17.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=17&csv=0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_2111_0011_0011_0011_0011_0011_0011_0011_4411_0001_0011_0011_1101_1301_4112_0011_2211_0011&hint=どちらかは捨てる&progresscnt=5&answercsv=11121_22310_13331_21330_05210_&title=どちらかは捨てる&authorname=市原誠&workupdate=2008/11/30[12:19:00]&point=6&level=3&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=11211&marrcsv1=22211&mdescsv1=▽同玉で▲2三飛成は馬の利きがあるのでできない。&manscsv2=13121&marrcsv2=22331&mdescsv2=▽3三玉で上空へ逃がしてしまう。となると使うべき飛車は…&manscsv3=05330&marrcsv3=22311&mdescsv3=▽3一玉で不詰め。以下▲2一飛成▽同玉とすすめると次の▲2三飛成が馬で取られてしまうため失敗。
1 change: 1 addition & 0 deletions src/tests/csv/18.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d=0&workid=18&csv=4311_3311_2311_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_0011_5101_0001_0011_0011_3111_0001_0011_0011_1102_0011_0011_0011_3211_0011&hint=銀を放り捨てる&progresscnt=3&answercsv=04410_32410_05420_&title=31金を動けなくする&authorname=市原誠&workupdate=2008/11/30[12:22:00]&point=5&level=3&key=DR7ZzwicTHEkQ1_0DNGa2FLEMsbZI3&manscsv1=04210&marrcsv1=32411&mdescsv1=これは取ってくれない。▽4一玉で失敗する。&manscsv2=05420&marrcsv2=31421&mdescsv2=▽4二同金で失敗する。以下▲2一銀には▽4一玉で逃れる。竜のにらみを最大限に生かすには?&manscsv3=05220&marrcsv3=32411&mdescsv3=これは取ってくれず▽4一玉で失敗する。以下▲3一竜▽5二玉で大海に逃がす。
Loading

0 comments on commit 32693fb

Please sign in to comment.