diff options
Diffstat (limited to 'mines')
-rw-r--r-- | mines/index.html | 64 | ||||
-rw-r--r-- | mines/mines.css | 176 | ||||
-rw-r--r-- | mines/mines.js | 490 |
3 files changed, 730 insertions, 0 deletions
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 @@ +<!Doctype HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
+ <link rel="stylesheet" type="text/css" href="./mines.css"/>
+
+ <script src="./mines.js"></script>
+ </head>
+ <body>
+ <div class="selector">
+ <label>Width: </label> <input id="x" min="4" max="50" value="6" type="number"/>
+ <br>
+ <label>Height: </label> <input id="y" min="4" value="6" type="number"/>
+ <br>
+ <label>Mines: </label> <input id="m" min="2" value="5" type="number"/>
+ <br>
+ <label>- OR -</label>
+ <br>
+ <label>Difficulty: </label>
+ <select id="d">
+ <option value="0.125">Easy</option>
+ <option value="0.15">Medium</option>
+ <option value="0.2">Hard</option>
+ <option value="0.25">Impossible</option>
+ <option value="0">Custom</option>
+ </select>
+ <br>
+ <label>- OR -</label>
+ <br>
+ <button id="save" onclick="game.save()">Save Game</button> | <label for="fin">Choose Save File</label><input id="fin" type="file" accept=".mgs"/> <button id="load" onclick="game.loadFromFile()">Load Game</button>
+ </div>
+ <br>
+ <div class="game">
+ <h1 id="mines">0</h1><button id="circle" onclick="game.reset()"></button><h1 id="time">0</h1>
+ <br>
+ <table>
+ <tbody id="gtable"><tr><td class="wt">◉</td></tr></tbody>
+ </table>
+ </div>
+ <script>
+ var xIn = document.getElementById("x");
+ var yIn = document.getElementById("y");
+ var mIn = document.getElementById("m");
+ var dIn = document.getElementById("d");
+
+ var sMines = document.getElementById("mines");
+ var sTime = document.getElementById("time");
+
+ var circle = document.getElementById("circle");
+
+ var table = document.getElementById("gtable");
+
+ var fileSel = document.getElementById("fin");
+
+ var game = new Board();
+
+ mIn.addEventListener("change", function(){
+ if(dIn.value !== "0") dIn.value = "0";
+ });
+ </script>
+ </body>
+</html>
\ 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);
+ }
+}
|