针对上一个缺陷版,这次的代码进行了许多方面的完善。以下就亮点进行说明,最后从总体上综述项目内容。
亮点:
1:用ArrayList存储棋子坐标位置->存储棋子对象
原本我是用一个ArrayList列表存储每个棋子坐标,就像{{1,2},{3,2},{4,3},{5,1}}这样顺次add,这样能够判断该位置能否下棋以及取出最后一子用于悔棋,但实操中也有功能缺陷,比如:不知道哪种颜色是最后一子,对棋子其它属性无法刻画等等。于是后来改用存储Chess对象,对象属性为坐标和颜色。
2:原本画棋盘和画棋子分开操作,棋盘一次画好不再更改->更新棋子时更新棋盘
原本的逻辑是好理解的,初始化界面的时候画好棋盘,之后画棋子,清空棋子,重画棋子的操作虽都在此棋盘画板上,但理应于棋盘无关了。但实操中发现,chessPanel里不容易区分初始化时画的棋盘,和之后画的棋子,这两种一个是初始化时自动跑的paint(自动产生的画笔),一个是从画板手动产生画笔,赋给监听器,再用画笔画的。我尝试用repaint方法重画棋盘,再手动重画棋子,这样尝试区分开两步骤,但发现repaint方法有点毛病:底层代码在repaint后的重画棋子早早进行完毕,但界面上的重画棋子一直跑不出来,猜测是repaint跑的时间很长。
另外当我们把这个界面最小化或者拖到屏幕以外,界面上的棋子就消失了。猜测也是先跑了一遍repaint,再重新完成之前的画棋子操作,但因为repaint莫名其妙的完不了,就导致来不及画棋子。
最后的操作是:在原本画棋盘的方法中同样更新棋子,也就是在想要大改棋子的时候直接重画棋盘。这样做一方面不用区分开画棋盘和画棋子(因为棋盘是随着棋子而重画的),另一方面,绑定在一起后,最小化界面和拖动不会单一的使棋盘画完而棋子来不及画(为什么会这样?可能是repaint在画完后有其他工作要做)。
3:原本刻画棋子占位的二维数组从对象属性->全局变量
原本的二维数组是在监听器类下的属性,每次鼠标点击后更新数组内容,调用时也是在监听器类中调用——悔棋和检查有无下过棋子。但因为在画棋盘的时候也要用到此二维数组(ChessPanel类),于是构思把此数组作为接口类下的公用变量(为什么是公用?而不是每个实现类都拥有一个?——
static既然没用,那猜测默认有static。)
4:数据的使用:从纯粹的数字->用静态变量代替
代码:
1:GoBangUI类
public class GoBangUI {
GoListener goListener=new GoListener();
JFrame jf=new JFrame("五子棋");
public void initUI(){
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
jf.setSize(900, 800);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setResizable(false);
jf.setLocationRelativeTo(null);
SwingUtilities.updateComponentTreeUI(jf);
}catch (Exception e){
e.printStackTrace();
}
JPanel btnPanel =new JPanel();
btnPanel.setBackground(Color.ORANGE);
btnPanel.setPreferredSize(new Dimension(110,100));
btnPanel.setLayout(new FlowLayout());
JLabel jla=new JLabel("五子棋");
jla.setHorizontalAlignment(SwingConstants.CENTER);
jla.setPreferredSize(new Dimension(100,100));
jla.setFont(new Font(null,Font.ITALIC,20));
Icon icon=new ImageIcon("https://pic.ntimg.cn/20140411/18441126_192718566176_2.jpg");
jla.setIcon(icon);
btnPanel.add(jla);
jf.setVisible(true);
String[] strs={"开始游戏","悔棋","退出游戏","历史记录"};
for (int i = 0; i < strs.length; i++) {
JButton jb=new JButton(strs[i]);
jb.setBackground(Color.WHITE);
//jb.setBounds(5,125+i*50,95,45); //setBounds直接设置了位置
jb.setPreferredSize(new Dimension(95,45)); //setSize后按钮会随着字多少而变大变小?
btnPanel.add(jb);
jb.addActionListener (goListener);//
}
ChessPanel cp=new ChessPanel();
cp.addMouseListener(goListener);
jf.add(btnPanel,BorderLayout.EAST);
jf.add(cp,BorderLayout.CENTER);
//jf.add(cp,BorderLayout.WEST); //west显示不出来
jf.setVisible(true);
goListener.ui=this;
goListener.g=cp.getGraphics();
goListener.chessPanel=cp;
}
public static void main(String[] args) {
GoBangUI ui=new GoBangUI();
ui.initUI();
}
}
GoListener类
public class GoListener implements ActionListener, MouseListener,GoData {
int chessFlag=0;
Graphics g;
ChessPanel chessPanel;
GoBangUI ui;
ArrayList<Chess> chessList=new ArrayList<>();
int blackWin=0;
int whiteWin=0;
JButton btn;
@Override
public void actionPerformed(ActionEvent e) {
String actionStr=e.getActionCommand();
btn=(JButton) e.getSource();
if(actionStr.equals("开始游戏")){
chessFlag=1;
btn.setText("结束游戏");
}
else if(actionStr.equals("结束游戏")){
for (int i = 0; i < COLS; i++) {
for (int j = 0; j < ROWS; j++) {
CHESS_ARRAY[i][j]=0;
}
}
chessList.clear();
chessPanel.paint(g); //这个画笔?????chessPanel和g都需要指定具体的,在此只是用声明的对象进行操作
btn.setText("开始游戏");
chessFlag=0;
}else if(actionStr.equals("悔棋")){
if (chessList.size()==0){
JOptionPane.showMessageDialog(null,"没有棋子可以悔棋了");
return;
}
Chess chess=chessList.get(chessList.size()-1);
//System.out.println(chess.c+"@@@"+chess.r);
CHESS_ARRAY[chess.c][chess.r]=9;
chessFlag=chess.chessFlag;
chessList.remove(chessList.size()-1);
chessPanel.paint(g);
} else if (actionStr.equals("退出游戏")) {
ui.jf.setVisible(false);
}else if(actionStr.equals("历史记录")){
JOptionPane.showMessageDialog(null,"黑方总计获胜次数:"+blackWin+"\n白方总计获胜次数:"+whiteWin);
}
}
public boolean check(int x, int y) {
int ans = CHESS_ARRAY[x][y];
System.out.println("x:"+x+"y:"+y);
try {
for (int i = 0; i < 5; i++) {
if (CHESS_ARRAY[x - 4 + i][y] == ans && CHESS_ARRAY[x - 3 + i][y] == ans && CHESS_ARRAY[x - 2 + i][y] == ans && CHESS_ARRAY[x - 1 + i][y] == ans &&CHESS_ARRAY[x+i][y]==ans) {
System.out.println("横着的是:"+i);
return true;
}
}
for (int i = 0; i < 5; i++) {
if (CHESS_ARRAY[x][y - 4 + i] == ans && CHESS_ARRAY[x][y - 3 + i] == ans && CHESS_ARRAY[x][y - 2 + i] == ans && CHESS_ARRAY[x][y - 1 + i] == ans && CHESS_ARRAY[x][y+i]==ans) {
System.out.println("竖着的是:"+i);
return true;
}
}
for (int i = 0; i < 5; i++) {
if (CHESS_ARRAY[x - 4 + i][y - 4 + i] == ans && CHESS_ARRAY[x - 3 + i][y - 3 + i] == ans && CHESS_ARRAY[x - 2 + i][y - 2 + i] == ans && CHESS_ARRAY[x - 1 + i][y - 1 + i] == ans && CHESS_ARRAY[x+i][y+i]==ans) {
System.out.println("捺的是:"+i);
return true;
}
}
for (int i = 0; i < 5; i++) {
if (CHESS_ARRAY[x - 4 + i][y + 4 - i] == ans && CHESS_ARRAY[x - 3 + i][y + 3 - i] == ans && CHESS_ARRAY[x - 2 + i][y + 2 - i] == ans && CHESS_ARRAY[x - 1 + i][y + 1 - i] == ans &&CHESS_ARRAY[x+i][y-i]==ans) {
System.out.println("撇的是"+i);
return true;
}
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}
@Override
public void mouseClicked(MouseEvent e) {
int x=e.getX();
int y=e.getY();
int r=(y-Y+SIZE/2)/SIZE;
int c=(x-X+SIZE/2)/SIZE;
if(chessFlag==0){
JOptionPane.showMessageDialog(null,"还没开始");
return;
}
if(r<0||c<0||r>ROWS||c>COLS){
JOptionPane.showMessageDialog(null,"不能在边界外下棋");
return;
}
if(CHESS_ARRAY[c][r]!=0){
return;
}
CHESS_ARRAY[c][r]=chessFlag;
chessList.add(new Chess(c,r,chessFlag));
if(chessFlag==1){
g.setColor(Color.BLACK);
chessFlag=2;
}else if(chessFlag==2){
g.setColor(Color.WHITE);
chessFlag=1;
}
int cx=c*SIZE+X-SIZE/2;
int cy=r*SIZE+Y-SIZE/2;
g.fillOval(cx,cy,SIZE,SIZE);
if (check(c,r)){
String ans;
if(CHESS_ARRAY[c][r]==1){
ans="黑方";
blackWin++;
}else{
ans="白方";
whiteWin++;
}
int a=JOptionPane.showConfirmDialog(null,new Object[]{"再来一局?"},"游戏结束,胜者是"+ans,JOptionPane.YES_NO_OPTION);
if(a==0){
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
CHESS_ARRAY[i][j]=0;
}
}
chessList.clear();
chessPanel.paint(g);//这个画笔?????
btn.setText("开始游戏");
chessFlag=0;
}
else{
ui.jf.setVisible(false);
}
}
for (int i = 0; i < CHESS_ARRAY.length; i++) {
for (int j = 0; j < CHESS_ARRAY[i].length; j++) {
System.out.print(CHESS_ARRAY[j][i]);
}
System.out.println(" ");
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
ChessPanel类
public class ChessPanel extends JPanel implements GoData{
public void paint(Graphics g){
g.setColor(Color.lightGray);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(Color.BLACK);
for (int i = 0; i < ROWS; i++) {
g.drawLine(X,Y+i*SIZE,X+GRID_NUM*SIZE,Y+i*SIZE);
}
for (int i = 0; i < COLS; i++) {
g.drawLine(X+i*SIZE,Y,X+i*SIZE,Y+GRID_NUM*SIZE);
}
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
int xDraw,yDraw;
xDraw=X+j*SIZE-SIZE/2;
yDraw=Y+i*SIZE-SIZE/2;
if(CHESS_ARRAY[j][i]==1){
g.setColor(Color.BLACK);
g.fillOval(xDraw,yDraw,SIZE,SIZE);
} else if (CHESS_ARRAY[j][i]==2) {
g.setColor (Color.WHITE);
g.fillOval (xDraw ,yDraw,SIZE,SIZE);
}
}
}
}
}
GoData接口
public interface GoData {
int X=75;
int Y=75;
int SIZE=40;
int ROWS=16;
int COLS=16;
int GRID_NUM=15;
static int[][] CHESS_ARRAY=new int[COLS][ROWS];
}
综述:
代码首先完成了UI界面设计,JFrame打底,多个JPanel上添加组件。之后监听器监听两种行为,分别是点击组件的ActionListener和鼠标点击的MouseListener。
行为监听包括开始游戏命令(这里用一个变量和一个弹窗完成),悔棋命令(重画了棋盘和之前的棋子),退出命令(将界面设置不可见),成绩记录命令(用两个变量记录双方输赢)。
鼠标监听中得到的xy坐标是界面的像素值,匹配到易于操作和理解的坐标值后,记录到数组,同时完成画棋子。