├── 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 |
22 |
23 | {this.props.label}
24 |
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 |
17 | {this.props.label}
18 |
21 | {this.props.options.map((el, index) => {
22 | return {el[1]}
24 | })}
25 |
26 |
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 |
22 |
23 | {this.props.type === 'play' ?
24 |
25 | : ''}
26 |
27 | {this.props.type === 'pause' ?
28 |
29 | : ''}
30 |
31 | {this.props.type === 'reset' ?
32 |
33 | : ''}
34 |
35 | {this.props.type === 'options' ?
36 |
37 | : ''}
38 |
39 | {this.props.buttonLabel}
40 |
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 | 
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 |
42 | Close
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 |
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 |
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 | Close
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 | This webapp use Javascript to work, please enable Javascript to use it.
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