SATySFiでテストをするためのパッケージ

概要

自分が作成したassert-eqというパッケージを使用することで、関数のテストが簡単に行えます。

インストール方法

使用しているプリミティブの都合で、SATySFi v0.0.4以降でないと行けません。

Satyrographosを使う場合

$ opam update
$ opam install satysfi-assert-eq
$ satyrographos install

でインストールができます。

手動で行う場合

まず

$ git clone https://github.com/puripuri2100/SATySFi-assert-eq.git

をすることで手元にリポジトリのコピーを作成します。

次に、LIBROOT/local/packages/以下にassert-eqというフォルダを作ります。

最後に、フォルダ内のassert-eq.satygLIBROOT/local/packages/assert-eq/にコピーします。

これで完了です。

提供する関数

モジュール名はAssertEqです。

  • assert-eq : string?-> ('a -> string) -> 'a -> 'a -> unit
  • \assert-eq : [string?; ('a -> string);'a; 'a] inline-cmd
  • +assert-eq : [string?; ('a -> string);'a; 'a] block-cmd

組版時には\assert-eqコマンドはinline-nilと同じ挙動をし、+assert-eqblock-nilと同じ挙動をします。

assert-eq <function> <left> <right><left><right>が一致すると標準出力でログが、一致しないとエラー報告が出ます。

<function><left><right>での値を文字列に変換するための関数です。

例えば

assert-eq arabic (1+2) 3

ではログが、

assert-eq arabic (1+2) 4

ではエラーが出ます。

ログを見るときにわかりやすいようにラベルを付けることができます。

assert-eq ?:(`label`) arabic (1+2) 3のようにして使えます(\assert-eq+assert-eqでも同様にしてに使えます)。

具体的な使い方

先日記事を書いたdebug-show-valueと組み合わせることで簡単にテストできることを期待しています。

例えば、num-conversionでは

@require: stdjabook
@import: ../num-conversion

@require: assert-eq/assert-eq
@require: debug-show-value/debug-show-value


open NumConversion
open DebugShowValue

in

document (|
  title = {変換テスト};
  author = {\@puripuri2100};
  show-title = false;
  show-toc = false;
|) '<
  +assert-eq(show-opt show-string)(to-zenkaku 1230)(Some(`1230`));
  +assert-eq(show-opt show-int)(from-zenkaku `1230`)(Some(1230));
  +assert-eq(show-opt show-string)(to-zenkaku (-100))(None);
  +assert-eq(show-opt show-int)(from-zenkaku `1230`)(None);


  +assert-eq(show-opt show-string)(to-roman-upper 0)   (None);
  +assert-eq(show-opt show-string)(to-roman-upper 1)   (Some(`I`));
  +assert-eq(show-opt show-string)(to-roman-upper 2)   (Some(`II`));
  +assert-eq(show-opt show-string)(to-roman-upper 3)   (Some(`III`));
  +assert-eq(show-opt show-string)(to-roman-upper 4)   (Some(`IV`));
  +assert-eq(show-opt show-string)(to-roman-upper 5)   (Some(`V`));
  +assert-eq(show-opt show-string)(to-roman-upper 6)   (Some(`VI`));
  +assert-eq(show-opt show-string)(to-roman-upper 7)   (Some(`VII`));
  +assert-eq(show-opt show-string)(to-roman-upper 8)   (Some(`VIII`));
  +assert-eq(show-opt show-string)(to-roman-upper 9)   (Some(`IX`));
  +assert-eq(show-opt show-string)(to-roman-upper 10)  (Some(`X`));
  +assert-eq(show-opt show-string)(to-roman-upper 11)  (Some(`XI`));
  +assert-eq(show-opt show-string)(to-roman-upper 14)  (Some(`XIV`));
  +assert-eq(show-opt show-string)(to-roman-upper 100) (Some(`C`));
  +assert-eq(show-opt show-string)(to-roman-upper 4999)(Some(`MMMMCMXCIX`));
  +assert-eq(show-opt show-string)(to-roman-upper 5000)(None);

  +assert-eq(show-opt show-string)(to-roman-lower 0)   (None);
  +assert-eq(show-opt show-string)(to-roman-lower 1)   (Some(`i`));
  +assert-eq(show-opt show-string)(to-roman-lower 2)   (Some(`ii`));
  +assert-eq(show-opt show-string)(to-roman-lower 3)   (Some(`iii`));
  +assert-eq(show-opt show-string)(to-roman-lower 4)   (Some(`iv`));
  +assert-eq(show-opt show-string)(to-roman-lower 5)   (Some(`v`));
  +assert-eq(show-opt show-string)(to-roman-lower 6)   (Some(`vi`));
  +assert-eq(show-opt show-string)(to-roman-lower 7)   (Some(`vii`));
  +assert-eq(show-opt show-string)(to-roman-lower 8)   (Some(`viii`));
  +assert-eq(show-opt show-string)(to-roman-lower 9)   (Some(`ix`));
  +assert-eq(show-opt show-string)(to-roman-lower 10)  (Some(`x`));
  +assert-eq(show-opt show-string)(to-roman-lower 11)  (Some(`xi`));
  +assert-eq(show-opt show-string)(to-roman-lower 14)  (Some(`xiv`));
  +assert-eq(show-opt show-string)(to-roman-lower 100) (Some(`c`));
  +assert-eq(show-opt show-string)(to-roman-lower 4999)(Some(`mmmmcmxcix`));
  +assert-eq(show-opt show-string)(to-roman-lower 5000)(None);


  +assert-eq(show-opt show-int)(from-roman-upper `I`)        (Some(1));
  +assert-eq(show-opt show-int)(from-roman-upper `II`)       (Some(2));
  +assert-eq(show-opt show-int)(from-roman-upper `III`)      (Some(3));
  +assert-eq(show-opt show-int)(from-roman-upper `IV`)       (Some(4));
  +assert-eq(show-opt show-int)(from-roman-upper `IIII`)     (Some(4));
  +assert-eq(show-opt show-int)(from-roman-upper `V`)        (Some(5));
  +assert-eq(show-opt show-int)(from-roman-upper `VI`)       (Some(6));
  +assert-eq(show-opt show-int)(from-roman-upper `VII`)      (Some(7));
  +assert-eq(show-opt show-int)(from-roman-upper `VIII`)     (Some(8));
  +assert-eq(show-opt show-int)(from-roman-upper `IX`)       (Some(9));
  +assert-eq(show-opt show-int)(from-roman-upper `VIIII`)    (Some(9));
  +assert-eq(show-opt show-int)(from-roman-upper `X`)        (Some(10));
  +assert-eq(show-opt show-int)(from-roman-upper `XI`)       (Some(11));
  +assert-eq(show-opt show-int)(from-roman-upper `XIV`)      (Some(14));
  +assert-eq(show-opt show-int)(from-roman-upper `C`)        (Some(100));
  +assert-eq(show-opt show-int)(from-roman-upper `MMMMCMXCIX`)(Some(4999));

  +assert-eq(show-opt show-int)(from-roman-lower `i`)        (Some(1));
  +assert-eq(show-opt show-int)(from-roman-lower `ii`)       (Some(2));
  +assert-eq(show-opt show-int)(from-roman-lower `iii`)      (Some(3));
  +assert-eq(show-opt show-int)(from-roman-lower `iv`)       (Some(4));
  +assert-eq(show-opt show-int)(from-roman-lower `iiii`)     (Some(4));
  +assert-eq(show-opt show-int)(from-roman-lower `v`)        (Some(5));
  +assert-eq(show-opt show-int)(from-roman-lower `vi`)       (Some(6));
  +assert-eq(show-opt show-int)(from-roman-lower `vii`)      (Some(7));
  +assert-eq(show-opt show-int)(from-roman-lower `viii`)     (Some(8));
  +assert-eq(show-opt show-int)(from-roman-lower `ix`)       (Some(9));
  +assert-eq(show-opt show-int)(from-roman-lower `viiii`)    (Some(9));
  +assert-eq(show-opt show-int)(from-roman-lower `x`)        (Some(10));
  +assert-eq(show-opt show-int)(from-roman-lower `xi`)       (Some(11));
  +assert-eq(show-opt show-int)(from-roman-lower `xiv`)      (Some(14));
  +assert-eq(show-opt show-int)(from-roman-lower `c`)        (Some(100));
  +assert-eq(show-opt show-int)(from-roman-lower `mmmmcmxcix`)(Some(4999));


  +assert-eq(show-opt show-string)(to-kansujix 100)(Some(`一〇〇`));
  +assert-eq(show-opt show-int)(from-kansujix `一〇〇`)(Some(100));
  +assert-eq(show-opt show-string)(to-kansujix (-100))(None);
  +assert-eq(show-opt show-int)(from-kansujix `1〇〇`)(None);
  +assert-eq(show-opt show-int)(from-kansujix `百`)(None);
>

のようにしてテストファイルを書き、コンパイルをしてエラーが出ないかを確かめています。

おわりに

これでようやくテストを回せるようになりました。

使いこなしていきたいものです。

SATySFiでlistやoptionなどを文字列化して表示する方法

概要

自分が作成したdebug-show-valueというパッケージを使用することで、リストやオプションなどを簡単に文字列化できます。

自分の作った関数のデバッグ用にとても便利です。

インストール方法

Satyrographosを使う場合

$ opam update
$ opam install satysfi-debug-show-value
$ satyrographos install

でインストールができます。

手動で行う場合

まず

$ git clone https://github.com/puripuri2100/SATySFi-debug-show-value

をすることで手元にリポジトリのコピーを作成します。

次に、LIBROOT/local/packages/以下にdebug-show-valueというフォルダを作ります。

最後に、フォルダ内のdebug-show-value.satygLIBROOT/local/packages/debug-show-value/にコピーします。

これで完了です。

提供する関数

モジュール名はDebugShowValueです。

プリミティブな型の変換

  • show-int : int -> string
  • show-string : string -> string
  • show-bool : bool -> string
  • show-length-pt : length -> string
  • show-length-mm : length -> string
  • show-length-inch : length -> string
  • show-unit : unit -> string
  • show-point : point -> string
  • show-color : color -> string

関数名から役割はわかるとは思いますが、その型を与えると文字列にします。lengthに関しては、基準とする単位を定めることができます。

組み合わせる型の変換

  • show-tuple : ('a -> string) -> 'a list -> string
  • show-list : ('a -> string) -> 'a list -> string
  • show-opt : ('a -> string) -> 'a option -> string
  • show-opt-list : ('a -> string) -> ('a option) list -> string

show-tupleでは同じ型のtupleしか表現できないので、異なる型でのtupleを表示したい場合は、

show-tuple (fun s -> s) [show-int 1; show-bool true]

などのようにしてください。

おわりに

リストの表示やoption型の表示は結構面倒なので、これで簡略化できるととても良いと思っています。

SATySFiでローマ数字・漢数字に変換する方法

概要

自分が作成したnum-conversionというパッケージを使用することで、ローマ数字変換・漢数字変換を楽に行うことができます。

インストール方法

注意事項

このパッケージではstring-explodeというプリミティブを使うので、2020/2/22以降のHEAD版が必要です(将来v0.0.5としてリリースされる予定のものです)。

Satyrographosを使う場合

$ opam update
$ opam install satysfi-num-conversion
$ satyrographos install

でインストールができます。

手動で行う場合

まず

$ git clone https://github.com/puripuri2100/SATySFi-num-conversion.git

をすることで手元にリポジトリのコピーを作成します。

次に、LIBROOT/local/packages/以下にnum-conversionというフォルダを作ります。

最後に、フォルダ内のnum-conversion.satyhLIBROOT/local/packages/num-conversionにコピーします。

これで完了です。

提供する関数

モジュール名はNumConversionです。

ローマ数字関連

  • to-roman-upper : int -> string option
  • to-roman-lower : int -> string option

どちらも、数字を与え、その数字が変換可能なものであった場合にローマ数字となる文字列を返します。 0より大きく5000より小さい整数が変換できます。

違いは、返ってくる文字列のアルファベットが大文字であるか小文字であるかだけです。

  • from-roman-upper : string -> int option
  • from-roman-lower : string -> int option

どちらも、文字列を与え、その文字列がローマ数字として解釈可能なものであった場合に整数に変換します。

漢数字関連

  • to-kansujix : int -> string option

数字を与え、その数字が変換可能なものであった場合に漢数字となる文字列を返します。

0以上の整数が変換可能です。

この漢数字列は算用数字の表記をそのまま漢数字に当てはめた、俗に一〇方式と呼ばれる方式の表記になります。

位も書く一般的な十方式はまだ実装していません。

  • from-kansujix string -> int option

一〇方式で表記された文字列を受け取ると整数に変換します。

全角数字関連

  • to-zenkaku : int -> string option

数字を与え、その数字が変換可能なものであった場合に全角数字となる文字列を返します。

0以上の整数が変換可能です。

  • from-zenkaku string -> int option

全角数字の文字列を受け取ると整数に変換します。

おわりに

ローマ数字あたりは使いどころがかなりありそうだと思っています。早く十方式の漢数字表記も実装したいと思っています。

使っていただけると嬉しいです。

絵文字結合を実装する

先日、SATySFiで絵文字の結合を実装したので、そこで得た知見をまとめたいと思います。

絵文字結合とは

世の中に絵文字はたくさんありますが、それだけでは飽き足らなかった人類は「絵文字を合字みたいに結合させて新しいものを作ってしまえばよくない?」と思いつきます。Unicodeポイントを浪費してはいけないという理性が無駄に働いてしまったのでしょうね。

具体的には国旗・肌の色・性別・職業などです。

詳しい話はここここに書いてあります。

大きく分けて、2つの特定の絵文字が並んだときに結合させる種類と、特定の文字でもって結合させる種類の2つが存在し、それぞれ対応しなければいけません。

実装の方針

前提:

  • 入力は複数の文字列の形で与えられる
  • 文字列を分解・結合させてIDを作り、事前に用意してあったグラフィックのリストの中からIDを使って適切なグラフィックを探す
  • 出力はグラフィック列
  • 適切に解析できれば意図している絵文字に必ずなる文字列が与えられている。つまり、結合できないのに結合できると勘違いして文字列があたえられることはないということ

特定の絵文字2つで結合させる

特定の絵文字2つで結合する規則を持っているのは

  • 国旗
  • 肌の色

の2つです。

国旗結合

国名のアルファベット2文字が並ぶと国旗になります。 例えば日本の国旗であれば、Jの絵文字(U+1F1EF)とPの絵文字(U+1F1F5)が並んだ時に成立します。 間に何らかの文字が入れば成立しません。

この機能の実装は

  1. 1文字目がアルファベットの絵文字かを判定します
  2. 1でそうだとわかれば2文字目もアルファベットの絵文字かを判定します
  3. 1文字目も2文字目もアルファベットの絵文字でなければ分割したまま終える
  4. 両方ともアルファベットの絵文字であれば、国旗の組み合わせとなる2つの絵文字であるかを判定し、そうであればペアとして扱い、そうでなければ分割したまま終える

という方法で行います。国旗の個数は200弱と多いですが、法則も何もないのであらかじめペアとなる2つの文字の組を用意しておかないといけません。

肌色の変更

肌の色の変更は"人に関連する絵文字"+"特定の色の絵文字"によって実現します。 なので、グラフィックが用意されている「人に関連する絵文字」のリストを用意しておき、1個目の絵文字が人に関連するもので、2個目がU+1F3FBからU+1F3FFの間に入っているものであれば、その2つを結合させるだけで終わります。

地域の旗の結合(おまけ)

実は国旗以外にも、結合する旗の絵文字があります。 "黒旗(U+1F3F5)" + "0-9-a-zのいずれか5文字" + "Cancel TagU+E007F" の列でウェールズなどの旗にすることができます。よくわからないですね。 現在のところ

の7つが登録されているので、黒旗を見つけたらとりあえず後ろの文字を読んでパターンマッチで該当すれば結合、していなければ分割で実装します。

ZWJを使った結合

ZWJ(U+200D)という文字を間に入れることで絵文字同士を結合させます。たとえば黒旗(U+1F3F4)とZWJ(U+200D)と髑髏(U+2620)の3つが並ぶと海賊旗になります。

結合の組み合わせも莫大な上、規則性も存在しないので「ZWJがあったらその前後の絵文字は結合させることができる」と決め打ちして実装します。

実装そのものは単純で、1文字目・2文字目・3文字目を常に見て、2文字目がZWJであれば1文字目と3文字目を結合させるだけです。

結合の組み合わせ

「特定の2文字で結合」と「ZWJを使って結合」は同時に起こります。 例えば"U+1F469 U+1F3FD U+200D U+1F91D U+200D U1F468 U+1F3FF"という文字列は「medium skin toneの肌色の女の人とdark skin toneの肌色の男の人」が手をつないでいる絵文字になります。(U+1F91Dは🤝という絵文字)

よって、結合の順序は

  1. まず特定の2文字で結合する絵文字について結合処理を行い、結合できる絵文字についてはリストにしてまとめる
  2. 次にZWJを間に持つ2つの文字のリストを探し、あればそれらをまとめる

という順序になります。最初にZWJで結合させようとすると、肌色や国旗の結合に対応できません。

U+FE0Fの存在

絵文字であることを明確にするためにU+FE0Fという文字を末尾に付けることがあります。気を付けましょう。

グラフィックが無かったら

グラフィックが見つからなかった場合、結合してある絵文字をもう一度分解し、それぞれに対してグラフィックを割り当てて表示しましょう。対応するグラフィックが無い場合の処理についてはここに書いてあります。

おわりに

絵文字の仕様よくわからん

SATySFiで文字列を分割する方法の速さを調べてみた

はじめに

SATySFi v0.0.4でstring-explode: string -> int listというプリミティブが追加されました。 これは文字列をユニコードポイントのリストにするもので、これによって文字列の分割や比較が楽になりました。

文字列を一文字ずつのリストにすることは結構ありますが、今まではstring-sub再帰関数を使って分割していましたが、この方法と比べてstring-explodeを使う方法はどれくらい早いのか気になったので調べてみました。

実装はstring-explodeを使う方が簡単なのですが、遅かったら使いにくいので検討してみる価値はあると思います。

実験してみた

実験方法

実装はこのようになっています。

  • string-subを使った実装
let str-to-lst-sub str =
  let str-len = string-length str in
  let-rec sub lst n =
    if n <= 0 then
      lst
    else
      let s = string-sub str (n - 1) 1 in
      sub (s::lst) (n - 1)
  in
  sub [] str-len
  • string-explodeを使った実装
let str-to-lst-explode str =
  str
  |> string-explode
  |> List.map (fun i -> string-unexplode [i])
  • テスト用文書として「吾輩は猫である」の冒頭を分割し再度文字列化するものを2000回繰り返した。A4サイズで1177ページ程度。
  • Windows 10 + Ubuntu on WSL + SATySFI v0.0.4 で比較した。
  • 3 回予備で実行した後、9 回実行して所要時間を計測、その中間にある 5 回分の平均値を求めた。

実験結果

  • string-subを使ったものは24.000秒
  • string-explodeを使ったものは24.507秒

string-explodeを使った方はstring-subを使った方に比べて1.02倍時間がかかる

考察

ほとんど時間が変わらない。string-explodeを使った結果実装が簡単になるならこちらを採用するのも良いかもしれない。

しかし、v0.0.4からなのでそこは気を付けないといけないかもしれない。

TeXConf 2019の供養

この記事はTeX & LaTeX Advent Calendar 2019の22日目の記事で、SATySFi Advent Calendar 2019の22日目の記事です


TeX & LaTeX Advent Calendar 2019の21日目はToshioCPさんで23日目はwtsnjpさんです。

SATySFi Advent Calendar 2019の21日目はmonaqaさんで23日目はbd_gfngfnさんです。


台風によって残念ながら中止となってしまったTeXConf 2019の発表で使う予定だったスライドを供養します。

SATySFiのテキストモードを使ってtexファイルを生成する話をする予定でした。

このスライドで紹介したパッケージはGitHubSATySFi-textmode-latex-packagesというリポジトリに置いてあります。

ペンパ描画ソフトウェアの構想(実装)

これは「ペンシルパズルA Advent Calendar 2019」の5日目の記事です。

4日目はpzdcさんでした。6日目はnyoroppyiさんです。

はじめに

普段はパズルと解いたりたまに作ったりしているpuripuri2100です。こんにちは。 最近では「ドッスンフワリ35*70早解き大会」を主催していました。

他には開成高校のパズル研究部の部長と編集長をしています。 編集長は3年目になります(技術継承しないと……)。

さて、世の中には数えきれないほど多種多様なペンシルパズルがあります。 ニコリに体裁されているパズルであれば「ぱずぷれ」でほとんど画像化と共有ができますし、「penpa-editor(ペンパくん)」を使えばオリジナルパズルだって画像化と共有ができてしまいます。

しかし、この二つのソフトウェアはGUIのWebソフトウェアであり実装もどちらもJava Scriptです。 ペンシルパズル描画ソフトウェアにはもっと多様性があっても良いのではないでしょうか?

そういうわけで今回「リスト構造・代数的データ型・パターンマッチ」等のある程度の機能が備わっている一般的なプログラミング言語であればペンシルパズルを描画できる方法を考案し、実際にSATySFiというPDFを出力するための言語で実装してみました。この方法の一番重要なのは「パズルを全て文字だけでかけるようする」という点です。

今回実装したものはここにおいてあります(メインはこの中のpuzzle.satyhというファイルです)ので、コードを眺めながらこの記事を読んでいただけるとわかりやすいかもしれません。

ここから先、プログラミング要素がかなり多くなります。よくわからない時は「そういうものなのか~」程度でのんびり読み進めていただけると嬉しいです。

何を描画するか

一口にペンシルパズルと言っても、ヤジリンのようにマスを塗って線を引くものから数独の用に数字を埋めるもの、ハニーアイランドのように6角形のマスのものまで様々です。

この中で、今回は四角いマスを持ちその中で線を引いたり升目を塗ったり丸を描いたり数字や文字を入れたりするパズルを描画できるようにしていきたいと思います。

理由として以下のことが挙げられます。 - マス内に入れるものが数字でも記号でも文字でも労力はあまり変わらない - 線を引けないとかなり多くのメジャーなパズルが描画できなくなるので対応できるようにしたい - 四角形ではないマスを使うパズルはかなり種類が少なく、対応する労力に見合うほどではない

描画する記号類

これに関しては使用する言語がどこまで表現できるかにかかっていますが、SATySFiで実装するものに関しては、ベジェ曲線などを使って描けるものなら何でも描けるようにします。

描画できる文字

こちらも使用する言語とPDFライブラリにかかっていますが、最低でも基本的なアルファベット程度は使えるようにしたいですね。

構想

さて、今回の記事のメインです。

ここではどんな風にすれば大体のペンパを描画できるかを考えていきます。

セルの種類

盤面にはよく見るとセルがたくさんありますが、これらを

  • 記号等が入って盤面を構成するセル
  • 記号等が入ってパズルを構成するものだが、盤面の外に配置されるセル
  • 何も記号が入らず、パズルを構成しない“空白”となるセル

の3つに分類します。 ここでは便宜的にそれぞれInSideCellOutSideCellNullCellと名づけます。

OutSideCellの代表的な使い道としてはキンコンカンでのアルファベットを書く部分でしょうか。

盤面を書く

さて、折角セルの種類を分けてみたので盤面を簡単に作ってみます。 中身の記号類は一旦放置して種類だけでも並べてみましょう。

例えば、このようなぬりみさきは

f:id:puripuri2100:20191206062048j:plain
ぬりみさき

IIIIII
IIIIII
IIIIII
IIIIII
IIIIII
IIIIII

のように書けますし、

f:id:puripuri2100:20191206062106j:plain
ビルディング

のようなビルディングは

NNNO
OIII
NIII
NIII

のように書けます。結構簡単ではないでしょうか? 本当はここに書いてあるIInSideCellの略です)やOOutSideCellの略です)に、書いてある数字や白丸についての情報が入るわけですが、すくなくともこれだけで盤面は作れてしまいます。

セルの分け目の線を引く

「セルの分け目の線」って何?と思った人も多いと思います。 簡単に言えば

f:id:puripuri2100:20191206062145p:plain
盤面
の太線と細線です。

太線は「盤面の内側と外側との境界」ですので、InSideCellOutSideCellが接している部分か、InSideCellNullCellが接している部分と言い換えることができます。

細線は「盤面内のセル同士の境界」なのでInSideCellInSideCellが接している部分となります。

実際に実装する際にはこのように横と縦に一つずつ見ていって隣あう二つのセルの種類で判定をします。この図で青丸のところが外側の境界線となる箇所で、赤丸が内側同士の境界線となる箇所です。

f:id:puripuri2100:20191206062157p:plain
境界線の見方

どの座標の境界線がどの種類の境界になるかを判定したらそれぞれデータとして保存しておきます。そして、このデータと、座標を元に線を引く関数を使ってあとで一括で盤面を描画します

さて、この方法を使うと複雑な形の盤面であっても対応することができます(当然ではありますが)のでドーナツ型の盤面を作りたい時などにも使えます。

置く記号の指定

一口に記号と言ってもいくつもの種類があるので、特徴ごとに分けて記号を指定して行きたいと思います。

まずは文字です。 String(セルの中での位置, 文字列)などのように書けると良いでしょう。

次に黒丸や黒マスなどの記号です。 Object(セルの中での位置, 記号本体の描画)で行けると思います。ただ、「記号本体の描画」はそれぞれのプログラミング言語とライブラリに強く依存します。

次に線です。 Line(太さなどの線の引き方, 始点の位置, 次の点のセルの相対位置とその中での位置)です。かなり冗長ですが、できなくはないと思います。

書き方はこれで良いのですが、一つのセルの中に記号が複数入る事はあるので念のためリスト構造にしておきましょう(リスト構造とは、同じ種類のデータを並べる時につかうもので、[1, 2, 3, 4]などのように書きます)。

さて、これで盤面はいつでも作れるようになりました。 ためしに

f:id:puripuri2100:20191206062048j:plain
ぬりみさき

を文字で書いてみましょう(出てくる関数や記法は架空のものです)。 InSideCellIに、OutSideCellOに、NullCellNにしています。

[
  [I(), I(), I(), I(), I(), I([Object(0.5, 0.5, cycle-num(6))])],
  [I([Object(0.5, 0.5, cycle-num(2))]), I(), I(), I(), I(), I()],
  [I(), I(), I([Object(0.5, 0.5, cycle-num(4))]), I(), I(), I()],
  [I(), I([Object(0.5, 0.5, cycle]), I(), I(), I(), I()],
  [I(), I(), I(), I(), I([Object(0.5, 0.5, cycle]), I()],
  [I(), I([Object(0.5, 0.5, cycle-num(3))]), I(), I([Object(0.5, 0.5, cycle]), I(), I()],
]

やや面倒ですが、結構書けますね。 もっと簡単にしたかったら

[
  {||||||\cycle{6}|};
  {|\cycle{2}||||||};
  {|||\cycle{4}||||};
  {||\cycle{}|||||};
  {|||||\cycle{}||};
  {||\cycle{3}||\cycle{}|||};
]

のような記法から最初のデータを生成するなどすればかなり楽になります。

実装

実装についても書く予定でしたが、間に合いませんでしたすみません……。 追記するかもしれません。

実装ではさらにいくつかの工夫を施していますが、そこまで重要ではないでしょう。

何はともあれオリジナルパズルを文字だけで描けるようになったのは画期的だと思います(個人的に)。