Jouez au tic-tac-toe et ne perdez jamais

14

(Il existe certains défis qui nécessitent d'utiliser la meilleure stratégie, mais ici nous ne le faisons pas. Même si vous êtes en mesure de gagner, vous êtes autorisé à faire un match nul)

Défi

Écrivez un programme qui joue au jeu tic-tac-toe. Il ne doit pas perdre (par conséquent, il doit terminer le match avec une égalité ou en gagnant).

Méthodes d'E / S autorisées

  1. L'entrée peut être la carte actuelle. Vous pouvez supposer que tous les coups précédents du 2ème joueur ont été joués par votre moteur.
  2. L'entrée peut être les mouvements du premier joueur, et votre fonction enregistre les mouvements qui se sont produits dans le passé. Dans ce cas, la fonction est appelée plusieurs fois, une fois pour chaque coup; ou l'entrée d'invite de fonction / programme à plusieurs reprises.
  3. Vous êtes autorisé à prendre une entrée supplémentaire indiquant si vous êtes le premier joueur, ou à écrire deux fonctions (éventuellement liées) pour résoudre le problème du premier joueur et celui du deuxième joueur. Si votre programme doit utiliser la méthode de saisie 2 (appel multiple), vous pouvez décider de ce qui sera passé lors du premier appel.
  4. La sortie peut être la carte après votre tour.
  5. La sortie peut être votre mouvement.
  6. Un mouvement peut être représenté par une paire de nombres (peut être 0 ou 1), un nombre compris entre 0 et 8 ou un nombre compris entre 1 et 9.
  7. La carte peut être représentée comme un tableau 3 × 3 ou un tableau de longueur 9. Même si la langue a un tableau d'indexation 0, vous pouvez utiliser l'indexation 1.
  8. Les cellules de la grille peut utiliser tous les 3 valeurs différentes pour indiquer X, Oet vide.

Critères gagnants

Code le plus court dans chaque langue.

l4m2
la source
Si une perte vous est donnée, votre solution n'est pas valide. Vous jouez avec d'autres, donc l'échiquier ne changera pas instantanément, doncwe can assume that all previous moves of the 2nd player were also played by our engine
l4m2
3
xkcd.com/832
totalement humain
1
@ l4m2 Redémarrez simplement l'interpréteur. Terminé. Pourquoi s'en préoccuper? Il augmente simplement inutilement le nombre d'octets pour rien.
user202729
4
Ne faites pas le bonus. Soit l'exiger ou le supprimer, ne le rendez pas facultatif. Le bonus gâche le défi ..
Rɪᴋᴇʀ

Réponses:

4

Befunge, 181 168 octets

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

Les positions sur le tableau sont numérotées de 1 à 9. Par défaut, vous obtenez le premier coup, mais si vous voulez permettre à l'ordinateur de commencer, vous pouvez simplement entrer 0 pour votre premier coup. Lorsque vous avez effectué un mouvement, l'ordinateur répondra par un numéro indiquant leur mouvement.

Il n'y a aucun contrôle pour vous assurer que vous n'entrez pas un mouvement valide, et il n'y a pas non plus de contrôle pour voir si quelqu'un a gagné ou perdu. Une fois qu'il n'y a plus de mouvements à faire, le programme entre simplement dans une boucle infinie.

Il est un peu difficile de tester cela en ligne, car il n'y a pas d'interprètes en ligne avec entrée interactive. Cependant, si vous savez à l'avance quels mouvements vous allez effectuer (ce qui suppose que vous savez comment l'ordinateur va réagir), vous pouvez en quelque sorte tester sur TIO avec ces mouvements préprogrammés.

L'utilisateur joue d'abord: Essayez-le en ligne!
L'ordinateur joue en premier: essayez-le en ligne!

Pour le rendre plus facile à voir ce qui se passe, j'ai également une version qui sort le tableau entre les mouvements.

L'utilisateur joue d'abord: Essayez-le en ligne!
L'ordinateur joue en premier: essayez-le en ligne!

Notez que vous devrez attendre que TIO expire avant de pouvoir voir les résultats.

Explication

La carte est stockée dans la zone de mémoire Befunge sous la forme d'un tableau plat de 9 valeurs, indexées de 1 à 9. Cela nous permet d'utiliser le décalage zéro comme cas spécial "pas de mouvement" lorsque nous voulons laisser l'ordinateur jouer en premier. Les mouvements des joueurs sont stockés sous forme de 4, et les mouvements de l'ordinateur sous forme de 5. Pour commencer, toutes les positions sont initialisées à 32 (la mémoire par défaut de Befunge), donc chaque fois que nous accédons au plateau, nous modifions avec 8, donc nous reviendrons soit 0, 4 ou 5.

Compte tenu de cet arrangement, si nous additionnons les valeurs de trois positions au tableau, nous savons que l'ordinateur est à un mouvement de gagner si le total est de 10, le joueur est à un mouvement de gagner si le total est 8, et le les positions sont partagées entre l'ordinateur et le joueur (mais toujours une position est libre) si le total est de 9.

Toute notre stratégie est basée sur ce concept. Nous avons une routine qui prend une liste de triplets indiquant des ensembles de trois positions sur la carte, nous calculons la somme de ces positions, et si la somme est égale à un certain total, l'ordinateur passe à celle des positions de l'ensemble qui est libre.

La liste principale des triplets que nous testons sont les combinaisons gagnantes (1/2/3, 1/5/9, 1/4/7, etc.). Nous cherchons d'abord un total de 10 (l'ordinateur est sur le point de gagner), puis un total de 8 (le joueur est sur le point de gagner et nous devons bloquer ce mouvement). Moins évidemment, nous vérifions également un total de 9 (si le joueur et l'ordinateur ont chacun une des positions, c'est une bonne stratégie pour que l'ordinateur prenne la troisième).

Avant ce dernier scénario, l'autre mouvement stratégique que nous faisons est de vérifier tous les ensembles de coins (1/2/4, 2/3/6, etc.) ainsi que deux combinaisons de coins opposés (1/8/9 et 3 / 7/8). Si l'une de ces combinaisons s'élève à 8, c'est-à-dire que le joueur a pris deux des positions, c'est une bonne stratégie pour que l'ordinateur prenne la position libre restante.

Enfin, il y a deux cas particuliers. Tout d'abord, nous essayons toujours de prendre la position centrale avant tout autre mouvement. Ceci est réalisé avec la même routine que tous nos autres mouvements, en passant simplement un seul triple, 5/5/5, et une somme cible de 0. De plus, si tous les autres tests n'ont pas réussi à trouver un mouvement, nous essayons de prendre l'un des coins supérieurs en dernier recours. Encore une fois, cela est simplement réalisé en testant les triplets 1/1/1 et 3/3/3, avec une somme cible de 0.

Je ne pense pas que ce soit nécessairement une stratégie parfaite - il peut y avoir des jeux que l'ordinateur dessine qui auraient pu potentiellement être gagnés - mais c'est assez bon pour ne jamais perdre un match. J'ai exécuté un script de test qui a essayé de jouer tous les coups possibles contre l'ordinateur, et pour chaque séquence de coups valide, l'ordinateur a gagné ou dessiné le jeu.

James Holderness
la source
Je ne connais pas très bien Befunge, mais peut-être que vous pouvez aller tester toutes les entrées possibles ( exemple )
l4m2
@ l4m2 FYI, j'ai maintenant exécuté un script de test qui a essayé tous les mouvements possibles contre l'ordinateur et peut confirmer qu'il ne perd jamais.
James Holderness
2

Python 2: 399 401 349 333 317 370 octets

2x Bug Fix: crédit à l4m2

-52 caractères: crédit au métro souterrain

-16 caractères: crédit à Jonathan Frech

-26 caractères: crédit à l'utilisateur 202729

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])

Essayez-le en ligne!

Le premier jour d'un cours d'algèbre linéaire que j'ai suivi le semestre dernier, mon instructeur étudiant diplômé astucieux a proposé que si vous représentez le tableau tic-tac-toe comme matrice:

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

puis en obtenir trois d'affilée équivaut à choisir trois nombres dans la plage [1,9] qui totalisent 15. Cette réponse exploite cette idée. La fonction prend une liste contenant neuf nombres représentant le tableau. 0 indique un espace vide, 1 est occupé par l'adversaire et 2 représente un jeu précédent effectué par le programme. Les 3 premières lignes indiquent quels numéros le programme a choisis (p), l'opposition a choisi (o) et sont toujours disponibles (a). Il examine ensuite les numéros disponibles et voit si l'un d'entre eux, combiné avec deux numéros qu'il a déjà choisis, s'ajoute à quinze. Si c'est le cas, il choisira cette case et gagnera. S'il n'y a pas de coups gagnants immédiats, il vérifiera si l'adversaire peut gagner en utilisant la même méthode. S'ils le peuvent, il leur faudra leur carré gagnant. S'il n'y a ni coup gagnant ni coupable disponible, il se déplacera dans un coin. Cela empêche un imbécile de compagnon:

- - - 
- X -
- - -

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

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

Si aucune de ces situations ne se produit, il choisira arbitrairement un carré. La fonction génère un nombre [0,8] représentant le carré indexé 0 choisi par l'algorithme.

Edit: l'algorithme priorise maintenant le centre sur la diagonale, ce qui empêchera une autre possibilité de fool mate indiquée par l4m2 et les stratégies associées.

Edit: Pour clarifier, la fonction prend une carte sous la forme d'un tableau et sort un mouvement sous forme d'entier sur [0,8]. Parce que cette stratégie d'E / S est si maladroite, voici un script wrapper qui le rend plus interactif. Il prend un seul argument de ligne de commande, qui devrait être 1 si le joueur va en premier et 0 si le programme va en premier.

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 
Coton Zachary
la source
1
Hacked
l4m2
1
Toutes vos returnlignes, à l'exception de la dernière, peuvent être placées sur la ligne avant elles, ce qui permet d'économiser des espaces
undergroundmonorail
1
Je ne peux pas empêcher de se demander si elle sauverait octets, au lieu de le faire e=enumerate, faire f=lambda n:[t[i]for i,j in enumerate(b)if j==n]et assign p, oet en autilisant la fonction. Je ne l'ai pas compté cependant
undergroundmonorail
3
Toujours piraté . xkcd.com/832 aide vraiment
l4m2
2
Hacked 3
l4m2