玩井字游戏,永不丢失


14

(存在一些挑战,需要使用最佳策略,但在这里我们不需要。即使您能够获胜,也可以平局)

挑战

编写一个玩井字游戏的程序。它一定不能输(因此,应该以平局或获胜结束游戏)。

允许的I / O方法

  1. 输入可能是当前板。您可以假设第二个玩家的所有先前举动都是由您的引擎进行的。
  2. 输入的内容可能是第一个玩家的举动,而您的函数将存储过去发生过的举动。在这种情况下,该函数被多次调用,每次移动一次。或多次输入功能/程序提示。
  3. 允许您输入额外的信息,例如您是第一位玩家,还是编写两个(可能是相关的)函数来解决第一位玩家和第二位玩家的问题。如果程序需要使用输入法2(多次调用),则可以决定在第一次调用中传递了什么。
  4. 轮到您后,输出可能是板。
  5. 输出可能是您的举动。
  6. 移动可以表示为一对数字(可以是0索引或1索引),0到8范围内的数字或1到9范围内的数字。
  7. 木板可以表示为3×3数组或长度为9的数组。即使语言具有0索引数组,也可以使用1索引。
  8. 网格上的细胞可以使用任何3个不同的值,以指示XO并清空。

获奖标准

每种语言中最短的代码将获胜。


如果输给您,那么您的解决方案将无效。您正在与其他人一起玩,因此棋盘不会立即发生变化,所以we can assume that all previous moves of the 2nd player were also played by our engine
l4m2


1
@ l4m2只需重新启动解释器即可。做完了 为什么要打扰呢?它只是不必要地增加了字节数。
user202729


4
不要做奖金。要求它或将其删除,请不要将其设为可选。奖金遗址的挑战..
Rɪᴋᴇʀ

Answers:


4

Befunge,181个 168字节

>>4&5pp20555>>03>16p\::5g8%6p5v
 ^p5.:g605$_ #!<^_|#:-1g61+%8g<
543217539511|:_^#->#g0<>8+00p3+5%09638527419876
v<304p$_v#:->#$$:2`#3_:^
>#\3#13#<111v124236478689189378

板上的位置从1到9编号。默认情况下,您会先走,但如果要允许计算机先走,则只需输入0就可以了。进行移动后,计算机将通过一个数字指示其移动。

没有检查以确保您没有输入有效的举动,也没有检查以查看是否有人赢了或输了。一旦不再需要采取任何措施,程序便进入无限循环。

由于没有在线交互翻译的口译员,因此很难在网上进行测试。但是,如果您事先知道要做出什么动作(假设您知道计算机的响应方式),则可以使用预先编程的动作在TIO上进行测试。

用户首先玩: 在线尝试!
电脑先播放: 在线试用!

为了更轻松地了解发生了什么,我还提供了一个版本,可以在两次移动之间输出木板。

用户首先玩: 在线尝试!
电脑先播放: 在线试用!

请注意,您必须等待TIO超时才能看到结果。

说明

评估板以9个值的平面数组(从1到9索引)的形式存储在Befunge存储区中。这让我们在想让计算机首先播放时将零偏移作为特殊情况“不动”使用。玩家的移动存储为4,计算机的移动存储为5。首先,将所有位置初始化为32(默认为Befunge内存),因此,每当我们访问该板时,我们将其设置为8,那么我们将返回0、4或5。

在这种安排下,如果我们将棋盘上任何三个位置的值相加,那么我们知道如果总数为10,则计算机距获胜仅一步之遥;如果总数为8,则玩家与获胜相差仅一步之遥。如果总数为9,则计算机和播放器之间共享位置(但仍然有一个位置是空闲的)。

我们的整个策略都基于这个概念。我们有一个例程,该例程使用一个三元组列表来指示板上的三个位置的集合,我们计算这些位置的总和,如果总和等于某个总数,计算机将移动到该集合中任意一个空闲位置。

我们测试的三元组的主要列表是获胜组合(1/2 / 3、1 / 5 / 9、1 / 4/7等)。我们首先寻找总共10个(计算机即将获胜),然后寻找总共8个(玩家即将获胜,我们需要阻止该移动)。不太明显的是,我们还要检查总共9个(如果玩家和计算机都占据一个位置,则计算机占据第三个位置是一个很好的策略)。

在最后一种情况之前,我们采取的另一项战略举措是检查所有角点集(1/2 / 4、2 / 3/6等)以及两个相对的角点组合(1/8/9和3 / 7/8)。如果这些组合中的任何一个加起来等于8,即玩家已经获得了两个位置,则计算机采取剩余的空闲位置是一个很好的策略。

最后,有两个特殊情况的动作。首先,我们总是尝试在其他任何动作之前占据中心位置。这是通过与我们所有其他举动相同的例程来实现的,只是传递了一个三重,5/5/5,目标总和为0。此外,如果所有其他测试都未能找到举动,我们将尝试采取不得已而为最高角之一。同样,这可以通过测试三元组1/1/1和3/3/3(目标总和为0)来简单地实现。

我认为这不一定是一个完美的策略-可能有一些计算机绘制的游戏可能会赢得胜利-但它足以防止输掉一场比赛。我运行了一个测试脚本,该脚本试图对着计算机进行所有可能的移动,并且对于每个有效的移动顺序,计算机要么赢得比赛,要么赢得比赛。


我不太了解Befunge,但也许您可以测试所有可能的输入(样本
l4m2,18年

@ l4m2仅供参考,我现在运行了一个测试脚本,该脚本尝试对计算机进行所有可能的移动,并可以确认它永远不会丢失。
詹姆斯·霍尔德尼斯

2

蟒2:399个 401 349 333 317 370字节

2x错误修复:贡献给l4m2

-52个字符:贷记入undergroundmonorail

-16个字元:归功于Jonathan Frech

-26个字符:贷记到user202729

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
 for i in 5,4,2,8,6:
    if i in a:return I(i)
 return I(a[0])

在线尝试!

在上学期的线性代数课程的第一天,我精明的研究生导师建议,如果您将井字游戏板表示为矩阵:

4 | 9 | 2
--+---+--
3 | 5 | 7
--+---+--
8 | 1 | 6

那么连续获得三个等于在[1,9]范围内选择三个加起来为15的数字。此答案利用了这个想法。该函数获取一个包含九个代表板的数字的列表。0表示有空位,1被对手占据,2表示程序进行的上一局。前三行指出程序选择了哪些数字(p),反对者选择了哪些数字(o),并且仍然可用(a)。然后,它查看可用的数字,看看是否有任何数字与已经选择的两个数字相加,得出15个数字。如果是这样,它将选择那个正方形并赢。如果没有立即获胜的举动,它将检查对手是否可以使用相同的方法获胜。如果他们能,那将是他们获胜的地方。如果既没有获胜也没有阻止举动,它会在一个角落移动。这样可以防止傻瓜伴侣:

- - - 
- X -
- - -

- O -             # Bad Move
- X -
- - -

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

如果这些情况都没有发生,它将任意选择一个正方形。函数输出一个数字[0,8],表示由算法选择的0索引平方。

编辑:该算法现在优先于对角线优先选择中心,这将防止l4m2和相关策略指出的另一个傻瓜交配可能性。

编辑:为明确起见,该函数以阵列形式接收板,并在[0,8]上以整数形式输出移动。由于此I / O策略非常笨拙,因此下面的包装脚本使它更具交互性。它只接受一个命令行参数,如果玩家先进入,则应为1;如果程序先进入,则应为0。

import sys

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
     for i in 5,4,2,8,6:
        if i in a:return I(i)
 return I(a[0])

board = [0,0,0,0,0,0,0,0,0]
rep = {0:"-",1:"X",2:"O"}

turn = int(sys.argv[1])
while True:
    for i in range(3):
        print rep[board[i*3]]+" "+rep[board[i*3+1]]+" "+rep[board[i*3+2]]
        print
    if turn:
        move = int(raw_input("Enter Move [0-8]: "))
    else:
        move = f(board)
    board[move] = turn+1
    turn = (turn+1)%2 


1
return除最后一条线外的所有线都可以放在其前面的行上,以节省空白
地下

1
此外,我不禁怀疑,如果将字节保存,而不是做e=enumerate,做f=lambda n:[t[i]for i,j in enumerate(b)if j==n]和分配po以及a使用功能。不过还没算在内
地下

3
仍然被黑xkcd.com/832确实有帮助
l4m2

2
砍了
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.