OOP exempel - Tre i rad

I denna guiden får du se exempel på hur man kan använda OOP för att skapa spelet tre i rad.

Det första man bör tänka igenom när man ställs inför ett problem man valt att lösa med OOP är att tänka ut vilka klasser man behöver. En klass är ganska uppenbar, nämligen klassen som representerar hela spelet. Vi kommer låta denna klassen ha namnet TreIRad. Men vilka metoder kommer vi använda på den? Jo, på något sätt måste vi kunna tala om att vi vill placera ut ett kryss eller en cirkel på en viss position, så det är en metod som behövs. Men först måste vi även kolla om en viss position är fri (man kan ju inte placera en ring på ett kryss), så det är ytterliggare en metod. Vi behöver även kunna kolla om någon vunnit, så vi kan avgöra när spelet är över. Klassen TreIRad ska behöver alltså följande metoder:

Kod - Random Kod
klass TreIRad{ ledig(x, y); // Är position x, y ledig? placera(tecken, x, y); // Placera tecken på position x, y finnsVinnare(); // Har någon vunnit? }

Om vi har denna klassen med dessa funktioner skulle vi kunna skapa och spela spelet med följande kod:

TODO - Write code

Som du ser behöver vi inte ha någon mer klass; bara vi lyckas implementera denna så är vi klara sen. Men hur ska vi implementera den? Kan det vara så att vi behöver använda oss av fler (nya) klasser för att lyckas lösa det på ett bra sätt? Det är inte ovanligt att man implementerar en klass genom att använda andra klasser, men att göra det i det här fallet är nog att överdriva. Det är en väldigt enkel klass vi ska implementera, så vi gör på det enkla sättet och har enbart denna klassen.

På något sätt behöver vi representera de nio rutorna i klassen. Hur gör man det på ett bra sätt? Jo, spelplanen är ju 3*3 rutor, så en tvådimensionell array med 3*3 element verkar lovande. Man kan tänka sig att ett kryss ('X') på en av dessa platser i arrayen motsvarar att ett kryss är placerat på den positionen nycklarna anger, och motsvarande för cirkel ('O'). Ett mellanslag kan representera att rutan är ledig.

I så fall skulle implementationen kunna se ut på följande sätt:

PHP - PHP: Hypertext Preprocessor
<?php class TreIRad{ private $rutor; public function __construct(){ // Sätt mellanslag i alla rutor $this->rutor = array(); for($y=1; $y<=3; $y++){ $this->rutor[$y] = array(); for($x=1; $x<=3; $x++){ $this->rutor[$y][$x] = ' '; } } } public function ledig($x, $y){ // Sitter det ett mellanslag på positionen är den ledig return $this->rutor[$y][$x] == ' '; } public function placera($tecken, $x, $y){ // Sätt angivet tecken på positionen $this->rutor[$y][$x] = $tecken; } // Hjälpfunktion till funktionen finnsVinnare. Returnerar sant om alla // tre argumenten är lika (t.ex. "X X X") och ej mellanslag, annars falskt. private function sammaOchEjMellanslag($a, $b, $c){ // Är $a mellanslag är det falskt if($a == ' '){ return false; }else{ // Annars, om $b och $c är samma som $a, är det sant if($a == $b && $b == $c){ return true; }else{ return false; } } } public function finnsVinnare(){ // Testa alla möjliga vinnarkombinationer if( $this->sammaOchEjMellanslag($this->rutor[1][1], $this->rutor[1][2], $this->rutor[1][3]) || // Raden i botten $this->sammaOchEjMellanslag($this->rutor[2][1], $this->rutor[2][2], $this->rutor[2][3]) || // Raden i mitten $this->sammaOchEjMellanslag($this->rutor[3][1], $this->rutor[3][2], $this->rutor[3][3]) || // Raden i toppen $this->sammaOchEjMellanslag($this->rutor[1][1], $this->rutor[2][1], $this->rutor[3][1]) || // Kolumnen till vänster $this->sammaOchEjMellanslag($this->rutor[1][2], $this->rutor[2][2], $this->rutor[3][2]) || // Kolumnen i mitten $this->sammaOchEjMellanslag($this->rutor[1][3], $this->rutor[2][3], $this->rutor[3][3]) || // Kolumnen till höger $this->sammaOchEjMellanslag($this->rutor[1][1], $this->rutor[2][2], $this->rutor[3][3]) || // Diagonal snett uppåt $this->sammaOchEjMellanslag($this->rutor[3][1], $this->rutor[2][2], $this->rutor[1][3]) // Diagonal snett neråt ){ return true; }else{ return false; } } } ?>

Som du ser är det inte helt enkelt att upptäcka när någon vunnit, men genom att använda en hjälpfunktion blev funktionen finnsVinnare ändå ganska enkel att implementera. Tekniken söndra och härska ska aldrig underskattas, om vi inte använt en hjälpfunktion skulle koden i finnsVinnare bli ganska svårläst.

Så, är vi klara nu? Eller kan vi göra klassen bättre? En sak som inte är optimalt är att vi hårdkodat in att tecknet 'X' representerar kryss, tecknet 'O' representerar en cirkel och att mellanslag representerar en ledig ruta. Visst är det lite logiskt att ha det så här för oss människor, men i datorvärlden bör man inte låta något som ser ut som en viss sak representera den saken. Ett 'X' är inte ett kryss, även om det som ut som ett, ett 'O' är inte en cirkel och ett mellanslag är inte "". Istället borde vi ha konstanter som representerar dessa värden. Vad konstanterna har för värden spelar inte så stor roll (bara de inte har samma värden), för värdena ska aldrig skrivas ut, bara jämföras med varandra.

Så, om vi använder konstanter blir vår kod istället såhär:

PHP - PHP: Hypertext Preprocessor
<?php class TreIRad{ // En av dessa ska stå i varje ruta public const LEDIG = 0; public const KRYSS = 1; public const CIRKEL = 2; private $rutor; public function __construct(){ $this->rutor = array(); for($y=1; $y<=3; $y++){ $this->rutor[$y] = array(); for($x=1; $x<=3; $x++){ // Använd konstanten istället $this->rutor[$y][$x] = self::LEDIG; } } } public function ledig($x, $y){ // Använd konstanten istället return $this->rutor[$y][$x] == self::LEDIG; } public function placera($tecken, $x, $y){ // $tecken är konstanten KRYSS eller CIRKEL $this->rutor[$y][$x] = $tecken; } private function sammaOchEjLedig($a, $b, $c){ // Använd konstanten istället if($a == self::LEDIG){ return false; }else{ // Annars, om $b och $c är samma som $a, är det sant if($a == $b && $b == $c){ return true; }else{ return false; } } } public function finnsVinnare(){ // Testa alla möjliga vinnarkombinationer if( $this->sammaOchEjLedig($this->rutor[1][1], $this->rutor[1][2], $this->rutor[1][3]) || // Raden i botten $this->sammaOchEjLedig($this->rutor[2][1], $this->rutor[2][2], $this->rutor[2][3]) || // Raden i mitten $this->sammaOchEjLedig($this->rutor[3][1], $this->rutor[3][2], $this->rutor[3][3]) || // Raden i toppen $this->sammaOchEjLedig($this->rutor[1][1], $this->rutor[2][1], $this->rutor[3][1]) || // Kolumnen till vänster $this->sammaOchEjLedig($this->rutor[1][2], $this->rutor[2][2], $this->rutor[3][2]) || // Kolumnen i mitten $this->sammaOchEjLedig($this->rutor[1][3], $this->rutor[2][3], $this->rutor[3][3]) || // Kolumnen till höger $this->sammaOchEjLedig($this->rutor[1][1], $this->rutor[2][2], $this->rutor[3][3]) || // Diagonal snett uppåt $this->sammaOchEjLedig($this->rutor[3][1], $this->rutor[2][2], $this->rutor[1][3]) // Diagonal snett neråt ){ return true; }else{ return false; } } } ?>