对对碰游戏在n*n的游戏池中进行,每个格子中有一个图案。鼠标连续选中两个横排或竖排相邻的图案,它们的位置会互换,互换后如果横排或者竖排有3个以上相同的图像,则可以消去该图像,并得分。
游戏的基本规则如下:
①交换
玩家选择两个横排或竖排相邻的图案进行位置互换,如果互换成功则能消去图案,否则取消位置交换。
②消去
玩家选择两个横排或竖排相邻的图案进行位置互换,互换后如果横排或者竖排上有超过3个相同的图像,则消去这几个相同的图像,消去图像后的空格由其上面的图案掉下来补齐,每次消去图像,玩家都可以获得分数。
③连锁
玩家消去图像后,上面的图像掉下来补齐,如果此时游戏池里有连续相同的3个或3个以上的图像,则可以消去这些图像。消去后的空格由上面的图像掉下来补充,继续触发连锁,直到不满足连锁条件为止。
本次制作的对对碰运行效果如下图所示:
使用到的素材文件夹:
素材及完整源码链接:https://pan.baidu.com/s/1J1iXvgvVrNZI_Waq3v3xNA 提取码: iefw
游戏面板是n*n的方块组成,在完成其游戏功能的前提下,需要尽可能保持其扩展性。在这里我设置图案的种类共有n-2种,假如10*10的面板,则有8种图案,假如是8*8的面板,则有6种图案。游戏池数据使用二维数组map保存,存储的是图案种类ID,使用Image数组pics存储各种图案的图像,绘画面板时通过数组信息和图片ID即可对游戏池状况进行绘画。在定时器progress的控制下,推动游戏进行,这里设置游戏时间是100秒。使用isClick变量去标记玩家是不是第二次点击图案,使用clickX、clickY变量记录第一次点击图案的数组下标。
获取图片及显示图片:
使用Toolkit工具类获取图片,存储到Image数组,再遍历map数组,根据数组下标转换成左上角像素坐标,比如说map[3][4],在这里它的左上角x坐标为4*W+leftX,y坐标为3*W+leftY。最后根据左上角坐标和图案ID,绘制边长为W的图案。
private void getPics() {
for(int i=0;i
玩家鼠标点击事件:
先获取鼠标点击处减去偏移量后的x,y坐标,如果不在游戏池面板内,return;
用tempX存储这次点击的二维数组x下标,tempY存储这次点击的二维数组y下标(形如map[x][y])。
①如果是第一次点击:
修改isClick标记变量为true,用clickX,clickY记录此次点击的数组x下标和y下标。
②如果是第二次点击:
判断两次点击的图案是否横排或竖排相邻,如果相邻则先交换两数组元素的值。
使用isThreeLinked方法判断两个图案交换后是否存在可消去图案的情况,如果存在,则消去可消去的图案并使用downAnimal方法补齐游戏池,接着扫描游戏池中是否存在可消除的图案,如果存在则触发连锁消去事件,接着使用downAnimal方法补齐游戏池....直到当前游戏池没有可消去的图案为止。
如果交换后不存在可消去图案的情况,两次点击的图案位置重新换回。
public void mousePressed(MouseEvent e) {
int x = e.getX()-leftX;
int y = e.getY()-leftY;
if(x<0||y<0||x>=50*n||y>=50*n){
return ;
}
int tempX = y/W;
int tempY = x/W;
if(isClick){//第二次点击
if((tempX==clickX&&(tempY==clickY+1||tempY==clickY-1))||(tempY==clickY&&(tempX==clickX+1||tempX==clickX-1))){//如果两次点击的图案相邻
//交换
int help = map[tempX][tempY];
map[tempX][tempY] = map[clickX][clickY];
map[clickX][clickY] = help;
repaint();
if(isThreeLinked(tempX,tempY)||isThreeLinked(clickX,clickY)){//判断是否存在可消去的方块
// System.out.println("可以消去");
if(isThreeLinked(tempX,tempY)){
removeThreeLinked(tempX,tempY);
}
if(isThreeLinked(clickX,clickY)){
removeThreeLinked(clickX,clickY);
}
downAnimal();
updateAnimal();
repaint();
while(globalSearach(1)){
globalSearach(2);
downAnimal();
updateAnimal();
repaint();
}
}else{
//System.out.println("没有可消去的方块");
//交换回来
help = map[tempX][tempY];
map[tempX][tempY] = map[clickX][clickY];
map[clickX][clickY] = help;
}
isClick = false;
}else{//不相邻或者就是点击的还是自身
isClick = true;
clickX = tempX;
clickY = tempY;
drawSelectedBlock(tempY*W+leftX, tempX*W+leftY, this.getGraphics());
}
}else{
isClick = true;
clickX = tempX;
clickY = tempY;
drawSelectedBlock(tempY*W+leftX, tempX*W+leftY, this.getGraphics());
}
}
判断map[x][y]处是否存在可消去图案:
使用count变量记录连续相同的图案数目,count=1,先水平方向判断是否存在三个以上相邻图案,如果count>=3则返回true;
否则再重置count=1,从垂直方向判断是否存在三个以上相邻图案,如果count>=3则返回true。
如果还不存在可消去图案,返回false:
//检测是否存在三个以上相连的方块
private boolean isThreeLinked(int x, int y) {
int count = 1;
if(x+1=0){
for(int i=x-1;i>=0;i--){
if(map[i][y]==map[x][y]){
count++;
}else{
break;
}
}
}
if(count>=3){
return true;
}
count = 1;
if(y+1=0){
for(int i=y-1;i>=0;i--){
if(map[x][i]==map[x][y]){
count++;
}else{
break;
}
}
}
if(count>=3){
return true;
}
return false;
}
消除map[x][y]处三个以上相连的图案:
使用count记录可消去的图案数量,linked作为标记水平方向或竖直方向上相连的图案数量,
先判断竖直方向上相连的图案数量是否>=3,如果是,则消除掉竖直方向相连的图案并且count++;
接着置linked为1,判断水平方向上相连的图案数量是否>=3,如果是,则消除水平方向相连的图案并且count++;
最后置map[x][y]为空,分数+=count*10;
//消除三个以上相连的方块
private void removeThreeLinked(int x, int y) {
int count = 1;
int linked = 1;
if(x+1=0){//向上探测
for(int i=x-1;i>=0;i--){
if(map[i][y]==map[x][y]){
linked++;
}else{
break;
}
}
}
if(linked>=3){//上下相邻超过三个方块
for(int i=x-1;i>=0;i--){
if(map[i][y]==map[x][y]){
count++;
map[i][y] = EMPTY;
}else{
break;
}
}
for(int i=x+1;i=0){//向左探测
for(int i=y-1;i>=0;i--){
if(map[x][i]==map[x][y]){
linked++;
}else{
break;
}
}
}
if(linked>=3){//左右相邻超过三个方块
for(int i=y-1;i>=0;i--){
if(map[x][i]==map[x][y]){
count++;
map[x][i] = EMPTY;
}else{
break;
}
}
for(int i=y+1;i
扫描游戏池:
如果flag==1,只判断游戏池中是否存在可消除的图案,如果存在返回true;
否则消除游戏池中可消除的所有图案。
//1扫描是否存在可消除方块
//2扫描并消除可消除方块
private boolean globalSearach(int flag) {
if(flag == 1){
for(int i=0;i
图案下落填充:
从最后一行向上扫描游戏池,如果数组元素为空,则找到和它同一列,在它上方的第一个非空元素进行交换
//图案下落
private void downAnimal() {
for(int i=n-1;i>=0;i--){
for(int j=0;j=0){
if(map[temp][j]!=EMPTY){
int help = map[i][j];
map[i][j] = map[temp][j];
map[temp][j] = help;
break;
}
temp--;
}
}
}
}
}
更新游戏池状况:
图案下落后,此时空块都位于最上层,可以直接随机生成图案ID赋值给空的数组元素:
//更新图案数组
private void updateAnimal() {
for(int i=0;i
画选中框:
根据左上角x,y像素坐标,画框:
//画选中框
private void drawSelectedBlock(int x, int y, Graphics g) {
Graphics2D g2 = (Graphics2D) g;//生成Graphics对象
BasicStroke s = new BasicStroke(1);//宽度为1的画笔
g2.setStroke(s);
g2.setColor(Color.RED);
g.drawRect(x+1, y+1, 48, 48);
}
历史记录读写:
基础的文件IO操作,如果文件不存在自动新建:
//读取历史记录
public int getBestScore(){
File file = new File("D://GameRecordAboutSwing");
if(!file.exists()){
file.mkdirs();
}
File record = new File("D://GameRecordAboutSwing/SupperzzleGame.txt");
try{
if(!record.exists()){//如果不存在,新建文本
record.createNewFile();
fos = new FileOutputStream(record);
dos = new DataOutputStream(fos);
String s = "0";
dos.writeBytes(s);
}
//读取记录
fis = new FileInputStream(record);
dis = new DataInputStream(fis);
String str = dis.readLine();
bestScore = Integer.parseInt(str);
}catch(Exception e){
e.printStackTrace();
}finally{
try {
if(fis!=null)
fis.close();
if(dis!=null)
dis.close();
if(fos!=null)
fos.close();
if(dos!=null)
dos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return bestScore;
}
//更新历史记录
public void updateBestScore(){
File record = new File("D://GameRecordAboutSwing/SupperzzleGame.txt");
try {
//清空原有记录
FileWriter fileWriter =new FileWriter(record);
fileWriter.write("");
fileWriter.flush();
fileWriter.close();
//重新写入文本
fos = new FileOutputStream(record);
dos = new DataOutputStream(fos);
String s = score.getText();
bestScore = Integer.parseInt(score.getText());
dos.writeBytes(s);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(fos!=null)
fos.close();
if(dos!=null)
dos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
HelpPanel.record.setText(bestScore+"");
}
开始游戏:
设定进度条progress最大值为100,线程每秒自增1,游戏开始时初始化进度条progress并为游戏面板添加鼠标监听和键盘事件,进度条满之后,移除游戏面板的监听并提示玩家游戏成绩,如果当前分数高于历史记录则进行历史记录的更新。
public MyFrame(){
actionPanel.setLayout(new FlowLayout());
actionPanel.add(buttonRestart,BorderLayout.CENTER);
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add(helpPanel,BorderLayout.NORTH);
this.getContentPane().add(gamePanel,BorderLayout.CENTER);
this.getContentPane().add(actionPanel,BorderLayout.SOUTH);
this.setSize(700,700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("对对碰");
this.setVisible(true);
buttonRestart.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(flag)
return ;
flag = true;
gamePanel.addKeyListener(gamePanel);
gamePanel.addMouseListener(gamePanel);
gamePanel.startGame();
buttonRestart.setEnabled(false);
HelpPanel.score.setText(0+"");
new Thread(new Runnable(){
@Override
public void run() {
nowTime = 0;
while(true){
try {
Thread.currentThread().sleep(1000);
nowTime++;
HelpPanel.setTime(nowTime);
if(nowTime==100){
gamePanel.removeMouseListener(gamePanel);
gamePanel.removeKeyListener(gamePanel);
int score = Integer.parseInt(helpPanel.score.getText());
int record = Integer.parseInt(helpPanel.record.getText());
if(score>record){
JOptionPane.showMessageDialog(null, "游戏结束,你的得分是"+score+",刷新了历史记录"+record);
helpPanel.updateBestScore();
}else{
JOptionPane.showMessageDialog(null, "游戏结束,你的得分是"+HelpPanel.score.getText());
}
buttonRestart.setEnabled(true);
flag = false;
break;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();;
}
});
}
主要游戏功能到这里已经介绍完毕,玩家可以使用A键打乱游戏池,游戏保证了开始时不存在图案连锁消除的情况。
由于完整源码篇幅过长,这里不再贴出,素材和工程均已上传到网盘。
素材与完整源码链接:https://pan.baidu.com/s/1J1iXvgvVrNZI_Waq3v3xNA 提取码: iefw