杀人游戏系列 之三 提供游戏代码下载

2023-11-06 09:50

本文主要是介绍杀人游戏系列 之三 提供游戏代码下载,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一节中,我们讲到了业务实体的设计。好多人可能都觉得,为什么没有在这些实体类中放置方法,不够oo. 这实际和项目整体的设计有关。在 杀人游戏系列 之一 一节中,我们提到了,整个项目的架构是网络模式的。而网络模式势必有client和server端。那client和server之间的通信成为这个游戏逻辑的推动力。本节从wcf层面的设计讲起,看看游戏逻辑是如何驱动的。
1,client与server的消息体的设计
既然client和server之间的通信是不可避免的。那需要先定义通信消息的数据格式。第一个被定义的是登陆消息格式。如下:
    [DataContract]
    
public   class  LogonFormat
    
{
        [DataMember]
        
public string LogonPerson getset; }

        [DataMember]
        
public string IPPortServiceString getset; }
    }
这里只有用户的用户名和其ip地址。
第二个被定义的是交谈消息的格式。如下:

    [DataContract]
    
public   class  ChatFormat
    
{
        
//ID
        [DataMember]
        
public string Sender getset; }

        [DataMember]
        
public string MsgBody getset; }

        
//TODO:When chat one to one
        
//"" if this message is not to a given person
        [DataMember]
        
public string Receipter getset; }

        
//Time flag, indicate daytime or night
        [DataMember]
        
public TimeFlag TimeFlag getset; }

    }

做下简单的介绍,sender是发送者,msgbody是消息体。receipter是接收人。timeflag是白天还是黑天。在这里,为了继续简化游戏。我们认为规定了,server端只维持一个game。这个设计无疑是个败笔。但是时间紧迫,5天内会熟练使用wcf就已经不错了,别说把游戏的逻辑设计的完美无缺陷。如果server端只维持一个游戏,整个逻辑就简单了许多。所以,chatformat并没有game编号的定义。
最后一个被定义消息体是,voteforamt,如下:
    [DataContract]
    
public   class  VoteFormat
    
{
        [DataMember]
        
public string Voter getset; }

        [DataMember]
        
public string Condidate getset; }

        
//Time flag, indicate daytime or night
        [DataMember]
        
public TimeFlag TimeFlag getset; }

    }

无论是用户杀人还是投票,都可以需要向服务器端发送消息。我们把杀人和投票这两个行为需要的消息体进行了整合。用timeflog来区别他们。当是白天时,那就是投票,而黑夜时,就是杀人。

2,游戏逻辑从实体对象的转移
有了这些数据格式的定义,该做的就是通信方法的定义了。定义如下接口:
通信接口的定义
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace ServerLib
{
    
// NOTE: If you change the interface name "IMessageDistribute" here, you must also update the reference to "IMessageDistribute" in Web.config.
    [ServiceContract(Namespace = "KillPersonServerApp", Name = "MessageDistribution")]
    
public interface IMessageDistribute
    
{
        
//nTime:Daytime and night, in Daytime, all person can send message to anyone
        
//      At night, only bad guys can send messages to those persons who are also bad persons
        
//nSenderID: The IdentityID of the sender.
        
//According to the time and the role of sender, the server host decides which persons this messages will be send to.
        
//-------------------------------------------
        
//|     Time    |   Sender Role | Reciever  |
        
//| DayTime     |   Civilian    | All       |
        
//| DayTime     |   Bad person  | All       |
        
//| DayTime     |   Dead person | All       |
        
//| Night       |   Civilian    | None      |
        
//| Night       |   Bad person  | Bad person|
        
//| Night       |   Dead person | None      |
        
//-------------------------------------------
        [OperationContract]
        
bool DistribChatMsg(ChatFormat chat);

        
//-------------------------------------------
        
//|     Time    |   Sender Role | Valid?    |
        
//| DayTime     |   Civilian    | Yes       |
        
//| DayTime     |   Bad person  | Yes       |
        
//| DayTime     |   Dead person | No        |
        
//| Night       |   Civilian    | No        |
        
//| Night       |   Bad person  | Yes       |
        
//| Night       |   Dead person | No        |
        
//-------------------------------------------
        [OperationContract]
        
bool DistribVoteMsg(VoteFormat vote);

        
//For server to broadcast news to client.
        [OperationContract]
        List
<string> Logon(LogonFormat logon);
    }

}

这个接口有三个方法。登陆,发送消息,和投票。在这里,游戏的逻辑继续被简化。首先,没有登陆游戏,也没有创建游戏,更没有开始游戏和退出游戏。那这些逻辑都在那里呢?这些逻辑在用户登陆server时,server自己判断。一旦人数超过了设定的人数,server自动创建一个游戏,并启动这个游戏。这里的简化和前面的设计有关,即server只维系一个game.这里依然将来需要做大的彻底更改的地方。
在 杀人游戏系列 之二 中,有游戏的逻辑图。在投票和杀人整合后,用户登陆以及启动游戏的地方再一次做了简化。刚才提到的投票和杀人的逻辑,事实是在 DistribVoteMsg发放中完成的。而 DistribChatMsg只负责单纯的交谈,并没有太多的游戏逻辑。写到这里,整个游戏设计思路就该明了了。游戏中出现的实体类,只是被驱动的对象,而不是游戏逻辑的载体。游戏逻辑被封装到了wcf层面的通信上了。 而与数据库的交互,被单独做成了静态的方法,独自行成一个类库。这些方法承载了部分的游戏逻辑。但又相互独立。在这个类库里,实体类依然只是传递数据,而不封装游戏逻辑。或许这与传统的oo方法有出入,但走linq To Sql这条路,这样的设计才能发挥其优势。

3,UI的设计
既然游戏的逻辑被简化,ui上也就不会有太多的麻烦。一个登陆框,加一个游戏的主界面。在这个游戏的主界面中,左边显示用户。中间是对话框,下面是消息输入框和杀人及投票的按钮。如图

如果,不简化游戏逻辑,还要增加游戏大厅里的对话框,以及创建game,加入game,开始game和退出game等。

4,一些新鲜设计或技术点
4.1 我们知道wcf只是一个类库,它实际上需要一个宿主来提供它的服务。我们在这里建立了一个console 的application来提供wcf的服务。但是,消息是单向的,只能是client从server请求,怎么样才能把消息从server端发到client端呢?这里,client也不得不启动一个wcf的服务,来接收从server的消息。这时,server是做为普通client端。
4.2 wcf的宿主还起到了另一个功能,初始化数据库。通常的数据库的项目。布置和配制数据库是相当的麻烦。这里,程序在启动服务时,先检查数据是否存在,如果数据库不存在,它讲初始化数据库。当然,这里也是人写好的。如下:
初始化数据库
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL;
using Utility;
using System.Reflection;

namespace ServerHostApp
{
    
internal static class Database
    
{

        
internal static void InitialDatabase()
        
{
            
using (DataMappingDataContext db = new DataMappingDataContext(true))
            
{
                
if (!db.DatabaseExists())
                
{
                    
// create new database
                    db.CreateDatabase();
                    
///我们不打算支持用户注册,初始化用户#region///我们不打算支持用户注册,初始化用户
                    User u1 
= new User();
                    u1.UserID 
= "tom";
                    u1.Name 
= "tom";
                    u1.Password 
= "tom";

                    User u2 
= new User();
                    u2.UserID 
= "boler";
                    u2.Name 
= "boler";
                    u2.Password 
= "boler";

                    User u3 
= new User();
                    u3.UserID 
= "peter";
                    u3.Name 
= "peter";
                    u3.Password 
= "peter";

                    User u4 
= new User();
                    u4.UserID 
= "sean";
                    u4.Name 
= "sean";
                    u4.Password 
= "sean";

                    User u5 
= new User();
                    u5.UserID 
= "cloudy";
                    u5.Name 
= "cloudy";
                    u5.Password 
= "cloudy";

                    db.Users.InsertOnSubmit(u1);
                    db.Users.InsertOnSubmit(u2);
                    db.Users.InsertOnSubmit(u3);
                    db.Users.InsertOnSubmit(u4);
                    db.Users.InsertOnSubmit(u5);
                    
#endregion

                    
/**//// 初始化Role
                    foreach (var item in Enum.GetValues(typeof(RoleFlag)))
                    
{
                        Role role 
= new Role();
                        role.RoleID 
= (int)item;
                        role.Name 
= item.ToString();
                        db.Roles.InsertOnSubmit(role);
                    }

                    
/**////初始化gamestatus
                    foreach (var item in Enum.GetValues(typeof(EnumGameStatus)))
                    
{
                        GameStatus gamestatus 
= new GameStatus();
                        gamestatus.StatusID 
= (int)item;
                        gamestatus.StatusName 
= item.ToString();
                        db.GameStatus.InsertOnSubmit(gamestatus);
                    }


                    
/**//// 初始化ActivityType
                    foreach (var item in Enum.GetValues(typeof(ActivityTypeFlag)))
                    
{
                        ActivityType ac 
= new ActivityType();
                        ac.ActivityTypeID 
= (int)item;
                        ac.ActivityTypeName 
= item.ToString();
                        ac.TimeCollapse 
= 1;
                        
if (item.ToString() == "Vote")
                        
{
                            ac.TimeCollapse 
= 4;
                        }


                        db.ActivityTypes.InsertOnSubmit(ac);
                    }


                    db.SubmitChanges();

                    Console.WriteLine(
"Create database done!!");
                }

            }

        }

    }

}

4.3 wpf中的回调
在登陆界面后,需要显示主游戏界面。而此时,牵扯到wpf中的回调。如下:
wpf中的回调
        private void ShowMainDailog()
        
{
            var lstGame 
= App.gameMainWindow;

            
if (!lstGame.CheckAccess())
            
{
                lstGame.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, 
new System.Threading.ThreadStart(this.ShowMainDailog));
            }

            
else
            
{
                App.gameMainWindow.Show();
                App.gameMainWindow.labelClientName.Content 
= App.currentClientName;
                App.gameMainWindow.imageTimeFlagMoon.Visibility 
= Visibility.Hidden;
                App.gameMainWindow.imageTimeFlagSun.Visibility 
= Visibility.Hidden;

            }

        }
在这里, ShowMainDailog函数体内,需要再一次调用 ShowMainDailog函数。主要是因为 !lstGame.CheckAccess()条件成立,即form是不可以访问的。当第二次调用时,就走else字句了。(说实话,这是wpf里的技术,我也晕着呢。)

5 结语
可能会让好多人失望,没有谈wpf和wcf技术。但是,谈游戏才是本篇的主题。技术是为这个主题服务的。在这里,游戏逻辑因为时间上的因素被简化依然让笔者心痛。这是个比赛,需要在第三天的时候,整个程序就能跑起来,剩下的2天调试排错。这无疑让我们不得忍痛割爱。在以后的日子里,如果时间允许的话,笔者还是会加上被砍掉的游戏逻辑。 放出所有的代码,供参考。(大家需要注意的是,我和peter最近再做修改,希望能添加上被砍掉的部分,所以,代码和blog里有些出入。)

上一篇
杀人游戏系列 之二
杀人游戏系列 之一
其他:
Linq To Sql进阶系列 -目录导航
C# 3.0入门系列-目录导航

这篇关于杀人游戏系列 之三 提供游戏代码下载的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/355899

相关文章

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置