迷路ゲームできた

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

迷路ゲームできた

投稿記事 by usao » 6年前

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

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

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

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 いわん » 6年前

10キーの無いノートPCでも片手で操作できるように W, A, D キーとかが使えたらいいなと思ったり( ̄▽ ̄)

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

Re: 迷路ゲームできた

投稿記事 by usao » 6年前

うーん,キーの種類よりも
「Enterも押さないといかん」せいで,なかなか片手操作には至らないかなぁ.

根本的な操作性の向上には,環境固有のAPIを使うしかなさそう.
キーじゃなくてマウスで操作したいし.

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

Re: 迷路ゲームできた

投稿記事 by usao » 6年前

更新.
・さらなるグラフィックの向上:遠方の壁は'+'で描画するようにした
・{W,A,D}でも操作できるようにした.

CODE:

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

//迷路データ
//  '#' : 壁
//  DName[]のいずれか : スタート地点.どの文字かによってスタート時の向きも決まる.
//  'G' : ゴール地点
//  その他の文字 : 通路
#if 0
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
};
#else
const int MAZE_W = 16;
const int MAZE_H = 12;
const char Maze[MAZE_H][MAZE_W+1] =
{
    //123456789ABCDEF
    "...#..........#.", //0
    "##.###.####.#.#.", //1
    "........#...#...", //2
    ".##.###.#.#...#.", //3
    "#.....#...#.##..", //4
    "#.###.##.##...##", //5
    "..#....#....#.#.", //6
    ".##.##.#.####.#.", //7
    ".......##...#...", //9
    ".#.####...#...#.", //A
    ".#N#....#...#.#.", //B
    "..##G##...##...#"  //C
    //123456789ABCDEF
};
#endif

//迷路データを走査して,スタート地点とスタート時の向きを決める.
//成功したら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] ); }

//ShowView()で使う作業用構造体.矩形範囲を表す.
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) {}
};

//ShowView()から使う作業関数.Buff[][]の指定範囲をCで埋める
void FillRect( 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 ShowView( 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 };
    {
        FillRect( Buff, Rect(0,0, 11,11), ' ' );
        //壁の描画
        const int fx=DX[iDir], fy=DY[iDir]; //(fx,fy):前方ベクトル
        const int rx=-fy, ry=fx;    //(rx,ry):右手方向ベクトル
        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 )
                {   FillRect( 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( " {W,F,8} : Walk Forward\n" );
    printf( " {A,L,4} : Turn Left\n" );
    printf( " {D,R,6} : Turn Right\n" );
    printf( " {Q}     : Quit This Game\n" );
    printf( " {H,?}   : Show This Help\n" );
    printf( "==========================\n" );
}

//入力処理.アルファベット小文字が入力された場合は大文字として返す.
char InputCmd()
{
    printf( "Cmd? : " );
    char Input;
    do{ Input = getchar();  }while( Input=='\r' || Input=='\n' );   //※前回のEnterの影響に対処
    return ( ( 'a'<=Input && Input<='z' )   ?   'A'+(Input-'a')   :   Input );  //小文字なら大文字にして返す
}

//main
int main()
{
    printf( "== The Maze Game ==\n(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===
    while( true )
    {
        //現在の状態を表示
        printf( "\n" );
        ShowView( PX,PY, iDir );    //視界の表示
        printf( "Pos(%d,%d) Dir(%c)\n", PX,PY,DName[iDir] );    //現在位置と向きの表示
        //ゴール判定
        if( Attr( PX,PY )=='G' )
        {
            printf( "\n *** GOAL! *** \n(Input Any Cmd to Quit)\n" );
            InputCmd();
            return 0;
        }
        //入力受付とゲーム進行
        switch( InputCmd() )
        {
        case 'W':   case 'F':   case '8':   //Forward
            {
                int FX = PX+DX[iDir],   FY = PY+DY[iDir];   //一歩前の座標(FX,FY)
                if( Attr(FX,FY) != '#' ){   PX = FX;    PY = FY;    }
            }
            break;
        case 'A':   case 'L':   case '4':   //Turn Left
            if( --iDir < 0 ){   iDir = 3;   }
            break;
        case 'D':   case 'R':   case '6':   //Turn Right
            if( ++iDir > 3 ){   iDir = 0;   }
            break;
        case 'Q':   //Quit
            return 0;
            break;
        case 'H':   case '?':   //Show Help
            ShowHelp();
            break;
        default:
            printf( "Invalid Cmd\n" );
            break;
        }
    }
    return 0;
}
最後に編集したユーザー usao on 2019年8月23日(金) 17:08 [ 編集 1 回目 ]

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

Re: 迷路ゲームできた

投稿記事 by usao » 6年前

先に鍵を拾っておかないと通れない扉とかを追加すると
微かにゲーム性が増すかもしれない
Gate.png
圧倒的なグラフィックだな
Gate.png (3.51 KiB) 閲覧数: 125 回

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

Re: 迷路ゲームできた

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

[Enter]キーで入力というのはいかんともしがたいですね。
3Dの迷路は大きくなるとどこをどう通ってるのかもわからなくなって
結局壁伝いの作業になってしまいます(;´・ω・)
上から見た図もあった方がいいのかな。良し悪しですが。

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

Re: 迷路ゲームできた

投稿記事 by usao » 6年前

オートマッピングみたいな見下ろし的な表示があると,歩く際にはそっちを見ればいい(3D表示側を見ない)というプレイスタイルになること必至なのがなんとも…

迷路的なことをする場合,DQ1方式が良い塩梅なのかも.