<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Some reminiscences, some memories &#187; Demo</title>
	<atom:link href="http://www.mikespook.com/index.php/tag/demo/feed" rel="self" type="application/rss+xml" />
	<link>http://www.mikespook.com</link>
	<description>Just another boring day</description>
	<lastBuildDate>Tue, 10 Jan 2012 03:14:06 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Twisted 的 MemcacheProtocol 使用</title>
		<link>http://www.mikespook.com/2010/02/twisted-%e7%9a%84-memcacheprotocol-%e4%bd%bf%e7%94%a8/</link>
		<comments>http://www.mikespook.com/2010/02/twisted-%e7%9a%84-memcacheprotocol-%e4%bd%bf%e7%94%a8/#comments</comments>
		<pubDate>Mon, 08 Feb 2010 14:16:11 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[defer]]></category>
		<category><![CDATA[Demo]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=467</guid>
		<description><![CDATA[<style type="text/css">
#leftcontainerBox {
	float:left;
	position: fixed;
	top: 60%;
	left: 70px;
}
#leftcontainerBox .buttons {
	float:left;
	clear:both;
	margin:4px 4px 4px 4px;
	padding-bottom:2px;
}
#bottomcontainerBox {
	width: 50%;
	padding-top: 1px;
}
#bottomcontainerBox .buttons {
	float: left;
	margin: 4px 4px 4px 4px;
}
</style>
Twisted 的 defer 是个强大但是诡异的东西，遗憾的是，除了官方文档和这些文档的翻译，其他资料不多。而基于 defer 的客户端协议实现资料就更少了。Memcache 的协议实现也是利用 defer，但是除了一个单元测试的代码外，很难看到其他可以参考的实现。搞了几天，总算是有一个简单的封装，可以避开 defer，无须让所有参与开发的技术人员一定要弄懂 defer 才能用 MemCacheProtocol。

上代码吧，这样封装，应该是比较好理解的……

#!/usr/bin/evn python

<span class="readmore"><a href="http://www.mikespook.com/2010/02/twisted-%e7%9a%84-memcacheprotocol-%e4%bd%bf%e7%94%a8/" title="Twisted 的 MemcacheProtocol 使用">阅读全文——共3517字</a></span>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
#leftcontainerBox {
	float:left;
	position: fixed;
	top: 60%;
	left: 70px;
}
#leftcontainerBox .buttons {
	float:left;
	clear:both;
	margin:4px 4px 4px 4px;
	padding-bottom:2px;
}
#bottomcontainerBox {
	width: 50%;
	padding-top: 1px;
}
#bottomcontainerBox .buttons {
	float: left;
	margin: 4px 4px 4px 4px;
}
</style>
<p>Twisted 的 defer 是个强大但是诡异的东西，遗憾的是，除了官方文档和这些文档的翻译，其他资料不多。而基于 defer 的客户端协议实现资料就更少了。Memcache 的协议实现也是利用 defer，但是除了一个单元测试的代码外，很难看到其他可以参考的实现。搞了几天，总算是有一个简单的封装，可以避开 defer，无须让所有参与开发的技术人员一定要弄懂 defer 才能用 MemCacheProtocol。<br />
上代码吧，这样封装，应该是比较好理解的……<span id="more-467"></span></p>
<pre lang="python">#!/usr/bin/evn python
# -*- coding:utf-8 -*-
'''
Created on 2010-2-7

@author: mikespook
'''
from twisted.internet import reactor, protocol
from twisted.protocols.memcache import MemCacheProtocol, DEFAULT_PORT

from pnp.log import log

class MemCache:
    '''
    memcache 调用接口
    >>>def printResult(result):
    >>>    print result
    >>>mc = MemCache()
    >>>mc.version(printResult)
    >>>reactor.callLater(1, reactor.stop)
    >>>reactor.run()
    '''
    # 客户端创建对象
    clientCreator = protocol.ClientCreator(reactor, MemCacheProtocol)
    def __init__(self, host, port=DEFAULT_PORT):
        '''
        初始化
        @param host: 主机名
        @param port: 端口号
        '''
        self.mcDefer = self.clientCreator.connectTCP(host, port)
        self.mcDefer.addErrback(self._onErr)

    def _onErr(self, err):
        '''
        错误回调
        '''
        log.err(err) 

    def version(self, callback):
        '''
        获取版本
        @param callback: 回调函数，结果将作为参数传递入该接口
        '''
        self.mcDefer.addCallback(self._mcVersion, callback)

    def _mcVersion(self, proto, callback):
        '''
        版本回调
        @param proto: twisted.p.mMemCacheProtocol
        @param callback: 回调函数返回结果
        '''
        d = proto.version()
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def increment(self, key, callback):
        '''
        增长
        @param key: 键
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcIncrement, key, callback)        

    def _mcIncrement(self, proto, key, callback):
        '''
        增长回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param callback: 回调函数返回结果
        '''
        d = proto.increment(key)
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def decrement(self, key, callback):
        '''
        减少
        @param key: 键
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcDecrement, key, callback)        

    def _mcDecrement(self, proto, key, callback):
        '''
        减少回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param callback: 回调函数返回结果
        '''
        d = proto.decrement(key)
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def get(self, key, callback):
        '''
        获取值
        @param key: 键
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcGet, key, callback)        

    def _mcGet(self, proto, key, callback):
        '''
        获取值回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param callback: 回调函数返回结果
        '''
        d = proto.get(key)
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def set(self, key, value, callback):
        '''
        设置值
        @param key: 键
        @param value: 值
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcSet, key, value, callback)        

    def _mcSet(self, proto, key, value, callback):
        '''
        设置值回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param value: 值
        @param callback: 回调函数返回结果
        '''
        d = proto.set(key, value)
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def delete(self, key, callback):
        '''
        删除
        @param key: 键
        @param value: 值
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcDelete, key, callback)        

    def _mcDelete(self, proto, key, value, callback):
        '''
        删除回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param value: 值
        @param callback: 回调函数返回结果
        '''
        d = proto.delete(key)
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def stats(self, callback):
        '''
        状态
        @param key: 键
        @param value: 值
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcStats, callback)        

    def _mcStats(self, proto, callback):
        '''
        状态回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param value: 值
        @param callback: 回调函数返回结果
        '''
        d = proto.stats()
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto

    def flushAll(self, callback):
        '''
        刷新
        @param key: 键
        @param value: 值
        @param callback: 回调函数
        '''
        self.mcDefer.addCallback(self._mcFlushAll, callback)        

    def _mcFlushAll(self, proto, callback):
        '''
        刷新回调
        @param proto: twisted.p.mMemCacheProtocol
        @param key: 键
        @param value: 值
        @param callback: 回调函数返回结果
        '''
        d = proto.flushAll()
        d.addErrback(self._onErr)
        d.addCallback(callback)
        return proto</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2010/02/twisted-%e7%9a%84-memcacheprotocol-%e4%bd%bf%e7%94%a8/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>在游戏中使用脚本语言</title>
		<link>http://www.mikespook.com/2009/05/%e5%9c%a8%e6%b8%b8%e6%88%8f%e4%b8%ad%e4%bd%bf%e7%94%a8%e8%84%9a%e6%9c%ac%e8%af%ad%e8%a8%80/</link>
		<comments>http://www.mikespook.com/2009/05/%e5%9c%a8%e6%b8%b8%e6%88%8f%e4%b8%ad%e4%bd%bf%e7%94%a8%e8%84%9a%e6%9c%ac%e8%af%ad%e8%a8%80/#comments</comments>
		<pubDate>Fri, 22 May 2009 05:55:34 +0000</pubDate>
		<dc:creator>mikespook</dc:creator>
				<category><![CDATA[Game]]></category>
		<category><![CDATA[NetBeans]]></category>
		<category><![CDATA[Demo]]></category>
		<category><![CDATA[Scripting]]></category>
		<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">http://www.mikespook.com/?p=330</guid>
		<description><![CDATA[<style type="text/css">
#leftcontainerBox {
	float:left;
	position: fixed;
	top: 60%;
	left: 70px;
}
#leftcontainerBox .buttons {
	float:left;
	clear:both;
	margin:4px 4px 4px 4px;
	padding-bottom:2px;
}
#bottomcontainerBox {
	width: 50%;
	padding-top: 1px;
}
#bottomcontainerBox .buttons {
	float: left;
	margin: 4px 4px 4px 4px;
}
</style>
这只是一个有趣的探索，demo 使用 java 编写。模拟了一个龙与地下城类 RPG 游戏中，在不同的房间内移动的简单游戏场景。

阅读本文前，首先下载使用 Netbeans 6.5 建立的完整项目代码：下载。然后，我会用 UML 图的方式来说明如何在游戏中使用脚本，其中可能还会简介一下游戏中实体对象的建立和管理（不知道值得不值得另外写一篇文章来介绍了）。

I have a dream&#8230;

<span class="readmore"><a href="http://www.mikespook.com/2009/05/%e5%9c%a8%e6%b8%b8%e6%88%8f%e4%b8%ad%e4%bd%bf%e7%94%a8%e8%84%9a%e6%9c%ac%e8%af%ad%e8%a8%80/" title="在游戏中使用脚本语言">阅读全文——共3691字</a></span>]]></description>
			<content:encoded><![CDATA[<style type="text/css">
#leftcontainerBox {
	float:left;
	position: fixed;
	top: 60%;
	left: 70px;
}
#leftcontainerBox .buttons {
	float:left;
	clear:both;
	margin:4px 4px 4px 4px;
	padding-bottom:2px;
}
#bottomcontainerBox {
	width: 50%;
	padding-top: 1px;
}
#bottomcontainerBox .buttons {
	float: left;
	margin: 4px 4px 4px 4px;
}
</style>
<p>这只是一个有趣的探索，demo 使用 java 编写。模拟了一个龙与地下城类 RPG 游戏中，在不同的房间内移动的简单游戏场景。</p>
<p>阅读本文前，首先下载使用 Netbeans 6.5 建立的完整项目代码：<a href="http://www.mikespook.com/wp-content/uploads/2009/05/gamescripting.zip">下载</a>。然后，我会用 UML 图的方式来说明如何在游戏中使用脚本，其中可能还会简介一下游戏中实体对象的建立和管理（不知道值得不值得另外写一篇文章来介绍了）。</p>
<p>I have a dream&#8230;<span id="more-330"></span></p>
<p>现在要构建一个极为无聊的小世界。说它小，是因为我只打算让它有三个房间，三个房间之间两两互通的门。仅此而已。首先上图：<a href="http://www.mikespook.com/wp-content/uploads/2009/05/rooms.gif"><img class="alignright size-full wp-image-333" title="三个房间" src="http://www.mikespook.com/wp-content/uploads/2009/05/rooms.gif" alt="三个房间" width="241" height="241" /></a></p>
<p>我们假设左边上面的房间叫 room-1，右边上面的房间叫 room-2，下面的房间叫 room-3。玩家在这三个房间中穿行，当然，不可能是穿墙。人，一定是要走门的。如图。就这么简单的逻辑而已，不用脚本语言也能轻松完成。不过如果希望多来一点拓展性呢？比如，room-3 不允许等级在5级以下玩家进入；room-2 当十级以上玩家进入后就会自动瞬间移动到 room-1；门锁住以后就不能通过，更夸张一点，门锁住以后如果不把锁打坏就不能通过……可能性太多了。不用脚本的情况下，如果要将这么多都实现，是一件非常繁琐的事情。</p>
<p>好吧，让我们来看看那些游戏公司是怎么解决这个问题的。哦，需要说明的是，这里的解决方案仅仅是一个 demo，只用来解释原理。真正的环境中，还要更复杂一点，不过也就是复杂一点点而已。</p>
<p><a href="http://www.mikespook.com/wp-content/uploads/2009/05/gamescripting-e7b1bbe59bbe-1.jpg"><img class="alignleft size-medium wp-image-334" title="类图" src="http://www.mikespook.com/wp-content/uploads/2009/05/gamescripting-e7b1bbe59bbe-1-300x211.jpg" alt="类图" width="300" height="211" /></a>游戏中实体类如图设计，为了简化期间，没有实体管理器，所以也没有集成自统一的父类。</p>
<p>Ninny 类也就是玩家，有保存当前所在房间的成员变量。Room 类也就是房间，保存有在当前房间的玩家列表。Door 是描述门的类，保存了这个门连接的两个房间的列表。</p>
<p>为了简单期间，demo 中使用了 java 内置的 javascript 作为脚本语言，详情看<a href="http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/" target="_blank">这里</a>。</p>
<p><span style="text-decoration: line-through;">本来还想多写一点的，急着出门。算了，反正有代码，大家看看先。有空回头写。重点在 js 目录下的那几个脚本 enterDoor.js，exitDoor.js，enterRoom.js，exitRoom.js。</span></p>
<p>好，继续！</p>
<p>为了用起来方便，稍稍封装了一下Scripting的代码。大家直接看代码，不说话：</p>
<pre lang="Java" line="1">
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class Script {

    private final static ScriptEngineManager factory = new ScriptEngineManager();
    private final static ScriptEngine engine = factory.getEngineByName("JavaScript");

    public void put(String key, Object value) {
        engine.put(key, value);
    }

    public Object get(String key) {
        return engine.get(key);
    }

    public Object eval(String fileName) throws ScriptException {
        try {
            return engine.eval(new FileReader(System.getProperty("user.dir", ".") + "/js/" + fileName + ".js"));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Script.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}
</pre>
<p>从类图上可以看到，Door 和 Room 都实现了 Enterable 接口，两个方法：一个入 enter，一个出 exit。显然，当进入一个 Room 的时候，必须从另外一个房间出来。为了保证这个一致性，所以限定玩家不能穿墙只能走门：</p>
<pre lang="Java" line="1">
        Room r1 = new Room("room-1");
        Room r2 = new Room("room-2");
        Door d12 = new Door();
        d12.add(r1);
        d12.add(r2);
        Ninny player = new Ninny("ninny", r1);
        d12.enter(player);
</pre>
<p>在不用脚本的情况下怎么写这个 Door 的 enter 方法呢？首先判断一下 ninny 是不是跟 Door 在同一个房间；获得 ninny 的当前房间后 exit；获得门另一端的房间 enter。就是这么简单。不过考虑到前面说说的那种种可能性，为了拓展让我们来看一看用脚本是如何处理的：</p>
<pre lang="Java" line="1">
// Door 的 enter 方法
    public boolean enter(Ninny ninny) {
        Room currentRoom = ninny.getOwner();
        // 玩家在当前门所在的房间
        if(!member.containsKey(currentRoom.getName())) {
            return false;
        }
        // 门另一侧的房间
        Room nextRoom = getAnotherRoom(currentRoom);

        try {
            Script script = new Script();
            script.put("room1", currentRoom);
            script.put("room2", nextRoom);
            script.put("door", this);
            script.put("player", ninny);
            script.eval("enterDoor");
            System.out.println("enterDoor: " + script.get("message"));
            return Boolean.valueOf(script.get("result").toString());
        } catch (ScriptException ex) {
            Logger.getLogger(Door.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }
</pre>
<p>这里实际上 enter 什么也没有做，只是传递了一些数据到 Script，然后执行了 enterDoor 这个脚本：</p>
<pre lang="JavaScript" line="1">
importClass(Packages.foobar.Room);
importClass(Packages.foobar.Ninny);
importClass(Packages.foobar.Door);

room1.exit(player);
room2.enter(player);

var message = player.getName() + " enter from the room " + room1.getName() +  " to the romm " + room2.getName() + "!";
var result = true;
</pre>
<p>脚本也很简单，只是让玩家推出当前房间，进入下一个房间。如果房间对进入的玩家有等级要求，则只需：</p>
<pre lang="JavaScript" line="1">
importClass(Packages.foobar.Room);
importClass(Packages.foobar.Ninny);
importClass(Packages.foobar.Door);

var result = false;
var message = '';
if(room2.needLevel >= player.level ) {
    room1.exit(player);
    room2.enter(player);

    message = player.getName() + " enter from the room " + room1.getName() +  " to the romm " + room2.getName() + "!";
    result = true;
} else {
    message = player.getName() + " can't enter the romm " + room2.getName() + "! Level" + room2.needLevel +" needed!";
}
</pre>
<p>在我的例子代码中并没有这部分代码，实际上 Room、Door、Ninny 这几个类都应该从一个父类 Entity 中继承。这个 Entity 有一个 Map<String, Object> 的成员变量。通过 setAttr(String, Object) 和 getAttr(String) 两个方法就可以分别设置和获取每个 Entity 的属性。这样就可以在脚本中自由的使用各种属性来实现各种功能。<br />
通过 Enterable（可进入），Pickable（可捡起），Attackable（可攻击）等接口，调用对应的脚本来完成真正的游戏逻辑。<br />
其实那神秘的游戏脚本化就是这么简单。本来还想画几个序列图说明一下脚本的调用，实在有些困了。<br />
准备洗洗睡觉。尚未补充完整的内容，全当大家进阶学习吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.mikespook.com/2009/05/%e5%9c%a8%e6%b8%b8%e6%88%8f%e4%b8%ad%e4%bd%bf%e7%94%a8%e8%84%9a%e6%9c%ac%e8%af%ad%e8%a8%80/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

