-
-
Save ajayvikas/d9a9e282c61f14956fdcb00507f2fa05 to your computer and use it in GitHub Desktop.
Minesweeper
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
<template> | |
<require from="./minesweeper.viewmodel"></require> | |
<minesweeper game.bind="game" view-model.ref="minesweeper"></minesweeper> | |
<ul class="actions"> | |
<li><a click.delegate="startNewGame()">New game</a></li> | |
<li><a click.delegate="minesweeper.undo()" hidden.bind="!minesweeper.canUndo()">Undo</a></li> | |
</ul> | |
</template> |
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
import {customElement, View, observable} from "aurelia-framework"; | |
import {createGame} from "./game"; | |
import {Tile} from "./tile"; | |
import {GameBoard} from "./game-board"; | |
import {MineSweeperViewModel} from "./minesweeper.viewmodel"; | |
@customElement("app") | |
export class AppViewModel { | |
game : GameBoard; | |
minesweeper: MineSweeperViewModel; | |
created(owningView: View, myView: View) { | |
this.startNewGame(); | |
} | |
startNewGame() { | |
//this.game = createGame({ cols: 4, rows: 4, mines: 4 }); | |
this.game = createGame({ cols: 48, rows: 100, mines: 900 }); | |
//this.game = createGame({ cols: 16, rows: 16, mines: 48 }); | |
} | |
} |
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
import {Aurelia} from 'aurelia-framework'; | |
export function configure(aurelia: Aurelia): void { | |
aurelia.use | |
.standardConfiguration() | |
.developmentLogging(); | |
//.plugin("aurelia-testing"); | |
aurelia.start().then(() => aurelia.setRoot("app")); | |
} |
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
import {Tile} from "./tile"; | |
export class GameBoard { | |
cols: number; | |
rows: number; | |
playingTime: number; | |
tiles: Tile[]; | |
isDead: boolean = false; | |
isSafe: boolean = true; | |
getTileById(id: number) { | |
if (this.tiles.length > id) return this.tiles[id]; | |
return null; | |
} | |
} |
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
import {partition, shuffle, repeat, keep, prop} from './util'; | |
import {Tile} from "./tile"; | |
import {GameBoard} from "./game-board"; | |
// Credits to Christian Johansen for game logic: | |
// https://github.com/cjohansen/react-sweeper | |
function initTiles(rows, cols, mines) : Tile[] { | |
var repeated: Tile[] = repeat(mines, {isMine: true, isRevealed: false, threatCount: 0}); | |
var concatanated = repeated.concat(repeat(rows * cols - mines, {isMine: false, isRevealed: false, threatCount: 0})); | |
var shuffled = shuffle(concatanated); | |
shuffled.forEach((tile, idx) => { | |
tile.id = idx; | |
}); | |
return shuffled; | |
} | |
function onWEdge(game : GameBoard, tile: Tile) { | |
return tile.id % game.cols === 0; | |
} | |
function onEEdge(game : GameBoard, tile: Tile) { | |
return tile.id % game.cols === game.cols - 1; | |
} | |
function nw(game: GameBoard, tile: Tile) { | |
return onWEdge(game, tile) ? null : game.getTileById(tile.id - game.cols - 1); | |
} | |
function n(game: GameBoard, tile: Tile) { | |
return game.getTileById(tile.id - game.cols); | |
} | |
function ne(game: GameBoard, tile: Tile) { | |
return onEEdge(game, tile) ? null : game.getTileById(tile.id - game.cols + 1); | |
} | |
function e(game: GameBoard, tile: Tile) { | |
return onEEdge(game, tile) ? null : game.getTileById(tile.id + 1); | |
} | |
function se(game: GameBoard, tile: Tile) { | |
return onEEdge(game, tile) ? null : game.getTileById(tile.id + game.cols + 1); | |
} | |
function s(game: GameBoard, tile: Tile) { | |
return game.getTileById(tile.id + game.cols); | |
} | |
function sw(game: GameBoard, tile: Tile) { | |
return onWEdge(game, tile) ? null : game.getTileById(tile.id + game.cols - 1); | |
} | |
function w(game: GameBoard, tile: Tile) { | |
return onWEdge(game, tile) ? null : game.getTileById(tile.id - 1); | |
} | |
const directions = [nw, n, ne, e, se, s, sw, w]; | |
function neighbours(game: GameBoard, tile: Tile): Tile[] { | |
return keep(directions, function (dir) { | |
return dir(game, tile); | |
}); | |
} | |
function getMineCount(game: GameBoard, tile: Tile) { | |
var nbs = neighbours(game, tile); | |
return nbs.filter(p => p.isMine).length; | |
} | |
function isMine(game: GameBoard, tile: Tile) { | |
return tile ? tile.isMine : false; | |
} | |
function isSafe(game: GameBoard) { | |
const tiles = game.tiles; | |
const mines = tiles.filter(p => p.isMine); | |
let ct = mines.filter(p => p.isRevealed && | |
tiles.length - mines.length === tiles.filter(p => p.isRevealed).length); | |
return ct.length != 0; | |
} | |
export function isGameOver(game: GameBoard) { | |
return isSafe(game) || game.isDead; | |
} | |
function addThreatCount(game: GameBoard, tile: Tile) { | |
if (tile == null) return; | |
tile.threatCount = getMineCount(game, tile); | |
return game; | |
} | |
function revealAdjacentSafeTiles(game: GameBoard, tile: Tile) { | |
if (isMine(game, tile)) { | |
return; | |
} | |
addThreatCount(game, tile); | |
tile.isRevealed = true; | |
if (tile.threatCount === 0) { | |
let neighbours = keep(directions, function (dir) { | |
return dir(game, tile); | |
}); | |
neighbours.forEach(nt => { | |
if (!nt.isRevealed) | |
revealAdjacentSafeTiles(game, nt); | |
}); | |
} | |
} | |
function attemptWinning(game: GameBoard) { | |
if (isSafe(game)) | |
game.isSafe = true; | |
return game; | |
} | |
function revealMine(tile: Tile) { | |
if (tile.isMine) tile.isRevealed = true; | |
return tile; | |
} | |
function revealMines(game: GameBoard) { | |
game.tiles.forEach(tile => revealMine(tile)); | |
return game; | |
} | |
export function revealTile(game: GameBoard, tile: Tile) { | |
let isChanged = tile.isRevealed == false; | |
tile.isRevealed = true; | |
if (isMine(game, tile)) { | |
game.isDead = true; | |
revealMines(game); | |
} | |
else { | |
revealAdjacentSafeTiles(game, tile); | |
attemptWinning(game); | |
} | |
return isChanged; | |
} | |
export function createGame(options) : GameBoard { | |
let game = new GameBoard(); | |
game.cols = options.cols; | |
game.rows = options.rows; | |
game.playingTime = 0; | |
game.tiles = initTiles(options.rows, options.cols, options.mines); | |
return game; | |
} |
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> | |
<title>Aurelia</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<link rel="stylesheet" href="style.css" /> | |
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/jspm_packages/system.js"></script> | |
<script src="https://cdn.rawgit.com/jdanyow/aurelia-bundle/v1.0.3/config.js"></script> <script> | |
System.import('aurelia-bootstrapper'); | |
</script> | |
</head> | |
<body aurelia-app="boot"> | |
<h1>Loading...</h1> | |
</body> | |
</html> |
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
<template> | |
<require from="./row.viewmodel"></require> | |
<div class="board"> | |
<row repeat.for="row of rows" row.bind="row" tile-clicked.call="handleTileClick($event)"></row> | |
</div> | |
</template> |
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
import {customElement, bindable, View} from "aurelia-framework"; | |
import {partition} from './util'; | |
import {revealTile, isGameOver} from './game'; | |
import {Tile} from "./tile"; | |
@customElement("minesweeper") | |
export class MineSweeperViewModel { | |
@bindable() | |
game: any; | |
rows; | |
history = []; | |
gameChanged() { | |
this.updateGame(); | |
} | |
updateGame(updateHistory = true) { | |
this.rows = partition(this.game.cols, this.game.tiles); | |
if (updateHistory) { | |
this.history.push(this.game); | |
} | |
} | |
handleTileClick(tile: Tile) { | |
if (!tile) { | |
return; | |
} | |
if (isGameOver(this.game)) { | |
return; | |
} | |
let isChanged = revealTile(this.game, tile); | |
if (isGameOver(this.game)) { | |
window.alert('GAME OVER!'); | |
} | |
} | |
undo() { | |
if (this.canUndo()) { | |
this.game = this.history.pop(); | |
// Don't update the history so we don't end up with | |
// the same game twice in the end of the list | |
this.updateGame(false); | |
} | |
} | |
canUndo() { | |
return this.history.length > 1; | |
} | |
} |
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
<template> | |
<require from="./tile.viewmodel"></require> | |
<div class="row"> | |
<tile repeat.for="tile of row" tile.bind="tile" tile-clicked.call="handleTileClick(tile)"></tile> | |
</div> | |
</template> |
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
import {customElement, bindable} from "aurelia-framework"; | |
@customElement("row") | |
export class RowViewModel { | |
@bindable() row: any; | |
@bindable() tileClicked: Function; | |
handleTileClick(tile) { | |
if (this.tileClicked) | |
this.tileClicked(tile); | |
} | |
} |
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
/* Styles go here */ | |
body { | |
background: #b5afa7 no-repeat 23px 30px; | |
background-size: 380px; | |
font-family: sans-serif; | |
} | |
#main { | |
margin: 0 auto 0; | |
width: 800px; | |
} | |
.time { | |
background: #736d65; | |
display: inline-block; | |
padding: 6px 10px; | |
} | |
.board { | |
border: 10px solid #a49e96; | |
border-radius: 10px; | |
margin: 20px; | |
background: #938d85; | |
display: inline-block; | |
} | |
.tile { | |
position: relative; | |
border: 1px solid #504d49; | |
width: 30px; | |
height: 30px; | |
float: left; | |
color: #100d09; | |
font-weight: bold; | |
line-height: 30px; | |
font-size: 20px; | |
vertical-align: middle; | |
text-align: center; | |
} | |
.lid { | |
position: absolute; | |
top: -1px; | |
left: -1px; | |
right: -1px; | |
bottom: -1px; | |
border: 2px outset #a49e96; | |
background-image: | |
radial-gradient( | |
circle at top left, | |
#a49e96, | |
#605d59 | |
); | |
} | |
.lid:hover { | |
background-image: | |
radial-gradient( | |
circle at bottom right, | |
#a49e96, | |
#605d59 | |
); | |
} | |
.mine { | |
background: url(../images/danger.png); | |
background: red; | |
} | |
a:hover{ | |
cursor: pointer; | |
} | |
ul.actions{ | |
list-style: none; | |
margin: 0 20px; | |
padding: 0; | |
} | |
ul.actions li{ | |
display: inline-block; | |
} | |
ul.actions a{ | |
border: 10px solid #a49e96; | |
border-radius: 10px; | |
padding: 6px 12px; | |
text-decoration: none; | |
font-weight: bold; | |
color: rgba(0,0,0,0.5); | |
} | |
ul.actions a:hover{ | |
background: #a49e96; | |
} |
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
export class Tile { | |
id : number; | |
isMine: boolean; | |
isRevealed: boolean; | |
threatCount: number; | |
constructor(isMine: boolean, isRevealed: boolean, threatCount: number) { | |
this.isMine = isMine; | |
this.isRevealed = isRevealed; | |
this.threatCount = threatCount; | |
} | |
} |
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
<template> | |
<div data-id="${tile.id}" class="tile ${tile.isMine ? 'mine' : ''}" click.delegate="onTileClicked($event)"> | |
<div if.bind="!tile.isRevealed && tile.isMine" style="background-color: yellow;"></div> | |
<div class="lid" if.bind="!tile.isRevealed && !tile.isMine"></div> | |
<div if.bind="tile.isRevealed && !tile.isMine"> | |
${ tile.threatCount > 0 ? tile.threatCount : '' } | |
</div> | |
</div> | |
</template> |
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
import {customElement, bindable} from "aurelia-framework"; | |
import {Tile} from "./tile"; | |
@customElement("tile") | |
export class TileViewModel { | |
@bindable() tile: Tile; | |
@bindable tileClicked: Function; | |
onTileClicked() { | |
if (this.tileClicked) this.tileClicked(this); | |
} | |
} |
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
// Credits to Christian Johansen for util logic: | |
// https://github.com/cjohansen/react-sweeper | |
import {Tile} from "./tile"; | |
import {GameBoard} from "./game-board"; | |
export function partition(size, coll) { | |
var res = []; | |
for (var i = 0, l = coll.size || coll.length; i < l; i += size) { | |
res.push(coll.slice(i, i + size)); | |
} | |
return res | |
} | |
export function identity(v) { | |
return v; | |
} | |
export function prop(n) { | |
return function (object) { | |
return object instanceof Map ? object.get(n) : object[n]; | |
}; | |
} | |
export function keep(list, pred) { | |
return list.map(pred).filter(identity); | |
} | |
export function repeat(n, val: {isMine: boolean, isRevealed: boolean, threatCount: number}) { | |
let res: Tile[] = []; | |
while (n--) { | |
res.push(new Tile(val.isMine, val.isRevealed, val.threatCount)); | |
//res.push(JSON.parse(JSON.stringify(val))); | |
} | |
return res; | |
} | |
export function shuffle<T>(list: T[]) : T[] { | |
return list.sort(function () { return Math.random() - 0.5; }); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment