ゲーム

ライフゲームをWebブラウザで遊ぶ Conway’s Game of Life – Javascript (ソースコード公開あり)

投稿日:2020年2月11日 更新日:

ブラウザで遊べるライフゲーム(Conway’s Game of Life)を作成してみました。
緑の自セルを配置して赤い敵セルを消してみましょう。
基本的なパターンの動きを確認するも良し。
パターンについては以下をご参照ください。
ライフゲーム – Wikipedia

さらにオンライン対戦版もあります!
ライフゲームバトル
友達を誘って対戦してみましょう!

Android版(無料)はこちら
https://play.google.com/store/apps/details?id=jp.co.g_llc.lifegamebattlemobile

1P版(vs CPU)

[操作]
・自セル配置:左クリック(タップ)
・配置セルの投下:青ボタン or スペースキー
・1時停止:緑ボタン or 「p」キー
・オールクリア:白ボタン
・グライダー配置:「g」キー
・移動物体パターン向きの変更:十字キー
・隠しコマンド:あるキーを押すと宇宙船がでます。
自セルは緑。敵セルは赤。
敵セルを全て消すとレベルが上がり敵セルが増加します。
(ゲームが最新版になっていない時は、「Ctrl」+「F5」でリロードしてみてください。)

{ver0.4}
・マウスムーブでセル配置を可能に

{ver0.3}
・レベルの追加。

{ver0.2}
・フィールド拡大。
・敵セルの追加。

{ver0.1}
・グライダー配置の追加
・セル配置の音を変更しました。

{ver0.0}
・公開

オンライン対戦版

ライフゲームバトル (別タブ開きます)

グライダー銃が実装されました。


マックスが実装されました。

ソースコード(1P版)

雑なコードですが載せておきます。

window.focus();
enchant();

window.onload = function () 
{
	console.log( mHelloLifeGameFunc()  );

	var gWidth =  540; //432;
	var gHeight = gWidth + 100;
	var game = new Game(gWidth, gHeight); 
	game.fps = 15;

	var ClickSound = "mBeam.wav";//"click.wav";	
	game.preload([ClickSound]); 				

	var CellSound = "shotgun-pumpaction1.mp3";
	game.preload([CellSound]); 				


	var mImgUp = "./image/mUp.png";
	game.preload([mImgUp]);

	var mImgDown = "./image/mDown.png";
	game.preload([mImgDown]);

	var mImgRight = "./image/mRight.png";
	game.preload([mImgRight]);

	var mImgLeft = "./image/mLeft.png";
	game.preload([mImgLeft]);

	var mImgG = "./image/mG.png";
	game.preload([mImgG]);

	/////////////////////////////////////////////////


	game.onload = function () 
	{	//

		//game.assets[mBGM1].play();
		//var se = Sound.load("GB-Shooter-A05-1(Stage4).mp3");
		//se.play();

		/////////////////////////////////////////////////

		var Point = 0;									//ポイント
		var State = 0;								//現在のゲーム状態

		/////////////////////////////////////////////////

		var mGrey ='rgb(80,80,80)';

		var S_MAIN = new Scene();				
		game.pushScene(S_MAIN);  				
		S_MAIN.backgroundColor = 'rgba(50,50,50,1)'; 

		var S_Text = new Label(); 				
		S_Text.font = "14px Meiryo";			
		S_Text.color = 'rgba(0,255,0,1)';		
		S_Text.width = 150;						
		S_Text.moveTo(gWidth-150, gWidth+10);	
		S_MAIN.addChild(S_Text);				
		S_Text.text = "Cell:0";	

		var E_Text = new Label(); 				
		E_Text.font = "14px Meiryo";			
		E_Text.color = 'rgba(255,0,0,1)';		
		E_Text.width = 150;						
		E_Text.moveTo(gWidth-150, gWidth+30);	
		S_MAIN.addChild(E_Text);				
		E_Text.text = "Cell:0";	

		var C_Text = new Label(); 					
		C_Text.font = "12px Meiryo";				
		C_Text.color = 'rgba(255,255,255,1)';		
		C_Text.width = 100;						
		C_Text.moveTo(gWidth-100, gHeight-20);	
		S_MAIN.addChild(C_Text);				
		C_Text.text = "(c) 2020 G LLC";	


		var mPause = 0;
		var mGsize = 10;

		//--- cell ---//
		var mCells = Array();
		var mState = Array();// 0:dead, 1:alive(me), -1:alive(enemy)
		var mPreState = Array();
		var mReady = Array();
		var mLastPos = Array();// 0:x, 1:y
		mLastPos[0] = 0;
		mLastPos[1] = 0;

		var mCellSprite = new Sprite(gWidth, gHeight);
		var mCellSurface = new Surface(gWidth, gHeight);
		mCellSprite.image = mCellSurface;
		S_MAIN.addChild(mCellSprite);

		var nW = gWidth / mGsize;
		var nH = gWidth / mGsize;//Square
		for(i=0;i<nH;i++){ 
			for(j=0;j<nW;j++){
				var id_ = j+i*nW;  

				mState[id_] = 0;
				mPreState[id_] = 0;
				mReady[id_] = 0; 

				mCellSurface.context.beginPath();
				mCellSurface.context.fillStyle = mGrey;//'rgb(50, 50, 50)';
				mCellSurface.context.fillRect(mGsize*j, mGsize*i, mGsize, mGsize);//X、Y、W、H
				mCellSurface.context.strokeStyle = 'rgb(0, 0, 0)';
				mCellSurface.context.strokeRect(mGsize*j, mGsize*i, mGsize, mGsize);

			}//j
		}//i

		function mSetCellColor(id_)
		{
			var color_ = mGrey;

			if(mState[id_]==0){
				
			}
			else if(mState[id_]==1){
				color_ = 'rgb(0,255,0)';
			}
			else if(mState[id_]==-1){
				color_ = 'rgb(255,0,0)';
			}

			if(mReady[id_]==1){
				color_ = 'rgb(0,0,255)';
			}

			// mCells[id_].image.context.beginPath();
			// mCells[id_].image.context.fillStyle = color_;
			// mCells[id_].image.context.fillRect(0, 0, mGsize, mGsize);//X、Y、W、H
			// mCells[id_].image.context.strokeStyle = 'rgb(0, 0, 0)';
			// mCells[id_].image.context.strokeRect(0, 0, mGsize, mGsize);	

			var y = Math.floor(id_/nW);
			var x = id_%nW;

			mCellSprite.image.context.beginPath();
			mCellSprite.image.context.fillStyle = color_;
			mCellSprite.image.context.fillRect(x*mGsize, y*mGsize, mGsize, mGsize);//X、Y、W、H
			mCellSprite.image.context.strokeStyle = 'rgb(0, 0, 0)';
			mCellSprite.image.context.strokeRect(x*mGsize, y*mGsize, mGsize, mGsize);	

		}//


		//--- Enemy cell ---//
		// var array_enemy = [0,1,2,4,5,6,8,9,10,12,13,14,16,17,18];
		// for(j=0;j<array_enemy.length;j++){
		// 	id_ = array_enemy[j] + nW*2;
		// 	mState[id_]=-1;
		// 	mSetCellColor(id_);
		// }//
		// for(j=1;j<nW;j++){
		// 	if(j%4!=0){
		// 		id_ = j + nW*2;
		// 		mState[id_]=-1;
		// 		mSetCellColor(id_);
		// 	}
		// }//

		var mEnemyLevel = 1;

		function mSetEnemyCell()
		{
			var n_ = 10 + (mEnemyLevel-1)*5
			for(i=0;i<n_;i++){
				var x = Math.floor(Math.random()*1000000000) % nW;
				var y = Math.floor(Math.random()*1000000000) % nH;
				x = Math.max(x,1);
				x = Math.min(x,nW-2);
				y = Math.max(y,1);
				y = Math.min(y,nH-2);
	
				var id_ = x + y*nW;
				mState[id_-1]=-1;
				mState[id_]=-1;
				mState[id_+1]=-1;
				mSetCellColor(id_-1);
				mSetCellColor(id_);
				mSetCellColor(id_+1);
			}
		}

		mSetEnemyCell();

		function mGetAroundCellCount(i, j) 
		{
			//console.log('mGetCellCount[' + i +',' + j + ']');
			var is_ = Math.max(0,i-1);
			var ie_ = Math.min(nH-1,i+1);
			var js_ = Math.max(0,j-1);
			var je_ = Math.min(nW-1,j+1);
			//console.log(is_ +',' + ie_ +',' + js_ + ','+je_);

			var c = 0;
			var d = 0;
			for(i2=is_;i2<=ie_;i2++){ 
				for(j2=js_;j2<=je_;j2++)
				{
					//console.log(i2+',' + j2 );
					var idx_ = j2+i2*nW;
					d += mPreState[idx_];

					if( (i2==i) && (j2==j) ){
						continue;
					}

					if(mPreState[idx_]!=0){
						c += 1;
					}
				}
			}
		 
			//console.log('c:' + c);
			//return c;

			return Array(c,d);
		}// func


		function mUpdateCell()
		{
			console.log('mUpdateCell:');

			for(i=0;i<nH;i++){ 
				for(j=0;j<nW;j++){
					var id_ = j+i*nW;
					mPreState[id_]=mState[id_];
				}
			}	

			for(i=0;i<nH;i++){ 
				for(j=0;j<nW;j++){
					var id_ = j+i*nW;

					var a_ = mGetAroundCellCount(i, j);
					//console.log('id='+id_+', a:'+a_);
					var c_ = a_[0];
					var d_ = a_[1];
					//console.log('id='+id_+':'+c_+','+d_);
					if( (mPreState[id_]==0) && (c_!=3) ){ //その他
						mState[id_] = 0;
					}
					else if( (mPreState[id_]==0) && (c_==3) ){ //誕生
						mState[id_] = 1;
						if(d_<0){
							mState[id_] = -1;
						}
					}
					else if( (mPreState[id_]!=0) && ((c_==2) || (c_==3)) ){ //生存
						mState[id_] = 1;
						if(d_<0){
							mState[id_] = -1;
						}
					}
					else if( (mPreState[id_]!=0) && (c_<=1) ){ //過疎
						mState[id_] = 0;
					}
					else if( (mPreState[id_]!=0) && (c_>=4) ){ //過密
						mState[id_] = 0;
					}
					
				}
			}	

			//--- Updated Color ---//
			for(i=0;i<nH*nW;i++){ 
				if(mState[i]!=mPreState[i]){
					mSetCellColor(i);
				}
			}

		}//


		function mAddReadyCell()
		{
			console.log('mAddReadyCell:');

			for(i=0;i<nH;i++){ 
				for(j=0;j<nW;j++){
					var id_ = j+i*nW;
					if(mReady[id_]==1){
						mState[id_]=1;
						mReady[id_]=0;
						mSetCellColor(id_);
					}
				}
			}	
		}


		function mClearCell()
		{
			console.log('mClearCell:');

			for(i=0;i<nH;i++){ 
				for(j=0;j<nW;j++){
					var id_ = j+i*nW;
						mState[id_]=0;
						mReady[id_]=0;
						mSetCellColor(id_);
				}
			}	
		}


		function mGetCellNumber()
		{
			var c_ = 0;
			for(i=0;i<nH*nW;i++){ 
				if(mState[i]==1){
					c_ += 1;
				}
			}
			return c_;
		}//

		function mGetEnemyCellNumber()
		{
			var c_ = 0;
			for(i=0;i<nH*nW;i++){ 
				if(mState[i]==-1){
					c_ += 1;
				}
			}
			return c_;
		}//

		var mDrop = new Sprite(30,30);
		mDrop.moveTo(10, 10+gWidth);		
		var surface_ = new Surface(30, 30);
			surface_.context.beginPath();
			surface_.context.fillStyle = 'rgb(0,0,255)';
			surface_.context.fillRect(0, 0, 30, 30);//X、Y、W、H
		mDrop.image = surface_;
		S_MAIN.addChild(mDrop);


		mDrop.ontouchend = function () 
		{	
			game.assets[ClickSound].clone().play();		

			//mUpdateCell();
			mAddReadyCell();
		};


		//--- Touch event ---//
		var mTouchDown = 0;
		S_MAIN.addEventListener('touchstart', function(e) 
		{
			mTouchDown = 1;

			//console.log('touchstart:'+ 'X:' + e.localX + ',' + 'Y:' + e.localY);
			var cx = Math.floor(e.localX/mGsize);
			var cy = Math.floor(e.localY/mGsize);
			console.log('touchstart:'+ 'X:' + cx +','+'Y:'+cy);

			if( (cx>=0) && (cx<nW) && (cy>=0) && (cy<nH) ){
				mLastPos[0] = cx;
				mLastPos[1] = cy;
				game.assets[CellSound].clone().play();

				var idx_ = cy*nW+cx;
				mReady[idx_] = (mReady[idx_]+1)%2;
				console.log('mReady:' + mReady[idx_]);

				mSetCellColor(idx_);
			}
		});

		S_MAIN.addEventListener('touchmove', function(e) 
		{
			if( mTouchDown == 0 ){
				return;
			}


			var cx = Math.floor(e.localX/mGsize);
			var cy = Math.floor(e.localY/mGsize);
			console.log('touchmove:'+ 'X:' + cx +','+'Y:'+cy);

			if( (cx>=0) && (cx<nW) && (cy>=0) && (cy<nH) )
			{
				var idx_ = cy*nW+cx;
				var last_idx_ = mLastPos[1]*nW+mLastPos[0];
				if( idx_ != last_idx_ )
				{
					game.assets[CellSound].clone().play();

					mReady[idx_] = (mReady[idx_]+1)%2;
					console.log('mReady:' + mReady[idx_]);

					mSetCellColor(idx_);

					mLastPos[0] = cx;
					mLastPos[1] = cy;
				}

			}
		});

		S_MAIN.addEventListener('touchend', function(e) 
		{
			mTouchDown = 0;
		});

		
		//--- Key command ---//
		var mDirection = 0; //0:up, 1;right, 2:down, 3:left

		function mSetReadyCell(array_)
		{
			console.log('mSetReadyCell:'+ array_.length);
			var n_ = array_.length / 2;

			for(p=0;p<n_;p++){ 
				var i = array_[p*2+1];
				var j = array_[p*2+0];
				var id_ = j+i*nW;
				mReady[id_]=1;
				mSetCellColor(id_);
			}	
		}


		mDrop.addEventListener('enterframe', function(e) 
		{
			if (game.input.left){
				console.log('left pressed:'); //-> not shown
				//game.assets[CellSound].clone().play(); //-> executed
				mDirection = 3;
				game.assets[CellSound].clone().play();
				mDirIndicator.moveTo(mBtnLeft.x-3,mBtnLeft.y-3);
			}
			if (game.input.right){
				mDirection = 1;
				game.assets[CellSound].clone().play();
				mDirIndicator.moveTo(mBtnRight.x-3,mBtnRight.y-3);
			}
			if (game.input.up){
				mDirection = 0;
				game.assets[CellSound].clone().play();
				mDirIndicator.moveTo(mBtnUp.x-3,mBtnUp.y-3);
			}   
			if (game.input.down){
				mDirection = 2;
				game.assets[CellSound].clone().play();
				mDirIndicator.moveTo(mBtnDown.x-3,mBtnDown.y-3);
			} 
			//console.log('mDirection:'+mDirection);//->OK

			if (game.input.g){
				game.assets[CellSound].clone().play();
				//mCreateGlider();
				mSetReadyCell( mCreateGlider(mLastPos[0],mLastPos[1],nW,nH,mDirection) );
			} 

			if (game.input.s){
				game.assets[CellSound].clone().play();
				mSetReadyCell( mCreateSpaceShipS(mLastPos[0],mLastPos[1],nW,nH,mDirection) );
			} 


			if (game.input.p){
				mPause = (mPause+1)%2;
				game.assets[CellSound].clone().play();
			}

			if (game.input.space){
				game.assets[ClickSound].clone().play();
				mAddReadyCell();
			}

		});

		game.keybind(71,'g');
		game.keybind(80,'p');
		game.keybind(83,'s');
		game.keybind(32,'space');
		//------------


		var mDirIndicator = new Sprite(36,36);
		mDirIndicator.moveTo(-200, 0);		
		var surface_d = new Surface(36, 36);
			surface_d.context.beginPath();
			surface_d.context.fillStyle = 'rgb(0,255,255)';
			surface_d.context.fillRect(0, 0, 36, 36);//X、Y、W、H
		mDirIndicator.image = surface_d;
		S_MAIN.addChild(mDirIndicator);


		var px_mBtnUp = 90;
		var mBtnUp = new Sprite(30,30);
		mBtnUp.moveTo(px_mBtnUp, gWidth+10);		
		mBtnUp.image = game.assets[mImgUp];
		S_MAIN.addChild(mBtnUp);

		mBtnUp.ontouchend = function () 
		{	
			game.assets[CellSound].clone().play();
			mDirection = 0;
			mDirIndicator.moveTo(mBtnUp.x-3,mBtnUp.y-3);
		};


		var mBtnRight = new Sprite(30,30);
		mBtnRight.moveTo(px_mBtnUp+35, gWidth+10+25);		
		mBtnRight.image = game.assets[mImgRight];
		S_MAIN.addChild(mBtnRight);

		mBtnRight.ontouchend = function () 
		{	
			game.assets[CellSound].clone().play();
			mDirection = 1;
			mDirIndicator.moveTo(mBtnRight.x-3,mBtnRight.y-3);
		};


		var mBtnDown = new Sprite(30,30);
		mBtnDown.moveTo(px_mBtnUp, gWidth+10+50);		
		mBtnDown.image = game.assets[mImgDown];
		S_MAIN.addChild(mBtnDown);

		mBtnDown.ontouchend = function () 
		{	
			game.assets[CellSound].clone().play();
			mDirection = 2;
			mDirIndicator.moveTo(mBtnDown.x-3,mBtnDown.y-3);
		};


		var mBtnLeft = new Sprite(30,30);
		mBtnLeft.moveTo(px_mBtnUp-35, gWidth+10+25);		
		mBtnLeft.image = game.assets[mImgLeft];
		S_MAIN.addChild(mBtnLeft);

		mBtnLeft.ontouchend = function () 
		{	
			game.assets[CellSound].clone().play();
			mDirection = 3;
			mDirIndicator.moveTo(mBtnLeft.x-3,mBtnLeft.y-3);
		};


		var mBtnGlider = new Sprite(30,30);
		mBtnGlider.moveTo(200, gWidth+10);		
		mBtnGlider.image = game.assets[mImgG];
		S_MAIN.addChild(mBtnGlider);

		mBtnGlider.ontouchend = function () 
		{	
			game.assets[CellSound].clone().play();		
			//mCreateGlider();
			mSetReadyCell( mCreateGlider(mLastPos[0],mLastPos[1],nW,nH,mDirection) );
		};


		var mClear = new Sprite(30,30);
		mClear.moveTo(350, gWidth+10+50);		
		var surface_c = new Surface(30, 30);
			surface_c.context.beginPath();
			surface_c.context.fillStyle = 'rgb(255,255,255)';
			surface_c.context.fillRect(0, 0, 30, 30);//X、Y、W、H
		mClear.image = surface_c;
		S_MAIN.addChild(mClear);

		mClear.ontouchend = function () 
		{	
			game.assets[ClickSound].clone().play();		
			mClearCell();
		};


		var mBtnPause = new Sprite(30,30);
		mBtnPause.moveTo(350, gWidth+10);		
		var surface_p = new Surface(30, 30);
			surface_p.context.beginPath();
			surface_p.context.fillStyle = 'rgb(0,200,0)';
			surface_p.context.fillRect(0, 0, 30, 30);//X、Y、W、H
		mBtnPause.image = surface_p;
		S_MAIN.addChild(mBtnPause);

		mBtnPause.ontouchend = function () 
		{	
			game.assets[ClickSound].clone().play();		
			mPause = (mPause+1)%2;
		};


		///////////////////////////////////////////////////
		//Main Loop
		game.onenterframe = function () 
		{
			if(mPause==0)
			{
				mUpdateCell();
				S_Text.text = "My Cell:" + mGetCellNumber(); 
				E_Text.text = "Enemy Lv."+mEnemyLevel+ ":" + mGetEnemyCellNumber(); 
				//mPause = 1;

				if(mGetEnemyCellNumber()==0){
					mSetEnemyCell();
					mEnemyLevel += 1;

					//game.assets[mBGM1].play();
				}
			}

		};


	};// onload

	game.start();
	
};


///----------

ソースコード(オンライン対戦版)

ご自由に改変してお楽しみください。

https://github.com/scriptma-n/conway-game-of-life-JS

-ゲーム

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

弾丸と壁/人の接触判定を少し真面目に実装してみる|ゲーム作成

フォートナイトみたいなオンラインバトロワゲームを作っています。優秀なサンプルコードから作成を開始したので早い段階で一応形にはなりました。しかし、サンプルをいじりすぎて接触判定に矛盾が生じてきてしまいま …

Microsoft AzureでNode.jsゲームをデプロイ – web.configを忘れるな

Node.js+Three.jsでフォートナイトのような建築FPSゲームを自作しております。Paasサービスは手軽で太っ腹なHerokuを利用していますがサーバーがアメリカにあるため通信ラグに悩まされ …

Macでフォートナイトが起動できない場合の対処 – MacBook Pro

目次 チャプター2シーズン4Macでチャプター2シーズン4をプレイする方法チャプター2シーズン3Epic Game Launcherが起動できない場合の対処法諦めてゲーミングWindowsPCを購入 …

no image

ショーモナイノ/ ソースコード(クライアントサイド)

サーバサイドのソースコードを公開したところ、結構ビュー数が伸びているようです。なのでクライアントサイドも公開しておきます。何かの役に立てればと思います。 内容はとんでもないジャンクコードとなっています …

Webブラウザ版フォートナイト?を自作してみた(その2)|Node.js + Three.js

1年半前くらいに簡易版フォートナイトをNode.jsとJavascriptで自作して放置していましたが、もう少し本家に近づけようと思いコードを改善し始めました。以前よりフォートナイトプレイの腕が上がっ …

スポンサーリンク