2006 年 6 月 4 日 23 時 26 分

PuzzleLine #4: イベントの受け取り


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


これで 2 つのアルゴリズムが準備できた。

PuzzleLine を改造し、状況によって
アルゴリズムを切り替えて自動的に解答するようにする。

行の解答状況を記憶するために、
以前準備しておいた、m_lProgress フィールドを使用しよう。
この値によって、進捗度を調べることにする。

では、Solve メソッドを書き換えてみる。

Public Function Solve() As Long
   
    Dim eState() As CellStateConstants
    Dim oSolver As IPuzzleLineSolver
    Dim lProgress As Long
   
    lProgress = m_lProgress

    ' まず、行の解答状況をバッファにコピーする
    ReDim eState(0 To m_lCellCount - 1)
    Call CopyStatesOfLine(eState)

    ' 進捗状況を基にアルゴリズムを選択して解く
    ' 解けなかった場合、上位のアルゴリズムを使う
 
    If lProgress = 0 Then
        Set oSolver = New OverlapDetector
        Solve = oSolver.Execute(m_oHints, eState)
        If Solve = 0 Then lProgress = 1
    End If
   
    If lProgress = 1 Then
        Set oSolver = New RaaSolver
        Solve = oSolver.Execute(m_oHints, eState)
        If Solve = 0 Then lProgress = 2
    End If
   
    ' 解答できた場合は反映する
    If Solve > 0 Then
        Call ApplyStatesOfLine(eState)
    End If
   
    m_lProgress = lProgress

End Function

進捗度 m_lProgress が 0 ならば、OverlapDetector を使う。
最も高速な処理だからだ。OverlapDetector でだめなら、
m_lProgress を 1 にし、RaaSolver を使う。
RaaSolver もだめなら、m_lProgress を 2 にする。

現在は、2 つのアルゴリズムしかないので、
RaaSolver で解けなければそれ以上の進捗はないが、
発想しだいでは、さらにいくつかのアルゴリズムを
使い分けるように変更できるはずだ。

さて、 PuzzleLine には、もうひとつ考えることがある。

解答欄は縦と横のヒントが必ず交差するため、
どこかの行によってセルの解答が得られれば、
必ずどこかの列の解答にも影響されるはずだ。

例えば、ある列の m_lProgress が 2 となり、
どのアルゴリズムでも解答が得られない状態であるとする。
そこ状態で、他の行によってセルの状態が変更された場合、
この列を再評価すれば、答えが得られるかもしれない。

そのため、行や列は、外部からのセルの変更に対応して、
進捗度をリセットする必要がある。

そのためにはどうすればいいか。

例えば、行が変更した場合に、対応する列に対して、
通知メソッドを呼ぶという方法がある。
しかし、これはあまりスマートではない。
確定と通知を同時に PuzzleLine に実装しなければならない。

よく考えると、確定と通知が常にセットであるということは、
通知に関してもセル管理クラスの方で行えば効率がいい。
そう、PuzzleAnswer の Changed イベントを使えばいいのだ。

PuzzleLine で PuzzleAnswer のイベントを受け取るには、
WithEvents キーワードとともにフィールドを定義する。


Private WithEvents m_oAnswer As PuzzleAnswer


こうすると、オブジェクトボックスに m_oAnswer が出る。
それを選択すると、自動的に Changed イベントが
コードに挿入されるはずだ。

PuzzleLine は、外部によって変更されたセルを調べ、
自分の行や列に該当した場合、進捗度をリセットする。


Private Sub m_oAnswer_Changed(ByVal x As Long, ByVal y As Long, ByVal NewState As CellStateConstants)

    ' 自分の行列でなければ抜ける
    If m_fColumn Then
        If x <> m_lLineIndex Then Exit Sub
    Else
        If y <> m_lLineIndex Then Exit Sub
    End If

    ' 進捗をリセット
    m_lProgress = 0
End Sub

進捗の通知は、自分自身でセルを変更した場合も通知される。
そのため、Solve メソッドでは、
m_lProgress を lProgress ローカル変数にコピーし、
ApplyStatesOfLine を呼び出した後に、
改めて m_lProgress に代入している。
それにより、自分自身での変更の場合は、
m_lProgress がリセットされずに済むのだ。

イベントを使う場合、このような注意も必要だ。

さて、コードが正しいか実行しようとすると、
なぜか「メソッドまたはデータメンバーが見つかりません」
のコンパイルエラーが出てしまう。

どうやら、コードに裏技的にデフォルト属性をつけたので、
WithEvents と組み合わせた場合、
Excel が対応していないようだ。

仕方ないので ApplyStatesOfLine の以下の行を書き換える。

修正前: m_oAnswer(x, y) = State(y)
修正後: m_oAnswer.Item(x, y) = State(y)

プロパティ名を明示した場合は問題なさそうだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.