pirika logo

ホームページ Pirikaで化学 ブログ 業務案内 お問い合わせ
情報化学+教育トップ 情報化学 MAGICIAN MOOC プログラミング
高校生レベルのプログラミングはMAGICIAN-Jrへどうぞ。
フィードバックはお問い合わせフォームからお願いします。

プログラミング・トップ > 化学,薬学系の親子で楽しみながらプログラミング > 第3時限:体育

2022.3.14

Pirika.comでプログラミング
山本博志

3-2:走れ、走れ

スタートボタンを押すと、迷路が現れます。
キーボードの矢印、もしくは、ボタンを使ってキャラクターを右下のゴールまで走らせてください。
かかった時間が表示されます。

00:00:00.000


完成形のプログラムをコピペしてHogeHoge.htmlとセーブしましょう。
セーブしたフォルダーの中に、imagesというフォルダーがあって、その中に、Daimaou.pngというキャラクターの画像が必要になります。
自分だけのキャラクターに置き換えても良いです。

完成形プログラム(▶︎をクリックして開く)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>走れ走れ</title>
  </head>
  <body>

<div id = "timer"> 00:00:00.000</div>

<input type="button" id="Start" value="スタート" ><input type="button" id="Reset" value="リセット"><br>
<canvas id="canvas" width="770" height="770" style="border:1px solid #CCC;"></canvas>

<table >
<td ></td>
<td ><input type="button" id="rB" value="→" onclick="GoRight()"></td>
<td><input type="button" id="lB" value="←" onclick="GoLeft()"></td>
<td><input type="button" id="uB" value="↑" onclick="GoUp()"></td>
<td><input type="button" id="dB" value="↓" onclick="GoDown()"></td>
</table>
 
<script type="text/javascript">
//タイマー
//https://qiita.com/ryomaDsakamoto/items/c49a9d4cd2017405af1b
const timer = document.getElementById("timer") ;
var start = document.getElementById('Start');
var reset = document.getElementById('Reset');

let startTime;
let elapsedTime = 0;
let intervalID;
//タイマーを止めるにはclearTimeoutを使う必要があり、そのためにはclearTimeoutの引数に渡すためのタイマーのidが必要
    var timerId;

    //タイマーをストップ -> 再開させたら0になってしまうのを避けるための変数。
    var timeToadd = 0;

//スタートボタンがクリックされたら、
start.addEventListener('click',function(){

        //在時刻を示すDate.nowを代入
        startTime = Date.now();
    displayMaze();//迷路作成
        //再帰的に使えるように関数を作る
        countUp();
    });
    
//resetボタンにクリック時のイベントを追加(タイマーリセットイベント)
    reset.addEventListener('click',function(){
    clearTimeout(timerId);
        //経過時刻を更新するための変数elapsedTimeを0にしてあげつつ、updateTimetTextで0になったタイムを表示。
        elapsedTime = 0;

        //リセット時に0に初期化したいのでリセットを押した際に0を代入してあげる
        timeToadd = 0;

        //updateTimetTextで0になったタイムを表示
        updateTimetText();
        ctx.clearRect(Imagex, Imagey,35,35);
        Imagey = 35;
     Imagex = 70;
     ctx.drawImage(img, Imagex, Imagey,35,35);
    
    });


function updateTimetText(){ //表示
       let m = Math.floor(elapsedTime / 60000);
       let s = Math.floor(elapsedTime % 60000 / 1000);
       let ms = elapsedTime % 1000;

       m = ('0' + m).slice(-2); 
       s = ('0' + s).slice(-2);
       ms = ('0' + ms).slice(-3);
      
       timer.textContent = m + ':' + s + ':' + ms;
   }
   
   function countUp(){

       timerId = setTimeout(function(){

       elapsedTime = Date.now() - startTime ;
       
       updateTimetText();//タイマー表示
       
       //countUp関数自身を呼ぶことで10ミリ秒毎に以下の計算を始める
       countUp();
       
       },10);
   }
   
 
      

//画面表示

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');


//迷路作成
//http://www5d.biglobe.ne.jp/~stssk/maze/make.html

// 乱数生成
  function rand(n) {
    return Math.floor(Math.random() * n);
  }

  // 迷路の横幅と高さを指定(奇数)
  const width = 21, height = 21;//33
  const maze = [];

  // 迷路の2次元配列作成 壁は1、通路は0
  function createMazeArray(height, width){
    ctx.clearRect(0,0,canvas.width,canvas.height)
    // 迷路のベースを作る
    for(let y = 1; y < height+1; y++){
      maze[y] = [];
      for(let x = 1; x < width+1; x++){
        // 1行目と最終行、1列目と最終列は1
        if(y == 1 || y == height || x == 1 || x == width){
        maze[y][x] = 1;
        }
        // 奇数行の奇数列は1
        else if(y % 2 == 1 && x % 2 == 1){
        maze[y][x] = 1;
        }
        // そのほかは0
        else{
        maze[y][x] = 0;
        }
      }
    }
    // 奇数行の奇数列のみ処理
    for(let y = 3; y < height; y+=2){
      for(let x = 3; x < width; x+=2){
        // 棒を倒せる方向を配列にする
        // 右と下は全パターンでOK
        const direction = ["right", "down"];
        // 1回目なら上もOK
        if(y == 3){
        direction.push("up");
        }
        // 左が壁じゃなければ左もOK
        if(maze[y][x-1] == 0){
        direction.push("left");
        }
        switch (direction[rand(direction.length)]) {
          case "up":
            maze[y-1][x] = 1;
            break;
          case "right":
            maze[y][x+1] = 1;
            break;
          case "down":
            maze[y+1][x] = 1;
            break;
          case "left":
            maze[y][x-1] = 1;
            break;
        }
      }
    }
    // 入口と出口を作成
    maze[1][2] = 0;
    maze[height][width-1] = 0;
  }

  // 迷路を表示
  function displayMaze(){
   Imagey = 35;
   Imagex = 70;
    createMazeArray(height, width);
    ctx.drawImage(img, Imagex, Imagey,35,35);

    for(let y = 1; y < height+1; y++){
      for(let x = 1; x < width+1; x++){
        if(maze[y][x] == 1){
          ctx.fillRect(x*35, y*35, 35, 35);//壁を塗りつぶす。
        }
      }
    }
    //ctx.scale(1, 1);

    
    
  }
  
 //イメージを読み込む 

var img = new Image();
img.src = './images/Daimaou.png';

var imageSizeX;
var imageSizeY;
var MyScaleX=0.3;
var MyScaleY=0.3;

//キャラクターの位置
var Imagey = 35;
var Imagex = 70;


// 画像読み込み終了してから描画
img.onload = function(){
    ctx.drawImage(img, Imagex, Imagey,35,35);//イメージのサイズを35*35
}

//イメージ移動

function GoRight(){//右ボタン
var xp=Math.round(Imagex/35);
var yp=Math.round(Imagey/35);
if(maze[yp][xp+1]==1){return}
ctx.clearRect(Imagex, Imagey,35,35);
Imagex += 35;
ctx.drawImage(img, Imagex, Imagey,35,35);
if(Imagex==700 && Imagey==735){//出口・タイマー止める
    clearTimeout(timerId)
}

}

function GoLeft(){//左ボタン
var xp=Math.round(Imagex/35);
var yp=Math.round(Imagey/35);
if(maze[yp][xp-1]==1){return}
ctx.clearRect(Imagex, Imagey,35,35);
Imagex -= 35;
ctx.drawImage(img, Imagex, Imagey,35,35);

if(Imagex==700 && Imagey==735){//出口・タイマー止める
    clearTimeout(timerId)
}
}

function GoUp(){//上ボタン
var xp=Math.round(Imagex/35);
var yp=Math.round(Imagey/35);
if(maze[yp-1][xp]==1){return}
ctx.clearRect(Imagex, Imagey,35,35);
Imagey -= 35;
ctx.drawImage(img, Imagex, Imagey,35,35);
if(Imagex==700 && Imagey==735){//出口・タイマー止める
    clearTimeout(timerId)
}


}

function GoDown(){//下ボタン
var xp=Math.round(Imagex/35);
var yp=Math.round(Imagey/35);
if(maze[yp+1][xp]==1){return}
ctx.clearRect(Imagex, Imagey,35,35);
Imagey += 35;
ctx.drawImage(img, Imagex, Imagey,35,35);
if(Imagex==700 && Imagey==735){//出口・タイマー止める
    clearTimeout(timerId)
}
}


 
//なにかキーが押されたとき、keydownfuncという関数を呼び出す
addEventListener( "keydown", keydownfunc );
 
//キーが押されたときに呼び出される関数
function keydownfunc( event ) {
    event.preventDefault();
    //押されたボタンに割り当てられた数値(すうち)を、key_codeに代入
    
    var xp=Math.round(Imagex/35);
    var yp=Math.round(Imagey/35);

    
    var key_code = event.keyCode;
    
        //画像の位置(いち)を反映(はんえい)させる
   
 
    if( key_code === 37 ) {
        if(maze[yp][xp-1]==1){return}
        ctx.clearRect(Imagex, Imagey,35,35);
        Imagex -= 35;       //「左ボタン」が押されたとき、xの値から35を引き算する
    }
    if( key_code === 38 ){
        if(maze[yp-1][xp]==1){return}
        ctx.clearRect(Imagex, Imagey,35,35);
        Imagey -= 35;       //「上ボタン」が押されたとき、yの値から35を引き算する
    }
    if( key_code === 39 ){
        if(maze[yp][xp+1]==1){return}
        ctx.clearRect(Imagex, Imagey,35,35);
        Imagex += 35;       //「右ボタン」が押されたとき、xの値に35を足し算する
    }
    if( key_code === 40 ){
        if(maze[yp+1][xp]==1){return}
        ctx.clearRect(Imagex, Imagey,35,35);
    Imagey += 35;       //「下ボタン」が押されたとき、yの値に35を足し算する
 
    }
       
    ctx.drawImage(img, Imagex, Imagey,35,35);
    if(Imagex==700 && Imagey==735){//出口・タイマー止める
        clearTimeout(timerId)
    }
    
}


</script>
  </body>
</html>

大まかなプログラムの構成は
1. ストップウオッチ関係
2. 迷路作成
3. キャラクターの表示と移動
になります。

キャラクターの表示と移動に関しては、No2-4で詳しく説明しています。
ここでの違いは、壁のある方向には動けない事だけですので、説明は省略します。


スタートボタンが押されると迷路が作成され、ストップウオッチがスタートします。キャラクターがゴールするとストップウオッチが停止します。
このプログラムはこちらから借用しています。
基本的に動作は、countUp関数の中で、countUp関数を10msごとに呼ぶ事によって、start Timeからどれだけ時間が経過したかをupdateTimetText();で表示するものです。

   function countUp(){

       timerId = setTimeout(function(){

       elapsedTime = Date.now() - startTime ;
       
       updateTimetText();//タイマー表示
       
       //countUp関数自身を呼ぶことで10ミリ秒毎に以下の計算を始める
       countUp();
       
       },10);
   }

迷路の作成に関してもこちらから借用しています。
迷路の作成法にはいくつかあるようですが、ここでは一番簡単な棒倒し法を使っています。

キャラとの兼ね合いで、一つのブロックを35*35に固定しています。またキャラクターのサイズも同じ35*35になっています。

迷路の横と縦は値を変更することも可能ですが、プログラムの都合上、奇数の値にします。

列、行を変更した時には、キャンバスのwidth、heightも変えます。
初期値は35*21になっています。
そして最後にゴールの位置も変えます。(プログラムの中で5箇所変えなくてはなりません)

<canvas id="canvas" width="770" height="770" style="border:1px solid #CCC;"></canvas>

  const width = 21, height = 21;
  
  
  if(Imagex==700 && Imagey==735){//右から2列目、最後の行の一つ前がゴール

道にいろいろな物を落としておいて、拾い集めるパックマンのようなゲーム、倉庫の中を片付ける倉庫番ゲームの基本になるようなプログラムなので、じっくり楽しんでください。

プログラミング・トップ > 化学,薬学系の親子で楽しみながらプログラミング


Copyright pirika.com since 1999-
Mail: yamahiroXpirika.com (Xを@に置き換えてください) メールの件名は[pirika]で始めてください。