├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── build ├── icon.afdesign ├── icon.ico ├── icon.json ├── icon.png ├── icon.svg └── icon@2x.icns ├── doc ├── screenshot_main_menu.png ├── screenshot_sandbox.png ├── screenshot_settings.png └── screenshot_tutorial.png ├── game-of-life ├── config.json ├── configuration-page │ ├── blinker.json │ ├── color-sliders │ │ ├── color-sliders.html │ │ └── color-sliders.js │ ├── configuration-page.html │ └── configuration-page.js ├── game-of-life.html ├── game-of-life.js ├── main-menu-page │ ├── clock.json │ ├── language-selection │ │ ├── language-selection.html │ │ ├── language-selection.js │ │ └── svg │ │ │ ├── de.svg │ │ │ └── gb.svg │ ├── main-menu-page-locales.json │ ├── main-menu-page.html │ └── main-menu-page.js ├── rough-window-frame │ ├── rough-window-frame.html │ └── rough-window-frame.js ├── sandbox-mode-page │ ├── rule-bottom-drawer │ │ ├── rule-bottom-drawer.html │ │ └── rule-bottom-drawer.js │ ├── sandbox-mode-page.html │ └── sandbox-mode-page.js ├── shared │ ├── game-field │ │ ├── calculateNextCellStates │ │ │ └── calculateNextCellStates.js │ │ ├── game-field.html │ │ ├── game-field.js │ │ └── readJsonFile │ │ │ └── readJsonFile.js │ ├── rough-filled-polygon-icon │ │ ├── rough-filled-polygon-icon.html │ │ └── rough-filled-polygon-icon.js │ ├── rough-line-icon │ │ ├── rough-line-icon.html │ │ └── rough-line-icon.js │ └── rule-editor │ │ ├── rule-editor.html │ │ └── rule-editor.js └── tutorial-page │ ├── r-pentomino_1.json │ ├── r-pentomino_2.json │ ├── tutorial-page-locales.json │ ├── tutorial-page.html │ └── tutorial-page.js ├── index.html ├── main.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | bin/* 4 | dist 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | ================== 3 | 4 | Statement of Purpose 5 | --------------------- 6 | 7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 8 | 9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 10 | 11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 12 | 13 | 1. Copyright and Related Rights. 14 | -------------------------------- 15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 16 | 17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 18 | ii. moral rights retained by the original author(s) and/or performer(s); 19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 24 | 25 | 2. Waiver. 26 | ----------- 27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 28 | 29 | 3. Public License Fallback. 30 | ---------------------------- 31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 32 | 33 | 4. Limitations and Disclaimers. 34 | -------------------------------- 35 | 36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon 3 |

Cells

4 |

A Conway's Game of Life implementation

5 | forthebadge 6 | forthebadge 7 |

8 |

9 | 10 |

11 | 12 | ## 📝 Source Code 13 | 14 | In order to use this source code you need to have Node.js, Git and Bower installed globally. After that you have to follow the steps below. 15 | 16 | ```bash 17 | # Clone this repository 18 | git clone https://github.com/florianfe/Cells 19 | # Go into the repository 20 | cd Cells 21 | # Install npm dependencies 22 | npm install 23 | # Install bower dependencies 24 | bower install 25 | # Run the app in test mode 26 | npm start 27 | # build application for release 28 | npm run-script dist 29 | ``` 30 | 31 | 32 | ## 🖼 Previev 33 | 34 | Main Menu 35 | Sandbox 36 | Tutorial 37 | Settings 38 | 39 | 40 | ## 💾 Download 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
PlatformVersionDownload 46 |
Mac1.0.0Download
Windows1.0.0Download
Linux (.deb)1.0.0Download
63 | 64 | 65 | ## 📖 License 66 | [![forthebadge](http://forthebadge.com/images/badges/cc-0.svg)](https://creativecommons.org/publicdomain/zero/1.0/) 67 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cells", 3 | "description": "Conway's Game of Life implementation", 4 | "main": "main.js", 5 | "authors": [ 6 | "Florian Fechner" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "game", 11 | "of", 12 | "life", 13 | "code", 14 | "competition", 15 | "polymer", 16 | "electron" 17 | ], 18 | "homepage": "", 19 | "private": true, 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ], 27 | "dependencies": { 28 | "iron-pages": "PolymerElements/iron-pages#^2.1.1", 29 | "paper-dialog": "PolymerElements/paper-dialog#^2.1.1", 30 | "paper-input": "PolymerElements/paper-input#^2.2.2", 31 | "paper-slider": "PolymerElements/paper-slider#^2.0.6", 32 | "paper-card": "PolymerElements/paper-card#^2.1.0", 33 | "paper-button": "PolymerElements/paper-button#^2.1.1", 34 | "app-storage": "PolymerElements/app-storage#^2.1.1", 35 | "app-localize-behavior": "PolymerElements/app-localize-behavior#^2.0.1", 36 | "polymer": "Polymer/polymer#^2.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /build/icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/build/icon.afdesign -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/build/icon.ico -------------------------------------------------------------------------------- /build/icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "columns": 5, 3 | "rows": 5, 4 | "data": [ 5 | 0, 0, 0, 0, 0, 6 | 0, 1, 1, 1, 0, 7 | 0, 1, 0, 0, 0, 8 | 0, 1, 1, 1, 0, 9 | 0, 0, 0, 0, 0 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/build/icon.png -------------------------------------------------------------------------------- /build/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /build/icon@2x.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/build/icon@2x.icns -------------------------------------------------------------------------------- /doc/screenshot_main_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/doc/screenshot_main_menu.png -------------------------------------------------------------------------------- /doc/screenshot_sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/doc/screenshot_sandbox.png -------------------------------------------------------------------------------- /doc/screenshot_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/doc/screenshot_settings.png -------------------------------------------------------------------------------- /doc/screenshot_tutorial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlorianFe/Cells/4da62157ad275b944ddec63a32742cc02c03b7fd/doc/screenshot_tutorial.png -------------------------------------------------------------------------------- /game-of-life/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "de", 3 | "interval": 1000 4 | } 5 | -------------------------------------------------------------------------------- /game-of-life/configuration-page/blinker.json: -------------------------------------------------------------------------------- 1 | { 2 | "columns": 5, 3 | "rows": 5, 4 | "data": [ 5 | 0, 0, 0, 0, 0, 6 | 0, 0, 0, 0, 0, 7 | 0, 1, 1, 1, 0, 8 | 0, 0, 0, 0, 0, 9 | 0, 0, 0, 0, 0 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /game-of-life/configuration-page/color-sliders/color-sliders.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /game-of-life/configuration-page/color-sliders/color-sliders.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class ColorSliders extends Polymer.Element 4 | { 5 | static get is() 6 | { 7 | return 'color-sliders'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | color: { type: String, notify: true}, 14 | r: { type: Number, value: 128 }, 15 | g: { type: Number, value: 128 }, 16 | b: { type: Number, value: 128 } 17 | } 18 | } 19 | 20 | static get observers() 21 | { 22 | return [ 23 | "onComponentChanged(r, g, b)" 24 | ]; 25 | } 26 | 27 | onComponentChanged(r, g, b) 28 | { 29 | this.color = this.rgbToHex(this.r, this.g, this.b); 30 | 31 | // force rendering on all game fields for uniform visual effect 32 | this.shadowRoot.querySelectorAll('game-field').forEach((gameField) => gameField.render()); 33 | } 34 | 35 | rgbToHex(r, g, b) 36 | { 37 | return '#' + this.decimalToHex(r) + this.decimalToHex(g) + this.decimalToHex(b); 38 | } 39 | 40 | decimalToHex(decimal) 41 | { 42 | if(decimal < 0 || decimal > 255) return '00'; 43 | return ((decimal < 16) ? '0' : '') + decimal.toString(16); 44 | } 45 | 46 | connectedCallback() 47 | { 48 | super.connectedCallback() 49 | } 50 | } 51 | 52 | customElements.define(ColorSliders.is, ColorSliders); 53 | })(); 54 | -------------------------------------------------------------------------------- /game-of-life/configuration-page/configuration-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /game-of-life/configuration-page/configuration-page.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class ConfigurationPage extends Polymer.Element 4 | { 5 | static get is() 6 | { 7 | return 'configuration-page'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | columns: { type: Number, value: 20, notify: true }, 14 | rows: { type: Number, value: 20, notify: true }, 15 | cellColor: { type: String, value: '#0000ff', notify: true }, // in RGB - Hexadecimal Code 16 | roughness: { type: Number, value: 1.0, notify: true }, 17 | bornRules: { type: Array, value: [ 0, 0, 0, 1, 0, 0, 0, 0, 0 ], notify: true }, // 0: no change, 1: breed me 18 | dieRules: { type: Array, value: [ 1, 1, 0, 0, 1, 1, 1, 1, 1 ], notify: true } // 0: no change, 1: kill me 19 | } 20 | } 21 | 22 | static get observers() 23 | { 24 | return [ 25 | "onRoughnessChange(roughness)" 26 | ]; 27 | } 28 | 29 | connectedCallback() 30 | { 31 | super.connectedCallback(); 32 | } 33 | 34 | onRoughnessChange() 35 | { 36 | // force rendering on all game fields for uniform visual effect 37 | this.shadowRoot.querySelectorAll('game-field').forEach((gameField) => gameField.render()); 38 | } 39 | 40 | tick() 41 | { 42 | this.shadowRoot.querySelector('#example-game-field').calculateNextGeneration(); 43 | } 44 | } 45 | 46 | customElements.define(ConfigurationPage.is, ConfigurationPage); 47 | })(); 48 | -------------------------------------------------------------------------------- /game-of-life/game-of-life.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /game-of-life/game-of-life.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const CONFIGURATION = require('./game-of-life/config.json'); 4 | 5 | const MENU_PAGE = 0; 6 | const SANDBOX_MODE_PAGE = 1; 7 | const TUTORIAL_PAGE = 2; 8 | const SETTINGS_PAGE = 3; 9 | 10 | class GameOfLife extends Polymer.Element 11 | { 12 | static get is() { return 'game-of-life'; } 13 | 14 | static get properties() 15 | { 16 | return { 17 | selectedPage: { type: Number, value: 0 }, 18 | language: { type: String } 19 | } 20 | } 21 | 22 | connectedCallback() 23 | { 24 | super.connectedCallback(); 25 | 26 | // just for the www.it-talents.de challange 27 | this.language = (CONFIGURATION.language) ? CONFIGURATION.language : 'en'; 28 | 29 | let ironPages = this.shadowRoot.querySelector('iron-pages'); 30 | let mainMenuPage = this.shadowRoot.querySelector('main-menu-page'); 31 | let windowFrame = this.shadowRoot.querySelector('rough-window-frame'); 32 | 33 | // centralized interval 34 | setInterval(() => ironPages.selectedItem.tick(), CONFIGURATION.interval); 35 | 36 | mainMenuPage.addEventListener('sandbox-mode-selected', () => { this.selectedPage = SANDBOX_MODE_PAGE; }); 37 | mainMenuPage.addEventListener('tutorial-selected', () => { this.selectedPage = TUTORIAL_PAGE; }); 38 | mainMenuPage.addEventListener('settings-selected', () => { this.selectedPage = SETTINGS_PAGE; }); 39 | 40 | windowFrame.addEventListener('back-to-menu', () => { this.selectedPage = MENU_PAGE; }) 41 | } 42 | } 43 | 44 | customElements.define(GameOfLife.is, GameOfLife); 45 | })(); 46 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/clock.json: -------------------------------------------------------------------------------- 1 | { 2 | "columns": 6, 3 | "rows": 6, 4 | "data": [ 5 | 0, 0, 0, 0, 0, 0, 6 | 0, 0, 1, 0, 0, 0, 7 | 0, 0, 0, 1, 1, 0, 8 | 0, 1, 1, 0, 0, 0, 9 | 0, 0, 0, 1, 0, 0, 10 | 0, 0, 0, 0, 0, 0 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/language-selection/language-selection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/language-selection/language-selection.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class LanguageSelection extends Polymer.Element 4 | { 5 | static get is() 6 | { 7 | return 'language-selection'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | language: { type: String, value: 'en', notify: true } 14 | } 15 | } 16 | 17 | connectedCallback() 18 | { 19 | super.connectedCallback(); 20 | 21 | this.shadowRoot.querySelector('#language-button').addEventListener('click', () => 22 | { 23 | this.language = (this.language === 'en') ? 'de' : 'en'; 24 | }); 25 | } 26 | 27 | isLanguageSelected(language, iso) 28 | { 29 | return (language === iso); 30 | } 31 | } 32 | 33 | customElements.define(LanguageSelection.is, LanguageSelection); 34 | })(); 35 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/language-selection/svg/de.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | 26 | 27 | image/svg+xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/language-selection/svg/gb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | 28 | 29 | 30 | 31 | image/svg+xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/main-menu-page-locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": 3 | { 4 | "sandbox mode": "Sandbox Mode", 5 | "tutorial": "Tutorial", 6 | "settings": "Settings" 7 | }, 8 | "de": 9 | { 10 | "sandbox mode": "Sandkasten Modus", 11 | "tutorial": "Anleitung", 12 | "settings": "Einstellungen" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/main-menu-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /game-of-life/main-menu-page/main-menu-page.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class MainMenuPage extends Polymer.mixinBehaviors([Polymer.AppLocalizeBehavior], Polymer.Element) 4 | { 5 | static get is() 6 | { 7 | return 'main-menu-page'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | cellColor: { type: String, value: '#0000ff' }, // in RGB - Hexadecimal Code 14 | roughness: { type: Number, value: 0.5 }, 15 | language: { type: String, notify: true } 16 | } 17 | } 18 | 19 | connectedCallback() 20 | { 21 | super.connectedCallback(); 22 | 23 | this.loadResources(this.resolveUrl('main-menu-page-locales.json')); 24 | 25 | let buttonSandBoxMode = this.shadowRoot.querySelector('#button-sandbox-mode'); 26 | let buttonTutorial = this.shadowRoot.querySelector('#button-tutorial'); 27 | let buttonSettings = this.shadowRoot.querySelector('#button-settings'); 28 | 29 | buttonSandBoxMode.addEventListener('click', () => this.dispatchEvent(new CustomEvent('sandbox-mode-selected', {}))); 30 | buttonTutorial.addEventListener('click', () => this.dispatchEvent(new CustomEvent('tutorial-selected', {}))); 31 | buttonSettings.addEventListener('click', () => this.dispatchEvent(new CustomEvent('settings-selected', {}))); 32 | } 33 | 34 | tick() 35 | { 36 | this.shadowRoot.querySelector('game-field').calculateNextGeneration(); 37 | } 38 | } 39 | 40 | customElements.define(MainMenuPage.is, MainMenuPage); 41 | })(); 42 | -------------------------------------------------------------------------------- /game-of-life/rough-window-frame/rough-window-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /game-of-life/rough-window-frame/rough-window-frame.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const { remote } = require('electron'); 4 | 5 | class RoughWindowFrame extends Polymer.Element 6 | { 7 | static get is() 8 | { 9 | return 'rough-window-frame'; 10 | } 11 | 12 | static get properties() 13 | { 14 | return { 15 | roughness: { type: Number, value: 1.0 } 16 | } 17 | } 18 | 19 | connectedCallback() 20 | { 21 | super.connectedCallback(); 22 | 23 | this.shadowRoot.querySelector('#menu-icon').addEventListener('click', () => this.dispatchEvent(new CustomEvent('back-to-menu', {}))) 24 | this.shadowRoot.querySelector('#close-icon').addEventListener('click', () => remote.BrowserWindow.getFocusedWindow().close()); 25 | this.shadowRoot.querySelector('#minimize-icon').addEventListener('click', () => remote.BrowserWindow.getFocusedWindow().minimize()); 26 | } 27 | } 28 | 29 | customElements.define(RoughWindowFrame.is, RoughWindowFrame); 30 | })(); 31 | -------------------------------------------------------------------------------- /game-of-life/sandbox-mode-page/rule-bottom-drawer/rule-bottom-drawer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /game-of-life/sandbox-mode-page/rule-bottom-drawer/rule-bottom-drawer.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class RuleBottomDrawer extends Polymer.Element 4 | { 5 | static get is() 6 | { 7 | return 'rule-bottom-drawer'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | opened: { type: Boolean, value: false }, 14 | roughness: { type: Number, value: 1.0 }, 15 | bornRules: { type: Array, value: [0, 0, 0, 1, 0, 0, 0, 0, 0], notify: true }, 16 | dieRules: { type: Array, value: [1, 1, 0, 0, 1, 1, 1, 1, 1], notify: true } 17 | } 18 | } 19 | 20 | connectedCallback() 21 | { 22 | super.connectedCallback(); 23 | 24 | this.shadowRoot.querySelector('#open-close-icon').addEventListener('click', () => (this.opened = !this.opened)); 25 | } 26 | 27 | static get observers() 28 | { 29 | return [ 30 | "openedChanged(opened)" 31 | ]; 32 | } 33 | 34 | openedChanged() 35 | { 36 | this.style.bottom = (this.opened) ? "0px" : "-140px"; 37 | } 38 | 39 | not(value) 40 | { 41 | return !value; 42 | } 43 | } 44 | 45 | customElements.define(RuleBottomDrawer.is, RuleBottomDrawer); 46 | })(); 47 | -------------------------------------------------------------------------------- /game-of-life/sandbox-mode-page/sandbox-mode-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /game-of-life/sandbox-mode-page/sandbox-mode-page.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | class SandboxModePage extends Polymer.Element 4 | { 5 | static get is() 6 | { 7 | return 'sandbox-mode-page'; 8 | } 9 | 10 | static get properties() 11 | { 12 | return { 13 | cellColor: { type: String, value: '#0000ff' }, // in RGB - Hexadecimal Code 14 | roughness: { type: Number, value: 1.0 }, 15 | running: { type: Boolean, value: false } 16 | } 17 | } 18 | 19 | constructor() 20 | { 21 | super(); 22 | } 23 | 24 | connectedCallback() 25 | { 26 | super.connectedCallback(); 27 | 28 | this.shadowRoot.querySelector('#play-button').addEventListener('click', () => 29 | { 30 | this.running = !this.running; 31 | }); 32 | } 33 | 34 | not(value) 35 | { 36 | return !value; 37 | } 38 | 39 | tick() 40 | { 41 | if(this.running) this.shadowRoot.querySelector('game-field').calculateNextGeneration(); 42 | } 43 | } 44 | 45 | customElements.define(SandboxModePage.is, SandboxModePage); 46 | })(); 47 | -------------------------------------------------------------------------------- /game-of-life/shared/game-field/calculateNextCellStates/calculateNextCellStates.js: -------------------------------------------------------------------------------- 1 | 2 | const zeros = require('zeros'); //from https://github.com/scijs/zeros 3 | 4 | const NEIGHBOURHOOD_VECTORS = [ 5 | [-1, -1], [0, -1], [+1, -1], 6 | [-1, 0], [+1, 0], 7 | [-1, +1], [0, +1], [+1, +1] 8 | ]; 9 | 10 | module.exports = (cellStates, bornRules, dieRules) => 11 | { 12 | const sx = cellStates.shape[0]; // size in x direction 13 | const sy = cellStates.shape[1]; // size in y direction 14 | 15 | let nextStates = zeros([sx, sy]); // create 2d - array filled with zeros 16 | 17 | for(let x=0; x 38 | getBorderInvariantLivingStatusOfCell(x + vector[0], y + vector[1], cellStates) 39 | ); 40 | 41 | return neighbours.reduce((sum, summand) => (sum + summand), 0); 42 | } 43 | 44 | function getBorderInvariantLivingStatusOfCell(x, y, cellStates) 45 | { 46 | const sx = cellStates.shape[0]; // size in x direction 47 | const sy = cellStates.shape[1]; // size in y direction 48 | 49 | while(x < 0) x += sx; // enforces x > 0 50 | while(y < 0) y += sy; // enforces y > 0 51 | 52 | const borderInvariantX = x % sx; // enforces x < sx 53 | const borderInvariantY = y % sy; // enforces y < sy 54 | 55 | return cellStates.get(borderInvariantX, borderInvariantY); 56 | } 57 | -------------------------------------------------------------------------------- /game-of-life/shared/game-field/game-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /game-of-life/shared/game-field/game-field.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const path = require('path'); 4 | const rough = require('roughjs'); // from https://roughjs.com/ 5 | const ndarray = require('ndarray'); // from https://github.com/scijs/ndarray 6 | const zeros = require('zeros'); //from https://github.com/scijs/zeros 7 | 8 | // relative to main.js 9 | const IMPORT_PATH = './game-of-life/shared/game-field/'; 10 | 11 | const calculateNextCellStates = require(IMPORT_PATH + 'calculateNextCellStates/calculateNextCellStates'); 12 | const readJsonFile = require(IMPORT_PATH + 'readJsonFile/readJsonFile'); 13 | 14 | const CELL_SIZE = 50; 15 | 16 | class GameField extends Polymer.Element 17 | { 18 | static get is() 19 | { 20 | return 'game-field'; 21 | } 22 | 23 | static get properties() 24 | { 25 | return { 26 | columns: { type: Number, value: 5 }, 27 | rows: { type: Number, value: 5 }, 28 | editable: { type: Boolean, value: false}, 29 | cellStates: { type: Object }, 30 | cellStatesData: { type: Array }, // 0: dead, 1: alive 31 | jsonFilePath: { type: String }, // relative to index.html 32 | cellColor: { type: String, value: '#0000ff' }, // in RGB - Hexadecimal Code 33 | roughness: { type: Number, value: 1.0 }, // Roughness of Cell (roughjs) 34 | bornRules: { type: Array, value : [ 0, 0, 0, 1, 0, 0, 0, 0, 0 ] }, // 0: no change, 1: breed me 35 | dieRules: { type: Array, value : [ 1, 1, 0, 0, 1, 1, 1, 1, 1 ] } // 0: no change, 1: kill me 36 | } 37 | } 38 | 39 | static get observers() 40 | { 41 | return [ 42 | "render(cellColor, roughness)", 43 | "loadJson(jsonFilePath)", 44 | "setup(cellStatesData)" 45 | ]; 46 | } 47 | 48 | connectedCallback() 49 | { 50 | super.connectedCallback(); 51 | 52 | this.setup(); 53 | 54 | this.addEventListener('click', (e) => 55 | { 56 | if(this.editable) 57 | { 58 | let globalX = e.pageX; 59 | let globalY = e.pageY; 60 | 61 | const svg = this.shadowRoot.querySelector('#field'); 62 | const bounds = e.target.getBoundingClientRect(); 63 | 64 | const localX = globalX - bounds.left; 65 | const localY = globalY - bounds.top; 66 | 67 | const sx = this.cellStates.shape[0]; // size in x direction 68 | const sy = this.cellStates.shape[1]; // size in y direction 69 | 70 | const svgWidth = svg.width.animVal.value; 71 | const svgHeight = svg.height.animVal.value 72 | 73 | const cellWidth = svgWidth / (sx + 1); 74 | const cellHeight = svgHeight / (sy + 1); 75 | 76 | const centerX = (localX / cellWidth) - 0.5; 77 | const centerY = (localY / cellHeight) - 0.5; 78 | 79 | const cellX = (centerX > 0) ? parseInt(centerX) : -1; 80 | const cellY = (centerY > 0) ? parseInt(centerY) : -1; 81 | 82 | if(this.isCoordinateInsideGameField(cellX, cellY, sx, sy)) 83 | { 84 | const cellState = this.cellStates.get(cellX, cellY); 85 | 86 | this.cellStates.set(cellX, cellY, !cellState); 87 | 88 | this.render(); 89 | } 90 | } 91 | }); 92 | } 93 | 94 | setup() 95 | { 96 | if(this.cellStatesData) 97 | { 98 | // create 2d - array with array data 99 | const raw = ndarray(new Int8Array(this.cellStatesData), [this.rows, this.columns]); 100 | 101 | this.cellStates = raw.transpose(1, 0); 102 | } 103 | else 104 | { 105 | // create 2d - array filled with zeros 106 | this.cellStates = zeros([this.columns, this.rows]); 107 | } 108 | 109 | this.render(); 110 | } 111 | 112 | loadJson() 113 | { 114 | readJsonFile(path.join(this.rootPath, this.jsonFilePath), (json) => 115 | { 116 | const obj = JSON.parse(json); 117 | 118 | this.columns = obj.columns; 119 | this.rows = obj.rows; 120 | this.cellStatesData = obj.data; 121 | }); 122 | } 123 | 124 | calculateNextGeneration() 125 | { 126 | this.cellStates = calculateNextCellStates(this.cellStates, this.bornRules, this.dieRules); 127 | 128 | this.render(); 129 | } 130 | 131 | isCoordinateInsideGameField(x, y, sx, sy) 132 | { 133 | if(x < 0 || y < 0) return false; 134 | if(x > sx - 1 || y > sy - 1) return false; 135 | return true; 136 | } 137 | 138 | render() 139 | { 140 | if(this.cellStates) 141 | { 142 | const sx = this.cellStates.shape[0]; // size in x direction 143 | const sy = this.cellStates.shape[1]; // size in y direction 144 | 145 | const fieldWidth = (sx + 1) * CELL_SIZE; 146 | const fieldHeight = (sy + 1) * CELL_SIZE; 147 | 148 | const svg = this.shadowRoot.querySelector('#field'); 149 | 150 | svg.setAttribute('viewBox', '0 0 ' + fieldWidth + ' ' + fieldHeight); 151 | svg.innerHTML = ''; // clean SVG - Element 152 | 153 | const rc = rough.svg(svg); 154 | 155 | const livingConfig = { roughness: this.roughness, fill: this.cellColor, fillWeight: 1.5 }; 156 | const deadConfig = { roughness: this.roughness }; 157 | 158 | for(let x=0; x 3 | { 4 | let rawFile = new XMLHttpRequest(); 5 | 6 | rawFile.overrideMimeType("application/json"); 7 | rawFile.open("GET", file, true); 8 | 9 | rawFile.onreadystatechange = () => 10 | { 11 | if(rawFile.readyState === 4 && rawFile.status == "200") 12 | { 13 | callback(rawFile.responseText); 14 | } 15 | } 16 | 17 | rawFile.send(null); 18 | } 19 | -------------------------------------------------------------------------------- /game-of-life/shared/rough-filled-polygon-icon/rough-filled-polygon-icon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /game-of-life/shared/rough-filled-polygon-icon/rough-filled-polygon-icon.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const rough = require('roughjs'); 4 | 5 | const draw = (svg, polygons, config) => 6 | { 7 | svg.innerHTML = ''; 8 | 9 | const rc = rough.svg(svg); 10 | 11 | polygons.forEach((polygon) => svg.appendChild(rc.polygon(polygon, config))); 12 | } 13 | 14 | class RoughFilledPolygonIcon extends Polymer.Element 15 | { 16 | static get is() 17 | { 18 | return 'rough-filled-polygon-icon'; 19 | } 20 | 21 | static get properties() 22 | { 23 | return { 24 | roughness: { type: Number, value: 1.0 }, 25 | fill: { type: String, value: '#000000' }, 26 | hoverColor: { type: String, value: '#aaaaaa' }, 27 | polygons: { type: Array, value: [ ] } 28 | } 29 | } 30 | 31 | static get observers() 32 | { 33 | return [ 34 | "render(roughness)" 35 | ]; 36 | } 37 | 38 | connectedCallback() 39 | { 40 | super.connectedCallback(); 41 | 42 | let svg = this.shadowRoot.querySelector('svg'); 43 | 44 | this.addEventListener('mouseover', () => draw(svg, this.polygons, { stroke: this.hoverColor, fill: this.hoverColor, roughness: this.roughness })); 45 | this.addEventListener('mouseout', () => draw(svg, this.polygons, { stroke: this.fill, fill: this.fill, roughness: this.roughness })); 46 | 47 | draw(svg, this.polygons, { stroke: this.fill, fill: this.fill, roughness: this.roughness }); 48 | } 49 | 50 | render() 51 | { 52 | let svg = this.shadowRoot.querySelector('svg'); 53 | 54 | draw(svg, this.polygons, { stroke: this.fill, fill: this.fill, roughness: this.roughness }); 55 | } 56 | } 57 | 58 | customElements.define(RoughFilledPolygonIcon.is, RoughFilledPolygonIcon); 59 | })(); 60 | -------------------------------------------------------------------------------- /game-of-life/shared/rough-line-icon/rough-line-icon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /game-of-life/shared/rough-line-icon/rough-line-icon.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const rough = require('roughjs'); 4 | 5 | const draw = (svg, lines, config) => 6 | { 7 | svg.innerHTML = ''; 8 | 9 | const rc = rough.svg(svg); 10 | 11 | lines.forEach((line) => svg.appendChild(rc.line(line.x1, line.y1, line.x2, line.y2, config))); 12 | } 13 | 14 | class RoughLineIcon extends Polymer.Element 15 | { 16 | static get is() 17 | { 18 | return 'rough-line-icon'; 19 | } 20 | 21 | static get properties() 22 | { 23 | return { 24 | roughness: { type: Number, value: 1.0 }, 25 | hoverColor: { type: String, value: '#aaaaaa' }, 26 | lines: { type: Array, value: [ ] } 27 | } 28 | } 29 | 30 | static get observers() 31 | { 32 | return [ 33 | "render(roughness)" 34 | ]; 35 | } 36 | 37 | connectedCallback() 38 | { 39 | super.connectedCallback(); 40 | 41 | let svg = this.shadowRoot.querySelector('svg'); 42 | 43 | this.addEventListener('mouseover', () => draw(svg, this.lines, { stroke: this.hoverColor, roughness: this.roughness })); 44 | this.addEventListener('mouseout', () => draw(svg, this.lines, { stroke: '#000000', roughness: this.roughness })); 45 | 46 | draw(svg, this.lines, { stroke: '#000000', roughness: this.roughness }); 47 | } 48 | 49 | render() 50 | { 51 | let svg = this.shadowRoot.querySelector('svg'); 52 | 53 | draw(svg, this.lines, { stroke: '#000000', roughness: this.roughness }); 54 | } 55 | } 56 | 57 | customElements.define(RoughLineIcon.is, RoughLineIcon); 58 | })(); 59 | -------------------------------------------------------------------------------- /game-of-life/shared/rule-editor/rule-editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /game-of-life/shared/rule-editor/rule-editor.js: -------------------------------------------------------------------------------- 1 | (() => 2 | { 3 | const rough = require('roughjs'); 4 | 5 | const CELL_SIZE = 75; 6 | const AMOUNT_OF_CELL_NEIGHBOUR_STATES = 9; 7 | const CIRCLE_DIAMETER = 30; 8 | 9 | class RuleEditor extends Polymer.Element 10 | { 11 | static get is() 12 | { 13 | return 'rule-editor'; 14 | } 15 | 16 | static get properties() 17 | { 18 | return { 19 | editable: { type: Boolean, value: false }, 20 | roughness: { type: Number, value: 1.0 }, 21 | bornRules: { type: Array, value: [0, 0, 0, 1, 0, 0, 0, 0, 0], notify: true }, 22 | dieRules: { type: Array, value: [1, 1, 0, 0, 1, 1, 1, 1, 1], notify: true } 23 | } 24 | } 25 | 26 | static get observers() 27 | { 28 | return [ 29 | "render(cellColor, roughness)" 30 | ]; 31 | } 32 | 33 | connectedCallback() 34 | { 35 | super.connectedCallback(); 36 | 37 | const svg = this.shadowRoot.querySelector('svg'); 38 | 39 | svg.addEventListener('click', (e) => 40 | { 41 | if(this.editable) 42 | { 43 | const globalX = e.pageX; 44 | const globalY = e.pageY; 45 | 46 | const bounds = svg.getBoundingClientRect(); 47 | 48 | const svgWidth = svg.width.animVal.value; 49 | const svgHeight = svg.height.animVal.value; 50 | 51 | const scaleX = 700 / svgWidth; 52 | const scaleY = 200 / svgHeight; 53 | 54 | const localX = scaleX * (globalX - bounds.left); 55 | const localY = scaleY * (globalY - bounds.top); 56 | 57 | for(let i=0; i < AMOUNT_OF_CELL_NEIGHBOUR_STATES; i++) 58 | { 59 | { 60 | const centerPointX = CELL_SIZE * i + 50; 61 | const centerPointY = 100; 62 | 63 | const diffX = centerPointX - localX; 64 | const diffY = centerPointY - localY; 65 | 66 | const distance = Math.sqrt(diffX ** 2 + diffY ** 2); 67 | const circleRadius = CIRCLE_DIAMETER / 2; 68 | 69 | if(distance <= circleRadius) 70 | { 71 | this.set(["bornRules", i], (this.bornRules[i]) ? 0 : 1); 72 | 73 | this.render(); 74 | } 75 | } 76 | 77 | { 78 | const centerPointX = CELL_SIZE * i + 50; 79 | const centerPointY = 150; 80 | 81 | const diffX = centerPointX - localX; 82 | const diffY = centerPointY - localY; 83 | 84 | const distance = Math.sqrt(diffX ** 2 + diffY ** 2); 85 | const circleRadius = CIRCLE_DIAMETER / 2; 86 | 87 | if(distance <= circleRadius) 88 | { 89 | this.set(["dieRules", i], (this.dieRules[i]) ? 0 : 1); 90 | 91 | this.render(); 92 | } 93 | } 94 | } 95 | } 96 | }); 97 | 98 | this.render(); 99 | } 100 | 101 | render() 102 | { 103 | const svg = this.shadowRoot.querySelector('svg'); 104 | svg.innerHTML = ''; // clean SVG - Element 105 | 106 | const rc = rough.svg(svg); 107 | 108 | const oneConfig1 = { roughness: this.roughness, fill: '#8bc34a', stroke: '#8bc34a' }; 109 | const oneConfig2 = { roughness: this.roughness, fill: '#ff5722', stroke: '#ff5722' }; 110 | const zeroConfig = { roughness: this.roughness }; 111 | 112 | for(let i=0; i < AMOUNT_OF_CELL_NEIGHBOUR_STATES; i++) 113 | { 114 | const config1 = (this.bornRules[i]) ? oneConfig1 : zeroConfig; 115 | const config2 = (this.dieRules[i]) ? oneConfig2 : zeroConfig; 116 | 117 | this.drawStrokes(svg, rc, i); 118 | 119 | const node1 = rc.circle(CELL_SIZE * i + 50, 100, CIRCLE_DIAMETER, config1); 120 | const node2 = rc.circle(CELL_SIZE * i + 50, 150, CIRCLE_DIAMETER, config2); 121 | 122 | svg.appendChild(node1); 123 | svg.appendChild(node2); 124 | } 125 | } 126 | 127 | drawStrokes(svg, rc, number) 128 | { 129 | if(number >= 5) 130 | { 131 | const x1 = CELL_SIZE * number - number * 3 - 2 + 50; 132 | const x2 = CELL_SIZE * number - number * 3 + 22 + 50; 133 | 134 | const node = rc.line(x1, 25, x2, 60, { roughness: this.roughness }); 135 | svg.appendChild(node); 136 | } 137 | 138 | for(let i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /game-of-life/tutorial-page/tutorial-page.js: -------------------------------------------------------------------------------- 1 | 2 | (() => 3 | { 4 | class TutorialPage extends Polymer.mixinBehaviors([Polymer.AppLocalizeBehavior], Polymer.Element) 5 | { 6 | static get is() 7 | { 8 | return 'tutorial-page'; 9 | } 10 | 11 | static get properties() 12 | { 13 | return { 14 | 15 | } 16 | } 17 | 18 | connectedCallback() 19 | { 20 | super.connectedCallback(); 21 | 22 | this.loadResources(this.resolveUrl('tutorial-page-locales.json')); 23 | } 24 | 25 | tick() 26 | { 27 | // method does nothing 28 | } 29 | } 30 | 31 | customElements.define(TutorialPage.is, TutorialPage); 32 | })(); 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Game of Life 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | 3 | const app = electron.app; 4 | const BrowserWindow = electron.BrowserWindow; 5 | const globalShortcut = electron.globalShortcut; 6 | const ipcMain = electron.ipcMain; 7 | 8 | const Menu = electron.Menu; 9 | 10 | const path = require('path'); 11 | const url = require('url'); 12 | 13 | let browserWindow = null; 14 | 15 | function createWindow() 16 | { 17 | browserWindow = new BrowserWindow( 18 | { 19 | width: 650, 20 | height: 650, 21 | backgroundColor: '#fff', 22 | frame: false 23 | }); 24 | 25 | browserWindow.loadURL(path.join('file://', __dirname, 'index.html')); 26 | 27 | browserWindow.on('closed', () => 28 | { 29 | browserWindow = null; 30 | }); 31 | 32 | const template = 33 | [ 34 | { 35 | label: 'Edit', 36 | submenu: [ 37 | {role: 'undo'}, 38 | {role: 'redo'}, 39 | {type: 'separator'}, 40 | {role: 'cut'}, 41 | {role: 'copy'}, 42 | {role: 'paste'}, 43 | {role: 'pasteandmatchstyle'}, 44 | {role: 'delete'}, 45 | {role: 'selectall'} 46 | ] 47 | }, 48 | { 49 | label: 'View', 50 | submenu: [ 51 | {role: 'reload'}, 52 | {role: 'forcereload'}, 53 | {role: 'toggledevtools'}, 54 | {type: 'separator'}, 55 | {role: 'resetzoom'}, 56 | {role: 'zoomin'}, 57 | {role: 'zoomout'}, 58 | {type: 'separator'}, 59 | {role: 'togglefullscreen'} 60 | ] 61 | }, 62 | { 63 | role: 'window', 64 | submenu: [ 65 | {role: 'minimize'}, 66 | {role: 'close'} 67 | ] 68 | }, 69 | { 70 | role: 'help', 71 | submenu: [ 72 | { 73 | label: 'Learn More', 74 | click () { require('electron').shell.openExternal('https://github.com/FlorianFe/Cells') } 75 | } 76 | ] 77 | } 78 | ] 79 | 80 | if (process.platform === 'darwin') { 81 | template.unshift({ 82 | label: app.getName(), 83 | submenu: [ 84 | {role: 'about'}, 85 | {type: 'separator'}, 86 | {role: 'services', submenu: []}, 87 | {type: 'separator'}, 88 | {role: 'hide'}, 89 | {role: 'hideothers'}, 90 | {role: 'unhide'}, 91 | {type: 'separator'}, 92 | {role: 'quit'} 93 | ] 94 | }) 95 | 96 | // Edit menu 97 | template[1].submenu.push( 98 | {type: 'separator'}, 99 | { 100 | label: 'Speech', 101 | submenu: [ 102 | {role: 'startspeaking'}, 103 | {role: 'stopspeaking'} 104 | ] 105 | } 106 | ) 107 | 108 | // Window menu 109 | template[3].submenu = [ 110 | {role: 'close'}, 111 | {role: 'minimize'}, 112 | {role: 'zoom'}, 113 | {type: 'separator'}, 114 | {role: 'front'} 115 | ] 116 | } 117 | 118 | let menu = Menu.buildFromTemplate(template); 119 | Menu.setApplicationMenu(menu); 120 | } 121 | 122 | app.on('ready', () => 123 | { 124 | createWindow(); 125 | }); 126 | 127 | app.on('activate', function () 128 | { 129 | if (browserWindow === null) 130 | { 131 | createWindow() 132 | } 133 | }); 134 | 135 | app.on('window-all-closed', () => 136 | { 137 | if(process.platform !== 'darwin') 138 | { 139 | app.quit() 140 | } 141 | }); 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cells", 3 | "version": "1.0.0", 4 | "description": "Conway's Game of Life implementation", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron .", 8 | "pack": "build --dir", 9 | "dist": "build" 10 | }, 11 | "build": { 12 | "appId": "de.florianfe.gameoflife", 13 | "mac": { 14 | "category": "public.app-category.games", 15 | "icon": "icon@2x" 16 | }, 17 | "win": { 18 | "icon": "build/icon" 19 | } 20 | }, 21 | "repository": "https://github.com/FlorianFe/", 22 | "keywords": [ 23 | "translation", 24 | "polymer", 25 | "electron" 26 | ], 27 | "author": "Florian Fechner", 28 | "license": "MIT", 29 | "devDependencies": { 30 | "electron": "^9.1.2", 31 | "electron-builder": "^20.8.1" 32 | }, 33 | "dependencies": { 34 | "electron-localshortcut": "^3.1.0", 35 | "ipc": "0.0.1", 36 | "ndarray": "^1.0.18", 37 | "npm": "^6.1.0", 38 | "path": "^0.12.7", 39 | "roughjs": "^2.1.3", 40 | "zeros": "^1.0.0" 41 | } 42 | } 43 | --------------------------------------------------------------------------------