16
\$\begingroup\$

I've been doing a simple Tic-Tac-Toe with PHP, generating only HTML code.

A few notes:

  • I didn't bother yet to give an AI to the opponents (the Os), and it is intentional.
  • There is no CSS style to it, I intend to append it with a css file.
  • tictactoe.php is naturally the name of the script itself, so each links are refering to the page itself.

Now I don't know if I did everything correctly, here are my concern:

  • If I want to expand the functionalities of the game in the future (adding javascript, opponent AIs, new game features), I fear that the structure of the code is not adapted for it. Should I change my code so it is Object Oriented? EDIT I have in mind to add new feature to the game, like powers who would change the state of the board, like rewind to a previous state and; some css and javascript in a non-intrusive way and add some way to check if options are disabled (like javascript/flash), or check for the version of the browser for html5/css3 features.
  • As for now, it is easy to "cheat" and directly introduce the wished board state. I would like to prevent that, and if possible without using javascript, as I want the game to be playable also without it.
<table>
<?php
//Determine if a player as aligned three of its symbols and return the id of the player (1
//for X Player, -1 for O Player(Computer)). Otherwise return 0;
function isWinState($board){
    $winning_sequences = '012345678036147258048642';
    for($i=0;$i<=21;$i+=3){
        $player = $board[$winning_sequences[$i]];
        if($player == $board[$winning_sequences[$i+1]]){
            if($player == $board[$winning_sequences[$i+2]]){
                if($player!=0){
                    return $player;
                }
            } 
        }   
    }
    return 0;
}

//Player O plays its turn at random
function OsTurn($board){
    if(in_array(0,$board)){
        $i = mt_rand(0,8);
        while($board[$i]!=0){
            $i = mt_rand(0,8);
        }
        $board[$i]=-1;
    }
    return $board;
}

$winstate = 0;
$values = array();

if(empty($_GET['values'])){
    //initializing the board
    $values = array_fill(0,9,0);
    //determine who begins at random
    if(mt_rand(0,1)){
        $values = OsTurn($values);
    }
}else{
    //get the board
    $values = explode(',',$_GET['values']);
    //Check if a player X won, if not, player 0 plays its turn.
    $winstate = isWinState($values);
    if($winstate==0){
        $values = OsTurn($values);
    }
    //Check if a player 0 won
    $winstate = isWinState($values);    
}
//Display the board as a table
for($i=0;$i<9;$i++){
    //begin of a row
    if(fmod($i,3)==0){echo '<tr>';}
    echo '<td>';
    //representation of the player token, depending on the ID
    if($values[$i]==1){
        echo 'X';
    }else if($values[$i]==-1){
        echo 'O';
    }else{
        //If noone put a token on this, and if noone won, make a link to allow player X to
        //put its token here. Otherwise, empty space.
        if($winstate==0){
            $values_link = $values;
            $values_link[$i]=1;
            echo '<a href="tictactoe.php?values='.implode(',',$values_link).'">&nbsp;</a>';
        }else{
            echo '&nbsp;';
        }
    }
    echo '</td>';
    //end of a row
    if(fmod($i,3)==2){echo '</tr>';}
}
?>
</table>
<?php
//If someone won, display the message
if($winstate!=0){
    echo '<p><b>Player '.(($winstate==1)?'X':'O').' won!</b></p>';
}
?>
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Brings back memories - TTT was one of the first games I built in C. \$\endgroup\$
    – Michael K
    Commented Jan 28, 2011 at 13:59

2 Answers 2

10
\$\begingroup\$

There isn't really a reason to make this "more object-oriented" if you don't need to. To add more AI constructs, for example, you can do:

switch ($aiLevel) {
   case 1: $values = $OsTurn($values);
   case 2: $values = $betterAi($values);
   case 3: $values = $unbeatableAi($values);
}

Adding more features could be done in a similiar manner. I'm not sure exacly wht you have in mind, so I can only make some general comments. Break down everything into functions, and make sure that it's easy to see the flow of the program. The way you've structured your code is good. It should not be difficult to add more features if you use similiar style.

CSS won't affect your script; it just changes how the page looks. You'll just need to be sure you use the right elements when outputting html. Javascript may be trickier, but there are many ways of doing that including form elements (possibly hidden) and page submits. There may be other ways that I'm not aware of as I'm not primarily a web programmer.

I believe when you are talking about cheating, you're saying that you can send a winning board value set to the script by typing the URL yourself. To prevent this you need a way to preserve state. A cookie would be an easy way to start; store the current state of the board in it after every page call, and check that the only change was the placing of another piece. A more robust, but somewhat more involved, solution would be to use session variables to store the state. This would avoid the "cookys are bad" problem and the possibility that someone might fake the cooky!

\$\endgroup\$
4
  • \$\begingroup\$ See my comment for precision on what I plan to do. Also do I need to use cookie? I have a superstitious "fear" of cookies, (discussing if cookies are evil belongs to programmers.se, so I'll check into it), but could I not obtain a similar result with session variables? \$\endgroup\$
    – Eldros
    Commented Jan 28, 2011 at 15:05
  • \$\begingroup\$ Yes, you could. That would probably be a better solution too. \$\endgroup\$
    – Michael K
    Commented Jan 28, 2011 at 15:07
  • \$\begingroup\$ One other question, I had always the impression that one could only do effective unit testing on OO code. But in case I'm mistaking, what would be the unit-test you would do on such a code? Testing some winning board would be one I thought of, especially some board where there is an empty line checked before the winning line, but there is surely more to it. Should add this question to the OP? Or should I make it this own question? \$\endgroup\$
    – Eldros
    Commented Feb 1, 2011 at 12:30
  • \$\begingroup\$ No, you don't need OO to unit test. In many ways straight procedural code is easier - there are often fewer dependencies so you don't need to mock as much. As for how to test it, all of your non-printing functions can be tested. You've given an example of testing isWinState(); the same could be applied to OsTurn(). Of course, this only works for a deterministic AI. \$\endgroup\$
    – Michael K
    Commented Feb 1, 2011 at 13:30
2
\$\begingroup\$
  • Regardless of whether you choose to use procedural or object-oriented syntax, you should definitely make a distinct separation in your script between the "processing" and "displaying" parts. In other words, don't park you function declarations inside your html table.
  • To defend your script from users hand-crafting/hacking the board layout, you could use SESSION variables to store the previous plays, then only submit the new play on each page load. This will help you to validate that the new play is available on the board.
  • I don't recommend the use of -1, 0, and 1 to mark the game board -- it is not instantly intuitive to other programmers. Furthermore, you have to translate these values into X, O, &nbsp;. I recommend using the values X, O, and (space) so that all characters are repesented by a single-byte. This new structure will allow you to pass the whole board as a nine-character string without imploding.
  • When presenting the board/table, use array_chunk() to avoid modulus-based conditions.
  • See this other tic-tac-toe review of mine that shows how to concisely and directly assess a game board using regex: https://codereview.stackexchange.com/a/243502/141885
  • Rather than asking php to make looped guesses in OsTurn(), I recommend that you create an array of the available spaces on the board and make a single random selection from that pool to avoid unproductive guesses.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.