diff --git a/Web Development/Basic/Sudoku Game/README.md b/Web Development/Basic/Sudoku Game/README.md new file mode 100644 index 000000000..497da93b0 --- /dev/null +++ b/Web Development/Basic/Sudoku Game/README.md @@ -0,0 +1,19 @@ +# Sudoku Game Website + +This is a Sudoku game website created using HTML, CSS, and JavaScript. + +## Overview + +Sudoku is a popular puzzle game that requires logic and critical thinking skills. This website provides a platform for users to play Sudoku online for free. + +## Features + +- Sudoku board generator that creates a unique puzzle every time +- Ability to input numbers and track progress +- Timer to track how long it takes to complete the puzzle +- Check functionality to see if current solution is correct +- Reset button to clear the board and start over + +## Usage + +To play the game, simply visit the website https://hypertext-workaholics.github.io/sudoku-game/ and click on the "New Game" button to generate a new Sudoku puzzle. Use the number buttons on the bottom side of the board to input your guesses. \ No newline at end of file diff --git a/Web Development/Basic/Sudoku Game/index.html b/Web Development/Basic/Sudoku Game/index.html new file mode 100644 index 000000000..ed7fd4339 --- /dev/null +++ b/Web Development/Basic/Sudoku Game/index.html @@ -0,0 +1,48 @@ + + + + + + + + + + Sudoku + + +
+

Sudoku

+
+

Mistakes : 0

+

+ 00 : 00 +

+
+
+
+
+ + + + + + + + +
+
+
Sudoku
+
+ + + + + + +
+ + \ No newline at end of file diff --git a/Web Development/Basic/Sudoku Game/script.js b/Web Development/Basic/Sudoku Game/script.js new file mode 100644 index 000000000..a49bd5788 --- /dev/null +++ b/Web Development/Basic/Sudoku Game/script.js @@ -0,0 +1,301 @@ + +import { generateSudoku } from "./sudokuGenerator.js"; +let board; +let ans; +// Globals +let color_box; +let clickFlag = -1; +var boardPosition = { left: 0, top: 0 }; +let mp = {}; +let count = 0; +let prevBox; +let currSelect = -1; +let Interval = undefined; +// +function startTimer(start) { + let minute = document.getElementById("minute"); + let sec = document.getElementById("second"); + Interval = setInterval(() => { + minute.innerText = parseInt(minute.innerText) + (sec.innerText == '59') ? 1 : 0; + sec.innerText = (parseInt(sec.innerText) + 1) % 60; + }, 1000); + +} +function createGrid() { + let grid = document.createElement("div"); + grid.classList.add("sudoku-board"); + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + let div = document.createElement("div"); + div.classList.add("box"); + if (i == 3 || i == 6) + div.classList.add("top"); + if (j == 3 || j == 6) + div.classList.add("left"); + div.id = `${i}-${j}`; + grid.appendChild(div); + } + } + document.getElementById("wrap").appendChild(grid); +} + +const fillGrid = () => { + let i, j; + let boxes = document.querySelectorAll(".box"); + boxes.forEach((box) => { + i = box.id[0]; + j = box.id[2]; + ["pre", "filled", "text-yellow", "purple", "wrong"].forEach((Class) => { + if (box.classList.contains(Class)) + box.classList.remove(Class); + }); + if (board[i][j] != "0") { + box.classList.add("pre"); + box.innerText = board[i][j]; + if (mp[board[i][j]] != undefined) + mp[board[i][j]]++; + else + mp[board[i][j]] = 1; + } else { + box.innerText = ""; + count++; + } + }); + + for (let it = 1; it < 10; it++) { + let numberBtn = document.getElementById(it); + numberBtn.className = ""; + if (mp[it] == 9) + numberBtn.className = "complete"; + } +} + + +function newGame(level) { + color_box = undefined; + clickFlag = -1; + currSelect = -1; + boardPosition = { left: 0, top: 0 }; + let error = document.getElementById("error-count").innerText = 0; + for (var member in mp) delete mp[member]; + count = 0; + prevBox = undefined; + document.getElementById("continue").classList.remove("hide"); + let temp = generateSudoku(level); + board = temp.grid; + ans = temp.solution; + changeWrapper(); + fillGrid(); + + startTimer(); +} + +function changeWrapper() { + let blurElement = document.getElementById("toggle-blur"); + let levelWrapper = document.getElementById("wrapper"); + toggleClass(blurElement, "hide"); + toggleClass(levelWrapper, "after"); + toggleClass(levelWrapper, "before"); +} + +window.onload = function () { + createGrid(); + // This is for the switching of level wrapper on click of ".level" + document.querySelectorAll(".level").forEach((level) => { + level.addEventListener("click", () => { + if (level.id != "continue") { + let minute = document.getElementById("minute").innerText = '00'; + let sec = document.getElementById("second").innerText = '00'; + newGame(level.id); + } + else { + startTimer(); + changeWrapper(); + } + }); + }); + + //newgame event listener + let levelWrapper = document.getElementById("wrapper"); + let blurElement = document.getElementById("toggle-blur"); + document.getElementById("new-game").addEventListener("click", () => { + if (Interval != undefined) + clearInterval(Interval); + changeWrapper(); + }); + + let numbers = document.querySelectorAll("button"); + numbers.forEach((number) => { + number.addEventListener("click", () => { + if (number.id != "moving-box" && number.classList != "level" && number.id != "new-game" && number.classList.contains("complete") == false) { + if (currSelect == number.id) + toggleEachNumber(currSelect, 0); + else if (currSelect != -1) + toggleEachNumber(currSelect, 0); + currSelect = number.id; + toggleEachNumber(currSelect, 1); + // moving the temporary box to number position + Move("moving-box", number.id, number.id); + if (clickFlag == 1) + toggleBG(prevBox, 0); + clickFlag = 0; + prevBox = undefined; + } + }); + }); + + let boxes = document.querySelectorAll(".box"); + boxes.forEach((box) => { + box.addEventListener("click", function (e) { + if (currSelect != -1 && mp[currSelect] < 9) { + if (box.classList.contains("wrong") == true) { + setTimeout( + (box) => { + box.innerText = ""; + box.classList.remove("wrong"); + }, + 10, + box + ); + } + if (box.innerText == "") { + let element = document.getElementById("moving-box"); + toggleClass(element, "transition"); + Move("moving-box", box.id, currSelect); + setTimeout(toggleClass, 500, element, "transition"); + } + } + // + if (box.innerText == "" && clickFlag == 1 && prevBox != undefined) { + toggleBG(prevBox, 0); + clickFlag = -1; + } + if (box.innerText == "" && clickFlag == -1) { + toggleBG(box, 1); + prevBox = box; + clickFlag = 1; + } + // + }); + }); +}; + +// toggle between "transition" +function toggleClass(element, className) { + element.classList.toggle(className); +} +// Return board-wrap coordinates +function getBoardPosition() { + let boardPos = document.getElementById("wrap").getBoundingClientRect(); + boardPosition.left = boardPos.left; + boardPosition.top = boardPos.top; +} + +//to move the temporary button to any specific position by destination id +function Move(sourceID, destID, text) { + getBoardPosition(); + let source = document.getElementById(sourceID); + source.innerText = text; + let dest = document.getElementById(destID); + let destX = dest.getBoundingClientRect().x; + let destY = dest.getBoundingClientRect().y; + source.style.position = "absolute"; + source.classList.remove("hidden"); + if (dest.localName == "button") { + source.style.color = "black"; + source.style.left = `${-boardPosition.left + destX - 3}px`; + source.style.top = `${-boardPosition.top + destY - 3}px`; + } else { + // may + source.style.left = `${-boardPosition.left + parseInt(destX)}px`; + source.style.top = `${-boardPosition.top + parseInt(destY)}px`; + source.style.color = "white"; + dest.innerText = text; + + if (isValid(dest) == false) { + let error = document.getElementById("error-count"); + error.innerText = ++error.innerText; + dest.classList.add("wrong"); + navigator.vibrate(50); + if (error.innerText == "1") { + setTimeout( + (msg) => { + alert(msg); + }, + 1000, + "Click again on RED number to remove it from the Sudoku grid." + ); + } + } else { + count--; + dest.classList.add("filled"); + dest.classList.add("text-yellow"); + if (mp[parseInt(dest.innerText)]) { + mp[parseInt(dest.innerText)]++; + } else { + mp[parseInt(dest.innerText)] = 1; + } + if (mp[parseInt(dest.innerText)] == 9) { + document.getElementById(dest.innerText).classList.add("complete"); + } + } + if (count == 0) { + setTimeout(win, 2000); + } + setTimeout(Move, 600, "moving-box", text, text); + } +} + +function isValid(box) { + let row = box.id[0]; + let col = box.id[2]; + return ans[row][col] == box.innerText; +} + + +function toggleBG(box, flag) { + let row = box.id[0]; + let col = box.id[2]; + let r = parseInt(row / 3) * 3; + let c = parseInt(col / 3) * 3; + for (let i = 0; i < 9; i++) { + let rowBox = document.getElementById(`${row}-${i}`); + let colBox = document.getElementById(`${i}-${col}`); + let DiagBox = document.getElementById( + `${r + parseInt(i / 3)}-${c + parseInt(i % 3)}` + ); + + if (flag) { + if (rowBox.classList.contains("pre") == false) + rowBox.classList.add("purple"); + if (colBox.classList.contains("pre") == false) + colBox.classList.add("purple"); + if (DiagBox.classList.contains("pre") == false) + DiagBox.classList.add("purple"); + } else { + if (rowBox.classList.contains("pre") == false) + rowBox.classList.remove("purple"); + if (colBox.classList.contains("pre") == false) + colBox.classList.remove("purple"); + if (DiagBox.classList.contains("pre") == false) + DiagBox.classList.remove("purple"); + } + } +} +// after completion of sudoku +function win() { + location.reload(); +} + +function toggleEachNumber(num, flag) { + let numberBOx = document.getElementById(num).classList.toggle("clicked"); + let boxes = document.querySelectorAll(".box"); + boxes.forEach((box) => { + if (box.innerText == num) { + if (flag == true) + box.classList.add("text-yellow"); + else + box.classList.remove("text-yellow"); + } + }); +} \ No newline at end of file diff --git a/Web Development/Basic/Sudoku Game/styles.css b/Web Development/Basic/Sudoku Game/styles.css new file mode 100644 index 000000000..df0374ac3 --- /dev/null +++ b/Web Development/Basic/Sudoku Game/styles.css @@ -0,0 +1,305 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + font-weight: bold; + font-size: 1.1rem; + } + :root{ + --text-yellow:#ffdb10; + --border-green:#2fff06; + } + body { + background-color: #031638; + mix-blend-mode: inherit; + background-position: unset; + overflow: hidden; + height: 100vh; + } + .numbers { + display: flex; + width: 350px; + justify-content: center; + margin-top: 450px; + color: black; + } + .numbers > * { + background-color: #3bff13; + width: 33px; + height: 33px; + margin: 3px; + border: none; + border-radius: 3px; + } + .numbers > *:not(#moving-box, .clicked, .complete) { + cursor: pointer !important; + box-shadow: 3px -4px 1px rgb(96, 177, 96) !important; + z-index: 999; + } + + .numbers > *:not(#moving-box):hover { + background-color: #2b9e14; + } + .numbers > *:not(#moving-box):active { + transform: translateY(3px); + } + .sudoku-board { + display: grid; + grid-template-columns: repeat(9, 1fr); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 350px; + height: 350px; + box-shadow: 1px 1px 15px 3px #6e76a7, -1px -1px 15px 3px #6e76a7; + border-radius: 2px; + border: 3px solid var(--border-green); + } + .box { + color: whitesmoke; + background-color: #091e42b6; + display: grid; + place-items: center; + outline: none; + cursor: pointer; + border-right: 1px solid #9bb39650; + border-bottom: 1px solid #9bb39650; + text-shadow: -1px 1px 5px #9bb396e1; + } + .box:hover { + background-color: rgba(119, 113, 150, 0.87); + border-radius: 1px; + text-shadow: -1px 1px 5px #1f201fe1; + } + .pre, + .pre:hover { + background-color: #020016; + color: whitesmoke; + text-shadow: -1px 1px 5px #9bb396e1; + } + .filled, + .filled:hover { + background-color: #6b007ed6;; + text-shadow: -1px 1px 3px #d9d9d9e1; + } + + .wrong { + animation: shake; + animation-timing-function: ease-in-out; + animation-duration: 150ms; + animation-iteration-count: 3; + animation-fill-mode: forwards; + text-shadow: none; + } + @keyframes shake { + 0% { + transform: translateX(0); + } + 50% { + transform: translateX(-3px); + color: rgba(255, 0, 0, 0.767); + } + 75% { + transform: translateX(2px); + } + 100% { + transform: translateX(0); + color: rgba(255, 0, 0, 0.767); + } + } + + .transition { + transition: 0.5s; + border: none; + z-index: 1; + } + + .board-wrap { + z-index: 11; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + #moving-box { + background-color: transparent; + color: whitesmoke; + z-index: 1; + } + .hidden { + display: none; + } + + .left { + border-left: 2px solid var(--border-green); + } + .top { + border-top: 2px solid var(--border-green); + } + .purple { + background-color: rgb(39, 0, 39); + } + + .win-card { + display: none; + background-color: rgba(252, 252, 252, 0.048); + width: 300px; + height: 170px; + position: absolute; + top: 44%; + left: calc(50% - 2px); + transform: translate(-50%, -50%); + backdrop-filter: blur(2px); + border-radius: 10px; + z-index: 999; + } + + .win-card > * { + background-color: transparent; + } + + #greeting-bg { + position: relative; + top: 32%; + left: 15px; + transform: translate(-50%, -50%); + display: inline; + font-size: 2rem; + color: black; + text-shadow: -1px -1px 4px rgb(47, 213, 255), 1px 1px 4px rgb(47, 213, 255); + + font-family: "Permanent Marker", cursive; + } + + .text-yellow, + .text-yellow:hover { + color: var(--text-yellow); + background-color: #00008b; + } + + .clicked:not(.complete), + .clicked:hover:not(.complete) { + box-shadow: 4px 4px 2px #60b160; + transform: translatey(2px); + background-color: #2b9e14; + } + + .error { + width: max-content; + margin: auto; + margin-top: 2rem; + color: var(--text-yellow); + text-align: center; + font-size: 10px; + } + + #heading,#toggle-blur span { + font-size: 2rem; + color: #fff8a0; + text-shadow: 1px -4px 4px black; + margin-top: -1rem; + margin-bottom: 18px; + cursor: pointer; + } + + .wrapper { + display: flex; + flex-direction: column; + width: calc(100vw - 400px); + min-width: 170px; + max-width: 400px; + background-color: rgb(253, 253, 253); + position: absolute; + bottom: 0px; + left: 50%; + transform: translate(-50%); + border: none; + border-radius: 5px; + z-index: 999; + box-sizing: border-box; + } + + .wrapper :first-child { + border-radius: 5px 5px 0 0; + margin-top: 5px; + } + + .wrapper :last-child { + padding-bottom: 10px; + border-radius: 0 0 5px 5px; + } + + .wrapper > * { + background-color: white; + border: none; + cursor: pointer; + padding: 8px 8px; + border-bottom: 1px solid rgba(65, 62, 62, 0.589); + } + + .wrapper :first-child { + border: none; + margin-bottom: 1.1rem; + } + + .before { + bottom: -235px; + } + + .before:hover, + .before:hover > * { + background-color: blue; + color: chartreuse; + } + + .after { + bottom: 50%; + transform: translate(-50%, 50%); + transition: 500ms; + transition-timing-function: ease-in-out; + } + + .after > *:hover, + .after:hover { + background-color: #031638; + width: 100%; + color: whitesmoke; + } + + .after :first-child { + display: none; + } + + .after :nth-child(2), + .after :nth-child(3) { + border-radius: 5px 5px 0 0; + } + + .blur { + display: block; + width: 100vw; + height: 100vh; + position: absolute; + top: 0; + background-color: rgba(87, 81, 81, 0.329); + backdrop-filter: blur(5px); + z-index: 900; + } + + #toggle-blur span{ + margin: auto; + display: block; + width: max-content; + margin-top: 1.5rem; + } + + .hide { + display: none; + } + .sub-heading { + display: flex; + width: 350px; + justify-content: space-around; + } \ No newline at end of file diff --git a/Web Development/Basic/Sudoku Game/sudokuGenerator.js b/Web Development/Basic/Sudoku Game/sudokuGenerator.js new file mode 100644 index 000000000..c7d4c787c --- /dev/null +++ b/Web Development/Basic/Sudoku Game/sudokuGenerator.js @@ -0,0 +1,103 @@ +let solution = Array(9); +const levels = { easy: 25, medium: 45, hard: 60,insane:75}; +let solCount = 0; +let grid; + +//create 9 x 9 empty array +function createArray(){ + for (let i = 0; i < 9; i++) + solution[i] = new Array(9).fill('0'); +} + + +// Using Backtracking leetcode sudoku solver to generate full sudoku grid(Solution) +const fillArray = (i, j) => { + for (; i < 9; i++) { + for (; j < 9; j++) { + if (solution[i][j] == '0') { + for (let k = parseInt(Math.random() * 9) + 1, ct = 0; ct < 9; k = (k == 9) ? 1 : k + 1) { + ct++; + if (isValid(i, j, k.toString(), solution)) { + solution[i][j] = k.toString(); + if (fillArray(i, j)) + return true; + solution[i][j] = '0'; + } + } + return false; + } + } + j = 0; + } + return true; +} + +// isValid checks that the placed number in board is violating sudoku rules or not +const isValid = (i, j, k, board) => { + let row = parseInt(i / 3) * 3; + let col = parseInt(j / 3) * 3; + for (let it = 0; it < 9; it++) { + if (board[i][it] == k) + return false; + if (board[it][j] == k) + return false; + if (board[row + parseInt(it / 3)][col + parseInt(it % 3)] == k) + return false; + } + return true; +} + +//removeCells after generating sudoku it removes some pre fetched cells number according to level +function removeCells(k) { + grid = JSON.parse(JSON.stringify(solution)); + let ct = 1; + let r = 40; + while (k && r) { + let row = parseInt(Math.random() * 9); + let col = parseInt(Math.random() * 9); + let removedNum = grid[row][col]; + grid[row][col] = '0'; + if (solve(0, 0, ct) == 1) { + k--; + ct++; + } else { + r--; + grid[row][col] = removedNum; + } + } +} + +// solve -> after removing a cell number it counts number of solutions +// if there are more than 1 solution than removed number will be placed again on it's original place +// if there a only one solution than next random position cell will be removed +const solve = (i, j, Count) => { + if (Count == 0) return 1; + let solCount = 0; + for (; i < 9; i++) { + for (; j < 9; j++) { + if (grid[i][j] == '0') { + for (let k = 1; k <= 9; k++) { + if (isValid(i, j, k.toString(), grid)) { + grid[i][j] = k.toString(); + solCount += solve(i, j, Count - 1); + grid[i][j] = '0'; + } + if (solCount > 1) + return solCount; + } + return solCount; + } + } + j = 0; + } + return solCount; +} + +export const generateSudoku = (level) => { + solution = Array(9); + grid = undefined; + createArray(); + fillArray(0, 0); + removeCells(levels[level]); + return { solution, grid }; +}; \ No newline at end of file