pirika logo

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

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

2022.3.4

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

5-4-2:炭酸ガスによる温暖化

2酸化炭素による地球温暖化はちゃんと教えようとすると大人でも難しいものです。
そこでゲームで教えましょう。

スタートボタンを押してください。

bouncing balls改良2


CO2 濃度 ppm
キーボードでも動作


左の方には太陽があります。
右には地球があります。
太陽の方から青い球が出てきます。青い球は太陽から降り注ぐ、エネルギーの高い光です。
右の地球にたどり着くと、赤い色の球になって反射して行きます。この光は遠赤外線というエネルギーが弱いですが、温める力が高い性質があります。
ヒーターなどで遠赤外線と書いてあるものがあるので聞いた事があるかもしれません。

画面に中に、炭酸ガスCO2があります。
炭酸ガスは青い球はすり抜けて行きます。
そして、赤い球は反射して戻って行きます。

難しい言い方をすると、炭酸ガスは、逆対称伸縮振動があるので赤外光を吸収して、その一部を地球に送り返します。


窒素や酸素は2原子分子なので、逆対称伸縮振動ができないので、赤外光を吸収しません。
入ったエネルギーと出るエネルギーが釣り合っていれば、地球の温度は変わりません。
だけど、CO2が遠赤外線を打ち返してしまうので、遠赤外効果で地球が温められてしまいます。

このCO2が増えるとどうなるか、シミュレーションして見ましょう。スライダーの作り方はNo2-3-1で説明しました。

CO2濃度が400ppmぐらいになると、どんどん球は右側の領域に閉じ込められていきます。
この表示はもちろん正しくありません。
濃度をどう表現するか?の問題です。
正しい表示がわかりやすいか?、プログラミング上簡単か?は別問題です。
もちろん、CO2を何個も画面に置くように改良しても良いです。

CO2の絵は2-5:目指せLineスタンプ・メーカー・メーカー 修正版を使って自分で描きました。そこで元のイメージサイズは大きいので縮小します。
それを表示するときに、MyScaleX,MyScaleYを0.3に指定して縮小します。

// Image オブジェクトを生成
var img = new Image();
img.src = 'https://www.pirika.com/Images20/CO2.jpg';

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

        //キャラクターの位置
var Imagey = 50;
var Imagex = 200;

// 画像読み込み終了してから描画
img.onload = function(){
    imageSizeX=img.naturalWidth;//元のイメージの横幅
    imageSizeY=img.naturalHeight;//元のイメージの縦幅
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}

そしてスライダーの値を変更したときに、MyScaleXの横幅は0.3と一定にして、MyScaleYを変更します。そして古いものを消して描き直すと、縦方向に間延びしたCO2が描かれます。
衝突判定は間延びした部分に広がるので、戻る量が簡単に増やせます。

function inputChange(event){//スライダーに変化があったら呼ばれる。
  MyScaleY=0.01*(parseFloat(S1Slider.value)-300)+0.3;//Slider.valueは文字列なので、paraseFloat関数で実数に変換する。
  ctx.fillStyle = 'rgb(255, 255, 255)';//白色に設定する。
   ctx.fillRect(oldImageX, oldImageY,  imageSizeX*MyScaleX, imageSizeY*MyScaleY);//元のCO2を消す。
       
   
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);//CO2を描き直す
}


さー、さらにに遊びながらプログラミングを覚えて行きましょう。
キーボードの矢印を押して見てください。

ボールを打ち返して見てください。
これは昔流行ったブロック崩しのゲームの亜流です。

この矢印キー、矢印ボタンのプログラムは、2-4で説明しています。
iPadなどではキーボードが無いので、矢印の代わりにボタンを表示しています。


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

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>炭酸ガスによる温暖化</title>
    <style>

    </style>
  </head>
  <body>
<canvas width="600" height="350"></canvas><br>
<input type="button" id="btnStart" value="Start" onclick="StartSimu()">

<div class="example-box">
                <table>
                    <tr>
                        <th>CO2 濃度  ppm</th>
                        <td><input id="slider1" type="range"  min="300" max="500"  oninput="this.nextSibling.value = this.value" onchange="this.nextSibling.value = this.value" /><input id="Xval" type="text" size="3" value="300" /></td>
                    </tr>
                    
                </table>
</div>
<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">
// set up canvas

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

const width = canvas.width ;//= window.innerWidth;
const height = canvas.height ;//= window.innerHeight;

let S1Slider = document.getElementById('slider1');//slider1のオブジェクトを指定します。
S1Slider.addEventListener('input', inputChange);//変化(イベント)が起きたら、inputChange関数を呼ぶ。

S1Slider.value="300";

function inputChange(event){//スライダーに変化があったら呼ばれる。
  MyScaleY=0.01*(parseFloat(S1Slider.value)-300)+0.3;//Slider.valueは文字列なので、paraseFloat関数で実数に変換する。
  ctx.fillStyle = 'rgb(255, 255, 255)';//白色に設定する。
   ctx.fillRect(oldImageX, oldImageY,  imageSizeX*MyScaleX, imageSizeY*MyScaleY);//元のCO2を消す。
       
   
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);//CO2を描き直す
  //MyY=parseInt(S2Slider.value);
  //MyRad=parseInt(S3Slider.value);
  //MyDraw();//オブジェクトを描画する。
}


//矢印ボタンが押された時の処理
function GoRight(){
ctx.clearRect(Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
Imagex += 32;
ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}
function GoLeft(){
ctx.clearRect(Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
Imagex -= 32;
ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}
function GoUp(){
ctx.clearRect(Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
Imagey -= 32;
ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}
function GoDown(){
ctx.clearRect(Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
Imagey += 32;
ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}



// Image オブジェクトを生成
var img = new Image();
img.src = './images/CO2.jpg';

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

        //キャラクターの位置
var Imagey = 50;
var Imagex = 200;

// 画像読み込み終了してから描画
img.onload = function(){
    imageSizeX=img.naturalWidth;
    imageSizeY=img.naturalHeight;
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
}


 
//なにかキーが押されたとき、keydownfuncという関数を呼び出す
addEventListener( "keydown", keydownfunc );
 
//キーが押されたときに呼び出される関数
var oldImageX;
var oldImageY;
function keydownfunc( event ) {
    event.preventDefault();
    //押されたボタンに割り当てられた数値(すうち)を、key_codeに代入
    oldImageX=Imagex;
    oldImageY=Imagey;
    var key_code = event.keyCode;
 
    if( key_code === 37 ) Imagex -= 32;     //「左ボタン」が押されたとき、xの値から32を引き算する
    if( key_code === 38 ) Imagey -= 32;     //「上ボタン」が押されたとき、yの値から32を引き算する
    if( key_code === 39 ) Imagex += 32;     //「右ボタン」が押されたとき、xの値に32を足し算する
    if( key_code === 40 ) Imagey += 32;     //「下ボタン」が押されたとき、yの値に32を足し算する
 
    //ctx.fillStyle = 'rgb(125, 125, 125)';
   //ctx.fillRect(oldImageX, oldImageY,  imageSizeX*MyScaleX, imageSizeY*MyScaleY);
       
   
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);
    //ctx.fill();
    
}





let MySimu = false;      // シミュレーション・フラグ

// function to generate random number

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// function to generate random RGB color value

function randomRGB() {
  return `rgb(${random(0, 255)},${random(0, 255)},${random(0, 255)})`;
}

class Ball {

   constructor(x, y, velX, velY, color, size) {
      this.x = x;
      this.y = y;
      this.velX = velX;
      this.velY = velY;
      this.color = color;
      this.size = size;
   }

   draw() {
            //var MyColor=this.color;
            var MyColor;
            if(this.velX>0){//右向き、青
                MyColor="rgb(0,0,255)";
            }
            else{//左向き、赤
                MyColor="rgb(255,0,0)";
            }
            var MyX=this.x;
            var MyY=this.y;
            var MyRad=this.size;
            var InitCx=MyX+MyRad*(204-175)/100;
            var InitCy=MyY+MyRad*(50-100)/100;
            var InitCr=0.1*MyRad;
            var EndCx=MyX+0.25*MyRad;
            var EndCy=MyY-0.25*MyRad;
            var EndCr=0.75*MyRad;
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
      var gradient2=ctx.createRadialGradient(InitCx,InitCy,InitCr,EndCx,EndCy,EndCr);
            gradient2.addColorStop(0.0,"#FFFFFF");
            gradient2.addColorStop(1.0,MyColor);
            ctx.fillStyle = gradient2; 
            ctx.fill();
   }

   update() {
      if ((this.x + this.size) >= width) {//右壁
         this.velX = -(this.velX);
      }

      if ((this.x - this.size) <= 0) {//左壁
         this.velX = -(this.velX);
      }
      
      if (this.velX < 0) { //左向きに進むときにはCO2と衝突、反射
          if((this.x - this.size) <= (Imagex+imageSizeX*MyScaleX)) {
              if(this.y>=Imagey){
                  if( this.y<= Imagey+imageSizeY*MyScaleY) {//CO2
                     this.velX = -(this.velX);
                  }
              }
          }

      }
      

      if ((this.y + this.size) >= height) {//下壁
         this.velY = -(this.velY);
      }

      if ((this.y - this.size) <= 0) {//上壁
         this.velY = -(this.velY);
      }
        //Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY
      this.x += this.velX;
      this.y += this.velY;
   }
   
   drawCO2(){//CO2を表示
    ctx.fillStyle = 'rgb(255, 255, 255)';
   ctx.fillRect(oldImageX, oldImageY,  imageSizeX*MyScaleX, imageSizeY*MyScaleY);
       
   
    ctx.drawImage(img, Imagex, Imagey,imageSizeX*MyScaleX,imageSizeY*MyScaleY);

}

   collisionDetect() {//衝突判定
      for (const ball of balls) {
         if (!(this === ball)) {
            const dx = this.x - ball.x;
            const dy = this.y - ball.y;
            const distance = Math.sqrt(dx * dx + dy * dy);

            if (distance < this.size + ball.size) {
              ball.color = this.color = randomRGB();
            }
         }
      }
   }

}

var speed=4;

const balls = [];
//x,y,velx,vely,color,size
while (balls.length < 40) {//全部で40個のボール
   const size = 7;
   const ball = new Ball(
      // ball position always drawn at least one ball width
      // away from the edge of the canvas, to avoid drawing errors
      random(0 + size,width - size),
      random(0 + size,height - size),
      random(-speed,speed),
      random(-speed,speed),
      randomRGB(),
      size
   );

  balls.push(ball);
}

function loop() {
    if(MySimu){
   ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
   ctx.fillRect(0, 0,  width, height);

   for (const ball of balls) {
     ball.draw();
     ball.update();
     ball.drawCO2();
     ball.collisionDetect();
   }
    
   requestAnimationFrame(loop);
   }
}

// 開始ボタンが押されたとき
function StartSimu() {
  if (MySimu) {
    MySimu = false;
    document.getElementById('btnStart').value = 'Start';
  } else {
    MySimu = true;
    document.getElementById('btnStart').value = 'Stop';
    loop();
  }
} 

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

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


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