React学习之围棋记谱本制作(四)前端开发初步完成

前端之家收集整理的这篇文章主要介绍了React学习之围棋记谱本制作(四)前端开发初步完成前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
  今天初始完成了页面端的开发工作。把遇到的问题说一说。
  (1)开始时,对javascript的对象或数组拷贝、赋值理解不是很透,折磨了我好长时间。 理解了对象或数组的赋值,实际上相当于C语言中的指针地址赋值,就知道了保存每一步的棋盘状态,要把对象拷贝一个副本,避免后继的变化,影响保存的状态。
  (2)JQuery提供了对象拷贝的方法,extend。这个方法有深拷贝、浅拷贝之分,如果浅拷贝,不复制对象中的对象。还有个问题,就是数组拷贝后,会变成一个伪数组,能用下标取值,但不支持length属性。调这个错也用了很长时间。还好chrome支持断点调试。

  目前支持功能:交替落子、布局摆子、撤销、重做、新建布局。界面如下图所示:




  整个工程涉及四个文件。组件文件、状态管理文件、样式文件、网页文件。下面提供的是源码,依赖bootstrap、jquery。

一、状态管理文件GoStateManager.js
  1. /**
  2. * 用于GO的状态管理。管理所有组件的状态,所有组件订阅事件,同步状态
  3. * http://wallimn.iteye.com
  4. */
  5. "use strict"
  6. var Events = require('events');
  7.  
  8. class GoStateManager {
  9. constructor() {
  10. this.initState();
  11. this.initList();
  12. this.eventEmitter = new Events.EventEmitter();
  13. this.eventEmitter.setMaxListeners(500);
  14.  
  15. var t1 = this.getDefaultPieceState();
  16. var t2 = this.getDefaultPieceState();
  17.  
  18. };
  19.  
  20. //一个棋子的初始状态
  21. getDefaultPieceState(){
  22. return {visibility:'hidden',num:0,black:false};
  23. }
  24.  
  25. //所有棋子的初始状态
  26. getDefaultPieces(){
  27. var pieces=[];//所有棋子状态
  28. for(var i=0 ; i<19*19; i++) pieces.push(this.getDefaultPieceState());
  29. return pieces;
  30. }
  31.  
  32. initState(){
  33. //指示当前要下的子的状态,该状态使用后,调用next方法,切换状态
  34. this.current= {
  35. index:1,//当前步数
  36. goBlack:true,//是否是黑子,指行棋时
  37. placeBlack:true,//是否是黑子,指布局时
  38. numShow:false,//是否显示数字
  39. place:false,//是否是布局摆子,如果是,不改变当前步数,布局时摆的棋子上面不显示数字
  40. };
  41. //所有棋子的状态
  42. this.pieces = this.getDefaultPieces();
  43.  
  44. }
  45.  
  46. //初始化重做、撤销两个队列
  47. initList(){
  48. this.undoList = [];//后进先出队列
  49. this.redoList = [];//后进先出队列
  50. }
  51.  
  52. //将状态压栈,保存,保存的是对象的副本。
  53. pushUndoList(current,pieces){
  54. this.undoList.push({
  55. current:this.cloneObject(current),pieces:this.cloneObject(pieces)
  56. });
  57.  
  58. }
  59.  
  60. //清空RedoList,当执行下一步时执行此方法
  61. clearRedoList(){
  62. var len=this.redoList.length;
  63. if(len>0)
  64. this.redoList.splice(0,len);
  65. }
  66.  
  67. //将状态压栈,保存,保存的是对象的副本。
  68. pushRedoList(current,pieces){
  69. this.redoList.push({
  70. current:this.cloneObject(current),pieces:this.cloneObject(pieces)
  71. });
  72. }
  73.  
  74. //弹出队列中的元素,复制一个副本
  75. popList(list) {
  76. var record = list.pop();
  77. return {
  78. current: this.cloneObject(record.current),pieces: this.cloneObject(record.pieces)
  79. }
  80. }
  81.  
  82. //输出链表内容,用于调试
  83. printList(list){
  84. for(var i=0; i<list.length; i++){
  85. console.log("第%d步:%s",i,this.getVisiblePieces(list[i].pieces));
  86. }
  87.  
  88. }
  89.  
  90. getVisiblePieces(pieces){
  91. var info = "";
  92. for(var j=0; j<19*19; j++){
  93. if(pieces[j].visibility=='visible'){
  94. info = info+pieces[j].num+',';
  95. }
  96. }
  97. //console.log("可见棋子序号:"+info);
  98. return info;
  99. }
  100.  
  101. //撤销
  102. undo(){
  103. if (this.undoList.length==0){
  104. console.log("不能撤销了!");
  105. return;
  106. }
  107.  
  108. //当前状态压入RedoList
  109. this.pushRedoList(this.current,this.pieces);
  110.  
  111. var record = this.popList(this.undoList)
  112. this.current = record.current;
  113. this.pieces = record.pieces;
  114.  
  115. this.pubCurrentChange();
  116. this.pubPieceChange();
  117. }
  118.  
  119. //前进一步
  120. redo(){
  121. if (this.redoList.length==0){
  122. console.log("不能前进了!");
  123. return;
  124. }
  125.  
  126. this.pushUndoList(this.current,this.pieces);
  127.  
  128. var record = this.popList(this.redoList)
  129. this.current = record.current;
  130. this.pieces = record.pieces;
  131.  
  132. //this.printList(this.undoList);
  133. //this.printList(this.redoList);
  134.  
  135. this.pubCurrentChange();
  136. this.pubPieceChange();
  137.  
  138. }
  139.  
  140. //订阅当前状态变化事件
  141. subCurrentChange(listener) {
  142. //console.log("订阅状态事件!");
  143. this.eventEmitter.addListener('currentChange',listener);
  144. }
  145.  
  146. //状态当前变化事件发生,通知监听器
  147. pubCurrentChange(){
  148. //console.log("发布状态事件");
  149. this.eventEmitter.emit("currentChange",this.current);
  150. }
  151.  
  152. //订阅棋子状态变化事件
  153. subPieceChange(listener) {
  154. //console.log("订阅棋子事件!");
  155. this.eventEmitter.addListener('pieceChange',listener);
  156. }
  157.  
  158. //状态棋子变化事件发生,通知监听器
  159. pubPieceChange(){
  160. //console.log("发布棋子事件");
  161. //传递数据,对解耦有一点儿帮助
  162. this.eventEmitter.emit("pieceChange",this.pieces);
  163. }
  164.  
  165. //推进当前状态到下一步
  166. //这个函数内部调用
  167. next(){
  168. this.printList(this.undoList);
  169.  
  170. if(this.current.place==true){
  171. //如果是布局状态,不改变编号、颜色
  172. }
  173. else{
  174. this.current.index++;
  175. this.current.goBlack=!this.current.goBlack;
  176. }
  177. this.pubCurrentChange();
  178.  
  179. }
  180.  
  181. //克隆对象
  182. //数组被jquery复制后,变成了类数组(伪数组),不带有length方法,这个也比较坑
  183. cloneObject(object){
  184. return $.extend(true,{},object);//深层次复制。这个比较坑
  185. }
  186.  
  187. //是否处于布局状态
  188. isPlace(){
  189. return this.current.place==true;
  190. }
  191.  
  192. //设置布局状态
  193. setPlace(bBlack){
  194. this.current.place=true;
  195. this.current.placeBlack=(bBlack==true);
  196. this.pubCurrentChange();
  197. }
  198.  
  199. //重新开始
  200. restart(){
  201. this.initState();
  202. this.initList();
  203. this.pubCurrentChange();
  204. this.pubPieceChange();
  205. }
  206.  
  207. //返回当前的步数
  208. getCurrentIndex(){
  209. return this.current.index;
  210. }
  211.  
  212. //返回当前状态
  213. getCurrent(){
  214. return this.current;
  215. }
  216.  
  217. //返回所有棋子状态
  218. getPieces(){
  219. return this.pieces;
  220. }
  221.  
  222. //设置先行方
  223. setFirst(bBlack) {
  224. this.current.place = false;
  225. //仅处于第一步时,可以改变行棋的黑白颜色
  226. if( this.current.index==1){
  227. this.current.goBlack=(bBlack==true);
  228. }
  229. this.pubCurrentChange();
  230.  
  231. }
  232.  
  233. //在棋上点击
  234. //如果棋子状态变化,则返回为true,否则返回false
  235. clickPiece(index){
  236. //每次下一步之前的状态都记下来,以便能够回退
  237. this.pushUndoList(this.current,this.pieces);
  238. this.clearRedoList();
  239.  
  240. var state=this.pieces[index];//应该传递的是指针,相当于起了个别名,实际对应同一块内存地址
  241.  
  242. if (state.visibility=='visible' && this.isPlace()==false){//棋子可见、非布局状态
  243. console.log("棋子可见、非布局状态,退出!");
  244. return false;
  245. }
  246. if (state.visibility=='visible' && this.isPlace()==true && state.num!=0){//棋子可见、布局状态,且非布局棋子
  247. console.log("棋子可见、布局状态,且非布局棋子,退出!");
  248. return false;
  249. }
  250.  
  251. //console.log('可以修改棋子状态');
  252. if (this.isPlace()==false){//行棋中
  253. state.num = this.current.index;
  254. state.black = this.current.goBlack;
  255. state.visibility = 'visible';
  256. }
  257. else{//布局
  258. state.num=0;//布局状态下,放的棋子,其数字设置为0
  259. //如果原来棋子已经显示,且颜色相同,用是布局摆的棋子,设置其隐藏
  260. if(state.visibility=='visible' && this.current.placeBlack==state.black && state.num==0){
  261. state.visibility = 'hidden';
  262. //棋子颜色不重要,下次再显示时,会设置颜色
  263. }
  264. else{
  265. state.black = this.current.placeBlack;
  266. state.visibility = 'visible';
  267. }
  268. }
  269.  
  270. this.pubPieceChange();
  271. //StateManager.setPieceState(this.state.pieceId,state);//这里有点儿乱
  272. //这个放最后,完成大大压栈工作
  273. this.next();
  274. //this.setState(state);
  275. return true;
  276. }
  277.  
  278. }
  279.  
  280. module.exports = new GoStateManager();


二、组件文件Go.js
  1. //http://wallimn.iteye.com
  2. var React = require('react');
  3. var ReactDOM = require('react-dom');
  4. require('../../../css/go.css');
  5. var StateManager = require('../../store/main/GoStateManager');
  6. "user strick"
  7. //当前步状态指示器,可以指标当前步数、落子方、是否处理布局状态等信息
  8. class CurrentLabel extends React.Component{
  9. constructor(props){
  10. super(props);
  11. //使用全局的状态作为初始状态
  12. var current = StateManager.getCurrent();
  13. this.state={
  14. index:current.index,goBlack:current.goBlack,placeBlack:current.placeBlack,place:current.place,};
  15. //设置currentChange函数的this
  16. this.currentChange=this.currentChange.bind(this);
  17. //注册事件监听器
  18. StateManager.subCurrentChange(this.currentChange);
  19. }
  20.  
  21. //状态改变事件监听器,调整组件的状态
  22. currentChange(current){
  23. this.setState({
  24. index:current.index,});
  25. }
  26.  
  27. render(){
  28. return <span className="bg-success">
  29. <strong>当前步数:</strong>{this.state.index}
  30. <strong> 落子方:</strong>{this.state.goBlack==true?'黑方':'白方'}
  31. <strong> 布局子:</strong>{this.state.placeBlack==true?'黑子':'白子'}
  32. <strong> 状态:</strong>{this.state.place==true?'布局':'行棋'}
  33. </span>;
  34. }
  35. }
  36.  
  37. //围棋桌面
  38. class GoDesk extends React.Component {
  39. constructor(props) {
  40. super(props);
  41. this.state = {
  42. refresh: false
  43. };
  44. }
  45.  
  46. render() {
  47. var self = this;
  48. this.state.refresh=false;
  49. var pieces = [];
  50. //每个交叉点上都放一个子,只是未点击时不显示,棋子黑白、编号都不重要,用户点击时会修改
  51. for (var i=0; i<19*19; i++){
  52. pieces.push(
  53. <GoPiece black={i % 2==0 ?true:false} key={'go'+(i+1)} pieceId={i}/>
  54. );
  55. }
  56. return <div className="go-desk">
  57. <div className="go-opr">
  58. <GoBtns />
  59. </div>
  60. <div className="go-board">
  61. {pieces}
  62. </div>
  63. <div className="text-center">
  64. <CurrentLabel />
  65. </div>
  66. </div>;
  67. }
  68. }
  69.  
  70. //使用bootstap的按钮组,可以不用控制按钮的状态,较为方便,还没有完全走通
  71. //使用radio按钮组实现几个控制行棋的按钮,因为只能处于其中一个状态
  72. class GoBtns extends React.Component{
  73. constructor(props){
  74. super(props);
  75. this.state={index:1};//指标按钮的激活状态,没有完成
  76. this.setFirstClickHandle=this.setFirstClickHandle.bind(this);
  77. this.setPlaceClickHandle=this.setPlaceClickHandle.bind(this);
  78. this.newClickHandle=this.newClickHandle.bind(this);
  79. this.saveClickHandle=this.saveClickHandle.bind(this);
  80. this.loadClickHandle=this.loadClickHandle.bind(this);
  81.  
  82. this.redoClickHandle=this.redoClickHandle.bind(this);
  83. this.undoClickHandle=this.undoClickHandle.bind(this);
  84. }
  85.  
  86. //这个还没有验证
  87. getActiveBtnIndex(){
  88. if (StateManager.current.place==false) return 1;//黑先、白先差别不大,似乎没有影响
  89. else if (StateManager.current.placeBlack)return 2;
  90. else return 3;
  91. }
  92. //设置黑先
  93. setFirstClickHandle(bBlack){
  94. console.log("设置落子方颜色:"+bBlack);
  95. StateManager.setFirst(bBlack);
  96. }
  97.  
  98. setPlaceClickHandle(bBlack){
  99. console.log("设置布局子颜色:"+bBlack);
  100. StateManager.setPlace(bBlack);
  101. }
  102.  
  103. newClickHandle(){
  104. if (confirm('您确定要新建布局吗?')==true){
  105. StateManager.restart();
  106. }
  107. }
  108.  
  109. saveClickHandle(){
  110. alert("暂示实现");
  111. }
  112.  
  113. loadClickHandle(){
  114. alert("暂示实现");
  115. }
  116. redoClickHandle(){
  117. StateManager.redo();
  118. }
  119.  
  120. undoClickHandle(){
  121. StateManager.undo();
  122. }
  123. render(){
  124. return <div>
  125. <span>
  126. <div className="btn-group" data-toggle="buttons">
  127. <label className="btn btn-sm btn-default active" onClick={this.setFirstClickHandle.bind(this,true)}><input type="radio" autoComplete="off" defaultChecked title="黑方先走" />黑先</label>
  128. <label className="btn btn-sm btn-default" onClick={this.setFirstClickHandle.bind(this,false)}><input type="radio" autoComplete="off" />白先</label>
  129. <label className="btn btn-sm btn-default" onClick={this.setPlaceClickHandle.bind(this,true)}><input type="radio" autoComplete="off" />黑子</label>
  130. <label className="btn btn-sm btn-default" onClick={this.setPlaceClickHandle.bind(this,false)}><input type="radio" autoComplete="off" />白子</label>
  131. </div>
  132. </span>
  133.  
  134. <span>
  135. <button className="btn btn-sm btn-default" onClick={this.newClickHandle}>新建</button>
  136. <button className="btn btn-sm btn-default" onClick={this.saveClickHandle}>保存</button>
  137. <button className="btn btn-sm btn-default" onClick={this.loadClickHandle}>打开</button>
  138. </span>
  139.  
  140. <span>
  141. <button className="btn btn-sm btn-default" onClick={this.undoClickHandle}>撤销</button>
  142. <button className="btn btn-sm btn-default" onClick={this.redoClickHandle}>重做</button>
  143. </span>
  144. </div>;
  145. }
  146. }
  147.  
  148. //棋子
  149. class GoPiece extends React.Component{
  150. constructor(props){
  151. super(props);
  152. var pieceId = props.pieceId;
  153. var pieceState = StateManager.getPieces()[pieceId];
  154. this.state={
  155. showNum:true,//是否显示数字,这个应该是个全局参数
  156. num:pieceState.num,//子上显示的数字,如果为零,表示布局时摆的子,不显示数字
  157. black:pieceState.black,//true表示为黑
  158. last:false,//是否是最后一个子
  159. pieceId:pieceId,//棋子的ID,左上为0,从左到右、从上到下,赋值后不发生变化
  160. visibility:pieceState.visibility,//不可见时,为未放子或者被吃掉,从全局变量中取,
  161. }
  162. //设置this,很重要
  163. this.handleClick=this.handleClick.bind(this);
  164. this.pieceChange=this.pieceChange.bind(this);
  165. StateManager.subPieceChange(this.pieceChange);
  166. }
  167.  
  168. pieceChange(piecesArray){
  169. //React会判断UI要不要更新,全部更新,不要紧
  170. this.setState({
  171. visibility:piecesArray[this.state.pieceId].visibility,black:piecesArray[this.state.pieceId].black,num:piecesArray[this.state.pieceId].num,showNum:StateManager.current.numShow,last:piecesArray[this.state.pieceId].num==StateManager.current.index,//没有想好如何判断
  172. });
  173. }
  174.  
  175. //这个函数不直接改变自己组件的状态
  176. handleClick(){
  177. StateManager.clickPiece(this.state.pieceId);
  178. }
  179. render(){
  180. var className="go-piece go-piece-"+(this.state.black==true?'black':'white');
  181. //console.log(this.state);
  182. if (this.state.visibility=='hidden') className = className+" go-piece-hidden";
  183.  
  184. var pieceNum = this.state.num==0?'':this.state.num;
  185. return <div className={className} onClick={this.handleClick} id={'piece_'+this.state.pieceId}>
  186. <span style={{visibility:this.state.visibility}}>{pieceNum}</span>
  187. </div>;
  188. }
  189. }
  190.  
  191. ReactDOM.render(
  192. <GoDesk />,document.getElementById('go-container')
  193. );


三、样式文件go.css
  1. html,body{
  2. height:100%;
  3. }
  4. .go-desk{
  5. background-image:url(../img/go/bk.png);
  6. width:100%;
  7. height:100%;
  8. padding:20px;
  9. }
  10.  
  11. .go-opr{
  12. height:30px;
  13. text-align:center;
  14. margin-bottom:1em;
  15. }
  16. .go-opr span{
  17. margin:0 0.5em;
  18. }
  19. .go-opr span button{
  20. margin:0 0.05em;
  21. }
  22.  
  23. .go-board{
  24. width:800px;
  25. height:800px;
  26. margin:0 auto;
  27. background-image:url(../img/go/board.png);
  28. background-repeat:no-repeat;
  29. padding:20px;
  30. }
  31.  
  32. .go-piece{
  33. width:40px;
  34. height:40px;
  35. float:left;
  36. background-image:url(../img/go/piece.png);
  37. text-align:center;
  38. line-height:40px;
  39. vertical-align:middle;
  40. font-size:20px;
  41. }
  42. .go-piece span{
  43. }
  44. .go-piece-white{
  45. background-position:-40px 0;
  46. color:black;
  47. }
  48. .go-piece-black{
  49. background-position:0 0;
  50. color:white;
  51. }
  52. .go-piece-hidden{
  53. background-image:none;
  54.  
  55. }


四、网页文件go.html
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <Meta charset="utf-8"/>
  5. <title><%= htmlWebpackPlugin.options.title%></title>
  6. </head>
  7. <body>
  8. <div id="go-container"></div>
  9. </body>
  10. </html>
  编译好的文件,请点击附件下载,可以单机基于浏览器运行。所有源码已经托管到码云,访问地址:https://git.oschina.net/wallimn/rwne.git。把插件也传上去了,有点儿大。

猜你在找的React相关文章