忍者ブログ
なんとなくだらだらと。方向性はまだ決まってない。 当方のプログラムでは、山田巧さん作成のDXライブラリを利用させていただいてます。 本サイト http://homepage2.nifty.com/natupaji/DxLib/index.html DX Library Copyright (C) 2001-2008 Takumi Yamada.
[34] [33] [32] [31] [1] [30] [29] [28] [27] [26] [25]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

以前、このタイトルでエントリした覚えがあるような気がする。

ほんと数日でかなりの高みに登ってしまった。
C++を極める道はまだまだ先が長いんだけど、コンソールRPG編はあと数回で
完結できると思う。


できるか分らないことを、参考書片手にとりあえずやっつけてみる。
出来たソースに電気が流れないと、どうにも理解できない。
この規模のプログラムで、すでに俺の理解を超えて「書いてみたらできた」部分が
結構出てきてる。


Entry022.png


ダウンロード  Entry023.zip (2009.08.03)






セーブロード完備できてしまった。



まずはセーブから。ホントはロードから作ったんだけど、結果的にセーブを先に
作るほうが早かった。


main.cpp

#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>     // 乱数取得に必要
#include <time.h>       // rand()の初期化に必要
#include <windows.h>    // 表示のウェイトのためだけに。
#include <list>
#include <fstream>

using namespace std;



// セーブ処理
/*void Save(Player &p)
{
    fstream file;
    string sstr = p.GetSaveTxt();

    file.open("savedata.dat",ios::out);
    if(file.is_open()){
        file << sstr;
        cout << "Save Success...\n";
    }
    file.close();

}*/

// セーブ処理
void Save(Player &p)
{
    ofstream ost("savedata.dat");
    ost << p.GetSaveTxt();

    if(ost) cout << "Save Success...\n";
    ost.close();
}




最初に作った、今までの方法が上のコメント式。
ファイルストリームを指定してファイル「savedata.dat」を開く。
ファイルのオープンに成功したらセーブ用に準備した文字列を書きこむ。
開いたファイルハンドルは必ず閉じる。お約束。

下側の、今回利用した方法は苦労の結晶。
ロードのときにいっしょに説明入れる。


Player.h


class Player : public Unit
{
public:
    bool Stay(int price);               // 宿に泊まる
    void AddMoney(int gd);              // 金額加算
    void ShowAllStatus();
    void Training(TrainingMenu tM);       // 強化訓練(3種)
    int  TrainingCost(TrainingMenu tM);   // 強化コスト
    string getShowHP();
    string GetSaveTxt();                    // セーブ用の文字列を作る

};



string Player::GetSaveTxt()
{
    stringstream sstr;
    sstr.str("");

    sstr << Name << " " << MaxHP << " " << Atp << " " << Dfp << " " << Agi << " " << Money;

    return(sstr.str());
}



セーブデータ用の文字列を作る。
メンバ変数にして隠蔽してるから、それならちゃんとPlayerクラスに仕事してもらわないと。



で、作ったSave()関数呼び出して、セーブできるようにする。
先にロードから作ったおかげで、問題点を先回りして解決できた。

コンストラクタに傷ついた現在HP入れてなかった。そういうのは考慮してなかった。
セーブロードのために初期化を変更するのも大袈裟な話なんで、それなら話はひとつ。

セーブできるのは、宿屋に泊った(HP=最大)のとき限定。

そういうわけで、宿屋のところに追加してみる。


main.cpp


Result TownEvent(Player& player)
{
    Result res;
    int cmd,cmdcnt = 0;
    static int stayPrice = 10;

    cout << "\n\n街にいます。\n";

    while(1){
        cmd = 0;
        res = NONE;
        player.ShowAllStatus();
        cout << "\n1:出撃\n"
             << "2:宿屋(" << stayPrice << "G)      体力 " << player.getShowHP() << "\n"
             << "3:体力強化(" << player.TrainingCost(THP) <<"G)\n"
             << "4:攻撃訓練(" << player.TrainingCost(TAtk) <<"G)\n"
             << "5:防御訓練(" << player.TrainingCost(TDef) <<"G)\n"
             << "6:素早さ (" << player.TrainingCost(TAgi) <<"G)\n"
             << "9:終了\n"
             << "どうする?";
        if(cmdcnt){
            cin.clear();
            cin.ignore();
        }
        cin >> cmd;
        cout << "\n";
        switch(cmd){
            case 1:     // 出撃
                res = DEPARTURE;
                break;
            case 2:     // 宿泊
                if(player.Stay(stayPrice))
                    Save(player);
                break;
            case 3:     // 体力強化
                player.Training(THP);
                break;




Player.h


class Player : public Unit
{
public:
    bool Stay(int price);               // 宿に泊まる
    void AddMoney(int gd);              // 金額加算
    void ShowAllStatus();
    void Training(TrainingMenu tM);       // 強化訓練(3種)
    int  TrainingCost(TrainingMenu tM);   // 強化コスト
    string getShowHP();
    string GetSaveTxt();                    // セーブ用の文字列を作る

};



bool Player::Stay(int price)
{
    if(HP == MaxHP){
        cout << "体力満タンです。   " << getShowHP() << "\n"
             << "泊まる必要ありませんよ。\n";
    } else if(Money >= price){
        cout << Name << "は、宿に泊まった・・・\n体力が全快した。\n";
        HP = MaxHP;
        Money -= price;
        return true;
    } else {
        cout << "お金がたりませんよ!\n";
    }
    return false;
}



宿屋ひやかすだけでセーブできる → 終了再開で体力回復とかは嫌なんで、
Stay() 関数に宿泊の成否判定を持ち込む。
超むかし、パスワード記録の時代のファミコンRPGにはあったんだけど、
終了再開にひと手間かかるから、それ考えたら割に合うかどうか。

以前、Stay() (宿泊)関数に成否判定入れるのを嫌ってたような気がするんだけど。
まあいいや。

いちおうこれで、セーブできるようになったと思うので確認してみる。
このセーブデータに対応したロード関数を作る。


main.cpp

/*
// プレイヤーデータのロード
// 戻り値はファイル処理の成否ではなく、プレイヤーがデータを使用するとき true
// ロード失敗(初回でデータが無いときなど)、および NewGame 選択時に false
bool LoadData(Player &player)
{
    ifstream ist("savedata.dat");
    if(!ist) return false;

    string name;
    int HP, Atp, Dfp, Agi, Money;

    ist >> name >> HP >> Atp >> Dfp >> Agi >> Money;
    ist.close();

    player.Init(name,HP,Atp,Dfp,Agi,Money);
    cout << "Load Success...\n";
    player.ShowAllStatus();
    cout << "\n\n";

    cout << "前回のデータをロードしますか?(Y/N):";
    char c;
    cin >> c;
    if(c == 'y' || c == 'Y'){
        return true;
    }
    return false;
}

void InitPlayer(Player &player)
{
    if(LoadData(player)) return;

    string name;
    cout << "あなたの名前を入れてください:";
    cin >> name;

    player.Init(name, 30, 8, 5, 6, 0);
}
*/

void InitPlayer(Player &player)
{
    ifstream ist("savedata.dat");
    if(ist) {
        string name;
        int HP, Atp, Dfp, Agi, Money;

        ist >> name >> HP >> Atp >> Dfp >> Agi >> Money;
        ist.close();

        player.Init(name,HP,Atp,Dfp,Agi,Money);
        cout << "Load Success...\n";
        player.ShowAllStatus();
        cout << "\n\n";

        cout << "前回のデータをロードしますか?(Y/N):";
        char c;
        cin >> c;
        if(c == 'y' || c == 'Y') return;
    }

    string name;
    cout << "あなたの名前を入れてください:";
    cin >> name;

    player.Init(name, 30, 8, 5, 6, 0);
}


例によっていろいろ迷走した結果、こんな感じに落ち着いた。
InitPlayer() がファイルロードまでやってしまうのは仕事が多すぎると思ってたけど
ファイルロード関係が思いのほか短く納まったので、InitPlayer 関数に戻すことにした。

読み込んだテキストファイルを分割・解析しないといけないと思ってたんだけど
いろいろ悩んでたら、いい感じに処理してくれる方法を見つけた。
例によって、参考資料そのまんま。
エラー検出もやってないんで、ほんとは結構怖いプログラム。
改造対策もしてない。データ触ろうと思えばすぐに改ざんできる。

ロード側で ifstream 使うようにしたから、セーブ側も同じように ofstream を
使うように変更。こっちも短くなった。


プログラム的な工夫のしどころは、「ロードしますか(Y/N)?」の段階で
すでにロード完了してるってところかな?

初めての起動のときとかエラーになるのをわかってて、イエスノー聞いた上で
「ファイルが見つかりません!」とか言われてもまわりくどいだけ。
だから、ロードできたらそのデータを使うか聞いて、使わないなら新規プレイヤー登録。
ロードすべきデータが無かったら、イエスノー聞かずに新規登録。




あとは俺の美学。

main.cpp


Result Patrol(Player &player, Unit (&eList)[MonsterMaxNum])
{
    Result res;
    Unit enemy;

    cout << player.getName() << "は、街を出て荒野に向かった。\n";

    for(int i=0;i<MonsterMaxNum;++i)
    {
        Sleep(800);
        enemy = eList[i];
        cout << "\n" << enemy.getName() << "が あらわれた!\n";
        res = Battle(player,enemy);
        if(res==WIN){         // プレイヤー勝利
            cout << "勝ち。\n";
            player.AddMoney(enemy.GetMoney());
        } else break;
    }
    if(res==WIN){
        // 戦闘勝利で終了してるってことは、大勝利の予感
        cout << "\n\n*+*+*  完 全 勝 利 !!  *+*+*\n";
        cout << "勇者 " << player.getName() << " の名を永遠に称えるがよい。\n\n";
        res = ABORT;
    }
    if(res==LOSE){
        cout << player.getName() << " は死亡した。\n\n-GAME OVER-\n";
        DeleteFile("savedata.dat");
        res = ABORT;
    }
    return res;
}


プレイヤーが死亡したら、データ消してやる!
死んだらそこでゲーム終了ですよ。賛否両論あるけど、ロストというやつだな。


まあどうでもいい事なんだけど、ファイルの消し方がわからない。
読み込んだりなんやかんやする方法は参考資料いろいろあるんだけど、
ファイルの消し方だけ載ってない。

これを使わなくてもいいと思うんだけど、Win32APIの「DeleteFile」関数なんて
引っ張り出してきた。
ちょうどうまい具合に、Sleep()のためだけに「Windows.h」読み込んでたから
有効活用させてもらう。


e023_2.png


我ながら、なんかおもしろいぞ。
勝てるわけない!って作ってた壁役の「あのひと」に勝ってしまうまでやりこんでる。
やりこみ大将。



ダウンロード  Entry023.zip (2009.08.03)
PR


忍者ブログ [PR]
カレンダー
03 2025/04 05
S M T W T F S
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
フリーエリア
バーコード
ブログ内検索
P R
アクセス解析
カウンター