├── img └── tetris.gif ├── src ├── index.css ├── constants │ └── index.js ├── TodoStore.js ├── utils │ └── TypesUtil.js ├── template.html ├── App.js ├── index.js ├── App.css ├── component │ ├── Tetris.js │ ├── gameboard.css │ ├── Modal.js │ ├── Modal.css │ └── Gameboard.js ├── App.test.js ├── stores │ ├── boardStore.test.js │ ├── boardStore.js │ └── gameBoardStore.js └── types │ └── shapeTypes.js ├── .babelrc ├── .gitignore ├── README.md ├── webpack.loaders.js ├── webpack.production.config.js ├── webpack.config.js └── package.json /img/tetris.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trknhr/mobx-react-tetris/HEAD/img/tetris.gif -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const BOARD_HEIGHT = 20 2 | export const BOARD_WIDTH = 10 3 | 4 | export const FRAME_TIME = 800 -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ['transform-decorators-legacy', 'transform-class-properties'] 4 | } -------------------------------------------------------------------------------- /src/TodoStore.js: -------------------------------------------------------------------------------- 1 | class TodoStore { 2 | todos = [] 3 | 4 | get completedTodosCount(){ 5 | return this.todos.filter( 6 | todo => todo.completed 7 | ) 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/utils/TypesUtil.js: -------------------------------------------------------------------------------- 1 | import {I, J, L, O, S, T, Z} from '../types/shapeTypes' 2 | 3 | export const getRandomShape = () => { 4 | const types = [I, J, L, O, S, T, Z] 5 | return types[Math.floor(Math.random() * types.length)] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://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 | dist 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | 17 | # ide 18 | .idea 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tetris by using react & mobx 2 | 3 | ![tetris](img/tetris.gif) 4 | 5 | - [demo](https://1984weed.github.io/mobx-react-tetris/dist) 6 | 7 | ## Why did I create it? 8 | 9 | to learn, to learn and to learn 10 | 11 | # how to start 12 | 13 | ``` 14 | npm start 15 | ``` 16 | 17 | let's see http://127.0.0.1:8888 -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 |
10 |
Loading...
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import Tetris from './component/Tetris' 4 | 5 | class App extends Component { 6 | render() { 7 | const {gameBoardStore} = this.props; 8 | 9 | return ( 10 | 11 | ); 12 | } 13 | } 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | import GameBoardStore from './stores/gameBoardStore' 6 | import BoardStore from './stores/boardStore' 7 | 8 | 9 | const gameBoardStore = new GameBoardStore(new BoardStore()) 10 | 11 | ReactDOM.render( 12 | , 13 | document.getElementById('app') 14 | ); 15 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | -------------------------------------------------------------------------------- /src/component/Tetris.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {observer} from 'mobx-react'; 3 | import Gameboard from './Gameboard' 4 | 5 | @observer 6 | export default class Tetris extends React.Component { 7 | render() { 8 | const {gameBoardStrore, viewStore} = this.props; 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | } 16 | 17 | Tetris.propTypes = { 18 | gameBoardStore: React.PropTypes.object 19 | }; 20 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ReactDOM from 'react-dom'; 4 | import TestUtils from 'react-addons-test-utils'; 5 | import App from './App'; 6 | import GameBoardStore from './stores/gameBoardStore' 7 | import BoardStore from './stores/boardStore' 8 | 9 | it('App init view', () => { 10 | const gameBoardStore = new GameBoardStore(new BoardStore()) 11 | const app = TestUtils.renderIntoDocument( 12 | , 13 | ); 14 | 15 | const appNode = ReactDOM.findDOMNode(app); 16 | 17 | expect(appNode.textContent).toEqual('Game over!Try again'); 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /src/stores/boardStore.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {I} from '../types/shapeTypes' 3 | import {BOARD_WIDTH, FRAME_TIME} from '../constants/' 4 | import BoardStore from '../stores/boardStore' 5 | 6 | it('setPiece', () => { 7 | const boardStore = new BoardStore() 8 | const shape = I 9 | const rotation = 0 10 | const position = { 11 | x: (BOARD_WIDTH / 2) - shape.blocks.length / 2, 12 | y: 0 13 | } 14 | boardStore.setPiece(shape, rotation, position) 15 | 16 | // expect(appNode.textContent).toEqual('Game over!Try again') 17 | expect(1).toEqual(1) 18 | }) 19 | 20 | -------------------------------------------------------------------------------- /src/component/gameboard.css: -------------------------------------------------------------------------------- 1 | table { 2 | height: 100%; 3 | width: 100%; 4 | } 5 | .game { 6 | height: 800px; 7 | width: 400px; 8 | position: relative; 9 | } 10 | 11 | table, td, th { 12 | border : 1px #808080 solid; 13 | } 14 | 15 | .shape-i { 16 | background: #6DFECD; 17 | } 18 | 19 | .shape-z { 20 | background: #FC3768; 21 | } 22 | 23 | .shape-o { 24 | background: #00F; 25 | } 26 | 27 | .shape-t { 28 | background: #CB6CFC; 29 | } 30 | 31 | .shape-j { 32 | background: #136BFB; 33 | } 34 | 35 | .shape-l { 36 | background: #FD9827; 37 | } 38 | 39 | .shape-s { 40 | background: #6DFD46; 41 | } 42 | -------------------------------------------------------------------------------- /src/component/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {observer} from 'mobx-react'; 3 | import './Modal.css' 4 | 5 | export default class Modal extends React.Component { 6 | render() { 7 | const style = this.props.isOver ? {display: 'block'} : {display: 'none'} 8 | return ( 9 |
10 |

Game over!

11 |
12 | Try again 13 |
14 |
15 | ); 16 | } 17 | } 18 | 19 | Modal.propTypes = { 20 | gameBoardStore: React.PropTypes.object 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/Modal.css: -------------------------------------------------------------------------------- 1 | a { color: #333; text-decoration: none; } 2 | 3 | .modalbg { 4 | position: absolute; 5 | display: block; 6 | top: 0; 7 | right: 0; 8 | bottom: 0; 9 | left: 0; 10 | z-index: 99999; 11 | background: rgba(4, 10, 30, 0.8); 12 | text-align: center; 13 | } 14 | .modalbg p { 15 | font-size: 60px; 16 | font-weight: bold; 17 | height: 60px; 18 | line-height: 60px; 19 | margin-top: 222px; 20 | color: #fc3768; 21 | } 22 | 23 | .lower{ 24 | display: block; 25 | margin-top: 59px; 26 | } 27 | a { 28 | display: inline-block; 29 | background: #8f7a66; 30 | border-radius: 3px; 31 | padding: 0 20px; 32 | text-decoration: none; 33 | color: #f9f6f2; 34 | height: 40px; 35 | line-height: 42px; 36 | margin-left: 9px; 37 | cursor: pointer; 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /webpack.loaders.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | test: /\.jsx?$/, 4 | exclude: /(node_modules|bower_components|public)/, 5 | loader: 'babel', 6 | query: { 7 | presets: ['es2015', 'react'], 8 | plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-class-properties'], 9 | } 10 | }, 11 | { 12 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 13 | exclude: /(node_modules|bower_components)/, 14 | loader: "file" 15 | }, 16 | { 17 | test: /\.(woff|woff2)$/, 18 | exclude: /(node_modules|bower_components)/, 19 | loader: "url?prefix=font/&limit=5000" 20 | }, 21 | { 22 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 23 | exclude: /(node_modules|bower_components)/, 24 | loader: "url?limit=10000&mimetype=application/octet-stream" 25 | }, 26 | { 27 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 28 | exclude: /(node_modules|bower_components)/, 29 | loader: "url?limit=10000&mimetype=image/svg+xml" 30 | }, 31 | { 32 | test: /\.gif/, 33 | exclude: /(node_modules|bower_components)/, 34 | loader: "url-loader?limit=10000&mimetype=image/gif" 35 | }, 36 | { 37 | test: /\.jpg/, 38 | exclude: /(node_modules|bower_components)/, 39 | loader: "url-loader?limit=10000&mimetype=image/jpg" 40 | }, 41 | { 42 | test: /\.png/, 43 | exclude: /(node_modules|bower_components)/, 44 | loader: "url-loader?limit=10000&mimetype=image/png" 45 | } 46 | ]; 47 | -------------------------------------------------------------------------------- /src/component/Gameboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {observer} from 'mobx-react'; 3 | import './gameboard.css' 4 | import key from 'keymaster' 5 | import Modal from './Modal' 6 | 7 | @observer 8 | export default class Gameboard extends React.Component { 9 | componentWillMount(){ 10 | const {gameBoardStore} = this.props; 11 | this.bindKeyboardEvents() 12 | gameBoardStore.start() 13 | } 14 | render() { 15 | const {gameBoardStore} = this.props; 16 | 17 | const gameBoard = gameBoardStore.board 18 | console.log('gameBoardStore.isOver', gameBoardStore.isOver) 19 | const rows = gameBoard.map((row, i) => { 20 | const blocksInRows = row.map((block, j) => { 21 | const classString = 'game-block ' + (block || 'block-empty') 22 | return ( 23 | 24 | ) 25 | }) 26 | return ( 27 | 28 | {blocksInRows} 29 | 30 | ) 31 | }) 32 | return ( 33 |
34 | 35 | 36 | 37 | {rows} 38 | 39 |
40 |
41 | ); 42 | } 43 | 44 | bindKeyboardEvents(){ 45 | const {gameBoardStore} = this.props 46 | 47 | key('down', gameBoardStore.moveDown) 48 | key('left', gameBoardStore.moveLeft) 49 | key('right', gameBoardStore.moveRight) 50 | key('up', gameBoardStore.rotatePiece) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var loaders = require('./webpack.loaders'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); 7 | 8 | // local css modules 9 | loaders.push({ 10 | test: /[\/\\]src[\/\\].*\.css/, 11 | exclude: /(node_modules|bower_components|public)/, 12 | loader: ExtractTextPlugin.extract('style', 'css') 13 | }); 14 | 15 | // local scss modules 16 | loaders.push({ 17 | test: /[\/\\]src[\/\\].*\.scss/, 18 | exclude: /(node_modules|bower_components|public)/, 19 | loader: ExtractTextPlugin.extract('style', 'css!postcss!sass') 20 | }); 21 | // global css files 22 | loaders.push({ 23 | test: /[\/\\](node_modules|global)[\/\\].*\.css$/, 24 | loader: ExtractTextPlugin.extract('style', 'css') 25 | }); 26 | 27 | module.exports = { 28 | entry: [ 29 | './src/index.js' 30 | ], 31 | output: { 32 | path: path.join(__dirname, 'dist'), 33 | filename: '[chunkhash].js' 34 | }, 35 | resolve: { 36 | extensions: ['', '.js', '.jsx'] 37 | }, 38 | module: { 39 | loaders 40 | }, 41 | plugins: [ 42 | new WebpackCleanupPlugin(), 43 | new webpack.DefinePlugin({ 44 | 'process.env': { 45 | NODE_ENV: '"production"' 46 | } 47 | }), 48 | new webpack.optimize.UglifyJsPlugin({ 49 | compress: { 50 | warnings: false, 51 | screw_ie8: true, 52 | drop_console: true, 53 | drop_debugger: true 54 | } 55 | }), 56 | new webpack.optimize.OccurenceOrderPlugin(), 57 | new ExtractTextPlugin('[contenthash].css', { 58 | allChunks: true 59 | }), 60 | new HtmlWebpackPlugin({ 61 | template: './src/template.html', 62 | title: 'Webpack App' 63 | }), 64 | new webpack.optimize.DedupePlugin() 65 | ] 66 | }; 67 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var webpack = require('webpack'); 3 | var path = require('path'); 4 | var loaders = require('./webpack.loaders'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | const HOST = process.env.HOST || "127.0.0.1"; 8 | const PORT = process.env.PORT || "8888"; 9 | 10 | loaders.push({ 11 | test: /[\/\\](node_modules|global)[\/\\].*\.css$/, 12 | loaders: [ 13 | 'style?sourceMap', 14 | 'css' 15 | ] 16 | }); 17 | // local scss modules 18 | loaders.push({ 19 | test: /[\/\\]src[\/\\].*\.scss/, 20 | exclude: /(node_modules|bower_components|public)/, 21 | loaders: [ 22 | 'style?sourceMap', 23 | 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]', 24 | 'sass' 25 | ] 26 | }); 27 | 28 | // local css modules 29 | loaders.push({ 30 | test: /[\/\\]src[\/\\].*\.css/, 31 | exclude: /(node_modules|bower_components|public)/, 32 | loaders: [ 33 | 'style?sourceMap', 34 | 'css' 35 | ] 36 | }); 37 | 38 | module.exports = { 39 | entry: [ 40 | `webpack-dev-server/client?http://${HOST}:${PORT}`, 41 | `webpack/hot/only-dev-server`, 42 | `./src/index.js` 43 | ], 44 | devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map', 45 | output: { 46 | path: path.join(__dirname, 'public'), 47 | filename: 'bundle.js' 48 | }, 49 | resolve: { 50 | extensions: ['', '.js', '.jsx'] 51 | }, 52 | module: { 53 | loaders 54 | }, 55 | devServer: { 56 | contentBase: "./public", 57 | noInfo: true, 58 | inline: true, 59 | historyApiFallback: true, 60 | port: PORT, 61 | host: HOST, 62 | devtool:"source-map" 63 | }, 64 | plugins: [ 65 | new webpack.NoErrorsPlugin(), 66 | new webpack.HotModuleReplacementPlugin(), 67 | new HtmlWebpackPlugin({ 68 | template: './src/template.html' 69 | }), 70 | ] 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobx-react-tetris", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "webpack --config webpack.production.config.js --progress --profile --colors", 7 | "start": "webpack-dev-server -d --progress --profile --colors", 8 | "lint": "eslint --ext js --ext jsx src exit 0", 9 | "test": "jest" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "6.14.0", 13 | "babel-jest": "^17.0.2", 14 | "babel-loader": "6.2.4", 15 | "babel-plugin-transform-class-properties": "^6.19.0", 16 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 17 | "babel-plugin-transform-runtime": "^6.15.0", 18 | "babel-preset-es2015": "6.14.0", 19 | "babel-preset-react": "6.11.1", 20 | "babel-runtime": "^6.11.6", 21 | "css-loader": "0.25.0", 22 | "enzyme": "^2.6.0", 23 | "extract-text-webpack-plugin": "^1.0.1", 24 | "file-loader": "0.9.0", 25 | "html-webpack-plugin": "^2.22.0", 26 | "identity-obj-proxy": "^3.0.0", 27 | "jest": "^17.0.3", 28 | "jest-css-modules": "^1.1.0", 29 | "react-addons-test-utils": "^15.4.1", 30 | "react-hot-loader": "^1.3.0", 31 | "style-loader": "0.13.1", 32 | "tape": "^4.6.3", 33 | "url-loader": "0.5.7", 34 | "webpack": "1.13.2", 35 | "webpack-cleanup-plugin": "^0.3.0", 36 | "webpack-dev-server": "1.15.1" 37 | }, 38 | "dependencies": { 39 | "keymaster": "^1.6.2", 40 | "mobx": "^2.6.0", 41 | "mobx-react": "^3.5.8", 42 | "react": "^15.3.2", 43 | "react-dom": "^15.3.2" 44 | }, 45 | "jest": { 46 | "modulePaths": [ 47 | "/shared/vendor/modules" 48 | ], 49 | "moduleFileExtensions": [ 50 | "js", 51 | "jsx" 52 | ], 53 | "moduleDirectories": [ 54 | "node_modules", 55 | "bower_components", 56 | "shared" 57 | ], 58 | "scriptPreprocessor": "/node_modules/babel-jest", 59 | "moduleNameMapper": { 60 | "^react(.*)$": "/vendor/react-master$1", 61 | "\\.(css|less)$": "identity-obj-proxy", 62 | "\\.(gif|ttf|eot|svg)$": "/__mocks__/fileMock.js" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/stores/boardStore.js: -------------------------------------------------------------------------------- 1 | import {BOARD_HEIGHT, BOARD_WIDTH, FRAME_TIME} from '../constants/' 2 | import {observable, computed, action} from 'mobx'; 3 | 4 | export default class BoardStore { 5 | 6 | board = new Array(BOARD_HEIGHT).fill(null).map(a => buildGameRow()) 7 | 8 | get currentBoard(){ 9 | return this.board.map(a => Object.assign([], a)) 10 | } 11 | 12 | isEmptyPosition = (shape, rotation, position) => { 13 | const blocks = shape.blocks[rotation]; 14 | 15 | for (let x = 0; x < shape.blocks[0].length; x++) { 16 | for (let y = 0; y < shape.blocks[0].length; y++) { 17 | const block = blocks[y][x]; 18 | const boardX = x + position.x 19 | const boardY = y + position.y 20 | 21 | if (block) { 22 | if (boardX >= 0 && boardX < BOARD_WIDTH && boardY < BOARD_HEIGHT) { 23 | if (this.board[boardY][boardX]) { 24 | return false 25 | } 26 | } else { 27 | return false 28 | } 29 | } 30 | } 31 | } 32 | return true; 33 | } 34 | 35 | setPiece = (shape, rotation, position) => { 36 | pieceSetter(this.board)(shape.blocks[rotation], position, shape.className) 37 | this.removeLines() 38 | } 39 | 40 | _setPiece = (blocks, position, className) => { 41 | for (var x = 0; x < blocks[0].length; x++) { 42 | for (var y = 0; y < blocks[0].length; y++) { 43 | if (blocks[y][x]) { 44 | const boardX = position.x + x 45 | const boardY = position.y + y 46 | this.board[boardY][boardX] = className; 47 | } 48 | } 49 | } 50 | } 51 | 52 | 53 | @action removeLines = () => { 54 | for (let y = 0; y < this.board.length; y++) { 55 | if (this.board[y].every(a => a)) { 56 | this.board.splice(y, 1) 57 | this.board.unshift(buildGameRow()) 58 | } 59 | } 60 | } 61 | } 62 | 63 | export const pieceSetter = (board) => (blocks, position, className) => { 64 | for (let x = 0; x < blocks[0].length; x++) { 65 | for (let y = 0; y < blocks[0].length; y++) { 66 | const block = blocks[y][x]; 67 | if (block) { 68 | const boardX = position.x + x 69 | const boardY = position.y + y 70 | board[boardY][boardX] = className 71 | } 72 | } 73 | } 74 | } 75 | 76 | export const buildGameRow = () => { 77 | return new Array(BOARD_WIDTH).fill(false) 78 | } 79 | -------------------------------------------------------------------------------- /src/stores/gameBoardStore.js: -------------------------------------------------------------------------------- 1 | import {observable, computed, reaction, action} from 'mobx'; 2 | import {BOARD_WIDTH, FRAME_TIME} from '../constants/' 3 | import {Z} from '../types/shapeTypes' 4 | import {pieceSetter} from './boardStore' 5 | import {getRandomShape} from '../utils/TypesUtil' 6 | 7 | export default class GameBoardStore { 8 | 9 | @observable piece 10 | @observable isOver = false 11 | 12 | _interval 13 | 14 | constructor(boardStore){ 15 | this.boardStore = boardStore 16 | this.setUpNewPiece() 17 | } 18 | 19 | @computed get board(){ 20 | const board = this.boardStore.currentBoard 21 | 22 | const setter = pieceSetter(board) 23 | setter( 24 | this.piece.shape.blocks[this.piece.rotation], 25 | this.piece.position, 26 | this.piece.shape.className 27 | ) 28 | 29 | return board 30 | } 31 | 32 | getShapeData = () => { 33 | return getPieceData() 34 | } 35 | 36 | start = () => { 37 | this._interval = setInterval(() => 38 | this.moveDown() 39 | , FRAME_TIME) 40 | } 41 | 42 | @action moveDown = () => { 43 | const newPosition = Object.assign({}, this.piece.position) 44 | newPosition.y += 1 45 | 46 | if(this.boardStore.isEmptyPosition(this.piece.shape, this.piece.rotation, newPosition)){ 47 | this.piece.position = newPosition 48 | } else { 49 | this.lockIn() 50 | } 51 | } 52 | 53 | @action moveLeft = () => { 54 | const newPosition = Object.assign({}, this.piece.position) 55 | newPosition.x -= 1 56 | 57 | if(this.boardStore.isEmptyPosition(this.piece.shape, this.piece.rotation, newPosition)) { 58 | this.piece.position = newPosition 59 | } 60 | } 61 | 62 | @action moveRight = () => { 63 | const newPosition = Object.assign({}, this.piece.position) 64 | newPosition.x += 1 65 | 66 | if(this.boardStore.isEmptyPosition(this.piece.shape, this.piece.rotation, newPosition)) { 67 | this.piece.position = newPosition 68 | } 69 | } 70 | 71 | lockIn = () => { 72 | this.boardStore.setPiece(this.piece.shape, this.piece.rotation, this.piece.position) 73 | this.setUpNewPiece() 74 | } 75 | 76 | rotatePiece = () => { 77 | const newRotation = (this.piece.rotation + 1) % this.piece.shape.blocks.length 78 | 79 | if(this.boardStore.isEmptyPosition(this.piece.shape, newRotation, this.piece.position)) { 80 | this.piece.rotation = newRotation 81 | } 82 | } 83 | 84 | playerLost = () => { 85 | clearInterval(this._interval) 86 | this.isOver = true 87 | } 88 | 89 | setUpNewPiece = () => { 90 | const shape = getRandomShape() 91 | const position = { 92 | x: (BOARD_WIDTH / 2) - shape.blocks.length / 2, 93 | y: 0 94 | } 95 | const newPiece = { 96 | shape, 97 | rotation: 0, 98 | position 99 | } 100 | if(!this.boardStore.isEmptyPosition(newPiece.shape, newPiece.rotation, position)) { 101 | this.playerLost() 102 | } else { 103 | this.piece = newPiece 104 | } 105 | } 106 | 107 | } 108 | 109 | class ShapeStore { 110 | position = { 111 | x: BOARD_WIDTH / 2, 112 | y: 0 113 | } 114 | getShapeData = () => { 115 | return { 116 | shape: Z, 117 | rotation: 0, 118 | position: this.position 119 | } 120 | } 121 | tick = () => { 122 | const newPosition = Object.assign({}, this.position) 123 | newPosition.y = 1 124 | } 125 | } -------------------------------------------------------------------------------- /src/types/shapeTypes.js: -------------------------------------------------------------------------------- 1 | export const I = { 2 | blocks: [ 3 | [ 4 | [0, 1, 0, 0], 5 | [0, 1, 0, 0], 6 | [0, 1, 0, 0], 7 | [0, 1, 0, 0] 8 | ], 9 | [ 10 | [0, 0, 0, 0], 11 | [1, 1, 1, 1], 12 | [0, 0, 0, 0], 13 | [0, 0, 0, 0] 14 | ], 15 | [ 16 | [0, 0, 1, 0], 17 | [0, 0, 1, 0], 18 | [0, 0, 1, 0], 19 | [0, 0, 1, 0] 20 | ], 21 | [ 22 | [0, 0, 0, 0], 23 | [0, 0, 0, 0], 24 | [1, 1, 1, 1], 25 | [0, 0, 0, 0] 26 | ] 27 | ], 28 | className: 'shape-i' 29 | } 30 | 31 | export const J = { 32 | blocks: [ 33 | [ 34 | [0, 1, 0, 0], 35 | [0, 1, 0, 0], 36 | [1, 1, 0, 0], 37 | [0, 0, 0, 0] 38 | ], 39 | [ 40 | [1, 0, 0, 0], 41 | [1, 1, 1, 0], 42 | [0, 0, 0, 0], 43 | [0, 0, 0, 0] 44 | ], 45 | [ 46 | [0, 1, 1, 0], 47 | [0, 1, 0, 0], 48 | [0, 1, 0, 0], 49 | [0, 0, 0, 0] 50 | ], 51 | [ 52 | [0, 0, 0, 0], 53 | [1, 1, 1, 0], 54 | [0, 0, 1, 0], 55 | [0, 0, 0, 0] 56 | ] 57 | ], 58 | className: 'shape-j' 59 | } 60 | 61 | export const L = { 62 | blocks: [ 63 | [ 64 | [0, 1, 0, 0], 65 | [0, 1, 0, 0], 66 | [0, 1, 1, 0], 67 | [0, 0, 0, 0] 68 | ], 69 | [ 70 | [0, 0, 0, 0], 71 | [1, 1, 1, 0], 72 | [1, 0, 0, 0], 73 | [0, 0, 0, 0] 74 | ], 75 | [ 76 | [1, 1, 0, 0], 77 | [0, 1, 0, 0], 78 | [0, 1, 0, 0], 79 | [0, 0, 0, 0] 80 | ], 81 | [ 82 | [0, 0, 1, 0], 83 | [1, 1, 1, 0], 84 | [0, 0, 0, 0], 85 | [0, 0, 0, 0] 86 | ] 87 | ], 88 | className: 'shape-l' 89 | } 90 | 91 | export const O = { 92 | blocks: [ 93 | [ 94 | [1, 1, 0, 0], 95 | [1, 1, 0, 0], 96 | [0, 0, 0, 0], 97 | [0, 0, 0, 0] 98 | ], 99 | [ 100 | [1, 1, 0, 0], 101 | [1, 1, 0, 0], 102 | [0, 0, 0, 0], 103 | [0, 0, 0, 0] 104 | ], 105 | [ 106 | [1, 1, 0, 0], 107 | [1, 1, 0, 0], 108 | [0, 0, 0, 0], 109 | [0, 0, 0, 0] 110 | ], 111 | [ 112 | [1, 1, 0, 0], 113 | [1, 1, 0, 0], 114 | [0, 0, 0, 0], 115 | [0, 0, 0, 0] 116 | ] 117 | ], 118 | className: 'shape-o' 119 | } 120 | 121 | export const S = { 122 | blocks: [ 123 | [ 124 | [0, 0, 0, 0], 125 | [0, 1, 1, 0], 126 | [1, 1, 0, 0], 127 | [0, 0, 0, 0] 128 | ], 129 | [ 130 | [1, 0, 0, 0], 131 | [1, 1, 0, 0], 132 | [0, 1, 0, 0], 133 | [0, 0, 0, 0] 134 | ], 135 | [ 136 | [0, 1, 1, 0], 137 | [1, 1, 0, 0], 138 | [0, 0, 0, 0], 139 | [0, 0, 0, 0] 140 | ], 141 | [ 142 | [0, 1, 0, 0], 143 | [0, 1, 1, 0], 144 | [0, 0, 1, 0], 145 | [0, 0, 0, 0] 146 | ] 147 | ], 148 | className: 'shape-s' 149 | } 150 | 151 | export const T = { 152 | blocks: [ 153 | [ 154 | [0, 0, 0, 0], 155 | [1, 1, 1, 0], 156 | [0, 1, 0, 0], 157 | [0, 0, 0, 0] 158 | ], 159 | [ 160 | [0, 1, 0, 0], 161 | [1, 1, 0, 0], 162 | [0, 1, 0, 0], 163 | [0, 0, 0, 0] 164 | ], 165 | [ 166 | [0, 1, 0, 0], 167 | [1, 1, 1, 0], 168 | [0, 0, 0, 0], 169 | [0, 0, 0, 0] 170 | ], 171 | [ 172 | [0, 1, 0, 0], 173 | [0, 1, 1, 0], 174 | [0, 1, 0, 0], 175 | [0, 0, 0, 0] 176 | ] 177 | ], 178 | className: 'shape-t' 179 | }; 180 | 181 | export const Z = { 182 | blocks: [ 183 | [ 184 | [0, 0, 0, 0], 185 | [1, 1, 0, 0], 186 | [0, 1, 1, 0], 187 | [0, 0, 0, 0] 188 | ], 189 | [ 190 | [0, 1, 0, 0], 191 | [1, 1, 0, 0], 192 | [1, 0, 0, 0], 193 | [0, 0, 0, 0] 194 | ], 195 | [ 196 | [1, 1, 0, 0], 197 | [0, 1, 1, 0], 198 | [0, 0, 0, 0], 199 | [0, 0, 0, 0] 200 | ], 201 | [ 202 | [0, 0, 1, 0], 203 | [0, 1, 1, 0], 204 | [0, 1, 0, 0], 205 | [0, 0, 0, 0] 206 | ] 207 | ], 208 | className: 'shape-z' 209 | } 210 | --------------------------------------------------------------------------------