题目分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/local/bin/python3
import tty
import sys
import hashlib

if sys.stdin.isatty():
tty.setcbreak(0)

g=(f:=[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1])[:]
n=[1,1,0,0,0,0,0,0,1,0,2,0,0,0,0,0,3,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,3,0,0,0,1,0,1,0,1,0,0,0,2,2,2,0,0]

def decrypt_flag(k):
h=hashlib.sha512(str(k).encode()).digest()
print(bytes(a^b for(a,b)in zip(h,bytes.fromhex("8b1e35ac3da64cb9db365e529ad8c9496388a4f499faf887386b4f6c43b616aae990f17c1b1f34af514800275673e0f3c689c0998fc73c342f033aa7cc69d199"))).decode())

m={'w':(-1,0),'s':(1,0),'a':(0,-1),'d':(0,1)}
def t(a,b,s=None):
if s is None:
s = set()
s.add((a,b))
for(i,j)in m.values():
x,y=a+i,b+j
if (x,y) not in s and x in range(7) and y in range(7) and g[x*7+y]==g[a*7+b]:
t(x,y,s)
return s

a,b=0,0
d=1
while 1:
if d:
print("\x1b[2J")
for i in range(7):
print(" ["[(a,b)==(i,0)],end="")
for j in range(7):
print("_#"[g[i*7+j]],end="["if(a,b)==(i,j+1)else" ]"[(a,b)==(i,j)])
print()
d=0
try:
c=sys.stdin.read(1)
if c == "":
break
except EOFError:
break
if c=='q':
break
elif c=='x':
if not f[i:=a*7+b]:
g[i]=1-g[i]
d=1
elif v:=m.get(c):
i,j=a+v[0],b+v[1]
if i in range(7) and j in range(7):
a,b=i,j
d=1
elif c=='c':
p=1
s=set()
for i in range(7):
for j in range(7):
if(i,j)not in s:
v=[0]*4
k=t(i,j)
s|=k
for(x,y)in k:
v[n[x*7+y]]+=1
if any(h not in (0,2) for h in v[1:]):
p=0
if p:
print("Correct!")
decrypt_flag(g)
else:
print("Incorrect!")



这段源码实现了一个基于终端的逻辑解谜游戏(类似于拼图或涂色游戏)。玩家需要在一个 $7 \times 7$ 的网格中切换单元格的状态(_#),使得每个连通区域内包含特定数量的标记点。

以下是详细的代码注释和逻辑分析:


1. 初始化与环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/local/bin/python3
import tty
import sys
import hashlib

# 如果是在终端环境下运行,将输入模式设置为 cbreak
# 这样不需要按回车键,程序就能直接读取每一个按键
if sys.stdin.isatty():
tty.setcbreak(0)

# f: 初始网格状态(不可变的部分标记)
# g: 当前网格状态(玩家操作的对象),初始化为 f 的副本
g=(f:=[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1])[:]

# n: 校验数组,存储了网格中每个点的“属性值”(0, 1, 2, 3)
n=[1,1,0,0,0,0,0,0,1,0,2,0,0,0,0,0,3,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,3,0,0,0,1,0,1,0,1,0,0,0,2,2,2,0,0]

2. 核心功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 解密 Flag:当玩家获胜时,用当前的网格状态 g 作为密钥解密并打印结果
def decrypt_flag(k):
h=hashlib.sha512(str(k).encode()).digest()
# 目标密文(十六进制)与哈希值进行 XOR 异或运算
cipher = "8b1e35ac3da64cb9db365e529ad8c9496388a4f499faf887386b4f6c43b616aae990f17c1b1f34af514800275673e0f3c689c0998fc73c342f033aa7cc69d199"
print(bytes(a^b for(a,b)in zip(h,bytes.fromhex(cipher))).decode())

# 移动映射表:w, s, a, d 对应坐标的变化
m={'w':(-1,0),'s':(1,0),'a':(0,-1),'d':(0,1)}

# 递归函数 t:使用深度优先搜索 (DFS) 寻找当前坐标 (a,b) 所在的颜色相同的连通区域
def t(a,b,s=None):
if s is None: s = set()
s.add((a,b))
for(i,j)in m.values():
x,y=a+i,b+j
# 检查边界,并确保相邻格子的值(0或1)与当前格子相同
if (x,y) not in s and x in range(7) and y in range(7) and g[x*7+y]==g[a*7+b]:
t(x,y,s)
return s

3. 游戏主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
a,b=0,0 # 玩家当前光标的坐标 (row, col)
d=1 # 刷新标志位,d=1 时重新绘制界面

while 1:
if d:
print("\x1b[2J") # ANSI 转义码:清屏
for i in range(7):
# 绘制行首的光标指示器
print(" ["[(a,b)==(i,0)], end="")
for j in range(7):
# 绘制单元格:0 对应 _,1 对应 #
char = "_#"[g[i*7+j]]
# 绘制单元格右侧的光标指示器
print(char, end="[" if(a,b)==(i,j+1) else " ]"[(a,b)==(i,j)])
print()
d=0

# 读取输入
try:
c=sys.stdin.read(1)
if c == "": break
except EOFError: break

if c=='q': break # 退出

# 按 'x' 键切换当前格子的状态
elif c=='x':
if not f[i:=a*7+b]: # 只有初始为 0 的格子才能被玩家修改
g[i]=1-g[i]
d=1

# 按 WASD 移动光标
elif v:=m.get(c):
i,j=a+v[0],b+v[1]
if i in range(7) and j in range(7):
a,b=i,j
d=1

# 按 'c' 键检查是否通关
elif c=='c':
p=1
s=set() # 记录已处理过的格子
for i in range(7):
for j in range(7):
if(i,j)not in s:
v=[0]*4 # 统计当前连通块中 n 数组各个值的出现次数
k=t(i,j) # 获取整个连通区域
s|=k
for(x,y)in k:
v[n[x*7+y]]+=1
# 胜利条件逻辑:
# 对于 n[i] > 0 的标记点(1, 2, 3),在同一个连通区域内,
# 它们的出现次数必须要么是 0 次,要么是刚好 2 次
if any(h not in (0,2) for h in v[1:]):
p=0
if p:
print("Correct!")
decrypt_flag(g)
else:
print("Incorrect!")

总结

  1. 目标:通过 x 键切换格子的状态(黑 # 或 白 _)。
  2. 规则:当你按下 c 校验时,程序会扫描所有的连通色块。对于数组 n 中定义的所有非零属性点(即某些特定的格子位置),如果它们被划分到了同一个色块里,那么在该色块中,每种属性值的点必须刚好成对出现(2个)。
  3. 操作
    • w/a/s/d: 移动光标 [ ]
    • x: 翻转当前格子的颜色(初始格子不可变)。
    • c: 验证结果,正确则输出 Flag。
    • q: 退出游戏。

网格中的一个单元格被 包裹[]。尝试不同的按键组合,可以发现各种控制方式:

  • 按键WASD使光标(用 表示[])沿垂直方向移动。
  • X键可将_光标高亮显示的元素更改为 a #,反之亦然(值得注意的是,它不会更改#网格上原本就有的 s)。
  • 按下该C键会吐出一个Incorrect!(这很可能是用来检查我们在解谜过程中的答案的)。
  • 最后,按下Q密钥即可退出程序。

关键数据结构

棋盘

1
g=(f:=[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1])[:]

这是 7×7=49 个格子,按行优先展开

1
g[x*7 + y]  <-->  第 x 行,第 y 列

1 = 墙/#
0 = 地/空/_

你可以用 WASD 移动光标,用 x 翻转当前格子(但某些格子不能翻)。

f不可翻转掩码

f[i] == 1 表示这个格子锁死不能改

1
2
3
elif c=='x':
if not f[i := a*7+b]:
g[i] = 1 - g[i]

也就是说:
你只能修改 f[i] = 0 的位置。

这是约束条件之一

n区域类型标签

n 也是长度 49 的列表,每个格子有一个“区域编号”:

1
n = [1,1,0,0,0,0,0,0,1,0,2,0, ...]

作用会在后面 c 检查里体现。

核心检查

c 时执行:

1
elif c=='c':

进入正确性检测


找连通区域

函数:

1
def t(a,b,s=None):

本质是洪水填充(Flood Fill)/ DFS 连通搜索

它会找到所有和 (a,b) 同值(同为 0 或同为 1)且上下左右相连的格子,返回一个集合 k

也就是说:它把棋盘分解成了多个连通区域(connected components)。

对每个区域做统计

核心代码:

1
2
for (x,y) in k:
v[n[x*7+y]] += 1

这里:

  • k = 一个连通区域
  • n[x*7+y] = 该格子的类别
  • v 是长度 4 的计数器(索引 0~3)

然后检查:

1
2
if any(h not in (0,2) for h in v[1:]):
p = 0

关键约束:对于每个连通区域

  • 在类别 1、2、3 中,每一类的格子数必须是 0 或 2
  • 不能是 1、3、4…… 只能是 0 或 2

也就是说:

✅ 每个连通块里:

  • 要么没有某类格子
  • 要么恰好有 2 个

这是一个成对匹配约束


成功条件

如果所有区域都满足上面规则:

1
2
print("Correct!")
decrypt_flag(g)

就会把当前棋盘 g 作为密钥 k

1
2
h = sha512(str(k)).digest()
flag = h XOR <固定密文>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import hashlib
import sys
sys.setrecursionlimit(10000)

# ================== 题目原始数据 ==================

# 初始固定格子(1 = 不可翻的 #)
f = [0,0,0,0,0,0,0,0,
1,0,0,0,0,0,0,0,
0,1,0,0,0,0,0,1,
0,1,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,
0,0,0,0,1,0,1,0,
1]

# 每个格子的标签(0/1/2/3)
n = [1,1,0,0,0,0,0,0,
1,0,2,0,0,0,0,0,
3,1,0,0,0,0,0,1,
0,1,0,0,0,0,0,1,
3,0,0,0,1,0,1,0,
1,0,0,0,2,2,2,0,
0]

# 四个方向
dirs = [(-1,0),(1,0),(0,-1),(0,1)]

# ================== flood fill ==================

def flood_fill(grid, x, y):
stack = [(x,y)]
comp = set()

while stack:
a,b = stack.pop()
if (a,b) in comp:
continue
comp.add((a,b))

for dx,dy in dirs:
nx, ny = a+dx, b+dy
if 0 <= nx < 7 and 0 <= ny < 7:
if (nx,ny) not in comp and grid[nx*7+ny] == grid[a*7+b]:
stack.append((nx,ny))
return comp

# ================== 完整合法性检查 ==================

def valid(grid):
seen = set()

for i in range(7):
for j in range(7):
if (i,j) not in seen:
comp = flood_fill(grid, i, j)
seen |= comp

v = [0,0,0,0]
for x,y in comp:
v[n[x*7+y]] += 1

# 题目要求:每种标签必须 0 或 2
for k in [1,2,3]:
if v[k] not in (0,2):
return False
return True

# ================== 局部剪枝检查 ==================

def partial_valid(grid):
seen = set()

for i in range(7):
for j in range(7):
if (i,j) not in seen:
comp = flood_fill(grid, i, j)
seen |= comp

v = [0,0,0,0]
open_border = False

for x,y in comp:
v[n[x*7+y]] += 1

# 看看是否还可能扩展
for dx,dy in dirs:
nx, ny = x+dx, y+dy
if 0 <= nx < 7 and 0 <= ny < 7:
if grid[nx*7+ny] == -1:
open_border = True

# 如果已经封闭,就必须严格检查
if not open_border:
for k in [1,2,3]:
if v[k] not in (0,2):
return False
return True

# ================== 打印棋盘 ==================

def print_grid(g):
for i in range(7):
row = ""
for j in range(7):
row += "#" if g[i*7+j] == 1 else "_"
print(row)

# ================== 还原 flag ==================

def decrypt_flag(g):
h = hashlib.sha512(str(g).encode()).digest()
secret = bytes.fromhex(
"8b1e35ac3da64cb9db365e529ad8c949"
"6388a4f499faf887386b4f6c43b616aa"
"e990f17c1b1f34af514800275673e0f3"
"c689c0998fc73c342f033aa7cc69d199"
)
flag = bytes(a^b for a,b in zip(h, secret))
print("\nFLAG:", flag.decode())

# ================== 搜索准备 ==================

free_cells = [i for i in range(49) if f[i] == 0]
print("可翻格子数:", len(free_cells))

# 初始棋盘:-1 = 未赋值
g = [-1]*49
for i in range(49):
if f[i] == 1:
g[i] = 1

# ================== 回溯搜索 ==================

def search(grid, idx):
if idx == len(free_cells):
if valid(grid):
print("\n找到解!\n")
print_grid(grid)
decrypt_flag(grid)
sys.exit(0)
return

pos = free_cells[idx]

# 尝试 0
grid[pos] = 0
if partial_valid(grid):
search(grid, idx+1)

# 尝试 1
grid[pos] = 1
if partial_valid(grid):
search(grid, idx+1)

# 回溯
grid[pos] = -1

# ================== 运行 ==================

search(g, 0)