- Darkwood Readme ๐
- ๐ Table of Contents
- โญ Purpose and goals of the website
- ๐ง User stories
- ๐น Game Functionality
- ๐ Content and Structure
- ๐ Design and Layout
- ๐ฅ Technology
- ๐ค Code
- ๐ Deployment
- ๐ Testing
- ๐ Known Issues and Bugs
- ๐ฉโ๐ป Resources
- ๐ Acknowledgments
- ๐ Future Improvements
- The website aims to provide an immersive and educational gaming experience, engaging players with a Darkwood Slavic Mythology-themed card matching game that entertains, educates, and introduces people to the world of Slavic folklore.
- The game is a simple card-matching game where each matched set equals one point. The player's goal is to match all 8 cards before the timer runs out. If the player is successful they progress to the next round and continue to accumulate points for matches.
- As a mythology enthusiast, I want to explore and learn about Slavic folklore through a card-matching game.
- As a casual gamer, I want to enjoy a visually appealing card-matching game with a mythology theme.
- As a student studying mythology, I want to be able to learn about mythology in a fun and engaging way.
- As a parent, I want to introduce my children to Slavic mythology through a fun and educational card-matching game.
- As a player, I want to challenge my memory and cognitive skills in a mythology-themed card-matching game.
- The game board consists of a grid of 8 cards, each with a hidden image on one side and a generic card back on the other.
- Players can flip cards by clicking on them.
- When two cards are flipped, the game checks if they match based on their image paths.
- If the cards match, they remain face-up (matched).
- If the cards don't match, they flip back face down after a brief delay.
- The game includes a timer displayed in MM:SS format.
- When a round starts, the timer begins counting down from the specified timeout (default is 60 seconds).
- The timer updates every second and triggers the end of the round when it reaches zero.
- The player's score is based on the number of matched pairs.
- Each time two cards match, the score increases by one.
- The game tracks the number of matched pairs
matchedPairs
to determine when all pairs have been found.
- The game consists of multiple rounds.
- Each round increases the number of cards on the board, making it more challenging.
- Players progress to the next round when all pairs are matched within the time limit.
- Pause and Restart
- The game includes buttons to pause and restart the game.
- Clicking the pause button pauses the timer and flips all cards back to their face-down position.
- Clicking the restart button restarts the game from round one.
-
Custom HTML, Custom CSS, CSS Flexbox, CSS variable roots, CSS Keyframes animations, Custom Javascript
-
Github Repo: https://github.com/emidombek/darkwood
-
Page structure:
-
Landing Page: index.html
-
This is a page designed to introduce the user to the feel and atmosphere of the game and to set up the user experience. It also acts as a 'gateway' to the user providing a link to the game at the end of a short animation sequence.
-
It consists of a Header with the Game Title. The body contains an image of a symbol and a fade-in fade-out fade-in Keyframes animation consisting of two different elements.
-
An element with some intro text and another element with an entry link for the game. The second link element has an additional Keyframes glow animation designed to highlight and entice the user to click on it.
-
The footer contains a link to my LinkedIn profile as well as my GitHub profile.
-
There is also an animated background (canvas element + js) consisting of small 'fireflies' flying around. The js for this animation and the keyframes fade in-out effects are contained within a separate graphics.js file to the game js which is in the script.js file.
-
-
Game Page: game.html
-
The actual game is located on this page.
-
When the user first enters the game it load with an overlay and a click-to-start mechanism. After the user clicks on the text the overlay is removed and the game starts.
-
The page itself consists of a Header with the Game Title. There is also a sub-heading container in the header that contains 3 labeled icons that are part of the game elements. These elements are the game score display, an instructions pop-up and a timer for each round.
-
The body consists of a fake 'grid' (a container with columns and rows)+ 2 rows containing, 8 dynamically generated cards in JS in pairs.
-
Within the game, there is a total of 16 different cards based on mythological beings from different regions (West, East and Southern Slavic Countries). Each round a new set of 4 matching pairs are placed within the game. Once the user starts the game they must match all 8 cards to progress, if the user runs out of time they will be prompted with a
-
Beneath the 'card grid' there is an instructions-controls container that holds 3 additional elements: an alternating pause/play button (depending on whether the user pauses the game), a home icon with a link back to the index page and a restart button to restart the game completely.
-
The footer contains links to my LinkedIn and GitHub profiles.
-
This page also uses the same animated background as the entry page. It also uses the keyframes glowing/glow animation to highlight certain clickable icons/text.
-
-
The design and layout of this website was created in Figma. A moodboard and wireframes were designed from scratch to help develop the look and feel of the game. Keyframes animations help emphasize important links and icons. Javascript for the background and Keyframes animations are stored in a different graphics.js file than the actual game code which is located in script.js. The setting of the game is inspired by Slavic mythology and the Witcher games/tv series/books.
Click here to view the Elements of the Design System
Color Palette
This color palette was selected to reflect the game theme, mood, contrast, and accessibility for an immersive and user-friendly experience.
This font was chosen for its bold and easy-to-read structure while maintaining the theme of the game.
Font
Nova Cut 400 imported from Google Fonts
Iconography
Icons are taken from Font Awesome
The front and back of the cards are taken from two artists under a private/personal use license, permission obtained:
- Front (Back ingame) of the cards: Slavic Pantheon Hanna Dola Art
- Back (Front ingame) of the cards: Playing Cards Losenko
The technology used in this project is as follows:
- CSS - style sheet language used to style the webpages.
- HTML - standard markup language.
- Javascript - a programming language used on the client side of web applications.
- Code Institute Repo Template - GitHub repository template used to create the repository.
- Visual Studio Code - source code editor used to create, edit and publish the webpages with the assistance of Git/GitHub/GitPages.
- Figma - cloud-based design tool I used to create the wireframes and moodboard.
- Git - distributed version control system.
- GitHub - Git repository hosting service with a web-based graphical interface.
- Gitpages- GitHub Pages is a static site hosting service.
- Firefly Graphics - Javascript based animations.
The majority of this game is written in custom Javascript, with custom CSS + Keyframes animations for styling as well as custom HTML.
The HTML used is a pretty standard implementation for both the index.html page and the game.html it includes a Head, Header, Body and Footer for each page. The index.html serves as an entry point for the game.html displaying a p element entry text that reveals an entry link element to the user via a CSS/Javascript keyframes animation sequence.
HTML index.html + game.html Summary
-
The
game.html
page has containers that hold game features and links: a Score icon/text with a span to display the game score, an icon with a pop-up trigger that triggers a pop-up containing game instructions and a timer icon with a span that displays the time remaining in the current round at the top. The main section holds the fake 'grid' or just a container with rows and grid game rows that hold the eight cards that are generated dynamically in Javascript. Below the card containers, there is another container with some controls and a link: the play/pause icon/text is a span that is toggled via JS depending on the game state function currently active, a home icon/text that contains a link back to the index Landing Page and a restart icon/text that triggers the restart game function in JS. The social media list items in the footer of both pages were taken from the Love Running project which will be listed in the resources below. -
A highlight of the HTML implementation of the canvas element on both pages which is used to facilitate the display of the background 'Firefly' Javascript animation.
-
Another highlight of the HTML is the elements on the
index.html
which facilitate the fade-in fade-out fade-in keyframes animation sequence. -
The
game.html
page besides holding the containers for the dynamically created cards and game controls + displays and links has containers that hold several pop-ups that are further styled and controlled in the script.js file.
The CSS for this game is pretty standard located inside the style.css file. The root variables define various colors, font families, and line height to maintain consistency throughout the game. At the top of the file, it imports the Nova Cut font from google fonts.
CSS style.css Summary
-
The CSS includes global styles for the body, main, and header elements, setting the background image and layout. It also configures the canvas element for firefly-like graphics animation found in the
graphics.js
file. There is also a setting at the very top that removes global settings (credit to my mentor Juliia). There are also several media queries to ensure the site remains responsive on several different screen sizes. -
Highlights of the CSS include some elements including being styled to be initially hidden to facilitate various effects:
.entry-link
: The "Enter" link on the entry page is initially hidden. It becomes visible later with a fade-in effect..hidden
: This class is used to hide elements. It's not directly applied to any specific element in the given CSS, but it's likely used in JavaScript or dynamically added to elements for certain interactions..fa-play
: The play icon used in pop-up elements is initially hidden.#playText
: This element, likely a text label for the play icon, is initially hidden.
-
The CSS file also defines animations via Keyframes for fade-in and glowing effects on elements. Resources for the Keyframes animations will be listed in the resources section of this readme.
The Javascript in this project is divided into two different files, the first file is the graphics.js file where the background fireflies animation is created and Keyframes fade-in fade-out sequence is controlled. This file is used on both of the web pages. The second file script.js is where the actual code that runs the game lives. This file is only used within the game.html webpage.
The entire code is wrapped in a 'DOMContentLoaded' event listener to ensure that the JavaScript code inside the event listener that runs after the DOM has been fully loaded and parsed.
SVG Flowchart of the entire graphics.js file
Control Fade-In Fade-Out Fade-In Animation Sequence
-
Initially, both
fadeElement
andentryLink
have the "hidden" class applied to them, which sets their opacity property to 0 in the CSS, making them invisible on the webpage. -
When the page loads and the
DOMContentLoaded
event fires, the JavaScript sets up ananimationend
event listener onfadeElement
. -
When
fadeElement
finishes its animation (a fade-out effect, for example), theanimationend
event is triggered. -
In the event listener callback function, the JavaScript code executes and adds the
hidden
class back tofadeElement
usingfadeElement.classList.add("hidden")
. -
Before this step, the code also sets up another event listener for the
animationend
event onentryLink
. -
When
fadeElement
finishes its animation, theanimationend
event listener onentryLink
triggers as well. -
In the event listener callback function for
entryLink
, the JavaScript code executes and removes thehidden
class fromentryLink
usingentryLink.classList.remove("hidden")
. -
By removing the
hidden
class fromentryLink
, the CSS transition now applies to it, and its opacity is gradually changed from 0 to 1 over time, creating the fade-in effect for the link. -
So, both fadeElement and entryLink have separate
animationend
event listeners, but the removal of thehidden
class fromentryLink
occurs on it's own event listener and is essential for triggering the fade-in animation for the link.
Firefly Background Animation
The majority of this code was taken from: https://github.com/owentr1369/animated-background-fireflies-youtube and modified.
Summary of the code:
-
The code defines a Firefly class that represents a firefly's behavior on a canvas. It has methods to update its position and display on the canvas
(move(),show(c))
. -
Variables
canvas
,context
,w
, andh
are declared to handle the canvas and its drawing context. -
An array
f
is created to store firefly objects. -
The
draw()
function manages the animation loop for the fireflies, adding new fireflies and removing those outside the canvas. -
The
init(elemid)
function sets up the canvas, fills it with a light green color, and returns the drawing context for further drawing operations. -
The
loop()
function sets up the animation loop usingwindow.requestAnimationFrame
to repeatedly clear the canvas and render the animation. -
The
window.resize
event listener updates the canvas size when the window is resized, reinitializes the canvas, and restarts the animation loop to reflect the changes. -
Finally, the code initializes the canvas and starts the animation loop.
The code creates a simulation of fireflies flying around on a canvas, and the fireflies move randomly within the canvas boundaries. The animation loop ensures a smooth visual effect, and fireflies are added or removed as needed to maintain the desired number of fireflies on the canvas.
Here is a list of what was modified:
-
Variable names: - Original: Uses
c
as the canvas context variable and returns it from the init function. - My edit: Usescontext
for the canvas context variable and returns it from the init function. It was easier to make sense of it going back to read it. -
Typo in move method: - Original: Contains a typo
this.and
when updating the angle of movement, should bethis.ang
. - My edit: Fixed the typo. -
Mouse movement tracking: - Original: The original code includes event listeners to track the mouse position and store it in mouse and
last_mouse
variables. Also includes mouse movement tracking using event listeners to store mouse coordinates. - My edit: Removed the mouse movement tracking event listeners because they were causing issues with the animation loading and I could not figure out what exactly was causing it. This also makes the animation purely self-contained and autonomous, meaning it will generate the firefly animation without any external input. -
Canvas initialization and animation loop: - Original: Uses
setInterval
for animation loop (60 FPS). - My edit: Useswindow.requestAnimationFrame
for animation loop. It provides a more efficient and optimized approach for achieving smooth animations while also being mindful of performance and battery life. -
Background fill color: - Original: Sets the canvas background fill color to a
dark gray (rgba(30, 30, 30, 1))
. - First Post: Sets the canvas background fill color to alight green (rgba(152, 255, 191, 1))
.
The entire code is wrapped in a DOMContentLoaded
event listener to ensure that the JavaScript code inside the event listener and runs after the DOM has been fully loaded and parsed.
SVG Flowchart of the script.js file
Timer & Timer Formatting
Timer Formatting (formatTime(seconds)): Formats the timer display from total seconds to MM:SS
format.
startTimer()
- This function initiates the game timer, ensuring it starts counting down properly.
- It checks whether the timer is already active (timerActive flag) to avoid re-initialization.
- The remaining seconds for the timer are determined based on whether the game was paused
isPaused
flag or if it's a new roundmatchTimeout
. - The timer display is updated with the remaining seconds using the updateTimerDisplay() function.
- The timerActive flag is set to true to indicate that the timer is running.
- If the game was paused, the timer resumes from the
remainingSeconds
; otherwise, the initial seconds are stored inremainingSeconds
. - The setInterval() function is used to create a timer that decrements
secondsLeft
every second. - When
secondsLeft
reaches zero or less, the interval is cleared, and theendRound()
function is called to conclude the current round.
updateTimerDisplay(element, seconds)
- This function updates the visual representation of the timer in the specified HTML element.
- The
formatTime()
function converts the seconds parameter into an easily readableMM:SS
format, whereMM
represents minutes andSS
represents seconds, always displaying seconds with two digits (e.g., '01', '05', '10', etc.) using leading zeros if necessary. - The formatted time is then set as the
textContent
of the given element.
formatTime(seconds)
- This function takes the number of seconds as input and converts it into a user-friendly time representation.
- It calculates the number of minutes by dividing seconds by 60 and using
Math.floor()
to round down to the nearest integer. - The remaining seconds are derived by taking the modulo (%) of seconds divided by 60.
- The function returns a formatted string in the format
MM:SS
, with leading zeros added to single-digit remaining seconds. - Note: The idea for this came from my previous life as a data analyst when I dealt with formatting dates and times extensively. This JS function was something I tried to convert from an SQL query I wrote please see this section for the SQL code.
Scoring Logic
Scoring Logic
- The game uses a simple point system to calculate the player's score.
- Each time the player successfully matches two cards, they earn one point.
- The
score
variable keeps track of the player's total score throughout the game. - When two cards are flipped, the game checks if they match by comparing their image names.
- If the two flipped cards have the same image name (i.e., they are a matching pair), the
score
is incremented by 1, and the cards are marked asmatched
. - The
matchedPairs
variable counts the number of pairs the player has successfully matched in the current round. - If the player matches all card pairs within the time limit (timer reaches zero), the round is completed, and the player proceeds to the next round.
- At the start of each round, the
score
andmatchedPairs
variables are reset to 0 to prepare for the new round. - The game interface visually displays the player's score to provide real-time feedback as they progress through the rounds
Shuffling
shuffle(array)
- This function performs array shuffling to ensure the randomization of the elements.
- The
array.sort()
method is utilized, taking a comparator function that returns a random number in the range of -0.5 to 0.5. - Random sorting reorders the elements in the array, effectively shuffling the elements.
Card Creation
The two functions, createCards(images)
and createGrid(cards)
, work together to dynamically create and display the card grid for the game.
createCards(images)
- This function takes an array of image paths (images) as input.
- It iterates over the first four images in the images array (as indicated by
i < 4
) to create card elements for the game. - For each image, it creates a new card element
<div>
element with the class "card". - It extracts the image name from the image path by splitting it at the '/' and taking the last part of the split
imageName
. - Adds a class to the card element with the name
card-image-{imageName}
to identify the card's back image. - Creates inner elements for the card (front and back) and appends them to the card element.
- The front of the card contains a placeholder image with an
alt
attribute set. - The back of the card contains an image element with the corresponding image path from the images array and an
alt
attribute set. - Finally, the card element is added to an array (cards) to keep track of all created cards.
- The function returns the array of card elements.
Grid Creation
The two functions, createCards(images)
and createGrid(cards)
, work together to dynamically create and display the card grid for the game.
createGrid(cards)
- This function takes an array of card elements (cards) as input.
- It checks if there are existing row containers with the class
grid-game-row
on the page by querying for them usingdocument.querySelectorAll('.grid-game-row')
. - If no row containers are found, it creates two new row containers (elements) and appends cards to them.
- The first half of the cards are appended to
rowContainer1
, and the second half are appended torowContainer2
. - The row containers are then appended to the
gridContainer
, which represents the main container for the card grid. - If row containers already exist, the function clears their content by removing all child elements using a while loop.
- After clearing the existing content, it appends the cards to the respective row containers again.
- The function effectively creates the layout for the card grid by organizing the cards into two rows.
Flip Card
FlipCard(card)
- Flips a card element when it is clicked.
- If the card is already matched, it returns without doing anything.
- If the card is not matched, it toggles the
flipped
class to change its appearance.
flipCard(clickedCard)
- Calls the
flipCard
function when a card is clicked (clickedCard represents the card element that was clicked). - flippedCards.push(clickedCard)Adds the clickedCard to the
flippedCards
array, which keeps track of the two cards currently flipped. if flippedCards.length === 2
Checks if there are two cards flipped (two elements in theflippedCards
array).- checkMatch()
- Called when two cards are flipped to check if they match.
- Compares the image names of the two flipped cards to see if they are a match.
- If the cards match, they remain face up and are marked as
matched
. - If they don't match, they are flipped back face down after a brief delay.
Check Match
checkMatch()
- Checks if the two flipped cards match based on their image names.
- Retrieves the image names of card1 and card2 using classList and
Array.from()
. - If the cards match, adds the
matched
class to both cards, increments the score, and updates the score display usingdisplayScore()
. - Increases the
matchedPairs
count, and if all pairs are matched, triggersendRound()
after a brief delay. - If the cards don't match, flips them back by calling
flipCard(card)
for each card. - Disables card click event listeners while cards are being matched and re-enables card click event listeners after a brief delay. This is done to prevent more than 2 cards being flipped at one time.
displayScore()
- Retrieves the HTML element displaying the player's score.
- Updates the element's content with the current score value.
Start Round
Start Round (startRound(previousScore))
Sets up the game board for a new round, including shuffling and placing cards, resetting variables, and starting the timer.
startRound(previousScore = 0)
previousScore
(optional): The score from the previous round. Defaults to 0 if not provided.- Initializes various game-related variables: score,
matchedPairs
,flippedCards
,timerActive
,timer
, andremainingSeconds
. - Shuffles the
uniqueImages
array to randomize the order of images for the cards. - Creates card elements using the
createCards()
function with the shuffled images. - Duplicates the cards by creating clones using the
map()
function andcloneNode()
method. - Combines the original and duplicated cards and shuffles them again to randomize their order.
- Creates the game grid by calling the
createGrid()
function with the combined cards. - Initiates the timer for the round by calling the
startTimer()
function. - Updates the score display on the game screen using the
displayScore()
function. - Adds an event listener for the click event on the
gridContainer
, enabling thehandleCardClick()
function when a card is clicked.
End Round
End Round endRound())
Handles the end of a round, triggering round completion or game over popups.
endRound()
- Logs a message to the console indicating the end of the round.
- Stops the timer by clearing the interval set by the
setInterval()
function usingclearInterval(timer)
. - Checks if the number of matched card pairs
matchedPairs
is equal to half of the total number of cardsnumberOfCards / 2
. If true, it means all pairs have been successfully matched. - If all pairs are matched, it logs a congratulatory message to the console and increments the round counter by 1.
- Displays the current round number and opens a popup
(openPopup2())
after a 1-second delay to indicate the completion of the round. - If not all pairs are matched, it means the time ran out before matching all pairs.
- Opens a popup
(openPopup3())
to notify the player that the round is over. - Updates the score display on the game screen using the
displayScore()
function.
Pause, Resume, Start and Restart
startGame()
This function is called to initiate the game.
Actions:
- Hides the overlay element by setting its display property to 'none'. (Hides the click-to-play overlay)
- Calls the
resumeGame()
function to start the game. (this is because thestartRound()
andpause()
are called before this game in order to generate the grid behind the 'click-to-play' overlay).
restartGame()
This function is called to restart the game. Actions:
- Logs a message indicating that the game is being restarted.
- If the game is currently paused (
isPaused
is true), it toggles the play/pause button to show the play button and its text label. - Resets the
isPaused
flag to false. - Clears any existing timers by calling
clearInterval(timer)
. - Resets the
timerActive
flag to false. - Resets the number of matched pairs
(matchedPairs)
to zero. - Empties the array storing flipped cards
(flippedCards)
. - Resets the player's score to zero.
- Resets the round number to one.
- Sets the remaining seconds for the timer to the initial timeout value
(matchTimeout)
. - Updates the timer display with the initial timeout value using the
updateTimerDisplay()
function. - Starts a new round by calling the
startRound()
function. - Updates the score display on the game screen using the
displayScore()
function. - Checks if play pause button eventlistners are active, if not they are re-enabled.
- Removes glow from restart button placed at the end of the game.
pauseGame() -This function is called to pause the game. Actions:
- Checks if the timer is active if not it exits the function.
- Sets the
timerActive
flag to false to pause the timer. - Logs a message indicating that the game is paused.
- Clears the timer interval using
clearInterval(timer)
to stop the timer. - Removes the click event, listener, for card clicks on the game grid by calling
gridContainer.removeEventListener('click', handleCardClick)
. - Toggles the play/pause button to show the play button and its text label.
- Stores the remaining seconds of the timer
(timerElement.textContent)
in theremainingSeconds
variable to track the time when the game was paused. - Adds glow to play button.
resumeGame() This function is called to unpause the game.
Actions:
- Sets the timerActive flag to false.
- Starts the timer by calling the
startTimer()
function. - Adds back the click event listener for card clicks on the game grid by calling
gridContainer.addEventListener('click', handleCardClick)
. - Toggles the play/pause button to show the pause button and its text label.
- Logs a message indicating that the game has been resumed.
togglePlayPauseButton(isPaused)
This function is used to toggle the display of play/pause buttons based on the game's paused state.
Actions:
- If the game is paused (
isPaused
is true), it hides the pause button and shows the play button along with their corresponding text labels. - If the game is not paused (
isPaused
is false), it hides the play button and shows the pause button along with their corresponding text labels.
Parameters:
isPaused
: A boolean indicating whether the game is paused true
or not false
.
Popup/Button/Card Click Handling
openPopup()
- This function is called to open a popup by setting the display property of
popupContainer
toblock
. It also calls thepauseGame()
function to pause the game when the popup is opened.
closePopupHandler()
- This function is called to close a popup by setting the display property of
popupContainer
tonone
. Sets flagisPaused
flag to true.
openPopup2()
- This function is called to open a specific popup
popupContainer2
. It updates the content of the popup to display the current round number. The function sets the display property ofpopupContainer2
toblock
to show the popup.
handleClosePopup2()
- This function is called to close the specific popup
popupContainer2
. It sets the display property ofpopupContainer2
to 'none' to hide the popup. The function also callsstartRound(score)
to start the next round with the current score.
openPopup3()
- This function is called to open a specific popup
popupContainer3
. It sets the display property ofpopupContainer3
toblock
to show the popup.
handlerestartgameButton()
- This function is called when the restart game button on
popupContainer3
is clicked. It closes the popuppopupContainer3
by setting its display property tonone
and then calls therestartGame()
function to restart the game.
handleendgameButton()
- This function is called when the end game button on
popupContainer3
is clicked. It closes the popuppopupContainer3
by setting its display property tonone
. - Sets timerActive flag to false.
- Adds glow animation to restart button.
handleCardClick(event) This function is the event listener for card clicks on the game grid. It takes an event object as a parameter.
Actions:
- It retrieves the clicked card element using event.target.closest('.card').
- If no card is clicked or the clicked card is already flipped, the function returns without further action.
- If a valid card is clicked, it calls the flipCard(clickedCard) function to flip the card.
- The clicked card is added to the
flippedCards
array. - If two cards are flipped (the
flippedCards
array length is 2), it calls thecheckMatch()
function to check if the flipped cards match.
Event Listeners
- Event Listener for Instructions Popup
Attached to: instructionsIcon
Triggers: When the Instructions icon is clicked.
Action: Calls the openPopup()
function to open the pop-up.
- Event Listener for
Close Popup
Button
Attached to: closePopup
Triggers: When the Close Popup button inside the pop-up is clicked.
Action: Calls the closePopupHandler()
function to close the pop-up.
- Event Listener for
Close Popup2
Button
Attached to: closePopup2
Triggers: When the Close Popup2 button inside the second pop-up is clicked.
Action: Calls the handleClosePopup2()
function to close the second pop-up and start a new round with the current score.
- Event Listener for Restart Game Button in Popup3
Attached to: restartgameButton
Triggers: When the Restart Game button inside the third pop-up is clicked.
Action: Calls the handlerestartgameButton()
function to close the third pop-up and restart the game.
- Event Listener for End Game Button in
Popup3
Attached to: endgameButton
Triggers: When the End Game button inside the third pop-up is clicked.
Action: Calls the handleendgameButton()
function to close the third pop-up.
- Event Listener for Overlay Click:
Attached to: overlay Triggers: When the overlay element is clicked. Action: Calls the startGame() function to start the game.
- Event Listener for Card Click:
Attached to: gridContainer Triggers: When any card inside the grid is clicked. Action: Calls the handleCardClick(event) function to handle the card click event.
- Event Listener for Restart Button Click:
Attached to: restartButton Triggers: When the restart game icon is clicked. Action: Calls the restartGame() function to restart the game.
- Event Listener for Pause Button Click:
Attached to: pauseButton Triggers: When the pause game button is clicked. Action: Toggles between pausing and resuming the game by calling the anonymous function. Description of the anonymous function: If eventListenersActive is true (allows the button to respond): If the game is not paused (isPaused is false), it calls the pauseGame() function to pause the game and displays the "Play" button. If the game is already paused (isPaused is true), it calls the resumeGame() function to resume the game and displays the "Pause" button. Finally, it toggles the value of isPaused.
- Event Listener for Play Button Click:
Attached to: playButton Triggers: When the play button is clicked. Action: Calls an anonymous function to handle the play button click event. Description of the anonymous function: If eventListenersActive is true (allows the button to respond): If the game is paused (isPaused is true), it calls the resumeGame() function to resume the game and displays the "Pause" button. It sets isPaused to false to indicate that the game is no longer paused.
Variables & Arrays
-
Initializes various variables to manage the game, such as:
uniqueImages
,score
,timer
,matchTimeout
,numberOfCards
,round
,flippedCards
,matchedPairs
,gridContainer
,combinedCards
,restartButton
,pauseButton
,playButton
,pauseText
,playText
,timerElement
,shuffledImages
,allCards
,duplicatedCards
,isPaused
,instructionsIcon
,popupContainer
,closePopup
,overlay
,popupContainer2
,closePopup2
,popupContainer3
,restartgameButton
andendgameButton
. -
uniqueImages
holds an array of unique image paths used in the game. -
score
represents the player's score, and timer is a reference to the game timer. -
matchTimeout
defines the time limit for each round. -
timerActive
is a flag to track if the timer is currently active. -
numberOfCards
indicates the total number of cards in the game. round stores the current round number. -
flippedCards
is an array to keep track of flipped cards during a round. -
matchedPairs
counts the number of matched card pairs. -
gridContainer
holds the container element for the grid of cards. -
Various elements such as buttons, texts, and pop-up containers are selected and stored in corresponding variables.
-
isPaused
is a flag to track if the game is currently paused. -
timerActive
is a flag to check whether the timer is active.
The site was deployed to GitHub pages. The steps to deploy are as follows:
- In the GitHub repository, on the Code page navigate to the Environments section on the right-hand side of the page. Click on GitHub pages, this will take you to deployments history where you will be able to click on 'view deployment'. The live link can be found here - Link to Live Site Here
Local Deployment
Prerequisites: Github account, Visual Studio Code, Live Server Extension for VScode.
-
To deploy the website locally and test it on your machine, you can follow these steps:
-
Ensure you have Visual Studio Code installed on your computer.
-
Clone the darkwood repository to your local machine using
git clone
. -
Navigate to the root folder of the cloned repository in Visual Studio Code.
-
Locate the main HTML file that represents your website's entry index.html
-
Install the Live Server extension.
-
Right-click on the HTML file in the Explorer sidebar and select "Open with Live Server" from the context menu or select "Go Live" from the bottom bar.
-
The Live Server extension will launch a local development server and automatically open the website in your default web browser.
-
You can now interact with and test the website locally. Any changes you make to the HTML, CSS, or JavaScript files will be automatically refreshed in the browser.
-
To stop the local server, simply close the browser tab displaying the website or click on the "Stop" button in the Live Server output panel.
- All pages (index.html and game.html) passed the W3C HTML validator with no warnings or errors.
- CSS file (style.css) passed the W3C CSS Validator/Jigsaw with no errors and 2 warnings.
- No errors or bugs found in jshint for both js files (script.js and graphics.js).
Click here to view validation screenshots for the HTML,CSS and Javascript code
- Script.js:
- The site was tested on the following devices:
-
Android:
Click here to view a list of Android Phones & Screenshots
- Samsung Galaxy S20
- Xiaomi Mi 11i
- Huawei P20 PRO
- Huawei P30 PRO
- Google Pixel5
- Oneplus Nord 2
- Galaxy Z Flip3
- OPPO Find X3 PRO
- Galaxy A12
- Google Pixel 6 PRO
- Xiaomi 12
Screenshots were taken on the Samsung Galaxy S20 but tested on all devices listed above.
-
iPhones:
-
Tablets:
-
Laptops/PC screens:
Pass | Action | Expected Result | Notes |
---|---|---|---|
Y | Load page | Fade in-out-in animation reveals entry link with Animation glow after intro text | |
Y | Click Entry Link | Link opens game.html | |
Y | Hover GitHub Icon | Hover effect on icon, icon gets lighter | |
Y | Hover LinkedIn Icon | Hover effect on icon, icon gets lighter | |
Y | Click GitHub Icon | New window opens with Github profile displayed | |
Y | Click LinkedIn Icon | New window opens with LinkedIn profile displayed |
Pass | Action | Expected Result | Notes |
---|---|---|---|
Y | Load page | 8 Cards are displayed with front and back, new Round starts and Game is Paused, Overlay displays with option to 'Click to Start', 5 sec glow animation on scroll icon | |
Y | Click on 'Click-to-Start' | Overlay is hidden and game is unpaused, timer display shows countdown from 1min | |
Y | Click Card 1 | Card 1 flip animation and wait for card 2 to be clicked and flip | |
Y | Click Card 2 | Card 2 flip animation and wait for cards to be checked for a match, no other cards should be flipped | |
Y | Click Card 3 | Card 3 flip animation and wait for card 2 to be clicked and flip | |
Y | Click Card 4 | Card 4 flip animation and wait for cards to be checked for a match, no other cards should be flipped | |
Y | Click Card 5 | Card 5 flip animation and wait for card 2 to be clicked and flip | |
Y | Click Card 6 | Card 6 flip animation and wait for cards to be checked for a match, no other cards should be flipped | |
Y | Click Card 7 | Card 7 flip animation and wait for card 2 to be clicked and flip | |
Y | Click Card 8 | Card 8 flip animation and wait for cards to be checked for a match, no other cards should be flipped | |
Y | if 2 Cards Match | 2 Cards stay flipped over, score display increases by 1 | |
Y | if 2 Cards Mismatch | 2 Cards flip back over | |
Y | Match All 8 cards | Next round pop-up appears with upcoming round number displayed, glow animation on close button, game is stopped | |
Y | Click close on Popup | Next round pop-up is hidden, next start round starts with current score | |
Y | 1st or New Round Starts | Cards are shuffled, current score from previous round is passed, timer restarts with 1min given for the round, round 1 starts | |
Y | Timer runs out before all cards are matched | Round has ended, the game stops and the user is prompted via pop-up to restart game or end game | |
Y | Click on 'End Game' on Popup | Pop-up is hidden and the game is completely stopped in a dormant state | |
Y | Click on 'Restart Game' on Popup | Pop-up is hidden and the game is completely restarted at Round 1 with the Timer and Score cleared, Timer should be counting down from 1 min | |
Y | Hover mouse over Pause icon | Hover effect on icon, icon gets lighter | |
Y | Click Pause icon | Play icon appears in place of Pause, Game enters Pause state with the user unable to click on anything and the timer and score retained | |
Y | Hover mouse over Play icon | Hover effect on icon, icon gets lighter | |
Y | Click Play icon | Pause icons appear in place of Play, Game resumes with the previous score and time remaining displayed | |
Y | Hover mouse over Home icon | Hover effect on icon, icon gets lighter | |
Y | Click Home icon | User is directed to the index.html page | |
Y | Hover over Restart icon | Hover effect on icon, icon gets lighter | |
Y | Click Restart icon | Cards are shuffled, cards, score, and timer are reset, new game starts from round 1 | |
Y | Click Instructions (scroll icon) | Popup listing instructions is displayed, including glowing close text, pause game | |
Y | Click close on Popup | Popup is hidden, and the game is resumed with previous time and score. Click to start will need to click to remove overlay if not previously clicked | |
Y | Hover GitHub Icon | Hover effect on icon, icon gets lighter | |
Y | Hover LinkedIn Icon | Hover effect on icon, icon gets lighter | |
Y | Click GitHub Icon | New window opens with Github profile displayed | |
Y | Click LinkedIn Icon | New window opens with LinkedIn profile displayed |
Currently there are no known bugs or issues with the game.
Previous Issues/Bugs that were resolved include:
-
Issue: Too many cards populating in subsequent rounds.
Description: The game experienced a problem where duplicated grid containers were being added at the beginning of each round. As a result, the number of cards kept increasing with each round, leading to unintended behavior.
Resolution: To resolve this issue, an if statement was added to the createGrid function to check if the grid container already exists. If the grid container exists, the function updates the existing grid layout with the new set of cards. If the grid container does not exist, it creates the grid layout and appends the cards to it. This way, the game ensures that only one grid container is used throughout the gameplay, preventing the duplication of cards and providing a consistent and correct number of cards in each round.
if (rowContainers.length === 0) {
-
Issue: Timer Jumping Around when using Restart or Resume feature, causing issues with timeout for unsuccessful rounds.
Description: The game faced an issue where the timer display would jump between different times if the Restart or Resume feature was used multiple times or if the player progressed through additional rounds. This issue was caused by multiple instances of the timer running simultaneously, resulting in conflicting time values and erratic behavior.
Solution: To resolve this issue, a boolean variable was introduced to serve as a flag indicating whether the timer should be active or not. Conditional statements were added inside the startTimer() and resumeGame() functions to check the value of this flag. The timer is only started or resumed if the flag is set to true, ensuring that only one instance of the timer is active at a time. This prevents the display from jumping around and maintains consistent timing throughout the gameplay.
boolean variable
let timerActive = false;
Example of conditional statement used
function startTimer() if (timerActive) { return; }
-
Issue: User able to click on more than two cards during the time that the two cards were flipping and being checked for a match, causing the game to break.
Description: A problem occurred in the game where the user could click on multiple cards while the two cards clicked previously were still flipping and being checked for a match. This led to unintended behavior and broke the game logic.
Solution: To address this issue, a solution was implemented in the checkMatch function. The click event listener for the card elements was temporarily disabled at the beginning of the function, preventing the user from clicking on additional cards while the match was being checked. Once the match check is complete, the event listener is reactivated, allowing the user to continue playing the game normally. This solution ensures that the game's logic remains intact and prevents the user from encountering issues by clicking on more than two cards during the matching process.
-
Cards were created using images from two Artists under a personal use license + permission obtained from the artists:
- Front (Back ingame) of the cards: Slavic Pantheon Hanna Dola Art
- Back (Front ingame) of the cards: Playing Cards Losenko
-
The background image and image featured on the entry page were both downloaded from Vecteezy under a free-use license.
-
Images used on the moodboard were taken from Vecteezy & Shuttershock under a free-use license and Yolo Design with purchase (these images were purchased as prints) and permission from the artist.
- Background Firefly Animations were taken from a youtube tutorial the code can be found [here](https: //github.com/owentr1369/animated-background-fireflies-youtube) a detailed description of what was changed can be found in this section of the Readme.
- The idea for the Keyframes Fade-in-out-in sequence was taken from a few sources:
- CSS Animation Resources here and here
- Keyframes Rule Info here
Resources used for the Game itself:
-
Memory Card Game Tutorials used to get an idea of the overall functionality and structure of the game:
- Ania Kubow
- developedbyed took the shuffle algorithm from here
- freeCodeCamp
-
For the Shuffle, I also used the second method here: https://www.freecodecamp.org/news/how-to-shuffle-an-array-of-items-using-javascript-or-typescript/
-
For the Dynamic Card and Grid Creation in JS I used these resources to implement the ideas and changed them to fit my requirements:
-
For Loops | Using For Loops to Dynamically Display HTML Elements on the Page here
-
For While Loops W3schools
-
Timer resources W3schools freeCodeCamp
-
Time formatting, worked backwards from an SQL query I created, reading about the corresponding JS functionality:
SELECT CASE WHEN minutes > 0 THEN CONCAT(minutes, ':', LPAD(remainingSeconds, 2, '0')) ELSE CAST(remainingSeconds AS VARCHAR(2)) END AS formatted_time FROM table_name;
- [padstart] (https://www.w3schools.com/jsref/jsref_string_padstart.asp)
- [tostring] https://www.geeksforgeeks.org/how-to-convert-seconds-to-time-string-format-hhmmss-using-javascript/
-
For the Rounds implemented in the game, the idea was taken from here.
-
Restart function resource here
-
Resource for how to implement pop-ups here
-
A snippet of HTML (social media menu in the footer) has been taken from the Love Running project here
-
For the timer I used this code as a source here
- I would like to acknowledge my daughter and husband for their support and understanding during this time of intense learning.
- My mentor Juliia for her guidance and suggestions who went above and beyond pushing me and making sure I could make my vision for this website come to life.
- Set up a database to be able to implement a leaderboard.
- Wave Testing.
- Reduction of cards based on screen-size to improve responsiveness.