迷路ゲームできた

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

迷路ゲームできた

投稿記事 by usao » 5年前

昔,迷路歩くやつを作ってた気がするが,それのコンソール版.
入力はgetchar(),出力はprintf().

画面はこんな.
一見,何が何だかわからんけど…
Real.png
現実の表示
Real.png (2.35 KiB) 閲覧数: 703 回

心の目で見れば,こんな感じに捉えることができるハズ.
Image.png
推奨される心象風景
Image.png (3.28 KiB) 閲覧数: 1052 回

CODE:

//向きに関する定数
const int DX[] = { 0, 1, 0, -1 };
const int DY[] = {-1, 0, 1,  0 };
const char DName[] = { 'N', 'E', 'S', 'W' };

//迷路データ
const int MAZE_W = 8;
const int MAZE_H = 6;
const char Maze[MAZE_H][MAZE_W+1] =
{   //'#' is Wall
    "....#...",
    ".####.#.",
    "........",
    ".#....##",
    ".##.#.#G",
    "....#..."
};

//迷路データの位置(x,y)の内容を取得する.
//(x,y)が領域外の場合は,壁を表す'#'が返される.
char Attr( int x, int y )
{   return ( ( x<0 || x>=MAZE_W || y<0 || y>=MAZE_H )   ?   '#'   :   Maze[y][x] ); }

//Show()で使う作業用構造体.矩形範囲を表す.
struct Rect
{
    int Left, Top, Right, Bottom;

    Rect( int Left=0, int Top=0, int Right=-1, int Bottom=-1 )
        : Left(Left), Top(Top), Right(Right), Bottom(Bottom) {}

    bool IsValid() const {  return (Left<=Right && Top<=Bottom );   }
};

//Show()から使う作業関数.Buff[][]の指定範囲をCで埋める
void DrawRect( char Buff[12][12+1], const Rect &R, char C )
{
    for( int y=R.Top; y<=R.Bottom; ++y )
    {
        for( int x=R.Left; x<=R.Right; ++x )
        {   Buff[y][x] = C; }
    }
}

//現在の視界の絵を表示
void Show( int PX,int PY, int iDir )
{
    const int kr[8] = { -1, 0, 1,   -1, 0, 1,   -1,1 };
    const int kf[8] = {  2, 2, 2,    1, 1, 1,    0,0 };
    const Rect FillRect[8][2] = {
        { Rect(0,4,3,7), Rect() }, { Rect(4,4,7,7), Rect() }, { Rect(8,4,11,7), Rect() },
        { Rect(0,2,2,9), Rect(3,3,3,8) }, { Rect(2,2,9,9), Rect() }, { Rect(9,2,11,9), Rect(8,3,8,8) },
        { Rect(0,0,0,11), Rect(1,1,1,10) }, { Rect(11,0,11,11), Rect(10,1,10,10) }
    };

    //Buff[][]に表示内容を作る
    char Buff[12][12+1] = { 0 };
    {
        DrawRect( Buff, Rect(0,0, 11,11), ' ' );
        //壁の描画
        const int fx = DX[ iDir ];
        const int fy = DY[ iDir ];
        const int rx = -fy;
        const int ry = fx;
        for( int i=0; i<8; ++i )
        {
            int x = PX + fx*kf[i] + rx*kr[i];
            int y = PY + fy*kf[i] + ry*kr[i];
            if( Attr(x,y) == '#' )
            {
                for( int iRect=0; iRect<2; ++iRect )
                {
                    if( FillRect[i][iRect].IsValid() )
                    {   DrawRect( Buff, FillRect[i][iRect], '#' );  }
                }
            }
        }
        //ゴールの描画
        if( Attr( PX,PY )=='G' ){   Buff[11][5] = 'G';  }
        else if( Attr( PX+fx, PY+fy )=='G' ){   Buff[9][5] = 'G';   }
    }

    //Buff[][]の内容を表示
    printf("+------------+\n" );
    for( int y=0; y<12; ++y ){  printf( "|%s|\n", Buff[y] );    }
    printf("+------------+\n" );
}

//操作方法表示
void ShowHelp()
{
    printf( "===Cmd List===============\n" );
    printf( "{f,F,8} : Walk Forward\n" );
    printf( "{l,L,4} : Turn Left\n" );
    printf( "{r,R,6} : Turn Right\n" );
    printf( "{q,Q}   : Quit This Game\n" );
    printf( "{h,H,?} : Show This Help\n" );
    printf( "==========================\n" );
}

//入力処理
char InputCmd()
{
    printf( "Cmd? : " );
    char Input;
    while( true )
    {
        Input = getchar();
        if( Input!='\r' && Input!='\n' )break;  //※前回のEnterの影響をどうにかする用
    }
    return Input;
}

//main
int main()
{
    printf( "== The Maze Game ==\n" );
    printf( "(input '?' Cmd to show help)\n" );

    //データ初期化
    //  (PX,PY):現在位置, iDir:現在の向き(0~3)
    int PX = 3;
    int PY = 0;
    int iDir = 3;

    //Game Loop
    bool bLoop = true;
    while( bLoop )
    {
        //現在の視界を表示
        printf( "\n" );
        Show( PX,PY, iDir );

        //ゴール判定
        if( Attr( PX,PY )=='G' )
        {
            printf( "\n *** GOAL! *** \n" );
            printf( "(Input Any Cmd to Quit)\n" );
            InputCmd();
            break;
        }

        //入力受付とゲーム進行
        printf( "Pos(%d,%d) Dir(%c)\n", PX,PY,DName[iDir] );
        switch( InputCmd() )
        {
        case 'f':   //Forward
        case 'F':
        case '8':
            {
                int FX = PX + DX[iDir];
                int FY = PY + DY[iDir];
                if( Attr(FX,FY) != '#' ){   PX = FX;    PY = FY;    }
            }
            break;

        case 'l':   //Turn Left
        case 'L':
        case '4':
            if( --iDir < 0 ){   iDir = 3;   }
            break;

        case 'r':   //Turn Right
        case 'R':
        case '6':
            if( ++iDir > 3 ){   iDir = 0;   }
            break;

        case 'q':   //Quit
        case 'Q':
            bLoop = false;
            break;

        case 'h':   //Show Help
        case 'H':
        case '?':
            ShowHelp();
            break;

        default:
            printf( "Invalid Cmd\n" );
            break;
        }
    }
    return 0;
}
最後に編集したユーザー usao on 2019年8月19日(月) 16:07 [ 編集 1 回目 ]

アバター
いわん
記事: 32
登録日時: 9年前

Re: 迷路ゲームできた

投稿記事 by いわん » 5年前

見える見える。
側面と正面の文字を変えたらもっとわかりやすいかも。

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

見えますか(さすがだ).
側面と正面… '#'は正面っぽい(気がする)んだけど,側面っぽい文字というのはなかなか見当たらないですね.

入力がgetchar()だと毎回enter押さねばならんし,"448"とか激しい先行入力とかされちゃうしでイマイチ…
C/C++の標準で,キーの「押下」を得る方法は無いっぽいんですよね.
(今回のコードは,初心者向きコードっぽい雰囲気をなんとなく目指したので,環境固有のAPIとかは使わない方向.)

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

側面を ':' に変えてみた.
確かにわかりやすいように思う.
Views.png
臨場感のある圧倒的グラフィック
Views.png (6.95 KiB) 閲覧数: 1044 回
コードの修正はShow()関数の内容のみ.
(あと,Rect::IsValid()メソッドが不要になったので削除してよい)

CODE:

//現在の視界の絵を表示
void Show( int PX,int PY, int iDir )
{
    const int kr[8] = { -1, 0, 1,   -1, 1, 0,   -1,1 };
    const int kf[8] = {  2, 2, 2,    1, 1, 1,    0,0 };
    const std::pair<int,int> DrawDataIndex[8] = {
        { 0,0 }, {1,1}, {2,2},  {3,5}, {6,8}, {9,9},    {10,11}, {12,13}
    };

    const std::pair<Rect,char> DrawData[] = {
        { Rect(0,4,3,7), '#' },
        { Rect(4,4,7,7), '#' },
        { Rect(8,4,11,7), '#' },

        { Rect(0,2,1,9), '#' }, { Rect(2,2,2,9), ':' }, { Rect(3,3,3,8), ':' },
        { Rect(10,2,11,9), '#' }, { Rect(9,2,9,9), ':' }, { Rect(8,3,8,8), ':' },
        { Rect(2,2,9,9), '#' },

        { Rect(0,0,0,11), ':' }, { Rect(1,1,1,10), ':' },
        { Rect(11,0,11,11), ':' }, { Rect(10,1,10,10), ':' }
    };

    //Buff[][]に表示内容を作る
    char Buff[12][12+1] = { 0 };
    {
        DrawRect( Buff, Rect(0,0, 11,11), ' ' );
        //壁の描画
        const int fx = DX[ iDir ];
        const int fy = DY[ iDir ];
        const int rx = -fy;
        const int ry = fx;
        for( int i=0; i<8; ++i )
        {
            int x = PX + fx*kf[i] + rx*kr[i];
            int y = PY + fy*kf[i] + ry*kr[i];
            if( Attr(x,y) == '#' )
            {
                for( int iDD=DrawDataIndex[i].first; iDD<=DrawDataIndex[i].second; ++iDD )
                {   DrawRect( Buff, DrawData[iDD].first, DrawData[iDD].second );    }
            }
        }
        //ゴールの描画
        if( Attr( PX,PY )=='G' ){   Buff[11][5] = 'G';  }
        else if( Attr( PX+fx, PY+fy )=='G' ){   Buff[9][5] = 'G';   }
    }

    //Buff[][]の内容を表示
    printf("+------------+\n" );
    for( int y=0; y<12; ++y ){  printf( "|%s|\n", Buff[y] );    }
    printf("+------------+\n" );
}

アバター
もるも
記事: 54
登録日時: 9年前

Re: 迷路ゲームできた

投稿記事 by もるも » 5年前

コンソールで臨場感出そうとするセンス・・・。

(そして昔、3D風ダンジョンエディタに挑戦してたのを思い出した)

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

おっと,うっかりグラフィックに力入れすぎちゃったかな?

コンソールは最も簡単に表示を行える手段の一つですから,
printf()とかで十分なグラフィック表示が達成できるならばそれに越したことはない!
…んだけど,
外字の利用が無理:
 PC98でN88-BASICやってたときは外字をプログラムでその場限り用に作って表示とかできた気がするんだけど
 windows上ではそういうのは無理そう(外字はシステム登録する物という扱いっぽい)
とかで,工夫の余地が無い感.
せめてGRPHキーで出せた文字群(wikipediaの「グラフキー」で見れるよ)があればなぁ…

アバター
もるも
記事: 54
登録日時: 9年前

Re: 迷路ゲームできた

投稿記事 by もるも » 5年前

迷路に夢中になるとエンターキー押し忘れて、ついつい連続入力してしまう(笑)

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

ちょっと広いの作ってみた.
スタート地点は変えてないので,迷路データ定義部分を差し替えるだけでOK.

CODE:

//迷路データ
const int MAZE_W = 16;
const int MAZE_H = 8;
const char Maze[MAZE_H][MAZE_W+1] =
{   //'#' is Wall
    "....#.........#.", //0
    "#.###.#.###.#...", //1
    "#...#.#..G#...#.", //2
    "..#.###########.", //3
    ".##...#..#.#....", //4
    ".#..#....#...#.#", //5
    ".##...##.##.##..", //6
    "....#..#.......#"  //7
    //123456789ABCDEF
};
こういうデータ作るときは Insertキー が大活躍ですね.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

現在の最近コード.

更新内容:
・スタート地点とスタート時の向きを迷路データに仕込めるようにした
・グラフィックのさらなる向上:ゴール地点の表示を改善

CODE:

//向きに関する定数
const int DX[] = { 0, 1, 0, -1 };
const int DY[] = {-1, 0, 1,  0 };
const char DName[] = { 'N', 'E', 'S', 'W' };

//迷路データ
// '#' : 壁
// DName[]のいずれか : スタート地点.どの文字かによってスタート時の向きも決まる.
// 'G' : ゴール地点
// その他の文字 : 通路
const int MAZE_W = 16;
const int MAZE_H = 8;
const char Maze[MAZE_H][MAZE_W+1] =
{   //123456789ABCDEF
    "...W#.........#.", //0
    "#.###.#.###.#...", //1
    "#...#.#..G#...#.", //2
    "..#.###########.", //3
    ".##...#..#.#....", //4
    ".#..#....#...#.#", //5
    ".##...##.##.##..", //6
    "....#..#.......#"  //7
    //123456789ABCDEF
};

//迷路データを走査して,スタート地点とスタート時の向きを決める.
//成功したらPX,PY,iDirに結果を格納してtrueを返す.
//falseを返した場合,PX,PY,iDirの値は変更されない.
bool FindStartPosAndDir( int &PX, int &PY, int &iDir )
{
    for( int y=0; y<MAZE_H; ++y )
    {
        for( int x=0; x<MAZE_W; ++x )
        {
            for( int i=0; i<4; ++i )
            {
                if( Maze[y][x] == DName[i] )
                {   PX=x;   PY=y;   iDir=i; return true;    }
            }
        }
    }
    return false;
}

//迷路データの位置(x,y)の内容を取得する.
//(x,y)が領域外の場合は,壁を表す'#'が返される.
char Attr( int x, int y ){  return ( ( x<0 || x>=MAZE_W || y<0 || y>=MAZE_H )   ?   '#'   :   Maze[y][x] ); }

//Show()で使う作業用構造体.矩形範囲を表す.
struct Rect
{
    int Left, Top, Right, Bottom;

    Rect( int Left=0, int Top=0, int Right=-1, int Bottom=-1 )
        : Left(Left), Top(Top), Right(Right), Bottom(Bottom) {}
};

//Show()から使う作業関数.Buff[][]の指定範囲をCで埋める
void DrawRect( char Buff[12][12+1], const Rect &R, char C )
{
    for( int y=R.Top; y<=R.Bottom; ++y )
    {
        for( int x=R.Left; x<=R.Right; ++x )
        {   Buff[y][x] = C; }
    }
}

//現在の視界の絵を表示
void Show( int PX,int PY, int iDir )
{
    const int kr[8] = { -1, 0, 1,   -1, 1, 0,   -1,1 };
    const int kf[8] = {  2, 2, 2,    1, 1, 1,    0,0 };
    const std::pair<int,int> DrawDataIndex[8] = {
        { 0,0 }, {1,1}, {2,2},  {3,5}, {6,8}, {9,9},    {10,11}, {12,13}
    };
    const std::pair<Rect,char> DrawData[] = {
        { Rect(0,4,3,7), '#' },
        { Rect(4,4,7,7), '#' },
        { Rect(8,4,11,7), '#' },

        { Rect(0,2,1,9), '#' }, { Rect(2,2,2,9), ':' }, { Rect(3,3,3,8), ':' },
        { Rect(10,2,11,9), '#' }, { Rect(9,2,9,9), ':' }, { Rect(8,3,8,8), ':' },
        { Rect(2,2,9,9), '#' },

        { Rect(0,0,0,11), ':' }, { Rect(1,1,1,10), ':' },
        { Rect(11,0,11,11), ':' }, { Rect(10,1,10,10), ':' }
    };

    //Buff[][]に表示内容を作る
    char Buff[12][12+1] = { 0 };
    {
        DrawRect( Buff, Rect(0,0, 11,11), ' ' );
        //壁の描画
        const int fx = DX[ iDir ];
        const int fy = DY[ iDir ];
        const int rx = -fy;
        const int ry = fx;
        for( int i=0; i<8; ++i )
        {
            int x = PX + fx*kf[i] + rx*kr[i];
            int y = PY + fy*kf[i] + ry*kr[i];
            if( Attr(x,y) == '#' )
            {
                for( int iDD=DrawDataIndex[i].first; iDD<=DrawDataIndex[i].second; ++iDD )
                {   DrawRect( Buff, DrawData[iDD].first, DrawData[iDD].second );    }
            }
        }
        //ゴール地点の描画
        if( Attr( PX,PY )=='G' ){   for( int i=0; i<4; ++i ){   Buff[11][4+i] = "GOAL"[i];  }   }
        else if( Attr( PX+fx, PY+fy )=='G' ){   Buff[8][5] = 'G';   Buff[8][6] = 'L';   }
    }

    //Buff[][]の内容を表示
    printf("+------------+\n" );
    for( int y=0; y<12; ++y ){  printf( "|%s|\n", Buff[y] );    }
    printf("+------------+\n" );
}

//操作方法表示
void ShowHelp()
{
    printf( "=== Cmd List ==============\n" );
    printf( "{f,F,8} : Walk Forward\n" );
    printf( "{l,L,4} : Turn Left\n" );
    printf( "{r,R,6} : Turn Right\n" );
    printf( "{q,Q}   : Quit This Game\n" );
    printf( "{h,H,?} : Show This Help\n" );
    printf( "==========================\n" );
}

//入力処理
char InputCmd()
{
    printf( "Cmd? : " );
    char Input;
    while( true )
    {
        Input = getchar();
        if( Input!='\r' && Input!='\n' )break;  //※前回のEnterの影響をどうにかする用
    }
    return Input;
}

//main
int main()
{
    printf( "== The Maze Game ==\n" );
    printf( "(input '?' Cmd to show help)\n" );

    //データ初期化
    int PX=0, PY=0, iDir=2; //(PX,PY):現在位置, iDir:現在の向き(0~3).
    if( !FindStartPosAndDir( PX,PY,iDir ) )
    {   printf( "\n(Start with Default Pos and Dir)\n" );   }   //※迷路データから決定できない時はエラーにせずに初期値のまま処理を進める

    //Game Loop
    bool bLoop = true;
    while( bLoop )
    {
        //現在の視界を表示
        printf( "\n" );
        Show( PX,PY, iDir );

        //ゴール判定
        if( Attr( PX,PY )=='G' )
        {
            printf( "\n *** GOAL! *** \n" );
            printf( "(Input Any Cmd to Quit)\n" );
            InputCmd();
            break;
        }

        //入力受付とゲーム進行
        printf( "Pos(%d,%d) Dir(%c)\n", PX,PY,DName[iDir] );
        switch( InputCmd() )
        {
        case 'f':   case 'F':   case '8':   //Forward
            {
                int FX = PX + DX[iDir];
                int FY = PY + DY[iDir];
                if( Attr(FX,FY) != '#' ){   PX = FX;    PY = FY;    }
            }
            break;
        case 'l':   case 'L':   case '4':   //Turn Left
            if( --iDir < 0 ){   iDir = 3;   }
            break;
        case 'r':   case 'R':   case '6':   //Turn Right
            if( ++iDir > 3 ){   iDir = 0;   }
            break;
        case 'q':   case 'Q':   //Quit
            bLoop = false;
            break;
        case 'h':   case 'H':   case '?':   //Show Help
            ShowHelp();
            break;
        default:
            printf( "Invalid Cmd\n" );
            break;
        }
    }
    return 0;
}

アバター
もるも
記事: 54
登録日時: 9年前

Re: 迷路ゲームできた

投稿記事 by もるも » 5年前

風景で道覚えるタイプの人間には無機質すぎて難しい・・・(;^ω^)

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: 迷路ゲームできた

投稿記事 by usao » 5年前

まぁこの手のやつは,
「場所の同定が風景からは原理的にほぼ不可能」という状態にしとかないと成り立たない(迷わない)ですからね.
壁のテクスチャは全部一緒(あるいは少数のパターンの使い回し)だし,ゴミ1つ落ちてないし.

しかし,なるほどなぁ…
【「ある日突然,無機質な空間に放り込まれてどうの」という状況に備えての訓練用】ということにすれば
実用性のあるソフトウェアになりますね! やったぜ!


#実世界の2大罠{直角だと思ったら違う,直線だと思ったら違う}がとても苦手