0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【JavaScript】550文字以下でライフゲームの作成

Last updated at Posted at 2025-05-29

ライフゲームのルール

誕生

死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する

生存

生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する

過疎

生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する

過密

生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する
(引用 https://ja.wikipedia.org/wiki/ライフゲーム)

ソースコード

<body id=D><script>F=[],S=20,P=S*S;for(i=0;i<P;i++)F[i]=0;T=()=>{N=[];for(i=0;
i<P;i++){I=[-S-1,-S,-S+1,-1,1,S-1,S,S+1],C=0;for(j=0;j<8;j++){e=i+I[j];if(e>=0&&
e<P&&F[e]&&Math.abs((e%S)-(i%S))<=1)C++;}N[i]=(!F[i]&&C==3)?1:(F[i]&&(C==3||C==2
))?1:0;}F=N;R();};R=()=>{D.innerHTML="";for(i=0;i<P;i++){D.innerHTML+=(F[i])?
"":(i==X)?"":" ";D.innerHTML+=((i+1)%S==0)?"<br>":""}};K=0;X=0;D.onkeydown=(e
)=>{K=e.keyCode;if(K==32)T();if(K==39)X++;if(K==37)X--;if(K==38)X-=S;if(K==40)
X+=S;if(K==81){F[X]^=1;}R();};R()</script></body>

このような感じです。

最初から解説していきます。

まず最初に変数とそれの説明です

変数名 解説
S フィールドの横幅です
P フィールドのマスの総数です
F 一次元配列でフィールドを表しています
K キーコードを格納する変数です
X カーソルの位置です
F=[],S=20,P=S*S;
/*省略*/
K=0;X=0;

では、フィールド(配列)、横のサイズ、全体のサイズを定義しています

S=400,P=Math.sqrt(S);

でもいいのですが文字数が増えてしまうので S*S を採用しました。
(フィールドは四角形)
また省略後のものですが、Kはキーコード、Xはカーソルの座標を示しています
関数T

T=()=>{N=[];for(i=0;
i<P;i++){I=[-S-1,-S,-S+1,-1,1,S-1,S,S+1],C=0;for(j=0;j<8;j++){e=i+I[j];if(e>=0&&
e<P&&F[e]&&Math.abs((e%S)-(i%S))<=1)C++;}N[i]=(!F[i]&&C==3)?1:(F[i]&&(C==3||C==2
))?1:0;}F=N;R();};

見やすく整理するとこうなります

T = () => {
    N = [];
    for (i = 0; i < P; i++) {
        I = [-S - 1, -S, -S + 1, -1, 1, S - 1, S, S + 1], C = 0;
        for (j = 0; j < 8; j++) {
            e = i + I[j];
            if (e >= 0 && e < P && F[e] && Math.abs((e % S) - (i % S)) <= 1)
                C++;
        }
        N[i] = (!F[i] && C == 3) ? 1 : (F[i] && (C == 3 || C == 2)) ? 1 : 0;
    }
    F = N;
    R();
};

Fはさっき定義したフィールドですね。
Nは次世代の状態を仮保存するための配列です(サイズはFと同じ)
最後に書き換えをしています。

I = [-S - 1, -S, -S + 1, -1, 1, S - 1, S, S + 1], C = 0;

Iは方向です

map.png
0が原点でこのようにあらわされます。

相対位置 説明
-S-1 左上(1段上 & 1列左)
-S 真上(1段上)
-S+1 右上(1段上 & 1列右)
-1 左(同じ行で左)
0 中心(現在のセル)
+1 右(同じ行で右)
+S-1 左下(1段下 & 1列左)
+S 真下(1段下)
+S+1 右下(1段下 & 1列右)

つぎのループでディレクションすべてを試しています

I.length

でもいいですがコードが長くなってしまうので採用はしませんでした。
simpleに8をループの中に入れれば済む話なので。

if (e >= 0 && e < P && F[e] && Math.abs((e % S) - (i % S)) <= 1)
    C++;

では判定が外に出ていないか、探索しているマスは生存しているか、行をまたいで判定していないかを判定しています。
そしてCをインクリメントします。
Cは周りに何個生存しているマスが存在しているかをカウントしている変数です。

N[i] = (!F[i] && C == 3) ? 1 : (F[i] && (C == 3 || C == 2)) ? 1 : 0;

Nのi番目の判定です。Nは最初空で設定しているのでF[i]を参照します。
!F[i]は自分自身のマスが死んでいることを示していて、C==3は周りに3つだけ生きたマスが存在しているかを確認しています。それがもしtrueなら自分のマスは誕生します。
次に F[i]が生きているかを判定して周りに生きたマスが3つか2つだけあるかも判定します。
それがもしtrueなら自分は生存して今までの判定が全てfalseなら自分自身のマスは死んでしまいます。

F = N;
R();

FをNで上書きした後R関数を呼び出してT関数は終わりです。

次に関数Rです

R=()=>{D.innerHTML="";for(i=0;i<P;i++){D.innerHTML+=(F[i])?
"":(i==X)?"":" ";D.innerHTML+=((i+1)%S==0)?"<br>":""}};

関数Rは描画関数です。
最初に、Dに書かれている文字を初期化します。
次にフィールド全体をループしてDに文字を書き込みます。

D.innerHTML+=(F[i])?"":(i==X)?"":" ";

まずFが生存しているマスなら"■"を表示します。
次にプレイヤーのカーソルの位置とiが一致していたら"@"を表示します
全部falseなら全角スペース(" ")を表示します。
その次に改行判定をします。

D.innerHTML+=((i+1)%S==0)?"<br>":""

この判定は意外と簡単でi+1をSで割った余りが0であるなら改行するという処理をかけば終わります。

最後に操作の処理です

D.onkeydown=(e
)=>{K=e.keyCode;if(K==32)T();if(K==39)X++;if(K==37)X--;if(K==38)X-=S;if(K==40)
X+=S;if(K==81){F[X]^=1;}R();};R()

見やすくします

D.onkeydown = (e) => {
    K = e.keyCode;
    if (K == 32) T();
    if (K == 39) X++;
    if (K == 37) X--;
    if (K == 38) X -= S;
    if (K == 40)X += S; 
    if (K == 81)F[X] ^= 1;
    R();
};
R()

キーコードですが
32はスペースキー、
39は右矢印キー、
37は左矢印キー、
38は上矢印キー、
40は下矢印キー、
81はQキーです。
スペースキーはステップを進める役割、
各矢印キーはプレイヤーの移動
Qキーは生死の反転をしています。
最後に、動かすごとに描画して終わりです。
全ての処理が書き終わった後、描画して終わりです。

最後に全体のコードを見やすくしたものを乗せて終わりたいと思います。

<body id=D>
<script>
F = [];
S = 20;
P = S * S;
for (i = 0; i < P; i++) F[i] = 0;
T = () => {
    N = [];
    for (i = 0; i < P; i++) {
        I = [-S - 1, -S, -S + 1, -1, 1, S - 1, S, S + 1];
        C = 0;
        for (j = 0; j < 8; j++) {
            e = i + I[j];
            if (e >= 0 && e < P && F[e] && Math.abs((e % S) - (i % S)) <= 1) {
                C++;
            }
        }
        N[i] = (!F[i] && C == 3) ? 1 : (F[i] && (C == 3 || C == 2)) ? 1 : 0;
    }
    F = N;
    R();
};
R = () => {
    D.innerHTML = "";
    for (i = 0; i < P; i++) {
        D.innerHTML += (F[i]) ? "" : (i == X) ? "" : " ";
        D.innerHTML += ((i + 1) % S == 0) ? "<br>" : "";
    }
};
K = 0;
X = 0;
D.onkeydown = (e) => {
    K = e.keyCode;
    if (K == 32) T();
    if (K == 39) X++;
    if (K == 37) X--; 
    if (K == 38) X -= S;
    if (K == 40) X += S;
    if (K == 81)F[X] ^= 1;
    R();
};
R();
</script>
</body>

^=1とは?(補足)^=1は XOR(排他的論理和) を使った代入演算子です

簡単に言えば
1 ^= 1なら0に、
0 ^= 1なら1に変わる演算です
0と1の切り替えを簡単に記述することができます

a^=1

の結果は下の表の通りになります

元の値 a a ^= 1 の結果
0 1
1 0

このように a^=1を使うことで0と1の切り替えを簡潔に行うことができます

最後に

ショートコーディングはいかに容量を減らせるかの戦い。
容量を減らすためにはいろいろなテクニックがいるわけです。
まだまだ僕のコードも削減できるところがあると思います。
皆さんもショートコーディングを始めてみるのもいいのではないでしょうか。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?