本节的主要工作是给棋子添加鼠标事件,实现选中棋子、走子以及吃子等功能,同时将服务器端的基础搭建好。

棋盘初始化完成之后,棋盘背景、棋子位置都已经绘制完毕。这时候就需要给上层的Canvas添加鼠标事件的侦听:

me.piecesCanvas.addEventListener("click",me.onClick,false);

当鼠标在棋盘区域进行单击事件的时候,就会触发me.onClick函数的执行,下面就看一下me.onClick的定义

me.onClick = function(e) {
    var x = e.clientX - me.startX - me.piecesCanvas.offsetLeft;
    var y = e.clientY - me.startY - me.piecesCanvas.offsetTop;
    var lineX = x / me.xStep;
    var lineY = y / me.yStep;
    lineX = parseInt(lineX + 1.5);
    lineY = parseInt(lineY + 1.5);
    var pX = (lineX - 1) * me.xStep;
    var pY = (lineY - 1) * me.yStep;
    var dist = Math.sqrt((x - pX) * (x - pX) + (y - pY) * (y - pY));
    var r = me.getPiecesR();
    if (dist < r) {
        if (me.status == "idle") {
            var selectPoint = me.board[lineY][lineX];
            if (selectPoint == "") {
                return;
            } else {
                var selectArr = selectPoint.split("-");
                var position = selectArr[0];
                var pieceStr = selectArr[1];
                if (position == "our") {
                    me.activePiece = pieceStr;
                    me.piecesList["our"][pieceStr].active = true;
                    me.status = "active";
                    me.reDrawPieces();
                }
            }
        } else if (me.status == "active") {
            var selectPoint = me.board[lineY][lineX];

            if (selectPoint == "") {
                me.pieceMoveTo("our", me.activePiece, lineX, lineY);
                me.piecesList["our"][me.activePiece].active = false;
                me.activePiece = "";
                me.status = "idle";
                me.reDrawPieces();
            } else {
                var selectArr = selectPoint.split("-");
                var position = selectArr[0];
                var pieceStr = selectArr[1];
                if (position == "opposite") {
                    me.pieceMoveTo("our", me.activePiece, lineX, lineY);
                    me.piecesList["our"][me.activePiece].active = false;
                    me.activePiece = "";
                    me.status = "wait";
                    me.reDrawPieces();
                } else if (position == "our") {
                    me.piecesList["our"][me.activePiece].active = false;
                    me.piecesList["our"][pieceStr].active = true;
                    me.activePiece = pieceStr;
                    me.reDrawPieces();
                }
            }

        }
    }
};

这里主要将棋局分成三个状态,idle、active和wait。在idle状态,我方可以选子,一旦选定一个棋子,状态就变成active状态,被选中棋子的active属性就变成true。active状态下,继续侦听鼠标事件,当鼠标选再次单击时判断单击的位置。如果仍然选中了我方棋子,那么就更改被选中的棋子,就是换一个棋子走。如果选中了空白棋子,就走到该位置,棋局状态变为wait状态。如果选中的是对方棋子,那么吃掉对方棋子,棋局状态变成wait状态。这里注意两点:一是还没有考虑不同棋子的走子规则,马走日字象走田嘛,这个以后添加。二是wait状态之后就需要和服务器通讯了,现在也还没有实现。

另外,服务器端使用express来处理web请求,同时使用socket.io来进行websocket通讯。服务端代码大致如下:

var express = require('express')
  , routes = require('./routes');

var app = module.exports = express.createServer();
var io = require('socket.io').listen(app);
// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.set('view options',{layout:false});
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);
var conns={};
io.sockets.on('connection',function(socket){
	//do something
});
app.listen(3000, function(){
  console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});

最近时间太紧,以后慢慢写。

场景如下:数据库里有大量记录,php程序需要取出来做一些运算,是一次取出所有还是一次取出部分?示例代码如下:

//一次读取
$start = '2012-06-11 00:00:00';
$end = '2012-06-18 00:00:00';
$rows = $db->query("select ... where
        time between '$start' and '$end'");
foreach ($rows as $row) {
    $name = $row["name"];
    $table[$name] = $row["value"];
}

//多次读取
$start = '2012-06-11 00:00:00';
$end = '2012-06-18 00:00:00';
$start_t = strtotime($start);
$end_t = strtotime($end);
$time_span = 43200;
for ($t = $start_t; $t < $end_t; $t += $time_span) {
    $this_s = date("Y-m-d H:i:s", $t);
    if ($t + 86400 > $end_t) {
        $this_e = date("Y-m-d H:i:s", $end_t);
    } else {
        $this_e = date("Y-m-d H:i:s", $t + $time_span);
    }
    $rows = $db->query("select ... where
            time between '$this_s' and '$this_e'");
    foreach ($rows as $row) {
        $name = $row["name"];
        $table[$name] = $row["value"];
    }
}

在上述代码中,做了几次实验,得出下列数据:

其中,总记录数是2296605,第一次测试是一下全部取出,需要占用2G左右内存,整个执行时间是4分多钟,逐步减少每次取出的记录数,当然同时要增加取出次数,当一次取出的次数在16W左右时,程序执行时间最短。
相同的代码在不同的数据规模上性能表现差别巨大,所以写代码还是要注意要处理的数据规模。

Posted in PHP.

本节主要是使用Canvas绘制棋盘和棋子,并确定前端的基本程序结构。
由于刚刚开始学习使用html5,所以程序写的比较挫,各位看客轻拍。
首先,写象棋的基本骨架,html页面

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <meta http-equiv="Content-Language" content="zh-cn" />
 <title>象棋示例</title>
 <link charset="utf-8" rel="stylesheet" href="style.css" type="text/css" />
 <script type="text/javascript" src="chess.js" charset="utf-8"></script>
</head>
<body>
<canvas id="chessCanvas" width="600" height="600">
 您的浏览器不支持canvas,推荐使用chrome!
</canvas>
<canvas id="PiecesCanvas" width="600" height="600"></canvas>
</body>
</html>

这里定义了两个Canvas,chessCanvas当棋盘使用,PiecesCanvas用来绘制棋子。棋局开始时先在chessCanvas上绘制棋盘,然后这个Canvas一直保持不变,作为棋子Canvas的背景,PiecesCanvas叠加在棋盘Canvas之上,每次棋局发生改变只需要重绘piecesCanvas即可,不需要重绘棋盘,降低了重绘代价。
使用简单的CSS来将两个Canvas叠加:

#chessCanvas {
 position: absolute;
 left: 0px;
 top: 0px;
 z-index: 0;
}
#PiecesCanvas {
 position: absolute;
 left: 0px;
 top: 0px;
 z-index: 9999;
}

然后需要在js中定义象棋需要使用的一些对象和变量

var me = {};
//棋盘Canvas
me.chessCanvas=document.getElementById(chessId);
me.cxt=me.chessCanvas.getContext("2d");

//棋盘高度
me.boardHight = me.chessCanvas.height;
//棋盘宽度
me.boardWidth = me.chessCanvas.width;

//棋子Canvas
me.piecesCanvas=document.getElementById(piecesId);
//保证棋子Canvas的高度宽度与棋盘相同
me.piecesCanvas.height = me.boardHight;
me.piecesCanvas.width = me.boardWidth;
me.piececxt=me.piecesCanvas.getContext("2d");

//设置棋子中字体
me.piececxt.font = 'bold 30px 宋体';
//棋子文字中线垂直对齐
me.piececxt.textBaseline = 'middle';
//棋子文字水平居中对齐
me.piececxt.textAlign = "center";

me.startX = 0;//棋盘从Canvas的左起始点
me.startY = 0;//棋盘从Canvas的上起始点
me.xStep = 0;//棋盘格子横向间距
me.yStep = 0;//棋盘格子垂直间距
me.piecesList = {};//棋子列表

另外,还定义了一些动作函数:

 /*
 *绘制棋盘,确定棋盘左上角起点位置以及横向纵向间距
 */
 me.drawChessBoard = function() {};

 /*
 * 绘制单个棋子
 */
 me.drawPiece = function(word, x, y, color) {};

 /*
 * 清空棋盘
 */
 me.erasePieces = function() {};

 /*
 * 初始化棋盘
 */
 me.initPieces = function() {};

 /*
 * 重绘棋局中所有活着的棋子
 */
 me.reDrawPieces = function() {};

目前还只是把棋盘和棋子绘制了出来,没有和鼠标事件关联,也没有和服务器通讯,这些后面慢慢做。
最后,放一张截图。

棋盘

互联网产品开发过程中,想了解用户体验一直是一件比较困难的事情。之前很难使用js获得用户访问网站的连接建立时间、dns时间等信息,想得到这些信息一般是建立固定的监测点或者使用专门的测试客户端软件。不过,现在情况有所改变,IE9和chrome6以上的版本都支持了一个新的api:window.performance( ie9中为window.msPerformance,chrome6-9为window.webkitPerformance,chrome10中是window.performance)(2012/6/19注:今天在本博客的测速日志里发现了firefox13和360浏览器,这说明现在这个api至少又多了两个浏览器支持)。该api目前在W3C上的状态是“Candidate Recommendation”,相信会有越来越多的浏览器支持。废话不多说,我们来看一下这个api能干哪些事情。

首先,该api的的接口定义为:


interface PerformanceTiming {
 readonly attribute unsigned long long navigationStart;
 readonly attribute unsigned long long unloadEventStart;
 readonly attribute unsigned long long unloadEventEnd;
 readonly attribute unsigned long long redirectStart;
 readonly attribute unsigned long long redirectEnd;
 readonly attribute unsigned long long fetchStart;
 readonly attribute unsigned long long domainLookupStart;
 readonly attribute unsigned long long domainLookupEnd;
 readonly attribute unsigned long long connectStart;
 readonly attribute unsigned long long connectEnd;
 readonly attribute unsigned long long secureConnectionStart;
 readonly attribute unsigned long long requestStart;
 readonly attribute unsigned long long responseStart;
 readonly attribute unsigned long long responseEnd;
 readonly attribute unsigned long long domLoading;
 readonly attribute unsigned long long domInteractive;
 readonly attribute unsigned long long domContentLoadedEventStart;
 readonly attribute unsigned long long domContentLoadedEventEnd;
 readonly attribute unsigned long long domComplete;
 readonly attribute unsigned long long loadEventStart;
 readonly attribute unsigned long long loadEventEnd;
};

这些变量都表示神马意思呢?莫急,看看浏览器的一般加载顺序,下图所示:
浏览器页面加载顺序图从上图就可以看出这些变量的具体含义了,这时想得到用户浏览器加载过程的各项指标就简单了。比如想拿到用户的dns时间,只需要拿domainLookupEnd减去domainLookupStart即可。

不过,在chrome下,这里有一个坑,按照W3C的标准,navigationStart应该是整个过程的开始,也就是时间点应该最早。但是chrome下很多时候不是这样,会有domainLookupStart时间早于navigationStart的情况,初步认为应该是chrome的各种优化机制和预渲染功能打乱了上图的顺序。IE9相对守规矩一点。

使用该api时需要在页面完全加载完成之后才能使用,最简单的办法是在window.onload事件中读取各种数据,因为很多值必须在页面完全加载之后才能得出。