MVC模式
现在我们先不来考虑联机的部分,单纯只考虑五子棋如何实现的,这里用MVC设计模式来实现
什么是MVC
模型(Model)
模型代表应用程序的数据和业务逻辑。它负责处理数据的存储、检索、更新和验证,同时也包括定义应用程序的业务规则。模型与用户界面和用户交互无关,使得数据的处理可以独立于视图和控制器。
视图(View)
视图是用户界面的表示,负责显示数据并将用户的输入传递给控制器。视图接收模型的数据并以某种形式(例如图形界面、命令行界面)呈现给用户。视图通常不包含业务逻辑,而只负责呈现数据。
控制器(Controller)
控制器是模型和视图之间的协调者。它接收用户的输入,根据输入更新模型的数据,并相应地更新视图。控制器负责将用户输入转换为对模型的操作,并通知视图更新以反映模型的变化。控制器使模型和视图能够相互独立工作,提高了系统的灵活性。
MVC模式的工作流程通常如下
- 用户与视图进行交互。
- 视图将用户的输入传递给控制器。
- 控制器根据用户输入对模型进行相应的操作。
- 模型更新后,通知控制器。
- 控制器通知视图更新,以反映模型的变化。
- 视图更新后等待用户下一次交互。
View
我们来看我们的View部分,View部分主要由两部分组成,一部分是棋盘的主题部分BorderPanel,另一部分是周围的按钮等即toolPanel。
设置背景图和棋子图片。
1 2 3 4
| Image background=((Image) new ImageIcon("src/pic/backgroun.png").getImage());
Image chess1=((Image) new ImageIcon("src/pic/chess1.png").getImage()); Image chess2=((Image) new ImageIcon("src/pic/chess2.png").getImage());
|
为面板提供交互操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public ChessPanel(){ this.setPreferredSize(new Dimension(800,800)); this.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { super.mouseDragged(e); tempPoint=e.getPoint();
repaint(); } }); this.setBackground(Color.decode("#000816")); this.addMouseListener(new MouseListener() { @Override public void mouseClicked(MouseEvent e) { System.out.println(e.getPoint().x+""+e.getPoint().y); try { Vars.controler.addChess(e.getPoint()); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override public void mousePressed(MouseEvent e) {
}
@Override public void mouseReleased(MouseEvent e) { try { Vars.controler.addChess(e.getPoint()); } catch (IOException ex) { throw new RuntimeException(ex); } tempPoint.x=10000; tempPoint.y=10000; } @Override public void mouseEntered(MouseEvent e) {
}
@Override public void mouseExited(MouseEvent e) {
} });
}
|
绘图模块,画线和画棋子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| protected void drawtempchess(Graphics g) { int cellWidth = Vars.chessPanel.getWidth() / 20; int cellHeight = Vars.chessPanel.getHeight() / 20; int x,y; x= tempPoint.x/cellWidth; y= tempPoint.y/cellHeight; System.out.println(x+" "+y); if(Vars.controler.getColor()){ g.drawImage(chess1,cellWidth/2+cellWidth*x,cellWidth/2+cellWidth*y,cellWidth,cellHeight,this); } else{ g.drawImage(chess2,cellWidth/2+cellWidth*x,cellWidth/2+cellWidth*y,cellWidth,cellHeight,this); }
}
protected void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(background,0,0,this.getWidth(),this.getHeight(),this); System.out.println(background.getHeight(this));
int cellWidth = getWidth() / 20; int cellHeight = getHeight() / 20; ((Graphics2D)g).setStroke(new BasicStroke(3)); for(int i=0;i<19;i++) { g.setColor(Color.decode("#0fa99a")); g.drawLine(cellWidth,cellWidth*(i+1),cellWidth*(19),cellWidth*(i+1)); g.drawLine(cellWidth*(i+1),cellWidth,cellWidth*(i+1),cellWidth*(19)); }
if(Vars.controler.turnyou)drawtempchess(g); LinkedList<chess> data=Vars.model.getModel(); for(chess chess:data) { if(chess.color){ g.drawImage(chess1,cellWidth/2+cellWidth*chess.x,cellWidth/2+cellWidth*chess.y,cellWidth,cellHeight,this); } else{ g.drawImage(chess2,cellWidth/2+cellWidth*chess.x,cellWidth/2+cellWidth*chess.y,cellWidth,cellHeight,this); }
}
}
|
Model模块
接下来我们看model模块该怎么写,首先我们想我们的视图需要反应模型,那么模型储存这什么呢,就是我们的棋盘数据,将整个棋盘以一个19X19的矩阵存入Model。但是光这样是不行的,应为我们会发现如果这么存的话,我们只知道棋盘上当前的情况,但却不知道这些棋子的顺序,如果这样的话,我们就无法实现我们的悔棋功能了,所以我们需要一个队列容器来记录每一步的棋子都下在了哪里。
然后我们再次可以提前考虑一下我们之后的联机模块,我们的联机是要与不同的对手对抗的,记录对手的信息以是必要的,还有双方之间的网络消息队列,也可以放在Model模块里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| import java.util.LinkedList;
public class Model { private LinkedList<chess> model=new LinkedList<>();
public LinkedList<user> userList=null;
public LinkedList<Message>messageModel=new LinkedList<Message>();
public String username;
public int enemyID=-1;
public String enemyname;
public int ID;
private int[][]board=new int[22][22];
public void addChess(int x,int y,boolean color) {
chess newChess = new chess(x, y, color); model.add(newChess); if (color) board[x][y] = 1; else board[x][y] = 2;
}
public void removeChess() { board[model.getLast().x][model.getLast().y]=0; model.removeLast();
}
public LinkedList<chess> getModel() { return model; }
public int[][] getboard() { return board; }
public int getboard(int x,int y) { return board[x][y]; }
public void clear(){ while(!model.isEmpty()) { model.removeLast(); } for(int i=0;i<22;i++) for(int j=0;j<22;j++) board[i][j]=0; }
} class chess{ boolean color; int x; int y; chess(int x,int y,boolean color)
{ this.x=x; this.y=y; this.color=color; } }
|
Controller模块
Controller 中包括了游戏中所有的操作函数接口。首先我们来分析一下项目中的一些操作需求
“下棋”操作
下棋应该是项目中最为基本的内容,下棋需要将棋子存入Model,还需要向对手发送消息,告知对手自己下在了哪一步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public void addChess(Point point) throws IOException { if(turnyou) {
int cellWidth = Vars.chessPanel.getWidth() / 20; int cellHeight = Vars.chessPanel.getHeight() / 20; int x, y; x = point.x / cellWidth; y = point.y / cellHeight; System.out.println(x + " " + y); if (Vars.model.getboard(x, y) == 0) {
Vars.model.addChess(x, y, chessColor); if (chessColor) { chessColor = false; } else { chessColor = true; } Vars.chessPanel.repaint();
Message msg=new Message("chess",x,y,chessColor); Vars.model.messageModel.addLast(msg); turnyou = false; Vars.timePanel.restart(); Vars.roundPanel.update(); judgement(); } }
}
|
“对手下棋”操作
同样,我们还需要执行对手下棋的操作,这两个步骤可不可以混合在一起?其实可以,但这样会是代码的可读性变差,而且回合的切换也会没有那么流畅。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void addNetChess(int x,int y) {
System.out.println(x + " " + y); if (Vars.model.getboard(x, y) == 0) {
Vars.model.addChess(x, y, chessColor); if (chessColor) { chessColor = false; } else { chessColor = true; } Vars.chessPanel.repaint(); turnyou=true; Vars.timePanel.restart(); Vars.roundPanel.update(); judgement();
} }
|
“判断胜负”操作
判断胜利: 在每次有玩家落子后,都需要检查是否有一方取得了胜利。检查的方法通常包括检查每个落子位置的水平、垂直、左上到右下、右上到左下四个方向上是否有连续的五个相同的棋子。
- 水平检查: 从左到右检查每一行是否有连续的五个相同的棋子。
- 垂直检查: 从上到下检查每一列是否有连续的五个相同的棋子。
- 左上到右下检查: 从左上角到右下角检查斜线上是否有连续的五个相同的棋子。
- 右上到左下检查: 从右上角到左下角检查斜线上是否有连续的五个相同的棋子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| public void judgement() { int[][] chessboard = Vars.model.getboard();
int winner = checkWinner(chessboard); if (winner == 1) { System.out.println("白棋获胜!"); Vars.dialogStyle.Gameover(Start.getStart(),"白棋获胜"); } else if (winner == 2) { System.out.println("黑棋获胜!"); Vars.dialogStyle.Gameover(Start.getStart(),"黑棋获胜");
} else { System.out.println("游戏尚未结束,或者平局!"); } }
public int checkWinner(int[][] chessboard) { for (int i = 0; i < 22; i++) { if (checkRow(chessboard, i) != 0) { return checkRow(chessboard, i); } }
for (int j = 0; j < 22; j++) { if (checkColumn(chessboard, j) != 0) { return checkColumn(chessboard, j); } }
if (checkDiagonal(chessboard) != 0) { return checkDiagonal(chessboard); }
return 0; }
public int checkRow(int[][] chessboard, int row) { for (int i = 0; i < 16; i++) { if (chessboard[row][i] == chessboard[row][i + 1] && chessboard[row][i + 1] == chessboard[row][i + 2] && chessboard[row][i + 2] == chessboard[row][i + 3] && chessboard[row][i + 3] == chessboard[row][i + 4] && chessboard[row][i] != 0) { return chessboard[row][i]; } } return 0; }
public int checkColumn(int[][] chessboard, int col) { for (int i = 0; i < 16; i++) { if (chessboard[i][col] == chessboard[i + 1][col] && chessboard[i + 1][col] == chessboard[i + 2][col] && chessboard[i + 2][col] == chessboard[i + 3][col] && chessboard[i + 3][col] == chessboard[i + 4][col] && chessboard[i][col] != 0) { return chessboard[i][col]; } } return 0; }
public int checkDiagonal(int[][] chessboard) { for (int i = 0; i < 16; i++) { for (int j = 0; j < 16; j++) { if (chessboard[i][j] == chessboard[i + 1][j + 1] && chessboard[i + 1][j + 1] == chessboard[i + 2][j + 2] && chessboard[i + 2][j + 2] == chessboard[i + 3][j + 3] && chessboard[i + 3][j + 3] == chessboard[i + 4][j + 4] && chessboard[i][j] != 0) { return chessboard[i][j]; } } }
for (int i = 0; i < 16; i++) { for (int j = 4; j < 19; j++) { if (chessboard[i][j] == chessboard[i + 1][j - 1] && chessboard[i + 1][j - 1] == chessboard[i + 2][j - 2] && chessboard[i + 2][j - 2] == chessboard[i + 3][j - 3] && chessboard[i + 3][j - 3] == chessboard[i + 4][j - 4] && chessboard[i][j] != 0) { return chessboard[i][j]; } } }
return 0; }
public boolean getColor(){ return chessColor; }
public void reportColor(){ if (chessColor) { chessColor = false; } else { chessColor = true; } }
|
悔棋操作
悔棋和对手悔棋的操作主要的就是看是否是自己的回合,如果是自己的回合,需要需要悔棋两步,如果不是自己的回合,只需要悔棋一步,并更改下棋方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void back(){
if(this.Backnum>0) { if(!turnyou) { Vars.model.removeChess(); Vars.chessPanel.repaint(); Vars.controler.reportColor(); turnyou=true; } else if (turnyou) { Vars.model.removeChess(); Vars.model.removeChess(); Vars.chessPanel.repaint();
} Backnum--; } }
|
对手悔棋操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void NETback() { if(!turnyou) { Vars.model.removeChess(); Vars.model.removeChess(); Vars.chessPanel.repaint();
} else if (turnyou) {
Vars.model.removeChess(); Vars.chessPanel.repaint(); Vars.controler.reportColor(); turnyou=false; } }
|