171230 編程-井字棋(逆)的先手必勝策略


1625-5 王子昂 總結《2017年12月30日》 【連續第456天總結】
A. bambooctf-toddler-notakto-revenge
B.

========== Welcome to the Notakto Game ==========
Notakto is tic-tac-toe with both players playing the same piece ( an ‘X’ )
The player who end the game will LOSE THE GAME

0 | 1 | 2
—+—+—
3 | 4 | 5
—+—+—
6 | 7 | 8

========== 1 Round ==========
Your move:

說人話就是輪流執子,以井字棋的方式進行,但是勝利條件反過來,結束游戲的人判負
玩家先手,也就是尋找該游戲的先手必勝策略

在這個九子棋盤中,很容易發現六子共存的情況只有一種

1 1 0
1 0 1
0 1 1

此時先手輸
觀察它的特征,可以發現破解這種局勢的方法就是鄰角下子或者中心點下子

也就是說,當鄰角或中心點有子時,后手第六子是必輸的
那么后手翻盤的機會在哪兒?
分別對應兩種情況

  • 中心點有子
1 1 0
1 1 0
0 0 0
  • 鄰角有子
1 0 1
0 0 0
1 0 1

此時第五子無處可放,先手輸

那么先手必勝的策略就是杜絕這兩種情況的出現
第一子無論下在哪里,第二子都要根據對方的反應來調整:
對方下在邊/中心點上,我方就要下在鄰角上
對方下在角上,我方就要下在中心點/邊上

三子定下來以后局勢就穩定了,只要不送后手就不可能贏

我選擇的是第一子下中心點的策略
落子思路為
第一子下中心點,此時每一子都會使得對稱點不可落子
因此只需要記錄4對點即可排除與中心點有關的結束線

之后每一子都下在對手落子對角/邊

當對手下在“1”時,可選x的位置下

x 0 x
0 0 0
0 1 0

作用在於
1. 與該落子無鄰邊關系。
除了4個過中心點的結束線以外,還有4個鄰邊的結束線。
當第三子落下時,排除玩家第一子(中心點)、對手第二子(無關)以外,只有玩家第二子和對手第一子可能形成鄰邊結束。但是由於下子策略,玩家第二子與對手第一子也不相鄰,因此可排除鄰邊結束。
2. 避免形成4子封死的局面

在兩個x中選取對稱點空白的點下即可

代碼如下:

from pwn import *
pairs = {0: (0, 8),1: (1, 7), 2:(2, 6), 3:(3, 5)}


def find(x):
    for i in range(4):
        if(x) in pairs[i]:
            return i


def choose(x):
    global flag
    p = pairs[find(x)]
    if(x)==p[0]:
        p = p[1]
    else:
        p = p[0]
    # 以對稱點為中心,選取4個點作為備選,遍歷它們,滿足在點棋盤上且對稱點未被下時可取
    k = [p-1, p+1, p-3, p+3]
    for i in k:
        if((i//3==p//3 or i%3 == p%3)and i<=8 and i>=0 and i!=4 and flag[find(i)]==0):
            return i


def iswin(s):
    global flag
    if(s.find("Round") != -1):
        p = s.find("Round")
        print(s[p-12:p+15])
        flag = [0 for x in range(4)]


def recv(s):
    p = s.find("My move:")
    if(p==-1):
        return -1

    for i in s:
        try:
            if(i[0] == 'M'):
                recv = int(i[9])
                return recv
        except:
            pass
    else:
        return -1


r = remote("bamboofox.cs.nctu.edu.tw", 58793)
s = r.recvuntil("Your move")
flag = [0 for i in range(4)]
while(1):
        iswin(s)
        k = s.find("My move")
        if(k==-1):
            r.sendline("4")
            print("send:", 4)

        else:
            k = int(s[k+9])
            print("recv:", k)
            flag[int(find(k))] = 1
            p = choose(k)
            print('send:', p)
            r.sendline(str(p))
            flag[find(p)] = 1

        try:
            s = r.recvuntil("Your move")
        except:
            print(r.recvall(timeout=3))
            break

值得一提的是nc連接只有1分鍾左右的時間,要通過50關才能拿到flag
這樣就杜絕了手動操作的可能性

由於每次最后消息收到都沒有EOF,因此剛開始我只能通過timeout來接收
試了一下timeout=1時完全來不及,只能下調到0.3左右
此時就可能造成某一次延遲稍大,消息沒收到就結束了

最后找到recvuntil函數,可以自主規定結束符,並且能接到所有內容
這樣可以根據延遲自己決定接受時間,輕松完成

PS:
這題50分,后面還有一題進階的500分,棋盤增加到5個,任意落子,規則相同,“結束游戲”(五個棋盤都結束時,每個棋盤獨立結束)的最后一子判負(:з」∠)

PPS:
思考的時候感覺這種

C. 明日計划
看書╮(╯_╰)╭


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: