情報学部 菅沼ホーム JavaScript 目次 索引

ぷよぷよ

  1. ステップ1: ゲームの枠組み

      ぷよぷよ風のゲームです.同じ色のピースが指定した数(ここでは,4 個)以上繋がると消去されます.基本的に,「ゲーム枠の作成」で説明した方法とほぼ同じ方法で作成します.ただし,画面のサイズは変更しています.また,ゲームクリアの画面やゲームのレベルは存在しません.以下,各プログラムに対して,「ゲーム枠の作成」の場合との違いについて説明していきます.

    1. HTML ファイル

        「ゲーム枠の作成」における HTML ファイルとほとんど同じです.ただし,「ゲームクリア」ボタンと「ゲームオーバー」ボタンを削除し,「回転」,「左」,「右」,及び,「色交換」ボタンを追加してあります.

      <!DOCTYPE HTML>
      <HTML>
      <HEAD>
      	<TITLE>ぷよぷよ:ステップ1</TITLE>
      	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
      	<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
      	<LINK REL="stylesheet" TYPE="text/css" HREF="../../../master.css">
      	<SCRIPT TYPE="text/javascript" SRC="main/MainPanel.js"></SCRIPT>
      	<SCRIPT TYPE="text/javascript" SRC="start/StartPanel.js"></SCRIPT>
      	<SCRIPT TYPE="text/javascript" SRC="game/GamePanel.js"></SCRIPT>
      	<SCRIPT TYPE="text/javascript" SRC="over/GameOverPanel.js"></SCRIPT>
      </HEAD>
      <BODY CLASS="eeffee" onLoad="mp_start()">
      	<H1>ぷよぷよ:ステップ1</H1>
      	<CANVAS ID="canvas_e" STYLE="background-color: #ffffff;" WIDTH="150" HEIGHT="360"></CANVAS><BR>
      	<A HREF="method.htm" TARGET="method"><BUTTON ID="method" CLASS="std">遊び方</BUTTON></A>
      	<BUTTON ID="start" CLASS="std" onClick="gp_start()">ゲーム開始</BUTTON>
      	<BUTTON ID="finish" CLASS="std" onClick="mp.finish()">終了</BUTTON>
      	<BUTTON ID="rot" CLASS="std" onClick="gp.py.operation(0)">回転</BUTTON><BR>
      	<BUTTON ID="left" CLASS="std" onClick="gp.py.operation(1)">左</BUTTON>
      	<BUTTON ID="right" CLASS="std" onClick="gp.py.operation(2)">右</BUTTON><BR>
      	<BUTTON ID="color" CLASS="std" onClick="gp.py.operation(3)">色交換</BUTTON>
      </BODY>
      </HTML>
      				

    2. MainPanel

        このプログラムに関しても,「ゲーム枠の作成」における MainPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.

      mp = null;   // MainPanel オブジェクト
      
      			//
      			// MainPanel の開始
      			//
      function mp_start()
      {
      					// キャンバス情報
      	let canvas = document.getElementById('canvas_e');   // キャンバス要素の取得
      	let ctx    = canvas.getContext('2d');   // キャンバスからコンテキストを取得
      					// MainPanel オブジェクト
      	mp = new MainPanel(canvas, ctx);
      					// StartPanel の表示
      	st_start();
      }
      			//
      			// MainPanel オブジェクト(プロパティ)
      			//
      function MainPanel(canvas, ctx)
      {
      	this.canvas = canvas;   // キャンバス要素
      	this.ctx    = ctx;   // キャンバスのコンテキスト
      	return this;
      }
      			//
      			// MainPanel オブジェクト(メソッド)
      			//
      MainPanel.prototype.finish = function()
      {
      					// キャンバスのクリア
      	mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      					// ボタンを非表示
      	document.getElementById('method').style.display = "none";
      	document.getElementById('start').style.display = "none";
      	document.getElementById('finish').style.display = "none";
      	document.getElementById('rot').style.display = "none";
      	document.getElementById('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      	document.getElementById('color').style.display = "none";
      }
      				

    3. StartPanel

        このプログラムに関しても,「ゲーム枠の作成」における StartPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.当然のことながら,ゲームタイトル及び「遊び方」の内容を変更しています.

      			//
      			// StartPanel の開始
      			//
      function st_start()
      {
      					// キャンバスのクリア
      	mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      					// ゲームタイトルの表示
      	mp.ctx.font = "40px 'MS ゴシック'";
      	mp.ctx.textBaseline = "middle";
      	mp.ctx.textAlign = "center";
      	mp.ctx.fillStyle = "rgb(0, 0, 0)";
      	mp.ctx.fillText("ぷよ", mp.canvas.width/2, mp.canvas.height/2-20);
      	mp.ctx.fillText("ぷよ", mp.canvas.width/2, mp.canvas.height/2+20);
      					// ボタンの表示制御
      	document.getElementById('method').style.display = "";
      	document.getElementById('start').style.display = "";
      	document.getElementById('finish').style.display = "none";
      	document.getElementById('rot').style.display = "none";
      	document.getElementById('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      	document.getElementById('color').style.display = "none";
      	document.getElementById('start').innerHTML = "ゲーム開始";
      }
      				

    4. GamePanel クラス

        GamePanel は,実際のゲームを実現する部分です.従って,「ゲーム枠の作成」における GamePanel とは,ゲームの種類によってその内容は大きく異なります.今後,このプログラムを完成させていくことになりますが,ここでは,必要なパネルを貼り付け,指定した位置にピースを 1 つだけ表示させています.

      01	gp = null;   // GamePanel オブジェクト
      02	
      03				//
      04				// GamePanel の開始
      05				//
      06	function gp_start()
      07	{
      08						// GamePanel オブジェクト
      09		gp = new GamePanel();
      10						// ピースの位置
      11		gp.py.p_x = 1;   // 左から2列目
      12		gp.py.p_y = 3;   // 上から4行目
      13		gp.py.p[gp.py.p_y][gp.py.p_x]   = 2;   // ピンク
      14		gp.py.p[gp.py.p_y][gp.py.p_x+1] = 3;   // 緑
      15		gp.py.draw();
      16						// ボタンの表示制御
      17		document.getElementById('method').style.display = "none";
      18		document.getElementById('start').style.display = "none";
      19		document.getElementById('finish').style.display = "none";
      20		document.getElementById('rot').style.display = "";
      21		document.getElementById('left').style.display = "";
      22		document.getElementById('right').style.display = "";
      23		document.getElementById('color').style.display = "";
      24	}
      25				//
      26				// GamePanel オブジェクト(プロパティ)
      27				//
      28	function GamePanel()
      29	{
      30		this.py = new Puyo();
      31		return this;
      32	}
      33				//
      34				// Puyo オブジェクト(プロパティ)
      35				//
      36	function Puyo()
      37	{
      38		this.p_x;   // 左または上のピースの座標(横)
      39		this.p_y;   // 左または上のピースの座標(縦)
      40		this.row = 12;   // ゲーム画面の行数
      41		this.col = 5;   // ゲーム画面の列数
      42		this.width = 30;   // ピースの幅と高さ
      43		this.rot;   // 0:横,1:縦
      44		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
      45		for (let i1 = 0; i1 < this.row; i1++) {
      46			this.p[i1] = new Array();
      47			for (let i2 = 0; i2 < this.col; i2++)
      48				this.p[i1][i2] = 0;
      49		}
      50		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
      51		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
      52		return this;
      53	}
      54				//
      55				// Puyo オブジェクト(メソッド draw)
      56				//
      57	Puyo.prototype.draw = function()
      58	{
      59						// キャンバスのクリア
      60		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      61						// 描画
      62		for (let i1 = 0; i1 < gp.py.row; i1++) {
      63			for (let i2 = 0; i2 < gp.py.col; i2++) {
      64				if (gp.py.p[i1][i2] > 0) {
      65					mp.ctx.beginPath();
      66					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
      67					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
      68					mp.ctx.fill();
      69				}
      70			}
      71		}
      72	}
      73				//
      74				// Puyo オブジェクト(メソッド operation)
      75				//
      76	Puyo.prototype.operation = function(sw)
      77	{
      78		gop_start();
      79	}
      				
      09 行目( gp_start 関数)

        GamePanel オブジェクトを生成し,その結果をグローバル変数 gp( 01 行目)に代入しています.具体的には,28 行目~ 32 行目に記述された GamePanel 関数が実行されます.

      11 行目~ 15 行目( gp_start 関数)

        プログラムの確認のため,指定した位置に Puyo オブジェクトのメソッド draw( 57 行目~ 72 行目)を使用してピースを描いています.各変数の意味については,Puyo オブジェクトの項で説明します.なお,この部分は,ステップ3で変更します.

      30 行目( MainPanel 関数)

        Puyo オブジェクトを生成しています.具体的には,36 行目~ 53 行目に記述された Puyo 関数が実行されます.

      38,39 行目( Puyo 関数)

        現在操作対象としている(落下中の)ピースが,セル上のどの位置にいるかを表すプロパティです.横並びの場合は左側のピースの位置,縦並びの場合は上のピースの位置を示しています.

      43 行目( Puyo 関数)

        2 つピースを横に並べた状態で落下を開始しますが,後ほど述べる操作によって縦に並べることも可能です.プロパティ rot は,現在,横に並んでいるのか( rot = 0 ),または,縦に並んでいるのか( rot = 1 )を示します.

      44 行目~ 49 行目( Puyo 関数)

        このプログラムでは,各セルの状態を 12 行 5 列の 2 次元配列で表すことにします.ここに示すように,2 次元配列は,まず配列を定義し( 44 行目),その配列の各要素を再び配列として定義する( 46 行目)ことによって可能です.各要素の値の意味する所は,コメントに記述したとおりです.

        一般に,配列の各要素を配列として定義することによって,多次元配列を定義することが可能です.以下に示すのは 2 行 3 列の 2 次元配列の例です.
      	let a = new Array(2);   // let a = new Array(); でも可
      	for (let i1 = 0; i1 < 2; i1++)
      		a[i1] = new Array(3);					
      初期設定も同時に行いたい場合は,例えば,以下のようにして行います.
      	let a = new Array(2);   // let a = new Array(); でも可
      	a[0] = new Array(1, 2, 3);
      	a[1] = new Array(4, 5, 6);					
      50 行目( Puyo 関数)

        ピースの色はランダムに指定されますが,そこで使用する色を,fillStyle メソッドに設定する値の配列として定義しています( 66 行目参照).

      57 行目~ 72 行目( draw メソッド)

        Puyo オブジェクトのプロパティ p の値に従って,ピースを描画しています.

      76 行目~ 79 行目( operation メソッド)

        Puyo オブジェクトのメソッド operation の定義です.「左」(ピースを左へ移動),「右」(ピースを右へ移動),「回転」(横を縦に,または,縦を横に変更),「色交換」(左右または上下の色を交換)ボタンがクリックされた時の処理を行います.ただし,現段階では,ゲーム状態の変化を確認するために,いずれのボタンをクリックしてもゲームオーバーの状態に移行します.

    5. GameOverPanel クラス

        このプログラムに関しても,「ゲーム枠の作成」における GameOverPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.

      			//
      			// GameOverPanel の開始
      			//
      function gop_start()
      {
      					// キャンバスのクリア
      	mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      					// タイトルの表示
      	mp.ctx.font = "40px 'MS ゴシック'";
      	mp.ctx.textBaseline = "middle";
      	mp.ctx.textAlign = "center";
      	mp.ctx.fillStyle = "rgb(0, 0, 0)";
      	mp.ctx.fillText("Game", mp.canvas.width/2, mp.canvas.height/2-20);
      	mp.ctx.fillText("Over!", mp.canvas.width/2, mp.canvas.height/2+20);
      					// ボタンの表示制御
      	document.getElementById('method').style.display = "none";
      	document.getElementById('start').style.display = "";
      	document.getElementById('finish').style.display = "";
      	document.getElementById('rot').style.display = "none";
      	document.getElementById('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      	document.getElementById('color').style.display = "none";
      	document.getElementById('start').innerHTML = "再開";
      }
      				

  2. ステップ2: ピースの落下

      ここでは,ステップ1で表示したピースを落下させ,画面の一番下で停止させる処理を行います.GamePanel の gp_start 関数,及び,GamePanel 関数を修正すると共に,Puyo オブジェクトに drop メソッドを追加します.

    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// GamePanel オブジェクト
    009		gp = new GamePanel();
    010						// ピースの位置
    011		gp.py.p_x = 1;
    012		gp.py.p_y = 3;
    013		gp.py.p[gp.py.p_y][gp.py.p_x]   = 2;
    014		gp.py.p[gp.py.p_y][gp.py.p_x+1] = 3;
    015		gp.py.draw();
    016						// タイマーのスタート
    017		gp.timerID = setInterval('gp.py.drop()', 500);
    018						// ボタンの表示制御
    019		document.getElementById('method').style.display = "none";
    020		document.getElementById('start').style.display = "none";
    021		document.getElementById('finish').style.display = "none";
    022		document.getElementById('rot').style.display = "";
    023		document.getElementById('left').style.display = "";
    024		document.getElementById('right').style.display = "";
    025		document.getElementById('color').style.display = "";
    026	}
    027				//
    028				// GamePanel オブジェクト(プロパティ)
    029				//
    030	function GamePanel()
    031	{
    032		this.timerID = -1;
    033		this.in_game = true;   // ゲーム中か否か
    034		this.py = new Puyo();
    035		return this;
    036	}
    037				//
    038				// Puyo オブジェクト(プロパティ)
    039				//
    040	function Puyo()
    041	{
    042		this.p_x;   // 左または上のピースの座標(横)
    043		this.p_y;   // 左または上のピースの座標(縦)
    044		this.row = 12;   // ゲーム画面の行数
    045		this.col = 5;   // ゲーム画面の列数
    046		this.width = 30;   // ピースの幅と高さ
    047		this.rot;   // 0:横,1:縦
    048		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    049		for (let i1 = 0; i1 < this.row; i1++) {
    050			this.p[i1] = new Array();
    051			for (let i2 = 0; i2 < this.col; i2++)
    052				this.p[i1][i2] = 0;
    053		}
    054		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    055		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    056		return this;
    057	}
    058				//
    059				// Puyo オブジェクト(メソッド drop)
    060				//
    061	Puyo.prototype.drop = function()
    062	{
    063						// ピースの落下
    064		if (gp.py.p_y < gp.py.row-1) {
    065			gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    066			gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    067			gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    068			gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    069			gp.py.p_y++;
    070		}
    071						// 再描画
    072		if (gp.in_game)
    073			gp.py.draw();
    074	}
    075				//
    076				// Puyo オブジェクト(メソッド draw)
    077				//
    078	Puyo.prototype.draw = function()
    079	{
    080						// キャンバスのクリア
    081		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    082						// 描画
    083		for (let i1 = 0; i1 < gp.py.row; i1++) {
    084			for (let i2 = 0; i2 < gp.py.col; i2++) {
    085				if (gp.py.p[i1][i2] > 0) {
    086					mp.ctx.beginPath();
    087					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    088					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    089					mp.ctx.fill();
    090				}
    091			}
    092		}
    093	}
    094				//
    095				// Puyo オブジェクト(メソッド operation)
    096				//
    097	Puyo.prototype.operation = function(sw)
    098	{
    099		clearInterval(gp.timerID);   // タイマーの停止
    100		gop_start();
    101	}
    			
    17 行目( gp_start 関数)

      500 ms 毎に Puyo オブジェクトのメソッド drop( 061 行目~ 074 行目)を実行するタイマーをスタートさせます.

    032,033 行目( MainPanel 関数)

      タイマーとゲーム中であるか否かを示す 2 つのプロパティを追加します.

    064 行目~ 070 行目( drop メソッド)

      ピースが最下段に到達していない場合は,ピースを落下させています.

    072,073 行目( drop メソッド)

      ゲームオーバーでない場合は,再描画します.

  3. ステップ3: ピースの生成と落下

      ここでは,画面の最上部(水平位置はランダム)にピースを生成し,そのピースを落下させ,画面の一番下で停止させる処理を行います.なお,ピースが停止すると,次のピースが生成され,落下が再び始まります.GamePanel の gp_start 関数,及び,drop メソッドを修正すると共に,Puyo オブジェクトに select メソッドを追加します.

    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// GamePanel オブジェクト
    009		gp = new GamePanel();
    010						// ピースの選択
    011		gp.py.select();
    012		gp.py.draw();
    013						// タイマーのスタート
    014		gp.timerID = setInterval('gp.py.drop()', 500);
    015						// ボタンの表示制御
    016		document.getElementById('method').style.display = "none";
    017		document.getElementById('start').style.display = "none";
    018		document.getElementById('finish').style.display = "none";
    019		document.getElementById('rot').style.display = "";
    020		document.getElementById('left').style.display = "";
    021		document.getElementById('right').style.display = "";
    022		document.getElementById('color').style.display = "";
    023	}
    024				//
    025				// GamePanel オブジェクト(プロパティ)
    026				//
    027	function GamePanel()
    028	{
    029		this.timerID = -1;
    030		this.in_game = true;   // ゲーム中か否か
    031		this.py = new Puyo();
    032		return this;
    033	}
    034				//
    035				// Puyo オブジェクト(プロパティ)
    036				//
    037	function Puyo()
    038	{
    039		this.p_x;   // 左または上のピースの座標(横)
    040		this.p_y;   // 左または上のピースの座標(縦)
    041		this.row = 12;   // ゲーム画面の行数
    042		this.col = 5;   // ゲーム画面の列数
    043		this.width = 30;   // ピースの幅と高さ
    044		this.rot;   // 0:横,1:縦
    045		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    046		for (let i1 = 0; i1 < this.row; i1++) {
    047			this.p[i1] = new Array();
    048			for (let i2 = 0; i2 < this.col; i2++)
    049				this.p[i1][i2] = 0;
    050		}
    051		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    052		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    053		return this;
    054	}
    055				//
    056				// Puyo オブジェクト(メソッド drop)
    057				//
    058	Puyo.prototype.drop = function()
    059	{
    060						// ピースの落下
    061		if (gp.py.p_y < gp.py.row-1) {
    062			gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    063			gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    064			gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    065			gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    066			gp.py.p_y++;
    067		}
    068		else
    069			gp.py.select();   // 次のピース
    070						// 再描画
    071		if (gp.in_game)
    072			gp.py.draw();
    073	}
    074				//
    075				// Puyo オブジェクト(メソッド select)
    076				//
    077	Puyo.prototype.select = function()
    078	{
    079		gp.py.p_y = 0;
    080		gp.py.p_x = Math.floor((gp.py.col - 1) * Math.random());
    081		gp.py.p[0][gp.py.p_x]   = 1 + Math.floor(4 * Math.random());
    082		gp.py.p[0][gp.py.p_x+1] = 1 + Math.floor(4 * Math.random());
    083	}
    084				//
    085				// Puyo オブジェクト(メソッド draw)
    086				//
    087	Puyo.prototype.draw = function()
    088	{
    089						// キャンバスのクリア
    090		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    091						// 描画
    092		for (let i1 = 0; i1 < gp.py.row; i1++) {
    093			for (let i2 = 0; i2 < gp.py.col; i2++) {
    094				if (gp.py.p[i1][i2] > 0) {
    095					mp.ctx.beginPath();
    096					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    097					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    098					mp.ctx.fill();
    099				}
    100			}
    101		}
    102	}
    103				//
    104				// Puyo オブジェクト(メソッド operation)
    105				//
    106	Puyo.prototype.operation = function(sw)
    107	{
    108		clearInterval(gp.timerID);   // タイマーの停止
    109		gop_start();
    110	}
    			
    011,012 行目( gp_start 関数)

      今まで適当な位置にピースを存在させていたのをやめ,Puyo オブジェクトのメソッド select( 077 行目~ 083 行目)を利用してピースを生成し,描画しています.

    068,069 行目( drop メソッド)

      ピースが最下段に到達した場合は,新しいピースを生成しています.

    077 行目~ 083 行目( select メソッド)

      新しいピースを最上段に生成しています.ピースの色と横位置はランダムに設定しています.

  4. ステップ4: ピースの移動・回転・色の交換

      ここでは,ピースの左右への移動,回転,色の交換の処理を行います.修正するプログラムは,GamePanel の Puyo 関数,drop メソッド,select メソッド,及び,operation メソッドです.

    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// GamePanel オブジェクト
    009		gp = new GamePanel();
    010						// ピースの選択
    011		gp.py.select();
    012		gp.py.draw();
    013						// タイマーのスタート
    014		gp.timerID = setInterval('gp.py.drop()', 500);
    015						// ボタンの表示制御
    016		document.getElementById('method').style.display = "none";
    017		document.getElementById('start').style.display = "none";
    018		document.getElementById('finish').style.display = "none";
    019		document.getElementById('rot').style.display = "";
    020		document.getElementById('left').style.display = "";
    021		document.getElementById('right').style.display = "";
    022		document.getElementById('color').style.display = "";
    023	}
    024				//
    025				// GamePanel オブジェクト(プロパティ)
    026				//
    027	function GamePanel()
    028	{
    029		this.timerID = -1;
    030		this.in_game = true;   // ゲーム中か否か
    031		this.py = new Puyo();
    032		return this;
    033	}
    034				//
    035				// Puyo オブジェクト(プロパティ)
    036				//
    037	function Puyo()
    038	{
    039		this.p_x;   // 左または上のピースの座標(横)
    040		this.p_y;   // 左または上のピースの座標(縦)
    041		this.row = 12;   // ゲーム画面の行数
    042		this.col = 5;   // ゲーム画面の列数
    043		this.width = 30;   // ピースの幅と高さ
    044		this.rot;   // 0:横,1:縦
    045		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    046		for (let i1 = 0; i1 < this.row; i1++) {
    047			this.p[i1] = new Array();
    048			for (let i2 = 0; i2 < this.col; i2++)
    049				this.p[i1][i2] = 0;
    050		}
    051		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    052		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    053		this.ok = true;   // 移動,回転,色の交換が可能か?
    054		return this;
    055	}
    056				//
    057				// Puyo オブジェクト(メソッド drop)
    058				//
    059	Puyo.prototype.drop = function()
    060	{
    061						// ピースの落下
    062		let ct = 0;
    063								// 横
    064		if (gp.py.rot == 0) {
    065			if (gp.py.p_y < gp.py.row-1) {
    066				ct = 1;
    067				gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    068				gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    069				gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    070				gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    071				gp.py.p_y++;
    072			}
    073			else
    074				gp.py.ok = false;
    075		}
    076								// 縦
    077		else {
    078			if (gp.py.p_y < gp.py.row-2) {
    079				ct = 1;
    080				gp.py.p[gp.py.p_y+2][gp.py.p_x] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    081				gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    082				gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    083				gp.py.p_y++;
    084			}
    085			else
    086				gp.py.ok = false;
    087		}
    088						// 次のピース
    089		if (ct == 0)
    090			gp.py.select();
    091						// 再描画
    092		if (gp.in_game)
    093			gp.py.draw();
    094	}
    095				//
    096				// Puyo オブジェクト(メソッド select)
    097				//
    098	Puyo.prototype.select = function()
    099	{
    100		gp.py.ok  = true;
    101		gp.py.rot = 0;
    102		gp.py.p_y = 0;
    103		gp.py.p_x = Math.floor((gp.py.col - 1) * Math.random());
    104		gp.py.p[0][gp.py.p_x]   = 1 + Math.floor(4 * Math.random());
    105		gp.py.p[0][gp.py.p_x+1] = 1 + Math.floor(4 * Math.random());
    106	}
    107				//
    108				// Puyo オブジェクト(メソッド draw)
    109				//
    110	Puyo.prototype.draw = function()
    111	{
    112						// キャンバスのクリア
    113		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    114						// 描画
    115		for (let i1 = 0; i1 < gp.py.row; i1++) {
    116			for (let i2 = 0; i2 < gp.py.col; i2++) {
    117				if (gp.py.p[i1][i2] > 0) {
    118					mp.ctx.beginPath();
    119					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    120					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    121					mp.ctx.fill();
    122				}
    123			}
    124		}
    125	}
    126				//
    127				// Puyo オブジェクト(メソッド operation)
    128				//
    129	Puyo.prototype.operation = function(sw)
    130	{
    131		if (gp.py.ok) {
    132			switch (sw) {
    133						// 左移動
    134				case 0:
    135					if (gp.py.p_x > 0) {
    136						if (gp.py.rot == 0 && gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0) {
    137							gp.py.p[gp.py.p_y][gp.py.p_x-1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    138							gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    139							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    140							gp.py.p_x--;
    141						}
    142						else if (gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x-1] == 0) {
    143							gp.py.p[gp.py.p_y][gp.py.p_x-1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    144							gp.py.p[gp.py.p_y+1][gp.py.p_x-1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    145							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    146							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    147							gp.py.p_x--;
    148						}
    149					}
    150					break;
    151						// 右移動
    152				case 1:
    153					if (gp.py.rot == 0) {
    154						if (gp.py.p_x < gp.py.col-2 && gp.py.p[gp.py.p_y][gp.py.p_x+2] == 0) {
    155							gp.py.p[gp.py.p_y][gp.py.p_x+2] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    156							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    157							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    158							gp.py.p_x++;
    159						}
    160					}
    161					else {
    162						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    163							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    164							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    165							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    166							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    167							gp.py.p_x++;
    168						}
    169					}
    170					break;
    171						// 上下または左右入れ替え
    172				case 2:
    173					if (gp.py.rot == 0) {
    174						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    175						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    176						gp.py.p[gp.py.p_y][gp.py.p_x+1] = k;
    177					}
    178					else {
    179						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    180						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    181						gp.py.p[gp.py.p_y+1][gp.py.p_x] = k;
    182					}
    183					break;
    184						// 90度または-90度回転
    185				case 3:
    186					if (gp.py.rot == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    187						if (gp.py.p_y < gp.py.row-1) {
    188							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    189							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    190							gp.py.rot = 1;
    191						}
    192					}
    193					else {
    194						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0) {
    195							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    196							gp.py.p[gp.py.p_y+1][gp.py.p_x] = 0;
    197							gp.py.rot = 0;
    198						}
    199					}
    200					break;
    201			}
    202						// 再描画
    203			if (gp.in_game)
    204				gp.py.draw();
    205		}
    206	}
    			
    053 行目( Puyo 関数)

      ピースが最下段に到着した場合や他のピースの上に積み上がった場合(後ほどの処理)には,移動,回転,色の交換を禁止するため,Puyo オブジェクトにプロパティ ok を定義しました.ok が true の場合だけ,移動,回転,色の交換が可能です.

    064 行目~ 087 行目( drop メソッド)

      横並びと縦並びの場合に分けて,ピースの落下処理を行っています.ct は落下できたか否かを表す変数(その値が 1 の時は落下できたことを示す)であり,落下できなかった場合(最下段に達した場合)は,次のピースを生成しています( 090 行目).また,落下できなかった場合は,Puyo オブジェクトのプロパティ ok を false に設定し,以後,移動,回転,色の交換を不可能にしています.

    100,101 行目( select メソッド)

      新しく生成するピースに対して,移動,回転,色の交換を可能にし,ピースを横並びとしています.

    135 行目~ 149 行目( operation メソッド)

      左への移動処理を行っています.その際,画面の外に出ないか,また,移動先に他のピースが存在しないかのチェックを行っています.switch 文( 132 行目~ 201 行目)の一般形式は以下の通りです.
      switch (式) {
        [case 定数式1  :]
          [文1]
        [case 定数式2  :]
          [文2]
          ・・・・・
        [default  :]
          [文n]
      }				
      まず,式が評価されます.その値が定数式の値のいずれかに等しければ,それ以降の文が実行されます(以降であることに注意).もちろん,文 i は,複数の文でも構いません.いずれの定数式の値にも一致しない場合,もし,default キーワードの項があればそれ以降が実行され,そうでなければ,何も実行されず switch 文以降の文が実行されます.各文 i の最後が break 文である場合は,上記の awitch 文は,
      if (式 == 定数式1)
    	  [文1]
      else if (式 == 定数式2)
    	  [文2]
         ・・・・・
      else
        [文n]				
    と等しくなります.しかし,例えば,文1の最後が break 文でなかった場合は,文1に続き,文2が実行されます.

    153 行目~ 169 行目( operation メソッド)

      右への移動処理を行っています.その際,画面の外に出ないか,また,移動先に他のピースが存在しないかのチェックを行っています.

    173 行目~ 182 行目( operation メソッド)

      現在のピースの並び方を調べ( 173 行目),左右または上下の色を交換しています.

    186 行目~ 199 行目( operation メソッド)

      現在のピースの並び方,ピースの位置,回転したときに障害となるピースの存在を調べ,90 度,または,-90 度の回転を行っています.その際,オブジェクトのプロパティ rot の再設定も行っています.

  5. ステップ5: ピースの積み上げ

      ここでは,ピースを他のピースの上に積み上げていきます.縦並びの場合はそれほど問題ではありませんが,横並びの場合は,例えば,左側のピースが他のピースの上に乗ったが,右側のピースの下には何もない場合,ピースを分離し,右側のピースだけを落下させる処理が必要となります.なお,ゲームオーバーの処理も追加してあります.修正するプログラムは,GamePanel の drop メソッド,及び,select メソッドです.

    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// GamePanel オブジェクト
    009		gp = new GamePanel();
    010						// ピースの選択
    011		gp.py.select();
    012		gp.py.draw();
    013						// タイマーのスタート
    014		gp.timerID = setInterval('gp.py.drop()', 500);
    015						// ボタンの表示制御
    016		document.getElementById('method').style.display = "none";
    017		document.getElementById('start').style.display = "none";
    018		document.getElementById('finish').style.display = "none";
    019		document.getElementById('rot').style.display = "";
    020		document.getElementById('left').style.display = "";
    021		document.getElementById('right').style.display = "";
    022		document.getElementById('color').style.display = "";
    023	}
    024				//
    025				// GamePanel オブジェクト(プロパティ)
    026				//
    027	function GamePanel()
    028	{
    029		this.timerID = -1;
    030		this.in_game = true;   // ゲーム中か否か
    031		this.py = new Puyo();
    032		return this;
    033	}
    034				//
    035				// Puyo オブジェクト(プロパティ)
    036				//
    037	function Puyo()
    038	{
    039		this.p_x;   // 左または上のピースの座標(横)
    040		this.p_y;   // 左または上のピースの座標(縦)
    041		this.row = 12;   // ゲーム画面の行数
    042		this.col = 5;   // ゲーム画面の列数
    043		this.width = 30;   // ピースの幅と高さ
    044		this.rot;   // 0:横,1:縦
    045		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    046		for (let i1 = 0; i1 < this.row; i1++) {
    047			this.p[i1] = new Array();
    048			for (let i2 = 0; i2 < this.col; i2++)
    049				this.p[i1][i2] = 0;
    050		}
    051		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    052		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    053		this.ok = true;   // 移動,回転,色の交換が可能か?
    054		return this;
    055	}
    056				//
    057				// Puyo オブジェクト(メソッド drop)
    058				//
    059	Puyo.prototype.drop = function()
    060	{
    061						// ピースの落下
    062		let ct = 0;
    063								// 横
    064		if (gp.py.rot == 0) {
    065			if (gp.py.p_y < gp.py.row-1) {
    066				if (gp.py.ok) {
    067					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    068						ct = 1;
    069						gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    070						gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    071						gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    072						gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    073						gp.py.p_y++;
    074					}
    075					else {
    076						gp.py.ok = false;
    077						if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    078							ct = 1;
    079							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    080							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    081							gp.py.p_y++;
    082						}
    083						else if (gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    084							ct = 1;
    085							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    086							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    087							gp.py.p_x++;
    088							gp.py.p_y++;
    089						}
    090					}
    091				}
    092				else {
    093					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    094						ct = 1;
    095						gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    096						gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    097						gp.py.p_y++;
    098					}
    099				}
    100			}
    101			else
    102				gp.py.ok = false;
    103		}
    104								// 縦
    105		else {
    106			if (gp.py.p_y < gp.py.row-2 && gp.py.p[gp.py.p_y+2][gp.py.p_x] == 0) {
    107				ct = 1;
    108				gp.py.p[gp.py.p_y+2][gp.py.p_x] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    109				gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    110				gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    111				gp.py.p_y++;
    112			}
    113			else
    114				gp.py.ok = false;
    115		}
    116						// 次のピース
    117		if (ct == 0)
    118			gp.py.select();
    119						// 再描画
    120		if (gp.in_game)
    121			gp.py.draw();
    122	}
    123				//
    124				// Puyo オブジェクト(メソッド select)
    125				//
    126	Puyo.prototype.select = function()
    127	{
    128		gp.py.ok  = true;
    129		gp.py.rot = 0;
    130		gp.py.p_y = 0;
    131		gp.py.p_x = Math.floor((gp.py.col - 1) * Math.random());
    132		if (gp.py.p[0][gp.py.p_x] == 0 && gp.py.p[0][gp.py.p_x+1] == 0) {
    133			gp.py.p[0][gp.py.p_x]   = 1 + Math.floor(4 * Math.random());
    134			gp.py.p[0][gp.py.p_x+1] = 1 + Math.floor(4 * Math.random());
    135		}
    136		else {   // ゲームオーバー
    137			gp.in_game = false;
    138			clearInterval(gp.py.timerID);   // タイマーの停止
    139			gop_start();
    140		}
    141	}
    142				//
    143				// Puyo オブジェクト(メソッド draw)
    144				//
    145	Puyo.prototype.draw = function()
    146	{
    147						// キャンバスのクリア
    148		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    149						// 描画
    150		for (let i1 = 0; i1 < gp.py.row; i1++) {
    151			for (let i2 = 0; i2 < gp.py.col; i2++) {
    152				if (gp.py.p[i1][i2] > 0) {
    153					mp.ctx.beginPath();
    154					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    155					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    156					mp.ctx.fill();
    157				}
    158			}
    159		}
    160	}
    161				//
    162				// Puyo オブジェクト(メソッド operation)
    163				//
    164	Puyo.prototype.operation = function(sw)
    165	{
    166		if (gp.py.ok) {
    167			switch (sw) {
    168						// 左移動
    169				case 0:
    170					if (gp.py.p_x > 0) {
    171						if (gp.py.rot == 0 && gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0) {
    172							gp.py.p[gp.py.p_y][gp.py.p_x-1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    173							gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    174							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    175							gp.py.p_x--;
    176						}
    177						else if (gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x-1] == 0) {
    178							gp.py.p[gp.py.p_y][gp.py.p_x-1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    179							gp.py.p[gp.py.p_y+1][gp.py.p_x-1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    180							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    181							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    182							gp.py.p_x--;
    183						}
    184					}
    185					break;
    186						// 右移動
    187				case 1:
    188					if (gp.py.rot == 0) {
    189						if (gp.py.p_x < gp.py.col-2 && gp.py.p[gp.py.p_y][gp.py.p_x+2] == 0) {
    190							gp.py.p[gp.py.p_y][gp.py.p_x+2] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    191							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    192							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    193							gp.py.p_x++;
    194						}
    195					}
    196					else {
    197						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    198							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    199							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    200							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    201							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    202							gp.py.p_x++;
    203						}
    204					}
    205					break;
    206						// 上下または左右入れ替え
    207				case 2:
    208					if (gp.py.rot == 0) {
    209						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    210						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    211						gp.py.p[gp.py.p_y][gp.py.p_x+1] = k;
    212					}
    213					else {
    214						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    215						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    216						gp.py.p[gp.py.p_y+1][gp.py.p_x] = k;
    217					}
    218					break;
    219						// 90度または-90度回転
    220				case 3:
    221					if (gp.py.rot == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    222						if (gp.py.p_y < gp.py.row-1) {
    223							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    224							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    225							gp.py.rot = 1;
    226						}
    227					}
    228					else {
    229						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0) {
    230							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    231							gp.py.p[gp.py.p_y+1][gp.py.p_x] = 0;
    232							gp.py.rot = 0;
    233						}
    234					}
    235					break;
    236			}
    237						// 再描画
    238			if (gp.in_game)
    239				gp.py.draw();
    240		}
    241	}
    			
    066 行目( drop メソッド)

      ピースが横に並んでいる場合,1 つのピースだけが他のピースの上に乗った場合も,オブジェクトのプロパティ ok を false にして,移動,回転,色の交換を不可能にしています.従って,ok が true であることは,2 つのピースが横に並んでいる場合を表します.

    068 行目~ 073 行目( drop メソッド)

      横に並んだ 2 つのピースが,そろって落下できる場合の処理です.

    076 行目~ 089 行目( drop メソッド)

      左右いずれかのピースだけが他のピースの上に乗った場合の処理です.オブジェクトのプロパティ ok を false に設定し,乗らなかったピースを分離し,そのピースだけを落下させていきます.

    094 行目~ 097 行目( drop メソッド)

      分離された 1 つのピースに対する落下処理です.

    107 行目~ 111 行目( drop メソッド)

      縦に並んだピースに対する落下処理です.

    137 行目~ 139 行目( select メソッド)

      新しく生成したピースの一部が,既に存在するピースと重なった場合,ゲームオーバーになります(ピースが,画面上部まで積み上がった場合に発生する).

  6. ステップ6: 完成

      指定された数(ここでは,4 )以上,同じ色のピースが繋がった場合,それらのピースを削除します.この処理を加え,ゲームは完成です.GamePanel の Puyo 関数,及び,drop メソッドを修正すると共に,Puyo オブジェクトに search メソッド,及び,del メソッドを追加します.

    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// GamePanel オブジェクト
    009		gp = new GamePanel();
    010						// ピースの選択
    011		gp.py.select();
    012		gp.py.draw();
    013						// タイマーのスタート
    014		gp.timerID = setInterval('gp.py.drop()', 500);
    015						// ボタンの表示制御
    016		document.getElementById('method').style.display = "none";
    017		document.getElementById('start').style.display = "none";
    018		document.getElementById('finish').style.display = "none";
    019		document.getElementById('rot').style.display = "";
    020		document.getElementById('left').style.display = "";
    021		document.getElementById('right').style.display = "";
    022		document.getElementById('color').style.display = "";
    023	}
    024				//
    025				// GamePanel オブジェクト(プロパティ)
    026				//
    027	function GamePanel()
    028	{
    029		this.timerID = -1;
    030		this.in_game = true;   // ゲーム中か否か
    031		this.py = new Puyo();
    032		return this;
    033	}
    034				//
    035				// Puyo オブジェクト(プロパティ)
    036				//
    037	function Puyo()
    038	{
    039		this.p_x;   // 左または上のピースの座標(横)
    040		this.p_y;   // 左または上のピースの座標(縦)
    041		this.row = 12;   // ゲーム画面の行数
    042		this.col = 5;   // ゲーム画面の列数
    043		this.width = 30;   // ピースの幅と高さ
    044		this.rot;   // 0:横,1:縦
    045		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    046		for (let i1 = 0; i1 < this.row; i1++) {
    047			this.p[i1] = new Array();
    048			for (let i2 = 0; i2 < this.col; i2++)
    049				this.p[i1][i2] = 0;
    050		}
    051		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    052		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    053		this.ok = true;   // 移動,回転,色の交換が可能か?
    054		this.d_no = 4;   // 同じ色のピースがd_no以上連続すると消去
    055		this.pp = new Array();   // work
    056		for (let i1 = 0; i1 < this.row; i1++)
    057			this.pp[i1] = new Array();
    058		return this;
    059	}
    060				//
    061				// Puyo オブジェクト(メソッド drop)
    062				//
    063	Puyo.prototype.drop = function()
    064	{
    065						// ピースの落下
    066		let ct = 0;
    067								// 横
    068		if (gp.py.rot == 0) {
    069			if (gp.py.p_y < gp.py.row-1) {
    070				if (gp.py.ok) {
    071					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    072						ct = 1;
    073						gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    074						gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    075						gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    076						gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    077						gp.py.p_y++;
    078					}
    079					else {
    080						gp.py.ok = false;
    081						if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    082							ct = 1;
    083							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    084							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    085							gp.py.p_y++;
    086						}
    087						else if (gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    088							ct = 1;
    089							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    090							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    091							gp.py.p_x++;
    092							gp.py.p_y++;
    093						}
    094					}
    095				}
    096				else {
    097					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    098						ct            = 1;
    099						gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    100						gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    101						gp.py.p_y++;
    102					}
    103				}
    104			}
    105			else
    106				gp.py.ok = false;
    107		}
    108								// 縦
    109		else {
    110			if (gp.py.p_y < gp.py.row-2 && gp.py.p[gp.py.p_y+2][gp.py.p_x] == 0) {
    111				ct = 1;
    112				gp.py.p[gp.py.p_y+2][gp.py.p_x] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    113				gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    114				gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    115				gp.py.p_y++;
    116			}
    117			else
    118				gp.py.ok = false;
    119		}
    120						// 消去と次のピース
    121		if (ct == 0) {
    122			ct = gp.py.d_no;
    123			while (ct >= gp.py.d_no) {
    124				ct = 0;
    125				for (let i1 = gp.py.row-1; i1 >= 0 && ct < gp.py.d_no; i1--) {
    126					for (let i2 = 0; i2 < gp.py.col && ct < gp.py.d_no; i2++) {
    127						if (gp.py.p[i1][i2] > 0) {
    128							for (let i3 = 0; i3 < gp.py.row; i3++) {
    129								for (let i4 = 0; i4 < gp.py.col; i4++)
    130									gp.py.pp[i3][i4] = 0;
    131							}
    132							gp.py.pp[i1][i2] = 1;
    133							ct = gp.py.search(i1, i2, 1);
    134						}
    135					}
    136				}
    137				if (ct >= gp.py.d_no)
    138					gp.py.del();
    139			}
    140			gp.py.select();
    141		}
    142						// 再描画
    143		if (gp.in_game)
    144			gp.py.draw();
    145	}
    146				//
    147				// Puyo オブジェクト(メソッド select)
    148				//
    149	Puyo.prototype.select = function()
    150	{
    151		gp.py.ok  = true;
    152		gp.py.rot = 0;
    153		gp.py.p_y = 0;
    154		gp.py.p_x = Math.floor((gp.py.col - 1) * Math.random());
    155		if (gp.py.p[0][gp.py.p_x] == 0 && gp.py.p[0][gp.py.p_x+1] == 0) {
    156			gp.py.p[0][gp.py.p_x]   = 1 + Math.floor(4 * Math.random());
    157			gp.py.p[0][gp.py.p_x+1] = 1 + Math.floor(4 * Math.random());
    158		}
    159		else {   // ゲームオーバー
    160			gp.in_game = false;
    161			clearInterval(gp.py.timerID);   // タイマーの停止
    162			gop_start();
    163		}
    164	}
    165				//
    166				// 同じ色のピースを探す
    167				//      k1,k2 : 対象としているピース
    168				//      c1 : 同じ色のピースの数
    169				//      return : 同じ色のピースの数
    170				//
    171	Puyo.prototype.search = function(k1, k2, c1)
    172	{
    173		let ct = c1;
    174	
    175		if (k1 > 0 && gp.py.p[k1-1][k2] == gp.py.p[k1][k2] && gp.py.pp[k1-1][k2] == 0) {
    176			gp.py.pp[k1-1][k2] = 1;
    177			ct = gp.py.search(k1-1, k2, ct+1);
    178		}
    179		if (k1 < gp.py.row-1 && gp.py.p[k1+1][k2] == gp.py.p[k1][k2] && gp.py.pp[k1+1][k2] == 0) {
    180			gp.py.pp[k1+1][k2] = 1;
    181			ct = gp.py.search(k1+1, k2, ct+1);
    182		}
    183		if (k2 > 0 && gp.py.p[k1][k2-1] == gp.py.p[k1][k2] && gp.py.pp[k1][k2-1] == 0) {
    184			gp.py.pp[k1][k2-1] = 1;
    185			ct = gp.py.search(k1, k2-1, ct+1);
    186		}
    187		if (k2 < gp.py.col-1 && gp.py.p[k1][k2+1] == gp.py.p[k1][k2] && gp.py.pp[k1][k2+1] == 0) {
    188			gp.py.pp[k1][k2+1] = 1;
    189			ct = gp.py.search(k1, k2+1, ct+1);
    190		}
    191	
    192		return ct;
    193	}
    194				//
    195				// 同じ色のピースを削除
    196				//
    197	Puyo.prototype.del = function()
    198	{
    199						// 削除
    200		for (let i1 = 0; i1 < gp.py.row; i1++) {
    201			for (let i2 = 0; i2 < gp.py.col; i2++) {
    202				if (gp.py.pp[i1][i2]  > 0)
    203					gp.py.p[i1][i2] = 0;
    204			}
    205		}
    206						// 詰める
    207		for (let i1 = 0; i1 < gp.py.col; i1++) {
    208			k1 = 1;
    209			for (let i2 = gp.py.row-1; i2 > 0 && k1 >= 0; i2--) {
    210				if (gp.py.p[i2][i1] == 0) {
    211					k1 = -1;
    212					for (i3 = i2-1; i3 >= 0 && k1 < 0; i3--) {
    213						if (gp.py.p[i3][i1] > 0)
    214							k1 = i3;
    215					}
    216					if (k1 >= 0) {
    217						k2 = i2;
    218						k3 = k2 - k1;
    219						while (k1 >= 0) {
    220							gp.py.p[k2][i1] = gp.py.p[k1][i1];
    221							k1--;
    222							k2--;
    223						}
    224						k1++;
    225						for (i3 = 0; i3 < k3; i3++)
    226							gp.py.p[i3][i1] = 0;
    227					}
    228				}
    229			}
    230		}
    231	}
    232				//
    233				// Puyo オブジェクト(メソッド draw)
    234				//
    235	Puyo.prototype.draw = function()
    236	{
    237						// キャンバスのクリア
    238		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    239						// 描画
    240		for (let i1 = 0; i1 < gp.py.row; i1++) {
    241			for (let i2 = 0; i2 < gp.py.col; i2++) {
    242				if (gp.py.p[i1][i2] > 0) {
    243					mp.ctx.beginPath();
    244					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    245					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    246					mp.ctx.fill();
    247				}
    248			}
    249		}
    250	}
    251				//
    252				// Puyo オブジェクト(メソッド operation)
    253				//
    254	Puyo.prototype.operation = function(sw)
    255	{
    256		if (gp.py.ok) {
    257			switch (sw) {
    258						// 左移動
    259				case 0:
    260					if (gp.py.p_x > 0) {
    261						if (gp.py.rot == 0 && gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0) {
    262							gp.py.p[gp.py.p_y][gp.py.p_x-1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    263							gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    264							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    265							gp.py.p_x--;
    266						}
    267						else if (gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x-1] == 0) {
    268							gp.py.p[gp.py.p_y][gp.py.p_x-1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    269							gp.py.p[gp.py.p_y+1][gp.py.p_x-1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    270							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    271							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    272							gp.py.p_x--;
    273						}
    274					}
    275					break;
    276						// 右移動
    277				case 1:
    278					if (gp.py.rot == 0) {
    279						if (gp.py.p_x < gp.py.col-2 && gp.py.p[gp.py.p_y][gp.py.p_x+2] == 0) {
    280							gp.py.p[gp.py.p_y][gp.py.p_x+2] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    281							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    282							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    283							gp.py.p_x++;
    284						}
    285					}
    286					else {
    287						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    288							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    289							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    290							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    291							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    292							gp.py.p_x++;
    293						}
    294					}
    295					break;
    296						// 上下または左右入れ替え
    297				case 2:
    298					if (gp.py.rot == 0) {
    299						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    300						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    301						gp.py.p[gp.py.p_y][gp.py.p_x+1] = k;
    302					}
    303					else {
    304						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    305						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    306						gp.py.p[gp.py.p_y+1][gp.py.p_x] = k;
    307					}
    308					break;
    309						// 90度または-90度回転
    310				case 3:
    311					if (gp.py.rot == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    312						if (gp.py.p_y < gp.py.row-1) {
    313							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    314							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    315							gp.py.rot = 1;
    316						}
    317					}
    318					else {
    319						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0) {
    320							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    321							gp.py.p[gp.py.p_y+1][gp.py.p_x] = 0;
    322							gp.py.rot = 0;
    323						}
    324					}
    325					break;
    326			}
    327						// 再描画
    328			if (gp.in_game)
    329				gp.py.draw();
    330		}
    331	}
    			
    054 行目~ 057 行目( Puyo 関数)

      Puyo オブジェクトに対して,同じ色のピースが何個以上繋がったら削除するかを示すプロパティ d_no と作業域として使用する配列 pp を定義しています.

    122 行目~ 139 行目( drop メソッド)

      d_no 個以上繋がったピースを削除するための処理です.この処理は,ピースの移動が停止した後( 121 行目)に行われ,削除処理の終了後,次のピースを生成します( 140 行目).実際に削除する部分は 124 行目~ 138 行目ですが,この処理だけですと,削除された後新たに d_no 個以上繋がったピースが現れた場合の処理を行えません.この点を考慮して,この部分を 123 行目の while 文で繰り返しています.

      あるセルにピースが存在した場合は( 127 行目),セルの状態を表すプロパティ p と同じサイズの配列 pp のすべての値を 0 に設定した後,ピースが存在する場所だけを 1 に設定し,そのピースと繋がっている同じ色のピースの数をメソッド search を利用して数えます( 133 行目).この結果,繋がっているピースの数( ct )が戻されると共に,プロパティ pp の繋がっているピースの位置に対応する要素の値が 1 にセットされます.繋がったピースの数が d_no 以上であった場合は,プロパティ p,及び,pp の情報を使用して,それらのピースを削除します( 138 行目).

    171 行目~ 193 行目( search メソッド)

      繋がった状態の同じ色のピースを数えるためのメソッドです.指定されたピースの上下左右のピースの色が,指定されたピースと同じ場合は,プロパティ pp のその場所に対応する要素の値を 1 に設定し,再帰的に search を呼びます.

    197 行目~ 231 行目( del メソッド)

      同じ色で繋がったピースを削除するためのメソッドです.まず,200 行目~ 205 行目において繋がったピースを削除した後,ピースの縦の並びの間に空いたセルがある場合は,上のピースを落下させて空いたセルがないようにします.

  7. ステップ6: 完成( BGM 付き)

      参考のため,BGM を付加した例を示しておきます.追加・修正した部分は,以下の通りです.なお,BGM は,平成 25 年度に本学を卒業した斉藤亮太さんに作成してもらいました.

    • HTML ファイル: 22 行目
    • GamePanel: 9 ~ 11 行目,164 ~ 165 行目

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>ぷよぷよ:ステップ6(完成)</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<LINK REL="stylesheet" TYPE="text/css" HREF="../../../master.css">
    07		<SCRIPT TYPE="text/javascript" SRC="main/MainPanel.js"></SCRIPT>
    08		<SCRIPT TYPE="text/javascript" SRC="start/StartPanel.js"></SCRIPT>
    09		<SCRIPT TYPE="text/javascript" SRC="game/GamePanel.js"></SCRIPT>
    10		<SCRIPT TYPE="text/javascript" SRC="over/GameOverPanel.js"></SCRIPT>
    11	</HEAD>
    12	<BODY CLASS="eeffee" onLoad="mp_start()">
    13		<H1>ぷよぷよ:ステップ6(完成)</H1>
    14		<CANVAS ID="canvas_e" STYLE="background-color: #ffffff;" WIDTH="150" HEIGHT="360"></CANVAS><BR>
    15		<A HREF="method.htm" TARGET="method"><BUTTON ID="method" CLASS="std">遊び方</BUTTON></A>
    16		<BUTTON ID="start" CLASS="std" onClick="gp_start()">ゲーム開始</BUTTON>
    17		<BUTTON ID="finish" CLASS="std" onClick="mp.finish()">終了</BUTTON>
    18		<BUTTON ID="rot" CLASS="std" onClick="gp.py.operation(3)">回転</BUTTON><BR>
    19		<BUTTON ID="left" CLASS="std" onClick="gp.py.operation(0)">左</BUTTON>
    20		<BUTTON ID="right" CLASS="std" onClick="gp.py.operation(1)">右</BUTTON><BR>
    21		<BUTTON ID="color" CLASS="std" onClick="gp.py.operation(2)">色交換</BUTTON>
    22		<AUDIO ID="BGM" LOOP SRC="Puyo_BGM.mp3"></AUDIO>  <!-- BGMのために追加 -->
    23	</BODY>
    24	</HTML>
    			
    001	gp = null;   // GamePanel オブジェクト
    002	
    003				//
    004				// GamePanel の開始
    005				//
    006	function gp_start()
    007	{
    008						// BGM の再生
    009		document.getElementById('BGM').volume = 0.5;
    010		document.getElementById('BGM').play();
    011		document.getElementById('BGM').currentTime = 0.5;   // 約0.5秒の空白をスキップ
    012						// GamePanel オブジェクト
    013		gp = new GamePanel();
    014						// ピースの選択
    015		gp.py.select();
    016		gp.py.draw();
    017						// タイマーのスタート
    018		gp.timerID = setInterval('gp.py.drop()', 500);
    019						// ボタンの表示制御
    020		document.getElementById('method').style.display = "none";
    021		document.getElementById('start').style.display = "none";
    022		document.getElementById('finish').style.display = "none";
    023		document.getElementById('rot').style.display = "";
    024		document.getElementById('left').style.display = "";
    025		document.getElementById('right').style.display = "";
    026		document.getElementById('color').style.display = "";
    027	}
    028				//
    029				// GamePanel オブジェクト(プロパティ)
    030				//
    031	function GamePanel()
    032	{
    033		this.timerID = -1;
    034		this.in_game = true;   // ゲーム中か否か
    035		this.py = new Puyo();
    036		return this;
    037	}
    038				//
    039				// Puyo オブジェクト(プロパティ)
    040				//
    041	function Puyo()
    042	{
    043		this.p_x;   // 左または上のピースの座標(横)
    044		this.p_y;   // 左または上のピースの座標(縦)
    045		this.row = 12;   // ゲーム画面の行数
    046		this.col = 5;   // ゲーム画面の列数
    047		this.width = 30;   // ピースの幅と高さ
    048		this.rot;   // 0:横,1:縦
    049		this.p = new Array();   // 画面の状態(0:存在しない,1:赤,2:ピンク,3:緑,4:青)
    050		for (let i1 = 0; i1 < this.row; i1++) {
    051			this.p[i1] = new Array();
    052			for (let i2 = 0; i2 < this.col; i2++)
    053				this.p[i1][i2] = 0;
    054		}
    055		this.color = new Array("rgb(255,0,0)", "rgb(255,192,203)",
    056		                       "rgb(0,255,0)", "rgb(0,0,255)");   // ピースの色
    057		this.ok = true;   // 移動,回転,色の交換が可能か?
    058		this.d_no = 4;   // 同じ色のピースがd_no以上連続すると消去
    059		this.pp = new Array();   // work
    060		for (let i1 = 0; i1 < this.row; i1++)
    061			this.pp[i1] = new Array();
    062		return this;
    063	}
    064				//
    065				// Puyo オブジェクト(メソッド drop)
    066				//
    067	Puyo.prototype.drop = function()
    068	{
    069						// ピースの落下
    070		let ct = 0;
    071								// 横
    072		if (gp.py.rot == 0) {
    073			if (gp.py.p_y < gp.py.row-1) {
    074				if (gp.py.ok) {
    075					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    076						ct = 1;
    077						gp.py.p[gp.py.p_y+1][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    078						gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    079						gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    080						gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    081						gp.py.p_y++;
    082					}
    083					else {
    084						gp.py.ok = false;
    085						if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    086							ct = 1;
    087							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    088							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    089							gp.py.p_y++;
    090						}
    091						else if (gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    092							ct = 1;
    093							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    094							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = 0;
    095							gp.py.p_x++;
    096							gp.py.p_y++;
    097						}
    098					}
    099				}
    100				else {
    101					if (gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    102						ct            = 1;
    103						gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    104						gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    105						gp.py.p_y++;
    106					}
    107				}
    108			}
    109			else
    110				gp.py.ok = false;
    111		}
    112								// 縦
    113		else {
    114			if (gp.py.p_y < gp.py.row-2 && gp.py.p[gp.py.p_y+2][gp.py.p_x] == 0) {
    115				ct = 1;
    116				gp.py.p[gp.py.p_y+2][gp.py.p_x] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    117				gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x];
    118				gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    119				gp.py.p_y++;
    120			}
    121			else
    122				gp.py.ok = false;
    123		}
    124						// 消去と次のピース
    125		if (ct == 0) {
    126			ct = gp.py.d_no;
    127			while (ct >= gp.py.d_no) {
    128				ct = 0;
    129				for (let i1 = gp.py.row-1; i1 >= 0 && ct < gp.py.d_no; i1--) {
    130					for (let i2 = 0; i2 < gp.py.col && ct < gp.py.d_no; i2++) {
    131						if (gp.py.p[i1][i2] > 0) {
    132							for (let i3 = 0; i3 < gp.py.row; i3++) {
    133								for (let i4 = 0; i4 < gp.py.col; i4++)
    134									gp.py.pp[i3][i4] = 0;
    135							}
    136							gp.py.pp[i1][i2] = 1;
    137							ct = gp.py.search(i1, i2, 1);
    138						}
    139					}
    140				}
    141				if (ct >= gp.py.d_no)
    142					gp.py.del();
    143			}
    144			gp.py.select();
    145		}
    146						// 再描画
    147		if (gp.in_game)
    148			gp.py.draw();
    149	}
    150				//
    151				// Puyo オブジェクト(メソッド select)
    152				//
    153	Puyo.prototype.select = function()
    154	{
    155		gp.py.ok  = true;
    156		gp.py.rot = 0;
    157		gp.py.p_y = 0;
    158		gp.py.p_x = Math.floor((gp.py.col - 1) * Math.random());
    159		if (gp.py.p[0][gp.py.p_x] == 0 && gp.py.p[0][gp.py.p_x+1] == 0) {
    160			gp.py.p[0][gp.py.p_x]   = 1 + Math.floor(4 * Math.random());
    161			gp.py.p[0][gp.py.p_x+1] = 1 + Math.floor(4 * Math.random());
    162		}
    163		else {   // ゲームオーバー
    164			document.getElementById('BGM').pause();   // BGMのために追加
    165			document.getElementById('BGM').load();   // BGMのために追加
    166			gp.in_game = false;
    167			clearInterval(gp.py.timerID);   // タイマーの停止
    168			gop_start();
    169		}
    170	}
    171				//
    172				// 同じ色のピースを探す
    173				//      k1,k2 : 対象としているピース
    174				//      c1 : 同じ色のピースの数
    175				//      return : 同じ色のピースの数
    176				//
    177	Puyo.prototype.search = function(k1, k2, c1)
    178	{
    179		let ct = c1;
    180	
    181		if (k1 > 0 && gp.py.p[k1-1][k2] == gp.py.p[k1][k2] && gp.py.pp[k1-1][k2] == 0) {
    182			gp.py.pp[k1-1][k2] = 1;
    183			ct = gp.py.search(k1-1, k2, ct+1);
    184		}
    185		if (k1 < gp.py.row-1 && gp.py.p[k1+1][k2] == gp.py.p[k1][k2] && gp.py.pp[k1+1][k2] == 0) {
    186			gp.py.pp[k1+1][k2] = 1;
    187			ct = gp.py.search(k1+1, k2, ct+1);
    188		}
    189		if (k2 > 0 && gp.py.p[k1][k2-1] == gp.py.p[k1][k2] && gp.py.pp[k1][k2-1] == 0) {
    190			gp.py.pp[k1][k2-1] = 1;
    191			ct = gp.py.search(k1, k2-1, ct+1);
    192		}
    193		if (k2 < gp.py.col-1 && gp.py.p[k1][k2+1] == gp.py.p[k1][k2] && gp.py.pp[k1][k2+1] == 0) {
    194			gp.py.pp[k1][k2+1] = 1;
    195			ct = gp.py.search(k1, k2+1, ct+1);
    196		}
    197	
    198		return ct;
    199	}
    200				//
    201				// 同じ色のピースを削除
    202				//
    203	Puyo.prototype.del = function()
    204	{
    205						// 削除
    206		for (let i1 = 0; i1 < gp.py.row; i1++) {
    207			for (let i2 = 0; i2 < gp.py.col; i2++) {
    208				if (gp.py.pp[i1][i2]  > 0)
    209					gp.py.p[i1][i2] = 0;
    210			}
    211		}
    212						// 詰める
    213		for (let i1 = 0; i1 < gp.py.col; i1++) {
    214			k1 = 1;
    215			for (let i2 = gp.py.row-1; i2 > 0 && k1 >= 0; i2--) {
    216				if (gp.py.p[i2][i1] == 0) {
    217					k1 = -1;
    218					for (i3 = i2-1; i3 >= 0 && k1 < 0; i3--) {
    219						if (gp.py.p[i3][i1] > 0)
    220							k1 = i3;
    221					}
    222					if (k1 >= 0) {
    223						k2 = i2;
    224						k3 = k2 - k1;
    225						while (k1 >= 0) {
    226							gp.py.p[k2][i1] = gp.py.p[k1][i1];
    227							k1--;
    228							k2--;
    229						}
    230						k1++;
    231						for (i3 = 0; i3 < k3; i3++)
    232							gp.py.p[i3][i1] = 0;
    233					}
    234				}
    235			}
    236		}
    237	}
    238				//
    239				// Puyo オブジェクト(メソッド draw)
    240				//
    241	Puyo.prototype.draw = function()
    242	{
    243						// キャンバスのクリア
    244		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    245						// 描画
    246		for (let i1 = 0; i1 < gp.py.row; i1++) {
    247			for (let i2 = 0; i2 < gp.py.col; i2++) {
    248				if (gp.py.p[i1][i2] > 0) {
    249					mp.ctx.beginPath();
    250					mp.ctx.fillStyle = gp.py.color[gp.py.p[i1][i2]-1];
    251					mp.ctx.fillRect(i2*gp.py.width, i1*gp.py.width, gp.py.width, gp.py.width);
    252					mp.ctx.fill();
    253				}
    254			}
    255		}
    256	}
    257				//
    258				// Puyo オブジェクト(メソッド operation)
    259				//
    260	Puyo.prototype.operation = function(sw)
    261	{
    262		if (gp.py.ok) {
    263			switch (sw) {
    264						// 左移動
    265				case 0:
    266					if (gp.py.p_x > 0) {
    267						if (gp.py.rot == 0 && gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0) {
    268							gp.py.p[gp.py.p_y][gp.py.p_x-1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    269							gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    270							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    271							gp.py.p_x--;
    272						}
    273						else if (gp.py.p[gp.py.p_y][gp.py.p_x-1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x-1] == 0) {
    274							gp.py.p[gp.py.p_y][gp.py.p_x-1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    275							gp.py.p[gp.py.p_y+1][gp.py.p_x-1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    276							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    277							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    278							gp.py.p_x--;
    279						}
    280					}
    281					break;
    282						// 右移動
    283				case 1:
    284					if (gp.py.rot == 0) {
    285						if (gp.py.p_x < gp.py.col-2 && gp.py.p[gp.py.p_y][gp.py.p_x+2] == 0) {
    286							gp.py.p[gp.py.p_y][gp.py.p_x+2] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    287							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y][gp.py.p_x];
    288							gp.py.p[gp.py.p_y][gp.py.p_x]   = 0;
    289							gp.py.p_x++;
    290						}
    291					}
    292					else {
    293						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x+1] == 0) {
    294							gp.py.p[gp.py.p_y][gp.py.p_x+1]   = gp.py.p[gp.py.p_y][gp.py.p_x];
    295							gp.py.p[gp.py.p_y+1][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    296							gp.py.p[gp.py.p_y][gp.py.p_x]     = 0;
    297							gp.py.p[gp.py.p_y+1][gp.py.p_x]   = 0;
    298							gp.py.p_x++;
    299						}
    300					}
    301					break;
    302						// 上下または左右入れ替え
    303				case 2:
    304					if (gp.py.rot == 0) {
    305						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    306						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    307						gp.py.p[gp.py.p_y][gp.py.p_x+1] = k;
    308					}
    309					else {
    310						let k = gp.py.p[gp.py.p_y][gp.py.p_x];
    311						gp.py.p[gp.py.p_y][gp.py.p_x]   = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    312						gp.py.p[gp.py.p_y+1][gp.py.p_x] = k;
    313					}
    314					break;
    315						// 90度または-90度回転
    316				case 3:
    317					if (gp.py.rot == 0 && gp.py.p[gp.py.p_y+1][gp.py.p_x] == 0) {
    318						if (gp.py.p_y < gp.py.row-1) {
    319							gp.py.p[gp.py.p_y+1][gp.py.p_x] = gp.py.p[gp.py.p_y][gp.py.p_x+1];
    320							gp.py.p[gp.py.p_y][gp.py.p_x+1] = 0;
    321							gp.py.rot = 1;
    322						}
    323					}
    324					else {
    325						if (gp.py.p_x < gp.py.col-1 && gp.py.p[gp.py.p_y][gp.py.p_x+1] == 0) {
    326							gp.py.p[gp.py.p_y][gp.py.p_x+1] = gp.py.p[gp.py.p_y+1][gp.py.p_x];
    327							gp.py.p[gp.py.p_y+1][gp.py.p_x] = 0;
    328							gp.py.rot = 0;
    329						}
    330					}
    331					break;
    332			}
    333						// 再描画
    334			if (gp.in_game)
    335				gp.py.draw();
    336		}
    337	}
    			

情報学部 菅沼ホーム JavaScript 目次 索引