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:
http://doc.gold.ac.uk/www/111/lab-exercises/resit-app/cgi-bin/splash.py?page=home
Configuration
-------------
- 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/config.py)
- 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 that...in 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') )
return
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
cursor.execute(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'
else:
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] ) )
else:
# the game was not recorded
return False
def leaderboard():
""" The leaderboard component
"""
print( utils.render_template( config['TEMPLATE_DIR'] + 'leaderboard.html') )
return
"""
Define global config variables here.
These will be accessible from other
scripts, so we only need to set them
here.
"""
# 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
cgitb.enable()
# 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':
components.leaderboard()
elif sent_data['page'].value == 'play':
components.play_game(cursor, player_id)
else:
components.home()
# 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
cnx.commit()
# close the connection to the database
cursor.close()
cnx.close()
# 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 )
print(db)
if db:
print("Connected to database. Trying to get a document...")
doc = db.flucks.find_one({})
if doc:
#print( doc )
print("PASS: db_connect")
else:
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))
else:
print(result['query'])
print("FAIL: Error in insert_fluck")
# run tests...
test_connect_db(config)
test_insert_fluck(config)
#!/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:
https://www.python.org/dev/peps/pep-0257/
"""
# 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)
"""
try:
cnx = mysql.connector.connect( user=config['DB_USER'],
password=config['DB_PASS'],
host=config['DB_HOST'],
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" )
else:
print(err)
else:
cnx.close()
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
Returns:
String: A formatted string of HTML
"""
try:
# open and read the template file
with open(temp_path, 'r') as f:
html = f.read()
except:
raise Exception("Could not open template")
else:
if data is not None:
try:
# replace placeholders with data in list
html = html.format(*data)
except:
raise Exception("Problem parsing data to template")
return html
/* Source: https://jsfiddle.net/phollott/x29ym2ag/ */
* {
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: https://jsfiddle.net/phollott/x29ym2ag/
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");
guessInput.style.display = 'inline';
guessButton.style.display = '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 = messages.win;
output.classList.add('win');
/* update the value of won field to true */
document.getElementById("won").value = "1";
/* submit form to database */
document.getElementById("hangman").submit();
} else {
output.innerHTML = messages.lose;
output.classList.add('error');
/* submit form to database */
document.getElementById("hangman").submit();
}
guessInput.style.display = guessButton.style.display = 'none';
guessInput.value = '';
}
/* Start game */
setup();
/* 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;
output.classList.add("warning");
}
/* 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++) {
lettersToShow[i].classList.add("correct");
}
/* 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) {
gameOver(true);
}
}
/* guess doesn't exist in current word and hasn't been guessed before, add to lettersGuessed, reduce lives & update user */
else {
lettersGuessed += guess;
lives--;
man.innerHTML = 'You have ' + lives + ' lives remaining';
if (lives === 0) gameOver();
}
}
/* not a valid letter, error */
else {
output.classList.add('error');
output.innerHTML = messages.validLetter;
}
}
/* no letter entered, error */
else {
output.classList.add('error');
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 ./simpleServer.py
Make sure all cgi scripts are executable
for single script:
chmod +x simpleServer.py
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: https://jsfiddle.net/phollott/x29ym2ag/ -->
<form id="hangman" method="POST" action="splash.py">
<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" />
</form>
<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>
<html>
<head>
<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>
</head>
<body>
<p>This is the home page!</p>
<p>TODO: make this page display player stats.</p>
<nav>
<ul>
<li><a href='splash.py?page=home'>Home</a></li>
<li><a href='splash.py?page=play'>Play Hangman</a></li>
<li><a href='splash.py?page=leaderboard'>Leaderboard</a></li>
</ul>
</nav>
<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