JavaScript:落ちものゲームでブロックが3つ以上そろったら消す

JavaScript

縦と横方向に落ちてきたブロックが3つ以上そろったら消す
落ちている最中にそろっても消さない。ブロックが全て落ちてから消すようにしている。

ソースコード

index.html

1<!DOCTYPE html>
2<html lang="ja">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>OTIMONO(07ブロックを消す)</title>
7    <style>
8        *{
9            margin: 0;
10            padding: 0;
11        }
12        body{
13            margin: 0 50px;
14        }
15    </style>
16    <script src="main.js" type="text/javascript"></script>
17</head>
18<body>
19    <canvas id="canvas" width="512" height="512"></canvas>
20</body>
21</html>

main.js

1// 07ブロックを消す
2let canvas = null;
3let g = null;
4let pos = {x:0, y:0};       // キャンバス上のマウス座標
5let cpos = {x:0, y:0};      // マウス座標から得られるセル位置
6let isMouseDown = false;    // マウスを押しているか?
7let dropSpeed = 3;          // 落下速度(10-->1秒, 1-->0.1秒)
8let gameTime = {start: 0, end: 0};  // 落下速度計測用
9 
10const CELL_SIZE = 64;                           // パズルキャラのサイズ(64x64ピクセル)
11const ROW_SIZE = COLUMN_SIZE = 512 / CELL_SIZE; // 行と列に何個並べるか?
12const CELL_LENGTH = ROW_SIZE * COLUMN_SIZE;     // パズルキャラの数
13 
14// ゲームに使う画像ファイル
15const image_files = ["space.png", "alien.png", "apple.png", "dog.png", "obake.png", "ultra.png", "usi.png"];
16 
17// 画像オブジェクト(画像ファイルをゲームで利用するため)
18let images = [];
19 
20// ステージ情報配列
21let stageState = new Array(CELL_LENGTH);
22 
23/*
24 * マウスを動かしている時の処理
25 */
26const mouseMoveListener = (e) => {
27    // オフセット位置:キャンバスがブラウザの左上からどれくらいの位置にあるか?(rect.left, rect.top)
28    const rect = e.target.getBoundingClientRect();
29 
30    // e.clientXとe.clientYはブラウザ左上からのキャンバスクリック位置なのでオフセット分を引くとキャンバス上の座標が分かる
31    pos.x = e.clientX - rect.left;
32    pos.y = e.clientY - rect.top;
33 
34    // マウス座標からセル位置を求める
35    cpos.x = Math.floor(pos.x / CELL_SIZE);
36    cpos.y = Math.floor(pos.y / CELL_SIZE);
37};
38 
39/*
40 * マウスボタンを押した時の処理
41 */
42const mouseDownListener = () => {
43    isMouseDown = true;
44    // ステージのクリック位置に画像ブロックを設定
45    //const index = Math.floor(Math.random() * (images.length-1)) + 1;  // ランダム
46    stageState[cpos.x + cpos.y * COLUMN_SIZE] = 1;  // とりあえず1番目のブロック(インベーダー)をセット
47    console.log(cpos);
48};
49 
50/*
51 * マウスボタンを離した時の処理
52 */
53const mouseUpListener = () => {
54    isMouseDown = false;
55};
56 
57/*
58 * 背景を表示
59 */
60const drawBackground = ()=> {
61    let x = 0, y = 0;   // 描画するセル位置
62    let color1 = "pink", color2 = "skyblue";    // 市松模様のステージ色
63 
64    for(i=0; i<CELL_LENGTH; i++){
65        // 1段下がると先頭の色を入れ替える
66        if(i % 8 == 0) [color1, color2] = [color2, color1];
67        // 格子状にブロックを表示
68        x = i % COLUMN_SIZE;            // x方向セル位置
69        y = Math.floor(i / ROW_SIZE);   // Y方向セル位置
70 
71        // 色を交互に設定
72        bgColor = (i % 2 == 0) ? color1: color2;
73        g.fillStyle = bgColor;
74        g.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
75    }
76}
77 
78/*
79 * カーソルを表示
80 */
81const drawCursor = ()=> {
82    g.fillStyle = "rgba(128, 128, 128, 0.5)";   // グレー色で透過
83    g.fillRect(cpos.x * CELL_SIZE, cpos.y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
84}
85 
86/*
87 * ステージを表示
88 */
89const drawStage = ()=> {
90    for(let i=0; i<CELL_LENGTH; i++){
91        x = i % COLUMN_SIZE;            // x方向セル位置
92        y = Math.floor(i / ROW_SIZE);   // Y方向セル位置
93 
94        // ブロックを表示
95        g.drawImage(images[stageState[i]], x*CELL_SIZE, y*CELL_SIZE);
96        // ステージの状態を表す配列内容を表示(確認用)
97        g.fillStyle = "#fffa";
98        g.textBaseline = "top";
99        g.fillText(stageState[i], x*CELL_SIZE+20, y*CELL_SIZE+20);
100    }
101}
102 
103/*
104 * 画像ファイル読み込み
105 */
106const loadImages = async ()=> {
107    const images = [];
108    const promises = [];
109 
110    image_files.forEach((url) =>{
111        const promise = new Promise((resolve)=>{
112            const img = new Image();
113            img.addEventListener("load", ()=>{
114                console.log("loaded " + url);
115                resolve();  // 読み込み完了通知
116            });
117            img.src = "./asset/" + url; // 画像ファイル読み込み開始
118            images.push(img);   // 画像オブジェクト配列に格納
119        });
120        promises.push(promise);
121    });
122    await Promise.all(promises);
123    return images;
124}
125 
126/*
127 * ステージ情報を生成
128 */
129const createStage = (stage)=> {
130    console.log(stage);
131    stageState = [];
132    stageState = [...stage];    // ステージ情報をコピー(stageStage <-- stage)
133    console.log(stageState);
134}
135 
136/*
137 * ブロックを落とす
138 */
139const dropBlock = ()=> {
140    // 落下タイミングチェック
141    gameTime.end = new Date().getTime();
142    const passed = (gameTime.end - gameTime.start) / 100;   // 10分の1秒に直す
143 
144    if(passed <= dropSpeed){
145        return; // 落下しない
146    }
147 
148    let dropCount = 0;  // 落としたブロックの数
149    for(let y=ROW_SIZE-2; y>=0; y--){    // ステージの下方向からチェック
150        for(let x=0; x<COLUMN_SIZE; x++){
151            const upIndex = y*ROW_SIZE+x;
152            const downIndex = (y+1)*ROW_SIZE+x;
153            if(stageState[upIndex] != 0 && stageState[downIndex] == 0){
154                // 入れ替え
155                [ stageState[upIndex], stageState[downIndex] ]= [ stageState[downIndex], stageState[upIndex] ];
156                dropCount++;    // 落とすブロックの数をカウント
157            }
158        }
159    }
160    gameTime.start = new Date().getTime();  // 落下タイマー再スタート
161 
162    return dropCount;   // 落としたブロック数を返す
163}
164 
165/*
166 * ブロックが消去できるかチェックする(仮)
167 */
168const checkBlock = () =>{
169    // ステージの状態をコピー
170    const check = [...stageState];
171 
172    // 横方向チェック
173    for(let y=0; y<ROW_SIZE; y++){
174        for(let x=1; x<COLUMN_SIZE-1; x++){
175            const currentIndex = y*ROW_SIZE + x;    // 中央
176            const rightIndex = y*ROW_SIZE + (x + 1);// 右側
177            const leftIndex = y*ROW_SIZE + (x - 1); // 左側
178            if(stageState[currentIndex] > 0){
179                if((stageState[currentIndex] === stageState[rightIndex] && stageState[currentIndex] === stageState[leftIndex])){
180                    check[currentIndex] = check[rightIndex] = check[leftIndex] = 0;
181                }
182            }
183        }
184    }
185    // 縦方向チェック
186    for(let y=1; y<ROW_SIZE-1; y++){
187        for(let x=0; x<COLUMN_SIZE; x++){
188            const currentIndex = y*ROW_SIZE + x;    // 中央
189            const upIndex = (y-1)*ROW_SIZE + x; // 上側
190            const downIndex = (y+1)*ROW_SIZE + x;   // 下側
191            if(stageState[currentIndex] > 0){
192                if((stageState[currentIndex] === stageState[upIndex] && stageState[currentIndex] === stageState[downIndex])){
193                    check[currentIndex] = check[upIndex] = check[downIndex] = 0;
194                }
195            }
196        }
197    }
198 
199    // チェック後の状態をステージ情報に戻す
200    stageState = [...check];
201}
202 
203/*
204 * ゲームのメイン処理(まだゲームではない)
205 */
206const GameMain = () =>{
207    // キャンバスクリア
208    g.clearRect(0, 0, canvas.width, canvas.height);
209    // ブロックを落とす
210    if(dropBlock() == 0){   // 落とすブロックが無くなったなら
211        // ブロックの状態をチェック(ブロックが縦と横に3つ以上そろっていれば消す)
212        checkBlock();
213    }
214    // ステージ描画
215    drawBackground();
216    // カーソル描画
217    drawCursor();
218    // ステージ表示
219    drawStage();
220    // フレーム再描画
221    requestAnimationFrame(GameMain);
222}
223 
224/*
225 * 起動時の処理
226 */
227window.addEventListener("load", async ()=>{
228    canvas = document.getElementById("canvas");
229    g = canvas.getContext("2d");
230    g.font = "bold 24px System";    // フォントサイズ・フォント種類 設定
231 
232    // マウスイベント設定
233    canvas.addEventListener("mousemove", mouseMoveListener, false);
234    canvas.addEventListener("mousedown", mouseDownListener, false);
235    canvas.addEventListener("mouseup", mouseUpListener, false);
236 
237    // 画像ファイルを読み込む
238    images = await loadImages(image_files);
239    console.log(images);
240 
241    // ステージ情報を生成する
242    const stage1 = [
243        0, 0, 0, 0, 0, 0, 0, 0,
244        0, 0, 4, 0, 0, 0, 0, 0,
245        0, 0, 4, 0, 0, 0, 0, 0,
246        0, 0, 2, 0, 4, 5, 6, 0,
247        0, 0, 2, 0, 0, 0, 0, 0,
248        0, 0, 1, 1, 0, 0, 0, 0,
249        0, 4, 2, 4, 0, 0, 0, 0,
250        0, 1, 2, 1, 0, 0, 0, 0
251    ];
252    createStage(stage1);
253 
254    // 落下タイマースタート
255    gameTime.start = new Date().getTime();
256 
257    // ゲームのメイン処理呼び出し(ゲーム開始)
258    GameMain();
259});
関連記事

コメント

タイトルとURLをコピーしました