Commit 8719abfc authored by Sorrel Harriet's avatar Sorrel Harriet
Browse files

adding resit cursework code

parent 96293c3f
Live Demo
A live deployment of this app can be viewed at:
- Clone this repo (or pull latest changes if you already did that at the start of term 1)
- Create a blank MySQL database (CLUE: see ``Intro to MySQL'' in the Lab Exercises Wiki)
- Look at the application code to figure out what tables need to be created, and what fields they should have
- Create the tables and insert some dummy data!
- Hook the database up to the application code (CLUE: edit cgi-bin/
- Deploy on a Virtual Server
Other notes
You can only play the game as player 1 because, as yet, no login system has been implemented.
It is supposed here that a login script would pass player_id to other views as a GET parameter.
You don't *have* to do it like fact, you're free to edit any of this application code as you see fit!
*HOWEVER* it is probably not in your interests to edit hangman.js or hangman.css, since you aren't being credited for client-side scripting.
#!/usr/bin/env python3
This module provides a set of reuseable functions
which provide the main app functionality.
We could even go a stage further and separate
database interactions from logic...
from config import config
import utils
def home():
""" The homepage functionality
print( utils.render_template( config['TEMPLATE_DIR'] + 'home.html') )
def play_game(cursor, player_id):
""" The game interface
Initiates a new hangman game
for the current user
# define query to fetch a random row from the Word table
query = ("SELECT letters, hint FROM Word ORDER BY RAND() LIMIT 1")
# execute the query
# if a word came back, output the game board
for (letters, hint) in cursor:
print( utils.render_template( config['TEMPLATE_DIR']+'hangman.html', data=[player_id, letters, hint, player_id] ) )
def record_game(cursor, form_data):
""" Check for form submission
and record game in database
# make a string based on won status
if int(form_data['won'].value) == 0:
game_status = 'loss'
game_status = 'win'
# define a query to insert a new row in Game table
query = ("INSERT INTO Game (player_id, won, guessed_word) VALUES (%s, %s, %s)")
# define data as tuple to replace tokens in query
data = ( int(form_data['player_id'].value), int(form_data['won'].value), form_data['guessed_word'].value)
# execute the query
cursor.execute(query, data)
# check if rows were produced
if cursor.lastrowid:
# output message
print( utils.render_template( config['TEMPLATE_DIR']+'record-game.html', data=[game_status] ) )
# the game was not recorded
return False
def leaderboard():
""" The leaderboard component
print( utils.render_template( config['TEMPLATE_DIR'] + 'leaderboard.html') )
Define global config variables here.
These will be accessible from other
scripts, so we only need to set them
# app configurables stored in a config object
config = {
"DB_HOST": "localhost",
"DB_USER": "sorrel",
"DB_PASS": "password123",
"DB_NAME": "hangman",
"TEMPLATE_DIR": "templates/"
#!/usr/bin/env python3
# import modules from Python Standard library
import cgi
import cgitb
# import custom modules
from config import config
import utils
import components
# tell browser to expect HTML
print("Content-Type: text/html\n")
# render header HTML
print( utils.render_template( config['TEMPLATE_DIR'] + 'header.html') )
# render navigation HTML
print( utils.render_template( config['TEMPLATE_DIR'] + 'nav.html') )
# get any data sent with a GET or POST request
sent_data = cgi.FieldStorage()
# for now, all players have the id of 1
player_id = 1
# connect to a database
cnx = utils.db_connect( config )
# check there's an open connection to database
if cnx:
# create a cursor object
cursor = cnx.cursor()
# decide what component to serve according to page parameter from query string
if 'page' in sent_data:
if sent_data['page'].value == 'leaderboard':
elif sent_data['page'].value == 'play':
components.play_game(cursor, player_id)
# respond to a gameplay form submission
elif 'won' in sent_data:
components.record_game(cursor, sent_data)
# Make sure data is committed to the database
# close the connection to the database
# render footer
print( utils.render_template( config['TEMPLATE_DIR']+'footer.html' ) )
This module contains some test functions that
can be called to check if a certain component
is doing what we expect it to do
from config import config
import utils
import components
def test_connect_db( config ):
db = utils.db_connect( config )
if db:
print("Connected to database. Trying to get a document...")
doc = db.flucks.find_one({})
if doc:
#print( doc )
print("PASS: db_connect")
print("Error in db_connect. No handle returned.")
def test_insert_fluck( config ):
db = utils.db_connect( config )
result = components.insert_fluck( db, "5a15779bbc93104b641721d2", 1 )
if result['status']:
print("PASS: insert_fluck. Fluck {} inserted in database".format(result))
print("FAIL: Error in insert_fluck")
# run tests...
#!/usr/bin/env python3
This module provides a set of reuseable utility functions
This is a Google style docstring by the way.
Read more about them here:
# import the mysql connector API
import mysql.connector
def db_connect( config ):
""" Creates a connection with a MySQL database
Returns a connection object (handle to the database)
cnx = mysql.connector.connect( user=config['DB_USER'],
database=config['DB_NAME'] )
return cnx
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
print( "Something is wrong with your user name or password" )
elif err.errno == errorcode.ER_BAD_DB_ERROR:
print( "Database does not exist" )
def render_template( temp_path, data=[] ):
""" Reads in an HTML string from file
replacing any placeholders with values
supplied in data list.
Input params:
temp_path: the path to the template file
data: optional list of data values
String: A formatted string of HTML
# open and read the template file
with open(temp_path, 'r') as f:
html =
raise Exception("Could not open template")
if data is not None:
# replace placeholders with data in list
html = html.format(*data)
raise Exception("Problem parsing data to template")
return html
/* Source: */
* {
box-sizing: border-box;
body {
font-family: sans-serif;
/* Keep it simple - improvement would be to not display characters */
.letter {
display: inline-block;
list-style: none;
width: 20px;
margin: 0 5px;
border-bottom: 1px solid black;
text-align: center;
color: white;
.current-word {
list-style: none;
.output {
opacity: 0;
transition: opacity 1s ease;
.correct {
background-color: green;
transition: all 1s ease;
.error {
color: red;
opacity: 1;
.warning {
color: orange;
opacity: 1;
.win {
color: green;
opacity: 1;
.hint {
font-style: italic;
// Adapted from:
function playHangman(word, hint, player_id) {
"use strict";
var availableLetters, guessInput, guess, guessButton, hintButton, lettersGuessed, lettersMatched, output, man, letters, lives, currentWord, numLettersMatched, messages;
/* update the value of the player_id and word fields in the form */
document.getElementById("player_id").value = player_id;
document.getElementById("guessed_word").value = word;
function setup() {
/* start config options */
availableLetters = "abcdefghijklmnopqrstuvwxyz";
lives = 5;
currentWord = word.toLowerCase();
messages = {
win: 'You win!',
lose: 'Game over!',
guessed: ' already guessed, please try again...',
validLetter: 'Please enter a letter from A-Z',
hint: hint
/* end config options */
lettersGuessed = lettersMatched = '';
numLettersMatched = 0;
/* make #man and #output blank, create vars for later access */
output = document.getElementById("output");
man = document.getElementById("man");
guessInput = document.getElementById("letter");
man.innerHTML = 'You have ' + lives + ' lives remaining';
output.innerHTML = '';
document.getElementById("letter").value = '';
/* attach click behaviour to hint button */
document.getElementById("hint").onclick = function () {
document.getElementById("hintText").innerHTML = messages.hint;
/* make sure guess button is enabled */
guessButton = document.getElementById("guess"); = 'inline'; = 'inline';
/* set up display of letters in current word */
letters = document.getElementById("letters");
letters.innerHTML = '<li class="current-word">Current word:</li>';
var letter, i;
for (i = 0; i < currentWord.length; i++) {
letter = '<li class="letter letter' + currentWord.charAt(i).toUpperCase() + '">' + currentWord.charAt(i).toUpperCase() + '</li>';
letters.insertAdjacentHTML('beforeend', letter);
function gameOver(win) {
if (win) {
output.innerHTML =;
/* update the value of won field to true */
document.getElementById("won").value = "1";
/* submit form to database */
} else {
output.innerHTML = messages.lose;
/* submit form to database */
} = = 'none';
guessInput.value = '';
/* Start game */
/* reset letter to guess on click */
guessInput.onclick = function () {
this.value = '';
/* main guess function when user clicks #guess */
document.getElementById('hangman').onsubmit = function (e) {
if (e.preventDefault) e.preventDefault();
output.innerHTML = '';
output.classList.remove('error', 'warning');
guess = guessInput.value.toLowerCase();
/* does guess have a value? if yes continue, if no, error */
if (guess) {
/* is guess a valid letter? if so carry on, else error */
if (availableLetters.indexOf(guess) > -1) {
/* has it been guessed (missed or matched) already? if so, abandon & add notice */
if ((lettersMatched && lettersMatched.indexOf(guess) > -1) || (lettersGuessed && lettersGuessed.indexOf(guess) > -1)) {
output.innerHTML = '"' + guess.toUpperCase() + '"' + messages.guessed;
/* does guess exist in current word? if so, add to letters already matched, if final letter added, game over with win message */
else if (currentWord.indexOf(guess) > -1) {
var lettersToShow;
lettersToShow = document.querySelectorAll(".letter" + guess.toUpperCase());
for (var i = 0; i < lettersToShow.length; i++) {
/* check to see if letter appears multiple times */
for (var j = 0; j < currentWord.length; j++) {
if (currentWord.charAt(j) === guess) {
numLettersMatched += 1;
lettersMatched += guess;
if (numLettersMatched === currentWord.length) {
/* guess doesn't exist in current word and hasn't been guessed before, add to lettersGuessed, reduce lives & update user */
else {
lettersGuessed += guess;
man.innerHTML = 'You have ' + lives + ' lives remaining';
if (lives === 0) gameOver();
/* not a valid letter, error */
else {
output.innerHTML = messages.validLetter;
/* no letter entered, error */
else {
output.innerHTML = messages.validLetter;
return false;
#!/usr/bin/env python3
# above 'she-bang' line makes the script executable from command line
""" A Simple Web Server
Run with ./
Make sure all cgi scripts are executable
for single script:
chmod +x
or for a whole directory:
chmod -r +x cgi-bin/
import http.server # import http.server module
import cgitb; cgitb.enable() # import and enable cgitb module for exception handling
PORT = 8000 # specifies the port number to accept connections on
server = http.server.HTTPServer # provides simple web server
handler = http.server.CGIHTTPRequestHandler # provides request handler
server_address = ("", PORT) # specify server directory and port number
handler.cgi_directories = ["/","/cgi-bin","/htbin"] # where CGI scripts will reside in relation to the `server' directory
print("Starting server...") # outputs a message
httpd = server(server_address, handler) # creates the server, passing it the server address and port number, as well as the CGI handler (httpd stands for HTTP Daemon)
print("serving at port", PORT) # outputs a message
httpd.serve_forever() # puts program in infinite loop so that the server can `serve_forever'
<!-- Source: -->
<form id="hangman" method="POST" action="">
<input type="hidden" id="won" name="won" value="0" />
<input type="hidden" id="player_id" name="player_id" value="{}" />
<input type="hidden" id="guessed_word" name="guessed_word" value="" />
<input type="text" maxlength="1" minlength="1" name="letter" id="letter" placeholder="Guess a letter" />
<input id="hint" name="hint" type="button" value="Hint" />
<input id="guess" name="guess" type="submit" value="Guess" />
<ul id="letters"></ul>
<p id="man"></p>
<p id="hintText" class="hint"></p>
<p id="output" class="output"></p>
<script type='text/javascript'>playHangman('{}','{}',{});</script>
<!DOCTYPE html>
<meta charset='utf-8'/>
<title>Hang Man Game</title>
<link href="../css/hangman.css" type="text/css" rel="stylesheet">
<script src="../js/hangman.js" type="text/javascript"></script>
<p>This is the home page!</p>
<p>TODO: make this page display player stats.</p>
<li><a href=''>Home</a></li>
<li><a href=''>Play Hangman</a></li>
<li><a href=''>Leaderboard</a></li>
<p>Your {} has been recorded.</p>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment