/* =============================================================================== Maze.mc : 迷路を自動生成して描画 =============================================================================== Copyright (C) 2010-2013 Masahiko Watanabe Edition History: 2010.11.06 初版( GK Library: Rev.2009.02.03 使用 ) 2013.02.15 MikoScript3(ユニコード版)用に変更 -----------------------------------------------------------------------------*/ /*****/ // GK Library のモジュールをロードする場合 #include #include #include if( ::Module.Include( "GkLibrary" ) > 0 ) Main( argc, argv ); return; /*****/ /***** // GK Library のソースをインクルードする場合 #include Main( argc, argv ); return; /*****/ //----------------------------------------------------------------------------- // デバッグプリント無効化(リリース時) #set pc #comment #set pv // デバッグプリント有効化(デバッグ時) //#set pc print //#set pv print #set p print //----------------------------------------------------------------------------- /// マクロ定義 /// #set MAIN_TITLE "迷路" // ウィンドウのキャプション #set Wnd_Xo // ウィンドウの左端位置(空白はデフォールト) #set Wnd_Yo // ウィンドウの上端位置(空白はデフォールト) #set DlgAreaYd 30 // ダイアログ領域の高さ #set FigAreaXd 640 // 図形描画領域の幅 #set FigAreaYd 480 // 図形描画領域の高さ #set IniXn 28 // 横のマス数の初期値 #set MinXn 10 //    〃   最小値 #set MaxXn 100 //    〃   最大値 #set IniYn 20 // 縦のマス数の初期値 #set MinYn 10 //    〃   最小値 #set MaxYn 100 //    〃   最大値 #set IniCd 18 // 1マスのサイズの初期値 #set MinCd 6 //     〃    最小値 #set MaxCd 24 //     〃    最大値 #set IniWd 3 // 壁の厚さの初期値 #set MinWd 1 //   〃  最小値 #set MaxWd 4 //   〃  最大値 #set RGB_FIG_BACK #RGB(255,255,255) // 図形描画領域の背景色 #set RGB_WALL #RGB(0,0,0) // 壁の色 #set RGB_START_POINT #RGB(0,0,255) // スタート地点の「S」の文字色 #set RGB_GOAL_POINT #RGB(255,0,0) // ゴール地点の「G」の文字色 #set RGB_ANS_MARK #RGB(0,240,0) // 正解経路のマスのマーク色 #set RND ( ^Rand() & 0x7FFFFFFF ) // ≪補説≫ 0 から k 個の整数値の一様乱数は、( #RND % k ) として取得する。 // これは、厳密ではないが、k が乱数の最大値( ここでは、0x7FFFFFFF )に // 比べて充分小さいので、殆ど誤差はない。 //============================================================================= function Main( argc, argv ) { #pc "開始"; ^DefValue = null; //::Math.srand(); ^Rand := ::Math.rand; SetupPaintConds(); class ^MainWindow : ::GK.Window {} R = ::GK.Rect( 0, 0, #FigAreaXd, #DlgAreaYd + #FigAreaYd ); ::GK.Window.GetAdjRect( R ); ^MainWindow.Construct( #MAIN_TITLE, #Wnd_Xo, #Wnd_Yo, R.Xd, R.Yd ); ^MainWindow.Open(); ::GK.WindowMsgLoop(); FIN: delete ^MainWindow; DeletePaintConds(); #pc "終了"; return; OnError: print ::ErrorMessage( $err_code, $err_info ); goto FIN; } //----------------------------------------------------------------------------- function SetupPaintConds() { ^WallPen = ::GK.Pen( #RGB_WALL, #PS_SOLID, 1 ); // 壁を描くペン ^MarkPen = ::GK.Pen( #RGB_ANS_MARK, #PS_SOLID, 1 ); // マークを描くペン ^BackBrush = ::GK.Brush( #RGB_FIG_BACK ); // 背景を塗り潰すブラシ ^MarkBrush = ::GK.Brush( #RGB_ANS_MARK ); // 正解のマークを描くブラシ ^DefWidgetFont = ::GK.Font( "MS ゴシック", 14, #SHIFTJIS_CHARSET, #FIXED_PITCH ); //^PointFont = ::GK.Font( //「S」と「G」の地点を示す文字のフォント // "Times New Roman", #IniCd, #ANSI_CHARSET, #VARIABLE_PITCH, ::GK.Font.BOLD ); ^PointFontHeigt = 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function DeletePaintConds() { delete ^WallPen, ^MarkPen; delete ^BackBrush, ^MarkBrush; delete ^DefWidgetFont, ^PointFont; } //============================================================================= function ^MainWindow.OnCreate() { #pc "OnCreate"; .MakeDlgItems(); .InizFigData(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.OnClose() { #pc "OnClose"; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.OnSize() { #pc "OnSize", .gc.Xd, .gc.Yd; .SetDlgAreaLayout(); .SetFigAreaLayout(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.OnPaint() { #pc "OnPaint"; .GetHotRect( R'STRUCT ); if( ! .DlgAreaRect.Out?( R ) ) .DrawDlgArea(); if( ! .FigAreaRect.Out?( R ) ) .DrawFigArea(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.OnCreateMazeButton() { #pc "OnCreateMazeButton"; .Xn = .XnInput.GetText()'int'cut( #MinXn, #MaxXn ); if( !.Xn ) .Xn = #IniXn; .XnInput.SetText( .Xn'd ); .Yn = .YnInput.GetText()'int'cut( #MinYn, #MaxYn ); if( !.Yn ) .Yn = #IniYn; .YnInput.SetText( .Yn'd ); .SetFigAreaLayout(); .Path = ^CreateMaze( .Xn, .Yn ); delete .Ans; .Answer? = #FALSE; .DrawFigArea(); .SolveMazeBtn.SetTitle( "解答表示" ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.OnSolveMazeButton() { #pc "OnSolveMazeButton"; .Answer? = ! .Answer?; if( .Answer? ) { if( !.Ans'exist? ) .Ans = ^GetMazeAnswer( .Path, .Xn, .Yn ); .SolveMazeBtn.SetTitle( "解答消去" ); } else .SolveMazeBtn.SetTitle( "解答表示" ); .DrawFigArea(); .XnInput.SetText( .Xn'd ); .YnInput.SetText( .Yn'd ); } //----------------------------------------------------------------------------- function ^MainWindow.MakeDlgItems() // 各種表示/設定項目を作成 { .DefWidgetFont := ^DefWidgetFont; R = ::GK.Rect( 0,0,0,0 ); #pv .XnTitle = .AddLabel( "横マス数:", R, #SS_RIGHT ); #pv .XnInput = .AddEditBox( #ES_CENTER | #ES_NUMBER, R ); .XnInput.SetText( #IniXn'd ); #pv .YnTitle = .AddLabel( "縦マス数:", R, #SS_RIGHT ); #pv .YnInput = .AddEditBox( #ES_CENTER | #ES_NUMBER, R ); .YnInput.SetText( #IniYn'd ); #pv .CreateMazeBtn = .AddButton( "push", "迷路作成", R ); .CreateMazeBtn.OnCommand = function() { .owner.SetFocus(); .owner.OnCreateMazeButton(); }; #pv .SolveMazeBtn = .AddButton( "push", "解答表示", R ); .SolveMazeBtn.OnCommand = function() { .owner.SetFocus(); .owner.OnSolveMazeButton(); }; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.SetDlgAreaLayout() { .DlgAreaRect = ::GK.Rect( 0, 0, .gc.Xd, #DlgAreaYd ); R1 = ::GK.Rect( 20, 7, 72, 20 ); R2 = ::GK.Rect( R1.Xo + R1.Xd + 2, 5, 30, 20 ); .XnTitle.Move( R1 ); .XnInput.Move( R2 ); R1.Xo = R2.Xo + R2.Xd + 20; R2.Xo = R1.Xo + R1.Xd + 2; .YnTitle.Move( R1 ); .YnInput.Move( R2 ); R = ::GK.Rect( R2.Xo + R2.Xd + 30, 3, 80, 24 ); .CreateMazeBtn.Move( R ); R.Xo += R.Xd + 20; .SolveMazeBtn.Move( R ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.DrawDlgArea() { .gc.DrawEdge( .DlgAreaRect, #EDGE_BUMP, #BF_BOTTOM|#BF_MIDDLE ); } //----------------------------------------------------------------------------- function ^MainWindow.InizFigData() { .Xn = #IniXn; .Yn = #IniYn; .Cd = #IniCd; .Wd = #IniWd; .Path = ^CreateMaze( .Xn, .Yn ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.SetFigAreaLayout() { .FigAreaRect = ::GK.Rect( 0, #DlgAreaYd, .gc.Xd, .gc.Yd - #DlgAreaYd ); XCd = ( .FigAreaRect.Xd - .Wd * ( .Xn + 1 )) / ( .Xn + 2 ); YCd = ( .FigAreaRect.Yd - .Wd * ( .Yn + 1 )) / ( .Yn + 2 ); .Cd = ::Math.min( XCd, YCd )'cut( #MinCd, #MaxCd ); .Wd = ( .Cd / 6 )'cut( #MinWd, #MaxWd ); Xd = ( .Wd + .Cd ) * .Xn + .Wd; Xo = ( .FigAreaRect.Xd - Xd ) / 2; if( Xo < .Cd ) Xo = .Cd; Yd = ( .Wd + .Cd ) * .Yn + .Wd; Yo = ( .FigAreaRect.Yd - Yd ) / 2; if( Yo < .Cd ) Yo = .Cd; .MazeRect = ::GK.Rect( Xo, Yo + #DlgAreaYd, Xd, Yd ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.DrawFigArea() { .gc.SetBrush( ^BackBrush ); .gc.ClearRect( .FigAreaRect ); .DrawMaze(); } //----------------------------------------------------------------------------- // 移動方向を示す値 #set DM_LEFT 0b0001 #set DM_RIGHT 0b0010 #set DM_UP 0b0100 #set DM_DOWN 0b1000 #set DM_NONE 0 // 移動マス関連 #set Pos( X, Y, D ) ((( #X ) << 18 )|(( #Y ) << 4 )|( #D )) #set GetXY( V ) ((( #V ) >> 18 ) & 0x3FFF, (( #V ) >> 4 ) & 0x3FFF ) #set GetD( V ) (( #V ) & 0b1111 0) #set GetXYD( V ) ((( #V ) >> 18 ) & 0x3FFF, (( #V ) >> 4 ) & 0x3FFF, ( #V ) & 0b1111 ) #set MaskPosXY 0xFFFFFFF0 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^MainWindow.DrawMaze() { // 全ての壁を描画 .gc.SetPen( ^WallPen ); Xs = .MazeRect.Xo; Ys = .MazeRect.Yo; Xe = Xs + .MazeRect.Xd; Ye = Ys + .MazeRect.Yd; D = .Wd + .Cd; for( i = 0 ; i < .Wd ; i++ ) { for( X = Xs + i ; X < Xe ; X += D ) // 各縦線の描画 { .gc.MoveTo( X, Ys ); .gc.LineTo( X, Ye ); } for( Y = Ys + i ; Y < Ye ; Y += D ) // 各横線の描画 { .gc.MoveTo( Xs, Y ); .gc.LineTo( Xe, Y ); } } // 移動可能経路を妨げている壁を消す if( ! .Path'exist? ) return; Xs += .Wd; Ys += .Wd; R = ::GK.Rect( 0,0,0,0 ); N = .Path'count; for( i = 0 ; i < N ; i++ ) { v = .Path( i ); ( x, y, d ) = #GetXYD( v ); R.Xo = Xs + ( x - 1 ) * D; R.Yo = Ys + ( y - 1 ) * D; switch( d ) { case #DM_LEFT: R.Xd = .Wd; R.Yd = .Cd; R.Xo += .Cd; break; case #DM_RIGHT: R.Xd = .Wd; R.Yd = .Cd; R.Xo -= .Wd; break; case #DM_UP: R.Xd = .Cd; R.Yd = .Wd; R.Yo += .Cd; break; case #DM_DOWN: R.Xd = .Cd; R.Yd = .Wd; R.Yo -= .Wd; break; default: continue; } .gc.ClearRect( R ); } // スタートとゴールのマスに「S」と「G」の文字を描画 if( ^PointFontHeight != .Cd ) { .gc.SetFont( ^DefWidgetFont ); delete ^PointFont; ^PointFont = ::GK.Font( "Times New Roman", .Cd, #ANSI_CHARSET, #VARIABLE_PITCH, ::GK.Font.BOLD ); ^PointFontHeight = .Cd; } .gc.SetFont( ^PointFont ); .gc.SetBackMode( #TRANSPARENT ); .gc.SetBackColor( #RGB_FIG_BACK ); R = ::GK.Rect( Xs - D, Ys, D, .Cd ); .gc.ClearRect( R ); .gc.SetTextColor( #RGB_START_POINT ); .gc.DrawText( R, "S" ); R.Xo = Xs + ( .Xn - 1 ) * D + .Cd; R.Yo = Ys + ( .Yn - 1 ) * D; .gc.ClearRect( R ); .gc.SetTextColor( #RGB_GOAL_POINT ); .gc.DrawText( R, "G" ); // 正解の経路上にマークを表示 if( .Answer? ) // 正解表示要? { Xs += .Cd / 3; Ys += .Cd / 3; R.Xd = R.Yd = .Cd / 3; .gc.SetPen( ^MarkPen ); .gc.SetBrush( ^MarkBrush ); N = .Ans'count; for( i = 0 ; i < N ; i++ ) { v = .Ans( i ); if( v == 0 ) break; ( x, y ) = #GetXY( v ); R.Xo = Xs + ( x - 1 ) * D; R.Yo = Ys + ( y - 1 ) * D; .gc.DrawRect( R ); } } } //----------------------------------------------------------------------------- // 迷路を自動生成する( Xn: 横のマス数, Yn: 縦のマス数 ) function CreateMaze( Xn, Yn ) { // 各マスの状態配列( =0:未通過, 1:通過済, -1:移動禁止 ) Ms'BYTE( Xn + 2, Yn + 2 ); //(注)外周用に本来よりひと回り大きく取る // 外周の各マスに移動禁止を示す値を設定 Xm = Xn + 1; Ym = Yn + 1; for( x = 0 ; x <= Xm ; x++ ) { Ms( x, 0 ) = -1; Ms( x, Ym ) = -1; } for( y = 1 ; y <= Yn ; y++ ) { Ms( 0 , y ) = -1; Ms( Xm, y ) = -1; } Path'ULONG( Xn * Yn ); // 通過経路(複数)を格納する配列 N = 0; // Path 配列内の現有効要素数 ( x, y ) = ( 1, 1 ); // 最初のマス(とりあえず左上端のマスにする) Path( N++ ) = #Pos( x, y, #DM_NONE ); Ms( x, y ) = 1; Branch'cbox!; // 分岐先を出し入れするための箱 for( ;; ) { b = 0; if( ! Ms( x - 1, y ) ) // 左へ進める? { Branch'post( #Pos( x - 1, y, #DM_LEFT ) ); b++; } if( ! Ms( x + 1, y ) ) // 右へ進める? { Branch'post( #Pos( x + 1, y, #DM_RIGHT ) ); b++; } if( ! Ms( x, y - 1 ) ) // 上へ進める? { Branch'post( #Pos( x, y - 1, #DM_UP ) ); b++; } if( ! Ms( x, y + 1 ) ) // 下へ進める? { Branch'post( #Pos( x, y + 1, #DM_DOWN ) ); b++; } switch( b ) { case 0: // どの方向へも進めない場合 // 分岐可能なマスを乱数で選ぶ for( k = Branch'count ; k > 0 ; k-- ) { v <- Branch'first( #RND % k ); // 分岐先を乱数で選んで取り出す ( x, y ) = #GetXY( v ); if( ! Ms( x, y ) ) // その分岐先のマスは未通過? goto SAVE_PATH; } quit; // 分岐先が無くなれば終了 case 1: // 1方向へだけ進める場合 v <- Branch'last; // 最後の分岐先を取り出す break; default: // 複数の方向へ進める場合 // 最後の b 個の分岐先のうちの1つを乱数で選んで取り出す v <- Branch'last( #RND % b ); break; } ( x, y ) = #GetXY( v ); SAVE_PATH: Path( N++ ) = v; Ms( x, y ) = 1; } return Path; } //----------------------------------------------------------------------------- // 迷路の正解の経路を返す function GetMazeAnswer( Path, Xn, Yn ) { Ans'ULONG( Xn * Yn ); // ゴールからスタートまでの経路(正解経路の逆順) K = 0; // この配列の現要素数 // ゴールのマスを探す v = #Pos( Xn, Yn, 0 ); N = Path'count; for( i = N - 1 ; i >= 0 ; i-- ) { if( v == ( Path( i ) & #MaskPosXY )) break; } v = Path( i ); ( x, y, d ) = #GetXYD( v ); // ゴールからスタートまでの逆経路をたどる for( ;; ) { NEXT: Ans( K++ ) = v; // 移動前のマス位置を求める switch( d ) { case #DM_LEFT: x++; break; case #DM_RIGHT: x--; break; case #DM_UP: y++; break; case #DM_DOWN: y--; break; default: quit; } // 移動前のマス位置を探す ( bx, by ) = ( x, y ); while( i > 0 ) { v = Path( --i ); ( x, y, d ) = #GetXYD( v ); if( x == bx && y == by ) goto NEXT; } quit; } return Ans; } //-----------------------------------------------------------------------------