├── public ├── favicon.ico ├── favicon.png ├── social-image.png ├── icons │ ├── launcher-icon-1x.png │ ├── launcher-icon-2x.png │ └── launcher-icon-4x.png ├── manifest.json ├── shapesLibrary.json └── index.html ├── src ├── media │ ├── icon-play.svg │ ├── icon-right-arrow.svg │ ├── icon-pause.svg │ ├── icon-cross.svg │ ├── icon-facebook.svg │ ├── icon-twitter.svg │ ├── icon-link.svg │ ├── icon-reset.svg │ └── icon-github.svg ├── controls │ ├── inputs │ │ ├── index.js │ │ ├── Checkbox.js │ │ └── Select.js │ ├── Counter.js │ ├── ControlButton.js │ ├── modals │ │ ├── ImportShapeModal.js │ │ ├── AboutModal.js │ │ ├── SaveShapeModal.js │ │ └── ManageShapesModal.js │ └── Controls.js ├── index.js ├── test │ └── App.test.js ├── css │ ├── index.css │ ├── App.css │ ├── controls │ │ ├── ControlButton.css │ │ ├── inputs.css │ │ ├── Modal.css │ │ └── Controls.css │ └── Footer.css ├── config │ ├── colorThemes.js │ ├── configHandler.js │ └── defaultConfig.js ├── Footer.js ├── notifications.js ├── shapesHandler.js ├── utils.js └── App.js ├── CHANGELOG.md ├── .gitignore ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── env.js ├── paths.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── scripts ├── test.js ├── build.js └── start.js ├── LICENSE ├── README.md ├── CONTRIBUTING.md └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/social-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/social-image.png -------------------------------------------------------------------------------- /public/icons/launcher-icon-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/icons/launcher-icon-1x.png -------------------------------------------------------------------------------- /public/icons/launcher-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/icons/launcher-icon-2x.png -------------------------------------------------------------------------------- /public/icons/launcher-icon-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarcadass/yagol/HEAD/public/icons/launcher-icon-4x.png -------------------------------------------------------------------------------- /src/media/icon-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icon-right-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/controls/inputs/index.js: -------------------------------------------------------------------------------- 1 | import Checkbox from './Checkbox' 2 | import Select from './Select' 3 | import '../../css/controls/inputs.css' 4 | 5 | export { Checkbox, Select } 6 | -------------------------------------------------------------------------------- /src/media/icon-pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import './css/index.css' 5 | 6 | ReactDOM.render(, document.getElementById('app')) 7 | -------------------------------------------------------------------------------- /src/media/icon-cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/media/icon-facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.1.0 2 | 3 | ### New Shape 4 | 5 | - Add **Double Glider** shape ([@earl](https://github.com/earle)) 6 | 7 | ### Miscellaneous 8 | 9 | - Update dependencies 10 | - Update README 11 | - Add CHANGELOG 12 | -------------------------------------------------------------------------------- /src/test/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from '../App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | }) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # misc 10 | .DS_Store 11 | .env 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | .idea/ 16 | *.code-workspace 17 | -------------------------------------------------------------------------------- /src/css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: fixed; 4 | overflow: hidden; 5 | } 6 | 7 | body { 8 | width: 100%; 9 | height: 100%; 10 | margin: 0; 11 | padding: 0; 12 | font-family: "Roboto Mono", monospace; 13 | font-size: 100%; 14 | color: #fff; 15 | background-color: #333; 16 | touch-action: none; 17 | } 18 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/controls/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import utils from '../utils' 3 | 4 | export default class Counter extends Component { 5 | render() { 6 | let count = utils.formatDigits(this.props.iterationCount) 7 | let label = this.props.iterationCount > 1 ? ' iterations' : ' iteration' 8 | return (
{count + label}
) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/css/App.css: -------------------------------------------------------------------------------- 1 | .main-wrapper { 2 | position: absolute; 3 | display: flex; 4 | width: 100%; 5 | height: 100%; 6 | align-items: center; 7 | justify-content: center; 8 | overflow: hidden; 9 | user-select: none; 10 | } 11 | 12 | #game { 13 | display: block; 14 | margin-top: -28px; 15 | cursor: crosshair; 16 | touch-action: none; 17 | } 18 | 19 | .ReactModalPortal { 20 | position: relative; 21 | z-index: 2; 22 | } 23 | 24 | a { 25 | color: inherit; 26 | } 27 | -------------------------------------------------------------------------------- /src/css/controls/ControlButton.css: -------------------------------------------------------------------------------- 1 | .play-cta { 2 | width: 80px; 3 | } 4 | 5 | .play-cta .icon { 6 | width: .75em; 7 | margin-right: 5px; 8 | } 9 | 10 | .options-cta .icon { 11 | width: .75em; 12 | transform: rotate(90deg) scale(1); 13 | margin-right: 5px; 14 | transition: transform .3s; 15 | } 16 | 17 | .options-cta.open .icon { 18 | transform: rotate(90deg) scale(-1); 19 | } 20 | 21 | .reset-cta .icon { 22 | width: 1em; 23 | transform: translateY(1px); 24 | margin-right: 4px; 25 | } 26 | -------------------------------------------------------------------------------- /src/media/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'test'; 4 | process.env.PUBLIC_URL = ''; 5 | 6 | // Load environment variables from .env file. Suppress warnings using silent 7 | // if this file is missing. dotenv will never modify any environment variables 8 | // that have already been set. 9 | // https://github.com/motdotla/dotenv 10 | require('dotenv').config({silent: true}); 11 | 12 | const jest = require('jest'); 13 | const argv = process.argv.slice(2); 14 | 15 | // Watch unless on CI or in coverage mode 16 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 17 | argv.push('--watch'); 18 | } 19 | 20 | 21 | jest.run(argv); 22 | -------------------------------------------------------------------------------- /src/media/icon-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Yagol", 3 | "name": "Yagol - Game of Life sandbox", 4 | "author": "Benjamin Blonde", 5 | "icons": [ 6 | { 7 | "src": "icons/launcher-icon-1x.png", 8 | "type": "image/png", 9 | "sizes": "48x48" 10 | }, 11 | { 12 | "src": "icons/launcher-icon-2x.png", 13 | "type": "image/png", 14 | "sizes": "96x96" 15 | }, 16 | { 17 | "src": "icons/launcher-icon-4x.png", 18 | "type": "image/png", 19 | "sizes": "192x192" 20 | } 21 | ], 22 | "start_url": "index.html?launcher=true", 23 | "display": "standalone", 24 | "background_color": "#eee", 25 | "theme_color": "#eee" 26 | } -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | -------------------------------------------------------------------------------- /src/media/icon-reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/config/colorThemes.js: -------------------------------------------------------------------------------- 1 | /* Color themes */ 2 | export default { 3 | 'default (dark)': { 4 | background: '#252525', 5 | deadCell: '#373737', 6 | aliveCell: '#007300', 7 | cellText: '#000', 8 | cellGraphText: '#fff', 9 | footerText: '#fff', 10 | controlsBackground: 'rgba(0,0,0,.75)', 11 | buttonHoverBackground: '#252525', 12 | controlsText: '#fff', 13 | controlsBorder: '#444' 14 | }, 15 | 16 | 'default (light)': { 17 | background: '#eee', 18 | deadCell: '#ddd', 19 | aliveCell: '#59d459', 20 | cellText: '#555', 21 | cellGraphText: '#000', 22 | footerText: '#000', 23 | controlsBackground: 'rgba(255,255,255,.9)', 24 | buttonHoverBackground: '#eee', 25 | controlsText: '#444', 26 | controlsBorder: '#bbb' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/controls/inputs/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class Checkbox extends Component { 4 | constructor(props) { 5 | super(props) 6 | this.handleChange = this.handleChange.bind(this) 7 | } 8 | 9 | handleChange(event) { 10 | this.props.handleChange(event) 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 | 21 | 25 |
26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/media/icon-github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/config/configHandler.js: -------------------------------------------------------------------------------- 1 | import defaultConfig from './defaultConfig' 2 | import colorThemes from '../config/colorThemes' 3 | 4 | export default { 5 | save: configToSave => { 6 | let savedConfig = localStorage.getItem('savedConfig') 7 | let newConfig = savedConfig ? JSON.parse(savedConfig) : {} 8 | Object.assign(newConfig, configToSave) 9 | localStorage.setItem('savedConfig', JSON.stringify(newConfig)) 10 | }, 11 | 12 | get: () => ({ 13 | ...defaultConfig, 14 | ...JSON.parse(localStorage.getItem('savedConfig')) 15 | }), 16 | 17 | getModalStyle: theme => ({ 18 | overlay: defaultConfig.modalStyle.overlay, 19 | content: { 20 | ...defaultConfig.modalStyle.content, 21 | ...{ 22 | background: colorThemes[theme].controlsBackground, 23 | color: colorThemes[theme].footerText, 24 | borderColor: colorThemes[theme].footerText, 25 | } 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/controls/inputs/Select.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class Select extends Component { 4 | constructor(props) { 5 | super(props) 6 | this.handleChange = this.handleChange.bind(this) 7 | } 8 | 9 | handleChange(event) { 10 | this.props.handleChange(event, this.props.type) 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 | 27 |
28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Benjamin Blonde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/config/defaultConfig.js: -------------------------------------------------------------------------------- 1 | /* Default config */ 2 | export default { 3 | // Responsive 4 | mobileThreshold: 560, 5 | // Basic 6 | bodyPaddingX: 14, 7 | bodyPaddingY: 28, 8 | randomSeedThreshold: 4, // (100 / n)% alive cells 9 | colorTheme: 'default (light)', // See './colorThemes.js' 10 | // Options 11 | isControlsOpen: true, 12 | isOptionsOpen: false, 13 | showNeighborsCount: false, 14 | randomSeed: false, 15 | timeCompressionList: [ 16 | [1000, '1 iteration / second'], 17 | [500, '2 iterations / second'], 18 | [250, '4 iterations / second'], 19 | [125, '8 iterations / second'], 20 | [0, 'Real Time'] 21 | ], 22 | timeCompression: 250, 23 | cellSizesList: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20], 24 | cellSize: 12, 25 | borderSizesList: [0, 1, 2, 3, 4, 5, 6], 26 | borderSize: 1, 27 | // Modal style 28 | modalStyle: { 29 | overlay: { backgroundColor: 'rgba(0, 0, 0, .5)' }, 30 | content: { 31 | position: 'absolute', 32 | width: '260px', 33 | background: 'rgba(0,0,0,.75)', 34 | overflow: 'auto', 35 | WebkitOverflowScrolling: 'touch', 36 | outline: 'none', 37 | padding: '15px' 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 4 | // injected into the application via DefinePlugin in Webpack configuration. 5 | 6 | var REACT_APP = /^REACT_APP_/i; 7 | 8 | function getClientEnvironment(publicUrl) { 9 | var raw = Object 10 | .keys(process.env) 11 | .filter(key => REACT_APP.test(key)) 12 | .reduce((env, key) => { 13 | env[key] = process.env[key]; 14 | return env; 15 | }, { 16 | // Useful for determining whether we’re running in production mode. 17 | // Most importantly, it switches React into the correct mode. 18 | 'NODE_ENV': process.env.NODE_ENV || 'development', 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': publicUrl 24 | }); 25 | // Stringify all values so we can feed into Webpack DefinePlugin 26 | var stringified = { 27 | 'process.env': Object 28 | .keys(raw) 29 | .reduce((env, key) => { 30 | env[key] = JSON.stringify(raw[key]); 31 | return env; 32 | }, {}) 33 | }; 34 | 35 | return { raw, stringified }; 36 | } 37 | 38 | module.exports = getClientEnvironment; 39 | -------------------------------------------------------------------------------- /src/controls/ControlButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import '../css/controls/ControlButton.css' 3 | import IconPlay from '../media/icon-play.svg' 4 | import IconPause from '../media/icon-pause.svg' 5 | import IconReset from '../media/icon-reset.svg' 6 | import IconRightArrow from '../media/icon-right-arrow.svg' 7 | 8 | export default class ControlButton extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.handleChange = this.handleChange.bind(this) 12 | } 13 | 14 | handleChange() {this.props.handleChange()} 15 | 16 | render() { 17 | return ( 18 | 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yagol 2 | 3 | Yet Another Game Of Life. Yagol is a Conway's Game of Life sandbox made with ReactJs. Customize the different options then create, share and watch your shapes evolve. 4 | This app was created with the excellent [create-react-app](https://github.com/facebook/create-react-app). 5 | 6 | ![Yagol Intro Animation](https://media.giphy.com/media/QfHUF4DU6HB9erASmv/giphy.gif "Yagol Intro Animation") 7 | 8 | **See the [app site](http://sarcadass.github.io/yagol)**. 9 | 10 | 11 | ## Installing Yagol locally 12 | 13 | - Clone this repo 14 | - In the folder you cloned the repo run `npm install` 15 | - Launch the local server (with hot js/css reloading) with `npm start` (note that it will be less performant than the builded version, see how to use the builded version below) 16 | 17 | ## Build the project and use it locally 18 | 19 | - Install yagol locally (see above) 20 | - Run `npm build` 21 | - You will have to use a local web server and set `build/` as your root folder to run Yagol if you want to use the default shapes 22 | - To do that you can use [http-server](https://github.com/indexzero/http-server). install it globally (`npm install http-server -g`) then set `build/` as your current working directory (`cd build`) then run `http-server`. The URL where you can access Yagol should be written in your terminal 23 | 24 | 25 | ## Dependencies 26 | 27 | - [react-modal](https://github.com/reactjs/react-modal) 28 | - [react-notification-system](https://github.com/igorprado/react-notification-system) 29 | - [react-svg-loader](https://github.com/boopathi/react-svg-loader/tree/master/packages/react-svg-loader) 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | You have different ways to contribute to this projects, see how to below. 3 | 4 | 5 | ## Submitting a bug 6 | If you find a bug, you can open an issue (make sure there is not a similar issue already open). Please be the more descriptive as possible: 7 | 8 | - What is the bug 9 | - What is the OS and web browser you encountered the bug with 10 | - What is the exact scenario to reproduce the bug 11 | 12 | 13 | ## Adding a default shape 14 | If you had created an interesting shape and you want to make it available as a default shape to everyone who use Yagol, follow these steps: 15 | 16 | - Fork this repository 17 | - Install Yagol locally (see how to on [README](https://github.com/sarcadass/yagol/blob/master/README.md)) 18 | - Save your shape as a custom shape 19 | - Decode the shape export string with this [site](https://www.base64decode.org/) 20 | - Save the decoded object in a new key with the same name as your shape in the `public/shapesLibrary.json` file 21 | - Commit your changes (without the builded files) 22 | - Submit a PR 23 | 24 | 25 | ## Adding a color theme 26 | If you want to add a new color theme for Yagol, follow these steps: 27 | 28 | - Fork this repository 29 | - Install Yagol locally (see how to on [README](https://github.com/sarcadass/yagol/blob/master/README.md)) 30 | - Create a new color theme with a name in `src/config/colorThemes.js` (copy-paste an existing one and change the color values) 31 | - Commit your changes (without the builded files) 32 | - Submit a PR 33 | 34 | 35 | ## Adding a new feature 36 | If you want to add a new feature, create an issue with the 'enhancement' tag first. 37 | -------------------------------------------------------------------------------- /src/controls/modals/ImportShapeModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactModal from 'react-modal' 3 | 4 | export default class SaveShapeModal extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.handleShowModal = this.handleShowModal.bind(this) 8 | this.importShape = this.importShape.bind(this) 9 | } 10 | 11 | handleShowModal() { 12 | this.props.handleShowModal() 13 | } 14 | 15 | importShape(event) { 16 | event.preventDefault() 17 | this.props.importShape(this.shapeImportInput.value) 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | 33 |

Import a custom shape

34 |
35 |

Paste the shape export string below.

36 | this.shapeImportInput = input} /> 40 | 41 |
42 | 43 |
44 |
45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/controls/modals/AboutModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactModal from 'react-modal' 3 | 4 | export default class AboutModal extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.handleShowModal = this.handleShowModal.bind(this) 8 | } 9 | 10 | handleShowModal() { 11 | this.props.handleShowModal() 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | 28 |

About this project

29 |

This project is a Conway's Game of Life sandbox game, it is coded with create-react-app.

30 |

I created this project to learn react, created by Benjamin Blonde, MIT License.

31 |

You can find the Github repo here.

32 | 33 |
34 |
35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/css/Footer.css: -------------------------------------------------------------------------------- 1 | footer { 2 | position: absolute; 3 | display: flex; 4 | justify-content: space-between; 5 | bottom: 0; 6 | left: 0; 7 | right: 0; 8 | padding: 5px 15px; 9 | font-family: Helvetica, Arial, sans-serif; 10 | } 11 | 12 | footer .credits-wrapper { 13 | margin: 0; 14 | line-height: 15px; 15 | font-size: 9px; 16 | text-align: right; 17 | } 18 | 19 | footer .credits-wrapper .first-row { 20 | display: block; 21 | } 22 | 23 | footer .credits-wrapper strong { 24 | font-size: 11px; 25 | } 26 | 27 | footer .social-share-wrapper { 28 | display: flex; 29 | align-items: center; 30 | } 31 | 32 | footer .social-share-wrapper a { 33 | display: block; 34 | margin-right: 10px; 35 | } 36 | 37 | footer .social-share-wrapper a:last-child { 38 | margin-right: 0; 39 | } 40 | 41 | footer .social-share-wrapper .github { 42 | height: 20px; 43 | } 44 | 45 | footer .social-share-wrapper .social-cta { 46 | position: relative; 47 | padding: 5px 10px 5px 23px; 48 | color: #fff; 49 | font-size: 10px; 50 | text-decoration: none; 51 | text-shadow: 1px 1px 1px rgba(0,0,0,.5); 52 | transition: background-color .15s; 53 | } 54 | 55 | footer .social-share-wrapper .social-cta.facebook { 56 | background-color: #3b5998; 57 | } 58 | 59 | footer .social-share-wrapper .social-cta.facebook:hover { 60 | background-color: #4264aa; 61 | } 62 | 63 | footer .social-share-wrapper .social-cta.twitter { 64 | background-color: #1dcaff; 65 | } 66 | 67 | footer .social-share-wrapper .social-cta.twitter:hover { 68 | background-color: #37d0ff; 69 | } 70 | 71 | footer .social-share-wrapper .social-cta svg { 72 | position: absolute; 73 | left: 5px; 74 | top: 5px; 75 | } 76 | 77 | @media screen and (max-width: 489px) { 78 | footer .social-share-wrapper { 79 | display: none; 80 | } 81 | 82 | footer .credits-wrapper { 83 | width: 100%; 84 | text-align: center; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/css/controls/inputs.css: -------------------------------------------------------------------------------- 1 | .input-block { 2 | padding-top: 1em; 3 | } 4 | 5 | .input-label { 6 | display: block; 7 | margin: 0; 8 | font-size: 1em; 9 | } 10 | 11 | .input-label span { 12 | display: block; 13 | padding-bottom: .375em; 14 | margin: 0; 15 | font-size: .75em; 16 | text-transform: uppercase; 17 | font-weight: normal; 18 | } 19 | 20 | .input-style { 21 | display: block; 22 | height: 2.334em; 23 | width: 100%; 24 | padding: 5px; 25 | border: solid 1px currentColor; 26 | background-color: transparent; 27 | font-size: 12px; 28 | color: inherit; 29 | box-sizing: border-box; 30 | border-radius: 0; 31 | } 32 | 33 | .small-cta { 34 | text-transform: uppercase; 35 | cursor: pointer; 36 | background-color: rgba(0,0,0,0); 37 | transition: all .3s; 38 | } 39 | 40 | .small-cta.disabled { 41 | opacity: .5; 42 | cursor: not-allowed; 43 | text-decoration: line-through; 44 | } 45 | 46 | /* CHECKBOX */ 47 | .checkbox-wrapper { 48 | padding-bottom: .25em; 49 | } 50 | 51 | .checkbox-wrapper input[type=checkbox] { 52 | display: none; 53 | } 54 | .checkbox-wrapper input[type=checkbox] + label { 55 | font-size: .75em; 56 | opacity: 1; 57 | transition: opacity .3s; 58 | } 59 | 60 | .checkbox-wrapper input[type=checkbox]:disabled + label { 61 | opacity: .5; 62 | cursor: not-allowed; 63 | text-decoration: line-through; 64 | } 65 | 66 | .checkbox-wrapper label { 67 | position: relative; 68 | display: inline-block; 69 | cursor: pointer; 70 | line-height: 20px; 71 | } 72 | 73 | .checkbox-wrapper label .checkbox-icon { 74 | position: absolute; 75 | display: block; 76 | width: 7px; 77 | height: 7px; 78 | top: 3px; 79 | border: solid 1px; 80 | } 81 | 82 | .checkbox-wrapper label .label-text { 83 | display: block; 84 | padding-left: 14px; 85 | line-height: 1.3em; 86 | } 87 | 88 | /* SELECT */ 89 | .select-wrapper option { 90 | color: #000; 91 | } 92 | -------------------------------------------------------------------------------- /public/shapesLibrary.json: -------------------------------------------------------------------------------- 1 | { 2 | "Yagol": { 3 | "name": "Yagol", 4 | "lifeMap": [0,44,88,132,176,1,45,89,133,177,220,221,222,223,224,225,264,265,266,267,268,269,270,226,182,138,94,50,6,7,51,95,139,183,227,271,315,359,403,447,491,535,534,533,532,531,530,529,528,484,485,486,487,488,489,490,446,402,358,314,537,493,449,405,361,317,273,229,185,141,97,53,9,10,11,12,13,14,15,16,60,104,148,192,236,280,324,368,412,456,500,544,543,499,455,411,367,323,279,147,103,191,235,59,58,57,55,54,56,98,142,186,230,274,318,450,494,538,406,362,231,232,233,234,275,276,277,278,18,62,106,150,194,238,282,326,370,414,458,502,19,20,21,22,23,24,25,546,547,548,549,550,551,552,553,509,465,421,377,333,63,107,151,195,239,283,327,371,415,459,503,241,242,243,244,245,289,285,286,287,288,332,376,420,464,508,507,506,505,504,69,68,67,66,65,64,27,28,29,30,31,32,33,34,555,511,467,423,379,335,291,247,203,159,115,71,556,557,558,559,560,561,562,518,474,430,386,342,298,254,210,166,122,78,77,121,165,209,253,297,341,385,429,473,517,516,515,514,513,512,468,424,380,336,292,248,204,160,116,72,73,74,75,76,36,564,565,566,567,568,569,570,571,520,476,432,388,344,300,256,212,168,124,80,527,526,525,524,523,522,521,477,433,389,345,301,257,213,169,125,81,37], 5 | "shapeSize": [44,13] 6 | }, 7 | 8 | "Glider": { 9 | "name": "Glider", 10 | "shapeSize": [3,3], 11 | "lifeMap": [6,7,8,5,1] 12 | }, 13 | 14 | "Double Glider": { 15 | "name": "Double Glider", 16 | "shapeSize": [5,5], 17 | "lifeMap": [20,15,10,5,0,6,12,18,24,19,14,9,4] 18 | }, 19 | 20 | "Exploder": { 21 | "name": "Exploder", 22 | "shapeSize": [5,5], 23 | "lifeMap": [0,10,5,15,20,22,2,4,9,14,19,24] 24 | }, 25 | 26 | "10 Cells Row": { 27 | "name": "10 cells row", 28 | "shapeSize": [10,1], 29 | "lifeMap": [0,1,2,3,4,5,6,7,8,9] 30 | }, 31 | 32 | "Small Spaceship": { 33 | "name": "Small Spaceship", 34 | "shapeSize": [5,4], 35 | "lifeMap": [15,5,18,14,9,4,3,2,1] 36 | }, 37 | 38 | "Tumblr": { 39 | "name": "Tumblr", 40 | "shapeSize": [7,6], 41 | "lifeMap": [2,1,8,9,4,11,12,5,18,25,32,16,23,30,40,41,34,27,36,35,28,21] 42 | }, 43 | 44 | "Gosper Glider Gun": { 45 | "name": "Gosper Glider Gun", 46 | "shapeSize": [38,15], 47 | "lifeMap": [76,77,115,114,122,160,161,85,86,124,168,206,244,169,208,98,99,60,23,24,62,480,481,482,518,557,72,34,35,73,301,302,341,339,377] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/controls/modals/SaveShapeModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactModal from 'react-modal' 3 | 4 | export default class SaveShapeModal extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.handleShowModal = this.handleShowModal.bind(this) 8 | this.saveShape = this.saveShape.bind(this) 9 | this.selectLink = this.selectLink.bind(this) 10 | } 11 | 12 | handleShowModal() { 13 | this.props.handleShowModal() 14 | } 15 | 16 | saveShape(event) { 17 | event.preventDefault() 18 | this.props.saveShape(this.shapeNameInput.value) 19 | } 20 | 21 | selectLink(event) { 22 | this.shapeNameLink.setSelectionRange(0, this.shapeNameLink.value.length) 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 | 38 |

Save & share your shape

39 | { // Display save shape form if not saved yet 40 | !this.props.activeShape.name ? 41 |
42 |

To save your shape,
you need to give it a name.

43 | this.shapeNameInput = input} 48 | /> 49 | 50 |
51 | : // Display saved notif and shape export string in a read-only input 52 |
53 |

✓ Custom shape saved

54 |

Share this shape by sending the shape export string below:

55 | this.shapeNameLink = input} 59 | onClick={this.selectLink} 60 | value={this.props.activeShape.exportString} /> 61 |
62 | } 63 | 64 |
65 |
66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/css/controls/Modal.css: -------------------------------------------------------------------------------- 1 | .ReactModal__Overlay { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | 6 | .modal { 7 | text-align: center; 8 | align-self: center; 9 | } 10 | 11 | .modal h2 { 12 | text-transform: uppercase; 13 | font-size: 16px; 14 | margin: 0; 15 | border-bottom: solid 1px; 16 | border-bottom-color: currentColor; 17 | padding-bottom: 10px; 18 | } 19 | 20 | .modal p { 21 | font-size: 12px; 22 | } 23 | 24 | .modal hr { 25 | margin: 20px 0; 26 | } 27 | 28 | /* Save shape modal */ 29 | .modal .input-style { 30 | width: 80%; 31 | margin: 0 auto 10px; 32 | } 33 | 34 | .import-shape-modal .input-style { 35 | text-align: center; 36 | } 37 | 38 | .modal .green-notification { 39 | font-size: 16px; 40 | color: #2ecc71; 41 | } 42 | 43 | .modal .loader-wrapper { 44 | margin-bottom: 16px; 45 | } 46 | 47 | .modal .loader { 48 | display: block; 49 | margin: 0 auto; 50 | width: 25px; 51 | height: 25px; 52 | border-width: 8px; 53 | border-style: solid; 54 | border-radius: 50%; 55 | animation: loader-spin 2s linear infinite; 56 | } 57 | 58 | @keyframes loader-spin { 59 | 0% { transform: rotate(0deg) } 60 | 100% { transform: rotate(360deg) } 61 | } 62 | 63 | /* Handle shapes modal */ 64 | .modal .shapes-list { 65 | max-height: 236px; 66 | padding: 0 10px; 67 | margin: 10px 0 20px; 68 | overflow: auto; 69 | list-style-type: none; 70 | } 71 | 72 | .modal .shapes-list li { 73 | display: flex; 74 | justify-content: space-between; 75 | padding: 7px 0; 76 | border-bottom: solid 1px; 77 | border-bottom-color: currentColor; 78 | line-height: 16px; 79 | } 80 | 81 | .modal .shapes-list h6 { 82 | flex-basis: 74%; 83 | text-align: left; 84 | margin: 0; 85 | font-size: 12px; 86 | word-break: break-all; 87 | } 88 | 89 | .modal .shapes-list .shape-size { 90 | font-size: 10px; 91 | margin-left: 5px; 92 | color: #aaa; 93 | } 94 | 95 | .modal .actions-wrapper { 96 | display: flex; 97 | flex-direction: row-reverse; 98 | } 99 | 100 | .modal .action-icon { 101 | display: block; 102 | width: 14px; 103 | height: 14px; 104 | padding: 2px; 105 | margin-left: 8px; 106 | cursor: pointer; 107 | opacity: .5; 108 | } 109 | 110 | .modal .action-icon:hover { 111 | opacity: 1; 112 | } 113 | 114 | .modal .action-icon:last-child { 115 | margin-left: 0 116 | } 117 | 118 | /* Handle shapes modal */ 119 | .modal.modal-about p { 120 | line-height: 1.6em; 121 | font-family: Helvetica, Arial, sans-serif; 122 | } 123 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | Yagol - Yet Another Game Of Life 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 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import colorThemes from './config/colorThemes' 3 | import IconGithub from './media/icon-github.svg' 4 | import IconFacebook from './media/icon-facebook.svg' 5 | import IconTwitter from './media/icon-twitter.svg' 6 | import AboutModal from './controls/modals/AboutModal' 7 | import pckg from '../package.json' 8 | const twitterLink = `https://twitter.com/intent/tweet?text=${encodeURIComponent(pckg.description)} 9 | &via=Sarcadass&url=${encodeURIComponent(pckg.url)}` 10 | import './css/Footer.css' 11 | 12 | export default class Footer extends Component { 13 | constructor(props) { 14 | super(props) 15 | this.state = { isAboutModalOpen: false } 16 | this.handleAboutModal = this.handleAboutModal.bind(this) 17 | } 18 | 19 | handleAboutModal(event) { 20 | if (event) event.preventDefault() 21 | this.setState(prevState => { 22 | return { isAboutModalOpen: !prevState.isAboutModalOpen } 23 | }) 24 | } 25 | 26 | render() { 27 | return ( 28 | 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | var appDirectory = fs.realpathSync(process.cwd()); 10 | function resolveApp(relativePath) { 11 | return path.resolve(appDirectory, relativePath); 12 | } 13 | 14 | // We support resolving modules according to `NODE_PATH`. 15 | // This lets you use absolute paths in imports inside large monorepos: 16 | // https://github.com/facebookincubator/create-react-app/issues/253. 17 | 18 | // It works similar to `NODE_PATH` in Node itself: 19 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 20 | 21 | // We will export `nodePaths` as an array of absolute paths. 22 | // It will then be used by Webpack configs. 23 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 24 | 25 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 26 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 27 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 28 | 29 | var nodePaths = (process.env.NODE_PATH || '') 30 | .split(process.platform === 'win32' ? ';' : ':') 31 | .filter(Boolean) 32 | .filter(folder => !path.isAbsolute(folder)) 33 | .map(resolveApp); 34 | 35 | var envPublicUrl = process.env.PUBLIC_URL; 36 | 37 | function ensureSlash(path, needsSlash) { 38 | var hasSlash = path.endsWith('/'); 39 | if (hasSlash && !needsSlash) { 40 | return path.substr(path, path.length - 1); 41 | } else if (!hasSlash && needsSlash) { 42 | return path + '/'; 43 | } else { 44 | return path; 45 | } 46 | } 47 | 48 | function getPublicUrl(appPackageJson) { 49 | return envPublicUrl || require(appPackageJson).homepage; 50 | } 51 | 52 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 53 | // "public path" at which the app is served. 54 | // Webpack needs to know it to put the right