前回「10 PRINT」のp5.js版を作りました。
この出力を眺めながら、僕の頭に浮かんでいたのは、あるレトロゲームでした。ユニバーサルが1981年に発売した「Lady Bug」です。
「Lady Bug」は、ナムコの「パックマン」(1980)がヒットした後に、後追いで当時のゲーセンにあふれたドットイート型ゲームのうちのひとつですが、「敵キャラから逃げつつ、迷路内にあるドットを消していく」という基本ルールに、多様な独自要素が加えてあり、単なる真似では終わらない、強いオリジナリティを感じさせる作品でした。
Lady Bugの特徴的な要素のひとつに「迷路の壁が動かせる」というのがあります。ゲーム画面の中で、真ん中が青くなっている緑色の壁は「回転扉」になっており、プレイヤーが押してやると青い軸を中心にパタンと回ります。
Lady Bugには、パックマンの「パワーエサ」のように、敵キャラに対してプレイヤーが能動的に使える攻撃手段がありません。「回転扉システム」は、この過酷な条件下でプレイヤーが生き延びるのに欠かせない、ゲーム性と強く結びついたギミックだったのです。僕としては「プレイヤーが自分で迷路の形を変えられる」というのが面白くて、特に攻略などは考えず、ガチャガチャとレバーを動かしながら、変化する画面を眺めて楽しんでいたことが多かった気がします(単にゲームが下手だったからなのですが)。
…で、この「Lady Bug」の迷路、「10 PRINT」の出力と似ていますよね。
それで作りたくなったのが「10 PRINTで出力された対角線を、後から変えられるスケッチ」です。
●Swappable Tiles - Diagonals(クリックすると別タブが開きます)
今回のスケッチは、開くとすぐに完成したパターンが表示されます。とりあえず、カンバス上のどこかをクリックしてみてください。そこにある対角線がもうひとつのパターンに切り替わります。できることはそれだけなのですが、ランダム表示ではなかなか出てこないような規則性のあるパターンを、後から「手作り」することができます。
気に入ったパターンができたら、「P」キーでカンバス部分をPNGの画像ファイルとしてダウンロードできます(Chrome、FireFoxで確認済み)。グリッドのサイズと背景色、表示色はランダムに選ばれますので、組み合わせが気に入らなかったら、F5キーなどで何度かリロードして変えてみてください。
const spArr = [25, 50, 75, 100, 150, 200, 300]; let sp; let gCount; let r, g, b; let fillCol; let x = 0, y = 0; const gridArr = []; function setup() { createCanvas(600, 600); noFill(); sp = spArr[floor(random(0, 7))]; gCount = width / sp; fillCol = colorPicker(); r = floor(random(0, 255)); g = floor(random(0, 255)); b = floor(random(0, 255)); for (let i = 0; i < gCount * gCount; i++) { gridArr[i] = floor(random(0, 2)); } } function draw() { background(fillCol); for (let i = 0; i < gCount * gCount; i++) { push(); stroke(r, g, b); strokeWeight(sp / 10); translate(x, y); flag = gridArr[i]; switch (flag) { case 0: line(0, 0, sp, sp); break; case 1: line(sp, 0, 0, sp); break; default: break; } pop(); x += sp; if (x >= width) { x = 0; y += sp; } } x = 0; y = 0; } function colorPicker() { if (random() < .5) { return 255; } else { return 0; } } function mouseReleased() { if (mouseX <= width && mouseX >= 0 && mouseY <= height && mouseY >= 0) { let xPos = floor(mouseX / sp); let yPos = floor(mouseY / sp); let mPos = yPos * gCount + xPos; gridArr[mPos] += 1; if (gridArr[mPos] > 1) { gridArr[mPos] = 0; } } } function keyPressed() { if (keyCode === 80) { saveCanvas(); } }
「タイルの並び替えによるパターン生成」が面白い
スケッチをいじりながらボンヤリと考えたのですが、「10 PRINT」で画面を構成している最小の正方形のパターンは「/」と「\」の「2つ」と考えることもできますし、「/」という「1つのパターン」が「中心を軸に90度回転している」(1パターン2種)と捉えることもできます。
この「回転可能な少数のパターンの組み合わせで、多種多様な模様を生成する」という仕組み、昔から多くのゲームやおもちゃで使われていますよね。
ビデオゲームだと、「ガッタンゴットン」(コナミ、1982年)「パイプドリーム」(ビデオシステム、1989年)「ゴルビーのパイプライン大作戦」(コンパイル、1991年)あたりのパズルゲームがそうです。パネルの「回転」が可能かどうかはゲームのルールで決まりますが、パネルに描かれた数パターンの「線路」や「パイプ」の絵を長くつなげていくというところは共通しています。
アナログゲームだと「水道管ゲーム」「コンタクトゲーム」「カルカソンヌ」あたりでしょうか。複数のプレイヤーがこの仕組みの中で共同で作っていく「場」の状態が、勝敗を左右することになります。ボードゲームやカードゲームは、基本的にパッケージングできるコンポーネントの量に物理的、原価的な制約があるので、決まった数やパターンのコンポーネントから、できる限り多様な展開を生みだせるという意味でも、この仕組みが有効なのかもしれません。
さて、個人的に大好きな「おもちゃ」に、知育玩具として売られている「模様作り積み木」というのがあります。
↑こんなヤツです(画像はAmazonより)。原色で塗り分けられた立方体の各面に描かれたシンプルなパターンを組み合わせて「花」や「金魚」、そのほかの幾何学的な模様などを作って遊ぶものです。先に挙げたゲームっぽいものをp5.jsで作ろうとすると、それなりに時間がかかりますが、この「模様作り積み木」的なスケッチであればすぐにできるかも…と思って書いてみたのが以下の2つです。
●Swappable Tiles – Arcs(クリックすると別タブが開きます)
●Swappable Tiles – Triangles(クリックすると別タブが開きます)
いずれも「Diagonals」と機能は同じですが「Diagonals」が「1パターン2種」の入れ替えなのに対し、「Arcs」は「2パターン6種」に「ブランク」(模様なし)を加えたもの、「Triangles」は「4パターン14種」に「ブランク」と「全面塗りつぶし」を加えたものをクリックごとに順に切り替えて表示します。「ブランク」「塗りつぶし」を加えたことで、↓にある絵のようなものも(根性さえあればw)描くことができます。「P」キーでの保存もできますので、お時間があるときにでもプチプチして遊んでみて下さい。
「Arcs」版のコードはこちら。
const spArr = [25, 50, 75, 100, 150, 200, 300]; let sp; let gCount; let r, g, b; let fillCol; let x = 0, y = 0; const gridArr = []; function setup() { createCanvas(600, 600); strokeCap(SQUARE); noFill(); sp = spArr[floor(random(0, 7))]; gCount = width / sp; fillCol = colorPicker(); r = floor(random(0, 255)); g = floor(random(0, 255)); b = floor(random(0, 255)); for (let i = 0; i < gCount * gCount; i++) { gridArr[i] = floor(random(0, 6)); } } function draw() { background(fillCol); for (let i = 0; i < gCount * gCount; i++) { push(); translate(x + sp / 2, y + sp / 2); flag = gridArr[i]; switch (flag) { case 0: drawArc(flag); break; case 1: rotate(radians(90)); drawArc(flag); break; case 2: rotate(radians(180)); drawArc(flag); break; case 3: rotate(radians(270)); drawArc(flag); break; case 4: drawArc(flag); break; case 5: rotate(radians(90)); drawArc(flag); break; default: break; } pop(); x += sp; if (x >= width) { x = 0; y += sp; } } x = 0; y = 0; } function drawArc(fg) { stroke(r, g, b); strokeWeight(sp / 3); arc(-sp / 2, -sp / 2, sp, sp, 0, radians(90)); stroke(fillCol); strokeWeight(sp / 3 * .7); arc(-sp / 2, -sp / 2, sp, sp, 0, radians(90)); stroke(r, g, b); strokeWeight(sp / 3 * .5); arc(-sp / 2, -sp / 2, sp, sp, 0, radians(90)); if (fg >= 4) { stroke(r, g, b); strokeWeight(sp / 3); arc(sp / 2, sp / 2, sp, sp, radians(180), radians(270)); stroke(fillCol); strokeWeight(sp / 3 * .7); arc(sp / 2, sp / 2, sp, sp, radians(180), radians(270)); stroke(r, g, b); strokeWeight(sp / 3 * .5); arc(sp / 2, sp / 2, sp, sp, radians(180), radians(270)); } } function colorPicker() { if (random() < .5) { return 255; } else { return 0; } } function mouseReleased() { if (mouseX <= width && mouseX >= 0 && mouseY <= height && mouseY >= 0) { let xPos = floor(mouseX / sp); let yPos = floor(mouseY / sp); let mPos = yPos * gCount + xPos; gridArr[mPos] += 1; if (gridArr[mPos] > 6) { gridArr[mPos] = 0; } } } function keyPressed() { if (keyCode === 80) { saveCanvas(); } }
「Triangles」版のコードはこちらになります。
const spArr = [25, 50, 75, 100, 150, 200, 300]; let sp; let gCount; let r, g, b; let fillCol; let x = 0, y = 0; const gridArr = []; function setup() { createCanvas(600, 600); strokeWeight(1); sp = spArr[floor(random(0, 7))]; gCount = width / sp; filCol = colorPicker(); r = floor(random(0, 255)); g = floor(random(0, 255)); b = floor(random(0, 255)); for (let i = 0; i < gCount * gCount; i++) { gridArr[i] = floor(random(0, 13)); } } function draw() { background(filCol); for (let i = 0; i < gCount * gCount; i++) { push(); stroke(r, g, b); fill(r, g, b); flag = gridArr[i]; switch (flag) { case 0: triangle(x, y, x + sp, y, x, y + sp); break; case 1: triangle(x, y, x + sp, y, x + sp, y + sp); break; case 2: triangle(x, y + sp, x + sp, y, x + sp, y + sp); break; case 3: triangle(x, y, x, y + sp, x + sp, y + sp); break; case 4: triangle(x + sp / 2, y + sp / 2, x, y, x + sp, y); break; case 5: triangle(x + sp / 2, y + sp / 2, x + sp, y, x + sp, y + sp); break; case 6: triangle(x + sp / 2, y + sp / 2, x, y + sp, x + sp, y + sp); break; case 7: triangle(x + sp / 2, y + sp / 2, x, y, x, y + sp); break; case 8: triangle(x + sp / 2, y + sp / 2, x, y, x + sp, y); triangle(x + sp / 2, y + sp / 2, x, y + sp, x + sp, y + sp); break; case 9: triangle(x + sp / 2, y + sp / 2, x + sp, y, x + sp, y + sp); triangle(x + sp / 2, y + sp / 2, x, y, x, y + sp); break; case 10: rect(x, y, sp - 1, sp - 1); stroke(filCol); fill(filCol); triangle(x + sp / 2, y + sp / 2, x, y, x + sp, y); break; case 11: rect(x, y, sp - 1, sp - 1); stroke(filCol); fill(filCol); triangle(x + sp / 2, y + sp / 2, x + sp, y, x + sp, y + sp); break; case 12: rect(x, y, sp - 1, sp - 1); stroke(filCol); fill(filCol); triangle(x + sp / 2, y + sp / 2, x, y + sp, x + sp, y + sp); break; case 13: rect(x, y, sp - 1, sp - 1); stroke(filCol); fill(filCol); triangle(x + sp / 2, y + sp / 2, x, y, x, y + sp); break; case 14: rect(x, y, sp - 1, sp - 1); break; default: break; } pop(); x += sp; if (x >= width) { x = 0; y += sp; } } x = 0; y = 0; } function colorPicker() { if (random() < .5) { return 255; } else { return 0; } } function mouseReleased() { if (mouseX <= width && mouseX >= 0 && mouseY <= height && mouseY >= 0) { let xPos = floor(mouseX / sp); let yPos = floor(mouseY / sp); let mPos = yPos * gCount + xPos; gridArr[mPos] += 1; if (gridArr[mPos] > 15) { gridArr[mPos] = 0; } } } function keyPressed() { if (keyCode === 80) { saveCanvas(); } }
模様の切替には「switch」文を使ってみました。「Diagonals」のような1パターンの切替であればもっといいやり方があるのですが、パターン数を後から追加したいような場合にはこっちのほうがシンプルで便利でした。各caseに割り当ててあるステートメントを変更すれば、別のパターンも表示できるようになりますので、ご興味があればぜひお試しを。
いろんなゲームを思い出しながら「10 PRINT」を作り替えてみた はコメントを受け付けていません