├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── app
├── app.css
├── app.html
├── app.js
├── boot.mjs
├── tetris
│ ├── block.vue
│ ├── cell.vue
│ ├── ground.vue
│ ├── player.vue
│ ├── soundService
│ │ ├── soundService.mjs
│ │ └── sounds
│ │ │ ├── bgm1.mp3
│ │ │ ├── bgm2.mp3
│ │ │ ├── bgm3.mp3
│ │ │ ├── bgm4.mp3
│ │ │ ├── complete.mp3
│ │ │ ├── drop.mp3
│ │ │ ├── gameover.mp3
│ │ │ ├── lineClear.mp3
│ │ │ └── title.mp3
│ ├── stageComputed.js
│ └── tetris.vue
└── util
│ ├── Deferred.mjs
│ ├── filters.mjs
│ └── util.mjs
├── package-lock.json
├── package.json
├── webpack.config.base.js
├── webpack.config.js
└── www
├── favicon.ico
└── index.html
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "production": {
4 | "presets": [
5 | ["es2015", {"modules": false}]
6 | ],
7 | "comments": false
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard",
3 | "plugins": [
4 | "html"
5 | ],
6 | "globals": {
7 | "_": true,
8 | "$": true,
9 | "jQuery": true,
10 | "Vue": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_STORE
3 | .netbeans
4 | .node_history
5 | .vscode
6 | .tscache
7 | .idea
8 | www/**
9 | !www/favicon.ico
10 | !www/index.html
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Tetris
2 | Vue based Tetris 🏰 [Try Demo](https://elevista.github.io/vue-tetris)
3 |
4 | [https://github.com/elevista/vue-tetris](https://github.com/elevista/vue-tetris)
5 |
6 | ## License
7 | The MIT License (MIT)
8 |
9 | Copyright (c) 2018 Elevista
10 |
--------------------------------------------------------------------------------
/app/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/app.css
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | import tetris from './tetris/tetris.vue'
2 | import 'animate.css/animate.css'
3 |
4 | export default {
5 | name: 'app',
6 | data () {
7 | return {}
8 | },
9 | components: {tetris},
10 | templateSrc: './app.html',
11 | styleSrc: './app.css'
12 | }
13 |
--------------------------------------------------------------------------------
/app/boot.mjs:
--------------------------------------------------------------------------------
1 | import app from './app'
2 | import Vue from 'vue'
3 | import './util/filters.mjs'
4 | import _ from 'lodash'
5 | import { lodashMixin } from './util/util.mjs'
6 |
7 | _.mixin(lodashMixin)
8 |
9 | new Vue({render: h => h(app), el: 'app'}) // eslint-disable-line no-new
10 |
--------------------------------------------------------------------------------
/app/tetris/block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | |
4 |
5 |
6 |
7 |
50 |
51 |
54 |
--------------------------------------------------------------------------------
/app/tetris/cell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
25 |
26 |
74 |
--------------------------------------------------------------------------------
/app/tetris/ground.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | |
6 |
7 |
8 |
9 |
10 |
11 |
62 |
63 |
73 |
--------------------------------------------------------------------------------
/app/tetris/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Next
10 |
11 |
12 |
13 |
14 |
15 |
Level
16 | {{level|numberComma}}
17 |
18 |
19 |
Score
20 | {{score|numberComma}}
21 |
22 |
23 |
Rows
24 | {{rowCleared|numberComma}}
25 |
26 |
27 |
28 |
Statistics
29 |
30 | {{key}}
31 | {{value}}
32 | {{((value / statistics.TOTAL) * 100 || 0)| round(2)}}%
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
{{stateText}}
41 |
42 |
43 |
44 |
45 |
254 |
255 |
276 |
--------------------------------------------------------------------------------
/app/tetris/soundService/soundService.mjs:
--------------------------------------------------------------------------------
1 | import title from './sounds/title.mp3'
2 | import bgm1 from './sounds/bgm1.mp3'
3 | import bgm2 from './sounds/bgm2.mp3'
4 | import bgm3 from './sounds/bgm3.mp3'
5 | import bgm4 from './sounds/bgm4.mp3'
6 | import complete from './sounds/complete.mp3'
7 | import gameover from './sounds/gameover.mp3'
8 | import drop from './sounds/drop.mp3'
9 | import lineClear from './sounds/lineClear.mp3'
10 | import _ from 'lodash'
11 |
12 | const audios = {
13 | title,
14 | bgm1,
15 | bgm2,
16 | bgm3,
17 | bgm4,
18 | complete,
19 | gameover,
20 | drop,
21 | lineClear
22 | }
23 |
24 | export default {
25 | data () {
26 | return {
27 | currentAudio: null,
28 | muted: false
29 | }
30 | },
31 | methods: {
32 | getAudio (fileName) {
33 | return new window.Audio(audios[fileName])
34 | },
35 | playEffect (name) {
36 | !this.muted && this.getAudio(name).play()
37 | },
38 | playSound (name, isRepeat = false) {
39 | if (this.currentAudio) this.currentAudio.pause()
40 | this.currentAudio = this.getAudio(name)
41 | if (isRepeat) this.currentAudio.loop = true
42 | this.currentAudio.volume = 0.4
43 | this.currentAudio.muted = this.muted
44 | this.currentAudio.play()
45 | },
46 | pauseSound () {
47 | this.currentAudio.pause()
48 | },
49 | resumeSound () {
50 | this.currentAudio.play()
51 | },
52 | stopSound () {
53 | if (this.currentAudio.currentTime > 0) {
54 | this.currentAudio.pause()
55 | this.currentAudio.currentTime = 0
56 | }
57 | },
58 | muteSound () {
59 | this.muted = !this.muted
60 | _.assign(this.currentAudio, {muted: this.muted})
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/bgm1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/bgm1.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/bgm2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/bgm2.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/bgm3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/bgm3.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/bgm4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/bgm4.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/complete.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/complete.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/drop.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/drop.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/gameover.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/gameover.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/lineClear.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/lineClear.mp3
--------------------------------------------------------------------------------
/app/tetris/soundService/sounds/title.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/app/tetris/soundService/sounds/title.mp3
--------------------------------------------------------------------------------
/app/tetris/stageComputed.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | cellSize () { return this.stage.cellSize },
4 | width () { return this.stage.width },
5 | height () { return this.stage.height },
6 | blocks () { return this.stage.blocks },
7 | blockType () { return this.stage.blockType }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/tetris/tetris.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
71 |
72 |
--------------------------------------------------------------------------------
/app/util/Deferred.mjs:
--------------------------------------------------------------------------------
1 | export function Deferred () {
2 | this.promise = new Promise((resolve, reject) => Object.assign(this, {resolve, reject}))
3 | }
4 |
--------------------------------------------------------------------------------
/app/util/filters.mjs:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import _ from 'lodash'
3 |
4 | Vue.filter('numberComma', n => `${n || 0}`.replace(/\B(?=(\d{3})+(?!\d))/g, ','))
5 | _.forEach(
6 | [
7 | 'ceil',
8 | 'floor',
9 | 'max',
10 | 'maxBy',
11 | 'mean',
12 | 'meanBy',
13 | 'min',
14 | 'minBy',
15 | 'round',
16 | 'sum',
17 | 'sumBy',
18 | 'clamp',
19 | 'camelCase',
20 | 'capitalize',
21 | 'endsWith',
22 | 'escape',
23 | 'escapeRegExp',
24 | 'kebabCase',
25 | 'lowerCase',
26 | 'lowerFirst',
27 | 'pad',
28 | 'padEnd',
29 | 'padStart',
30 | 'parseInt',
31 | 'repeat',
32 | 'replace',
33 | 'snakeCase',
34 | 'split',
35 | 'startCase',
36 | 'startsWith',
37 | 'template',
38 | 'toLower',
39 | 'toUpper',
40 | 'trim',
41 | 'trimEnd',
42 | 'trimStart',
43 | 'truncate',
44 | 'unescape',
45 | 'upperCase',
46 | 'upperFirst',
47 | 'words'
48 | ],
49 | fn => Vue.filter(fn, _[fn])
50 | )
51 |
--------------------------------------------------------------------------------
/app/util/util.mjs:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | export function sum (a, b) {
4 | return a + b
5 | }
6 |
7 | let lodash = _.runInContext()
8 | export let lodashMixin = _(['pull', 'pullAll', 'pullAllBy', 'pullAllWith', 'pullAt', 'remove'])
9 | .map(fnName => {
10 | let fn = lodash[fnName]
11 | return [
12 | fnName,
13 | function (v, ...args) {
14 | let ret = fn(v, ...args)
15 | if (v instanceof Array) v.push()
16 | return ret
17 | }]
18 | })
19 | .fromPairs()
20 | .value()
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-tetris",
3 | "private": true,
4 | "version": "0.0.0",
5 | "description": "Vue Tetris",
6 | "keywords": [],
7 | "devDependencies": {
8 | "babel-core": "^6.24.1",
9 | "babel-loader": "^7.0.0",
10 | "babel-plugin-istanbul": "^5.1.0",
11 | "babel-polyfill": "^6.26.0",
12 | "babel-preset-es2015": "^6.24.1",
13 | "css-loader": "^0.28.4",
14 | "eslint": "^4.15.0",
15 | "eslint-config-standard": "^10.2.1",
16 | "eslint-plugin-html": "^4.0.1",
17 | "eslint-plugin-import": "^2.7.0",
18 | "eslint-plugin-jasmine": "^2.8.4",
19 | "eslint-plugin-node": "^5.1.1",
20 | "eslint-plugin-promise": "^3.5.0",
21 | "eslint-plugin-standard": "^3.0.1",
22 | "file-loader": "^0.11.2",
23 | "jquery": "^3.2.1",
24 | "lodash": "^4.17.4",
25 | "node-sass": "^4.7.2",
26 | "push-dir": "^0.4.1",
27 | "sass-loader": "^6.0.6",
28 | "style-loader": "^0.18.2",
29 | "uglify-js": "^2.8.0",
30 | "uglifyjs-webpack-plugin": "0.4.3",
31 | "vue": "^2.5.13",
32 | "vue-loader": "^13.6.2",
33 | "vue-template-compiler": "^2.5.13",
34 | "vuejs-loader": "^2.1.0",
35 | "webpack": "^2.6.1",
36 | "webpack-dev-server": "^2.5.1"
37 | },
38 | "scripts": {
39 | "start": "webpack-dev-server --content-base www/ --hot",
40 | "build": "webpack",
41 | "build:prod": "webpack --env.prod",
42 | "gh-pages": "npm run build:prod && node node_modules/push-dir/bin/push-dir --dir=www --branch=gh-pages --cleanup",
43 | "lint": "eslint app test --ext .js,.mjs,.vue",
44 | "lint:fix": "eslint app test --ext .js,.mjs,.vue --fix"
45 | },
46 | "main": "app.js",
47 | "repository": {
48 | "type": "git"
49 | },
50 | "author": {
51 | "name": "Elevista",
52 | "email": "sunnyholic@sunnyholic.com",
53 | "homepage": "http://sunnyholic.com"
54 | },
55 | "license": "",
56 | "dependencies": {
57 | "animate.css": "^3.6.1"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | module.exports = function (name, env = {}) {
4 | return {
5 | name,
6 | module: {
7 | rules: [
8 | {test: /\.js$/, include: path.resolve(__dirname, name), loader: 'vuejs-loader'},
9 | {test: /\.mjs$/, loader: 'babel-loader'},
10 | {test: /\.vue$/, include: path.resolve(__dirname, name), loader: 'vue-loader'},
11 | {test: /\.css$/, loader: 'vue-style-loader!css-loader'},
12 | {test: /\.scss$/, loader: 'vue-style-loader!css-loader!sass-loader'},
13 | {test: /\.(png|woff|woff2|eot|ttf|svg|jpg|otf|gif|mp3)$/, loader: 'file-loader?outputPath=files/'}
14 | ]
15 | },
16 | plugins: [new webpack.ProvidePlugin({$: 'jquery', jQuery: 'jquery', _: 'lodash', Vue: 'vue'})],
17 | resolve: {alias: {'~': path.resolve(__dirname, `${name}`)}}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const baseConf = require('./webpack.config.base')
4 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
5 |
6 | module.exports = function (env = {}) {
7 | let sourceMapFileNameTemplate = info => 'webpack:///' + info.resourcePath.replace(/\.vue$/, '.vue.html')
8 | let sourceMapFileNameDupTemplate = info => sourceMapFileNameTemplate(info) + info.query
9 |
10 | let config = Object.assign(baseConf('app', env), {
11 | entry: ['./app/boot.mjs'],
12 | output: {
13 | filename: 'bundle.js',
14 | path: path.resolve(__dirname, 'www/'),
15 | devtoolModuleFilenameTemplate: sourceMapFileNameTemplate,
16 | devtoolFallbackModuleFilenameTemplate: sourceMapFileNameDupTemplate,
17 | }
18 | })
19 | if (env.prod) {
20 | process.env.NODE_ENV = 'production'
21 | config.plugins.push(new webpack.DefinePlugin({'process.env': {NODE_ENV: '"production"'}}), new UglifyJSPlugin())
22 | config.entry.unshift('babel-polyfill')
23 | } else {
24 | config.devtool = 'inline-source-map'
25 | }
26 | return config
27 | }
28 |
--------------------------------------------------------------------------------
/www/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Elevista/vue-tetris/d39d1cbba0849569d85ef71b6b54cb68f1941f39/www/favicon.ico
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vue Tetris
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------