<!DOCTYPE html>
<html>

  <head>
    <title>angular2 playground</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css" />
    <script src="https://code.angularjs.org/tools/system.js"></script>
    <script src="https://code.angularjs.org/tools/typescript.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.5/immutable.min.js"></script>
    <script src="config.js"></script>
    <script src="https://code.angularjs.org/2.0.0-alpha.46/angular2.min.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
    <app>
      Loading, please wait...
    </app>
  </body>

</html>
/* 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;
    width: 512px;
}

.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;
}




System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  //map tells the System loader where to look for things
  map: {
    app: "./src"
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    }
  }
});
// Import Angular dependencies
import {bootstrap, provide} from 'angular2/angular2';

// Import application component
import {App} from './app/app.component';

// Bootstrap the application
bootstrap(App, []).catch(err => console.error(err));
import {Component, Input, CORE_DIRECTIVES, ChangeDetectionStrategy} from 'angular2/angular2';
import {revealTile, isGameOver} from './game';
import {TileComponent} from './tile.component';

@Component({
  selector: 'minesweeper',
  template: `
  <div class="board">
    <tile *ng-for="#tile of getTiles()" [tile]="tile" (tile-click)="handleTileClick($event)"></tile>
  </div>
  `,
  directives: [CORE_DIRECTIVES, TileComponent]
  // If we enable this, the undo action does not update UI
  // changeDetection: ChangeDetectionStrategy.OnPush
})
export class MinesweeperComponent {
  @Input() game: any;
  history = Immutable.List();
  
  onChanges(changes){
    
    // Only update game when game has actually changed
    if(changes.hasOwnProperty('game')){
      this.updateGame()
    }
  }
  
  getTiles(){
    return this.game ? this.game.get('tiles') : [];
  }
  
  updateGame(updateHistory = true){
    this.history = this.history.push(this.game);
  }
  
  handleTileClick(tile){
    if(!tile){
      return;
    }
    if (isGameOver(this.game)) {
      return;
    }
    const newGame = revealTile(this.game, tile.get('id'));
    if (newGame !== this.game) {
      this.game = newGame;
      this.updateGame();
    }
    if (isGameOver(this.game)) {
      window.alert('GAME OVER!');
    }
  }
  
  undo(){
    if (this.canUndo()) {
      console.log('undo');
      this.history = this.history.pop();
      this.game = this.history.last();
    }
  }
  
  canUndo(){
    return this.history.size > 1;
  }
}
// Credits to Christian Johansen for game logic:
// https://github.com/cjohansen/react-sweeper

let {List,Map,fromJS} = Immutable;
import {partition, shuffle, repeat, keep, prop} from './util';

function initTiles(rows, cols, mines) {
  return shuffle(repeat(mines, Map({isMine: true, isRevealed: false})).
                 concat(repeat(rows * cols - mines, Map({isRevealed: false})))).
    map(function (tile, idx) {
      return tile.set('id', idx);
    });
}

function onWEdge(game, tile) {
  return tile % game.get('cols') === 0;
}

function onEEdge(game, tile) {
  return tile % game.get('cols') === game.get('cols') - 1;
}

function idx(game, tile) {
  if (tile < 0) { return null; }
  return game.getIn(['tiles', tile]) ? tile : null;
}

function nw(game, tile) {
  return onWEdge(game, tile) ? null : idx(game, tile - game.get('cols') - 1);
}

function n(game, tile) {
  return idx(game, tile - game.get('cols'));
}

function ne(game, tile) {
  return onEEdge(game, tile) ? null : idx(game, tile - game.get('cols') + 1);
}

function e(game, tile) {
  return onEEdge(game, tile) ? null : idx(game, tile + 1);
}

function se(game, tile) {
  return onEEdge(game, tile) ? null : idx(game, tile + game.get('cols') + 1);
}

function s(game, tile) {
  return idx(game, tile + game.get('cols'));
}

function sw(game, tile) {
  return onWEdge(game, tile) ? null : idx(game, tile + game.get('cols') - 1);
}

function w(game, tile) {
  return onWEdge(game, tile) ? null : idx(game, tile - 1);
}

const directions = [nw, n, ne, e, se, s, sw, w];

function neighbours(game, tile) {
  return keep(directions, function (dir) {
    return game.getIn(['tiles', dir(game, tile)]);
  });
}

function getMineCount(game, tile) {
  var nbs = neighbours(game, tile);
  return nbs.filter(prop('isMine')).length;
}

function isMine(game, tile) {
  return game.getIn(['tiles', tile, 'isMine']);
}

function isSafe(game) {
  const tiles = game.get('tiles');
  const mines = tiles.filter(prop('isMine'));
  return mines.filter(prop('isRevealed')) === 0 &&
    tiles.length - mines.length === tiles.filter(prop('isRevealed')).length;
}

export function isGameOver(game) {
  return isSafe(game) || game.get('isDead');
}

function addThreatCount(game, tile) {
  return game.setIn(['tiles', tile, 'threatCount'], getMineCount(game, tile));
}

function revealAdjacentSafeTiles(game, tile) {
  if (isMine(game, tile)) {
    return game;
  }
  game = addThreatCount(game, tile).setIn(['tiles', tile, 'isRevealed'], true);
  if (game.getIn(['tiles', tile, 'threatCount']) === 0) {
    return keep(directions, function (dir) {
      return dir(game, tile);
    }).reduce(function (game, pos) {
      return !game.getIn(['tiles', pos, 'isRevealed']) ?
        revealAdjacentSafeTiles(game, pos) : game;
    }, game);
  }
  return game;
}

function attemptWinning(game) {
  return isSafe(game) ? game.set('isSafe', true) : game;
}

function revealMine(tile) {
  return tile.get('isMine') ? tile.set('isRevealed', true) : tile;
}

function revealMines(game) {
  return game.updateIn(['tiles'], function (tiles) {
    return tiles.map(revealMine);
  });
}

export function revealTile(game, tile) {
  const updated = !game.getIn(['tiles', tile]) ?
          game : game.setIn(['tiles', tile, 'isRevealed'], true);
  return isMine(updated, tile) ?
    revealMines(updated.set('isDead', true)) :
    attemptWinning(revealAdjacentSafeTiles(updated, tile));
}

export function createGame(options) {
  return fromJS({
    cols: options.cols,
    rows: options.rows,
    playingTime: 0,
    tiles: initTiles(options.rows, options.cols, options.mines)
  });
}
// Credits to Christian Johansen for util logic:
// https://github.com/cjohansen/react-sweeper

let {fromJS, List, Map} = Immutable;

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 fromJS(res);
}

function identity(v) {
  return v;
}

function prop(n) {
  return function (object) {
    return object instanceof Map ? object.get(n) : object[n];
  };
}

function keep(list, pred) {
  return list.map(pred).filter(identity);
}

function repeat(n, val) {
  const res = [];
  while (n--) {
    res.push(val);
  }
  return List(res);
}

function shuffle(list) {
  return list.sort(function () { return Math.random() - 0.5; });
}

export {partition, identity, prop, keep, repeat, shuffle};
import {Component} from 'angular2/angular2';
import {MinesweeperComponent} from '../minesweeper/minesweeper.component';
import {createGame} from '../minesweeper/game';

@Component({
  selector: 'app',
  template: `
  <minesweeper [game]="game" #minesweeper></minesweeper>
  <ul class="actions">
    <li><a (click)="startNewGame()">New game</a></li>
    <li><a (click)="minesweeper.undo()" [hidden]="!minesweeper.canUndo()">Undo</a></li>
  </ul>
  `,
  directives: [MinesweeperComponent]
})
export class App {
  public game;
  constructor(){
    
  }
  onInit(){
    this.startNewGame();
  }
  startNewGame(){
    this.game = createGame({cols: 16, rows: 16, mines: 48});
  }
} 
import {Component, Input, Output, EventEmitter, CORE_DIRECTIVES, ChangeDetectionStrategy} from 'angular2/angular2';

@Component({
  selector: 'tile',
  template: `
  <div class="tile" [class.mine]="tile.get('isMine')" (click)="handleTileClick(tile)">
    <div class="lid" *ng-if="!tile.get('isRevealed')"></div>
    <div *ng-if="tile.get('isRevealed') && !tile.get('isMine')">
      {{ tile.get('threatCount') > 0 ? tile.get('threatCount') : '' }}
    </div>
  </div>
  `,
  directives: [CORE_DIRECTIVES],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TileComponent {
  @Input() tile: any;
  @Output() tileClick: EventEmitter = new EventEmitter();
  
  onChanges(changes){
    if(changes.tile){
      console.log('Tile %s changed', this.tile.get('id'));
    }
  }
  handleTileClick(tile){
    this.tileClick.next(tile);
  }
}