부분적으로 관찰 가능한 Connect-4


8

게임

Connect-4 의 (거의) 표준 게임을 할 것입니다 . 불행히도, 그것은 대응 게임이며 누군가가 맨 아래부터 시작하여 매 두 번째 행에 검은 색 테이프를 놓았 으므로이 행 내에서 상대방의 움직임을 볼 수 없습니다.

이미 가득 찬 칸 내에서의 모든 움직임은 턴을 통과 한 것으로 간주되며, 게임이 6 * 7턴 보다 오래 실행 되면 추첨으로 간주됩니다.

도전 사양

프로그램은 Python 3 함수로 구현해야합니다. 첫 번째 인수는 보드의 '보기'이며 알려진 보드 상태를 아래에서 위로 2D 행 목록으로 표시 1합니다. 첫 번째 플레이어 2의 이동, 두 번째 플레이어의 이동 0, 빈 위치 또는 숨겨진 상대에 의해 움직입니다.

두 번째 인수는에서 색인 된 턴 번호 0이며, 그 패리티는 어떤 플레이어인지 알려줍니다.

마지막 인수는 None각 게임이 시작될 때 초기화되는 임의의 상태로, 턴 사이의 상태를 유지하는 데 사용할 수 있습니다.

플레이하고자하는 컬럼 인덱스의 2 튜플을 반환하고, 다음 턴에 새로운 상태를 반환해야합니다.

채점

승리는로 +1, 무승부 0및 로 계산 됩니다 -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을 확인하는지 설명해 주시겠습니까? 나는 당신이 그것들을 어디에 넣었는지 알지 못하고 파이썬 지식은 약간 녹슨 것 같습니다.
Barbarian772

@ Barbarian772 해당 열에 여전히 공간이 있는지 확인하고 있습니다. 6 개의 행이 있으므로 맨 위 행이 완전히 관찰됩니다.
user1502040

1
이미 전체 열에 합격으로 간주해서는 안됩니다. 많은 연결 4 게임은 하나의 열만 채워지지 않고 끝나고 한 플레이어가 해당 열에서 플레이하여 패할 경우 그렇지 않으면 한 플레이어에게 강제로 승리 할 때 게임 넥타이를 만듭니다.
soktinpk

@soktinpk 전략의 또 다른 계층을 추가하지 않습니까? Connect-4는 결국 해결 된 게임이므로 턴 건너 뛰기 요소로 인해 규칙을 변경하는 것으로 충분할 수 있습니다.
mypetlion

1
맨 아래에서 0 인덱싱하면 테이프 오버 된 행 (0,2,4,6) 또는 (1,3,5)입니까? 일부 ASCII 아트가 도움이 될 것입니다.
SIGSTACKFAULT

답변:


6

이 봇은 확실한 승리를 거둔 뒤 라이벌을 막기 위해 다시 수직, 수평으로 추측하거나 무작위로 움직입니다.

pprint, math, collections, copy 가져 오기
데프 zsani_bot_2 (보기, 회전, 상태) :
    만약 state == None이라면 : #first own turn-항상 중간
        회전 == 0이면 state = (1, 2) else (2, 1) # (my_symbol, your symbol)
        #print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]))
        상태 3을 반환

    # 명확한 포인트 찾기
    범위 (1, 6)의 i의 경우 : #skip first row
        range (len (view [i]))의 j : #TODO : zip으로 최적화합니다. 명확성을 위해 지금 가십시오
            view [i] [j]! = 0이고 view [i-1] [j] == 0 인 경우 :
                보기 [i-1] [j] = 상태 [1]
    enemy_points = 수학 바닥 (턴 / 2)
    ++ enemy_points state [0] == 2면 else_points
    known_points = sum ([i.count (state [1]) in i])
    missing_points = 적점 _ 알려진 _ 점

    # 어느 방향에서나 승리하십시오.
    범위 (0, 7)의 j의 경우 : #every column
        범위 (4, -1, -1)의 i의 경우 :
            view [i] [j]! = 0 인 경우 :
                가장 높은 알려진 채워진 지점 찾기
        if ({1, 3, 5}에서 missing_points 또는 i + 1이 아님) :
            view1 = copy.deepcopy (view)
            시도 = apply_move (view1, state [0], j)
            시도 == WON 인 경우 :
               # print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + '승자 이동')
                j, 상태 반환

    # 모든 방향에서 적의 승리를 차단하십시오
    범위 (0, 7)의 j의 경우 :
        범위 (4, -1, -1)의 i의 경우 :
            view [i] [j]! = 0 인 경우 :
                가장 높은 알려진 채워진 지점 찾기
        if (missing_points 또는 (i, 1 in {1, 3, 5})) :
            view1 = copy.deepcopy (view)
            시도 = apply_move (view1, state [1], j)
            시도 == WON 인 경우 :
              # print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + '저장 이동')
                j, 상태 반환

    # 블록 벽
    범위 (0, 3)의 i의 경우 : # 열이 가득 찼을 때 행에 4를 얻는 것은 불가능합니다.
        범위 (0, 6)의 j의 경우 :
            만약 view [i] [j]! = 0이고 view [i] [j] == view [i + 1] [j] and view [i + 2] [j] == view [i + 3] [j ] == 0 :
             # print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + '열 이동')
                j, 상태 반환

    아래 행과 드롭 포인트에 대한 완벽한 정보를 제공하는 경우 #block 플랫폼
    범위 (0, 5)의 i의 경우 :
        범위 (0, 3)의 j의 경우 :
            통계 = 모음. 카운터 ([보기 [i] [j],보기 [i] [j + 1],보기 [i] [j + 2],보기 [i] [j + 3]])
            stats [0] == 2이고 (stats [state [0]] == 2 또는 stats [state [0]] == 2 인 경우 :
                k 범위 (0, 3)의 경우 :
                    보기 [i] [j + k] == 0 인 경우 :
                        단절
                {i == 0 또는 view [i-1] [j + k]! = 0) 및 {1, 3, 5}에서 missing_points 또는 i가 아님) :
                    #print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + 'platform move')
                    j + k를 반환, 상태
                그밖에:
                    범위 (k, 3)에서 l의 경우 :
                        보기 [i] [j + l] == 0 인 경우 :
                            단절
                        {i == 0 또는 view [i-1] [j + l]! = 0) 및 {1, 3, 5}에서 missing_points 또는 i가 아님) :
                     # print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + 'platform move')
                            j + l을 반환, 상태

    #fallback-> 임의
    진실한 동안 :
        j = random.randrange (0, 7)
        view [-1] [j] == 0 인 경우 :
            #print (pprint.pformat (view) + 'Turn :'+ str (turn) + 'Player :'+ str (state [0]) + 'random move')
            j, 상태 반환

run_game을 수정 해 주셔서 감사합니다!

변경 로그:

  • v2는 가로 블로킹을 추가합니다-4 행에 같은 플레이어가 두 개의 빈 자리와 두 개의 자리를 채운 경우, 그 중 하나를 채워서 한 행에 3 개를 줄이려고 시도합니다. 다음 차례에 대문자를 사용하십시오.

3
사이트에 오신 것을 환영합니다. 나는 코드 변경을 위해 편집을 거부하기로 결정했으며, OP가 코드로 무엇을 할 것인지 결정할 수있는 방법으로 주석으로 남겨 두는 것이 가장 좋습니다.
Ad Hoc Garf Hunter

메인 게시물에 대해 언급 할만한 명성이 없습니다. 수정 사항을 철회하려면 어떻게합니까?
Syfer Polski

편집을 철회 할 필요가 없습니다 (어쨌든 할 수 있다고 생각하지 않습니다). 앞으로는 의견을 말하면 충분하지만 답변에서 의견을 말 했으므로 OP가 보게 될 것입니다. 또한 OP는 거부 된 경우에도 제안하고 편집했음을 알 수 있다고 생각 합니다.
애드혹 가프 헌터

편집을 철회하려는 이유는 변경 사항에서 한 줄을 놓 쳤기 때문에 편집 한 코드가 완전히 실행되지 않기 때문입니다. 내 게시물의 수정 제안에 포함 시켰습니다. 도와 주셔서 감사합니다!
Syfer Polski

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의 1 라운드

이름 무승부 승점
manual_bot : 00 2 1.000 zsani_bot_2 : 00 -1.000

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.