Skip to content

Instantly share code, notes, and snippets.

@ajayvikas
Forked from jdanyow/app.html
Last active September 21, 2016 18:34
Show Gist options
  • Save ajayvikas/d9a9e282c61f14956fdcb00507f2fa05 to your computer and use it in GitHub Desktop.
Save ajayvikas/d9a9e282c61f14956fdcb00507f2fa05 to your computer and use it in GitHub Desktop.
Minesweeper
<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>
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 });
}
}
import {Aurelia} from 'aurelia-framework';
export function configure(aurelia: Aurelia): void {
aurelia.use
.standardConfiguration()
.developmentLogging();
//.plugin("aurelia-testing");
aurelia.start().then(() => aurelia.setRoot("app"));
}
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;
}
}
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;
}
<!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>
<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>
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;
}
}
<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>
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);
}
}
/* 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;
}
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;
}
}
<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>
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);
}
}
// 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