第一次写博客,排版啥的都比较乱,大家不要嫌弃啊。

所谓暴力开发,其实是啥也不会,硬怼哈哈哈。只是刚学会一点JAVA编程,参考网络资源写了个联机版五子棋(支持单机),来这里记录一下学习过程,而且我也是靠别人的分享才能写出来,所以本着互相帮助的原则,分享给大家,抛砖引玉,共同进步。

作为一个半路出家,正在学习道路上的渣渣,摸索着写了这样一个乱乱的五子棋,很多地方都不正规,比如因为没有系统学过GUI,对话框都用的JOptionPane组件,模式选择对话框也放在main方法里,算是偷懒吧。。

正如标题里写的“一个类一个main”,写的时候为了快速且方便,都放在一个类里了,因此大家想要测试的话,直接整个复制就行了,没啥黑科技,JDK自带的包就能运行。

因为个人的恶趣味,本五子棋起名“老铁互怼五子棋”,当然各位可以自己随意修改。

转载也请随意,标明出处就行了(如果感觉有用,点个赞就是最好的支持啦)。

游戏打开后是这个界面,直接选择游戏模式,不能半途选别的模式,如果要更换,需要重启游戏(不然老出线程异常,所以直接砍掉这个功能哈哈哈。。。)。

选择“是”则为联机版,“否”或关闭对话框为单机版。

联机版需要输入对方的主机名或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) {
	}
}

更多推荐

JAVA联机版五子棋——源码(一个类一个main暴力开发)