PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://bitbucket.org/sebastian_ekstrom/life2
Markdown | 220 lines | 129 code | 91 blank | 0 comment | 0 complexity | b766d103ad76ef05fb47d05f1464f5ee MD5 | raw file
  1. Life
  2. ======
  3. Spelet "Life" eller "Conway's Game of Life" skapades av matematiken John Horton Conway och presenterades i tidningen "Scientific America" i oktober 1970. Spelet, eller tankeexperimentet, är en simulator över hur en koloni av enkla organismer kan utveckas. Det är ett "zero-player"-spel vilket betyder att man efter grunduppställningen inte kan ge någon input, utan spelet fortsätter av sig själv.
  4. FILM
  5. Spelvärlden är två-dimensionell och består av ett rutnät, precis som ett rutat kollegieblock. Spelvärlden har en bestämd storlek, vilket gör att vi måste hålla reda kanten och hörnorna.
  6. Varje ruta har åtta grannrutor, varje ruta längs kanten har fem grannrutor och i hörnorna tre grannrutor:
  7. Bild:
  8. mitten kanten hörn
  9. 123 23
  10. 4*5 *5 *1
  11. 678 78 23
  12. Varje ruta kan innehålla en individ eller vara tom.
  13. # De tre reglerna:
  14. Det finns tre regler som beskriver hur spelet går till.
  15. * Överlevnad: Varje individ med två eller tre individer i grannrutorna överlever till nästa omgång
  16. * Födsel: Det uppstår en ny individ i varje tom ruta som har tre individer i grannrutorna
  17. * Döden: Om de två reglerna ovan inte inträffar ska rutan i fråga vara tom
  18. Alla rutor ska uppdateras samtidigt vilket betyder att man behöver komma ihåg två världar parallellt. En värld som är "nuet" och en som är "framtiden" där man fyller i hur nästa omgång ser ut enligt de tre reglerna. Spelet går ut att se hur en befolkning utvecklar sig över tiden. Man kan säga att det är en datorsimulering över en koloni bakterier.
  19. # Starta
  20. * Börja med att klona life-gitten ifrån bitbucket `https://bitbucket.org/patwic/life`.
  21. * Provkör filen `life.py` genom att i kommandotolken skriva
  22. $ python3 life.py
  23. * Nu ser du en startuppställning med sju individer i ett mönster i mitten av spelplanen.
  24. * I den här modulen kommer du att skriva kod som gör att kolonin utvecklas enligt de tre reglerna. I extrauppgifterna kommer övningar som gör att du kan ändra startuppställningen, räkna individerna och lite annat.
  25. # Nedbrytning av spelet i delar
  26. * Vi har nuvarande värld med individ och vi behöver en ny tom värld där vi kan fylla i hur nästa spelomgång ska se ut.
  27. * För att veta var vi ska placera ut invånarna i den nya världen behöver vi använda de tre spelreglerna.
  28. * Vi måste använda de tre reglerna varje enskild ruta i den nuvarande världen för att räkna ut hur den rutan ska se ut i nästa spelomgång.
  29. * Vad behöver vi veta för att kunna använda de tre reglerna?
  30. - Om det finns en individ eller inte i rutan i nuvarande spelomgång.
  31. - Antal individer i grannrutorna.
  32. * Vad behöver vi veta för att reda antal individer i grannrutorna?
  33. - Koordinater till de åtta grannrutorna.
  34. - Hantera kanter och hörnor att antal grannar räknas rätt sätt.
  35. - Avläsning av grannrutorna och summering av antal individer i dom.
  36. # Övningar
  37. ## Skapa en ny värld
  38. * Öppna filen `life.py` i geany. Den är ganska tom just nu, men här ska du själv skriva funktionerna som hanterar rutor, kanter, räknar individer och använder de olika reglerna.
  39. import curses
  40. import gui
  41. from world import World
  42. # Här ska du skriva dina funktioner
  43. def main(stdscreen):
  44. game_gui = gui.conwayGUI(stdscreen)
  45. my_world = None # fyll i ett funktionsanrop här
  46. game_gui.update_game(my_world)
  47. curses.wrapper(main)
  48. * De första tre raderna importerar färdigskriva funktioner och bibliotek som vi behöver använda. Variabeln game_gui (GUI: Graphic User Interface) hanterar skärmen och har en funktion som heter game_gui.update_game() som du använder för att uppdatera skärmen mellan spelomgångarna.
  49. * För att du ska veta hur stor världen är finns det i klassen conwayGUI två funktioner som heter `get_window_x()` och `get_window_y()`. Dessa funktioner returnerar storleken fönstret innanför ramen som är vår tillgängliga spelplan.
  50. * `from world import World` importerar klassen World som är objektet som håller reda vårt rutnät och alla individer. Objekt som är av typen World har några funktioner som kan vara bra att känna till.
  51. - `get_num_cols()` och `get_num_rows()` returnerar antal kolumner respektiver rader för världen.
  52. - `is_alive(kolumn, rad)` returnerar ett boolskt värde som berättar ifall individen i ruta (kolumn, rad) lever eller ej.
  53. - `alive(kolumn, rad)` och `dead(kolumn, rad)` som sätter värdet True respektive False för den aktuella rutan.
  54. * Definiera en funktion som skapar en ny värld. Som inargument ska funktionen ta antal rader och kolumner och funktionen ska returnera ett objekt av klassen World. För att skapa en ny värld anropar vi World(kolumner, rader).
  55. * Fyll sedan i definitionen av my_world med ett anrop till din funktion, att det blir ett World-objekt med samma storlek som fönstret. Kom ihåg att storleken fönstret kan fås genom `game_gui.get_window_x()` och `game_gui.get_window_y()`.
  56. ## För att testa din funktion
  57. * För att kunna kontrollera att funktionen du just skrev är rätt ska du raden ovanför din funktion skriva `@life(1)`. Det gör att du kan köra Patwics tester som berättar för dig om du har gjort rätt och kan vidare eller om du måste jobba vidare med funktionen.
  58. * Överst i filen `life.py` måste du lägga till raden `from lifetest import life`. För att testa funktionen skriver du i kommandotolken
  59. `grader life.py life.1`
  60. Om allting fungerar kan du nu köra programmet. Det gör inte mycket ännu utom att visa startkonfigurationen, men det ska du snart ändra .
  61. ## De tre reglerna
  62. * Skriv de två reglerna *Överlevnad* och *Födsel* i två olika funktioner som tar in två argument
  63. - har nuvarande ruta en invånare eller ej
  64. - antal individer i grannrutorna
  65. * Funktionerna ska returna **True** om regeln är uppfylld och annars **False**.
  66. * Funktionen som tar hand om *Döden* hanteras lättast som en `else`-sats till de andra två reglerna.
  67. * Dekorera överlevnadsregeln med `@life(2)` och födelseregeln med `@life(3)`för att kunna testa att du skrivit dem rätt. Testa genom att skriva `grader life.py life.2` respektive `grader life.py life.3` i kommandotolken.
  68. ## Är rutan bebodd eller inte?
  69. * Skriv en funktion som returnerar **True** om en ruta har en individ och **False** annars. Som inargument behövs världen och två koordinater för att peka ut vilken ruta som menas.
  70. * Här behövs kontrolleras att rutan finns. Tanken är att använda denna funktion när vi ska ta reda hur många grannar en ruta har och måste funktionen fungera längs kanterna i hörnen också. Om koordinaterna pekar en ruta utanför världen ska False returneras.
  71. * Med *utanför världen* menas när koordinaterna är mindre än 0 eller större än antalet kolumner respektive rader. Det går alldeles utmärkt att flytta ut den här kontrollen till en egen funktion som sedan anropas från den första.
  72. * Dekorera funktionen med `@life(4)` och kör grader-verktyget för att kontrollera om du har gjort rätt.
  73. ## Hantera kanterna
  74. * Här kan du testköra lite kod som försöker visa problematiken med kanterna.
  75. Det första exemplet går ut att visa att man måste vara uppmärksam koordinaterna jämfört med antal rader. Om `world.get_num_rows()` returnerar 8 är den sista raden som finns rad nummer 7.
  76. Exemplet kommer att fallera det försöker accessa en ruta som inte finns.
  77. <iframe src="http://pythontutor.com/iframe-embed.html#code=%0A%23+A+very+small+%22world%22%0A%23+filled+with+letters%0A%23+to+visualize.%0Aw+%3D+%5B%5B'a',+'b',+'c'%5D,+%0A+++++%5B'd',+'e',+'f'%5D,+%0A+++++%5B'g',+'h',+'i'%5D,+%0A+++++%5B'j',+'k',+'l'%5D%5D%0A%0Arows+%3D+len(w)%0Acolumns+%3D+len(w%5B0%5D)%0A%23+Note%3A+it+is+w%5Brow%5D%5Bcolumn%5D%0Aw00+%3D+w%5B0%5D%5B0%5D%0Aw11+%3D+w%5B1%5D%5B1%5D%0A%0A%23+The+next+line+won't+work!%0Aw43+%3D+w%5B4%5D%5B3%5D%0A&amp;cumulative=false&amp;heapPrimitives=false&amp;drawParentPointers=false&amp;textReferences=false&amp;showOnlyOutputs=false&amp;py=3&amp;curInstr=4&amp;codeDivWidth=350&amp;codeDivHeight=500" width="900" height="650"></iframe>
  78. http://pythontutor.com/visualize.html#code=%0A%23+A+very+small+%22world%22%0A%23+filled+with+letters%0A%23+to+visualize.%0Aw+%3D+%5B%5B'a',+'b',+'c'%5D,+%0A+++++%5B'd',+'e',+'f'%5D,+%0A+++++%5B'g',+'h',+'i'%5D,+%0A+++++%5B'j',+'k',+'l'%5D%5D%0A%0Arows+%3D+len(w)%0Acolumns+%3D+len(w%5B0%5D)%0A%23+Note%3A+it+is+w%5Brow%5D%5Bcolumn%5D%0Aw00+%3D+w%5B0%5D%5B0%5D%0Aw11+%3D+w%5B1%5D%5B1%5D%0A%0A%23+The+next+line+won't+work!%0Aw43+%3D+w%5B4%5D%5B3%5D%0A&mode=display&cumulative=false&heapPrimitives=false&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&curInstr=4
  79. Det andra exemplet visar vad som händer om man använder negativa koordinater.
  80. <iframe width="900" height="700" frameborder="0" src="http://pythontutor.com/iframe-embed.html#code=%0A%23+A+very+small+%22world%22+%0A%23+filled+with+letters%0A%23+to+visualize.%0Aw+%3D+%5B%5B'a',+'b',+'c'%5D,+%0A+++++%5B'd',+'e',+'f'%5D,%0A+++++%5B'g',+'h',+'i'%5D,%0A+++++%5B'j',+'k',+'l'%5D%5D%0A%0Arows+%3D+len(w)%0Acolumns+%3D+len(w%5B0%5D)%0A%23+Note%3A+it+is+w%5Brow%5D%5Bcolumn%5D%0Aw00+%3D+w%5B0%5D%5B0%5D%0Aw02+%3D+w%5B0%5D%5B2%5D%0Aw32+%3D+w%5B3%5D%5B2%5D%0A%0A%23+These+kinda+works,+%0A%23+since+-1+wraps.%0Awm1m1%3D+w%5B-1%5D%5B-1%5D%0Aw0m1%3D+w%5B0%5D%5B-1%5D&amp;cumulative=false&amp;heapPrimitives=false&amp;drawParentPointers=false&amp;textReferences=false&amp;showOnlyOutputs=false&amp;py=3&amp;curInstr=4&amp;codeDivWidth=350&amp;codeDivHeight=500"> </iframe>
  81. http://pythontutor.com/visualize.html#code=%0A%23+A+very+small+%22world%22+%0A%23+filled+with+letters%0A%23+to+visualize.%0Aw+%3D+%5B%5B'a',+'b',+'c'%5D,+%0A+++++%5B'd',+'e',+'f'%5D,%0A+++++%5B'g',+'h',+'i'%5D,%0A+++++%5B'j',+'k',+'l'%5D%5D%0A%0Arows+%3D+len(w)%0Acolumns+%3D+len(w%5B0%5D)%0A%23+Note%3A+it+is+w%5Brow%5D%5Bcolumn%5D%0Aw00+%3D+w%5B0%5D%5B0%5D%0Aw02+%3D+w%5B0%5D%5B2%5D%0Aw32+%3D+w%5B3%5D%5B2%5D%0A%0A%23+These+kinda+works,+%0A%23+since+-1+wraps.%0Awm1m1%3D+w%5B-1%5D%5B-1%5D%0Aw0m1%3D+w%5B0%5D%5B-1%5D&mode=display&cumulative=false&heapPrimitives=false&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&curInstr=4
  82. Det ser ut som om det fungerar, men notera värdet i wm1m1. Det är samma som w32. Och w02 är samma som w0m1. Med andra ord måste vi hålla koll koordinaterna för att världen inte ska "wrappa" i negativa riktningar eller.
  83. ## Räkna grannarna
  84. * Nu behövs en funktion som returnerar antal individer i en rutas grannrutor. Som inargument behövs världen och koordinater till rutan. Varje ruta kan ha upp till åtta grannrutor och för att hålla reda alla grannarna kan man använda matrisen nedan. Till exempel har grannrutan snett ovanför till vänster om en ruta koordinaterna (x-1, y-1) och grannrutan rakt nedanför har koordinaterna (x, y+1).
  85. # The relative neighbouring coordinates (x, y) at any given point.
  86. neighbours_list = [(-1,-1), ( 0,-1), ( 1,-1),
  87. (-1, 0), ( 1, 0),
  88. (-1, 1), ( 0, 1), ( 1, 1)]
  89. * Använd den här matrisen när du i tur och ordning anropar funktionen från förra uppgiften (den som du dekorerade med `@life(4)`) för att räkna ihop grannrutornas individer.
  90. * Dekorera den här funktionen med `@life(5)` och testkör den.
  91. ## Räkna grannar och använd reglerna
  92. Nu ska vi skriva en funktion som använder de tre reglerna och funktionen som räknar antal individer i grannrutorna. Funktionens inargument ska vara `def function_name(world, column, row, num_neighbours)` där num_neighbours är antal grannar till rutan med koordinater (column, row). Funktionen ska returnera **True** ifall den aktuella rutan ska vara bebodd i nästa spelomgång och False annars.
  93. De tre reglerna modelleras lättast genom en `if-elif-else`-sats. Där testar vi först regel **Överlevnad** `@life(2)` och annars testar vi regel **Födsel** `@life(3)` och annars inträffar den tredje regeln, **Döden**, automatiskt.
  94. Om du vill testa funktionen kan du använda `@life(6)`.
  95. ## En sista funktion
  96. Nu behövs en funktion som tar nuvarande värld som inargument och som returnerar nästa värld som utargument. I den här funktionen ska du alltså igenom samtliga rutor i världen och ta reda ifall de ska vara bebodda eller inte under nästa spelomgång. Dekorera funktionen med `@life(7)`för att kunna testa den.
  97. * Börja med att anropa din första funktion `@life(1)` för att skapa en ny värld.
  98. * Sedan behövs det två nästlade [for-loopar](https://canvas.swftw.se/courses/1/wiki/for?module_item_id=77). De ska räkna mellan 0 och antal rader respektive kolumner för att på så sätt gå igenom hela världen.
  99. for x in ...:
  100. for y in ...:
  101. kod
  102. * Inuti den innersta for-loopen behövs tre anrop:
  103. - Ta reda antal grannar genom att anropa funktionen som du dekorerade med `@life(5)`.
  104. - Sen anropar du funktionen dekorerad med `@life(6)`.
  105. - Det tredje anropet är till antingen `alive(x, y)` eller `dead(x, y)` som finns i klassen World, för att ange ifall rutan ska vara bebodd eller inte i nästa spelomgång.
  106. * Glöm inte att returnera den nya världen.
  107. ## Nu ska vi spela hela spelet
  108. * Det sista som behövs är en [evighetsloop](https://canvas.swftw.se/courses/1/wiki/while?module_item_id=20) för att hela tiden gå igenom stegen:
  109. - uppdatera världen till nästa spelomgång, `@life(7)`
  110. - uppdatera skärmen med den nya världen, `gui.update_game(my_world)`
  111. * Kör spelet! Nu ska mönstret förändras för varje varv som evighetsloopen körs. Du måste trycka ner en tangent mellan varje spelomgång och vill du avbryta spelet trycker du `Ctrl + C`.
  112. # Extrauppgifter
  113. * Gör en inmatningsrutin som skickar med ett start mönster till `load_world()`.
  114. Formatet som `load_world()` använder ser ut här:
  115. `pattern = [" ***", " * *", "* *"]`
  116. tex:
  117. `load_world(pattern = [" ***", " * *", "* *"])`
  118. Tips: Använd `input()` och en `while`-loop tills det kommer en tom rad och anta att inmatnigen är slut.
  119. * Gör en funktion som räknar ut antalet individer i världen och skicka med till `show_world()`.
  120. ex:
  121. `show_world(population = current_population)`
  122. där `current_population` är vad din funktion har räknat fram.
  123. Annotera funktionen med `@life(8)` och testa.
  124. * Skriv en funktion som kontrollerar om nuvarande och nästa värld är lika, och i fall
  125. returnerar `True`, annars `False`
  126. tex:
  127. `if nothing_happens(world, next_world):`
  128. ` break`
  129. Annotera funktionen med `@life(9)` och testa.