ホームページ | Pirikaで化学 | ブログ | 業務案内 | お問い合わせ |
情報化学+教育トップ | 情報化学 | MAGICIAN | MOOC | プログラミング |
2022.3.14
Pirika.comでプログラミング
山本博志
スタートボタンを押すと、迷路が現れます。
キーボードの矢印、もしくは、ボタンを使ってキャラクターを右下のゴールまで走らせてください。
かかった時間が表示されます。
完成形のプログラムをコピペして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]で始めてください。