主頁 >  其他 > java實作注冊登錄版五子棋對戰平臺(超詳細注釋,內含人機實作)

java實作注冊登錄版五子棋對戰平臺(超詳細注釋,內含人機實作)

2021-04-15 11:34:43 其他

目錄

  • 前言
  • 專案介紹
  • 功能演示
    • 登錄
    • 注冊
    • 選擇對手
    • 落子提示
    • 局時步時
    • 查看戰績
    • 落子五連
    • 悔棋
    • 聊天
    • 新局
    • 棋譜
      • 保存棋譜
      • 打開棋譜
    • 其它功能
      • 重繪
      • 上下頁
      • 認輸
      • 退出
      • 輪播圖片
      • 背景音樂
    • 求助小棋仙
  • 組織結構
  • 核心代碼決議
    • com.wupgig.login.UserLogin
    • com.wupgig.login.UserRegister
    • com.wupgig.robot.RobotPlay
      • 實作原理
      • 評分步驟
      • 核心代碼
    • com.wupgig.chess.Chess
    • com.wupgig.chess.ChessBoard
      • 顯示在線玩家
      • 處理對戰請求
      • 己方落子
      • 顯示對手的落子
    • 其它
  • 環境搭建
    • 開發工具
    • 開發環境
    • 搭建步驟
  • 啟動專案
  • 完整原始碼
    • Eclipse版本
    • IDEA版本
  • END

前言

這個落子難道真的沒得選擇了嗎?不!我不能輸!
出來吧!宇宙究極無敵巴啦啦小棋仙~


專案介紹

gobang專案是一個五子棋對戰平臺,基于JavaFX + Socket + JDBC + MySLQ 實作

包含注冊、登錄、選擇對手、落子提示、局時步時、查看戰績、悔棋、聊天、認輸、退出、新局、保存/打開棋譜、落子聲、背景音樂、背景圖片輪播和求助小棋仙等功能


功能演示

登錄

首先啟動該專案后會出現一個如下圖的登錄界面
登錄界面
默認會記住上一次登錄成功的賬號,選中記住密碼的復選框可以實作記住上一次登錄成功的密碼,

賬號密碼從資料庫中進行查詢,登錄失敗則提示賬號或密碼錯誤,登錄成功則打開棋盤界面,并關閉登錄界面,

限制重復登錄
在這里插入圖片描述
不能登錄一個已經在線的賬號


注冊

點擊注冊按鈕會打開一個如下的注冊界面
注冊界面
賬號密碼使用正則運算式進行判斷是否符合規則,
點擊注冊后會到資料庫中根據賬號查詢,如果賬號存在則提示賬號已存在,如果不存在則注冊成功,把賬號資訊保存到資料庫,密碼用MD5進行加密,關閉注冊界面,打開登錄界面,


選擇對手

在這里插入圖片描述


點擊對手名字,發送對戰請求
在這里插入圖片描述


收到對戰請求
在這里插入圖片描述


拒絕對戰請求
在這里插入圖片描述


無法向正在對戰的玩家發送請求
在這里插入圖片描述

顯示棋盤后會將當前所有在線的玩家的賬號分頁查詢顯示在棋盤如圖位置,點擊想要對戰的玩家即會向對方發送對戰申請
(無法向正在對戰、正在接受對戰請求、正在打譜、已經離線的玩家發送對戰請求)
如果成功發送對戰請求必須等對手回應(拒絕)后才能重新發送對戰請求,
對方同意后即可開始對戰,拒絕后會提示拒絕資訊,


落子提示

執棋和落子提示
對局開始后如上圖位置會顯示 :

我方賬號 我方棋子的顏色

VS

對手賬號 對手棋子的顏色

當前落子 棋子顏色

每次落子后當前落子的顏色會動態改變


局時步時

局時步時


自己超時
超時判負
自己超時后直接判負,并發送超時訊息和對局結果訊息給對手


對手超時
在這里插入圖片描述
收到對手超時的訊息后,直接判贏,收到對戰結果訊息后根據訊息所帶的資訊,保存結果和更新戰績表

局時總共10分鐘,步時一分鐘,每次輪到自己下棋的時候,局時、步時開始倒計時,輪到對手下棋的時候,局時暫停,步時重置為一分鐘,如果局時和步時兩者之中有一個為0后,就會直接判負,


查看戰績

查看自己的戰績
自己的戰績
點擊我的戰績按鈕直接從資料庫中查詢自己的戰績資訊,然后展示到界面上


查看對手的戰績
對手戰績
點擊對手戰績按鈕后,發送訊息給對手,對手收到訊息后,回復一個帶賬號的訊息,我方接受到訊息后根據對手的賬號查詢其戰績資訊后展示到頁面上


落子五連

落子
在這里插入圖片描述
在棋盤點擊的位置落下棋子,同時給對手發送落子訊息,攜帶棋子的資訊,對手接到訊息后將該棋子顯示到指定的位置


五連
在這里插入圖片描述
每次落子后判斷是否五連,如果五連,游戲結束,顯示贏棋彈窗,并發送對戰結果訊息給對手,由對手將保存結果和更新戰績表
每次顯示對手棋子時判斷是否五連,如果五連,游戲結束,顯示輸棋彈窗


悔棋

點擊悔棋按鈕
在這里插入圖片描述


對手同意悔棋請求
在這里插入圖片描述


對手拒絕悔棋請求
在這里插入圖片描述

點擊悔棋按鈕,給對手發送悔棋訊息,對手接受到悔棋訊息,棋盤上彈出提示框,

如果同意悔棋請求,則會移除棋盤上的最后一顆棋子,同時回傳同意悔棋的訊息給請求方,請求方接受到同意悔棋的訊息后,移除棋盤上的最后一顆棋子,

如果拒絕悔棋請求,則會給請求方回傳拒絕悔棋的訊息,請求方接受到拒絕悔棋的訊息后,會彈出一個提示框,

注:每個人只能成功悔棋一次,且輪到自己落子的時候無法悔棋


聊天

對戰時的親切問候
在這里插入圖片描述

對局結束后的友好交談
在這里插入圖片描述
透明的多行文本框
在這里插入圖片描述
在輸入框輸入訊息,點擊發送按鈕或者敲下回車鍵 ,顯示訊息在自己棋盤的指定位置,并發送聊天訊息給對手,對手收到聊天訊息后,將訊息顯示在棋盤的指定位置

即使當前對局結束,只要玩家還在同一個房間內,那么他們依然可以互相發送聊天訊息

房間概念:
玩家一向玩家二發起對戰請求,玩家二同意后,此時可以理解為玩家一和玩家二在同一個房間,此局游戲結束后,他們還是在當前房間,直到有一方退出游戲或者和別的玩家開始對戰了,那么此時玩家一和玩家二才不在同一個房間


新局

點擊新局按鈕
在這里插入圖片描述


拒絕新局在這里插入圖片描述

點擊新局按鈕后,給對手發送新局訊息,同時在自己的棋盤上顯示提示資訊

如果同意,先初始化自己的棋盤,然后發送同意新局的訊息給請求方,請求方收到同意訊息后,初始化自己的棋盤

如果拒絕,直接發送拒絕新局的訊息給請求方,請求方收到拒絕訊息后,顯示拒絕的訊息提示框


沒有對手
在這里插入圖片描述
值得注意的是,當兩個玩家在同一個房間的時候,新局按鈕才有效,什么意思呢,玩家一向玩家二發起對戰請求,玩家二同意后,此時可以理解為玩家一和玩家二在同一個房間,此局游戲結束后,他們還是在當前房間,直到有一方退出游戲或者和別的玩家開始對戰了,那么此時玩家一和玩家二才不在同一個房間


棋譜

保存棋譜

在這里插入圖片描述
點擊保存棋譜按鈕,通過io流將每個棋子的 x y 坐標和顏色分別保存到檔案中的每行,通過相同的分隔符隔開,方便打開棋譜時讀取

注:只有棋盤上有棋子且對局結束后才能保存棋譜


打開棋譜

在這里插入圖片描述
點擊打開棋譜按鈕,選擇之前保存過的棋譜檔案,進入打譜界面,可以通過上一步、下一步按鈕來還原之前對局的落子

注意:打開棋譜時,除了上圖的四個按鈕,其它多余的按鈕和文本都要隱藏或者清除掉,且只有對局結束后才能打開棋譜


其它功能

重繪

在這里插入圖片描述

點擊重繪按鈕,重新從資料庫中分頁查詢當前所有在線玩家,將其顯示到棋盤上的指定位置,并給每個文本系結點擊事件,實作點擊之后可以發送對戰請求


上下頁

在這里插入圖片描述
在這里插入圖片描述

點擊上一頁、下一頁按鈕,從資料庫分頁查詢在線玩家并展示到棋盤上的指定位置,并給每個文本系結點擊事件,實作點擊之后可以發送對戰請求

注意:當上一頁沒有資料時,上一頁按鈕失效,同理,當下一頁沒有資料時,下一頁按鈕失效


認輸

點擊按鈕
在這里插入圖片描述

確認提示框
在這里插入圖片描述
提示對手
在這里插入圖片描述
點擊認輸按鈕,顯示確認提示框,點擊確認,直接判負,發送認輸訊息和對戰結果訊息給對手,對手收到認輸訊息后,顯示贏棋提示框,并根據對戰結果保存結果和更新戰績表


退出

在這里插入圖片描述

在這里插入圖片描述

注意:在對戰時退出游戲,會直接判定為逃跑,同時發送逃跑訊息和對戰結果訊息通知對手,對手收到訊息后彈出贏棋提示框,根據對戰結果保存結果和更新戰績表


輪播圖片

點擊輪播按鈕前
在這里插入圖片描述

點擊輪播按鈕后
在這里插入圖片描述

點擊開始輪播按鈕,棋盤背景圖開始輪播,按鈕變成暫停輪播,再次點擊即可定格背景圖,輪播的速度和圖片的順序皆可隨便調整


背景音樂

暫停
在這里插入圖片描述
播放
在這里插入圖片描述


求助小棋仙

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述
點擊求助小棋仙按鈕,會彈出確認提示框,并提示還有幾次求助機會,點擊確認,小棋仙機器人會分析當前局勢,得到最終落子的位置,然后幫玩家在該位置落子,

注意:游戲未開始或沒輪到該玩家落子時,求助按鈕無效

小棋仙的具體實作邏輯,請查看代碼決議


組織結構

gobang
├── com-wupgig-dao-- 資料庫層
├── com-wupgig-service-- 業務邏輯層
├── com-wupgig-pojo-- 資料庫表中對應的物體類
├── com-wupgig-login-- 登錄、注冊
├── com-wupgig-record-- 我的戰績、對手戰績
├── com-wupgig-chess-- 棋盤、棋子
├── com-wupgig-robot-- 小棋仙機器人
├── com-wupgig-meassage-- 訊息類
├── com-wupgig-common -- 工具類和通用代碼
└── com-wupgig-main-- 啟動類


核心代碼決議

com.wupgig.login.UserLogin

核心代碼:

	// 記住賬號
	public void rememberAccount() {
		if (Global.myIP != null) {
			// 通過ip查詢賬號
			Address address = addressService.queryAccountByIP(Global.myIP);
			// 如果資料庫中有這個賬號,則直接將這個賬號寫入賬號框
			if (address != null) {
				this.account.setText(address.getAccount());
			}
		}
	}
	
	
	// 記住密碼
	public void isRememberPassword() {
		Address address = addressService.queryAccountByIP(Global.myIP);
		// 資料庫用戶地址表中有該賬號和ip地址
		if (address != null) {
			boolean isRemember = address.getRemember() == 1 ? true : false;
			check.setSelected(isRemember);
			// 如果用戶選擇了記住密碼
			if (isRemember) {			
				// 記住密碼到密碼框
				passwordField.setText(userService.queryUserByAccount(address.getAccount()).getPassword());
			}
		}
	}



	
	// 登錄邏輯
	private void login(Pane pane) {
		// 賬號或密碼不能為空
		if ("".equals(account.getText()) || "".equals(passwordField.getText())) {
			Alert alert = new Alert(AlertType.INFORMATION,"賬號或密碼不能為空!");
			alert.initOwner(this);
			alert.show();
			return;
		}
		// 根據輸入的賬號密碼查詢
		User user = userService.queryUserByAccountAndPassword(account.getText(), passwordField.getText());
		// 如果密碼正確或加密后的密碼正確,登錄成功,否則登錄失敗
		if (user == null) {
			// md5加密
			String md5Password = MD5Util.digest(passwordField.getText());
			User md5User = userService.queryUserByAccountAndPassword(account.getText(), md5Password);
			if (md5User == null) {
				Alert alert = new Alert(AlertType.INFORMATION,"賬號或密碼輸入錯誤!");
				alert.initOwner(this);
				alert.show();
				return;
			} else {
				// 判斷該玩家是否在線
				Sinfo sinfo = sinfoService.queryIPByAccount(account.getText());
				if (sinfo.getStatus() != 0) {
					Alert alert = new Alert(AlertType.INFORMATION,"賬號已在線,無法重復登錄!");
					alert.initOwner(this);
					alert.show();
					return;
				}
			}
		} else {
			Sinfo sinfo = sinfoService.queryIPByAccount(account.getText());
			if (sinfo.getStatus() != 0) {
				Alert alert = new Alert(AlertType.INFORMATION,"賬號已在線,無法重復登錄!");
				alert.initOwner(this);
				alert.show();
				return;
			}
		}
		
		// 將用戶賬號保存到Global類中
		Global.account = account.getText();
		// 查看用戶地址表中是否存在該ip和賬號
		Address address =  addressService.queryAccountByIP(Global.myIP);
		// 不存在就保存到資料庫
		if (address == null) {
			Address saveAddress = new Address();
			saveAddress.setAccount(Global.account);
			saveAddress.setAddress(Global.myIP);
			addressService.saveAddress(saveAddress);
		// 存在且賬號不相同
		} else if (!Global.account.equals(address.getAccount())) {
			// 更新賬號
			addressService.updateAccount(Global.myIP, Global.account);
		}
		
		// 將用戶是否記住密碼的選擇更新到資料庫
		if (check.isSelected()) {
			// 用戶選擇記住密碼
			addressService.updateRemember(1, Global.account);
		} else {
			// 用戶選擇不記住密碼
			addressService.updateRemember(0, Global.account);
		}
		
		Sinfo queryIPByAccount = sinfoService.queryIPByAccount(Global.account);
		
		// 如果對應的賬號下的ip發生了改變,則更新他的ip和在線空閑狀態即可
		if (!queryIPByAccount.getAddress().equals(Global.myIP)) {
			// 更新用戶ip地址
			sinfoService.updateIPByAccount(Global.account, Global.myIP);
			// 更改在在線狀態為空閑
			sinfoService.updateStatusByAccount(Global.account, 1);
			
		// 如果對應的賬號下的ip沒變,則更改為在線空閑狀態即可
		} else {
			// 更改在在線狀態為空閑
			sinfoService.updateStatusByAccount(Global.account, 1);
		}
		
		// 關閉登錄界面
		this.close();
		// 登錄后,關閉主界面
		this.stage.close();
		
		// 開啟server執行緒監聽對手客戶端在棋盤打開后發送的訊息
		ServerThread serverThread = new ServerThread();
		Thread boardThread = new Thread(serverThread);
		boardThread.start();
		
		// 打開棋盤界面
		ChessBoard chessBoard = new ChessBoard();
		chessBoard.show();
	}


com.wupgig.login.UserRegister

核心代碼:


	// 注冊
	private void register(Pane pane) {
		// 輸入框不能為空
		if ("".equals(account.getText()) || "".equals(password.getText()) 
				|| "".equals(confirmPassword.getText())) {
				Alert alert = new Alert(AlertType.INFORMATION,"賬號或密碼不能為空!");
				alert.initOwner(this);
				alert.show();
				return;
		}
		// 密碼和確認密碼不一致
		if (!(password.getText().equals(confirmPassword.getText()))) {
			Alert alert = new Alert(AlertType.INFORMATION,"輸入的兩次密碼不一致!");
			alert.initOwner(this);
			alert.show();
			return;
		}
		// 正則運算式規范賬號密碼 
		String patternAccount = "[\u4e00-\u9fa5_a-zA-Z0-9_]{1,15}";
		String patternPassword = "[a-zA-Z0-9_]{6,15}";
		boolean isPassword = Pattern.matches(patternPassword, password.getText());
		boolean isAccount = Pattern.matches(patternAccount, account.getText());
		if (!isAccount) {
			Alert alert = new Alert(AlertType.INFORMATION,"賬號需要為1-15位的中文,英文字母和數字及下劃線");
			alert.initOwner(this);
			alert.show();
			return;
		}
		if (!isPassword) {
			Alert alert = new Alert(AlertType.INFORMATION,"密碼需要為6-15位的英文字母和數字及下劃線");
			alert.initOwner(this);
			alert.show();
			return;
		}
		
		// 賬號已經存在
		String accountString = account.getText();
		User user = userService.queryUserByAccount(accountString);
		if (user != null) {
			Alert alert = new Alert(AlertType.INFORMATION,"賬號已存在!!!");
			alert.initOwner(this);
			alert.show();
			return;
		}
		// 將用戶資訊保存到資料庫中
		User nowUser = new User();
		nowUser.setAccount(accountString);
		// md5加密密碼
		nowUser.setPassword(MD5Util.digest(confirmPassword.getText()));
		nowUser.setRegTime(new Timestamp(System.currentTimeMillis()));
		Connection conn = null;
		try {
			conn = JdbcUtils.getConnection();
			JdbcUtils.disableAutocommit(conn);
			userService.saveUser(nowUser);
			// 保存離線用戶到資料庫
			Sinfo sinfo = new Sinfo();
			sinfo.setAccount(accountString);
			sinfo.setAddress(Global.myIP);
			sinfoService.saveSinfo(sinfo);
			JdbcUtils.commit(conn);
		} catch (Exception e) {
			JdbcUtils.rollback(conn);
		} finally {
			if (conn != null) {
				JdbcUtils.close(conn);
			}
		}

		// 顯示登錄界面
		UserLogin userLogin = new UserLogin();
		userLogin.show();
		// 關閉注冊界面
		this.close();
	}


com.wupgig.robot.RobotPlay

用于獲取機器人判斷出的落子坐標

實作原理

第一步:獲取當前棋盤上所有棋子附近不重復的空位(棋子周圍米字形所包含的空位位置即為棋子附近的空位),并將其以棋子物件的形式保存到集合中
第二步:為所有的空位打分,分數最高的那個空位即為小棋仙選擇的落子處,如果有多個位置的分數最高且相同,則隨機選擇一個位置落子,

第二步提到了一個為空位打分的概念,那么怎么打分呢?

為空位打分我們需要定義一張評分表作為評分的標準:


五子棋型及對應的分數
在這里插入圖片描述


四子棋型及對應的分數
在這里插入圖片描述


三子棋型及對應的分數
在這里插入圖片描述


二子棋型及對應的分數
在這里插入圖片描述


一子棋型及對應的分數
在這里插入圖片描述
該評分表對五連、活四、沖四、死四、活三、沖三、死三、活二、沖二、死二、活一、沖一、死一的棋型分別給予了相應的分數,有興趣的可以將跳活的棋型和對應的分數加進去,得到的評分表會可以使小棋仙考慮得更加全面

有了評分表之后就可以對空位進行評分了

評分步驟

橫向掃描:

以空位的左側為原點,向左掃描
如果遇到空格,記錄下左側為空格,停止向左掃描
如果遇到己方棋子,棋子個數加1,繼續向左掃描
如果遇到對方棋子,記錄下左側為對方棋子,停止向左掃描
如果已到達最左側,記錄下左側為墻,停止向左掃描

以空位為原點,向右掃描
如果遇到空格,記錄下右側為空格,停止向右掃描
如果遇到己方棋子,棋子個數加1,繼續向右掃描
如果遇到對方棋子,記錄下右側為對方棋子,停止向右掃描
如果已到達最右側,記錄下右側為墻,停止向右掃描

根據形成的棋型,對比評分表,得到該空位的評分score1


縱向掃描:
原理和橫向一樣
根據形成的棋型,對比評分表,得到該空位的評分score2


左斜方向掃描:
原理和橫向一樣
根據形成的棋型,對比評分表,得到該空位的評分score3


右斜方向掃描:
原理和橫向一樣
根據形成的棋型,對比評分表,得到該空位的評分score4


那么該空位的評分即為score1+score2+score3+score4

這就是這個空位的最終評分了嗎?

仔細想想即可發現,該空位的評分只考慮了己方棋子的棋型,而完全沒有考慮到對方棋子的棋型

如果只根據這個評分所得到的最終落子位置,則完全只會考慮進攻,而不會防守

所以我們還需要讓小棋仙去判斷對方棋子的棋型,并將對方棋型的評分和己方棋型的評分相加,最終評分最高的空位即為最終落子的位置,可謂是攻防皆備


核心代碼

/**
	 * 獲取該點在橫向上的得分
	* @param x 位置橫坐標
	* @param y 位置縱坐標
	* @param color 機器人落子的顏色
	* @param colors 所有位置棋子的顏色
	* @param size 橫縱棋子最大個數
	* @return 評分
	 */
	private static int getYScore(int x, int y, Color color, Color[][] colors, int size) {
		// 自己棋子的顏色
		Color myself  = color;
		
		// 對方棋子的顏色
		Color other = myself.equals(Color.BLACK) ? Color.ALICEBLUE : Color.BLACK;
		// 模擬落子
		colors[x][y] = myself;

		//左側、右側的狀態,用來記錄棋型
		int leftStatus = 0;
		int rightStatus = 0;
		// 相連棋子個數
		int count = 0;
		
		//掃描記錄棋型
		for (int i = x; i < size; i++) {
			if (myself.equals(colors[i][y]))
				count++;
			else {
				if (colors[i][y] == null)
					rightStatus = 1;// 右側為空
				else if (other.equals(colors[i][y]))
					rightStatus = 2;// 右側被對方堵住
				break;
			}
		}
		for (int i = x - 1; i >= 0; i--) {
			if (myself.equals(colors[i][y]))
				count++;
			else {
				if (colors[i][y] == null)
					leftStatus = 1;// 左側為空
				else if (other.equals(colors[i][y]))
					leftStatus = 2;// 左側被對方堵住
				break;
			}
		}
		// 恢復
		colors[x][y] = null;
		
		return getScoreBySituation(count, leftStatus, rightStatus);
	}



/**
	 * 根據棋型計算位置得分
	* @param count 連子個數
	* @param leftStatus 左側封堵情況 1:空位,2:對方或墻
	* @param rightStatus 右側封堵情況 1:空位,2:對方或墻
	* @return 分數
	 */
	private static int getScoreBySituation(int count, int leftStatus, int rightStatus) {
		int score = 0;
		
		// 五子情況
		if (count >= 5)
			score += 200000;// 贏了

		// 四子情況
		else if (count == 4) {
			if (leftStatus == 1 && rightStatus == 1)
				score += 50000;
			if ((leftStatus == 2 && rightStatus == 1) || (leftStatus == 1 && rightStatus == 2))
				score += 3000;
			if (leftStatus == 2 && rightStatus == 2)
				score += 1000;
		}

		//三子情況
		else if (count == 3) {
			if (leftStatus == 1 && rightStatus == 1)
				score += 3000;
			if ((leftStatus == 2 && rightStatus == 1) || (leftStatus == 1 && rightStatus == 2))
				score += 1000;
			if (leftStatus == 2 && rightStatus == 2)
				score += 500;
		}
		
		//二子情況
		else if (count == 2) {
			if (leftStatus == 1 && rightStatus == 1)
				score += 500;
			if ((leftStatus == 2 && rightStatus == 1) || (leftStatus == 1 && rightStatus == 2))
				score += 200;
			if (leftStatus == 2 && rightStatus == 2)
				score += 100;
		}
		
		//一子情況
		else if (count == 1) {
			if (leftStatus == 1 && rightStatus == 1)
				score += 100;
			if ((leftStatus == 2 && rightStatus == 1) || (leftStatus == 1 && rightStatus == 2))
				score += 50;
			if (leftStatus == 2 && rightStatus == 2)
				score += 30;
		}
		
		return score;
	}



	/**	
	 * 獲取需要打分的空位的集合
	 * 對每個非空位置,將其米字形周圍的空位添加到集合中
	 * 注意去掉重復的位置
	* @param arr 用于判斷棋盤上指定坐標是否有棋子
	* @param size 棋盤的橫豎線的條數
	* @return 需要打分的空位的集合
	 */
	private static List<Chess> getallMayRobotChess(boolean[][] arr, int size) {
		List<Chess> allMayRobotChess = new ArrayList<>();
		
		// 搜索棋盤獲取可行棋的點,存在重復,
		// 利用addToList(List<RobotChess> allMayRobotChess, int x, int y)去重
		// 原理為,遍歷棋盤上所有棋子,其周圍米字形(九宮格除了中間的剩下八個)內的空位即為可行棋的點
		for (int i = 0; i < size; i++)
			for (int j = 0; j < size; j++) {
				if (arr[i][j]) {
					if (j != 0 && !arr[i][j - 1])
						addToList(allMayRobotChess, i, j - 1);
					if (j != (size - 1) && !arr[i][j + 1])
						addToList(allMayRobotChess, i, j + 1);
					if (i != 0 && j != 0 && !arr[i - 1][j - 1])
						addToList(allMayRobotChess, i - 1, j - 1);
					if (i != 0 && !arr[i - 1][j])
						addToList(allMayRobotChess, i - 1, j);
					if (i != 0 && j != (size - 1) && !arr[i - 1][j + 1])
						addToList(allMayRobotChess, i - 1, j + 1);
					if (i != (size - 1) && j != 0 && !arr[i + 1][j - 1])
						addToList(allMayRobotChess, i + 1, j - 1);
					if (i != (size - 1) && !arr[i + 1][j])
						addToList(allMayRobotChess, i + 1, j);
					if (i != (size - 1) && j != (size - 1) && !arr[i + 1][j + 1])
						addToList(allMayRobotChess, i + 1, j + 1);
				}
			}
		return allMayRobotChess;
	}



	/**
	 * 為坐標為(x,y)的空位評分
	* @param x
	* @param y
	* @param color 機器人落子的顏色
	* @param colors 所有棋子的顏色
	* @param size 棋盤的橫豎線的條數
	* @return 分數
	 */
	private static int getScore(int x, int y, Color color, Color[][] colors, int size) {
		// 對方棋子顏色
		Color otherColor = color.equals(Color.BLACK) ? Color.ALICEBLUE : Color.BLACK;
		//己方棋子和對方棋子模擬落子計算分數和,以達到攻守皆備
		// 縱向得分
		int verticalScore = getVerticalScore(x, y, color, colors, size) + getVerticalScore(x, y, otherColor, colors, size);
		// 橫向得分
		int levelScore = getLevelScore(x, y, color, colors, size) + getLevelScore(x, y, otherColor, colors, size);
		// 正斜得分
		int skewScore1 = getSkewScore1(x, y, color, colors, size) + getSkewScore1(x, y, otherColor, colors, size);
		// 反斜得分
		int skewScore2 = getSkewScore2(x, y, color, colors, size) + getSkewScore2(x, y, otherColor, colors, size);
		return verticalScore + levelScore + skewScore1 + skewScore2;
	}


com.wupgig.chess.Chess

棋子類

/**
 * 棋子類,里面包含棋子的顏色,和在棋盤上的x y坐標
 * 和該棋子在小棋仙幫忙落子時評估的分數
 */
public class Chess {
	// 棋子在棋盤上的x軸坐標
	private int x;
	// 棋子在棋盤上的y軸坐標
	private int y;
	// 棋子顏色
	private Color color;
	// 小棋仙對空位評估的分數
	private int score;

	public Chess(int x, int y, Color color) {
		this.x = x;
		this.y = y;
		this.color = color;
	}

	public Chess(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	// get、set方法
}



com.wupgig.chess.ChessBoard

棋盤類,所有類中最重要的一個類


下面會經常用到這個方法

NetUtils.sendMessage(message, oppoIP)
/**
 * 客戶端給服務端發送訊息的工具類
 */
public class NetUtils {
	/**
	 * 客戶端給服務器發送訊息
	* @param message 需要發送的訊息
	 */
	public static void sendMessage(Message message, String oppoIP) {
		try (Socket socket = new Socket(oppoIP, Global.oppoPort);
				ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
			oos.writeObject(message);
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			Alert alert = new Alert(Alert.AlertType.ERROR, "連接對手出錯!請稍后再試!");
			alert.showAndWait();
		}
	}
}

該方法用于客戶端給服務端發送訊息,即對戰雙方一方給另一方發送訊息,而在com.wupgig.chess.UserLogin類中已啟動服務端,代碼如下

		// 開啟server執行緒監聽對手客戶端在棋盤打開后發送的訊息
		ServerThread serverThread = new ServerThread();
		Thread boardThread = new Thread(serverThread);
		boardThread.start();


// 接受客戶端發送的訊息
public class ServerThread implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		ServerSocket serverSocket = null;
		try {
			// 創建服務器端的ServerSocket,指明自己的埠號
			serverSocket = new ServerSocket(Global.myPort);
			
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			// 出現例外后,終止該執行緒
			return;
		}
		// 一直監聽客戶端的訊息
		while (true) {
			// 呼叫accept()表示接受來自客戶端的socket
			try (Socket socket = serverSocket.accept()) {
				// 獲取客戶端的輸入流
				ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
				// 將流中的訊息物件讀取出來
				Message message = (Message)ois.readObject();
				// 處理訊息,指定將訊息發送到ChessBoard類中的upDateUI方法里面
				Platform.runLater(() -> Global.chessBoard.upDateUI(message));

			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}
}

值得注意的是:

// 處理訊息,指定將訊息發送到ChessBoard類中的upDateUI方法里面
				Platform.runLater(() -> Global.chessBoard.upDateUI(message));

這行代碼,會將服務端接受到的訊息傳到 com.wupgig.chess.ChessBoard中的 void upDateUI(Message message) 方法中,所以處理訊息的代碼,會寫在upDateUI方法中

顯示在線玩家

	/**
	 * 分頁查詢所有在線玩家賬號名,并顯示在棋盤上
	* @param index 分頁查詢起始索引
	* @param size 每頁的數量
	 */
	private void queryAllAccountShowSinfo(int index, int size) {
		// 分頁查詢所有在線用戶,一頁顯示兩個在線用戶
		List<Sinfo> list = sinfoService.queryAllByLimit(index, size);
		// 移除棋盤上的在線玩家
		if (!textList.isEmpty()) {
			pane.getChildren().removeAll(this.textList);
			textList.clear();
		}
		this.showSinfo(list);
	}


	/**
	 * 顯示在線玩家的賬號名在棋盤右上方
	 * 并給每個顯示的玩家名系結點擊發送對戰請求事件
	* @param list 所有在線玩家的集合
	 */
	private void showSinfo(List<Sinfo> list) {
		
		int count = 0;
		for (Sinfo sinfo : list) {		
			Text text = new Text(770, 160 + (count++ * 40), 
					sinfo.getAccount() + "(" + (sinfo.getStatus() == 1 ? "空閑" : "忙碌") +  ")");
			text.setFill(Color.MAGENTA);
			text.setFont(Font.font("宋體", 
					 FontPosture.REGULAR, 25));
			// 加入文本集合
			this.textList.add(text);
			pane.getChildren().add(text);
			
			//給每個顯示的玩家名系結點擊發送對戰請求事件
			text.setOnMouseClicked(e -> {
				// 游戲已經開始
				if (!gameOver) {
					return;
				}
				// 滑鼠點擊玩家賬號名后,從資料庫中重新查詢玩家當前在線狀態,
				// 防止別的玩家棋盤展示的是之前的在線玩家,可能已經離線
				Sinfo nowSinfo = sinfoService.queryIPByAccount(sinfo.getAccount());
				// 已經發送過對戰請求
				if (isSend) {
					Alert alert = new Alert(AlertType.INFORMATION,"已經發送過對戰請求,請耐心等待!");
					alert.initOwner(this);
					alert.show();
					return;
				}
				// 對方正在對戰中
				if (nowSinfo.getStatus() == 2) {
					Alert alert = new Alert(AlertType.INFORMATION, sinfo.getAccount() + "正在激戰中,請換個對手!");
					alert.initOwner(this);
					alert.show();
					return;
				}
				// 對方離線
				if (nowSinfo.getStatus() == 0) {
					Alert alert = new Alert(AlertType.INFORMATION, sinfo.getAccount() + "已經離線,請換個對手!");
					alert.initOwner(this);
					alert.show();
					return;
				}
				// 不能和自己對戰
				if (sinfo.getAccount().equals(Global.account)) {
					Alert alert = new Alert(AlertType.INFORMATION, "你個憨憨,點自己干吉爾!");
					alert.initOwner(this);
					alert.show();
					return;
				}
				
				// 獲取對手ip
				Global.oppoIP = sinfoService.queryIPByAccount(sinfo.getAccount()).getAddress();
				// 取反
				this.isSend = !isSend;
				// 給對手發送對戰請求訊息
				GameRequestMeaasge gameRequestMeaasge = new GameRequestMeaasge();
				gameRequestMeaasge.setAccount(Global.account);
				gameRequestMeaasge.setRequestType(GameRequestMeaasge.GAME_REQUEST);
				this.waitText.setText("已給" +  sinfo.getAccount() + "發送對戰請求,請耐心等待……");
				
				NetUtils.sendMessage(gameRequestMeaasge, Global.oppoIP);
				// 請求對戰后由于未知原因會停止背景音樂,需要繼續播放背景音樂
				mediaPlayer.play();
				musicButton.setText("暫停音樂");
				playMusic = !playMusic;
			});
		}
		
	}

處理對戰請求

之前有個房間的概念:玩家一向玩家二發起對戰請求,玩家二同意后,此時可以理解為玩家一和玩家二在同一個房間,此局游戲結束后,他們還是在當前房間,直到有一方退出游戲或者和別的玩家開始對戰了,那么此時玩家一和玩家二才不在同一個房間

這里有個臨時對手ip的概念,就是為了能讓同一房間的玩家點擊新局按鈕后能夠在來一局,當玩家一和玩家二下完一盤棋后,玩家二又去和玩家三開始下棋,此時玩家二就需要通知玩家一,我退出房間了啊,趕緊把我的臨時ip清掉,我們已經不可能通過新局再次開始游戲了,別了~

同理,游戲結束后,在同一房間里還能聊天也是通過臨時ip實作的

值得注意的是,不加個臨時ip的話,上述功能也完全能實作,不過需要判斷的邏輯就會繁瑣很多,所以最終我選擇了添加臨時ip

		// 如果是對戰請求訊息
		else if (message instanceof GameRequestMeaasge) {
			this.gameRequestMeaasge(message);
		}

	/**
	 * 對戰請求訊息
	* @param message
	 */
	private void gameRequestMeaasge(Message message) {
		GameRequestMeaasge gameRequestMeaasge = (GameRequestMeaasge)message;

		// 如果是請求訊息
		if (gameRequestMeaasge.getRequestType() == GameRequestMeaasge.GAME_REQUEST) {
			if (this.isAccept) {
				// 我方正在接受對戰請求
				// 不同意
gameRequestMeaasge.setRequestType(GameRequestMeaasge.GAME_REFUSE);
			    // 發送訊息
            	NetUtils.sendMessage(gameRequestMeaasge, sinfoService.queryIPByAccount(gameRequestMeaasge.getAccount()).getAddress());
				return;
			}
			// 已接受對戰請求
			this.isAccept = true;
			// 更新對手ip
			Global.oppoIP = sinfoService.queryIPByAccount(gameRequestMeaasge.getAccount()).getAddress();
			Alert alert = new Alert(AlertType.CONFIRMATION, gameRequestMeaasge.getAccount() + "請求一戰,是否給個面子?",
					new ButtonType("拒絕",  ButtonData.NO), 
					new ButtonType("同意",  ButtonData.YES));
			alert.initOwner(this);
			
			Optional<ButtonType> button = alert.showAndWait();
			// 如果同意
			if (button.get().getButtonData() == ButtonData.YES) {
				this.stopThread();
				// 告訴原先對手,讓他死心(清除臨時對手ip)
				if (Global.temporaryOppoIP != null) {
				    // 發送訊息
	            	NetUtils.sendMessage(new EscapeMessage(), Global.temporaryOppoIP);
				}
				// 更新臨時對手ip
				Global.temporaryOppoIP = Global.oppoIP;
				
				// 隨機選擇棋子顏色
				this.selectColor();
				
				// 游戲初始化
				this.startNew(gameRequestMeaasge.getAccount());
				
				// 將自己的賬號放入訊息類中
				gameRequestMeaasge.setAccount(Global.account);
				// 發送訊息
				gameRequestMeaasge.setRequestType(GameRequestMeaasge.GAME_AGRRE);
            	NetUtils.sendMessage(gameRequestMeaasge, Global.oppoIP);
				
			} else {
				// 更新對手ip
				Global.oppoIP = sinfoService.queryIPByAccount(gameRequestMeaasge.getAccount()).getAddress();
				// 拒絕后變為沒接受對戰請求
				this.isAccept = false;
				// 如果不同意
				gameRequestMeaasge.setRequestType(GameRequestMeaasge.GAME_REFUSE);
				
			    // 發送訊息
            	NetUtils.sendMessage(gameRequestMeaasge, Global.oppoIP);
				// 移除對手ip
				Global.oppoIP = null;
			}
			
		// 同意對戰請求
		} else if (gameRequestMeaasge.getRequestType() == GameRequestMeaasge.GAME_AGRRE) {
			this.stopThread();
			// 告訴原先對手,讓他死心,我不在愛你了(清除臨時對手ip)
			if (Global.temporaryOppoIP != null) {
			    // 發送訊息
            	NetUtils.sendMessage(new EscapeMessage(), Global.temporaryOppoIP);
			}
			// 更新臨時對手ip
			Global.temporaryOppoIP = Global.oppoIP;

			// 初始化資料
			this.startNew(gameRequestMeaasge.getAccount());
			Alert alert = new Alert(AlertType.INFORMATION);
			alert.setContentText(gameRequestMeaasge.getAccount() + "同意對戰,開始游戲!");
			alert.initOwner(this);
			alert.show();
		
		// 拒絕對戰請求
		} else if (gameRequestMeaasge.getRequestType() == GameRequestMeaasge.GAME_REFUSE) {
			
			// 拒絕后,改回請求對戰狀態
			this.isSend = !isSend;
			// 清除對手ip
			Global.oppoIP = null;
			// 移除
			this.waitText.setText("");
			Alert alert = new Alert(AlertType.INFORMATION);
			alert.setContentText("對方不給面子,拒絕了你的請求!");
			alert.initOwner(this);
			alert.show();
		}
	}


己方落子

	/**
	 * 滑鼠點擊棋盤后的邏輯
	 */
	private void mouseClikedChessboard() {
		pane.setOnMouseClicked(e -> {
			// 游戲開始且輪到你落子
			if (!gameOver && isPlay) {
				double x = e.getX(); 
				double y = e.getY();

				// 當 點擊的x 或 y 坐標超出棋盤范圍時,落子無效,設定10的偏移量
				if (x < 40 || x > 630 || y < 40 || y > 620) {
					return;
				}
				
				// 給棋盤上的橫軸交叉的點定義坐標
				int xIndex = (int)Math.round((x - 50) / 40);
				int yIndex = (int)Math.round((y - 50) / 40);

				// 把棋子加入到棋盤中
				this.piece(xIndex, yIndex);
			}

		});
	}




	/**
	 * 落子
	* @param x
	* @param y
	 */
	private void piece(int x, int y) {
		if (chessList.size() == SIZE * SIZE) {
			System.out.println("棋盤已滿,游戲結束");
			// 平局
			// 給對手發送訊息,讓他更新資料庫資訊
			ResultMessage resultMessage = new ResultMessage();
			// 根據當前用戶的棋子的顏色設定訊息類結果屬性
			resultMessage.setResult(Color.BLACK.equals(this.color) ?
					ResultMessage.BLACK_DRAW : ResultMessage.WHITE_DRAW);
			
			// 顯示提示框
			chessFullReminder();
			return;
		}
		
		// 判斷下子是否重復
		if (arr[x][y]) {
			System.out.println("同一坐標重復落子,無效!");
			return;
		}
		// 播放落子聲
		this.soundMoveLater();
		
		// 局時倒計時暫停
		gameTimeline.pause();
		// 暫停并重置步時
		stepTimeline.pause();
		this.stepTimeText.setText("步時 60");
		this.stepTimeNum = 60;
		
		// 去除上一個紅色的標志
		if (!chessList.isEmpty()) {
			pane.getChildren().remove(redCircle);
			
		}
		
		// 當前落子文本后面棋子的顏色
		nowChess.setFill(isBlack ? Color.ALICEBLUE: Color.BLACK);
		

		// 落完一子后,要先等對手落子,才能繼續落子
		isPlay = !isPlay;
		arr[x][y] = true;
		int tempX = x * LINE_SPACING + 50;
		int tempY = y * LINE_SPACING + 50;
		// 繪制棋子
		Circle circle = new Circle();
		// 棋子落點的x坐標
		circle.setCenterX(tempX);
		// 棋子落點的y坐標
		circle.setCenterY(tempY);
		
		// 設定棋子的顏色
		circle.setFill(this.color);
		// 將棋子的顏色記錄到陣列colors 中
		colors[x][y] = this.color;
		
		// 設定棋子的半徑
		circle.setRadius(CHESS_RADIUS);
		
		// 把棋子加入到棋盤中
		pane.getChildren().add(circle);
		
		// 標志落點的x坐標
		redCircle.setCenterX(tempX);
		// 標志落點的y坐標
		redCircle.setCenterY(tempY);
		// 設定為紅色
		redCircle.setFill(Color.RED);
		// 把標志加入到棋盤中
		pane.getChildren().add(redCircle);
		
		// 將棋子的資訊保存到陣列中
		Chess chess = new Chess(x, y, this.color);
		chessList.add(chess);
		// 更換棋子顏色
		isBlack = !isBlack;
    	// 給對手發送該落子的資訊
    	NetUtils.sendMessage(new ChessMessage(x, y,this.blackOrWhite), Global.oppoIP);
		// 出現五連子,結束游戲
		if (isWin(chess)) {
    		// 局時倒計時停止
    		gameTimeline.pause();
    		// 步時倒計時停止
    		stepTimeline.pause();
			
			// 出現五連,給對手發送訊息,對手將更新資料庫
			ResultMessage resultMessage = new ResultMessage();
			// 設定為不是認輸
			resultMessage.setLose(false);
			resultMessage.setAccount(Global.account);
			resultMessage.setResult(Color.BLACK.equals(this.color) ? 
					ResultMessage.BLACK_WIN : ResultMessage.WHITE_WIN);
			
        	// 發送對戰結果訊息
        	NetUtils.sendMessage(resultMessage, Global.oppoIP);
			
			// 清除對手ip
			Global.oppoIP = null;

			// 清除棋盤上雙方VS的文字和后面的棋子
			// 重新添加重繪、上一頁和下一頁按鈕
			this.removeEndTextAndCircle();
			
			// 顯示對局結束彈窗
			this.gameOverReminder("勝利");
			
		}
	}

顯示對手的落子

		// 在自己的棋盤上顯示對手下的棋子
		if (message instanceof ChessMessage) {
			this.chessMessage(message);
		}
			/**
	 * 在自己的棋盤上顯示對手下的棋子
	* @param message 對手發送的棋子訊息
	 */
	private void chessMessage(Message message) {
		// 播放落子聲
		this.soundMoveLater();
		
		// 局時倒計時開始
		this.gameTimeline.play();
		// 步時倒計時開始
		this.stepTimeline.play();
		// 去除上一個紅色的標志
		if (!chessList.isEmpty()) {
			pane.getChildren().remove(redCircle);
		}
		// 設定當前落子文本后面棋子的顏色
		nowChess.setFill(isBlack ? Color.ALICEBLUE: Color.BLACK);
		
		// 對手下完棋后,自己可以下棋了
		this.isPlay = true;
		ChessMessage chessMessage = (ChessMessage)message;
		// 獲取對手棋子的坐標和顏色
		int x = chessMessage.getX();
		int y = chessMessage.getY();
		Color nowColor = chessMessage.getBlackOrWhite() == 1 ? Color.BLACK : Color.ALICEBLUE;

		Circle circle = new Circle(x * 40 + 50,
				y * 40 + 50,CHESS_RADIUS);

		circle.setFill(nowColor);
		// 如果對手是黑棋,那么自己就是白棋
		if (chessMessage.getBlackOrWhite() == 1) {
			this.blackOrWhite = 0;
			this.color = Color.ALICEBLUE;
		}

		// 更新當前棋子資訊
		isBlack = !isBlack;

		// 記錄對手下的棋的資訊
		arr[x][y] = true;
		colors[x][y] = nowColor;
		Chess chess = new Chess(x, y, nowColor);
		chessList.add(chess);
		pane.getChildren().add(circle);

		// 標志落點的x坐標
		redCircle.setCenterX(x * 40 + 50);
		// 標志落點的y坐標
		redCircle.setCenterY(y * 40 + 50);
		// 設定為紅色
		redCircle.setFill(Color.RED);
		// 把標志加入到棋盤中
		pane.getChildren().add(redCircle);
		
		// 對手五連,結束游戲
		if (this.isWin(chess)) {
    		// 局時倒計時停止
    		gameTimeline.pause();
    		// 步時倒計時停止
    		stepTimeline.pause();
			
			// 清除對手ip
			Global.oppoIP = null;
			// 清除棋盤上雙方VS的文字和后面的棋子
			// 重新添加重繪、上一頁和下一頁按鈕
			this.removeEndTextAndCircle();
			
			this.gameOverReminder("失敗");
		}
		// 棋盤已滿
		if (chessList.size() == SIZE * SIZE) {
			// 提示框
			this.chessFullReminder();
		}
	}


其它

由于篇幅太長了,其它的代碼就不繼續往這里放了,完整原始碼已上傳GitHub,需要的直接過去下載即可,Eclipse 和IDEA兩個版本都有,鏈接在文章的最后面


環境搭建

開發工具

工具說明官網
IDEA最好的java開發工具https://www.jetbrains.com/idea/download
Eclipse開源的java開發工具https://www.eclipse.org/downloads/
Navicat資料庫連接工具http://www.formysql.com/xiazai.html
PowerDesigner資料庫設計工具 http://powerdesigner.de/
Xmind思維導圖設計工具https://www.xmind.cn/
ProcessOn流程圖繪制工具https://www.processon.com/
TyporaMarkdown編輯器https://typora.io/
qq螢屏截圖工具https://im.qq.com/
Snipaste螢屏截圖工具https://www.snipaste.com/

開發環境

工具版本號下載
JDK1.8https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
MySQL5.7https://downloads.mysql.com/archives/installer/

搭建步驟

Windows 環境部署
IDEA

  • 關于IDEA的安裝與使用請參考:https://github.com/judasn/IntelliJ-IDEA-Tutorial
  • 將專案下載到本地,然后直接打開:
    在這里插入圖片描述
    在這里插入圖片描述

Eclipse

  • 關于Eclipse的安裝與使用請參考:https://blog.csdn.net/rothschild666/article/details/82914600?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=1330144.8071.16180379552492035&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
  • 將專案下載到本地,然后直接打開:
    在這里插入圖片描述
    在這里插入圖片描述

MySQL

  • 下載并安裝mysql5.7版本,下載地址:https://dev.mysql.com/downloads/installer/
  • 設定資料庫帳號密碼:root root
  • 下載并安裝客戶端連接工具Navicat,下載地址:http://www.formysql.com/xiazai.html
  • 創建資料庫 gobang
  • 匯入gobang/sql下的gobang.sql檔案

啟動專案

  • 將src下的db.properties檔案中url后面的ip和埠改成你自己的主機ip和mysql的埠號

  • 將com.wupgig.common.Global類中的myPort(我的埠號) 和oppoPort (別人的埠號) 都設定為主機一般不會被占用的埠比如 8088 (兩個埠設定要相同,這是不同電腦之間對戰的設定)

  • 如果你想在你自己的電腦上啟動兩個五子棋程式,并讓他們兩個程式之間進行對戰,可以在第一次啟動的時候將myPort設定為8088,oppoPort設定為8089,第二次啟動的時候myPort設定為8089,oppoPort設定為8088,那么即可在同一臺電腦上開始對戰了(不一定非得8088、和8089,只要保證兩個埠沒被占用,且兩次啟動的埠反過來設定即可)

  • 不同電腦之間的對戰必須保證你的MySQL打開了遠程訪問權限,
    執行sql陳述句:grant all privileges on . to ‘root’@’%’ identified by ‘root’ with grant option;flush privileges;即可開放MySQL的遠程訪問權限
    注意:前面的root為賬號名,后面的root為密碼

  • 不同電腦之間的對戰需要關閉電腦的防火墻或者打開相關埠的遠程權限

  • 由于上網所用的ip地址基本都是路由器或者運營商提供的局域網ip地址,這種ip地址是不能在外網直接訪問到的,即不同電腦之間的對戰只能在同一局域網中,如果想要在不同的網路下實作聯機對戰,可以使用一些工具對ip進行內網穿透,至于怎么做內網穿透,請自行百度

  • 最后運行com.wupgig.main.GobangMainApplication的main方法即可


完整原始碼

Eclipse版本

GitHub地址:https://github.com/wupgig/GoBang-Eclipse

IDEA版本

GitHub地址:https://github.com/wupgig/Gobang-IDEA

END

最后:如果對代碼有任何疑問可以直接在評論區留言,看到了就會及時回復,當然,這個代碼肯定會存在一些問題,歡迎發現問題的朋友在評論區指正,大家共同進步的哈

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276277.html

標籤:其他

上一篇:圖的題目基礎類與方法

下一篇:回圈結構程式設計練習2

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more