スライドパズルの最短手数を求めるプログラムについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
根岸

スライドパズルの最短手数を求めるプログラムについて

#1

投稿記事 by 根岸 » 9年前

先日、こちらのスレッドで質問させて頂きました根岸です。
その説はお世話になりました!

現在、スライドパズルゲームの問題作成と難易度についてのアルゴリズムを組んでおります。
難しい問題を作ろうと悩んでいた所、最短手数の数で並び替えるのはどうかとアドバイスを頂きました。

制作しているものは下の横版
[youtube]QHg8CG8uVzg[/youtube]

前回、投稿スレッド
http://dixq.net/forum/viewtopic.php?f=3&t=16432

------------------------------------

>難しさの判定
単純にクリアするために必要な手数が多い ≒ 難しい という話なのかわかりませんけど…

(1)適当に(クリア可能な)ステージを生成する手段
(2)ステージを与えられたらそれを解く(最短の手順を求める)手段

の2つを用意し,

(1)の作ったステージを (2)に食わせて出力される手数が多い奴を収集

( (1)ではクリア可能かどうかすら考えず,(2)が「これは解けないし」と判定するという形でもよい )

------------------------------------

このようなアドバイスを頂き、
適当にクリアが可能かわからないステージの生成はできたのですが、
コレに対して、最短手数を求めるアルゴリズムの書き方がわかりません。

マップテーブルは以下の仕様です。

■番号
1・・・自機
2・・・動かせないオブジェクト
100〜199・・・横に動くオブジェクト
200〜299・・・縦に動くオブジェクト

2マスあったりする同じオブジェクトに関しては同じ番号を割り振っております。
たとえば、下部の201が↓に動く場合、隣り合っている201も下に動きます。

1番の自機の右側が全て0になるか、一番右端に到達していたらクリア出来るかと思うのですが、
ココらへんの最短手数を求める方法がわからなくて・・・。

コード:

{ 
    { 0,201,0,0,0,0,0 },
    { 0,201,0,0,202,0,200 },
    { 0,0,0,0,202,0,200 },
    { 1,1,0,2,0,101,101 },
    { 0,0,0,0,0,0,0 },
    { 0,0,0,0,0,0,0 },
    { 0,0,0,0,100,100,0 }
},
調べてみると、
下限値(LowerBound)枝刈り法
などがあるようですが、自機以外は目標の場所がわからないので、どのようにすればよいか悩んでおります。

↓マップ生成プログラム

コード:

#define TBL_WIDTH 7
#define TBL_HEIGHT 7
#define OBJECT_SIZE_MIN 2
#define OBJECT_SIZE_MAX 3

// 変数の宣言
int tblMaster[TBL_HEIGHT][TBL_WIDTH] = {
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    {1,1,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0},
    {0,0,0,0,0,0,0}
};

/*

 InitObject関数を呼んでOBJECT用の配列をセットしてから、InitMap関数を呼ぶこと。

*/

class MAP_EDIT{
public:

    enum MOVE{ MOVE_NOT, MOVE_V, MOVE_H, MOVE_MAX };
    enum{ OBJECT_PLAYER = 1, OBJECT_NOT_MOVE = 2, OBJECT_MOVE_V = 100, OBJECT_MOVE_H = 200 };

    typedef struct{
        int Size;
        MOVE Move;
    }OBJECT;
    
    int tblMap[TBL_HEIGHT][TBL_WIDTH];
    int ObjectMax;
    int CountV, CountH;
    std::vector<OBJECT> Obj;
    
    void Init();		// 各種変数の初期化
    void InitObject( int max );	// ランダムなオブジェクトをmax分生成
    void InitObject( int move_not, int move_v, int move_h );	// オブジェクトの個数を指定して生成
    void InitMap();	// Mapを生成
    void SetMap( OBJECT obj );
    void SetMap( int x, int y, MOVE move, int size );		
    bool IsSetCheck( int x, int y, MOVE move, int size );	// 置けるか
    void Log();
	
};

void MAP_EDIT::Init(){
    
    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            this->tblMap[y][x] = tblMaster[y][x];
        }
    }

    this->ObjectMax = 0;
    this->CountV = 0;
    this->CountH = 0;
    this->Obj.resize( 0 );
    
}

void MAP_EDIT::InitObject( int max ){
    
    this->ObjectMax = max;
    this->Obj.resize( this->ObjectMax );
    
    // 何のオブジェクトを置くか
    for(int i=0; i<this->Obj.size(); i++){
        
        int r = rand() % MOVE_MAX;
        OBJECT obj;
        
        // MOVE_NOT, MOVE_V, MOVE_H,
        
        if( r==0 ){
            obj.Size = 1;
            obj.Move = MOVE_NOT;
        }
        
        if( r==1 ){
            obj.Size = OBJECT_SIZE_MIN + ( rand()%( OBJECT_SIZE_MAX-OBJECT_SIZE_MIN ) );
            obj.Move = MOVE_V;
        }
        
        if( r==2 ){
            obj.Size = OBJECT_SIZE_MIN + ( rand()%( OBJECT_SIZE_MAX-OBJECT_SIZE_MIN ) );
            obj.Move = MOVE_H;
        }
        
        this->Obj[i] = obj;
        
    }

}

void MAP_EDIT::InitObject( int move_not, int move_v, int move_h ){
    
    this->ObjectMax = move_not + move_v + move_h;
    this->Obj.resize( this->ObjectMax );
    
    // 何のオブジェクトを置くか
    for(int i=0; i<this->Obj.size(); i++){
        
        int r = 0;
        if( i < move_not ) r = 0;
        else if( i < move_not + move_v ) r = 1;
        else if( i < move_not + move_v + move_h ) r = 2;
        
        OBJECT obj;
        
        // MOVE_NOT, MOVE_V, MOVE_H,
        
        if( r==0 ){
            obj.Size = 1;
            obj.Move = MOVE_NOT;
        }
        
        if( r==1 ){
            obj.Size = OBJECT_SIZE_MIN + ( rand()%( OBJECT_SIZE_MAX-OBJECT_SIZE_MIN ) );
            obj.Move = MOVE_V;
        }
        
        if( r==2 ){
            obj.Size = OBJECT_SIZE_MIN + ( rand()%( OBJECT_SIZE_MAX-OBJECT_SIZE_MIN ) );
            obj.Move = MOVE_H;
        }
        
        this->Obj[i] = obj;
        
    }

}

void MAP_EDIT::InitMap(){
    
    
    // 探索開始
    for(int i=0; i<this->Obj.size(); i++){
        this->SetMap( this->Obj[i] );
    }

}

void MAP_EDIT::SetMap( OBJECT obj ){
    
    // 置ける場所を探索
    bool tblIndex[TBL_HEIGHT][TBL_WIDTH];
    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            tblIndex[y][x] = false;
        }
    }

    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            tblIndex[y][x] = this->IsSetCheck( x, y, obj.Move, obj.Size );
        }
    }
    
    // ログ
    /*
     for(int y=0; y<TBL_HEIGHT; y++){
     for(int x=0; x<TBL_WIDTH; x++){
     printf( "%d", tblIndex[y][x] );
     }
     printf( " \r\n" );
     }
     */
    
    int cnt = 0;
    int cntMax = 0;
    int r = 0;
    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            if( tblIndex[y][x] ) cntMax++;
        }
    }
    
    //NSLog( @"max %d", cntMax );
    
    // 置ける場所があったら
    if( cntMax > 0 ){
        
        r = rand() % cntMax;
        
        NSLog( @"r %d", r );
        
        for(int y=0; y<TBL_HEIGHT; y++){
            for(int x=0; x<TBL_WIDTH; x++){
                
                if( tblIndex[y][x] ){
                    if( r == cnt ){
                        this->SetMap( x, y, obj.Move, obj.Size );
                        cnt++;
                        
                        break;
                        
                    }else{
                        cnt++;
                    }
                }
                
            }
        }
    }
}

void MAP_EDIT::SetMap( int x, int y, MOVE move, int size ){
    
    //if( this->IsSetCheck( x, y, move, size ) ){

        // マップを置いていく処理
        for(int i=0; i<size; i++){
            
            if( move == MOVE_NOT ){
                this->tblMap[y][x] = OBJECT_NOT_MOVE;
            }
            
            if( move == MOVE_V ){
                this->tblMap[y][x+i] = OBJECT_MOVE_V + this->CountV;
            }
            
            if( move == MOVE_H ){
                this->tblMap[y+i][x] = OBJECT_MOVE_H + this->CountH;
            }
        }
        
        //
        if( move == MOVE_V ){
            this->CountV++;
        }
        
        if( move == MOVE_H ){
            this->CountH++;
        }
        
    //}

}

bool MAP_EDIT::IsSetCheck( int x, int y, MOVE move, int size ){
    
    // エラー処理
    if( x < 0 ) return false;
    if( x >= TBL_WIDTH ) return false;
    if( y < 0 ) return false;
    if( y >= TBL_HEIGHT ) return false;
    
    if( move == MOVE_NOT ){
        if( size != 1 ) return false;
    }
    
    if( move == MOVE_V ){
        if( x > TBL_WIDTH-size ) return false;
    }
    
    if( move == MOVE_H ){
        if( y > TBL_HEIGHT-size ) return false;
    }

    // 連続したマスが空欄なら
    bool flg = true;
    
    for(int i=0; i<size; i++){
        
        if( move == MOVE_NOT ){
            if( tblMap[y][x] != 0 ) flg = false;
        }
        
        if( move == MOVE_V ){
            if( tblMap[y][x+i] != 0 ) flg = false;
        }
        
        if( move == MOVE_H ){
            if( tblMap[y+i][x] != 0 ) flg = false;
        }
        
    }
    
    // どれにも引っかからなければ
    return flg;

}

void MAP_EDIT::Log(){
    
    printf( "{ " );
    printf( "\r\n" );

    for(int y=0; y<TBL_HEIGHT; y++){

        printf( "{ " );

        for(int x=0; x<TBL_WIDTH; x++){
            printf( "%d", this->tblMap[y][x] );
            
            if( x!=TBL_WIDTH-1 ){
                printf( "," );
            }
        
        }
        
        printf( " }" );

        if( y!=TBL_HEIGHT-1 ){
            printf( "," );
        }
        
        printf( "\r\n" );
        
    }
    
    printf( "}," );
    printf( "\r\n" );

}

static MAP_EDIT Map;

◆main系

        Map.Init();
        // Map.InitObject( 5 );
        Map.InitObject( 1, 2, 3 );
        Map.InitMap();
        Map.Log();

アバター
usao
記事: 1887
登録日時: 11年前

Re: スライドパズルの最短手数を求めるプログラムについて

#2

投稿記事 by usao » 9年前

・同じものを2手連続で動かす意味は無い
・ある物を動かしたことで,別の物の動かせる箇所が増えないならばその手は意味が無い
・以前の盤面と同じになる手は意味が無い

とか,思いつく限りの明白なルールだけを実装して
とりあえず効率は度外視で,全パターンの探索をやらせるのではダメでしょうか.

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#3

投稿記事 by 根岸 » 9年前

usao様

再度、ご回答ありがとうございます!
実はこういった最短手数を求める問題(クリアできる問題なのか)というプログラムを書いたことがありません。

その為、どういったように書けばいいのか、
どのようにデータを持つべきなのかが全く検討がついておりません。

保存するテーブルデータはおそらく1手前のものだけではダメそうですし、どうすればよいのかなーと。
ここがかなり重要なロジックそうなので、何とか解いてみたいんですよね・・・。

たいちう
記事: 418
登録日時: 13年前

Re: スライドパズルの最短手数を求めるプログラムについて

#4

投稿記事 by たいちう » 9年前

> 保存するテーブルデータはおそらく1手前のものだけではダメそうですし、どうすればよいのかなーと。
> ここがかなり重要なロジックそうなので、何とか解いてみたいんですよね・・・。

プログラムは一通り完成していて、完璧を期すために問題を探していたのだと思っていました。
すぐにプログラムを作りたいなら、「箱入り娘」というパズルを調べてください。
すぐに解説付きのプログラムが見つかるはずです。
根岸さんの作りたいプログラムとはルールが若干違いますが、
「箱入り娘」のプログラムをしっかり理解できたら、すぐに応用できると思います。

自力で解決したいならば、あえて「箱入り娘」は調べずに挑戦してください。
きっとその方が楽しいです。usaoさんの書いているアプローチがベストでしょう。
最初の問題はできるだけ簡単に。
自分の車の他に1台だけ車があり、その車を1マス動かせば自分の車がゴールできるような。
自分の車だけという問題でも良いかもしれません。

アバター
lbfuvab
記事: 72
登録日時: 13年前

Re: スライドパズルの最短手数を求めるプログラムについて

#5

投稿記事 by lbfuvab » 9年前

1マスの内容が1byteで表現出来、NxNマスのフィールドで最初からM手目まで考えるとしたら

コード:

unsigned char HistryStack[N*N][M];
int NowTurn=0;

//手を打った後の盤面の登録
int RegisterHistry(const unsigned char Board[N*N]){
	if(NowTurn >= M)
		return -1;
	memcpy(HistryStack[NowTurn++],Board,N*N);
	return 0;
}

//手を戻す
void DeleteHistry(void){
	if(NowTurn >0)
		NowTurn--;
}

//以前の盤面で一致する物があるか探す
int HistryCmp(const unsigned char Board[N*N]){
	int i;
	
	for(i=0;i<NowTurn && memcmp(HistryStack[i++],Board,N*N););
	
	return i==NowTurn;
}
とかいう感じで履歴を実現できそうな気がするんですが、どうでしょう?

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: スライドパズルの最短手数を求めるプログラムについて

#6

投稿記事 by みけCAT » 9年前

lbfuvab さんが書きました:1マスの内容が1byteで表現出来、NxNマスのフィールドで最初からM手目まで考えるとしたら

コード:

unsigned char HistryStack[N*N][M];
int NowTurn=0;

//手を打った後の盤面の登録
int RegisterHistry(const unsigned char Board[N*N]){
	if(NowTurn >= M)
		return -1;
	memcpy(HistryStack[NowTurn++],Board,N*N);
	return 0;
}

//手を戻す
void DeleteHistry(void){
	if(NowTurn >0)
		NowTurn--;
}

//以前の盤面で一致する物があるか探す
int HistryCmp(const unsigned char Board[N*N]){
	int i;
	
	for(i=0;i<NowTurn && memcmp(HistryStack[i++],Board,N*N););
	
	return i==NowTurn;
}
とかいう感じで履歴を実現できそうな気がするんですが、どうでしょう?
C++ならばダメだと思います。最初の行を

コード:

unsigned char HistryStack[M][N*N];
としてください。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
lbfuvab
記事: 72
登録日時: 13年前

Re: スライドパズルの最短手数を求めるプログラムについて

#7

投稿記事 by lbfuvab » 9年前

ありゃりゃ、なんだか全体的に寝ぼけてますね。
このfor文だとiがNowTurn-1で一緒だったらバグりますね。
修正です。

コード:

unsigned char HistryStack[M][N*N];
int NowTurn=0;
 
//手を打った後の盤面の登録
int RegisterHistry(const unsigned char Board[N*N]){
    if(NowTurn >= M)
        return -1;
    memcpy(HistryStack[NowTurn++],Board,N*N);
    return 0;
}
 
//手を戻す
void DeleteHistry(void){
    if(NowTurn >0)
        NowTurn--;
}
 
//以前の盤面で一致する物があるか探す
int HistryCmp(const unsigned char Board[N*N]){
    int i;
    
    for(i=0;i<NowTurn && memcmp(HistryStack[i],Board,N*N);i++);
    
    return i==NowTurn;
}
にしないといけませんね。

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#8

投稿記事 by 根岸 » 9年前

ご連絡が遅くなってしまいすみません。
皆さま回答ありがとうございます!

まずは箱入り娘のロジック(下記)をベースに試行錯誤しております。
http://vivi.dyndns.org/tech/doc.phtml?FILE_NAME=hako-1

この土日費やしたのですが、ロジックをプログラムにするのが難しくて奮闘しております。
聞きっぱなしになってしまっているので、申し訳ないです・・・。

こういった解法プログラム難しすぎですね・・・。

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#9

投稿記事 by 根岸 » 9年前

度々すみません。
1手だけ全てのパターンを保存し、クリアしているかの判定が出来ました。
2手目以降どういう風に配列を管理すればよいかわからなくて、躓いてしまっております。

以下のような手順で、1手目のパターンを全て保存することが出来ました。

①指定したオブジェクトの、指定方向に何個連続で0があるかを探索する。
②0が見つかった数だけfor文を回し、指定方向にオブジェクトをずらす。
 ※左右に動けるオブジェクトに関しては、左方向、右方向の探索を行う
③各盤面のクリア判定

シンプルに1手で解ける問題なら、あってそうです。
しかし、2手目以降の盤面をどのように管理すればよいかわからなくて、戸惑っております。
Vectorが使える環境なのですが、こちらアドバイスを頂くことは難しいでしょうか。

処理や変数の詳細は以下の様な形です。

// 盤面用
typedef struct{
int Tbl[TBL_HEIGHT][TBL_WIDTH]; // #define定義 7,7
}TBL;

// 盤面を保存する用
std::vector<TBL> Vec;

// 何の数字が何個あるかを格納 Counter.size()でオブジェクトの種類数を取得可能
std::vector<COUNT> Counter;

// 移動する方向
enum DIR { DIR_LEFT, DIR_RIGHT, DIR_DOWN, DIR_UP };

// 盤面上のnum盤を移動できる場所に移動
void MoveObject( int num );

// 指定した方向に0が何個あるか?
int MoveCount( DIR dir, int startCx, int startCy, int endCx, int endCy );

// 指定したオブジェクト番号をlength分DIR方向に移動させる
TBL MoveTbl( int num, DIR dir, int length );

// ログ出力用
void Log( TBL tbl );

ソースを添付させて頂きます。

コード:


// 盤面用
typedef struct{
    int Tbl[TBL_HEIGHT][TBL_WIDTH];
}TBL;

std::vector<TBL> Vec;
std::vector<COUNT> Counter;

enum DIR { DIR_LEFT, DIR_RIGHT, DIR_DOWN, DIR_UP };

void MoveObject( int num );
int MoveCount( DIR dir, int startCx, int startCy, int endCx, int endCy );
TBL MoveTbl( int num, DIR dir, int length );
void Log( TBL tbl );

// クリアしているか
bool IsClearCheck( TBL tbl ){
    
    int x = TBL_WIDTH-1;
    int y = floor(TBL_HEIGHT/2);
    if( tbl.Tbl[y][x] == MAP_EDIT::OBJECT_PLAYER ) return true;
    
    return false;

}

// num番のオブジェクトを動かす
void MoveObject( int num ){
    
    int startCx = -1;
    int startCy = -1;
    int endCx = -1;
    int endCy = -1;
    
    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            if( Map.tblMap[y][x] == num ){

                // スタート地点
                if( startCx == -1 && startCy == -1 ){
                    startCx = x;
                    startCy = y;
                }
                
                // ゴール地点
                endCx = x;
                endCy = y;
            }
        }
    }
    
    // オブジェクトが存在したら
    if( startCx != -1 && startCy != -1 && endCx != -1 && endCx != -1 ){
        
        int cnt = 0;

        // 自機なら
        if( num == MAP_EDIT::OBJECT_PLAYER ){
            
            cnt = MoveCount( DIR_LEFT, startCx, startCy, endCx, endCy );
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_LEFT, cnt );
                Vec.push_back( tbl );
            }
            
            cnt = MoveCount( DIR_RIGHT, startCx, startCy, endCx, endCy );
            
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_RIGHT, i );
                Vec.push_back( tbl );
            }
        }
        
        // 横向きに移動するオブジェクト
        if( num >= MAP_EDIT::OBJECT_MOVE_V && num <= MAP_EDIT::OBJECT_MOVE_H-1 ){
            
            cnt = MoveCount( DIR_LEFT, startCx, startCy, endCx, endCy );
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_LEFT, i );
                Vec.push_back( tbl );
            }
            
            cnt = MoveCount( DIR_RIGHT, startCx, startCy, endCx, endCy );
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_RIGHT, i );
                Vec.push_back( tbl );
            }
            
        }
        
        // 縦向きに移動するオブジェクト
        if( num >= MAP_EDIT::OBJECT_MOVE_H && num <= MAP_EDIT::OBJECT_MOVE_H+99 ){
            
            cnt = MoveCount( DIR_UP, startCx, startCy, endCx, endCy );
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_UP, i );
                Vec.push_back( tbl );
            }
            //NSLog( @"d %d", cnt );
            
            cnt = MoveCount( DIR_DOWN, startCx, startCy, endCx, endCy );
            for(int i=1; i<=cnt; i++){
                TBL tbl = MoveTbl( num, DIR_DOWN, i );
                Vec.push_back( tbl );
            }
            
            //NSLog( @"d %d", cnt );

        }

    }
    
    // ログ
    for(int i=0; i<Vec.size(); i++){
        Log( Vec[i] );
    }
}

// 指定した場所からDIR方向に何マス0があるか
int MoveCount( DIR dir, int startCx, int startCy, int endCx, int endCy ){
    
    int cnt = 0;
    
    switch ( dir ) {

        case DIR_LEFT:
            
            // 左端か
            if( startCx == 0 ){
                cnt = 0;
            }else{
                for(int x=startCx-1; x>=0; x--){
                    if( Map.tblMap[startCy][x] == 0 ) cnt++;
                    else break;
                }
            }
            
            break;
            
        case DIR_RIGHT:
            
            // 左端か
            if( endCx == TBL_WIDTH-1 ){
                cnt = 0;
            }else{
                for(int x=endCx+1; x<TBL_WIDTH; x++){
                    if( Map.tblMap[startCy][x] == 0 ) cnt++;
                    else break;
                }
            }
            
            break;
        
        case DIR_UP:
            
            // 左端か
            if( startCy == 0 ){
                cnt = 0;
            }else{
                for(int y=startCy-1; y>=0; y--){
                    if( Map.tblMap[y][startCx] == 0 ) cnt++;
                    else break;
                }
            }
            
            break;
            
        case DIR_DOWN:
            
            // 左端か
            if( endCy == TBL_HEIGHT-1 ){
                cnt = 0;
            }else{
                for(int y=endCy+1; y<TBL_HEIGHT; y++){
                    if( Map.tblMap[y][startCx] == 0 ) cnt++;
                    else break;
                }
            }
            
            break;
            
        default:
            break;
    }
    
    return cnt;
}

TBL MoveTbl( int num, DIR dir, int length ){

    TBL tbl, r_tbl;

    // 初期値を保存
    for(int y=0; y<TBL_HEIGHT; y++){
        for(int x=0; x<TBL_WIDTH; x++){
            tbl.Tbl[y][x] = Map.tblMap[y][x];
            r_tbl.Tbl[y][x] = Map.tblMap[y][x];
        }
    }

    // テーブル

    // 左方向
    if( dir == DIR_LEFT ){
        for(int y=0; y<TBL_HEIGHT; y++){
            for(int x=0; x<TBL_WIDTH; x++){
                if( tbl.Tbl[y][x] == num ){
                    
                    if( x-length < 0 ){
                        NSLog( @"error" );
                        continue;
                    }
                    
                    r_tbl.Tbl[y][x] = 0;
                    r_tbl.Tbl[y][x-length] = num;
                }
            }
        }
    }
    
    // 右方向
    if( dir == DIR_RIGHT ){
        for(int y=0; y<TBL_HEIGHT; y++){
            for(int x=TBL_WIDTH-1; x>=0; x--){
                if( tbl.Tbl[y][x] == num ){

                    if( x+length >= TBL_WIDTH ){
                        NSLog( @"error" );
                        continue;
                    }

                    r_tbl.Tbl[y][x] = 0;
                    r_tbl.Tbl[y][x+length] = num;
                }
            }
        }
    }
    
    // 上方向
    if( dir == DIR_UP ){
        for(int y=0; y<TBL_HEIGHT; y++){
            for(int x=0; x<TBL_WIDTH; x++){
                if( tbl.Tbl[y][x] == num ){
                
                    if( y-length < 0 ){
                        NSLog( @"error" );
                        continue;
                    }

                    r_tbl.Tbl[y][x] = 0;
                    r_tbl.Tbl[y-length][x] = num;
                }
            }
        }
    }
    
    // 下方向
    if( dir == DIR_DOWN ){
        //NSLog( @"move s" );

        for(int y=TBL_HEIGHT-1; y>=0; y--){
            for(int x=0; x<TBL_WIDTH; x++){
                if( tbl.Tbl[y][x] == num ){

                    if( y+length >= TBL_HEIGHT ){
                        NSLog( @"error" );
                        continue;
                    }
                    
                    r_tbl.Tbl[y][x] = 0;
                    r_tbl.Tbl[y+length][x] = num;
                }
            }
        }
    }
    
    
    return r_tbl;
    
}

void Log( TBL tbl ){
    
    printf( "{ " );
    printf( "\r\n" );
    
    for(int y=0; y<TBL_HEIGHT; y++){
        
        printf( "{ " );
        
        for(int x=0; x<TBL_WIDTH; x++){
            printf( "%d", tbl.Tbl[y][x] );
            
            if( x!=TBL_WIDTH-1 ){
                printf( "," );
            }
            
        }
        
        printf( " }" );
        
        if( y!=TBL_HEIGHT-1 ){
            printf( "," );
        }
        
        printf( "\r\n" );
        
    }
    
    printf( "}," );
    printf( "\r\n" );
}

void CountSet(){
    
    // カウンター
    Counter.resize( 0 );
    
    //
    for(int y=TBL_HEIGHT-1; y>=0; y--){
        for(int x=0; x<TBL_WIDTH; x++){
            if( Map.tblMap[y][x] > 0 && Map.tblMap[y][x] != MAP_EDIT::OBJECT_NOT_MOVE ){
            
                bool f = false;
                
                // すでに配列にあるか
                for(int j=0; j<Counter.size(); j++){
                    if( Map.tblMap[y][x] == Counter[j].Ptn && !f ){
                        Counter[j].Max++;
                        f = true;
                    }
                }
                
                // 配列上になかったら新規で追加
                if( !f ){
                    COUNT Cnt;
                    Cnt.Init( Map.tblMap[y][x] );
                    Cnt.Max = 1;
                    Counter.push_back( Cnt );
                }
            }
            
        }
    }
}

static void Init(){
    
}

static void Loop(){

    if( System.touch.trgFlag ){
        
        CountSet();

        Vec.resize( 0 );

        Map.Init();
        //Map.InitObject( 5 );
        //Map.InitObject( 1, 2, 3 );
        //Map.InitMap();
        //Map.Log();
       
        for(int i=0; i<Counter.size(); i++){
            MoveObject( Counter[i].Ptn );
        }
        
        
        for(int i=0; i<Vec.size(); i++){
            bool f = IsClearCheck( Vec[i] );
            NSLog( @"clear %d", f );
        }
        
        //NSLog( @"clear %d", IsClearCheck( TBL tbl ) );
    }

}


アバター
usao
記事: 1887
登録日時: 11年前

Re: スライドパズルの最短手数を求めるプログラムについて

#10

投稿記事 by usao » 9年前

この手の探索は
vector< TBL > ではなく,スタックかキューを使うと楽なのではないかと思います.
#まぁvectorでもいいのだけど.
(以下,スタックだとして書きます)


空のスタックを用意
初期の盤面をスタックに積む
 ↓
while( スタックが空ではない )
{
 スタックから盤面を取りだす
 取り出した盤面から取り得る「1手進めた盤面」全て(※有効なものだけ)をスタックに積む
}


…的な処理で探索.
ただし,千日手的なのを防ぐために(※)の部分とかで判定が必要かと思います.

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#11

投稿記事 by 根岸 » 9年前

usao様

何度もご回答助かります。
聞ける人が周りにいないので助かります。

プログラムが独学でして、スタックとキューについて
なんとなく把握しているのですが、コード上に落としこむことが出来ません。

その為、PushやPopできるようなVector配列を使っております。
自分の力不足で非常に悔しいのですが、なんとかして解きたいです。

取りあえずは現状のVectorなら少しは
理解できるので、これはこのまま使っていきたいです。
Vectorだとやはり難しいのでしょうか。

アバター
usao
記事: 1887
登録日時: 11年前

Re: スライドパズルの最短手数を求めるプログラムについて

#12

投稿記事 by usao » 9年前

かなり書き方が悪かったですね.すみません.

スタックとかキューとかはその意味というか考え方(FIFO, FILOだとか)がわかっていれば
それを何で実現しても問題ありません.
例えば,std::vector を push_back(),pop_back() でスタック的に使うとかでOKです.


>2手目以降どういう風に配列を管理すればよいか

…に関して, スタックあるいはキュー的な【やり方】 が良いのではないか,ということですね.
>vector< TBL > ではなく,
の部分は激しく不要でした.

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: スライドパズルの最短手数を求めるプログラムについて

#13

投稿記事 by みけCAT » 9年前

根岸 さんが書きました:プログラムが独学でして、スタックとキューについて
なんとなく把握しているのですが、コード上に落としこむことが出来ません。
C++のSTLが使える環境であれば、自分で実装しなくてもそれぞれstd::stackstd::queueを用いればいいと思います。
NSLog( @"r %d", r );という表現が利用されていることからC++ではなさそうなので、STLが使えないのであればごめんなさい。現状の"Vector"を用いる方法でいいと思います。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: スライドパズルの最短手数を求めるプログラムについて

#14

投稿記事 by みけCAT » 9年前

usao さんが書きました:>2手目以降どういう風に配列を管理すればよいか

…に関して, スタックあるいはキュー的な【やり方】 が良いのではないか,ということですね.
素直に関数の再帰呼び出しで(反復深化)深さ優先探索を行うのはどうでしょうか?(関数の再帰では内部ではスタックが使われているかもしれません)

…と書きましたが、むしろキューを用いた幅優先探索の方がいいかもしれません。
ハッシュ関数を用いた連想配列、赤黒木やAVL木などの平衡二分探索木(、C++のSTLが使えるのであればstd::set)などを用いて前に調べた盤面かを管理するといいでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#15

投稿記事 by 根岸 » 9年前

>>usao様

とんでもないです!
他に効率のよいやり方があるはずなのに、私がワケのわからないコードかいているので・・・。

FIFO, FILOについても理解はしております。
コードに落とし込めるかは別として、
それらをスタック、キューという名称とるのも存じております。

無理やりソースかいてるので、自分で何書いたかわからなくなってきております(笑)

>>みけCAT様

ご回答ありがとうございます。
開発環境を書いておらず申し訳ないです。

時間があれば最近はこれらのことを考えているので、Xcodeで開発している時も御座います。
VisualStadio2005とXcodeの2種類で開発しております。

C++で書いており、std::vectorを使っておりますのでSTLも使えます。

素直に関数の再帰呼び出しで(反復深化)深さ優先探索を行うのはどうでしょうか?
(関数の再帰では内部ではスタックが使われているかもしれません)

→ここらへんがコードに落とし込めないんですよね・・・。
 再帰関数を作りたいのですが、現状の私の仕様だと、
 再帰の引数を何にするかなど頭の中で整理できておりません。

…と書きましたが、むしろキューを用いた幅優先探索の方がいいかもしれません。
ハッシュ関数を用いた連想配列、赤黒木やAVL木などの平衡二分探索木(、C++のSTLが使えるのであればstd::set)などを用いて前に調べた盤面かを管理するといいでしょう。

→ここらへんになると理解もできないです(汗)
 Googleなどでかなり文献をみてはいたのですが、
 こういった単語を理解している方は天才かと・・・。

 幅優先探索の方が良いと言える、みけCAT様半端ないです!
 周りに聞ける人がいないので本当に助かります。

何度も質問してしまい申し訳御座いません。
課題とか仕事ではないので、解けなくても良い問題なのですが、どうしても解きたいです!

アバター
usao
記事: 1887
登録日時: 11年前

Re: スライドパズルの最短手数を求めるプログラムについて

#16

投稿記事 by usao » 9年前

あくまでも個人的な意見(というか「好み」の領域か?)ですが…

「最初から効率的に動いてくれないとダメ」的な事情があるのでなければ,まずは
「効率が良いのかもしれないが何やら難しい話」よりも「理解しやすくて実装が容易なもの」な側主体で
作ってしまう方が良いのではないかと思います.
必要な要素を列挙して,個々の要素をまずは「とりあえず版」で作ってみてはどうでしょうか.
(「最低限何があればいけるのか」の「最低限」ラインを狙っていく感じで,というか.)

たいちう
記事: 418
登録日時: 13年前

Re: スライドパズルの最短手数を求めるプログラムについて

#17

投稿記事 by たいちう » 9年前

箱入り娘を紹介したのは、よく似たパズルなので、
一方をちゃんと理解したら、もう一方もできるだろうという理由です。
解説とソースコードを見つけた箱入り娘を理解するよりも、
試行錯誤してラッシュアワーを作る方が難しいですよ。
「このサイトの解説の、この部分が理解できない」とか、
「このソースコードの何行目からの処理が理解できない」とかいう質問も可能です。

まあ取り組み方はひとそれぞれなので、
箱入り娘をスルーした場合の説明を書きかけたのですが、
ロジックをプログラムにできないのでしたね。
この方針で他の方の回答に私が付け加えることはありませんでした。
やっぱり、ロジックとプログラムを見ながら理解に努めるのが一番ではないですか?

私が昔勉強させていただいたサイトが健在ですので紹介します。
反復深化や幅優先探索など判りやすく説明されていますし、
ラッシュアワーや箱入り娘よりも易しめのパズルの解法があります。
易しめですが方向性は同じですので、ちゃんと理解すれば応用できると思います。
理解するためには必ず手を動かしてくださいね。

コンピュータ&パズル
パズル問題解法のアルゴリズム講座

根岸

Re: スライドパズルの最短手数を求めるプログラムについて

#18

投稿記事 by 根岸 » 9年前

お礼が遅くなってしまいすみません。
皆様ご回答ありがとうございました!

サイトも拝見させて頂き、別プログラムも書いてみましたが、やはり難しいですね・・・。
ゲームロジックは出来ているのに、問題が作れないとなると残念ですw

全パターンを保持するのもなんとなく動いているのですが、
クリア出来ない場合ほぼ無限ループに陥ることがあります。
(※処理は無限ではないのですが、手数が増えていくにつれて膨大な計算量になるため)

この数週間、箱入り娘や15パズルなどの解法など見てきたのですが、
自動問題生成となるとレベルが桁違いに難しいのですね・・・。

発想を変えてクリアできるパターンからシャッフルしていく方法も
考えたのですが、あまり難しい問題になりませんでした。

解決していないのですが、これ以上ご迷惑をかけられないので、解決とさせて頂いた方が宜しいでしょうか?

たいちう
記事: 418
登録日時: 13年前

Re: スライドパズルの最短手数を求めるプログラムについて

#19

投稿記事 by たいちう » 9年前

質問を迷惑と感じる人は、最初から回答を書き込まなかったり、やがて回答しなくなります。
迷惑行為をしているわけではなく、質問掲示板のまっとうな使い方をしているので、
遠慮は必要ないでしょう。


それより、自分の抱えている問題をごっちゃにしていませんか?
どれも中途半端になっているような印象を受けますが。
自分のためにも回答者のためにも、何ができていて、何ができていないのか、
はっきりまとめると良いと思います。

根岸さんの作りたいもの、途中の段階について、次のようなものが書かれています。
・ゲームロジック(ロジックを日本語で説明したもの?)
・プログラム(ゲームロジックを実装したもの?)
・自動問題生成(のロジックとプログラム)
ゲームロジックはできているが、プログラムの実装は途中であり、
自動問題生成は殆ど手を付けられていない、という状態でしょうか?
時間をかけてもプログラムの実装ができないならば、
ゲームロジックはまだまだ曖昧で、
できているとは言えない状況だと思うのですが、違うのでしょうか?

また、他のパズルについては、解法を見ただけ?
それともコーディングまでしてみましたか?
どのように役に立ちましたか?

根岸さんの現状が正確につかめていないので、以下の回答は憶測を含んでしまいます。
(双方にとって無駄なので、改善する方法を考えてください。)


幅優先探索で実装を進めているのだと思います。
この方法は実装が簡単で、通常は探索も早いのですが、欠点もあります。
探索中に発生した全ての盤面をメモリ(HDDでも良いのですが)上に持つというのは、
問題が大きすぎた場合にメモリ不足により解けなくなってしまいます。
もしこれが原因で解けないのでしたら、反復深化で探索を行う必要があります。

しかし、一連の書き込みから私が憶測すると、まだこれが原因ではないと思います。
比較的小さな解なしの問題を、「解なし」と処理できるようにしてはどうでしょうか。

問題の自動生成のことは一旦忘れ、簡単な問題から着実に解ける(あるいは解なしが判る)
ように取り組んではどうでしょうか。

閉鎖

“C言語何でも質問掲示板” へ戻る