您的位置:首页 > 编程语言

171230 编程-井字棋(逆)的先手必胜策略

2018-01-01 19:41 274 查看
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:

说人话就是轮流执子,以井字棋的方式进行,但是胜利条件反过来,结束游戏的人判负

玩家先手,也就是寻找该游戏的先手必胜策略

在这个九子棋盘中,很容易发现六子共存的情况只有一种

110
101
011
此时先手输

观察它的特征,可以发现破解这种局势的方法就是邻角下子或者中心点下子

也就是说,当邻角或中心点有子时,后手第六子是必输的

那么后手翻盘的机会在哪儿?

分别对应两种情况

中心点有子

110
110
000
邻角有子

101
000
101
此时第五子无处可放,先手输

那么先手必胜的策略就是杜绝这两种情况的出现

第一子无论下在哪里,第二子都要根据对方的反应来调整:

对方下在边/中心点上,我方就要下在邻角上

对方下在角上,我方就要下在中心点/边上

三子定下来以后局势就稳定了,只要不送后手就不可能赢

我选择的是第一子下中心点的策略

落子思路为

第一子下中心点,此时每一子都会使得对称点不可落子

因此只需要记录4对点即可排除与中心点有关的结束线

之后每一子都下在对手落子对角/边



当对手下在“1”时,可选x的位置下

x0x
000
010
作用在于

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.recvu
4000
ntil("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. 明日计划

看书╮(╯_╰)╭
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: