Skip to content

Instantly share code, notes, and snippets.

@dbernar1
Forked from stungeye/index.php
Last active August 29, 2015 13:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbernar1/10542999 to your computer and use it in GitHub Desktop.
Save dbernar1/10542999 to your computer and use it in GitHub Desktop.
<?php
/*
* This code implements a simple higher/lower guessing game.
*
* The computer picks a random number and the user guesses by way of an HTML form.
*
* The user is given hints if they guess incorrectly with the ability to try again.
*
* Upon winning the game the user can add their name to a session-based leaderboard.
*/
require 'details.php';
when_the( 'user_submits_a_guess', 'let_them_know_whether_they_guessed_correctly' );
when_the( 'user_submits_their_name_for_leaderboard', 'add_their_name_to_leaderboard_and_restart_game' );
when_the( 'user_requests_a_reset', 'refresh_to_start_a_new_game' );
otherwise( 'start_a_new_game' );
<?php
/* Constants to specify the numeric bounds for the guessing name. */
define('SECRET_NUMBER_MINIMUM_VALUE', 1);
define('SECRET_NUMBER_MAXIMUM_VALUE', 10);
session_start();
function let_them_know_whether_they_guessed_correctly() {
increment_the_number_of_guess_attempts();
if ( the_guess_is_correct() ) {
tell_the_user(
'You got it in ' . number_of_guess_attempts() . ' guesses!',
$and_display = 'leaderboard-highscore-form'
);
} elseif ( the_guess_is_too_low() ) {
tell_the_user(
'Incorrect Guess! Try a larger number.',
$and_display = 'guess-form'
);
} else /* the guess is too high */ {
tell_the_user(
'Incorrect Guess! Try a smaller number.',
$and_display = 'guess-form'
);
}
}
function add_their_name_to_leaderboard_and_restart_game() {
add_user_name_to_leaderboard();
refresh_to_start_a_new_game();
}
function refresh_to_start_a_new_game() {
header( 'HTTP/1.0 302 Found' );
header( 'Location: ' . $_SERVER[ 'PHP_SELF' ] );
}
function start_a_new_game() {
reset_guess_attempt_count();
create_new_secret_number();
tell_the_user(
"I've picked a random number between " . SECRET_NUMBER_MINIMUM_VALUE . " and " . SECRET_NUMBER_MAXIMUM_VALUE . ". Can you guess it?",
$and_display = 'guess-form'
);
}
function reset_guess_attempt_count() {
$_SESSION['guess_count'] = 0;
}
function create_new_secret_number() {
$_SESSION['secret_number'] = create_random_number_between( SECRET_NUMBER_MINIMUM_VALUE, SECRET_NUMBER_MAXIMUM_VALUE );
}
function create_random_number_between( $min, $max ) {
return rand( $min, $max );
}
function tell_the_user( $message, $form_to_display = 'guess-form' ) {
?>
<!DOCTYPE html>
<html>
<head>
<title>Guessing Game</title>
</head>
<body>
<h1>Guessing Game</h1>
<p><?php echo $message ?></p>
<?php if ( 'guess-form' === $form_to_display ): ?>
<form method="post">
<label for="user_guess">Your Guess</label>
<input id="user_guess" name="user_guess">
<input type="submit" value="Guess" name="button">
<input type="submit" value="Reset" name="button">
</form>
<?php elseif ( 'leaderboard-highscore-form' === $form_to_display ): ?>
<form method="post">
<label for="user_name">Your Name For The Highscore Leaderboard:</label>
<input id="user_name" name="user_name">
<input type="submit" value="Submit Name" name="button">
</form>
<?php endif ?>
<h2>Leader Board</h2>
<?php
if ( any_highscores_have_been_recorded() ) {
display_leaderboard();
} else {
display_notice_about_no_highscores();
}
?>
</body>
</html>
<?php
}
function display_notice_about_no_highscores() {
?>
<p>No highscores yet!</p>
<?php
}
function display_leaderboard() {
?>
<ul>
<?php foreach ( $_SESSION['leaderboard'] as $highscore ): ?>
<li>
<?= $highscore['guess_count'] ?> - <?= $highscore['user_name'] ?>
</li>
<?php endforeach ?>
</ul>
<?php
}
/* Functions for dealing with user POSTed forms. */
function user_submits_a_guess() {
return user_presses_button_labeled( 'Guess' );
}
function user_requests_a_reset() {
return user_presses_button_labeled( 'Reset' );
}
function user_submits_their_name_for_leaderboard() {
return user_presses_button_labeled( 'Submit Name' );
}
function user_presses_button_labeled( $label ) {
return isset($_POST['button']) && $_POST['button'] === $label;
}
/* These functions deal with the state of our game. */
function any_highscores_have_been_recorded() {
return $the_leaderboard_is_not_empty = ! empty( $_SESSION['leaderboard'] );
}
/* These functions deal with a user's guess. */
function the_guess_is_correct() {
return
$user_has_submitted_a_guess =
isset($_POST['user_guess'])
&& $submitted_guess_matches_secret_number =
(int) $_POST['user_guess'] === $_SESSION['secret_number']
;
}
function the_guess_is_too_high() {
return $submitted_guess_is_greater_than_secret_number =
$_POST['user_guess'] > $_SESSION['secret_number']
;
}
function the_guess_is_too_low() {
return $submitted_guess_is_less_than_secret_number =
$_POST['user_guess'] < $_SESSION['secret_number']
;
}
function number_of_guess_attempts() {
return $_SESSION['guess_count'];
}
function increment_the_number_of_guess_attempts() {
$_SESSION['guess_count']++;
}
function add_user_name_to_leaderboard() {
initialize_the_leaderboard_if_needed();
add_submitted_user_name_to_leaderboard();
sort_the_leaderboard();
}
function initialize_the_leaderboard_if_needed() {
if ( the_leaderboard_has_not_been_initialized() ) {
initialize_the_leaderboard();
}
}
function the_leaderboard_has_not_been_initialized() {
return ! isset( $_SESSION['leaderboard'] );
}
function initialize_the_leaderboard() {
$_SESSION['leaderboard'] = array();
}
function add_submitted_user_name_to_leaderboard() {
$_SESSION['leaderboard'][] = array(
'user_name' => $_POST['user_name'],
'guess_count' => $_SESSION['guess_count']
);
}
function sort_the_leaderboard() {
usort( $_SESSION['leaderboard'], compare_by( 'guess_count' ) );
}
/* This function is used by the previous one as a custom comparator for usort. */
function compare_by( $column_name ) {
return function( $a, $b ) use ( $column_name ){
if ( $a[ $column_name ] == $b[ $column_name ] ) {
return 0;
}
return ( $a[ $column_name ] < $b[ $column_name ] ) ? -1 : 1;
};
}
/* Vocabulary helpers */
function when_the( $user_performs_an_action, $respond_by_doing ) {
if ( $user_performs_an_action() ) {
$respond_by_doing();
exit;
}
}
function otherwise( $do_this ) {
$do_this();
}
@stungeye
Copy link

Interesting! The when_the and otherwise "vocabulary helpers" make the high level code read like a spec. I forgot about PHP's ability to dynamically call functions via variables like you do inside these functions!

There are a lot of function now, so in some way it's hard to navigate the code. However, with an editor that would let you jump directly from function calls to their definitions this wouldn't be a problem.

I'm not sure how I feel about the functions that included embedded HTML. Switching between PHP and HTML modes with <?php and ?> within a function feels strange to me.

I really like how you named thing. Even little things like calling the sorting comparator "compare_by" so that it reads compare_by( 'guess_count' ) when called. That's nice.

@dbernar1
Copy link
Author

Thanks for the feedback. Yeah, it does seem that the high-level code is a spec. I've been noticing that in programs I write. When people say stuff like "read the tests to get an idea of how the code works", I feel encouraged for implementing more of the specs in the code itself.

Yeah, there is the problem of the lot of code/functions, and navigability. I am hoping that things like modifying the code would not be affected by this problem. I wonder whether the file as a whole matters if each function is well explained, because reading code seems to not be linear.

There is some unusualness with naming ( with functions ) pieces of HTML output. I suppose they would normally be views and partials.

As far as the naming, the thing I've been playing with is that names are defined when they are called, even if it is a bit awkward for a function name. Same for parameters and stuff. You can see that with something like user_submits_a_guess(), which probably would usually get a name like user_has_submitted_a_guess() because of its boolean nature.

Thanks for taking a look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment