ホームページ | Pirikaで化学 | ブログ | 業務案内 | お問い合わせ |
情報化学+教育トップ | 情報化学 | MAGICIAN | MOOC | プログラミング |
2022.3.4
Pirika.comでプログラミング
山本博志
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]で始めてください。