Web-программирование, HTML/CSS
Gusarev (обсуждение | вклад) (→Рисоване мышкой) |
Gusarev (обсуждение | вклад) (→03.03.2022) |
||
Строка 854: | Строка 854: | ||
context.closePath(); | context.closePath(); | ||
} | } | ||
+ | </source> | ||
+ | |||
+ | ====Крестики-нолики==== | ||
+ | |||
+ | tic-tac-toe.html | ||
+ | <source lang='html4strict'> | ||
+ | <!doctype html> | ||
+ | <html lang="en"> | ||
+ | <head> | ||
+ | <meta charset="UTF-8"> | ||
+ | <meta name="viewport" | ||
+ | content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> | ||
+ | <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
+ | <title>Tic Tac Toe</title> | ||
+ | <link rel="stylesheet" href="style.css"> | ||
+ | </head> | ||
+ | <body> | ||
+ | <section> | ||
+ | <h1 class="game--title">Tic Tac Toe</h1> | ||
+ | <div class="game--container"> | ||
+ | <div data-cell-index="0" class="cell"></div> | ||
+ | <div data-cell-index="1" class="cell"></div> | ||
+ | <div data-cell-index="2" class="cell"></div> | ||
+ | <div data-cell-index="3" class="cell"></div> | ||
+ | <div data-cell-index="4" class="cell"></div> | ||
+ | <div data-cell-index="5" class="cell"></div> | ||
+ | <div data-cell-index="6" class="cell"></div> | ||
+ | <div data-cell-index="7" class="cell"></div> | ||
+ | <div data-cell-index="8" class="cell"></div> | ||
+ | </div> | ||
+ | <h2 class="game--status"></h2> | ||
+ | <button class="game--restart">Restart Game</button> | ||
+ | </section> | ||
+ | <script src="script.js"></script> | ||
+ | </body> | ||
+ | </html> | ||
+ | </source> | ||
+ | |||
+ | style.css | ||
+ | <source lang="css"> | ||
+ | body { | ||
+ | font-family: "Arial", sans-serif; | ||
+ | } | ||
+ | section { | ||
+ | text-align: center; | ||
+ | } | ||
+ | .game--container { | ||
+ | display: grid; | ||
+ | grid-template-columns: repeat(3, auto); | ||
+ | width: 306px; | ||
+ | margin: 50px auto; | ||
+ | } | ||
+ | .cell { | ||
+ | font-family: "Permanent Marker", cursive; | ||
+ | width: 100px; | ||
+ | height: 100px; | ||
+ | box-shadow: 0 0 0 1px #333333; | ||
+ | border: 1px solid #333333; | ||
+ | cursor: pointer; | ||
+ | line-height: 100px; | ||
+ | font-size: 60px; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | script.js | ||
+ | <source lang="javascript"> | ||
+ | /* | ||
+ | We store our game status element here to allow us to more easily | ||
+ | use it later on | ||
+ | */ | ||
+ | const statusDisplay = document.querySelector('.game--status'); | ||
+ | /* | ||
+ | Here we declare some variables that we will use to track the | ||
+ | game state throught the game. | ||
+ | */ | ||
+ | /* | ||
+ | We will use gameActive to pause the game in case of an end scenario | ||
+ | */ | ||
+ | let gameActive = true; | ||
+ | /* | ||
+ | We will store our current player here, so we know whos turn | ||
+ | */ | ||
+ | let currentPlayer = "X"; | ||
+ | /* | ||
+ | We will store our current game state here, the form of empty strings in an array | ||
+ | will allow us to easily track played cells and validate the game state later on | ||
+ | */ | ||
+ | let gameState = ["", "", "", "", "", "", "", "", ""]; | ||
+ | /* | ||
+ | Here we have declared some messages we will display to the user during the game. | ||
+ | Since we have some dynamic factors in those messages, namely the current player, | ||
+ | we have declared them as functions, so that the actual message gets created with | ||
+ | current data every time we need it. | ||
+ | */ | ||
+ | const winningMessage = () => `Player ${currentPlayer} has won!`; | ||
+ | const drawMessage = () => `Game ended in a draw!`; | ||
+ | const currentPlayerTurn = () => `It's ${currentPlayer}'s turn`; | ||
+ | /* | ||
+ | We set the inital message to let the players know whose turn it is | ||
+ | */ | ||
+ | statusDisplay.innerHTML = currentPlayerTurn(); | ||
+ | function handleCellPlayed(clickedCell, clickedCellIndex) { | ||
+ | /* | ||
+ | We update our internal game state to reflect the played move, | ||
+ | as well as update the user interface to reflect the played move | ||
+ | */ | ||
+ | gameState[clickedCellIndex] = currentPlayer; | ||
+ | clickedCell.innerHTML = currentPlayer; | ||
+ | } | ||
+ | |||
+ | function handlePlayerChange() { | ||
+ | currentPlayer = currentPlayer === "X" ? "O" : "X"; | ||
+ | statusDisplay.innerHTML = currentPlayerTurn(); | ||
+ | } | ||
+ | |||
+ | const winningConditions = [ | ||
+ | [0, 1, 2], | ||
+ | [3, 4, 5], | ||
+ | [6, 7, 8], | ||
+ | [0, 3, 6], | ||
+ | [1, 4, 7], | ||
+ | [2, 5, 8], | ||
+ | [0, 4, 8], | ||
+ | [2, 4, 6] | ||
+ | ]; | ||
+ | function handleResultValidation() { | ||
+ | let roundWon = false; | ||
+ | for (let i = 0; i <= 7; i++) { | ||
+ | const winCondition = winningConditions[i]; | ||
+ | let a = gameState[winCondition[0]]; | ||
+ | let b = gameState[winCondition[1]]; | ||
+ | let c = gameState[winCondition[2]]; | ||
+ | if (a === '' || b === '' || c === '') { | ||
+ | continue; | ||
+ | } | ||
+ | if (a === b && b === c) { | ||
+ | roundWon = true; | ||
+ | break | ||
+ | } | ||
+ | } | ||
+ | if (roundWon) { | ||
+ | statusDisplay.innerHTML = winningMessage(); | ||
+ | gameActive = false; | ||
+ | return; | ||
+ | } | ||
+ | /* | ||
+ | We will check weather there are any values in our game state array | ||
+ | that are still not populated with a player sign | ||
+ | */ | ||
+ | let roundDraw = !gameState.includes(""); | ||
+ | if (roundDraw) { | ||
+ | statusDisplay.innerHTML = drawMessage(); | ||
+ | gameActive = false; | ||
+ | return; | ||
+ | } | ||
+ | /* | ||
+ | If we get to here we know that the no one won the game yet, | ||
+ | and that there are still moves to be played, so we continue by changing the current player. | ||
+ | */ | ||
+ | handlePlayerChange(); | ||
+ | } | ||
+ | function handleCellClick(clickedCellEvent) { | ||
+ | /* | ||
+ | We will save the clicked html element in a variable for easier further use | ||
+ | */ | ||
+ | const clickedCell = clickedCellEvent.target; | ||
+ | /* | ||
+ | Here we will grab the 'data-cell-index' attribute from the clicked cell to identify where that cell is in our grid. | ||
+ | Please note that the getAttribute will return a string value. Since we need an actual number we will parse it to an | ||
+ | integer(number) | ||
+ | */ | ||
+ | const clickedCellIndex = parseInt( | ||
+ | clickedCell.getAttribute('data-cell-index') | ||
+ | ); | ||
+ | /* | ||
+ | Next up we need to check whether the call has already been played, | ||
+ | or if the game is paused. If either of those is true we will simply ignore the click. | ||
+ | */ | ||
+ | if (gameState[clickedCellIndex] !== "" || !gameActive) { | ||
+ | return; | ||
+ | } | ||
+ | /* | ||
+ | If everything if in order we will proceed with the game flow | ||
+ | */ | ||
+ | handleCellPlayed(clickedCell, clickedCellIndex); | ||
+ | handleResultValidation(); | ||
+ | } | ||
+ | |||
+ | function handleRestartGame() { | ||
+ | gameActive = true; | ||
+ | currentPlayer = "X"; | ||
+ | gameState = ["", "", "", "", "", "", "", "", ""]; | ||
+ | statusDisplay.innerHTML = currentPlayerTurn(); | ||
+ | document.querySelectorAll('.cell') | ||
+ | .forEach(cell => cell.innerHTML = ""); | ||
+ | } | ||
+ | /* | ||
+ | And finally we add our event listeners to the actual game cells, as well as our | ||
+ | restart button | ||
+ | */ | ||
+ | document.querySelectorAll('.cell').forEach(cell => cell.addEventListener('click', handleCellClick)); | ||
+ | document.querySelector('.game--restart').addEventListener('click', handleRestartGame); | ||
</source> | </source> |
Версия 16:48, 3 марта 2022
Содержание |
Какими программами и сайтами пользоваться
Notepad++ |
VS Code |
Справочник по всему в HTML |
Учебник по всему в JavaScript
|
Материалы некоторых занятий
21.10.2021
Списки
lists.html
<html>
<head>
<title>
Lists
</title>
</head>
<body>
<ol>
<li>List item 1</li>
<li>List item 2</li>
<li>List item 3</li>
<li>List item 4</li>
</ol>
<ul>
<li>unordered list item</li>
<li>unordered list item</li>
<li>unordered list item</li>
<li>unordered list item</li>
<li>unordered list item</li>
</ul>
</body>
</html>
Формы (простой вариант)
forms.html
<html>
<head>
<title>Forms</title>
</head>
<body>
<form>
<input type="text"
placeholder="Type here..."
name="just_text">
<input type="submit" value="Ага, согласен">
</form>
</body>
</html>
Формы (вариант посложнее)
forms_extra.html
<html>
<head>
<link rel="stylesheet" href="variables.css">
<title>
Forms (extra)
</title>
</head>
<body>
<form>
<div>
<p><input name="user_name" type="text" placeholder="User name"></p>
<p><input name="password" type="password" placeholder="password"></p>
</div>
<div>
Favorite color?
<input name="color" type="radio" value="white"> White
<input name="color" type="radio" value="black"> Black
<input name="color" type="radio" value="red"> Red
<input name="color" type="radio" value="blue"> Blue
<input name="color" type="radio" value="green"> Green
</div>
<div>
<input name="ocean" list="oceans" placeholder="Oceans">
<datalist id="oceans">
<option value="Pacific">
<option value="Indian">
<option value="Atlantic">
<option value="Arctic">
</datalist>
</div>
<div>
<input type="checkbox" id="fav_oceans" name="fav_oceans" checked>
<label for="pacific">Pacific</label>
</div>
<div>
<input type="checkbox" id="fav_oceans" name="fav_oceans">
<label for="indian">Indian</label>
</div>
</div>
<div>
<input type="checkbox" id="fav_oceans" name="fav_oceans">
<label for="Arctic">Arctic</label>
</div>
<div>
<input type="checkbox" id="fav_oceans" name="fav_oceans" checked>
<label for="Atlantic">Atlantic</label>
</div>
</form>
</body>
</html>
Стили (inline, type)
styles.html
<html>
<head>
<title>Styles</title>
<style>
h1, h2 {
color:red;
text-align: center;
}
</style>
</head>
<body>
<h1>My first first heading!</h1>
<h1 style="text-align:right;">Second heading here!</h1>
<h2>Secret header</h1>
Hello, world!
</body>
</html>
Стили (внешний css-файл)
styles_css.html
<html>
<head>
<link rel="stylesheet" href="styles.css">
<title>Styles (CSS)</title>
</head>
<body>
<h1>My first first heading!</h1>
<h1 style="color:blue; text-align: right">Second heading here!</h1>
Hello, world!
</body>
</html>
styles.css
h1 {
text-align:center;
color:red;
}
28.10.2021
Таблицы (+thead)
<html>
<head>
<link rel="stylesheet" href="variables.css">
<title>
Tables
</title>
<style>
table {
border: 1px solid black;
border-collapse: collapse;
margin: 10px;
}
/* td {
border: 1px solid black;
}
th {
border: 1px solid black;
} */
td, th {
border: 1px solid black;
padding: 10px;
}
</style>
</head>
<body>
<table>
<thead>
<th>Ocean</th>
<th>Max depth, m</th>
<th>Avg depth, m</th>
</thead>
<tr>
<td>Atlantic</td>
<td>3646</td>
<td>8486</td>
</tr>
<tr>
<td>Pacific</td>
<td>11022</td>
<td>4000</td>
</tr>
<tr>
<td>Indian</td>
<td>8047</td>
<td>3741</td>
</tr>
</table>
</body>
</html>
Области (div)
<html>
<head>
<title>div size</title>
<style>
div {
border: 3px solid black;
background-color:rgb(0, 183, 255);
width: 550px;
height: 400px;
padding: 5px;
margin: 10px;
font-family: 'Courier New', Courier, monospace;
font-size: 28px;
/* font-weight: bold; */
}
</style>
</head>
<body>
<div>
<p>Это пример тега div</p>
<p>А это вторая строка этого div</p>
</div>
</body>
</html>
Styles (id, class)
<html>
<head>
<title>Style ids</title>
<style>
h1 {
color:green;
}
.some_class {
color: orange;
}
#some_id {
color: blue;
}
</style>
</head>
<body>
<!-- <h1>My first heading!</h1>
<h1 id="some_id">My second heading!</h1>
<h1 id="another_id">My third heading!</h1> -->
<h1 id="some_id">My first heading!</h1>
<h1 class="some_class">My second heading!</h1>
<h1 class="some_class">My third heading!</h1>
Hello, world!
</body>
</html>
Изменение при наведении на объект (hover)
<html>
<head>
<title>
Hover
</title>
<style>
button {
border-width: 1px;
width: 200px;
height: 50px;
font-size: 25px;
background-color: green;
}
button:hover {
background-color: chocolate;
}
</style>
</head>
<body>
<button>Click me</button>
</body>
</html>
11.11.2021
JavaScript и управление активностью кнопки
lists.html
<html>
<head>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
document.getElementById('send').disabled = "true";
});
function myFunction() {
var nameInput = document.getElementById('name').value;
console.log(nameInput);
if (nameInput === "") {
document.getElementById('send').disabled = true;
} else {
document.getElementById('send').disabled = false;
}
}
</script>
</head>
<body>
<form action="sent.php" method="post" name="frm">
<input type="text" name="name_input" id="name" onkeyup="myFunction()"><br>
<button type="submit" class="button button-dark" id="send">Send message</button>
</form>
</body>
</html>
18.11.2021
Удивительный сайт, где можно узнать, как написать CSS-код для выравнивания в разных обстоятельствах.
Несколько div, выравнивание, реакция на hover
lists.html
<html>
<head>
<title>
Hover
</title>
<style>
#div_header {
width: 100%;
height: 150px;
font-size: 50px;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
vertical-align: middle;
background-color: #667766;
}
.menu_item {
border-width: 1px;
width: 300px;
height: 100px;
margin-top: 5px;
line-height: 100px;
font-size: 30px;
text-align: center;
background-color: #AABBDD;
}
.menu_item:hover {
background-color: #BBCCEE;
}
</style>
</head>
<body>
<div id="div_header">HEADER</div>
<div class="menu_item">menu item 1</div>
<div class="menu_item"> menu item 2</div>
<div class="menu_item">menu item 3</div>
</body>
</html>
02.12.2021
Как использовать grid и flexbox для вёрстки "прямоугольного" сайта.
Простой пример с вёрсткой grid/flexbox
flex-example.html
<html>
<head>
<title>Document</title>
<link rel="stylesheet" href="flex-example.css">
</head>
<body>
<div class="container">
<div class="header">
Header
<div class="app_logo">My App</div>
<div class="home">Home</div>
<div class="search">Search</div>
<div class="logout">Log out</div>
</div>
<div class="sidebar">
Sidebar
</div>
<div class="content">
Content
</div>
<div class="footer">
Footer
</div>
</div>
</body>
</html>
flex-example.css
html, body {
margin: 0;
padding: 0;
}
.container {
/* background-color: gainsboro;
padding: 10px; */
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: 10vh 80vh 10vh;
}
.header {
background-color: coral;
padding: 10px;
grid-column: 1 / -1;
display: flex;
flex-direction: row;
/* justify-content: start;
justify-content: center;
justify-content: space-between;
justify-content: space-around; */
justify-content: space-evenly;
}
/* .search {
margin-left: auto;
} */
.sidebar {
background-color: darkgoldenrod;
padding: 10px;
grid-column: 1 / 2;
}
.content {
background-color: darkseagreen;
padding: 10px;
grid-column: 2 / -1;
}
.footer {
background-color: dimgrey;
padding: 10px;
grid-column: 1 / -1;
}
09.12.2021
Дополнение к предыдущему коду, использование flex для вариантов выравнивания по горизонтали.
Пример с вёрсткой grid/flexbox: выравниваем заголовок
flex-example.html
<html>
<head>
<title>Document</title>
<link rel="stylesheet" href="flex-example.css">
</head>
<body>
<div class="container">
<div class="header">
<div class="header_left_part">
<div class="app_logo">My App</div>
<div class="home">Home</div>
</div>
<div class="header_right_part">
<div class="search">Search</div>
<div class="logout">Log out</div>
</div>
</div>
<div class="sidebar">
<div class="menu_item">Menu item 1</div>
<div class="menu_item">Menu item 2</div>
<div class="menu_item">Menu item 3</div>
<div class="menu_item">Menu item 4</div>
</div>
<div class="content">
Contentem grew ghr guhr geqhr uqhre gheq r
</div>
<div class="footer">
Footer
</div>
</div>
</body>
</html>
flex-example.css
html, body {
margin: 0;
padding: 0;
}
.container {
/* background-color: gainsboro;
padding: 10px; */
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: 20vh 60vh 20vh;
}
.header {
border-color: blue;
border-style: double;
background-color: coral;
color: white;
grid-column: 1 / -1;
padding: 0px;
display: flex;
}
.header_left_part {
width: 100%;
border-color: yellow;
border-style: double;
display: flex;
}
.header_right_part {
width: 20%;
border-color: red;
border-style: double;
display: flex;
/* margin-left: auto; */
justify-content: flex-end;
}
.app_logo {
padding: 5px;
}
.home {
padding: 5px;
}
.search {
padding: 5px;
}
.logout {
padding: 5px;
}
.sidebar {
background-color: darkgoldenrod;
padding: 10px;
grid-column: 1 / 5;
}
.content {
background-color: darkseagreen;
padding: 10px;
grid-column: 5 / -1;
}
.footer {
background-color: dimgrey;
padding: 10px;
grid-column: 1 / -1;
}
13.01.2022
Пара примеров того, как можно использовать JS (Javascript) на своей странице.
Создание элементов при загрузке окна (onload) и изменение свойств существующих объектов
DOM_example_1.html
<html>
<head>
<script>
// run this function when the document is loaded
window.onload = function() {
// create a couple of elements in an otherwise empty HTML page
const heading = document.createElement("h1");
const heading_text = document.createTextNode("Это header, который был сделан кодом на JS!");
heading.appendChild(heading_text);
document.body.appendChild(heading);
}
</script>
</head>
<body>
<div id="test">default text</div>
<input type="button" style="width:200px;height:50px" value="Click me!" onclick="document.getElementById('test').textContent='Привет-привет!'"></button>
</body>
</html>
27.01.2022
Как обойтись без flex/grid (древние таблицы приходят на помощь)
Задаём структуру страницы таблицами
table_design.html
<html>
<head>
<title>Это заголовок</title>
</head>
<body width="100%" height="100%">
<table border=1 width=100% height=100%>
<tr>
<td height=20% colspan="2">...</td>
</tr>
<tr>
<td width=20%>
Item 1
</td>
<td rowspan="3">
Основной контент
</td>
</tr>
<tr>
<td>
Item 2
</td>
</tr>
<tr>
<td>
Item 3
</td>
</tr>
</table>
</body>
</html>
10.02.2022
Создание динамического меню с помощью JavaScript/CSS
Выпадающее меню
drop_down.html
<html>
<head>
<script>
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
</script>
<style>
/* Dropdown Button */
.dropbtn {
background-color: #3498DB;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
/* Dropdown button on hover & focus */
.dropbtn:hover, .dropbtn:focus {
background-color: #2980B9;
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd}
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
.show {display:block;}
</style>
</head>
<body>
<div class="dropdown">
<button onclick="myFunction()" class="dropbtn">Dropdown</button>
<div id="myDropdown" class="dropdown-content">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#">Link 3</a>
</div>
</div>
</body>
</html>
Обработка вводимого текста и изменение атрибутов объектов на странице
Активная/неактивная кнопка
activate_button.html
<html>
<head>
<title>
Activate button by entering text
</title>
<script type="text/javascript">
function Disabled_change(text) {
// alert(text)
if (text == "123") {
document.getElementById("clickme").disabled = false;
}
else {
document.getElementById("clickme").disabled = true;
}
}
</script>
</head>
<body>
<input type="text" id="pwd" onkeyup="Disabled_change(document.getElementById('pwd').value)">
<button id="clickme" disabled>Click</button>
</body>
</html>
03.03.2022
Интерактив с помощью JavaScript/CSS
Рисование мышкой
example.html
<html>
<h1>
Drawing with mouse events
</h1>
<canvas id="myPics" width="560" height="360"></canvas>
<script src="script.js"></script>
</html>
style.css
canvas {
border: 1px solid black;
width: 560px;
height: 360px;
}
script.js
// When true, moving the mouse draws on the canvas
let isDrawing = false;
let x = 0;
let y = 0;
const myPics = document.getElementById('myPics');
const context = myPics.getContext('2d');
// event.offsetX, event.offsetY gives the (x,y) offset from the edge of the canvas.
// Add the event listeners for mousedown, mousemove, and mouseup
myPics.addEventListener('mousedown', e => {
x = e.offsetX;
y = e.offsetY;
isDrawing = true;
});
myPics.addEventListener('mousemove', e => {
if (isDrawing === true) {
drawLine(context, x, y, e.offsetX, e.offsetY)т;
x = e.offsetX;
y = e.offsetY;
}
});
window.addEventListener('mouseup', e => {
if (isDrawing === true) {
drawLine(context, x, y, e.offsetX, e.offsetY);
x = 0;
y = 0;
isDrawing = false;
}
});
function drawLine(context, x1, y1, x2, y2) {
context.beginPath();
context.strokeStyle = 'black';
context.lineWidth = 1;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
}
Крестики-нолики
tic-tac-toe.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<section>
<h1 class="game--title">Tic Tac Toe</h1>
<div class="game--container">
<div data-cell-index="0" class="cell"></div>
<div data-cell-index="1" class="cell"></div>
<div data-cell-index="2" class="cell"></div>
<div data-cell-index="3" class="cell"></div>
<div data-cell-index="4" class="cell"></div>
<div data-cell-index="5" class="cell"></div>
<div data-cell-index="6" class="cell"></div>
<div data-cell-index="7" class="cell"></div>
<div data-cell-index="8" class="cell"></div>
</div>
<h2 class="game--status"></h2>
<button class="game--restart">Restart Game</button>
</section>
<script src="script.js"></script>
</body>
</html>
style.css
body {
font-family: "Arial", sans-serif;
}
section {
text-align: center;
}
.game--container {
display: grid;
grid-template-columns: repeat(3, auto);
width: 306px;
margin: 50px auto;
}
.cell {
font-family: "Permanent Marker", cursive;
width: 100px;
height: 100px;
box-shadow: 0 0 0 1px #333333;
border: 1px solid #333333;
cursor: pointer;
line-height: 100px;
font-size: 60px;
}
script.js
/*
We store our game status element here to allow us to more easily
use it later on
*/
const statusDisplay = document.querySelector('.game--status');
/*
Here we declare some variables that we will use to track the
game state throught the game.
*/
/*
We will use gameActive to pause the game in case of an end scenario
*/
let gameActive = true;
/*
We will store our current player here, so we know whos turn
*/
let currentPlayer = "X";
/*
We will store our current game state here, the form of empty strings in an array
will allow us to easily track played cells and validate the game state later on
*/
let gameState = ["", "", "", "", "", "", "", "", ""];
/*
Here we have declared some messages we will display to the user during the game.
Since we have some dynamic factors in those messages, namely the current player,
we have declared them as functions, so that the actual message gets created with
current data every time we need it.
*/
const winningMessage = () => `Player ${currentPlayer} has won!`;
const drawMessage = () => `Game ended in a draw!`;
const currentPlayerTurn = () => `It's ${currentPlayer}'s turn`;
/*
We set the inital message to let the players know whose turn it is
*/
statusDisplay.innerHTML = currentPlayerTurn();
function handleCellPlayed(clickedCell, clickedCellIndex) {
/*
We update our internal game state to reflect the played move,
as well as update the user interface to reflect the played move
*/
gameState[clickedCellIndex] = currentPlayer;
clickedCell.innerHTML = currentPlayer;
}
function handlePlayerChange() {
currentPlayer = currentPlayer === "X" ? "O" : "X";
statusDisplay.innerHTML = currentPlayerTurn();
}
const winningConditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
function handleResultValidation() {
let roundWon = false;
for (let i = 0; i <= 7; i++) {
const winCondition = winningConditions[i];
let a = gameState[winCondition[0]];
let b = gameState[winCondition[1]];
let c = gameState[winCondition[2]];
if (a === '' || b === '' || c === '') {
continue;
}
if (a === b && b === c) {
roundWon = true;
break
}
}
if (roundWon) {
statusDisplay.innerHTML = winningMessage();
gameActive = false;
return;
}
/*
We will check weather there are any values in our game state array
that are still not populated with a player sign
*/
let roundDraw = !gameState.includes("");
if (roundDraw) {
statusDisplay.innerHTML = drawMessage();
gameActive = false;
return;
}
/*
If we get to here we know that the no one won the game yet,
and that there are still moves to be played, so we continue by changing the current player.
*/
handlePlayerChange();
}
function handleCellClick(clickedCellEvent) {
/*
We will save the clicked html element in a variable for easier further use
*/
const clickedCell = clickedCellEvent.target;
/*
Here we will grab the 'data-cell-index' attribute from the clicked cell to identify where that cell is in our grid.
Please note that the getAttribute will return a string value. Since we need an actual number we will parse it to an
integer(number)
*/
const clickedCellIndex = parseInt(
clickedCell.getAttribute('data-cell-index')
);
/*
Next up we need to check whether the call has already been played,
or if the game is paused. If either of those is true we will simply ignore the click.
*/
if (gameState[clickedCellIndex] !== "" || !gameActive) {
return;
}
/*
If everything if in order we will proceed with the game flow
*/
handleCellPlayed(clickedCell, clickedCellIndex);
handleResultValidation();
}
function handleRestartGame() {
gameActive = true;
currentPlayer = "X";
gameState = ["", "", "", "", "", "", "", "", ""];
statusDisplay.innerHTML = currentPlayerTurn();
document.querySelectorAll('.cell')
.forEach(cell => cell.innerHTML = "");
}
/*
And finally we add our event listeners to the actual game cells, as well as our
restart button
*/
document.querySelectorAll('.cell').forEach(cell => cell.addEventListener('click', handleCellClick));
document.querySelector('.game--restart').addEventListener('click', handleRestartGame);