开会不准带电脑,手机app玩扫雷玩到眼快瞎,而且每次都要忍受长达十秒的广告,自己写一个算了。
详细代码在git里,还在更新。
用webpack主要目的是为了搭建一个简单的webpack server, 顺带着直接用它打包发布好了.
React需要安装一系列相关包,什么ReactDom啊之类的,直接 npm install XX --save-dev
安装之就好。
npm install webpack --save-dev
npm install webpack-dev-server --save-dev
npm install html-webpack-plugin --save-dev
--save-dev
是因为这些包只为开发用,不是给user用的
然后配置一下webpack.config.js:
entry: { //程序的入口,类似于定义java的main方法,逻辑从这里开始
app: path.resolve(__dirname, 'src') + '/index.js'
},
devServer: {//定义webpack-server
port: 8089, // localhost:8089/会访问到本地的页面
contentBase: [
path.join(__dirname, 'site/'), // localhost:8089/index.html会访问到server的site/index.html
],
},
output: { //所有的js代码都输出到bundle.js里,然后放到site/文件夹里
filename: 'bundle.js',
path: path.resolve(__dirname, 'site/')
},
resolve: {
modules: ['node_modules'],
extensions: ['.js', '.jsx'], //import xx.js或者xx.jsx可以省略后缀
},
plugins: [ // 因为有一个html template,所以需要html-webpack-plugin来添加html文件。
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html'//html template直接放在src根目录里。
}),
],
然后在package.json里面的
scripts
里面添加:
"build": "webpack -d",
"start:dev": "webpack -d && webpack-dev-server --progress --colors --config ./webpack.config.js"
这样就可以用 npm run build
来打包所有代码到site里
用npm run start:dev
来启动本地的webpack server
webpack要点大概就这些了。配置好了之后就开始写代码了。
扫雷的数据结构没什么可以纠结的,一个二维数组。
也没啥好纠结的,数字嘛。
数字范围就是1-8,也没啥悬念嘛。 表示周围有1-8个雷(玩了几千把扫雷,只遇到过一次8)。
空白的格子就用0表示了,表示周围没有雷嘛。
还剩下一个9就用来表示雷好雷嘛。到此为止,一位数的数字全部用上了。
剩下的问题就是,怎么把这个数组生成出来。
首先,根据我几千把扫雷的经验,这应该是个随机生成的数组。不大可能有什么库之类的。
那么如何随机生成?
每一盘,雷的个数是不变的,那也就是说,我们可以随机找几个为止把雷放进去,然后计算剩下的每个格子周围有多少个雷就可以了。
事情就简单起来了:(以简单为例子,10*8的矩阵,10个雷。)
一个由0-9组成的二维数组就好了。注意数雷的时候小心处理边界条件。
react的话 html 的template就很简单了。只有几行:
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>saoleititle>
head>
<body>
<div id="game-wrapper"/>
body>
html>
第二行 meta
是为了以后调mobile用的css用的
实际上html的本体只有一行,game-wrapper
。 不用提react的事,也不用提css,也不用提scripts。
上面的webpack说好了,入口要从src/index.js
开始, 所以这个文件就是入口了,也很简单:
import React from 'react';
import ReactDOM from 'react-dom'
import Game from './components/Game';
ReactDOM.render(<Game />, document.getElementById('game-wrapper'));
将id为game-wrapper
的元素替换为Game
component. Game是从 ./components/Game
里import的,但是不用提html的事情。 这两个会在webpack打包的时候结合为一体。
打包之后的html: site/index.html
:
<body>
<div id="game-wrapper"/>
<script type="text/javascript" src="bundle.js">script>
body>
请不要吐槽变量名字
最顶层的Components是Game
然后看,Game
要包含啥
扫雷嘛,就一个MN的格子加上几个按钮(开始,难度),所以需要两种Component: GameBoard
和Button
最后:
GameBoard
其实是有MN个小的格子组成,所以GameBoard
需要一种Component: SingleGrid
所以components包如图:
在下面解释中,Game
是GameBoard
和Button
的爸爸,GameBoard
是SingleGrid
的爸爸。
作为最顶层的Game,
status
里import React from 'react';
import GameBoard from './GameBoard';
import Button from './Button';
import '../styles/index.css';
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
totalRows: 10,
totalColumns: 8,
totalLei: 10,
status: 'inProgress',
gameIndex: 0,
};
//function bind this省略
}
gameFinish() {/*游戏结束时触发这个方法*/}
/*button们的方法在这里*/
setEasyGame() {/*省略*/}
setMediateGame() {/*省略*/}
setHardGame() {/*省略*/}
reStart() {/*省略*/}
render () {
return (
<React.Fragment>
<div className={`gameboard${this.state.totalColumns}`}>
<GameBoard key={this.state.gameIndex} {/*把方法和属性传给儿子GameBoard*/}/>
</div>
<div className={`buttons${this.state.totalColumns}`}>
<Button content="New Game" color="green" onClick={this.reStart}/>
<Button content="Easy" color="green" onClick={this.setEasyGame}/>
//还有俩button
</div>
</React.Fragment>
)
}
}
export default Game
作为大格子,需要知道它爸爸定义了的游戏等级,自己有多少行多少列(从Game那里得到)
gameMatrix
。 每个新的游戏都要generate一个新的矩阵。除此之外,还需要知道哪些格子被打开了(如果点到空白的格子,需要检查周围的格子)。所以,在这一层维护了一个opened
数组,用来记录被打开了的格子,和方便计算成功或者失败。
import React from 'react';
import SingleGrid from './SingleGrid';
import {generateGame} from "../service/dataService";
import '../styles/grid.css'
class GameBoard extends React.Component {
constructor(props) {
super(props);
const {totalRows, totalColumns, totalLei} = props;
this.state = {
gameMatrix: generateGame(totalRows, totalColumns, totalLei),
opened: [],
};
//方法绑定this
}
gameFail(status) {/*被儿子调用,然后调用爸爸的gameFail方法*/};
openGrid(position) {/*被儿子调用然后计算是不是最后一个数字格子,如果是,调用爸爸的gameWin方法*/};
handleZero(position, totalRows, totalColumns){/*如果被儿子通知,点到空白的格子了,*/
/*则需要检查四周格子,把所有空白格子都打开*/}
render () {
const { totalRows, totalColumns, gameStatus } = this.props;
return (
<ul> // 用两个map加载小格子们
{this.state.gameMatrix.map((row, rowNumber)=>
(<p className="row" key={ rowNumber }>{row.map(
(grid, gridNumber) =>
<SingleGrid value={grid} {/*其他需要传给小格子的属性*/}>
</SingleGrid>
)}</p>)
)}
</ul>
)
}
}
export default GameBoard
作为最小的单元,也是和user直接交互的component,需要知道:
/**
* Created by xzou2 on 11/13/18.
*/
import React from 'react';
import '../styles/grid.css'
class SingleGrid extends React.Component {
constructor(props) {
super(props);
this.state = {status: 'default', opened: false};
//方法绑定this
}
handleClick() {/*左键点击,需要根据当前自己的状态来判断状态变化*/}
handleRightClick(e) {/*右键点击,设置为小红旗或者是取消小红旗*/}
handleTouchEnd(){/*略*/}
touchStart(){/*给手机等触屏玩儿的*/}
componentWillReceiveProps(nextProps) {/*检查从爸爸那里得来的opened属性,*/
/*如果是爸爸让自己打开,而自己还没有打开,那么改变状态*/}
render () {
const { value, gameStatus } = this.props;
let currentStatue = '';
if (gameStatus !== 'fail') {
//判断每个格子需要显示的东西
} else {
//游戏已经输了判断每个格子的
}
return <span
className={`grid${currentStatue} grid ${currentStatue && currentStatue!== 'marked' ? 'open' : 'default' }`}
onClick={this.handleClick}
onContextMenu={this.handleRightClick}
onTouchStart={this.touchStart}
onTouchEnd={this.handleTouchEnd}
>
{/[1-8]/.test(currentStatue) || currentStatue === 'X' ? currentStatue: ''}</span>
}
}
export default SingleGrid
一个简单的component
class Button extends React.Component {
render() {
const {
color,
content,
onClick,
} = this.props;
return (
<button className={color} onClick={onClick}>
{content}
</button>
)
}
}
export default Button
这大概是最好玩儿最头疼的一个步骤了。逻辑比较复杂,如果需要,请看源码
在最开始的时候,所有的小格子都需要藏起来,这时候小格子的状态是没被打开过的。
根据上面的component设计,用户点击的是每一个小格子。每个小格子都知道自己的值,用户点击之后:
小红旗和隐藏切换。
右键点击事件比较抓狂的是,手机不支持右键,这个纠结过程在另一个博客里了
小红旗的图片和雷的图片用css就可以绑定到状态上。className可以根据情况拼一个字符串出来。
最后的最后,戳这里可以直接开会的时候玩儿扫雷了。