├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── app ├── App.vue ├── Minesweeper │ ├── Cell.vue │ ├── Minesweeper.css │ ├── Minesweeper.vue │ └── sprite.png ├── boot.mjs └── 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": [ 3 | "plugin:vue/recommended", 4 | "standard" 5 | ], 6 | "rules": { 7 | "prefer-const": "error", 8 | "no-var": "error", 9 | "prefer-template": "error", 10 | "vue/require-prop-types": "off", 11 | "vue/require-default-prop": "off", 12 | "vue/max-attributes-per-line": [ 13 | "error", 14 | { 15 | "singleline": 5, 16 | "multiline": { 17 | "max": 5, 18 | "allowFirstLine": true 19 | } 20 | } 21 | ], 22 | "vue/singleline-html-element-content-newline": "off", 23 | "vue/component-name-in-template-casing": "off" 24 | }, 25 | "plugins": [ 26 | "vue" 27 | ], 28 | "globals": { 29 | "_": true, 30 | "Vue": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | .netbeans 4 | .node_history 5 | .vscode 6 | .tscache 7 | .idea 8 | build 9 | www/** 10 | !www/favicon.ico 11 | !www/index.html -------------------------------------------------------------------------------- /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 Minesweeper 2 | Vue based classic Minesweeper 🚩 [Try Demo](https://elevista.github.io/vue-minesweeper) 3 | 4 | ![minesweeper-vue-electron](https://user-images.githubusercontent.com/9513647/52012475-f9fe9080-251d-11e9-8970-f61dc29541e0.png) 5 | ![minesweeper-vue-electron](https://user-images.githubusercontent.com/9513647/52012474-f9fe9080-251d-11e9-885d-8bf1e4b8d110.png) 6 | 7 | (`electron` branch) 8 | 9 | [https://github.com/elevista/vue-minesweeper](https://github.com/elevista/vue-minesweeper) 10 | 11 | ## License 12 | The MIT License (MIT) 13 | 14 | Copyright (c) 2018 Elevista 15 | -------------------------------------------------------------------------------- /app/App.vue: -------------------------------------------------------------------------------- 1 | 26 | 58 | 129 | -------------------------------------------------------------------------------- /app/Minesweeper/Cell.vue: -------------------------------------------------------------------------------- 1 | 4 | 45 | 74 | -------------------------------------------------------------------------------- /app/Minesweeper/Minesweeper.css: -------------------------------------------------------------------------------- 1 | .minesweeper { 2 | display: inline-block; 3 | background-color: silver; 4 | border: outset 2px #eee; 5 | padding: 6px; 6 | } 7 | .board {border: inset 2px #eee;} 8 | .board .row { 9 | height: 16px; 10 | white-space: nowrap; 11 | } 12 | .indicator { 13 | height: 25px; 14 | border: inset 2px #eee; 15 | padding: 4px 6px; 16 | text-align: center; 17 | margin-bottom: 6px; 18 | } 19 | .indicator * {background-repeat: no-repeat;} 20 | .indicator > * {color: transparent;} 21 | .indicator .smiley { 22 | width: 26px; 23 | height: 26px; 24 | display: inline-block; 25 | border: none; 26 | outline: none; 27 | margin: 0; 28 | padding: 0; 29 | background: url(sprite.png) no-repeat; 30 | background-position-y: -55px; 31 | background-position-x: 0; 32 | } 33 | .indicator .smiley.ooh {background-position-x: -52px;} 34 | .indicator .smiley.dead {background-position-x: -78px;} 35 | .indicator .smiley.win {background-position-x: -104px;} 36 | .indicator .smiley:active {background-position-x: -26px;} 37 | .indicator .left {float: left;} 38 | .indicator .right {float: right;} 39 | .indicator .count { 40 | width: 13px; 41 | height: 23px; 42 | display: inline-block; 43 | background: url(sprite.png) no-repeat; 44 | background-position-y: 0; 45 | } 46 | .indicator .count.n0 {background-position-x: 0;} 47 | .indicator .count.n1 {background-position-x: -13px;} 48 | .indicator .count.n2 {background-position-x: -26px;} 49 | .indicator .count.n3 {background-position-x: -39px;} 50 | .indicator .count.n4 {background-position-x: -52px;} 51 | .indicator .count.n5 {background-position-x: -65px;} 52 | .indicator .count.n6 {background-position-x: -78px;} 53 | .indicator .count.n7 {background-position-x: -91px;} 54 | .indicator .count.n8 {background-position-x: -104px;} 55 | .indicator .count.n9 {background-position-x: -117px;} 56 | .indicator .count.n- {background-position-x: -130px;} 57 | -------------------------------------------------------------------------------- /app/Minesweeper/Minesweeper.vue: -------------------------------------------------------------------------------- 1 | 2 | 25 | 172 | 173 | -------------------------------------------------------------------------------- /app/Minesweeper/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Elevista/vue-minesweeper/020d1c12fdce5237e67a664d6d78be7ea1893fea/app/Minesweeper/sprite.png -------------------------------------------------------------------------------- /app/boot.mjs: -------------------------------------------------------------------------------- 1 | import App from './App.vue' 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({ el: 'app', render: h => h(App) }) // eslint-disable-line no-new 10 | -------------------------------------------------------------------------------- /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 | const lodash = _.runInContext() 8 | export const lodashMixin = _(['pull', 'pullAll', 'pullAllBy', 'pullAllWith', 'pullAt', 'remove']) 9 | .map(fnName => { 10 | const fn = lodash[fnName] 11 | return [ 12 | fnName, 13 | function (v, ...args) { 14 | const 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-minesweeper", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "Vue Minesweeper", 6 | "keywords": [], 7 | "devDependencies": { 8 | "css-loader": "^2.1.0", 9 | "eslint-plugin-vue": "^5.1.0", 10 | "file-loader": "^3.0.1", 11 | "lodash": "^4.17.11", 12 | "push-dir": "^0.4.1", 13 | "standard": "^12.0.1", 14 | "style-loader": "^0.23.1", 15 | "vue": "^2.5.22", 16 | "vue-loader": "^15.5.1", 17 | "vue-template-compiler": "^2.5.22", 18 | "webpack": "^4.29.0", 19 | "webpack-cli": "^3.2.1", 20 | "webpack-dev-server": "^3.1.14" 21 | }, 22 | "scripts": { 23 | "start": "webpack-dev-server --content-base www/ --hot", 24 | "build": "webpack", 25 | "build:prod": "webpack --mode=production", 26 | "gh-pages": "npm run build:prod && push-dir --dir=www --branch=gh-pages --cleanup", 27 | "lint": "eslint --ext .js,.mjs,.vue . --fix" 28 | }, 29 | "main": "app.js", 30 | "repository": { 31 | "type": "git" 32 | }, 33 | "author": { 34 | "name": "Elevista", 35 | "email": "sunnyholic@sunnyholic.com", 36 | "homepage": "http://sunnyholic.com" 37 | }, 38 | "eslintIgnore": ["node_modules", "www", "build"], 39 | "license": "" 40 | } 41 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 4 | module.exports = function (name) { 5 | return { 6 | mode: 'development', 7 | name, 8 | module: { 9 | rules: [ 10 | { test: /\.vue$/, loader: 'vue-loader' }, 11 | { test: /\.css$/, loader: 'vue-style-loader!css-loader' }, 12 | { test: /\.(png|woff|woff2|eot|ttf|svg|jpg|otf|gif)$/, loader: 'file-loader?outputPath=files/' } 13 | ] 14 | }, 15 | plugins: [new VueLoaderPlugin(), new webpack.ProvidePlugin({ _: 'lodash', Vue: 'vue' })], 16 | resolve: { alias: { '~': path.resolve(__dirname, `${name}`) } } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const baseConf = require('./webpack.config.base') 3 | 4 | module.exports = function (env, argv = {}) { 5 | const sourceMapFileNameTemplate = info => `webpack:///${info.resourcePath.replace(/\.vue$/, '.vue.html')}` 6 | const sourceMapFileNameDupTemplate = info => sourceMapFileNameTemplate(info) + info.query 7 | 8 | const config = Object.assign(baseConf('app'), { 9 | entry: './app/boot.mjs', 10 | devtool: argv.mode === 'production' ? false : 'inline-source-map', 11 | output: { 12 | filename: 'bundle.js', 13 | path: path.resolve(__dirname, 'www/'), 14 | devtoolModuleFilenameTemplate: sourceMapFileNameTemplate, 15 | devtoolFallbackModuleFilenameTemplate: sourceMapFileNameDupTemplate 16 | } 17 | }) 18 | return config 19 | } 20 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Elevista/vue-minesweeper/020d1c12fdce5237e67a664d6d78be7ea1893fea/www/favicon.ico -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vue Minesweeper 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------