Temoj Kovritaj dum tiu ĉapitro:
- Artefarita Inteligenteco
- Listo Referencoj
- Mallonga cirkvito Taksado
- La Neniu Valoro
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
Ĉ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
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
- # Tic Tac Toe
- importi hazarda
- def drawBoard (tabulo):
- # Ĉi tiu funkcio presas el la estraro ke ĝi pasis.
- # "Tabulo" estas listo de 10 kordoj reprezentantaj la tabulo (ignori indekso 0)
- print ('| |')
- print ('' + estraro [7] + '|' + estraro [8] + '|' + estraro [9])
- print ('| |')
- print ('-----------')
- print ('| |')
- print ('' + estraro [4] + '|' + estraro [5] + '|' + estraro [6])
- print ('| |')
- print ('-----------')
- print ('| |')
- print ('' + estraro [1] + '|' + estraro [2] + '|' + estraro [3])
- print ('| |')
- def inputPlayerLetter ():
- # Estu Estas la ludanto tipo kiu letero volas esti.
- # Returns lerta kun la ludanto leteron kiel la unua ero, kaj la komputilo la letero kiel la dua.
- letero =''
- dum ne (litero == 'X' aux leteron == 'O'):
- presi ('Ĉu vi volas esti X aŭ O?')
- letero = input (). supra ()
- # La unua elemento de la listo estas la ludanto letero, la dua estas la komputilo letero.
- se letero == 'X':
- reveno ['X', 'ho']
- alie:
- reveno ['ho', 'X']
- def whoGoesFirst ():
- # Hazarde elekti la ludanto kiu iras unue.
- se random.randint (0, 1) == 0:
- reveno 'komputilo'
- alie:
- reveno 'ludanto'
- def playAgain ():
- # Ĉi tiu funkcio redonas True se la ludanto volas ludi denove, alie False.
- presi ('Ĉu vi volas ludi denove? (jes aŭ ne)')
- revenu enigo (). malsupreniri (). startswith ('y')
- def makeMove (tabulo, litero, movado):
- estraro [movado] = letero
- def isWinner (bo, le):
- # Donita estraro kaj ludanto la letero, ĉi tiu funkcio redonas Vera se tiu ludanto venkis.
- # Ni uzas bo anstataŭ estraro kaj le anstataŭ letero do ni ne devas tajpi tiel.
- reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ # trans la supron
- (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ # trans la mezo
- (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ # trans la fundo
- (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ # malsupren la maldekstra flanko
- (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ # malsupren la mezo
- (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ # malsupren la dekstra flanko
- (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ # diagonalo
- (Bo [9] == le kaj bo [5] == le kaj bo [1] == le)) # diagonalo
- def getBoardCopy (tabulo):
- # Faru duobligita de la estraro listo kaj reveni al ĝi la duobligita.
- dupeBoard = []
- por i en tabulo:
- dupeBoard.append (i)
- revenu dupeBoard
- def isSpaceFree (tabulo, movado):
- # Reveno vera se la pasinta movado estas libera pri la pasinta estraro.
- revenu estraro [movado] == ''
- def getPlayerMove (tabulo):
- # Lasu la ludanto tipo en sia movado.
- movi = ''
- dum movado ne en '1 2 3 4 5 6 7 8 9 '. split () aŭ ne isSpaceFree (tabulo, int (movado)):
- print ("Kio estas via sekva movo? (1-9) ')
- movi = input ()
- reveno int (movado)
- def chooseRandomMoveFromList (tabulo, movesList):
- # Returns validan movon de la pasinta listo de la pasinta estraro.
- # Returns Neniu se ne estas valida movo.
- possibleMoves = []
- por i en movesList:
- se isSpaceFree (tabulo, i):
- possibleMoves.append (i)
- se len (possibleMoves)! = 0:
- revenu random.choice (possibleMoves)
- alie:
- reveni Neniu
- def getComputerMove (tabulo, computerLetter):
- # Donita estraro kaj la komputilo la letero, determini kie movi kaj reveni kiuj movas.
- se computerLetter == 'X':
- playerLetter = 'O'
- alie:
- playerLetter = 'X'
- # Jen nia algoritmo por nia Tic Tac Toe AI:
- # Unua, kontrolu se ni povas gajni en la proksima movado
- por i en gamo (1, 10):
- Kopio = getBoardCopy (tabulo)
- se isSpaceFree (kopio, i):
- makeMove (kopio, computerLetter, i)
- se isWinner (kopio, computerLetter):
- revenu i
- # Kontroli se la ludanto povis gajni en lia proksima movado, kaj bloki ilin.
- por i en gamo (1, 10):
- Kopio = getBoardCopy (tabulo)
- se isSpaceFree (kopio, i):
- makeMove (kopio, playerLetter, i)
- se isWinner (kopio, playerLetter):
- revenu i
- # Provu preni unu el la anguloj, se ili estas liberaj.
- movado = chooseRandomMoveFromList (tabulo, [1, 3, 7, 9])
- se movadon! = None:
- revenu movas
- # Provu preni la centron, se ĝi estas senpaga.
- se isSpaceFree (tabulo, 5):
- revenu 5
- # Movu sur unu el la flankoj.
- reveno chooseRandomMoveFromList (tabulo, [2, 4, 6, 8])
- def isBoardFull (tabulo):
- # Reveno Vera se ĉiu spaco sur la tabulo estis prenita. Alie reveni False.
- por i en gamo (1, 10):
- se isSpaceFree (tabulo, i):
- revenu Falsaj
- revenu Vera
- print ('Bonvenon Tic Tac Toe!')
- dum Vera:
- # Restarigi la estraro
- theBoard = [''] * 10
- playerLetter, computerLetter = inputPlayerLetter ()
- turni = whoGoesFirst ()
- print ('La' + siavice + 'iros unue.')
- gameIsPlaying = True
- dum gameIsPlaying:
- se siavice == 'ludanto':
- # Ludanto laŭvice.
- drawBoard (theBoard)
- movi = getPlayerMove (theBoard)
- makeMove (theBoard, playerLetter, movado)
- se isWinner (theBoard, playerLetter):
- drawBoard (theBoard)
- print ('Hura! Vi gajnis la ludo!)
- gameIsPlaying = False
- alie:
- se isBoardFull (theBoard):
- drawBoard (theBoard)
- print ('La ludo estas egaleco!')
- rompi
- alie:
- turni = 'komputilo'
- alie:
- # Computer turnon.
- movi = getComputerMove (theBoard, computerLetter)
- makeMove (theBoard, computerLetter, movado)
- se isWinner (theBoard, computerLetter):
- drawBoard (theBoard)
- print ('La komputilo venkis vi! Vi perdos.')
- gameIsPlaying = False
- alie:
- se isBoardFull (theBoard):
- drawBoard (theBoard)
- print ('La ludo estas egaleco!')
- rompi
- alie:
- turni = 'ludanto'
- se ne playAgain ():
- rompi
Desegni la Programo
Figuro 10-1: Flow abako por Tic Tac Toe
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.
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.
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:
- Unue, ĉu tie estas movado la komputilo povas fari ke gajnos la ludon. Se estas, preni tiun movadon. Alie, iru al paŝo 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.
- 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.
- Kontrolu se la centro estas libera. Se jes, movi tie. Se ne estas, tiam iru al paŝo 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.
Figuro 10-4: La kvin ŝtupoj de la "Get komputilo movado" algoritmo.
La sagoj lasante iri al la "Check se komputilo venkis" skatolo.
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
- # Tic Tac Toe
- importi hazarda
Presi la Estraro en la ekrano
- def drawBoard (tabulo):
- # Ĉi tiu funkcio presas el la estraro ke ĝi pasis.
- # "Tabulo" estas listo de 10 kordoj reprezentantaj la tabulo (ignori indekso 0)
- print ('| |')
- print ('' + estraro [7] + '|' + estraro [8] + '|' + estraro [9])
- print ('| |')
- print ('-----------')
- print ('| |')
- print ('' + estraro [4] + '|' + estraro [5] + '|' + estraro [6])
- print ('| |')
- print ('-----------')
- print ('| |')
- print ('' + estraro [1] + '|' + estraro [2] + '|' + estraro [3])
- print ('| |')
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):
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 | | |
Lasi la Ludanto esti X aŭ O
- def inputPlayerLetter ():
- # Estu Estas la ludanto tipo kiu letero volas esti.
- # Returns lerta kun la ludanto leteron kiel la unua ero, kaj la komputilo la letero kiel la dua.
- letero =''
- dum ne (litero == 'X' aux leteron == 'O'):
- presi ('Ĉu vi volas esti X aŭ O?')
- letero = input (). supra ()
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'):
dum ne ('X' == 'X' aux 'X' == 'O'):
dum ne (Vera aŭ Falsa):
dum ne (Vera):
dum ne Vera:
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. dum ne ('X' == 'X' aux 'X' == 'O'):
dum ne (Vera aŭ Falsa):
dum ne (Vera):
dum ne Vera:
dum Falsa:
- # La unua elemento de la listo estas la ludanto letero, la dua estas la komputilo letero.
- se letero == 'X':
- reveno ['X', 'ho']
- alie:
- reveno ['ho', 'X']
Decidi Kiu Goes Unua
- def whoGoesFirst ():
- # Hazarde elekti la ludanto kiu iras unue.
- se random.randint (0, 1) == 0:
- reveno 'komputilo'
- alie:
- reveno 'ludanto'
Petante la Ludanto al Ludu Denove
- def playAgain ():
- # Ĉi tiu funkcio redonas True se la ludanto volas ludi denove, alie False.
- presi ('Ĉu vi volas ludi denove? (jes aŭ ne)')
- revenu enigo (). malsupreniri (). startswith ('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
- def makeMove (tabulo, litero, movado):
- estraro [movado] = letero
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. >>> Fromaĝo = spamado
>>> Spamado = 100
>>> Spamado
100
>>> Fromaĝo
42
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. >>> Fromaĝo = spamado
>>> Fromaĝo [1] = 'Saluton!'
>>> Spamado
[0, 'Saluton!', 2, 3, 4, 5]
>>> Fromaĝo
[0, 'Saluton!', 2, 3, 4, 5]
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.
Figuro 10-6: Du variabloj stoki du aludoj al la sama listo.
Figuro 10-7: Ŝanĝi la listo ŝanĝas ĉiuj variabloj kun referencoj al tiu listo.
>>> 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: >>> Fromaĝo = [0, 1, 2, 3, 4, 5]
>>> 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: >>> 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: Du variabloj ĉiu stokante referencojn al du malsamaj listoj.
Uzanta Listo Referencoj en makeMove ()
Ni reiru al la makeMove () funkcio:- def makeMove (tabulo, litero, movado):
- estraro [movado] = letero
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
- def isWinner (bo, le):
- # Donita estraro kaj ludanto la letero, ĉi tiu funkcio redonas Vera se tiu ludanto venkis.
- # Ni uzas bo anstataŭ estraro kaj le anstataŭ letero do ni ne devas tajpi tiel.
- reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ # trans la supron
- (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ # trans la mezo
- (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ # trans la fundo
- (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ # malsupren la maldekstra flanko
- (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ # malsupren la mezo
- (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ # malsupren la dekstra flanko
- (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ # diagonalo
- (Bo [9] == le kaj bo [5] == le kaj bo [1] == le)) # diagonalo
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 aŭ 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: X | |
| |
-----------
| |
| X |
| |
-----------
| |
Ho | ho | ho
| |
- reveno ((bo [7] == le kaj bo [8] == le kaj bo [9] == le) aŭ
- (Bo [4] == le kaj bo [5] == le kaj bo [6] == le) aŭ
- (Bo [1] == le kaj bo [2] == le kaj bo [3] == le) aŭ
- (Bo [7] == le kaj bo [4] == le kaj bo [1] == le) aŭ
- (Bo [8] == le kaj bo [5] == le kaj bo [2] == le) aŭ
- (Bo [9] == le kaj bo [6] == le kaj bo [3] == le) aŭ
- (Bo [7] == le kaj bo [5] == le kaj bo [3] == le) aŭ
- (Bo [9] == le kaj bo [5] == le kaj bo [1] == le))
Jen estas la esprimo kiel ĝi estas en la kodo:
- reveno (('X' == 'ho' kaj '' == 'ho' kaj '' == 'O') aŭ
- ('' == 'Ho' kaj 'X' == 'ho' kaj '' == 'O') aŭ
- ('O' == 'ho' kaj 'O' == 'ho' kaj 'O' == 'O') aŭ
- ('X' == 'ho' kaj '' == 'ho' kaj 'O' == 'O') aŭ
- ('' == 'Ho' kaj 'X' == 'ho' kaj 'O' == 'O') aŭ
- ('' == 'Ho' kaj '' == 'ho' kaj 'O' == 'O') aŭ
- ('X' == 'ho' kaj 'X' == 'ho' kaj 'O' == 'O') aŭ
- ('' == 'Ho' kaj 'X' == 'ho' kaj 'O' == 'O'))
Unua Python anstataŭos la variablo bo kun la valoron ene de ĝi:
- reveno ((Falsa kaj Falsa kaj Falsa) aŭ
- (Falsa kaj Falsa kaj Falsa) aŭ
- (Vera kaj Vera kaj Vera) aŭ
- (Falsa kaj Falsa kaj Vera) aŭ
- (Falsa kaj Falsa kaj Vera) aŭ
- (Falsa kaj Falsa kaj Vera) aŭ
- (Falsa kaj Falsa kaj Vera) aŭ
- (Falsa kaj Falsa kaj Vera))
Tuj poste, Python taksos ĉiuj tiuj == komparoj ene la parantezoj al Bulea valoro:
- reveno ((Falsa) aŭ
- (Falsa) aŭ
- (Vera) aŭ
- (Falsa) aŭ
- (Falsa) aŭ
- (Falsa) aŭ
- (Falsa) aŭ
- (Falsa))
Tiam la Python interpretisto taksos ĉiuj tiuj esprimoj ene la krampoj:
- reveni (Falsa aŭ
- Falsa aŭ
- Vera aŭ
- Falsa aŭ
- Falsa aŭ
- Falsa aŭ
- Falsa aŭ
- Falsa)
Ekde nun ekzistas nur unu valoron ene de la krampoj, ni povas forigi ilin:
- reveni (Vera)
Nun ni taksi la esprimon kiu estas connecter de ĉiuj tiuj aŭ operatoroj:
- revenu Vera
Denove, ni forigi la krampojn, kaj ni restas kun tiu valoro:
Duobligante la Estraro Datumoj
- def getBoardCopy (tabulo):
- # Faru duobligita de la estraro listo kaj reveni al ĝi la duobligita.
- dupeBoard = []
- por i en tabulo:
- dupeBoard.append (i)
- revenu dupeBoard
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
- def isSpaceFree (tabulo, movado):
- # Reveno vera se la pasinta movado estas libera pri la pasinta estraro.
- revenu estraro [movado] == ''
Lasi la Ludanto Entajpu Ilia Move
- def getPlayerMove (tabulo):
- # Lasu la ludanto tipo en sia movado.
- movi = ''
- dum movado ne en '1 2 3 4 5 6 7 8 9 '. split () aŭ ne isSpaceFree (tabulo, int (movado)):
- print ("Kio estas via sekva movo? (1-9) ')
- movi = input ()
- reveno int (movado)
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 maldekstra aŭ dekstra flanko de la aŭ ŝ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 aŭ 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. 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'
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
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): 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
- def TrueFizz(message):
- print(message)
- return True
- def FalseFizz(message):
- print(message)
- return False
- if FalseFizz('Cats') or TrueFizz('Dogs') :
- print('Step 1')
- if TrueFizz('Hello') or TrueFizz('Goodbye'):
- print('Step 2')
- if TrueFizz('Spam') and TrueFizz('Cheese') :
- print('Step 3')
- if FalseFizz('Red') and TrueFizz('Blue'):
- print('Step 4')
- Cats
- Hundoj
- Step 1
- Saluton
- Paŝo 2
- Spam
- Cheese
- Paŝo 3
- Red
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
- def chooseRandomMoveFromList(board, movesList):
- # Returns a valid move from the passed list on the passed board.
- # Returns None if there is no valid move.
- possibleMoves = []
- for i in movesList:
- if isSpaceFree(board, i):
- possibleMoves.append(i)
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.
- if len(possibleMoves) != 0:
- return random.choice(possibleMoves)
- alie:
- return None
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
- def getComputerMove(board, computerLetter):
- # Given a board and the computer's letter, determine where to move and return that move.
- if computerLetter == 'X':
- playerLetter = 'O'
- alie:
- playerLetter = 'X'
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
- # Here is our algorithm for our Tic Tac Toe AI:
- # First, check if we can win in the next move
- for i in range(1, 10):
- copy = getBoardCopy(board)
- if isSpaceFree(copy, i):
- makeMove(copy, computerLetter, i)
- if isWinner(copy, computerLetter):
- return i
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
- # Check if the player could win on his next move, and block them.
- for i in range(1, 10):
- copy = getBoardCopy(board)
- if isSpaceFree(copy, i):
- makeMove(copy, playerLetter, i)
- if isWinner(copy, playerLetter):
- return i
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)
- # Try to take one of the corners, if they are free.
- move = chooseRandomMoveFromList(board, [1, 3, 7, 9])
- if move != None:
- return move
- # Try to take the center, if it is free.
- if isSpaceFree(board, 5):
- return 5
- # Move on one of the sides.
- return chooseRandomMoveFromList(board, [2, 4, 6, 8])
Checking if the Board is Full
- def isBoardFull(board):
- # Return True if every space on the board has been taken. Otherwise return False.
- for i in range(1, 10):
- if isSpaceFree(board, i):
- return False
- return True
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
- print('Welcome to Tic Tac Toe!')
- dum Vera:
- # Reset the board
- theBoard = [' '] * 10
Deciding the Player's Mark and Who Goes First
- playerLetter, computerLetter = inputPlayerLetter()
- turn = whoGoesFirst()
- print('The ' + turn + ' will go first.')
- gameIsPlaying = True
Running the Player's Turn
- while gameIsPlaying:
- if turn == 'player':
- # Player's turn.
- drawBoard(theBoard)
- move = getPlayerMove(theBoard)
- makeMove(theBoard, playerLetter, move)
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.
- if isWinner(theBoard, playerLetter):
- drawBoard(theBoard)
- print('Hooray! You have won the game!')
- gameIsPlaying = False
Then we set gameIsPlaying to False so that execution does not continue on to the computer's turn.
- alie:
- if isBoardFull(theBoard):
- drawBoard(theBoard)
- print('The game is a tie!')
- rompi
Running the Computer's Turn
- alie:
- turn = 'computer'
- alie:
- # Computer's turn.
- move = getComputerMove(theBoard, computerLetter)
- makeMove(theBoard, computerLetter, move)
- if isWinner(theBoard, computerLetter):
- drawBoard(theBoard)
- print('The computer has beaten you! You lose.')
- gameIsPlaying = False
- alie:
- if isBoardFull(theBoard):
- drawBoard(theBoard)
- print('The game is a tie!')
- rompi
- alie:
- turn = 'player'
- if not playAgain():
- rompi
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