├── 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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------