Difference between revisions of "Blackjack"
Line 203: | Line 203: | ||
Notice that since our modules are packages, we simply specify the package name and node handles finding the appropriate code to import, but since our config file is something we've created, we must specify the full path to that file. | Notice that since our modules are packages, we simply specify the package name and node handles finding the appropriate code to import, but since our config file is something we've created, we must specify the full path to that file. | ||
+ | |||
+ | Next we'll finish setting up our server and database connection, as well as do a little trick to allow us to avoid using callbacks for all our MySQL calls. (if you don't know what I mean when I say callback, or are unfamiliar with the idea of asyncronous programming, check out [https://www.zeolearn.com/magazine/asynchronous-nature-of-javascript this introduction to the asyncronous nature of javascript]). | ||
+ | |||
+ | <source lang="js"> | ||
+ | // initialize our API server | ||
+ | const app = express(); | ||
+ | |||
+ | // POST body middleware (parses POST body to json for use in routes) | ||
+ | app.use(bodyParser.json()); | ||
+ | |||
+ | // establish mysql connection settings | ||
+ | const conn = mysql.createConnection({ | ||
+ | host: config.mysqlHost, | ||
+ | user: config.mysqlUser, | ||
+ | password: config.mysqlPassword, | ||
+ | database: config.mysqlDatabase | ||
+ | }); | ||
+ | |||
+ | // make conn.query a promise, this allows us to use 'await' instead of a callback | ||
+ | // don't worry about the details of this, just include this line | ||
+ | // now instead of using 'conn.query(query, params, callback)' we'll use 'await query' | ||
+ | const query = utils.promisify(conn.query).bind(conn); | ||
+ | |||
+ | // connect to our mysql database | ||
+ | conn.connect((err) => { | ||
+ | if (err) { | ||
+ | console.log('Unable to connect to mysql'); | ||
+ | throw err; | ||
+ | } | ||
+ | }); | ||
+ | </source> | ||
+ | |||
+ | Than's all the setup we need! | ||
=== Python API Server (Ignore if you set up the NodeJS API Server === | === Python API Server (Ignore if you set up the NodeJS API Server === |
Revision as of 00:57, 31 December 2018
Contents
Overview
This tutorial will be the guide for an intro to Web Development project building Blackjack to gain a basic understanding of the major building blocks of web applications: APIs, MySQL databases, Client Webpages, Cloud Servers and Github.
// TODO flow chart of the final project
// TODO screen shot of end project
Each project will utilize the following major pieces:
- The deckofcardsapi will be utilized to store deck state, as well as manage drawing cards and tracking player/dealer hands
- An AWS MySQL instance will be utilized to store data about a series of games, including current deckId for use in the external API, as well as keeping track of total wins/losses
- A client webpage will be constructed via HTML/CSS/JS
- An API will be built utilizing either Python or NodeJS, which will manage interaction between the client and MySQL/deckofcardsapi
There are two main paths to complete the API portion of this project: python and nodejs. If you have significant experience with python, feel free to use it as your API language, however otherwise we recommend you build a NodeJS server so that you don't have to gain familiarity in both Javascript and Python to complete this project.
Overview of the project specification
Each series of Blackjack games will be connected to a gameName. This name is what will allow us to keep track of how many wins/losses you have for that series, and will keep track of a deckId for the current game. This Id is what the deckofcards API uses as an identifier for its decks. This will allow us begin a game/series of games, close the browser, and continue right where we left off.
In a given browser session, the user will specify a gameName. This will send a request to our client API to either load an in progress game series, or create a new game series. Once we have loaded a game or created a new one, a user will have the option to either hit or stand. The user can continue to hit until their score reaches 21, at which point they win, or exceeds 21, in which case they lose. When they chose to stand, the dealer will draw cards until their score meets or exceeds 17, at which point a winner will be determined and a new game will be created.
There are four reasons in which the client webpage will interact with the client API:
- The client is requesting a file from the server
- A gameName is sent for a game series to be created or resumed
- A deckId and playerName is sent so that a card may be drawn to that player's hand
- A gameName and winnerName is sent so that a game can be completed and a new game begun
There are three reasons why we might interact with our MySQL database:
- We want to see if a gameName exists in our database already
- We want to insert a new game series into our database
- We want to update our database with a new deckId and increment gamesWon or gamesLost after the completion of a game
There are four reasons why we might interact with the deckofcards API:
- We want to generate a new, shuffled deck and retrieve a deckId
- We want to draw a card
- We want to add a drawn card to a player's hand (a 'pile' in the API's terminology)
- We want to retrieve a list of cards in a player's hand (a 'pile')
Though this seems like a lot, we will step through each piece an explain what is happening in detail to help simplify how everything works together.
Materials/Prerequisites
// TODO js bible i.e. async/await, callbacks, functions, interacting with DOM
// TODO python bible
// TODO HTML/CSS bible??
We will be utilizing a variety of tools/technologies to accomplish this project.
If you are using Python, we expect you to have Python 3.x.x installed. You can check your python version with the command:
python --verision
If you are using NodeJs, we expect you to have installed a version of Node, which can be obtained here.
You should also have a Git repository that you can use. Feel free to use the one already established for your group, just locate everything in a subfolder (i.e. GroupRepo Blackjack).
At this point you should complete the MySQL database on AWS tutorial, which can be found here.
Process
Setting up our MySQL Table
Open up MySQL Workbench and open your already-established connection to your RDS instance. Once you're connected, in the left sidebar under Schemas ensure your database is highlighted, so that any query you execute will be against your database.
We want to create our games table. Recall this table needs to track several properties: gamesWon, gamesLost, deckId, and gameName. We must also include an id column, which is what will distinguish each row from each other and act as our Primary Key.
Sample Table:
id gameName deckId gamesWon gamesLost ------------------------------------------ 1 game1 d643sj 12 9 2 myGame y73nd1 2 0
In MySQL workbench, create a new SQL tab for executing queries (this should be one of the icons in the upper right hand corner). Paste the following code and click on the lightning bolt icon to create the games table:
CREATE TABLE games (
id INT PRIMARY KEY AUTO_INCREMENT,
gameName VARCHAR(50) NOT NULL UNIQUE,
deckId VARCHAR(50),
gamesWon INT DEFAULT 0,
gamesLost INT DEFAULT 0
);
CREATE INDEX gameNameIndex ON games (gameName);
There are a few things to take note of here:
- AUTO_INCREMENT automatically increments the id column- this means we don't need to specify an id when adding a row to the table. It also means that if we delete a row of the table (say row 6) that index is gone forever- the next row will be created at row 7 even though only indexes 1-5 are the only indexes taken.
- VARCHAR(50) specifies that this column will contain a string of a maximum length of 50
- CREATE INDEX allows us to query against that column- now we can perform SQL searches using WHERE gameName = 'something'
Create another SQL tab and executing the following command should yield an empty table:
SELECT * FROM games;
-------------------------------------------
RESULT:
-------------------------------------------
id gameName deckId gamesWon gamesLost
NULL NULL NULL NULL NULL
Your database is all good to go now!
Setting up our project directory
In this step we'll set up our files/folders for the project. The main concern here is we want to isolate any files which should be accessible through the API's file server from our code files. This will help ensure the security of our source code.
Create the following directory structure:
Repository Root
|-public
|- index.html
|- index.css
|- index.js
|- server.js/server.py
|- config.json
Any files we want our browser to be able to access, we will store exclusively in our public folder/subfolders, while our server code will remain outside that folder.
Our config.json is another best-practices thing we will do. In it we will store things like the root for our deckofcards API endpoint (to reduce the number of magic strings), as well as things we wish to keep secure (such as our MySQL connection information). In industry, we would probably tell git to ignore our config.json file and would manually upload it to the server, so that any secure information stored in it wouldn't be available to people who have visibility to your git repository, however that particular application is outside the scope of this tutorial.
NodeJS API Server (Or skip to python if using python)
This section will be dedicated to developing our blackjack client API. The purpose of this API is threefold: to interface between the client and requests for web resources (linked stylesheets/scripts, serving webpages and static assets), interfacing between the client and the AWS RDS instance, and interfacing between the client and the deckofcards API.
The first thing you'll need to do is establish your node project and install packages to assist in development. Open up a terminal in the root directory of your project and execute
npm init
Step through the prompts, feel free to leave them all as default. You should now see a package.json file has been created in your project's directory. Next, we will install all the packages we need to facilitate our API. This will consist of:
- axios, for making API calls from our server
- mysql, for making MySQL queries against a database
- express, for intercepting and handling requests to our server
- body-parser, for parsing POST request bodies (not used in this project, but you will want this 9/10 times in API development).
All four of these packages are well reputed, well maintained, and well documented, and you should have no trouble finding resolutions to any issues you have with them. In the console, type:
npm install --save axios mysql express body-parser
If you look in your package.json, you should now see a section titles requirements which contains the four packages (and an associated minimum version number). You should also see a node_modules folder with hundred(s) of subfolders.
In the meantime, we will also want to ensure our start script is set correctly. Check the scripts' section of your package.json to ensure the start entry is specified:
"scripts": {
...
"start": "node server.js",
...
}
This will allow you to run your server.js file from the terminal using the command npm start.
The last thing you're going to want to do is ensure that you don't commit your package-lock.json or node_modules to git. If you haven't already, create a file called .gitignore and add the following entries:
**/node_modules/
**/package-lock.json
Now that we have configured our node project, we will assemble our server.js code. Add the following lines to import the required packages to our server:
// import appropriate modules
// mysql module, for mysql requests
const mysql = require('mysql');
// util, to allow us to use async/await
// util is built in to nodejs, so we don't have to install it
const utils = require('util');
// axios, to make API requests
const axios = require('axios');
// express, to maintain our API server
const express = require('express');
// API post body middleware
const bodyParser = require('body-parser');
// local json file with important data/global constants
const config = require('./config.json');
Notice that since our modules are packages, we simply specify the package name and node handles finding the appropriate code to import, but since our config file is something we've created, we must specify the full path to that file.
Next we'll finish setting up our server and database connection, as well as do a little trick to allow us to avoid using callbacks for all our MySQL calls. (if you don't know what I mean when I say callback, or are unfamiliar with the idea of asyncronous programming, check out this introduction to the asyncronous nature of javascript).
// initialize our API server
const app = express();
// POST body middleware (parses POST body to json for use in routes)
app.use(bodyParser.json());
// establish mysql connection settings
const conn = mysql.createConnection({
host: config.mysqlHost,
user: config.mysqlUser,
password: config.mysqlPassword,
database: config.mysqlDatabase
});
// make conn.query a promise, this allows us to use 'await' instead of a callback
// don't worry about the details of this, just include this line
// now instead of using 'conn.query(query, params, callback)' we'll use 'await query'
const query = utils.promisify(conn.query).bind(conn);
// connect to our mysql database
conn.connect((err) => {
if (err) {
console.log('Unable to connect to mysql');
throw err;
}
});
Than's all the setup we need!
Python API Server (Ignore if you set up the NodeJS API Server
TODO: do this
Client Webpage
If you know nothing about HTML and CSS, I highly recommend you look for a decent primer on those and then come back (w3 schools is a great resource), or at least check out the TODO_INSERT_HTMLCSS_BIBLE_LINKS_HERE.
We've already developed a boilerplate HTML page and a very basic CSS stylesheet to go along with it for this project, so simply copy the contents into their respective files.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<!--Standard HTML setup stuff, don't worry about what this does, just include it-->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--Title of the page- shows up in the browser tab-->
<title>Blackjack</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<!--Login information- hidden while in game-->
<div id="login-wrapper">
<div class="input-label">Deck Name</div>
<input type="text" placeholder="myBlackjackDeck" id="deckNameInput" />
<div id="enter-game-button" class="button">Create/Load Game</div>
</div>
<!--Game information- hidden while logging in-->
<div id="game-wrapper" class="hidden">
<div id="dealer-and-player-wrapper">
<div id="dealer-wrapper">
<div class="title">Dealer Hand</div>
<div id="dealer-card-wrapper">
<div class="card">Placeholder Card</div>
</div>
</div>
<div id="player-wrapper">
<div class="title">Player Hand</div>
<div id="player-card-wrapper">
<div class="card">Placeholder Card</div>
</div>
</div>
</div>
<div id="actions-wrapper">
<div id="hit-button">Hit</div>
<div id="stand-button">Stand</div>
</div>
<div id="info-banner">Games won: 0, Games lost: 0</div>
</div>
</body>
<!--Load scripts and styles at the end- ensures elements are loaded before trying to add event listeners, and speeds up l=page load times-->
<link rel="stylesheet" type="text/css" media="screen" href="index.css" />
<script src="index.js"></script>
</html>
/* index.css */
.input-label {
text-align: center;
font-size: 20px;
margin: 20px;
}
input[type="text"] {
margin: 20px;
/*
Calc is a special css property-
it allows dynamic calcualtion of a property.
Used here to ensure input is centered in screen after accounting for margin & border
*/
width: calc(100% - 44px);
text-align: center;
font-size: 24px;
}
.hidden, #game-wrapper.hidden {
display: none;
}
#dealer-and-player-wrapper {
display: flex;
flex-direction: column;
}
#player-wrapper, #dealer-wrapper {
display: flex;
flex-direction: column;
}
#actions-banner {
display: flex;
flex-direction: row;
justify-content: space-around;
}
#actions-banner > div {
margin: 20px 30px 20px 30px;
}
.title {
color: rgb(32, 32, 32);
font-size: 24px;
}
.subheading {
color: rgb(32, 32, 32);
font-size: 20px;
}
.card {
background-color: rgb(137, 137, 255);
padding: 20px;
border-radius: 10px;
text-align: center;
color: rgb(32, 32, 32);
font-size: 18px;
margin: 15px;
}
#hit-button, #stand-button, #enter-game-button {
background-color: rgb(255, 90, 68);
border-radius: 5px;
text-align: center;
font-size: 24px;
padding: 10px;
margin: 10px 0px 0px 0px;
color: rgb(32,32,32);
cursor: pointer;
}
#hit-button:hover, #stand-button:hover, #enter-game-button:hover {
background-color: rgb(190, 61, 44);
border-radius: 5px;
cursor: pointer;
}
Client Script
Authors
Ethan Shry, Winter 2018
Group Link
N/A
External References
N/A