반응형
가장 짧은 시간 안에 주어진 문제의 답이 맞는지 맞추는 게임입니다.
<!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>Math Sprint</title>
<script src="https://kit.fontawesome.com/833be080c6.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="./main.css">
<script defer type="module" src="./index.js"></script>
</head>
<body>
<!-- Main container -->
<div class="card">
<!-- Title -->
<h1 class="title">math sprint</h1>
<!-- Stage 1 -->
<div class="contents">
<input id="1" name="choice" type="radio" value="10">
<label for="1"> 10 questions
<small>best score: <span class="best-record"></span>''</small>
</label>
<input id="2" name="choice" type="radio" value="40">
<label for="2"> 40 questions
<small>best score: <span class="best-record"></span>''</small>
</label>
<input id="3" name="choice" type="radio" value="70">
<label for="3"> 70 questions
<small>best score: <span class="best-record"></span>''</small>
</label>
<input id="4" name="choice" type="radio" value="120">
<label for="4">120 questions
<small>best score: <span class="best-record"></span>''</small>
</label>
</div>
<!-- Countdown -->
<div class="contents hide">
<div class="count">
<div class="num num1">1</div>
<div class="num num2">2</div>
<div class="num num3">3</div>
<div class="num num4">Go</div>
</div>
</div>
<!-- Stage 2 -->
<div class="contents hide question-box">
<div class="current-question"></div>
<div class="question">1</div>
<div class="question">1</div>
<div class="question">1</div>
<div class="question">1</div>
</div>
<!-- Stage 3 -->
<div class="contents hide">
<p class="result">YOUR TIME</p>
<h1><span class="result-time"></span>''</h1>
<p class="result">base time: <span class="result-base"></span>''</p>
<p class="result">penalty: <span class="result-penalty"></span>''</p>
</div>
<!-- Buttons -->
<div class="btn-container">
<button class="btn btn-main">start</button>
<button class="btn btn-wrong" hidden>wrong</button>
<button class="btn btn-correct" hidden>correct</button>
<button class="btn btn-reset" hidden>try again</button>
</div>
</div>
</body>
</html>
CSS
@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: flex;
font-family: 'Lato';
align-items: center;
justify-content: center;
background-color: rgb(154, 218, 218);
text-transform: capitalize;
}
/* Card styles */
.card {
height: 400px;
width: 300px;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
flex-direction: column;
justify-content: space-between;
border-radius: 5px;
box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
}
/* Title styles */
h1 {
background-color: #696969;
text-align: center;
padding: 5px;
color: #fff;
}
/* Stages global styles */
.contents {
height: 100%;
overflow: hidden;
}
.contents.hide {
display: none;
}
.contents label {
height: 40px;
display: flex;
align-items: center;
margin: 5px;
padding: 0 0 0 10px;
border: 1px solid #696969;
border-radius: 5px;
position: relative;
}
/* Stage - 1 styles */
small {
width: 140px;
text-align: center;
display: inline-block;
position: absolute;
right: -4px;
top: -4px;
background-color: rgb(107, 188, 94);
border-radius: 2px;
box-shadow: 1px 1px 1px 1px rgba(154, 218, 218, 0.3);
color: #fff;
}
.contents input {
display: none;
}
.contents input:checked + label {
animation: select 1s ease forwards;
}
@keyframes select {
from {
background-color: #696969;
}
to {
background-color: rgb(0, 208, 255);
color: #fff;
}
}
/* Countdown styles */
.count {
height: 60px;
width: 60px;
position: absolute;
top: 50%;
left: 50%;
margin: auto;
overflow: hidden;
transform: translate(-50%, -50%);
}
.count .num {
height: 50px;
position: absolute;
bottom: -5px;
left: 50%;
transform-origin: bottom;
transform: translateX(-50%) rotate(180deg);
font-size: 2.5rem;
}
.count .num:nth-child(1) {
animation: spin 2s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
}
.count .num:nth-child(2) {
animation: spin 2s 2s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
}
.count .num:nth-child(3) {
animation: spin 2s 4s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
}
.count .num:nth-child(4) {
animation: spin 2s 6s cubic-bezier(0.075, 0.82, 0.165, 1) forwards;
}
@keyframes spin {
0% {
transform: translateX(-50%) rotate(180deg);
}
30% {
transform: translateX(-50%) rotate(-10deg);
}
60% {
transform: translateX(-50%) rotate(10deg);
}
100% {
transform: translateX(-50%) rotate(-180deg);
}
}
/* Stage - 2 styles */
.question-box {
display: flex;
flex-direction: column;
justify-content: end;
position: relative;
}
.current-question {
height: 42px;
width: 100%;
border: 4px solid rgb(134, 205, 161);
position: absolute;
top: 132px;
animation: blink 1s ease infinite;
}
.question {
height: 40px;
display: flex;
flex-shrink: 0;
align-items: center;
padding: 0 10px;
border: 1px solid rgb(205, 193, 134);
margin: 3px;
border-radius: 2px;
}
@keyframes blink {
from {
opacity: 0.3;
}
to {
opacity: 1;
}
}
/* Stage - 3 styles */
.result {
text-align: center;
margin: 10px;
font-size: 1.4rem;
margin: 30px 0;
}
/* Button styles */
.btn-container {
height: 50px;
background-color: #696969;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.btn-container .btn {
height: 32px;
width: 100px;
background-color: aliceblue;
border: none;
border-radius: 5px;
transition: 0.2s ease;
font-size: 1.1rem;
text-transform: uppercase;
}
.btn-container .btn:hover {
filter: brightness(110%);
}
.btn-container .btn:active {
transform: scale(0.97);
}
.btn-container .btn-wrong {
background-color: rgb(245, 145, 145);
}
.btn-container .btn-correct {
background-color: greenyellow;
}
index.js
import { chooseOperator, chooseNumber, getAnswer, getWrongAnswer } from './quiz.generator.js'
// Elements
const btnMain = document.querySelector('.btn-main')
const btnWrong = document.querySelector('.btn-wrong')
const btnCorrect = document.querySelector('.btn-correct')
const btnReset = document.querySelector('.btn-reset')
const inputs = document.querySelectorAll('input')
const contents = document.querySelectorAll('.contents')
const questionBox = document.querySelector('.question-box')
// Variables
let questions = []
let answers = []
let contentIndex = 0
let questionMax = 0
let questionId = 0
let questionIdx = 0
let startTime = 0
let record = 0
let penalty = 0
let bestScores = {
0: 0,
1: 0,
2: 0,
3: 0,
}
// Local storage
function updateRecord() {
if (localStorage.getItem('bestScores')) {
const bestEls = document.querySelectorAll('.best-record')
bestScores = JSON.parse(localStorage.getItem('bestScores'))
Object.keys(bestScores).forEach((key, idx) => {
bestEls[idx].textContent = bestScores[key]
})
}
}
updateRecord()
// Event handlers
btnMain.addEventListener('click', () => {
inputs.forEach((input, idx) => {
if (input.checked) {
changeContentsBox()
questionId = idx
questionMax = input.value
createQuestions()
}
})
if (questionMax === 0) {
alert('Select an option')
}
})
btnWrong.addEventListener('click', () => {
answers.push(false)
moveQuestions()
})
btnCorrect.addEventListener('click', () => {
answers.push(true)
moveQuestions()
})
btnReset.addEventListener('click', () => {
questions = []
answers = []
contentIndex = -1
questionMax = 0
questionId = 0
questionIdx = 0
startTime = 0
record = 0
penalty = 0
btnMain.hidden = false
btnReset.hidden = true
document.querySelectorAll('.question').forEach((initialQuestionEl) => {
initialQuestionEl.remove()
})
changeContentsBox()
})
// Functions
function beginTimer() {
startTime = new Date()
}
function endTimer() {
record = new Date() - startTime
}
function createQuestions() {
for (let i = 0; i < questionMax; i++) {
const num1 = chooseNumber()
const num2 = chooseNumber()
let operator = chooseOperator()
let trueFalse = Math.floor(Math.random() * 2) === 0 ? true : false
const answer = getAnswer(num1, operator, num2)
questions.push({
question: `${num1} ${operator} ${num2} = ${
trueFalse ? answer : getWrongAnswer(answer, operator)
}`,
correct: trueFalse,
})
}
addInitialQuiz()
}
function addInitialQuiz() {
const initialQuestionsEl = document.querySelectorAll('.question')
if (initialQuestionsEl.length === 0) {
for (let i = 0; i < 4; i++) {
const newQuestionEl = document.createElement('div')
newQuestionEl.className = 'question'
newQuestionEl.textContent = questions[questionIdx].question
questionIdx++
questionBox.appendChild(newQuestionEl)
}
}
initialQuestionsEl.forEach((initialQuestionEl) => {
initialQuestionEl.textContent = questions[questionIdx].question
questionIdx++
})
}
function changeContentsBox() {
contentIndex++
if (contentIndex > 3) contentIndex = 0
contents.forEach((content) => {
content.classList.add('hide')
})
contents[contentIndex].classList.remove('hide')
if (contentIndex === 1) {
btnMain.hidden = true
setTimeout(() => {
changeContentsBox()
btnCorrect.hidden = false
btnWrong.hidden = false
beginTimer()
}, 7500)
}
if (contentIndex === 3) {
const recordTime = +(record / 1000).toFixed(2)
const newRecord = +(recordTime + penalty).toFixed(2)
document.querySelector('.result-time').textContent = newRecord
document.querySelector('.result-base').textContent = recordTime
document.querySelector('.result-penalty').textContent = penalty
if (bestScores[questionId] > newRecord) {
bestScores[questionId] = newRecord
}
localStorage.setItem('bestScores', JSON.stringify(bestScores))
btnCorrect.hidden = true
btnWrong.hidden = true
btnReset.hidden = false
updateRecord()
}
}
function moveQuestions() {
const newQuestion = document.createElement('div')
newQuestion.className = 'question'
if (questionIdx < questions.length) {
newQuestion.textContent = questions[questionIdx].question
} else if (questionIdx == questions.length) {
newQuestion.textContent = 'finished'
newQuestion.style.display = 'flex'
newQuestion.style.justifyContent = 'center'
newQuestion.style.textTransform = 'uppercase'
newQuestion.style.color = '#fff'
newQuestion.style.backgroundColor = '#696969'
} else {
newQuestion.textContent = ''
}
if (questionIdx > questions.length + 2) {
getPenalty()
endTimer()
changeContentsBox()
}
if(questionIdx > 6) document.querySelector('.question').remove()
questionIdx++
questionBox.appendChild(newQuestion)
}
function getPenalty() {
questions.forEach((q, idx) => {
if (q.correct !== answers[idx]) penalty += 0.5
})
}
quiz.generator.js
// operation
const operators = ['-', '+', '/', '*']
// Returns a random operator
function chooseOperator() {
return operators[Math.floor(Math.random() * operators.length)]
}
// Returns a random number
function chooseNumber() {
return Math.floor(Math.random() * 150)
}
// Returns the correct answer
function getAnswer(num1, operator, num2) {
switch (operator) {
case '-':
return num1 - num2
case '+':
return num1 + num2
case '/':
return num1 / num2
case '*':
return num1 * num2
}
}
// Returns a wrong answer
function getWrongAnswer(answer, operator) {
const randomNumber = Math.floor(Math.random() * 10 + 1)
switch (operator) {
case '-':
return answer - randomNumber
case '+':
return answer + randomNumber
case '/':
return answer / randomNumber
case '*':
return answer * randomNumber
}
}
export { chooseOperator, chooseNumber, getAnswer, getWrongAnswer }
데모
Document
Math Sprint 10 questions best score: 40 questions best score: 70 questions best score: 120 questions best score: 1 2 3 Go 1 1 1 1 your time 11s base time: penalty: start wrong correct
jin-co-jcg.vercel.app
프로젝트 출처
https://www.udemy.com/course/javascript-web-projects-to-build-your-portfolio-resume/
728x90
반응형
'프론트엔드 > 자바스크립트' 카테고리의 다른 글
드래그 앤 드랍 (0) | 2022.12.26 |
---|---|
프로젝트 - 가위 바위 보 도마뱀 스폭 게임 (0) | 2022.12.25 |
연산자 (Operators) (0) | 2022.12.24 |
모듈 (Module) (0) | 2022.12.21 |
웹팩 (webpack) (0) | 2022.12.21 |