/* =============================================================================== NumPlace.mc : 数独(ナンプレ)パズルゲーム =============================================================================== Copyright (C) 2008-2013 Masahiko Watanabe Edition History: 2008.10.07 数独パズルの表示、数字入力、ヒント表示、正解表示、 問題の自動作成 2013.03.04 MikoScript3(ユニコード版)用に変更 -----------------------------------------------------------------------------*/ /*****/ // GK Library のモジュールをロードする場合 #include "GkLib\GkDefs.mh" #include "GkLib\GkSysDefs.mh" #include "GkLib\GkKey.mh" if( ::Module.Include( "GkLibrary" ) > 0 ) Main( argc, argv ); return; /*****/ /***** // GK Library のソースをインクルードする場合 #include "GkLib\GkLibrary.mc" #include "GkLib\GkKey.mh" Main( argc, argv ); return; /*****/ //----------------------------------------------------------------------------- // デバッグプリント無効化(リリース時) #set pc #comment #set pv // デバッグプリント有効化(デバッグ時) //#set pc print //#set pv print #set p print #set RGB_FRAME #RGB(0,0,0) // 盤の枠線色 #set RGB_A_CELL #RGB(224,255,224) // 解答マスの背景色 #set RGB_B_CELL #RGB(255,255,255) // 問題マスの背景色 #set RGB_CUR_CELL #RGB(255,224,224) // 操作対象マスの背景色 #set RGB_IMP_CELL #RGB(255,255,128) // 解答不能マスの背景色 #set RGB_ANS_CELL #RGB(255,216,224) // 正解答マスの背景色(解答表示窓内) #set RGB_A_NUM #RGB(0,128,0) // 解答マスの数字色 #set RGB_B_NUM #RGB(0,0,0) // 問題マスの数字色 #set RGB_Ap_NUM #RGB(0,128,80) // 解答マスの小型数字色(入力可能) #set RGB_Av_NUM #RGB(192,192,192) // 解答マスの小型数字色(入力不能) #set RGB_AnsNUM #RGB(128,0,0) // 正解答マスの数字色(解答表示窓内) #set RGB_ANS_LABEL #RGB(000,000,000) // 正解答の順位と総数の色(解答表示窓内) #set RGB_CONGRAT #RGB(255,000,000) // 完成祝福メッセージの表示色 #set ANS_PZL_TIMER_ID 101 // 問題解答待ちタイマーID #set NEW_PZL_TIMER_ID 102 // 問題生成待ちタイマーID #set CONGRAT_TIMER_ID 103 // 完成祝福メッセージの点滅用タイマーID //============================================================================= function Main( argc, argv ) { #pc "開始"; ^DefValue = null; #pv ::File.SetCurPath( ::Module.FilePath() ); #pc ::File.GetCurPath(); ^OM'BYTE(9,9) = // 初期表示用の問題 { { 0,0,0,7,5,0,0,0,0 }, { 0,3,0,0,4,8,0,2,0 }, { 1,0,0,0,0,0,0,0,6 }, { 0,4,0,0,0,0,0,0,8 }, { 7,9,0,0,0,0,0,3,1 }, { 2,0,0,0,0,0,0,7,0 }, { 5,0,0,0,0,0,0,0,7 }, { 0,8,0,3,2,0,0,4,0 }, { 0,0,0,0,6,9,0,0,0 }, // { 5,0,0,0,0,0,2,0,0 }, // { 0,0,0,0,0,0,0,0,0 }, // { 0,0,7,0,0,0,1,0,0 }, // { 2,0,0,0,3,0,4,0,0 }, // { 4,0,0,0,7,0,0,0,0 }, // { 0,0,9,0,0,0,0,0,0 }, // { 0,0,4,0,1,0,5,0,0 }, // { 7,0,6,0,0,0,9,0,3 }, // { 0,0,8,0,2,0,0,0,0 }, }; ^AM'BYTE(9,9); // 各マスの解答数字 ^BM = ^OM; // 各マスの問題数字 ^CM'USHORT(9,9); // 各マスの入力不能数字( Bit-N=1: 数字 N 入力不能 ) ^FramePen = ::GK.Pen( #RGB_FRAME, #PS_SOLID, 1 ); ^A_CellBrush = ::GK.Brush( #RGB_A_CELL ); ^B_CellBrush = ::GK.Brush( #RGB_B_CELL ); ^CurCellBrush = ::GK.Brush( #RGB_CUR_CELL ); ^ImpCellBrush = ::GK.Brush( #RGB_IMP_CELL ); ^AnsCellBrush = ::GK.Brush( #RGB_ANS_CELL ); ^DefWidgetFont = ::GK.Font( "MS Pゴシック", 14, #SHIFTJIS_CHARSET, #VARIABLE_PITCH ); ^AnsCntFont = ::GK.Font( "MS Pゴシック", 14, #SHIFTJIS_CHARSET, #VARIABLE_PITCH, ::GK.Font.BOLD ); //(注)ウィンドウサイズに依存するフォントは、適時動的に生成/破棄 class ^NP_Window : ::GK.Window {} class ^AnsWindow : ::GK.Window {} ^NP_Window.Construct( "ナンプレ(数独)パズル", , , 550, 440 ); ^NP_Window.Setup(); ^NP_Window.Open(); ::GK.WindowMsgLoop(); FIN: delete ^NP_Window, ^AnsWindow; delete ^FramePen; delete ^A_CellBrush, ^B_CellBrush, ^CurCellBrush, ^ImpCellBrush, ^AnsCellBrush; delete ^DefWidgetFont, ^AnsCntFont; #pc "終了"; return; OnError: print ::ErrorMessage( $err_code, $err_info ); goto FIN; } //----------------------------------------------------------------------------- /// ナンプレ(数独)メインウィンドウ /// function ^NP_Window.Construct( Title, Xo, Yo, Xd, Yd ) { #pc "Construct"; .[ ::GK.Window.Construct ]( Title, Xo, Yo, Xd, Yd ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.Destruct() { #pc "Destruct"; .[ ::GK.Window.Destruct ](); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.Setup() { #pc "Setup"; .DefWidgetFont := ^DefWidgetFont; .Px'SHORT(9,9); // 各マスの内側左端のX座標 .Py'SHORT(9,9); // 各マスの内側上端のY座標 .CI = -1; // 現マスの列インデックス .CJ = -1; // 現マスの行インデックス .ConfirmMatrix(); .IsHint = #FALSE; .IsCongrat = #FALSE; .IsCongratShown = #FALSE; .CongratCount = 0; // 操作ボタンの設定 R = ::GK.Rect( 0,0,0,0 ); .Btn[0] = .AddButton( "push", "ヒント表示", R ); .Btn[0].OnButton = .AlterHint; .Btn[1] = .AddButton( "push", "リセット", R ); .Btn[1].OnButton = .ResetPuzzleSolving; .Btn[2] = .AddButton( "push", "解答表示", R ); .Btn[2].OnButton = .ShowSolution; .Btn[3] = .AddButton( "push", "次の問題", R ); .Btn[3].OnButton = .MakeNewPuzzle; .nBtns = 4; for( i = 0 ; i < .nBtns ; i++ ) { .Btn[i].OnCommand = function( wgt_id ) { .owner.CurButton := this; .owner.SetFocus(); if( .OnButton'exist? ) .owner.[ .OnButton ](); }; } // 数字入力用コンテキストメニューの設定 .ContMenu = ::GK.Menu(); scope .ContMenu { .InsertItem( 1, "1" ); .InsertItem( 2, "2" ); .InsertItem( 3, "3" ); .InsertItem( 4, "4" ); .InsertItem( 5, "5" ); .InsertItem( 6, "6" ); .InsertItem( 7, "7" ); .InsertItem( 8, "8" ); .InsertItem( 9, "9" ); .InsertItem( 10, "消去" ); } } //----------------------------------------------------------------------------- function ^NP_Window.OnSize() { #pc "OnSize", .gc.Xd, .gc.Yd; // ウィンドウサイズに応じて、マスの位置と大きさを設定 .PXo = 24; // パズル枠の左端位置 .PYo = 24; // パズル枠の上端位置 .Pd = ( .gc.Yd - ( 24 + 24 + 3*4 + 1*6 )) / 9; // 1マスの内側のサイズ if( .Pd < 12 ) .Pd = 12; .Pw = 3*4 + 1*6 + .Pd * 9; // パズル枠の外側のサイズ Y = .PYo; for( I = 0 ; I < 9 ; I++ ) { Y += ( I % 3 == 0 ) ? 3 : 1; X = .PXo; for( J = 0 ; J < 9 ; J++ ) { X += ( J % 3 == 0 ) ? 3 : 1; .Px(I,J) = X; .Py(I,J) = Y; X += .Pd; } Y += .Pd; } // ウィンドウサイズに依存するフォントを削除 if( .NumFont'exist? ) delete .NumFont; if( .S_NumFont'exist? ) delete .S_NumFont; if( .CongraFont'exist? ) delete .CongraFont; // ウィンドウサイズに応じて、操作ボタンを配置 .BXo = .PXo + .Pw + 24; // ボタン表示の左端基準位置 .BYo = .PYo - 3 + 36 + 16; // ボタン表示の上端基準位置 .BXd = 108; .BYd = 28; .BYw = 36; R = ::GK.Rect( .BXo, .BYo, .BXd, .BYd ); for( i = 0 ; i < .nBtns ; i++ ) { .Btn[i].Move( R ); R.Yo += .BYw; } } //----------------------------------------------------------------------------- function ^NP_Window.OnPaint() { #pc "OnPaint"; //.GetHotRect( R'STRUCT ); //.gc.ClearRect( R, #RGB_B_CELL ); R = ::GK.Rect( 0, 0, .gc.Xd, .gc.Yd ); .gc.DrawEdge( R, #EDGE_SUNKEN, #BF_RECT|#BF_MIDDLE ); .DrawPuzzle(); if( .IsCongrat && // 完成メッセージ表示中? .CongratCount % 2 != 0 ) { .ShowCongrat(); } } //----------------------------------------------------------------------------- function ^NP_Window.OnClose() { #pc "OnClose"; if( .FinishPuzzleSolving() == -1 ) return -1; if( ^AnsWindow.hWnd != null ) ^AnsWindow.Close(); .StopTimer( #ANS_PZL_TIMER_ID ); .StopTimer( #NEW_PZL_TIMER_ID ); delete .AnsPzlProc; delete .NewPzlProc; return 0; } //----------------------------------------------------------------------------- function ^NP_Window.DrawPuzzle() // ナンプレの枠線と全9x9マスの表示 { if( ! .NumFont'exist? ) .NumFont = ::GK.Font( "MS ゴシック", .Pd - 5, #SHIFTJIS_CHARSET, #FIXED_PITCH, ::GK.Font.BOLD ); if( ! .S_NumFont'exist? ) .S_NumFont = ::GK.Font( "MS ゴシック", .Pd / 3, #SHIFTJIS_CHARSET, #FIXED_PITCH ); // 9x9マスの枠線の描画 .gc.SetPen( ^FramePen ); X1 = .PXo; X2 = .PXo + .Pw; Y = .PYo; for( i = 0 ; i <= 9 ; i++ ) { if( i % 3 == 0 ) { .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); ++Y; .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); ++Y; } .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); Y += .Pd + 1; } Y1 = .PYo; Y2 = .PYo + .Pw; X = .PXo; for( i = 0 ; i <= 9 ; i++ ) { if( i % 3 == 0 ) { .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); ++X; .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); ++X; } .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); X += .Pd + 1; } // 9x9マス内の背景と数字の描画 .gc.SetBackMode( 0 ); R.Xd = R.Yd = .Pd; r.Xd = r.Yd = .Pd / 3; for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) { R.Xo = .Px(I,J); R.Yo = .Py(I,J); if(( N = ^BM(I,J) ) > 0 ) { // 問題数字マス .gc.SetBrush( ^B_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_B_NUM ); .gc.DrawText( R, N'd ); } else { // 解答数字マス if(( N = ^AM(I,J) ) > 0 ) { .gc.SetBrush( ^A_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_A_NUM ); .gc.DrawText( R, N'd ); } else if( .IsHint ) { V = ^CM( I, J ); .gc.SetBrush( V == 0x3FE ? ^ImpCellBrush : ^A_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .S_NumFont ); for( n = 1 ; n <= 9 ; n++ ) { r.Xo = R.Xo + .Pd * (( n - 1 ) % 3 ) / 3; r.Yo = R.Yo + .Pd * (( n - 1 ) / 3 ) / 3; .gc.SetTextColor( ( V & ( 1 << n )) ? #RGB_Av_NUM : #RGB_Ap_NUM ); .gc.DrawText( r, n'd ); } } else { .gc.SetBrush( ^A_CellBrush ); .gc.ClearRect( R ); } } } } .DrawCell( .CI, .CJ, #TRUE ); .IsCongratShown = #FALSE; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.DrawCell( I, J, IsHiup ) // 指定マスの背景と数字の表示 { if( I < 0 || I >= 9 || J < 0 || J >= 9 ) return; R.Xo = .Px(I,J); R.Yo = .Py(I,J); R.Xd = R.Yd = .Pd; r.Xd = r.Yd = .Pd / 3; if(( N = ^BM(I,J) ) > 0 ) { // 問題数字マス .gc.SetBrush( IsHiup ? ^CurCellBrush : ^B_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_B_NUM ); .gc.SetBackMode( 0 ); .gc.DrawText( R, N'd ); } else { // 解答数字マス if(( N = ^AM(I,J) ) > 0 ) { .gc.SetBrush( IsHiup ? ^CurCellBrush : ^A_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_A_NUM ); .gc.SetBackMode( 0 ); .gc.DrawText( R, N'd ); } else if( .IsHint ) { V = ^CM( I, J ); if( IsHiup ) .gc.SetBrush( ^CurCellBrush ); else .gc.SetBrush( V == 0x3FE ? ^ImpCellBrush : ^A_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .S_NumFont ); for( n = 1 ; n <= 9 ; n++ ) { r.Xo = R.Xo + .Pd * (( n - 1 ) % 3 ) / 3; r.Yo = R.Yo + .Pd * (( n - 1 ) / 3 ) / 3; .gc.SetTextColor( ( V & ( 1 << n )) ? #RGB_Av_NUM : #RGB_Ap_NUM ); .gc.DrawText( r, n'd ); } } else { .gc.SetBrush( IsHiup ? ^CurCellBrush : ^A_CellBrush ); .gc.ClearRect( R ); } } } //----------------------------------------------------------------------------- function ^NP_Window.IsZeroMatrix( M ) { for( I = 0 ; I < 9 ; I++ ) for( J = 0 ; J < 9 ; J++ ) if( M( I, J ) != 0 ) return #FALSE; return #TRUE; } function ^NP_Window.ClearMatrix( M ) { for( I = 0 ; I < 9 ; I++ ) for( J = 0 ; J < 9 ; J++ ) M( I, J ) = 0; } function ^NP_Window.CompareMatrix( A, B ) { for( I = 0 ; I < 9 ; I++ ) for( J = 0 ; J < 9 ; J++ ) if( A( I, J ) != B( I, J ) ) return -1; return 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.ConfirmMatrix() { .ClearMatrix( ^CM ); for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) { if(( N = ^BM( I, J )) == 0 && ( N = ^AM( I, J )) == 0 ) continue; V = 1 << N; for( K = 0 ; K < 9 ; K++ ) ^CM( I, K ) |= V; for( K = 0 ; K < 9 ; K++ ) ^CM( K, J ) |= V; Io = ( I / 3 ) * 3; Jo = ( J / 3 ) * 3; for( i = 0 ; i < 3 ; i++ ) { for( j = 0 ; j < 3 ; j++ ) { ^CM( Io + i, Jo + j ) |= V; } } } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.VerifyMatrix() { for( I = 0 ; I < 9 ; I++ ) for( J = 0 ; J < 9 ; J++ ) { v = ^BM( I, J ); if( v < 0 ) ^BM( I, J ) = v = 0; else if( v > 9 ) ^BM( I, J ) = 9; if( v != 0 ) ^AM( I, J ) = 0; else { v = ^AM( I, J ); if( v < 0 ) ^AM( I, J ) = 0; else if( v > 9 ) ^AM( I, J ) = 9; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.IsMatrixComplete() { for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) { if( ^BM( I, J ) == 0 && ^AM( I, J ) == 0 ) return #FALSE; } } return #TRUE; } //----------------------------------------------------------------------------- function ^NP_Window.StartPuzzleSolving() { .ClearMatrix( ^AM ); .ConfirmMatrix(); if( .IsHint ) { .IsHint = #FALSE; .Btn[0].SetTitle( "ヒント表示" ); } .CI = .CJ = -1; .DrawPuzzle(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.FinishPuzzleSolving() { .ClearAnsState(); .StopCongrat(); if( ! .IsZeroMatrix( ^AM ) ) { switch( .MessageBox( "入力されている内容が消えますが、\n" "よろしいですか?", "確認", #MB_YESNO | #MB_ICONQUESTION ) ) { default: case #IDYES: break; case #IDNO: return -1; } } return 0; } //----------------------------------------------------------------------------- function ^NP_Window.AlterHint() // ヒント表示/消去を切り換える { #pc "NP_Window.AlterHint: ": this'name, .CurButton, .IsHint; .IsHint = ! .IsHint; .CurButton.SetTitle( .IsHint ? "ヒント消去" : "ヒント表示" ); .ConfirmMatrix(); .DrawPuzzle(); } //----------------------------------------------------------------------------- function ^NP_Window.ResetPuzzleSolving() // 全入力数字をクリアする { if( .IsZeroMatrix( ^AM ) ) return; if( .MessageBox( "全入力数字をクリアします。\nよろしいですか?", "リセット", #MB_YESNO | #MB_ICONQUESTION ) != #IDYES ) return; //.ClearAnsState(); .StopCongrat(); .ClearMatrix( ^AM ); .ConfirmMatrix(); .DrawPuzzle(); } //----------------------------------------------------------------------------- function ^NP_Window.OnMouseButton( x, y, bs ) { #pc "OnMouseButton: ", x, y, bs'x; .SetFocus(); if( x < .PXo || x >= .PXo + .Pw || y < .PYo || y >= .PYo + .Pw ) return; for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) { if(( d = x - .Px(I,J) ) >= 0 && d < .Pd && ( d = y - .Py(I,J) ) >= 0 && d < .Pd ) goto INSIDE; } } return; INSIDE: #pc "Inside", I, J, ^BM(I,J); if( ^BM(I,J) != 0 ) // 問題数字のマス? return; if(( bs & (#MK_LBUTTON|#MK_RBUTTON) ) == 0 ) // マウスボタン押下なし? return; #pv IsActCell = ( .CI == I && .CJ == J ); if( ! IsActCell ) { // 操作対象のマスを移動 .DrawCell( .CI, .CJ, #FALSE ); .DrawCell( .CI = I, .CJ = J, #TRUE ); } if( bs & #MK_RBUTTON ) // 右ボタン押下? { // 操作対象のマスに数字入力のメニューをポップアップ表示 .ConfirmMatrix(); V = ^CM( I, J ); for( N = 1 ; N <= 9 ; N++ ) { if( V & ( 1 << N )) .ContMenu.DisableItem( N ); else .ContMenu.EnableItem( N ); } #pv .PopupMenu( .ContMenu, x, y ); } } //----------------------------------------------------------------------------- function ^NP_Window.OnMenu( id ) { #pc "OnMenu: " : id; .Update(); if( 1 <= id && id <= 10 ) { if( id == 10 ) id = 0; ^AM( .CI, .CJ ) = id; if( .IsHint ) { .ConfirmMatrix(); .DrawPuzzle(); .CheckPuzzleComplete(); } else { .CheckPuzzleComplete(); .DrawCell( .CI, .CJ, #TRUE ); } } } //----------------------------------------------------------------------------- function ^NP_Window.OnKeyDown( vk ) { #pc "OnKeyDown:", vk'x(4); I = .CI; J = .CJ; IsOut = ( I < 0 || I >= 9 || J < 0 || J >= 9 ); switch( vk & #VKF_MASK ) { case #VK_SPACE, #VK_DELETE, `0`: N = 0; goto SET_CELL; case `1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`: N = ( vk & #VKF_MASK ) - `0`; goto SET_CELL; case #VK_NUMPAD0, #VK_NUMPAD1, #VK_NUMPAD2, #VK_NUMPAD3, #VK_NUMPAD4, #VK_NUMPAD5, #VK_NUMPAD6, #VK_NUMPAD7, #VK_NUMPAD8, #VK_NUMPAD9: N = ( vk & #VKF_MASK ) - #VK_NUMPAD0; goto SET_CELL; case #VK_HOME: I = 4, J = 4; goto MOVE_HIUP; case #VK_LEFT: if( --J < 0 ) J = 8; break; case #VK_RIGHT: if( ++J > 8 ) J = 0; break; case #VK_UP: if( --I < 0 ) I = 8; break; case #VK_RETURN: J = 0; case #VK_DOWN: if( ++I > 8 ) I = 0; break; case #VK_BACK: if( IsOut ) return; goto DRAW_CELL; } if( J < 0 ) J = 0; else if( J > 8 ) J = 8; if( I < 0 ) I = 0; else if( I > 8 ) I = 8; MOVE_HIUP: .DrawCell( .CI, .CJ, #FALSE ); .CI = I; .CJ = J; DRAW_CELL: .DrawCell( I, J, #TRUE ); return; SET_CELL: if( IsOut ) return; .ConfirmMatrix(); V = ^CM( I, J ); if( ^BM( I, J ) != 0 || ^AM( I, J ) == N ) return; if( V & ( 1 << N )) { ::Beep( 1 ); return; } ^AM( I, J ) = N; if( .IsHint ) { .ConfirmMatrix(); .DrawPuzzle(); .CheckPuzzleComplete(); return; } .CheckPuzzleComplete(); .DrawCell( I, J, #TRUE ); return; } //----------------------------------------------------------------------------- function ^NP_Window.OnChar( c ) { #pc "OnChar:", c'c; if( c <= 0x20 || c >= 0x7F || ( `0` <= c && c <= `9` )) return; I = .CI; J = .CJ; if( I < 0 || I >= 9 || J < 0 || J >= 9 ) return; if( ^AM( I, J ) <= 0 ) return; .DrawCell( I, J, #TRUE ); } //----------------------------------------------------------------------------- function ^NP_Window.OnTimer( id ) { switch( id ) { case #CONGRAT_TIMER_ID: switch( ++.CongratCount % 2 ) { case 0: .HideCongrat(); break; case 1: .ShowCongrat(); break; } break; case #ANS_PZL_TIMER_ID: .CheckSolution(); break; case #NEW_PZL_TIMER_ID: .CheckNewPuzzle(); break; } } //----------------------------------------------------------------------------- function ^NP_Window.CheckPuzzleComplete() { if( .IsMatrixComplete() ) { if( .IsCongrat ) // 完成メッセージは既に表示中? return; .IsCongrat = #TRUE; .CongratCount = 0; .ShowCongrat(); .StartTimer( #CONGRAT_TIMER_ID, 720 ); } else { if( ! .IsCongrat ) // 完成メッセージは既に消去済? return; .StopTimer( #CONGRAT_TIMER_ID ); .HideCongrat(); //.CongratCount = 0; .IsCongrat = #FALSE; } } //----------------------------------------------------------------------------- function ^NP_Window.StopCongrat() { if( .IsCongrat ) { .StopTimer( #CONGRAT_TIMER_ID ); .HideCongrat(); .IsCongrat = #FALSE; } } function ^NP_Window.ShowCongrat() { if( ! .IsCongratShown ) { if( ! .CongraFont'exist? ) .CongraFont = ::GK.Font( "MS P明朝", .Pd * 5 / 3, #SHIFTJIS_CHARSET, #FIXED_PITCH, ::GK.Font.BOLD ); .gc.SetFont( .CongraFont ); .gc.SetBackMode( 0 ); .gc.SetTextColor( #RGB_CONGRAT ); R = ::GK.Rect( .PXo, .PYo, .Pw, .Pw ); .gc.DrawText( R, "完成!" ); .IsCongratShown = #TRUE; } } function ^NP_Window.HideCongrat() { if( .IsCongratShown ) { .DrawPuzzle(); //.IsCongratShown = #FALSE; } } //----------------------------------------------------------------------------- function ^NP_Window.ShowSolution() // 正解を表示する処理を開始 { .StopCongrat(); .ClearAnsState(); S'USHORT(9,10); for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) S(I,J) = ^BM(I,J) + `0`; S(I,9) = `\n`; } #pc S'gets; // sudoku.exe の実行 #pv .AnsPzlProc = ::Process( "sudoku.exe s10", 1, 0, 0, -1, "Shift-JIS" ); if( .AnsPzlProc == null ) { ::Alert( "sudoku.exe を起動できません!\n" ); return; } #pc "標準入力バイト数 = " : .AnsPzlProc.PutStdin( S'gets ); .Stdout = ""; .StartTimer( #ANS_PZL_TIMER_ID, 100 ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.CheckSolution() // 正解解答の完了確認 { switch( so = .AnsPzlProc.GetStdout() ) { case 0: // 空 #pc " 空 "; return; default: // 正常に読み出し #pc " 正常読出 "; .Stdout += so; break; case null: // 無効 or エラー #pc " 無効 or エラー "; break; } ec = .AnsPzlProc.GetExitCode(); if( ec == 0x103 ) // プロセス実行中(終了していない)? return; #pc " 終了コード = " : ec; .StopTimer( #ANS_PZL_TIMER_ID ); delete .AnsPzlProc; // 標準出力の文字列をバッファに書き出す #pc "標準出力: \n" : .Stdout; T = ::Buffer(); T.Write( .Stdout ); T.Seek(0); // バッファ内の標準出力の各要素を正規表現との照合で抽出していく nAnswers = 0; IsAns = #FALSE; S'USHORT(9,9); Regex = // 標準出力解析用正規表現 $'#s#`' // メタ文字変更 \ → ` $' ^.*問題.*`n #1 |' // 問題の開始行 $' ^解答`(@(`d+)`).*`n #2 |' // 解答の開始行 @1 $' ^@(`d{9}).*`n #3 |' // 数列 @2 $' ^.*`n #4 '; // その他 for( cn = T.Find( Regex ) ; cn >= 0 ; cn = T.FindNext() ) { switch( T.FoundID() ) { case 1: IsAns = #FALSE; break; case 2: IsAns = #TRUE; K = 0; #pv AnsCn = T.FoundText(1)'int; break; case 3: if( ! IsAns ) break; S'puts( T.FoundText(2), K, 0 ); //#pc K, S'gets( 9, K, 0 ); if( ++K == 9 ) { Ans[ nAnswers ]'BYTE(9,9); A := Ans[ nAnswers ]; for( I = 0 ; I < 9 ; I++ ) { #pc I, S'gets( 9, I, 0 ); for( J = 0 ; J < 9 ; J++ ) A(I,J) = ( ^BM(I,J) == 0 ) ? S(I,J) - `0` : 0 ; } ++nAnswers; } break; //case 4: #pc "* ": T.FoundText(); // break; } } if( nAnswers <= 0 ) { .MessageBox( "解はありません!", "解答表示", #MB_OK | #MB_ICONINFORMATION ); return; } ^AnsWindow.nAnswers = nAnswers; ^AnsWindow.AnsIndex = 0; ^AnsWindow.Ans <- Ans; ^AnsWindow.BM = ^BM; ^AnsWindow.Construct( "解答表示", , , 370, nAnswers > 1 ? 420 : 380 ); ^AnsWindow.Open(); if( nAnswers >= 10 ) ^AnsWindow.MessageBox( "解が10個以上あります!", "解答表示", #MB_OK | #MB_ICONINFORMATION ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.ClearAnsState() { if( ^AnsWindow.hWnd != null ) ^AnsWindow.Close(); } //----------------------------------------------------------------------------- function ^NP_Window.MakeNewPuzzle() // 新規問題を生成する処理を開始 { if( .FinishPuzzleSolving() == -1 ) return; // sudoku.exe の実行 #pv .NewPzlProc = ::Process( "sudoku.exe m12", 1, , 0, -1, "Shift-JIS" ); if( .NewPzlProc == null ) { ::Alert( "sudoku.exe を起動できません!\n" ); return; } .Stdout = ""; .StartTimer( #NEW_PZL_TIMER_ID, 100 ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function ^NP_Window.CheckNewPuzzle() // 問題生成の完了確認 { // sudoku.exe の終了待ち switch( so = .NewPzlProc.GetStdout() ) { case 0: // 空 #pc " 空 "; return; default: // 正常に読み出し #pc " 正常読出 "; .Stdout += so; break; case null: // 無効 or エラー #pc " 無効 or エラー "; break; } ec = .NewPzlProc.GetExitCode(); if( ec == 0x103 ) // プロセス実行中(終了していない)? return; #pc " 終了コード = " : ec; .StopTimer( #NEW_PZL_TIMER_ID ); delete .NewPzlProc; // 標準出力の文字列をバッファに書き出す #pc "標準出力: \n" : .Stdout; T = ::Buffer(); T.Write( .Stdout ); T.Seek(0); // バッファ内の標準出力の各要素を正規表現との照合で抽出していく Regex = // 標準出力解析用正規表現 $'#s#`' // メタ文字変更 \ → ` $' ^問題:`n #1 |' // 問題の開始行 $' ^@(`d{9}).*`n #2 |' // 数列 @1 $' ^.*`n #3 '; // その他 S'USHORT(9,9); K = -1; for( cn = T.Find( Regex ) ; cn >= 0 ; cn = T.FindNext() ) { switch( T.FoundID() ) { case 1: K = 0; break; case 2: if( K < 0 ) break; S'puts( T.FoundText(1), K, 0 ); #pc K, S'gets( 9, K, 0 ); if( ++K == 9 ) { for( I = 0 ; I < 9 ; I++ ) { #pc I, S'gets( 9, I, 0 ); for( J = 0 ; J < 9 ; J++ ) ^BM(I,J) = S(I,J) - `0`; } } break; case 3: #pc "* ": T.FoundText(); break; } } .StartPuzzleSolving(); } //============================================================================= /// 解答表示ウィンドウ /// function ^AnsWindow.Construct( Title, Xo, Yo, Xd, Yd ) { .Px'SHORT(9,9); .Py'SHORT(9,9); .[ ::GK.Window.Construct ]( Title, Xo, Yo, Xd, Yd ); if( .nAnswers > 1 ) { .DefWidgetFont := ^DefWidgetFont; R = ::GK.Rect( 0, 0, 0, 0 ); .PrevAnsBtn = .AddButton( "push", "前解", R ); .PrevAnsBtn.OnCommand = function() { .owner.ShowPrevAnswer(); }; .NextAnsBtn = .AddButton( "push", "次解", R ); .NextAnsBtn.OnCommand = function() { .owner.ShowNextAnswer(); }; } } function ^AnsWindow.Destruct() { .[ ::GK.Window.Destruct ](); } //----------------------------------------------------------------------------- function ^AnsWindow.OnCreate() { #pc "Ans-OnCreate"; for( i = 0 ; i < 4 ; i++ ) ^NP_Window.Btn[i].Disable(); } function ^AnsWindow.OnClose() { #pc "Ans-OnClose"; for( i = 0 ; i < 4 ; i++ ) ^NP_Window.Btn[i].Enable(); } //----------------------------------------------------------------------------- function ^AnsWindow.OnSize() { #pc "Ans-OnSize"; Yd = 18 + 3*4 + 1*6 + 10; if( .nAnswers > 1 ) Yd += 10 + 18 + 10 + 24; .Pd = ( .gc.Yd - Yd ) / 9; // 1マスの内側のサイズ if( .Pd < 18 ) .Pd = 18; .Pw = 3*4 + 1*6 + .Pd * 9; // 全マスの外側のサイズ .PXo = ( .gc.Xd - .Pw ) / 2; // 全マスの左端位置 if( .PXo < 20 ) .PXo = 20; .PYo = 18; // 全マスの上端位置 Y = .PYo; for( I = 0 ; I < 9 ; I++ ) { Y += ( I % 3 == 0 ) ? 3 : 1; X = .PXo; for( J = 0 ; J < 9 ; J++ ) { X += ( J % 3 == 0 ) ? 3 : 1; .Px( I, J ) = X; .Py( I, J ) = Y; X += .Pd; } Y += .Pd; } if( .NumFont'exist? ) delete .NumFont; if( .nAnswers <= 1 ) return; X = .PXo + .Pw / 2; Y = .PYo + .Pw + 10; .AnsCntRect = ::GK.Rect( X - 60, Y, 120, 18 ); Y += 18 + 12; R = ::GK.Rect( X - 100, Y, 80, 24 ); .PrevAnsBtn.Move( R ); R.Xo = X + 20; .NextAnsBtn.Move( R ); } //----------------------------------------------------------------------------- function ^AnsWindow.OnPaint() { #pc "Ans-OnPaint"; R = ::GK.Rect( 0, 0, .gc.Xd, .gc.Yd ); .gc.DrawEdge( R, #EDGE_SUNKEN, #BF_RECT|#BF_MIDDLE ); // 最外枠線の描画 //R.Xo = .PXo - 3; //R.Yo = .PYo - 3; //R.Xd = R.Yd = .Pw + 5; //.gc.DrawEdge( R, #EDGE_BUMP, #BF_RECT ); // 9x9マスの枠線の描画 .gc.SetPen( ^FramePen ); X1 = .PXo; X2 = .PXo + .Pw; Y = .PYo; for( i = 0 ; i <= 9 ; i++ ) { if( i % 3 == 0 ) { .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); ++Y; .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); ++Y; } .gc.MoveTo( X1, Y ); .gc.LineTo( X2, Y ); Y += .Pd + 1; } Y1 = .PYo; Y2 = .PYo + .Pw; X = .PXo; for( i = 0 ; i <= 9 ; i++ ) { if( i % 3 == 0 ) { .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); ++X; .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); ++X; } .gc.MoveTo( X, Y1 ); .gc.LineTo( X, Y2 ); X += .Pd + 1; } .DrawAnswer(); } function ^AnsWindow.DrawAnswer() { M := .Ans[ .AnsIndex ]; // 9x9マス内の背景と数字の描画 if( ! .NumFont'exist? ) .NumFont = ::GK.Font( "MS Pゴシック", .Pd - 5, #SHIFTJIS_CHARSET, #FIXED_PITCH, ::GK.Font.BOLD ); .gc.SetBackMode( 0 ); R.Xd = R.Yd = .Pd; for( I = 0 ; I < 9 ; I++ ) { for( J = 0 ; J < 9 ; J++ ) { R.Xo = .Px(I,J); R.Yo = .Py(I,J); if(( N = .BM(I,J) ) > 0 ) { // 問題数字マス .gc.SetBrush( ^B_CellBrush ); .gc.ClearRect( R ); .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_B_NUM ); .gc.DrawText( R, N'd ); } else { // 解答数字マス .gc.SetBrush( ^AnsCellBrush ); .gc.ClearRect( R ); if(( N = M(I,J) ) > 0 ) { .gc.SetFont( .NumFont ); .gc.SetTextColor( #RGB_AnsNUM ); .gc.DrawText( R, N'd ); } } } } // 解答数の描画 if( .nAnswers <= 1 ) return; .gc.DrawEdge( .AnsCntRect, 0, #BF_MIDDLE ); .gc.SetFont( ^AnsCntFont ); .gc.SetBackMode( 0 ); .gc.SetTextColor( #RGB_ANS_LABEL ); .gc.DrawText( .AnsCntRect, "解答  " + ( .AnsIndex + 1 )'d + " / " + .nAnswers'd ); } function ^AnsWindow.ShowPrevAnswer() { if( --.AnsIndex < 0 ) .AnsIndex = .nAnswers - 1; .DrawAnswer(); } function ^AnsWindow.ShowNextAnswer() { if( ++.AnsIndex >= .nAnswers ) .AnsIndex = 0; .DrawAnswer(); } //-----------------------------------------------------------------------------