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

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

  目前支持功能:交替落子、布局摆子、撤销、重做、新建布局。界面如下图所示:
[align=center]
[img]http://dl2.iteye.com/upload/attachment/0121/8419/cb0d69e7-a310-3b91-9570-f6fb50076c2a.png[/img]
[/align]

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

一、状态管理文件GoStateManager.js

/**
* 用于GO的状态管理。管理所有组件的状态,所有组件订阅事件,同步状态
* http://wallimn.iteye.com
*/
"use strict"
var Events = require('events');

class GoStateManager {
constructor() {
this.initState();
this.initList();
this.eventEmitter = new Events.EventEmitter();
this.eventEmitter.setMaxListeners(500);

var t1 = this.getDefaultPieceState();
var t2 = this.getDefaultPieceState();

};

//一个棋子的初始状态
getDefaultPieceState(){
return {visibility:'hidden',num:0,black:false};
}

//所有棋子的初始状态
getDefaultPieces(){
var pieces=[];//所有棋子状态
for(var i=0 ; i<19*19; i++) pieces.push(this.getDefaultPieceState());
return pieces;
}

initState(){
//指示当前要下的子的状态,该状态使用后,调用next方法,切换状态
this.current= {
index:1,//当前步数
goBlack:true,//是否是黑子,指行棋时
placeBlack:true,//是否是黑子,指布局时
numShow:false,//是否显示数字
place:false,//是否是布局摆子,如果是,不改变当前步数,布局时摆的棋子上面不显示数字
};
//所有棋子的状态
this.pieces = this.getDefaultPieces();

}

//初始化重做、撤销两个队列
initList(){
this.undoList = [];//后进先出队列
this.redoList = [];//后进先出队列
}

//将状态压栈,保存,保存的是对象的副本。
pushUndoList(current,pieces){
this.undoList.push({
current:this.cloneObject(current),
pieces:this.cloneObject(pieces)
});

}

//清空RedoList,当执行下一步时执行此方法
clearRedoList(){
var len=this.redoList.length;
if(len>0)
this.redoList.splice(0,len);
}

//将状态压栈,保存,保存的是对象的副本。
pushRedoList(current,pieces){
this.redoList.push({
current:this.cloneObject(current),
pieces:this.cloneObject(pieces)
});
}

//弹出队列中的元素,复制一个副本
popList(list) {
var record = list.pop();
return {
current: this.cloneObject(record.current),
pieces: this.cloneObject(record.pieces)
}
}

//输出链表内容,用于调试
printList(list){
for(var i=0; i console.log("第%d步:%s",i,this.getVisiblePieces(list[i].pieces));
}

}

getVisiblePieces(pieces){
var info = "";
for(var j=0; j<19*19; j++){
if(pieces[j].visibility=='visible'){
info = info+pieces[j].num+',';
}
}
//console.log("可见棋子序号:"+info);
return info;
}

//撤销
undo(){
if (this.undoList.length==0){
console.log("不能撤销了!");
return;
}

//当前状态压入RedoList
this.pushRedoList(this.current,this.pieces);

var record = this.popList(this.undoList)
this.current = record.current;
this.pieces = record.pieces;

this.pubCurrentChange();
this.pubPieceChange();
}

//前进一步
redo(){
if (this.redoList.length==0){
console.log("不能前进了!");
return;
}

this.pushUndoList(this.current,this.pieces);

var record = this.popList(this.redoList)
this.current = record.current;
this.pieces = record.pieces;

//this.printList(this.undoList);
//this.printList(this.redoList);

this.pubCurrentChange();
this.pubPieceChange();

}

//订阅当前状态变化事件
subCurrentChange(listener) {
//console.log("订阅状态事件!");
this.eventEmitter.addListener('currentChange',listener);
}

//状态当前变化事件发生,通知监听器
pubCurrentChange(){
//console.log("发布状态事件");
this.eventEmitter.emit("currentChange",this.current);
}

//订阅棋子状态变化事件
subPieceChange(listener) {
//console.log("订阅棋子事件!");
this.eventEmitter.addListener('pieceChange',listener);
}

//状态棋子变化事件发生,通知监听器
pubPieceChange(){
//console.log("发布棋子事件");
//传递数据,对解耦有一点儿帮助
this.eventEmitter.emit("pieceChange",this.pieces);
}

//推进当前状态到下一步
//这个函数内部调用
next(){
this.printList(this.undoList);

if(this.current.place==true){
//如果是布局状态,不改变编号、颜色
}
else{
this.current.index++;
this.current.goBlack=!this.current.goBlack;
}
this.pubCurrentChange();

}

//克隆对象
//数组被jquery复制后,变成了类数组(伪数组),不带有length方法,这个也比较坑
cloneObject(object){
return $.extend(true,{},object);//深层次复制。这个比较坑
}

//是否处于布局状态
isPlace(){
return this.current.place==true;
}

//设置布局状态
setPlace(bBlack){
this.current.place=true;
this.current.placeBlack=(bBlack==true);
this.pubCurrentChange();
}

//重新开始
restart(){
this.initState();
this.initList();
this.pubCurrentChange();
this.pubPieceChange();
}

//返回当前的步数
getCurrentIndex(){
return this.current.index;
}

//返回当前状态
getCurrent(){
return this.current;
}

//返回所有棋子状态
getPieces(){
return this.pieces;
}

//设置先行方
setFirst(bBlack) {
this.current.place = false;
//仅处于第一步时,可以改变行棋的黑白颜色
if( this.current.index==1){
this.current.goBlack=(bBlack==true);
}
this.pubCurrentChange();

}

//在棋上点击
//如果棋子状态变化,则返回为true,否则返回false
clickPiece(index){
//每次下一步之前的状态都记下来,以便能够回退
this.pushUndoList(this.current,this.pieces);
this.clearRedoList();

var state=this.pieces[index];//应该传递的是指针,相当于起了个别名,实际对应同一块内存地址

if (state.visibility=='visible' && this.isPlace()==false){//棋子可见、非布局状态
console.log("棋子可见、非布局状态,退出!");
return false;
}
if (state.visibility=='visible' && this.isPlace()==true && state.num!=0){//棋子可见、布局状态,且非布局棋子
console.log("棋子可见、布局状态,且非布局棋子,退出!");
return false;
}

//console.log('可以修改棋子状态');
if (this.isPlace()==false){//行棋中
state.num = this.current.index;
state.black = this.current.goBlack;
state.visibility = 'visible';
}
else{//布局
state.num=0;//布局状态下,放的棋子,其数字设置为0
//如果原来棋子已经显示,且颜色相同,用是布局摆的棋子,设置其隐藏
if(state.visibility=='visible' && this.current.placeBlack==state.black && state.num==0){
state.visibility = 'hidden';
//棋子颜色不重要,下次再显示时,会设置颜色
}
else{
state.black = this.current.placeBlack;
state.visibility = 'visible';
}
}

this.pubPieceChange();
//StateManager.setPieceState(this.state.pieceId,state);//这里有点儿乱
//这个放最后,完成大大压栈工作
this.next();
//this.setState(state);
return true;
}

}

module.exports = new GoStateManager();


二、组件文件Go.js

//http://wallimn.iteye.com
var React = require('react');
var ReactDOM = require('react-dom');
require('../../../css/go.css');
var StateManager = require('../../store/main/GoStateManager');
"user strick"
//当前步状态指示器,可以指标当前步数、落子方、是否处理布局状态等信息
class CurrentLabel extends React.Component{
constructor(props){
super(props);
//使用全局的状态作为初始状态
var current = StateManager.getCurrent();
this.state={
index:current.index,
goBlack:current.goBlack,
placeBlack:current.placeBlack,
place:current.place,
};
//设置currentChange函数的this
this.currentChange=this.currentChange.bind(this);
//注册事件监听器
StateManager.subCurrentChange(this.currentChange);
}

//状态改变事件监听器,调整组件的状态
currentChange(current){
this.setState({
index:current.index,
goBlack:current.goBlack,
placeBlack:current.placeBlack,
place:current.place,
});
}

render(){
return
当前步数:{this.state.index}
 落子方:{this.state.goBlack==true?'黑方':'白方'}
 布局子:{this.state.placeBlack==true?'黑子':'白子'}
 状态:{this.state.place==true?'布局':'行棋'}
;
}
}

//围棋桌面
class GoDesk extends React.Component {
constructor(props) {
super(props);
this.state = {
refresh: false
};
}

render() {
var self = this;
this.state.refresh=false;
var pieces = [];
//每个交叉点上都放一个子,只是未点击时不显示,棋子黑白、编号都不重要,用户点击时会修改
for (var i=0; i<19*19; i++){
pieces.push(

);
}
return





{pieces}




;
}
}

//使用bootstap的按钮组,可以不用控制按钮的状态,较为方便,还没有完全走通
//使用radio按钮组实现几个控制行棋的按钮,因为只能处于其中一个状态
class GoBtns extends React.Component{
constructor(props){
super(props);
this.state={index:1};//指标按钮的激活状态,没有完成
this.setFirstClickHandle=this.setFirstClickHandle.bind(this);
this.setPlaceClickHandle=this.setPlaceClickHandle.bind(this);
this.newClickHandle=this.newClickHandle.bind(this);
this.saveClickHandle=this.saveClickHandle.bind(this);
this.loadClickHandle=this.loadClickHandle.bind(this);

this.redoClickHandle=this.redoClickHandle.bind(this);
this.undoClickHandle=this.undoClickHandle.bind(this);
}

//这个还没有验证
getActiveBtnIndex(){
if (StateManager.current.place==false) return 1;//黑先、白先差别不大,似乎没有影响
else if (StateManager.current.placeBlack)return 2;
else return 3;
}
//设置黑先
setFirstClickHandle(bBlack){
console.log("设置落子方颜色:"+bBlack);
StateManager.setFirst(bBlack);
}

setPlaceClickHandle(bBlack){
console.log("设置布局子颜色:"+bBlack);
StateManager.setPlace(bBlack);
}

newClickHandle(){
if (confirm('您确定要新建布局吗?')==true){
StateManager.restart();
}
}

saveClickHandle(){
alert("暂示实现");
}

loadClickHandle(){
alert("暂示实现");
}
redoClickHandle(){
StateManager.redo();
}

undoClickHandle(){
StateManager.undo();
}
render(){
return




















;
}
}

//棋子
class GoPiece extends React.Component{
constructor(props){
super(props);
var pieceId = props.pieceId;
var pieceState = StateManager.getPieces()[pieceId];
this.state={
showNum:true,//是否显示数字,这个应该是个全局参数
num:pieceState.num,//子上显示的数字,如果为零,表示布局时摆的子,不显示数字
black:pieceState.black,//true表示为黑
last:false,//是否是最后一个子
pieceId:pieceId,//棋子的ID,左上为0,从左到右、从上到下,赋值后不发生变化
visibility:pieceState.visibility,//不可见时,为未放子或者被吃掉,从全局变量中取,
}

//设置this,很重要
this.handleClick=this.handleClick.bind(this);
this.pieceChange=this.pieceChange.bind(this);
StateManager.subPieceChange(this.pieceChange);
}

pieceChange(piecesArray){
//React会判断UI要不要更新,全部更新,不要紧
this.setState({
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,//没有想好如何判断
});
}

//这个函数不直接改变自己组件的状态
handleClick(){
StateManager.clickPiece(this.state.pieceId);
}

render(){
var className="go-piece go-piece-"+(this.state.black==true?'black':'white');
//console.log(this.state);
if (this.state.visibility=='hidden') className = className+" go-piece-hidden";

var pieceNum = this.state.num==0?'':this.state.num;
return

{pieceNum}
;
}
}

ReactDOM.render(
,
document.getElementById('go-container')
);


三、样式文件go.css

html,body{
height:100%;
}
.go-desk{
background-image:url(../img/go/bk.png);
width:100%;
height:100%;
padding:20px;
}

.go-opr{
height:30px;
text-align:center;
margin-bottom:1em;
}
.go-opr span{
margin:0 0.5em;
}
.go-opr span button{
margin:0 0.05em;
}

.go-board{
width:800px;
height:800px;
margin:0 auto;
background-image:url(../img/go/board.png);
background-repeat:no-repeat;
padding:20px;
}

.go-piece{
width:40px;
height:40px;
float:left;
background-image:url(../img/go/piece.png);
text-align:center;
line-height:40px;
vertical-align:middle;
font-size:20px;
}
.go-piece span{
}
.go-piece-white{
background-position:-40px 0;
color:black;
}
.go-piece-black{
background-position:0 0;
color:white;
}
.go-piece-hidden{
background-image:none;

}


四、网页文件go.html





<%= htmlWebpackPlugin.options.title%>







  编译好的文件,请点击附件下载,可以单机基于浏览器运行。所有源码已经托管到码云,访问地址:https://git.oschina.net/wallimn/rwne.git。把插件也传上去了,有点儿大。

你可能感兴趣的:(JAVA,WEB开发,node.js,react,javascript,围棋,ViewUI)