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++;
	}
	
}

  

  

posted @ 2021-07-31 10:16  清霜半落沙痕浅  阅读(1226)  评论(1编辑  收藏  举报