processingとopenframeworksを比較してみる
processing(以下P)やopenframeworks(以下OF)を使ってグラフィックを創作する人はたくさんいて、中にはコードを公開している人もいます。普段僕はprocessingを使っているけど、この人の公開しているコードはopenframeworksなのか・・と読むのを諦めてしまうのはもったいないです。
ここではPとOFのどちらかを主流言語にしている人向けに、このメソッドはこっちではこうなります、といったことを列挙していきます。(需要があるかどうかはともかくとして、やってみたかったので)
PまたはOFのどちらかを初めて触る人の参考にもなる気がしています。
processingとopenframeworksの簡単な特徴
processingはjavaベースの言語です。一つ一つの命令が簡単で非常に書きやすいです。また初学者が詰まりやすい環境構築も一瞬で、公式サイトからzipを取ってきて解凍するだけです。
openframeworksは言語というよりはライブラリで、C/C++をベースに書けます。C/C++を使うだけあって実行速度が速いので、多少重い処理でもスムーズに動きます。その代わり環境構築が少し大変な面があります。
基本的な構造
PとOF両者とも、setup()とdraw()があります。setup()は一番最初に実行されて、draw()は実行中に1秒間に数十回という速さで何度も実行されます。これにより、パラパラ漫画のようにアニメーションが作れます。
また、OFにはdraw()の他にupdate()が存在します。一応、draw()の中には実際に図形を描画して、update()では変数の更新を行うといった役割があるようですが、特に気にしなくても動きます。
以下はテンプレです。
//P void setup(){ } void draw(){ }
//OF #include "ofApp.h" void ofApp::setup(){ } void ofApp::update(){ } void ofApp::draw(){ }
また、Pではsetup()やdraw()を絶対に使わないと動かないわけではありません。描くものによりますが、使わないときもあります。OFは必須です。
以下ではよく使う(と思われる)メソッドを両方で見ていきます。
図形描画とシステム関係
・点
Pはpoint( )で点を打てます。(x座標、y座標)です。
OFには点を打つメソッドはありません。(もしあれば教えてください。。)もし打ちたいときは、後述するofDrawCircle( )の半径を極端に小さくすれば良いです。
/*P*/ point(50,50); /*OF*/ ofDrawCircle(50,50,1,1); //半径を1くらいにすれば点っぽくなるだろう
・線
lineです。(始点のx、始点のy、終点のx、終点のy)です。
//(50,50)から(100,100)に線を引く /*P*/ line(50,50,100,100); /*OF*/ ofDrawLine(50,50,100,100);
・四角
rectangleですね。デフォルトでは(左上の頂点のx、左上の頂点のy、横の長さ、縦の長さ)です。
/*P*/ rect(100, 100, 100, 50); /*OF*/ ofDrawRectangle(100, 100, 100, 50);
デフォルトでは指定した座標が左上の頂点に来ますが、四角の中心に持ってくることもできます。後述するrotate系を扱うときには便利です。
この設定は以下のようにできます。
/*P*/ rectMode(CENTER); /*OF*/ ofSetRectMode(OF_RECT_MODE_CENTER);
・円と楕円
(中心のx、中心のy、横の直径、縦の直径)です。もちろん、横と縦の直径を揃えれば綺麗な円になります。直径であることに注意してください。
また、OFには円しか書かない人向けのofDrawCircle(中心x、中心y、半径)があります。指定するのは半径であることに注意です。
/*P*/ ellipse(100, 100, 50, 100); /*円と楕円*/ /*OF*/ ofDrawEllipse(100, 100, 50, 100); /*円と楕円*/ /*OF*/ ofDrawCircle(100, 100, 250); /*円*/
・三角形
三角形は頂点が3つあるので、(x1, y1, x2, y2, x3, y3)のように3点分のx,y座標を順番に入れれば良いです。
/*P*/ triangle(100, 100, 150, 150, 50, 150); /*OF*/ ofDrawTriangle(100, 100, 150, 150, 50, 150);
・好きな頂点を好きな数設定して繋ぐ
vertexですね。
繋ぎたい頂点を全てvertex(x座標、y座標)で列挙して、それらをbeginShape(); とendShape();で挟むイメージです。これで挟まないと描かれません。頂点は全て繋がれて、内側は塗りつぶされますが、noFill()で塗りつぶさないこともできます。
/*P*/ beginShape(); vertex(50,50); vertex(100,100) ....... endShape(); /*OF*/ ofBeginShape(); ofVertex(50,50); ofVertex(100,100) ....... ofEndShape();
・画面幅を取得する
Pではシステム変数としてwidth,heightが用意されていますが、これらの値を使って何かやるときはsetup()でsize()を指定した後で使わないと正しい値が得られません。(size()指定前の初期値は100でエラーは出ないので、気づきにくいです。)
OFではofGetWidth(), ofGetHeight()です。
//画面の中心に円を描く /*P*/ ellipse(width/2, height/2, 50, 50); /*OF*/ ofDrawCircle(ofGetWidth()/2, ofGetHeight()/2, 50);
・文字をコンソールに出力
プログラムが予想通りに動かないとき、if文が本当に実行されているか、for文は本当に回っているかなどの確認をするときに、適当な位置に文字を出すプログラムを書けば良いです。
文字が出ればそのif文は実行されているなあ、ということが分かります。
/*P*/ println(値など) /*OF*/ cout<<値など<<endl;
色関係
まずは背景色ですね。
/*P*/ background(255,0,0); /*赤色*/ /*OF*/ ofSetBackgroundColor(255,255,0); /*黄色*/
次に色を設定するメソッドです。これを書いた行以下の図形は、この色になります。
/*P*/ fill(0,255,0); /*緑*/ /*OF*/ ofSetColor(0,0,255); /*青*/
・色情報を格納する変数を作る
色情報を格納する変数の型と、色情報を作るためのメソッド名は同じです。でもその名前は両者で違いますね。
/*P*/ color c = color(255,255,0); //(R,G,B) /*OF*/ ofColor c = ofColor(255,0,255); //(R,G,B)
ここでデフォルトではRGBで色情報を与えますが、これをHSBで与えたいときは以下のような書き方をします。
//P colorMode(HSB); color c = color(70,255,255); //(H,S,B) //OF color c; c.setHsb(70,255,255); //(H,S,B)
・色を塗りつぶさない
輪郭線だけになります。
/*P*/ noFill(); /*OF*/ ofNoFill();
入出力系
・マウス座標を取得
/*P*/ ellipse(mouseX, mouseY, 50,50); /*OF*/ ofDrawCircle(ofGetMouseX(), ofGetMouseY(),50);
・マウスやキーが押されたかどうかを取得
PはmousePressed と keyPressedがあります。押されたらtrue,そうでなければfalseです。
OFはofGetMousePressed(), ofGetKeyPressed()があります。これも同様のbool値です。
if(mousePressed)と条件式に直接書くだけで判定できますね。
/*P*/ if(mousePressed) { /*処理*/ } /*OF*/ if(ofGetMousePressed()) { /*処理*/ }
しかし、これでは高速に回るdraw()の中で何回も判定が起こってしまいます。(1回のクリックでも2,3回if文が実行されることがあります。)
これを「1回のクリックで1回だけ実行」という形にしたいとき、関数バージョンを使いましょう。
/*P*/ void mousePressed(){ //処理 } void keyPressed(){ //処理 } /*OF*/ void ofApp::mousePressed(int x, int y, int button){ //処理 } void ofApp::keyPressed(int key){ //処理 }
これらの使い分けは、「押している間ずっとしてほしい処理」か「押すたびにしてほしい処理」かで使い分けると良いです。
・どのボタンが押されたか、どのキーが押されたか
先ほどの方法でクリックやキーが押された判定を受け取れば、押されたボタンやキーによってさらに処理を変えたいところです。
Pでは システム変数としてmouseButton,keyがあります。
・mouseButton={LEFT, CENTER, RIGHT}
・key='R'のようにシングルクーテーションで囲む。大文字と小文字の区別有り。ただし矢印キーだけは、keyCode={UP, DOWN,RIGHT, LEFT}。
いずれのシステム変数も,最後に押されたキーの情報が残り続けます。よって,「押している間」みたいな処理では,必ずkeyPressedと組み合わせましょう。
OFでは、前述した関数の中身のmousePressed( int x, int y, int button)において、buttonでどのボタンかを得ます。
button = 0(左)、1(中央)、2(右)
キーに関しては、keyPressed(int key) におけるkeyに't' や'a'のように'シングルクオーテーション' で書けます。EnterやShift、矢印キーなど1文字では表せないものは、
OF_KEY_ENTER や OF_KEY_UPなど、OF_KEY_[キー名]で書けます。
//P if(mousePressed && mouseButton==LEFT) //左クリックしている間 void mousePressed(){ if(mouseButton==LEFT) //左クリックしたら(1クリックにつき1回) } if(keyPressed && keyCode==UP) //矢印キーの上を押している間 if(keyPressed && key=='R') //Rキー(大文字)を押している間 //OF void ofApp::mousePressed(int x, int y, int button){ if(button==0) //左クリック } void ofApp::keyPressed(int key){ if(key=='t') //tが押されたら if(key==OF_KEY_SHIFT) //shiftが押されたら }
乱数系・数学系・数値操作系
・ランダムな値を生成
ランダムなfloat型(小数込み)の乱数を生成します。
random(乱数の範囲の始め、乱数の範囲の終わり)です。範囲の終わりに指定した値自体は、範囲に含まれないことに注意です。範囲にはマイナスを含めることもできます。また、random(x)と1つだけ値を書けば、0~xの範囲で乱数が生成されます。
/*P*/ float x= random(20,100); /*20~99*/ float y= random(100); /*0~99*/ /*OF*/ float x= ofRandom(-25,75); /*-25~74*/ float y= ofRandom(200); /*0~199*/
・(連続的な)乱数を生成
ノイズのことです。これは概念が少し難しいので、読み飛ばしても大丈夫です。
ノイズは与えた値が同じであれば同じ乱数を返します。また、入れた値が近ければ近いほど、より近い乱数を返します。ノイズによる乱数は必ず0~1の値です。
例として、noise(100) で 0.5を得るとします。このとき、noise(99.8)のように近い値を入れれば、0.48のように近い値を得ます。逆にnoise(40)のように全く違う値を入れれば、0.87のように全く違う値を得ます。
/*P*/ float x = noise(100); /*OF*/ float x = ofNoise(100);
・三角関数
三角関数は両者ともsin(),cos(),tan()で書けます。三角関数の性質から、-1 ~ 1の範囲の値を得ます。しかし度数法からラジアンへの変換の方法は違います。
(度数法とは0~360度で表されるものです。ラジアンは0 ~ 2πの範囲で表されるものです。三角関数において角度情報はラジアンで与えないといけないため、30度→π/6 のように変換する必要があります。 )
Pではradians(度数)で、 OFでは度数*DEG_TO_RADで変換できます。
DEG_TO_RAD は「degree(度数) to radians(ラジアン)」ということですね。
/*P*/ float x = sin(radians(180)); /*OF*/ float y = sin(180*DEG_TO_RAD));
・ある範囲を取る変数を別の範囲に移す
mapです。(変数名、元範囲の始まり、元範囲の終わり、目標範囲の始まり、目標範囲の終わり)です。
例えば三角関数の値が 0.5だとします。このとき、三角関数は-1から1の範囲しか取らないため、-1側からは75%、1側からは25%の位置にいると考えられます。これを0~100の範囲に移すとき、この割合を守れば 0.5 は75になります。このような操作を行うのがmapです。
これは前述したノイズの値と組み合わせたり、ある値を画面幅の割合に対応させるときなどに使います。
/*P*/ float x = sin(radians(30)); map(x, -1, 1, 1, 100); //sinなので-1 ~ 1の範囲を取る変数xを、0 ~ 100の範囲で同じ割合の数値にする。 /*OF*/ float x=sin(30*DEG_TO_RAD); ofMap(x, -1, 1, 1, 100);
・2点間の距離を返す
distです。円同士の当たり判定などで役に立ちます。
/*P*/ dist(100,100,200,200); /*OF*/ ofDist(100,100,200,200);
・開始からの経過時間を取得
Pではmills()です。
OFでは探してみると、ofGetElapsedTimeMillis()が近い感じでした。
これらはミリ秒(1秒=1000ミリ秒)で取得するので、1秒を測りたければmillis()/1000のようにして使います。
/*P*/ int time=millis(); /*OF*/ int time=ofGetElapsedTimeMillis();
・開始からの経過フレーム数を取得
フレーム数を取得します。これも三角関数の引数にしたり、一定時間ごとのアクションを起こすのに役立ちます。
//100フレーム毎に実行 /*P*/ if(frameCount % 100==0){処理} /*OF*/ if(getFrameNum() % 100==0){処理}
座標操作系
・原点の移動
PもOFも、デフォルトでは原点が画面左上にあります。
この原点の位置を移動させることができるtranslate()系メソッドがあります。
//両方、原点の位置を画面中心に持ってくる /*P*/ translate(width/2,height/2); /*OF*/ ofTranslate(ofGetWidth()/2, ofGetHeight()/2);
・原点周りの回転
原点を中心に座標系を回すrotate()系メソッドがあります。
/*P*/ rotate(PI/3); /*OF*/ ofRotate(PI/3);
この2つのメソッドは共に原点に関する処理をするもので、非常に親密な関係にあります。
これらをうまく使えば、周期運動など面白い作品を作ることができます。
蛇足ですが、ずっと回し続けたいときには、
rotate(millis())
のように、ラジアンの部分に時間取得系の変数を突っ込んでやれば良いです。
・座標状態を保存
translateやrotateで動かした座標系は、同じメソッドにマイナスの値を与えることで元に戻ります。
/*P*/ translate( width/2, height/2 ); rotate(PI/6); //何かしらの処理 rotate( - PI/6); //回転させた分だけ逆に回すことで戻す translate( - width/2, - height/2 ); //移動させた分だけ逆に移動すれば戻る
この操作をもう少し分かりやすく記述するために、pushMatrix()とpopMatrix()があります。
pushMatrix()は座標系を保存します。
popMatrix()はpushMatrix()で保存した座標系を取り出して適用します。
これらは何か変数に代入するとかではなくて、単に書くだけで大丈夫です。
/*P*/ pushMatrix(), popMatrix(); /*OF*/ ofPushMatrix(), ofPopMatrix();
先ほどの処理を書き直せば以下のようになります。
/*P*/ pushMatrix(); //動かす前の座標系を保存 translate( width/2, height/2 ); rotate(PI/6); //何かしらの処理 popMatrix(); //保存したものを適用することで、元に戻す
画像を読み込む
画像を扱う流れは共通で、
・画像を扱う変数を宣言
・変数にどの画像を使うかのパスを与える
・実際に描画
です。
Pはあくまで独立したメソッドを使い、OFは変数のメンバ関数を使って指定していくところが少し異なりますね。
使う画像は.pdeや、.xcodeprojなど、プログラム本体以下の階層に置き、相対パスで指定します。
/*P*/ PImage pic; //PImage型変数 pic=loadImage(相対パス); //loadImageを用いてパスを指定 image( pic, 100, 100); //image(変数、左上の頂点のx座標、左上の頂点y座標)で描画 /*OF*/ ofImage pic; //ofImage型変数 pic.load(相対パス); //メンバ関数load( )でパスを指定 pic.draw(100,100); //メンバ関数draw()で描画
主なメソッドはこの辺りだと思います。また何か思いついたら、随時追加します。
掻き分けて、円
マウスカーソルから円が逃げます。
「掻き分けて、円」
— ごつちやん (@gotutiyan_kapi) 2018年5月25日
マウスカーソルから円が逃げます#openframeworks
コードと解説は以下からhttps://t.co/yYp6YpQ2Lz pic.twitter.com/qYkEfF0DcE
たまにはマウス座標を使おうと思って作りましたが、案外実装が楽で終わってしまったので、移動具合によって色も変えました。numの個数を変えれば敷き詰める円の個数も変わります。
変数を闇雲に作ったのと、三項演算子バリバリのコーディングをしたので少し見にくいかもしれません。
def_x,def_yが、各円の座標の初期値です。ここからどれくらい離れたかで色を変えます。
point_diffは、マウスから逃げる円の、逃げる距離の最大値です。つまり、円はマウス位置からpoint_diffを半径とした円周上まで逃げます。
diff_x,diff_yは、マウス位置と円の中心を比べた時の、x,y座標の差です。この値を使って、point_diffと比べます。
2点間の距離を調べるときには三平方の定理を使うわけですが、一応競技プログラミング界隈の人間なので、別に気にしなくても良い誤差を気にしてsqrt()は使わず、距離の2乗で比較しています。
#include "ofApp.h" #include <vector> #include <utility> #include <algorithm> #include <cmath> #define rep(i,j,k) for(int i=j;i<k;i++) int num=10; //縦横の個数num class Pic{ public: float x,y,r; float def_x,def_y; float diffx,diffy,point_diff; ofColor c; void init(float x0,float y0,float r0,float point_diff0){ x=x0; y=y0; r=r0; point_diff=point_diff0; def_x=x; def_y=y; } void move(){ //元の位置からの距離を測って、その距離をそのままHSBのH要素に c.setHsb(ofMap((x-def_x)*(x-def_x)+(y-def_y)*(y-def_y),0,5000,0,255),255,255); ofSetColor(c); ofDrawCircle(x,y,r); float distx=ofGetMouseX()-x, disty=ofGetMouseY()-y; //円の中心とマウスの距離がpoint_diffより小さい時(逃げるところ) if(distx*distx+disty*disty<point_diff*point_diff){ x+=(distx<0)?0.5:-0.5; y+=(disty<0)?0.5:-0.5; //逃げた円が元に戻るところ }else { distx=x-def_x; disty=y-def_y; if(distx) x+=(distx<0)?0.2:-0.2; if(disty) y+=(disty<0)?0.2:-0.2; } } }; vector<vector<Pic>> pic(num,vector<Pic>(num)); void ofApp::setup(){ ofSetRectMode(OF_RECTMODE_CENTER); ofSetFrameRate(60); ofSetBackgroundColor(0); ofSetColor(255); ofSetCircleResolution(64); ofNoFill(); //初期化。numの値によって自動で変わるようになっている。 float pos=ofGetHeight()/num; rep(i,0,num)rep(j,0,num)pic[i][j].init(pos/2+pos*i, pos/2+pos*j, pos/2,70); } void ofApp::update(){ } void ofApp::draw(){ rep(i,0,num)rep(j,0,num)pic[i][j].move(); }
フレア
今までで一番好きな見た目の作品です!見た目がフレアっぽいのでね。
「フレア」
— string s="ごつちやん"; (@gotutiyan_kapi) 2018年5月24日
すごく気に入りましたね、うん#openframeworks
コードとその解説は以下から。https://t.co/6wpsCXPyji pic.twitter.com/ZaczGONb9V
仕組みは以下のようになっています。(説明には最後に載せたコードで使われる変数名を含みます)
まず、点をdiv個等間隔に打った円を作ります。(画像はdiv=60)
次にこれの大小を変えてnum個配置します。
あとは同じ放射状にある点を繋いで、かつ全ての点をノイズでずらせば完成ですね!
コードは以下の通りです。
#include "ofApp.h" #include <vector> #include <utility> #include <algorithm> #define rep(i,j,k) for(int i=j;i<k;i++) int num=30; //並べる円の個数 class Pic{ public: float div=60,r; //divは何個点を打つか vector<pair<float,float>> v; //点の情報(x,y)をペア(first,second)で格納 vector<float> noiseSeed; //初期化関数 void init(float r0){ rep(i,0,div)noiseSeed.push_back(ofRandom(0,1000)); r=r0; rep(i,0,div)v.push_back(pair<float,float>(0,0)); } void move(){ rep(i,0,div){ noiseSeed[i]+=0.01; //ラジアンをノイズでずらすことで、打った点をずらす v[i].first=r*cos(DEG_TO_RAD*(i*(360/div)+ofMap(ofNoise(noiseSeed[i]),0,1,0,30))); v[i].second=r*sin(DEG_TO_RAD*(i*(360/div)+ofMap(ofNoise(noiseSeed[i]),0,1,0,30))); } } }; vector<Pic> pic(num); void ofApp::setup(){ ofSetRectMode(OF_RECTMODE_CENTER); ofSetFrameRate(60); ofSetBackgroundColor(0); ofSetColor(255); ofSetCircleResolution(64); ofNoFill(); rep(i,0,num)pic[i].init(i*(250.0/num)); } void ofApp::update(){ } void ofApp::draw(){ ofTranslate(ofGetWidth()/2,ofGetHeight()/2); rep(i,0,num){ pic[i].move(); ofColor c; c.setHsb(i*(255.0/num),255,255); ofSetColor(c); if(i){ rep(j,0,pic[i].v.size()){ //隣同士の円の各点について線を引く ofDrawLine(pic[i-1].v[j].first,pic[i-1].v[j].second,pic[i].v[j].first,pic[i].v[j].second); } } } }
Processingでブロック崩しを作る
本記事は,以下のURLにて新しく書き直されました.新しい記事の方が圧倒的に詳しい内容になっていますので,ぜひご覧ください.
ブロック崩しを作ります。完成図は以下のツイートの通りです。
今日部活でブロック崩しの話題が出たのでそれっぽく実装。
— ご つ ち や ん (@gotutiyan_kapi) 2018年5月9日
いくつかテクを思い出したので、それを実装したらだいぶそれっぽくなった。 pic.twitter.com/52sAIZVs6L
今回作るのは、「ボールがブロックに当たったらブロックが消える」部分だけです。操作できるバーなどは扱いません。
初心者は、ボールと四角の当たり判定で頭が爆発しがちです。そこをクリアできるようになるのが目標です。
size(500,500)で、50個のブロックを並べます。
・ボールを動かす
まずは、準備体操として、ボールを動かす部分を解説します。
ボールを動かすには、
・ボールの中心のx,y座標と半径
・x方向、y方向のスピード
が必要になります。これらをballx,bally,r,speedx,speedyとします。
void draw()の中で、ballx+=speedx, bally+=speedyという形で足すことで、ボールは動きます。
float ballx=width/2,bally=400,speedx=5,speedy=5,r=50; void setup(){省略} void draw(){ ellipse(ballx,bally,r,r); ballx+=speedx; bally+=speedy; }
・跳ね返り
次に跳ね返るという処理ですが、頭を爆発させずに目的の座標をコードでしっかり表現できることが大切ですね。
まずは以下のWord感バリバリの図を見てみましょう。
以下、この4点を0時、3時、6時、9時の点と呼びます。
まず把握しなければいけないのは、座標を加算するにしろ、壁で跳ね返すにしろ、あくまでも「x軸方向とy軸方向に分けて考えること」です。斜めに移動している物体でも、それはx,y座標を同時に動かしているから斜めに移動しているだけです。
跳ね返りも、x軸での跳ね返り、y軸での跳ね返りと別々に書くことになります。
跳ね返りのコードは以下の通りです。
//x軸 if(ballx-r/2<0 || ballx+r/2>width)speedx*=-1; //y軸 if(bally-r/2<0 || bally+r/2>width)speedy*=-1;
今回は偶然、xとyが変わるだけで、他は全く変わりません。
x軸については、3時の点がwidthより大きくなるか、9時の点が0より小さくなれば跳ね返ります。
y軸については、6時の点がheightより大きくなるか、0時の点が0より小さくなれば跳ね返ります。
また、跳ね返すということは、「ある方向に進んでいたものが逆方向に進む」ことです。もう少し言えば、「正の値を足していたものが負の値を足されるようになる」ことです。
つまり、跳ね返りの処理はspeedx,speedyの正負を入れ替えるだけで済みます。これは重要な考え方です。
(もちろん、負の値を足していたものが正の値を足されるようにする、と言い換えることもできます。)
・いざ、ブロックを崩す
さて、ブロックを崩します。崩すためには、最初にブロックを配置する必要があります。
今回は配列を使った実装を考えます。
まず必要な配列は以下の通りです。
・各ブロックの、左上のx,y座標を保存するint配列 「x,y」
・各ブロックが生きているかを管理する二次元boolean配列「ok」
今回は横に10個、縦に5個並べるので、x[ 10 ]、y[ 5 ]、ok[10][5] の要素数で用意します。
int x[]=new int[10],y[]=new int[5]; boolean ok[][]=new boolean[10][5]
・配列の初期化
配列の初期位置を決めます。
setup()でfor文を回して代入していきます。
サイズは500の正方形を想定していて、横に10個並べ、縦に5個並べます。
これにより1つあたりのブロックの大きさは横50*縦25にしたいと思います。
この想定のもとで、rectで書くときの左上の座標を決めていくと、
x={0,50,100,150,,,,450}
y={0,25,50,75,100}
のような等差数列になるので、for文のループ変数をうまく使って代入します。
また、最初は全てのブロックが生きているので、okは全てtrueです。
void setup(){ for(int i=0;i<10;i++) x[i]=i*50; for(int i=0;i<5;i++)y[i]=i*25; for(int i=0;i<10;i++)for(int j=0;j<5;j++) ok[i][j]=true; }
・実際に四角を描く
配列を使って四角を書いていきます。配列を用いるということから、やはりfor文を回す中で50個描くという発想です。
for(int i=0;i<10;i++){ for(int j=0;j<5;j++){ if(ok[i][j]) rect(x[i],y[j],50,25); } }
x[ ]には添字 i を、y[ ]には添字 j を使うことに注意です。
これにより、
(x[0],y[0]) (x[1],y[0]) (x[2],y[0])...(x[9],y[0])
(x[0],y[1]) (x[1],y[1]) (x[2],y[1])...(x[9],y[1])
...........
(x[0],y[4]) (x[1],y[4]) (x[2],y[4])...(x[9],y[4])
という形で、かつ、okがtrueであればブロックが描かれます。
(x[i],y[j])のブロックにはok[i][j]が対応していることが重要です。
・ブロックとの当たり判定
次に当たり判定です。これも、「for文を回す中で、50個のブロック全てについて当たっているかどうかを順番に見ていく」という発想です。
for(int i=0;i<10;i++){ for(int j=0;j<5;j++){ if(y[j]<bally-r/2 && bally-r/2<y[j]+25 && ballx+r/2>x[I] && ballx-r/2<x[i]+50 && ok[i][j]){ speedy*=-1; ok[i][j]=false; } } }
当たり判定のif文が非常に複雑ですが、これは結局「0時の点がブロックの中に入っていて、3時の点と9時の点のどちらかのx座標が、ブロックのx座標の範囲と被っている」ことを示しています。
まず前半のy[j] < bally-r/2 && bally-r/2 < y[j]+25 では、y座標について考えていて、以下の図の範囲に対応しています。条件式の文法上2つに分かれていますが、内容は
y[i] < bally-r/2 < y[i]+25
の不等式を書いているだけです。
最後に、ok[i][j]がtrue の時に当たり判定を発動させます。okは四角を描くかどうかだけでなく、当たり判定の有無にも関わってきます。
後半のballx+r/2 > x[i]&&ballx-r/2 < x[i]+50 では、
ballx+r/2 > x[i] が3時の点がブロックの左側より右側にある
ballx-r/2 < x[i]+50 が9時の点がブロックの右側より左側にある
ことを示します。何言ってんだって感じですね、ほんとに。
結局、ボールの中心が以下の図の範囲にあるときです。だいぶ広めに取っています。
これらの条件式を全て満たしたとき、ボールは跳ね返ります。つまり、speedyにマイナスを掛けます。また、ok[i][j]をfalseにすることで、当たったブロックはそれ以降描かれなくなります。
x軸周りの当たり判定を広く取ることで、ブロックに対してボールが真横から当たることを未然に防いでいるので、speedyをいじるだけで結構自然な挙動になります。
・最後にひと足し
さあ、これでだいぶ形にはなりました。もっと当たり判定を厳密にするために、当たり判定のfor文の中に魔法の1行を加えます。
bally=y[j]+25+1+r/2
これですね。これなんですね。
これは何をしているかというと、当たった瞬間に0時の点を、ブロックの下側より1ピクセル下にずらします。つまり、ボールとブロックが被らないようにします。
これにより、座標の更新による当たり判定の重複などを起こりにくくします。
speedyの値にもよりますが、時と場合と運により、当たり判定が重複して発生し、speedy*=-1が2回素早く行われて結局意味が無くなったり、消したブロックのそのさらに上のブロックまで突っ込んだりと言った不具合がよくあります。この魔法の1行は(多分)それらを全て解決します。
最終的なコードは以下のようになります。
int x[]=new int[10],y[]=new int[5]; boolean ok[][]=new boolean[10][5]; float ballx=width/2,bally=400,speedx=5,speedy=5,r=50; void setup(){ size(500,500); //初期化 for(int i=0;i<10;i++) x[i]=i*50; for(int i=0;i<5;i++)y[i]=i*25; for(int i=0;i<10;i++)for(int j=0;j<5;j++) ok[i][j]=true; } void draw(){ background(255); //四角を描くところ for(int i=0;i<10;i++){ for(int j=0;j<5;j++){ if(ok[i][j])rect(x[i],y[j],50,25); } } //ボールを描くところ ellipse(ballx,bally,r,r); ballx+=speedx; bally+=speedy; if(ballx-r/2<0 || ballx+r/2>width)speedx*=-1; if(bally-r/2<0 || bally+r/2>width)speedy*=-1; //当たり判定の部分。当たれば跳ね返して、okをfalseに。 for(int i=0;i<10;i++){ for(int j=0;j<5;j++){ if(y[j]<bally-r/2 && bally-r/2<y[j]+25 && ballx+r/2>x[i]&&ballx-r/2<x[i]+50 && ok[i][j]){ speedy*=-1; bally=y[j]+25+1+r/2; ok[i][j]=false; } } } }
・・・たくさん書きましたね。本当はもっと簡素なものにするつもりだったんですが。書き出すと止まらないですね・・。ではでは。
AOJ-ICPC 「Prime Gap」
問題
Prime Gap | Aizu Online Judge
素数を並べた要素数10^5の数列 { 2,3,5,7,11,13,17....1299709 } がある。この時、「prime gap」を考える。例えば、10であるときには、数列の[7 11]の間に入るので、prime gap は11-7=4となる。
解説
上の問題文は要約です。詳しくは翻訳に突っ込むなりしてください。
まず、入力が素数の時はprime gapは0です。
そうでない時は、入力の値が数列のどこに入るのかを調べます。厳密には、入力x、素数配列をprimeとした時、prime[i] < x < prime[i+1]となるようなiが存在するので、prime[i+1]-prime[i]を出力します。この時のiの求め方ですが、素直にforを回しても良いですが、lower_boundを使うと楽です。
lower_bound(検索する配列の先頭ポインタ, 後ろのポインタ, 検索したい値)
これを使うことで、指定された要素以上の値が現れる最初の位置のイテレータを取得できます。
これにより取得したイテレータと、その1つ後ろのイテレータに格納された要素の差を出力すればこの問題は解けました。
#include <bits/stdc++.h> #define rep(i,j,k) for(int i=(int)j;i<(int)k;i++) #define itrep(i,x) for(auto i=(x).begin(); i!=(x).end();i++) #define Sort(x) sort((x).begin(),(x).end()) #define all(x) (x).begin(),(x).end() #define fi first #define se second #define vi vector<int> #define INF (int)1e9 #define INFL 1e18 #define MOD 1000000007 #define pb push_back #define MP make_pair #define PI 3.1415926535 typedef long long int ll; //typedef std::pair<int,int> P; int D=1; int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0}; using namespace std; //素数判定関数 bool isPrime(int x){ if(x==1)return false; if(x==2)return true; if(x%2==0)return false; rep(i,3,sqrt(x)+1){ if(x%i==0)return false; } return true; } int main(){ vector<int> prime; //素数テーブル prime.pb(2); rep(i,3,1299710){ if(isPrime(i)){ prime.pb(i); } } int x; while(cin>>x&&x){ if(isPrime(x)){ //xが素数ならprime gapは0 cout<<0<<endl; continue; } auto it=lower_bound(all(prime),x); cout<<*it-*(it-1)<<endl; //素数で無ければ、差を出力 } return 0; }
AOJ-ICPC 「君のプライバシーを守れ!」
問題
Save Your Privacy! | Aizu Online Judge
解説
少し入力がめんどくさいのですが、一つずつ流れを追えば大丈夫です。
結局、解答が定まる時は、「各データセットの最後にあるK個ある番号を、全部知っている構成員がただ1人いる時」です。情報を漏らした構成員は1人だけなので、全部知っている人が2人いたらどちらが漏らしたか分からないし、逆に誰も居なければ矛盾したことになります。このような時、−1を出力します。
解法としては、まずは2次元配列vを作って、v[構成員番号][情報を知っている構成員番号] という形で管理します。知っている時1、知らない時0です。例えば、2番目の構成員が3番目の構成員の情報を知っている時、v[1][2]=1 です。0オリジンであることに注意してください。
次にK個のデータを配列ans[ ]に格納すれば、いよいよ見比べる作業に移れます。
各構成員とK個のデータを見比べて、ある構成員が全ての番号について知っていればbool ok がtrueのまま降りてくるので、そのような時countを増やします。ansnumには、答えとなる構成員の番号が入ります。
結局、countが1の時、上記の解答が定まる条件に合致するので、ansnumを出力します。
#include <bits/stdc++.h> #define rep(i,j,k) for(int i=(int)j;i<(int)k;i++) #define itrep(i,x) for(auto i=(x).begin(); i!=(x).end();i++) #define Sort(x) sort((x).begin(),(x).end()) #define all(x) (x).begin(),(x).end() #define fi first #define se second #define vi vector<int> #define INF (int)1e9 #define INFL 1e18 #define MOD 1000000007 #define pb push_back #define MP make_pair #define PI 3.1415926535 typedef long long int ll; //typedef std::pair<int,int> P; int D=1; int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0}; using namespace std; int main(){ int n; while(cin>>n &&n){ vector<vector<int>> v(n,vector<int>(n)); //v[構成員番号][相手の構成員番号] rep(i,0,n){ int x; cin>>x; //最初にデータの個数を読み込み rep(j,0,x){ //データをx個受けます int num; cin>>num; num--; v[i][num]=1; } } int k; cin>>k; //実際に漏洩があった構成員番号を受けます。 vector<int> ans; rep(i,0,k){ int num; cin>>num; num--; ans.pb(num); } int count=0,ansnum=0; rep(i,0,n){ bool ok=true; rep(j,0,ans.size()){ //i+1番目の構成員について、1つでも情報を知らなければその時点でokはfalseになります。 if(v[i][ans[j]]!=1)ok=false; } //okがtrueのまま降りてこれば、i+1番目の構成員は全てを知っていました。情報を漏らすことができます。 if(ok){ count++; ansnum=i+1; } } if(count==1)cout<<ansnum<<endl; else cout<<-1<<endl; } return 0; }
AOJ-ICPC 「審判は君だ!」
問題
You Are the Judge | Aizu Online Judge
解説
問題の方針は「ICPCの順位づけ」と変わりません。ほぼ同じ問題です。
gotutiyan.hatenablog.com
正解したかどうかのAC、不正解数のWA、ペナルティのtimをそれぞれ準備して、R個のデータについて格納していきます。
その後、AC_countの配列に結局各チームが何問正解したかどうかを格納します。
最後にpairを入れ子にした配列で、「問題数、ペナルティ、チーム番号」の順に入れて、一気に昇順ソートします。ここで、問題数は降順にソートしたいので、あらかじめマイナスをつけて入れておきます。
「ICPCの順位づけ」と異なるところは、同順位のときの扱い程度です。
#include <bits/stdc++.h> #define rep(i,j,k) for(int i=(int)j;i<(int)k;i++) #define itrep(i,x) for(auto i=(x).begin(); i!=(x).end();i++) #define Sort(x) sort((x).begin(),(x).end()) #define all(x) (x).begin(),(x).end() #define fi first #define se second #define vi vector<int> #define INF (int)1e9 #define INFL 1e18 #define MOD 1000000007 #define pb push_back #define MP make_pair #define PI 3.1415926535 typedef long long int ll; //typedef std::pair<int,int> P; int D=1; int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0}; using namespace std; int main(){ int T,P,R; while(cin>>T>>P>>R && T){ vector<vector<int>> AC(T,vector<int>(P,0)),WA(T,vector<int>(P,0)); vector<int> tim(T,0); rep(i,0,R){ int t,p,timee; string s; cin>>t>>p>>timee>>s; t--; p--; if(s=="CORRECT"){ AC[t][p]++; tim[t]+=timee+WA[t][p]*1200; }else if(s=="WRONG"){ WA[t][p]++; } } vector<int> AC_count(T,0); rep(i,0,T){ rep(j,0,P){ if(AC[i][j])AC_count[i]++; } } vector<pair<int,pair<int,int>>> rank(T); rep(i,0,T)rank[i]=MP(-AC_count[i],MP(tim[i],i+1)); Sort(rank); rep(i,0,T){ cout<<rank[i].se.se<<" "<<-rank[i].fi<<" "<<rank[i].se.fi<<endl; } } return 0; }