├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.scss │ ├── game │ │ ├── cell.ts │ │ └── board.ts │ ├── app.module.ts │ ├── app.component.ts │ ├── app.component.spec.ts │ └── app.component.html ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── styles.scss ├── index.html ├── main.ts ├── test.ts └── polyfills.ts ├── screenshot.png ├── tsconfig.app.json ├── e2e ├── tsconfig.json ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts └── protractor.conf.js ├── .editorconfig ├── tsconfig.spec.json ├── README.md ├── tsconfig.json ├── .gitignore ├── karma.conf.js ├── package.json ├── angular.json ├── tslint.json └── privacy.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenFluin/angular-minesweeper/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenFluin/angular-minesweeper/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/app/game/cell.ts: -------------------------------------------------------------------------------- 1 | export class Cell { 2 | status: 'open' | 'clear' | 'flag' = 'open'; 3 | mine = false; 4 | proximityMines = 0; 5 | 6 | constructor(public row: number, public column: number) {} 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Minesweeper 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | imports: [ 11 | BrowserModule 12 | ], 13 | providers: [], 14 | bootstrap: [AppComponent] 15 | }) 16 | export class AppModule { } 17 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minesweeper built in Angular 2 | 3 | [Play the game](https://stephenfluin.github.io/angular-minesweeper/) 4 | 5 | [![Game Screenshot](screenshot.png)](https://stephenfluin.github.io/angular-minesweeper/) 6 | 7 | ## Development 8 | All custom code lives in /src/app 9 | 10 | * `ng serve` to do live development reload 11 | * `ng build --prod` to build 12 | * [`ngh`](https://www.npmjs.com/package/angular-cli-ghpages) to deploy to GitHub pages -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), { 14 | teardown: { destroyAfterEach: false } 15 | } 16 | ); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | "declaration": false, 9 | "module": "es2020", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "target": "ES2022", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ], 22 | "useDefineForClassFields": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to minesweeper!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Board } from './game/board'; 3 | import { Cell } from './game/cell'; 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'], 8 | standalone: false 9 | }) 10 | export class AppComponent { 11 | title = 'minesweeper'; 12 | board: Board; 13 | constructor() { 14 | this.reset(); 15 | } 16 | 17 | checkCell(cell: Cell) { 18 | const result = this.board.checkCell(cell); 19 | if (result === 'gameover') { 20 | alert('You lose'); 21 | } else if (result === 'win') { 22 | alert('you win'); 23 | } 24 | } 25 | flag(cell: Cell) { 26 | if (cell.status === 'flag') { 27 | cell.status = 'open'; 28 | } else { 29 | cell.status = 'flag'; 30 | } 31 | } 32 | 33 | reset() { 34 | this.board = new Board(20, 50); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'minesweeper'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('minesweeper'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to minesweeper!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/minesweeper'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | Board: 14 | 15 | 16 | 37 | 38 |
21 | 22 |
{{ cell.proximityMines }}
23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | 34 | 35 |
36 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minesweeper", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^19.1.5", 16 | "@angular/common": "^19.1.5", 17 | "@angular/compiler": "^19.1.5", 18 | "@angular/core": "^19.1.5", 19 | "@angular/forms": "^19.1.5", 20 | "@angular/platform-browser": "^19.1.5", 21 | "@angular/platform-browser-dynamic": "^19.1.5", 22 | "@angular/router": "^19.1.5", 23 | "angular-cli-ghpages": "^0.6.0", 24 | "rxjs": "~6.5.3", 25 | "source-map-explorer": "^2.5.2", 26 | "tslib": "^2.0.0", 27 | "zone.js": "~0.15.0" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^19.1.6", 31 | "@angular/cli": "^19.1.6", 32 | "@angular/compiler-cli": "^19.1.5", 33 | "@angular/language-service": "^19.1.5", 34 | "@types/jasmine": "~3.6.0", 35 | "@types/jasminewd2": "~2.0.3", 36 | "@types/node": "^12.11.1", 37 | "codelyzer": "^6.0.0", 38 | "jasmine-core": "~3.6.0", 39 | "jasmine-spec-reporter": "~5.0.0", 40 | "karma": "~6.3.17", 41 | "karma-chrome-launcher": "~3.1.0", 42 | "karma-coverage-istanbul-reporter": "~3.0.2", 43 | "karma-jasmine": "~4.0.0", 44 | "karma-jasmine-html-reporter": "^1.5.0", 45 | "protractor": "~7.0.0", 46 | "ts-node": "~7.0.0", 47 | "tslint": "~6.1.0", 48 | "typescript": "~5.7.3" 49 | }, 50 | "prettier": { 51 | "trailingComma": "es5", 52 | "tabWidth": 2, 53 | "singleQuote": true, 54 | "printWidth": 120 55 | } 56 | } -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags.ts'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /src/app/game/board.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from './cell'; 2 | 3 | const PEERS = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]; 4 | 5 | export class Board { 6 | cells: Cell[][] = []; 7 | 8 | private remainingCells = 0; 9 | private mineCount = 0; 10 | 11 | constructor(size: number, mines: number) { 12 | for (let y = 0; y < size; y++) { 13 | this.cells[y] = []; 14 | for (let x = 0; x < size; x++) { 15 | this.cells[y][x] = new Cell(y, x); 16 | } 17 | } 18 | 19 | // Assign mines 20 | for (let i = 0; i < mines; i++) { 21 | this.getRandomCell().mine = true; 22 | } 23 | 24 | // Count mines 25 | 26 | for (let y = 0; y < size; y++) { 27 | for (let x = 0; x < size; x++) { 28 | let adjacentMines = 0; 29 | for (const peer of PEERS) { 30 | if ( 31 | this.cells[y + peer[0]] && 32 | this.cells[y + peer[0]][x + peer[1]] && 33 | this.cells[y + peer[0]][x + peer[1]].mine 34 | ) { 35 | adjacentMines++; 36 | } 37 | } 38 | this.cells[y][x].proximityMines = adjacentMines; 39 | 40 | if (this.cells[y][x].mine) { 41 | this.mineCount++; 42 | } 43 | } 44 | } 45 | this.remainingCells = size * size - this.mineCount; 46 | } 47 | 48 | getRandomCell(): Cell { 49 | const y = Math.floor(Math.random() * this.cells.length); 50 | const x = Math.floor(Math.random() * this.cells[y].length); 51 | return this.cells[y][x]; 52 | } 53 | 54 | checkCell(cell: Cell): 'gameover' | 'win' | null { 55 | if (cell.status !== 'open') { 56 | return; 57 | } else if (cell.mine) { 58 | this.revealAll(); 59 | return 'gameover'; 60 | } else { 61 | cell.status = 'clear'; 62 | 63 | // Empty cell, let's clear the whole block. 64 | if(cell.proximityMines === 0) { 65 | for(const peer of PEERS) { 66 | if ( 67 | this.cells[cell.row + peer[0]] && 68 | this.cells[cell.row + peer[0]][cell.column + peer[1]] 69 | ) { 70 | this.checkCell(this.cells[cell.row + peer[0]][cell.column + peer[1]]); 71 | } 72 | } 73 | } 74 | 75 | 76 | if (this.remainingCells-- <= 1) { 77 | return 'win'; 78 | } 79 | return; 80 | } 81 | } 82 | revealAll() { 83 | for (const row of this.cells) { 84 | for (const cell of row) { 85 | if (cell.status === 'open') { 86 | cell.status = 'clear'; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "cli": { 4 | "analytics": "074d8cd0-2bac-4bc1-99ba-71c74fe90e85" 5 | }, 6 | "version": 1, 7 | "newProjectRoot": "projects", 8 | "projects": { 9 | "minesweeper": { 10 | "projectType": "application", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:application", 22 | "options": { 23 | "outputPath": { 24 | "base": "dist/" 25 | }, 26 | "index": "src/index.html", 27 | "polyfills": [ 28 | "src/polyfills.ts" 29 | ], 30 | "tsConfig": "tsconfig.app.json", 31 | "assets": ["src/favicon.ico", "src/assets"], 32 | "styles": ["src/styles.scss"], 33 | "scripts": [], 34 | "extractLicenses": false, 35 | "sourceMap": true, 36 | "optimization": false, 37 | "namedChunks": true, 38 | "browser": "src/main.ts" 39 | }, 40 | "configurations": { 41 | "production": { 42 | "fileReplacements": [ 43 | { 44 | "replace": "src/environments/environment.ts", 45 | "with": "src/environments/environment.prod.ts" 46 | } 47 | ], 48 | "optimization": true, 49 | "outputHashing": "all", 50 | "sourceMap": false, 51 | "baseHref": "/angular-minesweeper/", 52 | "extractLicenses": true, 53 | "budgets": [ 54 | { 55 | "type": "initial", 56 | "maximumWarning": "2mb", 57 | "maximumError": "5mb" 58 | }, 59 | { 60 | "type": "anyComponentStyle", 61 | "maximumWarning": "6kb" 62 | } 63 | ] 64 | } 65 | }, 66 | "defaultConfiguration": "" 67 | }, 68 | "serve": { 69 | "builder": "@angular-devkit/build-angular:dev-server", 70 | "options": { 71 | "buildTarget": "minesweeper:build" 72 | }, 73 | "configurations": { 74 | "production": { 75 | "buildTarget": "minesweeper:build:production" 76 | } 77 | } 78 | }, 79 | "extract-i18n": { 80 | "builder": "@angular-devkit/build-angular:extract-i18n", 81 | "options": { 82 | "buildTarget": "minesweeper:build" 83 | } 84 | }, 85 | "test": { 86 | "builder": "@angular-devkit/build-angular:karma", 87 | "options": { 88 | "main": "src/test.ts", 89 | "polyfills": "src/polyfills.ts", 90 | "tsConfig": "tsconfig.spec.json", 91 | "karmaConfig": "karma.conf.js", 92 | "assets": ["src/favicon.ico", "src/assets"], 93 | "styles": ["src/styles.scss"], 94 | "scripts": [] 95 | } 96 | }, 97 | "e2e": { 98 | "builder": "@angular-devkit/build-angular:protractor", 99 | "options": { 100 | "protractorConfig": "e2e/protractor.conf.js", 101 | "devServerTarget": "minesweeper:serve" 102 | }, 103 | "configurations": { 104 | "production": { 105 | "devServerTarget": "minesweeper:serve:production" 106 | } 107 | } 108 | }, 109 | "deploy": { 110 | "builder": "angular-cli-ghpages:deploy", 111 | "options": {} 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-parens": false, 12 | "arrow-return-shorthand": true, 13 | "deprecation": { 14 | "severity": "warn" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "curly": true, 19 | "directive-class-suffix": true, 20 | "directive-selector": [ 21 | true, 22 | "attribute", 23 | "app", 24 | "camelCase" 25 | ], 26 | "component-selector": [ 27 | true, 28 | "element", 29 | "app", 30 | "kebab-case" 31 | ], 32 | "eofline": true, 33 | "import-blacklist": [ 34 | true, 35 | "rxjs/Rx" 36 | ], 37 | "import-spacing": true, 38 | "indent": { 39 | "options": [ 40 | "spaces" 41 | ] 42 | }, 43 | "interface-name": false, 44 | "max-classes-per-file": false, 45 | "max-line-length": [ 46 | true, 47 | 140 48 | ], 49 | "member-access": false, 50 | "member-ordering": [ 51 | true, 52 | { 53 | "order": [ 54 | "static-field", 55 | "instance-field", 56 | "static-method", 57 | "instance-method" 58 | ] 59 | } 60 | ], 61 | "no-consecutive-blank-lines": false, 62 | "no-console": [ 63 | true, 64 | "debug", 65 | "info", 66 | "time", 67 | "timeEnd", 68 | "trace" 69 | ], 70 | "no-empty": false, 71 | "no-inferrable-types": [ 72 | true, 73 | "ignore-params" 74 | ], 75 | "no-non-null-assertion": true, 76 | "no-redundant-jsdoc": true, 77 | "no-switch-case-fall-through": true, 78 | "no-var-requires": false, 79 | "object-literal-key-quotes": [ 80 | true, 81 | "as-needed" 82 | ], 83 | "object-literal-sort-keys": false, 84 | "ordered-imports": false, 85 | "quotemark": [ 86 | true, 87 | "single" 88 | ], 89 | "trailing-comma": false, 90 | "no-conflicting-lifecycle": true, 91 | "no-host-metadata-property": true, 92 | "no-input-rename": true, 93 | "no-inputs-metadata-property": true, 94 | "no-output-native": true, 95 | "no-output-on-prefix": true, 96 | "no-output-rename": true, 97 | "semicolon": { 98 | "options": [ 99 | "always" 100 | ] 101 | }, 102 | "space-before-function-paren": { 103 | "options": { 104 | "anonymous": "never", 105 | "asyncArrow": "always", 106 | "constructor": "never", 107 | "method": "never", 108 | "named": "never" 109 | } 110 | }, 111 | "no-outputs-metadata-property": true, 112 | "template-banana-in-box": true, 113 | "template-no-negated-async": true, 114 | "typedef-whitespace": { 115 | "options": [ 116 | { 117 | "call-signature": "nospace", 118 | "index-signature": "nospace", 119 | "parameter": "nospace", 120 | "property-declaration": "nospace", 121 | "variable-declaration": "nospace" 122 | }, 123 | { 124 | "call-signature": "onespace", 125 | "index-signature": "onespace", 126 | "parameter": "onespace", 127 | "property-declaration": "onespace", 128 | "variable-declaration": "onespace" 129 | } 130 | ] 131 | }, 132 | "use-lifecycle-interface": true, 133 | "use-pipe-transform-interface": true, 134 | "variable-name": { 135 | "options": [ 136 | "ban-keywords", 137 | "check-format", 138 | "allow-pascal-case" 139 | ] 140 | }, 141 | "whitespace": { 142 | "options": [ 143 | "check-branch", 144 | "check-decl", 145 | "check-operator", 146 | "check-separator", 147 | "check-type", 148 | "check-typecast" 149 | ] 150 | } 151 | }, 152 | "rulesDirectory": [ 153 | "codelyzer" 154 | ] 155 | } -------------------------------------------------------------------------------- /privacy.md: -------------------------------------------------------------------------------- 1 | PRIVACY POLICY MODEL FOR MOBILE APPLICATIONS 2 | This privacy policy governs your use of the software application Angular Minesweeper (“Application”) for mobile devices that was created by Stephen Fluin. The Application is a simple minesweeper game. 3 | 4 | 5 | What information does the Application obtain and how is it used? 6 | User Provided Information 7 | 8 | The Application obtains the information you provide when you download and register the Application. Registration with us is optional. However, please keep in mind that you may not be able to use some of the features offered by the Application unless you register with us. 9 | 10 | 11 | 12 | When you register with us and use the Application, you generally provide (a) your name, email address, age, user name, password and other registration information; (b) transaction-related information, such as when you make purchases, respond to any offers, or download or use applications from us; (c) information you provide us when you contact us for help; (d) credit card information for purchase and use of the Application, and; (e) information you enter into our system when using the Application, such as contact information and project management information. 13 | 14 | We may also use the information you provided us to contact your from time to time to provide you with important information, required notices and marketing promotions. 15 | 16 | Automatically Collected Information 17 | 18 | 19 | 20 | In addition, the Application may collect certain information automatically, including, but not limited to, the type of mobile device you use, your mobile devices unique device ID, the IP address of your mobile device, your mobile operating system, the type of mobile Internet browsers you use, and information about the way you use the Application. 21 | 22 | 23 | Does the Application collect precise real time location information of the device? 24 | When you visit the mobile application, we may use GPS technology (or other similar technology) to determine your current location in order to determine the city you are located within and display a location map with relevant advertisements. We will not share your current location with other users or partners. 25 | 26 | If you do not want us to use your location for the purposes set forth above, you should turn off the location services for the mobile application located in your account settings or in your mobile phone settings and/or within the mobile application. 27 | 28 | 29 | 30 | Do third parties see and/or have access to information obtained by the Application? 31 | Yes. We will share your information with third parties only in the ways that are described in this privacy statement. 32 | 33 | We may disclose User Provided and Automatically Collected Information: 34 | 35 | as required by law, such as to comply with a subpoena, or similar legal process; 36 | 37 | when we believe in good faith that disclosure is necessary to protect our rights, protect your safety or the safety of others, investigate fraud, or respond to a government request; 38 | 39 | with our trusted services providers who work on our behalf, do not have an independent use of the information we disclose to them, and have agreed to adhere to the rules set forth in this privacy statement. 40 | 41 | if Stephen Fluin is involved in a merger, acquisition, or sale of all or a portion of its assets, you will be notified via email and/or a prominent notice on our Web site of any change in ownership or uses of this information, as well as any choices you may have regarding this information. 42 | 43 | 44 | 45 | What are my opt-out rights? 46 | You can stop all collection of information by the Application easily by uninstalling the Application. You may use the standard uninstall processes as may be available as part of your mobile device or via the mobile application marketplace or network. You can also request to opt-out via email, at minesweeper@mortalpowers.com. 47 | 48 | 49 | 50 | Data Retention Policy, Managing Your Information 51 | We will retain User Provided data for as long as you use the Application and for a reasonable time thereafter. We will retain Automatically Collected information for up to 24 months and thereafter may store it in aggregate. If you’d like us to delete User Provided Data that you have provided via the Application, please contact us at minesweeper@mortalpowers.comand we will respond in a reasonable time. Please note that some or all of the User Provided Data may be required in order for the Application to function properly. 52 | 53 | 54 | 55 | Children 56 | We do not use the Application to knowingly solicit data from or market to children under the age of 13. If a parent or guardian becomes aware that his or her child has provided us with information without their consent, he or she should contact us at minesweeper@mortalpowers.com. We will delete such information from our files within a reasonable time. 57 | 58 | 59 | Security 60 | We are concerned about safeguarding the confidentiality of your information. We provide physical, electronic, and procedural safeguards to protect information we process and maintain. For example, we limit access to this information to authorized employees and contractors who need to know that information in order to operate, develop or improve our Application. Please be aware that, although we endeavor provide reasonable security for information we process and maintain, no security system can prevent all potential security breaches. 61 | 62 | 63 | 64 | Changes 65 | This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy on GitHub and making it public. You are advised to consult this Privacy Policy regularly for any changes, as continued use is deemed approval of all changes. You can check the history of this policy by visiting it on GitHub. 66 | 67 | 68 | 69 | Your Consent 70 | By using the Application, you are consenting to our processing of your information as set forth in this Privacy Policy now and as amended by us. "Processing,” means using cookies on a computer/hand held device or using or touching information in any way, including, but not limited to, collecting, storing, deleting, using, combining and disclosing information, all of which activities will take place in the United States. If you reside outside the United States your information will be transferred, processed and stored there under United States privacy standards. 71 | 72 | 73 | 74 | Contact us 75 | If you have any questions regarding privacy while using the Application, or have questions about our practices, please contact us via email at minesweeper@mortalpowers.com. 76 | --------------------------------------------------------------------------------