Partially-Observable Connect-4


8

ゲーム

Connect-4の(ほぼ)標準的なゲームをプレイします。残念ながら、それは通信ゲームであり、誰かが下から順に2行ごとに黒いテープを貼ったため、これらの行内での対戦相手の動きを見ることができません。

すでに満杯の列内での移動は、あなたのターンを通過したものとしてカウントされ、ゲームが6 * 7ターンよりも長く続く場合、それはドローとして裁定されます。

チャレンジ仕様

プログラムはPython 3関数として実装する必要があります。最初の引数はボードの「ビュー」であり、既知のボードの状態を下から上への2D行リストとして表します。1最初のプレーヤー2による移動、2番目のプレーヤーによる移動0、空の位置または非表示です。対戦相手に移動します。

2番目の引数は、からインデックスが付けられたターン番号0であり、そのパリティにより、どのプレイヤーかがわかります。

最後の引数は任意の状態であり、None各ゲームの開始時に初期化され、ターン間で状態を保持するために使用できます。

再生したいカラムインデックスの2タプルを返す必要があり、新しい状態は次のターンで返されます。

得点

勝利は+1、引き分けは0、損失はとしてカウントされ-1ます。あなたの目標は、ラウンドロビントーナメントで最高の平均スコアを達成することです。私は明確な勝者を特定するために必要なだけの試合を実行しようとします。

ルール

競合他社は一度に最大1つの競合するボットを持つ必要がありますが、改善を加えた場合はエントリを更新しても問題ありません。ボットを1ターンあたりの思考時間を最大1秒に制限してみてください。

テスト中

以下は、コントローラーのソースコードと、参照用のいくつかの競合しないボットの例です。

import itertools
import random

def get_strides(board, i, j):
    yield ((i, k) for k in range(j + 1, 7))
    yield ((i, k) for k in range(j - 1, -1, -1))
    yield ((k, j) for k in range(i + 1, 6))
    yield ((k, j) for k in range(i - 1, -1, -1))
    directions = [(1, 1), (-1, -1), (1, -1), (-1, 1)]
    def diag(di, dj):
        i1 = i
        j1 = j
        while True:
            i1 += di
            if i1 < 0 or i1 >= 6:
                break
            j1 += dj
            if j1 < 0 or j1 >= 7:
                break
            yield (i1, j1)
    for d in directions:
        yield diag(*d)

DRAWN = 0
LOST = 1
WON = 2
UNDECIDED = 3

def get_outcome(board, i, j):
    if all(board[-1]):
        return DRAWN
    player = board[i][j]
    strides = get_strides(board, i, j)
    for _ in range(4):
        s0 = next(strides)
        s1 = next(strides)
        n = 1
        for s in (s0, s1):
            for i1, j1 in s:
                if board[i1][j1] == player:
                    n += 1
                    if n >= 4:
                        return WON
                else:
                    break
    return UNDECIDED

def apply_move(board, player, move):
    for i, row in enumerate(board):
        if board[i][move] == 0:
            board[i][move] = player
            outcome = get_outcome(board, i, move)
            return outcome
    if all(board[-1]):
        return DRAWN
    return UNDECIDED

def get_view(board, player):
    view = [list(row) for row in board]
    for i, row in enumerate(view):
        if i % 2:
            continue
        for j, x in enumerate(row):
            if x == 3 - player:
                row[j] = 0
    return view

def run_game(player1, player2):
    players = {1 : player1, 2 : player2}
    board = [[0] * 7 for _ in range(6)]
    states = {1 : None, 2 : None}
    for turn in range(6 * 7):
        p = (turn % 2) + 1
        player = players[p]
        view = get_view(board, p)
        move, state = player(view, turn, states[p])
        outcome = apply_move(board, p, move)
        if outcome == DRAWN:
            return DRAWN
        elif outcome == WON:
            return p
        else:
            states[p] = state
    return DRAWN

def get_score(counts):
    return (counts[WON] - counts[LOST]) / float(sum(counts))

def run_tournament(players, rounds=10000):
    counts = [[0] * 3 for _ in players]
    for r in range(rounds):
        for i, player1 in enumerate(players):
            for j, player2 in enumerate(players):
                if i == j:
                    continue
                outcome = run_game(player1, player2)
                if outcome == DRAWN:
                    for k in i, j:
                        counts[k][DRAWN] += 1
                else:
                    if outcome == 1:
                        w, l = i, j
                    else:
                        w, l = j, i
                    counts[w][WON] += 1
                    counts[l][LOST] += 1
        ranks = sorted(range(len(players)), key = lambda i: get_score(counts[i]), reverse=True)
        print("Round %d of %d\n" % (r + 1, rounds))
        rows = [("Name", "Draws", "Losses", "Wins", "Score")]
        for i in ranks:
            name = players[i].__name__
            score = get_score(counts[i])
            rows.append([name + ":"] + [str(n) for n in counts[i]] + ["%6.3f" % score])
        lengths = [max(len(s) for s in col) + 1 for col in zip(*rows)]
        for i, row in enumerate(rows):
            padding = ((n - len(s)) * ' ' for s, n in zip(row, lengths))
            print(''.join(s + p for s, p in zip(row, padding)))
            if i == 0:
                print()
        print()

def random_player(view, turn, state):
    return random.randrange(0, 7), state

def constant_player(view, turn, state):
    return 0, state

def better_random_player(view, turn, state):
    while True:
        j = random.randrange(0, 7)
        if view[-1][j] == 0:
            return j, state

def better_constant_player(view, turn, state):
    for j in range(7):
        if view[-1][j] == 0:
            return j, state

players = [random_player, constant_player, better_random_player, better_constant_player]

run_tournament(players)

ハッピーKoTHing!

暫定結果

Name                    Draws Losses Wins  Score  

zsani_bot:              40    5377   94583  0.892 
better_constant_player: 0     28665  71335  0.427 
constant_player:        3     53961  46036 -0.079 
normalBot:              38    64903  35059 -0.298 
better_random_player:   192   71447  28361 -0.431 
random_player:          199   75411  24390 -0.510 

view [-1] [j] == 0をチェックする理由を説明していただけますか?あなたがそれらをどこに埋めたのか私が完全に確信しているわけではありません、そして私のpythonの知識は少し錆びているようです。
Barbarian772

@ Barbarian772その列にまだスペースがあるかどうかを確認しています。6つの行があるため、一番上の行が完全に観察されていることに注意してください。
user1502040

1
既に満杯の列に配置することをパスとして数えるべきではありません。多くのコネクト4ゲームは、1つの列だけが入力されていない状態で終了し、1つのプレーヤーがその列でのプレーによって負けた場合、1人のプレーヤーが強制的に勝利したときにゲームを引き分けます。
soktinpk 2018

@soktinpkそれは戦略の別のレイヤーを追加するだけではありませんか?結局、Connect-4は解決済みのゲームなので、ターンスキップファクターは、コントリビューターが標準のアルゴリズムを使用するだけでは十分ではないルールの変更である可能性があります。
mypetlion 2018

1
下からゼロインデックスを作成します。テープで覆われた行は(0,2,4,6)または(1,3,5)ですか?いくつかのASCIIアートが役に立ちます。
SIGSTACKFAULT 2018

回答:


6

このボットはすべての確実な勝利を取り、フォールバックしてライバルをブロックし、次に垂直方向と水平方向にそれらを推測するか、ランダムな動きをします。

pprint、数学、コレクション、コピーのインポート
def zsani_bot_2(view、turn、state):
    if state == None:#first own turn-常に中間のため
        state =(1、2)if turn == 0 else(2、1)#(my_symbol、your symbol)
        #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0]))
        リターン3、状態

    #明らかなポイントを見つける
    for i(1、6)の場合:#最初の行をスキップ
        範囲内のj(len(view [i])):#TODO:zipで最適化。分かりやすくする
            if view [i] [j]!= 0 and view [i-1] [j] == 0:
                ビュー[i-1] [j] =状態[1]
    enemy_points = math.floor(turn / 2)
    ++ enemy_points if state [0] == 2 else enemy_points
    既知のポイント= sum([i.count(state [1])for i in view])
    missing_points = enemy_points-既知のポイント

    #確実にあらゆる方向に勝つ
    range(0、7)のjの場合:#すべての列
        範囲(4、-1、-1)のiの場合:
            if view [i] [j]!= 0:
                ブレーク#既知の最高の塗りつぶしポイントを見つける
        if(missing_pointsまたは{1、3、5}のi + 1ではない):
            view1 = copy.deepcopy(view)
            attempt = apply_move(view1、state [0]、j)
            試行==成功した場合:
               #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0])+ '勝者の移動')
                j、状態を返す

    #敵があらゆる方向に勝利することを確実にする
    範囲(0、7)のjの場合:
        範囲(4、-1、-1)のiの場合:
            if view [i] [j]!= 0:
                ブレーク#既知の最高の塗りつぶしポイントを見つける
        if(missing_pointsまたは({1、3、5}のi + 1)でない場合):
            view1 = copy.deepcopy(view)
            attempt = apply_move(view1、state [1]、j)
            試行==成功した場合:
              #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0])+ '保存移動')
                j、状態を返す

    #壁をブロック
    for i in range(0、3):#列がいっぱいの場合、行で4を取得することは不可能
        範囲(0、6)のjの場合:
            if view [i] [j]!= 0およびview [i] [j] == view [i + 1] [j]およびview [i + 2] [j] == view [i + 3] [j ] == 0:
             #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレーヤー:' + str(state [0])+ '列移動')
                j、状態を返す

    #block以下の行とドロップポイントに完全な情報がある場合はプラットフォームをブロックする
    for i(0、5)の場合:
        range(0、3)のjの場合:
            stats = collections.Counter([view [i] [j]、view [i] [j + 1]、view [i] [j + 2]、view [i] [j + 3]])
            stats [0] == 2および(stats [state [0]] == 2またはstats [state [0]] == 2)の場合:
                range(0、3)のkの場合:
                    view [i] [j + k] == 0の場合:
                        ブレーク
                if(i == 0 or view [i-1] [j + k]!= 0)and(not missing_points or i in {1、3、5}):
                    #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0])+ 'プラットフォーム移動')
                    j + k、状態を返す
                そうしないと:
                    範囲(k、3)のlの場合:
                        view [i] [j + l] == 0の場合:
                            ブレーク
                        if(i == 0 or view [i-1] [j + l]!= 0)and(not missing_points or i in {1、3、5}):
                     #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0])+ 'プラットフォーム移動')
                            j + l、状態を返す

    #フォールバック->ランダム
    Trueの間:
        j = random.randrange(0、7)
        ビュー[-1] [j] == 0の場合:
            #print(pprint.pformat(view)+ 'ターン:' + str(turn)+ 'プレイヤー:' + str(state [0])+ 'ランダム移動')
            j、状態を返す

run_gameを修正していただき、ありがとうございます。

変更ログ:

  • v2は水平方向のブロックを追加します-4の行で2つの空のスポットと2つのスポットが同じプレーヤーによって埋められた場合、それらの1つを埋めて3つを連続させ、対戦相手の行をブロックします。次のターンで活用されます。

3
サイトへようこそ。コードに変更するための編集を拒否するように投票しました。コメントとして残しておくと、OPがコードの処理方法を決定できます。
アドホックガーフハンター2018

メインポストにコメントするのに十分な評判がありません。編集を取り消すにはどうすればよいですか?
Syfer Polski、2018

編集を撤回する必要はありません(とにかくできるとは思いません)。将来的にはコメントで十分ですが、回答でそれを言ったので、OPが表示する可能性があります。プラス私は考える OPは、あなたがそれを拒否された場合でも、提案して編集することを参照すること。
アドホックガーフハンター2018

編集を取り消したいのは、変更の1行を逃したためです。この行がないと、編集したコードが完全に実行されません。投稿の編集提案に含めました。ご協力ありがとうございました!
Syfer Polski、2018

2

normalBotは、中央のスポットが両端のスポットよりも価値があるという仮定に基づいて再生します。したがって、中央を中心とした正規分布を使用して、選択肢を決定します。

def normalBot(view, turn, state):
    randomNumber = round(np.random.normal(3, 1.25))
    fullColumns = []
    for i in range(7):
        if view[-1][i] != 0:
            fullColumns.append(i)
    while (randomNumber > 6) or (randomNumber < 0) or (randomNumber in fullColumns):
        randomNumber = round(np.random.normal(3, 1.25))
    return randomNumber, state

-1

これは明らかに非競合ですが、それでもデバッグに非常に役立ちます。ボットをだますのに十分知っている場合は驚くほど楽しいです。

import math, pprint
def manual_bot(view, turn, state):
    if state == None:
        state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol)

#locate obvious points
    for row in range (1, 6):
        for j in range(len(view[row])):
            if view[row][j] != 0 and view[row-1][j] == 0:
                view[row-1][j] = state[1]

#if you're second, the opponent has one more point than half the turns
    enemy_points = math.ceil(turn/2)
    known_points = sum([row.count(state[1]) for row in view])
    missing_points = enemy_points - known_points

    print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' Missing points: ' + str(missing_points))
    while True:
        try:
            move = int(input("What is your move?(0-6) "))
        except ValueError:
            continue
        if move in {0, 1, 2, 3, 4, 5, 6}:
            return move, state

グリッドは上下逆です(一番下の行が最も高い)。勝者の発表を得るには、ゲームコントローラにパッチを適用し、勝利を返す前に印刷ステートメントを追加する必要があります。

elif outcome == WON:
    print(pprint.pformat(board) + ' Turn: ' + str(turn) +' Winner: '+ str(p))
    return p

[[0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:0プレイヤー:1欠落ポイント:0
あなたの動きは何ですか?(0-6)3
[[0、0、0、1、0、0、0]、
 [0、0、0、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:2プレイヤー:1欠落ポイント:0
あなたの動きは何ですか?(0-6)2
[[0、0、1、1、0、0、0]、
 [0、0、0、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:4プレーヤー:1欠落ポイント:1
あなたの動きは何ですか?(0-6)4
[[0、0、1、1、1、0、0]、
 [0、0、0、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:6プレーヤー:1欠落ポイント:2
あなたの動きは何ですか?(0-6)1
[[2、1、1、1、1、2、0]、
 [0、0、0、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:6勝者:1
[[0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:1プレイヤー:2ミッシングポイント:1
あなたの動きは何ですか?(0-6)2
[[0、0、2、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:3プレーヤー:2欠落ポイント:2
あなたの動きは何ですか?(0-6)3
[[0、0、2、1、0、0、0]、
 [0、0、1、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:5プレーヤー:2欠落ポイント:1
あなたの動きは何ですか?(0-6)4
[[0、0、2、1、2、0、0]、
 [0、0、1、2、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:7プレーヤー:2欠落ポイント:2
あなたの動きは何ですか?(0-6)1
[[0、2、2、1、2、0、0]、
 [0、0、1、2、0、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、0、0、0、0、0]、
 [0、0、0、0、0、0、0]]ターン:9プレーヤー:2欠落ポイント:1
あなたの動きは何ですか?(0-6)2
[[0、2、2、1、2、0、0]、
 [0、0、1、2、0、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:11プレーヤー:2不足点:1
あなたの動きは何ですか?(0-6)4
[[0、2、2、1、2、0、0]、
 [0、0、1、2、2、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、1、0、0、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:13プレーヤー:2欠落ポイント:2
あなたの動きは何ですか?(0-6)4
[[0、2、2、1、2、0、0]、
 [0、1、1、2、2、0、0]、
 [0、0、1、0、1、0、0]、
 [0、0、1、0、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:15プレーヤー:2ミッシングポイント:1
あなたの動きは何ですか?(0-6)3
[[0、2、2、1、2、0、0]、
 [0、1、1、2、2、0、0]、
 [0、0、1、2、1、0、0]、
 [0、0、1、0、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:17プレーヤー:2欠落ポイント:2
あなたの動きは何ですか?(0-6)5
[[0、2、2、1、2、1、1]、
 [0、1、1、2、2、2、1]、
 [0、0、1、2、1、0、0]、
 [0、0、1、0、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:19プレーヤー:2欠落ポイント:0
あなたの動きは何ですか?(0-6) 
あなたの動きは何ですか?(0-6)6
[[0、2、2、1、2、1、1]、
 [0、1、1、2、2、2、1]、
 [0、0、1、2、1、0、2]、
 [0、0、1、0、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:21プレーヤー:2不足点:1
あなたの動きは何ですか?(0-6)1
[[0、2、2、1、2、1、1]、
 [0、1、1、2、2、2、1]、
 [0、2、1、2、1、0、2]、
 [0、1、1、0、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:23プレーヤー:2ミッシングポイント:1
あなたの動きは何ですか?(0-6)3
[[0、2、2、1、2、1、1]、
 [0、1、1、2、2、2、1]、
 [0、2、1、2、1、0、2]、
 [0、1、1、2、2、0、0]、
 [0、0、2、0、0、0、0]、
 [0、0、1、0、0、0、0]]ターン:25プレーヤー:2不足点:2
あなたの動きは何ですか?(0-6)6
[[0、2、2、1、2、1、1]、
 [0、1、1、2、2、2、1]、
 [0、2、1、2、1、0、2]、
 [0、1、1、2、2、0、2]、
 [0、0、2、1、0、0、0]、
 [0、0、1、1、0、0、0]]ターン:27プレーヤー:2ミッシングポイント:1
あなたの動きは何ですか?(0-6)5
[[1、2、2、1、2、1、1]、
 [1、1、1、2、2、2、1]、
 [0、2、1、2、1、2、2]、
 [0、1、1、2、2、0、2]、
 [0、0、2、1、0、0、0]、
 [0、0、1、1、0、0、0]]ターン:29プレーヤー:2ミッシングポイント:0
あなたの動きは何ですか?(0-6)5
[[1、2、2、1、2、1、1]、
 [1、1、1、2、2、2、1]、
 [0、2、1、2、1、2、2]、
 [0、1、1、2、2、2、2]、
 [0、0、2、1、0、0、0]、
 [0、0、1、1、0、0、0]]ターン:29勝者:2
第1ラウンド

名前が負けた勝ちのスコア
manual_bot:0 0 2 1.000 zsani_bot_2:0 2 0 -1.000

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