九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

新浪微博体验报告 -- 做一个懂设计的程序员

write by 九天雁翎(JTianLing) -- www.jtianling.com

新浪微博 -- 腾讯微博 -- 讨论新闻组 -- 代码库 -- 豆瓣


程序员最大的问题在于对美术的依赖。  -- 工作以来的最大感慨

看过老罗的海淀剧院演讲后,大家都应该知道该怎么办了......................

    新公司对产品的设计和交互(可以浓缩为用户体验4字)有近乎苛刻的要求,受其文化影响,最近超出程序员的范围,看了一些关于设计和交互的书籍,收获不少。由于以前一直使用腾讯微博,博客中很多人留言告诉我新浪微博更好,要我使用新浪的,因为腾讯微博中很多朋友在也没有多在意。直到前段时间碰到一个朋友,一句"在我认识的人里面,使用腾讯微博的人还真是不多",虽然说的很委婉,但是很明显的,我被严重的鄙视了。。。。。。于是乎,今天下午,开始尝试使用新浪微博,因此,在我的博客发布模板中,添加了右上角的新浪微博的链接
    但是,说新浪微博好的人很多很多,但是真的很好吗?好在哪里?以我的不专业的眼光,"体验了"(公司专用术语)一下新浪的微博,感觉产品做的其实有一些不足,导致,我的新浪微博几乎已经变成了我对新浪微博的抱怨专博,唉,我甚至愿意在这里总结一下,有疑议的,尽管大家提出来讨论。


新浪微博的iphone客户端体验和弄不好,就不多举例子了,在我抱怨其不好以后,大部分朋友推荐我使用第三方开发的weico客户端。但是即使都是eico这个国内著名的设计公司设计的,我感觉都不是很好,就算不说设计上的问题,bug也的确比较多,特别是刷新上的问题。这一点,ipad的新浪微博也同样有。

 

讲网页微博的设计吧,新浪是做网站出身的:(其实一下内容都是来自于我在新浪微博上发的抱怨的微博,所以可能有些过于口语化,见谅)
1.从页面风格的色彩搭配来说,新浪微博背景的颜色有些过淡(惨淡,惨淡的)右边框与背景颜色区分度太小,同时搜索的时候,因为右变宽也变白了,页面过白,因为这个改变,同时也导致搜索与平时的页面页面的风格不统一的问题。虽然设计在不同人的眼里会有不同的意见,但是我感觉大部分人会同意这一条。
2.网页刷新内容过慢,刷新几分钟,提示的新粉丝都还是看不到,这种情况,可以列入bug,的确应该改进。


3.关注一个细节,左边腾讯微博的页面用的是标签页的展示方式,而右边新浪微博是按钮,哪个更好自有公论,不过,在看过《Don't make me think》后,我倾向与标签页的方式,因为表达的含义更多。

4.名人堂,只有每个分类的关注已选,没有一个统一的关注已选,使用不是太方便?

 

更多的就不谈了,稍微总结一下吧:

新浪微博的优势自然是很明显的,起步早,又有其天生的媒体基因的优势,合理的借鉴了从新浪博客学来的名人效应。
但是,说到具体的产品,今天正好看同事在微博上谈到新浪的微博,他的意思也是新浪就是做内容的媒体公司,血液里面就没有做产品的基因。我是非常的同意啊,也许,这也解释了,为啥新浪的iphone微博客户端做的不是很好,同时,几大门户在网游行业赚的盆满钵盈的时候,新浪却一小块蛋糕都没有分到。怎么做优秀的产品,新浪尚需学习。也许这个问题,不是后面童鞋评论中说的没钱的问题,新浪很缺钱吗?我感觉是公司重点关注的东西在哪的问题。当然,我不是新浪的人,具体怎么样,也就只有公司内部的人才能知道了。

 

最后:
看一看几大门户的微博页面吧,几乎一模一样,(现在很多产品都这样)感叹现在创意和产品都被模仿的很严重,最后并不一定就是新来者会取得胜利,而是最关注产品的用户体验,最关注产品的每个细节的公司取得最后胜利。新浪的确有领先的优势,但是假如以此以为可以高枕无忧,那就错的太远了,路途尚远,新浪+U。

PS:原文因为来自刚开始使用新浪微博时在新浪微博上的抱怨,言辞有些过于激烈,被一些人认为是软文,在此表示抱歉,今天将一些较“过”的语言删除掉,使得文章更为公正一些。

阅读全文....

Lisp的给力特性(V.S. Python3) 第二篇

前一篇在这里。

Lisp特性列表

表处理(List Processing):

Lisp:

CL-USER> (defvar *nums* (list 0 1 2 3 4 5 6 7 8 9 10))

*NUMS*

CL-USER> (list (fourth *nums*) (nth 8 *nums*) (butlast *nums*))

(3 8 (0 1 2 3 4 5 6 7 8 9))

CL-USER> (remove-if-not #'evenp *nums*)

(0 2 4 6 8 10)

在Lisp中几乎什么都是list。。。。比在lua中什么都是table还要过分,因为不仅是数据,任何表达式,函数调用啥的也是表。。。。。不过用惯了[n]形式的我,还是对first,last,rest,butlast,nth n这种获取表中特定索引值的方式有些不适应。但是很明显有一点,Lisp对list的操作是不用循环就支持遍历的,看起来也许你会说不过就是语法糖而已,其实代表了更高层次的抽象,这就象C++中STL的for_each,transform一样,只是,他们的使用有多么不方便,我想每个用C++的人都能体会。

Python中虽然不是什么都是列表,但是Python对于list的处理还算是比较强大。

Python:

>>> l = list(range(11))

>>> l

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> lo = [l[3], l[7], [l[0:len(l)-1 ]]]

>>> lo

[3, 7, [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]]

>>> [x for x in l if not x % 2]

[0, 2, 4, 6, 8, 10]

lisp comprehensions(列表解析)这个强大的特性的存在,可以使得Python做类似Lisp的这种不使用外部循环的遍历与Lisp一样的方便。(特别提及,列表解析是用从与Lisp同族的函数语言Haskell学过来的)

其实,这里我想多说几句,能够直接(即不使用循环)来对一个序列进行操作是非常方便的,也是更高级的抽象,这样才符合我们应该表达Do what,而不是去表达How to do,而用循环就是表达How to do的低层次了。事实上,即使在C++中,我们也是更喜欢高级抽象的,这也完全不会有性能损失,比如前面提到的for_each,transform,其实还有min_element,max_element,都是典型的例子,但是,对比Lisp和Python,还是显得功能比较弱小。

Lisp:

CL-USER> (defvar *capital-cities* '((NZ . Wellington)

(AU . Canberra)

(CA . Ottawa)))

*CAPITAL-CITIES*

CL-USER> (cdr (assoc 'CA *capital-cities*))

OTTAWA

CL-USER> (mapcar #'car *capital-cities*)

(NZ AU CA)

关联列表,类似C++中的map,Python中的字典,我并没有感觉有多么强大。。。。。

Python:

>>> capital_cities = { "NZ" : "Wellington", "AU" : "Canberra", "CA" : "Ottawa"}

>>> capital_cities["CA"]

'Ottawa'

>>> [ x for x in capital_cities]

['NZ', 'AU', 'CA']

在Python中,遍历一个dict默认是遍历key,所以用列表解析可以很简单的处理。虽然,Python在这些方面完全不输于Lisp,但是得提到,Python之所以不输给Lisp...是因为它从Haskell学到了列表解析.......

 

 

匿名列表(Lambda lists):

CL-USER> (defun explode (string &optional (delimiter #/Space))

(let ((pos (position delimiter string)))

(if (null pos)

(list string)

(cons (subseq string 0 pos)

(explode (subseq string (1+ pos))

delimiter)))))

CL-USER> (explode "foo, bar, baz" #/,)

("foo" " bar" " baz")

CL-USER> (explode "foo, bar, baz")

("foo," "bar," "baz")

Lambda一般童鞋都不会太陌生,说的通俗点叫匿名函数,稍微现代点的语言都有,我以前还尝试过C++的Boost::Lambda

,但是这个特性不是指lambda,因为太普通了,看清楚了,是lambda list, 不过说的虽然玄乎,其实还是比较普通,相当于参数传递的时候是利用了list的特性,可以实现可选参数以及关键字参数而已(也叫Named arguments),默认参数也许对于C还算神奇,可选参数对于C++都很普通了,关键字参数是BS主动放弃的C++特性之一,(见《C++设计与演化》在Python中完全支持。有点意思的是,上面的例子用了递归来实现string的遍历。。。。实在是太花哨了。。。。。而这个例子对于Python来说实在太普通了,Python甚至自带此函数实现的功能,我都懒得实现了。。。。。。

Python:

>>> import string

>>> "foo, bar, baz".split(",")

['foo', ' bar', ' baz']

>>> "foo, bar, baz".split()

['foo,', 'bar,', 'baz']

 

 

第一类值符号:(First-class symbols)

Lisp:

CL-USER> (defvar *foo* 5)

*FOO*

CL-USER> (symbol-value '*foo*)

5

听多了第一类值函数,倒是很少听说第一类值符号,在Lisp中,符号可以作为变量的名字,也可以就是作为一个占位符,说白了就有点像不当字符串处理的字符串。Python中应该没有类似概念。。。。。上文说多了Python没有类似的东西的话,惹得一些Python粉丝怒了,这里对我浅薄的Python知识表示抱歉,谨以此娱乐,作为了解Lisp特性的一种消遣,不要太当真,语言真的算不上啥信仰,平台也不是啥信仰,神马都是浮云,用着开心就好。

第一类值包:(First-class packages)

CL-USER> (in-package :foo)

#<Package "FOO">

FOO> (package-name *package*)

"FOO"

FOO> (intern "ARBITRARY"

(make-package :foo2 :use '(:cl)))

FOO2::ARBITRARY

NIL

没有看懂有什么太强的作用,注意上面的代码在使用:foo包后的反应....命令行提示符都变了,同时也明白了,以前的CL-USER其实就是代表CL-USER包,在Lisp中包也就是类似命名空间的作用,虽然说C++的命名空间真的也就是个空间,但是JAVA,Python,Lua中的包已经非常好用了,不仅带命名空间,而且带独立分割的实体。此条不懂,不知道是Lisp的古老特性在现在太流行了,所以变得已经没有什么好奇怪的了,还是说,我太火星了?高手请指教。

特别的变量:(Special variables)

Lisp:

FOO> (with-open-file (file-stream #p "somefile"

:direction :output)

(let ((*standard-output* file-stream))

(print "This prints to the file, not stdout."))

(print "And this prints to stdout, not the file."))

"And this prints to stdout, not the file."

"And this prints to stdout, not the file."

话说Lisp的变量是动态语法范围的,也就是说你可以将变量的范围动态的从全局变为局部,反之亦然,这个有点反常规。但是就例子中的代码看,就像是重定向功能,是将标准输出定向到某个文件上,这个功能Python也有。

Python:

>>> import sys

>>> stdoutTemp = sys.stdout

>>> sys.stdout = open("somefile", "w+")

>>> print("This prints to the file from Python, not stdout.")

>>> sys.stdout.close()

控制转移:(Control transfer)

翻译过来有些不好懂,大概的意思就是执行语句从一个地方跳到另一个地方,类似从汇编时代就有的goto语句。

Lisp:

CL-USER> (block early

       (loop :for x :from 1 :to 5 :collect x :into xs

:finally (return-from early xs)))

(1 2 3 4 5)

CL-USER> (block early

        (loop :repeat 5 :do

       (loop :for x :from 1 :to 5 :collect x :into xs

:finally (return-from early xs))))

(1 2 3 4 5)

可能有些不好懂,其实此时的return-from就类似于goto,而block就像是C++的以:开头的标识。

Lisp:

CL-USER> (defun throw-range (a b)

       (loop :for x :from a :to b :collect x :into xs

:finally (throw :early xs)))

THROW-RANGE

CL-USER> (catch :early

(loop :repeat 5 :do

(throw-range 1 10)))

(1 2 3 4 5 6 7 8 9 10)

Catch/throw语法的跳转,有点像现代语言的异常了,事实也是如此,看看例子中的cath吧,对:early throw出一个xs值,然后后来每次都能catch住,这个有点意思,虽然有点诡异。。。。。。

也许是因为现代的语言已经足够的强大了,即使是从命令式语言繁衍过来,(命令式语言的本质是模拟和简化机器语言)也慢慢的具有了越来越高级的抽象,并且也学习了很多函数语言的东西,所以,其实,语言的选择并没有那么重要,(参考这里)根据具体的情况进行选择,什么熟悉,什么用的高兴就行。(当然,不是一个人高兴,是整个开发团队)

另外,这里还看到一本特别有意思的书《Python for Lisp Programmers》,其中较为详细的列举了Python和Lisp的相同及不同,可以看到Python其实与Lisp还是比较像的,其中也有一些关于现在语言的效率信息,可以作为参考,也算给看了上一篇文章并且对Lisp比Python快有疑义的人的一种回答。其实这个世界的人都很怪,用C/C++的人可以强调效率,因为我们高效嘛,用Python的人可以强调我们类伪码,开发效率高嘛,现在程序员的时间比机器的时间要贵重的多,于是C/C++的人又回答了,你用Python给我开发个操作系统试试,Python本身都是C语言编写的,类似的争吵不在少数。呵呵,但是,当效率和抽象都要更加强大的时候,那就更加有话语权了,就如Lisp的使用者宣称Lisp是世界上最好的语言一样.........其实~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  最后,感觉后来的Lisp特性就没有前面的那么特异和突出了,有些平淡,再加上晚餐喝了几两二锅头,所以现在也不多写了,待续吧。。。。

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Lisp的给力特性(V.S. Python3) -- 第一篇

BS,Gosling,Anders,Guido都要被打屁股?

以前就听说过Lisp被很多人认为是世界上最好的语言,但是它是那么的古老,这种言论很可能来自于不能进化到学习Ruby,Python的老古董,所以其实也没有太在意。

Lisp(这里特指Common lisp,下同)1978年被设计完成,是个多么古老的语言啊。。。。却总是不停的听到太多的Lisp的好话,总是感觉有些难以相信,BS说,"一个新设计的语言不比老的好,那新语言的设计者是要被打屁股的",假如Lisp还是世界上最好的语言,那么从BS自己,到James Gosling到Anders到Guido van Rossum 不都要被打屁股啊?

你说我能轻易相信吗?

如果我们把流行的编程语言,以这样的顺序排列:Java、Perl、Python、Ruby。你会发现,排在越后面的语言,越像Lisp。这个话真的可信吗?

正好碰到一篇妄图说服我的文章《Features of Common Lisp》,眼见为实,是骡子是马,跑起来看看^^本文以Features一文为线索,(标题翻译的不好,请童鞋们见谅)与Python作为对比,C++啥的就都不太好意思拿出来比了,因为总是可以以效率为理由搪塞过去,但是,有可能我还是顺带提及一下。但是,总的来说,这是在Lisp的领地上战斗,,C++,Python是很吃亏的,但是作为已经足够强大的语言,还能够自我革新,Python的最新版本(3.1.1,以下没有特指,均指此版本)能够怎么应付,我拭目以待。特别提及,相比Lisp实现,CPython的运行速度慢得惊人,甚至差5-10倍,对于一个牺牲速度来换取表达性的语言,要是比不过速度比自己快那么多的语言,实在有些说不过去,即使是在别人的地盘..........迎战的Lisp是lispbox-0.7版本中集成的common lisp。

 

Lisp特性列表

强大抽象的数学计算(Rich, exact arithmetic):

大数(Bignums):

不用当心溢出。

Lisp:
CL-USER>

(expt (expt (expt (expt 10 10) 10) 10) 10)


100000000000000000000000000000000000...

这个貌似还行,C++虽然是需要通过外部库来处理,(在新标准中有过关于大数的提案,我以前还翻译过一篇《(N1744)Big Integer Library Proposal for C++0x

》)

但是Python对大数的支持还是非常不错的,特别提及,在Python2.1时代,虽然有大数支持,但是你得自己指定,假如都是用普通整数,还是会溢出。在2.2以后版本中已默认会进行类型提升。(参考PEP237

)呵呵,这点挺符合越新的语言(在Python来看,也就是越新的版本越接近)也就是越接近Lisp的理论。

Python:
>>> ((((10 ** 10) ** 10) ** 10) ** 10)
1000000000000000000000000000000000000000000

.....

分数(Rational numbers):

保留成比例的形式。

Lisp:
CL-USER>
(+ 5/9 3/4)
47/36

这个很牛很牛。。。。我目前懂的语言,(C/C++,Lua,Python,Objc以后提及均表示此意)还没有哪个是这样计算分数的,给你的都是浮点数。

特别提及一点,在Python2.x时代,上面的整数运算会像C++中那样,直接变成整数(5/9也就是0),但是新版本中已经不那么扭曲了。
Python 2.6.4:
>>> 5 / 9

0

Python3:
>>> 5 / 9

0.5555555555555556

我很遗憾的表示,同样的,python3比2.X版本更加像lisp,但是还不是足够像。

复数(Complex numbers):

内建支持

Lisp:
CL-USER>
(* 2 (+ #c(10 5) 4))

#C(28 10)

这个也还算方便,虽然我平时好像也用不上,C++中得通过库处理。Python也内建支持。

Python:
>>> (((10 + 5j) + 4) * 2)

(28+10j)

相对来说,以近似伪码著称的Python表达还是更加清晰一些。

 

统一的引用(Generalized references):

Lisp:

CL-USER> (defvar *colours* (list 'red 'green 'blue))

*COLOURS*

CL-USER> (setf (first *colours*) 'yellow)

YELLOW

CL-USER> *colours*

(YELLOW GREEN BLUE)

CL-USER> (push 'red (rest *colours*))

(RED GREEN BLUE)

CL-USER> *colours*

(YELLOW RED GREEN BLUE)

Lisp的操作都是使用引用对列表进行操作,你可以改变这个列表,实际操作的是同一个列表,就像你使用了rest操作,并对其进行push,但是实际也还是会改变原来的colours,因为rest返回的也还是引用而不是临时变量,这个特性看起来很有意思,有些特殊,具体的使用范围我还不是太清除(因为毕竟没有lisp编写大型的程序)

比如:

Python:

>>> l

['red', 'yellow', 'green', 'blue']

>>> l ;

['red', 'yellow', 'green', 'blue']

>>> l = ["red", "green", "blue"]

>>> l[0] = "yellow"

>>> l

['yellow', 'green', 'blue']

>>> l[1:]

['green', 'blue']

>>> l2 = l[1:].insert(0, "red")

>>> l2

>>> l

['yellow', 'green', 'blue']

需要注意的是,l[1:].insert(0, "red")操作是不会返回['red','green','blue']的,这样你临时的变量都获取不到,同样的,用切片操作来模拟的话,不仅没有返回值,原列表更不会改变,因为切片后的是临时变量,而不是引用。

多重值(Multiple values):

Lisp:

CL-USER> (floor pi)

3

0.14159265358979312D0

有简单的内建语法支持多个值,因此能方便的让函数返回多个值。此点C++就靠其他手段吧,比如异常ugly的用传指针/引用,然后再通过指针/引用传出去,虽然用了这么多年的C++了,这种方式也习惯了,但是一比较,就知道那不过是个因为语言的抽象能力太弱,使用的walk round的办法而已。 Python还是可以的。

虽然,Python的floor不返回两个值。
Python:

>>> import math

>>> math.floor(math.pi)

3

但是,你的确是可以返回多个值。
Python:

>>> def myFloor(x):

return math.floor(x), x - math.floor(x)

>>> myFloor(math.pi)

(3, 0.14159265358979312)

但是,需要特别注意的是,这只是个假象......因为实际上是相当于将返回值作为一个tuple返回了。
Lisp:

CL-USER> (+ (floor pi) 2)

5

在计算时,让第一个多重值的第一个变量作为计算的变量,所以非常方便。

因为Python的返回值如上面所言,其实是用tuple模拟多个返回值的,不要奢望了。


Python:

>>> myFloor(math.pi) + 1

Traceback (most recent call last):

File "<pyshell#58>", line 1, in <module>

myFloor(math.pi) + 1

TypeError: can only concatenate tuple (not "int") to tuple


不过,lua倒是可以,可能lua还是从lisp那吸收了很多东西吧:


Lua(5.1.2以下同):

> math.floor(math.pi)

> print(math.floor(math.pi))

3

> function myFloor(x)

>> return math.floor(x), x - math.floor(x)

>> end

> print(myFloor(math.pi)+ 1)

4

而且在Lisp中可以很方便的使用多重值的第二个值。(通过multiple-value-bind)


Lisp:

CL-USER> (multiple-value-bind (integral fractional)

         (floor pi)

(+ integral fractional))

3.141592653589793D0


Python因为返回的是tuple,指定使用其他值倒是很方便,包括第一个(虽然不是默认使用第一个)


Python:

>>> myFloor(math.pi)

(3, 0.14159265358979312)

>>> myFloor(math.pi)[0] + myFloor(math.pi)[1]

3.141592653589793


最后,即使这样Lisp在表达方面还是有优势的,因为在lisp中floor只计算了一次,而python的表达方式得多次计算,除非起用临时变量。

宏(Macros):

lisp的宏有点像其他语言中的函数,但是却是在编译期展开(这就有点想inline的函数了),但是在Lisp的fans那里,这被称作非常非常牛的syntactic abstraction

(语法抽象),同时用于支持元编程,并且认为可以很方便的创造自己的领域语言。目前我不知道到底与C++的宏(其实也是一样的编译期展开),还有比普通函数的优势在哪。(原谅我才学Lisp没有几天)

LOOP宏(The LOOP macro):

Lisp:CL-USER> (defvar *list*

(loop :for x := (random 1000)

:repeat 5

:collect x))

*LIST*

CL-USER> *list*

(441 860 581 120 675)

这个我有些不明白,难道在Lisp的那个年代,语言都是循环这个概念的。。。。导致,循环都是一个很牛的特性?

继续往下看就牛了

Lisp:

CL-USER> (loop :for elt :in *list*

        :when (oddp elt)

:maximizing elt)

675

when,max的类SQL寻找语法

Lisp:

CL-USER> (loop :for elt :in *list*

        :collect (log elt) :into logs

:finally

(return (loop :for l :in logs

:if (> l 5.0) :collect l :into ms

:else :collect l :into ns

:finally (return (values ms ns)))))

(6.089045 6.7569323 6.364751 6.514713)

(4.787492)

遍历并且进行分类。

我突然想到最近Anders在一次关于语言演化的演讲中,讲到关于声明式编程与DSL

例子,用的是最新加入.Net平台的LINQ:

他表示,语言是从:

Dictionary<string, Grouping> groups = new Dictionary<string, Grouping>();

foreach(Product p in products)

{
    if

(p.UnitPrice >= 20)

{
        if

(!groups.ContainsKey(p.CategoryName))

{
        Grouping r = new Grouping();

r.CategoryName = p.CategoryName;

r.ProductCount = 0;

groups[p.CategoryName] = r;

}

groups[p.CategoryName].ProductCount++;

}

}

List<Grouping> result = new List<Grouping>(groups.Values);

result.Sort(delegate(Grouping x, Grouping y)

{
    return

x.ProductCount > y.ProductCount ? -1 :

x.ProductCount < y.ProductCount ? 1 : 0;

}

);


这种形式到:

LINQ:

var

result = products

.Where(p => p.UnitPrice >= 20)

.GroupBy(p => p.CategoryName)

.OrderByDescending(g => g.Count())

.Select(g => new

{ CategoryName = g.Key, ProductCount = g.Count() });


看看与Lisp有多像吧(连名字都像^^)。。。。。(具体例子参见《编程语言的发展趋势及未来方向(2):声明式编程与DSL》)

而,Anders提到这个是在2010年。。。。。Lisp是1958年的语言。。。这个恐怖了。。。

突然,《为什么Lisp语言如此先进?》里面的话,“编程语言现在的发展,不过刚刚赶上1958年Lisp语言的水平。当前最新潮的编程语言,只是实现了他在1958年的设想而已。

”在耳边响起。。。。。。因为Anders的例子更有意思,Python那简单的while,for循环,我就不列举了。

Format函数(The FORMAT function):

Lisp:

CL-USER> (defun format-names (list)

  (format nil "~{~:(~a~)~#[.~; and ~:;, ~]~}" list))

FORMAT-NAMES

CL-USER>  (format-names '(doc grumpy happy sleepy bashful sneezy dopey))

"Doc, Grumpy, Happy, Sleepy, Bashful, Sneezy and Dopey."

CL-USER> (format-names '(fry laurie))

"Fry and Laurie."

CL-USER> (format-names '(bluebeard))

"Bluebeard."


Format函数现在的语言基本都有,连C/C++语言都有,为啥值得一提?因为lisp的format更加强大。。。。。强大到可以自动遍历list(而lisp的最主要结构就是list)。上面的format可能感觉有些不好理解,其实主要是因为lisp中不用%而是用~,总体来说,不比C系语言的format更难理解,但是更加强大。最强大的地方就是~{~}组合带来的遍历功能。当然,最后一个单词带来的“and”,也是很震撼。。。这些都不是用一系列的循环加判断实现的,用的是一个format,强大到有点正则表达式的味道。

看python同样的例子,就能够理解了,要实现同样的功能,必须得加上外部的循环。

Python:

>>>import sys

>>>def format_name(list):

    if len(list) > 0:

sys.stdout.write(list[0])

if len(list) > 1:

for index in range(1, len(list) - 1):

sys.stdout.write(", %s" % list[index])

sys.stdout.write(" and %s" % list[-1])

>>> format_name(l)

a, b and c

>>> format_name(l[0])

a

>>> format_name(l[0:2])

a and b

哪个更加简单,一目了然,lisp的format函数的确强大,即使在python3上也无法有可以媲美的东西。(新的format模版也好不到哪里去)

Functional functions:(不知道怎么翻译)

函数是第一类值,可以被动态创建,作为参数传入,可以被return,这些特性相当强大。这也算是函数类语言最强大的特点之一了。(也许不需要之一吧)记得

Lisp:


CL-USER> (sort (list 4 2 3 1) #'<)

(1 2 3 4)


回顾一下C/C++中的函数指针,C++中的函数对象有多么麻烦,看到这里差不多要吐血了。(PS:在新的标准中可能会添加进原Boost中的Function库,还是不错的,以前写过一篇关于这个

的,这同样也印证越现代的语言是向越来越像Lisp的方向进化,只是在C++中,表现为通过库来弥补语言本身的不足

Python,lua的函数都是第一类值,(这里有个讲Python的FP编程的文章,非常不错)所以用起来一样的方便,只是python原生的sorted倒是不支持类似的操作:

sorted

(iterable
[

, key
]
[

, reverse
]

)

不过我们可以自己实现。


Python:(改自这里的实现

>>> def sort(L, f):

    if not L:

return []

return sort([x for x in L[1:] if f(x , L[0])], f) + L[0:1] + /

sort([x for x in L[1:] if not f(x, L[0])], f)

>>> def less(a, b):

    if a < b:

return True

else:

return False

>>> def greater(a, b):

if a > b:

return True

else:

return False

>>> l = [4, 2, 3, 1]

>>> sort(l, less)

[1, 2, 3, 4]

>>> sort(l, greater)

[4, 3, 2, 1]


总的来说,在这个函数编程的核心领域,Python还是不错的,看看上面的文章就知道,用Python也完全可以写出类似FP的代码,只是因为Python的OOP特性的存在,大部分人完全忽略了其FP特性而已。

为什么Lisp语言如此先进?》里面举了个例子:

我们需要写一个函数,它能够生成累加器,即这个函数接受一个参数n,然后返回另一个函数,后者接受参数i,然后返回n增加(increment)了i后的值。

此时

Lisp:

CL-USER> (defun foo(n)

       (lambda(i)(incf n i)))

FOO

Lisp的实现很简单,但是Python的如下实现:


Python:

  def foo (n):

    return lambda i:  n + i

文中说这样的语法是不合法的。。。。。。。于是乎,作者猜想“Python总有一天是会支持这样的语法的”,而事实上,经我测试,现在的Python已经完全支持此语法。。。。。(修正以前文章中的错误,原来的测试有误,
这点也印证了程序语言进化的方向。

但是,我还是感叹,即使学习了Python这样现代的语言,我的脑袋中也很少有关于函数的抽象,比如上面的例子中的那种,语言决定着你的思维的方式,决不是假话,我受了太多OO的教育,以至于失去了以其他方式思考程序的能力。

属于更为激进的一派,他认为,不良好支持函数抽象的语言根本不值的学习。(这里的函数抽象不是指有函数,而是指类似上面的形式。)现在的语言,从Ruby,Javascript,甚至Perl 5,都已经支持类似的函数编程语法,同时也感叹,能够进化的语言,你的学习总是不会白费的^^比如总是给我惊喜的Python。

未完,待续......................

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

Cocos2D for iPhone的Mac版本程序创建

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

最新的版本是cocos2d-iphone-0.99.5-rc1.tar.gz
,里面包含cocos2d-mac工程,直接运行,可以看到Mac下Cocos2D运行的效果,happy......

因为暂时还没有好用的模版程序(就像Cocos2D-iphone原来自带的install_template.sh一样),第一反应是相信Cocos2D的强大的开源社区,果然不让人失望.....《HowTo: Create a Cocos2d Mac Project
》一文,完美的描述了从零开始,怎么创建一个Cocos2D for Mac的工程。

然后,最绝的是,更好的解决方案出现了,在论坛的一个thread中
,直接提供了模版下载
.

我越来越依赖于一个这样强大的社区,虽然说自己摸索这些细节也没有太大的问题,也能学到东西,但是,将这些东西交由强大的社区去探索,而自己主力的研究自己真正关注的东西,这样的感觉实在是太好了.......我祝福这些为开源社区做出贡献的先行者........是他们使得本来预计长度与《HowTo: Create a Cocos2d Mac Project
》一样的本文,最后就只需要两个链接而已。

留图:



 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

程序员自虐式杀脑细胞计划


程序员自虐式杀脑细胞计划

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

    在家呆的时间有点久,比较随意,特别是作息,经常通宵看书学习,最近准备重出江湖(没那么夸张,就是再次工作而已......)于是在想调整作息从美国时间到中国时间,但是却比较不适应,感觉白天头晕,四肢乏力,困乏不堪,喝了咖啡都不给力,晚上四眼放光,躺在床上看最高深的天书也不催眠。。。。。。作息的调整看来不是一天两天的事情,常常强行调整结果无功而返。非常无奈,一筹莫展。

    然后,碰巧最近帮一个朋友做iphone的摄像头的相关项目,(类似大头贴,即时在视频信息上添加图像)因为apple没有开放类似的API实现功能,只能通过一种很扭曲的方式
来实现。可以说是类似hack,再加上后期在此之上的一些与Cocoa touch相关问题(我对Cocoa Touch的学习并不多),真是那个绞尽脑汁,过程中虽然没有七窍生烟,也是头脑供血长时间过大。完成后,一时有点高中做数学题目的感觉了,呵呵,那久违的感觉啊,突然想到那时候数学老师给我们讲的一个数学多好的故事,大意是某商业人士,虽然从事的工作与数学关系不是非常大,但是平时都以做数学题为乐,无聊的时候就做几道。。。。。。当时听着感觉非常不可思议,没事那么费脑子干什么啊,还嫌脑细胞死的不够多啊,但是当离开了高中以后,发现能够动用脑细胞的时间越来越少,即时是毕业后,用了几乎所有的业余时间来学习,那也主要是一种知识积累型的学习,说实话,还真的不是那么太需要脑力。除了搞了一段时间反汇编,还有点意思,甚至,很多时候,我都是研究怎么更加节省时间脑力,考虑更加快速的完成任务,比如学习更高效的语言(比如简单的事情不用C++而用Python),比如用更方便的UI库(比如抛弃MFC使用Qt),以至于到现在,有种脑细胞旧的不去,新的怎么来的感叹,(我是认为脑袋越用越活的)难怪世上有那么多杀脑细胞的游戏了。这些游戏虽然也是我平时的最爱,但是最近其实玩的也是越来越少了。

    再于是乎,有了个杀脑细胞的计划。。。。。。。。简单的说,弄点难点的,需要费脑力的事情来折腾一下,好像很久没有这样折腾过了。。。。。。但是鉴于生活还是得继续,暂时把一些计划定在计划内,也就是平时都可以学习的东西,然后,计划外的,无功利的,我就每天拨两个小时吧。。。。。不干扰正常生活,同时也不让生活过于无聊^^曾经有人说我的想法有些自虐。。。。。暂时这里借用一下。。。。。。

唯一的计划内计划:

    图形方面的学习。

    从某天拿起一本书OpenGL ES 2.0的书,然后发现看的一头雾水,根本不知道那些光照shader的算法是从哪里冒出来的那天开始,就决心搞定这个领域,然后从很多人推荐的 《Real-Time Rendering》,再到真正所谓的名字就叫图形学的书《计算机图形学》(Computer Graphics with OpenGL Third Edition),知道这个领域还真是杀脑细胞啊。。。。特别是一些算法,要是书上描述的还不是那么好的话,你会看的一头雾水。(比如这里
)而且,这个领域实在太宽广,太深了,学习是没有止境的。。。。。正合我意。。。。。。无聊的时候,看看D3D这边的东西其实也行吧(虽然工作暂时用不上)

一堆的计划外计划:

计划一:

    语言的学习。

    似乎一直一来都是因为工作需求(比如Lua,Obj-C),或者是学习更加高效,方便的语言(比如Python,Bash),或者纯粹处于兴趣及简单学习的语言(比如Small Basic),而这些,都很少需要费脑子,真正可能稍微难点的东西,或者因为可能平时基本用不上的东西,并没有去学习,想想这样似乎有点过于功利,比如C++的模版元编程,比如Lisp,比如GO,按照以前看到的某篇文章说,作为程序员,要多学习新的语言。。。。。是吧,自从转到iphone上学习Obj-C后,已经有不短的时间了,这段时间都没有学习新的语言,那么,接下来,从C++模版元这个自认为C++最后的角落开始吧,(似乎在大学的时候JAVA就已经比C++应用更加广泛了,但是那时出于本能式的选择了明知道更复杂的C++,工作后,好像还是功利了很多)然后是Lisp,然后是Go。再然后,也许Java,Javascript或者C#都行吧。

计划二:

    算法的学习。

    当年学习算法都太过于功利,拿起书本就看看对自己有用的东西,到自己感觉差不多了,平时够用了也就停下来了,这样其实漏过了很多有趣的东西。。。。首先弄基本基础的看看,然后搞一本《算法导论》,再考虑是不是弄《计算机编程艺术》系列来看看。这方面估计可以杀掉挺久的时间。

计划三:

    数学的学习。

    这肯定是一个对我来说无止境的领域。。。。。。。到了这个地步,啥都不说了。。。。从基础到难的,拿本书玩命看贝。。。呵呵,也许一辈子也不用找其它的东西来杀时间了。。。。

其实,总的来说,该计划还是有一定功利性的,毕竟和我的工作多少有些关联,(即使实际工作上能用到的少的可怜)也许这样我才能更加愿意投入时间去弄吧,虽然我也对一些纯粹的杀脑细胞智力题有兴趣,但是暂时也就是看看,没有准备花太多时间研究。(真有这样的人的......比如这里

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

【转】游戏设计的秘密——翻译GDC2010 blizzard的一个演讲


游戏设计的秘密——翻译GDC2010 blizzard的一个演讲

转自:http://blog.sina.com.cn/s/blog_62c6329f0100hmia.html

 

  翻译了一篇blizzard在GDC2010上的演讲。感谢cyndi的校正和粽子的润色。有不对的地方,还请大家指出。


游戏设计的秘密

      


理念一:游戏性第一。有些公司把技术看得比游戏性还重要,这是毫无意义的。在暴雪,包括美术、策划、程序每个人都很关注游戏性,这并非策划的专利。——无论策划能想出了多么牛的方案,大家依然从中找出瑕疵。

      


让我们来看一个例子:在魔兽争霸3

中,我们有一个设定是这样的:只有暗夜精灵男性才能成为德鲁伊。但换到了魔兽世界中,假如依然只有暗夜精灵,依然只有男性才能成为德鲁伊,这太没意思了!所以,我们修改了这条设定。

 

      


理念二:易于上手,难于精通。我们把这条格言稍微地进行了修改:易于上手,几乎不可能精通。

游戏要做到极致才能让玩家为之疯狂,

从而投入大量的游戏时间。

      


这些年来,暴雪都很关注多人游戏。我无法明白许多开发人员愿意花费大量的精力在不到10

小时的单人游戏上,却只是在完工前草草地补上多人部分,这简直不可理喻!相对来说,
多人游戏有明显的优势和潜质能吸引玩家投入更长的游戏时间。

      


在星际争霸2

的开发过程中,整个研发组用了2

年时间制作多人模式后,才开始制作单人战役。值得一提的是:在多人部分制作完毕后,调整单人战役的平衡要容易得多。

 

      


理念三:带入感。游戏带给玩家的体验能完美地符合玩家期望——如吉他英雄一般演奏,像奎托斯一般弑神,这就是强大的带入感。

      


在这一点上,原版的星际争霸做得并不好,特别是在英雄单位上。按道理说,英雄应该是战场上非常强大的单位,甚至能左右战场的局势。但在游戏中,英雄往往是很脆弱的,特别是在超过50

个单位作战的时候就更加明显了。因此,玩家不再让他们的英雄加入战斗,而只是把它们藏在自己的基地之中。我们在魔兽争霸3

中解决了这个问题——
缩小了战斗规模,让有复生能力的传奇英雄们成为战场上真正的主宰者。正如我们设计的初衷一样,玩家们也乐于使用这些强大的英雄单位。

      


值得注意的是:我们应该让单位在

单独存在时,让它有一种无懈可击的感觉,但当众多单位同时存在的时候,却又有相生相克的关系,从而维持游戏的平衡性。其中有一种方法可以解决这个问题——加强单位与单位之间的差异:如果你提升了某些单位的攻击力,那么就该相应提升另外一些单位的防御力。

      


无论是在背景设定、游戏性还是故事本身的延续,让游戏中的事物有史诗般的感觉是百利而无一害的。以wow

中龙的模型为例,最初的模型只有玩家角色5

倍大小而已。我让研发做了一个GM

技能,可以使任意目标放大或是缩小10%

。有一次,在一个孤岛上,我对着一条龙施放“放大术”近20

次才满意的停手。至今为止,我都还没有对任何单位使用过“缩小术”。

      


过去发生的某些事,就当它发生在10000

年前好了,何必纠结于8

尺身高的人是否存在的问题?大胆的做就好了。有了这样的念头,我们的故事和设定就不会有太多约束。

 

      


理念四:少而精。我们要提炼游戏中必不可少的元素,最精彩的部分而不是放出一大堆复杂的功能和玩法。在这一点上,魔兽世界的交通系统做得并不好,而星际争霸的单位特色却非常成功。

      

      


理念五:要玩家去体验,而不要平铺直叙
。这个理念来自一个古老的谚语——要秀出来,不要说出来!游戏诉说故事的首要方法是让玩家去体验、去感受,而非文字、声音、动画。

      


在这一点上,我们曾经在暗黑2

中栽过跟头。游戏中的任务通常会毫无目的地给出大段描述,简单的说:去某某地方,干掉某某!相比而言,魔兽争霸3

的“灭绝!斯坦索姆”任务就做得很不错:整个村庄都被瘟疫所侵袭,不久村民将成为新的僵尸加入恐惧魔王的势力。而你必须带领部队,在他们变成僵尸之前,把村民们全部干掉!

 

      


理念六:激励。激励的作用往往要大于惩罚,但设计师们总是很容易先想到如何去惩罚玩家。在wow

beta

阶段,我们有一个设计:一旦玩家在线时间过长,他获得经验值的效率将降低——这通常让玩家很郁闷。

      


在无数的批评反馈中,我们修改了设计:以前玩家在线时间过长,只会获得50%

的经验值,现在我们让他获得100%

!而正常时间内,我们让他获得200%

的经验。与此同时,我们把人物升级经验加了1

倍——
玩家获得经验值的效率并没有改变,我们仅仅是在思维和表达方式上化贬为褒,就再也没人抱怨过了。

 

      


理念七:操作性。有时候,我们为了操作性得放弃一些听起来很酷的点子。最初,设计师们希望坐骑系统具有塞尔达风格——当召唤时,玩家的坐骑会从地平线那端
跑到玩家跟前。但最后,我们采用了现在的方式——“噗”的一声,玩家就召唤出了坐骑。这是因为我们发现,坐骑出现的时间、地点对玩家来说更重要。

 

      


理念八:调整。游戏调整是很经常的事情,但重要的是得想清楚:这些修改是为了哪些用户,以及为什么这么改。然而有些设计师,在还没想清楚这些问题之前,就盲目的修改,这是非常失败的。

 

      


理念九:切忌闭门造车。设计师们如果能在早期就跟大家分享、沟通自己的设计案,那么他能得到许多重要的反馈,从而做出改进。但如果一个设计师,闷头做了很久,就会很容易变得情绪化。最后,他向大家宣布设计的时候,更像在寻求别人的肯定而不是什么建议。

 

      


理念十:产品化。我们会在游戏之初就开始坚持游戏产品化工作,而不是等到游戏基本定型后才开始——在游戏还没有可玩版本的时候我们会做,甚至这个概念还只是出现在白板之上的时候,我们就已经开始产品化了。

      


我们有一个“突击队”,他们由公司其他项目的同事和一些外来人员组成。他们的任务是给我们游戏的提出反馈意见。他们像新鲜血液一样,总能带来新的东西。产品化

也起着至关重要的作用,暴雪是绝对不会在未完成产品化之前发售游戏的。

      

p.s.

:所有的这些理念都是为了做一款杰出的游戏而存在的,离开这个目标,它们都是毫无意义的。你们应该总结自己的设计理念,只有让游戏符合自己的价值观,才能体验到游戏设计的快乐。

 

comment:即使作为程序,我也觉的很有意思,理念一更加是非常认同。

阅读全文....

在iPhone游戏中Ogre的UI选择和Ogre的内置UI学习

关于UI的选择,看过一篇比较有意思并且全面的文章,但是里面谈论到的是只做网游时,而对于iPhone这种硬件限制远远多于PC的环境来说(特别是内存紧张),使用Ogre本身就是一种很奢侈的事情了,在UI部分消耗有很多内存,那就几乎没有办法去创建稍微复杂点的场景了,(我尝试过Ogre+Bullet+OgreBullet,在载入一个不复杂的场景,仅包含几十个Box的时候,我的touch 3代就会报内存警告,并且强制程序退出了)所以,在选择UI时,有更多不同的考虑,当然,那些在PC下都有效率问题的UI更加是不用考虑了。

    首先说需求,很明显做iPhone游戏,特别是比较简单的iPhone游戏,对UI的需求比做网游要求还是少的,对于我来说,按钮与文字的显示就已经能够包含90%以上的需求了,要是按钮还能够多点定制的灵活性,文字还能够显示中文,然后还有个进度条(用于loading)就完美了,没有其他更多的需求。唯一

知道需求后,就会发现,CEGUI

这个成熟并且有足够灵活性的UI是很多公司开发网游的最爱,但是还是太庞大,太复杂了一些。再加上这里有个人总结过iphone下UI的选择
,谈到了CEGUI在iPhone下的问题,所以不予考虑。(虽然这个我以前用过,可能上手会快一些)我认为在前面提及文章中作者最后选择的MyGUI可能都复杂了,加上在网上找了找相关的信息,发现iPhone下也会有一些问题
。并且,我发现官方版本的MyGUI
甚至没有MacOS版本。这个问题太严重了,我还是不想再做吃螃蟹的人了,我需要的东西是那么简单。。。。于是,我决定尝试Ogre的内置UI,(可惜文档太少)假如内置UI不能解决问题,再去考虑其他的UI。

Ogre内置的UI

说实话,Ogre其实没有内置的UI。。。。。。事实上,demo中那些控件的实现其实都是直接建立在demo工程中的,也就是说,这些UI根本就不是Ogre SDK的一部分,随时有可能被抛弃或者更改,也没有人保证这些代码的稳定性,(所以类的命名上,有Sdk这样特殊的前缀)但是,如上所述,实在是没有太多更好的选择,好在这些代码还是比较简洁(因为Ogre就没有想做一个庞大的UI系统,仅仅是想在demo中有个UI可以使用),就算直接拷贝使用,然后自己维护,都还是可以接受的方案。这些代码都在demo的SDKTrays.h文件中,都在OgreBites命名空间下。

另外,因为这些代码都不是Ogre本身SDK的一部分,(只是demo的一部分)所以,甚至连API文档
都没有,(只有overlay这一层的文档)更别说详细的说明文档了,没有文档,那么源代码就是最好的文档。。。。。。。。

使用

这里的使用以Ogre的1.7.2的SDK为起点,不涉及在Windows中的编译等问题。

Sdk相关的文件有两个,(在SDK的包中)分别在目录

OgreSDK_vc9_v1-7-2/Samples/Common/include

OgreSDK_vc9_v1-7-2/include/OGRE

中,但是因为考虑到此文件可能需要自己修改,建议复制一份,改个名字,然后自己包含到工程中使用。

首先,创建你自己的SdkTrayManager类型的对象,我推荐弄成单件。在Ogre VC9 AppWizard
中的BaseApplication中已经有成员变量mTrayManager了,(standard application模板)并且恰当的初始化了,拿来直接用就可以。

具体控件的使用实在不能再简单了。。。。。

比如label:

mTrayMgr->createLabel(TL_TOPLEFT, "HelloWorldLabel", "HelloWorld");

第一个参数是位置,第二个参数是label对象的名字,第三个参数是label显示的文字。

比如按钮:

创建:

mTrayMgr->createButton(TL_TOPLEFT, "Quit", "Quit");

第一个参数是位置,第二个参数是按钮对象的名字,第三个参数是按钮上显示的文字。

查询状态:(在你的循环逻辑中调用啊,比如frameRenderingQueued)

Button* bt = (Button*)mTrayMgr->getWidget("Quit");

if (bt->getState() == BS_DOWN) {

// put your codes here

}

比如进度条:

创建:

mTrayMgr->createProgressBar(TL_TOPLEFT, "LoadingControl", "Loading", 200.0, 250.0);

最后两个参数一个表示进度条的长度,一个表示注释文字的长度,会在caption的后面建立一个让文字显示非常浅的框。。。。。但是,又没有一个comment文字参数的直接传入,所以你只能靠caption文字自己来设想了。。。。这个设计倒是可以理解,比如初始化读取文件的时候,前面显示标题,后面显示正在读取的文件,但是就是为啥没有一个comment文字参数呢?不理解。而且还去不掉。。。。。

设置进度:(半分比,以0到1的浮点数来表示)

ProgressBar* pb = mTrayMgr->createProgressBar(TL_TOPLEFT, "LoadingControl", "Loading", 200.0, 250.0);

pb->setProgress(.5); // 50%

控件的使用是比较简单的,但是有几个需要注意的地方:

1.假如需要鼠标指针的话(iphone自然不需要了):

mTrayMgr->showCursor();

2.通过上面的方式控件创建后,会发现caption显示不出来,因为字体还没有载入,需要下列代码:

Ogre::FontManager::getSingleton().getByName("SdkTrays/Caption")->load();

依次创建上述三个控件的效果:

点击I Will Quit按钮,可以正常退出。

有些意思的是同一个位置的控件会自动排列,这点就像qt等UI中的layout,这个非常方便,因为不需要手动指定绝对坐标位置。(比起古老的MFC而言)但是,好像这个layout(先这么称呼吧)只能垂直排列,不能设定为水平排列。

待解决问题:

1.label在文字较长的时候是不会自动扩展大小的(按钮会)。

2.这些UI还是太简单了,起码的自定义图片按钮都不能做到,别说啥主题切换(换肤)功能了,要是真用这些原始UI做一个游戏,会被别人当作Ogre的demo处理的。。。。-_-!即使通过直接替换这些控件的图片,游戏中一个控件也只能有一个样子了,可能会稍微单调一点。。。。。

3.ProgressBar不知道怎么才能方便的使用,现在的设计实在有问题的太离谱了。

还好,有了个简单的基础了,这些功能就慢慢自己加吧,说不定哪天提供给有同样需求的兄弟下载。

另外,loading进度条我看到sdkManager里面有个showLoadingBar函数,还没有试用,好用的话,也许可以直接用。

实现

Ogre对UI的支持在Overlay这一层(有个OverlayManager),demo的UI就是从这里开始的。
事件的响应全部通过下列listener的回调,你也能看出大概此套UI关注哪些事件。

/*
=============================================================================

    | Listener class for responding to tray events.

    =============================================================================
*/

class
SdkTrayListener

{

public
:

virtual
~SdkTrayListener() {}

virtual
void
buttonHit(Button* button) {}

virtual
void
itemSelected(SelectMenu* menu) {}

virtual
void
labelHit(Label* label) {}

virtual
void
sliderMoved(Slider* slider) {}

virtual
void
checkBoxToggled(CheckBox* box) {}

virtual
void
okDialogClosed(const
Ogre::DisplayString& message) {}

virtual
void
yesNoDialogClosed(const
Ogre::DisplayString& question, bool
yesHit) {}

};

SdkTrayManager
是从此listener继承来的:

/*
=============================================================================

    | Main class to manage a cursor, backdrop, trays and widgets.

    =============================================================================
*/

class
SdkTrayManager : public
SdkTrayListener, public
Ogre::ResourceGroupListener

虽然SdkTrayManager的主要功能可能与OverlayManager类似,但是因为OverlayManager是个单件,(所以实际也就决定了不方便继承使用)所以,实际每次用到的时候直接获取此类的对象然后使用即可。

事件的响应部分,和CEGUI等UI类似,利用了一个injectXX接口,这里就不多说了。

然后,所有的控件有个基类:Widget

此类中有3个成员变量:

Ogre::OverlayElement* mElement;

TrayLocation mTrayLoc;

SdkTrayListener* mListener;

OverlayElement自然是与OverlayManager配合使用的,TrayLocation用于表示位置,Listener就是前面那个,这里也看出一些问题,即使是个按钮,也会有个这样庞大的listener。。。。这个可能与C++的没有方便的回调或者协议使用方式有关,假如Ogre愿意为此使用信号或者消息模式,就不需要这样,不然的话,为每个类型的控件建立单独的listener类是最好的办法,Ogre的这个UI又仅仅是设计给demo用的,不想弄那么复杂,所以就用了这种非常丑陋的用法。于是,你甚至可以在一个按钮控件中等待按钮根本不会响应的事件。

具体的控件,以按钮为例:

class Button : public Widget

按钮继承自Widget,有下列成员变量:

ButtonState mState;

Ogre::BorderPanelOverlayElement* mBP;

Ogre::TextAreaOverlayElement* mTextArea;

bool mFitToContents;

mState自然表示按钮的状态:

enum ButtonState   // enumerator values for button states

{

BS_UP,

BS_OVER,

BS_DOWN

};

BorderPanelOverlayElement提供了一个有边框的Overlay元素。(因为Widget已经包含了一个OverlayElement,可以看出此UI的设计上还是有些问题,可能毕竟是仅仅设计给Demo用的东西吧)

TextAreaOverlayElement提供一个文字Overlay元素。

按钮就是一个有边框的,可选显示文字,能够反映点击状态的控件,上述成员变量可以完全的诠释按钮。。。。。。

mFitToContents变量就是使得按钮可以自动扩展适应文字宽度的值,当手动设定按钮宽度时为false,不然就是true。label中就是没有这样的配置,然后就不能自适应。

实现上,在setCaption中会有

if (mFitToContents) mElement->setWidth(getCaptionWidth(caption, mTextArea) + mElement->getHeight() - 12);

这个操作。label没有。

稍微浏览了一下源码,基本上没有太多内容。。。。。。。

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

从源码编译新版OGRE 1.7.2 [Cthugha] for iphone/ipad

新版本的Ogre(1.7.2)彻底解决了前面版本关于iOS4的一些问题,但是用SDK编译release版本可以做到WOB,但是假如需要debug版本的Ogre的话,还是得自己编译,用CMake 2.8-3版本,添加OGRE_BUILD_PLATFORM_IPHONE的bool变量,然后勾选ogles,去掉ogl,配置一下freetype库(这最隐蔽,不然会得到一些链接错误),生成后在生成的工程目录运行> ../SDK/iPhone/fix_linker_paths.sh,还特别注意一下需要使用SDK4.1版本编译就行了(老版本似乎会缺少一些新的OGLES扩展)基本上还是比较简单。只是我尝试在ipad上运行时,速度还勉强,但是触摸响应极慢。。。。。。。。难道这就是传说中的display link的触摸响应延迟问题?(Ogre 1.7.2的what't news中提到了一点)

ipad下的截图:

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

直线的光栅化算法


直线的光栅化算法

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

看"参考1"碰到的第一个算法,有点意思,总结一下,特别是因为"参考1"没有找到随书的源码下载(Pearson的源码都要教师申请才能下载),自己写点源码,也算是桩乐事。

因为在我们画图的时候,都是以vertex为基础的,所以算法都是以直线(实际是数学中的线段)的起点(x0,y0)加终点(xEnd,yEnd)作为输入来绘制直线的。

 

基础知识

    因为这是第一次提到图形学的算法,这里弄一些预备知识,Glut,OpenGL啥的我倒是不多说了,只是最近我习惯了Mac,所以源代码工程都是XCode的,并且OpenGL包含的目录可能与Win32下不同,这点需要特别注意。

    另外,很明显的,OpenGL本身就可以直接绘制直线了。。。。。。所以这里自然不能利用OpenGL的相关功能,不然也没有啥好学的了,更加牵涉不到所谓的"算法",直接看"参考4"即可。

    所以,这里只使用一个功能,那就是setPixel函数,此函数也利用OpenGL完成,特别的,为了适应OpenGL ES,我没有如一般的书籍,使用glBegin,glEnd API。

setPixel函数实现如下:

void setPixel(int x, int y) {

  GLint vertices[] = {x, y};

  glVertexPointer(2, GL_INT, 0, vertices);

  glDrawArrays(GL_POINTS, 0, 1);

}

另外,关于绘制直线的环境,这里我为了做到真实显示像素与窗口中坐标点的一一对应,所以如下设置:

static int gw, gh;

void init2d(int w, int h) {

  gw = w;

  gh = h;

  /* attributes */

  glClearColor(1.0, 1.0, 1.0, 1.0); /* white background */

 

  glMatrixMode(GL_PROJECTION);

  glLoadIdentity();

  gluOrtho2D( - w / 2, w / 2, - h / 2, h / 2);

  glMatrixMode(GL_MODELVIEW);

 

  glEnableClientState(GL_VERTEX_ARRAY);

}

并且,还是维持OpenGL原点在中心的特点,同时也方便展示算法在4个象限中的不同。

具体的Glut main函数代码那就非常简单了,这里也贴一下:

int main (int argc, char *argv[]) {

  glutInit(&argc, argv);

  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

  glutInitWindowSize(640, 480);

  glutInitWindowPosition(200, 200);

  glutCreateWindow("draw lines");

  init2d(640, 480);

  glutDisplayFunc(display);

  glutMainLoop();

 

  return 0;

}

实际我们现在需要关心的也就是display回调函数了。(再次提醒,不明白OpenGL 或者 Glut的,请自行参考本文的"参考4")

 

利用直线方程

首先,我们知道输入了,

void drawLine(int x0, int y0, int xEnd, int yEnd);

最容易想到的就是直接利用高中学到的直线方程了,这里有2个已知点了,那么直线的点斜式方程就非常容易求出来了。

国外的教科书一般如下:

直线点斜式方程: y = m/times x + b

(式1)

斜率 m = /frac{(y_{End} - y_0)}{ (x_{End} - x_0)}

截距 b = y0 - m/times x0

将m,b带入式1,对于直线上任意一点,知道x坐标,那么

 y = (x - x_0) /times m + y_0

(式2)

当然,假如更加直观的用直线两点式方程来算y,会更加直接。

通过

直线两点式方程:/frac{(x - x_0)}{ (x_{End} - x_0)} = /frac{(y - y_0)}{(y_{End} - y_0)}

(式3)

可以直接求得式2.

可以得出初步的通过直线方程来画直线的算法:

1.计算斜率m

2.从x0开始,每次让x坐标递增1,求得直线的y坐标。

我们可以得到下面的代码:

void drawLineWithEquation(int x0, int y0, int xEnd, int yEnd) {  

  // slope, notice the difference between the true C/C++ code and the math equation

  float m = (float)(yEnd - y0) / (xEnd - x0);

// y = (x - x0) * m + y0;

  for (int x = x0; x <= xEnd; ++x) {

    int y = lroundf(( x - x0 ) * m + y0);

    setPixel(x, y);

  }

}

需要特别注意的是,对于强类型语言的变量计算时与数学公式的区别所在,都用整数计算那就会发生严重的精度丢失问题。

我们绘制如下直线:

  drawLineWithEquation(0, 0, 300, 50);

  drawLineWithEquation(0, 0, 300, 100);

  drawLineWithEquation(0, 0, 300, 150);

  drawLineWithEquation(0, 0, 300, 300);

  drawLineWithEquation(0, 0, 150, 300);

  drawLineWithEquation(0, 0, 100, 300);

  drawLineWithEquation(0, 0, 50, 300);

对于一些直线,我们已经可以看到漂亮的结果了。。。。。。。但是,很明显的,当X变化较小,而Y变化较大时,(也就是斜率m>1时)效果非常不好,显示出来的效果可以很明显的看到是一系列离散的点。

原因其实想象就比较容易理解了,因为我们递增的总是X坐标,只需要通过判断斜率,适时的递增Y坐标即可。

那么,就需要根据已知的Y坐标求直线上的点的X坐标了,通过前面的式3,可以直接求得:
x = /frac{(y - y_0)}{m} + x_0

(式4)

改进后的代码如下:

void drawLineWithEquation(int x0, int y0, int xEnd, int yEnd) {  

  // slope, notice the difference between the true C/C++ code and the math equation

  float m = (float)(yEnd - y0) / (xEnd - x0);

  float mr = 1 / m;

  if (m <= 1) {

    // when m <= 1: y = (x - x0) * m + y0

    for (int x = x0; x <= xEnd; ++x) {

      int y = lroundf(( x - x0 ) * m + y0);

      setPixel(x, y);

    }

  }

  else {

    // when m > 1: x = (y-y0) / m + x0

    for (int y = y0; y <= yEnd; ++y) {

      int x = lroundf(( y - y0 ) * mr + x0);

      setPixel(x, y);

    }

  }

}

这样效果就好了:


我们一直都用递增来完成直线方程,这里就还是有问题了,因为有可能直线斜率是<0的,(即起点高,终点低),那么我们还需要判断,并通过递减来解决问题。但是实际这样会使得代码进一步复杂化,我们预先通过x0与xEnd,y0与yEnd之间的比较,在的确xEnd,yEnd比x0,y0小的时候,对应的交换xEnd与x0, yEnd与y0即可。从逻辑上来看,因为绘制直线的时候没有方向的概念,那么一条x,y坐标递减的直线总是能看做反方向递增的直线。(相当于反过来画此直线)这样就能在不大量增加代码复杂度的时候确保,总是通过递增能够解决此问题。

但是,假如按上面的判断方式,将有很多种情况,分别是dx,dy与0的比较,以及m与0,1,-1的比较,也就是4*2=8个分支。代码会类似下面这样:

void drawLineWithEquation(int x0, int y0, int xEnd, int yEnd) {  

  // slope, notice the difference between the true C/C++ code and the math equation

  int dx = xEnd - x0;

  int dy = yEnd - y0;

  float m = (float)dy / dx;

  float mr = 1 / m;

  if (m <-1) {

    if (dy < 0) {

      swap(y0, yEnd);

      swap(x0, xEnd);

    }

    for (int y = y0; y <= yEnd; ++y) {     

      int x = lroundf(( y - y0 ) * mr + x0);

      setPixel(x, y);

    }

  }

  else if (m < 0) {

    if (dx < 0) {

      swap(x0, xEnd);

      swap(y0, yEnd);

    }

    for (int x = x0; x <= xEnd; ++x) {

      int y = lroundf(( x - x0 ) * m + y0);

      setPixel(x, y);

    }

  }

  else if (m <= 1) {

    if (dx < 0) {

      swap(x0, xEnd);

      swap(y0, yEnd);

    }

    // when m <= 1: y = (x - x0) * m + y0

    for (int x = x0; x <= xEnd; ++x) {

      int y = lroundf(( x - x0 ) * m + y0);

      setPixel(x, y);

    }

  }

  else {

    if (dy < 0) {

      swap(y0, yEnd);

      swap(x0, xEnd);

    }

    // when m > 1: x = (y-y0) / m + x0

    for (int y = y0; y <= yEnd; ++y) {

      int x = lroundf(( y - y0 ) * mr + x0);

      setPixel(x, y);

    }

  }

}

这样又太麻烦了。事实上,问题总是归结与两种情况,一种是X变化大,一种是Y变化大,因此可以仅进行dx,dy的大小判断以减少代码的分支,也就是判断fabs(dx)及fabs(dy)的大小关系。如此,可以得到较为简单的代码:

void drawLineWithEquation(int x0, int y0, int xEnd, int yEnd) {  

  // slope, notice the difference between the true C/C++ code and the math equation

  int dx = xEnd - x0;

  int dy = yEnd - y0;

  float m = (float)dy / dx;

  float mr = 1 / m;

  if ( abs(dx) >= abs(dy) ) {

    if (dx < 0) {

      swap(x0, xEnd);

      swap(y0, yEnd);

    }

    for (int x = x0; x <= xEnd; ++x) {

      int y = lroundf( ( x - x0 ) * m + y0);

      setPixel(x, y);

    }

  }

  else {

    if (dy < 0) {

      swap(y0, yEnd);

      swap(x0, xEnd);

    }

    // when m > 1: x = (y-y0) / m + x0

    for (int y = y0; y <= yEnd; ++y) {

      int x = lroundf(( y - y0 ) * mr + x0);

      setPixel(x, y);

    }

  }

}

作为完整性测试,这里以原点为圆心,生成一个圆,直线的另一个端点总是落在圆周上,以此可以检验4个象限的绘制。

圆的参数方程:
x = rcos/theta

y = rsin/theta

  (式5)

然后以15度递增(15度非常有意思,递增时会绘制较为特殊的45,90,180...等较为特殊的角度)以此式完成的测试代码如下:

 int r = 300;

 for (float theta = PI / 12; theta <= 2 * PI ; theta += PI / 12) {

    int x = r * cos(theta);

    int y = r * sin(theta);

    drawLineWithEquation(0, 0, x, y);

  }

最后绘制的效果如下,显示效果基本完美:

 

DDA算法(digital differential analyzer)

个人感觉从直线的方程绘制直线的方法想到DDA算法还算比较自然。

观察一下上面通过方程绘制直线的代码,有个地方会觉得明显还可以进一步优化,那就是swap函数的存在。因为在上面的算法种我们总是希望通过递增来解决问题,而事实上,递减又未尝不可,假如可以接受递减,那么就不需要swap的操作了,而同时,又不希望开新的分支来区分递增递减,那么很自然的可以想到,直接将x,y的递增递减量算出来,负的就递减,正的就递增,而我们只需要用+来处理,根本不用关心其符号,因为都正好符合要求。同时,因为x0与xEnd,y0与yEnd的大小判断此时已经不方便作为循环结束的标识(不然又得分支),所以同时提出循环次数,此时循环次数就等于max(fabs(dx),fabs(dy)),就能很自然的得到DDA算法。(这种描述是我自己从代码的优化角度推导出DDA算法的过程,需要更加严格数学推导及描述的,可以参考"参考1“的DDA算法部分。

通过这种思想,可以得出DDA算法的画线代码:

void drawLineWithDDA(int x0, int y0, int xEnd, int yEnd) {  

  // slope, notice the difference between the true C/C++ code and the math equation

  int dx = xEnd - x0;

  int dy = yEnd - y0;

  float x = x0;

  float y = y0;

  int steps = max(abs(dx), abs(dy));

  float xIncrement = float(dx) / steps;

  float yIncrement = float(dy) / steps;

    

  setPixel(x, y);

  for (int i = 0; i <= steps; ++i) {

    x += xIncrement;

    y += yIncrement;

    setPixel(lroundf(x), lroundf(y));

  }

}

效果不变,这里就不截图了。同时,可以看到,这个算法提炼了一些概念以后,是要比前一个算法效率高的,用每次绘制直线只需要一次的除法替代了循环中的乘法。

简单的说,该算法的效率提升来自从原来直线方程算法的连续计算到离散运算。

但是此算法用到了浮点运算,并且最后有浮点到整数的取整操作,这些操作使得算法效率还有改进的空间。

 

Bresenham算法

    Bresenham算法是由Bresenham 1962年发明,1965年发表,(见WIKI

,"参考3"认为发表的时间就是发明的时间,可能有误)鬼才知道,为啥在那个年代,还在用绘图仪画线时,Bresenham这种牛人是怎么想到这种扭曲,思维跳跃的算法的。突然想到某地方看到一个笑话,话说某个中学生特别讨厌牛顿,莱布尼茨等人。。。。。因为他们不弄出那么一堆奇怪的定理和算法,那么学习会容易的多。。。。呵呵

    Bresenham算法是现在硬件和软件光栅化的标准算法。(见"参考2"352面)相比DDA算法的好处在于完全避免了浮点运算,以及DDA算法因为有浮点运算而带来的取整运算,因此更加高效。

    不过算法的发明的思维简直是无敌了,pf啊pf......简单的说,该算法简单的说,将DDA的严谨的离散运算都简化成上一个点,下一个点的逻辑判断了。。。。牛啊

    该算法书中描述得很多,"参考1"描述的最多,有严格的数学推导,但是不好理解,因为太数学化,没有很好的描述偏移error的概念,"参考2"较为简略,而且有点奇怪的用i+1/2这样的像素点位置,单独看估计看不懂,"参考3"的思路是从纯逻辑的思路来看的,没有数学推导,所以不太严谨,但是并没有较为完美的描述,于是,我找到了作者原来的论文,进行参考。才发现,"参考1"的推导比作者原来表述的还要详细,原论文以证明为主,但是同时要更加严谨一些,比如在"参考1"中直接提出了dUpper和dLower的概念,并以此为根据来判断像素点,事实上"参考6"是先提出了到直线的距离,然后根据(dLower-dUpper)与先前的点到直线的距离的差符号一致,才开始转入对dUpper和dLower的使用,同时,"参考6"也完整的描述了所有区段的算法使用。

    另外,在查找资料的过程中,发现很多奇怪的现象,比如,为了简化Bresenham算法的推导,很多资料完全放弃了数学公式的推导,然后仅仅凭空的添加进0.5这个值,意思就是直线在k+1点过了下一个像素位置一半,那么就选择y+1点,不然就选择y点,如WIKI

,《The Bresenham Line-Drawing Algorithm

》,然后再煞有介事的提出进一步的优化方案,以消除这个浮点数。实际上,看过原论文以后,我发现这些资料简直就是误人子弟,因为在作者提出算法的时候就从来没有用过啥0.5这个值,而且作者在描述此算法的优点时,就明确的提到不需要浮点运算。也就是说,前面的那些误人子弟的推导,推导出来的东西,根本就不能称作Bresenham算法,但是偏偏广为流传。。。。。。。。。。。

    因为该算法的推导较为复杂,而"参考1"已经写的很详细了,(加上原论文)我也懒得在此重新写一遍了。没有此书的人已经不必要再继续看了,这里在此补充一些"参考1"没有提到的信息,已经解决一些"参考1"的问题。

1.推导的严谨程度来说,直接的初始条件是从点yk及yk+1到直线距离,哪个近,选择哪个点,因为此距离差与文中提及的dLower和dUpper差一致,所以才可以转为判断pk。

2.所谓重新安排等式(莫名其妙),其实就是在式(3.13)右边乘以/Delta{x}

,以形成Bresenham对决策参数的定义式。

3.你怎么样都不能在书中提及的条件下,将x0,y0,以及m=/frac{/Delta{y}}{/Delta{x}}

带入式(3.14)计算得到p_0 = 2/Delta{y}-/Delta{x}

,这里还需要用到原作者论文中提到过的假设,那就是x_0 = 0, y_0=0,b=0

,x_{end}=/Delta{x},y_{end}=/Delta{y}

,因为作者在之前有坐标系平移的一步,将新坐标系的原点移到(x0,y0)点,估计“参考1”的作者也不是完全理解算法,所以就马虎的忽悠一下企图过关了。

另外提醒大家,网络的资料有的时候还是谨慎着点看,还是较为权威的教材最靠谱,(虽然也不一定没有问题)学习算法时,第一次提出该算法的论文实在是最好的参考资料。因为"参考1"有上述我提到的3个问题,所以一开始我跟着学习总是很纳闷,结合了很多资料来看,又碰到一堆误人子弟的资料,还是原始资料最靠谱。。。。。。。别人可以理解这个算法,但是有人能够理解的比发明这个算法的人要透彻的吗?

参考:

1.计算机图形学, Computer Graphics with OpenGL, Third Edition, Donald Hearn, M.Pauline Baker著, 蔡世杰等译, 电子工业出版社

2.交互式计算机图形学--基于OpenGL的自顶向下方法(第五版),Interactive Computer Graphics -- A Top-Down Approach Using OpenGL, Fifth Edition 英文版 Edward Angel 著, 电子工业出版社

3.Windows游戏编程大师技巧(第二版), Tricks of the Windows Game Programming Gurus Second Edition, Andre Lamothe著, 沙鹰 译, 中国电力出版社

4.OpenGL 编程指南(第六版) OpenGL Programming Guide, OpenGL Archiecture Review Board, Dave Shreiner等著, 徐波 译, 机械工业出版社

5.Princeton University
开放课程信息

6.Algorithm for computer control of a digital plotter

,J. E. Bresenham的关于Bresenham算法的原始论文,IBM Systems Journal 1965

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....