リスト構造はデータの挿入や削除が得意と書きました。
このデータ構造に合ったシチュエーションを考えてみます。
シューティングゲームの弾の扱いについてシミュレートしてみましょう。
自機の弾について↓のように設定します。
弾は連射可能 画面外に出るか敵に当たった場合消える |
このように設定した場合、↓の状況が考えられます。
何発発射されるか分からない 先に撃った弾から消えるとは限らない |
何発発射されるか分からない |
この状況はベクタでもリストでも問題ありません。
データの個数が分からない時のためのコンテナですから、どちらも得意とするところです。
先に撃った弾から消えるとは限らない |
こちらの状況は、リストが有利ですね。
ベクタは最後尾のデータを削除するのは得意ですが、途中のデータを消すのは不得意です。
と言う訳で、これをリストで作ってみましょう。
実際に発射は出来ませんから、状況を考えてシミュレートします。
弾の構造体は次のようにします。
struct Shot { int x; int y; bool bShoot; }; |
X座標とY座標、発射中のフラグの3つです。
座標はデータを区別するために設定しました。
発射中フラグがfalseのデータは画面外に出たか敵に当たって消える弾を指しています。
発射した弾の中で、発射中フラグがfalseのデータを削除するプログラムを作ります。
まずは、ヘッダファイル、構造体宣言、main関数の一部から作ります。
<sample program cpp017-01>
#include <cstdlib> #include <ctime> #include <list> #include <iostream> struct Shot { int x; int y; bool bShoot; }; int main() { srand((unsigned int)time(NULL)); std::list<Shot> lstShot; return 0; } |
ここから弾を発射(追加)していきますが、何発撃つか分からない状況を作るため乱数を使います。
また、座標と発射中フラグも乱数で設定して、リストに追加しましょう。
<sample program cpp017-02>
#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>
struct Shot {
int x;
int y;
bool bShoot;
};
int main()
{
srand((unsigned int)time(NULL));
std::list<Shot> lstShot;
Shot work;
int shotMax = rand() % 6 + 5;
for (int i = 0; i < shotMax; i++) {
work.x = rand() % 100;
work.y = rand() % 100;
if (rand() % 2) {
work.bShoot = true;
}
else {
work.bShoot = false;
}
lstShot.push_back(work);
}
return 0;
}
|
乱数を使って、5発から10発の弾を発射するようにしました。
X、Y座標は0から99までの数値が入ります。
フラグはif文を使って、trueかfalseを入れるように作りました。
最後に、イテレータを使って発射後の状況を表示します。
<sample program cpp017-03>
#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>
struct Shot {
int x;
int y;
bool bShoot;
};
int main()
{
srand((unsigned int)time(NULL));
std::list<Shot> lstShot;
Shot work;
int shotMax = rand() % 6 + 5;
for (int i = 0; i < shotMax; i++) {
work.x = rand() % 100;
work.y = rand() % 100;
if (rand() % 2) {
work.bShoot = true;
}
else {
work.bShoot = false;
}
lstShot.push_back(work);
}
std::list<Shot>::iterator it;
std::cout << "削除前" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
return 0;
}
|
<実行結果>
削除前 x = 47 : y = 4 : bShoot = 0 x = 29 : y = 79 : bShoot = 1 x = 11 : y = 38 : bShoot = 0 x = 4 : y = 38 : bShoot = 0 x = 46 : y = 16 : bShoot = 1 x = 93 : y = 54 : bShoot = 1 x = 93 : y = 81 : bShoot = 0 x = 7 : y = 20 : bShoot = 1 続行するには何かキーを押してください・・・
前にイテレータはポインタ変数だと説明しました。
イテレータ経由で構造体のメンバにアクセスするためには「->演算子」が必要です。
上の結果では、4発の弾が削除候補になっています。
削除後のデータも確認出来るように、表示のプログラムをコピーしておきます。
<sample program cpp017-04>
#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>
struct Shot {
int x;
int y;
bool bShoot;
};
int main()
{
srand((unsigned int)time(NULL));
std::list<Shot> lstShot;
Shot work;
int shotMax = rand() % 6 + 5;
for (int i = 0; i < shotMax; i++) {
work.x = rand() % 100;
work.y = rand() % 100;
if (rand() % 2) {
work.bShoot = true;
}
else {
work.bShoot = false;
}
lstShot.push_back(work);
}
std::list<Shot>::iterator it;
std::cout << "削除前" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
std::cout << "削除後" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
return 0;
}
|
<実行結果>
削除前 x = 83 : y = 90 : bShoot = 1 x = 90 : y = 57 : bShoot = 0 x = 60 : y = 81 : bShoot = 0 x = 87 : y = 20 : bShoot = 1 x = 34 : y = 79 : bShoot = 0 x = 95 : y = 50 : bShoot = 0 x = 98 : y = 35 : bShoot = 0 x = 54 : y = 27 : bShoot = 1 削除後 x = 83 : y = 90 : bShoot = 1 x = 90 : y = 57 : bShoot = 0 x = 60 : y = 81 : bShoot = 0 x = 87 : y = 20 : bShoot = 1 x = 34 : y = 79 : bShoot = 0 x = 95 : y = 50 : bShoot = 0 x = 98 : y = 35 : bShoot = 0 x = 54 : y = 27 : bShoot = 1 続行するには何かキーを押してください・・・
ここからが本番です。
データを削除するには、erase関数を使います。
erase関数の引数はイテレータです。
引数で指定したデータを削除してくれます。
繰り返しの中で、発射中フラグがfalseのデータを消してみましょう。
<sample program cpp017-05>
#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>
struct Shot {
int x;
int y;
bool bShoot;
};
int main()
{
srand((unsigned int)time(NULL));
std::list<Shot> lstShot;
Shot work;
int shotMax = rand() % 6 + 5;
for (int i = 0; i < shotMax; i++) {
work.x = rand() % 100;
work.y = rand() % 100;
if (rand() % 2) {
work.bShoot = true;
}
else {
work.bShoot = false;
}
lstShot.push_back(work);
}
std::list<Shot>::iterator it;
std::cout << "削除前" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
for (it = lstShot.begin(); it != lstShot.end(); it++) {
if (!it->bShoot) {
lstShot.erase(it);
}
}
std::cout << "削除後" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
return 0;
}
|
<実行結果>
エラーになりました・・・
メッセージには、イテレータが増加できないとか書いてあります。
このエラーの原因を簡単に書くと、↓のようになります。
erase関数でデータを消した場合、次のデータのポインタが分からなくなってしまっている。
これを防ぐにはerase関数の戻り値を使います。
erase関数は削除したデータの次のデータの場所を戻します。
それをイテレータで受け取ってやれば良いのです。
<sample program cpp017-06>
#include <cstdlib>
#include <ctime>
#include <list>
#include <iostream>
struct Shot {
int x;
int y;
bool bShoot;
};
int main()
{
srand((unsigned int)time(NULL));
std::list<Shot> lstShot;
Shot work;
int shotMax = rand() % 6 + 5;
for (int i = 0; i < shotMax; i++) {
work.x = rand() % 100;
work.y = rand() % 100;
if (rand() % 2) {
work.bShoot = true;
}
else {
work.bShoot = false;
}
lstShot.push_back(work);
}
std::list<Shot>::iterator it;
std::cout << "削除前" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
for (it = lstShot.begin(); it != lstShot.end(); it++) {
if (!it->bShoot) {
it = lstShot.erase(it);
}
}
std::cout << "削除後" << std::endl;
for (it = lstShot.begin(); it != lstShot.end(); it++) {
std::cout << "x = " << it->x
<< " : y = " << it->y
<< " : bShoot = " << it->bShoot
<< std::endl;
}
return 0;
}
|
<実行結果>
上手く終わるケースもあればエラーで止まるケースもあります・・・
実は、もう1段階対応しなければならない事があります。
erase関数は消した次のデータの場所を返していると書きました。
と言う事は、イテレータは勝手に次へ進んでいるのです。
for文の最後に「it++」と書いてありますから、
データを削除した場合、次のデータに移っているにも関わらずさらに1つ先へ進んでいる
のです。
そこで↓のように対処します。
<sample program cpp017-07>
#include <cstdlib> #include <ctime> #include <list> #include <iostream> struct Shot { int x; int y; bool bShoot; }; int main() { srand((unsigned int)time(NULL)); std::list<Shot> lstShot; Shot work; int shotMax = rand() % 6 + 5; for (int i = 0; i < shotMax; i++) { work.x = rand() % 100; work.y = rand() % 100; if (rand() % 2) { work.bShoot = true; } else { work.bShoot = false; } lstShot.push_back(work); } std::list<Shot>::iterator it; std::cout << "削除前" << std::endl; for (it = lstShot.begin(); it != lstShot.end(); it++) { std::cout << "x = " << it->x << " : y = " << it->y << " : bShoot = " << it->bShoot << std::endl; } for (it = lstShot.begin(); it != lstShot.end(); ) { if (!it->bShoot) { it = lstShot.erase(it); } else { it++; } } std::cout << "削除後" << std::endl; for (it = lstShot.begin(); it != lstShot.end(); it++) { std::cout << "x = " << it->x << " : y = " << it->y << " : bShoot = " << it->bShoot << std::endl; } return 0; } |
<実行結果>
削除前 x = 89 : y = 46 : bShoot = 0 x = 57 : y = 1 : bShoot = 1 x = 66 : y = 13 : bShoot = 0 x = 97 : y = 38 : bShoot = 1 x = 19 : y = 4 : bShoot = 1 x = 73 : y = 8 : bShoot = 0 x = 11 : y = 56 : bShoot = 1 削除後 x = 57 : y = 1 : bShoot = 1 x = 97 : y = 38 : bShoot = 1 x = 19 : y = 4 : bShoot = 1 x = 11 : y = 56 : bShoot = 1 続行するには何かキーを押してください・・・
for文の増分を消している事に注意してください。
erase関数によってデータを消した場合は、イテレータを進めないようにしました。
これで完成です!
2Dグラフィックスが扱えるようになったら、もう一度やってみましょう。