JavaFx-----五子棋(单机版和对战版)
一.项目介绍
使用 JavaFx + MySql + MyBatis 实现单机和网络版五子棋对战.
二.功能介绍
1. 登录
-- 使用MyBatis和JDBC连接数据库, 实现登录功能
-- 使用I/O流,实现本地文件记住密码功能
2.注册
-- 使用MyBatis和JDBC连接数据库, 实现注册功能
-- 注册完密码后,返回登录界面,自动填充注册的用户名和密码
3.网络模式选择
4.单机版
-- 对战
-- 新局
-- 悔棋
-- 保存棋谱
-- 打开棋谱
5.网络版
-- 显示在线用户
-- 连接对战
三. 项目源码
package com.wn.main; import java.net.InetAddress; import java.net.UnknownHostException; import com.wn.pojo.Global; import com.wn.ui.LoginStage; import javafx.application.Application; import javafx.stage.Stage; public class MainApplication extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { //获取当前用户的本机IP地址 InetAddress inetAddress = null; try { inetAddress = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } //将本机ip存储到全局变量中 Global.myIp = inetAddress.getHostAddress(); new LoginStage().show(); } }
package com.wn.ui; import com.wn.CommonUtils.DAOUtils; import com.wn.CommonUtils.DataHanding; import com.wn.dao.UserDAO; import com.wn.dao.UserStatusDAO; import com.wn.pojo.Global; import com.wn.pojo.User; import com.wn.pojo.UserStatus; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; public class LoginStage extends Stage implements EventHandler<ActionEvent> { private Button loginBtn; private Button registerBtn; private TextField account; private PasswordField password; private CheckBox checkBox; public LoginStage() { setTitle("五子棋-登录"); Pane pane = new Pane(); Scene scene = new Scene(pane, 400, 250); setScene(scene); getIcons().add(new Image("Imgs/icon-2.jpg")); pane.setBackground(new Background(new BackgroundFill(Color.GAINSBORO, null, null))); // 设置窗口不可变 setResizable(false); // 标题:用户登录 Label title = new Label("用户登录"); title.setFont(new Font("KaiTi", 30)); title.setLayoutX(150); title.setLayoutY(30); // 账户标签 Label accountLabel = new Label("账户 : "); accountLabel.setFont(new Font("KaiTi", 20)); accountLabel.setLayoutX(80); accountLabel.setLayoutY(90); // 账户输入框 account = new TextField(); account.setLayoutX(150); account.setLayoutY(90); // 密码标签 Label passwordLabel = new Label("密码 : "); passwordLabel.setFont(new Font("KaiTi", 20)); passwordLabel.setLayoutX(80); passwordLabel.setLayoutY(140); // 密码输入框 password = new PasswordField(); password.setLayoutX(150); password.setLayoutY(140); // 记住密码 checkBox = new CheckBox(); checkBox.setLayoutX(80); checkBox.setLayoutY(180); Text text = new Text(100, 195,"记住密码"); text.setFont(new Font("KaiTi",15)); // 登录按钮 loginBtn = new Button("登\t录"); loginBtn.setLayoutX(80); loginBtn.setLayoutY(210); loginBtn.setDefaultButton(true); loginBtn.setOnAction(this); // 注册按钮 registerBtn = new Button("注\t册"); registerBtn.setLayoutX(250); registerBtn.setLayoutY(210); registerBtn.setOnAction(this); // 把元素添加到面板 pane.getChildren().addAll(title, accountLabel, account, passwordLabel, password, loginBtn, registerBtn,checkBox,text); // 如果用户刚注册,则在登录界面读取用户注册的账户和密码 String content = null; if (Global.account != null && Global.password != null) { this.account.setText(Global.account); this.password.setText(Global.password); //读取用户上一次在本机登录的账户和密码 }else if ((content=DataHanding.readLastAccount())!=null) { String[] strings = content.split(","); if (strings[strings.length-1].equals(Global.myIp)) { account.setText(strings[0]); password.setText(strings[1]); } } } public void login() { String accountText = account.getText(); String passwordText = password.getText(); Global.account = accountText; //用户名或密码不能为空 if (accountText.equals("")||passwordText.equals("")) { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("用户名或密码不能为空"); alert.show(); return; } //获取用户当前登录状态,如果已登录,不能重复登录 UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); UserStatus status = statusDAO.selectInfoByAccount(accountText); if (status.getStatus() == 1) { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("当前用户已在线,不能重复登录"); alert.show(); return; } // 获取UserDAO的实现类 UserDAO userDAO = DAOUtils.getMapper(UserDAO.class); // 根据用户输入的account获得User对象 User user = userDAO.getByAccount(accountText); if (user != null && user.getPassword().equals(passwordText)) { //登录成功,判断用户是否选择记住密码 if (checkBox.isSelected()) { //用户选择记住密码,调用记住密码方法 DataHanding.rememberLastAccount(accountText, passwordText,Global.myIp); } //更新用户状态为在线状态 statusDAO.updateStatusByAccount(accountText,Global.myIp, 1); // 已设置自动提交事务 // 打开游戏模式选择界面 new ModeChooseStage().show(); this.close(); } else { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("用户名或密码错误 ,登录失败"); alert.showAndWait(); } } @Override public void handle(ActionEvent event) { //获取用户点击的按钮 Button btn = (Button) event.getSource(); String text = btn.getText(); switch (text) { case "登\t录": login(); break; case "注\t册": new RegisterStage().show(); this.close(); break; } } }
package com.wn.ui; import com.wn.CommonUtils.DAOUtils; import com.wn.CommonUtils.DataHanding; import com.wn.dao.UserDAO; import com.wn.dao.UserStatusDAO; import com.wn.pojo.Global; import com.wn.pojo.User; import com.wn.pojo.UserStatus; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; public class LoginStage extends Stage implements EventHandler<ActionEvent> { private Button loginBtn; private Button registerBtn; private TextField account; private PasswordField password; private CheckBox checkBox; public LoginStage() { setTitle("五子棋-登录"); Pane pane = new Pane(); Scene scene = new Scene(pane, 400, 250); setScene(scene); getIcons().add(new Image("Imgs/icon-2.jpg")); pane.setBackground(new Background(new BackgroundFill(Color.GAINSBORO, null, null))); // 设置窗口不可变 setResizable(false); // 标题:用户登录 Label title = new Label("用户登录"); title.setFont(new Font("KaiTi", 30)); title.setLayoutX(150); title.setLayoutY(30); // 账户标签 Label accountLabel = new Label("账户 : "); accountLabel.setFont(new Font("KaiTi", 20)); accountLabel.setLayoutX(80); accountLabel.setLayoutY(90); // 账户输入框 account = new TextField(); account.setLayoutX(150); account.setLayoutY(90); // 密码标签 Label passwordLabel = new Label("密码 : "); passwordLabel.setFont(new Font("KaiTi", 20)); passwordLabel.setLayoutX(80); passwordLabel.setLayoutY(140); // 密码输入框 password = new PasswordField(); password.setLayoutX(150); password.setLayoutY(140); // 记住密码 checkBox = new CheckBox(); checkBox.setLayoutX(80); checkBox.setLayoutY(180); Text text = new Text(100, 195,"记住密码"); text.setFont(new Font("KaiTi",15)); // 登录按钮 loginBtn = new Button("登\t录"); loginBtn.setLayoutX(80); loginBtn.setLayoutY(210); loginBtn.setDefaultButton(true); loginBtn.setOnAction(this); // 注册按钮 registerBtn = new Button("注\t册"); registerBtn.setLayoutX(250); registerBtn.setLayoutY(210); registerBtn.setOnAction(this); // 把元素添加到面板 pane.getChildren().addAll(title, accountLabel, account, passwordLabel, password, loginBtn, registerBtn,checkBox,text); // 如果用户刚注册,则在登录界面读取用户注册的账户和密码 String content = null; if (Global.account != null && Global.password != null) { this.account.setText(Global.account); this.password.setText(Global.password); //读取用户上一次在本机登录的账户和密码 }else if ((content=DataHanding.readLastAccount())!=null) { String[] strings = content.split(","); if (strings[strings.length-1].equals(Global.myIp)) { account.setText(strings[0]); password.setText(strings[1]); } } } public void login() { String accountText = account.getText(); String passwordText = password.getText(); Global.account = accountText; //用户名或密码不能为空 if (accountText.equals("")||passwordText.equals("")) { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("用户名或密码不能为空"); alert.show(); return; } //获取用户当前登录状态,如果已登录,不能重复登录 UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); UserStatus status = statusDAO.selectInfoByAccount(accountText); if (status.getStatus() == 1) { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("当前用户已在线,不能重复登录"); alert.show(); return; } // 获取UserDAO的实现类 UserDAO userDAO = DAOUtils.getMapper(UserDAO.class); // 根据用户输入的account获得User对象 User user = userDAO.getByAccount(accountText); if (user != null && user.getPassword().equals(passwordText)) { //登录成功,判断用户是否选择记住密码 if (checkBox.isSelected()) { //用户选择记住密码,调用记住密码方法 DataHanding.rememberLastAccount(accountText, passwordText,Global.myIp); } //更新用户状态为在线状态 statusDAO.updateStatusByAccount(accountText,Global.myIp, 1); // 已设置自动提交事务 // 打开游戏模式选择界面 new ModeChooseStage().show(); this.close(); } else { Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("用户名或密码错误 ,登录失败"); alert.showAndWait(); } } @Override public void handle(ActionEvent event) { //获取用户点击的按钮 Button btn = (Button) event.getSource(); String text = btn.getText(); switch (text) { case "登\t录": login(); break; case "注\t册": new RegisterStage().show(); this.close(); break; } } }
package com.wn.ui; import com.wn.CommonUtils.DAOUtils; import com.wn.dao.UserStatusDAO; import com.wn.pojo.ChessMode; import com.wn.pojo.Global; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class ModeChooseStage extends Stage implements EventHandler<ActionEvent> { Pane pane; public ModeChooseStage() { setTitle("五林大会"); pane = new Pane(); Scene scene = new Scene(pane,460,260); setScene(scene); getIcons().add(new Image("Imgs/icon-2.jpg")); Image image = new Image("Imgs/Gobang.jpg",480,280,false,true,true); ImageView imageView = new ImageView(image); pane.getChildren().add(imageView); pane.setBackground(new Background( new BackgroundFill(Color.DARKOLIVEGREEN, null, null))); // 设置窗口不可变 setResizable(false); setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); statusDAO.setStatusByAccount(Global.account, 0); } }); Label title = new Label("选择游戏模式"); title.setFont(new Font("KaiTi", 30)); title.setLayoutX(155); title.setLayoutY(80); //添加单机版和网络部按钮 Button buttonOnline = new Button("网络版"); buttonOnline.setFont(new Font("KaiTi",20)); buttonOnline.setPrefSize(100, 60); buttonOnline.setLayoutX(280); buttonOnline.setLayoutY(140); buttonOnline.setOnAction(this); Button buttonSingle = new Button("单机版"); buttonSingle.setFont(new Font("KaiTi",20)); buttonSingle.setPrefSize(100, 60); buttonSingle.setLayoutX(80); buttonSingle.setLayoutY(140); buttonSingle.setOnAction(this); //将按钮添加进面板 pane.getChildren().addAll(title,buttonOnline,buttonSingle); } /** * 在线模式 * @author Dracarys */ public void playOnineVersion() { new OnlineConfigStage().show(); this.close(); Global.mode = ChessMode.NETWORK; } /** * 单机版按钮及单机版游戏事件 * @author Dracarys * @param pane */ public void playSingleVersion() { try { new GameStage().show(); Global.mode = ChessMode.SINGLE; this.close(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void handle(ActionEvent event) { Button btn = (Button)event.getSource(); switch (btn.getText()) { case "单机版": playSingleVersion(); break; case "网络版": playOnineVersion(); break; } } }
package com.wn.ui; import java.util.List; import com.wn.CommonUtils.DAOUtils; import com.wn.CommonUtils.NetUtils; import com.wn.CommonUtils.ReceiveThread; import com.wn.dao.UserStatusDAO; import com.wn.pojo.ConnectionMessage; import com.wn.pojo.Global; import com.wn.pojo.UserStatus; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class OnlineConfigStage extends Stage implements EventHandler<ActionEvent> { private Pane pane; //舞台面板 private TextField myPort; //本机端口 private TextField conIp; //连接ip private TextField conPort; //连接端口 private List<UserStatus> list; //在线用户列表 public OnlineConfigStage(){ setTitle("网络连接设置"); pane = new Pane(); Scene scene = new Scene(pane,600,400); setScene(scene); getIcons().add(new Image("Imgs/icon-2.jpg")); pane.setBackground(new Background(new BackgroundFill(Color.GAINSBORO, null, null))); // 设置窗口不可变 setResizable(false); addElement(); getOnlineUser(); //当用户退出程序时,将用户的状态设置为下线 setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); statusDAO.setStatusByAccount(Global.account, 0); } }); } private void addTipsInfo() { //在线玩家标题提示 Label title = new Label("当前在线玩家"); title.setFont(new Font("KaiTi", 30)); title.setLayoutX(350); title.setLayoutY(30); pane.getChildren().add(title); //在线用户信息行 String[] tipsInfo = {"用户名","连接IP","连接PORT"}; for (int i = 0; i < tipsInfo.length; i++) { Label Lable = new Label(tipsInfo[i]); Lable.setFont(new Font("KaiTi", 20)); Lable.setLayoutX(280+100*i); Lable.setLayoutY(80); pane.getChildren().add(Lable); } } private void addElement() { //网络连接设置标题提示 Label title = new Label("网络连接设置"); title.setFont(new Font("KaiTi", 30)); title.setLayoutX(50); title.setLayoutY(30); //连接地址文本提示 Label myPortLable = new Label("本机端口"); myPortLable.setFont(new Font("KaiTi", 20)); myPortLable.setLayoutX(60); myPortLable.setLayoutY(90); //添加本机端口输入框 myPort = new TextField(); myPort.setLayoutX(60); myPort.setLayoutY(120); myPort.setText("6666"); //测试port //连接端口文本提示 Label conIPLable = new Label("连接IP"); conIPLable.setFont(new Font("KaiTi", 20)); conIPLable.setLayoutX(60); conIPLable.setLayoutY(160); //添加连接IP输入框 conIp = new TextField(); conIp.setLayoutX(60); conIp.setLayoutY(190); conIp.setText("192.172.4.8"); //测试ip //连接端口文本提示 Label conPortLable = new Label("连接端口"); conPortLable.setFont(new Font("KaiTi", 20)); conPortLable.setLayoutX(60); conPortLable.setLayoutY(230); //添加连接PORT输入框 conPort = new TextField(); conPort.setLayoutX(60); conPort.setLayoutY(260); conPort.setText("6666"); //测试port //添加确定和取消按钮 Button conBtn = new Button("连\t接"); conBtn.setPrefSize(150, 30); conBtn.setLayoutX(60); conBtn.setLayoutY(320); conBtn.setOnAction(this); Button canBtn = new Button("取\t消"); canBtn.setPrefSize(150, 25); canBtn.setLayoutX(60); canBtn.setLayoutY(360); canBtn.setOnAction(this); pane.getChildren().addAll(title,myPortLable,myPort,conIPLable,conIp,conPortLable,conPort,conBtn,canBtn); } private void getOnlineUser() { //获取UserStatusDAO实现类 UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); //获取在线用户列表 list = statusDAO.selectInfoByStatus(1); if (list.size()<=1) { return; } addTipsInfo(); int i = 0; for (UserStatus userStatus : list) { if (!userStatus.getAccount().equals(Global.account)) { //添加用户名信息 Label accountLable = new Label(userStatus.getAccount()); accountLable.setFont(new Font("KaiTi", 20)); accountLable.setLayoutX(290); accountLable.setLayoutY(120+60*i); //添加ip信息 Label ipLable = new Label(userStatus.getIp()); ipLable.setFont(new Font("KaiTi", 20)); ipLable.setLayoutX(360); ipLable.setLayoutY(120+60*i); //添加port信息 Label portLable = new Label(userStatus.getPort()+""); portLable.setFont(new Font("KaiTi", 20)); portLable.setLayoutX(500); portLable.setLayoutY(120+60*i); pane.getChildren().addAll(accountLable,ipLable,portLable); i++; } } } public void connect(){ //当网络连接信息未填写时,弹窗提醒 if (myPort.getText().equals("") || conIp.getText().equals("") || conPort.getText().equals("")) { Alert alert = new Alert(AlertType.INFORMATION,"网络连接信息不能为空"); alert.initOwner(this); alert.showAndWait(); return; } //如果没有在线用户时,无法正常连接 if (list.size()<=1) { Alert alert = new Alert(AlertType.INFORMATION,"当前无在线用户,无法进行连接"); alert.initOwner(this); alert.showAndWait(); return; } //尝试将用户输入的端口信息转为int型 try { Global.myPort = Integer.parseInt(myPort.getText()); Global.oppoIp = conIp.getText(); Global.oppoPort = Integer.parseInt(conPort.getText()); } catch (NumberFormatException e1) { e1.printStackTrace(); return; } try { //启动线程,接收客户端连接 ReceiveThread receiveThread = new ReceiveThread(); Thread thread = new Thread(receiveThread); thread.start(); new GameStage().show(); //将用户输入的本机端口保存到数据库 UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); statusDAO.updatePortByAccount(Global.account, Global.myPort); //向服务端发送连接请求信息,连接信息中包含客户端的用户 NetUtils.sendMessage(new ConnectionMessage(Global.account)); } catch (InterruptedException e) { e.printStackTrace(); } this.close(); } @Override public void handle(ActionEvent event) { Button btn = (Button)event.getSource(); switch (btn.getText()) { case "连\t接": connect(); break; case "取\t消": new ModeChooseStage().show(); this.close(); break; } } }
package com.wn.ui; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.ArrayList; import java.util.List; import com.wn.CommonUtils.DAOUtils; import com.wn.CommonUtils.NetUtils; import com.wn.dao.UserStatusDAO; import com.wn.pojo.ButtonMessage; import com.wn.pojo.ChessMessage; import com.wn.pojo.ChessMode; import com.wn.pojo.ConnectionMessage; import com.wn.pojo.Global; import com.wn.pojo.Message; import com.wn.pojo.MessageTimes; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class GameStage extends Stage implements EventHandler<ActionEvent> { private List<Circle> chessList = new ArrayList<Circle>(); private boolean isBlack = true; //判断棋子是否是黑色 private boolean gameOver = false; //判断游戏是否结束 true游戏结束,false代表可以下棋 private int topMargin = 50;//上间距 private int leftMargin = 350;//上间距 private int gap = 50; //内间距 private int chessWidth = 1400; //棋盘宽 private int chessHeight = 830; //棋盘高 private int size = 15; //棋盘线条数 private int buttonWidth = 80; //按钮宽 private int buttonLength = 40; //按钮高 private int cicreRadius = 18; //棋子半径 private int i = 0; //打开棋谱记录当前是第几手棋 private boolean canPlay = false; //定义网络模式下,当前是否能下棋 Pane pane; public GameStage() throws InterruptedException { //设置窗口不可改变 // setResizable(false); //创建面板对象 pane = new Pane(); //创建场景对象 Scene scene = new Scene(pane,chessWidth,chessHeight); //将场景放进舞台 setScene(scene); getIcons().add(new Image("Imgs/icon-2.jpg")); Image image = new Image("Imgs/background.jpg",1400,830,false,true,true); ImageView imageView = new ImageView(image); pane.getChildren().add(imageView); setTitle("五子棋"); drawLine(); addButton(); Global.gameStage = this; playChess(); //当用户退出程序时,将用户的状态设置为下线 setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { exitGame(); } }); } /** * 根据接收到的Message对象,更新棋子信息 * @author Dracarys */ public void updateUI(Message message) { if (message instanceof ConnectionMessage) { ConnectionMessage conMessage = (ConnectionMessage)message; if (conMessage.getClientUser()!=null) { //有客户端连接时,弹窗提醒 Alert alert = new Alert(AlertType.CONFIRMATION,conMessage.getClientUser()+"已进入战斗!!"); alert.initOwner(this); alert.show(); } //接收到的消息为棋子信息消息 }else if (message instanceof ChessMessage) { ChessMessage chessMessage = (ChessMessage)message; double x = chessMessage.getX(); double y = chessMessage.getY(); double pieceX = x * gap + leftMargin; double pieceY = y * gap + topMargin; dropPiece(pieceX, pieceY); //接收到消息之后将标签改完false canPlay = false; return; //接收到的消息为按钮信息消息 }else if (message instanceof ButtonMessage) { ButtonMessage btnMessage = (ButtonMessage)message; String btnName = btnMessage.getBtnName(); switch (btnName) { case "新\t局": //接收到点击新局按钮的第一次消息 if (btnMessage.getTimes().equals(MessageTimes.FIRST)) { //弹窗提醒,让接收方选择是否同意开启新局 Alert alert = new Alert(AlertType.CONFIRMATION,"对方想开启新局"); alert.initOwner(this); alert.showAndWait(); //如果用户选择同意开启新局,则开启新局 if (alert.getResult() == ButtonType.OK) { startGame(); //然后用户不同意开启新局,将按钮消息的同意情况更改为false }else { //不同意,更改isAgree为false btnMessage.setAgree(false); } //向新局发起方发送消息 btnMessage.setTimes(MessageTimes.SECOND); NetUtils.sendMessage(btnMessage); }else if (btnMessage.getTimes().equals(MessageTimes.SECOND)) { Alert alert = new Alert(AlertType.INFORMATION); //对方不同意开启新局 if (!btnMessage.isAgree()) { alert.setContentText("对方不同意开启新局"); alert.initOwner(this); alert.showAndWait(); }else { alert.setContentText("对方同意开启新局"); alert.initOwner(this); alert.showAndWait(); startGame(); } } break; case "悔\t棋": //第一次接收到悔棋消息时,让接收方选择是否悔棋 if (btnMessage.getTimes().equals(MessageTimes.FIRST)) { //弹窗提醒,通过同意,就开启新局 Alert alert = new Alert(AlertType.CONFIRMATION,"对方想悔棋"); alert.showAndWait(); //如果用户选择同意悔棋,则直接悔棋,并发送消息 if (alert.getResult() == ButtonType.OK) { regretChess(); canPlay = false; //然后用户不同意悔棋,将按钮消息的同意情况更改为false }else { //不同意,更改isAgree为false btnMessage.setAgree(false); } //向悔棋发起方发送消息 btnMessage.setTimes(MessageTimes.SECOND); NetUtils.sendMessage(btnMessage); }else if (btnMessage.getTimes().equals(MessageTimes.SECOND)) { Alert alert = new Alert(AlertType.INFORMATION); //对方不同意悔棋 if (!btnMessage.isAgree()) { alert.setContentText("对方不同意悔棋"); alert.show(); }else { regretChess(); canPlay = false; Global.regretTimes = 1; } } break; } } } /** * 保存棋谱 * @author Dracarys * @param pane */ public void saveChessRecord() { if (!gameOver) { return; } //创建打开文件弹窗对象 FileChooser fileChooser = new FileChooser(); //设置弹窗标题 fileChooser.setTitle("保存棋谱"); //设置默认目录 fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); //设置文件后缀 fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("csv", "*.csv")); //打开文件保存对话框 File file = fileChooser.showSaveDialog(this); //点击取消,则文件为空,停止保存 if (file == null) { return; } try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) { for (Circle chess : chessList) { bw.write(chess.getCenterX()+","+chess.getCenterY()+","+chess.getFill()); bw.newLine(); } Alert alert = new Alert(AlertType.INFORMATION); alert.setContentText("保存棋谱成功"); alert.show(); } catch (Exception e) { } } /** * 打开棋谱 * @author Dracarys * @param pane */ public void openChessScore() { //打开棋谱仅在单机版有效,网络版点击弹框提醒 if (Global.mode.equals(ChessMode.NETWORK)) { Alert alert = new Alert(AlertType.INFORMATION,"该功能仅在单机版有效"); alert.showAndWait(); return; } //创建打开文件弹窗对象 FileChooser fc = new FileChooser(); //设置对象标题 fc.setTitle("打开棋谱"); //设置打开的初始路径 fc.setInitialDirectory(new File(System.getProperty("user.home"))); //设置打开的文件默认后缀 fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("csv", "*.csv")); //将选择的文件赋值给file对象 File file = fc.showOpenDialog(this); //如果文件为空则不继续打开 if (file == null) { return; } //打开棋谱时情况棋子集合和棋盘上的棋子 chessList.clear(); pane.getChildren().removeIf(c-> c instanceof Circle); i = 0; //创建缓冲流,读取棋谱文件 try (BufferedReader br = new BufferedReader(new FileReader(file)) ) { String content; while ((content = br.readLine())!=null) { String[] list = content.split(","); //根据文件棋子信息创建棋子 Circle chess = new Circle(Double.parseDouble(list[0]), Double.parseDouble(list[1]), cicreRadius,Color.web(list[2])); //将棋子添加进集合 chessList.add(chess); } } catch (Exception e) { } //产生三个按钮控制棋谱读取 String[] list = {"<",">","x"}; for (int i = 0; i < list.length; i++) { Button btn = new Button(list[i]); btn.setPrefSize(30, 30); btn.setLayoutX(1060); btn.setLayoutY(300+60*i); btn.setOnAction(this); pane.getChildren().add(btn); } } /** * 退出游戏 * @author Dracarys * @param pane */ public void exitGame() { Alert alert = new Alert(AlertType.CONFIRMATION); alert.setTitle("退出游戏确认"); alert.setHeaderText("点击确认按钮将退出游戏程序"); alert.setContentText("您确认退出吗?"); alert.showAndWait(); if (alert.getResult() == ButtonType.OK) { if (Global.mode.equals(ChessMode.SINGLE)) { System.exit(0); }else { UserStatusDAO statusDAO = DAOUtils.getMapper(UserStatusDAO.class); statusDAO.setStatusByAccount(Global.account, 0); System.exit(0); } } } /** * 悔棋 * @author Dracarys */ public void regretChess() { if (chessList.size()>=1&&gameOver == false) { chessList.remove(chessList.size()-1); pane.getChildren().remove(pane.getChildren().size()-1); isBlack = !isBlack; } } /** * 开始游戏事件 * @author Dracarys * @param pane */ public void startGame() { // if (!gameOver) { // Alert alert = new Alert(AlertType.INFORMATION,"游戏还未结束不能开始新局"); // alert.showAndWait(); // return; // } gameOver = false; isBlack = true; chessList.clear(); pane.getChildren().removeIf(c -> c instanceof Circle); } /** * 添加棋盘线 * @author Dracarys * @param pane */ public void drawLine() { //棋盘700*700,横竖14条线,每条先间隔50 //第一条横线起点(350,50),y起点(1050,50);第一条竖线x起点(350,50),y起点(350,650) //第二条横线x起点(350,100),y起点(1050,100);第二条竖线x起点(1050,50),y起点(1050,650) for (int i = 0; i < size; i++) { //横线 Line line1 = new Line(leftMargin, topMargin+i*gap, leftMargin+50*14, topMargin+i*gap); //竖线 Line line2 = new Line(leftMargin+i*gap, topMargin, leftMargin+i*gap, topMargin+50*14); pane.getChildren().add(line1); pane.getChildren().add(line2); } } //添加按钮 public void addButton() { String[] btnList = {"新\t局","保存棋谱","打开棋谱","悔\t棋","退\t出"}; for (int i = 0; i < btnList.length; i++) { Button button = new Button(btnList[i]); button.setPrefSize(buttonWidth, buttonLength); button.setLayoutX(leftMargin+75*i+buttonWidth*i); button.setLayoutY(size*topMargin+20); button.setOnAction(this); pane.getChildren().add(button); } } /** * 在鼠标点击的位置落子 * @author Dracarys * @param pane */ public void playChess() { pane.setOnMouseClicked(e -> { //网络版:判断用户是否已发送棋子消息,如果已发送 if (canPlay) { return; } if (gameOver) { return; } double x = e.getX(); //获取用户鼠标点击的坐标x值 double y = e.getY(); //获取用户鼠标点击的坐标y值 //左上顶点坐标(50,50),棋子半径为18,边界为坐标-棋子半径 //右下顶点坐标(750,750),棋子半径为18,边界为坐标+棋子半径 if (x < leftMargin - cicreRadius || x > chessWidth - leftMargin + cicreRadius) { return; } if (y < topMargin - cicreRadius || y > 800 - topMargin + cicreRadius) { //50-18 = 32 return; } //将鼠标的坐标x和y都换算成(1,1)->(14,14)范围 double xIndex = Math.round(((x - leftMargin) / gap)); double yIndex = Math.round(((y - topMargin) / gap)); //判断当前鼠标点的位置是否存在棋子 double pieceX = xIndex * gap + leftMargin; double pieceY = yIndex * gap + topMargin; if (hasPiece(pieceX, pieceY)) { return; } //落棋子 dropPiece(pieceX,pieceY); //创建棋子消息对象 if (Global.mode.equals(ChessMode.NETWORK)) { ChessMessage message = new ChessMessage(xIndex,yIndex); //发送棋子消息对象 NetUtils.sendMessage(message); canPlay = true; } }); } public void dropPiece(double x,double y) { Circle circle = new Circle(); //设置棋子落子的坐标 circle.setCenterX(x); circle.setCenterY(y); circle.setRadius(cicreRadius); if (isBlack) { circle.setFill(Color.BLACK); } else { circle.setFill(Color.WHITE); } pane.getChildren().add(circle); isBlack = !isBlack; Circle chess = new Circle(x, y,cicreRadius, isBlack ? Color.BLACK : Color.WHITE); chessList.add(chess); //判断游戏输赢 if (isWin(chess)) { Alert alert = new Alert(AlertType.INFORMATION); alert.setTitle("游戏结束"); alert.setHeaderText("游戏结果"); alert.setContentText(isBlack ? "白棋胜" : "黑棋胜"); alert.show(); gameOver = true; } } /** 判断当前坐标位置是否有棋子 * @author Dracarys * @param x * @param y * @return */ private boolean hasPiece(double x,double y) { //遍历数组 for (int i = 0; i < chessList.size(); i++) { Circle c = chessList.get(i); if (c.getCenterX() == x && c.getCenterY() == y) { return true; } } return false; } /** * 判断是否胜利 * @author Dracarys * @param chess * @return */ private boolean isWin(Circle chess) { int count = 1; //判断x轴向左边判断另外4个位置棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessLeft = new Circle(chess.getCenterX()-i*gap, chess.getCenterY(),cicreRadius,chess.getFill()); if (contain(chessLeft)) { count++; }else { break; } } //判断x轴向右边判断另外4个位子棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessRight = new Circle(chess.getCenterX()+i*gap, chess.getCenterY(),cicreRadius,chess.getFill()); if (contain(chessRight)) { count++; }else { break; } } if (count>=5) { return true; } count = 1; //判读y轴向上4个位置的棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessTop = new Circle(chess.getCenterX(), chess.getCenterY()-i*gap,cicreRadius,chess.getFill()); if (contain(chessTop)) { count++; }else { break; } } //判读y轴向下4个位置的棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessDown = new Circle(chess.getCenterX(), chess.getCenterY()+i*gap,cicreRadius,chess.getFill()); if (contain(chessDown)) { count++; }else { break; } } if (count>=5) { return true; } count = 1; //判读左斜线边4个棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessTopLeft = new Circle(chess.getCenterX()-i*gap, chess.getCenterY()-i*gap,cicreRadius,chess.getFill()); if (contain(chessTopLeft)) { count++; }else { break; } } //判读左斜线边4个棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessTopRight = new Circle(chess.getCenterX()+i*gap, chess.getCenterY()+i*gap,cicreRadius,chess.getFill()); if (contain(chessTopRight)) { count++; }else { break; } } if (count>=5) { return true; } count = 1; //判读右斜线边4个棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessDownLeft = new Circle(chess.getCenterX()+i*gap, chess.getCenterY()-i*gap,cicreRadius,chess.getFill()); if (contain(chessDownLeft)) { count++; }else { break; } } //判读右斜线边4个棋子是否同色 for (int i = 1; i < 5; i++) { Circle chessDownRight = new Circle(chess.getCenterX()-i*gap, chess.getCenterY()+i*gap,cicreRadius,chess.getFill()); if (contain(chessDownRight)) { count++; }else { break; } } if (count>=5) { return true; } return false; } /** * 判断棋子集合中是否包含当前棋子 * @author Dracarys * @param chess * @return */ public boolean contain(Circle chess) { for (Circle circle : chessList) { if (circle.getCenterX() == chess.getCenterX() && circle.getCenterY()==chess.getCenterY()&&circle.getFill().equals(chess.getFill())) { return true; } } return false; } /** * 重写用户点击按钮的方法 */ @Override public void handle(ActionEvent event) { Button source = (Button) event.getSource(); String text = source.getText(); switch (text) { case "新\t局": if (Global.mode.equals(ChessMode.SINGLE)) { startGame(); }else { ButtonMessage btnMessage = new ButtonMessage(text,true,MessageTimes.FIRST); NetUtils.sendMessage(btnMessage); } break; case "保存棋谱": saveChessRecord(); break; case "打开棋谱": openChessScore(); break; case "悔\t棋": if (gameOver) { return; } if (chessList.isEmpty()) { return; } if (Global.regretTimes>=1) { Alert alert = new Alert(AlertType.INFORMATION,"已经悔悔过棋, 不能再次悔棋"); alert.initOwner(this); alert.show(); return; } if (!canPlay) { Alert alert = new Alert(AlertType.INFORMATION,"不能悔棋"); alert.initOwner(this); alert.show(); return; } if (Global.mode.equals(ChessMode.SINGLE)) { regretChess(); }else if (Global.mode.equals(ChessMode.NETWORK)) { System.out.println(Global.regretTimes); //网络模式下,一方选择悔棋,发送悔棋消息给另一方,按钮为名称为悔棋,发送方同意悔棋,消息次数为第一次 NetUtils.sendMessage(new ButtonMessage("悔\t棋", true, MessageTimes.FIRST)); } break; case "退\t出": exitGame(); break; case "<": openChessScoreLast(); break; case ">": openChessScoreNext(); break; case "x": openChessScoreAll(); break; } } /** * 打开棋谱,点击x,显示所有棋子 * @author Dracarys */ private void openChessScoreAll() { for (int j = i; j < chessList.size(); j++) { pane.getChildren().add(chessList.get(j)); } pane.getChildren().remove(36); pane.getChildren().remove(36); pane.getChildren().remove(36); } /** * 打开棋谱,点击上一步按钮 * @author Dracarys */ private void openChessScoreLast() { if (i == 0) { return; } pane.getChildren().remove(pane.getChildren().size()-1); i--; } /** * 打开棋谱,点击下一步按钮 * @author Dracarys */ private void openChessScoreNext() { if (i == chessList.size()) { pane.getChildren().remove(36); pane.getChildren().remove(36); pane.getChildren().remove(36); return; } Circle chess = chessList.get(i); pane.getChildren().add(chess); i++; } }