├── .vscode ├── settings.json ├── cSpell.json └── launch.json ├── renovate.json ├── .eslintignore ├── .github └── quotion.png ├── public ├── favicon.ico ├── favicon.png ├── favicon-maskable.png ├── manifest.json ├── sitemap.xml └── index.html ├── src ├── assets │ ├── audio │ │ ├── move.mp3 │ │ └── popup.mp3 │ ├── images │ │ ├── github.png │ │ └── github-white.png │ ├── svg │ │ ├── arrow.svg │ │ ├── undo.svg │ │ ├── speaker-on.svg │ │ ├── reset.svg │ │ └── speaker-off.svg │ └── styles │ │ ├── index.scss │ │ ├── vars.scss │ │ └── normalize.scss ├── utils │ ├── helpers.js │ ├── __tests__ │ │ ├── registerServiceWorker.test.js │ │ └── mobileEvents.test.js │ ├── gitalk.js │ ├── i18n.js │ ├── mobileEvents.js │ └── registerServiceWorker.js ├── components │ ├── Board │ │ ├── board.scss │ │ ├── index.js │ │ └── __tests__ │ │ │ ├── Board.test.js │ │ │ └── __snapshots__ │ │ │ └── Board.test.js.snap │ ├── WrapperButton │ │ ├── wrapperButton.scss │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── WrapperButton.test.js.snap │ │ │ └── WrapperButton.test.js │ │ └── index.js │ ├── Firework │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Firework.test.js.snap │ │ │ └── Firework.test.js │ │ ├── index.js │ │ └── firework.scss │ ├── Speaker │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Speaker.test.js.snap │ │ │ └── Speaker.test.js │ │ └── index.js │ ├── Tips │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Tips.test.js.snap │ │ │ └── Tips.test.js │ │ ├── tips.scss │ │ └── index.js │ ├── Comments │ │ ├── __tests__ │ │ │ ├── Comments.test.js │ │ │ └── __snapshots__ │ │ │ │ └── Comments.test.js.snap │ │ └── index.js │ ├── Modal │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Modal.test.js.snap │ │ │ └── Modal.test.js │ │ ├── index.js │ │ └── modal.scss │ ├── Scores │ │ ├── __tests__ │ │ │ ├── Scores.test.js │ │ │ └── __snapshots__ │ │ │ │ └── Scores.test.js.snap │ │ ├── index.js │ │ └── scores.scss │ ├── Cell │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Cell.test.js.snap │ │ │ └── Cell.test.js │ │ ├── index.js │ │ └── cell.scss │ ├── Button │ │ ├── __tests__ │ │ │ ├── Button.test.js │ │ │ └── __snapshots__ │ │ │ │ └── Button.test.js.snap │ │ ├── index.js │ │ └── button.scss │ ├── Footer │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Footer.test.js.snap │ │ │ └── Footer.test.js │ │ ├── footer.scss │ │ └── index.js │ └── Row │ │ ├── index.js │ │ └── __tests__ │ │ ├── Row.test.js │ │ └── __snapshots__ │ │ └── Row.test.js.snap ├── containers │ ├── Ranking │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── Ranking.test.js.snap │ │ │ └── Ranking.test.js │ │ ├── ranking.scss │ │ └── index.js │ ├── App │ │ ├── index.js │ │ ├── __tests__ │ │ │ └── App.test.js │ │ └── App.js │ ├── WebApp │ │ ├── index.js │ │ ├── webApp.scss │ │ ├── WebApp.js │ │ └── __tests__ │ │ │ ├── WebApp.test.js │ │ │ └── __snapshots__ │ │ │ └── WebApp.test.js.snap │ ├── ControlPanel │ │ ├── controlPanel.scss │ │ ├── index.js │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── ControlPanel.test.js.snap │ │ │ └── ControlPanel.test.js │ │ └── ControlPanel.js │ ├── MobileApp │ │ ├── mobileApp.scss │ │ ├── index.js │ │ ├── __tests__ │ │ │ ├── MobileApp.test.js │ │ │ └── __snapshots__ │ │ │ │ └── MobileApp.test.js.snap │ │ └── MobileApp.js │ └── GameOver │ │ ├── __tests__ │ │ ├── GameOver.test.js │ │ └── __snapshots__ │ │ │ └── GameOver.test.js.snap │ │ ├── gameOver.scss │ │ └── index.js ├── reducers │ ├── index.js │ ├── ranking.js │ ├── __tests__ │ │ └── board.test.js │ └── board.js ├── layouts │ ├── Header │ │ ├── __tests__ │ │ │ ├── Header.test.js │ │ │ └── __snapshots__ │ │ │ │ └── Header.test.js.snap │ │ ├── header.scss │ │ └── index.js │ ├── index.js │ ├── Main │ │ ├── index.js │ │ └── __tests__ │ │ │ └── Main.test.js │ └── __tests__ │ │ └── Layout.test.js ├── index.js ├── store.js ├── apis │ └── index.js └── sagas │ └── index.js ├── internals ├── screenshots │ ├── pwa.gif │ ├── web.png │ ├── i18n.png │ ├── comments.gif │ ├── iPhone.png │ ├── reponsive.gif │ ├── data-persist.gif │ └── redux-state.gif └── img │ ├── eslint-padded-90.png │ ├── jest-padded-90.png │ ├── react-padded-90.png │ ├── redux-padded-90.png │ ├── yarn-padded-90.png │ ├── webpack-padded-90.png │ ├── redux-saga-padded-90.png │ └── react-router-padded-90.png ├── scripts ├── deploy.sh ├── test.js ├── start.js └── build.js ├── .travis.yml ├── .editorconfig ├── .babelrc ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── paths.js ├── env.js └── webpackDevServer.config.js ├── .gitignore ├── LICENSE ├── README.md └── package.json /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["codecov", "heroku"] 3 | } 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /config/ 2 | /scripts/ 3 | /public/ 4 | /build/ 5 | /coverage/ 6 | -------------------------------------------------------------------------------- /.github/quotion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/.github/quotion.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/assets/audio/move.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/src/assets/audio/move.mp3 -------------------------------------------------------------------------------- /src/assets/audio/popup.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/src/assets/audio/popup.mp3 -------------------------------------------------------------------------------- /internals/screenshots/pwa.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/pwa.gif -------------------------------------------------------------------------------- /internals/screenshots/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/web.png -------------------------------------------------------------------------------- /public/favicon-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/public/favicon-maskable.png -------------------------------------------------------------------------------- /src/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/src/assets/images/github.png -------------------------------------------------------------------------------- /internals/screenshots/i18n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/i18n.png -------------------------------------------------------------------------------- /internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /internals/screenshots/comments.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/comments.gif -------------------------------------------------------------------------------- /internals/screenshots/iPhone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/iPhone.png -------------------------------------------------------------------------------- /src/assets/images/github-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/src/assets/images/github-white.png -------------------------------------------------------------------------------- /internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /internals/screenshots/reponsive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/reponsive.gif -------------------------------------------------------------------------------- /internals/img/redux-saga-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/redux-saga-padded-90.png -------------------------------------------------------------------------------- /internals/screenshots/data-persist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/data-persist.gif -------------------------------------------------------------------------------- /internals/screenshots/redux-state.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/screenshots/redux-state.gif -------------------------------------------------------------------------------- /internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devrsi0n/React-2048-game/HEAD/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo $PWD 4 | npm run build 5 | cd ../re2048 6 | git add . 7 | git commit -m "update frontend" 8 | git push heroku master 9 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | export function isObjEqual(preObj, obj) { 3 | return JSON.stringify(preObj) === JSON.stringify(obj); 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | install: 5 | - npm i -g codecov 6 | - yarn 7 | script: 8 | - yarn run ci 9 | - yarn run build 10 | after_script: 11 | - yarn run codecov 12 | -------------------------------------------------------------------------------- /src/components/Board/board.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/vars'; 2 | 3 | .board { 4 | border-collapse: separate; 5 | border-spacing: 0.25vw; 6 | } 7 | 8 | @media all and (max-width: $break-point) { 9 | .board { 10 | border-spacing: 1vw; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/assets/svg/arrow.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/utils/__tests__/registerServiceWorker.test.js: -------------------------------------------------------------------------------- 1 | import register, { unregister } from '../registerServiceWorker'; 2 | 3 | describe('registerServiceWorker.js', () => { 4 | it('not throw', () => { 5 | expect(register).not.toThrow(); 6 | expect(unregister).not.toThrow(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/WrapperButton/wrapperButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/vars'; 2 | 3 | .btn { 4 | margin-right: 0.77vw; 5 | margin-top: 0.77vw; 6 | } 7 | 8 | @media all and (max-width: $break-point) { 9 | .btn { 10 | margin-right: 4vw; 11 | margin-top: 4vw; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/containers/Ranking/__tests__/__snapshots__/Ranking.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |
11 |
12 | `; 13 | -------------------------------------------------------------------------------- /src/containers/Ranking/ranking.scss: -------------------------------------------------------------------------------- 1 | .main { 2 | align-items: center; 3 | display: flex; 4 | height: 75vh; 5 | justify-content: center; 6 | margin-top: 1vw; 7 | width: 90vw; 8 | 9 | .echarts { 10 | height: 100%; 11 | padding-left: 5vw; 12 | width: 100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Firework/__tests__/__snapshots__/Firework.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |
10 |
13 |
14 | `; 15 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import undoable from 'redux-undo'; 3 | import board from './board'; 4 | import ranking from './ranking'; 5 | 6 | export default combineReducers({ 7 | board: undoable(board, { 8 | limit: 11 // set a limit for the history 9 | }), 10 | ranking 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/__tests__/mobileEvents.test.js: -------------------------------------------------------------------------------- 1 | import swipeDetect from '../mobileEvents'; 2 | 3 | describe('mobileEvents.js', () => { 4 | it('not throw', () => { 5 | const el = document.createElement('div'); 6 | expect(swipeDetect.bind(Object.create(null), [el, () => {}])).not.toThrow( 7 | 'error' 8 | ); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "react", 10 | "stage-2" 11 | ], 12 | "env": { 13 | "test": { 14 | "presets": [ 15 | "env", 16 | "stage-2" 17 | ], 18 | "sourceMaps": "inline" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Speaker/__tests__/__snapshots__/Speaker.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 | 13 | `; 14 | -------------------------------------------------------------------------------- /src/components/Tips/__tests__/__snapshots__/Tips.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |

10 | hello 11 |

12 |

15 | world 16 |

17 |
18 | `; 19 | -------------------------------------------------------------------------------- /src/components/Comments/__tests__/Comments.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import Comments from '..'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | const comments = renderer.create().toJSON(); 8 | expect(comments).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/assets/svg/undo.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/Modal/__tests__/__snapshots__/Modal.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |
10 |
13 |
14 | test 15 |
16 |
17 |
18 | `; 19 | -------------------------------------------------------------------------------- /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/components/Scores/__tests__/Scores.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import Scores from '..'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | const scores = renderer 8 | .create() 9 | .toJSON(); 10 | expect(scores).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/WrapperButton/__tests__/__snapshots__/WrapperButton.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 | 15 |
16 | `; 17 | -------------------------------------------------------------------------------- /src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { initMatrix } from '../../reducers/board'; 3 | import App from './App'; 4 | 5 | const mapStateToProps = state => ({ 6 | matrix: state.present.board.present.matrix 7 | }); 8 | 9 | const mapDispatchToProps = { 10 | onInit: initMatrix 11 | }; 12 | 13 | export default connect(mapStateToProps, mapDispatchToProps)(App); 14 | -------------------------------------------------------------------------------- /src/reducers/ranking.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | list: [] 3 | }; 4 | 5 | export default function reducer(state = initState, action) { 6 | switch (action.type) { 7 | case 'SET_RANKING_LIST': 8 | return { ...state, list: action.data }; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | export const GAMEOVER = 'GAMEOVER'; 15 | 16 | export const gameOver = () => ({ type: GAMEOVER }); 17 | -------------------------------------------------------------------------------- /src/containers/Ranking/__tests__/Ranking.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { Rank } from '../index'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | const ranking = renderer 8 | .create( {}} list={[]} />) 9 | .toJSON(); 10 | expect(ranking).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/assets/svg/speaker-on.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/Modal/__tests__/Modal.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import Modal from '..'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | const modal = renderer 8 | .create( 9 | 10 |
test
11 |
12 | ) 13 | .toJSON(); 14 | expect(modal).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /.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 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | /.idea 24 | /profiles 25 | /monitor 26 | /.vscode/chrome 27 | -------------------------------------------------------------------------------- /src/assets/svg/reset.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/WrapperButton/__tests__/WrapperButton.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import WrapperButton from '..'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | const btn = renderer 8 | .create( 9 | 10 |

test

11 |
12 | ) 13 | .toJSON(); 14 | expect(btn).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/Tips/tips.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/vars'; 2 | 3 | .tips { 4 | background: $gray; 5 | border-radius: 5px; 6 | color: $black; 7 | margin: 3.85vw auto 0; 8 | text-align: left; 9 | width: 45vw; 10 | 11 | .title { 12 | font-size: 1.25rem; 13 | font-weight: 500; 14 | padding: 1.54vw 0 0.77vw 1.54vw; 15 | } 16 | 17 | .content { 18 | color: $light-black; 19 | font-size: 1.1rem; 20 | padding: 0.23vw 0 1.54vw 1.54vw; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/layouts/Header/__tests__/Header.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { MemoryRouter } from 'react-router'; 4 | import Header from '..'; 5 | 6 | describe('
', () => { 7 | it('component render', () => { 8 | const header = renderer 9 | .create( 10 | 11 |
12 | 13 | ) 14 | .toJSON(); 15 | expect(header).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React 2048", 3 | "name": "2048 game", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "450x450", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "favicon-maskable.png", 12 | "sizes": "550x550", 13 | "type": "image/png", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": "./index.html", 18 | "display": "standalone", 19 | "theme_color": "#000000", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Scores/__tests__/__snapshots__/Scores.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |

10 | SCORE 11 | 14 | 123 15 | 16 |

17 |

20 | BEST 21 | 24 | 456 25 | 26 |

27 |
28 | `; 29 | -------------------------------------------------------------------------------- /src/components/Firework/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import PropTypes from 'prop-types'; 3 | import './firework.scss'; 4 | 5 | // Display firework animation when game over 6 | export default class Firework extends React.Component { 7 | // Render once, as no props and state 8 | shouldComponentUpdate() { 9 | return false; 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |
16 |
17 |
18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/containers/WebApp/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { reset } from '../../reducers/board'; 3 | import WebApp from './WebApp'; 4 | 5 | const mapStateToProps = state => ({ 6 | matrix: state.present.board.present.matrix, 7 | score: state.present.board.present.score, 8 | bestScore: state.present.board.present.bestScore, 9 | gameOver: state.present.board.present.gameOver 10 | }); 11 | 12 | const mapDispatchToProps = { 13 | onReset: reset 14 | }; 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(WebApp); 17 | -------------------------------------------------------------------------------- /src/components/Cell/__tests__/__snapshots__/Cell.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 | 5 |
8 |
11 | 32 12 |
13 |
14 | 15 | `; 16 | 17 | exports[` component render 2`] = ` 18 | 19 |
22 |
25 | 2048 26 |
27 |
28 | 29 | `; 30 | -------------------------------------------------------------------------------- /src/components/Comments/__tests__/__snapshots__/Comments.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 |
7 |
10 |

18 |

22 |
23 |
24 | `; 25 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | import Main from './Main'; 4 | import Footer from '../components/Footer'; 5 | 6 | export default function Layouts() { 7 | return ( 8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/containers/WebApp/webApp.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/vars'; 2 | 3 | .app { 4 | margin: 2vw auto; 5 | text-align: center; 6 | width: 80vw; 7 | } 8 | 9 | .title { 10 | color: $black; 11 | font-size: 3rem; 12 | margin: 0.67em 0; 13 | } 14 | 15 | .box { 16 | display: flex; 17 | justify-content: center; 18 | } 19 | 20 | .board { 21 | // flex: $flex; 22 | } 23 | 24 | .panel { 25 | align-items: flex-end; 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: space-between; 29 | margin-bottom: 0.5vw; 30 | margin-left: 3vw; 31 | } 32 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | https://re2048.herokuapp.com/ 11 | 2017-10-31T02:34:45+00:00 12 | daily 13 | 14 | -------------------------------------------------------------------------------- /src/assets/svg/speaker-off.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import './assets/styles/normalize.scss'; 6 | import './assets/styles/index.scss'; 7 | import Layouts from './layouts'; 8 | import store from './store'; 9 | import registerServiceWorker from './utils/registerServiceWorker'; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | 20 | registerServiceWorker(); 21 | -------------------------------------------------------------------------------- /src/layouts/Main/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import App from '../../containers/App'; 4 | import Comments from '../../components/Comments'; 5 | import Ranking from '../../containers/Ranking'; 6 | 7 | const Main = () => ( 8 |
9 | 10 | 11 | 12 | 13 | {/* 404 Page */} 14 | 15 | 16 |
17 | ); 18 | 19 | export default Main; 20 | -------------------------------------------------------------------------------- /src/components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import styles from './modal.scss'; 5 | 6 | // Modal, parent componet for popup elements 7 | export default function Modal({ display, children }) { 8 | return ( 9 |
10 |
11 |
{children}
12 |
13 | ); 14 | } 15 | 16 | Modal.propTypes = { 17 | display: PropTypes.bool.isRequired, 18 | children: PropTypes.node.isRequired 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Button/__tests__/Button.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import Button from '..'; 4 | 5 | describe('', () => { 6 | it('component render', () => { 7 | let board = renderer.create( 18 | ) 19 | .toJSON(); 20 | expect(board).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/Button/__tests__/__snapshots__/Button.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` component render 1`] = ` 4 | 10 | `; 11 | 12 | exports[` component render 2`] = ` 13 | 19 | `; 20 | 21 | exports[` component render 3`] = ` 22 | 30 | `; 31 | -------------------------------------------------------------------------------- /.vscode/cSpell.json: -------------------------------------------------------------------------------- 1 | { 2 | // cSpell Settings 3 | // cSpell Settings 4 | // Version of the setting file. Always 0.1 5 | "version": "0.1", 6 | // language - current active spelling language 7 | "language": "en", 8 | // words - list of words to be always considered correct 9 | "words": [ 10 | "codecov", 11 | "gitment", 12 | "heroku", 13 | "onstatechange", 14 | "onupdatefound", 15 | "Unmount", 16 | "WASD", 17 | "xmlns" 18 | ], 19 | // flagWords - list of words to be always considered incorrect 20 | // This is useful for offensive words and common spelling errors. 21 | // For example "hte" should be "the" 22 | "flagWords": ["hte"] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Firework/__tests__/Firework.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import Enzyme, { shallow } from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import Firework from '..'; 6 | 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | 9 | describe('', () => { 10 | it('component render', () => { 11 | const firework = renderer.create().toJSON(); 12 | expect(firework).toMatchSnapshot(); 13 | }); 14 | 15 | it('shouldComponentUpdate', () => { 16 | const firework = shallow().instance(); 17 | expect(firework.shouldComponentUpdate()).toBe(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/Tips/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './tips.scss'; 4 | 5 | // Game tips, render once enough 6 | export default class Tips extends React.Component { 7 | static propTypes = { 8 | title: PropTypes.string.isRequired, 9 | content: PropTypes.string.isRequired 10 | }; 11 | 12 | shouldComponentUpdate() { 13 | return false; 14 | } 15 | 16 | render() { 17 | const { props: { title, content } } = this; 18 | 19 | return ( 20 |
21 |

{title}

22 |

{content}

23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/containers/ControlPanel/controlPanel.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/styles/vars'; 2 | 3 | .panel { 4 | align-items: center; 5 | display: flex; 6 | flex: 1; 7 | flex-direction: column; 8 | 9 | .row-btn { 10 | align-items: center; 11 | display: flex; 12 | flex-grow: 1; 13 | } 14 | 15 | .btn { 16 | margin-right: 0.77vw; 17 | margin-top: 0.77vw; 18 | } 19 | 20 | img { 21 | margin-bottom: -0.35vw; 22 | width: 2.1vw; 23 | } 24 | 25 | .arrows { 26 | display: flex; 27 | flex-direction: row; 28 | } 29 | 30 | .left { 31 | transform: rotate(270deg); 32 | } 33 | 34 | .right { 35 | transform: rotate(90deg); 36 | } 37 | 38 | .down { 39 | transform: rotate(180deg); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Scores/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './scores.scss'; 4 | import i18n from '../../utils/i18n'; 5 | 6 | export default function Scores({ score, bestScore }) { 7 | const { index, text, best } = styles; 8 | return ( 9 |
10 |

11 | {i18n.score} 12 | {score} 13 |

14 |

15 | {i18n.best} 16 | {bestScore} 17 |

18 |
19 | ); 20 | } 21 | 22 | Scores.propTypes = { 23 | score: PropTypes.number.isRequired, 24 | bestScore: PropTypes.number.isRequired 25 | }; 26 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './vars'; 2 | 3 | // global styles 4 | 5 | :global { 6 | html { 7 | font-size: 1vw; 8 | touch-action: manipulation; 9 | } 10 | 11 | body { 12 | background: #fff; 13 | font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | @media all and (max-width: $break-point) { 19 | html { 20 | font-size: 4vw; 21 | } 22 | } 23 | 24 | input, 25 | button, 26 | submit { 27 | border: 0; 28 | } 29 | 30 | h1, 31 | h2, 32 | h3, 33 | h4, 34 | h5, 35 | h6, 36 | p { 37 | margin: 0; 38 | } 39 | 40 | img { 41 | max-width: 100%; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Footer/__tests__/__snapshots__/Footer.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`