When I expressed my growing boredom with just doing random PHP tutorials, my sweetie suggested I do what he does when he's learning a new language: write a guess the number game where users try to "guess a number between 1 and 100" that also gives players an indication as to whether their guesses are high or low and, of course, when they get it right.
Me being me, I decided to take it a few steps further. In the hidden object adventure games I am so fond of, a regularly occurring puzzle is having to guess a 4 digit number with a limited number of guesses. For each guess, the system tells you if each number is far off, close, or correct rather than high/low. If you run out of guesses, the number resets. So I decided to try making a game like that. ๐
It was an interesting challenge to be sure. It took me most of the week and it certainly got me exposure to some more elements of PHP and hopefully got me at least somewhat more comfortable with coding in it. When I was done, I ended up with five files, plus a font file. The first are all set up/display, the header/footer and the CSS. Really, for this one, the header/footer is probably overkill since it really only has one display page, but I wanted to go ahead and split things up a bit.
For the header, of course, we have the usual doc type declarations. I used Google's jQuery rather than my own for easier portability.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title><?php echo $pageTitle ?></title> <link type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/redmond/jquery-ui.css" rel="stylesheet" /> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script> <link type="text/css" href="/guessGame/game.css" rel="stylesheet" /> </head> <body>
The footer is also pretty basic, mostly just popping in our jQuery to do the tabs/buttons and closing off our body and html tags.
<script type="text/javascript" charset="utf-8">
if ($("#tabs").length)
$('#tabs').tabs({ heightStyle: "auto" });
$("input:submit, button").button();
</script>
</body>
</html>
A few styles in our CSS to make it a little prettier.
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 15px;
padding: 10px;
}
h1, h2, h3 { text-align: center; }
h1 { font-size: 24px; }
h2 {
font-size: 20px;
color: #030;
margin-top: -5px;
margin-bottom: 10px;
}
th {
white-space: nowrap;
text-align: right;
}
th, td {
padding: 5px;
}
#submitGuess {
display: block;
margin: 10px auto 0px;
}
#tabs {
width: 500px;
margin-left: auto;
margin-right: auto;
}
#codeScreen {
height: 100px;
background: #000;
border: 5px dashed #0F0;
margin-bottom: 15px;
}
#codeScreen img {
width: 100px;
height: 100px;
margin-left: 5px;
margin-right: 5px;
}
.winner { color: #090; font-size: 36; }
.lost { color: #F00; }
Okay, now we can get into the big stuff ๐ First, the game.php file, which is where my classes are, of which there are two: Game and displayScreen. The Game class, of course, is the big one. I decided to use sessions for storing the current game's winning number and the player's guess list. In a "real" system, I'd probably encrypt the number to reduce chances for cheating.
class Game {
// set our initial constant and our properties
const numTriesAllowed = 10;
public $aGuesses = array();
public $aLastGuess = array('*','*','*','*');
public $aNumberStates = array("wrong","wrong","wrong","wrong");
public $aCorrectNumber = array();
public $hasWon = false;
public $gameOver = false;
// on first load, check to see if we have a number right now; if not, make one because its a new game
public function __construct(){
$this->getCorrectNumber();
if (count($this->aCorrectNumber) != 4)
$this->generateNumber();
}
// get the current winning number and the previous guesses, if any, and put in the appropriate property
public function getCorrectNumber() {
if (isset($_SESSION['CorrectNumber'])) {
$this->aCorrectNumber = str_split($_SESSION["CorrectNumber"]);
if (isset($_SESSION['Guesses']))
$this->aGuesses = $_SESSION["Guesses"];
}
else {
$aCorrectNumber = $this->aCorrectNumber;
}
}
// Generate a new random number - done as a loop so we can have 0 as the first number too
public function generateNumber(){
$aCorrectNumber = $this->aCorrectNumber;
if (count($aCorrectNumber) != 4) {
$aNewNumber = array();
while (count($aNewNumber) != 4) {
array_push($aNewNumber, rand(0, 9));
}
$this->aCorrectNumber = $aNewNumber;
$_SESSION["CorrectNumber"] = implode($aNewNumber);
}
}
// If we have a new guess, store it then call the appropriate functions to check it, store it, and see how the player is doing
public function submitGuess($newGuess){
if ($newGuess != '') {
$this->aLastGuess = str_split($newGuess);
$this->checkGuess();
$this->addGuessToList($newGuess);
$this->checkTurns();
}
}
// Compare guess to correct number and determine the state; store status in appropriate array
public function checkGuess(){
$aNewGuess = $this->aLastGuess;
$aCorrectNumber = $this->aCorrectNumber;
$numberstate = "wrong";
if(count($aNewGuess) == 4) {
for ($counter = 0; $counter <= 3; $counter++){
$checkValue = abs($aCorrectNumber[$counter] - $aNewGuess[$counter]);
$checkValue2 = abs($aNewGuess[$counter] - $aCorrectNumber[$counter]);
if ($checkValue == 0)
$numberstate = "correct";
elseif ($checkValue <= 2 || $checkValue2 <=2)
$numberstate = "close";
else
$numberstate = "wrong";
$this->aNumberStates[$counter] = $numberstate;
}
// if the number is spot on, flip the win flag on, yay!
if ($aNewGuess === $aCorrectNumber) {
$this->hasWon = true;
$this->gameOver = true;
}
}
// if they don't give us four numbers - punish them with all wrongs!!!
else {
for ($counter = 0; $counter <= 3; $counter++){
$this->aNumberStates[$counter] = $numberstate;
}
}
}
// Add new guesses to stored array
public function addGuessToList($newGuess){
array_push($this->aGuesses, $newGuess);
$_SESSION["Guesses"] = $this->aGuesses;
}
// how many turns have they used? If its 10 or more, gmae is over!
public function checkTurns(){
if(count($this->aGuesses) >= 10) {
$this->gameOver = true;
$this->getCorrectNumber();
$_SESSION["CorrectNumber"] = '';
}
}
// reset the game after a win or game is over; this is separate for a very important reason....
public function resetGame(){
$this->aLastGuess = array('*','*','*','*');
$this->aGuesses = array();
$this->aCorrectNumber = array();
$this->aNumberStates = array("wrong","wrong","wrong","wrong");
$this->hasWon = false;
$this->gameOver = false;
$_SESSION["CorrectNumber"] = '';
$_SESSION["Guesses"] = $this->aGuesses;
}
}
The second class is the displayScreen, which basically takes a number to input and generates the image for it with the appropriate colors. I could make this more secure by only having it work if the number is a number or by using precompiled images, but this let me play around with the image functions a bit. ๐
class displayScreen {
public function generateImage($numberToDisplay, $numberState){
$correct = array(0,255,0);
$close = array(255,255,0);
$wrong = array(255,0,0);
$font = "Audiowide";
$filename = $numberToDisplay . "_" . $numberState . ".png";
$newImage = imageCreateTrueColor(100, 100);
$aColors = $$numberState;
$text_color = imageColorAllocate($newImage, $aColors[0], $aColors[1], $aColors[2]);
imagettftext($newImage, 75, 0, 5, 85, $text_color, $font, $numberToDisplay);
imagepng($newImage, $filename);
imagedestroy($newImage);
return $filename;
}
}
And then the final file is the index.php. Now I imagine in a "well designed" PHP app, this wouldn't have such a mix of PHP and display code, but for something this simple, I'm fine with it. So after pulling in my game and header files and initiating our classes, I check for a guess and submit it if we have one. For the display, I went with a tabbed display and the code flips the header based on your current status. If the game is over or the player has won then at the very end of the page, i.e. after we've used the final guess for the display stuff, then the reset game function is triggered so on the next page load, we have a new game going.
<?php
session_start();
$pageTitle = "Solve the Code!";
$baseDirectory = getcwd() . "/";
require_once($baseDirectory . "game.php");
require_once($baseDirectory . "header.php");
$myGame = new Game();
$displayScreen = new displayScreen();
if (isset($_POST["guess"]))
$myGame->submitGuess($_POST["guess"]);
?>
<h1>Welcome Player! Can You Solve the Code?</h1>
<div id="tabs">
<ul>
<li><a href="#gameScreen">Play</a></li>
<?php
if (count($myGame->aGuesses) > 0)
echo '<li><a href="#previousGuesses">Previous Guesses</a></li>';
?>
<li><a href="#instructions">Instructions</a></li>
</ul>
<div id="gameScreen">
<?php
if (count($myGame->aGuesses) == 0)
echo "<h2>Game Start!</h2>";
elseif ($myGame->hasWon)
echo '<h2 class="winner">You Win!</h2>';
elseif ($myGame->gameOver)
echo '<h2 class="lost">Sorry, you ran out of turns. The correct number was ' . implode($myGame->aCorrectNumber) . "</h2>";
else
echo "<h2>You Have " . ($myGame::numTriesAllowed - count($myGame->aGuesses)) . " Guesses Left!</h2>";
?>
<div id="codeScreen">
<?php
foreach($myGame->aLastGuess as $key => $value) {
echo '<img src="' . $displayScreen->generateImage($value, $myGame->aNumberStates[$key]) . '" />';
}
?>
</div>
<?php
if (!$myGame->gameOver && !$myGame->hasWon) {
?>
<form action="index.php" method="post">
<table style="width: 200px; margin-right: auto; margin-left: auto;">
<tr>
<th><label for="guess" class="required">What's Your Guess:</label></th>
<td><input type="text" name="guess" id="guess" maxlength="4" size="5" autocomplete="off" /></td>
</tr>
</table>
<button name="submit" id="submitGuess" type="submit">Check It</button>
</form>
<?php
}
else
echo '<h3>Would you like to play again?</h3>' . "\n"
. '<h3><a href="index.php">Yes!</a> ~ <a href="http://google.com">No</a></h3>' . "\n";
?>
</div>
<?php
if (count($myGame->aGuesses) > 0)
echo '<div id="previousGuesses">';
if (count($myGame->aGuesses) > 0) {
echo "<ul>" . "\n";
foreach($myGame->aGuesses as $thisGuess) {
echo "<li>" . $thisGuess . "</li>\n";
}
echo "</ul>" . "\n";
}
if (count($myGame->aGuesses) > 0)
echo '</div>';
?>
<div id="instructions">
<p>How to play:</p>
<ul>
<li>A four digit code has been randomly selected</li>
<li>Enter in a possible code, each digit can be any number from 0 to 9</li>
<li>The numbers on the game screen will change colors based on your last guess:
<ul>
<li><strong style="color: #F00;">Red</strong> - wrong</li>
<li><strong style="color: #FF0;">Yellow</strong> - close</li>
<li><strong style="color: #0F0;">Green</strong> - correct</li>
</ul>
</li>
<li>Keep guessing until you get it right - but remember, you only get <?php echo Game::numTriesAllowed; ?> tries, then the code is reset!</li>
</ul>
</div>
</div>
<?php
if($myGame->gameOver || $myGame->hasWon)
$myGame->resetGame();
require_once("footer.php");
?>
So final results?
Initial Loading Screen

Instruction Screen

After Making a Guess

Hey, we got one right!

List of Previous Guesses

Winner Screen!

Woops, I Lost!

I was going to post a link to play the game, but alas, my site does not have PHP enabled. You are, however, welcome to download the files and try it on your own site ๐ Now, I'm sure folks more experienced with PHP can make this way better, but on the whole, I'm pretty happy with it.
With this done, I think next week I will start dipping my toe into the whole CakePHP – which I'm already not looking for from the random things my partner tells me, especially the whole ORM crap. Ugh….
