ページ 11

チョコボの自動生成マップ

Posted: 2010年8月21日(土) 23:23
by バズー
http://www.play21.jp/board/formz.cgi?ac ... &rln=58983
で質問させて頂いた者です。

チョコボの様な自動生成PGを作っております。

教えて頂いたURLを元に色々と試してみているのですが上手くマップを自走生成できません・・・。
どのようにすればよいのか教えて頂けないでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月22日(日) 10:45
by kazuoni
どのページを参考にして、どこまで実装し、何で滞っているのか追記していただけますか?
できれば現在のコードも一緒に挙げて、軽く解説してもらうとよりよい回答が得られるかもしれません。

Re:チョコボの自動生成マップ

Posted: 2010年8月24日(火) 11:25
by バズー
連絡が遅くなってすいません。

http://racanhack.sourceforge.jp/rhdoc/coderoom.html
上記を参考にしております。

区画を作る関数と部屋を作る関数を作ってみたのですが
これをどの様に使うか(正しくはどのタイミングで)がイマイチわかりません。
又、部屋を作った後にどの様に繋ぐかもわからないです・・・。
サイトに書いてある枠線を利用して作るという概念は理解出来るのですが
ソースの書き方が思いつきません。

関数が指定した範囲の枠線を書くものです。
関数が指定した範囲の枠内を塗りつぶすものです。
DXライブラリを使っております。

Re:チョコボの自動生成マップ

Posted: 2010年8月24日(火) 12:21
by softya
こちらは参考になりませんか?
まず、考え方を理解するのが先だと思いますよ。
http://lnl.sourceforge.jp/pukiwiki/inde ... C%C2%B5%DC

Re:チョコボの自動生成マップ

Posted: 2010年8月24日(火) 12:36
by バズー
ご回答ありがとうございます。
考え方は理解できているのですが
プログラムにすることができません・・・。

1、区分をつくる
2、その中に収まる部屋を作る
3、1・2を何回か繰り返す
4、各部屋通しを繋ぐ

4は部屋の同じ位置の区分のライは強制的にあきそこから線を繋げる
※説明が下手ですみません。
イメージ
AAAAAA
AAAAAA | BBB  
AAAAAA | BBB
AAAAAA | BBB
AAAAAA
AAAAAA
|が同じ高さなので線をつけこれを繋げる
縦も同じようにする

Re:チョコボの自動生成マップ

Posted: 2010年8月24日(火) 12:38
by バズー
すいません(汗)
途中で投稿してしまいました。


上のようなことだと思っております。
貼って頂いたURLも見たのですがこれをプログラムで書けないです・・・。

ファイル入出力関数

Posted: 2010年8月24日(火) 13:18
by くじら
初投稿で分かりづらい点色々あるかと思いますがよろしくお願いします.

[1] 質問文
 [1.1] 自分が今行いたい事は何か
行ないたいことはまず初めに読み込みたいテキストファイル名を入力.
ファイルに書かれている半角英字の小文字を大文字に変換してそのファイルに書き込む.
(while文の条件部分はとりあえず2行だけでやってみようと思いcnt2 != 2にしています.)

ababa
aiu
↓このように変換したいと思っています.
ABABA
AIU

 [1.2] どのように取り組んだか
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
int cnt;
int cnt2 = 0;
char buf[1024];
char buf2[1024];
char ans[4][64]={""};

gets(buf2);
fp = fopen(buf2,"r+");
if(fp == NULL){
ferror(fp);
return;
}

while(cnt2 != 2)
{
fgets(buf,sizeof(buf),fp);
for(cnt=0;cnt<1024;cnt++)
{
if(buf[cnt] == 0)
{
break;
}
else if(buf[cnt] == 10)
{
ans[cnt2][cnt] = buf[cnt];
break;
}

else if(buf[cnt] >= 97 && buf[cnt] <= 122)
{
ans[cnt2][cnt] = buf[cnt] - 32;
}
}
cnt2++;
}
fputs(ans,fp);
fclose(fp);
return;
}
 [1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
 Warning W8019 hw-asobi.c 15: Code has no effect in function main
Warning W8075 hw-asobi.c 42: Suspicious pointer conversion in function main

[1.4] 今何がわからないのか、知りたいのか
現在の状況は読み込んだファイルに書き込む事ができない.
while文の中も上手く動作してない.



[2] 環境  
 [2.1] OS : Windows
 [2.2] コンパイラ名 :CPad の Borand C++
[3] その他
3ヶ月前から授業で少しずつ勉強している程度です.
 宣言の仕方、書き方がまだ慣れていないので見づらくてすいません.
もしお時間ありましたら教えてください、よろしくお願いします. 画像

Re:チョコボの自動生成マップ

Posted: 2010年8月24日(火) 13:42
by softya
もっと簡単な事はサンプルなしに自力で出来ますか?

例えば、
1.マップを壁で埋め尽くして初期化。
2.ランダムなスタート地点とゴール点を壁の何処かに決める。
3.スタート地点とゴールを道で結ぶ。
ただし斜めの道を作ってはいけない。必ず90度曲がる道で無くてはいけない。

どこかでわからないなら番号と分からないところを書いてください。

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 14:45
by バグ
テキストファイルの最大行数は決まっていますか?

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 14:52
by toyo
fopenを読み書きモードで開いた場合に読み込みから書き込みに(逆の場合も)モードを変える時はfseek等の関数を使う必要があります

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 15:29
by Mr.Vince
どうも、回答するのは初めてですので、不備がありましたらご容赦ください。


とりあえずざっと読んだ所では、
以下の4点が問題なのではないかと。



・バッファオーバーフローが起きてます

for(cnt=0;cnt<1024;cnt++)

で、実質cntの範囲は0~1024まで、となるのですが、それをインデックスに使っている

ans[cnt2][cnt] = buf[cnt];

などで、ansの2番目のインデックスは最大で64までしかないにもかかわらず(宣言時より)、1024までカウントされてしまい、結果他の変数を打ち消している可能性があると思われます。
ただ、余り長文を入力してはいないようなので、即刻問題が起きる可能性は低いと思うのですが…


・fputsの引数タイプの間違い ※致命的
ansは2次元配列ですので、実質タイプは char**(「charタイプのポインタ」へのポインタ)となります。
fputsの1番目の引数はchar*タイプ(charタイプのポインタ)でなければならないので、問題が起きる可能性が高いです。
fputs(ans[0],fp); など、行数をちゃんと代入してくださいませ。


・cntのリセット
1行目を読み終わった後、再度forループに進入する際、cntが0までリセットされてしまい再度bufの1から読み直しになります。このため二回とも1行目がansに読み込まれます。


・r+モードの衝突 ※致命的
toyoさんも述べていますが、書き換えを行うにはfseekなどを使う必要があるようです。
慣れない内は読んだファイルとは別のファイルへ書き込む を試した方が良いのかもしれません。





とりあえず、※致命的 とある2点を修正すれば一応は動くと思います。

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:00
by フリオ
 
 この場合は、ファイルの内容を全て読み込んでいるので、
ファイルポインタはファイルの末尾に達しているから、そのまま書き込んでも
問題ないのでは?
 

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:04
by バグ
設計思想が全く変わってしまうので、参考にはならないと思いますが…(^_^;)

1:ファイルの行数を数える
2:全文字を保持できるだけの領域を動的に確保
3:読み出しモードでファイルオープン
4:fgets関数を使用して1行分の文字列を読み出し
5:4の文字列をtoupper関数を使用して小文字を大文字へ変換する
6:5の文字列を2の領域に保持
7:ファイルの最後まできたらファイルクローズして8へ、そうでなければ4へ
8:書き込みモードでファイルオープン
9:fputs関数を使用して2の領域に書き込まれている文字列を書き込んでいく
10:ファイルクローズ
11:2の領域を解放

こんな感じです。
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* 1行の最大文字数 */
#define MAX_STR_LEN        1024

/* ファイルの行数を取得する */
int get_line_count(const char* path)
{
    int cnt = 0;
    char str[MAX_STR_LEN] = {0, };
    FILE* pf = fopen(path, "r");

    /* ファイルオープン */
    if (pf == NULL)
        return -1;

    /* 行数のカウント */
    while (fgets(str, 1024, pf) != NULL)
        ++cnt;

    /* ファイルクローズ */
    fclose(pf);
    return cnt;
}

/* ファイルを読み込み、小文字を大文字に変換しつつ、バッファに保持していく */
int read_file(const char* path, const int line, char* buf)
{
    int i = 0;
    size_t j = 0;
    char str[MAX_STR_LEN] = {0, };
    FILE* pf = NULL;

    /* ファイルオープン */
    if ((pf = fopen(path, "r")) == NULL)
        return -1;

    for (i = 0; i < line; ++i)
    {
        /* 1行分の文字列を取得する */
        fgets(str, MAX_STR_LEN, pf);
        
        /* 半角英小文字を大文字に変換する */
        for (j = 0; j < strlen(str); ++j)
            str[j] = (char)(toupper((int)(str[j])));

        /* バッファに保持する */
        strcpy(buf + (i * MAX_STR_LEN), str);
    }

    /* ファイルクローズ */
    fclose(pf);
    return 0;
}

/* 変換した文字列を書き込む */
int write_file(const char* path, const int line, const char* buf)
{
    int i = 0;
    char str[MAX_STR_LEN] = {0, };
    FILE* pf = NULL;

    /* ファイルオープン */
    if ((pf = fopen(path, "w")) == NULL)
        return -1;

    /* 文字列を1行ずつ書き込んでいく */
    for (i = 0; i < line; ++i)
    {
        strcpy(str, buf + (i * MAX_STR_LEN));
        fputs(str, pf);
    }

    /* ファイルクローズ */
    fclose(pf);
    return 0;
}

int main(void)
{
    int line = 0;
    char* buf = NULL;

    /* ファイルの行数を取得する */
    line = get_line_count("test.txt");

    /* ファイルオープンに失敗、もしくは行数が0の場合は終了 */
    if (line == -1 || line == 0)
        return -1;

    /* 読み込んだ行数分のデータを保持する領域を確保する */
    buf = (char*)(malloc(sizeof(char) * line * MAX_STR_LEN));

    /* 領域の確保に失敗した場合は終了 */
    if (buf == NULL)
        return -2;

    /* ファイルの読み込み、書き込みを行う */
    if (read_file("test.txt", line, buf) == -1 || write_file("test.txt", line, buf) == -1)
    {
        /* 領域を解放して終了する */
        free(buf);
        return -3;
    }

    /* 領域を解放して終了する */
    free(buf);
    return 0;
}

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:08
by バグ
>>フリオさん
>>この場合は、ファイルの内容を全て読み込んでいるので、
>>ファイルポインタはファイルの末尾に達しているから、そのまま書き込んでも
>>問題ないのでは?

あぁ、たしかに!
変換したいとあるから、勝手に同じ位置に書き込みしたいのかと解釈していましたが、トピ主のソースを見る限りでは、変換した文字列をファイルの末尾に追加したいのかもしれませんね…(^_^;)

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:40
by くじら
皆様書き込み誠にありがとうございます!

>>バグさん
遅くなってすいません!
特別文字数に指定はありません。

>>toyoさん
seekを導入してようやく書き込めるようになりました!
これはカレントポジションを先頭にしたことによって書き込めたのでしょうか?
それともseekなどの関数にモードを変える機能があるということなのでしょうか?

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:54
by くじら
>>Mr.Vinceさん

指摘いただいた点を書き換えたら動作することができました!
特にまだ二次元配列、ポインタの理解がまだまだなので
とてもわかりやすく助かりました、ありがとうございます!

>>フリオさん

というのはseekじゃない方法があるということでしょうか?
読解力がなくてすいません。。


>>バグさん

短時間で説明文まで凄いです、まだ作りを完璧に理解してませんが
ソースを保存して参考書がわりにさせていただきます笑
行数の部分だけでも理解して取り入れたいと思います!
ほんとうにありがとうございます。

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 16:59
by くじら
皆様ありがとうございました、助かりました!
是非また機会がありましたらよろしくお願いします。

でもできるだけ次がないように努力します!

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 18:40
by フリオ
 
 自分で言っといてなんですが、末尾に追加する場合でも
最初に末尾まで読み込めなかった場合を考えて、fseek による処理を
入れるべきですね。
考えが足りませんでした。
 
 上書きする方法を考えてみました。
 
#include <stdio.h>
#include <ctype.h>

int main(int argc, char *argv[/url])
{
    FILE *fp;
    
    if(argc < 2) return 1;
    if(!(fp = fopen(argv[1], "r+"))) return 1;
    while(1){
        char s[16];
        int c, i;
        long fpos = ftell(fp);
        
        if(fpos < 0) break;
        if(fseek(fp, fpos, SEEK_SET)) break;
        for(i = 0; i < sizeof s - 1 && (c = fgetc(fp)) != EOF; ++ i) s = toupper(c);
        s = '\0';
        if(!fseek(fp, fpos, SEEK_SET)) fputs(s, fp);
        else break;
        if(c == EOF) break;
    }
    fclose(fp);
    return 0;
}

画像

Re:ファイル入出力関数

Posted: 2010年8月24日(火) 18:45
by シエル
私も気になったんですが、fseekってモードを切り換える役割もあるんですか?

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 09:41
by バズー
ご回答ありがとうございます。
こんな感じでしょうか?
rx1,ry1からrx2,ry2に向かって2種類の線を引きます。
先にシタに行くかヨコに行くかです。
RoomLine( rx1, ry1, rx2, ry2, rand()%2 );

void RoomLine( int rx1, int ry1, int rx2, int ry2, int mode ){

    // 差分を取る
    int dx = abs( rx1 - rx2 );
    int dy = abs( ry1 - ry2 );
    int sx = 0;
    int sy = 0;
    
    if(rx1 < rx2) sx = rx1 + (dx/2);
    else sx = rx2 + (dx/2);
    if(ry1 < ry2) sy = ry1 + (dy/2);
    else sy = ry2 + (dy/2);
    
    switch(mode){
        case 0:    
            // 縦方向の線を引く
            if(ry1 < ry2){
                for (int y=ry1; y<ry1+dy+1; y++) {
                    if( Map[y][sx] == 0 ) Map[y][sx] = 1;
                }
            }else{
                for (int y=ry2; y<ry2+dy+1; y++) {
                    if( Map[y][sx] == 0 ) Map[y][sx] = 1;
                }
            }
            
            // 横方向の線を引く
            if(rx1 < rx2){
                // Y軸
                for (int x=rx1; x<rx1+int(dx/2); x++) {
                    if( Map[ry1][x] == 0 ) Map[ry1][x] = 1;
                }
                for (int x=rx2-int(dx/2); x<rx2; x++) {
                    if( Map[ry2][x] == 0 ) Map[ry2][x] = 1;
                }
            }else{
                // Y軸
                for (int x=rx2; x<rx2+int(dx/2); x++) {
                    if( Map[ry2][x] == 0 ) Map[ry2][x] = 1;
                }
                for (int x=rx1-int(dx/2); x<rx1; x++) {
                    if( Map[ry1][x] == 0 ) Map[ry1][x] = 1;
                }
            }
            break;
        case 1:
            // 横方向の線を引く
            if(rx1 < rx2){

                // X軸
                for (int x=rx1; x<rx1+dx+1; x++) {
                    if( Map[sy][x] == 0 ) Map[sy][x] = 1;
                }
            }else{

                // X軸
                for (int x=rx2; x<rx2+dx+1; x++) {
                    if( Map[sy][x] == 0 ) Map[sy][x] = 1;
                }
            } 

            // 縦方向の線を引く
            if(ry1 < ry2){  
                // Y軸
                for (int y=ry1; y<ry1+int(dy/2); y++) {
                    if( Map[y][rx1] == 0 ) Map[y][rx1] = 1;
                }
                for (int y=ry2-int(dy/2); y<ry2; y++) {
                    if( Map[y][rx2] == 0 ) Map[y][rx2] = 1;
                }
            }else{  
                // Y軸
                for (int y=ry2; y<ry2+int(dy/2); y++) {
                    if( Map[y][rx2] == 0 ) Map[y][rx2] = 1;
                }
                for (int y=ry1-int(dy/2); y<ry1; y++) {
                    if( Map[y][rx1] == 0 ) Map[y][rx1] = 1;
                }
            }
            break;    
    }
}

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 09:45
by バズー
連レス失礼します。

通路の作り方はわかるのですが
私が作った関数でどのように部屋と区分を作り
それをどう繋げるかがわかりません・・・。

本当に宜しければで構いません・・・。

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 09:47
by バズー
またまた途中で投稿してしまいました。
申し訳ないです。

本当に宜しければで構いません・・・。
私が作ったサンプルを追記してサンプルソースを作って頂けないでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 10:30
by softya
>rx1,ry1からrx2,ry2に向かって2種類の線を引きます。
>先にシタに行くかヨコに行くかです。

>RoomLine( rx1, ry1, rx2, ry2, rand()%2 );

この関数ちゃんと動きますか?
そもそも壁というか部屋を壊す気がするんですが。

>2.ランダムなスタート地点とゴール点を壁の何処かに決める。
>3.スタート地点とゴールを道で結ぶ。
2の部分が空間の途中で始まっていますので、まず外側の壁から始めるようにしてください。
3の条件に外側の壁は出来るだけ壊さないを付け加えておきます。

それとお手伝いはしますが自力で解けるのが大事だと思っていますので一歩一歩理解をしていく形で進めませんか?

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 17:59
by バズー
すみません・・・動いてるとは思うのですが違うのでしょうか?
0が壁でそれ以外が道か部屋です。

良く読んでみたのですが2の意味がよくわかりません・・・
添付したソースの様な事ではないのでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月25日(水) 19:09
by softya
これは私の説明が悪かったですね。
壁際と言うかマップの端から道を作って欲しかったんです。
最新のは、マップの中のランダムなスタート地点からゴール地点まで道を書いてますよね?
スタート地点とゴール地点をマップの端にして道を作ってみていただけますか?

次のステップとしては、マップ内に部屋を一つ作って部屋の隅をスタート地点を決めてマップ端のゴール地点に道を結ぶをやってみたいと思います。

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 02:51
by バズー
ご回答ありがとうございます。
いえいえ・・・私の理解力がないので・・・ごめんなさい。

// 端から端まで
RoomLine( 0, 0, MAP_W-1, MAP_H-1, rand()%2 );

// 部屋を作って端まで
MakeRoom( 10, 10, 15, 15 );
RoomLine( (int)Avg(10,15), (int)Avg(10,15), MAP_W-1, MAP_H-1, rand()%2 ); // 隅なら第一第二に10,15を入れる

Avgは引数の平均を返す関数です。
作りました。
こういうことであっているのでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 10:53
by softya
そうですね。
まず、マップの端まで道が描けてません。
マップのある空間を灰色で描いてみたら発見しました。

それと両方共座標が乱数で決めれていないので、乱数でお願いします。
// 端から端まで
RoomLine( 0, 0, MAP_W-1, MAP_H-1, rand()%2 );
もスタートとゴールを乱数にしてほしいです。
ちなみに現在のままだとマップ端に道を書いているので、それは避けてください。

同様に
// 部屋を作って端まで
MakeRoom( 10, 10, 15, 15 );
RoomLine( (int)Avg(10,15), (int)Avg(10,15), MAP_W-1, MAP_H-1, rand()%2 ); // 隅なら第一第二に10,15を入も部屋の位置を乱数にしてほしいのと道の接続位置も乱数にして欲しいですね。もちろんゴールも。

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 16:01
by バズー
すみません・・・問題が理解できません・・・。
何を作ればいいのでしょうか?

>>まず、マップの端まで道が描けてません。
>>マップのある空間を灰色で描いてみたら発見しました。
線が引かれているように見えるのですが
何処かおかしいのでしょうか・・・。

// 端から端まで
RoomLine( 0, rand()%(MAP_H-1), MAP_W-1, rand()%(MAP_H-1), rand()%2 );
こういうことですか?

アドバイスを頂いてとてもありがたいのですが良く理解できないです><
本当にわからないんです・・・。

修正したサンプルをどうか・・・どうか・・・頂けないでしょうか・・・。

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 16:23
by softya
分かり辛かったですか?
少し表現を工夫してみてみます。

>線が引かれているように見えるのですが
>何処かおかしいのでしょうか・・・。
void Draw(){
のところの
else DrawBox( x*CEL_SIZE, y*CEL_SIZE, x*CEL_SIZE+CEL_SIZE, y*CEL_SIZE+CEL_SIZE, 0x000000, 255 );

else DrawBox( x*CEL_SIZE, y*CEL_SIZE, x*CEL_SIZE+CEL_SIZE, y*CEL_SIZE+CEL_SIZE, 0x404040, 255 );
に変えてみてください。
それで分かると思います。

>RoomLine( 0, rand()%(MAP_H-1), MAP_W-1, rand()%(MAP_H-1), rand()%2 );
>こういうことですか?

マップ端と言うことでスタートとゴールの両方を
1)x=0 y=1 + rand()%(MAP_H-2)
2)x=(MAP_W-1) y=1 + rand()%(MAP_H-2)
3)x=1 + rand()%(MAP_W-2) y=0
4)x=1 + rand()%(MAP_W-2) y=(MAP_H-1)
の4択でお願いします(角にならないように角は避けています)。
マップ端にスタートとゴールは別として連続で通路ができるのも避けてください。

「マップ端に通路の例」
これはOK。
0000
0000
1100
0100
0100
これはNG。
0000
0000
1000
1000
1100

最後の
>同様に
>// 部屋を作って端まで
>MakeRoom( 10, 10, 15, 15 );
>RoomLine( (int)Avg(10,15), (int)Avg(10,15), MAP_W-1, MAP_H- 1, rand()%2 ); // 隅なら第一第二に10,15を入も部屋の位置を乱数にしてほしいのと道の接続位置も乱数にして欲しいですね。もちろんゴールも。

は、部屋の位置、部屋から出ていく通路の開始の位置、通路のゴールのマップ端の位置全部を乱数にして欲しいって事です。乱数じゃないのは部屋の大きさぐらいですね。
何処に出来るか分からない通路のことも考えて、部屋をマップ端に近づけすぎない工夫も必要です。
上と同様にマップ端に通路ができるのも避けてください。

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 22:44
by バズー
何度もありがとうございます。

端から線を引くと言うのはこういうことでしょうか?
int Diff( int rx1, int ry1, int rx2, int ry2 ){
    int diff;
    if( (abs(rx1-rx2) - abs(ry1-ry2)) > 0 ) diff = 0;
    else diff = 1;
    return diff;
}

void Init(){
    for (int y=0; y<MAP_H; y++) {
        for (int x=0; x<MAP_W; x++) {
            Map[y][x] = 0;
        };
    };

    
    int rx1, ry1, rx2, ry2;
    int r = rand()%2;
    if( r == 0 ){
        rx1 = 0;
        ry1 = 1 + rand() % (MAP_H-2);
        rx2 = (MAP_W-1);
        ry2 = 1 + rand()%(MAP_H-2);
    }else if( r == 1 ){
        rx1 = 1 + rand()%(MAP_W-2);
        ry1 = 0;
        rx2 = 1 + rand()%(MAP_W-2);
        ry2 = (MAP_H-1);
    }


    RoomLine( rx1, ry1, rx2, ry2, Diff( rx1, ry1, rx2, ry2 ) );

Re:チョコボの自動生成マップ

Posted: 2010年8月26日(木) 23:25
by バズー
連レス失礼します。
ランダムにマップを作製するのは
typedef struct{
    int sx,sy,ex,ey;
    int rx,ry;
}ROOM;
ROOM Room[5];

// 範囲内でランダムを返す
int RandRange( int a, int b ){
    int range;
    range = rand()%(b-a-1) + a ;
    return range;
}
int Diff( int rx1, int ry1, int rx2, int ry2 ){
    int diff;
    if( (abs(rx1-rx2) - abs(ry1-ry2)) > 0 ) diff = 0;
    else diff = 1;
    return diff;
}    
         Room[0].sx = rand()%3 + 1;
    Room[0].sy = rand()%3 + 1;
    Room[0].ex = Room[0].sx + rand()%4 + 4;
    Room[0].ey = Room[0].sy + rand()%4 + 4;
    Room[0].rx = RandRange(Room[0].sx,Room[0].ex);
    Room[0].ry = RandRange(Room[0].sy,Room[0].ey);
    
    Room[1].sx = rand()%3 + 1 + 10;
    Room[1].sy = rand()%3 + 1 + 10;
    Room[1].ex = Room[1].sx + rand()%4 + 4;
    Room[1].ey = Room[1].sy + rand()%4 + 4;
    Room[1].rx = RandRange(Room[1].sx,Room[1].ex);
    Room[1].ry = RandRange(Room[1].sy,Room[1].ey);

    MakeRoom( Room[0].sx, Room[0].sy, Room[0].ex, Room[0].ey );
    MakeRoom( Room[1].sx, Room[1].sy, Room[1].ex, Room[1].ey );
    RoomLine( Room[0].rx, Room[0].ry, Room[1].rx, Room[1].ry,  Diff( Room[0].rx, Room[0].ry, Room[1].rx, Room[1].ry ) );
この様な感じでしょうか?
本当にわかりません・・・。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 00:29
by softya
OK!です。
順調だと思いますよ。
じゃあ、区画を作る関数と部屋を作る関数で復数の部屋だけ配置してみましょか。
やり方は分かりますか?

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 04:00
by バズー
何度も確認ありがとうございます。

ごめんなさい・・・。
そこがよくわかりません。
ランダムで部屋を置くにしても重なってはいけませんよね?
区画を作ってもどうやってその中に部屋を納めるのか。

これだけは想像がつかないのですが私が作った関数だけで対応できるのでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 05:13
by バズー
連レスまたまた失礼いたします。
現在のソースです。
重ならないようには配置出来たのですが
部屋の情報をセットする関数を呼ぶと何回に1回か強制的にプログラムが落ちてしまいます。
//RoomSet( &Room[1], Rect[1] ); ここが原因のようなのですが・・・
ポインタがおかしいのでしょうか?

またこのようなソースであっているのでしょうか・・・?
typedef struct{
    int sx,sy,ex,ey;
    int rx,ry;
}ROOM;

ROOM Room[ROOM_MAX];
ROOM Rect[ROOM_MAX];

// 分割する
void RectSplit( int lx, int ly, int hx, int hy ){
    for (int y=ly; y<=hy; y++) {
         for (int x=lx; x<=hx; x++) {
             if( x==lx || x==hx ) Map[y][x] = 1;
             if( y==ly || y==hy ) Map[y][x] = 1;
         };
     };
}

// 部屋を作る
void MakeRoom( int lx, int ly, int hx, int hy ){
    for (int y=ly; y<=hy; y++) {
        for (int x=lx; x<=hx; x++) {
            Map[y][x] = 2;
            Map[y][x] = 2;
        };
    };
}

// 部屋と部屋を結ぶ
void RoomLine( int rx1, int ry1, int rx2, int ry2, int mode ){

    // 差分を取る
    int dx = abs( rx1 - rx2 );
    int dy = abs( ry1 - ry2 );
    int sx = 0;
    int sy = 0;
    
    if(rx1 < rx2) sx = rx1 + (dx/2);
    else sx = rx2 + (dx/2);
    if(ry1 < ry2) sy = ry1 + (dy/2);
    else sy = ry2 + (dy/2);
    
    switch(mode){
        case 0:    
            // 縦方向の線を引く
            if(ry1 < ry2){
                for (int y=ry1; y<ry1+dy+1; y++) {
                    if( Map[y][sx] == 0 ) Map[y][sx] = 1;
                }
            }else{
                for (int y=ry2; y<ry2+dy+1; y++) {
                    if( Map[y][sx] == 0 ) Map[y][sx] = 1;
                }
            }
            
            // 横方向の線を引く
            if(rx1 < rx2){
                // Y軸
                for (int x=rx1; x<rx1+int(dx/2); x++) {
                    if( Map[ry1][x] == 0 ) Map[ry1][x] = 1;
                }
                for (int x=rx2-int(dx/2); x<rx2; x++) {
                    if( Map[ry2][x] == 0 ) Map[ry2][x] = 1;
                }
            }else{
                // Y軸
                for (int x=rx2; x<rx2+int(dx/2); x++) {
                    if( Map[ry2][x] == 0 ) Map[ry2][x] = 1;
                }
                for (int x=rx1-int(dx/2); x<rx1; x++) {
                    if( Map[ry1][x] == 0 ) Map[ry1][x] = 1;
                }
            }
            break;
        case 1:
            // 横方向の線を引く
            if(rx1 < rx2){

                // X軸
                for (int x=rx1; x<rx1+dx+1; x++) {
                    if( Map[sy][x] == 0 ) Map[sy][x] = 1;
                }
            }else{

                // X軸
                for (int x=rx2; x<rx2+dx+1; x++) {
                    if( Map[sy][x] == 0 ) Map[sy][x] = 1;
                }
            } 

            // 縦方向の線を引く
            if(ry1 < ry2){  
                // Y軸
                for (int y=ry1; y<ry1+int(dy/2); y++) {
                    if( Map[y][rx1] == 0 ) Map[y][rx1] = 1;
                }
                for (int y=ry2-int(dy/2); y<ry2; y++) {
                    if( Map[y][rx2] == 0 ) Map[y][rx2] = 1;
                }
            }else{  
                // Y軸
                for (int y=ry2; y<ry2+int(dy/2); y++) {
                    if( Map[y][rx2] == 0 ) Map[y][rx2] = 1;
                }
                for (int y=ry1-int(dy/2); y<ry1; y++) {
                    if( Map[y][rx1] == 0 ) Map[y][rx1] = 1;
                }
            }
            break;    
    }
}
// 平均を表示
float Avg( int num1, int num2 ){
    float avg;
    avg = float(num1 + num2)/2;
    return avg;
}

// 差分を表示
int Diff( int rx1, int ry1, int rx2, int ry2 ){
    int diff;
    if( (abs(rx1-rx2) - abs(ry1-ry2)) > 0 ) diff = 0;
    else diff = 1;
    return diff;
}

// 範囲内でランダムを返す
int RandRange( int a, int b ){
    int range;
    range = rand()%(b-a-1) + a ;
    return range;
}

// いけるマップのカウント
int MapCount( int Map[MAP_H][MAP_W] ){
    int cnt = 0;
    for (int y=0; y<MAP_H; y++) {
        for (int x=0; x<MAP_W; x++) {
            if( Map[y][x] == 1 ) cnt++;
        }
    }
    return cnt;
}

// 区画から部屋の大きさ等を自動的にセット
// 始点は左上から区画の中央までの範囲で
// 終点は区画の中央から右下まで
void RoomSet( ROOM *room, ROOM rect ){
    (*room).sx = RandRange( rect.sx+2, int((rect.ex - rect.sx)/2) );
    (*room).sy = RandRange( rect.sy+2, int((rect.ey - rect.sy)/2) );
    (*room).ex = RandRange( int((rect.ex - rect.sx)/2) + 2, rect.ex-2 );
    (*room).ey = RandRange( int((rect.ey - rect.sy)/2) + 2, rect.ey-2 );
    (*room).rx = RandRange((*room).sx,(*room).ex);
    (*room).ry = RandRange((*room).sy,(*room).ey);
}

void Init(){

    for (int y=0; y<MAP_H; y++) {
        for (int x=0; x<MAP_W; x++) {
            Map[y][x] = 0;
        };
    };
    
    for( int i=0; i<4; i++ ){
        ZeroMemory( &Room, sizeof(ROOM) );
        ZeroMemory( &Rect, sizeof(RECT) );
    }

    int rectX = 0;
    int rectY = 0;
    RectSplit( 0, 0, MAP_W-1, MAP_H-1 );
    
    rectX = rand()%10 + MIN_RECT;
    RectSplit( rectX, rectY, MAP_W-1, MAP_H-1 );
    
    Rect[0].sx = 0;
    Rect[0].sy = 0;
    Rect[0].ex = rectX;
    Rect[0].ey = MAP_H-1;

    rectY = rand()%10 + MIN_RECT;
    RectSplit( rectX, rectY, MAP_W-1, MAP_H-1 );

    Rect[1].sx = Rect[0].ex;
    Rect[1].sy = 0;
    Rect[1].ex = MAP_W-1;
    Rect[1].ey = rectY;

    rectX = rectX + rand()%10 + MIN_RECT;
    RectSplit( rectX, rectY, MAP_W-1, MAP_H-1 );

    Rect[2].sx = Rect[1].sx;
    Rect[2].sy = rectY;
    Rect[2].ex = rectX;
    Rect[2].ey = MAP_H-1;
    
    Rect[3].sx = rectX;
    Rect[3].sy = rectY;
    Rect[3].ex = MAP_W-1;
    Rect[3].ey = MAP_H-1;

    RoomSet( &Room[0], Rect[0] );
    MakeRoom( Room[0].sx, Room[0].sy, Room[0].ex, Room[0].ey );

//    RoomSet( &Room[1], Rect[1] ); // これを呼ぶと何回に一回か強制終了してしまう
//    MakeRoom( Room[1].sx, Room[1].sy, Room[1].ex, Room[1].ey );

//    MakeRoom( Rect[1].sx, Rect[1].sy, Rect[1].ex, Rect[1].ey ); //deb
    
}

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 07:14
by へろりくしょん
ソースはまったく見てませんが。

とりあえず、そのRoomSet()関数に与えられている引数は正しい物ですか?
RoomSet() 関数内でassertをかけて見ればいいと思いますよ。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 13:02
by softya
すいません完璧なソースを添付してください。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 15:29
by バズー
回答ありがとうございます。
貼らせて頂きます。
ご迷惑をお掛け致します。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 15:53
by softya
実行してみましたRandRange内でaとbが同じ値の時に0割り算して異常終了しますので、同じ値にならないように工夫してみてください。
それと、VisualStudioなら実行時にエラー出ている行とエラー内容が標示されるはずです。
>DXtest.exe の 0x005726c9 で初回の例外が発生しました: 0xC0000094: Integer division by zero
必ず確認してエラー内容を正確に報告してください。慣れてくれば自分でも分かるはずです。

それとまず部屋を作るのは後回しにして、区画数をランダムになるようにしてみては?今のままだとかなり画一的なので、区画数が2~7個で可変とかどうでしょう。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 16:16
by バズー
回答ありがとうございます。
ポインタのミスではなかったのですね・・・。
助かりました。

実は区画をどの様に分ければいいのかがわかりません。
何とか無理やり分けてみたのが添付したソースです。
可変にするとしたらどのように分ければいいのでしょうか?

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 16:37
by softya
参考元のサイトは再帰呼び出しを使って復数の区画に分割している様ですね。
再帰呼び出しが分からないと辛いと思うので別の方法でやってみますか?

1)まず分割数を乱数で決める。2~7
2)分割数カウンタを1にする。
3)Rect[0]をマップ全体のサイズに設定する。
ここからはループ処理になります。
4)区画をRect[0]~Rect[分割数カウンタ-1]からランダムに選ぶ。
つまり最初はRect[0]しか選ばれない。
5)横分割か縦分割かランダムに決める。
6)Rect[4)で決めた添字]の区画が分割するほど十分な大きさがあるかチェックしてダメなら4)に戻る。
基準は自分で決めてください。
7)区画を決めた方向に分割する。分割位置はランダムに決定。
ただし最低限のサイズは残るように計算のこと。
8)分割数カウンタに1を加算。
9)分割された区画の情報をそれぞれRect[4)で決めた添字]とRect[分割数カウンタ-1]に記録。
10)決めた分割数に達していなければ、4)に戻る。

こんな感じでどうでしょう。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 17:35
by バズー
なるほど・・・そのようにして分割する場所を選べるのですね。
考え方が硬すぎました。
ご丁寧に順序までありがとうございます。
アドバイスを元に作成してみたのですが
9)Rect[分割数カウンタ-1]に記録。 が上手くいっていないようです。
分割される部屋も狭すぎるのですがこれは調整が必要なのですよね?

また6の戻るの処理はコンテニューでいいのでしょうか?
何度も質問してしまい申し訳御座いません。
void Init(){

    for (int y=0; y<MAP_H; y++) {
        for (int x=0; x<MAP_W; x++) {
            Map[y][x] = 0;
        };
    };
    
    int RoomSplit = rand()%7 + 1;
    int SplitCnt = 1;

    Rect[0].sx = 0;
    Rect[0].sy = 0;
    Rect[0].ex = MAP_W-1;
    Rect[0].ey = MAP_H-1;

    int rs,allow;

    for(int i=0; i<RoomSplit; i++){

        rs = rand()%SplitCnt; //randSplit
        allow = rand()%2; //分割の向き

        if((Rect[rs].ex-Rect[rs].sx) > 10 && (Rect[rs].ey-Rect[rs].sy) > 10){
            Rect[SplitCnt].sx = Rect[rs].sx;
            Rect[SplitCnt].sy = Rect[rs].sy;
            Rect[SplitCnt].ex = Rect[rs].ex;
            Rect[SplitCnt].ey = Rect[rs].ey;

            if( allow == 0){
                Rect[SplitCnt].sx = RandRange( Rect[rs].sx+5, (Rect[rs].ex-Rect[rs].sx)/2 );
    //            Rect[rs].ex = Rect[SplitCnt].sx;
            }else{
                Rect[SplitCnt].sy = RandRange( Rect[rs].sy+5, (Rect[rs].ey-Rect[rs].sy)/2 );
    //            Rect[rs].ey = Rect[SplitCnt].sy;
            }            

            SplitCnt++;
        }else{
            continue;
        }

        RectSplit( Rect.sx, Rect.sy, Rect.ex, Rect.ey );
    }
}

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 18:00
by バズー
連レス失礼します。

ごめんなさい・・・。
9の分割が上手くいかない原因は初期化でした。
for( int i=0; i<ROOM_MAX; i++ ){
ZeroMemory( &Room, sizeof(ROOM) );
ZeroMemory( &Rect, sizeof(RECT) );
}
と書いた所上手く動きました。
ただ条件に当てはまらなかった時(if((Rect[rs].ex-Rect[rs].sx) > 10 && (Rect[rs].ey-Rect[rs].sy) > 10)
)のfor文のやり直しの書き方がわかりません。
continueでは上手く動いていないみたいです。

環境ごとに構造体のメモリ管理が違うらしいので
ZeroMemoryはあまり使わない方がいいみたいなのですが今回のみ使用しました。

Re:チョコボの自動生成マップ

Posted: 2010年8月27日(金) 18:11
by softya
continueだとカウンタが進んでしまいますので、4)~6)をdo~while(1);で囲んで6)でOKなときbreak;で抜けてはどうでしょうか。

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 00:25
by バズー
何度も質問してしまい申し訳御座いません。
分割がどうしても上手くいきません。
下のラインが消えてしまったり極端に区画が狭かったりしてしまいます。
サイズはかなり広げたのですが上手く動きません・・・。

何度も他人のソースを見直すのは面倒かと思いますが
今一度アドバイスを頂けないでしょうか?
SRand((unsigned) time(NULL));

    for( int i=0; i<ROOM_MAX; i++ ){
        ZeroMemory( &Room, sizeof(ROOM) );
        ZeroMemory( &Rect, sizeof(RECT) );
    }

    for (int y=0; y<MAP_H; y++) {
        for (int x=0; x<MAP_W; x++) {
            Map[y][x] = 0;
        };
    };
    
    int RoomSplit = rand()%4 + 2;
    int SplitCnt = 1;

    Rect[0].sx = 0;
    Rect[0].sy = 0;
    Rect[0].ex = MAP_W-1;
    Rect[0].ey = MAP_H-1;

    int rs,allow;

    for(int i=0; i<RoomSplit; i++){
        do{
            rs = rand()%SplitCnt; //randSplit
            allow = rand()%2; //分割の向き
            Rect[SplitCnt].sx = Rect[rs].sx;
            Rect[SplitCnt].sy = Rect[rs].sy;
            Rect[SplitCnt].ex = Rect[rs].ex;
            Rect[SplitCnt].ey = Rect[rs].ey;

            if( allow == 0){
                Rect[SplitCnt].sx = RandRange( Rect[rs].sx+7, Rect[rs].ex-7 );
                Rect[rs].ex = Rect[SplitCnt].sx;
            }else{
                Rect[SplitCnt].sy = RandRange( Rect[rs].sy+7, Rect[rs].ey-7 );
                Rect[rs].ey = Rect[SplitCnt].sy;
            }
            SplitCnt++;
            break;            
        }while((Rect[rs].ex-Rect[rs].sx) > 14 && (Rect[rs].ey-Rect[rs].sy) > 14); 

        RectSplit( Rect.sx, Rect.sy, Rect.ex, Rect.ey );
    }

    for(int i=0; i<SplitCnt+1; i++){
        RectSplit( Rect.sx, Rect.sy, Rect.ex, Rect.ey );
//        RoomSet( &Room[i], Rect[i] );
//        MakeRoom( Room[i].sx, Room[i].sy, Room[i].ex, Room[i].ey );
    }

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 09:27
by バズー
連レス失礼します。
ランダムな部屋の配置が出来ました!!
こんな感じでいいのでしょうか?
softyaさんありがとうございます!!

部屋の線を結ぶのが上手くいきません。
0から1へとやってみたのですが他の部屋をまたいでしまいます。
X座標をソートをするような関数を作ってみたのですがこれも上手く動きませんでした。
どの様に改善すればよいのでしょうか・・・。

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 11:57
by softya
考え方としては、今までの道のやり方だとマズイので変更してみましょう。
これがベストか試していないので分かりませんが。

1)それぞれの部屋の接続ポイントをランダムに決める
2)縦と横で近い方を知らべて方向を決める。
ここからループ
3)目的ポイントに達したら終了。
4)スタート地点の部屋が属する区画の端に達していたら、目的とする部屋の方向に角度を90度曲げる。
5)移動方向に合わせて軸が一致したら、目的とする部屋の方向に角度を90度曲げる。
(縦の移動ならy座標が一致、横の移動ならx座標が一致)
6) 3)にもどる。

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 20:10
by バズー
色々試してみたのですがもうわかりません・・・。
23についても実装してみたいのですが上手く動きません。
サンプルソースでは比較して線を引いてるのですが変な方向に延びてしまいます。
質問しておいて何なのですが正直プログラムを書く気力がなくなってきてしまいました・・・。
なら辞めろと言われそうなのですがどうしても作りたいんです。

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 21:14
by dic
わずわらしかったらスルーしてください
チョコボが何を指しているかわかりませんが

1.全体図を作成(2次元配列)
2.配列の中身を決定(道、壁
3.配列[0,0]から目的地までたどれるか調べる
 3-1.右の配列は道か調べる
 3-2.下の配列は道か調べる
 3-3.左の配列は道か調べる
 3-4.上の配列は道か調べる
4.道だったらそこを拠点に3を繰り返す(壁だったら何もしないで戻る
5.目的地についたら今までたどってきた道を画面に表示

考え方としては 再帰というものを使っています
(目的地に必ずつくというのが前提です)

この再帰を習得すると結構考え方に幅がでてきます

Re:チョコボの自動生成マップ

Posted: 2010年8月28日(土) 22:15
by softya
休憩したり、違うことをやると良い案が浮かぶこともありますよ。
とdicさんの書き込みが・・・。
そうですね。再帰を使ったほうが楽だったかも(汗)

とりあえず、私は再帰なしで説明しますね。
ソース直しましたので、これ使ってください。
細かいところを整理して、情報を表示させています。
ただ、道の問題は直していません。

問題の1つは道の曲がり方が良くないので部屋と結合する可能性があること。
問題の2つめは隣り合う部屋が分からないのに闇雲に接続しようとしていることです。これは、ソートでは解決しないのでソートを外しました。

この問題を解決するために、まず問題2から片付けましょう。
私の方針は、部屋から一つだけ道を別の部屋に道を伸ばして更にその別の部屋から道を繋いでいくという方針で行きます。練れていないのでシンプルさが足らないかも知れません。
1)区画ごとに4方向の接続済みのフラグを持ちます。これを0にしておきます。
2)区画は0からスタート。
ここからループ
3)全部の方向が埋まっていたら終了。
4)4方向からランダムに方向を選ぶ。ただし未接続なこと。
5)隣り合う区画を調べる ⇒ これは矩形同士の当たり判定を使う。
6)隣り合う区画があったら、8)へ
7)隣り合う区画がなかったら、その方向は接続済みにして3)に戻る。
8)その方向は接続済みにして、隣り合う区画の部屋まで道を接続。
9)接続され側の区画もその方向(元の区画逆方向)は接続済みにする。
10)接続された区画を新たな起点として、3)へ戻る。

検証してないのでバグがあったらごめんなさい。

Re:チョコボの自動生成マップ

Posted: 2010年8月29日(日) 06:52
by バズー
dic様
全然わずらわしくなんてありません!!
アドバイスありがとうございます。
今回のプログラムが完成したら再帰について勉強してみます。

softya様
私にもわかるように説明して頂いているのでやりにくくて申し訳御座いません。
お忙しいのにサンプルソースをありがとうございます。
これ以上softya様の貴重な時間を頂くのが本当に申し訳ないので質問を締めさせて頂きます。
まだ全然できておりませんが新しく頂いたアドバイスを元に修正してみます。
長い間、誠にありがとうございました。

Re:チョコボの自動生成マップ

Posted: 2010年8月29日(日) 11:39
by softya
気分転換にやっておりますので、時間のことでしたら私は別にかまいませんよ。まぁ、焦らないのは良いことなのでじっくり考えてみてくださいね。
中々進まずもどかしいかと思いますが、1つ1つ解決していけばやがて全て解決します。

Re:チョコボの自動生成マップ

Posted: 2010年8月29日(日) 22:32
by Tatu
ダンジョンのマップ作成プログラムを作ってみました。

ESCキーで終了。Rキーで再作成。

区画は3×3に分け、それぞれの区画に部屋が一つずつ。
上下左右で隣り合う区画の部屋同士を通路で結ぶようにしています。