見出し画像

Puzzlinkで新エディタを開発してみる日記<1日目:クロクローン編①>

流れ

今年に入ってから puzzlink でオモパのエディタを開発するのが目標になっていました。
なぜかと言えば、3月発売の182号向けに送った自作オモパを推したいから。掲載とほぼ同時期にエディタを公開したら、同期オモパに差をつけれそうじゃないですか。そもそも掲載されるのか分からんのですが。
そんな邪念駆動の開発なのであります。
某箱15の原稿と確定申告が終わり、ようやく手が付けられた次第。

さて、ニコリストであれば、「ぱずぷれ」の改造に手を出そうとした方も多いはず。
とはいえ昔はgitの知識はおろかJavaScriptもろくに知らず即挫折したわけですが、あれから5年以上経った今、robx 氏により puzzlink としてオープンソース化され、新種のエディタを共同開発していける基盤が出来上がりました。
基盤APIも使いやすく強化され、開発のノウハウがいろいろ共有できるようになったわけです。

そんなわけで、数年越しにリベンジ。
まずは「練習」として、既存要素の組み合わせで作れそうな題材、そしてせっかくなら、昔の自分が他力本願でエディタできたらいいな~と思って結局過去に消えていったパズル。
「パズル通信ニコリ vol.153」初登場のオモパ、「クロクローン」のエディタを作れないか試行錯誤してみます。

<さっくりルール>

  1. いくつかのマスを黒く塗る。

  2. 各部屋では、同じ形(回転・裏返しOK)をした黒マスのブロックを2つ置く。

  3. 黒マスは部屋をまたいで隣合わない

  4. 矢印付き数字は、矢印側の隣のマスにその数字の大きさのブロックが入ることを表す。

  5. 矢印付き数字のマスにブロックは置けない。

例題                    解答  

名前を先に思い浮いてルールを逆算するという方法で生まれたパズルで、名前的・時期的に「ストストーン」の兄貴分にあたるオモパであります。
ルール4の矢印数字の使い方は地味ながら良い発明だったなと思います。

さて、このパズルの「必要な要素」は以下のように分解できます。

・盤面要素
 ・境界線    ⇒ 島国を使いまわせる
 ・矢印付き数字 ⇒ ヤジリンを使いまわせる
 ・黒マス    ⇒ 島国を使いまわせる。
・解答判定アルゴリズム
 ・部屋またぎ禁止   ⇒ 島国を使いまわせる
 ・矢印の先ルール   ⇒ これくらいは自前でがんばる
 ・ブロックが2つだけ ⇒ がんばる
 ・同型判定      ⇒ 「チェンブロ」でやってるはずなのでそれをパクる。

「島国」スクリプトをベースに、入力UIから描画、URL/ファイル出力、解答チェックまで既存スクリプトのツギハギでいけそうです。
一番大変そうな「同型判定」も、今となっては「ダブルチョコ」「チェンブロ」という面々がおられますので、彼らの判定アルゴリズムをパクってくればよいでしょう。

そういうわけで、さっそく開発へGO。

本編(入力UI編)

まずは `README.md`(以下、りどみ)の通り、`src/pzpr/variety.js` にパズル名を追記して、`src/variety`下に新たに`kuroclone.js` を作ります。
1からファイルを作る方法と、既存ファイルに追記して一部を使いまわす方式がありますが、前者の方がコードがシンプルにまとまっている印象があったので今回は前者を採用。
後者の方式では第二回(予定)で、最新181号より「マルタリング」を`shimaguni.js` に追加実装しようと思います。
とにかく今回は`/shimaguni.js` をコピーして `kuroclone.js` を作成。
最初の方に実装パズル名をリスト指定しているところがあるので、そこを編集して`kuroclone`だけにします。

ここで試しにビルド。`$ make format`で整形してから`$ make`で実行。
エラーなく通ったら `$ make serve` でローカルwebサーバを立ち上げ、`http://localhost:8000/p.html?kuroclone` にアクセスします。
まだトップページからのリンクは作ってないので、パズル名をクエリに直接指定してアクセス。

ローカルホストのpuzzlinkにアクセスしてみる

おお、ちゃんと動いている、やったね。

しかしここはスタートラインに過ぎないのである。

まずは入力UIである `MouseEvent` の改造をしましょうか。
マウスの入力処理の部分です。
なるほど、問題モードと解答モードでそれぞれ使う入力モードを指定していく形らしい。
問題モードでは「境界線」のほかに「矢印付き数字」が欲しい。解答モードはそのままでOK。
というわけでさっそく `yajikazu.js` からパクってきます。ヤジリンスクリプトはなんか複雑だったので撤退。
なるほど、どうやら`number`と`direc`に分けて指定すれば良いらしい。

というわけでできたコードが以下。その結果が上の画像だったのでした。
ちゃんと「向き」という項目が出ていますね。

//---------------------------------------------------------
	// マウス入力系
	MouseEvent: {
		use: true,
		inputModes: {
			edit: ["border", "number", "direc", "clear"],
			play: ["shade", "unshade"]
		},
		mouseinput_auto: function() {
			if (this.puzzle.playmode) {
				if (this.mousestart || this.mousemove) {
					this.inputcell();
				}
			} else if (this.puzzle.editmode) {
				if (this.mousestart || this.mousemove) {
					this.inputborder();
				} else if (this.mouseend && this.notInputted()) {
					this.inputqnum();
				}
			}
		}
	},

`mouseinput_auto()` は「自動モード」時の入力処理で、どういうマウス入力操作の時に何の要素を入力するかの場合分けをすればよいらしい。
りどみ曰く、ここは各パズルで組みたまえとのこと。
現時点で中身は「島国」のままなので、これだけでは当然ちゃんと動いてはくれませぬ。

<ここまでの疑問点と自己解決>

  • Q: `MouseEvent`の`use: true`はなんなのか。とりあえずマウス使うし`true`でいいのかね。

    • A:「左右ボタン」「1ボタン」を切り替えられるようにするフラグだった。

    • これを省略すると上の画像のようにこのメニュー自体が消えてしまう。

  • Q: ドラッグ入力時に境界線と矢印入力を区別させたい

    • A: ヤジタタミの入力UIを見たところ、境界か中身かどうか自動判断する `isBorderMode()` メソッドを使うらしい。

    • 概要:クリック位置に応じて境界寄りかセル寄りか判定、その結果をMouseEventオブジェクトに一時保存したうえで返す。

  • Q: 数字が左上にしか置けんのだけど

    • A: `roommgr.hastop` フラグが立っていると、ヒントが強制的に左上に置かれるらしい(`roommgr`は部屋情報を管理するオブジェクト)

    • `AreaRoomGraph`クラスが`roommgr`の大元なので、この`hastop`属性を`false`にしてやります。

以上を踏まえて、`mouseinput_auto`の中身を改造していきます。
ついでに、島国スクリプトに元々あった不要そうなコードを削除。ストストーン専用のコード量が多く、これにはまこと感謝の念しかないです、といいつつ容赦なく削除。
なお "クラス名@パズル名" で「特定のパズルにだけ適用するクラス」という意味になるらしい。
これはJSの言語仕様というよりはぱずぷれ独自?の記法で、裏方の`src/pzpr/classmgr.js`でクラスの動的管理をがんばっている様子。

ついでに「ヤジタタミ」のスクリプトから、キーボード処理や盤面回転・反転に伴う矢印の処理、矢印数字の描画処理もパクってきました。
試行錯誤の末、以下のコードが完成 ↓

//---------------------------------------------------------
	// マウス入力系
	MouseEvent: {
		use: true,
		inputModes: {
			edit: ["border", "number", "direc", "clear"],
			play: ["shade", "unshade"]
		},
		mouseinput_auto: function() {
			if (this.puzzle.playmode) {
				if (this.mousestart || this.mousemove) {
					this.inputcell();
				}
			} else if (this.puzzle.editmode) {
				if (this.mousestart || this.mousemove) {
					if (this.isBorderMode()) {
						this.inputborder();   // 境界のそばなら境界線
					} else {
						this.inputdirec();    // 真ん中なら矢印(UI的に書きづらいけど)
					}
				} else if (this.mouseend && this.notInputted()) {
					this.inputqnum();
				}
			}
		}
	},

	//---------------------------------------------------------
	// キーボード入力系   ヤジタタミからそのまま拝借
	KeyEvent: {
		enablemake: true,
		moveTarget: function(ca) {
			if (ca.match(/shift/)) {
				return false;
			}
			return this.moveTCell(ca);
		},

		keyinput: function(ca) {
			if (this.key_inputdirec(ca)) {
				return;
			}
			this.key_inputqnum(ca);
		}
	},

	//---------------------------------------------------------
	// 盤面管理系
	Cell: {
		minmum: 1,   // 数字の範囲を設定
		maxnum: 99
	},
	Board: {
		hasborder: 1
	},
	BoardExec: {   // 盤面回転・反転時の矢印マスの処理
		adjustBoardData: function(key, d) {
			this.adjustNumberArrow(key, d);
		}
	},
	AreaRoomGraph: {
		enabled: true,
		hastop: false    // ヒントが部屋左上にいかないように
	},

	//---------------------------------------------------------
	// 画像表示系
	Graphic: {
		gridcolor_type: "LIGHT",

		enablebcolor: true,
		bgcellcolor_func: "qsub1",

		paint: function() {
			this.drawBGCells();           // マス背景
			this.drawGrid();              // 実線グリッド 
			this.drawShadedCells();       // 黒マス
			this.drawArrowNumbers();      // 矢 印 付 き 数 字
			this.drawBorders();           // 境界線
			this.drawChassis();           // 外枠。シャーシってスペルこう書くんすね。
			this.drawBoxBorders(false);   // 良く分からんけどそのまま引き継いでる
			this.drawTarget();            // カーソル
		}
	},


いい感じだあ

結果:いい感じだけど、数字マスを黒マスにできてしまう!

最初は矢印が描けず何故だと格闘していたら、単純にUI的に矢印を書きづらかっただけという。
格闘の副産物として、ブラウザコンソール上で `ui.puzzle.board` を叩けば盤面オブジェクトの生態を見れることを知ったので収支はプラスです。

さて、コーヒーを補充したので数字マスを黒マスにしない方法を探しましょうか。
「ウソワン」は数字を黒マスにできないので、`usoone.js`を眺めてみます。
すると、ちょうど`Cell`クラスにドンピシャの設定があった。

Cell: {
  numberRemainUnshaded: true
}

これをクロクローンに追記します。
うむ、いい感じ。canvas描画までいけたので画像出力もできます。
でもまだURL出力が島国のままなのでエラーになるのね~。
最後に出来立てほやほやクロクローンの問題です。

<後日URL出力出来たらここに貼る>

眠いので、今日はここでおしまい。
明日はURLとファイル出力の部分にいこうかなと思います。

おやすみ。

<2日目へ>

この記事が気に入ったらサポートをしてみませんか?