Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

神杀核心架构设计 #212

Open
YanGuam opened this issue Jan 12, 2015 · 81 comments
Open

神杀核心架构设计 #212

YanGuam opened this issue Jan 12, 2015 · 81 comments

Comments

@YanGuam
Copy link
Member

YanGuam commented Jan 12, 2015

讨论基本原则:

  • 不发言表默认,有意见请提出修改意见。
  • 不要使用“我主观上认为不合理,但愿意服从组织”之类的评论,组织正在探讨合理性,没有明确定论。
  • 不以代码改动量为参考因素,因为设计的是最终的架构,不是一天内要把架构改成什么样
  • 架构尽量要让以后的维护者在不接触实现者本人的情况下能够维护,所以讨论时尽量以没写过神杀代码的身份来思考,不能以“我知道这里要用特殊写法,所以别人都知道”的思路来考虑架构
  • 设计标准是逻辑上合理、清晰,而不是功能强大,也不是“能用就行”(现状就是能用,保持现状的话从UI到AI都可以散伙,只需要一两个人改改技能就行,这显然不是大家的愿望)
@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

首先开始对设计目标的讨论,即首先统一理想中的神杀架构,这部分讨论的附加原则:

  • 不考虑具体的实现,包括难度 性能等各方面
  • 注意是理想中的,现有的架构是不存在的,从0开始。在设计之前脑洞大开比设计完之后脑洞大开时机上更合适。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

架构设计宗旨(草案)

  1. 本宗旨
    1. 本宗旨是大多数开发者对“你想要神杀未来变成什么样?”问题的回答

      Q:空谈性质的宗旨意义何在?
      A:宗旨是开发组奋斗的目标,我们允许由我们能力等因素而不能完全实现宗旨,但不允许今后的开发背离宗旨的精神。

    2. 宗旨明确了今后程序设计过程中的原则,宗旨地位在具体设计之上,宗旨的设立不受具体实现的影响。

      Q:明知道不可能实现,为什么要写入宗旨?
      A:因为这代表的是大部分开发者的期许,这是他们想实现的愿望。在2年前,你可能认为神杀不改代码而能作为应用在手机上打开是天方夜谭。你无法保证我们所依赖的平台不会进步,也无法保证你之后的开发者水平都比你差。

      Q:感觉这个不大可能实现吧?不赞成。
      A:只要你觉得这样比不这样好,那就赞成。比如3.ii.,如果你觉得“技能里需要额外的代码才能符合规则”比“技能里不需要额外的代码就能符合规则”好,你就可以不赞成。但你不可以因为觉得不可能实现而否定这条很可能是大多数人愿望的宗旨。另,你的能力不代表别的成员的能力,也不代表未来成员的能力,甚至有时不代表你真实的能力。

    3. Mogara成员中对本项目增加或删除代码在1000行以上者(记为A类成员)和战略上重点培养的Mogara第二梯队成员(记为B类成员)有权对本宗旨及之后的各项具体设计投票。A类成员每个投票记作2次,考虑到经验因素,B类成员每个投票记为1次。目前的B类成员有: @ainomelody @thiswangba @Xusine1131 @longaotianyisi @iDingDong @lwtmusou . 投票时除明确表明“我反对”和“我弃权”之外,均视为赞成。投票截止时间为投票发起后的72小时。宗旨的补充或修改应获得总票数(赞成+反对+弃权)3/4票数以上的同意,不符合的可以作为具体设计再次提出。具体设计的敲定需要非弃权票(赞成+反对)1/2以上同意。(为保证本宗旨能有序产生,本条宗旨以Mogara第一任组长的权力确认通过,在此之后本条宗旨视为本宗旨中普通的一条,对本条宗旨的修改须满足本条宗旨规定的条件)

  2. 名词定义
    1. 模块

      负责各自独特功能的代码块。比如负责服务器与客户端数据交换的网络模块,负责接收玩家命令并告知玩家游戏信息的UI模块。

    2. 三国杀模块

      保证神杀能模拟三国杀并且不承担其他模块工作的模块。其他模块从三国杀模块获知如何模拟三国杀。

    3. 内核

      三国杀模块中,负责支撑游戏规则的一部分,内核是对游戏规则的完全实现。
      具体表现为:内核之外的三国杀模块可以认为游戏规则已经存在而编程,不需要用多余的代码描述规则的流程。内核确保上一句话能够成立。

  3. 设计原则
    1. 各模块独立。并且各模块间有一个协议,规定模块A必须向模块B开放哪些命令。只要遵守这一协议,新编写的模块可以代替原来的对应模块。

    2. 程序逻辑对精通相关语言或类库者而言易于理解,不对以后的维护者造成可以避免的困惑,即大多数维护者会有“如果是我写我也会这样写”的认同感。

      例如,在设计万箭齐发时,对每个目标调用杀的效果(确实是有三国杀程序在这么用),但会给新人带来小小的困惑(虽然这种困惑很容易消除,但确实是可以避免的,理想状态是不应该有这种困惑的)。
      例如,旧据守继承据守,公有继承在C++里描述一种is-a的关系,在Qt元对象机制中,这种继承在运行时甚至是暴露给用户的,会造成可以避免的小困惑。

      Q:以上两例在代码复用上不是做的很好吗?
      A:是。但这种促进作用却会引发本可以避免的副作用。以上两例,确实是促进了代码复用,但这种代码复用的意义很小。函数可以更理想的解决代码复用,却不会描述两个类的关系。雷杀继承杀是合理的(思路:雷杀is-a杀),据守继承旧据守就显得很随意了(思路:据守is-a旧据守?哦,不是,程序员在这里是为了节约代码而这样做的,据守不是旧据守。)

      Q:为什么C++公有继承表明两者具有is-a关系?
      A:如果对C++公有继承建立了is-a关系有疑惑,那应该源于对C++继承理解上的偏差,C++的inheritance也是一种subtyping。附wikipedia:http://en.wikipedia.org/wiki/Is-a#C.2B.2B

      Subtyping is said to establish an is-a relationship between the subtype and some existing abstraction, either implicitly or explicitly, depending on language support.
      The following C++ code establishes an explicit inheritance relationship between classes B and A, where B is both a subclass and a subtype of A

      class A 
      { public:
      void DoSomethingALike() const {}
      };
      
      class B : public A 
      { public:
        void DoSomethingBLike() const {}
      };

      Q:既然NosJushou is-a Jushou with more cards to draw成立,那两者是不是存在is-a关系?
      A:是的。NosJushou和Jushou with more cards to draw存在is-a关系。

      Q:精通相关语言的大神怎么会看不懂我们的设计?
      A:精通相关语言不代表精通你的设计意图,并能按照你最初的意图维护程序;能看懂不代表不困惑。比如上例,在Jushou和NosJushou之间困惑的往往是大神(默认大神参与过结构严谨的项目),对这方面接触少的小白往往是不会察觉问题的。比如一堆乱糟糟的继承关系,大神会思索为何汽车要继承苹果,但小白会觉得这是这一定是一种他没见过但正确的写法。

      Q:我不想费劲让小白也能轻松看懂我的代码,所以让代码易于理解必要性何在?
      A:我也没有这种想法。我们尽量用常用的写法减少能合格运用相关技术者的不适应感。

      Q:我们的代码相比一些项目的代码已经很好理解了,为什么要提出这一宗旨?
      A:宗旨是我们共同的愿望,我们不希望以后的维护者让代码变得不好理解(在不考虑维护者水平等非主观因素的情况下),也希望在发现一处代码难以理解时,大家会尽力用容易理解的代码代替。

    3. 一些会影响游戏进程且在内核中被用到的状态参数应该用成员属性记录。

      例如,一名角色因为技能喝了两次酒,不应该增加两个名叫"drank"的mark,对于这种非常重要的状态参数,应设置为整型成员属性。

      Q:既然用mark能实现,而且能减少Player类里的代码量(一处属性声明,一处元对象宏,一处初始化,一对getter/setter),为什么不沿用?
      A:你也可以把Player里除tags/flags/marks之外的属性中,bool属性用flag,int属性用mark,其他类型用tag代替,这样就有了一个无比简洁的Player类。但问题是巨大的,在阅读Player类时,你不知道所有能影响它的状态参数,依赖字符串的状态获取和设置在维护上也是不方便的。

      Q:我在写Lua时,就算把"drank"写成了"drink",我一样可以把错误找出来。我认为这种修改不影响维护。
      A:我们在使用C++时,应充分利用它带来的便利,而不是使用另一种语言的特性。这种错误可以使用C++的特性避免。如果错误在编译时能够被指出,就没有必要在运行时找错误。Qt5的QObject::connect相对于Qt4的优势就在这里。

  4. 三国杀模块
    1. 内核不需要按照游戏规则编程,只需要保证在编写不是内核的部分时可以把内核看作已实现的规则。

      例如【奋命】,陈武&董袭死亡后,不能执行后续的弃牌效果,这属于游戏规则,所以应在内核中实现,技能中不需要体现这一点。

      Q:是不是这样的宗旨封杀了所有胆创技能?
      A:不是。规则可以通过某些途径被技能影响,但未声明这种影响的技能视为根据规则执行

      Q:这样实现有什么好处呢?
      A:内核负责维护规则,技能就不需要考虑规则是怎样的了。比如内核更换或修改后,技能仍可以符合预期的运行。比如在用户自定义的规则下,摸牌时需要失去一点体力,则修改内核对摸牌的实现,而不用改动每个摸牌技能。

      Q:我觉得身份局枭姬和国战中的枭姬是同一个技能,为什么要内核调整描述中的“一次”和“一张”?
      A:内核不负责调整“一次”或“一张”。按照技能描述有技能代码对应的关系,技能描述有变动,也应该调整技能代码,不是调整内核。如果在用户A自制的“武逆乾坤”模式中,枭姬的技能描述和仁德一致,就算可以认为这是同一个【枭姬】的多种形态,也应该在技能层面而不是内核层面区分。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

基本原则4,这条对于一个有经验的程序员来说,无论多麻烦的程序都没问题,对于没接触过编程的人来说,无论架构多简单,他们都认为在看天书。如果想让一个不懂c++的人上手就维护,无异于让他们一步登天。所以我认为无论特殊写法有还是没有,这条都是不可能的。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

而且现状很残酷,“看不懂”“没时间”的人越来越多,这种人还都想一步登天,简直扯蛋。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

如果汽车继承一个水果类(极端的例子),这种继承显然很难理解,我们要尽量规避这种很难理解的特殊写法。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

4的意思是符合常人的思维,或者让这种设计思路容易理解。没有 @Fsu0413 说的意思

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

容易理解……要想程序容易理解,除非全部推重,这种类似打补丁的修正方式无异于给已经搭好的房子砌砖

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

到最后砖头到处都是,房子变成迷宫了

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

王垠有一段自称很厉害的代码,只有40行。 可能真的很厉害,世界上也一定存在能理解的人,但是确实很难理解,所以也没有哪里用上这段代码。 我觉得我们的代码应该把好理解作为原则之一。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

所以我们从最基本的开始讨论我们设想的架构 现有框架全部不存在。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

不过推重的工作量你懂的。啦啦你可以想象一下,为什么我在公司做的软件,都是为新机型开发,但是都是在旧机型已有代码的基础上改?因为走迷宫所需要的时间和精力远远小于拆了房子重建。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

参考讨论基本原则的第三点。我们首先统一一下大家的目标,先有共同的目标,再针对现状做一些符合实际的调整。 不然A认为神杀应该窃取用户机密,B认为神杀有权利保护用户电脑的安全,他们也不知道对方的想法,然后A改的部分是为A的目的服务的,B改的部分是为B的目的服务的,到最后只会引发更大的改动量。 符合共同目标的话,工作量大的话就当是给以后的维护人员一个任务吧。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

神杀的代码是我见过的最容易理解的项目的代码了,我可以透露下我们公司的代码结构,那个代码……一层接着一层的函数调用和消息打包发送,各种异步接口同步接口,分析代码真的就是在走迷宫,分析的稍微有点偏差,绝对再也回不来,只能从头开始重新走。那个代码从00年的base开始就一直用了,一直在搭好的房子里砌砖,那是个真·迷宫,啦啦没进过公司肯定不知道这现象。事实上大多数商品里程序的代码,比神杀的代码难理解的多,作为神杀开发者竟然认为神杀代码难理解,我真的无法想象这样的事。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

最初的第一套base肯定要我们去写,推重的提出者是现在的mogara而不是未来的mogara。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

美国宪法的序言只有一句话:
We the people of the United States, in order to form a more perfect Union, establish justice, insure domestic tranquility, provide for the common defense, promote the general welfare, and secure the blessings of liberty to ourselves and our posterity, do ordain and establish this Constitution for the United States of America.
但这句话作为宗旨,明确了美国应该是怎样的国家。我们今天的设计,明确了神杀应该是怎样的程序。我们的宗旨之一是尽量让代码容易理解。具体设计的时候注意到这点基本就没问题了,相对我们良好的基础,不难做到。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

如果宗旨只是让代码容易理解的话,那么大家现在就可以散伙。我这句话完全不开玩笑,因为我感觉现在这个程度的代码,只要有点基础的人应该都能看懂。啦啦是真没进过软件公司不知道维护公司代码的难度,还是空想可以让没有基础的人一步登天呢??

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

我睡觉了……明天再说,可能我说的话有点极端……各位见谅

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

我是想把这一条纳入宗旨的,但不是宗旨的全部呀。如果大家对神杀应该怎样有了普遍的共识,如果一个设计违背了大多数人的愿望,就可以用很小的成本否定这个设计了。从基本的愿望到设计的具体细节,一步步来,就可以高效有条理的敲定架构从宏观到具体的实现方法;而现状是对架构的设计至今都没有完整的方案。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

而且宗旨另一个目的是为了避免今后违背宗旨。宗旨不是改革目标,不是改完就作废了。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

不管怎么说0基础一步登天是完全不可能的。如果把它当作宗旨,那么为了这个虚无的宗旨的努力又有什么意义呢,反正也实现不了

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

你从哪里推断出我说的代码易于理解是“0基础也可以看懂代码”的呢?

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

上面的回复啊……

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

哪一条?

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

“我睡觉了”的下一条

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

我是想把这一条纳入宗旨的,但不是宗旨的全部呀。如果大家对神杀应该怎样有了普遍的共识,如果一个设计违背了大多数人的愿望,就可以用很小的成本否定这个设计了。从基本的愿望到设计的具体细节,一步步来,就可以高效有条理的敲定架构从宏观到具体的实现方法;而现状是对架构的设计至今都没有完整的方案。

这一条里除了“这一条”三个字指“代码易于理解”,其他我都是在说我们应该先把愿望统一。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

不过现在我的感觉是这个愿望太虚无……

PS:睡不着了……

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 12, 2015

不行我必须睡了……明天还要上班

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

。。。睡吧 establish justice, insure domestic tranquility, provide for the common defense, promote the general welfare, and secure the blessings of liberty to ourselves and our posterity
树立正义,保障国内安宁,提供共同防务,促进公共福利,并使我们自己和后代得享自由的幸福
这也是很虚无的,但它说明我们应该树立正义,而不是树立邪恶。所以关于让代码容易理解,我们应该追求它容易理解,而不是追求代码更复杂。在树立追求阶段,我觉得我们的追求应该是让代码容易理解的,我们也允许我们的能力无法让代码绝对容易理解(或达到顶尖开源项目的水平)。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

即在一个复杂不易理解的设计和一个易于理解的设计面前,我们共同期望那个易于理解的设计。

现在我们只谈共同期望。先明确一些期望,各位奆神才能更好的设计程序。才能进一步打破现在想改,但一直又没有方案的局面。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 12, 2015

通宵对宗旨目前面临的各种疑问进行了补充,希望能更好理解。睡了。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

描述相同的技能,在不同模式下结算有一些差别,不需要在技能里额外的代码体现这种差别。这是内核的任务。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

如果把帷幕在新出的(可能是用户自定义模式)里改成不相干的效果,内核没有义务让相同的代码能在新模式正常工作。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

所以在内核层面看,相同的技能指名字和效果均一致的技能。内核的目的是实现相同的技能在不同规则下有应有的效果。

@BeginnerSlob
Copy link
Member

=。=啦啦说的对

@iDingDong
Copy link

我投赞成票

@thiswangba
Copy link

我投赞成票←_←感觉其实虽然讨论很激烈但是共识还是有的

@takashiro
Copy link
Member

刚下课肥来0.0~
反复看了很多遍,印象最深的片段应当是“难理解的神杀代码”。

一种代码的默契,“如果是我写我也会这样写”的认同感,大概就是宗旨的意图吧。
对以上宗旨没有异议。

@omnisreen
Copy link
Contributor

我是期望能达成开发新桌游的效果,这也就是独孤安河神所想的“管理资源,提供服务”的神杀。
神上也在群里提出了类似于minix的微内核,想来便是如此。
这个想法并不与rara的冲突,问题在于难度较高。。。
毕竟三国杀式微不可逆转,我的想法虽不是甩掉三国杀,但也没必要仅仅做其一根绳上的蚂蚱。
衷心祝愿mogara团队能走得更好更远。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

吸收各方意见,并解答了一些疑问的开发宗旨修改完成了,宏观上的设计思想是“微内核”,即3.i

现在可以就一些具体的模块提出构想,当然总的开发宗旨也可以继续补充。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

首先是三国杀模块中的内核。

内核的宗旨很明确了,即讨论在技能未知的情况下如何实现规则。
我正在运用一个较为成熟的面向对象的设计步骤组织大家设计程序,首先要明确的是要达到“低耦合,高内聚”的理想状态,就要强调模块间的封装,即“隐藏对象的属性和实现细节仅对外公开接口控制在程序中属性的读取和修改的访问级别”。
如果我们在设计三国杀模块,理想中如果别的模块已经封装的非常好的话,我们就不需要考虑另一个模块的实现(因为封装的好的话精通这一模块的开发者可以对另一模块完全不了解),而是直接发挥另一模块的功能就好。
在面向对象的软件开发过程中,大致有两种角色,架构师与普通程序员。架构师构建好骨架,交由各个程序员实现。骨架具体由什么组成呢?C++里可以是抽象基类。这个基类只告诉其他的基类它能做什么,比如基类A明确表示它的派生类都可以唱歌,那么实现基类B的程序员就可以不管实现A的程序员有没有实现或者怎么实现。同理AB互相用各自基类声明的方法时,不必考虑对方的进度。
我希望大家在设计每个模块以及更小的微观模块前能熟悉这一流程,这样能保证我们专心设计一个模块,而不是内核还没设计完就先把别的模块的一部分设计完了。
至于觉得啦啦年纪轻轻,也没有工作经验,她主持的流程一定不科学的猜测确实是有道理。但我的父母以及我家族中大部分人都从事软件行业,在耳濡目染下,我自信我在设计方面比写代码强,也希望大家给我一个机会,积极配合。如果今后我非常肯定的设计方法和基本原则和你多年的世界观不一致,也请相信我一次,我也会尽量拿出一些案例或者找出一些出处减轻你的顾虑。

以下继续内核设计,如何在角色、技能、游戏牌等内核外因素未知的情况下实现内核。为什么要先敲定内核呢?因为各模块之间的关系往往是网状的,唯有三国杀模块与各模块关系是星状的,而三国杀模块中,内核之外的部分是在规则环境下编写的,所以与各职能模块产生联系的只是内核。敲定了内核设计之后,各模块必须实现哪些接口函数就一目了然了。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 13, 2015

这是我为了敲定设计而第二次通宵,希望有想法的小朋友都能积极提出意见。毕竟我也有不熟悉的模块,这些部分我就算是一直不休息也没有能力主持相关的具体设计。

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 14, 2015

@takashiro 其实我不知道这个所谓“难理解的神杀代码”是什么,况且如果连我都认为(仅限于我维护的部分的)神杀代码难理解的话,我也就不用在软件公司呆着了。
当然我不否认简化代码的意义,不过我认为在现在这个条件下,咱们这个房子虽然有点砖瓦补丁,但是也绝对没达到迷宫那个程度,那么拆了房子重新建的工作量,和走迷宫的工作量相比,哪个更大?

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

@Fsu0413 那我们先以high cohesion and low coupling的原则重新设计一下程序,完成后比对一下这种结构和现有结构的差别再说,OK?而且有一点很明确,神杀是要继续3年5年10年的,没有新的架构的话,我们以后要始终对2010年就定型的架构添砖加瓦。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

继续内核。
既然内核负责三国杀模块与别的模块的全部联系,根据模块可替换原则,模块也要建立在一定的协议之上。既然我们在设计模块的时候,还不知道其他模块对游戏模块的需求,所以也先不管具体的协议了。
既然确实是要有协议,那我们就先创建MAbstractLogic(具体的,内核是为了构建一款游戏的逻辑)抽象基类。C++里用抽象基类作为程序内部的协议再合适不过了。然后创建一个继承它的SanguoshaLogic类,我们在这个类下完善内核。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

补充一下,我们的设想中三国杀模块是可拆卸的,并且架构成熟后,这个模块是有被拆卸的预期的,所以我们三国杀模块之内的部分不以"M"开头。同理,因为可拆卸,所以我们是可以作为第三方库被使用的,这时候就要避免和开发者本身设计的类重名,所以类统一用“M”开头。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

根据三国杀模块可更换的预期,我们要根据第三方开发者水平等因素,规定低水平开发者不得调用一些危险的命令,所以在协议上有记录权限等级的必要(根据封装设计的原则,这里不讨论具体实现)。
那么

  • MAbstractLogic的协议
    • 暴露自己具有的权限等级,供其他模块查询

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

根据面向对象的五个基本原则(SOLID)之首SRP:Single Responsibility Principle,在三国杀模块中的角色只需具有三国杀规则概念中角色的属性就够了,其他如何通信什么的问题都去死吧。这样就C/S结构中三国杀模块的角色分布就清晰了:Server和Client都会有三国杀概念中的角色(SanguoshaPlayer),这个角色的职能很小,只是记录游戏状态,所以无论是Server和Client都会用到它的全部状态,但是通信由网络模块承担。不仅是三国杀有角色呀,几乎每一款游戏都有角色,所以在内核中,要实现这个抽象基类及其协议:

  • MAbstactPlayer
    • 向MAbstractLogic暴露hp

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

具体到继承MAbstractPlayer的SanguoshaPlayer

  • SanguoshaPlayer
    • 向SanguoshaLogic暴露hp
    • 向SanguoshaLogic暴露别的三国杀角色特有属性

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

为了保证各角色信息在C/S之间传输通畅,在内核内部要有通信协议(这份协议不负责玩家在线还是离线、用户名之类杀无关的传统Player属性的传输)。这份协议是封装在内部的,网络模块不知道具体情况,所以需要内核主动调用网络模块的API。

  • MAbstractLogicPacket(内核)
    • 能把内核的给出的一堆数据打包成对象
  • MAbstractNetworkSender(网络模块)
    • 能发送一个MAbstractLogicPacket

经历一个封包过程的好处是,两模块相对独立。网络模块能发送MAbstractLogicPacket,但其构成只有内核自己知道,也就是说无论内核怎么实现,网络模块都能在不知道派生类实现的情况下发送出去,同样是我们“高内聚,低耦合”的思路。
网络模块从一端发送到另一端,就需要扮演另一种Responsibility

  • MAbstractNetworkReceiver(网络模块)
    • 能接收一个MAbstractLogicPacket
  • MAbstractLogic协议
    • 暴露自己具有的权限等级,供其他模块查询
    • 要给网络模块提供一个把收到的数据包交付处理的通道
  • SanguoshaLogic
    • ...(这个表示基类的要求实现的职责,不重复列举了,下同)
    • 解析SanguoshaLogicPacket(根据名字是指符合MAbstractLogicPacket协议的派生类,之后新出现的类可以根据名字看)
  • SanguoshaPlayer
    • ...
    • 向SanguoshaLogic暴露别的三国杀角色特有属性(有修改权限)

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

qq 20150114123630

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

虽然用了UML绘图工具,但不完全是类图,所以线条和模块就很随意,不代表具体的关系,根据文字设计有具体含义。

上图中,有几个关键点很清晰。

  1. 网络模块能够传输符合协议的数据包
  2. 内核有途径让网络模块帮忙传输数据包
  3. 网络模块有途径把接收到的数据包给内核
  4. 只有内核有能力封装包
  5. 只有内核有能力解析包

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

内核和网络模块的协议基本靠上述关键点中的前三个建立了(先不考虑权限等级,以后可以在抽象包基类上再分各种等级的包)。

内核与UI的关系就有些复杂了,我们首先要理顺这个关系才能定制合适的协议,从而用抽象基类搭建架构。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

UI 与内核关系相当密切,而且理想中换掉游戏模块,UI 也应焕然一新才对。
在“高内聚,低耦合”的神圣思想指导下,这一切看似是不可能的。在你接触 QtQuick 之前。
大部分情况下,在利用 QtQuick 构建 UI 时,你不必接触到 Painter 甚至是 OpenGL 的纹理、着色器之类不明觉厉的东西,但你确确实实运用了这些高深莫测的东西完成了 Qt 绘制系统 (QtQuick 1) 或是 OpenGL (Qt Quick 2) 所实现的 UI,这就源于良好的封装。在这种设计的鼓励下,UI 模块的职责也更清晰了一点, UI 模块对内核的义务也呼之欲出——提供封装完善的组件供内核定义。

Q:内核直接指定了具体的UI,不是违反了“模块独立”吗?
A:没有,UI模块负责真正的绘制,并根据协议提供各种有大量参数的基础组件给内核自定义,这些参数在被UI系统接受后会被处理成真正的UI组件。内核更多的是进行声明工作,而UI要负责解析。由于声明由内核进行,内核除协议规定暴露的结构外的结构并没有暴露;UI根据协议完成一些基础组件,并根据协议开放规定开放参数。两模块只是协议更为紧密、开放,不违反设计初衷。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

UI 对内核的协议应该开放手牌操作面板、选牌框、角色指示物等的具体样式,实现方式是把其中具体组件的位置、样式等信息任由内核修改。同时开放按钮、方块、圆等基础组件,供内核拼接组成新的高级组件。
UI 原则上不直接影响内核。S端内核根据和网络模块的协议请求一次用户应答->内核请求网络模块发送【内核与UI模块协议下的数据包】->UI接收并根据协议解包->由内核定义的相关UI组件根据内核提供的规则解析数据并执行逻辑->用户通过UI做出应答->UI请求网络模块发送【内核与UI模块协议下的数据包】->S端内核接收回答,通信过程结束。

看起来很复杂?其实现在的通信机制和这个区别并不大,明确网络模块不参与封包和协议之外的解包是游戏内核独立出来(i.e. 内核更换后,网络模块能不影响程序正常工作)的关键。

以上一例简单,一例复杂,一例组合了三种模块,用三种差异较大的情况分析了这种思路的实现,在设计其他模块之间的协作时,可以有个参照。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

qq 20150114203837

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

A指向B表示A为B提供服务,根据服务构建协议,虚线指通过网络模块提供服务。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

AI模块属于内核,还是独立于游戏模块是个问题。考虑到一般卡牌游戏也存在敌友关系,以及威胁性等判断,暂时把AI作为独立的模块讨论,AI和内核S的协议可能不会太严格。

@YanGuam
Copy link
Member Author

YanGuam commented Jan 14, 2015

qq 20150114212940

整理一下 上面6个模块构成服务器
下面6个模块构成客户端

@Fsu0413
Copy link
Contributor

Fsu0413 commented Jan 17, 2015

前几天和啦啦讨论之后决定ai作为客户端,与人类玩家地位相同

@YanGuam
Copy link
Member Author

YanGuam commented Jan 21, 2015

首先,登陆用例。
界面上,一个登录名输入框、一个密码输入框、一个登陆按钮、记住密码和自动登陆复选框

  1. 输入登录名(可以是用户名或邮箱)->登陆->界面显示登录中
    1.1正确->进入服务器页面
    1.2错误->再次输入
    1.2.1同1.1
    1.2.2错误->显示验证码及输入框
    1.2.3输入用户名、密码、验证码
    ……

登录名或密码未输入或验证码->框高亮
勾选自动登录->自动勾选记住密码并禁用
取消勾选自动登录->启用记住密码
如果登陆成功->保存用户名
如果登陆成功且勾选记住密码->保存密码

启动时用户名一栏填上次登陆的用户名、下拉框显示记录的其他用户名、每一条旁边有个X,点击删除记录
如果用户名对应的密码有记住->自动填写
启动时勾选自动登陆->登陆

注册账号->启动浏览器打开注册页面
找回密码->启动浏览器打开找回页面

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants