Comment

Kar Kin Leung - Code Sample

Code Smaple

jQuery Sample - Scene Box
node.js Sample - Sever Side ( expressJS, sockJS )
node.js Sample - Client Side ( sockJS, jQuery, CreateJS )
PHP Sample - Codeigniter

Check a SceneBox prototype to control CSS sprite.

SceneBox.prototype = {
    initSprite: function (_spriteClassName, _width, _height, _scale) {
    	this.spriteClassName = _spriteClassName;
		this.sceneDiv.css({
            overflow: "hidden",
			width: _width,
			height: _height,
            transformOrigin: "top left",
            transform: "scale("+_scale+", "+_scale+")"
		});
    },
    addSprite: function (_name, _imgSelector) {
        var spriteDiv = $("<div></div>").addClass("sprite_"+_name);
        spriteDiv.css({
        	position: "absolute",
        	top: "0",
        	left: "0",
            maxWidth: "100%",
            backgroundSize: "100%",
            backgroundImage: "url("+$(_imgSelector).attr("src")+")",
            backgroundColor: "black",
            width: "100%",
            height: "100%",
            zIndex: 0
        });
        this.sceneDiv.append(spriteDiv);
    },
    addMovieClip: function (_startFrame, _endFrame, _spriteName) {
        this.movieClip[name] = new MovieClip(_startFrame, _endFrame, _spriteName);
    },
    playMoiveClip: function (_name, _loop) {
        this.animationQueue.push({"name": _name, "loop": _loop});
        if(this.debug){
	    	console.log("playMoiveClip: ");
        	console.log(this.animationQueue);
        }
    },
    nextMovieClip: function(){
    	if(this.animationQueue.length > 0){
    		this.animationQueue[0].loop = false;
    	}
    },
    setFrameCover: function(_num, _spriteName){
        if($("#cssSpritePool > ."+this.spriteClassName+_num).length <= 0){
            var _div = $("<div></div>").addClass(this.spriteClassName+_num);
            $("#cssSpritePool").append(_div);
        }
        var _bgPosX = $("."+this.spriteClassName+_num).css('background-position-x');
        var _bgPosY = $("."+this.spriteClassName+_num).css('background-position-y');

        this.sceneDiv.find("div[class*=sprite_]").css({
            zIndex:1
        });
        this.sceneDiv.find("div.sprite_"+_spriteName).css({
        	backgroundPositionX: _bgPosX,
        	backgroundPositionY: _bgPosY,
            zIndex:2
        });
    },
    setFrameNum: function(_num, _movieClip){
        this.setFrameCover(_num, _movieClip.spriteName);
    },
    updateMoiveClip: function (){
    	if(this.animationQueue.length > 0){
    		var _movieClip = this.movieClip[this.animationQueue[0].name];	
    		if(this.sceneCurrentFrame == 0){
    			this.sceneCurrentFrame = _movieClip.startFrame;
    		}
			this.setFrameNum(this.sceneCurrentFrame, _movieClip);

	    	if(++this.sceneCurrentFrame > _movieClip.endFrame){
	    		if(this.animationQueue[0].loop){
	    			this.sceneCurrentFrame = _movieClip.startFrame;
	    		}else{
	    			if(this.animationQueue.length > 1){
	    				var _movieClipNext = this.movieClip[this.animationQueue[1].name];
	    				this.sceneCurrentFrame = _movieClipNext.startFrame;
	    				this.animationQueue.shift();
	    			}else{
	    				this.sceneCurrentFrame = _movieClip.endFrame;
	    			}
	    		}
	    	}
    	}
    }
};

A multi-player WebSocket project - Sever Side

var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log("master start...");

    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('listening',function(worker,address){
        console.log('listening: worker ' + worker.process.pid +', Address: '+address.address+":"+address.port);
    });

    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    });

    return;
}

var sockjs  = require('sockjs');
var redis   = require('redis');

var http    = require('http');
var uuid    = require('node-uuid');

var app = require('./app');

// Redis publisher
var publisher = redis.createClient();

// Sockjs server
var sockjs_opts = {sockjs_url: "javascripts/sockjs.min.js"};
var sockjs_chat = sockjs.createServer(sockjs_opts);

sockjs_chat.on('connection', function(conn) {
    console.log("======== New Player!!! ========");
    // Init Redis DB
    var browser = redis.createClient();
    var channel_id = "";
    browser.subscribe('public_channel');
    browser.on("message", function(channel, message){
        conn.write(message);
    });

    // Call when data received 
    conn.on('data', function(message) {
        var msgJSON = JSON.parse(message);
        console.log("Method: "+msgJSON.method);
        switch(msgJSON.method){
            // Open new channel
            case "new-channel":
                channel_id = uuid.v1();
                console.log("channel_id: "+channel_id);
                browser.subscribe(channel_id);
                publisher.publish(channel_id, JSON.stringify({
                        'channel': channel_id, 
                        'action': "qrcode", 
                        'player': "1"}));
                break;
            // Player two join
            case "join-channel":
                channel_id = msgJSON.channel;
                browser.subscribe(channel_id);
                publisher.publish(channel_id, JSON.stringify({
                        'channel': channel_id, 
                        'action': "start", 
                        'player': "0"}));
                break;
            // Call when loading finish
             case "loading-unlock":
                console.log("Method: "+msgJSON.method+" "+msgJSON.player+" "+msgJSON.perload_count);
                publisher.publish(channel_id, JSON.stringify({
                    'channel': channel_id, 
                    'action': "loading-unlock", 
                    'perload_count': msgJSON.perload_count, 
                    'player': msgJSON.player}));
                break;
            // Next Status
            case "next-action":
                publisher.publish(channel_id, JSON.stringify({
                    'channel': channel_id, 
                    'action': "next-action", 
                    'player': "0"}));
                break;
        }
    });

    // Clearup and unsubscribe when connection close
    conn.on('close', function() {
        publisher.publish(channel_id, JSON.stringify({'method': "close"}));
        console.log('Close: '+channel_id);
        browser.unsubscribe(channel_id);
        browser.end();
    });
});

// Creat http server
var server = http.createServer(app);
sockjs_chat.installHandlers(server, {prefix:'/lobby'});

console.log(' [*] Listening on 0.0.0.0:9001' );
server.listen(9001, '0.0.0.0');

A multi-player WebSocket project - Client Side

document.ontouchmove = function(event){
    event.preventDefault();
}

// ScokJS
var sockjs_url = '/lobby';
var sockjs = new SockJS(sockjs_url);
sockjs.onopen = function() {
    // perloading image and audio when sock open
    perLoading0();
}
sockjs.onclose   = function()  {print('Closing Connection.');};
sockjs.onerror = function(e) {print(e.data);};
// When sock msg received
sockjs.onmessage = function(e) {
    var json = jQuery.parseJSON(e.data);
    switch(json.action){
        // Show qrcoe
        case "qrcode":
            if(parseInt(json.player) == player){
                channelID = json.channel;
                showQRCode(channelID);
            }
            break;
        // All player ready - Start game
        case "start":
            gameStatus = 1;
            gameStatusChange(gameStatus);
            sendLoadingLock(perloadCountSync[player]);
            break;
        // Loading finished
        case "loading-unlock":
            perloadCountSync[json.player] = json.perload_count;
            loadingUnLock();
            break;
        // Next Status
        case "next-action":
            gameStatus++;
            gameStatusChange(gameStatus);
            break;           
    }
}

var channelID = "";
var gameStatus = 0;
var player = 1;
var current_url = $.url();
// Check Player
if(current_url.fparam('join_game') == 1){
    player = 2;
    if(current_url.fparam('channel')){
        channelID = current_url.fparam('channel');
    }
}
window.location.hash = '#';

// Resize canvas for different screen
var screenRate = 640 / 1029;
var canvasWidth;
var canvasHeight;
var spriteScale, bgScale;

$(window).resize(resize);
resize();

function resize() {
    var _width = $("#mainCanvas").height() * screenRate;
    $("#mainCanvas, #loadingCanvas").css({
        width: _width,
        marginLeft: -_width/2
    });
    $("#sceneHome > .qrcode > img").css({
        position: "absolute",
        top: 0.145*$("#sceneHome").height(),
        left: ($("#sceneHome").width()-0.25*$("#sceneHome").height())/2,
        height: 0.25*$("#sceneHome").height()
    })
}

// Device orientation
function checkOrientation(){
    setTimeout(function(){
        $("#popupTip").fadeIn(300);
        orientationLock = false;
    }, 1000);
}
window.addEventListener('deviceorientation', orientationCallback, false);
function orientationCallback(eventData){
    if(!orientationLock && eventData.gamma > orientationGammaMin && eventData.gamma < orientationGammaMax){
        orientationLock = true;
        nextAnimation();
    }
}

// Init Div
$("#popupTip, #sharePage, #sceneHome").fadeOut(0);

// Init Player
var orientationGammaMin = 0;
var orientationGammaMax = 0;
if(player == 2){
    orientationGammaMin = -80;
    orientationGammaMax = -60;
    $("#popupTip").css({
        backgroundImage : "url(./images/p2_tips.png)"
    });
    $("body").css({
        backgroundColor: "#000000"
    });
    $("#loadingCanvas").css({
        backgroundColor: "rgba(0, 0, 0, 0.5)"
    });
}else if(player == 1){
    orientationGammaMin = 60;
    orientationGammaMax = 80;
    $("#popupTip").css({
        backgroundImage : "url(./images/p1_tips.png)"
    });
    $("#sceneHome").css({
        backgroundImage: "url(./images/bg_qrcode.jpg)"
    });
    $("body").css({
        backgroundColor: "#FFFFF"
    });
    $("#loadingCanvas").css({
        backgroundColor: "rgba(255, 255, 255, 0.5)"
    });
}

var stage,loader, manifest;
var spriteAnimation = [], spriteJson, loadingAnimation;
var backgroundMusic;
var perloadCount = 0;
var perloadCountSync = [];
perloadCountSync[1] = 0;
perloadCountSync[2] = 0;

function perLoading0() {
    stage = new createjs.Stage("mainCanvas");
    stageLoading = new createjs.Stage("loadingCanvas");

    manifest = [
        {src: "images/sprite/loading.png", id: "sprtie_loading"}
    ];
    spriteJson = loadingSpriteJson;

    loader = new createjs.LoadQueue(false);
    loader.installPlugin(createjs.Sound);
    loader.setMaxConnections(5);
    loader.addEventListener("complete", handleComplete);
    loader.addEventListener('progress', function(e){
        if(perloadCount > 0){
            var progress = Math.round(145*e.loaded);
            var rect = new createjs.Shape();
            if(player == 1){
                rect.graphics.beginFill("#000").drawRect(95, 300, Math.round(145*e.loaded), 1);
            }else if(player == 2){
                rect.graphics.beginFill("#FFF").drawRect(95, 300, Math.round(145*e.loaded), 1);
            }
            stageLoading.addChild(rect);
        }
    });
    loader.loadManifest(manifest, true, "./");
}

function perLoading1() {
    if(player == 1){
        manifest = [
            {src: "audios/white-背景音乐一直loop.mp3", id: "audio_bg"},
            {src: "audios/white-蛇变龙.mp3", id: "audio_p1_dragon"},
            {src: "audios/蛇.mp3", id: "audio_p1_snake"},
            {src: "audios/white-蜜蜂.mp3", id: "audio_p1_bee"},
            {src: "images/sprite/p1-a-0.jpg", id: "sprtie_p1_a_0"},
            {src: "images/sprite/p1-a-1.jpg", id: "sprtie_p1_a_1"},
            {src: "images/sprite/p1-a-2.jpg", id: "sprtie_p1_a_2"},
            {src: "images/sprite/p1-a-3.jpg", id: "sprtie_p1_a_3"},
            {src: "images/sprite/p1-b-0.jpg", id: "sprtie_p1_b_0"},
            {src: "images/sprite/p1-b-1.jpg", id: "sprtie_p1_b_1"},
            {src: "images/sprite/p1-b-2.jpg", id: "sprtie_p1_b_2"},
            {src: "images/sprite/p1-b-3.jpg", id: "sprtie_p1_b_3"},
            {src: "images/sprite/p1-c-0.jpg", id: "sprtie_p1_c_0"},
            {src: "images/sprite/p1-c-1.jpg", id: "sprtie_p1_c_1"},
            {src: "images/sprite/p1-c-2.jpg", id: "sprtie_p1_c_2"},
            {src: "images/sprite/p1-c-3.jpg", id: "sprtie_p1_c_3"},
        ];
    }else if(player == 2){
        manifest = [
            {src: "audios/black-剑.mp3", id: "audio_p2_knife"},
            {src: "audios/black-剑变忍者.mp3", id: "audio_p2_ninja"},
            {src: "audios/black-背景音乐一直loop.mp3", id: "audio_bg"},
            {src: "audios/black-蜂变鸟.mp3", id: "audio_p2_bee"},
            {src: "images/sprite/p2-a-0.jpg", id: "sprtie_p2_a_0"},
            {src: "images/sprite/p2-a-1.jpg", id: "sprtie_p2_a_1"},
            {src: "images/sprite/p2-a-2.jpg", id: "sprtie_p2_a_2"},
            {src: "images/sprite/p2-a-3.jpg", id: "sprtie_p2_a_3"},
            {src: "images/sprite/p2-b-0.jpg", id: "sprtie_p2_b_0"},
            {src: "images/sprite/p2-b-1.jpg", id: "sprtie_p2_b_1"},
            {src: "images/sprite/p2-b-2.jpg", id: "sprtie_p2_b_2"},
            {src: "images/sprite/p2-b-3.jpg", id: "sprtie_p2_b_3"},
            {src: "images/sprite/p2-c-0.jpg", id: "sprtie_p2_c_0"},
            {src: "images/sprite/p2-c-1.jpg", id: "sprtie_p2_c_1"},
            {src: "images/sprite/p2-c-2.jpg", id: "sprtie_p2_c_2"},
            {src: "images/sprite/p2-c-3.jpg", id: "sprtie_p2_c_3"},
        ];
    }
    loader.loadManifest(manifest, true, "./");
}

function perLoading2(){
    if(player == 1){
        manifest = [
            {src: "audios/white-ending页面.mp3", id: "audio_p1_end"},
            {src: "audios/white-忍者出现.mp3", id: "audio_p1_ninja"},
            {src: "images/sprite/p1-d-0.jpg", id: "sprtie_p1_d_0"},
            {src: "images/sprite/p1-d-1.jpg", id: "sprtie_p1_d_1"},
            {src: "images/sprite/p1-e-0.jpg", id: "sprtie_p1_e_0"},
            {src: "images/sprite/p1-e-1.jpg", id: "sprtie_p1_e_1"},
            {src: "images/sprite/p1-e-2.jpg", id: "sprtie_p1_e_2"},
            {src: "images/sprite/p1-e-3.jpg", id: "sprtie_p1_e_3"},
            {src: "images/sprite/p1-e-4.jpg", id: "sprtie_p1_e_4"},
            {src: "images/sprite/p1-e-5.jpg", id: "sprtie_p1_e_5"},
            {src: "images/sprite/p1-e-6.jpg", id: "sprtie_p1_e_6"},
            {src: "images/sprite/p1-e-7.jpg", id: "sprtie_p1_e_7"},
            {src: "images/sprite/p1-e-8.jpg", id: "sprtie_p1_e_8"}
        ];
    }else if(player == 2){
        manifest = [
            {src: "images/sprite/p2-d-0.jpg", id: "sprtie_p2_d_0"},
            {src: "images/sprite/p2-d-1.jpg", id: "sprtie_p2_d_1"},
            {src: "images/sprite/p2-d-2.jpg", id: "sprtie_p2_d_2"},
            {src: "images/sprite/p2-e-0.jpg", id: "sprtie_p2_e_0"},
            {src: "images/sprite/p2-e-1.jpg", id: "sprtie_p2_e_1"},
            {src: "images/sprite/p2-e-2.jpg", id: "sprtie_p2_e_2"},
            {src: "images/sprite/p2-e-3.jpg", id: "sprtie_p2_e_3"},
            {src: "images/sprite/p2-e-4.jpg", id: "sprtie_p2_e_4"},
            {src: "images/sprite/p2-e-5.jpg", id: "sprtie_p2_e_5"},
            {src: "images/sprite/p2-e-6.jpg", id: "sprtie_p2_e_6"},
            {src: "images/sprite/p2-e-7.jpg", id: "sprtie_p2_e_7"},
            {src: "images/sprite/p2-e-8.jpg", id: "sprtie_p2_e_8"}
        ];
    }
    loader.loadManifest(manifest, true, "./");
}

function loadingComplete0(){
    spriteJson.images = [        
        loader.getResult("sprtie_loading")
    ];
    
    var spriteSheet = new createjs.SpriteSheet(spriteJson);
    loadingAnimation = new createjs.Sprite(spriteSheet);
    loadingAnimation.x = 95;
    loadingAnimation.y = 186;

    if(player == 1){
        loadingAnimation.gotoAndPlay("p1_loading");
    }else if(player == 2){
        loadingAnimation.gotoAndPlay("p2_loading");
    }
    stage.snapToPixelEnabled = true;
    stage.autoClear = true;

    stageLoading.snapToPixelEnabled = true;
    stageLoading.autoClear = true;
    stageLoading.addChild(loadingAnimation);

    createjs.Ticker.addEventListener("tick", tick);
    createjs.Ticker.setFPS(12);
}

function loadingComplete1(){
    if(player == 1){
        p1SpriteJsonS1.images = [        
            loader.getResult("sprtie_p1_a_0"),
            loader.getResult("sprtie_p1_a_1"),
            loader.getResult("sprtie_p1_a_2"),
            loader.getResult("sprtie_p1_a_3")
        ];
        p1SpriteJsonS2.images = [        
            loader.getResult("sprtie_p1_b_0"),
            loader.getResult("sprtie_p1_b_1"),
            loader.getResult("sprtie_p1_b_2"),
            loader.getResult("sprtie_p1_b_3")
        ];
        p1SpriteJsonS3.images = [        
            loader.getResult("sprtie_p1_c_0"),
            loader.getResult("sprtie_p1_c_1"),
            loader.getResult("sprtie_p1_c_2"),
            loader.getResult("sprtie_p1_c_3")
        ];
        var spriteSheet = new createjs.SpriteSheet(p1SpriteJsonS1);
        spriteAnimation[0] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p1SpriteJsonS2);
        spriteAnimation[1] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p1SpriteJsonS3);
        spriteAnimation[2] = new createjs.Sprite(spriteSheet);
    }else if(player == 2){
        p2SpriteJsonS1.images = [        
            loader.getResult("sprtie_p2_a_0"),
            loader.getResult("sprtie_p2_a_1"),
            loader.getResult("sprtie_p2_a_2"),
            loader.getResult("sprtie_p2_a_3")
        ];    
        p2SpriteJsonS2.images = [        
            loader.getResult("sprtie_p2_b_0"),
            loader.getResult("sprtie_p2_b_1"),
            loader.getResult("sprtie_p2_b_2"),
            loader.getResult("sprtie_p2_b_3")
        ];   
        p2SpriteJsonS3.images = [        
            loader.getResult("sprtie_p2_c_0"),
            loader.getResult("sprtie_p2_c_1"),
            loader.getResult("sprtie_p2_c_2"),
            loader.getResult("sprtie_p2_c_3")
        ];
        var spriteSheet = new createjs.SpriteSheet(p2SpriteJsonS1);
        spriteAnimation[0] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p2SpriteJsonS2);
        spriteAnimation[1] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p2SpriteJsonS3);
        spriteAnimation[2] = new createjs.Sprite(spriteSheet);
    }

    if(player == 1){
        createGameRoom();
    }else if(player == 2){
        if(channelID){
            joinGameRoom(channelID);
        }  
    }
    $("#loadingCanvas").fadeOut(300);
}

function loadingComplete2(){
    if(player == 1){
        p1SpriteJsonS4.images = [        
            loader.getResult("sprtie_p1_d_0"),
            loader.getResult("sprtie_p1_d_1")
        ];
        p1SpriteJsonS5.images = [        
            loader.getResult("sprtie_p1_e_0"),
            loader.getResult("sprtie_p1_e_1"),
            loader.getResult("sprtie_p1_e_2"),
            loader.getResult("sprtie_p1_e_3"),
            loader.getResult("sprtie_p1_e_4"),
            loader.getResult("sprtie_p1_e_5"),
            loader.getResult("sprtie_p1_e_6"),
            loader.getResult("sprtie_p1_e_7"),
            loader.getResult("sprtie_p1_e_8")
        ];
        var spriteSheet = new createjs.SpriteSheet(p1SpriteJsonS4);
        spriteAnimation[3] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p1SpriteJsonS5);
        spriteAnimation[4] = new createjs.Sprite(spriteSheet);
    }else if(player == 2){    
        p2SpriteJsonS4.images = [        
            loader.getResult("sprtie_p2_d_0"),
            loader.getResult("sprtie_p2_d_1"),
            loader.getResult("sprtie_p2_d_2")
        ]; 
        p2SpriteJsonS5.images = [        
            loader.getResult("sprtie_p2_e_0"),
            loader.getResult("sprtie_p2_e_1"),
            loader.getResult("sprtie_p2_e_2"),
            loader.getResult("sprtie_p2_e_3"),
            loader.getResult("sprtie_p2_e_4"),
            loader.getResult("sprtie_p2_e_5"),
            loader.getResult("sprtie_p2_e_6"),
            loader.getResult("sprtie_p2_e_7"),
            loader.getResult("sprtie_p2_e_8")
        ];   
        var spriteSheet = new createjs.SpriteSheet(p2SpriteJsonS4);
        spriteAnimation[3] = new createjs.Sprite(spriteSheet);
        var spriteSheet = new createjs.SpriteSheet(p2SpriteJsonS5);
        spriteAnimation[4] = new createjs.Sprite(spriteSheet);
    }
}

// Loading Complete Handler
function handleComplete() {
    if(perloadCount > 1){
        sendLoadingLock(perloadCount);
    }
    perloadCountSync[player] = perloadCount;
    switch(perloadCount){
        case 0: 
            loadingComplete0(); 
            perLoading1();
            break;
        case 1: 
            loadingComplete1(); 
            perLoading2();
            break;
        case 2: 
            loadingComplete2();
            break;
    }
    perloadCount++;
}

var loadingCanvas = $("#loadingCanvas");
// Canvas Update
function tick(event) {
    stage.update(event);
    if (loadingCanvas.is(":visible")) {
        stageLoading.update(event);
    }
}

function createGameRoom(cid){
    sockjs.send(JSON.stringify({'method': 'new-channel'}));
}

var soundLoop;
var orientationLoadingLockCount = 0;
var orientationLoadingLockPlayer = 0;
var orientationLock = true;
function gameStatusChange(game_status){
    switch(game_status){
        case 1:
            $("#sceneHome").fadeOut(300, function(){
                if(backgroundMusic){
                    backgroundMusic.stop();
                }
                backgroundMusic = createjs.Sound.play("audio_bg");                
                backgroundMusic.setLoop(99999);
                if(player == 2){
                    backgroundMusic.volume = 0.1;
                }
            });
            stage.addChild(spriteAnimation[0]);
            spriteAnimation[0].gotoAndPlay("animation");
            spriteAnimation[0].on("animationend", function(evt){
                if(evt.name == "animation"){
                    if(player == 1){
                        checkOrientation();
                    }
                }
            });

            if(player == 1){
                setTimeout(function(){
                    soundLoop = createjs.Sound.play("audio_p1_bee");
                    soundLoop.setLoop(99999);
                }, 4000);
            }
            break;
        case 2:
            $("#popupTip").fadeOut(100);
            stage.removeChild(spriteAnimation[0]);
            stage.addChild(spriteAnimation[1]);
            spriteAnimation[1].gotoAndPlay("animation");
            spriteAnimation[1].on("animationend", function(evt){
                if(evt.name == "animation"){
                    if(player == 2){
                        checkOrientation();
                    }
                }
            });
            if(player == 1){
                if(soundLoop){
                    setTimeout(function(){
                        soundLoop.stop();
                    }, 1500);
                }
                setTimeout(function(){
                    soundLoop = createjs.Sound.play("audio_p1_snake");
                    soundLoop.setLoop(99999);
                }, 2200);
            }else{
                soundLoop = createjs.Sound.play("audio_p2_bee");
            }
            break;
        case 3:
            $("#popupTip").fadeOut(100);
            stage.removeChild(spriteAnimation[1]);
            stage.addChild(spriteAnimation[2]);
            spriteAnimation[2].gotoAndPlay("animation");
            spriteAnimation[2].on("animationend", function(evt){
                if(evt.name == "animation"){
                    if(player == 1){
                        checkOrientation();
                    }
                }
            });
            if(player == 1){
                if(soundLoop){
                    setTimeout(function(){
                        soundLoop.stop();
                    }, 1800);
                }
                setTimeout(function(){
                    soundLoop = createjs.Sound.play("audio_p1_dragon");
                    soundLoop.setLoop(99999);
                }, 2000);
            }else{
                setTimeout(function(){
                    soundLoop = createjs.Sound.play("audio_p2_knife");
                }, 2500);
            }
            break;
        case 4:
            $("#popupTip").fadeOut(100);
            stage.removeChild(spriteAnimation[2]);
            stage.addChild(spriteAnimation[3]);
            spriteAnimation[3].gotoAndPlay("animation");
            spriteAnimation[3].on("animationend", function(evt){
                if(evt.name == "animation"){
                    checkLoadingSync(2, 2);
                }
            });
            if(player == 1){
                if(soundLoop){
                    setTimeout(function(){
                        soundLoop.stop();
                    }, 1000);
                }
            }else{
                setTimeout(function(){
                    createjs.Sound.play("audio_p2_ninja");
                }, 300);
            }
            break;
        case 5:
            $("#popupTip").fadeOut(100);
            if(soundLoop){
                soundLoop.stop();
            }
            stage.removeChild(spriteAnimation[3]);
            stage.addChild(spriteAnimation[4]);
            if(player == 1){
                createjs.Sound.play("audio_p1_ninja");
            }
            spriteAnimation[4].gotoAndPlay("animation");
            spriteAnimation[4].on("animationend", function(evt){
                if(evt.name == "animation"){
                    sharePage();
                }
            });
            setTimeout(function(){
                if(backgroundMusic){
                    backgroundMusic.stop();
                }
                if(player == 1){
                    backgroundMusic = createjs.Sound.play("audio_p1_end");
                    backgroundMusic.setLoop(99999);
                }
            }, 8000);
            break;
    }
}

function checkLoadingSync(_count, _player){
    if(perloadCountSync[1] < _count || perloadCountSync[2] < _count){
        $("#loadingCanvas").fadeIn(300);
        orientationLoadingLockCount = _count;
        orientationLoadingLockPlayer = _player;
    }else{
        if(player == _player){
            checkOrientation();
        }
        orientationLoadingLockCount = 0;
        orientationLoadingLockPlayer = 0;
    }
}

function loadingUnLock(){
    $("#loadingCanvas").fadeOut(100);
    if(orientationLoadingLockCount != 0 || orientationLoadingLockPlayer != 0){
        checkLoadingSync(orientationLoadingLockCount, orientationLoadingLockPlayer);
    }
}

function sendLoadingLock(_count){
    sockjs.send(JSON.stringify({'method': 'loading-unlock', "player" : player, "perload_count" : _count}));
}

function nextAnimation(){
    sockjs.send(JSON.stringify({'method': 'next-action', "player" : player}));
}

function joinGameRoom(cid){
    sockjs.send(JSON.stringify({'method': 'join-channel', 'channel': channelID}));
}

function showQRCode(cid){
    $("#sceneHome > .qrcode").qrcode({
        "render": "image",
        "size": 250,
        "color": "#3a3",
        "text": current_url.attr('protocol')+"://"+
                current_url.attr('host')+":"+
                current_url.attr('port')+
                current_url.attr('path')+"#join_game=1&channel="+cid
    });
    setTimeout(function(){
        $("#sceneHome > .qrcode > img").css({
            position: "absolute",
            top: 0.145*$("#sceneHome").height(),
            left: ($("#sceneHome").width()-0.25*$("#sceneHome").height())/2,
            height: 0.25*$("#sceneHome").height()
        })
        $("#sceneHome").fadeIn(300);
    }, 500);
}

function sharePage(){
    $("#sharePage").fadeIn(0);
}

// Share Screen
$("#sharePage > .btnPlayAgain").click(function(){
    window.location.href = "/";
});

$("#sharePage > .btnSharePopup").click(function(){
    $("#sharePage > .btnBack").fadeIn(0);
    spriteAnimation[4].gotoAndPlay("share");
});

$("#sharePage > .btnBack").click(function(){
    $(this).fadeOut(300);
    spriteAnimation[4].gotoAndPlay("share_reverse");
});

A Codeigniter controllers sample

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

include __DIR__."/../assets/lib/wechat.class.php";

date_default_timezone_set('Asia/Shanghai');

define('PATH_UPLOAD_ORIGINAL', "uploads/original/");

define('APPID', '');
define('SECRET', '');
define('LOG_HISTORY', __DIR__."/../assets/log/history.txt");

function logdebug($text){
    file_put_contents('log.txt',$text."\n",FILE_APPEND);     
}

function shortenSinaUrl($long_url){
	$apiKey='2499377203';
	$apiUrl='http://api.t.sina.com.cn/short_url/shorten.json?source='.$apiKey.'&url_long='.$long_url;
	$curlObj = curl_init();
	curl_setopt($curlObj, CURLOPT_URL, $apiUrl);
	curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($curlObj, CURLOPT_SSL_VERIFYPEER, 0);
	curl_setopt($curlObj, CURLOPT_HEADER, 0);
	curl_setopt($curlObj, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
	$response = curl_exec($curlObj);
	curl_close($curlObj);
	$json = json_decode($response);
	return $json[0]->url_short;
}

class Admin extends CI_Controller {
	
	public $data;

	public function __construct()
	{
		parent::__construct();

		$this->load->helper(array('form', 'url'));
		$this->load->model('portraits_model');

		$this->data=array(
            "assets_path"=>base_url("/application/assets/")
        );
	}
	function logout()
	{
		$this->session->unset_userdata('logged_in');
		session_destroy();
		redirect('login', 'refresh');
	}

	public function index()
	{
		if($this->session->userdata('logged_in')){
			if(isset($_REQUEST["mode"])){
				$this->data['mode'] = $_REQUEST["mode"];
			}else{
				$this->data['mode'] = 0;
			}
            $this->data['stats'] = $this->portraits_model->get_all();
			switch($this->data['mode']){
				case 0:
				case 1:
				case 2:
					$this->data['portraits'] = $this->portraits_model->get_all_by_approval_status_limit($this->data['mode']);
					$this->load->view('admin/header.php', $this->data);
					$this->load->view('admin/index.php', $this->data);
					$this->load->view('admin/footer.php', $this->data);
					break;
				case 3:
					$this->data['log_history'] = read_file(LOG_HISTORY);
					$this->load->view('admin/header.php', $this->data);
					$this->load->view('admin/log.php', $this->data);
					$this->load->view('admin/footer.php', $this->data);
					break;
				case 4:
					$this->data['portraits'] = $this->portraits_model->get_processing_status();
					$this->load->view('admin/header.php', $this->data);
					$this->load->view('admin/index.php', $this->data);
					$this->load->view('admin/footer.php', $this->data);
					break;
			}
		}else{
			redirect('login', 'refresh');
		}
	}

	public function api()
	{
		header("Access-Control-Allow-Origin: *");
		$action = $_REQUEST["action"];
		if(!isset($action)){
			die(json_encode(array("error_code"=>"100", "message"=>"Parameter[action] missing.")));
		}
		switch($action){
			case "upload":
				$this->upload_resize();
				break;
			case "gallery":
				$pager = 0;
				$number = 20;
				if(isset($_REQUEST['pager'])) $pager = $_REQUEST['pager'];
				if(isset($_REQUEST['number'])) $number = $_REQUEST['number'];
				echo json_encode($this->portraits_model->get_gallery($pager, $number));
				break;
			case "gallery_debug":
				echo json_encode($this->portraits_model->get_gallery_all());
				break;
			case "regenerate":
				if(!isset($_REQUEST["pid"])){
					die(json_encode(array("error_code"=>"301", "message"=>"Parameter[pid] missing.")));
				}
				$pArr = $this->portraits_model->get_portrait($_REQUEST["pid"]);
				if(count($pArr)>0){
					$this->send_photoshop_server($pArr["id"], "/".PATH_UPLOAD_ORIGINAL.$pArr["photo_name"], $pArr["photo_quote"], $pArr["photo_key"]);
				}
				break;
		}
	}

	public function upload()
	{
		$this->load->view('normal_upload1.php', $this->data);
	}

	public function upload_resize()
	{
		$minWidth = "640";
		$minHeight = "853";

		if(!isset($_REQUEST['uid']) || 
		   !isset($_REQUEST['sns']) || 
		   !isset($_REQUEST['key']) || 
		   !isset($_REQUEST['quote']) || 
		   empty($_FILES["file"]["name"])){
			die(json_encode(array("error_code"=>"101", "message"=>"Parameter missing.")));
		}

		$username="";
		if(isset($_REQUEST['username'])){
			$username=$_REQUEST['username'];
		}
		$pid = $this->portraits_model->add($_REQUEST['sns'], $_REQUEST['uid'], $username, $_REQUEST['key'], $_REQUEST['quote']);
		$photo_name = $pid.".".pathinfo($_FILES["file"]["name"], PATHINFO_EXTENSION);
		$photo_name_new = $pid.".jpg";

		$config['upload_path'] = "./".PATH_UPLOAD_ORIGINAL;
		$config['allowed_types'] = 'jpg|png';
		$config['max_size']	= '5000';
		$config['max_width']  = '4096';
		$config['max_height']  = '4096';
		$config['overwrite']  = true;
		$this->load->library('upload', $config);

		$_FILES['file']['name'] = $photo_name;
		if(!$this->upload->do_upload('file')){
			die(json_encode(array("error_code"=>"102", "message"=>$this->upload->display_errors())));
		}else{
			$config['image_library'] = 'ImageMagick';
            $config['source_image'] = $this->upload->upload_path.$this->upload->file_name;
            $config['new_image'] = $this->upload->upload_path.$photo_name_new;
            $config['maintain_ratio'] = FALSE;
            $config['quality'] = '95%';
            $config['library_path'] = '/usr/bin/convert';

            list($imagewidth, $imageheight, $imageType) = getimagesize($this->upload->upload_path.$this->upload->file_name);
            $rWidth = $minWidth/$imagewidth;
            $rHeight = $minHeight/$imageheight;
            if($rWidth > $rHeight){
            	$config['width'] = $rWidth*$imagewidth;
            	$config['height'] = $rWidth*$imageheight;
            }else{
            	$config['width'] = $rHeight*$imagewidth;
            	$config['height'] = $rHeight*$imageheight;
            }
			$this->load->library('image_lib', $config);

			if (!$this->image_lib->resize()){
				die(json_encode(array("error_code"=>"103", "message"=>$this->image_lib->display_errors())));
			}

			$this->portraits_model->update_photo($pid, $photo_name_new);
			$this->send_photoshop_server($pid, "/".PATH_UPLOAD_ORIGINAL.$photo_name_new, $_REQUEST['quote'], $_REQUEST['key']);

			die(json_encode(array("error_code"=>"0", "message"=>"File upload success.")));
		}
	}

	public function send_photoshop_server($pid, $photo_path, $quote, $key)
	{
		$target_url = 'server/index.php';
		$img_path = "@".realpath('./').$photo_path;
		$data = array("pid" => $pid, 
					  "action" => "sendPhoto", 
					  "quote" => $quote, 
					  "selectedKey" => $key, 
					  "picture" => $img_path);
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL,$target_url);
		curl_setopt($ch, CURLOPT_POST,1);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
	    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);	
		$result=curl_exec ($ch);
		
		$this->portraits_model->update_curl_log($pid, $result);
		$this->portraits_model->update_photo_status($pid, "processing");
		$this->log_history("SUCCESS: Image sent to photoshop server: ".$pid." $result");
	}

	public function approve_portrait()
	{
		$pid = $_REQUEST["pid"];
		$sns_id = $_REQUEST["sns_id"];
		if(isset($pid)){
			if($this->portraits_model->update_approve_status($pid, 1)){
				if($sns_id == 1){
					$this->send_wechat_message($pid, 1);
				} else if($sns_id == 3) {
                    $this->send_douban_message($pid, 1);
                }
				echo json_encode(array("error"=>0));
				$this->log_history("SUCCESS: Portrait approved: ".$pid);
			}else{
				echo json_encode(array("error"=>1));
				$this->log_history("ERROR: Portrait approve: ".$pid);
			}
		}
	}

	public function reject_portrait()
	{
		$pid = $_REQUEST["pid"];
		$sns_id = $_REQUEST["sns_id"];
		if(isset($pid)){
			if($this->portraits_model->update_approve_status($pid, 2)){
				if($sns_id == 1){
					$this->send_wechat_message($pid, 0);
				} else if($sns_id == 3) {
                    $this->send_douban_message($pid, 0);
                }
				echo json_encode(array("error"=>0));
				$this->log_history("SUCCESS: Portrait rejected: ".$pid);
			}else{
				echo json_encode(array("error"=>1));	
				$this->log_history("ERROR: Portrait reject: ".$pid);
			}
		}
	}

	public function resize_photo()
	{
		$pid = $_REQUEST["pid"];
		$scale = $_REQUEST["scale"];
		$imgX = $_REQUEST["x"]*$scale;
		$imgY = $_REQUEST["y"]*$scale;
		$width = $_REQUEST["width"];
		$height = $_REQUEST["height"];
		$imageName = $_REQUEST["name"];

		if(!isset($pid) || 
		   !isset($imgX) || 
		   !isset($imgY) || 
		   !isset($width) || 
		   !isset($height) || 
		   !isset($imageName)){
			die(json_encode(array("error" => 1)));
		}

		$image = "./uploads/original/".$imageName;
		$imageCrop = "./uploads/crop/".$imageName;
		
		list($imagewidth, $imageheight, $imageType) = getimagesize($image);
		$imageType = image_type_to_mime_type($imageType);
		$newImageWidth = ceil($width * $scale);
		$newImageHeight = ceil($height * $scale);
		$newImage = imagecreatetruecolor($newImageWidth,$newImageHeight);
		switch($imageType) {
			case "image/gif":
				$source=imagecreatefromgif($image); 
				break;
		    case "image/pjpeg":
			case "image/jpeg":
			case "image/jpg":
				$source=imagecreatefromjpeg($image); 
				break;
		    case "image/png":
			case "image/x-png":
				$source=imagecreatefrompng($image); 
				break;
	  	}
		imagecopyresampled($newImage,$source,0,0,$imgX,$imgY,$newImageWidth,$newImageHeight,$newImageWidth,$newImageHeight);
		
		switch($imageType) {
			case "image/gif":
		  		imagegif($newImage,$imageCrop); 
				break;
	      	case "image/pjpeg":
			case "image/jpeg":
			case "image/jpg":
		  		imagejpeg($newImage,$imageCrop,90); 
				break;
			case "image/png":
			case "image/x-png":
				imagepng($newImage,$imageCrop);  
				break;
	    }
	    $this->portraits_model->update_photo_cropped($pid);
	    $arr = $this->portraits_model->get_portrait($pid);
	    $this->send_photoshop_server($pid, "/uploads/crop/".$imageName, $arr["photo_quote"], $arr["photo_key"]);
	    die(json_encode(array("error" => 0)));
	}

	public function callback()
	{
		// log_message('error', 'PS Callback');
		$config['upload_path'] = './uploads/processed/';
		$config['allowed_types'] = 'gif|jpg|png';
		$config['max_size']	= '10000';
		$config['max_width']  = '2048';
		$config['max_height']  = '2048';
		$config['overwrite']  = true;
		
		$pid = $_REQUEST["pid"];

		$_FILES['picture']['name'] = $_REQUEST["pid"].'.jpg';

		$this->load->library('upload', $config);
		if (!$this->upload->do_upload('picture'))
		{
			echo '{"response":"ERROR"}';
			$this->portraits_model->update_photo_status($pid, "process error");
			$this->log_history("ERROR: Image processing: ".$pid);
		}else{
			echo '{"response":"OK"}';
			$this->portraits_model->update_photo_status($pid, "processed");
			$this->log_history("SUCCESS: Image processed: ".$pid);
		}
	}

	public function send_wechat_message($pid, $status)
	{
		if(!isset($pid)) return false;
		$wechat_sdk_options = array(
			'token'=>'weixin_key',
			'appid'=>APPID,
			'appsecret'=>SECRET,
			'debug'=>true,
			'logcallback'=>'logdebug'
		);
		$weChatObj = new WechatSDK($wechat_sdk_options);
		$portraitArr = $this->portraits_model->get_portrait($pid);
		if(count($portraitArr)>0 ){
			if($status == 1){
				$newmedia = $weChatObj->uploadMedia('image', "/uploads/processed/".$portraitArr["photo_name"]);
				$weChatObj->sendCustomImage($newmedia['media_id'], $portraitArr['user_id']);
				sleep(3);
				$weChatObj->sendCustomText("诚邀您分享至微博。".shortenSinaUrl("portrait.php?pic=".$portraitArr['photo_name']), $portraitArr['user_id']);	
			}else{
				$newmedia = $weChatObj->uploadMedia('image', "/application/assets/images/wechat_rejected_message.jpg");
				$weChatObj->sendCustomImage($newmedia['media_id'], $portraitArr['user_id']);
			}
			$this->log_history("SUCCESS: Send wechat message to ".$pid."(".$portraitArr['user_id'].")");
		}else{
			$this->log_history("ERROR: Send wechat message, no ".$pid);
		}
	}

    public function send_douban_message($pid, $status)
    {
        $portraitArr = $this->portraits_model->get_portrait($pid);
        if(count($portraitArr)>0 ){
            if($status == 1){
                $ch = curl_init();
                $data = array('user_id' => $portraitArr['user_id'], 'image' => '@' . "/data/webroot/www/backend/uploads/processed/".$portraitArr["photo_name"]);
                curl_setopt($ch, CURLOPT_URL, "/upload");
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
                $doubanResult = curl_exec($ch);
                //$this->log_history("DOUBAN DEBUG: " . json_encode($data));

                if ($doubanResult === FALSE) {
                    $this->log_history("ERROR (1): Send douban message, no ".$pid . " error: " . curl_error($ch));
                } else {
                    $dataDouban = json_decode($doubanResult);
                    if(isset($dataDouban->url)) {
                        $this->log_history("SUCCESS: Send douban message to ".$pid."(".$dataDouban->url.")");
                    } else {
                        $this->log_history("ERROR (2): Send douban message, no ".$pid . " error: " . $dataDouban);
                    }
                }
            }else{
                $ch = curl_init();
                $data = array('user_id' => $portraitArr['user_id']);
                curl_setopt($ch, CURLOPT_URL, "/reject");
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
                $doubanResult = curl_exec($ch);
                if ($doubanResult === FALSE) {
                    $this->log_history("ERROR (1): Reject douban photo, no ".$pid . " error: " . curl_error($ch));
                } else {
                    $dataDouban = json_decode($doubanResult);
                    if(isset($dataDouban->r) && $dataDouban->r == 0) {
                        $this->log_history("SUCCESS: Reject douban photo");
                    } else {
                        $this->log_history("ERROR (2): Reject douban photo, error: " . $dataDouban);
                    }
                }
            }
        }else{
            $this->log_history("ERROR (4): Send douban message, no ".$pid);
        }
    }

	public function wechat_portal()
	{
		$options = array(
			'token'=>'weixin_key',
			'appid'=>APPID,
			'appsecret'=>SECRET,
			'debug'=>false,
			'logcallback'=>'logdebug'
		);

		$weObj = new WechatSDK($options);
		$weObj->valid();
		$type = $weObj->getRev()->getRevType();
		$sleep_time = 1.5;		
		
		switch($type) {
			case WechatSDK::MSGTYPE_TEXT:
				break;
			case WechatSDK::MSGTYPE_EVENT:
				$event_array = $weObj->getRevEvent();
				$event_type = $event_array["event"];

				// subscribe
				if(strcasecmp($event_type, "subscribe") == 0){
					$weObj->sendCustomText("感谢关注。", $weObj->getRevFrom());				
				}
				break;
		}
	}

	Private function log_history($text)
	{
		$file = read_file(LOG_HISTORY);
		$text = date('Y/m/d/ h:i:s a', time())." ".$text."<br />\n".$file;
		write_file(LOG_HISTORY, $text, "w+");
	}
}
Google Analytics Alternative