第4回 プログラムは奥深く「4.プログラムの性格」

4−4 プログラムの性格

では「人間臭く」を具体的に考えるには、どうしたら良いのでしょうか?
それは、簡単。人間であるあなたが考えれば良いのです。

例えば、あなたは連続してジャンケンをする時に、どうやって出す手を考えているのでしょうか?
ジャンケンの手の出し方は、自分の癖や、相手の手や性格から推定して出すこともあります、また、本当に無意識のこともあるでしょうし、宇宙から電波が来ちゃうこともあるかもしれません。したがって、これらの方法のどれかをプログラミングすれば、人間臭いプログラムになるという事です。
人間臭いプログラム、いわば、プログラムの性格とでも言うのかもしれません。

そこで、今回は次のような性格を考えてみました。

  「前回、相手が出した手以外の手を出す」

これは比較的理解しやすいですが、強いのか弱いのか謎です。
例えば、プレイヤーがグーを出したら、次のジャンケンでPalmはグー以外のチョキかパーを出しますので、比較的プレイヤーの動作に連動して出す手が変化します。
まぁ、確率は同じハズなので、あまり強くなることは期待できませんが、上手くツボにはまると、連敗するようです。そして、これもジャンケンの手の出し方の1つであることは確かです。

では、早速プログラムに移りましょう。

前の手、ということは、変数mode_myの「前回」の値が必要になります。
しかし、Palm側の手を決めるルーチンが処理されている時には、既にタップされ、mode_myは今回出す手の値になっているので、この変数mode_myの値を参照してもしかたがありません。
そう、そんなときも、グローバルな変数を1つ用意します。どこでも参照できるグローバル変数はもう定番ですが、このように、前の値を保存する場合にも活用できます。今回は、mode_Prevという変数を用意しておきましょう。
まず、Startupでmode_Prevを定義します。

    Global mode_Prev as Integer

次に、同じ手が出ないような処理を作ります。
プログラム中で、条件によって処理を分ける方法にIf〜ThenやSelect Caseなど「条件分岐」を覚えましたが、ここではもう1つの重要な考え方である「繰り返し」を使ってみましょう。
繰り返しを処理するコマンドに、Do〜Loopというコマンドがあります。
Do〜Loopコマンドは、次のような書式で、DoとLoopの間の処理を繰り返し実行する働きをします。

    Do
      処理
    Loop

繰り返しの事を「ループ」といいますが、このまま置いておけば、Palmデバイスの電池が切れるか、リセットするまで延々「処理」を処理し続けてしまう「無限ループ」になってしまいます。
そうならないために、何らかの方法でループから抜け出さなければなりません。
ループを強制的に抜け出すには、Exit Doコマンドを使います。 Doループは、このExit Doコマンドに合うと、ループから抜け出してLoopの次の行へ処理が移ります。
例えば、
  Do
    処理1
    If 条件 Then
      Exit Do
      End If
    処理2
  Loop
  処理3
のような場合、ループ内のIf文の条件が成立すれば、Exit Doに処理が移り、ループ内の処理2を実行することなく処理3へ移ります。
したがって、Exit Doを使ってループから抜ける時は、条件式の位置によって、処理されない部分などが出てきますので、その位置が重要になります。

もう1つの方法は、ループ全体に条件をつける方法です。
    Do While 条件
        処理
    Loop
又は、
    Do Until 条件
        処理
    Loop
のいずれかの書式で、Whileは条件が「成り立っている間」又はUntilは条件が「成り立っていない間」ループが繰り返されます。
ややこしいですが、WhileとUntilはお互いに逆の関係ですので、どちらかを覚えておけば問題ありません。

条件の書き方は、Ifと同じ要領で、例えば、
    i=0
    Do While i<5
        i=i+1
        処理1
    Loop
とすれば、iが5未満の間は処理1が繰り返して処理され、5になったらループから抜けます。
Untilを使うなら i>=5 ですね。

さて、ジャンケンに戻りましょう。
Do〜Loopを使って、
    Do While mode_Palm=mode_Prev
        mode_Palm = rand()*3+1
    Loop
とすれば、mode_Palmとmode_Prevが同じである間、つまりPalmが今回出そうとしている手と、プレイヤーが前回出した手が同じである間はループが終わりません。したがって、ループを抜けた時は、必ず違う値がセットされているはずですね。

ただ、一般的なBASICでは、条件をDoの後でなく、Loopの後にも置くことも可能で、この場合、最低1回のループを処理してから条件をチェックしますが、NS Basicではできませんのでちょっと注意が必要です。
前に条件があるループの場合、ループに入る前に、必ず条件がチェックされてしまいます。したがって、
        Dim mode_Palm as Integer
        
        Do While mode_Palm=mode_Prev
            mode_Palm = rand()*3+1
        Loop
とした場合、最初にDimで宣言していますので、ループの前の時点で変数mode_Palmには初期値の 0が入っています。
一方、前の手である変数mode_Prevは少なくとも、1,2,3のいずれかですから、この条件は必ず成立してしまい、一度もループに入らずに進んでしまいます。
そこで、確実な初期値として変数mode_Palmに変数mode_Prevの値を入れておけば、両者は等しく、条件が不成立になるので、必ずループに入ります。
        Dim mode_Palm as Integer
        
        mode_Palm=mode_Prev
        
        Do While mode_Palm=mode_Prev
            mode_Palm = rand()*3+1
        Loop
条件がLoopの後に置ければ問題はないのですが、ま、NS Basicの場合、ちょっとした工夫で乗り越えましょう。

こうしてできたルーチンは、先ほども紹介しました通り「ツボ」にはまると連敗するようですので、前に作った「必勝法」ルーチンもある確率で出現するようにしておきます。また、完全に乱数だけのルーチンも出現するようにしておけば、Palm側の手に3つのロジックが組み込まれたことになります。
        Dim mode_Palm as Integer
        
        mode_Palm=rand()*3+1

        If Rand()>0.45 Then        
            Do While mode_Palm=mode_Prev
                mode_Palm = rand()*3+1
            Loop
        End if

        If Rand()>0.75 Then
            mode_Palm=Val(Mid("312",mode_my,1))        
        End if
こんなロジックでどうでしょう?
まず、最初にmode_Palmの初期値を乱数で決めてみました。先ほどのループに入らない問題もクリアできますね。
次に、0.45より大きな場合、おおよそ55%の確率で「相手の前の手を出さない」ルーチンを処理しますので、Palm側の手が必勝の手に入れ替わる場合もあります。そして、最後に、約25%の確率で、最強(インチキ)ルーチンが入っており、1つのジャンケンロジックとしました。

また、忘れてはならないのが、mode_Prevの値を代入する部分です。これで、この回にプレイヤーが出した手を記憶しています。
こうして、3つのロジックを含んだジャンケンルーチンが完成しました。
    Sub Form1004_after()

        If mode_my<>0 Then
    
            Dim mode_Palm as Integer
            Dim f as Integer
        
            mode_Palm=rand()*3+1

            If Rand()>0.45 Then        
                Do While mode_Palm=mode_Prev
                    mode_Palm = rand()*3+1
                Loop
            End If

            If Rand()>0.75 Then
                mode_Palm=Val(Mid("312",mode_my,1))        
            End If
        
            count_Game = count_Game + 1
                  
            f = Val(Mid("915591159",mode_my*3+mode_Palm-3,1))
    
            If f=1 Then
                count_Win = count_Win + 1
            End If

            DrawBitmap 1010+mode_Palm,8,20
            DrawBitmap 1007+mode_my,88,55
            Lbl1018.Label=Mid("Win!LoseEven",f,4)
            Lbl1017.Label=Format(count_Win/count_Game * 100,"##0.0%")
        
            mode_Prev=mode_my
        End If

    End Sub
一応、テストで試してみましたら、100回ジャンケンして、勝率15.8%でした。私が弱いのか、ロジックが強いのかわかりませんが、同じ手を出しつづけたら、若干勝率が上がりましたので、何らかの攻略法が存在しそうですね。

今回は、ジャンケンのロジックを幾つか考えてみましたが、基本的に遅出しジャンケンでいかにゲーム性を高くするか、という点がポイントでした。要するに「プログラムはアイディア勝負」ということです。
今は、3つのルーチンしか含んでいませんが、このジャンケンゲームを色々な人がプログラミングすると、十人十色、様々な性格のジャンケンルーチンが出来あがるのではないでしょうか。


前へ     目次へ     次へ

第4回 プログラムは奥深く「4.プログラムの性格」