diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f673a71 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5502 +} \ No newline at end of file diff --git a/README.md b/README.md index e4fdfbf..5d6e1bf 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -[](https://classroom.github.com/online_ide?assignment_repo_id=10911957&assignment_repo_type=AssignmentRepo) + +# assignment-2---css-games-group_12 + + +The project is a memory card game where the player needs to match pairs of cards with identical images. It first defines a function to handle the clicking of checkboxes, which checks if two checkboxes have been clicked and matches the images on the cards. If they match, the checkboxes remain checked, and if they don't match, they become unchecked after a second. The code then shuffles an array of image URLs and assigns them to the cards, creating randomized cards for the game. + +This game is useful because it creates an interactive game that can provide entertainment and cognitive stimulation for users. Memory games are also a popular way to improve memory and concentration skills. This game is targeted at a younger audience. + +The game is built using only html, css and javascript. + +The project is be public and can be found at [https://github.com/AdvancedCSS2023/assignment-2---css-games-group_12](https://github.com/AdvancedCSS2023/assignment-2---css-games-group_12) For help feel free to contact any of the repository contributors. This game is made by a group of web development students at NTNU Gjovik. The group consists of Petter, Emil and Tobias. diff --git a/images/african-background.jpeg b/images/african-background.jpeg new file mode 100644 index 0000000..ec6a82d Binary files /dev/null and b/images/african-background.jpeg differ diff --git a/images/african-village.jpeg b/images/african-village.jpeg new file mode 100644 index 0000000..d5d0f34 Binary files /dev/null and b/images/african-village.jpeg differ diff --git a/images/plane.jpeg b/images/plane.jpeg new file mode 100644 index 0000000..385d2a0 Binary files /dev/null and b/images/plane.jpeg differ diff --git a/images/replay.png b/images/replay.png new file mode 100644 index 0000000..8a15f82 Binary files /dev/null and b/images/replay.png differ diff --git a/images/university.jpeg b/images/university.jpeg new file mode 100644 index 0000000..80343d6 Binary files /dev/null and b/images/university.jpeg differ diff --git a/images/water.jpeg b/images/water.jpeg new file mode 100644 index 0000000..8506c2a Binary files /dev/null and b/images/water.jpeg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..e44e93e --- /dev/null +++ b/index.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Memory game</title> + <link rel="stylesheet" href="styles/styling.css"> + </head> + + <body> + <h1 class="page__title">African Themed Memory Game</h1> + + <div class="game-container"> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card1" /> + <label for="card1" class="card__label"> + <img + src="./images/african-village.jpeg" + alt="Card 1" + class="card__img" + /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card2" /> + <label for="card2" class="card__label"> + <img + src="./images/african-village.jpeg" + alt="Card 1" + class="card__img" + /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card3" /> + <label for="card3" class="card__label"> + <img src="./images/plane.jpeg" alt="Card 2" class="card__img" /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card4" /> + <label for="card4" class="card__label"> + <img src="./images/plane.jpeg" alt="Card 2" class="card__img" /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card5" /> + <label for="card5" class="card__label"> + <img src="./images/university.jpeg" alt="Card 3" class="card__img" /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card6" /> + <label for="card6" class="card__label"> + <img src="./images/university.jpeg" alt="Card 3" class="card__img" /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card7" /> + <label for="card7" class="card__label"> + <img src="./images/water.jpeg" alt="Card 4" class="card__img" /> + </label> + </div> + <div class="card"> + <input type="checkbox" class="card__checkbox" id="card8" /> + <label for="card8" class="card__label"> + <img src="./images/water.jpeg" alt="Card 4" class="card__img" /> + </label> + </div> + </div> + + + <a href="#" onclick="location.href = location.href + '?t=' + Date.now()"> + <img src="/images/replay.png" id="replay" alt="Play Again Icon" /> + </a> + + <script src="/script.js" type="text/javascript"></script> + </body> +</html> diff --git a/script.js b/script.js new file mode 100644 index 0000000..c1f32a7 --- /dev/null +++ b/script.js @@ -0,0 +1,69 @@ +// Select all elements with the class 'card-checkbox' and store them in the 'checkboxes' variable +const checkboxes = document.querySelectorAll('.card__checkbox'); +// Create variables 'firstCard' and 'secondCard' +let firstCard, secondCard; + +// Defining 'handleCheck' and making a function out of it +function handleCheck() { + // If the clicked checkbox is not the same as 'firstCard', then put 'secondCard' to the clicked checkbox + if (this !== firstCard) { + secondCard = this; + + // Making an if statement when both 'firstCard' and 'secondCard are defined + if (firstCard && secondCard) { + // and if the images of both 'firstCard' and 'secondCard' are the same + if (firstCard.nextElementSibling.querySelector('img').src === secondCard.nextElementSibling.querySelector('img').src) { + // Set both checkboxes to checked and reset both 'firstCard' and 'secondCard' to null + firstCard.checked = true; + secondCard.checked = true; + firstCard = null; + secondCard = null; + // If the cards are different, then uncheck the checkboxes and reset them to null after 1000ms/1 second + } else { + // If 'firstCard' is not defined, set it to the clicked checkbox + setTimeout(() => { + firstCard.checked = false; + secondCard.checked = false; + firstCard = null; + secondCard = null; + }, 1000); + } + } else { + firstCard = this; + } + } +} + +// Add event listeners to all checkboxes, and making a click function on the 'handleCheck'. +checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck)); +// Select all elements with the class 'card-label' and store them in the 'cards' variable +const cards = document.querySelectorAll('.card__label'); +// Create an array of image URLs +const imgUrls = [ './images/african-village.jpeg', './images/plane.jpeg', './images/university.jpeg', './images/water.jpeg']; + +// This code shuffels the order of the cards at random (the array), by defining the 'shuffle' function. +function shuffle(array) { + let currentIndex = array.length; + let temporaryValue, randomIndex; + + while (currentIndex !== 0) { + // pick a random index from 0 to currentINdex -1 + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + // Swap the element at the current index with the element at the random index + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } +// Return the shuffled array + return array; +} +// Calling the 'shuffel' function with the 'imgUrls' array as argument, which shuffles the array in place +shuffle(imgUrls); + +// Set the 'src' attribute of each image in the 'cards' array to a shuffled image URL +// The modulus operator (%) ensures that the shuffled URL are repeated if there are more cards than URL +for (let i = 0; i < cards.length; i++) { + cards[i].querySelector('img').src = imgUrls[i % imgUrls.length]; +} + diff --git a/styles/_animations.scss b/styles/_animations.scss new file mode 100644 index 0000000..d0d2d8b --- /dev/null +++ b/styles/_animations.scss @@ -0,0 +1,11 @@ +@keyframes hover-jump { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } + 100% { + transform: translateY(0); + } +} \ No newline at end of file diff --git a/styles/_variables.scss b/styles/_variables.scss new file mode 100644 index 0000000..4cc4310 --- /dev/null +++ b/styles/_variables.scss @@ -0,0 +1,9 @@ +$background-color-brown: #4b2e2e; +$border-container-color-gold: goldenrod; +$card-boarder-black: black; +$card-background-grey: #aaa; +$card-hover-white: rgb(69, 61, 61); +$card-label-color-white: rgba(255, 255, 255, 0.8); +$standard-border-radius: 10px; +$standard-width: 100%; +$standard-height: 100%; diff --git a/styles/styling.css b/styles/styling.css new file mode 100644 index 0000000..3d321bc --- /dev/null +++ b/styles/styling.css @@ -0,0 +1,165 @@ +@import url("https://fonts.googleapis.com/css2?family=Nabla&display=swap"); +@keyframes hover-jump { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } + 100% { + transform: translateY(0); + } +} +@keyframes hover-jump { + 0% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } + 100% { + transform: translateY(0); + } +} +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + text-align: center; + background-color: #4b2e2e; +} + +.game-container { + border: solid goldenrod; + max-width: 80%; + max-height: 100%; + margin: auto; + background-image: url(../images/african-background.jpeg); + background-repeat: round; + border-radius: 10px; +} + +.page__title { + text-align: center; + margin-top: 50px; + font-size: 40px; + font-family: "Nabla", cursive; + margin-bottom: 2em; +} + +.card { + width: 200px; + height: 200px; + margin: 10px; + border-radius: 10px; + cursor: pointer; + position: relative; + display: inline-block; + position: relative; + background: #aaa; + margin: 2em; + border: solid black; + background: #aaa; + margin: 2em; + border: solid black; +} + +.card__img { + display: block; + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.card__checkbox { + display: none; +} + +.card:hover { + background-color: rgb(69, 61, 61); +} + +.card:after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 10px; + z-index: 1; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.card:hover:after { + opacity: 1; +} + +.card__label { + display: block; + position: relative; + width: 100%; + height: 100%; + border-radius: 10px; + overflow: hidden; +} + +.card__label::after { + background: rgba(255, 255, 255, 0.8); + content: ""; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 1; +} + +.card__label img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + -o-object-fit: cover; + object-fit: cover; + opacity: 0; + transition: opacity 0.2s ease-in-out; + z-index: 2; +} + +.card__checkbox:checked + label img { + opacity: 1; +} + +.card__checkbox:checked ~ .card-checkbox:checked + label img { + opacity: 1; +} + +.matched { + pointer-events: none; +} + +@media (max-width: 768px) { + .game-container { + max-width: 500px; + margin: 0 auto; + } +} +img#replay { + display: block; + margin: 2% auto; + max-height: 30%; + max-width: 30%; + cursor: pointer; + margin: 2% auto; +} + +img#replay:hover { + animation: hover-jump 1s infinite; +}/*# sourceMappingURL=styling.css.map */ \ No newline at end of file diff --git a/styles/styling.css.map b/styles/styling.css.map new file mode 100644 index 0000000..c2e50f1 --- /dev/null +++ b/styles/styling.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["styling.scss","_animations.scss","styling.css","_variables.scss"],"names":[],"mappings":"AAAQ,0EAAA;ACAR;EACI;IACI,wBAAA;ECEN;EDAE;IACI,4BAAA;ECEN;EDAE;IACI,wBAAA;ECEN;AACF;ADXA;EACI;IACI,wBAAA;ECaN;EDXE;IACI,4BAAA;ECaN;EDXE;IACI,wBAAA;ECaN;AACF;AFjBA;EACE,sBAAA;EACA,SAAA;EACA,UAAA;AEmBF;;AFhBA;EACE,kBAAA;EAEA,yBGduB;ADgCzB;;AFfA;EACE,uBAAA;EACA,cAAA;EACA,gBGZgB;EHahB,YAAA;EACA,wDAAA;EACA,wBAAA;EACA,mBAAA;AEkBF;;AFfA;EACE,kBAAA;EACA,gBAAA;EACA,eAAA;EACA,6BAAA;EACA,kBAAA;AEkBF;;AFfA;EACE,YAAA;EACA,aAAA;EACA,YAAA;EACA,mBGjCuB;EHkCvB,eAAA;EACA,kBAAA;EACA,qBAAA;EAEA,kBAAA;EACA,gBAAA;EACA,WAAA;EACA,mBAAA;EAEA,gBG9CqB;EH+CrB,WAAA;EACA,mBAAA;AEgBF;;AFbA;EACE,cAAA;EACA,WGjDe;EHkDf,YGjDgB;EHkDhB,oBAAA;KAAA,iBAAA;AEgBF;;AFbA;EACE,aAAA;AEgBF;;AFbA;EACE,iCG9DiB;AD8EnB;;AFbA;EACE,WAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,WGnEe;EHoEf,YGnEgB;EHoEhB,mBGtEuB;EHuEvB,UAAA;EACA,UAAA;EACA,oCAAA;AEgBF;;AFbA;EACE,UAAA;AEgBF;;AFbA;EACE,cAAA;EACA,kBAAA;EACA,WGlFe;EHmFf,YGlFgB;EHmFhB,mBGrFuB;EHsFvB,gBAAA;AEgBF;;AFbA;EACE,oCG3FuB;EH4FvB,WAAA;EACA,YG1FgB;EH2FhB,OAAA;EACA,kBAAA;EACA,MAAA;EACA,WG/Fe;EHgGf,UAAA;AEgBF;;AFbA;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,WGvGe;EHwGf,YGvGgB;EHwGhB,oBAAA;KAAA,iBAAA;EACA,UAAA;EACA,oCAAA;EACA,UAAA;AEgBF;;AFbA;EACE,UAAA;AEgBF;;AFbA;EACE,UAAA;AEgBF;;AFbA;EACE,oBAAA;AEgBF;;AFbA;EACE;IACE,gBAAA;IACA,cAAA;EEgBF;AACF;AFbA;EACE,cAAA;EACA,eAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;EACA,eAAA;AEeF;;AFZA;EACE,iCAAA;AEeF","file":"styling.css"} \ No newline at end of file diff --git a/styles/styling.scss b/styles/styling.scss new file mode 100644 index 0000000..0c90e22 --- /dev/null +++ b/styles/styling.scss @@ -0,0 +1,149 @@ +@import url("https://fonts.googleapis.com/css2?family=Nabla&display=swap"); +@import "./animations"; +@import "./variables"; +@import "./animations"; + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + text-align: center; + + background-color: $background-color-brown; +} + +.game-container { + border: solid $border-container-color-gold; + max-width: 80%; + max-height: $standard-height; + margin: auto; + background-image: url(../images/african-background.jpeg); + background-repeat: round; + border-radius: 10px; +} + +.page__title { + text-align: center; + margin-top: 50px; + font-size: 40px; + font-family: "Nabla", cursive; + margin-bottom: 2em; +} + +.card { + width: 200px; + height: 200px; + margin: 10px; + border-radius: $standard-border-radius; + cursor: pointer; + position: relative; + display: inline-block; + + position: relative; + background: #aaa; + margin: 2em; + border: solid black; + + background: $card-background-grey; + margin: 2em; + border: solid $card-boarder-black; +} + +.card__img { + display: block; + width: $standard-width; + height: $standard-height; + object-fit: cover; +} + +.card__checkbox { + display: none; +} + +.card:hover { + background-color: $card-hover-white; +} + +.card:after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: $standard-width; + height: $standard-height; + border-radius: $standard-border-radius; + z-index: 1; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.card:hover:after { + opacity: 1; +} + +.card__label { + display: block; + position: relative; + width: $standard-width; + height: $standard-height; + border-radius: $standard-border-radius; + overflow: hidden; +} + +.card__label::after { + background: $card-label-color-white; + content: ""; + height: $standard-height; + left: 0; + position: absolute; + top: 0; + width: $standard-width; + z-index: 1; +} + +.card__label img { + position: absolute; + top: 0; + left: 0; + width: $standard-width; + height: $standard-height; + object-fit: cover; + opacity: 0; + transition: opacity 0.2s ease-in-out; + z-index: 2; +} + +.card__checkbox:checked + label img { + opacity: 1; +} + +.card__checkbox:checked ~ .card-checkbox:checked + label img { + opacity: 1; +} + +.matched { + pointer-events: none; +} + +@media (max-width: 768px) { + .game-container { + max-width: 500px; + margin: 0 auto; + } +} + +img#replay { + display: block; + margin: 2% auto; + max-height: 30%; + max-width: 30%; + cursor: pointer; + margin: 2% auto; +} + +img#replay:hover { + animation: hover-jump 1s infinite; +}