1. 程式人生 > >JAVA聯機版五子棋——原始碼(一個類一個main暴力開發)

JAVA聯機版五子棋——原始碼(一個類一個main暴力開發)

第一次寫部落格,排版啥的都比較亂,大家不要嫌棄啊。

所謂暴力開發,其實是啥也不會,硬懟哈哈哈。只是剛學會一點JAVA程式設計,參考網路資源寫了個聯機版五子棋(支援單機),來這裡記錄一下學習過程,而且我也是靠別人的分享才能寫出來,所以本著互相幫助的原則,分享給大家,拋磚引玉,共同進步。

作為一個半路出家,正在學習道路上的渣渣,摸索著寫了這樣一個亂亂的五子棋,很多地方都不正規,比如因為沒有系統學過GUI,對話方塊都用的JOptionPane元件,模式選擇對話方塊也放在main方法裡,算是偷懶吧。。

正如標題裡寫的“一個類一個main”,寫的時候為了快速且方便,都放在一個類裡了,因此大家想要測試的話,直接整個複製就行了,沒啥黑科技,JDK自帶的包就能執行。

因為個人的惡趣味,本五子棋起名“老鐵互懟五子棋”,當然各位可以自己隨意修改。

轉載也請隨意,標明出處就行了。

遊戲開啟後是這個介面,直接選擇遊戲模式,不能半途選別的模式,如果要更換,需要重啟遊戲(不然老出線程異常,所以直接砍掉這個功能哈哈哈。。。)。

遊戲開啟介面

選擇“是”則為聯機版,“否”或關閉對話方塊為單機版。

聯機版需要輸入對方的主機名或IP(區域網)。

輸入對方主機名或IP

沒有整合GUI,所以主機名和埠號都需要分別輸入(偷個懶)。

這裡輸入本機的埠號。

輸入己方埠號

輸入對方埠號,雙方埠號需要互相對應。

輸入對方埠號

以上步驟都沒錯且網路沒問題,則連線成功,點選確定可以開始遊戲,先落子的為黑棋。

說明一下,如果輸入的主機名或地址為本機,且輸入的埠號一致,則為單機模式(預防我預設的9653埠號被佔,這種方式可以自己設定埠號)。

因為使用了執行緒,可以本機開啟兩個遊戲介面,聯機模式下可以左右互搏(狗頭),也是實現單機模式的原理(好像也是開發中執行緒老是佔用的罪魁禍首,難受)。

聯機模式確認

如果一開始選擇了“否”或關閉對話方塊,則為單機模式,顯示下圖所示對話方塊,點選確定開始遊戲。

單機模式確認

遊戲介面如下,剛落下的棋子有外框顯示。

遊戲開始

程式碼如下(註釋可能有點囉嗦。。):

package netWuZiQiDemo;

//開啟遊戲選擇"是"則為聯機模式,選擇"否"或關閉對話方塊則為單機模式,預設埠號9653(這是一個神奇數字)
//本版本聯機需要輸入對方主機名或IP地址,並且需要雙方埠號對應相反
//本版本聯機模式,雙方輪流執黑
//如果主機名或IP為自己主機(或沒有輸入),則轉為單機模式,此時需兩個埠號一致
//為了解決執行緒異常問題(水平有限...),特推出此魔改版,開啟遊戲必須選擇其中一種模式(聯機或單機),要想更換,則要關閉遊戲重新開啟

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
 * 老鐵互懟五子棋1.0
 * @author Arno-Woke
 *
 */
public class NetLTWZQDemo extends JPanel implements MouseListener, Runnable {

	int x;// 落子處棋盤座標
	int y;
	int[][] chess = new int[12][12];// 棋盤大小為12x12

	int ex;// 傳送滑鼠點選座標
	int ey;
	boolean me = true;// 初始化為己方先落黑子
	boolean one = true;// 控制聯機雙方每次只能一個人下子
	static boolean start = false;// 預設開啟遊戲不能下子,需先選擇遊戲模式

	static InetAddress inetAddress;// 客戶端確認服務端的IP地址
	static String UsernameOrIp;// 聯機對方主機名或IP地址

	// 客戶端和服務端埠號一致則為單機版五子棋
	static int clientPort;// 設定客戶端埠
	static int serverPort;// 設定服務端埠
	static boolean danJi;// 選擇單機模式,則程式只判斷一次勝負,防止對局結束後無法落子,若聯機模式,則判斷兩次勝負

	static Thread t;// 宣告一個執行緒
	private static final long serialVersionUID = 1L;//emmm 沒什麼用,提示要有一個就放一個吧

	public static void main(String[] args) {
		JFrame jFrame = new JFrame("老鐵互懟五子棋");
		jFrame.setBounds((Toolkit.getDefaultToolkit().getScreenSize().width - 655) / 2,
				(Toolkit.getDefaultToolkit().getScreenSize().height - 675) / 2, 655, 675);// 視窗螢幕正中放置
		jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jFrame.setResizable(false);//窗體大小固定
		jFrame.setVisible(true);//窗體設定可見
		NetLTWZQDemo netLTWZQDemo = new NetLTWZQDemo();
		jFrame.add(netLTWZQDemo);
		t = new Thread(netLTWZQDemo);

		//為了方便(又懶又笨..),直接把所有內容寫到一個類裡,且把模式選擇對話方塊放在main方法裡
		int respones = JOptionPane.showConfirmDialog(null, "老鐵你愁啥,想找個人懟懟?", "生死看淡,不服就幹!", JOptionPane.YES_NO_OPTION,
				JOptionPane.WARNING_MESSAGE);

		if (respones == JOptionPane.YES_OPTION) {
			try {
				UsernameOrIp = JOptionPane.showInputDialog(null, "輸入丫主機名或IP", "記小本本上每天看兩遍", JOptionPane.WARNING_MESSAGE)
						.trim();
				clientPort = Integer.parseInt(JOptionPane
						.showInputDialog(null, "老鐵從幾號門出發?", "門牌號:1024~65535", JOptionPane.WARNING_MESSAGE)
						.trim());
				serverPort = Integer.parseInt(JOptionPane
						.showInputDialog(null, "對面在幾號門?", "門牌號:1024~65535", JOptionPane.WARNING_MESSAGE)
						.trim());
			} catch (Exception e) {
				JOptionPane.showMessageDialog(null, "輸錯了,老鐵從頭再來吧!", "憐憫的眼神看著你", JOptionPane.ERROR_MESSAGE);
				System.exit(0);
				e.printStackTrace();
			}

			try {
				inetAddress = InetAddress.getByName(UsernameOrIp);
			} catch (UnknownHostException ex) {
				ex.printStackTrace();
			}
			if (inetAddress != null) {
				JOptionPane.showMessageDialog(null, "開懟!", "安排上了!", JOptionPane.WARNING_MESSAGE);
				start = true;//設定遊戲開始
				if (clientPort == serverPort) {// 若主機名或IP為自己電腦(或不填寫)且兩個埠一樣,則為單機模式
					danJi = true;//開啟單機模式
				} else {
					danJi = false;// 聯機模式,設定單機模式為false
				}
				t.start();
			}
		}

		if (respones == JOptionPane.NO_OPTION || respones == JOptionPane.CLOSED_OPTION) {
			JOptionPane.showMessageDialog(null, "自懟?啥也別說了,老鐵雙擊666", "冷笑不語", JOptionPane.INFORMATION_MESSAGE);
			try {
				inetAddress = InetAddress.getLocalHost();
				serverPort = clientPort = 9653;// 預設埠號為9653
				start = true;//設定遊戲開始
				danJi = true;// 開啟單機模式
				t.start();//開啟執行緒開啟遊戲
			} catch (UnknownHostException ex) {
				JOptionPane.showMessageDialog(null, "老鐵,本地網路出問題了吧?", "是時候換臺電腦了老鐵", JOptionPane.ERROR_MESSAGE);
				System.exit(0);
				ex.printStackTrace();
			}
		}
	}

	@Override
	public void run() {// 使用執行緒,埠號一致可進行單機遊戲
		clearChess();//遊戲開啟,重繪棋盤
		receive();//開啟服務端接收資料
	}

	public NetLTWZQDemo() {
		setBackground(Color.gray);
		addMouseListener(this);//註冊滑鼠事件
		setVisible(true);//設定窗體可見
	}

	public void paintComponent(Graphics g) {
		Graphics2D g2D = (Graphics2D) g;// 使用Graphics2D使構圖平滑
		g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		super.paintComponent(g);
		drawChessBoard(g);
		drawChess(g);
	}

	public void drawChess(Graphics g) {
		for (int i = 0; i < 12; i++) {
			for (int j = 0; j < 12; j++) {
				if (chess[i][j] == 1) {
					g.setColor(Color.black);
					g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
				} else if (chess[i][j] == 2) {
					g.setColor(Color.white);
					g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
				} else if (chess[i][j] == 11) {// 點選落黑子時外加個黑框
					g.setColor(Color.black);
					g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
					g.drawRect((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
				} else if (chess[i][j] == 22) {// 點選落白子時棋子外加個白框
					g.setColor(Color.white);
					g.fillOval((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
					g.drawRect((i + 1) * 50 - 19, (j + 1) * 50 - 19, 38, 38);
				}
			}
		}
	}

	public void drawChessBoard(Graphics g) {
		// 畫棋盤
		for (int i = 50; i <= 600; i += 50) {
			g.setColor(Color.WHITE);
			g.drawLine(i, 50, i, 600);
			g.drawLine(50, i, 600, i);
		}
	}

	public void clearChess() {// 初始化棋盤
		for (int i = 0; i < 12; i++) {
			for (int j = 0; j < 12; j++) {
				chess[i][j] = 0;
			}
		}
		me = true;// 初始化為己方落黑子
		repaint();
	}

	//這個判斷勝負的方法是抄的網上一段程式碼,略作修改(為保持原貌幾乎沒有修改,提醒自己不能這麼幹,嗯!),emmm,互相學習嘛,哈哈哈(尬笑),實在是想的腦闊疼,偷個懶...(不過好像點的太快,會出現bug,導致沒有5子連著也會判勝,可能是錯覺吧...)
	void checkWiner() {// 判斷勝方
		int black_count = 0;
		int white_count = 0;
		for (int i = 0; i < 12; i++) {// 橫向判斷
			for (int j = 0; j < 12; j++) {
				if (chess[i][j] == 1 || chess[i][j] == 11) {
					black_count++;
					if (black_count == 5) {
						JOptionPane.showMessageDialog(this, "黑棋勝利");
						clearChess();
						return;
					}
				} else {
					black_count = 0;
				}
				if (chess[i][j] == 2 || chess[i][j] == 22) {
					white_count++;
					if (white_count == 5) {
						JOptionPane.showMessageDialog(this, "白棋勝利");
						clearChess();
						return;
					}
				} else {
					white_count = 0;
				}
			}
		}
		for (int i = 0; i < 12; i++) {// 豎向判斷
			for (int j = 0; j < 12; j++) {
				if (chess[j][i] == 1 || chess[j][i] == 11) {
					black_count++;
					if (black_count == 5) {
						JOptionPane.showMessageDialog(this, "黑棋勝利");
						clearChess();
						return;
					}
				} else {
					black_count = 0;
				}
				if (chess[j][i] == 2 || chess[j][i] == 22) {
					white_count++;
					if (white_count == 5) {
						JOptionPane.showMessageDialog(this, "白棋勝利");
						clearChess();
						return;
					}
				} else {
					white_count = 0;
				}
			}
		}
		for (int i = 0; i < 7; i++) {// 左向右斜判斷
			for (int j = 0; j < 7; j++) {
				for (int k = 0; k < 5; k++) {
					if (chess[i + k][j + k] == 1 || chess[i + k][j + k] == 11) {
						black_count++;
						if (black_count == 5) {
							JOptionPane.showMessageDialog(this, "黑棋勝利");
							clearChess();
							return;
						}
					} else {
						black_count = 0;
					}
					if (chess[i + k][j + k] == 2 || chess[i + k][j + k] == 22) {
						white_count++;
						if (white_count == 5) {
							JOptionPane.showMessageDialog(this, "白棋勝利");
							clearChess();
							return;
						}
					} else {
						white_count = 0;
					}
				}
			}
		}
		for (int i = 4; i < 12; i++) {// 右向左斜判斷 11->12
			for (int j = 6; j >= 0; j--) {
				for (int k = 0; k < 5; k++) {
					if (chess[i - k][j + k] == 1 || chess[i - k][j + k] == 11) {
						black_count++;
						if (black_count == 5) {
							JOptionPane.showMessageDialog(this, "黑棋勝利");
							clearChess();
							return;
						}
					} else {
						black_count = 0;
					}
					if (chess[i - k][j + k] == 2 || chess[i - k][j + k] == 22) {
						white_count++;
						if (white_count == 5) {
							JOptionPane.showMessageDialog(this, "白棋勝利");
							clearChess();
							return;
						}
					} else {
						white_count = 0;
					}
				}
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		System.out.println(e.getX() + "==" + e.getY());// 在棋盤上點選滑鼠時在控制檯輸出座標資訊,方便測試
		if (start == false) {
			return;
		}
		ex = e.getX();
		ey = e.getY();
		if (ex >= 25 && ex <= 625 && ey >= 25 && ey <= 625) {// 控制滑鼠點選位於棋盤內部
			x = (ex - 19) / 50;// 使棋子排列在棋盤交點處
			y = (ey - 19) / 50;
		}
		if (chess[x][y] != 0) {// 當點選位置已經有棋子,則此次點選無效,並繼續落子
			return;
		}
		if (one == true) {// 輪到一方落子時,落子判斷勝負,並傳送滑鼠點選的座標
			doit();// 若傳送座標前判斷勝負,則會導致最後一顆棋子落子資訊阻塞,判斷完畢後再次傳送,故此處不判斷
			sendXY(ex, ey);// 先傳送座標再判斷勝負
			if (danJi == false) {// 聯機模式下才進行此次勝負判斷
				checkWiner();
			}
		}
		one = false;// 換到對方落子
	}

	private void doit() {// 判斷落子顏色及判斷勝方
		x = (ex - 19) / 50;
		y = (ey - 19) / 50;
		if (chess[x][y] == 0) {// 點選位置無棋子
			if (me == true) {
				chess[x][y] = 11;// 黑子
				for (int i = 0; i < 12; i++) {
					for (int j = 0; j < 12; j++) {
						if (chess[i][j] == 22) {
							chess[i][j] = 2;// 把剛才下的加白框的白子轉換為普通白子
						}
					}
				}
				me = false;// 換為白子落子
			} else if (me == false) {
				chess[x][y] = 22;// 白子
				for (int i = 0; i < 12; i++) {
					for (int j = 0; j < 12; j++) {
						if (chess[i][j] == 11) {
							chess[i][j] = 1;// 把剛才下的加黑框的黑子轉換為普通黑子
						}
					}
				}
				me = true;// 換為黑子落子
			}
		}
		repaint();// 重繪棋盤後判斷勝方
	}

	public void sendXY(int xSend, int ySend) {// 傳送滑鼠點選的座標
		try {// 問題在於每次傳送座標都要重新建立一個socket物件,不知道怎麼改進
			Socket socket = new Socket(inetAddress, serverPort);// 設定對方伺服器端IP,並設定埠號為9653;
			OutputStream os = socket.getOutputStream();
			String strr = xSend + "-" + ySend;// 使用-符號連線座標,並以字串形式傳送到服務端
			os.write(strr.getBytes());
			socket.shutdownOutput();// 及時傳送位置資訊,不用closs()是保持通訊一直連線
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(null, "老鐵,多人一起懟不合適吧?", "懟亦有道!", JOptionPane.ERROR_MESSAGE);
			System.exit(0);
			ex.printStackTrace();
		}
	}

	public void receive() {// 服務端接收對方發來的座標
		try {
			ServerSocket serverSocket = new ServerSocket(clientPort);// 從該埠接收資料
			while (true) {
				Socket sSocket = serverSocket.accept();
				InputStream is = sSocket.getInputStream();
				byte[] bys = new byte[1024];
				int len = is.read(bys);
				String strReceive = new String(bys, 0, len);
				System.out.println(strReceive);//接收到的字串輸出在控制檯,方便測試
				String[] strs = strReceive.split("-");// 使用"-"分割字串,得到座標
				ex = Integer.parseInt(strs[0]);
				ey = Integer.parseInt(strs[1]);
				doit();// 得到座標後,重繪棋盤並判斷勝方
				checkWiner();
				one = true;// 己方不能落子,棋權交給對方
			}
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "老鐵,多人一起懟不合適吧?", "懟亦有道!", JOptionPane.ERROR_MESSAGE);
			System.exit(0);
			e.printStackTrace();
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}
}