综合运用java网络编程知识(必须用到流、线程池、TCP或UDP,以及安全通信技术)
(相关代码在底部)
游戏界面均使用JavaFX
相关文档:JavaFX中文文档
制作了一个简易的表,对应用户的账号密码经验值邮箱。扩展:表中可以加入md5密码串列,验证密码时通信传输的过程不是明文密码,而是经过md5加密的字符串。验证密码只需要对比表中对应md5即可。
用户的账号密码放在数据库表中,所以注册是需要检查用户名是否已经存在,如果存在则注册失败,相反则注册成功。
登录除了基本的账号密码检查之外,还要注意该用户是否已经登录,如果已经登录则提醒已登录并关闭游戏窗口。
截取屏幕生成图片保存棋盘和txt文本。
后续可以读取txt获取当时的游戏棋盘布局,自行扩展。
public class ServerConfig {
//服务器开启的UDP端口
public final static SocketAddress SERVER_ADDR = new InetSocketAddress("0.0.0.0", 6666);
//public final static int CLIENT_LISTEN_PORT_DEFAULT = 5000;
private static HashMap<String, Player> hostMap = new HashMap<String, Player>();//用于存放擂主信息
private static HashMap<String, Player> playerMap = new HashMap<String, Player>();//用于存放所有玩家的信息
private static HashMap<String,Player> playersComList=new HashMap<String, Player>();//用于存在所有在线玩家的信息
private static HashMap<String,Player> playingMap = new HashMap<String,Player>();//用于存放正在比赛的玩家
private static HashMap<String,Player> guanZhanMap = new HashMap<String,Player>();//存放观战的玩家
public static void addHost(String name, Player player){
hostMap.put(name, player);
}//添加擂主
public static void addComPlayer(String name ,Player player ){playersComList.put(name,player);}//添加聊天室在线玩家
public static void addPlayingPlayer(String name ,Player player ){playingMap.put(name,player);}//添加游戏正在对战玩家
public static boolean containHost(String name){
return hostMap.containsKey(name);
}//判断擂主是否存在
public static boolean containPlaying(String name){
return playingMap.containsKey(name);
}//判断擂主是否存在
public static boolean containPlayer(String name){
return playerMap.containsKey(name);
}//判断玩家是否存在
public static HashMap<String,Player> getHostMap(){return hostMap;}//获取擂主Map
public static HashMap<String, Player> getPlayerMap(){
return playerMap;
}//获取玩家Map
public static HashMap<String,Player> getPlayerComMap(){return playersComList;}//获取聊天室玩家Map
public static HashMap<String,Player> getGuanZhanMap(){return guanZhanMap;}//获取观战Map
public static Player getPlayer(String name){
return playerMap.get(name);
}//获取玩家
public static Player getComPlayer(String name){return playersComList.get(name);}//获取聊天室玩家
public static Player getGuanZhan(String name){return guanZhanMap.get(name);}//获取观战玩家
public static void addPlaying(String name, Player player){
playingMap.put(name, player);
}//添加在线玩家
public static void addPlayer(String name, Player player){
playerMap.put(name, player);
}//添加玩家
public static void addGuanZhanPlayer(String name,Player player){guanZhanMap.put(name,player);}//添加观战玩家
public static void delComPlayer(String name){
playersComList.remove(name);
}//删除聊天室玩家
public static void delHostPlayer(String name){ hostMap.remove(name); }//删除擂主
public static void delPlaying(String name){ playingMap.remove(name); }//删除在线玩家
public static void delMapPlayer(String name){playerMap.remove(name);}//删除玩家
public static void delGuanZhan(String name){guanZhanMap.remove(name);}//删除观战玩家
public static void delAllPlayer(String name){
delGuanZhan(name);
delMapPlayer(name);
delHostPlayer(name);
delComPlayer(name);
delPlaying(name);
}
//获取玩家地址
public static SocketAddress getPlayerAddr(String name){ return getPlayer(name).getAddress(); }
//获取聊天室玩家列表
public static String getComNamesList(){
String str = "";
Iterator<String> it = playersComList.keySet().iterator();
while(it.hasNext()){
str = str + it.next() + ":";
}
System.out.println("get:"+str);
return str;
}
//获取擂主列表
public static String getHostNamesList(){
String str = "";
Iterator<String> it = hostMap.keySet().iterator();
while(it.hasNext()){
str = str + it.next() + ":";
}
System.out.println("get:"+str);
return str;
}
}
import java.io.*;
public class Message implements Serializable{
int msgType;//信息类型,参看类MessageType中的定义
int status;//信息的状态:分为成功和失败
Object content;//信息的内容
Player fromPlayer;//信息的发送方
Player toPlayer;//信息的接收方
public Player getFromPlayer() {
return fromPlayer;
}
public void setFromPlayer(Player fromPlayer) {
this.fromPlayer = fromPlayer;
}
public Player getToPlayer() {
return toPlayer;
}
public void setToPlayer(Player toPlayer) {
this.toPlayer = toPlayer;
}
public int getMsgType() {
return msgType;
}
public void setMsgType(int msgType) {
this.msgType = msgType;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
public String toString(){
return "["+msgType +"," + status + ", " + fromPlayer + ", " + toPlayer + ", " + content;
}
/**
* 将信息Message的对象转换成字节数组(传递给DatagramPacket),用于socket通信
* @param obj
* @return
*/
public static byte[] convertToBytes(Message obj){
try(
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream ois = new ObjectOutputStream(baos);
) {
ois.writeObject(obj);
return baos.toByteArray();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
/**
* 将字节数组(从DatagramPacket中获取)转换成Message的对象。
* @param bytes
* @param offset
* @param length
* @return
*/
public static Message convertToObj(byte[] bytes, int offset, int length){
try (
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
){
return (Message) ois.readObject();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
}
public class MessageType {
public static final int SUCCESS = 0;
public static final int FAIL = -1;
public static final int JOIN_GAME = 1;//申请加入游戏的请求信息
public static final int JOIN_HOST = 2;//申请加入擂主的请求信息
public static final int CHALLENGE = 3;//挑战擂主的请求信息
public static final int CHALLENGE_RSP = 4;//挑战擂主的请求信息
public static final int UPDATE_HOST = 5;//更新擂主列表的信息
public static final int PLAY = 6;//玩家下了一步棋的信息
public static final int HUIQI = 8;//悔棋
public static final int HUIQI_RSP = 9;
public static final int SURRENDER = 10;//投降
public static final int DOGFALL = 11;//平局
public static final int DOGFALL_RSP = 12;//平局回应
public static final int JOIN_COM = 15;//加入聊天室
public static final int PUBLIC_CHAT = 16;//广播
public static final int PRIVATE_CHAT = 17;//私聊
public static final int UPDATE_COM = 20;//更新在线玩家的信息
public static final int QUIT_COM=21;//退出聊天室
public static final int QUIT_HOST=22;//退出擂台
public static final int QUIT_GAME=23;//退出游戏
public static final int SENDFILES=24;//发送图片请求
public static final int SENDFILES_RSP=25;//图片回应
public static final int GUIZHAN=26;//观战
public static final int QUI_GUANZHAN=27;//退出观战
}
public class Player implements Serializable{
String name;//玩家名字
SocketAddress address;//玩家的socket地址
int color;//玩家的棋子颜色
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public Player(String name, SocketAddress address){
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SocketAddress getAddress() {
return address;
}
public void setAddress(SocketAddress address) {
this.address = address;
}
public String toString(){
return "[" + name+"," + address + "]";
}
}
import java.security.MessageDigest;
import java.sql.*;
public class DataBase {
private static final String JDBC_DEIVER = "com.mysql.cj.jdbc.Driver";
private static final String DB_URL = "jdbc:mysql://localhost:3306/mysql?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT\n";
//?useUnicode=true&characterEncoding=utf8&useSSL=false
private static final String USER = "root";
private static final String PASS = "123456";
private static Connection conn = null;
private static Statement stmt = null;
private static ResultSet rs = null;
public static String string2MD5(String inStr){
MessageDigest md5 = null;
try{
md5 = MessageDigest.getInstance("MD5");
}catch (Exception e){
System.out.println(e.toString());
e.printStackTrace();
return "";
}
char[] charArray = inStr.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++){
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
/**
* 加密解密算法 执行一次加密,两次解密
*/
public static String convertMD5(String inStr){
char[] a = inStr.toCharArray();
for (int i = 0; i < a.length; i++){
a[i] = (char) (a[i] ^ 't');
}
String s = new String(a);
return s;
}
// 连接数据库
public static void connectDB() {
try {
Class.forName(JDBC_DEIVER);
//STEP 3:建立连接到数据库
// System.out.println("正在连接数据库");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
stmt = conn.createStatement();
} catch (ClassNotFoundException | SQLException e) {
System.out.println("连接数据库失败");
e.printStackTrace();
}
}
public static int checkUser(String name,String password){
System.out.println("加密后的密码: "+password);
password= convertMD5((password));//解密
System.out.println("解密后的密码: "+password);
int result = 99;
try {
String sqlContent = "SELECT COUNT(*) FROM game WHERE name='%s' and password='%s';";
String sql = String.format(sqlContent, name,password);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
result = rs.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
// 查询用户表中是否存在该用户名 返回值:0没有1有
public static int checkName(String name) {
int result = 99;
try {
String sqlContent = "SELECT COUNT(*) FROM game WHERE name='%s';";
String sql = String.format(sqlContent, name);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
result = rs.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
// 断开数据库
public static void closeDB() {
try {
if (stmt != null)
stmt.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
System.out.println("关闭数据库失败");
e.printStackTrace();
}
System.out.println("关闭数据库成功");
}
// 添加新用户 返回值:0失败1成功
public static int addUser(String name, String password, String email) {
try {
if (checkName(name) == 1) {
System.out.println("添加用户失败,该用户名已经存在。");
return 0;
} else {
String sqlC = "INSERT INTO game(name,password,email) VALUE('%s','%s','%s');";
String sql = String.format(sqlC, name, password, email);
stmt.executeUpdate(sql);
System.out.println("添加用户成功");
}
} catch (SQLException e) {
e.printStackTrace();
}
return 1;
}
// 增加经验
public static void addExc(String name) {
int exp = 0;
try {
String sqlContent = "SELECT * FROM game WHERE name='%s';";
String sql = String.format(sqlContent, name);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
exp = rs.getInt("exp") + 10;
// result=rs.getString("exc");
// result = rs.getInt(1);
}
String updata = "update game set exp='%s' where name='%s';";
String updataSql = String.format(updata, exp, name);
stmt.executeUpdate(updataSql);
System.out.println("用户:" + name + "增加10经验成功。");
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void delExc(String name){
int exp=0;
try {
String sqlContent = "SELECT * FROM game WHERE name='%s';";
String sql = String.format(sqlContent, name);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
exp = rs.getInt("exp") - 10;
// result=rs.getString("exc");
// result = rs.getInt(1);
}
String updata = "update game set exp='%s' where name='%s';";
String updataSql = String.format(updata, exp, name);
stmt.executeUpdate(updataSql);
System.out.println("用户:" + name + "减少10经验成功。");
} catch (SQLException e) {
e.printStackTrace();
}
}
// 查看用户信息
public static String selectUser(String name) {
String password = null;
String email = null;
// String grade=null;
int exp = 0;
try {
String sqlContent = "SELECT * FROM game WHERE name='%s';";
String sql = String.format(sqlContent, name);
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
password = rs.getString("password");
exp = rs.getInt("exp");
email = rs.getString("email");
// grade=rs.getString("grade");
}
} catch (SQLException e) {
e.printStackTrace();
}
String grade=grade(exp);
return "\n用户名:"+name + "\n密码:" + password + "\n经验:" + exp + "\n邮箱:" + email+ "\n段位:" + grade;
}
public static String grade(int exp){
String grade="";
if(exp<50){
grade="青铜";
}else if(exp<200){
grade="白银";
}else if(exp<500){
grade="黄金";
}else if(exp<1000){
grade="钻石";
}else if(exp<2500){
grade="王者";
}else{
grade="大神";
}
return grade;
}
}
import java.io.Serializable;
public class Point implements Serializable{
public int column, row,action;
public int color;
public Point(int column, int row, int color, int action){
this.column = column;
this.row = row;
this.color = color;
this.action = action;
}
public String toString(){
return "[" + this.column + "," + row + "," + color + "," + action + "]";
}
}
ClientConfig.java
public class ClientConfig {
//服务器的地址
public static final SocketAddress SERVER_ADDR
= new InetSocketAddress("0.0.0.0", 6666);
//本地的地址
public static final SocketAddress LOCAL_ADDR
= new InetSocketAddress("0.0.0.0", 1111);
public static final int ACTION_TYPE_ADD = 1;//新下棋
public static final int ACTION_TYPE_DEL = 0;//悔棋
public static final int COLOR_WHITE=2;
public static final int COLOR_BLACK=1;
public static final int COLOR_BLANK=0;
}
Chess5Pane.java
import CurriculumDesign.Server.Message;
import CurriculumDesign.Server.Point;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Chess5Pane extends Pane {
private int x_0 = 30, y_0 = 30; //棋盘左上角的位置
private int N = 9; //N*N的棋盘
private int k = 60; //方格宽(高)度
private int r = 20; //棋子(圆)半径
private int color = ClientConfig.COLOR_BLACK;//先手为黑子: 1表示黑子,2表示白子,0表示没有棋子
private int[][] data = new int[N + 1][N + 1];
public Competition competition;
public Chess5Pane() {
setMaxSize(N * k + x_0 * 2, N * k + y_0 * 2);
setMinSize(N * k + x_0 * 2, N * k + y_0 * 2);
//-fx-border-color:设置边框颜色;-fx-border-width设置边框宽度;-fx-background-color:设置背景颜色
setStyle("-fx-border-color:#EED8AE;-fx-background-color:#EED8AE;-fx-border-width:5");
setOnMouseClicked(this::mouseClickHandler);
//画棋盘
drawBoard();
}
public Competition getCompetition() {
return this.competition;
}
/**
* 画棋盘线
*/
public void drawBoard() {
//画横向线
for (int i = 0; i <= N; i++) {
getChildren().add(new Line(x_0, y_0 + i * k, x_0 + N * k, y_0 + i * k));
}
//画纵向线
for (int i = 0; i <= N; i++) {
getChildren().add(new Line(x_0 + i * k, y_0, x_0 + i * k, y_0 + N * k));
}
}
public void setColor(int color) {
this.color = color;
}
public void setCompetition(Competition com) { this.competition = com; }
public void setData(int[][] data){this.data=data;}
public int[][] getData() {
return data;
}
// public void setData(int[][] data) {
// this.data = data;
// }
/**
* 在指定棋盘下棋点绘制指定颜色的棋子(圆)
*
* @param row 棋盘行数
* @param column 棋盘列数
* @param color 棋子颜色,1为黑子,2为白子
*/
public void drawChess(int row, int column, int color) {
//根据给定的行列值,求出实际的坐标点
int x_c = x_0 + column * k;
int y_c = y_0 + row * k;
Circle circle = new Circle(x_c, y_c, r, color == 1 ? Color.BLACK : Color.WHITE);
getChildren().add(circle);
data[row][column] = color;
}
public void removeChess(int row, int column, int color) {
//根据给定的行列值,求出实际的坐标点
int x_c = x_0 + column * k;
int y_c = y_0 + row * k;
// Circle circle = new Circle(x_c,y_c,r,color==1?Color.BLACK:Color.WHITE);
Circle circle = new Circle(x_c, y_c, r, color == 1 ? Color.BLACK : Color.WHITE);
// getChildren().removeIf(a ->(Color.WHITE));
getChildren().remove(circle);
data[row][column] = 0;
}
public static String printArr1(int[][] arr) {
String str = "";
for (int x = 0; x < arr.length; x++) {
for (int y = 0; y < arr[x].length; y++) {
str = str + arr[x][y];
}
}
return str;
}
/**
* 响应鼠标事件,检查对应位置的落子情况,并在对应位置添加棋子
*
* @param e
*/
public void mouseClickHandler(MouseEvent e) {
if (e.getClickCount() == 2) {//鼠标双击
int column = (int) Math.round((e.getX() - x_0) / (k));
int row = (int) Math.round((e.getY() - y_0) / (k));
if (data[row][column] == 0) {
drawChess(row, column, color); //画棋子
try (DatagramSocket socket = new DatagramSocket(0)) {
// socket.setSoTimeout(5000);
Message msg = new Message();
msg.setFromPlayer(competition.getLocalPlayer());
msg.setToPlayer(competition.getRemotePlayer());
msg.setMsgType(6);
msg.setContent(new Point(column, row, color, ClientConfig.ACTION_TYPE_ADD));
byte[] msgBytes = Message.convertToBytes(msg);
DatagramPacket pout = new DatagramPacket(msgBytes, msgBytes.length,
ClientConfig.SERVER_ADDR);
socket.send(pout);
setMouseTransparent(true);
} catch (Exception ex) {
// TODO: handle exception
ex.printStackTrace();
}
//color= color==1?2:1;//变换棋子颜色
if (Judgement.judge(data) == color) {
competition.setResult(color);
setMouseTransparent(true);
}
} else {
Chess5Utils.showAlert("该位置已下子");
}
} else {
//System.out.println("click");
}
}
/**
* 1. 清除棋盘上所有棋子,
* 2. 同时将数组data的所有元素值重置为0,
*/
public void clear() {
//只移除棋子,在这里不能用getChildren().clear(),否则会将棋盘线也清除
getChildren().removeIf(shape -> (shape instanceof Circle));
//将数组data的所有元素值重置为0
data = new int[N + 1][N + 1];
}
}
Judgement.java
public class Judgement {
/** 连续多少子算赢 */
public static int MAX = 5;
/**
* 根据棋盘状态评判胜负。
* 若水平、垂直、斜线或反斜线4个方向中,存在连续MAX个相同颜色的棋子,则胜负已分!
* @param data 棋盘状态(二维数组),0无子,1黑子,2白子
* @return 返回棋局评判,0胜负未分,1黑胜,2白胜
*/
public static int judge(int[][] data){
int j1 = hLine(data);
if(j1>0) return j1;
int j2 = vLine(data);
if(j2>0) return j2;
int j3 = xLine(data);
if(j3>0) return j3;
int j4 = rxLine(data);
if(j4>0) return j4;
return 0;
}
/**
* 水平方向的胜负判断
* @param data 棋盘状态(二维数组),0无子,1黑子,2白子
* @return 返回棋局评判,0尚无胜负,1黑胜,2白胜
*/
private static int hLine(int[][] data){
int black=0,white=0;
for(int i=0; i<data.length; i++){
black=0; white=0;
for(int j=0; j<data[i].length; j++){
if(data[i][j]==1){
black++;
white = 0;
}
else if(data[i][j]==2){
white++;
black = 0;
}else{
black = white = 0;
}
if(black>=MAX) return 1;
if(white>=MAX) return 2;
}
}
return 0;
}
/**
* 垂直方向的胜负判断
* @param data 棋盘状态(二维数组),0无子,1黑子,2白子
* @return 返回棋局评判,0尚无胜负,1黑胜,2白胜
*/
private static int vLine(int[][] data){
int black=0,white=0;
for(int i=0; i<data.length; i++){
black=0; white=0;
for(int j=0; j<data[i].length; j++){
if(data[j][i]==1){
black++;
white = 0;
}
else if(data[j][i]==2){
white++;
black = 0;
}else{
black = white = 0;
}
if(black>=MAX) return 1;
if(white>=MAX) return 2;
}
}
return 0;
}
/**
* 斜线(右上-左下)方向
* 同一条线上,x+y等于常数(规律为0到N+N)。
* @param data 棋盘状态(二维数组),0无子,1黑子,2白子
* @return 返回棋局评判,0尚无胜负,1黑胜,2白胜
*/
private static int xLine(int[][] data){
int black=0,white=0;
//Bug: sum<=data.length => sum<=data.length*2-2
for(int sum=0; sum<=data.length*2-2; sum++){
black=0; white=0;
for(int i=0; i<=sum; i++) {//Bug: i i<=sum
int j = sum - i;
if(i>=data.length || i<0) continue;
if(j>=data.length || j<0) continue;
if(data[i][j]==1){
black++;
white = 0;
}
else if(data[i][j]==2){
white++;
black = 0;
}else{
black = white = 0;
}
if(black>=MAX) return 1;
if(white>=MAX) return 2;
}
}
return 0;
}
/**
* 反斜线(左上-右下)方向
* 同一条线上,x-y等于常数(规律为从负N-1到正N-1)
* @param data 棋盘状态(二维数组),0无子,1黑子,2白子
* @return 返回棋局评判,0尚无胜负,1黑胜,2白胜
*/
private static int rxLine(int[][] data){
int black=0,white=0;
for(int sub=-(data.length-1); sub<=data.length-1; sub++){
black=0; white=0;
for(int i=0; i<data.length; i++) {
int j = i - sub;
if(i>=data.length || i<0) continue;
if(j>=data.length || j<0) continue;
if(data[i][j]==1){
black++;
white = 0;
}
else if(data[i][j]==2){
white++; //Bug: black++ => white++
black = 0;
}else{
black = white = 0;
}
if(black>=MAX) return 1;
if(white>=MAX) return 2;
}
}
return 0;
}
}
Competition.java
public class Competition {
Player localPlayer, remotePlayer;
int[][]data;
int result;
public Player getLocalPlayer() {
return localPlayer;
}
public void setLocalPlayer(Player localPlayer) {
this.localPlayer = localPlayer;
}
public Player getRemotePlayer() {
return remotePlayer;
}
public void setRemotePlayer(Player remotePlayer) {
this.remotePlayer = remotePlayer;
}
public int[][] getData() {
return data;
}
public void setData(int[][] data) {
this.data = data;
}
public int getResult() {
return result;
}
public void setResult(int result) {
this.result = result;
}
}
服务器和客户端的多线程就不在这里展示了,避免文章过长。
源代码不免费喔,需要的小伙伴请私信我。