网络编程五子棋设计报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
封面
成都信息工程学院
课程设计
题目:JA V A网络版五子棋
作者姓名:张樊
班级:网络083
学号:2008121105
指导教师:方睿
日期:2011年 9 月 27 日
作者签名:
java网络版五子棋
摘要
传统五子棋的棋具与围棋相同,棋子分为黑白两色,棋盘为15×15,棋子放置于棋盘线交叉点上。
两人对局,各执一色,轮流下一子,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。
随着五子棋的发展,逐步发现先行优势非常大,最后得出“先行必胜”。
五子棋要成为竞技运动,核心问题是怎样解决传统五子棋下法中“先行必胜”的问题。
于是有如下的解决办法:
“交换”思维
“交换”思维有个形象的比喻:一个人切蛋糕,一个人选蛋糕。
即:一人开局,另一人选择黑方还是白方。
此方法最为公平、最有效率,且最为简洁实用。
不过此方法使开局者不走已知的必胜,把棋艺的较量演化为对局者开局的博弈,而“先行必胜”依然存在。
关键词:五子棋,java版,C/S结构
目录
1引言 (1)
1.1课题背景 (1)
1.2国内外研究现状 (1)
1.3本课题研究的意义 (2)
1.4本课题的研究方法 (2)
2概要设计 (2)
2.1开发环境 (2)
E CLIPSE简介: (2)
2.2功能需求设计 (3)
3详细设计 (3)
3.1总体设计 (3)
3.2各功能模块详细设计 (5)
3.2.1用户列表面板 (6)
3.2.2用户操作面板 (6)
3.2.3开发棋子类 (7)
3.2.4开发棋盘面板 (7)
3.2.5棋盘线程 (11)
3.2.6服务器信息面板 (13)
3.2.7 服务器线程 (14)
3.2.8 服务器端 (14)
3.2.9 客户端线程 (16)
3.2.10 客户端 (18)
测试与分析 (22)
结论 (24)
参考文献 (24)
1引言
1.1课题背景
传统五子棋的棋具与围棋相同,棋子分为黑白两色,棋盘为15×15,棋子放置于棋盘线交叉点上。
两人对局,各执一色,轮流下一子,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。
随着五子棋的发展,逐步发现先行优势非常大,最后得出“先行必胜”。
五子棋要成为竞技运动,核心问题是怎样解决传统五子棋下法中“先行必胜”的问题。
于是有如下的解决办法:“交换”思维
“交换”思维有个形象的比喻:一个人切蛋糕,一个人选蛋糕。
即:一人开局,另一人选择黑方还是白方。
此方法最为公平、最有效率,且最为简洁实用。
不过此方法使开局者不走已知的必胜,把棋艺的较量演化为对局者开局的博弈,而“先行必胜”依然存在。
如:欧洲的Swap2、Swap1;中国的Swap3、第一手交换规则等。
“禁手”思维
禁止以某种手段取胜。
为了平衡先后手之间的差距,削弱先手优势,日本连珠提出“禁手”。
后来连珠的发展证明禁手并不能平衡先后手之间的差距,依然是“先行必胜”。
“泡沫”原理
像泡沫一样会破灭。
如“吃子”五子棋。
连成5个后消失,同时拿掉对方一个棋子,自己再补一子。
此原理的的特点是:把五子棋金球制改成了“进球制”,同时也最大效率地利用了棋盘和棋子,带来了全新的思维。
问题是怎样计算胜负。
“井字游戏”原理
井字游戏又叫圈叉棋,圈叉棋是和棋,原因是空间很小。
通过缩小棋盘来抑制五子棋先行的优势。
如:15路棋盘,13路棋盘等等。
问题是多大的棋盘才能是和棋,知道和棋后还能成为竞技运动吗。
“跷跷板”原理
五子棋始终是先行领先一子。
如果双方轮流领先一子呢?台湾教授发明的六子棋,就如“跷跷板”一样。
先行先下一子,然后双方轮流下两子,先连成6子者胜利。
六子棋为第11届奥林匹亚计算机游戏程序竞赛项目,验证其公平性与复杂性。
1.2国内外研究现状
中国
长久以来五子棋在中国一直没有得到发展。
2001年,国家体育总局批准中国棋院试办全国性五子棋比赛(更准确地说,这里的“五子棋”应该被称为“连珠五子棋”或“连珠棋”)。
2002年8月2-4日,中国棋院举办了首届全国五子棋个人邀请赛,此后每年举办一次。
2003年,国家体育总局批准了中国棋院审定的《中国五子棋竞赛规则》(试行)(参考资料[1])和《中国五子棋段级位制》(试行)(参考资料[2])。
2006年2月,国家体育总局对五子棋正式立项,作为围棋下的一个二级项目。
全国各地相继成立了新的五子棋俱乐部。
日本
据日本史料文献记载,中国古代的五子棋先由中国传到高丽(朝鲜),然后于公元1688年至1704年日本的元禄时代再从高丽传到日本,最初在皇宫和贵族大家庭中流行,到元禄末期,开始在民间盛行。
1899年,对传统五子棋进行规则改良后,经过公开征名,“联珠”
这一名称才被正式确定下来。
取意于《汉书·律历志上》中“日月如合璧,五星如联珠”一句。
现写做“连珠”。
欧洲
20世纪初传统五子棋及连珠从日本传入欧洲。
五子棋英译为“Five In A Row”(缩写为FIR),汉语拼音“wǔ zǐ qi”,同时根据日语的罗马拼音,把“五目、五目碰”英译为“Gomoku、Gobang”,“连珠”英译为“Renju”。
1958年以及1979年,瑞典和前苏联分别成立了连珠联盟。
1988年,日本、前苏联以及瑞典三个国家的连珠组织在瑞典成立国际连珠联盟(Renju International Federation,简称RIF).
20世纪90年代欧洲一些国家将传统五子棋引入“交换”等规则后,经过发展逐渐形成一系列的Gomoku新规则,中欧一些国家成立了Gomoku组织,并和六子棋共同成立了国际联盟(GCIF)。
21世纪初在举办世界连珠(renju)锦标赛的同时也举办世界Gomoku锦标赛(GT)及六子棋国际公开赛(CT)。
2009年8月在捷克共和国(CzechRepublic)帕尔杜比采市(Pardubice)将举行第11届世界连珠锦标赛和第3届世界Gomoku锦标赛。
1.3本课题研究的意义
本课题研究的目的在于开发一个基于C/S模式的网络五子棋对战平台,提供给用户进行网络对战,因为与PC进行游戏太过单一,而且容易出现一种模式化的感觉,久而久之缺乏新鲜感,而与人对弈的好处是每一盘棋都在不停地变化,很难出现一模一样的两盘棋,而且与不同的人对战,难度不同,增加不少乐趣。
同时基于网络的对战平台,可以供给在不同地方的用户进行游戏的机会。
1.4本课题的研究方法
本课题通过java语言基于socket进行开发,采用了java的多线程模式。
2概要设计
2.1开发环境
本软件用java语言开发,使用的集成开发软件为eclipse3.5.2
Java简介:
Java平台由Java虚拟机(Java Virtual Machine)和Java 应用编程接口(Application Programming Interface、简称API)构成。
Java 应用编程接口为Java应用提供了一个独立于操作系统的标准接口,可分为基本部分和扩展部分。
在硬件或操作系统平台上安装一个Java平台之后,Java应用程序就可运行。
现在Java平台已经嵌入了几乎所有的操作系统。
这样Java程序可以只编译一次,就可以在各种系统中运行。
Java应用编程接口已经从1.1x 版发展到1.2版。
目前常用的Java平台基于Java1.5,最近版本为Java1.7。
Java分为三个体系JavaSE(Java2 Platform Standard Edition,java平台标准版),JavaEE(Java 2 Platform,Enterprise Edition,java平台企业版),JavaME(Java 2 Platform Micro Edition,java平台微型版)。
Eclipse简介:
Eclipse是著名的跨平台的自由集成开发环境(IDE)。
最初主要用来Java语言开发,但是目前亦有人通过插件使其作为其他计算机语言比如C++和Python的开发工具。
Eclipse
的本身只是一个框架平台,但是众多插件的支持使得Eclipse拥有其他功能相对固定的IDE 软件很难具有的灵活性。
许多软件开发商以Eclipse为框架开发自己的IDE。
Eclipse 最初由OTI和IBM两家公司的IDE产品开发组创建,起始于1999年4月。
IBM 提供了最初的Eclipse代码基础,包括Platform、JDT 和PDE。
目前由IBM牵头,围绕着Eclipse项目已经发展成为了一个庞大的Eclipse联盟,有150多家软件公司参与到Eclipse 项目中,其中包括Borland、Rational Software、Red Hat及Sybase等。
Eclipse是一个开发源码项目,它其实是Visual Age for Java的替代品,其界面跟先前的Visual Age for Java差不多,但由于其开放源码,任何人都可以免费得到,并可以在此基础上开发各自的插件,因此越来越受人们关注。
近期还有包括Oracle在内的许多大公司也纷纷加入了该项目,并宣称Eclipse将来能成为可进行任何语言开发的IDE集大成者,使用者只需下载各种语言的插件即可。
2.2功能需求设计
对于普通的五子棋博弈而言,需要实现以下功能:有黑白两种棋子,两位棋手各执一棋,率先五连珠者获胜。
所以需要设计黑白两类棋子类及键盘类并设计判断获胜的算法。
对于网络五子棋而言,在普通的五子棋基础上还需要添加如下功能:拥有服务器端和客户端,用户通过客户端登录服务器后可与其他登陆用户进行对弈,服务器支持多组用户同时进行对弈,用户可以在服务器上创建新游戏或者加入已创建的游戏,用户在下棋的时候可以进行聊天交流。
本程序采用TCP协议进行传输,采用多线程设计。
3详细设计
3.1总体设计
本程序采用TCP通信,使用java语言,且采用多线程设计,其中TCP协议的工作流程如图:
相对于其他语言的TCP协议工作模式,java过程略微简单一两步。
游戏从程序运行来看,分为客户端和服务器,从功能来分,可分为:服务器线程,服务器程序,客户端线程程序,出题程序和验证程序,以及客户端面板,么一部分的具体设计在3.2详解。
总体设计流程图:
3.2各功能模块详细设计模块一览表:
3.2.1用户列表面板
用户列表面板提供了显示所有用户的列表框,用户通过客户端登陆服务器后,可以通过此面板查看服务器上用户的情况。
在初始状态下,默认显示10个用户。
public class UserListPad extends Panel
{
public List userList = new List(10);
public UserListPad()
{
setLayout(new BorderLayout());
for (int i = 0; i < 10; i++)
{
userList.add(i + "." + "无用户");
}
add(userList, BorderLayout.CENTER);
}
}
3.2.2用户操作面板
用户操作面板提供了显示用户主机信息的标签,创建,加入,放弃及退出游戏的按钮以及连接到主机的按钮。
用户操作面板采用了“FlowLayout”的布局,含有显示IP地址的标签等。
public class UserControlPad extends JPanel
{
public JLabel ipLabel = new JLabel("IP", JLabel.LEFT);
public JTextField ipInputted = new JTextField("localhost", 10);
public JButton connectButton = new JButton("连接到服务器");
public JButton createButton = new JButton("创建游戏");
public JButton joinButton = new JButton("加入游戏");
public JButton cancelButton = new JButton("放弃游戏");
public JButton exitButton = new JButton("退出程序");
public UserControlPad()
{
setLayout(new FlowLayout(FlowLayout.LEFT));
setBackground(Color.LIGHT_GRAY);
add(ipLabel);
add(ipInputted);
add(connectButton);
add(createButton);
add(joinButton);
add(cancelButton);
add(exitButton);
}
}
3.2.3开发棋子类
以黑棋为例:棋盘继承了Canvas类,即画布:
public class GamePointBlack extends Canvas
{
GamePad padBelonged; // 黑棋所属的棋盘
public GamePointBlack(GamePad padBelonged)
{
setSize(20, 20); // 设置棋子大小
this.padBelonged = padBelonged;
}
public void paint(Graphics g)
{ // 画棋子
g.setColor(Color.black);
g.fillOval(0, 0, 14, 14);
}
}
同理可以得到白棋类,将颜色参数设置为Color.white。
3.2.4开发棋盘面板
棋盘面板将提供五子棋的棋盘功能,除了绘制棋盘的方法外,该类还需提供判断棋盘当前状态是否为胜利状态的方法,因为是网络五子棋,还需要实现网络对已功能。
首先看棋盘的属性和构造方法,其中为了实现棋盘的网络功能,用到了Socket类的chessSocket属性来作为棋盘的套接口来连接到服务器,并用该套接口来创建监听网络的输入流inputData 及发送信息的输出流outputData,布局方法暂时省略。
public class GamePad extends Panel implements MouseListener, ActionListener
{
// 鼠标是否能使用
public boolean isMouseEnabled = false;
// 是否胜利
public boolean isWinned = false;
// 是否在下棋中
public boolean isGaming = false;
// 棋子的x轴坐标位
public int chessX_POS = -1;
// 棋子的y轴坐标位
public int chessY_POS = -1;
// 棋子的颜色
public int chessColor = 1;
// 黑棋x轴坐标位数组
public int chessBlack_XPOS[] = new int[200];
// 黑棋y轴坐标位数组
public int chessBlack_YPOS[] = new int[200];
// 白棋x轴坐标位数组
public int chessWhite_XPOS[] = new int[200];
// 白棋y轴坐标位数组
public int chessWhite_YPOS[] = new int[200];
// 黑棋数量
public int chessBlackCount = 0;
// 白棋数量
public int chessWhiteCount = 0;
// 黑棋获胜次数
public int chessBlackVicTimes = 0;
// 白棋获胜次数
public int chessWhiteVicTimes = 0;
// 套接口
public Socket chessSocket;
public DataInputStream inputData;
public DataOutputStream outputData;
public String chessSelfName = null;
public String chessPeerName = null;
public String host = null;
public int port = 4331;
public TextField statusText = new TextField("请连接服务器!");
public GameThread gameThread = new GameThread(this);
public GamePad()
{
setSize(440, 440);
setLayout(null);
setBackground(Color.LIGHT_GRAY);
addMouseListener(this);
add(statusText);
statusText.setBounds(new Rectangle(40, 5, 360, 24));
statusText.setEditable(false);
}
ConnectServer方法:
该方法主要用于将棋盘连接到主机以进行网络对弈,首先创建指定ip地址和端口的套接口,然后创建基于该套接口的输入流和输出流,最后启用棋盘的多线程功能,线程启动后将利用上面的套接口和输入输出流来监听网络的数据流向。
// 连接到主机
public boolean connectServer(String ServerIP, int ServerPort) throws Exception
{
try
{
// 取得主机端口
chessSocket = new Socket(ServerIP, ServerPort);
// 取得输入流
inputData = new
DataInputStream(chessSocket.getInputStream());
// 取得输出流
outputData = new
DataOutputStream(chessSocket.getOutputStream());
gameThread.start();
return true;
}
catch (IOException ex)
{
statusText.setText("连接失败! \n");
}
return false;
}
setLocation方法
该方法用于设定指定的棋子的位置,其中参数xPos和yPos分别给出了棋子的横坐标和纵坐标,而参数chessColor给出了棋子的颜色,1为黑棋,-1为白棋。
chessBlack_XPOS和chessBlack__YPOS分别为保存黑棋的横坐标和纵坐标的两个数组,该数组通过在棋盘上的个数chessBlackCount来进行索引。
白棋的方法类似。
public void setLocation(int xPos, int yPos, int chessColor) {
if (chessColor == 1)
{ // 棋子为黑棋时
chessBlack_XPOS[chessBlackCount] = xPos * 20;
chessBlack_YPOS[chessBlackCount] = yPos * 20;
chessBlackCount++;
}
else if (chessColor == -1)
{ // 棋子为白棋时
chessWhite_XPOS[chessWhiteCount] = xPos * 20;
chessWhite_YPOS[chessWhiteCount] = yPos * 20;
chessWhiteCount++;
}
}
checkVicStatus方法
用于判断当前棋盘是否为胜利状态,获取一个棋子的位置,然后判断其左右,上下,左上和右下,左下和右上是否存在五连珠即可。
以左右为例,用循环判断右边是否为同一颜色,,当连接数达到5,就返回true。
其余类似
public boolean checkVicStatus(int xPos, int yPos, int chessColor) {
int chessLinkedCount = 1; // 连接棋子数
int chessLinkedCompare = 1; // 用于比较是否要继续遍历一个棋子的相邻网格
int chessToCompareIndex = 0; // 要比较的棋子在数组中的索引位置
int closeGrid = 1; // 相邻网格的位置
if (chessColor == 1)
{ // 黑棋时
chessLinkedCount = 1; // 将该棋子自身算入的话,初始连接数为1
//以下每对for循环语句为一组,因为下期的位置能位于中间而非两端
for (closeGrid = 1; closeGrid <= 4; closeGrid++)
{ // 遍历相邻4个网格
for (chessToCompareIndex = 0; chessToCompareIndex <= chessBlackCount; chessToCompareIndex++)
{ // 遍历棋盘上所有黑棋子
if (((xPos + closeGrid) * 20 ==
chessBlack_XPOS[chessToCompareIndex])
&& ((yPos * 20) ==
chessBlack_YPOS[chessToCompareIndex]))
{ // 判断当前下的棋子的右边4个棋子是否都为黑棋
chessLinkedCount = chessLinkedCount + 1; // 连接数加1
if (chessLinkedCount == 5)
{ // 五子相连时,胜利
return true;
}
}
}
if (chessLinkedCount == (chessLinkedCompare + 1)) {
chessLinkedCompare++;
}
else {// 若中间有一个棋子非黑棋,则会进入此分支,此时无需再遍历
break;
}
}
其余方向判断方法类似,就不列出。
setVicStatus方法:
设置胜利方法,设定胜利时棋盘状态,指定胜利棋子的颜色,并且清空棋盘。
public void setVicStatus(int vicChessColor)
{
// 清空棋盘
this.removeAll();
// 将黑棋的位置设置到零点
for (int i = 0; i <= chessBlackCount; i++)
{
chessBlack_XPOS[i] = 0;
chessBlack_YPOS[i] = 0;
}
// 将白棋的位置设置到零点
for (int i = 0; i <= chessWhiteCount; i++)
{
chessWhite_XPOS[i] = 0;
chessWhite_YPOS[i] = 0;
}
// 清空棋盘上的黑棋数
chessBlackCount = 0;
// 清空棋盘上的白棋数
chessWhiteCount = 0;
add(statusText);
statusText.setBounds(40, 5, 360, 24);
if (vicChessColor == 1)
{ // 黑棋胜
chessBlackVicTimes++;
statusText.setText("黑方胜,黑:白 "+ chessBlackVicTimes+ ":" + chessWhiteVicTimes
+ ",游戏重启,等待白方...");
}
else if (vicChessColor == -1)
{ // 白棋胜
chessWhiteVicTimes++;
statusText.setText("白方胜,黑:白 "+ chessBlackVicTimes+ ":" + chessWhiteVicTimes
+ ",游戏重启,等待黑方...");
}
}
3.2.5棋盘线程
从获取的信息中解析出对方棋子所在的位置,并将其画在棋盘上。
public class GameThread extends Thread
{
GamePad currPad; // 当前线程的棋盘
public GameThread(GamePad currPad)
{
this.currPad = currPad;
}
// 处理取得的信息
public void dealWithMsg(String msgReceived)
{
if (msgReceived.startsWith("/chess "))
{ // 收到的信息为下棋
StringTokenizer userMsgToken = new
StringTokenizer(msgReceived, " ");
// 表示棋子信息的数组、0索引为:x坐标;1索引位:y坐标;2索引位:棋子颜色
String[] chessInfo = { "-1", "-1", "0" };
int i = 0; // 标志位
String chessInfoToken;
while (userMsgToken.hasMoreTokens())
{
chessInfoToken = (String) userMsgToken.nextToken(" ");
if (i >= 1 && i <= 3)
{
chessInfo[i - 1] = chessInfoToken;
}
i++;
}
currPad.paintNetFirPoint(Integer.parseInt(chessInfo[0]), Integer
.parseInt(chessInfo[1]),
Integer.parseInt(chessInfo[2]));
}
else if (msgReceived.startsWith("/yourname "))
{ // 收到的信息为改名
currPad.chessSelfName = msgReceived.substring(10);
}
else if (msgReceived.equals("/error"))
{ // 收到的为错误信息
currPad.statusText.setText("用户不存在,请重新加入!");
}
}
// 发送信息
public void sendMessage(String sndMessage)
{
try
{
currPad.outputData.writeUTF(sndMessage);
}
catch (Exception ea)
{
ea.printStackTrace();;
}
}
public void run()
{
String msgReceived = "";
try
{
while (true)
{ // 等待信息输入
msgReceived = currPad.inputData.readUTF();
dealWithMsg(msgReceived);
}
}
catch (IOException es){}
}
}
3.2.6服务器信息面板
用于提供各种信息的显示,用于显示服务器状态和信息输出.
public class ServerMsgPanel extends Panel
{
public TextArea msgTextArea = new TextArea("", 22, 50,
TextArea.SCROLLBARS_VERTICAL_ONLY);
public JLabel statusLabel = new JLabel("当前连接数:", Label.LEFT);
public Panel msgPanel = new Panel();
public Panel statusPanel = new Panel();
public ServerMsgPanel()
{
setSize(350, 300);
setBackground(Color.LIGHT_GRAY);
setLayout(new BorderLayout());
msgPanel.setLayout(new FlowLayout());
msgPanel.setSize(210, 210);
statusPanel.setLayout(new BorderLayout());
statusPanel.setSize(210, 50);
msgPanel.add(msgTextArea);
statusPanel.add(statusLabel, BorderLayout.WEST);
add(msgPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.NORTH);
}
}
3.2.7 服务器线程
为支持多组用户同时进行网络对弈,需要为服务器添加多线程的功能。
其中包含三个hash表,clientDataHash用于保存客户端套接字与输出流对应,其中套接口为key,输出流为value,clientNameHash为客户端套接口和客户名的对应,其中套接口为Key,客户名为value。
Chesspeerhash用于保存游戏创建者和游戏加入者的对应,游戏创建者为key,游戏加入者为value。
Feedback方法用于发送反馈信息给当前线程链接到主机的用户,首先从clientDataHash中取得当前的套接口和数据输出流,接着调用writeUtf方法输出。
public void Feedback(String feedBackMsg)
{
synchronized (clientDataHash)
{
DataOutputStream outputData = (DataOutputStream) clientDataHash
.get(clientSocket);
try
{
outputData.writeUTF(feedBackMsg);
}
catch (Exception eb)
{
eb.printStackTrace();
}
}
}
后面还包括像所有用户发送信息,向指定用户发送所有信息的方法
3.2.8 服务器端
服务器端用于生成服务器的界面,创建服务器以及初始化服务器线程中的三个哈希表,其中包含main方法,是服务器程序启动的入口。
// 用指定端口和面板创建服务器
public void createServer(int port, ServerMsgPanel serverMsgPanel) throws IOException
{
Socket clientSocket; // 客户端套接口
long clientAccessNumber = 1; // 连接到主机的客户数量
this.serverMsgPanel = serverMsgPanel; // 设定当前主机
try
{
serverSocket = new ServerSocket(port);
serverMsgPanel.msgTextArea.setText("服务器启动于:"
+ InetAddress.getLocalHost() + ":"//djr
+ serverSocket.getLocalPort() + "\n");
while (true)
{
// 监听客户端套接口的信息
clientSocket = serverSocket.accept();
serverMsgPanel.msgTextArea.append("已连接用户:" + clientSocket + "\n");
// 建立客户端输出流
DataOutputStream outputData = new
DataOutputStream(clientSocket
.getOutputStream());
// 将客户端套接口和输出流绑定
clientDataHash.put(clientSocket, outputData);
// 将客户端套接口和客户名绑定
clientNameHash
.put(clientSocket, ("新玩家" + clientAccessNumber++));
// 创建并运行服务器端线程
GameServerThread thread = new
GameServerThread(clientSocket,
clientDataHash, clientNameHash, chessPeerHash, serverMsgPanel);
thread.start();
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == clearMsgButton)
{ // 清空服务器信息
serverMsgPanel.msgTextArea.setText("");
}
if (e.getSource() == serverStatusButton)
{ // 显示服务器信息
try
{
serverMsgPanel.msgTextArea.append("服务器信息:"
+ InetAddress.getLocalHost() + ":"
+ serverSocket.getLocalPort() + "\n");
}
catch (Exception ee)
{
ee.printStackTrace();
}
}
if (e.getSource() == closeServerButton)
{ // 关闭服务器
System.exit(0);
}
}
3.2.9 客户端线程
因为是支持多组用户,所以要开发客户端的多线程,其原理类似于服务器的多线程,线程启动后将调用客户端的readUTF方法进行监听,病进入等待状态,当取得消息就调用dealWithMSg进行处理。
public void dealWithMsg(String msgReceived)
{
if (msgReceived.startsWith("/userlist "))
{ // 若取得的信息为用户列表
StringTokenizer userToken = new StringTokenizer(msgReceived, " ");
int userNumber = 0;
// 清空客户端用户列表
erList.removeAll();
// 清空客户端用户下拉框
while (userToken.hasMoreTokens())
{ // 当收到的用户信息列表中存在数据时
String user = (String) userToken.nextToken(" "); // 取得用户信息
if (userNumber > 0 && !user.startsWith("[inchess]"))
{ // 用户信息有效时
erList.add(user);// 将用户信息添加到用户列表中
}
userNumber++;
}
}
else if (msgReceived.startsWith("/yourname "))
{ // 收到的信息为用户本名时
firClient.chessClientName= msgReceived.substring(10); // 取得用户本名
firClient.setTitle("Java 五子棋客户端 " + "用户名:"
+ firClient.chessClientName); // 设置程序Frame的标题}
else if (msgReceived.equals("/reject"))
{ // 收到的信息为拒绝用户时
try
{
firClient.firPad.statusText.setText("不能加入游戏!");
erControlPad.cancelButton.setEnabled(false);
erControlPad.joinButton.setEnabled(true);
erControlPad.createButton.setEnabled(true);
}
catch (Exception ef)
{
}
erControlPad.joinButton.setEnabled(true);
}
else if (msgReceived.startsWith("/peer "))
{ // 收到信息为游戏中的等待时
firClient.firPad.chessPeerName = msgReceived.substring(6);
if (firClient.isCreator)
{ // 若用户为游戏建立者
firClient.firPad.chessColor = 1; // 设定其为黑棋先行
firClient.firPad.isMouseEnabled = true;
firClient.firPad.statusText.setText("黑方下...");
}
else if (firClient.isParticipant)
{ // 若用户为游戏加入者
firClient.firPad.chessColor = -1; // 设定其为白棋后性
firClient.firPad.statusText.setText("游戏加入,等待对手.");
}
}
else if (msgReceived.equals("/youwin"))
{ // 收到信息为胜利信息
firClient.isOnChess = false;
firClient.firPad.setVicStatus(firClient.firPad.chessColor);
firClient.firPad.statusText.setText("对手退出");
firClient.firPad.isMouseEnabled = false;
}
else if (msgReceived.equals("/OK"))
{ // 收到信息为成功创建游戏
firClient.firPad.statusText.setText("游戏创建等待对手");
}
else if (msgReceived.equals("/error"))
{ // 收到信息错误
}
}
3.2.10 客户端
客户端程序是提供五子棋博弈的界面,包括各种信息显示的面板和按钮等。
在客户端的各个属性中,clientSocket将作为客户端的套接口与服务器相连,并被用来创建监听网络的输入流和输出流。
public boolean connectToServer(String serverIP, int serverPort) throws Exception
{
try
{
// 创建客户端套接口
clientSocket = new Socket(serverIP, serverPort);
// 创建输入流
inputStream = new
DataInputStream(clientSocket.getInputStream());
// 创建输出流
outputStream = new
DataOutputStream(clientSocket.getOutputStream());
// 创建客户端线程
GameClientThread clientthread = new GameClientThread(this);
// 启动线程,等待聊天信息
clientthread.start();
isOnChat = true;
return true;
}
catch (IOException ex)
{
}
return false;
}
// 客户端事件处理
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == userControlPad.connectButton)
{ // 连接到主机按钮单击事件
host= firPad.host= userControlPad.ipInputted.getText(); // 取得主机地址
try
{
if (connectToServer(host, port))
{ // 成功连接到主机时,设置客户端相应的界面状态
userControlPad.connectButton.setEnabled(false);
userControlPad.createButton.setEnabled(true);
userControlPad.joinButton.setEnabled(true);
firPad.statusText.setText("连接成功,请等待!");
}
}
catch (Exception ei)
{
}
}
if (e.getSource() == userControlPad.exitButton)
{ // 离开游戏按钮单击事件
if (isOnChat)
{ // 若用户处于聊天状态中
try
{ // 关闭客户端套接口
clientSocket.close();
}
catch (Exception ed){}
}
if (isOnChess || isGameConnected)
{ // 若用户处于游戏状态中
try
{ // 关闭游戏端口
firPad.chessSocket.close();
}
catch (Exception ee){}
}
System.exit(0);
}
if (e.getSource() == userControlPad.joinButton)
{ // 加入游戏按钮单击事件
String selectedUser =
(String)erList.getSelectedItem(); // 取得要加入的游戏if (selectedUser == null ||
selectedUser.startsWith("[inchess]") ||
selectedUser.equals(chessClientName))
{ // 若未选中要加入的用户,或选中的用户已经在游戏,则给出提示信息
firPad.statusText.setText("必须选择一个用户!");
}
else
{ // 执行加入游戏的操作
try
{
if (!isGameConnected)
{ // 若游戏套接口未连接
if (firPad.connectServer(firPad.host,
firPad.port))
{ // 若连接到主机成功
isGameConnected = true;
isOnChess = true;
isParticipant = true;
userControlPad.createButton.setEnabled(false);
userControlPad.joinButton.setEnabled(false);
userControlPad.cancelButton.setEnabled(true);
firPad.gameThread.sendMessage("/joingame "
+
(String)erList.getSelectedItem() + " "
+ chessClientName);
}
}
else
{ // 若游戏端口连接中
isOnChess = true;
isParticipant = true;
userControlPad.createButton.setEnabled(false);
userControlPad.joinButton.setEnabled(false);
userControlPad.cancelButton.setEnabled(true);
firPad.gameThread.sendMessage("/joingame "
+
(String)erList.getSelectedItem() + " "
+ chessClientName);
}
}
catch (Exception ee)
{
isGameConnected = false;
isOnChess = false;
isParticipant = false;
userControlPad.createButton.setEnabled(true);
userControlPad.joinButton.setEnabled(true);
userControlPad.cancelButton.setEnabled(false);
}
}
}
if (e.getSource() == userControlPad.createButton)
{ // 创建游戏按钮单击事件
try
{
if (!isGameConnected)
{ // 若游戏端口未连接
if (firPad.connectServer(firPad.host, firPad.port))
{ // 若连接到主机成功
isGameConnected = true;
isOnChess = true;
isCreator = true;
userControlPad.createButton.setEnabled(false);
userControlPad.joinButton.setEnabled(false);
userControlPad.cancelButton.setEnabled(true);
firPad.gameThread.sendMessage("/creatgame "
+ "[inchess]" + chessClientName);
}
}
else
{ // 若游戏端口连接中
isOnChess = true;
isCreator = true;
userControlPad.createButton.setEnabled(false);
userControlPad.joinButton.setEnabled(false);
userControlPad.cancelButton.setEnabled(true);
firPad.gameThread.sendMessage("/creatgame "
+ "[inchess]" + chessClientName);
}
}
catch (Exception ec)
{
isGameConnected = false;
isOnChess = false;
isCreator = false;
userControlPad.createButton.setEnabled(true);
userControlPad.joinButton.setEnabled(true);
userControlPad.cancelButton.setEnabled(false);
ec.printStackTrace();
}
}
if (e.getSource() == userControlPad.cancelButton)
{ // 退出游戏按钮单击事件
if (isOnChess)
{ // 游戏中
firPad.gameThread.sendMessage("/giveup " + chessClientName);
firPad.setVicStatus(-1 * firPad.chessColor);
userControlPad.createButton.setEnabled(true);
userControlPad.joinButton.setEnabled(true);
userControlPad.cancelButton.setEnabled(false);
firPad.statusText.setText("请创建或加入游戏!");
}
if (!isOnChess)
{ // 非游戏中
userControlPad.createButton.setEnabled(true);
userControlPad.joinButton.setEnabled(true);
userControlPad.cancelButton.setEnabled(false);
firPad.statusText.setText("请创建或加入游戏!");
}
isParticipant = isCreator = false;
}
}
测试与分析
程序完成后,首先启动服务器,如图:
然后打开两个客户端界面,
分别点击连接服务器,然后可以看到出现了新玩家5,6
然后其中一个创建游戏,另一个再选中该用户,选择加入游戏,就可以进行游戏
完成一盘游戏后,会显示哪一方获胜,并且自动交换先后手进行下一盘游戏。
点击放弃游戏后,会提示重新创建或者加入游戏。
测试中遇到的问题是,用户列表中中文字符不能正常显示。
结论
本程序设计了五子棋的对弈算法,以及java网络编程,多线程编程等方法。
是用了Socket 来创建套接口,进行网络通信。
同时通过多线程的设计模式,可以同时支持多组用户进行游戏。
参考文献
Java2 核心技术机械工业出版社叶乃文等编译
Java面向对象程序设计清华大学出版社瞿中等译編。