五子棋个人报告
一、个人负责任务
我完成的部分主要是服务端进程与多线程,客户端进程与多线程,服务端与客户端连接,以及服务端的GUI界面编写。确保在游戏过程中客户端与服务端的连接不会中断,客户端与服务端的信息传送不会出错。
二、实现功能与解释
1.实现服务器多线程处理信息:在游戏开始时,玩家点击连接,创建游戏等按钮时,客户端会向服务端传输信息,而服务端要分析这些信息并且反馈给客户端相应的信息。
public void dealWithMsg(String msgReceived){
String peerName;
if(msgReceived.startsWith("/")){
//更新用户列表
if("/list".equals(msgReceived)){
Feedback(getUserList());
//创建游戏
}else if (msgReceived.startsWith("/creatgame [inchess]")){
//获取服务器名
String gameCreaterName = msgReceived.substring(20);
//将用户端口放入用户列表中
clientNameHash.put(clientSocket, msgReceived.substring(11));
//将主机设置为等待状态
chessPeerHash.put(gameCreaterName, "wait");
Feedback("/yourname " + clientNameHash.get(clientSocket));
sendGamePeerMsg(gameCreaterName, "/OK");
sendPublicMsg(getUserList());
//收到的信息为加入游戏
}else if (msgReceived.startsWith("/joingame ")){
//StringTokenizer用于分隔字符串
StringTokenizer userTokens = new StringTokenizer(msgReceived," ");
String userToken;
String gameCreatorName;
String gamePaticipantName;
String[] playerNames = {"0","0"};
int nameIndex = 0;
while(userTokens.hasMoreTokens()){
userToken =userTokens.nextToken(" ");
if(nameIndex >= 1 && nameIndex <= 2){
playerNames[nameIndex - 1] = userToken;
}
nameIndex++;
}
gameCreatorName = playerNames[0];
gamePaticipantName = playerNames[1];
//游戏已经创建
if(chessPeerHash.containsKey(gameCreatorName)
&& "wait".equals(chessPeerHash.get(gameCreatorName))){
//增加游戏加入者的接口与名称对应
clientNameHash.put(clientSocket, ("[inchess]" + gamePaticipantName));
// 增加或修改游戏创建者与游戏加入者的名称的对应
chessPeerHash.put(gameCreatorName, gamePaticipantName);
sendPublicMsg(getUserList());
//发送信息给游戏加入者
sendGamePeerMsg(gamePaticipantName,("/peer " + "[inchess]" + gameCreatorName));
//发送游戏给游戏创建者
sendGamePeerMsg(gameCreatorName,("/peer " + "[inchess]" + gamePaticipantName));
//游戏未创建 则拒绝加入游戏
}else{
sendGamePeerMsg(gamePaticipantName,"/reject");
try{
closeClient();
}catch (Exception e){
e.printStackTrace();
}
}
//游戏中
} else if (msgReceived.startsWith("/[inchess]")) {
int firstLocation = 0;
int lastLocation = msgReceived.indexOf(" ",0);
peerName = msgReceived.substring((firstLocation + 1),lastLocation);
msgReceived = msgReceived.substring((lastLocation + 1));
if(sendGamePeerMsg(peerName,msgReceived)){
Feedback("/error");
}
//放弃游戏
}else if(msgReceived.startsWith("/giveup ")){
String chessClientName = msgReceived.substring(8);
//胜利方为加入游戏者,发送胜利信息
if(chessPeerHash.containsKey(chessClientName)
&& !"wait".equals(chessPeerHash.get(chessClientName))){
sendGamePeerMsg((String) chessPeerHash.get(chessClientName),"/youwin");
//删除退出游戏的用户
chessPeerHash.remove(chessClientName);
}
//胜利方为游戏创建者,发送胜利信息
if(chessPeerHash.containsValue(chessClientName)){
sendGamePeerMsg((String) getHashKey(chessPeerHash,chessClientName),"/youwin");
// 删除退出游戏的用户
chessPeerHash.remove(getHashKey(chessPeerHash, chessClientName));
}
//收到其他信息
}else{
int lastLocation = msgReceived.indexOf(" ",0);
if(lastLocation == -1){
Feedback("无效命令");
}
}
} else {
msgReceived = clientNameHash.get(clientSocket) + ">" +msgReceived;
serverMsgPanel.msgTextArea.append(msgReceived + "\n");
sendPublicMsg(msgReceived);
serverMsgPanel.msgTextArea.setCaretPosition(serverMsgPanel.msgTextArea.getText().length());
}
}
服务端处理信息中,可以看到服务端要给特定的客户端发送信息,还要发回信息给发送端,还有一些信息要发送给所有的客户端,因此我加入了三个方法来实现这三个功能
/**
*发送反馈信息给连接到主机的人
*/
public void Feedback(String feedBackMsg) {
DataOutputStream outputData = (DataOutputStream) clientDataHash.get(clientSocket);
try {
outputData.writeUTF(feedBackMsg);
} catch (Exception eb) {
eb.printStackTrace();
}
}
/**
*发送信息给指定的游戏中的用户
*/
public boolean sendGamePeerMsg(String gamePeerTarget, String gamePeerMsg) {
// 遍历以取得游戏中的用户的套接口
for (Enumeration enu = clientDataHash.keys(); enu.hasMoreElements();) {
Socket userClient = (Socket) enu.nextElement();
// 找到要发送信息的用户时
if (gamePeerTarget.equals(clientNameHash.get(userClient))
&& !gamePeerTarget.equals(clientNameHash.get(clientSocket))) {
// 建立输出流
DataOutputStream peerOutData = (DataOutputStream) clientDataHash.get(userClient);
try {
// 发送信息
peerOutData.writeUTF(gamePeerMsg);
} catch (IOException es) {
es.printStackTrace();
}
return false;
}
}
return true;
}
/**
*发送公开消息
*/
public void sendPublicMsg(String publicMsg) {
//遍历客户端口与流 elements()用于获取值的枚举
for (Enumeration enu = clientDataHash.elements(); enu.hasMoreElements();) {
DataOutputStream outputData = (DataOutputStream) enu.nextElement();
try {
outputData.writeUTF(publicMsg);
} catch (IOException es) {
es.printStackTrace();
}
}
}
2.实现客户端多线程处理信息:同样的服务端需要处理相应的信息,客户端也需要处理服务端发来的信息,因此在客户端多线程中也有处理信息的相应代码
public void dealWithMsg(String msgReceived) {
// 若取得的信息为用户列表
if (msgReceived.startsWith("/userlist ")) {
StringTokenizer userToken = new StringTokenizer(msgReceived, " ");
int userNumber = 0;
// 清空客户端用户列表
clientChess.userListPad.userList.removeAll();
// 当收到的用户信息列表中存在数据时
while (userToken.hasMoreTokens()) {
// 取得用户信息
String user = userToken.nextToken(" ");
// 用户信息有效时
if (userNumber > 0 && !user.startsWith("[inchess]")) {
// 将用户信息添加到用户列表中
clientChess.userListPad.userList.add(user);
}
userNumber++;
}
// 收到的信息为用户本名时
} else if (msgReceived.startsWith("/yourname ")) {
// 取得用户本名
clientChess.chessClientName = msgReceived.substring(10);
// 设置程序的标题
clientChess.setTitle("五子棋 " + "用户名:" + clientChess.chessClientName);
// 收到的信息为拒绝用户时
} else if ("/reject".equals(msgReceived)) {
try {
clientChess.chessBoard.statusText.setText("不能加入游戏!");
clientChess.userControllerPad.cancelButton.setEnabled(false);
clientChess.userControllerPad.joinButton.setEnabled(true);
clientChess.userControllerPad.createButton.setEnabled(true);
} catch (Exception ef) {
clientChess.userChatPad.chatTextArea
.setText("Cannot close!");
}
clientChess.userControllerPad.joinButton.setEnabled(true);
// 收到信息为游戏中的等待时
} else if (msgReceived.startsWith("/peer ")) {
clientChess.chessBoard.chessPeerName = msgReceived.substring(6);
// 若用户为游戏建立者
if (clientChess.isCreator) {
// 设定其为黑棋先行
clientChess.chessBoard.chessColor = 1;
clientChess.chessBoard.isMouseEnabled = true;
clientChess.chessBoard.statusText.setText("黑方下...");
// 若用户为游戏加入者
} else if (clientChess.isParticipant) {
// 设定其为白棋后性
clientChess.chessBoard.chessColor = -1;
clientChess.chessBoard.statusText.setText("游戏开始,等待对手...");
}
// 收到信息为胜利信息
} else if ("/youwin".equals(msgReceived)) {
clientChess.isOnChess = false;
clientChess.chessBoard.setVicStatus(clientChess.chessBoard.chessColor);
clientChess.chessBoard.statusText.setText("对手溜了!!!");
clientChess.chessBoard.isMouseEnabled = false;
// 收到信息为成功创建游戏
} else if ("/OK".equals(msgReceived)) {
clientChess.chessBoard.statusText.setText("游戏已创建,等待对手中...");
// 收到信息错误
} else if ("/error".equals(msgReceived)) {
clientChess.userChatPad.chatTextArea.append("发送错误,退出程序...\n");
} else {
clientChess.userChatPad.chatTextArea.append(msgReceived + "\n");
clientChess.userChatPad.chatTextArea.setCaretPosition(
clientChess.userChatPad.chatTextArea.getText().length());
}
}
3.客户端发送信息给服务端:在客户端的游戏界面中,各种按钮的点击都会实现一定的功能,而这些功能,需要客户端来发送信息给服务端,服务端处理完毕后反馈信息给客户端,调整客户端的状态,来实现联机下的操作。
这是重写了ActionListener接口中的actionPerformed方法
@Override
public void actionPerformed(ActionEvent e) {
//连接到主机 按钮单击事件
if (e.getSource() == userControllerPad.connectButton){
//获取主机地址
host = chessBoard.host = userControllerPad.ipInput.getText();
try{
// 成功连接到主机时,设置客户端相应的界面状态
if (connectToServer(host, port)) {
userChatPad.chatTextArea.setText("");
userControllerPad.connectButton.setEnabled(false);
userControllerPad.createButton.setEnabled(true);
userControllerPad.joinButton.setEnabled(true);
chessBoard.statusText.setText("连接成功,请等待!!!");
}
} catch (Exception ex) {
userChatPad.chatTextArea.setText("Sorry,不能连接!!!\n");
}
}
//离开游戏
if (e.getSource() == userControllerPad.exitButton){
//若用户处于游戏状态中
if(isOnChess || isGameConnected){
try{
//关闭游戏端口
chessBoard.chessSocket.close();
}catch (Exception ignored){}
}
System.exit(0);
}
// 加入游戏按钮单击事件
if (e.getSource() == userControllerPad.joinButton) {
// 取得要加入的游戏
String selectedUser = userListPad.userList.getSelectedItem();
// 若未选中要加入的用户,或选中的用户已经在游戏,则给出提示信息
if (selectedUser == null || selectedUser.startsWith("[inchess]") ||
selectedUser.equals(chessClientName)) {
chessBoard.statusText.setText("必须选择一个用户!");
} else {
// 执行加入游戏的操作
try {
// 若游戏套接口未连接
if (!isGameConnected) {
// 若连接到主机成功
if (chessBoard.connectServer(chessBoard.host, chessBoard.port)) {
isGameConnected = true;
isOnChess = true;
isParticipant = true;
userControllerPad.createButton.setEnabled(false);
userControllerPad.joinButton.setEnabled(false);
userControllerPad.cancelButton.setEnabled(true);
chessBoard.chessThread.sendMessage("/joingame "
+ userListPad.userList.getSelectedItem() + " "
+ chessClientName);
}
} else {
// 若游戏端口连接中
isOnChess = true;
isParticipant = true;
userControllerPad.createButton.setEnabled(false);
userControllerPad.joinButton.setEnabled(false);
userControllerPad.cancelButton.setEnabled(true);
chessBoard.chessThread.sendMessage("/joingame "
+ userListPad.userList.getSelectedItem() + " "
+ chessClientName);
}
} catch (Exception ee) {
isGameConnected = false;
isOnChess = false;
isParticipant = false;
userControllerPad.createButton.setEnabled(true);
userControllerPad.joinButton.setEnabled(true);
userControllerPad.cancelButton.setEnabled(false);
userChatPad.chatTextArea.setText("不能连接: \n" + ee);
}
}
}
//创建游戏
if(e.getSource() == userControllerPad.createButton){
try{
//若游戏端口未连接
if(!isGameConnected){
if(chessBoard.connectServer(chessBoard.host, chessBoard.port)){
//若成功连接到主机
isGameConnected = true;
isOnChess = true;
isCreator = true;
userControllerPad.createButton.setEnabled(false);
userControllerPad.joinButton.setEnabled(false);
userControllerPad.cancelButton.setEnabled(true);
chessBoard.chessThread.sendMessage("/creatgame " + "[inchess]" + chessClientName);
}
}else{
// 若游戏端口连接中
isOnChess = true;
isCreator = true;
userControllerPad.createButton.setEnabled(false);
userControllerPad.joinButton.setEnabled(false);
userControllerPad.cancelButton.setEnabled(true);
chessBoard.chessThread.sendMessage("/creatgame "
+ "[inchess]" + chessClientName);
}
} catch (Exception ex) {
isGameConnected = false;
isOnChess = false;
isCreator = false;
userControllerPad.createButton.setEnabled(true);
userControllerPad.joinButton.setEnabled(true);
userControllerPad.cancelButton.setEnabled(false);
ex.printStackTrace();
userChatPad.chatTextArea.setText("Sorry,不能连接: \n" + ex);
}
}
//退出游戏
if(e.getSource() == userControllerPad.cancelButton){
//游戏中
if(isOnChess){
chessBoard.chessThread.sendMessage("/giveup " + chessClientName);
chessBoard.setVicStatus(-1 * chessBoard.chessColor);
userControllerPad.createButton.setEnabled(true);
userControllerPad.joinButton.setEnabled(true);
userControllerPad.cancelButton.setEnabled(false);
chessBoard.statusText.setText("请选择创建房间或加入游戏!!!");
}if (!isOnChess){
// 非游戏中
userControllerPad.createButton.setEnabled(true);
userControllerPad.joinButton.setEnabled(true);
userControllerPad.cancelButton.setEnabled(false);
chessBoard.statusText.setText("请选择创建房间或加入游戏!!!");
}
isParticipant = isCreator = false;
}
}
三、学会的知识
通过本次课程设计,我学会了如何用git来管理自己的代码以及跟伙伴用git来完成我们的课程设计。并且在代码实现的过程中,运用socket编程去完成客户端与服务端的连接是我觉得在本次课设中我所遇到的困难,好在通过百度等方式,我也从中得到了启发,让客户端与服务端去发送信息交流来实现游戏的运行。还有游戏按钮的事件监听器的重写也是我本次课设中的一个重要部分。
四、改进部分
1.可以添加玩家实时聊天功能,提高游戏的趣味性
2.应该将服务端代码放在云服务器中跑,这样就不需要在同一局域网下来实现联机