Last active
September 24, 2021 02:19
-
-
Save ThePotatoChronicler/bc2b6f694c6e518ce434feefad5e4cef to your computer and use it in GitHub Desktop.
A TicTacToe program I've made for a school assignment
This file contains hidden or 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> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>TicTacToe</title> | |
| <style> | |
| html, body { | |
| height: 100%; | |
| margin: 0; | |
| } | |
| body { | |
| background-color: #333333; | |
| transition: background 1s ease-in-out | |
| } | |
| body.circle-turn { | |
| background: rgb(0,0,127) | |
| } | |
| body.cross-turn { | |
| background: rgb(127,0,0); | |
| } | |
| #selection-holder { | |
| z-index: 1; | |
| position: absolute; | |
| height: 100%; | |
| width: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| #end-holder { | |
| z-index: 1; | |
| position: absolute; | |
| height: 100%; | |
| width: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| flex-direction: column; | |
| animation: end-anim 2s ease-out; | |
| } | |
| #end-text { | |
| font-size: 2rem; | |
| } | |
| #end-circle { | |
| height: 50%; | |
| width: 50%; | |
| filter: drop-shadow(0 0 30vmax black); | |
| } | |
| #end-circle > circle.circle { | |
| animation-duration: 4s; | |
| } | |
| #end-cross { | |
| height: 50%; | |
| width: 50%; | |
| filter: drop-shadow(0 0 30vmax black); | |
| } | |
| #end-cross > line.crossline1 { | |
| animation-duration: 2s; | |
| } | |
| #end-cross > line.crossline2 { | |
| animation-delay: 2s; | |
| animation-duration: 2s; | |
| } | |
| @keyframes end-anim { | |
| from { | |
| bottom: 100%; | |
| } | |
| to { | |
| bottom: 0%; | |
| } | |
| } | |
| #author { | |
| position: absolute; | |
| bottom: 0; | |
| } | |
| #board-selection { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| #board-holder { | |
| height: 100%; | |
| width: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| transition: filter 4s; | |
| } | |
| #board-holder.blur-out { | |
| filter: blur(4px); | |
| } | |
| #board { | |
| background-color: #888888; | |
| border-color: #444444; | |
| border-left-width: 10px; | |
| border-right-width: 10px; | |
| border-left-style: solid; | |
| border-right-style: solid; | |
| height: 100%; | |
| width: 100vh; | |
| display: grid; | |
| grid-gap: 3px; | |
| } | |
| #board > div { | |
| background-color: #666666; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| overflow: hidden; | |
| } | |
| #board > div > svg { | |
| margin: 5%; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| circle.circle { | |
| stroke-dasharray: 236; | |
| stroke-dashoffset: 236; | |
| animation: dashtozero 1s ease-in-out; | |
| animation-fill-mode: forwards; | |
| } | |
| line.crossline1 { | |
| stroke-dasharray: 100; | |
| stroke-dashoffset: 100; | |
| animation: dashtozero 0.5s ease-in-out 0.5s; | |
| animation-fill-mode: forwards; | |
| } | |
| line.crossline2 { | |
| stroke-dasharray: 100; | |
| stroke-dashoffset: 100; | |
| animation: dashtozero 0.5s ease-in-out; | |
| animation-fill-mode: forwards; | |
| } | |
| @keyframes dashtozero { | |
| to { | |
| stroke-dashoffset: 0; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <svg width="100" height="100" viewbox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" | |
| id="circle-orig" style="visibility: hidden; display: none;"> | |
| <circle class="circle" cx="50" cy="50" r="37.5" stroke="#0000FF" stroke-width="25"/> | |
| </svg> | |
| <svg width="86" height="86" viewbox="0 0 86 86" fill="none" xmlns="http://www.w3.org/2000/svg" | |
| id="cross-orig" style="visibility: hidden; display: none;"> | |
| <line class="crossline1" x1="78.2843" y1="7.57359" x2="7.57359" y2="78.2843" stroke="#FF0000" stroke-width="20"/> | |
| <line class="crossline2" x1="7.07107" y1="7.57359" x2="77.7817" y2="78.2843" stroke="#FF0000" stroke-width="20"/> | |
| </svg> | |
| <div id="selection-holder"> | |
| <div id="board-selection"> | |
| <p class="selection-text" style="margin: 0;">Select board size</p> | |
| <div style="display: flex; justify-content: center; align-items: center;"> | |
| <input type="range" id="board-range" value="3" min="3" max="10" style="margin-right: 10px;"/> | |
| <output>3</output> | |
| </div> | |
| <p class="selection-text" style="margin-bottom: 0;">Select win length</p> | |
| <div style="display: flex; justify-content: center; align-items: center;"> | |
| <input type="range" id="win-range" value="3" min="3" max="10" style="margin-right: 10px;"/> | |
| <output>3</output> | |
| </div> | |
| <input type="button" id="start-button" value="Start" /> | |
| </div> | |
| <div id="author"> | |
| <p>Author: Potato Chronicler</p> | |
| </div> | |
| </div> | |
| <div id="end-holder" style="display: none; visibility: hidden;"> | |
| <svg id="end-circle" width="100" height="100" viewbox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <circle class="circle" cx="50" cy="50" r="37.5" stroke="#0000FF" stroke-width="25"/> | |
| </svg> | |
| <svg id="end-cross" width="86" height="86" viewbox="0 0 86 86" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <line class="crossline1" x1="78.2843" y1="7.57359" x2="7.57359" y2="78.2843" stroke="#FF0000" stroke-width="20"/> | |
| <line class="crossline2" x1="7.07107" y1="7.57359" x2="77.7817" y2="78.2843" stroke="#FF0000" stroke-width="20"/> | |
| </svg> | |
| <p id="end-text"></p> | |
| <input type="button" value="Restart" style="font-size: 1.5rem; padding: 0.5rem;" onclick="location.reload()" /> | |
| </div> | |
| <div id="board-holder" class="blur-out"> | |
| <div id="board"> | |
| </div> | |
| </div> | |
| </body> | |
| <script> | |
| let turn = false | |
| const board = document.getElementById("board") | |
| const board_holder = document.getElementById("board-holder") | |
| const selection_holder = document.getElementById("selection-holder") | |
| const end_holder = document.getElementById("end-holder") | |
| const board_range = document.getElementById("board-range") | |
| const win_range = document.getElementById("win-range") | |
| const start_button = document.getElementById("start-button") | |
| const circle_template = (function () { | |
| const orig = document.getElementById("circle-orig") | |
| const e = orig.cloneNode(true) | |
| orig.remove() | |
| e.setAttribute("id", "") | |
| e.dataset.symbol = "o" | |
| e.style.visibility = "" | |
| e.style.display = "" | |
| return e | |
| })() | |
| const cross_template = (function () { | |
| const orig = document.getElementById("cross-orig") | |
| const e = orig.cloneNode(true) | |
| orig.remove() | |
| e.setAttribute("id", "") | |
| e.dataset.symbol = "x" | |
| e.style.visibility = "" | |
| e.style.display = "" | |
| return e | |
| })() | |
| board_range.addEventListener("input", function (e) { | |
| win_range.setAttribute("max", board_range.valueAsNumber) | |
| board_range.nextElementSibling.value = board_range.value | |
| win_range.nextElementSibling.value = win_range.value | |
| while (board.children[0]) board.removeChild(board.children[0]) | |
| const bsize = board_range.valueAsNumber | |
| for (let i = 0; i < (bsize * bsize); i++) { | |
| const e = document.createElement("div") | |
| e.addEventListener("click", function (e) { | |
| if (e.target.children.length > 0) { | |
| return | |
| } | |
| const bsize = board_range.valueAsNumber | |
| const wlen = win_range.valueAsNumber | |
| e.target.appendChild(turn ? circle_template.cloneNode(true) : cross_template.cloneNode(true)) | |
| let winner = false | |
| win_found: { | |
| // Creates a helpful board to quickly | |
| // check which tiles are the player's that just played | |
| const bvarr = (() => { | |
| const arr = [] | |
| for (let row = 0; row < bsize; row++) { | |
| const inarr = [] | |
| for (let col = 0; col < bsize; col++) { | |
| const c = board.children[row + bsize * col] | |
| if (c.firstChild == null) { | |
| inarr.push(false) | |
| continue | |
| } | |
| inarr.push(turn ? | |
| c.firstChild.dataset.symbol == "o" : | |
| c.firstChild.dataset.symbol == "x") | |
| } | |
| arr.push(inarr) | |
| } | |
| return arr | |
| })() | |
| const barr = (() => { | |
| const arr = [] | |
| for (let col = 0; col < bsize; col++) { | |
| const inarr = [] | |
| for (let row = 0; row < bsize; row++) { | |
| const c = board.children[row + bsize * col] | |
| if (c.firstChild == null) { | |
| inarr.push(false) | |
| continue | |
| } | |
| inarr.push(turn ? | |
| c.firstChild.dataset.symbol == "o" : | |
| c.firstChild.dataset.symbol == "x") | |
| } | |
| arr.push(inarr) | |
| } | |
| return arr | |
| })() | |
| // Checks if wlen true values are consecutively in array a | |
| function winc(a) { | |
| let i = 0 | |
| let c = 0 | |
| for (const v of a) { | |
| if (v) { | |
| c++; | |
| } else { | |
| c = 0 | |
| } | |
| if (c == wlen) return true | |
| } | |
| return false | |
| } | |
| // Test vertical | |
| for (let col = 0; col < bsize; col++) { | |
| for (let row = 0; row <= ((bsize - wlen)); row++) { | |
| if(winc(bvarr[col].slice(row, row + wlen))) { | |
| winner = true | |
| break win_found | |
| } | |
| } | |
| } | |
| // Test horizontal | |
| for (let row = 0; row < bsize; row++) { | |
| for (let col = 0; col <= (bsize - wlen); col++) { | |
| if(winc(barr[row].slice(col, col + wlen))) { | |
| winner = true | |
| break win_found | |
| } | |
| } | |
| } | |
| // Test diagonal (left bottom to top right) | |
| { | |
| let row = wlen - 1 | |
| let over = 1 | |
| for (;row >= (wlen - 1); row += over) { | |
| if (over == 1) { | |
| const arr = [] | |
| for (let col = row; col >= 0; col--) { | |
| arr.push(bvarr[col][row-col]) | |
| } | |
| if (winc(arr)) { | |
| winner = true | |
| break win_found | |
| } | |
| } else { | |
| const arr = [] | |
| for (let col = row; col >= 0; col--) { | |
| arr.push(bvarr[(bsize-1)-col][(bsize-1)-(row-col)]) | |
| } | |
| if (winc(arr)) { | |
| winner = true | |
| break win_found | |
| } | |
| } | |
| if ((row + 2) > bsize) over = -over | |
| } | |
| } | |
| // Test diagonal (left bottom to top right) | |
| { | |
| let row = wlen - 1 | |
| let over = 1 | |
| for (;row >= (wlen - 1); row += over) { | |
| if (over == 1) { | |
| const arr = [] | |
| for (let col = row; col >= 0; col--) { | |
| arr.push(barr[(bsize-1)-(row-col)][col]) | |
| } | |
| if (winc(arr)) { | |
| winner = true | |
| break win_found | |
| } | |
| } else { | |
| const arr = [] | |
| for (let col = row; col >= 0; col--) { | |
| arr.push(bvarr[(bsize-1)-(row-col)][col]) | |
| } | |
| if (winc(arr)) { | |
| winner = true | |
| break win_found | |
| } | |
| } | |
| if ((row + 2) > bsize) over = -over | |
| } | |
| } | |
| } | |
| let tie | |
| { | |
| let i = 0 | |
| for (;i < board.children.length; i++) { | |
| if (board.children[i].children.length == 0) break; | |
| } | |
| tie = (i == board.children.length) | |
| } | |
| if (tie || winner) { | |
| board_holder.classList.add("blur-out") | |
| end_holder.style.visibility = "" | |
| end_holder.style.display = "" | |
| } | |
| if (winner) { | |
| document.getElementById("end-text").textContent = (turn ? "Circle" : "Cross") + " is the winner!" | |
| if (turn) { | |
| document.getElementById("end-cross").remove() | |
| } else { | |
| document.getElementById("end-circle").remove() | |
| } | |
| } else if (tie) { | |
| const text = document.getElementById("end-text") | |
| text.textContent = "A tie!" | |
| text.style.marginTop = "0" | |
| document.getElementById("end-cross").remove() | |
| document.getElementById("end-circle").remove() | |
| } | |
| if (tie || winner) return | |
| if (tie) { | |
| document.body.classList.remove("circle-turn") | |
| document.body.classList.remove("cross-turn") | |
| } else { | |
| if (turn) { | |
| document.body.classList.remove("circle-turn") | |
| document.body.classList.add("cross-turn") | |
| } else { | |
| document.body.classList.remove("cross-turn") | |
| document.body.classList.add("circle-turn") | |
| } | |
| } | |
| turn = !turn | |
| }) | |
| board.appendChild(e) | |
| board.style.gridTemplateColumns = `repeat(${bsize}, 1fr)` | |
| board.style.gridTemplateRows = `repeat(${bsize}, 1fr)` | |
| } | |
| }) | |
| win_range.addEventListener("input", function (e) { | |
| win_range.nextElementSibling.value = win_range.value | |
| }) | |
| board_range.dispatchEvent(new Event("input")) | |
| start_button.addEventListener("click", function (e) { | |
| turn = false | |
| selection_holder.style.visibility = "hidden" | |
| board_holder.classList.remove("blur-out") | |
| document.body.classList.add("cross-turn") | |
| }) | |
| </script> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment