三目並べで負けない


14

(最良の戦略を使用する必要があるいくつかの課題が存在しますが、ここではそうではありません。たとえあなたが勝つことができたとしても、あなたはネクタイを作ることができます)

チャレンジ

ゲーム三目並べを再生するプログラムを作成します。負けてはいけません(したがって、引き分けか勝利によってゲームを終了する必要があります)。

許可されるI / Oメソッド

  1. 入力は現在のボードである可能性があります。2番目のプレーヤーの以前のすべての動きがエンジンでプレイされたと仮定できます。
  2. 入力は最初のプレーヤーの動きであり、関数は過去にどの動きが起こったかを保存します。この場合、関数は複数回呼び出されます(1回の移動ごとに1回)。または、複数回の機能/プログラムプロンプト入力。
  3. あなたが最初のプレイヤーであるかどうかを追加で入力するか、最初のプレイヤーの問題と2番目のプレイヤーの問題を解決するための2つの(おそらく関連する)関数を書くことができます。プログラムでインプットメソッド2(複数呼び出し)を使用する必要がある場合、最初の呼び出しで何を渡すかを決定できます。
  4. 出力はあなたの番の後のボードかもしれません。
  5. 出力はあなたの動きかもしれません。
  6. 移動は、数値のペア(0インデックスまたは1インデックス)、0〜8の範囲の数値、または1〜9の範囲の数値として表すことができます。
  7. ボードは、3×3配列、または長さ9の配列として表される場合があります。言語に0インデックス配列がある場合でも、1インデックスを使用できます。
  8. グリッド上のセルは、3つの異なる値を使用してXOおよび空を示します。

受賞基準

各言語で最も短いコードが優先されます。


負けがあなたに与えられた場合、あなたの解決策は無効です。チェス盤は即座に変更されませんので、あなたはそう、他で遊んでいるwe can assume that all previous moves of the 2nd player were also played by our engine
l4m2


1
@ l4m2インタプリタを再起動するだけです。できた なぜわざわざ?無駄にバイトカウントを増やすだけです。
user202729


4
ボーナスをしないでください。必要か削除するかのどちらかです。オプションにしないでください。ボーナスはチャレンジを台無しにします。
Rɪᴋᴇʀ17年

回答:


4

Befunge、181 168バイト

>>4&5pp20555>>03>16p\::5g8%6p5v
 ^p5.:g605$_ #!<^_|#:-1g61+%8g<
543217539511|:_^#->#g0<>8+00p3+5%09638527419876
v<304p$_v#:->#$$:2`#3_:^
>#\3#13#<111v124236478689189378

ボード上の位置には1〜9の番号が付けられています。デフォルトでは最初の動きが得られますが、コンピューターを最初に動かしたい場合は、最初の動きに0を入力するだけです。移動すると、コンピューターはその移動を示す数字で応答します。

有効なムーブを入力しないことを確認するチェックはありません。また、勝ち負けを確認するチェックもありません。それらが実行される必要がなくなると、プログラムは無限ループに入ります。

これをオンラインでテストするのは少し難しいです。なぜなら、インタラクティブな入力を行うオンライン通訳がいないからです。ただし、どの動きを事前に行うかがわかっている場合(コンピューターがどのように応答するかを知っていることを前提としています)、これらの動きを事前にプログラムしてTIOでテストできます。

ユーザーが最初にプレイする: オンラインで試してみてください!
コンピューターが最初にプレイする: オンラインで試してみてください!

何が起こっているのかを見やすくするために、移動の間にボードを出力するバージョンもあります。

ユーザーが最初にプレイする: オンラインで試してみてください!
コンピューターが最初にプレイする: オンラインで試してみてください!

結果を表示するには、TIOがタイムアウトするのを待つ必要があることに注意してください。

説明

ボードは、1〜9のインデックスが付けられた9つの値のフラット配列としてBefungeメモリ領域に格納されます。これにより、コンピューターを最初にプレイさせたいときに、特別なケース「移動なし」としてゼロオフセットを使用できます。プレーヤーの動きは4として、コンピューターの動きは5として保存されます。すべての位置から開始するには32(Befungeメモリのデフォルト)に初期化されるため、ボードにアクセスするたびに8でmodするため、0、4のいずれかが返されますまたは5。

その配置を考えると、ボード上の任意の3つのポジションの値を合計すると、合計が10の場合はコンピューターが勝ちから1歩離れ、合計が8の場合はプレーヤーが勝ちから1歩離れていることがわかります。合計が9の場合、位置はコンピューターとプレーヤーの間で共有されます(ただし、1つの位置は無料です)。

私たちの戦略全体は、このコンセプトに基づいています。ボード上の3つのポジションのセットを示すトリプルのリストを取得するルーチンがあり、それらのポジションの合計を計算します。合計が特定の合計に等しい場合、コンピューターはセットのいずれかのポジションに移動します。

テストするトリプルの主なリストは、勝ちの組み合わせ(1/2 / 3、1 / 5 / 9、1 / 4/7など)です。最初に合計10(コンピューターが勝ちます)を探し、次に合計8(プレーヤーが勝ち、その動きをブロックする必要があります)を探します。それほど明らかではありませんが、合計9もチェックします(プレーヤーとコンピューターがそれぞれ1つのポジションを持っている場合、コンピューターが3番目のポジションを取るのは良い戦略です)。

その最後のシナリオの前に、私たちが行う他の戦略的な動きは、すべてのコーナーセット(1/2 / 4、2 / 3/6など)と2つの対向するコーナーの組み合わせ(1/8/9および3 / 7/8)。これらの組み合わせのいずれかが合計で8になった場合、つまりプレーヤーが2つのポジションを取った場合、コンピューターが残りのフリーポジションを取るのは良い戦略です。

最後に、2つの特別なケースの動きがあります。最初に、私たちは常に他の動きの前に中心位置をとろうとします。これは、他のすべての動きと同じルーチンで達成され、単一のトリプル、5/5/5、および目標合計0を渡すだけです。さらに、他のすべてのテストが動きを見つけることができなかった場合、最後の手段としての頂点の一つ。繰り返しますが、これは単純に1/1/1と3/3/3のトリプルをテストすることで達成され、ターゲットの合計は0です。

これは必ずしも完璧な戦略だとは思いません-コンピューターが描く可能性のあるゲームが勝つ可能性があります-しかし、それは試合に負けないほど十分です。コンピューターに対してあらゆる可能な動きをプレイしようとするテストスクリプトを実行しました。有効な動きのシーケンスごとに、コンピューターがゲームに勝ったか引いたのです。


Befungeはよくわかりませんが、可能性のあるすべての入力(サンプル)をテストすることができます
l4m2

@ l4m2参考までに、私はコンピューターに対してあらゆる可能な動きを試み、それが失われないことを確認できるテストスクリプトを実行しました。
ジェームズホルダーネス

2

Python 2:399 401 349 333 317 370バイト

2xバグ修正:l4m2のクレジット

-52文字:地下モノレールのクレジット

-16文字:Jonathan Frechの功績

-26文字:user202729のクレジット

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
 for i in 5,4,2,8,6:
    if i in a:return I(i)
 return I(a[0])

オンラインでお試しください!

前学期の線形代数コースの初日に、私の鋭敏な大学院生のインストラクターは、三目並べボードをマトリックスとして表すと提案しました。

4 | 9 | 2
--+---+--
3 | 5 | 7
--+---+--
8 | 1 | 6

次に、3を連続して取得することは、範囲[1,9]で合計15までの3つの数値を選択することと同じです。この答えはこの考えを利用しています。この関数は、ボードを表す9つの数字を含むリストを取ります。0は空きスペースを示し、1は相手によって占有され、2はプログラムによって行われた以前のプレイを表します。最初の3行は、プログラムが選択した番号(p)、反対側が選択した番号(o)、およびまだ利用可能な番号(a)を示しています。次に、使用可能な番号を調べて、すでに選択した2つの番号と組み合わせて、それらのいずれかが15に追加されるかどうかを確認します。もしそうなら、その広場を選んで勝ちます。即座に勝つ動きがない場合、同じ方法を使用して相手が勝つことができるかどうかを確認します。可能であれば、彼らの勝利の広場が必要になります。勝つ動きもブロックする動きもない場合、隅に移動します。これは愚かな仲間を防ぎます:

- - - 
- X -
- - -

- O -             # Bad Move
- X -
- - -

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

これらの状況のいずれも発生しない場合、任意に正方形を選択します。この関数は、アルゴリズムによって選択された0のインデックス付き正方形を表す数値[0,8]を出力します。

編集:アルゴリズムは、対角線よりも中心を優先するようになりました。これにより、l4m2および関連する戦略によって指摘された別の愚か者の交配の可能性が防止されます。

編集:明確にするために、関数は配列の形でボードを取り、[0,8]の整数としてムーブを出力します。このI / O戦略は非常に不格好なので、ここではよりインタラクティブにするラッパースクリプトを示します。単一のコマンドライン引数を取ります。プレーヤーが最初に移動する場合は1、プログラムが最初に移動する場合は0です。

import sys

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
     for i in 5,4,2,8,6:
        if i in a:return I(i)
 return I(a[0])

board = [0,0,0,0,0,0,0,0,0]
rep = {0:"-",1:"X",2:"O"}

turn = int(sys.argv[1])
while True:
    for i in range(3):
        print rep[board[i*3]]+" "+rep[board[i*3+1]]+" "+rep[board[i*3+2]]
        print
    if turn:
        move = int(raw_input("Enter Move [0-8]: "))
    else:
        move = f(board)
    board[move] = turn+1
    turn = (turn+1)%2 


1
return最後の行を除くすべての行をその前の行に配置して、空白を節約することができます
地下

1
また、私は何もせずに、バイトを保存してe=enumerate、do f=lambda n:[t[i]for i,j in enumerate(b)if j==n]、assign poおよびa関数を使用する代わりに、バイトを節約するかどうか疑問に思います。それを数えていない
地下

3
まだハッキングされています。xkcd.com/832は本当に役立ちます
l4m2

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.