This app is an example of the classic game Tic-Tac-Toe, created as a deliverable for the Software Engineering Immersive program with General Assembly. Click here to play right now!
- Instructions for Use
- Version Notes
- Tech Framework
- Manifest.json
- Future Plans
- Contribute
- Special Thanks
- Appendices
The game loads with 9 grey boxes. Two people will take turns clicking squares until one person has claimed three squares forming a straight line and wins. The line may be horizontal, vertical, or diagonal. At this point the game will inform both players that the game has been won and will become non-interactable. The game also ends if all nine squares are claimed without either player able to select three squares in a row. In this case the players will be informed they have reached a stalemate.
During gameplay, if a square is free for the current player to claim it will turn orange when hovered over with the cursor. When clicked it will mark the square as claimed with either an X or O depending on the current player.
At any point during or after play, players may click the marked reset box to start a new game.
The version 1.1.7.4 of this app is currently uploaded and live. In cases where I update the README without any updates to the website itself I do not update the version number or use branches for my work. Each version number will lack a commit number until the next version is uploaded. The current commit number is always added retroactively. In general, for version number format X.Y.Z:
- X: increases in this number represent a complete overhaul of some section of the website, source code, or UI
- Y: increases in this number represent a major functional change/aesthetic change or addition to the app
- Z: increases in this number represent changes that are relatively minor but still warrant a new commit
Versions prior to 1.0.0 are pre-deployment and will have varying levels of functionality. If you are looking for a functioning version of the app be sure to fork from versions 1.0.0 and later.
- Set up boilerplate HTML in
index.html
. - Linked
styles.css
andscripts.js
toindex.html
.
- Wireframe is rendered with basic functionality.
- Game has full functionality completed.
- I refactored players from
red
/green
to1
/2
. Easier to code this way. I initially usedred
/green
because clicked boxes were color-coded but I don't like this aesthetic now that I see it.
- Clicks on game tiles now render X or O depending on player turn.
- Game squares now highlight orange when hovered over. This is why I stopped color-coding clicked boxes to indicate player selections.
- Added a textbox that shows instructions and declares a winner or stalemate when appropriate.
- Cleaned out unnecessary
console.log()
statements fromindex.html
left over from testing. - Centered game horizontally and vertically in the browser.
- Added
<header>
and<footer>
sections. - Reduced game board size to make room for
<header>
and<footer>
sections. - Built a button to restart the game.
- Refactored
function declareWinner()
to use a ternary rather than anif/else
statement. - Fully built-out
README.md
file.
- Fixed a hyperlink in
<footer>
which previously redirected to the wrong GitHub repository.
- Fixed a bug where the game remains interactable after declaring a winner.
- Refactored
function renderGame()
andfunction renderGameDivs()
to de-spaghettify.
- Fixed versioning error in
README.md
on previous commit. - Renamed
function checkWin()
to tofunction checkEndGame()
. - Refactored
function checkEndGame()
to use.forEach
HOF instead of for-loops. - Refactored
function checkEndGame()
andfunction checkForWinner()
to removeif/else
trees. Now the function can be called on either player in its native state. Previously half the code wouldn't run. - Refactored argument
color
asplayer
infunction declareWinner()
;
- Universal refactor of value
'p1'
to'player'1
and'p2'
to'player2'
. - Refactor
function handleClick()
to remove spaghetti. Now it launches a series of functions to process a player move and evaluate if endgame has been reached. - Moved images for
README.md
from/images
directory to/images/readme-images
. Updated image filepaths inREADME.md
accordingly. - Added
manifest.json
to store metadata and link in<head>
ofindex.html
file. - Created and added a favicon.
- Fixed bug in CSS that allowed
<header>
and<footer>
containers to overrun<main>
container.
- Winning square get a green background when the game is won.
- Fixed bug that allowed a square to be clicked and claimed multiple times by different players. Now once a square is clicked its event listener is removed.
- Refactored code to add CSS classes to HTML elements via
element.classList.add()
rather than by string concatenation. - Removed a number of icebox features still listed in Future Plans section of this
README.md
file which have been implemented.
- Changed CSS classes so that background color highlighting on mouse hover is only active for unclaimed boxes. JavaScript now removes class
unclaimed
and replaces it withp1-box
orp2-box
on click.
- Removed dead code inadvertently left commented-out.
- Relocate
function resetGame()
from Structural Functions to Event Listeners section of code. - Add comments to
scripts.js
file to better show code organization.
- Updated
README.md
screenshot for game victory with new highlighting on winning squares. - Fixed a bug causing
manifest.json
and favicon to not load on the deployed app.
- Fixed typos in filepaths linking images in this
README.md
file.
Important! When numbering this version, update values at both the top of this file AND in manifest.json
This app uses:
- HTML5 and CSS3
- JavaScript ES6
I built this app with:
- VSCode version 1.53.2
- Google Chrome version 88.0.4324.192 (Official Build) (arm64)
- Homebrew 3.0.2
- Zsh 5.8 with Oh My Zsh
- GitHub (online, not desktop)
- macOS Big Sur version 11.2.1, Apple M1 chip
This app is hosted at:
Manifest.json is a new technology, still experimental at the time of me writing this, and was developed per MDN guidelines. This file isn't wholly necessary for the function of this app but is important for future-proofing and good practice.
Here are some notes about this file:
- The keys
"name"
,"manifest-version"
, and"version"
are mandatory. All others are optional and included as I see them relevant to his app. - The key
"manifest-version"
must always be set to2
(datatype integer). This is in the requirements as specified in MDN docs. Presumably this is still an experimental feature and2
is the value that prevents things from breaking. - The
"version"
key will be updated at any time I push a new version of this app. All other keys will likely never change. - I'm unlikely to add more keys unless I develop this app to exist outside of a GitHub hosted site or more keys are published for use.
- All keys must be entered as a string.
- Double-quotes only; single-quotes will not be recognized.
- As far as I am aware there is no way to insert inline notes.
- Favicons still need to be linked in the
<head>
of<index.html>
although the manifest lists them with correct locations. Listing them in the manifest is a future-proofing measure.
- Important! Before using CSS
:hover
to highlight squares which are available for selection I tried using event handlersonmouseover
and onmouseout
. It was a nightmare. Do not attempt to refactor in this way unless the refactor also involves implementing jQuery which handles these events more easily. - Figure out why favicon appear on my local machine but not on my GitHub-hosted site.
- Refactor game squares and restart button as
<button>
elements. This will improve accessibility. - Work on better separation of concerns in
scripts.js
file. - Create a tournament mode that tracks best-of-3/5/7 games.
Although I'm always interested in meeting new collaborators I prefer to keep this an individual project as a skills demonstrator.
I'm happy to let anyone reuse my code so long as you contact me for advance permission and give attribution where appropriate. Some materials are specified in this README as proprietary material from General Assembly and should only be used with the direct permission of General Assembly.
If you'd like to learn more about the developer, please visit my website and my GitHub.
- Instructors and peers from the General Assembly SEIR-EC-2-22 cohort for collaboration in developing this app. While this app represents my own original work we gave each other assistance in developing our own versions thereof.
- The
X
andO
characters use the Comic Neue font which I found with Google Fonts. It was designed by Craig Rozynski and made available under the SIL Open Font License. - The text box uses the RocknRoll One font which I found with Google Fonts. It was designed by Fontworks, Inc and made available under the SIL Open Font License.
- Creating a favicon was easy using favicon.io, a free resource created by John Sorrentino.
This assignment is a deliverable! Turn it in to the appropriate pinned thread in Slack before the start of class on March 5, 2021.
Build a Tic-Tac-Toe game incorporating the web technologies and techniques you’ve worked with thus far:
- HTML
- CSS
- JavaScript
- DOM Manipulation
- Display an empty tic-tac-toe board when the page is initially displayed.
- A player can click on the nine cells to make a move.
- Every click will alternate between marking an
X
andO
. - Once occupied with an
X
orO
, the cell cannot be played again. - Provide a
Reset Game
button that will clear the contents of the board.
- Follow the approach as described in the Guide on How to Build a Browser Game.
- Fork & clone the starter code repository by doing the following:
- Go into your labs directory (located at ~/code/sei/labs) ****- ****use the
sei
alias we made to help. - Fork the ttt-weekend repository to your GitHub account and clone it to your machine with this command:
gh repo fork SEI-Remote/ttt-weekend --clone=true
- Go into your labs directory (located at ~/code/sei/labs) ****- ****use the
- Using
id
and/orclass
attributes will help you target elements for styling and wiring up your click event listeners. - Programs, including games, are frequently focused on manipulating data and displaying that data to a user. Decide on the data structures held in variables you will use to maintain the game’s state (data / status).
- Note that the values you use to represent your game state don’t necessarily have to match what you want to display. For example, just because you want to display X and Os doesn’t mean that you have to use those letters in your data structure. You might choose to use 1 to represent player X and -1 to represent player O, for example. Then, in your render function, you would have the logic to translate data to what you want to display.
- Wire up your click event listener(s). Using a single listener with event bubbling is recommended but not required.
- Lots of little functions!
Pseudocode is a detailed description of what an application must do, written in natural language instead of a particular programming language.
Well-written pseudocode should be easily translated into actual code.
The best way to get started is to start with the “big picture” of what the app needs to do:
- Define the required constants
- Define the required variables used to track the state of the game
- Store elements on the page that will be accessed in code more than once in variables to make code more concise, readable, and performant.
- Upon loading, the app should:
- Initialize the state variables
- Render those values to the page
- Wait for the user to click a square
- Handle a player clicking a square
- Handle a player clicking the replay button
Since most web apps are event-driven by nature. Coding an event-driven program generally requires that we set up the application when it loads (steps 1 - 4) and wait for the user to interact with the app (steps 5 & 6).
Now we can start adding as many detailed steps as desired.
Typically, pseudocode does not have to be as detailed as the following - it is highly detailed here to help you as much as possible:
- Define the required constants:
- Define a colors object with keys of 'null' (when the square is empty) and players 1 & -1. The value assigned to each key represents the color to display for an empty square (null), player 1, and player -1.
- Define the 8 possible winning combinations, each containing three indexes of the board that make a winner if they hold the same player value.
- Define the required variables used to track the state of the game:
- Use a board array to represent the squares.
- Use a turn variable to remember whose turn it is.
- Use a winner variable to represent three different possibilities - a player that won, a tie, or a game in play.
- Store elements on the page that will be accessed in code more than once in variables to make code more concise, readable, and performant:
- Store the 9 elements that represent the squares on the page.
- Store the element that displays the game status on the page.
- Upon loading, the app should:
- Initialize the state variables:
- Initialize the board array to 9 nulls to represent empty squares. The 9 elements will "map" to each square, where index 0 maps to the top-left square and index 8 maps to the bottom-right square.
- Initialize whose turn it is to 1 (player 'X'). Player 'O' will be represented by -1.
- Initialize winner to null to represent that there is no winner or tie yet. Winner will hold the player value (1 or -1) if there's a winner. Winner will hold a 'T' if there's a tie.
- Render those state variables to the page:
- Render the board:
- Loop over each of the 9 elements that represent the squares on the page, and for each iteration:
- Use the index of the iteration to access the mapped value from the board array.
- Set the background color of the current element by using the value as a key on the colors lookup object (constant).
- Render a message:
- If winner has a value other than null (game still in progress), render whose turn it is - use the color name for the player, converting it to upper case.
- If winner is equal to 'T' (tie), render a tie message.
- Otherwise, render a congratulatory message to which player has won - use the color name for the player, converting it to uppercase.
- Render the board:
- Wait for the user to click a square
- Initialize the state variables:
- Handle a player clicking a square:
- Obtain the index of the square that was clicked by either:
- "Extracting" the index from an id assigned to the element in the HTML, or
- Looping through the cached square elements using a for loop and breaking out when the current square element equals the event object's target.
- If the board has a value at the index, immediately return because that square is already taken.
- If winner is not null, immediately return because the game is over.
- Update the board array at the index with the value of turn.
- Flip turns by multiplying turn by -1 (flips a 1 to -1, and vice-versa).
- Set the winner variable if there's a winner:
- Loop through the each of the winning combination arrays defined.
- Total up the three board positions using the three indexes in the current combo.
- Convert the total to an absolute value (convert any negative total to positive).
- If the total equals 3, we have a winner! Set winner to the board's value at the index specified by the first index in the combo array. Exit the loop.
- If there's no winner, check if there's a tie:
- Set winner to 'T' if there are no more nulls in the board array.
- All state has been updated, so render the state to the page (step 4.2).
- Handle a player clicking the replay button:
- Add a replay button to the HTML document
- Store the new replay button element
- Call the function you already built that does steps 4.1 (initialize the state variables) and 4.2 (render).
- Obtain the index of the square that was clicked by either:
Using a numbered outline is not required but helps organize the more complex steps into detailed steps.
- Display whose turn it is (“X” or “O”).
- Provide win logic and display a winning message.
- Provide logic for a cat’s game (tie), also displaying a message.
- Add your personal touch with unique styling.