Páginas

domingo, 3 de março de 2013

skribu-kun-python-cxap10


Temoj Kovritaj dum tiu ĉapitro:

  • Artefarita Inteligenteco
  • Listo Referencoj
  • Mallonga cirkvito Taksado
  • La Neniu Valoro
Ni nun krei Tic Tac Toe ludo kie la ludanto ludas kontraŭ simpla artefarita inteligenteco. Artefarita inteligenteco (aŭ AI) estas komputila programo kiu povas inteligente respondis al la ludanto movas. Ĉi tiu ludo ne enkondukas ajna komplika novaj konceptoj. Ni vidos, ke la artefarita inteligenteco kiu ludas Tic Tac Toe estas vere nur kelkaj linioj de kodo.
Tic Tac Toe estas simpla ludo por ludi kun papero kaj krajono inter du personoj. Unu ludanto estas X kaj la alia ludanto estas O. La simpla naŭ kvadrata krado (kiu oni nomas la tabulo), la ludantoj turnan metante siajn X aŭ O) sur la tabulo. Se ludanto ricevas tri de iliaj markoj sur la tabulo en vico, kolumno aŭ unu el la du diagonaloj, ili gajnas.
Plej ludoj de Tic Tac Toe fino en egaleco, kio okazas kiam la tabulo estas plenigita supren kun neniu ludanto havanta tri markoj en vico. Anstataŭ de dua homa ludanto, nia artefarita inteligenteco faros movadoj kontraŭ la uzanto. Vi povas lerni pli pri Tic Tac Toe el Vikipedio: http://en.wikipedia.org/wiki/Tic-tac-toe
Dum ĉi tiu ĉapitro ne enkonduki multajn novajn programado konceptoj, ne uzas nian ekzistanta programado kono por fari inteligentan Tic Tac Toe ludanto. Ni komenci per rigardante specimeno ripeto de la programo. La ludanto faras sian movon, enirante en la numero de la spaco ili volas. Tiuj nombroj estas en la samaj lokoj kiel la nombro klavoj sur via klavaro la klavaro (vidu Figuron 10-2).

Specimeno Run de Tic Tac Toe

Bonvenon Tic Tac Toe!
Ĉu vi volas esti X aŭ O?
X
La komputilo iros unue.
| |
Ho | |
| |
-----------
| |
| |
| |
-----------
| |
| |
| |
Kio estas via sekva movo? (1-9)
3
| |
Ho | |
| |
-----------
| |
| |
| |
-----------
| |
Ho | | X
| |
Kio estas via sekva movo? (1-9)
4
| |
Ho | | ho
| |
-----------
| |
X | |
| |
-----------
| |
Ho | | X
| |
Kio estas via sekva movo? (1-9)
5
| |
Ho | ho | ho
| |
-----------
| |
X | X |
| |
-----------
| |
Ho | | X
| |
La komputilo venkis vi! Vi perdos.
Ĉu vi volas ludi denove? (Jes aŭ ne)
neniu

Fonta Kodo de Tic Tac Toe

En nova dosiero redaktanto fenestro, tajpu ĉi fontkodo kaj konservi ĝin kiel tictactoe.py. Tiam kuris la ludo premante F5. Vi ne bezonas enmeti tiun programon antaŭ legi tiun ĉapitron. Vi ankaŭ povas elŝuti la fontkodon vizitante la retejon ĉe la URL http://inventwithpython.com/chapter10 kaj post la instrukciojn sur la retpaĝo.
tictactoe.py
Tiu kodo estas elŝutebla el http://inventwithpython.com/tictactoe.py
Se vi ricevas erarojn post tajpi tiun kodon en, kompari ĝin al la libro kodo kun la linio malsamoj ilo en http://inventwithpython.com/diff aŭ retpoŝtu la aŭtoro en al@inventwithpython.com
  1. # Tic Tac Toe

  2. importi hazarda

  3. def drawBoard (tabulo):
  4. # Ĉi tiu funkcio presas el la estraro ke ĝi pasis.

  5. # "Tabulo" estas listo de 10 kordoj reprezentantaj la tabulo (ignori indekso 0)
  6. print ('| |')
  7. print ('' + estraro [7] + '|' + estraro [8] + '|' + estraro [9])
  8. print ('| |')
  9. print ('-----------')
  10. print ('| |')
  11. print ('' + estraro [4] + '|' + estraro [5] + '|' + estraro [6])
  12. print ('| |')
  13. print ('-----------')
  14. print ('| |')
  15. print ('' + estraro [1] + '|' + estraro [2] + '|' + estraro [3])
  16. print ('| |')

  17. def inputPlayerLetter ():
  18. # Estu Estas la ludanto tipo kiu letero volas esti.
  19. # Returns lerta kun la ludanto leteron kiel la unua ero, kaj la komputilo la letero kiel la dua.
  20. letero =''
  21. dum ne (litero == 'X' aux leteron == 'O'):
  22. presi ('Ĉu vi volas esti X aŭ O?')
  23. letero = input (). supra ()

  24. # La unua elemento de la listo estas la ludanto letero, la dua estas la komputilo letero.
  25. se letero == 'X':
  26. reveno ['X', 'ho']
  27. alie:
  28. reveno ['ho', 'X']

  29. def whoGoesFirst ():
  30. # Hazarde elekti la ludanto kiu iras unue.
  31. se random.randint (0, 1) == 0:
  32. reveno 'komputilo'
  33. alie:
  34. reveno 'ludanto'

  35. def playAgain ():
  36. # Ĉi tiu funkcio redonas True se la ludanto volas ludi denove, alie False.
  37. presi ('Ĉu vi volas ludi denove? (jes aŭ ne)')
  38. revenu enigo (). malsupreniri (). startswith ('y')

  39. def makeMove (tabulo, litero, movado):
  40. estraro [movado] = letero

  41. def isWinner (bo, le):
  42. # Donita estraro kaj ludanto la letero, ĉi tiu funkcio redonas Vera se tiu ludanto venkis.
  43. # Ni uzas bo anstataŭ estraro kaj le anstataŭ letero do ni ne devas tajpi tiel.
  44. reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ # trans la supron
  45. (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ # trans la mezo
  46. (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ # trans la fundo
  47. (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ # malsupren la maldekstra flanko
  48. (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ # malsupren la mezo
  49. (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ # malsupren la dekstra flanko
  50. (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ # diagonalo
  51. (Bo [9] == le kaj bo [5] == le kaj bo [1] == le)) # diagonalo

  52. def getBoardCopy (tabulo):
  53. # Faru duobligita de la estraro listo kaj reveni al ĝi la duobligita.
  54. dupeBoard = []

  55. por i en tabulo:
  56. dupeBoard.append (i)

  57. revenu dupeBoard

  58. def isSpaceFree (tabulo, movado):
  59. # Reveno vera se la pasinta movado estas libera pri la pasinta estraro.
  60. revenu estraro [movado] == ''

  61. def getPlayerMove (tabulo):
  62. # Lasu la ludanto tipo en sia movado.
  63. movi = ''
  64. dum movado ne en '1 2 3 4 5 6 7 8 9 '. split () aŭ ne isSpaceFree (tabulo, int (movado)):
  65. print ("Kio estas via sekva movo? (1-9) ')
  66. movi = input ()
  67. reveno int (movado)

  68. def chooseRandomMoveFromList (tabulo, movesList):
  69. # Returns validan movon de la pasinta listo de la pasinta estraro.
  70. # Returns Neniu se ne estas valida movo.
  71. possibleMoves = []
  72. por i en movesList:
  73. se isSpaceFree (tabulo, i):
  74. possibleMoves.append (i)

  75. se len (possibleMoves)! = 0:
  76. revenu random.choice (possibleMoves)
  77. alie:
  78. reveni Neniu

  79. def getComputerMove (tabulo, computerLetter):
  80. # Donita estraro kaj la komputilo la letero, determini kie movi kaj reveni kiuj movas.
  81. se computerLetter == 'X':
  82. playerLetter = 'O'
  83. alie:
  84. playerLetter = 'X'

  85. # Jen nia algoritmo por nia Tic Tac Toe AI:
  86. # Unua, kontrolu se ni povas gajni en la proksima movado
  87. por i en gamo (1, 10):
  88. Kopio = getBoardCopy (tabulo)
  89. se isSpaceFree (kopio, i):
  90. makeMove (kopio, computerLetter, i)
  91. se isWinner (kopio, computerLetter):
  92. revenu i

  93. # Kontroli se la ludanto povis gajni en lia proksima movado, kaj bloki ilin.
  94. por i en gamo (1, 10):
  95. Kopio = getBoardCopy (tabulo)
  96. se isSpaceFree (kopio, i):
  97. makeMove (kopio, playerLetter, i)
  98. se isWinner (kopio, playerLetter):
  99. revenu i

  100. # Provu preni unu el la anguloj, se ili estas liberaj.
  101. movado = chooseRandomMoveFromList (tabulo, [1, 3, 7, 9])
  102. se movadon! = None:
  103. revenu movas

  104. # Provu preni la centron, se ĝi estas senpaga.
  105. se isSpaceFree (tabulo, 5):
  106. revenu 5

  107. # Movu sur unu el la flankoj.
  108. reveno chooseRandomMoveFromList (tabulo, [2, 4, 6, 8])

  109. def isBoardFull (tabulo):
  110. # Reveno Vera se ĉiu spaco sur la tabulo estis prenita. Alie reveni False.
  111. por i en gamo (1, 10):
  112. se isSpaceFree (tabulo, i):
  113. revenu Falsaj
  114. revenu Vera


  115. print ('Bonvenon Tic Tac Toe!')

  116. dum Vera:
  117. # Restarigi la estraro
  118. theBoard = [''] * 10
  119. playerLetter, computerLetter = inputPlayerLetter ()
  120. turni = whoGoesFirst ()
  121. print ('La' + siavice + 'iros unue.')
  122. gameIsPlaying = True

  123. dum gameIsPlaying:
  124. se siavice == 'ludanto':
  125. # Ludanto laŭvice.
  126. drawBoard (theBoard)
  127. movi = getPlayerMove (theBoard)
  128. makeMove (theBoard, playerLetter, movado)

  129. se isWinner (theBoard, playerLetter):
  130. drawBoard (theBoard)
  131. print ('Hura! Vi gajnis la ludo!)
  132. gameIsPlaying = False
  133. alie:
  134. se isBoardFull (theBoard):
  135. drawBoard (theBoard)
  136. print ('La ludo estas egaleco!')
  137. rompi
  138. alie:
  139. turni = 'komputilo'

  140. alie:
  141. # Computer turnon.
  142. movi = getComputerMove (theBoard, computerLetter)
  143. makeMove (theBoard, computerLetter, movado)

  144. se isWinner (theBoard, computerLetter):
  145. drawBoard (theBoard)
  146. print ('La komputilo venkis vi! Vi perdos.')
  147. gameIsPlaying = False
  148. alie:
  149. se isBoardFull (theBoard):
  150. drawBoard (theBoard)
  151. print ('La ludo estas egaleco!')
  152. rompi
  153. alie:
  154. turni = 'ludanto'

  155. se ne playAgain ():
  156. rompi

Desegni la Programo


Figuro 10-1: Flow abako por Tic Tac Toe
Tic Tac Toe estas tre facila kaj mallonga ludo ludi sur papero. En nia Tic Tac Toe komputila ludo, ni lasu la ludanto elektas se ili volas esti X aŭ O, hazarde elektas kiu iras unua, kaj tiam la ludanto kaj komputilo postrestas igas fari movas sur la tabulo. Figuro 10-1 estas kion fluo leteron de Tic Tac Toe povus aspekti.
Vi povas vidi amason de la skatoloj en la maldekstra flanko de la tabulo estas kio okazas dum la ludanto laŭvice. La dekstra flanko de la tabulo montras kio okazas en la komputilo la vico. La ludanto havas ekstran skatolo por desegni la tabulo ĉar la komputilo ne bezonas la estraro presita sur la ekrano. Post la ludanto aŭ komputilo faras movon, ni kontrolu ĉu ili gajnis aŭ kaŭzita egaleco, kaj tiam la ludo ŝanĝas turnoj. Post la ludo estas finita, ni petas la ludanto se ili volas ludi denove.

Reprezentante la Estraro kiel Datumoj

Unue, ni devas kalkuli kiom tuj prezenti la tabulo kiel variablo. Sur papero, la Tic Tac Toe tabulo estas desegnita kiel paro de horizontalaj linioj kaj paro de vertikalaj linioj, kun ĉu X, ho, aŭ malplena spaco en ĉiu de la naŭ spacoj.
En nia programo, tuj prezenti la Tic Tac Toe tabulo kiel listo de ĉenoj. Ĉiu kordo reprezentas unu el la naŭ pozicioj sur la tabulo. Ni donos kelkajn al ĉiu de la spacoj de la tabulo. Por fari ĝin pli facile memori kiu indekso en la listo estas por kiu peco, ni reflektas la nombroj sur la klavaro de nia klavaro. Vidu Figuro 10-2.

Figuro 10-2: La estraro estos kalkulitaj kiel la klavaro la numero pad.
La kordoj havos ĉu esti 'X' por la X ludanto, 'ho' por la O ludanto, aŭ spaco ĉeno '' por marki lokon sur la tabulo kie neniu markis ankoraŭ. La indico de la ĉenon en la listo ankaŭ estos la nombro de la spaco sur la tabulo.
Do, se ni havis liston kun dek ŝnuroj nomita estraro, tiam estraro [7] estus la supro-maldekstra kvadrato sur la tabulo (ĉu X, ho, aŭ vakuaj spaco). Estraro [5] estus la centro. Kiam la ludanto tipoj en kiu loko ili volas movi, ili tajpu numeron de 1 ĝis 9. (Ĉar estas nenia 0 sur la klavaro, ni simple ignoras la kordoj ĉe indeksa 0 en nia listo.)

Ludo AI


Figuro 10-3: Lokoj de la flanko, angulo, kaj centro lokoj.
Kiam ni parolas pri kiel nia AI kondutas, ni parolos pri kiuj tipoj de spacoj en la estraro ĝi antaŭeniras. Nur por esti klara, ni estos marko tri tipoj de spacoj Tic Tac Toe tabulo: anguloj, flankoj, kaj la centro. Figuro 10-3 estas letero de kion ĉiu spaco estas:
La AI por ĉi tiu ludo sekvos simpla algoritmo. Algoritmo estas serio de instrukcioj por komputi ion. Tio ĉi estas vasta difino de algoritmo. Sola programo povas uzi plurajn malsamajn algoritmoj. Algoritmo, kiel kompleta programo, povas esti prezentita kun fluo abako. En la kazo de nia Tic Tac Toe AI algoritmo, la serio de paŝoj determinos kiu estas la plej bona loko por moviĝi. (Vidu Figuro 10-4.) Estas nenio en la kodo kiu diras, "Ĉi tiuj linioj estas algoritmo." kiel estas kun funkcia def-bloko. Ni nur konsideri la AI algoritmo kiel ĉiuj kodo kiu estas uzata en nia programo kiu determinas la AI la proksima movado.
Nia algoritmo havas jenajn paŝojn:
  1. Unue, ĉu tie estas movado la komputilo povas fari ke gajnos la ludon. Se estas, preni tiun movadon. Alie, iru al paŝo 2.
  2. Vidu se estas movado la ludanto povas fari tiun kaŭzos la komputilo perdi la partion. Se estas, ni devas translokiĝi tie por bloki la ludanto. Alie, iru al paŝo 3.
  3. Kontrolu se iu el la angulo spacoj (spacoj 1, 3, 7, aŭ 9) estas liberaj. (Ni ĉiam volas preni angulo peco anstataŭ la centro aŭ flanko peco.) Se neniu angulo peco estas libera, tiam iru al paŝo 4.
  4. Kontrolu se la centro estas libera. Se jes, movi tie. Se ne estas, tiam iru al paŝo 5.
  5. Movu en ajna de la flanko pecoj (spacoj 2, 4, 6, aŭ 8). Ne estas pli paŝojn, ĉar se ni atingis paŝo 5 flanke spacoj estas la sola spacoj restas.
Ĉi ĉio okazas en la "Get komputilo movado." skatolo sur nia fluo abako. Ni povus aldoni tiun informon al nia fluo abako tiamaniere:

Figuro 10-4: La kvin ŝtupoj de la "Get komputilo movado" algoritmo.
La sagoj lasante iri al la "Check se komputilo venkis" skatolo.
Ni efektivigos tiun algoritmon kiel kodo en nia getComputerMove () funkcio, kaj la aliaj funkcioj kiuj getComputerMove () alvokoj.

Kiel la Kodo Verkoj: Linioj 1 ĝis 81

Nun ke ni scias pri kiel ni volas ke la programo funkcias, ni rigardu kion ĉiu linio faras.

La Komenco de la Programo

  1. # Tic Tac Toe

  2. importi hazarda
La unua paro de linioj estas komento, kaj importi la hazarda modulo por ke ni povu uzi la randint () funkcion en nia ludo.

Presi la Estraro en la ekrano

  1. def drawBoard (tabulo):
  2. # Ĉi tiu funkcio presas el la estraro ke ĝi pasis.

  3. # "Tabulo" estas listo de 10 kordoj reprezentantaj la tabulo (ignori indekso 0)
  4. print ('| |')
  5. print ('' + estraro [7] + '|' + estraro [8] + '|' + estraro [9])
  6. print ('| |')
  7. print ('-----------')
  8. print ('| |')
  9. print ('' + estraro [4] + '|' + estraro [5] + '|' + estraro [6])
  10. print ('| |')
  11. print ('-----------')
  12. print ('| |')
  13. print ('' + estraro [1] + '|' + estraro [2] + '|' + estraro [3])
  14. print ('| |')
Ĉi tiu funkcio estos presi la tabulo, markita kiel direktita de la estraro parametro. Memoru ke nia estraro estas reprezentita kiel listo de dek ŝnuroj, kie la kordoj ĉe indekso 1 estas la markon sur spaco 1 en la Tic Tac Toe tabulo. (Kaj memoru ke ni ignoras la kordoj ĉe indeksa 0, ĉar la spacoj estas etikedita kun ciferoj 1 ĝis 9.) Multaj el niaj funkcioj funkcios per pasante la tabulo kiel listo de dek ŝnuroj al niaj funkcioj. Nepre akiri la Interspacigo dekstre en la kordoj, alie la estraro aspektos amuza kiam estas presita en la ekrano.
Nur kiel ekzemplo, jen iuj valoroj kiujn la estraro parametro povus havi (la maldekstra flanko de la tablo) kaj kia estas la drawBoard () funkcio devus presi (dekstre):
Tabelo 10-1: Ekzemploj de valoroj de tabulo kaj eligo de drawBoard (tabulo) alvokoj.
tabulo valoro drawBoard (tabulo) eligo
['', '', '', '', 'X',
'O', '', 'X', '', 'ho']
| |
X | | ho
| |
-----------
| |
X | ho |
| |
-----------
| |
| |
| |
['', 'Ho', 'ho', '', '',
'X', '', '', '', '']
| |
| |
| |
-----------
| |
| X |
| |
-----------
| |
Ho | ho |
| |
['', '', '', '', '',
'', '', '', '', '']
| |
| |
| |
-----------
| |
| |
| |
-----------
| |
| |
| |
['', 'X', 'X', 'X', 'X',
'X', 'X', 'X', 'X', 'X']
| |
X | X | X
| |
-----------
| |
X | X | X
| |
-----------
| |
X | X | X
| |
['0 ', '1', '2 ', '3', '4 ',
'5 ', '6', '7 ', '8', '9 ']
| |
7 | 8 | 9
| |
-----------
| |
4 | 5 | 6
| |
-----------
| |
1 | 2 | 3
| |
La dua al la lasta tabulo plena X ne povus esti okazinta (se ne la ikso ludanto saltis ĉiuj ho ludanto turnoj!) Kaj la lasta tabulo havas kordojn de ciferoj anstataŭ X kaj O, kiuj estas nevalida kordoj por la estraro. Sed la drawBoard () funkcio ne gravas. Ĝi simple presas la estraro parametro kiu estis aprobita. Komputilaj programoj nur fari precize kion vi diros al ili, eĉ se vi diros al ili la malbona aĵoj fari. Ni simple certigi tiujn nevalida kordoj ne estas metita en la pasinta lerta en la unua loko.

Lasi la Ludanto esti X aŭ O

  1. def inputPlayerLetter ():
  2. # Estu Estas la ludanto tipo kiu letero volas esti.
  3. # Returns lerta kun la ludanto leteron kiel la unua ero, kaj la komputilo la letero kiel la dua.
  4. letero =''
  5. dum ne (litero == 'X' aux leteron == 'O'):
  6. presi ('Ĉu vi volas esti X aŭ O?')
  7. letero = input (). supra ()
La inputPlayerLetter () estas simpla funkcio. Ĝi petas se la ludanto volas esti X aŭ O, kaj gardos petante la ludanto (kun la dum loop) ĝis la ludanto tipoj en X aŭ O. avizo on line 26 ke ni aŭtomate ŝanĝi la kordo revenis de la alvoko al enigo () por Majusklaj Literoj kun la supra () kordoj metodo.
La dum buklo la kondiĉo enhavas krampoj, kio signifas la esprimo ene de la krampoj estas taksita unue. Se la letero variablo estis fiksita al 'X', la esprimo estus taksi jene:
dum ne (litero == 'X' aux leteron == 'O'):
Al malsupren sago
dum ne ('X' == 'X' aux 'X' == 'O'):
Al malsupren sago
dum ne (Vera aŭ Falsa):
Al malsupren sago
dum ne (Vera):
Al malsupren sago
dum ne Vera:
Al malsupren sago
dum Falsa:
Kiel vi povas vidi, se litero havas la valoron 'X' aux 'ho', tiam la ciklo la kondiĉo estos Falsa kaj lasas la programon ekzekuto daŭrigi.
  1. # La unua elemento de la listo estas la ludanto letero, la dua estas la komputilo letero.
  2. se letero == 'X':
  3. reveno ['X', 'ho']
  4. alie:
  5. reveno ['ho', 'X']
Ĉi tiu funkcio redonas liston kun du erojn. La unua ero (tio estas, la kordoj ĉe indekso 0) estos la ludanto leteron, kaj la dua ero (tio estas, la kordoj ĉe indekso 1) estos la komputilo letero. Tiu se - alia aserto elektas la taŭga listo reveni.

Decidi Kiu Goes Unua

  1. def whoGoesFirst ():
  2. # Hazarde elekti la ludanto kiu iras unue.
  3. se random.randint (0, 1) == 0:
  4. reveno 'komputilo'
  5. alie:
  6. reveno 'ludanto'
La whoGoesFirst () funkcio ne virtuala monero klaki por determini kiu iras unue, la komputilo aŭ la ludanto. Anstataŭ flipping reala monero, ĉi tiu kodo ricevas hazarda nombro de ĉu 01 nomante la random.randint () funkcio. Se ĉi tiu funkcio alvokon resendas 0, la whoGoesFirst () funkcio redonas la ĉeno 'komputilo'. Alie, la funkcio redonas la ĉeno 'ludanto'. La kodo kiu nomas tiun funkcion uzos la reveno valoro scii kiu faros la unua movo de la ludo.

Petante la Ludanto al Ludu Denove

  1. def playAgain ():
  2. # Ĉi tiu funkcio redonas True se la ludanto volas ludi denove, alie False.
  3. presi ('Ĉu vi volas ludi denove? (jes aŭ ne)')
  4. revenu enigo (). malsupreniri (). startswith ('y')
La playAgain () funkcio petas la ludanto se ili volas ludi alian ludon. La funkcio redonas True se la ludanto tipoj en 'jes''JES''kaj' aŭ io, kiu komenciĝas per la litero Y. Por ĉiu alia respondo, la funkcio False. La ordo de la metodo alvokoj on line 45 estas grava. La reveno valoro de la alvoko al la enigo () funkcio estas ĉeno kiu havas lian suba () metodo nomata sur ĝi. La suba () metodo revenas alia cxeno (La minuskla kordoj) kaj kiu kordoj havas sian startswith () metodo nomata sur ĝi, pasante la argumento 'y'.
Ne estas ciklo, ĉar ni supozu, ke se la uzanto eniris ion krom ĉenon kiu komencas kun 'y', ili volas ĉesi ludi. Do, ni nur demandi la ludanto tuj.

Metante markon sur la Estraro

  1. def makeMove (tabulo, litero, movado):
  2. estraro [movado] = letero
La makeMove () funkcio estas tre simpla kaj nur unu linion. La parametroj estas lerta kun dek ŝnuroj nomita estraro, unu el la ludanto literoj (aŭ 'X' aux 'O') nomita letero, kaj loko sur la tabulo kie tiu ludanto volas iri (kiu estas entjero de 1 ĝis 9 ) nomata movado.
Sed atendu dua. Vi eble pensas ke tiu funkcio ne faras tiel. Ĝi ŝajnas ŝanĝi unu el la eroj en la tabulo listo al la valoro en letero. Sed ĉar ĉi-kodo estas en funkcio, la estraro parametro estos forgesita, kiam ni eliras tiun funkcion kaj forlasas la funkcia medion.
Fakte, tiu ne estas la kazo. Ĉi tiu estas ĉar lertaj (kaj vortarojn) estas specialaj kiam vi pasos ilin kiel argumentoj al funkcioj. Ĉi tiu estas ĉar vi pasas referenco al la listo (aux vortaro) kaj ne la listo mem. Ni lernu pri la diferenco inter lertaj kaj listo referencoj.

Listo Referencoj

Provu eniri la sekva en la ŝelon:
>>> Spamado = 42
>>> Fromaĝo = spamado
>>> Spamado = 100
>>> Spamado
100
>>> Fromaĝo
42
Tiu makes sense de kion ni scias ĝis nun. Ni asigni 42 al la spamado variablo, kaj poste ni kopii la valoron en spamado kaj atribui gxin al la variablo fromaĝo. Kiam ni poste ŝanĝu la valoron en spamado al 100, ĉi tio ne tuŝas la valoron en fromaĝo. Ĉi tiu estas ĉar spamado kaj fromaĝo estas malsamaj variabloj kiuj stokas malsamajn valorojn.
Sed lertaj ne funkcias tiel. Kiam vi atribuas liston al variablo kun la signo =, vi estas vere atribuante listo referenco al la variablo. Referenco estas valoro kiu punktoj al iu iom de datumoj, kaj lerta aludo estas valoro kiu notas al listo. Jen iom da kodo kiu faros tiun pli facile komprenas. Tajpi ĉi tiu enen la ŝelo:
>>> Spamado = [0, 1, 2, 3, 4, 5]
>>> Fromaĝo = spamado
>>> Fromaĝo [1] = 'Saluton!'
>>> Spamado
[0, 'Saluton!', 2, 3, 4, 5]
>>> Fromaĝo
[0, 'Saluton!', 2, 3, 4, 5]
Ĉi aspektas nepara. La kodo nur ŝanĝis la fromaĝo listo, sed ŝajnas ke ambaŭ la fromaĝon kaj spamado lertaj ŝanĝis.
Rimarku ke la linio fromaĝo = spamado kopias la listo referenco en spamado al fromaĝo, anstataŭ kopii la listo valoro mem. Tio estas ĉar la valoro stokita en la spam variablo estas listo referenco, kaj ne la listo valoro mem. Tio signifas ke la valoroj stokitaj en ambaŭ spamado kaj fromaĝo rilatas al la sama listo. Estas nur unu listo ĉar la listo ne estis kopiitaj, la referenco al la listo estis kopiitaj. Do kiam vi modifas fromaĝo en la fromaĝo [1] = 'Saluton!' Linio, vi modifi la sama listo, ke spamado referencas al. Jen kial spamado ŝajnas havi la saman liston valoro kiu fromaĝo faras.
Memoru ke variabloj estas kiel skatoloj kiuj enhavas valorojn. Listo variabloj ne reale enhavi lertaj tute ne, ili enhavas referencojn al listoj. Jen kelkaj bildoj kiuj klarigas kio okazas en la kodo kiun vi ĵus tajpis en:

Figuro 10-5: Variabloj faru nenian vendejo lertaj, sed prefere referencojn al listoj.
En la unua linio, la efektiva listo ne enhavis en la spam variablo sed referenco al la listo. La listo mem ne stokas en ajna variablo.

Figuro 10-6: Du variabloj stoki du aludoj al la sama listo.
Kiam vi atribuas la referenco en spamado al fromaĝo, la fromaĝo variablo enhavas kopion de la referenco en spamado. Nun ambaŭ fromaĝo kaj spamado rilatas al la sama listo.

Figuro 10-7: Ŝanĝi la listo ŝanĝas ĉiuj variabloj kun referencoj al tiu listo.
Kiam vi ŝanĝas la listo ke fromaĝo raportas al la listo ke spamado referencas al ĝi ankaŭ ŝanĝis ĉar ili estas la sama listo. Se vi volas spamado kaj fromaĝo stoki du malsamaj listoj, vi devas krei du malsamajn listoj anstataŭ kopii referenco:
>>> Spamado = [0, 1, 2, 3, 4, 5]
>>> Fromaĝo = [0, 1, 2, 3, 4, 5]
En la supra ekzemplo, spamado kaj fromaĝo havas du malsamajn listoj stokitaj en ili (eĉ se tiuj listoj estas identaj en enhavo). Nun se vi modifas unu el la listoj, ĝi ne tuŝos la aliajn ĉar spamado kaj fromaĝo havas referencojn al du malsamaj listoj:
>>> Spamado = [0, 1, 2, 3, 4, 5]
>>> Fromaĝo = [0, 1, 2, 3, 4, 5]
>>> Fromaĝo [1] = 'Saluton!'
>>> Spamado
[0, 1, 2, 3, 4, 5]
>>> Fromaĝo
[0, 'Saluton!', 2, 3, 4, 5]
Figuro 10-8 montras kiel la du referencoj punkto al du malsamaj listoj:

Figuro 10-8: Du variabloj ĉiu stokante referencojn al du malsamaj listoj.
Vortaroj labori en la sama maniero. Vortaroj ne stokas valoroj, ili stokas referencojn al valoroj. Tiuj estas nomataj vortaro referencoj (aŭ vi povas voki tiel vortaro referencoj kaj listo referencoj por la ebenaĵo nomo, "referenco".)

Uzanta Listo Referencoj en makeMove ()

Ni reiru al la makeMove () funkcio:
  1. def makeMove (tabulo, litero, movado):
  2. estraro [movado] = letero
Kiam ni pasis listo valoro kiel la argumento por la estraro parametro, la funkcia loka variablo estas kopio de la referenco, ne kopion de la listo mem. La letero kaj movado parametroj estas kopioj de la kordo kaj entjero valoroj kiujn ni pasas. Ĉar ili estas kopioj, se ni modifi literomovi en ĉi tiu funkcio, la originala variabloj ni uzis kiam ni nomas makeMove () ne estus modifita. Nur la kopioj estus modifita.
Sed kopion de la referenco ankoraŭ aludas al la sama listo, ke la originala referenco referencas al. Do, se ni faras ŝanĝojn al tabulo en tiu funkcio, la originala listo estas modifita. Kiam ni eliras la makeMove () funkcio, la kopio de la referenco estas forgesita kune kun la aliaj parametroj. Sed ĉar ni efektive ŝanĝanta la originala listo, tiuj ŝanĝoj restas post ni eliras la funkcio. Jen kiel la makeMove () funkcio modifas la listo ke referenco de ĝi pasis.

Kontrolanta se la ludanto gajnis

  1. def isWinner (bo, le):
  2. # Donita estraro kaj ludanto la letero, ĉi tiu funkcio redonas Vera se tiu ludanto venkis.
  3. # Ni uzas bo anstataŭ estraro kaj le anstataŭ letero do ni ne devas tajpi tiel.
  4. reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ # trans la supron
  5. (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ # trans la mezo
  6. (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ # trans la fundo
  7. (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ # malsupren la maldekstra flanko
  8. (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ # malsupren la mezo
  9. (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ # malsupren la dekstra flanko
  10. (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ # diagonalo
  11. (Bo [9] == le kaj bo [5] == le kaj bo [1] == le)) # diagonalo
Linioj 53 al 60 en la isWinner () funkcio estas fakte tre longaj se aserto. Ni uzas bo kaj le por la estraro kaj litero parametroj por ke ni havas malpli por tajpi en ĉi tiu funkcio. (Ĉi tiu estas lertaĵo programistoj foje uzas por redukti la kvanton ili bezonas por tajpi. Nepre aldoni komenton, ke tio klarigas tiun kvankam, alikaze vi povas forgesi kio bo kaj le estas devigataj volas diri.)
Estas ok eblaj manieroj por gajni ĉe Tic Tac Toe. Vi povas havi linion trans la supro, mezo, kaj malsupre. Aŭ vi povas havi linion por la maldekstra, meza, aŭ dekstren. Kaj vi povas havi iu el la du diagonaloj. Notu ke ĉiu linio de la kondiĉo kontrolas se la tri spacoj estas egala al la letero provizita (kombinita kun la kaj telefonisto) kaj ni uzas la operatoro kombini la ok malsamaj manieroj por gajni. Tio signifas nur unu el la ok manieroj devas esti vera por ke ni diru ke la ludanto kiu posedas leteron en le estas la venkinto.
Ni pretendi ke le estas 'ho', kaj la estraro aspektas jene:
| |
X | |
| |
-----------
| |
| X |
| |
-----------
| |
Ho | ho | ho
| |
Se la estraro aspektas tiel, tiam bo devas esti egala al ['', 'ho', 'ho', 'ho', '', 'X', '', 'X', '', '']. Jen kiel la esprimo post la reveno ŝlosilvorto on line 53 povus taksi:
    Jen estas la esprimo kiel ĝi estas en la kodo:
  1. reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ
  2. (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ
  3. (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ
  4. (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ
  5. (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ
  6. (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ
  7. (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ
  8. (Bo [9] == le kaj bo [5] == le kaj bo [1] == le))
Al malsupren sago
    Unua Python anstataŭos la variablo bo kun la valoron ene de ĝi:
  1. reveno (('X' == 'ho' kaj '' == 'ho' kaj '' == 'O') aŭ
  2. ('' == 'Ho' kaj 'X' == 'ho' kaj '' == 'O') aŭ
  3. ('O' == 'ho' kaj 'O' == 'ho' kaj 'O' == 'O') aŭ
  4. ('X' == 'ho' kaj '' == 'ho' kaj 'O' == 'O') aŭ
  5. ('' == 'Ho' kaj 'X' == 'ho' kaj 'O' == 'O') aŭ
  6. ('' == 'Ho' kaj '' == 'ho' kaj 'O' == 'O') aŭ
  7. ('X' == 'ho' kaj 'X' == 'ho' kaj 'O' == 'O') aŭ
  8. ('' == 'Ho' kaj 'X' == 'ho' kaj 'O' == 'O'))
Al malsupren sago
    Tuj poste, Python taksos ĉiuj tiuj == komparoj ene la parantezoj al Bulea valoro:
  1. reveno ((Falsa kaj Falsa kaj Falsa) aŭ
  2. (Falsa kaj Falsa kaj Falsa) aŭ
  3. (Vera kaj Vera kaj Vera) aŭ
  4. (Falsa kaj Falsa kaj Vera) aŭ
  5. (Falsa kaj Falsa kaj Vera) aŭ
  6. (Falsa kaj Falsa kaj Vera) aŭ
  7. (Falsa kaj Falsa kaj Vera) aŭ
  8. (Falsa kaj Falsa kaj Vera))
Al malsupren sago
    Tiam la Python interpretisto taksos ĉiuj tiuj esprimoj ene la krampoj:
  1. reveno ((Falsa) aŭ
  2. (Falsa) aŭ
  3. (Vera) aŭ
  4. (Falsa) aŭ
  5. (Falsa) aŭ
  6. (Falsa) aŭ
  7. (Falsa) aŭ
  8. (Falsa))
Al malsupren sago
    Ekde nun ekzistas nur unu valoron ene de la krampoj, ni povas forigi ilin:
  1. reveni (Falsa aŭ
  2. Falsa aŭ
  3. Vera aŭ
  4. Falsa aŭ
  5. Falsa aŭ
  6. Falsa aŭ
  7. Falsa aŭ
  8. Falsa)
Al malsupren sago
    Nun ni taksi la esprimon kiu estas connecter de ĉiuj tiuj operatoroj:
  1. reveni (Vera)
Al malsupren sago
    Denove, ni forigi la krampojn, kaj ni restas kun tiu valoro:
  1. revenu Vera
Do donita tiuj valoroj por bo kaj le, la esprimo estus taksi al True. Memoru ke la valoro de le aferoj. Se le estas 'ho' kaj X gajnis la ludo, la isWinner () revenus False.

Duobligante la Estraro Datumoj

  1. def getBoardCopy (tabulo):
  2. # Faru duobligita de la estraro listo kaj reveni al ĝi la duobligita.
  3. dupeBoard = []

  4. por i en tabulo:
  5. dupeBoard.append (i)

  6. revenu dupeBoard
La getBoardCopy () funkcio estas ĉi tie por ke ni povas facile fari kopion de donita 10-kordoj listo kiu reprezentas Tic Tac Toe tabulo en nia ludo. Estas fojoj, ke ni volas nian AI algoritmo por fari temporal modifojn al portempa kopion de la tabulo sen ŝanĝi la originalan tabulo. En tiu kazo, ni nomas tiun funkcion por fari kopion de la estraro-listo. La efektiva nova listo estas kreita la linio 64, kun la malplenan liston krampoj [].
Linio 64 reale kreas tute nova listo kaj stokas referenco al tio en dupeBoard. Sed la listo stokitaj en dupeBoard estas nur malplenan liston. La por buklo iros tra la tabulo parametro, appending kopion de la kordo valoroj en la originala tabulo al nia duplikatajn tabulo. Fine, post la ciklo, ni redonos la dupeBoard variablo estas referenco al la duobligitaj tabulo. Do vi povas vidi kiel la getBoardCopy () funkcio estas kreskigi la kopion de la originala tabulo kaj revenante referenco al tiu nova estraro, ne la originalo.

Kontrolanta se Spaco en la Estraro estas Libera

  1. def isSpaceFree (tabulo, movado):
  2. # Reveno vera se la pasinta movado estas libera pri la pasinta estraro.
  3. revenu estraro [movado] == ''
Tiu estas simpla funkcio kiu, donita Tic Tac Toe estraro kaj ebla movo revenos se tiu movado estas disponebla aŭ ne. Memoru ke la libera spacoj en nia estraro lertaj estas markitaj kiel sola spaco kordoj.

Lasi la Ludanto Entajpu Ilia Move

  1. def getPlayerMove (tabulo):
  2. # Lasu la ludanto tipo en sia movado.
  3. movi = ''
  4. dum movado ne en '1 2 3 4 5 6 7 8 9 '. split () aŭ ne isSpaceFree (tabulo, int (movado)):
  5. print ("Kio estas via sekva movo? (1-9) ')
  6. movi = input ()
  7. reveno int (movado)
La getPlayerMove () funkcio petas la ludanton eniri la nombro por la spaco ili volas movi. La funkcio certigas ke ili eniras spacon kiu estas valida spaco (entjero 1 ĝis 9). Ĝi ankaŭ kontrolas ke la spaco kiu estas ne jam farita, donita la Tic Tac Toe tabulo pasis al la funkcio en la tabulo parametro.
La du linioj de kodo ene la tempo buklo simple peti la ludanto eniri numeron de 1 ĝis 9. La ciklo de kondiĉo gardos looping, tio estas, ĝi konservos petante la ludanto por spaco, tiel longe kiel la kondiĉo estas vera. La kondiĉo estas True se iu el la esprimoj en la maldekstradekstra flanko de la ŝlosilvorto estas True.
La esprimo sur la maldekstra flanko ĉekojn se la movado ke la ludanto eniris egalas '1 ', '2', '3 ', kaj tiel plu ĝis '9' kreante liston kun tiuj kordoj (kun la divido () metodo) kaj kontrolado se movo estas en ĉi tiu listo. '1 2 3 4 5 6 7 8 9 '. split () taksas esti la sama kiel ['1', '2 ', '3', '4 ',' 5 ', '6', '7 ', '8', '9 '], sed ĝi pli facile tajpi.
La esprimo sur la dekstra flanko kontrolas se la movado ke la ludanto eniris estas libera spaco sur la tabulo. Ĝi kontrolas ĉi nomante la isSpaceFree () funkcio ni simple skribis. Memoru ke isSpaceFree () revenos True se la movado ni pasas estas disponebla en la tabulo. Notu ke isSpaceFree () atendas entjero por movado, do ni uzu la int () funkcion por taksi entjero formo de movado.
Ni aldonu la ne operatoroj al ambaŭ flankoj por ke la kondiĉo estos Vera kiam ambaŭ de ĉi tiuj kondiĉoj estas neplenumita. Tio kaŭzas la buklo demandi la ludanto denove kaj denove ĝis ili eniras taŭgan movado.
Fine, sur linio 81, ni redonos la entjero formo de kiom movado la ludanto eniris. Memoru ke enigo () redonas kordoj, do ni volas uzi la int () funkcion por taksi la kordo kiel entjero.

Mallonga cirkvito Taksado

Vi eble rimarkis estas ebla problemo en nia getPlayerMove () funkcio. Kio se la ludanto tajpita en 'X' aux iu alia ne-entjero kordoj? La movado ne en '1 2 3 4 5 6 7 8 9 '. Split () esprimon sur la maldekstra flanko de aŭ revenus Falsa kiel atendis, kaj tiam ni devus taksi la esprimo sur la dekstra flanko de la operatoro. Sed kiam ni pasi 'X' (kiu estus la valoron en movado) al la int () funkcion, int ('X') donus al ni eraron. Ĝi donas al ni tiu eraro cxar la int () funkcio povas nur preni kordoj de numero karakteroj, kiel '9 'aŭ '0', ne kordoj kiel 'X'.
Kiel ekzemplo de ĉi tiu tipo de eraro, provu eniri ĉi tiu enen la ŝelo:
>>> Int ('42 ')
42
>>> Int ('X')

Traceback (plej lasta alvoko lasta):
Dosieron "<pyshell#3>", linio 1, en <module>
_int_ ('X')
ValueError: nevalida laŭvorta por int () kun bazo 10: 'X'
Sed kiam vi ludas nian Tic Tac Toe ludo kaj provi eniri 'X' por via movado, tiu eraro ne okazas. La kialo estas pro la dum buklo la kondiĉo estas mallonga circuited.
Kio mallonga circuiting signifas ke ĉar la esprimo sur la maldekstra flanko de la aŭ ŝlosilvorto (movi ne en '1 2 3 4 5 6 7 8 9 '. Split ()) taksas al Vera, la Python interpretisto scias, ke la tuta esprimo will evaluate to True . It doesn't matter if the expression on the right side of the or keyword evaluates to True or False , because only one value on the side of the or operator needs to be True .
Think about it: The expression True or False evaluates to True and the expression True or True also evaluates to True . If the value on the left side is True , it doesn't matter what the value is on the right side. So Python stops checking the rest of the expression and doesn't even bother evaluating the not isSpaceFree(board, int(move)) part. This means the int() and the isSpaceFree() functions are never called as long as move not in '1 2 3 4 5 6 7 8 9'.split() is True .
This works out well for us, because if the expression on the right side is True then move is not a string in number form. That would cause int() to give us an error. The only times move not in '1 2 3 4 5 6 7 8 9'.split() evaluates to False are when move is not a single-digit string. In that case, the call to int() would not give us an error.

An Example of Short-Circuit Evaluation

Here's a short program that gives a good example of short-circuiting. Open a new file in the IDLE editor and type in this program, save it as truefalsefizz.py , then press F5 to run it. Don't add the numbers down the left side of the program, those just appear in this book to make the program's explanation easier to understand. The function calls in bold are the function calls that are evaluated.
truefalsefizz.py
This code can be downloaded from http://inventwithpython.com/truefalsefizz.py Se vi ricevas erarojn post tajpi tiun kodon en, kompari ĝin al la libro kodo kun la linio malsamoj ilo en http://inventwithpython.com/diff aŭ retpoŝtu la aŭtoro en al@inventwithpython.com
  1. def TrueFizz(message):
  2. print(message)
  3. return True

  4. def FalseFizz(message):
  5. print(message)
  6. return False

  7. if FalseFizz('Cats') or TrueFizz('Dogs') :
  8. print('Step 1')

  9. if TrueFizz('Hello') or TrueFizz('Goodbye'):
  10. print('Step 2')

  11. if TrueFizz('Spam') and TrueFizz('Cheese') :
  12. print('Step 3')

  13. if FalseFizz('Red') and TrueFizz('Blue'):
  14. print('Step 4')
When you run this program, you can see the output (the letters on the left side have been added to make the output's explanation easier to understand):
  1. Cats
  2. Hundoj
  3. Step 1
  4. Saluton
  5. Paŝo 2
  6. Spam
  7. Cheese
  8. Paŝo 3
  9. Red
This small program has two functions: TrueFizz() and FalseFizz() . TrueFizz() will display a message and return the value True , while FalseFizz() will display a message and return the value False . This lets us determine if these functions are being called, or if these functions are being skipped due to short-circuiting.

The First if Statement (Cats and Dogs)

The first if statement on line 9 in our small program will first evaluate TrueFizz() . We know this happens because Cats is printed to the screen (on line A in the output). The entire expression could still be True if the expression to the right of the or keyword is True . So the call TrueFizz('Dogs') on line 9 is evaluated, Dogs is printed to the screen (on line B in the output) and True is returned. On line 9, the if statement's condition evaluates to False or True , which in turn evaluates to True . 'Step 1' is then printed to the screen. No short-circuiting took place for this expression's evaluation.

The Second if Statement (Hello and Goodbye)

The second if statement on line 12 also has short-circuiting. This is because when we call TrueFizz('Hello') on line 12, it prints Hello (see line D in the output) and returns True . The Python interpreter doesn't call TrueFizz('Goodbye') because it doesn't matter what is on the right side of the or keyword. You can tell it is not called because Goodbye is not printed to the screen. The if statement's condition is True , so 'Step 2' is printed to the screen (see line E).

The Third if Statement (Spam and Cheese)

The third if statement on line 15 does not have short-circuiting. The call to TrueFizz('Spam') returns True , but we do not know if the entire condition is True or False because of the and operator. So Python will call TrueFizz('Cheese') , which prints Cheese and returns True . The if statement's condition is evaluated to True and True , which in turn evaluates to True . Because the condition is True , 'Step 3' is printed to the screen (see line H).

The Fourth if Statement (Red and Blue)

The fourth if statement on line 18 does have short-circuiting. The FalseFizz('Red') call prints Red (see line I in the output) and returns False . Because the left side of the and keyword is False , it does not matter if the right side is True or False , the condition will evaluate to False anyway. So TrueFizz('Blue') is not called and Blue does not appear on the screen. Because the if statement's condition evaluated to False , 'Step 4' is also not printed to the screen.
Short-circuiting can happen for any expression that includes the Boolean operators and and or . It is important to remember that this can happen; otherwise you may find that some function calls in the expression are never called and you will not understand why.

How the Code Works: Lines 83 to 94

Choosing a Move from a List of Moves

  1. def chooseRandomMoveFromList(board, movesList):
  2. # Returns a valid move from the passed list on the passed board.
  3. # Returns None if there is no valid move.
  4. possibleMoves = []
  5. for i in movesList:
  6. if isSpaceFree(board, i):
  7. possibleMoves.append(i)
The chooseRandomMoveFromList() function will be of use to us when we are implementing the code for our AI. The first parameter board is the 10-string list that represents a Tic Tac Toe board. The second parameter movesList is a list of integers that represent possible moves. For example, if movesList is [1, 3, 7, 9] , that means we should return the number for one of the corner spaces on the board.
The chooseRandomMoveFromList() function will then choose one of those moves from the possibleMoves list. It also makes sure that the move that it chooses is not already taken. To do this, we create a blank list and assign it to possibleMoves . The for loop will go through the list of moves passed to this function in movesList . If that move is available (which we figure out with a call to isSpaceFree() ), then we add it to possibleMoves with the append() method.
  1. if len(possibleMoves) != 0:
  2. return random.choice(possibleMoves)
  3. alie:
  4. return None
At this point, the possibleMoves list has all of the moves that were in movesList that are also free spaces on the board represented by board . If the list is not empty, then there is at least one possible move that can be made on the board.
This list might be empty. For example, if movesList was [1, 3, 7, 9] but the board represented by the board parameter had all the corner spaces already taken, the possibleMoves list would have been empty.
If possibleMoves is empty, then len(possibleMoves) will evaluate to 0 and the code in the else-block will execute. Notice that it returns something called None .

The None Value

None is a special value that you can assign to a variable. The None value represents the lack of a value. None is the only value of the data type NoneType. (Just like the Boolean data type has only two values, the NoneType data type has only one value, None .) It can be very useful to use the None value when you need a value that means "does not exist". For example, say you had a variable named quizAnswer which holds the user's answer to some True-False pop quiz question. You could set quizAnswer to None if the user skipped the question and did not answer it. Using None would be better because if you set it to True or False before assigning the value of the user's answer, it may look like the user gave an answer the question even though they didn't.
Calls to functions that do not return anything (that is, they exit by reaching the end of the function and not from a return statement) will evaluate to None . The None value is written without quotes and with a capital "N" and lowercase "one".

How the Code Works: Lines 96 to 187

Creating the Computer's Artificial Intelligence

  1. def getComputerMove(board, computerLetter):
  2. # Given a board and the computer's letter, determine where to move and return that move.
  3. if computerLetter == 'X':
  4. playerLetter = 'O'
  5. alie:
  6. playerLetter = 'X'
The getComputerMove() function is where our AI will be coded. The arguments are a Tic Tac Toe board (in the board parameter) and which letter the computer is (either 'X' or 'O' in the computerLetter parameter). The first few lines simply assign the other letter to a variable named playerLetter . This lets us use the same code, no matter who is X and who is O. This function will return the integer 1 to 9 that represents which space the computer will move.
Remember how our algorithm works: First, see if there is a move the computer can make that will win the game. If there is, take that move. Otherwise, go to the second step.
Second, see if there is a move the player can make that will cause the computer to lose the game. If there is, we should move there to block the player. Otherwise, go to the third step.
Third, check if any of the corner spaces (spaces 1, 3, 7, or 9) are free. (We always want to take a corner piece instead of the center or a side piece.) If no corner piece is free, then go to the fourth step.
Fourth, check if the center is free. If so, move there. If it isn't, then go to the fifth step.
Fifth, move on any of the side pieces (spaces 2, 4, 6, or 8). There are no more steps, because if we have reached this step then the side spaces are the only spaces left.

The Computer Checks if it Can Win in One Move

  1. # Here is our algorithm for our Tic Tac Toe AI:
  2. # First, check if we can win in the next move
  3. for i in range(1, 10):
  4. copy = getBoardCopy(board)
  5. if isSpaceFree(copy, i):
  6. makeMove(copy, computerLetter, i)
  7. if isWinner(copy, computerLetter):
  8. return i
More than anything, if the computer can win in the next move, the computer should immediately make that winning move. We will do this by trying each of the nine spaces on the board with a for loop. The first line in the loop (line 106) makes a copy of the board list. We want to make a move on the copy of the board, and then see if that move results in the computer winning. We don't want to modify the original Tic Tac Toe board, which is why we make a call to getBoardCopy() . We check if the space we will move is free, and if so, we move on that space and see if this results in winning. If it does, we return that space's integer.
If moving on none of the spaces results in winning, then the loop will finally end and we move on to line 112.

The Computer Checks if the Player Can Win in One Move

  1. # Check if the player could win on his next move, and block them.
  2. for i in range(1, 10):
  3. copy = getBoardCopy(board)
  4. if isSpaceFree(copy, i):
  5. makeMove(copy, playerLetter, i)
  6. if isWinner(copy, playerLetter):
  7. return i
At this point, we know we cannot win in one move. So we want to make sure the human player cannot win in one more move. The code is very similar, except on the copy of the board, we place the player's letter before calling the isWinner() function. If there is a position the player can move that will let them win, the computer should move there to block that move.
If the human player cannot win in one more move, the for loop will eventually stop and execution continues on to line 120.

Checking the Corner, Center, and Side Spaces (in that Order)

  1. # Try to take one of the corners, if they are free.
  2. move = chooseRandomMoveFromList(board, [1, 3, 7, 9])
  3. if move != None:
  4. return move
Our call to chooseRandomMoveFromList() with the list of [1, 3, 7, 9] will ensure that it returns the integer for one of the corner spaces. (Remember, the corner spaces are represented by the integers 1 , 3 , 7 , and 9 .) If all the corner spaces are taken, our chooseRandomMoveFromList() function will return the None value. In that case, we will move on to line 125.
  1. # Try to take the center, if it is free.
  2. if isSpaceFree(board, 5):
  3. return 5
If none of the corners are available, we will try to move on the center space if it is free. If the center space is not free, the execution moves on to line 129.
  1. # Move on one of the sides.
  2. return chooseRandomMoveFromList(board, [2, 4, 6, 8])
This code also makes a call to chooseRandomMoveFromList() , except we pass it a list of the side spaces ( [2, 4, 6, 8] ). We know that this function will not return None , because the side spaces are the only spaces we have not yet checked. This is the end of the getComputerMove() function and our AI algorithm.

Checking if the Board is Full

  1. def isBoardFull(board):
  2. # Return True if every space on the board has been taken. Otherwise return False.
  3. for i in range(1, 10):
  4. if isSpaceFree(board, i):
  5. return False
  6. return True
The last function we will write is isBoardFull() , which returns True if the 10-string list board argument it was passed has an 'X' or 'O' in every index (except for index 0 , which is just a placeholder that we ignore). If there is at least one space in board that is set to a single space ' ' then it will return False .
The for loop will let us check spaces 1 through 9 on the Tic Tac Toe board. (Remember that range(1, 10) will make the for loop iterate over the integers 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , and 9 .) As soon as it finds a free space in the board (that is, when isSpaceFree(board, i) returns True ), the isBoardFull() function will return False .
If execution manages to go through every iteration of the loop, we will know that none of the spaces are free. So at that point (on line 137), we will execute return True .

The Start of the Game

  1. print('Welcome to Tic Tac Toe!')
Line 140 is the first line that isn't inside of a function, so it is the first line of code that is executed when we run this program.
  1. dum Vera:
  2. # Reset the board
  3. theBoard = [' '] * 10
This while loop has True for the condition, so that means we will keep looping in this loop until we encounter a break statement. Line 144 sets up the main Tic Tac Toe board that we will use, named theBoard . It is a 10-string list, where each string is a single space ' '. Remember the little trick using the multiplication operator with a list to replicate it: [' '] * 10 . That evaluates to [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] , but is shorter for us to type [' '] * 10 .

Deciding the Player's Mark and Who Goes First

  1. playerLetter, computerLetter = inputPlayerLetter()
The inputPlayerLetter() function lets the player type in whether they want to be X or O. The function returns a 2-string list, either ['X', 'O'] or ['O', 'X'] . We use the multiple assignment trick here that we learned in the Hangman chapter. If inputPlayerLetter() returns ['X', 'O'] , then playerLetter is set to 'X' and computerLetter is set to 'O'. If inputPlayerLetter() returns ['O', 'X'] , then playerLetter is set to 'O' and computerLetter is set to 'X'.
  1. turn = whoGoesFirst()
  2. print('The ' + turn + ' will go first.')
  3. gameIsPlaying = True
The whoGoesFirst() function randomly decides who goes first, and returns either the string 'player' or the string 'computer' . On line 147, we tell the player who will go first. The gameIsPlayer variable is what we will use to keep track of whether the game has been won, lost, tied or if it is the other player's turn.

Running the Player's Turn

  1. while gameIsPlaying:
This is a loop that will keep going back and forth between the player's turn and the computer's turn, as long as gameIsPlaying is set to True .
  1. if turn == 'player':
  2. # Player's turn.
  3. drawBoard(theBoard)
  4. move = getPlayerMove(theBoard)
  5. makeMove(theBoard, playerLetter, move)
The turn variable was originally set by the whoGoesFirst() call on line 146). It is either set to 'player' or 'computer' . If turn contains the string 'computer' , then the condition is False and execution will jump down to line 169.
The first thing we do when it is the player's turn (according to the flow chart we drew at the beginning of this chapter) is show the board to the player. Calling the drawBoard() and passing the theBoard variable will print the board on the screen. We then let the player type in his move by calling our getPlayerMove() function, and set the move on the board by calling our makeMove() function.
  1. if isWinner(theBoard, playerLetter):
  2. drawBoard(theBoard)
  3. print('Hooray! You have won the game!')
  4. gameIsPlaying = False
Now that the player has made his move, our program should check if they have won the game with this move. If the isWinner() function returns True , we should show them the winning board (the previous call to drawBoard() shows the board before they made the winning move) and print a message telling them they have won.
Then we set gameIsPlaying to False so that execution does not continue on to the computer's turn.
  1. alie:
  2. if isBoardFull(theBoard):
  3. drawBoard(theBoard)
  4. print('The game is a tie!')
  5. rompi
If the player did not win with his last move, then maybe his last move filled up the entire board and we now have a tie. In this else-block, we check if the board is full with a call to the isBoardFull() function. If it returns True , then we should draw the board by calling drawBoard() and tell the player a tie has occurred. The break statement will break us out of the while loop we are in and jump down to line 186.

Running the Computer's Turn

  1. alie:
  2. turn = 'computer'
If the player has not won or tied the game, then we should just set the turn variable to 'computer' so that when this while loop loops back to the start it will execute the code for the computer's turn.
  1. alie:
If the turn variable was not set to 'player' for the condition on line 151, then we know it is the computer's turn and the code in this else-block will execute. This code is very similar to the code for the player's turn, except the computer does not need the board printed on the screen so we skip calling the drawBoard() function.
  1. # Computer's turn.
  2. move = getComputerMove(theBoard, computerLetter)
  3. makeMove(theBoard, computerLetter, move)
The code above is almost identical to the code for the player's turn on lines 154 and 155.
  1. if isWinner(theBoard, computerLetter):
  2. drawBoard(theBoard)
  3. print('The computer has beaten you! You lose.')
  4. gameIsPlaying = False
We want to check if the computer won with its last move. The reason we call drawBoard() here is because the player will want to see what move the computer made to win the game. We then set gameIsPlaying to False so that the game does not continue. Notice that lines 174 to 177 are almost identical to lines 157 to 160.
  1. alie:
  2. if isBoardFull(theBoard):
  3. drawBoard(theBoard)
  4. print('The game is a tie!')
  5. rompi
The lines of code above are identical to the code on lines 162 to 165. The only difference is this is a check for a tied game after the computer has moved, instead of the player.
  1. alie:
  2. turn = 'player'
If the game is neither won nor tied, it then becomes the player's turn. There are no more lines of code inside the while loop, so execution would jump back to the while statement on line 150.
  1. if not playAgain():
  2. rompi
These lines of code are located immediately after the while-block started by the while statement on line 150. Remember, we would only exit out of that while loop if it's condition (the gameIsPlaying variable) was False . gameIsPlaying is set to False when the game has ended, so at this point we are going to ask the player if they want to play again.
Remember, when we evaluate the condition in this if statement, we call the playAgain() function which will let the user type in if they want to play or not. playAgain() will return True if the player typed something that began with a 'y' like 'yes' or 'y' . Otherwise playAgain() will return False .
If playAgain() returns False , then the if statement's condition is True (because of the not operator that reverses the Boolean value) and we execute the break statement. That breaks us out of the while loop that was started on line 142. But there are no more lines of code after that while-block, so the program terminates.

Summary: Creating Game-Playing Artificial Intelligences

Creating a program that can play a game comes down to carefully considering all the possible situations the AI can be in and how it should respond in each of those situations. Our Tic Tac Toe AI is fairly simple because there are not many possible moves in Tic Tac Toe compared to a game like chess or checkers.
Our AI simply blocks the players move if the player is about to win. If the player is not about to win, it checks if any possible move can allow itself to win. Then the AI simply chooses any available corner space, then the center space, then the side spaces. This is a simple algorithm for the computer to follow.
The key to implementing our AI is by making copies of the board data and simulating moves on the copy. That way, the AI code can see if a move will result in a win or loss. Then the AI can make that move on the real board. This type of simulation is very effective at predicting what is a good move or not.

Nenhum comentário:

Postar um comentário