在前一篇博文中完成了单机五子棋的编写,接下来将在其基础上完成联机模式,这里的联机模式采用主从形式,需要服务器端启动后,等待客户端连接以匹配游戏
目录
服务器类
为了进行联机模式,首先需要一个服务器类来完成ServerSocket的创建,其中封装listen()方法提供监听调用
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class GoBang_Server {
private final int port = 20000;
private final ServerSocket serverSocket;
GoBang_Server() {
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Socket listen() {
try {
return serverSocket.accept();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
联机模式按键
创建并在UI中添加联机模式按键,这就不做赘述了,重要的是在其监听器上加入创建线程用于建立连接并且接收来自客户端的信息
{
//更新模式标志位
board_listener.setOnline_flag(true);
board_listener.setOffline_flag(false);
chess_utils.show_dialog("联机模式开启成功,请等待对手连接到对局");
//创建线程用于通信
if(!board_listener.isThread_flag())
create_thread();
}
这里封装了一个create_thread,它创建了一个线程用于监听来自客户端的连接,并且创建一些通信流,并且每一次通信都按照一定的规则(开头的int是cmd),接收到命令后执行对应操作
public void create_thread(){
new Thread(() -> {
DataInputStream dataInputStream;
DataOutputStream dataOutputStream;
socket = goBang_server.listen();
try {
dataInputStream = new DataInputStream(socket.getInputStream());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
board_listener.setConnected(true);
try {
dataOutputStream = new DataOutputStream(socket.getOutputStream());
board_listener.setDataOutputStream(dataOutputStream);
regret_button_listener.setDataOutputStream(dataOutputStream);
startover_button_listener.setDataOutputStream(dataOutputStream);
exit_button_listener.setDataOutputStream(dataOutputStream);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
chess_utils.show_dialog("已配对到对手,请您先落子");
while (true) {
try {
int cmd = dataInputStream.readInt();
//下棋通信
switch (cmd) {...}
}
}
}
操作请求
在联机模式下,悔棋、重新开始等都需要经过对方同意,因此要发送相关cmd,这里以请求悔棋为例
//联机模式
else if(board_listener.isOnline_flag()){
//到我下了 不能悔棋
if(board_listener.isOnline_myturn() || board_listener.getWin_flag()!=0 || chess_flow.empty() || board_listener.isStartover_flag() || board_listener.isRegret_flag()){
chess_utils.show_dialog("当前不可悔棋");
}
else{
try {
board_listener.setRegret_flag(true);
dataOutputStream.writeInt(REGRET_REQUEST);
dataOutputStream.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
对方在收到这个REGRET_REQUEST的cmd后,会在线程中进行处理,这里贴出对悔棋请求的处理,这个处理是在上面贴出的创建的通信线程的switch语句中进行的
//悔棋通信
case REGRET_REQUEST:
int i=JOptionPane.showConfirmDialog(null,"对方请求悔棋,是否同意","提示",JOptionPane.YES_NO_OPTION);
//不同意悔棋
if(i==1){
//发送REGRET_REQUEST_DENY
dataOutputStream.writeInt(REGRET_REQUEST_DENY);
dataOutputStream.flush();
}
//同意悔棋
else{
//发送REGRET_REQUEST_APPROVE
dataOutputStream.writeInt((REGRET_REQUEST_APPROVE));
dataOutputStream.flush();
board_listener.setOnline_myturn(false);
int[]pos_1=chess_flow.pop();
int chess_flag=map[pos_1[0]][pos_1[1]];
if(chess_flag>0){
map[pos_1[0]][pos_1[1]]=0;
board_listener.setChess_flag(chess_flag);
panel_board.repaint();
}
}
break;
控制flag
在加入通信后,由于各自要维护棋局信息,因此要加入很多的用于控制的flag,这里不做展示了,大家可以自己思考,这里仅给出一些例子,比如在请求悔棋时不能进行落子、请求重新开始等,这就需要一个flag来进行控制!
客户端
客户端的基本架构与服务器一致,仅仅在先后行棋等方面不同!另外也不需要一个客户端类来维护socket,直接在online_button_listener中维护一个线程进行通信即可
至此,联机模式下的一些功能已经完善,这里附上 git仓库