From 06f962066c57818232cc8d59ef49e879043418df Mon Sep 17 00:00:00 2001 From: Kyle Gunger Date: Thu, 27 Feb 2020 15:55:51 -0500 Subject: Add mines to the page --- Mines/index.html | 64 ++++++++ Mines/mines.css | 176 ++++++++++++++++++++ Mines/mines.js | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 730 insertions(+) create mode 100644 Mines/index.html create mode 100644 Mines/mines.css create mode 100644 Mines/mines.js diff --git a/Mines/index.html b/Mines/index.html new file mode 100644 index 0000000..5b27806 --- /dev/null +++ b/Mines/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ + +
+ +
+ | +
+
+
+

0

0

+
+ + +
+
+ + + \ No newline at end of file diff --git a/Mines/mines.css b/Mines/mines.css new file mode 100644 index 0000000..810e74e --- /dev/null +++ b/Mines/mines.css @@ -0,0 +1,176 @@ +*{ + font-family: 'Open Sans', sans-serif; +} + +body{ + text-align: center; +} + +div.selector, div.game{ + display: inline-block; +} + +div.selector{ + text-align: right; +} + +div.game{ + min-width: 50%; + margin: 50px; +} + +input, select, label[for], button{ + padding: 5px; + margin: 5px; + background-color: #ddd; + border: none; + border-radius: 5px; + transition-duration: 0.2s; + box-sizing: border-box; + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + font-size: medium; +} + +input:hover, select:hover, label[for]:hover, button:hover{ + background-color: #bbb; +} + +input:focus, select:focus, label[for]:focus, button:focus{ + background-color: #bdf; + border: none; +} + +input:active, select:active, label[for]:active, button:active{ + background-color: #bdf; + border: none; +} + +input[type="file"]{ + width: 0; + height: 0; + + padding: 0; + margin: 0; + + border-radius: 0; + opacity: 0; +} + +h1{ + width: calc(50% - 70px); + + display: inline; + + background-color: #ddd; + + border-radius: 50px; +} + +h1#mines{ + float: left; +} + +h1#time{ + float: right; +} + +/* Table */ + +table{ + display: inline-table; + background-color: #ddd; + border-radius: 10px; + padding: 5px; +} + +td{ + width: 45px; + height: 45px; + + margin: 5px; + padding: 2px; + + background-color: #aaa; + + box-sizing: border-box; + + border-radius: 25%; + + transition-duration: 0.2s; + + cursor: pointer; +} + +td.wt:hover{ + background-color: #ccc; +} + +td.wt:active{ + background-color: #999; +} + +td.chkd{ + background-color: #777; + cursor: default; +} + +/* Main button */ + +button#circle{ + padding: 35px; + border: none; + border-radius: 50%; + box-sizing: border-box; + cursor: pointer; +} + +button#circle:active{ + border: none; +} + +button#circle.win{ + background-color: #0f4; +} + +button#circle.win:hover{ + background-color: #0d2; +} + +button#circle.lose{ + background-color: #f33; +} + +button#circle.lose:hover{ + background-color: #d11; +} + +button#circle.ingame{ + background-color: #fa0; + animation: 1s infinite alternate shrink; +} + +button#circle.ingame:hover{ + background-color: #d80; +} + +button#circle.loading{ + background-color: #0af; + animation: 1s infinite alternate shrink; +} + +button#circle.loading:hover{ + background-color: #08d; +} + +@keyframes shrink{ + from{ + padding: 35px; + margin-top: 0px; + } + to{ + padding: 15px; + margin-top: 20px + } +} \ No newline at end of file diff --git a/Mines/mines.js b/Mines/mines.js new file mode 100644 index 0000000..5585bb6 --- /dev/null +++ b/Mines/mines.js @@ -0,0 +1,490 @@ +var colors = ["blue", "limegreen", "orange", "red", "purple", "cyan", "gold", "black"]; + +var mine = "\u25C9"; + +function Board(){ + table.parentElement.addEventListener("contextmenu", function(e){e.preventDefault();}); + + this.mines = [[0]]; + this.mTotal = 0; + this.mChecked = 0; + + this.boardDim = [1, 1]; + this.checked = 0; + + this.time = 0; + this.clock = -1; + + this.running = false; + this.started = false; + +} + +Board.prototype = { + + //Second + + sec: function(){ + this.time++; + sTime.textContent = this.time; + }, + + //Game states + + win: function(){ + if(!this.running) return; + this.running = false; + clearInterval(this.clock); + this.clock = -1; + circle.className = "win"; + + this.revealMines("#08d"); + }, + + lose: function(x, y){ + if(!this.running) return; + this.running = false; + clearInterval(this.clock); + this.clock = -1; + circle.className = "lose"; + + this.revealMines("black"); + + this.getMineEl(x, y).style.color = "red"; + }, + + revealMines: function(color){ + let lp = this.mines.length; + for(let i = 0; i < lp; i++){ + let lp2 = this.mines[i].length; + for(let j = 0; j < lp2; j++){ + let e = this.getMineEl(this.mines[i][j], i); + e.style.color = color; + e.textContent = mine; + } + } + }, + + closeToStart: function(x, y, sx, sy){ + let length = Math.sqrt((x-sx)*(x-sx) + (y-sy)*(y-sy)); + if(length < 1.5) return true; + return false; + }, + + replaceMine: function(x, y, sx, sy){ + let flag = false; + if(!this.isMine(x, y)) return; + while(!flag){ + for(let i = 0; i < this.boardDim[1] && !flag; i++){ + for(let j = 0; j < this.boardDim[0] && !flag; j++){ + if(!this.isMine(j, i) && !this.closeToStart(j, i, sx, sy) && Math.floor(Math.random()*4) == 0){ + this.mines[i].push(j); + flag = true; + } + } + } + } + this.mines[y].splice(this.mines[y].indexOf(x), 1); + }, + + start: function(x, y){ + this.started = true; + + let dx = x; + this.replaceMine(dx, y, x, y); + this.replaceMine(dx, y+1, x, y); + this.replaceMine(dx, y-1, x, y); + dx++; + this.replaceMine(dx, y, x, y); + this.replaceMine(dx, y+1, x, y); + this.replaceMine(dx, y-1, x, y); + dx -= 2; + this.replaceMine(dx, y, x, y); + this.replaceMine(dx, y+1, x, y); + this.replaceMine(dx, y-1, x, y); + + this.clock = setInterval(this.sec.bind(this), 1000); + }, + + //Event managers + + click: function(e){ + let el = e.target; + var x = parseInt(el.getAttribute("x")), y = parseInt(el.getAttribute("y")); + + if(!this.running || el.textContent !== " " || el.classList.contains("chkd")) return; + + if(!this.started){ + this.start(x, y); + } + + if(this.isMine(x, y)){ + this.lose(x, y); + return; + } + + this.check(x, y); + }, + + ctxMenu: function(e){ + let el = e.target; + var x = parseInt(el.getAttribute("x")), y = parseInt(el.getAttribute("y")); + + if(el.classList.contains("chkd") || !this.running || !this.started) { + e.preventDefault(); + return; + } + + if(el.textContent == "!"){ + + el.textContent = "?"; + this.mChecked--; + }else if(el.textContent == "?"){ + + el.textContent = " "; + }else{ + + el.textContent = "!"; + this.mChecked++; + } + sMines.textContent = this.mTotal - this.mChecked; + e.preventDefault(); + }, + + //Recognizing the click + + isMine: function(x, y){ + if(y >= this.mines.length || y < 0) return false; + return this.mines[y].includes(x); + }, + + isChecked: function(x, y){ + if(y >= this.boardDim[1] || y < 0) return true; + if(x >= this.boardDim[0] || x < 0) return true; + return table.children[y].children[x].className == "chkd"; + }, + + numAround: function(x, y){ + let m = 0;let dx = x; + if(this.isMine(dx, y+1)) m++; + if(this.isMine(dx, y-1)) m++; + dx = x+1; + if(this.isMine(dx, y)) m++; + if(this.isMine(dx, y+1)) m++; + if(this.isMine(dx, y-1)) m++; + dx = x-1; + if(this.isMine(dx, y)) m++; + if(this.isMine(dx, y+1)) m++; + if(this.isMine(dx, y-1)) m++; + return m; + }, + + getMineEl: function(x, y){ + return table.children[y].children[x]; + }, + + check: function(x, y){ + if(!this.running) return; + + let n = this.numAround(x, y); + let e = this.getMineEl(x, y); + + if(e.textContent == "!") return; + + e.className = "chkd"; + + if(n !== 0){ + + e.style.color = colors[n-1]; + e.textContent = n; + }else{ + + this.findPath(x, y); + } + + this.checked++; + if(this.checked == (this.boardDim[0]*this.boardDim[1] - this.mTotal)) this.win(); + }, + + findPath: function(x, y){ + let dx = x; + if(!this.isChecked(dx, y+1)) this.check(dx, y+1); + if(!this.isChecked(dx, y-1)) this.check(dx, y-1); + dx = x+1; + if(!this.isChecked(dx, y+1)) this.check(dx, y+1); + if(!this.isChecked(dx, y-1)) this.check(dx, y-1); + if(!this.isChecked(dx, y)) this.check(dx, y); + dx = x-1; + if(!this.isChecked(dx, y+1)) this.check(dx, y+1); + if(!this.isChecked(dx, y-1)) this.check(dx, y-1); + if(!this.isChecked(dx, y)) this.check(dx, y); + }, + + //Managing the board + + reset: function(){ + setTimeout(this.loading(), 10); + setTimeout(this.setup.bind(this), 100); + }, + + loading: function(){ + circle.className = "loading"; + }, + + setup: function(){ + let x = xIn.value; + let y = yIn.value; + + let mines = mIn.value; + + if(parseFloat(dIn.value) !== 0) mines = Math.round(x*y*parseFloat(dIn.value)); + + if(x <= 3 && y <= 3){ + circle.className = "lose"; + alert("Board size too small!"); + return; + }else if(mines > x*y-9) { + circle.className = "lose"; + alert("Too many mines selected!"); + return; + } + + this.started = false; + + this.boardDim = [x, y]; + + while(table.firstChild){ + table.firstChild.remove(); + } + + this.mTotal = mines; + this.mChecked = 0; + this.mines = []; + + this.checked = 0; + + sMines.textContent = mines; + + this.time = 0; + sTime.textContent = this.time; + if(this.clock !== -1) clearInterval(this.clock); + this.clock = -1; + + let mRarity = x*y*0.8; + + for(let i = 0; i < y; i++){ + + let row = document.createElement("tr"); + + this.mines.push([]); + + for(let j = 0; j < x; j++){ + + let cell = document.createElement("td"); + cell.textContent = " "; + cell.setAttribute("x", j); + cell.setAttribute("y", i); + cell.classList = ["wt"]; + cell.addEventListener("click", this.click.bind(this)); + cell.addEventListener("contextmenu", this.ctxMenu.bind(this)); + row.appendChild(cell); + + if(mines > 0 && Math.floor(Math.random()*mRarity) == 0){ + this.mines[i].push(j); + mines--; + } + } + + table.appendChild(row); + } + + while(mines > 0){ + for(let i = 0; i < y; i++){ + if(mines <= 0) break; + for(let j = 0; j < x; j++){ + if(mines <= 0) break; + if(!this.isMine(j, i) && Math.floor(Math.random()*mRarity) == 0){ + this.mines[i].push(j); + mines--; + } + } + } + } + + circle.className = "ingame"; + this.running = true; + }, + + // Loading a previously saved game + + getSaveData: function(){ + var data = {}; + + data.mines = this.mines; + data.mTotal = this.mTotal; + data.checked = this.checked; + data.time = this.time; + + var boardState = []; + + for(var i = 0; i < table.children.length; i++){ + boardState.push([]); + let row = table.children[i]; + for(var j = 0; j < row.children.length; j++){ + if(row.children[j].textContent == " "){ + if(row.children[j].classList.contains("chkd")) boardState[i].push("0"); + else boardState[i].push(" "); + }else boardState[i].push(row.children[j].textContent); + } + } + + data.boardState = boardState; + + return data; + }, + + loadSaveData: function(e){ + if(this.clock !== -1) clearInterval(this.clock); + this.clock = -1; + + var data; + + try{ + data = JSON.parse(e.target.result); + }catch(err){ + this.loadError(err); + return; + } + + while(table.firstChild){ + table.firstChild.remove(); + } + + this.mines = data.mines; + this.mTotal = data.mTotal; + this.mChecked = 0; + + this.boardDim = [data.boardState[0].length, data.boardState.length]; + this.checked = data.checked; + + this.time = data.time; + + var finFlag = false; + + for(let i = 0; i < data.boardState.length; i++){ + + let row = document.createElement("tr"); + + this.mines.push([]); + + for(let j = 0; j < data.boardState[0].length; j++){ + + let cell = document.createElement("td"); + cell.classList = ["wt"]; + + switch(data.boardState[i][j]){ + case mine: + finFlag = true; + case "!": + this.mChecked++; + default: + cell.textContent = data.boardState[i][j]; + let n = parseInt(data.boardState[i][j]); + if(!isNaN(n)){ + cell.style.color = colors[n-1]; + cell.className = "chkd"; + } + break; + + case "0": + cell.className = "chkd"; + cell.textContent = " "; + break; + } + + cell.setAttribute("x", j); + cell.setAttribute("y", i); + + cell.addEventListener("click", this.click.bind(this)); + cell.addEventListener("contextmenu", this.ctxMenu.bind(this)); + row.appendChild(cell); + + if(mines > 0 && Math.floor(Math.random()*mRarity) == 0){ + this.mines[i].push(j); + mines--; + } + } + + table.appendChild(row); + } + + sMines.textContent = this.mTotal - this.mChecked; + sTime.textContent = this.time; + + this.clock = setInterval(this.sec.bind(this), 1000); + + circle.className = "ingame"; + + this.running = true; + this.started = true; + + if(finFlag){ + if(this.checked == (this.boardDim[0]*this.boardDim[1] - this.mTotal)) this.win(); + else this.lose(); + } + }, + + loadError: function(err){ + alert("Error loading previously saved game!\nCheck the console for more info."); + throw err; + }, + + save: function(){ + if(!this.started){ + alert("Start a game to save it!"); + return; + } + var dat = this.getSaveData(); + download(JSON.stringify(dat), "MinesSave.mgs", "text/plain"); + }, + + loadFromFile: function(){ + if(this.running){ + this.running = false; + clearInterval(this.clock); + this.clock = -1; + circle.className = "loading"; + } + + if(fileSel.files.length < 1){ + alert("No file selected!"); + return; + } + var file = fileSel.files[0]; + var fr = new FileReader(); + fr.addEventListener("load", this.loadSaveData.bind(this)); + fr.addEventListener("error", this.loadError); + + fr.readAsText(file); + } +}; + +//Taken from Kanchu on Sack Overflow +//Reference: https://stackoverflow.com/questions/13405129/javascript-create-and-save-file +function download(data, filename, type) { + var file = new Blob([data], {type: type}); + if (window.navigator.msSaveOrOpenBlob) // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + else { // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } +} -- cgit v1.2.3