提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
这是我面向对象程序设计课程的大作业,希望能够帮到大家。因为这个项目中一些设计需要用到矢量图才能完成,我找了许久发现我的世界的素材比较好用,于是就整体走的是我的世界风。
一、效果展示
话不多说,直接上图。用swing做的。
1.游戏主界面
![](https://i-blog.csdnimg.cn/blog_migrate/5966ece85329273bf5515b7e57dd72ee.png)
2.游戏内界面
3.排行榜
二、连连看算法
连连看最重要的算法应该是连接的算法了,连连看里有三种连接方式:无拐点连接,单拐点连接,双拐点连接。
1.无拐点连接
![](https://i-blog.csdnimg.cn/blog_migrate/23e6b4369ef19deeed543eb2663a4c24.png)
最简单的一种吧,不做过多解释。
代码如下(示例):
//在以下连接判断中,是在map这个二维数组中判断的,二维数组中的坐标是[y][x]
//写法是[clickX][clickY]。把ClickX当作y,把ClickY当作x
private boolean zeroCornerLink(int clickX1, int clickY1, int clickX2, int clickY2) {
if (clickX1 == clickX2) {//如果两个选中图片的所在行数相同,说明可能可以水平相联
if (clickY1 > clickY2) {//保证y1<y2
int temp = clickY1;
clickY1 = clickY2;
clickY2 = temp;
}
for (int i = clickY1 + 1; i < clickY2; i++) {
if (map[clickX1][i] != BLANK_STATE) {//如果两图片中间不空则无法相连
return false;
}
}
linkMethod = ZEROCORNER;
return true;
}
if (clickY1 == clickY2) {//两个图片处于同一列
if (clickX1 > clickX2) {//让第一个图片在上方
int temp = clickX1;
clickX1 = clickX2;
clickX2 = temp;
}
for (int i = clickX1 + 1; i < clickX2; i++) {
if (map[i][clickY1] != BLANK_STATE) {//如果两图片中间还有其他图片,说明不能直接垂直相连
return false;
}
}
linkMethod = ZEROCORNER;
return true;
}
return false;
}
2.单拐点连接
对于单拐点连接来说,拐点的x与一个相同,y与另一个相同。对于单拐点连接问题可以转换成两个拐点能否与两个点进行无拐点连接。
比如图中的(x1,y1)与(x2,y2)能单拐点连接则只需满足以下两种情况的任意一种:
1.(x1,y1)与(x1,y2)无拐点连接,(x2,y2)与(x1,y2)无拐带点连接
2. (x1,y1)与(x2,y1)无拐点连接,(x2,y2)与(x2,y1)无拐带点连接
//一个拐点的情况,拐点横坐标与一个相同,纵坐标与另一个相同
private boolean oneCornerLink(int clickX1, int clickY1, int clickX2, int clickY2) {
if (map[clickX1][clickY2] == BLANK_STATE && zeroCornerLink(clickX1, clickY2, clickX1, clickY1) && zeroCornerLink(clickX1, clickY2, clickX2, clickY2)) {
linkMethod = ONECORNER;
z1 = new Node(clickX1, clickY2);
return true;
}
if (map[clickX2][clickY1] == BLANK_STATE && zeroCornerLink(clickX2, clickY1, clickX1, clickY1) && zeroCornerLink(clickX2, clickY1, clickX2, clickY2)) {
linkMethod = ONECORNER;
z1 = new Node(clickX2, clickY1);
return true;
}
return false;
}
3.双拐点连接
图画的可能有点抽象,我的算法也可能不是最优的,但我的方法是这样的:点1(x1,y1)与点2(x2,y2)想要连接,那么假设从点1开始出发,先往上走一个格子,然后用新位置与点2判断是否可以进行单拐点连接,如果可以那么新位置算一个拐点,新位置和点2单拐点连接又有一个拐点,加起来就算双拐点了。如果不能进行单拐点连接,那么就继续往上走,如果走到顶了,还不行?那么回到点1位置往左往右往下一直走。
//判断是否可以通过两个拐点相连,思路是通过上下左右走直线,每走一步便用这一步的地点和目标地点用单拐点连接
private boolean twoCornerLink(int clickX1, int clickY1, int clickX2, int clickY2) {
//向上查找
for (int i = clickX1 - 1; i >= -1; i--) {
if (i > -1) {
if (map[i][clickY1] == BLANK_STATE) {
if (oneCornerLink(i, clickY1, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(i, clickY1);
z2 = new Node(i, clickY2);
return true;
}
} else
break;
} else if (i == -1 && zeroCornerLink(i, clickY2, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(i, clickY1);
z2 = new Node(i, clickY2);
return true;
}
}
//向下查找
for (int i = clickX1 + 1; i <= n; i++) {
if (i < n) {
if (map[i][clickY1] == BLANK_STATE) {
if (oneCornerLink(i, clickY1, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(i, clickY1);
z2 = new Node(i, clickY2);
return true;
}
} else
break;
} else if (i == n && zeroCornerLink(i, clickY2, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(i, clickY1);
z2 = new Node(i, clickY2);
return true;
}
}
//向左查找
for (int i = clickY1 - 1; i >= -1; i--) {
if (i > -1) {
if (map[clickX1][i] == BLANK_STATE) {
if (oneCornerLink(clickX1, i, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(clickX1, i);
z2 = new Node(clickX2, i);
return true;
}
} else
break;
} else if (i == -1 && zeroCornerLink(clickX2, i, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(clickX1, i);
z2 = new Node(clickX2, i);
return true;
}
}
//向右查找
for (int i = clickY1 + 1; i <= n; i++) {
if (i < n) {
if (map[clickX1][i] == BLANK_STATE) {
if (oneCornerLink(clickX1, i, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(clickX1, i);
z2 = new Node(clickX2, i);
return true;
}
} else
break;
} else if (i == n && zeroCornerLink(clickX2, i, clickX2, clickY2)) {
linkMethod = TWOCORNER;
z1 = new Node(clickX1, i);
z2 = new Node(clickX2, i);
return true;
}
}
return false;
}
三、 杂项
连连看的算法虽然很简单,但大家可能会卡在一些根连连看算法无关的地方,回顾我写这个大作业的历程,我觉得有必要把一些觉得比较重要的东西告诉大家。
1.怎么把图片读进去?
//从Picture里面读取图片
private void getPics() {
pics = new Image[n];
for (int i = 0; i < n; i++) {
pics[i] = Toolkit.getDefaultToolkit().getImage("Picture/" + (i + 1) + ".png");
}
}
2.你是怎么做到点击连连看里的图标的?
我的这个项目是点击不了图片的,我一开始想做图片按钮,就是把按钮里填充图片,然后用按钮实现,但是黄了,不会弄。后面参考别人的资料后我就学着改为获取鼠标点击界面的xy坐标,然后进行后续的操作。
先看代码:
@Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
Graphics g = this.getGraphics();
int x = e.getX() - leftX;//点击位置x-偏移量x
int y = e.getY() - leftY;//点击位置y-偏移量y
int i = y / imageHeight;//对应数组行数,根据像素坐标转换成数组下标坐标
int j = x / imageWidth;//对应数组列数
// if(i<0||j<0||i>n||j>n)//超出地图范围
// return;
if (Enabled) {
if (!isClick) {//如果是第一次点击
if (map[i][j] != BLANK_STATE) {
//选中图片并画框
clickId = map[i][j];
isClick = true;
clickX = i;
clickY = j;
drawSelectedBlock(j * imageWidth + leftX, i * imageHeight + leftY, g);
}
} else {//第二次点击了
if (map[i][j] != BLANK_STATE) {
if (map[i][j] == clickId) {//点击的是同一种图片
//两次选同一位置的图片解除选中状态
if (i == clickX && j == clickY) {
clearSelectBlock(clickX, clickY, g);
isClick = false;
}
//如果可以连通,画线连接,然后消去选中图片并重置第一次选中标识
else if (zeroCornerLink(clickX, clickY, i, j) || oneCornerLink(clickX, clickY, i, j) || twoCornerLink(clickX, clickY, i, j)) {
drawSelectedBlock(j * imageWidth + leftX, i * imageHeight + leftY, g);
drawLink(clickX, clickY, i, j);//画线连接
isClick = false;
} else {//相同图片但连接失败,把选定框给新选的
clearSelectBlock(clickX, clickY, g);
clickId = map[i][j];
clickX = i;
clickY = j;
drawSelectedBlock(j * imageWidth + leftX, i * imageHeight + leftY, g);
}
} else {//选的图片都不是同一种,把选定框给新的
clearSelectBlock(clickX, clickY, g);
clickId = map[i][j];//重新选中图片并画框
clickX = i;
clickY = j;
drawSelectedBlock(j * imageWidth + leftX, i * imageHeight + leftY, g);
}
}
}
}
}
如果你认真看完了代码,我相信你应该知道了,我定了一个地图数组,当我“点击”图片时,点击的xy值会经过一些列计算映射为一个map[i][j],然后后续的判断是否能连接,或者是画线消去之类的操作都只跟映射的map[i][j]有关。
3.你是如何实现map的?
我嘴笨,代码注释的够多了,请自己先看代码:
import java.util.ArrayList;
public class Map {
private final int[][] map;
private final int count;
private int n;
public Map(int count, int n) {//一共有count种不同的图案,n行n列
map = MapFactory.getMap(n);//获取n行n列的数组
this.count = count;
this.n = n;
}
public int[][] getMap(int m) {
n = m;
ArrayList<Integer> list = new ArrayList<Integer>();//先将等量图片ID添加到list中
//
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
list.add(j);
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int index = (int) (Math.random() * list.size());//从list中随机取一个图片ID,并将其添加到map数组中,再从list中删除掉它
map[i][j] = list.get(index);
list.remove(index);
}
}
return map;//返回一个图片随机生成的地图数组
}
public int[][] getResetMap() {//获取再次打乱后的地图信息
ArrayList<Integer> list = new ArrayList<Integer>();//list用来存储原先的地图信息
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (map[i][j] != -1)//如果(x,y)有图片,那么将该图片添加到list
list.add(map[i][j]);
map[i][j] = -1;//即使原来ID不为-1也要标空,不然后续重新绘图的时候图片会变多
}
}
//将原先地图上剩余的未消去的图片打乱
while (!list.isEmpty()) {
int index = (int) (Math.random() * list.size());//从list中随机取一个图片ID,并将其添加到数组中,再从list中删除掉它
boolean flag = false;
while (!flag) {
int i = (int) (Math.random() * n);//获取随机的地图行列
int j = (int) (Math.random() * n);
if (map[i][j] == -1) {//如果该位置无图片
map[i][j] = list.get(index);
list.remove(index);
flag = true;
}
}
}
return map;
}
public class MapFactory {
static int[][] map;
public static int[][] getMap(int n) {
map = new int[n][n];//生成n*n地图
//初始化地图信息为空
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
map[i][j] = -1;
}
}
return map;
}
}
}
首先声明,这段代码有借鉴的成分在里面,不是我原创的。
简单来讲就是在游戏开始时,给每个图片编个号,确定每个图片的位置,然后再在界面中画出来。
4.怎么把棋盘/地图画出来?
//画出棋盘和棋盘上的图片
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//图片长宽
imageWidth = getWidth() / (4 + n);
imageHeight = getHeight() / (2 + n);
//偏移量
leftX = getWidth() / (n - 2);
leftY = getHeight() / (n + 2);
//画背景图片
g.drawImage(backgroundImage.getImage(), 0, 0, getWidth(), getHeight(), this);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g.drawImage(blank.getImage(), leftX + j * imageWidth, leftY + i * imageHeight, imageWidth, imageHeight, this);
if (map[i][j] != BLANK_STATE) {
g.drawImage(pics[map[i][j]], leftX + j * imageWidth, leftY + i * imageHeight, imageWidth, imageHeight, this);
}
}
}
}
5.如何实现连接图片后图片消失?
连接图片后,图片在map数组中的值会被标为空格子,从数据层面上实现消失。
接着是以下代码,在原来的图片的位置再重新画个空格子就实现了图片的消失。
//清除选中框,用棋盘的格子把原来的地方盖住,然后再在上面画图片
public void clearSelectBlock(int i, int j, Graphics g) {
g.drawImage(blank.getImage(), leftX + j * imageWidth, leftY + i * imageHeight, imageWidth, imageHeight, this);
g.drawImage(pics[map[i][j]], leftX + j * imageWidth, leftY + i * imageHeight, imageWidth, imageHeight, this);
}
总结
觉得有用的话就点个赞吧。有什么不懂的地方欢迎提问。
链接:https://pan.baidu.com/s/1Ct8ZnhRg9GzBhTEquohdTg?pwd=j9yn
提取码:j9yn