This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang=»ru»> | |
<head> | |
<meta charset=»utf-8″> | |
<meta http-equiv=»X-UA-Compatible» content=»IE=edge»> | |
<title>Игра в загадки </title> | |
<meta name=»author» content=»Vladimir Pavlychev http://vk.com/vovs03″> | |
<script> | |
// убрал объявленную переменную zag | |
function getG(){ | |
// Игра в загадки | |
var zag = prompt(«В огороде, на дорожке,n» + | |
«Под моим окошком n» + | |
«Расцвело сегодня солнце n» + | |
«На высокой ножке.» | |
); | |
if (zag ==»подсолнух» || zag == «Подсолнух» || zag == «ПОДСОЛНУХ» || zag == «пОДСОЛНУХ»){ | |
alert(«Поздравляем! Вы угадалиn» + | |
» Правильный ответ: » + zag ); | |
} | |
else { | |
alert(«Вы не отгадали…»); | |
} | |
alert(«Игра закончена.»); | |
} | |
</script> | |
</head> | |
<body> | |
<h1>Игра в загадки.</h1> | |
<ul> | |
<li>a. Загадать загадку. Если ответ верен – поздравить пользователя. Затем сообщить, что | |
игра окончена. | |
<button onclick=»getG();»>Отгадай загадку</button> | |
</li> | |
<li>b. Если ответ неверный – написать пользователю, что он не угадал.</li> | |
<li>c. Добавить еще 3 загадки. Подсчитать количество правильных ответов, сообщить | |
пользователю.</li> | |
</ul> | |
</body> | |
</html> |
Народ, помогите пожалуйста, функции с массивами никак не осилю, задали задание, не могу понять как реализовать
a. Избавиться в игре в загадки от дублирования кода, используя массивы и функции.
b. Сделать так, чтобы программа поддерживала несколько правильных ответов на один и тот же вопрос. Все возможные варианты задаются программистом в коде программы.
Пример загадки:
Javascript | ||
|
__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь
Однажды, когда я сидел в универе на скучной паре, мне почему-то вспомнилась одна забавная головоломка из квеста «Машинариум». Я давно забыл, как она решается и мне почему-то очень захотелось вспомнить решение. Рисовать ход решения на бумаге мне показалось утомительным, и я решил написать эту головоломку на js. Позднее мне захотелось поделиться этой забавной игрушкой с вами. Головоломка безымянная, но чтобы хоть как-то обозначить, о чём мы будем говорить, я решил назвать её «cтрелки».
Правила
Игровое поле в этой головоломке представляет собой семь вертикально расположенных ячеек, в трёх верхних из которых расположены стрелки, указывающие вниз, в трёх нижних – стрелки, указывающие вверх, а центральная ячейка пустая. Задача состоит в том, чтобы расположить стрелки, указывающие вниз, в трёх нижних ячейках, а стрелки указывающие вверх в трёх верхних. Указывающие вниз стрелки можно передвигать только вниз, указывающие вверх только вверх. Стрелка может переместится только в пустую ячейку перед собой, либо перепрыгнуть в пустую ячейку через заполненную.
HTML
Я решил, что игровое поле будет представляться в виде таблицы, а стрелки будут символами ↑ и ↓. Так же я решил добавить возможность делать шаг назад в решении.
<table id="game-table"> <tr> <td>↓</td> </tr> <tr> <td>↓</td> </tr> <tr> <td>↓</td> </tr> <tr> <td></td> </tr> <tr> <td>↑</td> </tr> <tr> <td>↑</td> </tr> <tr> <td>↑</td> </tr> </table> <a href="#">← шаг назад</a>
CSS
Для того, чтобы игрушка выглядела более привлекательно я добавил css-код.
body { text-align : center; background-color: #2a2a2a; font-family: Tahoma; } #game-table { width: 60px; height: 420px; margin: 20px auto; } #game-table td { border: solid 1px gray; font-size: 35px; color: gray; height: 50px; } #game-table td:hover { color: white; cursor: pointer; border-color: white; } a { color: gray; cursor: pointer; text-decoration: none; margin: 10px; }
На это работа над внешним видом «стрелок» была закончена.
JavaScript
Написание js-кода я начал с объявления констант. Как таковых констант в js, к сожалению, нет, но значения, которые считаются константными желательно хранить в переменных, имена которых написаны большими буквами.
var UP = 1, DOWN = -1, EMPTY = 0;
Я советую вам всегда использовать подобный подход и избегать использования в коде повторяющихся числовых значений для того, чтобы не заставлять читающего ваш код человека или даже себя думать, что же всё таки значит значение, встретившееся в коде.
Затем я объявил массив, хранящий исходное положение стрелок и массив, который хранит их выигрышное положение.
var arrows = [DOWN, DOWN, DOWN, EMPTY, UP, UP, UP], rightArrows = [UP, UP, UP, EMPTY, DOWN, DOWN, DOWN];
Также мне понадобился массив, который будет хранить ячейки таблицы, которая и представляет собой игровое поле.
var cells = [];
Для реализации возможности возвращения на шаг назад необходимо хранить номера ячеек, которые были задействованы на каком-либо шаге. Для этих целей я использовал ещё один массив.
var steps = [];
Инициализировать игрушку будет функция arrowsGame, в качестве параметров она принимает таблицу, которая представляет собой игровое поле, и, необязательно, функцию, которая сработает, когда головоломка будет решена.
function arrowsGame(table, onWin) { /* * Получаем стоки таблицы и * обявляем переменну для хранение ячейки */ var rows = table.rows, cell; for (var i = 0; i < rows.length; i++) { cell = rows[i].cells[0]; // Запоминаем номер ячейки cell.setAttribute('data', i); // Обрабатываем клик по ячейке cell.onclick = cellClick; // Сохраняем ячейки cells.push(cell); } /* * Если параметр onWin был передан, * то переопределяем заданную по умолчанию функцию, * вызывающеюся, если решение найдено. */ if (typeof onWin == 'function') { onWinDefault = onWin; } }
Функция onWinDefault выглядит просто, но сложнее и не нужно, ведь её всегда можно переопределить передав в параметре onWin другую функцию.
function onWinDefault() { alert('Ура!'); }
Функция cellClick, вызывающаяся при клике по ячейке таблицы выглядит вот так:
function cellClick(e) { var e = e || window.event, el = e.srcElement || e.target, i = parseInt(el.getAttribute('data')); if (arrows[i] == UP) { if (arrows[i - 1] == EMPTY) { swap(el, cells[i - 1]); } else if (arrows[i - 2] == EMPTY) { swap(el, cells[i - 2]); } } else if (arrows[i] == DOWN) { if (arrows[i + 1] == EMPTY) { swap(el, cells[i + 1]); } else if (arrows[i + 2] == EMPTY) { swap(el, cells[i + 2]); } } if (isWin()) { onWinDefault(); } }
В качестве параметра эта функция в полноценных браузерах принимает объект события в котором хранится элемент, с которым это событие произошло (источник события), и прочие данные. Для обеспечения кроссбраузерного получения объекта события нужна строка:
var e = e || window.event,
Для кроссбраузерного получения источника события нужен следующий код:
el = e.srcElement || e.target
Далее происходит извлечение номера ячейки и приведение его к целочисленному типу.
i = parseInt(el.getAttribute('data'));
Далее в этой функции следует код, реализующий сам ход, если он возможен. Обратите внимание, что использование «констант» вносит некоторую ясность в код, благодаря их говорящим именам.
if (arrows[i] == UP) { if (arrows[i - 1] == EMPTY) { swap(el, cells[i - 1]); } else if (arrows[i - 2] == EMPTY) { swap(el, cells[i - 2]); } } else if (arrows[i] == DOWN) { if (arrows[i + 1] == EMPTY) { swap(el, cells[i + 1]); } else if (arrows[i + 2] == EMPTY) { swap(el, cells[i + 2]); } }
Далее, если положение стрелок выигрышное вызывается функция onWinDefault.
if (isWin()) { onWinDefault(); }
Функция swap меняет местами содержимое двух ячеек, задействованных при ходе и производит соответствующие изменения в массиве arrows.
function swap(cell1, cell2, isUndo) { // Извлекаем номера ячеек var i1 = parseInt(cell1.getAttribute('data')), i2 = parseInt(cell2.getAttribute('data')), t; // меняем местами их содержимое t = cell1.innerHTML; cell1.innerHTML = cell2.innerHTML; cell2.innerHTML = t; // меняем местами соответствующие элемены массива arrows t = arrows[i1]; arrows[i1] = arrows[i2]; arrows[i2] = t; // Запоминаем ход, если функция не вызвана для возвращения на шаг назад if (!isUndo) { steps.push([i1, i2]); } }
Функция, проверяющая, является ли положение стрелок выигрышным.
function isWin() { for (var i = 0; i < arrows.length; i++) { if (arrows[i] != rightArrows[i]) { return false; } } return true; }
И, наконец, функция, реализующая возвращение на шаг назад:
arrowsGame.undo = function () { var step = steps.pop(); if (step) { swap(cells[step[0]], cells[step[1]], true); } }
Весь описанный выше код я заключил в анонимную функцию, которую тут же вызвал.
var arrowsGame = (function () { // весь код тут return arrowsGame; })();
Подобного рода функции в js называются замыканиями. В моем случае эта функция так же возвращает функцию, необходимую для инициализации игры. Обратите внимание, что в свойство undo возвращаемой функции записана функция, реализующая возвращение на шаг назад.
Замыкание необходимо для того, чтобы не засорять глобальную область видимости переменными, которые там не нужны. Всё, что доступно извне этого замыкания — это функция arrowsGame со своим свойством undo.
Для инициализации этой игрушки необходимо просто подключить файл с описанным выше кодом в html-документ, в котором находится игровое поле и вызвать функцию arrowsGame передав ей в качестве параметра таблицу, которая будет игровым полем. Сделать это нужно, естественно, после загрузки документа.
window.onload = function () { arrowsGame(document.getElementById('game-table')); }
Для реализации возможности возвращаться на шаг назад в ходе решения нужно повесить на ссылку или кнопку обработчик события:
<a href="#" onclick="return undo()">← шаг назад</a>
Функция undo может выглядеть вот так:
function undo() { arrowsGame.undo(); return false; }
Демо
Скачать исходники
This article is about building a simple quiz web application using HTML, CSS and pure JavaScript, I plan on making this short and simple as possible. An image of final result can been seen below;
firstly, we’d need to create 3 files,
index.html //to hold our html codes
index.css //to hold our css codes
index.js //to hold our javascript codes
//feel free to name the files whatever you like, but this article I'd be going with those names above...
Enter fullscreen mode
Exit fullscreen mode
Let start by adding the following codes to the html file
<body onload="NextQuestion(0)">
<main>
<!-- creating a modal for when quiz ends -->
<div class="modal-container" id="score-modal">
<div class="modal-content-container">
<h1>Congratulations, Quiz Completed.</h1>
<div class="grade-details">
<p>Attempts : 10</p>
<p>Wrong Answers : <span id="wrong-answers"></span></p>
<p>Right Answers : <span id="right-answers"></span></p>
<p>Grade : <span id="grade-percentage"></span>%</p>
<p ><span id="remarks"></span></p>
</div>
<div class="modal-button-container">
<button onclick="closeScoreModal()">Continue</button>
</div>
</div>
</div>
<!-- end of modal of quiz details-->
<div class="game-quiz-container">
<div class="game-details-container">
<h1>Score : <span id="player-score"></span> / 10</h1>
<h1> Question : <span id="question-number"></span> / 10</h1>
</div>
<div class="game-question-container">
<h1 id="display-question"></h1>
</div>
<div class="game-options-container">
<div class="modal-container" id="option-modal">
<div class="modal-content-container">
<h1>Please Pick An Option</h1>
<div class="modal-button-container">
<button onclick="closeOptionModal()">Continue</button>
</div>
</div>
</div>
<span>
<input type="radio" id="option-one" name="option" class="radio" value="optionA" />
<label for="option-one" class="option" id="option-one-label"></label>
</span>
<span>
<input type="radio" id="option-two" name="option" class="radio" value="optionB" />
<label for="option-two" class="option" id="option-two-label"></label>
</span>
<span>
<input type="radio" id="option-three" name="option" class="radio" value="optionC" />
<label for="option-three" class="option" id="option-three-label"></label>
</span>
<span>
<input type="radio" id="option-four" name="option" class="radio" value="optionD" />
<label for="option-four" class="option" id="option-four-label"></label>
</span>
</div>
<div class="next-button-container">
<button onclick="handleNextQuestion()">Next Question</button>
</div>
</div>
</main>
<script src="index.js"></script>
</body>
Enter fullscreen mode
Exit fullscreen mode
That’ll be all for the HTML file. Next, we’ll be adding the following code to the CSS file to give the HTML some styling.
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body{
height: 100%;
}
body{
font-family: 'Montserrat', serif;
}
main{
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: black;
background: url('../assets/background_image.jpg');
background-color : black;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
}
.game-quiz-container{
width: 40rem;
height: 30rem;
background-color: lightgray;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
border-radius: 30px;
}
.game-details-container{
width: 80%;
height: 4rem;
display: flex;
justify-content: space-around;
align-items: center;
}
.game-details-container h1{
font-size: 1.2rem;
}
.game-question-container{
width: 80%;
height: 8rem;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid darkgray;
border-radius: 25px;
}
.game-question-container h1{
font-size: 1.1rem;
text-align: center;
padding: 3px;
}
.game-options-container{
width: 80%;
height: 12rem;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-around;
}
.game-options-container span{
width: 45%;
height: 3rem;
border: 2px solid darkgray;
border-radius: 20px;
overflow: hidden;
}
span label{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.3s;
font-weight: 600;
color: rgb(22, 22, 22);
}
span label:hover{
-ms-transform: scale(1.12);
-webkit-transform: scale(1.12);
transform: scale(1.12);
color: white;
}
input[type="radio"] {
position: relative;
display: none;
}
input[type=radio]:checked ~ .option {
background: paleturquoise;
}
.next-button-container{
width: 50%;
height: 3rem;
display: flex;
justify-content: center;
}
.next-button-container button{
width: 8rem;
height: 2rem;
border-radius: 10px;
background: none;
color: rgb(25, 25, 25);
font-weight: 600;
border: 2px solid gray;
cursor: pointer;
outline: none;
}
.next-button-container button:hover{
background-color: rgb(143, 93, 93);
}
.modal-container{
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
flex-direction: column;
align-items: center;
justify-content: center;
-webkit-animation: fadeIn 1.2s ease-in-out;
animation: fadeIn 1.2s ease-in-out;
}
.modal-content-container{
height: 20rem;
width: 25rem;
background-color: rgb(43, 42, 42);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
border-radius: 25px;
}
.modal-content-container h1{
font-size: 1.3rem;
height: 3rem;
color: lightgray;
text-align: center;
}
.grade-details{
width: 15rem;
height: 10rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.grade-details p{
color: white;
text-align: center;
}
.modal-button-container{
height: 2rem;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.modal-button-container button{
width: 10rem;
height: 2rem;
background: none;
outline: none;
border: 1px solid rgb(252, 242, 241);
color: white;
font-size: 1.1rem;
cursor: pointer;
border-radius: 20px;
}
.modal-button-container button:hover{
background-color: rgb(83, 82, 82);
}
@media(min-width : 300px) and (max-width : 350px){
.game-quiz-container{
width: 90%;
height: 80vh;
}
.game-details-container h1{
font-size: 0.8rem;
}
.game-question-container{
height: 6rem;
}
.game-question-container h1{
font-size: 0.9rem;
}
.game-options-container span{
width: 90%;
height: 2.5rem;
}
.game-options-container span label{
font-size: 0.8rem;
}
.modal-content-container{
width: 90%;
height: 25rem;
}
.modal-content-container h1{
font-size: 1.2rem;
}
}
@media(min-width : 350px) and (max-width : 700px){
.game-quiz-container{
width: 90%;
height: 80vh;
}
.game-details-container h1{
font-size: 1rem;
}
.game-question-container{
height: 8rem;
}
.game-question-container h1{
font-size: 0.9rem;
}
.game-options-container span{
width: 90%;
}
.modal-content-container{
width: 90%;
height: 25rem;
}
.modal-content-container h1{
font-size: 1.2rem;
}
}
@keyframes fadeIn {
from {opacity: 0;}
to {opacity:1 ;}
}
@-webkit-keyframes fadeIn {
from {opacity: 0;}
to {opacity: 1;}
}
Enter fullscreen mode
Exit fullscreen mode
If you successfully followed along correctly up to this point, you should be having the below image on your page;
Yes, there are no questions yet, don’t worry our friend JavaScript would take care of that.
Now lets add the following codes to the JavaScript file..
//this would be the object shape for storing the questions
//you can change the questions to your own taste or even add more questions..
const questions = [
{
question: "How many days makes a week ?",
optionA: "10 days",
optionB: "14 days",
optionC: "5 days",
optionD: "7 days",
correctOption: "optionD"
},
{
question: "How many players are allowed on a soccer pitch ?",
optionA: "10 players",
optionB: "11 players",
optionC: "9 players",
optionD: "12 players",
correctOption: "optionB"
},
{
question: "Who was the first President of USA ?",
optionA: "Donald Trump",
optionB: "Barack Obama",
optionC: "Abraham Lincoln",
optionD: "George Washington",
correctOption: "optionD"
},
{
question: "30 days has ______ ?",
optionA: "January",
optionB: "December",
optionC: "June",
optionD: "August",
correctOption: "optionC"
},
{
question: "How manay hours can be found in a day ?",
optionA: "30 hours",
optionB: "38 hours",
optionC: "48 hours",
optionD: "24 hours",
correctOption: "optionD"
},
{
question: "Which is the longest river in the world ?",
optionA: "River Nile",
optionB: "Long River",
optionC: "River Niger",
optionD: "Lake Chad",
correctOption: "optionA"
},
{
question: "_____ is the hottest Continent on Earth ?",
optionA: "Oceania",
optionB: "Antarctica",
optionC: "Africa",
optionD: "North America",
correctOption: "optionC"
},
{
question: "Which country is the largest in the world ?",
optionA: "Russia",
optionB: "Canada",
optionC: "Africa",
optionD: "Egypt",
correctOption: "optionA"
},
{
question: "Which of these numbers is an odd number ?",
optionA: "Ten",
optionB: "Twelve",
optionC: "Eight",
optionD: "Eleven",
correctOption: "optionD"
},
{
question: `"You Can't see me" is a popular saying by`,
optionA: "Eminem",
optionB: "Bill Gates",
optionC: "Chris Brown",
optionD: "John Cena",
correctOption: "optionD"
},
{
question: "Where is the world tallest building located ?",
optionA: "Africa",
optionB: "California",
optionC: "Dubai",
optionD: "Italy",
correctOption: "optionC"
},
{
question: "The longest river in the United Kingdom is ?",
optionA: "River Severn",
optionB: "River Mersey",
optionC: "River Trent",
optionD: "River Tweed",
correctOption: "optionA"
},
{
question: "How many permanent teeth does a dog have ?",
optionA: "38",
optionB: "42",
optionC: "40",
optionD: "36",
correctOption: "optionB"
},
{
question: "Which national team won the football World cup in 2018 ?",
optionA: "England",
optionB: "Brazil",
optionC: "Germany",
optionD: "France",
correctOption: "optionD"
},
{
question: "Which US state was Donald Trump Born ?",
optionA: "New York",
optionB: "California",
optionC: "New Jersey",
optionD: "Los Angeles",
correctOption: "optionA"
},
{
question: "How man states does Nigeria have ?",
optionA: "24",
optionB: "30",
optionC: "36",
optionD: "37",
correctOption: "optionC"
},
{
question: "____ is the capital of Nigeria ?",
optionA: "Abuja",
optionB: "Lagos",
optionC: "Calabar",
optionD: "Kano",
correctOption: "optionA"
},
{
question: "Los Angeles is also known as ?",
optionA: "Angels City",
optionB: "Shining city",
optionC: "City of Angels",
optionD: "Lost Angels",
correctOption: "optionC"
},
{
question: "What is the capital of Germany ?",
optionA: "Georgia",
optionB: "Missouri",
optionC: "Oklahoma",
optionD: "Berlin",
correctOption: "optionD"
},
{
question: "How many sides does an hexagon have ?",
optionA: "Six",
optionB: "Sevene",
optionC: "Four",
optionD: "Five",
correctOption: "optionA"
},
{
question: "How many planets are currently in the solar system ?",
optionA: "Eleven",
optionB: "Seven",
optionC: "Nine",
optionD: "Eight",
correctOption: "optionD"
},
{
question: "Which Planet is the hottest ?",
optionA: "Jupitar",
optionB: "Mercury",
optionC: "Earth",
optionD: "Venus",
correctOption: "optionB"
},
{
question: "where is the smallest bone in human body located?",
optionA: "Toes",
optionB: "Ears",
optionC: "Fingers",
optionD: "Nose",
correctOption: "optionB"
},
{
question: "How many hearts does an Octopus have ?",
optionA: "One",
optionB: "Two",
optionC: "Three",
optionD: "Four",
correctOption: "optionC"
},
{
question: "How many teeth does an adult human have ?",
optionA: "28",
optionB: "30",
optionC: "32",
optionD: "36",
correctOption: "optionC"
}
]
let shuffledQuestions = [] //empty array to hold shuffled selected questions out of all available questions
function handleQuestions() {
//function to shuffle and push 10 questions to shuffledQuestions array
//app would be dealing with 10questions per session
while (shuffledQuestions.length <= 9) {
const random = questions[Math.floor(Math.random() * questions.length)]
if (!shuffledQuestions.includes(random)) {
shuffledQuestions.push(random)
}
}
}
let questionNumber = 1 //holds the current question number
let playerScore = 0 //holds the player score
let wrongAttempt = 0 //amount of wrong answers picked by player
let indexNumber = 0 //will be used in displaying next question
// function for displaying next question in the array to dom
//also handles displaying players and quiz information to dom
function NextQuestion(index) {
handleQuestions()
const currentQuestion = shuffledQuestions[index]
document.getElementById("question-number").innerHTML = questionNumber
document.getElementById("player-score").innerHTML = playerScore
document.getElementById("display-question").innerHTML = currentQuestion.question;
document.getElementById("option-one-label").innerHTML = currentQuestion.optionA;
document.getElementById("option-two-label").innerHTML = currentQuestion.optionB;
document.getElementById("option-three-label").innerHTML = currentQuestion.optionC;
document.getElementById("option-four-label").innerHTML = currentQuestion.optionD;
}
function checkForAnswer() {
const currentQuestion = shuffledQuestions[indexNumber] //gets current Question
const currentQuestionAnswer = currentQuestion.correctOption //gets current Question's answer
const options = document.getElementsByName("option"); //gets all elements in dom with name of 'option' (in this the radio inputs)
let correctOption = null
options.forEach((option) => {
if (option.value === currentQuestionAnswer) {
//get's correct's radio input with correct answer
correctOption = option.labels[0].id
}
})
//checking to make sure a radio input has been checked or an option being chosen
if (options[0].checked === false && options[1].checked === false && options[2].checked === false && options[3].checked == false) {
document.getElementById('option-modal').style.display = "flex"
}
//checking if checked radio button is same as answer
options.forEach((option) => {
if (option.checked === true && option.value === currentQuestionAnswer) {
document.getElementById(correctOption).style.backgroundColor = "green"
playerScore++ //adding to player's score
indexNumber++ //adding 1 to index so has to display next question..
//set to delay question number till when next question loads
setTimeout(() => {
questionNumber++
}, 1000)
}
else if (option.checked && option.value !== currentQuestionAnswer) {
const wrongLabelId = option.labels[0].id
document.getElementById(wrongLabelId).style.backgroundColor = "red"
document.getElementById(correctOption).style.backgroundColor = "green"
wrongAttempt++ //adds 1 to wrong attempts
indexNumber++
//set to delay question number till when next question loads
setTimeout(() => {
questionNumber++
}, 1000)
}
})
}
//called when the next button is called
function handleNextQuestion() {
checkForAnswer() //check if player picked right or wrong option
unCheckRadioButtons()
//delays next question displaying for a second just for some effects so questions don't rush in on player
setTimeout(() => {
if (indexNumber <= 9) {
//displays next question as long as index number isn't greater than 9, remember index number starts from 0, so index 9 is question 10
NextQuestion(indexNumber)
}
else {
handleEndGame()//ends game if index number greater than 9 meaning we're already at the 10th question
}
resetOptionBackground()
}, 1000);
}
//sets options background back to null after display the right/wrong colors
function resetOptionBackground() {
const options = document.getElementsByName("option");
options.forEach((option) => {
document.getElementById(option.labels[0].id).style.backgroundColor = ""
})
}
// unchecking all radio buttons for next question(can be done with map or foreach loop also)
function unCheckRadioButtons() {
const options = document.getElementsByName("option");
for (let i = 0; i < options.length; i++) {
options[i].checked = false;
}
}
// function for when all questions being answered
function handleEndGame() {
let remark = null
let remarkColor = null
// condition check for player remark and remark color
if (playerScore <= 3) {
remark = "Bad Grades, Keep Practicing."
remarkColor = "red"
}
else if (playerScore >= 4 && playerScore < 7) {
remark = "Average Grades, You can do better."
remarkColor = "orange"
}
else if (playerScore >= 7) {
remark = "Excellent, Keep the good work going."
remarkColor = "green"
}
const playerGrade = (playerScore / 10) * 100
//data to display to score board
document.getElementById('remarks').innerHTML = remark
document.getElementById('remarks').style.color = remarkColor
document.getElementById('grade-percentage').innerHTML = playerGrade
document.getElementById('wrong-answers').innerHTML = wrongAttempt
document.getElementById('right-answers').innerHTML = playerScore
document.getElementById('score-modal').style.display = "flex"
}
//closes score modal, resets game and reshuffles questions
function closeScoreModal() {
questionNumber = 1
playerScore = 0
wrongAttempt = 0
indexNumber = 0
shuffledQuestions = []
NextQuestion(indexNumber)
document.getElementById('score-modal').style.display = "none"
}
//function to close warning modal
function closeOptionModal() {
document.getElementById('option-modal').style.display = "none"
}
Enter fullscreen mode
Exit fullscreen mode
And that’s all, you should have a fully functioning simple quiz if you correctly followed this article.
You can follow this link : https://codepen.io/Sulaimon-Olaniran/pen/zYKJLjK to see it live on Codepen.
Puzzle Script — это минималистичный игровой движок для создания головоломок для HTML5, имеет открытые исходники. Примеры готовых игр можно посмотреть здесь.
Часть 1. Создаём первую игру на Puzzle Script.
Puzzle Script — это бесплатная онлайн-программа, которая используется для создания игр-головоломок. Наиболее известен она благодаря созданию головоломок с толканием блоков наподобие моей The Nodus. В этой части мы создадим игру, изучив базовые функции Puzzle Script, а в следующей приступим к программированию.
Перейдите на веб-сайт движка. Нажмите Make a Game, чтобы открыть редактор Puzzle Script.
Загрузка примеров
Для начала давайте рассмотрим несколько примеров. В верхней части экрана откройте список «Load Example» и выберите первый пример под названием «Basic». Теперь нажмите на «Run».
Появится экран игры. Щёлкните внутри его окна и нажмите Enter на клавиатуре.
Попробуйте сыграть в игру. Ваша цель — дотолкать оранжевые ящики до чёрных квадратов-целей. Когда на каждой цели будет стоять по ящику, уровень будет пройден. Можно нажимать на клавиатуре Z для отмены хода или R для перезапуска уровня.
Делаем первую игру
Теперь мы создадим несколько уровней. Один я создам вместе с вами, а другие советую придумать самостоятельно. Нажмите на «Level Editor» в верхнем меню.
Если вы не видите экрана редактора уровней, то нажмите «Run» и запустите игру. Попав на уровень, снова нажмите на кнопку «Level Editor». Это позволит редактировать уровень, на котором вы только что находились.
Создаём новый уровень
Вверху находятся игровые объекты. При левом щелчке отрисовывается выбранный объект. Правый щелчок отрисовывает «фоновый» объект. Левый щелчок на краю карты увеличивает её размер, правый щелчок его уменьшает.
Чтобы пройти уровень, нужно поставить по ящику на каждую из целей, поэтому на каждом уровне должны быть минимум:
- 1 ящик
- 1 цель
- 1 игрок
Пока правым щелчком превратим всё в траву. Затем щёлкнем левой клавишей на краю уровня, чтобы сделать его больше, и наконец нарисуем уровень, который похож на показанный ниже.
Добавляем его в список уровней
Закончив создание уровня, мы добавим его в список уровней. В редакторе уровней нажмите на белую букву S рядом со списком игровых объектов, чтобы сохранить созданный уровень.
Под редактором уровней должно отобразиться сообщение об успешной компиляции и сетка из забавных символов, как показано ниже.
Эти забавные символы обозначают только что созданный нами уровень. Каждый символ представляет отдельный объект. В левой части экрана опуститесь вниз и найдите LEGEND. В легенде представлено объяснение каждого из символов:
. = Background
# = Wall
P = Player
* = Crate
@ = Crate and Target
O = Target
Всё, что находится в левой части экрана — это код игры, разделённый на разные части, такие как OBJECTS или LEGEND. Опуститесь вниз к LEVELS. Здесь мы видим уровни, которые используются в примере.
Чтобы добавить новый уровень, создадим новую пустую строку в нижней части раздела уровней. Затем скопируем символы, сгенерированные для нашего уровня и вставим их туда. Всё, уровень добавлен.
Протестируем его. После создания нового уровня нужно снова нажать кнопку «Run» в верхней части экрана, чтобы перезагрузить игру с новым уровнем. Иногда это не срабатывает, и тогда нужно нажать кнопку «Rebuild», а затем снова нажать «Run».
Сохранение и загрузка игры
Попробуйте создать ещё несколько новых уровней. Когда будете готовы сохранить игру, поднимитесь вверх к началу кода и введите собственное название, имя автора и домашнюю страницу, а затем нажмите на кнопку «Save».
Ограниченное количество сохранений хранится в меню «Load» на используемом вами компьютере. Однако в верхней части экрана существует кнопка «Share». При нажатии на неё генерируется сообщение с двумя веб-ссылками.
Одна из ссылок — исходный код вашего проекта. Вторая — это ссылка на играбельную версию игры, которой можно поделиться с друзьями. Рекомендую периодически создавать новую ссылку на исходный код и сохранять её в какой-нибудь текстовый документ, чтобы у вас оставалась постоянно хранимая версия проекта.
Экспорт игр
Также можно экспортировать игру как файл html5, который вы затем сможете загрузить на игровые порталы, например на itch.io, Kongregate или Newgrounds. Просто нажмите «Export» и загрузите скачанный файл html на игровой портал.
Наш пример проекта можно посмотреть здесь.
Часть 2. Начинаем программировать в Puzzle Script
В этой части мы узнаем, как начать программировать в Puzzle Script.
Код
Откройте пример проекта. Код программы находится в левой части экрана, он разделён на части: Objects, Legend, Sounds и т.д. В разделе Rules задаются правила взаимодействия объектов. Зайдите в него. Здесь должна быть только одна строка кода:
[ > Player | Crate ] -> [ > Player | > Crate ]
Эта строка означает, что если игрок находится рядом с ящиком и перемещается в его сторону, то игра перемещает игрока и толкает ящик. Чтобы объяснить, как это работает, нужно понять, что код Puzzle Script следует такой структуре:
[ Условие ] -> [ Событие ]
Это означает следующее:
[ Если это условие выполняется ] -> тогда [ Делать это ]
Puzzle Script проверяет истинность условий слева от стрелки, например, находится ли объект игрока рядом с объектом ящика. Если условие истинно, то мы что-то делаем, например, толкаем ящик.
Примеры условий
Вот пример условия:
[ object1 | object2 ]
Данное событие проверяет, находится ли object1 рядом с object2. Можно проверить, находятся ли два объекта рядом друг с другом, поместив между ними прямую | линию
, вводимую нажатием shift + . Условия всегда заключены в квадратные [ ] скобки
.
[ crate | crate ]
Приведённый выше код проверяет, находятся ли рядом друг с другом два ящика.
[ crate | crate | crate ]
Это условие проверяет, находятся ли рядом три ящика.
[ crate target ]
Данное условие проверяет, находится ли ящик поверх цели, потому что прямой | линии
между двумя объектами нет. Объекты могут находиться друг на друге, если они расположены в разных слоях коллизий, которые мы рассмотрим в следующих частях туториала.
Сохраняем равенство
Правила должны быть уравновешены. И проверка условия, и следующее за ним событие должны описываться одинаковым образом. Я покажу, что это значит.
[ player | crate ] -> [ player | ]
Эта строка кода уничтожает ящик, если рядом с ним находится игрок. Нельзя записать:
[ player | crate ] -> [ player ]
потому что условие слева проверяет наличие соседних объектов в двух отдельных пространствах сетки, но событие описывает только одно пространство сетки, которое занимает игрок. Puzzle Script должен знать, что делать с проверяемыми им пространствами. Правильный код для уничтожения ящика должен сообщать следующее:
[ Если игрок | рядом с ящиком ] -> тогда [ игрок не делает ничего | ящик удаляется ]
[ player | crate ] -> [ player | ]
То есть даже пустые пространства в коде имеют значение. Однако следующая запись допустима:
[ player target ] -> [ player ]
Так как в условии мы говорим только об одном пространстве сетки, то событие описывает то же пространство сетки.
Как двигать ящики
Вернёмся к исходной строке кода.
[ > Player | Crate ] -> [ > Player | > Crate ]
Другими словами:
[ Если игрок двигается к ящику | и находится рядом с ящиком ] -> тогда [ переместить игрока | толкнуть ящик ]
Стрелка > подчёркивает движение.
Иногда нам требуется писать комментарии, чтобы помнить, что делает код. Puzzle Script игнорирует комментарии — они предназначены только для пользователя. Чтобы записать комментарий, нужно поместить текст в скобки. Напишем над нашим правилом комментарий, описывающий то, что оно делает:
(Игрок толкает ящик)
[ > Player | Crate ] -> [ > Player | > Crate ]
Теперь под кодом толкания ящика напишем следующее:
(Игрок тащит ящик)
[ < Player | Crate ] -> [ < Player | < Crate ]
Перевёрнутая стрелка означает, что если игрок движется от ящика, то он тянет ящик. Нажмите «Run», чтобы протестировать это действие. У вас должна появиться возможность толкать и тащить ящики. В программировании даже при опечатке в одну букву компьютер может не понять код, поэтому избавляйтесь от всех ошибок. При внесении изменений в код снова нажимайте «Run», чтобы загрузить изменения. Если игра ведёт себя не так, как нужно, попробуйте нажать на «Rebuild», чтобы очистить память программы, а затем нажмите «Run».
Иногда бывает нужно деактивировать строку кода. Просто превратите её в комментарий, чтобы сохранить на будущее. Давайте закомментируем код толкания ящиков, чтобы игрок мог только таскать ящики:
(Игрок толкает ящик)
([ > Player | Crate ] -> [ > Player | > Crate ])
(Игрок тащит ящик)
[ < Player | Crate ] -> [ < Player | < Crate ]
Если это сработало, то закомментируйте код таскания ящиков и попробуйте сделать следующее:
[ < Player | Crate ] -> [ < Player | > Crate ]
Если игрок отодвигается от ящика, то игрок и ящик переместятся в противоположных направлениях. Стрелки определяют, в каком направлении объект или движется, или будет двигаться. Теперь закомментируем это и попробуем следующее:
[ > Player | Crate ] -> [ Player | > Crate ]
Ящик двигается, но игрок остаётся на месте. Поэкспериментируйте с ^ и v (буква v), чтобы посмотреть, как будут двигаться объекты.
Ошибки
Давайте намеренно напишем неверное правило и посмотрим, что произойдёт. Введите такую строку:
[ < Player | Crate ] -> [ < Player ]
Попробуйте запустить программу. Вы должны увидеть такое сообщение:
line 81: In a rule, each pattern to match on the left must have a corresponding pattern on the right of equal length (number of cells).
Обычно Puzzle Script очень хорошо описывает ошибку. Однако иногда Puzzle Script ошибается сам. В таких случаях вам нужно самим пройтись по коду и разобраться, где ошибка.
Ещё немного экспериментов
Попробуйте ещё поэкспериментировать и самостоятельно писать правила. Вот несколько примеров.
[ > Player | … | Crate ] -> [ > Player | … | > Crate ]
В показанном выше примере игрок будет толкать ящик, если они находятся в любом месте одной строки уровня, и игрок движется к ящику.
[ > Player | Crate ] -> [ Crate | Player ]
Этот код меняет игрока и ящик местами.
[ > Player | Crate ] -> [ Player | Target ]
В этом коде если игрок рядом с ящиком и движется к нему, то игрок перестанет двигаться, но ящик превратится в цель. Самое лучшее в Puzzle Script — простота создания нового и возможность экспериментов.
Часть 3. Создание объектов
Все графические фрагменты в играх на Puzzle Script обозначают объекты. Для создания игр на Puzzle Script необходимо создавать собственные объекты. В этой части я расскажу, как создавать их и добавлять в свой код.
Общие сведения
Откройте пример проекта. Процесс создания объекта состоит из следующих шагов:
- Создание его в списке объектов
- Добавление объекта в легенду
- Добавление в слой коллизий
Сделав все эти шаги, можно начать использовать объект.
Создание объекта
Несколько объектов уже существует. В каждой игре должен быть фоновый объект. Все объекты создаются из сетки размером 5 x 5 пикселей и имеют хотя бы один цвет. Ниже показан фоновый объект.
Background
LIGHTGREEN GREEN
11111
01111
11101
11111
10111
Числа обозначают пиксели изображения. Каждое число соответствует своему цвету. Первый цвет имеет число 0, второй — 1, и так до 9. Может быть до десяти цветов. В нашем случае каждая 1 окрашивает пиксель в светло-зелёный (Light Green), а 0 — в зелёный (Green). Результат выглядит так:
Объекты всегда создаются следующим образом:
- Название
- Цвета
- Изображение
Название всегда находится в верхней строке. Цвета всегда во второй строке, а изображение занимает следующие 5 строк, по 5 символов на строку, что формирует сетку 5 x 5. Или можно сделать следующее:
Background
LIGHTGREEN
Этот код создаст объект с названием «Background», который будет сеткой 5 x 5 пикселей светло-зелёного цвета. Если не описать сетку изображения, то мы получим блок сплошного цвета, что иногда может быть полезно.
Даём названия объектам
Объекты можно называть как угодно, но название не может начинаться с символа и оно должно быть одним словом без пробелов. Давайте объектам понятные названия, но не переборщите. PlayerStill — хорошее название, PlayerThatIsStandingStill — слишком длинное и многословное.
Цвета
Необходимо объявить цвета, которые вы хотите использовать для объекта, и разделить их пробелом. Puzzle Script имеет заранее заданные цвета:
- black
- white
- grey
- darkgrey
- lightgrey
- gray
- darkgray
- lightgray
- red
- darkred
- lightred
- brown
- darkbrown
- lightbrown
- orange
- yellow
- green
- darkgreen
- lightgreen
- blue
- lightblue
- darkblue
- purple
- pink
- transparent
Можно также задавать цвета в шестнадцатеричном виде, что даёт нам гораздо больший диапазон цветов. Для выбора шестнадцатеричных цветов можно использовать веб-сайты, например такой. Выберите нужный цвет, а затем перепишите код цвета, указанный над изображением. Шестнадцатеричные коды цветов записываются в Puzzle Script следующим образом:
Название
#51A2BD #ff0000 #ffffff
Коду цвета всегда предшествует символ #.
Добавляем объект в легенду
Создав объект, нужно добавить его в легенду. Легенда выглядит так:
. = Background
# = Wall
P = Player
* = Crate
@ = Crate and Target
O = Target
Каждый символ обозначает объект на уровне. То есть когда мы видим такую сетку символов:
#p.*.##
#.**.##
#..#..#
##....#
##...o#
#######
то на самом деле она описывает наш уровень:
Каждому создаваемому объекту нужно присвоить букву, символ или число, обозначающие этот объект на уровне. Вот так:
P = player
Группируем объекты в легенде
Также мы можем создавать в легенде группы объектов. Например, если у нас есть несколько разноцветных ящиков, то можно сделать так:
O = OrangeCrate
B = BlueCrate
G = GreenCrate
Что позволит нам использовать ящики в редакторе уровне. Но для создания кода можно в Legend сгруппировать их вместе, вот так:
Crates = OrangeCrate or GreenCrate or BlueCrate
И всё вместе это будет выглядеть так:
=======
LEGEND
=======
O = OrangeCrate
B = BlueCrate
G = GreenCrateCrates = OrangeCrate or GreenCrate or BlueCrate
Зачем это делать? Потому что тогда вместо создания таких правил:
[ > Player | OrangeCrate ] -> [ > Player | > OrangeCrate ]
[ > Player | BlueCrate] -> [ > Player | > BlueCrate ]
[ > Player | GreenCrate] -> [ > Player | > GreenCrate]
можно просто записать:
[ > Player | Crates ] -> [ > Player | > Crates ]
И этот код будет работать для всей группы объектов.
Кроме того, в разделе Слои коллизий можно будет ссылаться на слой, в котором находится группа, а не вводить каждый отдельный объект.
Слои коллизий
По умолчанию раздел слоёв коллизий выглядит следующим образом:
Background
Target
Player, Wall, Crate
Каждая строка выделяет объекты в свой собственный слой. Порядок расположения слоёв объектов определяет, какие объекты будут поверх других. Объекты в верхней строке будут находиться на нижнем слое, следующая строка будет в слое над ним, и так далее. Фон всегда должен находиться в верхней строке, чтобы располагаться на нижнем слое. Объекты в одном слое не могут находиться друг поверх другого. То есть такого быть не может:
[ player wall ] -> [ player wall ]
Можно сделать так, чтобы объекты в разных слоях взаимодействовали друг с другом. Например, можно написать:
[ > Player | Target ] -> [ > Player | > Target ]
Эксперимент
Создайте несколько новых объектов. Создайте тип ящиков, которые можно только толкать. Создайте другой ящик, который можно только тащить. Создайте ящик, который исчезает, когда его касаешься. Продолжая эксперименты, вы будете лучше запоминать, как всё делается.
Часть 4. Условия победы
Все любят побеждать. Мы, как игроки, хотим побеждать. В этой части мы узнаем, как запрограммировать условия победы в игре.
Условия победы
Загрузите пример проекта и перейдите к разделу кода Win Conditions. Вы должны увидеть следующее:
All Target on Crate
Игра выиграна, если на каждой цели есть ящик. Если у вас есть 3 ящика и 2 цели, то выиграете, поставив на цели всего 2 ящика. Если поменять местами:
All crate on target
то каждый ящик должен будет находиться на цели.
Условий может быть одно или несколько. В случае множественных условий они должны выполняться все. Например, у нас может быть следующее:
All Target on Crate
All Target2 on Crate2
Если на уровне есть target и target2, то на них, соответственно, crate и crate2. Если на уровне нет ни одного из объектов, необходимых для выполнения определённого условия победы, например, нет target2, то это условие выполняется автоматически.
Различные типы условий победы
Существует несколько разных типов условий победы.
No Object
В этом случае победа настаёт тогда, когда на уровне нет ни одного такого объекта.
Some Object
Вы выигрываете, когда на уровне есть хотя бы один объект указанного типа.
Some Object1 on Object2
Нужно, чтобы хотя бы один из объектов Object1 находился на Object2.
No Object1 On Object2
Это условие противоположно All Target on Crate
. В данном случае нам нужно, чтобы все указанные объекты находились отдельно друг от друга, а не друг на друге. Также можно комбинировать друг с другом различные условия победы.
Эксперимент
Поэкспериментируем с разными условиями победы. Попробуйте создать игру, в которой выигрыш наступает, если все ящики находятся не на целях. Или сделайте игру, в которой нужно уничтожить все ящики определённого типа, но на вашем пути находятся другие толкаемые ящики.
Мой завершённый пример проекта можно посмотреть здесь.
Часть 5. Команда late
В PuzzleScript есть очень полезная команда под названием «late». Порядок происхождения событий в игре важен, и иногда для получения нужных результатов нужен код, который выполняется позже. В этой части я расскажу об использовании команды late.
Зачем она нам понадобится
Откройте пример проекта, после чего вставьте в игру следующий код и запустите её:
[ player | target ] -> [ player | ]
Можно ожидать, что как только игрок встанет рядом с целью, цель будет уничтожена, однако этого не происходит. Вместо этого цель исчезает в ходе после того, как рядом с ней встал игрок. Поэтому попробуем другой код:
late [ player | target ] -> [ player | ]
Как только вы встанете рядом с целью, она исчезнет. Так получилось потому, что всё, обозначенное как late, происходит после выполнения всего остального кода. Иногда такое бывает нужно.
Порядок событий
Вот как выполняется код в Puzzle Script при каждом перемещении.
- Puzzle Script обнаруживает, что игрок хочет переместиться
- По возможности правила считываются и выполняются сверху вниз
- Игрок перемещается, если это возможно
- Применяются Late-правила
Компьютер начинает с верхней строки кода и считывает вниз, строка за строкой. проверяя каждое условие. Поэтому когда вы пытаетесь переместиться, Puzzle Script считывает все правила и проверяет, истинны ли условия, и если это так, то он что-то делает. Например, первая строка может быть такой:
[ player | spikeTrap ] -> [ | spikeTrap ]
Если игрок не стоит рядом с ловушкой из кольев, то код продолжает выполнение. Это значит, что важен порядок написания строк кода. Можно применять команду late в некоторых случаях, которые вы узнаете со временем на практике.
Способы использования Late на практике
По моему опыту, лучше всего использовать команду late, когда вы проверяете, находятся ли объекты поверх друг друга или рядом друг с другом, но бывают и другие случаи. Если вы проверяете, находится ли один объект на другом, то событие не зарегистрируется до следующего хода, если только не использовать команду late:
[ player spikeTrap ] -> [ spikeTrap ]
В приведённом выше случае игрок не будет убит ловушкой с кольями до следующего хода после перемещения в ловушку. Чтобы игрок умер мгновенно, просто добавим команду late,
late [ player spikeTrap ] -> [ spikeTrap ]
Чтобы перезапустить весь уровень при смерти персонажа, можно сделать следующее:
late [ player spikeTrap ] -> restart
И уровень перезапустится, когда игрок попадёт в ловушку с кольями.
Готовый пример проекта можно посмотреть здесь.
Часть 6. Работа со звуковыми эффектами
Мы сделали отличную игру на Puzzle Script, но теперь нужно добавить в неё звуки. Как это сделать? Сейчас я вам расскажу!
Генерация звуков
Откройте пример проекта. Добавим в него звуков. Под экраном игры можно увидеть чёрные квадраты с белыми символами. Они используются для генерации звука. Каждый символ генерирует уникальный тип звука, а крестик удаляет созданные звуки. Попробуйте понажимать на квадраты и послушайте звуки.
Жёлтые числа — это уникальные коды, которые нужно копировать и вставлять в те места кода, где необходимы звуки.
Как использовать звуки
Найдя подходящий звук, вы должны вставить его в раздел Sounds кода игры.
Звуки можно использовать несколькими способами. Наиболее простой — создание нового звукового эффекта (sfx). Они должны быть пронумерованы. Мы создаём новый sfx, назначая ему число от 0 до 10 при вставке числового ID звука. В списке Sounds создадим новый sfx под названием sfx0 и присвоим ему сгенерированный звук:
sfx0 36301705
Для использования звука нужно вставить его в правила после события. Давайте прикрепим только что созданный sfx0 к событию уничтожения ящика (событие уже присутствует в примере проекта):
(The player destroys a crate)
[ > Player | CrateVanish ] -> [ Player | ] sfx0
Также звуки можно объявлять для воспроизведения с определёнными событиями, например:
Crate MOVE 36772507
В этом случае звук воспроизведётся при перемещении Crate. Сгенерируем новый звуковой эффект для перетаскивания объектов CratePull и заставим его воспроизводиться при перемещении CratePull:
CratePull MOVE 12735307
Звуки событий нужно объявлять только в разделе Sounds: их не требуется упоминать в правилах.
Список способов воспроизведения звуков
Ниже представлен список различных звуков событий, которые можно использовать, взятый из документации Puzzle Script.
Object Action 541566 — воспроизводится, когда объект во время хода подвергается действию (action).
Object Create 641667 — воспроизводится при создании определённого объекта.
EndGame 5416789 — воспроизводится при завершении игры.
EndLevel 6417822 — воспроизводится после завершения уровня.
Object CantMove 781673 — воспроизводится, когда объект безуспешно пытался сдвинуться в любом направлении.
Player CantMove Down Left 464674 — воспроизводится, когда объект безуспешно пытался сдвинуться вниз или влево.
CloseMessage 344456 — воспроизводится, когда игрок закрывает окно с сообщением.
Object Destroy 187975 — воспроизводится при уничтожении объекта.
Object Move 264567 — воспроизводится, когда объект успешно перемещается в любом направлении.
Object Move Down Left 765432 — воспроизводится, когда объект успешно сдвинулся вниз или влево.
Object Move Horizontal 345367 — воспроизводится, когда объект успешно переместился по горизонтали. Можно также использовать Vertical.
Restart 7865435 — воспроизводится, когда игрок нажимает кнопку перезапуска R.
SFX0 765743 — может быть чем угодно от SFX0 до SFX10. Это особые звуковые события, которые можно выполнять из правил.
ShowMessage 478483 — воспроизводится при отображении сообщения.
StartGame 234626 — воспроизводится в начале новой игры.
Startlevel 765436 — воспроизводится при начале каждого уровня.
TitleScreen 876543 — воспроизводится после загрузки экрана заставки.
Undo 436234 — воспроизводится, когда игрок нажимает клавишу отмены (Z).
Для move и cantmove можно указывать направления, чтобы при перемещении в разных направлениях воспроизводились разные звуки.
Готовый пример проекта находится здесь.
Часть 7. Воспроизведение музыки
Мы научились создавать игры на Puzzle Script, но разве не здорово будет добавить в них музыку? Это возможно, и сейчас я расскажу, как это делается.
Примечание: похоже, на данный момент эта функция в Puzzle Script сломана, так что переходите к следующей части.
Что нужно делать
Откройте заготовку проекта. Музыка работает в Puzzle Script следующим образом: в игру можно вставить ссылку на одно видео с Youtube PuzzleScript автоматически будет воспроизводить все звуки этого видео. Откройте Youtube и выберите любое видео, или используйте следующее:
youtube.com/watch?v=CKAc3nYEatw
Чтобы воспроизвести музыку из видео, нам нужно получить уникальный ID видео. Выделенная зелёным часть в строке выше и есть уникальный ID.
Под меткой домашней страницы автора в начале проекта добавьте метку youtube, а после неё уникальный ID видео, например, так:
youtube CKAc3nYEatw
Чтобы убедиться в правильности работы, нажмите на «Share» и щёлкните по ссылке игры (не по ссылке исходного кода). Во время тестирования внутри редактора музыку Puzzle Script воспроизводить невозможно. Если вы хотите, чтобы в игре была музыка, то нужно загрузить её как видео на Youtube и вставить в свой проект уникальный ID. В каждом проекте может быть только одно видео Youtube.
Пример проекта
Пример проекта Puzzle Script можно найти здесь.
Часть 8. Как использовать команду Action
Мы уже знаем, как перемещаться, толкать и тащить блоки, но что если нам нужно сделать что-то при нажатии определённой клавиши, например пробела?
Возможности ввода в PuzzleScript довольно ограничены, в основном это клавиши-стрелки, Z для отмены, R для перезапуска, и мы не можем их изменить. Но движок даёт нам дополнительную клавишу для действий — нажатие на пробел или клавишу X.
Работа с командой Action следует описанному выше формату. Мы использовали базовый формат кода PuzzleScript:
[ Условие ] -> [ Событие ]
Если условие истинно, то мы выполняем событие. Команда action используется таким же образом, но у неё есть собственные правила. Она выглядит вот так:
[ Action дополнительные условия ] -> [ Событие ]
Вот пример использования команды Action:
[ Action Player ] -> [ Crate ]
Во-первых, Action всегда является первым словом условия.
Во-вторых, если мы хотим воздействовать на определённый объект в игре, то нужно упомянуть этот объект и в условии, и в событии (если упомянуть другой объект, то программа удалит исходный и заменит его на новый объект, а если объект не указать, то он просто удалится).
Наконец, слово Action нужно использовать только в условии, однако иногда стоит использовать его и в условии, и в событии. Об этом я расскажу ниже.
Проверим показанный выше код в проекте игры. Можно начать с этого примера. Вы увидите, что при нажатии на пробел или X игрок становится ящиком.
Условие сообщает [ Если мы нажимаем клавишу Action и на уровне есть объект Player ] -> тогда [ заменить объект игрока на ящик ]
Теперь попробуем в примере проекта следующий код:
[ Action Player | Crate ] -> [ Player | > Crate ]
Сделайте так, чтобы вас со всех сторон окружали ящики, как на изображении выше. Вы заметите, что действие влияет только на один ящик за раз. Не знаю точно, в чём причина, но если вы хотите воздействовать с помощью команды Action на несколько объектов, то нужно указать её и в условии, и в событии.
Заменим код обновлённой версией:
[ Action Player | Crate ] -> [ Action Player | > Crate ]
Теперь мы можем толкать все ящики одновременно. Если вам нужно применить действие к нескольким объектам, то поместите Action и в условие, и в событие.
Часть 9. Проверка множественных условий
Давайте научимся проверять несколько условий, например, наличие бомбы И разрушаемого блока.
Откройте пример проекта. Теперь добавьте в него следующий код:
late [Player Switch][DoorClosed] -> [Player Switch][DoorOpen]
Код соответствует такому формату:
[ Условие 1 ] [ Условие 2 ] -> [ Действие 1 ] [ Действие 2 ]
Если условие 1 истинно и условие 2 истинно, то выполняются действие 1 и действие 2. В нашем случае в условии 1 проверяется, находится ли Player на Switch. Если да, то проверяется условие 2, то есть наличие на уровне закрытой двери? Если условие истинно, то объект DoorClosed превращается в объект DoorOpen, открывая дверь.
Допустим, нам нужно, чтобы дверь закрывалась, когда герой уходит с переключателя, потому что мы хотим, чтобы для открывания двери игрок толкнул ящик на переключатель. Можно написать нечто подобное:
late [Player | Switch][DoorOpen] -> [Player | Switch][DoorClosed]
Если игрок стоит рядом с переключателем и где-то на уровне есть открытая дверь, то мы закрываем дверь. Наконец, нам нужно, чтобы дверь оставалась открытой, если мы толкнём ящик на переключатель:
late [Crate Switch][DoorClosed] -> [Crate Switch][DoorOpen]
Теперь дверь будет оставаться открытой, пока на переключателе стоит ящик.
Готовый пример проекта можно посмотреть здесь.
Часть 10. Создание контрольных точек
Возможно, у вас есть хорошая идея для игры на Puzzle Script, но для неё нужны контрольные точки (чекпоинты), чтобы игрок в случае смерти восстанавливался на них. Как это сделать? Довольно просто, и сейчас я объясню, как.
Создание контрольных точек
Откройте пример проекта. Теперь нам нужно запрограммировать контрольную точку. Для этого достаточно всего одной строки кода:
late [ Player FlagRed ] -> CHECKPOINT
FlagRed — это контрольная точка. Когда игрок находится поверх флага, этот код создаёт контрольную точку (CHECKPOINT). Если не использовать late, то функция контрольной точки не сработает.
Протестируйте игру. Пройдите над контрольной точкой, а затем немного дальше и нажмите R. Вы должны будете начать с контрольной точки.
Несколько контрольных точек
При наличии нескольких контрольных точек игра использует последнюю, активированную игроком.
Чтобы избежать повторной активации уже использованной контрольной точки, нужно сменить её на другой объект. Для этого создадим в разделе OBJECTS кода под красным флагом его белую копию.
FlagWhite
White Orange
.1…
.00..
.000.
.1…
.1…
Теперь перепишем эту строку в легенде:
Flag = FlagRed or FlagWhite
Мы можем создать группу объектов. В таком случае Flag будет или FlagRed, или FlagWhite. Пока хотя бы одному из сгруппированных объектов присвоен символ (мы присвоили FlagRed символ F), нам не нужно будет присваивать символы другим объектам группы, при этом получить доступ к ним вы сможете только в коде, но не в редакторе уровней. Затем можно присвоить группе слои коллизий, что мы сделали. Обращение к группе объектов, например, Flag, обращается ко всей группе. Поэтому:
[ > Player | Flag ] -> [ > Player | ]
Этот код повлияет и на красный, и на белый флаг.
Изменение объекта флага
Вот как поменять FlagRed на FlagWhite:
late [ Player FlagRed ] -> [ Player FlagWhite ]
Если в конце хода игрок находится на красном флаге, то мы превращаем его в белый. Однако нужно сделать так, чтобы графика флага менялась после создания контрольной точки, потому что код читается сверху вниз. Протестируйте программу.
Изменение второго флага
В проекте есть два флага. Давайте сделаем так, чтобы при активации второго флага старый белый флаг превращался в чёрный, чтобы его нельзя было снова использовать. Запишем следующее:
late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]
Код сообщает: если игрок находится на красном флаге и где-то в игре есть белые флаги, то нужно сделать белые флаги чёрными. Так как код считывается сверху вниз, нам нужно выполнить это в следующем порядке:
late [ Player FlagRed ] -> CHECKPOINT
late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]
late [ Player FlagRed ] -> [ Player FlagWhite ]
Если вы не понимаете, почему код должен быть в таком порядке, то попробуйте поменять порядок строк и протестируйте программу. Чтобы решать собственные задачи, вам нужно научиться делать шаг назад и обдумывать то, что делает код. Но я дам вам подсказку — когда вы играете, где находятся красные флаги во время последней строки кода в следующем неправильном примере?
[ > Player | Crate ] -> [ > Player | > Crate ]
[ > Crate | Flag ] -> [ Crate | Flag ]
late [ Player FlagRed ] -> CHECKPOINT
late [ Player FlagRed ] -> [ Player FlagWhite ]
late [ Player FlagRed ][ FlagWhite] -> [ Player FlagRed ][FlagBlack]
Просмотреть готовый пример проекта можно здесь. Кроме того, я только что продемонстрировал вам простейший способ анимации. В следующей части я расскажу о создании более сложных анимаций, например взрывов.
Часть 11. Анимации
В PuzzleScript есть два способа создания анимаций. В одном из них используется реальное время, но в этой части я не буду о нём говорить. Другой используется для быстрых одноразовых анимаций, таких как взрыв или спускающийся по вертикальной лестнице персонаж.
Откройте пример проекта. Мы создадим бомбу и серию кадров взрыва, а затем анимируем их.
Анимация объектов
Для 2D-анимаций необходимо несколько рисунков объекта, переходящего из одного состояния в другое, например, спрайт бегущего Марио. Он содержит 4 кадра анимации.
Для симуляции перехода одного кадра анимации в другой мы создадим в PuzzleScript несколько объектов, которые будут кадрами анимации, а затем используем код для переключения между ними.
Бомба
Создадим такой объект-бомбу.
Bomb
black yellow grey
..1..
..1..
.000.
00020
.000.
Не забудьте добавить его в слои и в легенду.
Чтобы анимировать взрыв, нам нужно создать каждый кадр анимации как отдельный объект, а затем переключаться между ними в коде. Давайте создадим объекты взрыва.
Explosion1
black yellow grey red
..1..
..1..
.000.
00320
.000.Explosion2
black yellow grey red
..1..
..1..
.333.
03330
.333.Explosion3
black yellow grey red
..1..
.333.
33333
33333
.333.Explosion4
black yellow grey red
.333.
33333
33333
33333
.333.Explosion5
black yellow grey red
.333.
33333
33.33
33333
.333.Explosion6
black yellow grey red
.333.
3...3
3...3
3...3
.333.Explosion7
black yellow grey red
.....
.....
.....
.....
.....
Правильно будет пронумеровать объекты, чтобы знать, какой кадр анимации нам нужен. После добавления их в слои и в легенду мы можем дописать несколько строк кода.
[Explosion7] -> []
[Explosion6] -> [Explosion7]
[Explosion5] -> [Explosion6]
[Explosion4] -> [Explosion5]
[Explosion3] -> [Explosion4]
[Explosion2] -> [Explosion3]
[Explosion1] -> [Explosion2]
[Bomb] -> [Explosion1]
Попробуйте добавить этот код, а затем разместите на уровне бомбу и запустите игру. Вы заметите, что при каждом перемещении анимация бомбы изменяется на один кадр. Переключаясь между объектами, мы создаём анимацию.
Критически важен порядок переключения между анимациями. Последний кадр должен находиться сверху, а первый — внизу. Не забывайте, что код считывается сверху вниз. Если кадры анимации будут находиться в другом порядке, то мы никогда не увидим изменений. Мы увидим только последний кадр, а в нашем случае бомба просто исчезнет. Она будет изменяться на первый кадр, потом на второй и так далее, в одном кадре, ещё до того, как вы увидите графику. Поместив последний кадр в начало, мы в каждом ходу будем видеть следующее изменение.
Использование команды Again
Чтобы анимировать всё вместе, нам нужна команда again. Again означает, что после считывания всего кода PuzzleScript сделает паузу, а затем считает код снова, выполняя все команды again. Её можно использовать для гравитации, скольжения по льду, а в нашем случае и для анимаций. Всё, что нужно — это переписать код следующим образом:
[Explosion7] -> []
[Explosion6] -> [Explosion7] again
[Explosion5] -> [Explosion6] again
[Explosion4] -> [Explosion5] again
[Explosion3] -> [Explosion4] again
[Explosion2] -> [Explosion3] again
[Explosion1] -> [Explosion2] again
[Bomb] -> [Explosion1] again
Протестируйте игру. Вы увидите, что вся анимация бомбы воспроизводится сразу. Если на ваш взгляд она слишком медленная или быстрая, то скорость можно изменить. В самом начале текста программы, под homepage напишите следующий код:
again_interval 0.1
Это часть того, что в PuzzleScript называется prelude. Это место, в котором до остальной части кода можно задать дополнительные правила, определяющие поведение игры. Теперь анимация должна воспроизводиться быстрее. Попробуйте изменить число после again_interval и проверьте, что поменялось.
Готовый пример проекта можно посмотреть здесь.
Часть 12. Гравитация
Обычно PuzzleScript используется для создания игр с видом сверху (top down), но на самом деле можно симулировать некоторые элементы сайдскроллеров, хоть и с ограниченными правилами PuzzleScript, то есть они всё равно будут пошаговыми. В этой части я расскажу, как реализовать гравитацию.
Пример проекта
Откройте пример проекта. Я создал простой уровень, в нём есть игрок, ящик на уступе и дверь внизу. Мы хотим сделать так, чтобы когда игрок толкает ящик с уступа, тот падал на землю. Также мы хотим, чтобы сойдя с края, игрок тоже падал на землю.
Падение, этап 1
Вот первая часть кода, который мы будем использовать:
down [ Player | no Object ] -> [ | Player ]
down [ Crate | no Object no Player ] -> [ | Crate ]
Попробуйте столкнуть ящик с края. Вы заметите, что после перемещения ящик зависает в воздухе, но после ещё одного хода он падает на землю. Тут происходит несколько вещей.
Во-первых, давайте поговорим о ключевом слове down. Добавляя down, мы ограничиваем правило тем, что оно применяется только в направлении «вниз». Именно поэтому ящик сдвигается вниз. Если заменить down на right, то вы увидите, что ящик застрянет в правой стене, как в игре с антигравитацией. Попробуйте так сделать.
Далее сделаем что-нибудь необычное. Вместо того, чтобы проверять, находится ли игрок рядом с определённым объектом (например, ящиком), мы проверяем, находится ли он рядом с обобщённым object. Если посмотреть на легенду в коде примера, то вы увидите, что мы определили object как группу объектов, то есть при каждом использовании слова object мы подразумеваем группу объектов. То есть мы проверяем, есть ли какие-то из этих объектов под игроком. Если нет, то мы приказываем игроку занять это пустое пространство и покинуть предыдущее пространство, из-за слова down следуя в направлении вниз.
Но также можно заметить, что после сталкивания с уступа ящик не падает до следующего хода, и что он мгновенно достигает земли. Чтобы устранить это отложенное падение, можно использовать ключевое слово late:
late down [ Player | no Object ] -> [ | Player ]
late down [ Crate | no Object no Player ] -> [ | Crate ]
Но как сделать так, чтобы он падал постепенно, кадр за кадром?
Падение, этап 2
Теперь мы используем ключевое слово random. Перепишем код следующим образом:
random down [ Player | no Object ] -> [ | Player ]
random down [ Crate | no Object no Player ] -> [ | Crate ]
Запустите код. Он работает очень похоже на предыдущий код, но одним важным различием. Ящик зависает в воздухе, но при каждом перемещении игрока он падает вниз на одну ячейку. Это происходит благодаря слову random. Строго говоря, random предназначено для создания игр со случайными элементами, но нам оно пригодилось здесь. Оно заставляет выполняться соответствующую строку кода по одному разу за ход. PuzzleScript выполняет за один ход каждое правило сколько может раз, и только потом игрок видит графические изменения. Именно поэтому кажется, что ящик падает на землю мгновенно. Но при использовании слова random мы разрешаем ему падать только на одну ячейку за раз.
Падение, этап 3
Теперь мы добавим ключевое слово again:
random down [ Player | no Object ] -> [ | Player ] again
random down [ Crate | no Object no Player ] -> [ | Crate ] again
Запустите игру. Всё почти идеально. Ящик на какое-то время зависает в воздухе, но если вы ещё раз переместитесь, то он постепенно падает на землю. Мы уже знакомы с ключевым словом again, по сути оно означает, что в конце хода PuzzleScript снова считывает код и пытается выполнить все команды again как отдельный ход, после чего делает паузу, затем повторяет их снова столько раз, сколько сможет. Важно здесь то, что он делает паузы между ходами again, что позволяет нам увидеть падение ящика.
Последний этап
И последний штрих. Чтобы ящик падал до самой земли сразу после выталкивания с края, нам нужно добавить ещё одну строку кода над той, которую мы только что написали:
[moving Player] -> [moving Player] again
random down [ Player | no Object ] -> [ | Player ] again
random down [ Crate | no Object no Player ] -> [ | Crate ] again
Ключевое слово moving, использованное в квадратных скобках [] перед player означает, что мы проверяем, движется ли игрок. Это значит, что если игрок движется, то мы приказываем ему продолжать двигаться и запускать команду again. Тогда сразу же сработает всё, что использует ключевое слово again; в нашем случае это анимации падения ящика.
Готовый пример проекта можно посмотреть здесь.
Часть 13. Проверка направления движения
Указание направления
Иногда бывает, что нужно проверить что-то только в одном направлении. Например, вам нужно сделать блоки, которые можно толкать только по горизонтали, или создать утку, которая может ходить только влево.
Как бы то ни было, мы можем указывать, чтобы события происходили только в определённых направлениях.
Откройте PuzzleScript, создайте игру на основе примера Basic и замените его Rules следующим кодом:
[ Left Player ] -> [ Crate ]
Запустите игру и посмотрите, что произойдёт.
Если попытаться пойти влево, то игрок превратится в ящик. Это хороший пример того, как работает указание направлений.
Мы уже привыкли к формату, в котором я объяснял код PuzzleScript:
[ Условие ] -> [ Событие ]
Если событие истинно, то происходит событие.
Но теперь, когда нам нужно проверять направления, он будет следовать новым правилам:
[ Movement Direction Object Affected ] -> [ New State of Object ]
То есть наш предыдущий код:
[ Left Player ] -> [ Crate ]
проверяет, движется ли игрок влево (Left). Если да, то мы заменяем игрока на объект ящика.
Типы направлений
Можно выбирать из следующих типов направлений:
- Up
- Down
- Left
- Right
- Horizontal (проверяет, есть ли движение по горизонтали)
- Vertical (проверяет, есть ли движение по вертикали)
Часть 14. Создание настраиваемых перемещений
По каким-то причинам в игре может понадобиться настраиваемое перемещение. Утки могут ходить только влево, ящики можно толкать только по горизонтали, и так далее. Сделать это очень просто. Давайте ещё раз взглянем на базовый формат кода PuzzleScript:
[ Условие ] -> [ Событие ]
Если условие истинно, то происходит событие. Чтобы создать настраиваемое перемещение, нужно сделать следующее:
[ Condition ] -> [ Movement Direction Object To Move]
Вот как будет выглядеть пример:
[ Player | Crate ] -> [ Player | Left Crate ]
Загрузите PuzzleScript, откройте пример Basic, вставьте этот код и посмотрите, что произойдёт. Подходите к ящику, вокруг которого нет стен.
Вы увидите, что когда рядом с ящиком находится игрок, то ящик толкается влево. Однако так как ключевого слова Late нет, это происходит в ходе после того, как вы сделали первый шаг к нему.
При указании в Event кроме объекта ещё и направления движения, например Left, PuzzleScript будет пытаться переместить объект в указанном направлении. Именно поэтому ящик, а не игрок движется влево — Left находится рядом с Crate.
Помните этот код из предыдущей части?
[ Направление движения Объект] -> [ Новое состояние объекта]
Если указать направление движения в условии рядом с объектом, то оно проверяет, двигается ли этот объектов данном направлении. Это важное отличие. Можно переписать это так:
[ Проверяем это ] -> [ Делаем это ]
Допустимые настраиваемые перемещения
Настраиваемые движения можно создавать с любым из этих слов:
- Up
- Down
- Left
- Right
Нельзя использовать Horizontal или Vertical, потому что PuzzleScript не поймёт, в каком направлении вы хотите переместить объект, или чтобы сделать это, потребуется много обходных действий. Нужно указывать конкретное направление.
Часть 15. Проверка ячеек рядом с объектами
Иногда бывает необходимо проверить, что находится рядом с объектом. Сделать это довольно просто. Давайте ещё раз посмотрим на формат кода PuzzleScript:
[ Условие ] -> [ Событие ]
то есть
[ Проверяем это ] -> если это истинно [ То делаем это ]
Чтобы проверить по сторонам от объекта, нам нужно добавить один элемент. Формат выглядит так:
Проверяемая сторона [ проверяемый object1 | object2 рядом ] -> [ object1 | object2]
Перед условием мы проверяем, в какой стороне от объекта мы хотим выполнить проверку.
Внутри условия мы предполагаем, что нужно не меньше двух ячеек. Ячейка — это, по сути, любые объекты, находящиеся в одном пространстве и не рядом друг с другом.
[ Это одна ячейка ]
[ Это первая ячейка | это вторая ячейка | это третья ячейка ]
Первая ячейка — это объект, стороны которого мы проверяем. Вторая ячейка — это объект, наличие которого мы проверяем. Попробуйте следующие примеры:
Left [ Player | Crate ] -> [ Player | ]
Показанный выше код удаляет ящики, если они находятся в квадрате слева от игрока.
Left [ Crate | Crate ] -> [ Player | Crate ]
Этот код проверяет, находится ли ящик слева от другого ящика. Если да, то ящик, сторона которого проверяется, станет новым объектом игрока.
Left [ Crate | Crate ] -> [ Crate | Player ]
В приведённом выше коде есть такая же проверка, но объектом игрока становится ящик слева.
Left [ Crate | Crate | Crate ] -> [ Crate | Player | Crate ]
В этом коде тоже используется аналогичный паттерн. Ящик непосредственно слева от проверяемого становится игроком, если 3 ящика находятся рядом друг с другом по горизонтали.
Left [ Crate | Crate | Crate ] -> [ Crate | Crate | Player ]
Если 3 ящика находятся рядом друг с другом по горизонтали, то самый левый становится игроком.
Если вы протестируете каждый из этих примеров, то начнёте понимать закономерность. Первая ячейка — это объект, который мы проверяем, чтобы увидеть, что находится рядом. Следующие ячейки, от ближайшей до дальней, являются объектами, наличие которых мы проверяем.
Ключевые слова
- Up — проверяет над объектом
- Down — проверяет под объектом
- Left — проверяет слева от объекта
- Right — проверяет справа от объекта
- Horizontal — проверяет слева и справа от объекта
- Vertical — проверяет над и под объектом
Введение: ползунок перетаскивается, чтобы убедиться, что многие места используются во многих местах. Я думал о том, чтобы написать его на выходных и поставить его, чтобы посмотреть, сможет ли кто -нибудь использовать его!
Эффект:
Идеи реализации:
- Нарисуйте исходную диаграмму кусочком ткани, а затем нарисуйте квадрат начинки, чтобы было достигнуто недостаток эффекта (квадратная координата является случайной);
- Нарисуйте блок перетаскивания с тканью для рисования и в то же время используйте рисование, чтобы перехватить те же координаты и размер, что и предыдущая область в китайской форме, в качестве диаграммы проверки, и поместите диаграмму проверки на левую сторону;
- В блоке перетаскивания нажмите мышь и перетащите, перетащите блок и диаграмму проверки, чтобы следовать за мышью, пусть мышь будет выпущена после достижения определенного диапазона, и проверка будет проверена;
- Проверка проходит, проверка будет успешной, а проверка блока и диаграмма проверки будет возвращена в дальний левый.
3 конструкторы
Функция структуры изображения
// Конструктор ImageDraw объекта изображения
function ImageDraw(o,obj){
this.id='',
this.image = 0, // объект изображения (обязательно)
this.sx = 0, // нарезка нарезов x Положение x Положение (не нужно заполнять при отображении всего изображения)
this.sysy = 0, // Срезы изображений запускают y положение (не нужно заполнять при отображении всего изображения)
This.swidth = 0, // нарезка нарезка запускается шириной (не нужно заполнять всю картинку)
This.sheight = 0, // нарезка картинки начинается высоко (не нужно заполнять при отображении всего изображения)
this.dx = 0, // Цель изображения x позиция (требуется)
this.dy = 0, // Целевая цель изображения y (требуется)
This.dwidth = 0, // Ширина отображения цели изображения (нет необходимости заполнять ширину без масштабирования)
This.dheight = 0 // Высота цели и высота изображения (нет необходимости заполнять, когда высота не увеличивается)
this.init(o,obj);
}
ImageDraw.prototype.init=function(o,obj){
for(var key in o){
this[key]=o[key];
}
return this;
}
ImageDraw.prototype.render=function(context){
draw(context,this);
function draw(context,obj) {
var ctx=context;
ctx.save();
if(!obj.image || getType(obj.dx)=='undefined' || getType(obj.dy)=='undefined'){
Выбросить новую ошибку («рисунок с изображениями отсутствует параметры»);
return;
}
ctx.translate(obj.dx,obj.dy);
if(getType(obj.sx)!='undefined' && getType(obj.sy)!='undefined' && obj.sWidth && obj.sHeight && obj.dWidth && obj.dHeight){
// сократить изображение, есть масштабирование, когда оно отображается
ctx.drawImage(obj.image, obj.sx, obj.sy, obj.sWidth, obj.sHeight, 0, 0, obj.dWidth, obj.dHeight);
}else if(obj.dWidth && obj.dHeight){
ctx.drawimage (obj.image, 0, 0, obj.dwidth, obj.dheight); // оригинальные картинки, шоу
}else{
ctx.drawimage (obj.image, 0, 0); // исходное изображение, нет усадки, когда оно отображается
}
ctx.restore();
}
}
ImageDraw.prototype.isPoint=function(pos){
// x и y положения мыши должны быть больше, чем dx, dy и x и y меньше, чем dx+dwidth, dy+dheight, соответственно.
If (pos.x> this.dx && pos.y> this.dy && pos.x <this.dx+this.dwidth && pos.y <this.dy+this.dheight) {// означает в текущей картине объект внутри
return true;
}
return false;
}
Функция квадратной структуры
function Rect(o){
this.x = 0, // x координаты
this.y = 0, // y координаты
this.width = 100, // широкий
this.height = 40, // высокий
это. thin = true, // немного тоньше
this.init(o);
}
Rect.prototype.init=function(o){
for(var key in o){
this[key]=o[key];
}
}
Rect.prototype.render=function(context){
this.ctx=context;
innerRender(this);
function innerRender(obj){
var ctx=obj.ctx;
ctx.save()
ctx.beginPath();
ctx.translate(obj.x,obj.y);
if(obj.lineWidth){
ctx.lineWidth=obj.lineWidth;
}
if(obj.thin){
ctx.translate(0.5,0.5);
}
ctx.rect(0,0,obj.width,obj.height);
if (obj.fill) {// заполнять ли
obj.fillStyle?(ctx.fillStyle=obj.fillStyle):null;
ctx.fill();
}
if (obj.stroke) {// рисовать ребра
obj.strokeStyle?(ctx.strokeStyle=obj.strokeStyle):null;
ctx.stroke();
}
ctx.restore();
}
return this;
}
Функция структуры текста
function Text(o){
this.x = 0, // x координаты
this.y = 0, // y координаты
this.text = '', // content
this.font = null; // font
this.textalign = null; // выравнивание
this.init(o);
}
Text.prototype.init=function(o){
for(var key in o){
this[key]=o[key];
}
}
Text.prototype.render=function(context){
this.ctx=context;
innerRender(this);
function innerRender(obj){
var ctx=obj.ctx;
ctx.save()
ctx.beginPath();
ctx.translate(obj.x,obj.y);
if(obj.font){
ctx.font=obj.font;
}
if(obj.textAlign){
ctx.textAlign=obj.textAlign;
}
if (obj.fill) {// заполнять ли
obj.fillStyle?(ctx.fillStyle=obj.fillStyle):null;
ctx.fillText(obj.text,0,0);
}
ctx.restore();
}
return this;
}
Нарисуйте исходную диаграмму и отсутствующий блок
var img = new ImageDraw({image:this.imgObj[0],dx:0, dy:0 ,dWidth:640,dHeight:360},this);
this.renderArr.push(img);
var x = _. getrandom (100 580); // x от 100-580
var y y = _. getrandom (0,300); // y, чтобы взять между 0-300
this.validPos={x:x,y:y};
// отсутствие блок -рисунка
var rect = new Rect({
x:x,
y:y,
width:60,
height:60,
fill:true,
fillStyle:'gray'
})
this.renderArr.push(rect);
// Нарисуйте длинную полоску проверки
var rect = new Rect({
x:0,
y:360,
width:640,
height:40,
fill:true,
fillStyle:'#E8E8E8'
})
this.renderArr.push(rect);
// рисовать текст
var text = new Text({
x:300,
y:390,
Текст: «Перетащить проверку ползунка»,
font:'18px serif',
textAlign:'center',
fill:true,
//fillStyle:'white'
});
this.renderArr.push(text);
Эффект страницы в настоящее время выглядит следующим образом
Нарисуйте диаграмму проверки и блок перетаскивания
ПРИМЕЧАНИЕ. Координаты чертежа схемы проверки совпадают с отсутствием координат смачивания, нарисованными на предыдущем этапе.
Var pos = this.validpos; // ранее рисовать недостающие координаты, диаграмма проверки должна быть перехвачена в соответствии с этими координатами
var img = new ImageDraw({image:this.imgObj[0],sx:pos.x,sy:pos.y,sWidth:60,sHeight:60,dx:0, dy:pos.y,dWidth:60,dHeight:60},this);
this.renderArr2.push(img);
var img1 = new ImageDraw({image:this.imgObj[1],dx:0, dy:360 ,dWidth:40,dHeight:40},this);
this.renderArr2.push(img1);
Карта эффекта:
Gotic 2 Add Event
// Добавить событие Click в холст Canvas
canvas2.addEventListener('mousedown',this.mouseDown.bind(this));
canvas2.addEventListener('mouseup',this.mouseUp.bind(this));
canvas2.addEventListener('mousemove',this.mouseMove.bind(this));
Нажмите на событие
- Запишите координаты x при нажатии мыши, чтобы мыши двигались.
- Измените движущуюся отметку на истину, чтобы предотвратить влияние движения движения, не перетаскивая слайдер.
Slider.prototype.mouseDown=function(e){
var pos = _.getoffset (e); // Получить положение мыши
if(!this.block) return ;
if (this.block.ispoint (pos)) {// Нажмите, позиция - это положение слайдера
this.move = true; // означает, что это может быть перемещено
This.downx = pos.x; // Запишите положение нажатия мыши, чтобы продолжать двигаться
}
}
Событие движения мыши
- Первоначальные x координаты мыши щелкнут при перемещении диаграммы проверки и слайдера.
- Когда он превышает определенный диапазон, его нельзя перенести, чтобы предотвратить диапазон холста.
Slider.prototype.mouseMove=function(e){
if (! this.move) return; // мобильная марка ложна и возвращается напрямую
var pos = _.getOffset(e);
pos.x- = this.downx; // Чтобы вычесть начальную позицию щелчка мыши
if(pos.x>580){
return ;
}
this.update (pos); // mobile
}
// Обновление
Slider.prototype.update=function(pos){
// Измените координаты ползунка и диаграмму проверки
_.each(this.renderArr2,function(item){
if(item){
item.dx=pos.x;
}
});
// Рисовать
this.render();
}
Событие выпуска мыши
- Двигаться двигаться двигаться как ложные;
- Мышь высвобождается, не достигая объема проверки, слайдер и схемы проверки вернется в крайний левый;
- Когда движение диаграммы проверки достигает определенного диапазона, это означает, что проверка проходит;
- После одобрения проверки проходит проверка быстрого, и необходимо изменить соответствующее содержание, например, удаление отсутствующего блока, изменение текстового содержимого текста;
Slider.prototype.mouseUp=function(e){
this.move=false;
var pos = _.getOffset(e);
pos.x -= this.downX;
var value = this.validpos; // Проверьте быстрые координаты
if (math.abs (pos.x- value.x) <= 10) {// verification (диапазон положения x находится в диапазоне положения положения x)
console.log ('через')
this.suc();
} else {// проверка не может пройти
this.update({x:0});
}
this.render();
}
Slider.prototype.suc=function(){
this.readrarr.splice (2,1); //
this.block=null;
// схема слайдера и проверки
this.renderArr2.length=0;
// Измените цвет длинных полос
this.renderArr[1].fillStyle='#78C430';
var text = this.renderArr[2];
// напоминание о изменении контента
text.fillStyle='white';
text.text = "проверка успеха";
}
Следующее следующее:
Загрузите полный код, нет баллов
Дайте брату из трех человек!
В разных компаниях эта игра называется по-разному: «Мемори», «Найди пару» или «Мемо», но суть одна:
- Есть стопка карточек, где у каждой картинки есть такая же пара.
- Эти карточки раскладываются картинками вниз на игровом поле.
- Игроки по очереди переворачивают любые две карточки.
- Если картинки совпали — игрок забирает их себе и повторяет ход.
- Если не совпали — переворачивает их назад на то же место и ход переходит к другому игроку.
- Побеждает тот, кто соберёт больше пар.
Этим мы сегодня и будем заниматься.
Что делаем
Чтобы проект не превратился в огромный лонгрид, сделаем всё поэтапно. Сегодня будет самое начало:
- мы сделаем игровое поле;
- добавим карточки;
- и настроим механизм самой игры, чтобы их можно было переворачивать и угадывать пары.
Остальное сделаем в другой раз, этого уже будет достаточно для полноценной игры. Для реализации нам понадобятся стандартные для веба инструменты: HTML, CSS и JavaScript. Игра будет работать в браузере, на мобильниках тоже пойдёт, если высота экрана будет 500 пикселей или больше.
Готовим HTML-файл
Чтобы мы могли управлять всеми карточками, будем создавать их в скрипте — так мы сможем сразу добавить их в память и получить над ними полный контроль. Поэтому всё, что у нас будет на странице, — размеченные блоки для будущего контента.
Ещё мы сразу добавим модальное окно с поздравлениями — то, что увидит игрок после победы. Там будет два элемента — поздравительный текст и кнопка с предложением сыграть ещё. Мы всё это скроем с помощью стилей, а пока разместим всё на странице.
Также нам понадобится:
- Нормализатор CSS — он сделает так, чтобы страница выглядела одинаково во всех браузерах.
- Библиотека jQuery — с её помощью мы получим доступ ко всем элементам на странице. Можно и без неё, но с ней проще.
- Своя таблица стилей — её мы сделаем позже, пока просто подключим.
Создаём новый файл index.html
и добавляем туда сразу весь код страницы:
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Найди пару</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- подключаем нормализатор CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<!-- подключаем свои стили -->
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- общий блок для всего -->
<div class="wrap">
<!-- блок с игрой -->
<div class="game"></div>
<!-- модальное окно, которое появится после сбора всех пар -->
<div class="modal-overlay">
<div class="modal">
<!-- поздравительная надпись -->
<h2 class="winner">Победа!</h2>
<!-- кнопка перезапуска игры -->
<button class="restart">Сыграем ещё?</button>
</div>
</div>
</div>
<!-- подключаем jQuery -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<!-- и наш скрипт -->
<script src="./script.js"></script>
</body>
</html>
У нас пока ничего нет из оформления, поэтому пока на странице мы увидим только текст и кнопку:
Настраиваем модальное окно
Раз у нас уже есть окно, его можно сразу настроить — добавить на страницу нужные стили и посмотреть, как будет выглядеть сообщение о победе.Создаём файл style.css
и добавляем туда сначала общие настройки для всей страницы:
/* для всех элементов ограничиваем их размеры размерами блока */
* {
box-sizing: border-box;
}
/* общие настройки страницы */
html, body {
height: 100%;
}
/* ставим тёмный фон и растягиваем на всю высоту */
body {
background: black;
min-height: 100%;
font-family: "Arial", sans-serif;
}
Теперь оформим модальное окно:
- Используем блок
modal-overlay
, чтобы затемнить всю страницу с её содержимым. - Поверх него в блоке
modal
покажем наше окно с сообщением о победе. - Отдельно в стилях настроим внешний вид текста и кнопки.
Добавим этот код в файл со стилями:
/* настройки затемнения при выводе модального окна */
.modal-overlay {
/* затемняем экран */
background: rgba(0, 0, 0, 0.8);
/* располагаем окно по центру экрана */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* настройки модального окна */
.modal {
position: relative;
width: 500px;
height: 300px;
max-height: 90%;
max-width: 90%;
min-height: 380px;
margin: 0 auto;
background: white;
top: 50%;
transform: translateY(-50%);
padding: 30px 10px;
}
/* настройки шрифта сообщения о победе */
.modal .winner {
font-size: 80px;
text-align: center;
color: #4d4d4d;
text-shadow: 0px 3px 0 black;
}
/* если ширина окна маленькая, делаем шрифт поменьше */
@media (max-width: 480px) {
.modal .winner {
font-size: 60px;
}
}
/* настройки кнопки перезапуска игры */
.modal .restart {
margin: 30px auto;
padding: 20px 30px;
display: block;
font-size: 30px;
border: none;
background: #4d4d4d;
background: linear-gradient(#4d4d4d, #222);
border: 1px solid #222;
border-radius: 5px;
color: white;
text-shadow: 0px 1px 0 black;
cursor: pointer;
}
/* меняем фон при наведении мышки на кнопку */
.modal .restart:hover {
background: linear-gradient(#222, black);
}
/* выравниваем надписи на модальном окне по центру */
.modal .message {
text-align: center;
}
Окно готово, но на старте оно не нужно, поэтому скроем его: добавим display: none;
в оба первых блока. Это скроет окно, но всё оформление останется — как только окно нам понадобится, мы уберём это свойство через скрипт.
Создаём карточки
Теперь нам понадобится скрипт. Создаём файл script.js
и добавляем в него пустую функцию — вся работа будет происходить внутри неё:
(function(){
})();
Так как у нас журнал про технологии и программирование, для карточек мы будем использовать логотипы популярных языков, программ и технологий. Для этого сходим в Википедию, возьмём оттуда ссылки на логотипы и используем их для создания массива с карточками. Этот массив, как и всё остальное, положим внутрь нашей основной функции:
// весь скрипт — это одна большая функция
(function(){
// карточки
var cards = [
{
// название
name: "php",
// адрес картинки
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/297px-PHP-logo.svg.png",
// порядковый номер пары
id: 1,
},
{
name: "css3",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/CSS3_logo.svg/160px-CSS3_logo.svg.png",
id: 2
},
{
name: "html5",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/160px-HTML5_logo_and_wordmark.svg.png",
id: 3
},
{
name: "jquery",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/JQuery-Logo.svg/440px-JQuery-Logo.svg.png",
id: 4
},
{
name: "javascript",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Unofficial_JavaScript_logo_2.svg/160px-Unofficial_JavaScript_logo_2.svg.png",
id: 5
},
{
name: "node",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/262px-Node.js_logo.svg.png",
id: 6
},
{
name: "photoshop",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Adobe_Photoshop_CC_icon.svg/164px-Adobe_Photoshop_CC_icon.svg.png",
id: 7
},
{
name: "python",
img: "https://www.python.org/static/img/python-logo@2x.png",
id: 8
},
{
name: "rails",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Ruby_On_Rails_Logo.svg/425px-Ruby_On_Rails_Logo.svg.png",
id: 9
},
{
name: "sass",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Sass_Logo_Color.svg/213px-Sass_Logo_Color.svg.png",
id: 10
},
{
name: "sublime",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Breezeicons-apps-48-sublime-text.svg/160px-Breezeicons-apps-48-sublime-text.svg.png",
id: 11
},
{
name: "wordpress",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/WordPress_logo.svg/440px-WordPress_logo.svg.png",
id: 12
},
];
})();
На экране пока ничего не появится — мы просто запомнили ссылки на картинки и сложили их в массив. Чтобы сформировать сами карточки, нам понадобится объект — переменная, внутри которой можно объявлять и вызывать разные методы.
Вот что будет происходить в этом объекте:
- Получаем доступ ко всем элементам на странице.
- Виртуально перемешиваем карточки.
- Для каждой карточки динамически создаём HTML-код — с его помощью мы увидим эту карточку на игровом поле.
- Получаем доступ ко всем карточкам.
- Устанавливаем стартовые значения разных свойств и переменных.
- Добавляем всем элементам свою реакцию на нажатие.
- Прописываем логику сравнения выбранной пары.
- Если пары закончились — показываем победное сообщение.
- Если нажали на перезапуск — запускаем игру сначала.
Получается, что за всю логику игры у нас отвечает один объект — мы настраиваем методы и обработку событий, а он дальше сам разбирается, что с этим делать и как реагировать в каждом случае. В этом сила объектно-ориентированного программирования: мы задаём общее поведение, а дальше компьютер решает всё сам.
Для «рубашки» наших карточек возьмём бесплатную фотографию какого-то кода из сервиса Unsplash.
// объявляем объект, внутри которого будет происходить основная механика игры
var Memory = {
// создаём карточку
init: function(cards){
// получаем доступ к классам
this.$game = $(".game");
this.$modal = $(".modal");
this.$overlay = $(".modal-overlay");
this.$restartButton = $("button.restart");
// собираем из карточек массив — игровое поле
this.cardsArray = $.merge(cards, cards);
// перемешиваем карточки
this.shuffleCards(this.cardsArray);
// и раскладываем их
this.setup();
},
// как перемешиваются карточки
shuffleCards: function(cardsArray){
// используем встроенный метод .shuffle
this.$cards = $(this.shuffle(this.cardsArray));
},
// раскладываем карты
setup: function(){
// подготавливаем код с карточками на страницу
this.html = this.buildHTML();
// добавляем код в блок с игрой
this.$game.html(this.html);
// получаем доступ к сформированным карточкам
this.$memoryCards = $(".card");
// на старте мы не ждём переворота второй карточки
this.paused = false;
// на старте у нас нет перевёрнутой первой карточки
this.guess = null;
// добавляем элементам на странице реакции на нажатия
this.binding();
},
// как элементы будут реагировать на нажатия
binding: function(){
// обрабатываем нажатие на карточку
this.$memoryCards.on("click", this.cardClicked);
// и нажатие на кнопку перезапуска игры
this.$restartButton.on("click", $.proxy(this.reset, this));
},
// что происходит при нажатии на карточку
cardClicked: function(){
// получаем текущее состояние родительской переменной
var _ = Memory;
// и получаем доступ к карточке, на которую нажали
var $card = $(this);
// если карточка уже не перевёрнута и мы не нажимаем на ту же самую карточку второй раз подряд
if(!_.paused && !$card.find(".inside").hasClass("matched") && !$card.find(".inside").hasClass("picked")){
// переворачиваем её
$card.find(".inside").addClass("picked");
// если мы перевернули первую карточку
if(!_.guess){
// то пока просто запоминаем её
_.guess = $(this).attr("data-id");
// если мы перевернули вторую и она совпадает с первой
} else if(_.guess == $(this).attr("data-id") && !$(this).hasClass("picked")){
// оставляем обе на поле перевёрнутыми и показываем анимацию совпадения
$(".picked").addClass("matched");
// обнуляем первую карточку
_.guess = null;
// если вторая не совпадает с первой
} else {
// обнуляем первую карточку
_.guess = null;
// не ждём переворота второй карточки
_.paused = true;
// ждём полсекунды и переворачиваем всё обратно
setTimeout(function(){
$(".picked").removeClass("picked");
Memory.paused = false;
}, 600);
}
// если мы перевернули все карточки
if($(".matched").length == $(".card").length){
// показываем победное сообщение
_.win();
}
}
},
// показываем победное сообщение
win: function(){
// не ждём переворота карточек
this.paused = true;
// плавно показываем модальное окно с предложением сыграть ещё
setTimeout(function(){
Memory.showModal();
Memory.$game.fadeOut();
}, 1000);
},
// показываем модальное окно
showModal: function(){
// плавно делаем блок с сообщением видимым
this.$overlay.show();
this.$modal.fadeIn("slow");
},
// прячем модальное окно
hideModal: function(){
this.$overlay.hide();
this.$modal.hide();
},
// перезапуск игры
reset: function(){
// прячем модальное окно с поздравлением
this.hideModal();
// перемешиваем карточки
this.shuffleCards(this.cardsArray);
// раскладываем их на поле
this.setup();
// показываем игровое поле
this.$game.show("slow");
},
// Тасование Фишера–Йетса - https://bost.ocks.org/mike/shuffle/
shuffle: function(array){
var counter = array.length, temp, index;
while (counter > 0) {
index = Math.floor(Math.random() * counter);
counter--;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
},
// код, как добавляются карточки на страницу
buildHTML: function(){
// сюда будем складывать HTML-код
var frag = '';
// перебираем все карточки подряд
this.$cards.each(function(k, v){
// добавляем HTML-код для очередной карточки
frag += '<div class="card" data-id="'+ v.id +'"><div class="inside">
<div class="front"><img src="'+ v.img +'"
alt="'+ v.name +'" /></div>
<div class="back"><img src="https://images.unsplash.com/photo-1576836165612-8bc9b07e7778?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80"
alt="Codepen" /></div></div>
</div>';
});
// возвращаем собранный код
return frag;
}
};
Чтобы объект «ожил», добавим в самый конец нашей основной функции вызов первого метода объекта:
// запускаем игру
Memory.init(cards);
У нас появилось много всякого разного на странице, но выглядит хаотично, а всё потому, что мы не настроили стили для карточек. Сейчас будем исправлять.
Настраиваем стили карточек
Наша задача сейчас — разместить картинки по карточкам, а сами карточки расположить по сетке 6 × 4. Для этого мы сначала подготовим два блока — общий и блок с карточками:
/* стили основного блока */
.wrap {
/* устанавливаем относительное позиционирование */
position: relative;
/* высота элементов */
height: 100%;
/* минимальная высота и отступы */
min-height: 500px;
padding-bottom: 20px;
}
/* блок с игрой */
.game {
/* добавляем трёхмерность для эффекта вращения */
transform-style: preserve-3d;
perspective: 500px;
/* пусть элементы занимают всё доступное им пространство */
min-height: 100%;
height: 100%;
}
Теперь сделаем сетку — добавим стили именно для карточек, где укажем их высоту и ширину. Ещё добавим медиазапрос, чтобы на небольших экранах карточки тоже выглядели хорошо:
/* стили карточек */
.card {
/* параметры расположения, высоты и ширины карточки */
float: left;
width: 16.66666%;
height: 25%;
/* отступы */
padding: 5px;
/* выравнивание по центру */
text-align: center;
/* подключаем блочные элементы и перспективу */
display: block;
perspective: 500px;
/* добавляем относительное позиционирование */
position: relative;
cursor: pointer;
z-index: 50;
}
/* настройки размера карт при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
.card {
width: 25%;
height: 16.666%;
}
}
У нас появилась сетка, но это всё ещё не похоже на нормальные карточки — всё налезает друг на друга и непонятно, как с этим работать. Чтобы это исправить, разделим карточки на лицевую и обратную стороны:
/* обратная сторона карточки */
.card .inside {
/* содержимое занимает весь размер карточки */
width: 100%;
height: 100%;
display: block;
/* анимация переворачивания */
transform-style: preserve-3d;
transition: 0.4s ease-in-out;
/* у лицевой стороны будет белый фон */
background: white;
}
/* общие настройки для обеих сторон карточки */
.card .front, .card .back {
/* рисуем границу */
border: 1px solid black;
/* прячем обратную сторону */
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
/* абсолютное позиционирование */
position: absolute;
top: 0;
left: 0;
/* размеры и отступ */
width: 100%;
height: 100%;
padding: 20px;
}
/* настройки изображения на лицевой и обратной стороне */
.card .front img, .card .back img {
/* картинка занимает всю ширину */
max-width: 100%;
/* отображаем как блочный элемент, без отступов */
display: block;
margin: 0 auto;
max-height: 100%;
}
/* настройки лицевой стороны */
.card .front {
/* переворачиваем карточку обложкой вверх */
transform: rotateY(-180deg);
}
/* настройки при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
.card .front {
padding: 5px;
}
.card .back {
padding: 10px;
}
}
/* запускаем анимацию переворачивания при клике на карточку */
.card .inside.picked, .card .inside.matched {
transform: rotateY(180deg);
}
Карточки расположились по сетке, переворачиваются и возвращаются назад, если пара не совпала. Единственное, чего нам не хватает, — анимации совпадения. Сделаем так, чтобы при переворачивании пары фон у карточек из неё на мгновение становился зелёным:
/* задаём ключевые кадры анимации совпадения */
@keyframes matchAnim {
0% {
/* зелёный фон */
background: #bcffcc;
}
100% {
/* белый фон */
backgroud: white;
}
}
/* и делаем то же самое для движка WebKit */
@-webkit-keyframes matchAnim {
0% {
background: #bcffcc;
}
100% {
background: white;
}
}
/* анимация совпадения пары */
.card .inside.matched {
-webkit-animation: 1s matchAnim ease-in-out;
animation: 1s matchAnim ease-in-out;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
Поиграть на странице проекта.
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Найди пару</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- подключаем нормализатор CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<!-- подключаем свои стили -->
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- общий блок для всего -->
<div class="wrap">
<!-- блок с игрой -->
<div class="game"></div>
<!-- модальное окно, которое появится после сбора всех пар -->
<div class="modal-overlay">
<div class="modal">
<!-- поздравительная надпись -->
<h2 class="winner">Победа!</h2>
<!-- кнопка перезапуска игры -->
<button class="restart">Сыграем ещё?</button>
</div>
</div>
</div>
<!-- подключаем jQuery -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<!-- и наш скрипт -->
<script src="./script.js"></script>
</body>
</html>
/* для всех элементов ограничиваем их размеры размерами блока */
* {
box-sizing: border-box;
}
/* общие настройки страницы */
html, body {
height: 100%;
}
/* ставим тёмный фон и растягиваем на всю высоту */
body {
background: black;
min-height: 100%;
font-family: "Arial", sans-serif;
}
/* стили основного блока */
.wrap {
/* устанавливаем относительное позиционирование */
position: relative;
/* высота элементов */
height: 100%;
/* минимальная высота и отступы */
min-height: 500px;
padding-bottom: 20px;
}
/* блок с игрой */
.game {
/* добавляем трёхмерность для эффекта вращения */
transform-style: preserve-3d;
perspective: 500px;
/* пусть элементы занимают всё доступное им пространство */
min-height: 100%;
height: 100%;
}
/* стили карточек */
.card {
/* параметры расположения, высоты и ширины карточки */
float: left;
width: 16.66666%;
height: 25%;
/* отступы */
padding: 5px;
/* выравнивание по центру */
text-align: center;
/* подключаем блочные элементы и перспективу */
display: block;
perspective: 500px;
/* добавляем относительное позиционирование */
position: relative;
cursor: pointer;
z-index: 50;
}
/* настройки размера карт при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
.card {
width: 25%;
height: 16.666%;
}
}
/* обратная сторона карточки */
.card .inside {
/* содержимое занимает весь размер карточки */
width: 100%;
height: 100%;
display: block;
/* анимация переворачивания */
transform-style: preserve-3d;
transition: 0.4s ease-in-out;
/* у лицевой стороны будет белый фон */
background: white;
}
/* общие настройки для обеих сторон карточки */
.card .front, .card .back {
/* рисуем границу */
border: 1px solid black;
/* прячем обратную сторону */
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
/* абсолютное позиционирование */
position: absolute;
top: 0;
left: 0;
/* размеры и отступ */
width: 100%;
height: 100%;
padding: 20px;
}
/* настройки изображения на лицевой и обратной стороне */
.card .front img, .card .back img {
/* картинка занимает всю ширину */
max-width: 100%;
/* отображаем как блочный элемент, без отступов */
display: block;
margin: 0 auto;
max-height: 100%;
}
/* настройки лицевой стороны */
.card .front {
/* переворачиваем карточку обложкой вверх */
transform: rotateY(-180deg);
}
/* настройки при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
.card .front {
padding: 5px;
}
.card .back {
padding: 10px;
}
}
/* запускаем анимацию переворачивания при клике на карточку */
.card .inside.picked, .card .inside.matched {
transform: rotateY(180deg);
}
/* задаём ключевые кадры анимации совпадения */
@keyframes matchAnim {
0% {
/* зелёный фон */
background: #bcffcc;
}
100% {
/* белый фон */
backgroud: white;
}
}
/* и делаем то же самое для движка WebKit */
@-webkit-keyframes matchAnim {
0% {
background: #bcffcc;
}
100% {
background: white;
}
}
/* анимация совпадения пары */
.card .inside.matched {
-webkit-animation: 1s matchAnim ease-in-out;
animation: 1s matchAnim ease-in-out;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
/* настройки затемнения при выводе модального окна */
.modal-overlay {
/* на старте его не видно */
display: none;
/* затемняем экран */
background: rgba(0, 0, 0, 0.8);
/* располагаем окно по центру экрана */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* настройки модального окна */
.modal {
display: none;
position: relative;
width: 500px;
height: 300px;
max-height: 90%;
max-width: 90%;
min-height: 380px;
margin: 0 auto;
background: white;
top: 50%;
transform: translateY(-50%);
padding: 30px 10px;
}
/* настройки шрифта сообщения о победе */
.modal .winner {
font-size: 80px;
text-align: center;
color: #4d4d4d;
text-shadow: 0px 3px 0 black;
}
/* если ширина окна маленькая, делаем шрифт поменьше */
@media (max-width: 480px) {
.modal .winner {
font-size: 60px;
}
}
/* настройки кнопки перезапуска игры */
.modal .restart {
margin: 30px auto;
padding: 20px 30px;
display: block;
font-size: 30px;
border: none;
background: #4d4d4d;
background: linear-gradient(#4d4d4d, #222);
border: 1px solid #222;
border-radius: 5px;
color: white;
text-shadow: 0px 1px 0 black;
cursor: pointer;
}
/* меняем фон при наведении мышки на кнопку */
.modal .restart:hover {
background: linear-gradient(#222, black);
}
/* выравниваем надписи на модальном окне по центру */
.modal .message {
text-align: center;
}
// Memory Game
// © 2014 Nate Wiley
// License -- MIT
// весь скрипт — это одна большая функция
(function(){
// объявляем объект, внутри которого будет происходить основная механика игры
var Memory = {
// создаём карточку
init: function(cards){
// получаем доступ к классам
this.$game = $(".game");
this.$modal = $(".modal");
this.$overlay = $(".modal-overlay");
this.$restartButton = $("button.restart");
// собираем из карточек массив — игровое поле
this.cardsArray = $.merge(cards, cards);
// перемешиваем карточки
this.shuffleCards(this.cardsArray);
// и раскладываем их
this.setup();
},
// как перемешиваются карточки
shuffleCards: function(cardsArray){
// используем встроенный метод .shuffle
this.$cards = $(this.shuffle(this.cardsArray));
},
// раскладываем карты
setup: function(){
// подготавливаем код с карточками на страницу
this.html = this.buildHTML();
// добавляем код в блок с игрой
this.$game.html(this.html);
// получаем доступ к сформированным карточкам
this.$memoryCards = $(".card");
// на старте мы не ждём переворота второй карточки
this.paused = false;
// на старте у нас нет перевёрнутой первой карточки
this.guess = null;
// добавляем элементам на странице реакции на нажатия
this.binding();
},
// как элементы будут реагировать на нажатия
binding: function(){
// обрабатываем нажатие на карточку
this.$memoryCards.on("click", this.cardClicked);
// и нажатие на кнопку перезапуска игры
this.$restartButton.on("click", $.proxy(this.reset, this));
},
// что происходит при нажатии на карточку
cardClicked: function(){
// получаем текущее состояние родительской переменной
var _ = Memory;
// и получаем доступ к карточке, на которую нажали
var $card = $(this);
// если карточка уже не перевёрнута и мы не нажимаем на ту же самую карточку второй раз подряд
if(!_.paused && !$card.find(".inside").hasClass("matched") && !$card.find(".inside").hasClass("picked")){
// переворачиваем её
$card.find(".inside").addClass("picked");
// если мы перевернули первую карточку
if(!_.guess){
// то пока просто запоминаем её
_.guess = $(this).attr("data-id");
// если мы перевернули вторую и она совпадает с первой
} else if(_.guess == $(this).attr("data-id") && !$(this).hasClass("picked")){
// оставляем обе на поле перевёрнутыми и показываем анимацию совпадения
$(".picked").addClass("matched");
// обнуляем первую карточку
_.guess = null;
// если вторая не совпадает с первой
} else {
// обнуляем первую карточку
_.guess = null;
// не ждём переворота второй карточки
_.paused = true;
// ждём полсекунды и переворачиваем всё обратно
setTimeout(function(){
$(".picked").removeClass("picked");
Memory.paused = false;
}, 600);
}
// если мы перевернули все карточки
if($(".matched").length == $(".card").length){
// показываем победное сообщение
_.win();
}
}
},
// показываем победное сообщение
win: function(){
// не ждём переворота карточек
this.paused = true;
// плавно показываем модальное окно с предложением сыграть ещё
setTimeout(function(){
Memory.showModal();
Memory.$game.fadeOut();
}, 1000);
},
// показываем модальное окно
showModal: function(){
// плавно делаем блок с сообщением видимым
this.$overlay.show();
this.$modal.fadeIn("slow");
},
// прячем модальное окно
hideModal: function(){
this.$overlay.hide();
this.$modal.hide();
},
// перезапуск игры
reset: function(){
// прячем модальное окно с поздравлением
this.hideModal();
// перемешиваем карточки
this.shuffleCards(this.cardsArray);
// раскладываем их на поле
this.setup();
// показываем игровое поле
this.$game.show("slow");
},
// Тасование Фишера–Йетса - https://bost.ocks.org/mike/shuffle/
shuffle: function(array){
var counter = array.length, temp, index;
while (counter > 0) {
index = Math.floor(Math.random() * counter);
counter--;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
},
// код, как добавляются карточки на страницу
buildHTML: function(){
// сюда будем складывать HTML-код
var frag = '';
// перебираем все карточки подряд
this.$cards.each(function(k, v){
// добавляем HTML-код для очередной карточки
frag += '<div class="card" data-id="'+ v.id +'"><div class="inside">
<div class="front"><img src="'+ v.img +'"
alt="'+ v.name +'" /></div>
<div class="back"><img src="https://images.unsplash.com/photo-1576836165612-8bc9b07e7778?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80"
alt="Codepen" /></div></div>
</div>';
});
// возвращаем собранный код
return frag;
}
};
// карточки
var cards = [
{
// название
name: "php",
// адрес картинки
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/297px-PHP-logo.svg.png",
// порядковый номер пары
id: 1,
},
{
name: "css3",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/CSS3_logo.svg/160px-CSS3_logo.svg.png",
id: 2
},
{
name: "html5",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/160px-HTML5_logo_and_wordmark.svg.png",
id: 3
},
{
name: "jquery",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/JQuery-Logo.svg/440px-JQuery-Logo.svg.png",
id: 4
},
{
name: "javascript",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Unofficial_JavaScript_logo_2.svg/160px-Unofficial_JavaScript_logo_2.svg.png",
id: 5
},
{
name: "node",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/262px-Node.js_logo.svg.png",
id: 6
},
{
name: "photoshop",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Adobe_Photoshop_CC_icon.svg/164px-Adobe_Photoshop_CC_icon.svg.png",
id: 7
},
{
name: "python",
img: "https://www.python.org/static/img/python-logo@2x.png",
id: 8
},
{
name: "rails",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Ruby_On_Rails_Logo.svg/425px-Ruby_On_Rails_Logo.svg.png",
id: 9
},
{
name: "sass",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Sass_Logo_Color.svg/213px-Sass_Logo_Color.svg.png",
id: 10
},
{
name: "sublime",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Breezeicons-apps-48-sublime-text.svg/160px-Breezeicons-apps-48-sublime-text.svg.png",
id: 11
},
{
name: "wordpress",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/WordPress_logo.svg/440px-WordPress_logo.svg.png",
id: 12
},
];
// запускаем игру
Memory.init(cards);
})();
Что дальше
Сейчас у нас всё работает, но как-то неопрятно:
- картинки не по центру;
- непонятно, как играть нескольким игрокам;
- нет подсчёта пар.
Исправим это в следующей версии. Подпишитесь, чтобы не пропустить продолжени.
Вёрстка:
Кирилл Климентьев