Thứ Tư, 10 tháng 10, 2012

Làm game KENO với html5 - phần 2

Ở phần 2, chúng ta sẽ chuyển trạng thái của số khi kết quả máy chọn là trúng và không trúng.

Nếu không trúng thì sẽ hiển thị dấu x, còn nếu trúng thì sẽ hiển thị hình tròn.

Tạo 1 canvas mới đè lên canvas cũ. Chúng ta sẽ vẽ dấu x và hình tròn trên canvas này.

<canvas id="gameCanvas1" width="415" height="335"></canvas>

Tạo 2 button play và new game :
<button id="play" onclick="play()">Play</button>
<button id="newGame" onclick="newGame()">New Game</button>
Css :
<style>
    body {
        margin: 0;
        padding: 0;
        position: relative;
    }
    #gameCanvas,
    #gameCanvas1 {
        float: left;
    }
    #gameCanvas {
        background: #000;
    }
    #gameCanvas1 {
        position: absolute;
        left: 0;
        top: 0;
        display: none;
    }
    #play {
        float: left;
    }
    #newGame {
        display: none;
    }
</style>

Tạo function play để bắt đầu chơi.
function play() {
    document.getElementById("gameCanvas1").style.display="block";

    var tempArray = new Array();
    for (i=0;i<80;i++)
    {
        tempArray[i]=i;
    }
    // limit random 20 cross and circle
    for(i=0;i<20;i++)
    {                
        var r = Math.floor(Math.random()*tempArray.length);
        var index = tempArray[r];
        tempArray.splice(r,1);
        
        if (arrayNumber[index].status==0) {
            arrayNumber[index].status=2;
        } else if(arrayNumber[index].status==1){
            arrayNumber[index].status=3;
        }
    }

    document.getElementById("play").disabled = true;
    document.getElementById("newGame").style.display="block";
    draw();
}

Trong function draw chúng ta sẽ vẽ hình tròn + dấu x :
function draw()
{            
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    var gameCanvas1 = document.getElementById("gameCanvas1");
    var graphic1 =  gameCanvas1.getContext('2d');
    graphic.clearRect(0,0,gameCanvas.width, gameCanvas.height);
    graphic1.clearRect(0,0,gameCanvas.width, gameCanvas.height);
    drawBoard(graphic);
    for(var i =0 ;i<arrayNumber.length;i++) {
        temp = arrayNumber[i];
        if(i<9)
            offset=20;
        else
            offset=15;

        // set the x,y coordinates
        var x = offset+(i%10)*sizeSquare;
        var y = 32+sizeSquare*(Math.floor(i/10));
        
        var x1 = 10+(i%10)*sizeSquare;
        var y1 = 10+sizeSquare*(Math.floor(i/10));
        
        if(temp.status==1) {
            graphic.fillStyle="#fff";

        } else if(temp.status==2) {
            // draw cross
            graphic1.lineWidth = 2;
            graphic1.beginPath();
            graphic1.moveTo(x1,y1);
            graphic1.lineTo(x1+30,y1+30);
            graphic1.strokeStyle = "#FFFF00";
            graphic1.stroke();
            
            graphic1.beginPath();
            graphic1.moveTo(x1,y1+30);
            graphic1.lineTo(x1+30,y1);
            graphic1.strokeStyle = "#FFFF00";
            graphic1.stroke();
            graphic.fillStyle="#5F5F5F";
            
        } else if(temp.status==3) {
            //draw circle
            graphic1.lineWidth = 2;
            graphic.fillStyle="#fff";
            graphic1.beginPath();
            graphic1.arc(beginX+(sizeSquare/2)+(i%10)*sizeSquare,beginY+(sizeSquare/2)+sizeSquare*(Math.floor(i/10)),17,0,2*Math.PI);
            graphic1.strokeStyle = "#00FF00";
            graphic1.stroke();
        }
        else if(temp.status==0) {
            graphic.fillStyle="#5F5F5F";
        }
        graphic.fillText(temp.value,x,y);
        graphic.fillStyle="#5F5F5F";
    }
}

Trong function canvasClicked, chúng ta sẽ giới hạn số lần click của người chơi trong 1 game là 10 lần.
function canvasClicked(e)
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    var x;
    var y;
    if (e.pageX || e.pageY) { 
      x = e.pageX;
      y = e.pageY;
    }
    else {
      x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
      y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
    }
    x -= gameCanvas.offsetLeft;
    y -= gameCanvas.offsetTop;

    var row = Math.floor((y-beginY)/sizeSquare);
    var col = Math.floor((x-beginX)/sizeSquare);
    var offsetOfi = col+(row)*colQuantity;

    if(arrayNumber[offsetOfi].status==1) {
        arrayNumber[offsetOfi].status=0;    
    } else if(arrayNumber[offsetOfi].status==0){
        // limit click : 10
        var count = 0;
        for(var i=0; i<arrayNumber.length;i++) {
            if(arrayNumber[i].status == 1) {
                count ++;
            }
        }
        // if 9 number clicked, do nothing
        if(count > 9) {
            return;
        }
        // if 3 number clicked, enable button play
        if(count > 1) {
            document.getElementById("play").disabled = false;
        }
        arrayNumber[offsetOfi].status=1;
    }
    draw();
}

Tạo function newgame, sau khi kết thúc 1 game sẽ chơi tiếp game khác :
function newGame() {
    document.getElementById("gameCanvas1").style.display="none";            
    document.getElementById("newGame").style.display="none";
    for(i=0;i<arrayNumber.length;i++) {
        arrayNumber[i].status=0;
    }
    draw();
}

Trong function initgame, ẩn nút play (khi người chơi click trên 2 số thì mới hiện nút play)

function initGame()
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    graphic.font="bold 18px arial";
    drawBoard(graphic);
    draw();            
    gameCanvas.addEventListener("mousedown", canvasClicked, false);
    document.getElementById("play").disabled = true;
}

Kết quả :

Thứ Sáu, 5 tháng 10, 2012

Làm game KENO với html5 - phần 1

Mọi người có thể chơi thử game Keno với phiên bản flash : http://www.owensworld.com/games/fullscreen.php?gid=3176&secret=ccfc4e42cb9f17f7f92d3989717d9e02
Tạo file .html sau đó chèn thẻ canvas vào trong body :
HTML
 
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Keno Game</title>
    </head>
    <body>
        <canvas height="335" id="gameCanvas" width="415"></canvas>
    </body>
</html>

Javascript
- Tạo function khởi tạo cho game :
<script language="javascript">
function initGame()
{
   var gameCanvas = document.getElementById("gameCanvas");
   var graphic =  gameCanvas.getContext('2d');
   graphic.font="bold 18px arial";   
}
</script>
trong thẻ body, chúng ta sẽ gọi hàm này ra
<body onload="initGame()">
- Tạo bàn chơi
//Khai báo tham số

var beginX = 5;
var beginY = 5;
var sizeSquare = 40;
var colQuantity = 10;
var rowQuantity = 8;
var heightX = sizeSquare*rowQuantity+beginX;
var widthY = sizeSquare*colQuantity+beginY;

// Vẽ bàn chơi
function drawBoard(graphic)
{
    
    // line x
    for (i=0; i<=colQuantity; i++){
        graphic.lineWidth = 1;
        graphic.beginPath();
        graphic.moveTo(beginX+i*sizeSquare,beginY);
        graphic.lineTo(beginX+i*sizeSquare,heightX);                
        graphic.strokeStyle = '#ccc';
        graphic.stroke();               
    }
                
    // line y
    for (i=0; i<=rowQuantity; i++){
        graphic.lineWidth = 1;
        graphic.beginPath();
        graphic.moveTo(beginX,beginY+i*sizeSquare);
        graphic.lineTo(widthY,beginY+i*sizeSquare);
        graphic.strokeStyle = '#ccc';
        graphic.stroke();
    }
}
Gọi hàm drawBoard trong function initGame :
function initGame()
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    graphic.font="bold 18px arial";
    drawBoard(graphic);
}

Tiếp đến chúng ta sẽ tạo các số trên bàn chơi (từ 1 - 80)
// Tạo số
var GameNumber = function(value)
{
    this.value = value;           
}

var arrayNumber = [];
for(var i=0;i<80;i++)
{
    arrayNumber[i]= new GameNumber(i+1);
}
Vẽ các số trên bàn chơi + căn chỉnh tọa độ
// vẽ số + căn chỉnh tọa độ
function draw()
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    graphic.clearRect(0,0,gameCanvas.width, gameCanvas.height);
    drawBoard(graphic);
    for(var i =0 ;i<arrayNumber.length;i++)
        {
            temp = arrayNumber[i];
            if(i<9)
                offset=20;
            else
                offset=15;
            
            // set the x,y coordinates
            var x = offset+(i%10)*sizeSquare;
            var y = 32+sizeSquare*(Math.floor(i/10));
            graphic.fillStyle="#000000";                       
            graphic.fillText(temp.value,x,y);
                
        }            
}
Gọi hàm draw() trong function initGame :
function initGame()
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    graphic.font="bold 18px arial";
    drawBoard(graphic);
    draw();
}
Tạo hiệu ứng cho các số. Các số này sẽ có 4 trạng thái :
  • Bình thường
  • Người dùng chọn
  • Máy chọn (không trúng)
  • Máy chọn (trúng)
Chúng ta sẽ bắt đầu tạo hiệu ứng cho số khi ở trang thái người dùng chọn. Khi click vào số nào thì số đó sẽ đổi mầu.
Thêm trạng thái cho số vào đối tượng GameNumber
var GameNumber = function(value)
{
    this.value = value;
    this.status = 0;
    // 0 = normal , 1 = active , 2 = cross , 3 = round + blink            
}
Khởi tạo sự kiện click trong function initGame
function initGame()
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    graphic.font="bold 18px arial";
    drawBoard(graphic);
    draw();
    gameCanvas.addEventListener("mousedown", canvasClicked, false);
}
Viết hàm canvasClicked để gọi sự kiện click
function canvasClicked(e)
{
    var gameCanvas = document.getElementById("gameCanvas");
    var graphic =  gameCanvas.getContext('2d');
    var x;
    var y;
    if (e.pageX || e.pageY) { 
      x = e.pageX;
      y = e.pageY;
    }
    else { 
      x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 
      y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 
    } 
    x -= gameCanvas.offsetLeft;
    y -= gameCanvas.offsetTop;

    var row = Math.floor((y-beginY)/sizeSquare);
    var col = Math.floor((x-beginX)/sizeSquare);
    var offsetOfi = col+(row)*colQuantity;
    if(arrayNumber[offsetOfi].status==1)
      arrayNumber[offsetOfi].status=0;
    else
      arrayNumber[offsetOfi].status=1;
    draw();
}
Tiếp theo sẽ thay mầu cho số. Vào function draw(), sửa đoạn code :
graphic.fillStyle="#000000";                       
graphic.fillText(temp.value,x,y);
thành :
if(temp.status==1)
{
    graphic.fillStyle="#FF0000";                       
    graphic.fillText(temp.value,x,y);                                                
}
else
{
    graphic.fillStyle="#000000";                       
    graphic.fillText(temp.value,x,y);
}
Hết phần 1.

Thứ Tư, 11 tháng 1, 2012

Một điểm rất hay của Yii Model

Hôm nay xem overview của Yii Framework, khám phá ra cách tổ chức validation rules của Yii rất là hay. Tôi hay sử dụng CakePHP và không thỏa mãn với cách thiết lập validation rules của nó:

CakePHP
Model::$validate = array(); # là một biến trong một class. Vì thế rất là bất tiện khi muốn có một validation rule tùy biến tham số được (ví dụ password pattern lấy ra từ configuration, hoặc database). Thông thường sẽ phải viết một hàm callback cho cái rule mới này.

Yii
public function rules() {

return array(

array('title, content, status', 'required'),
array('title', 'length', 'max'=>128),
array('status', 'in', 'range'=>array(1,2,3)), # range này có thể tùy biến được
array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/',
'message'=>'Tags can only contain word characters.'),
array('tags', 'normalizeTags'),
array('title, status', 'safe', 'on'=>'search'),

);

}

Model::rules(); # trả về một mảng các validation rules. Vì vậy rất tiện lợi nếu như chúng ta cần tùy biến tham số từ database.

Một điểm có thể học tập được từ Yii validation rules là cách tổ chức theo nhóm. Bạn thử tưởng tượng bạn đang sử dụng CakePHP để làm điều này? Một table có khoảng 20 fields mà thực hiện validation theo chuẩn CakePHP thì quá oải.

Copied from http://datgs.wordpress.com/2012/01/11/mot-diem-rat-hay-cua-yii-model/

Thứ Hai, 9 tháng 1, 2012

Tại sao nên sử dụng kiểu ENUM khi thiết kế Database?

Trước đây khi thiết kế database tôi thường sử dụng kiểu INT để cho các trường dữ liệu. Ví dụ kiểu boolean là 0 = No, 1 = Yes. Sau đó khi code định nghĩa các constant tương ứng:
const YES = 1;
const NO = 0;

Hoặc
const STATUS_ACTIVE = 1;
const STATUS_PENDING =2;
const STATUS_APPROVED = 3;

Tuy nhiên kiểu thiết kế này rất bất lợi cho việc bảo trì hệ thống. Bởi vì khi đọc trực tiếp trên database, các giá trị 0,1,2,3... không có ý nghĩa. Buộc phải đọc code để hiểu các giá trị trên là gì. Điều này đã ngốn không ít thời gian của tôi.

Khi đã có kinh nghiệm hơn, tôi chuyển sang sử dụng kiểu ENUM lúc này Yes sẽ là 'Yes', No sẽ là 'No'. Nhờ vậy, tôi chỉ cần đọc dữ liệu là hiểu được ý nghĩa của nó. Giảm đi một thao tác vô ích.