技术标签: Windows 编程 C# 网络编程 五子棋 Socket
这个网络版五子棋游戏是今年四月初写的。当时觉得自己应该学一些网络编程的东西。而我课程设计的题目已经定了———做一个Everything。 那就帮我斐哥做个网络版的五子棋吧。
源码:https://pan.baidu.com/s/1oLYgg-PykBkCtT0MtKI_xQ
界面是WinForm的,使用GDI绘图来完成棋盘与棋子的绘制,落子坐标通过定义的公式来计算。我原先做过人机对战版的五子棋,因此游戏逻辑这个最重要的部分并没有花很多时间。这个程序一个多星期就搞的差不多了。
不过现在看来,当时的代码太青涩了,一是课程设计马上就要中期检查没太多时间,二是水平和眼界确实不高。
比如:
源码:https://pan.baidu.com/s/1oLYgg-PykBkCtT0MtKI_xQ
玩家对战与人机对战的区别其实就是将玩家A的操作发送给玩家B,玩家B那边的界面渲染。我将游戏里的操作指令封装为了枚举类型。
public enum MsgType
{
LuoZi=0,//玩家落子
Connect=1,//玩家上线
Quit=2,//玩家退出房间
IsWin=3,//是否胜利
CreateRoom=4,//创建房间
JoinRoom=5,//加入房间
UserList=6,//请求|发送玩家列表
RoomList,//请求|发送房间列表
Other,//其他
Start,//开始游戏
Exit,//玩家连接断开
OtherName,//忘了干嘛的了
Restart,//重新开始游戏
Msg//聊天
}
消息对象:
public class MessagePackage
{
public MsgType msgType;
public string data;
public string senderIP = "";
public string senderName = "";
public string sendTime;
public MessagePackage()
{
}
public MessagePackage(string msg)
{
string[] msgs = msg.Split('|');
msgType = (MsgType)int.Parse(msgs[0]);
data = msgs[1];
senderIP = msgs[2];
senderName = msgs[3];
sendTime = msgs[4];
}
public MessagePackage(MsgType msg, string data, string senderIP, string senderName, string sendTime)
{
this.msgType = msg;
this.data = data;
this.senderIP = senderIP;
this.senderName = senderName;
this.sendTime = sendTime;
}
public string ConvertToString()
{
string msg = ((int)msgType).ToString() + "|" + data + "|" + senderIP + "|" + senderName + "|" + sendTime;
return msg;
}
}
进入游戏房间后,我会用GDI画出15*15的棋盘。使用过GDI的朋友都知道,它是根据像素为单位的,这样做是不简单的。
比如你想将棋子落在棋盘上(7,7)这个点上,那就需要用GDI来画一个白色的棋子在那个位置上。GDI提供的绘圆方法是什么呢?FillEllipse,你需要指定一个长方形,包括这个长方形左上角的横纵坐标,以及它的长和宽,以及填充的颜色。这个方法才能为你画出这个长方形里最大的那个圆,或是椭圆。
private bool GraphicsPiece(Point upleft, Color c)
{
Graphics g = this.panel1.CreateGraphics();
if (upleft.X != -1 || upleft.Y != -1)
{
g.FillEllipse(new SolidBrush(c), upleft.X, upleft.Y, CheckerBoard.chessPiecesSize, CheckerBoard.chessPiecesSize);
return true;
}
return false;
}
重点就是这个长方形的左上角坐标怎么得到?我们知道鼠标点击事件中,参数Args带给我们的是一个以像素为单位的,相对与绘图区的位置。而且你不能指望用户正好点在棋盘的那个点上,他可能点在(7,7)上面一点,或是下面一点。因此我们就需要对鼠标点击的坐标值就行处理,将其转化相对的表现形式(7,7)。
public static Piece ConvertPointToCoordinates(Point p,int flag)
{
int x, y;
Piece qi;
if (p.X<leftBorder||p.Y<topBorder||p.X>(lineNumber-1)*distance+leftBorder|| p.Y > (lineNumber - 1) * distance + topBorder)
{
qi= new Piece(-1,-1,flag);
}
else
{
float i = ((float)p.X - leftBorder) / distance;
float j= ((float)p.Y - topBorder) / distance;
x = Convert.ToInt32(i);
y = Convert.ToInt32(j);
if (GameControl.ChessPieces[x, y] != 0)
{
qi = new Piece(-1, -1, flag);
}
else
{
qi = new Piece(x, y,flag);
}
}
return qi;
}
public static Point ConvertCoordinatesToPoint(Piece p)
{
int x, y;
x = p.X * distance + leftBorder - chessPiecesSize / 2;
y = p.Y * distance + topBorder - chessPiecesSize / 2;
return new Point(x, y);
}
Piece p = CheckerBoard.ConvertPointToCoordinates(new Point(e.X, e.Y), 1);
if (p.X != -1)
{
Point point = CheckerBoard.ConvertCoordinatesToPoint(p);
if (Program.gc.AddPiece(p))
{
GraphicsPiece(point, myColor);
MessageBox.Show("黑棋获胜");
return;
}
else
{
GraphicsPiece(point, myColor);
p = Program.gc.MachineChoose();
point = CheckerBoard.ConvertCoordinatesToPoint(p);
if (Program.gc.AddPiece(p))
{
GraphicsPiece(point, otherColor);
turnFlag = true;
MessageBox.Show("白棋获胜");
return;
}
GraphicsPiece(point, otherColor);
lbmyscore.Text = (0 - Program.gc.GetScore()).ToString();
lbhisscore.Text = Program.gc.GetScore().ToString();
turnFlag = true;
}
}
case MsgType.LuoZi:
{
string[] qi = mp.data.Split(',');
int x = int.Parse(qi[0]);
int y = int.Parse(qi[1]);
Piece p = new Piece(x, y, 3 - flag);
Point point = CheckerBoard.ConvertCoordinatesToPoint(p);
if (Program.gc.AddPiece(p))
{
GraphicsPiece(point, otherColor);
start = false;
btnStart.Enabled = true;
MessageBox.Show("对方获胜");
}
else
{
GraphicsPiece(point, otherColor);
turnFlag = true;
}
break;
}
将相对坐标转化成本地像素坐标,绘制棋子,然后本人落子。
没有考虑很多,实现“上传下达”的功能就好了。
case MsgType.Quit:
{
GameRoom r = SearchRoomBySenderName(mp.senderName);
GamePlayer p = SearchUserByName(mp.senderName);
r.QuitRoom(p);
if (r.PlayerNumber == 0)
roomList.Remove(r);
else
{
mp = new MessagePackage(MsgType.Quit, "", "", "", "");
tcpServer.Send(r.RoomMaster.Session, mp.ConvertToString());
}
mp = new MessagePackage(MsgType.RoomList, GetRoomList(), "", "", DateTime.Now.ToString());
foreach (Session session in tcpServer.SessionTable.Values)
{
tcpServer.Send(session, mp.ConvertToString());
}
break;
}
重点来了,我开头就说要学网络编程的。最后简单介绍一下C#中Socket编程。当然,C#也提供了更高级别的封装如TcpClient,TcpListener。以及更高性能的异步套接字:SocketAsyncEventArgs。
mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
mainSocket.Bind(new IPEndPoint(IPAddress.Any, 4396));
mainSocket.Listen(5);
mainSocket.BeginAccept(new AsyncCallback(AcceptConn), mainSocket);
新建Socket实例:指定使用IPv4,流传输,TCP协议。
绑定到本机,4396端口
开始监听,连接队列最大为5
将AcceptConn函数注册为连接回调函数。回调函数必须接收一个类型为IAsyncResult的参数。
mainSocket.BeginAccept(new AsyncCallback(AcceptConn), mainSocket);
protected virtual void AcceptConn(IAsyncResult iar)
{
Socket Server = (Socket)iar.AsyncState;
Socket client = Server.EndAccept(iar);
if (clientCount == maxClient)
{
ServerFull?.Invoke(this, new NetEventArgs(new Session(client)));
}
else
{
Session clientSession = new Session(client);
sessionTable.Add(clientSession.SessionId, clientSession);
clientCount++;
clientSession.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(ReceiveData), clientSession.Socket);
ClientConn?.Invoke(this, new NetEventArgs(clientSession));
Server.BeginAccept(new AsyncCallback(AcceptConn), Server);
}
}
从IAsyncResult中获取到mainSocket,并结束异步操作。这是较为经典的异步编程模型写法。
服务器满,触发ServerFull事件,通知客户端无法进入。
服务器未满,将接入的socket连接进行封装,加入到玩家集合中
开始接收该Socket的消息
clientSession.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(ReceiveData), clientSession.Socket);
BeginReceive函数有多种重载形式,看看说明不难理解。
服务端继续监听连接
Server.BeginAccept(new AsyncCallback(AcceptConn), Server);
连接
Socket newSoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), port);
newSoc.BeginConnect(remoteEP, new AsyncCallback(Connected), newSoc);
发送
public virtual void Send(string datagram)
{
if (datagram.Length == 0)
{
return;
}
if (!isConnected)
{
throw (new ApplicationException("没有连接服务器,不能发送数据"));
}
//获得报文的编码字节
byte[] data = coder.GetEncodingBytes(datagram);
session.Socket.BeginSend(data, 0, data.Length, SocketFlags.None,new AsyncCallback(SendDataEnd), session.Socket);
}
接收
session.Socket.BeginReceive(receiveBuffer, 0, DefaultBufferSize, SocketFlags.None, new AsyncCallback(RecvData), socket);
当然实际编程的时候会遇到好多问题,比如:
文章浏览阅读3.5k次。微信公众号:javafirst下面是 Java 程序员面试失败最有可能的5大原因,当然也许这5点原因适用于所有的程序员,所以,如果你是程序员,请认真阅读以下内容。看点01说得太少尤其是那些开放式的问题,如“请介绍下你自己”或“请讲一下你曾经解决过的复杂问题”。面试官会通过你对这些技术和非技术问题的回答来评估你的激情。他们也会通过模拟团队氛围和与你的交流互动来判断你的经验和能力。所以,仅仅只用两三句..._java程序员面试:工作中有没有碰到比较棘手的问题,你是怎么解决的
文章浏览阅读1.4k次。EMMC驱动中常用命令说明及初始化顺序一、命令说明mmc_go_idle发送CMD0指令,GO_IDLE_STATE使mmc card进入idle state。虽然进入到了Idle State,但是上电复位过程并不一定完成了,这主要靠读取OCR的busy位来判断,而流程归结为下一步。mmc_send_op_cond发送CMD1指令,SEND_OP_COND这里会设置card的工作电..._mmc_send_cid
文章浏览阅读2.6k次,点赞2次,收藏6次。 2022.3.28 早十文章目录4.1 修改器建模综述4.2 修改器命令面板4.3 二维图形编辑修改器4.4 三维图形编辑修改器4.4.1弯曲修改器4.4.2锥化修改器4.4.3扭曲修改器4.4.4FFD修改器(自由变形)5.1 世界空间修改器概述5.2 路径变形修改器5.2 毛发修改器4.1 修改器建模综述#mermaid-svg-12QA56qYK5ChV8B7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16p_由二维图纸转换为三维模型的插件
文章浏览阅读202次。数据是新时代的生产要素;保护数据原生价值,实现数据的所有权保护、交换与管理;完善数据在收集、使用、存储等阶段的全生命周期安全;研究分析复杂物理数据交互场景中的数据安全攻防机理;在保护数据所有权的前提下实现高价值数据的安全交易;安全技术标准的推广与法律法规的完善。一、数据安全防护是重大战略需求当前的行业共识认为数据是驱动数字经济发展的核心动力。以数据为基础的云计算、物联网、区块链、人工智能等经济生态及相关产业链在智慧城市升级、国家重大基建产业发展等方面发挥着积极的作用。《中国数字经济发展白皮书》显示,2.
文章浏览阅读1.1k次,点赞34次,收藏9次。这个系列介绍了常用的单智能体强化学习方法,也有些没有写到,比如 SAC,希望以后有时间可以回来补完。下个系列会开始介绍 RLXF(包括 RLHF、RLAIF)欢迎关注。奋斗,追求,不达目的,誓不罢休!
文章浏览阅读666次,点赞7次,收藏6次。html5黑色大气的个人博客全屏滚动个人主页源码HTML+JS+CSS
文章浏览阅读1k次。LWIP互联网资料汇总分类: UCOSII/LWIP2013-01-01 11:06 4946人阅读 评论(2) 收藏 举报目录(?)[+]本文主要搜集了下互联网上关于LWIP的资料和教程欢迎补充第一部分:移植LWIP在UCOS上移植LWIP 在STM32上移植 http://www.docin.com/p-459242028_lwip cli
文章浏览阅读432次。if you could meet a famous entertainer or athlete, who would that be , and why?_famous enterainer and athlete
文章浏览阅读2.4k次。本期,我将给大家介绍14款实用的测试工具,希望能够帮到大家!(建议收藏)UI自动化测试工具1.uiautomator2Github地址:https://github.com/openatx/uiautomator2star:1.9k介绍:openatx开源的ui自动化工具,支持android和ios。主要面向的编程语言是python,api设计简洁易用,在开源社区也是很受欢迎。原理图:与appi..._测骨龄软件
文章浏览阅读1.4w次,点赞3次,收藏14次。使用uart1串口,需要用到stm8s_uart1.c和stm8s_uart1.h两个文件1、建立工程目录结构如下:2、编写uart.h文件如下:#ifndef __UART_H#define __UART_H#include "stm8s.h"#include "stm8s_clk.h"void USART_Configuration(void); //串口配置函数void UART_sen..._stm8串口发送字符串
文章浏览阅读275次,点赞4次,收藏9次。自动监控把sass编译成css文件,命令行在监控的sass后面,可以为 sass 生成 css 样式指定生成的格式,默认是nested型;通过 --style nested( 嵌套 - 默认 )|compact( 紧促型 )|compressed( 压缩 )|expended( 扩展 ) 命令,可以为 sass 生成 css 样式指定生成的格式2、合成文件@improt在sass中,使用@improt可以把多个不同的sass文件合成一个css文件,在合成的sass中有两种方式,
文章浏览阅读5.5k次。首先,我们得知道struts2是什么,那我们才知道这个struts2有什么优缺点,是吧。所以,我先来解释一下struts2到底是什么。Apache Struts是一个免费,开源,MVC框架, 现代Java web应用框架。 它有利于约定优于配置, 可扩展的使用一个插件架构,并附带插件的支持 休息,AJAX和JSON。 所以呢,针对于struts2是什么,我们就可以知道struts2有什么优点了。_struts2框架优点