第 8 回:ファイル入出力とアルゴリズム

インタラクティブ・ゲーム制作
<プログラミングコース>
第8回
ファイル入出力と
アルゴリズム
今日の内容
• やるやる言いつつできなかった
– ファイル入出力
• 本質ではないけど見た目もこだわる
– 石のアニメーション
• アルゴリズムをガチで考える
– 置ける場所とひっくり返し判定
こ、今度こそ!
ファイル入出力
ファイル入出力の重要性
• セーブ/ロードをしたいなら必須処理
• ソース中にパラメータを直書きするのは
「ハードコーディング」と言われて、
現場では忌み嫌われる行為
– ちょっとマップを手直しするのにもビルドが
必要=Visual Studioが必要=マップも自分で
作らなくてはいけなくなる=あばばばばばb
読み書きのターゲットは
• とりあえずテキストファイル
– 特にCSVやタブ区切りテキストは扱えると
超便利
• マップデータ、キャラクタパラメータ、
シナリオデータ、セーブデータなどなど
• 「テキストファイルとか見られたら
恥ずかしいし…」とか言うのは後だ!
とりあえず使うもの
• C++の標準機能だけでやってみよう
– 読み込みは「ifstream」クラス
– 書き込みは「ofstream」クラス
• インクルードは以下の通り
– #include <iostream>
– #include <fstream>
– 両方必要なので気をつける
• 必要に応じてusing namespace std;も
書き込みは絶望的に簡単だ!
• インスタンス生成時
にファイル名指定
• is_open()で開けて
いるかチェック
• <<で書き込むデータ
を繋げて流し込む
• 最後はclose()
ofstream out_file(“ファイル名”);
if(out_file.is_open() == false) エ
ラー処理;
out_file << “書き込みたい文字” <<
endl;
// int値やdouble値も<<で繋いで書ける
// もちろんstring型やchar型もOK
out_file << x << “,” << y << endl;
//書き込み終わったらcloseして終了
out_file.close();
読み込みも
ただ読み込むだけなら簡単だ!
• インスタンス生成時
にファイル名指定
• is_open()で開けて
いるかチェック
• getline()で
1行ずつ取り出し、
処理する
• 最後はclose()
ifstream
in_file(“ファイル名”);
string
lineStr;
vector<string> readBuffer;
if(in_file.is_open() == false) {
エラー処理;
}
// 1行ずつwhileループで読み出す
while(getline(in_file, lineStr) ==
true) {
// lineStrに1行分入る
// とりあえず配列にしまうならこう
readBuffer.push_back(lineStr);
}
in_file.close();
結局何が面倒って
• 読み込んだ後の文字列処理なんです
• C++のstringクラスは基本的な機能しか
ないので、ちょっと高度なことをやろう
とすると、自前で頑張るか、boostなどに
頼ることになります。
• 最低限の処理を使えるようになり、
データを取り出しやすいファイル構造を
作ろう
区切り取り出し関数実装例
vector<string> fk_StrSplit(string argStr, string
argToken)
{
vector<string>
retStrArray;
string::size_type
curPos = 0, nextPos = 0;
while(nextPos != string::npos) {
nextPos = argStr.find(argToken, curPos);
retStrArray.push_back(argStr.substr(curPos,
nextPos-curPos));
curPos = nextPos+argToken.size();
}
return retStrArray;
}
区切って取り出す
• fk_StrSplit()関数
– 第1引数の文字列を、第2引数の文字で
区切り、バラした結果を返す
– vector<string>型で受け取る
• FKUT/Misc.hで定義してあるので、
利用する場合はこれをインクルードする
整数値・実数値変換
• Cの標準関数をなんだかんだで使う
– atoi()が整数変換にあたる
– atof()が実数変換にあたる
• ただしstring型の変数は直接引数に
渡せないので、c_str()関数を使う
– atoi(anyStr.c_str())のようにする
• 実数値は誤差に厳しいものだと変換時に
値がズレることがあるので注意
データ形式を仮決めしよう
• 次のようなテキストファイルを作ること
にする
– 最初の行に[MY BOARD DATA]と表記する
– 次の行に石の個数を記述する
– 3行目以降から、
1行で石1個分を表すことにする
• X座標, Z座標, 色指定(とりあえず英語で)
データの例
[BOARD DATA]
4
-5.0, -5.0, Black
5.0, -5.0, White
5.0, 5.0, Black
-5.0, 5.0, White
細かいところだけど重要な
石のアニメーションで
使っているテクニック
ポイントになるもの
• update()関数の利用
– 毎フレーム呼び出して動きを更新する
– アニメーションの進行状態が分かるように
メンバ変数を持たせておく
• 三角関数おいしい!
– 一番好きな関数です!
– 時間経過に応じて0~1の値を得るのに便利
動きを付ける時によくやるやり方
• 線形補間
P = (1 - t)*(startPos) + t*(endPos)
• 半分半分また半分
P = 0.5*(nowPos) + 0.5*(endPos)
• 色々な数式や関数の特徴を覚えておくと、
色んな場面で役立ちます!
本日のメインディッシュ
置ける場所とひっくり返し判定
置ける場所の定義(1)
• 置くことで
ひっくり返せること
– そのためには、
置く場所の
周囲8マスに
相手の石が
置かれているのが
前提条件になる
– 初手で黒が置けるのは、
前提条件のみだと
右の赤いセル
○ ●
●○
あるセルにおける
周囲の情報を得るには
• cellInfo[x][y]の周囲8セル
– [x-1][y-1], [x][y-1], [x+1][y-1]
– [x-1][y],
[x+1][y]
– [x-1][y+1], [x][y+1], [x+1][y+1]
• 範囲外(0未満、8以上)の場合は対象外
• これをこのままコーディングすると
ださいので、座標のペアを配列に
しまってループで処理できるとクール
置ける場所の定義(2)
• 相手の石があった
方向の先に、
自分の石があること
– 端に到達した場合は
その時点でNG
– 隣接する相手の石が
複数存在した場合は、
それぞれの方向で判定
– 初手の場合青いセルが
最終的にOKになる
○ ●
●○
実装方針
• 位置と方向を与えて、その方向を調べる
関数を作る
• 先ほどリストアップした隣接した相手の
石リストを関数に流し込んでいく
• あとは実演!
TO BE CONTINUED…