什么2048数字游戏?
《2048》,是一款益智小游戏,这款游戏是由年仅19岁的意大利程序员加布里勒希鲁尼(Gabriele Cirulli)开发出来的,官方版本只能在网页上或通过其移动网站运行。
在传统版本中,有16个格子,初始时会有两个格子上安放了两个数字2,每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方随即出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。
在改朝换代版本中,数字置换成中国自夏开始的各个朝代,就是两个夏拼成一个商,两个商拼成一个周……每次碰撞后便生成下一个朝代,以此类推直到中华人民共和国完成通关。(本程序涉及的朝代只有夏、商、周、秦、汉、隋、唐、宋、元、明、清,如果需要更完整的朝代更迭可自行添加)
【代码如下】
首先我们定义一个App类,在主函数中创建一个MainFrame的实例,而MainFrame在创建实例时会执行它的构造方法。
package com;
public class App {
public static void main(String[] args){
new MainFrame();
}
}
在MainFrame的构造方法中,调用了 initFrame()用于初始化窗体,调用了initData()用于初始化数据,调用了initMenu()用于初始化菜单,调用了paintView()用于初始化游戏界面,调用了playMusic()用于循环播放背景音乐,最后再添加键盘监听,使得用户在按下↑、↓、←、→键时程序能够有所反应。(注意:在playMusic()函数中关于背景音乐的打开路径和paintView()函数中关于游戏界面图片的打开路径根据自身存放位置考虑,建议使用相对路径。而且背景音乐的格式不能是mp3格式,可以用wav格式。)
package com;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
import javax.swing.*;
import java.util.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
public class MainFrame extends JFrame implements KeyListener,ActionListener{
int[][] datas=new int[4][4];
/*
int[][] datas={//失败数组
{2,4,8,4},
{16,32,64,8},
{128,2,256,2},
{512,8,1024,64}
};*/
int loseflag=1; //是否展现失败图片的开关:1关闭,0开启
int score=0; //得分
String theme="flower";//图片类别
//创建JMenuItem对象
JMenuItem item1=new JMenuItem("flower");
JMenuItem item2=new JMenuItem("gold");
JMenuItem item3=new JMenuItem("blackWhite");
/**构造方法*/
public MainFrame(){
//初始化窗体
initFrame();
//初始化数据
initData();
//初始化菜单
initMenu();
//绘制界面
paintView();
//为窗体添加键盘监听
this.addKeyListener(this);
//设置窗体可见
setVisible(true);
//播放背景音乐
playMusic();
}
public void playMusic() {// 背景音乐播放
try {
AudioInputStream ais = AudioSystem.getAudioInputStream(new File("music//1.wav")); //绝对路径
AudioFormat aif = ais.getFormat();
final SourceDataLine sdl;
DataLine.Info info = new DataLine.Info(SourceDataLine.class, aif);
sdl = (SourceDataLine) AudioSystem.getLine(info);
sdl.open(aif);
sdl.start();
FloatControl fc = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
// value可以用来设置音量,从0-2.0
double value = 2;
float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
fc.setValue(dB);
int nByte = 0;
final int SIZE = 1024 * 64;
byte[] buffer = new byte[SIZE];
while (nByte != -1) {
nByte = ais.read(buffer, 0, SIZE);
sdl.write(buffer, 0, nByte);
}
sdl.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
public void initMenu() {
//创建JMenuBar对象
JMenuBar menuBar=new JMenuBar();
//创建JMenu栏目对象
JMenu menu=new JMenu("换肤");
menuBar.add(menu);
menu.add(item1);
menu.add(item2);
menu.add(item3);
//注册监听
item1.addActionListener(this);
item2.addActionListener(this);
item3.addActionListener(this);
//给窗体设置菜单
setJMenuBar(menuBar);
}
/**该方法用于初始化数据--对datas数组进行初始化*/
public void initData() {
createNum();
createNum();
}
/**此方法用于初始化窗体*/
public void initFrame() {
setDefaultCloseOperation(3);//调用成员方法,设置Java程序随着窗口关闭而关闭
setSize(514,538);//调用成员方法,设置窗口大小(宽,高)
setLocationRelativeTo(null);//调用成员方法,设置窗口处于显示器中间位置
setAlwaysOnTop(true);//调用成员方法,设置窗口置顶
setTitle("改朝换代");//调用成员方法,设置窗口标题
}
/**此方法用于绘制界面内容*/
public void paintView() {
//移除掉界面内所有内容
getContentPane().removeAll();
//界面得分内容
JLabel scoreLabel=new JLabel("得分:"+score);
scoreLabel.setBounds(50,20,100,20);
getContentPane().add(scoreLabel);
if(loseflag==0) {
JLabel loseLabel=new JLabel(new ImageIcon("image\\"+theme+"\\fail.png"));
loseLabel.setBounds(50,50,400,300);
getContentPane().add(loseLabel);
}
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
JLabel image=new JLabel(new ImageIcon("image\\"+theme+"\\"+datas[i][j]+".png"));
image.setBounds(50+100*j,50+100*i,100,100);
getContentPane().add(image);
}
}
JLabel image=new JLabel(new ImageIcon("image\\"+theme+"\\background.png"));
image.setBounds(40,40,420,420);
getContentPane().add(image);
//刷新界面的方法
getContentPane().repaint();
}
/**无法监听到上下左右按键,无需关注*/
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
/**键盘按下时所触发的方法,在这个方法中区分出上下左右按键*/
@Override
public void keyPressed(KeyEvent e) {
int KeyCode = e.getKeyCode();
if(KeyCode==37) {
//调用左移动的方法
moveToLeft(1);
createNum() ;
}else if(KeyCode==38) {
//调用上移动的方法
moveToTop(1);
createNum() ;
}else if(KeyCode==39) {
//调用右移动的方法
moveToRight(1);
createNum() ;
}else if(KeyCode==40) {
//调用下移动的方法
moveToBottom(1);
createNum() ;
}
//重新绘制界面
paintView();
//判断游戏是否结束
check();
}
/**此方法用于处理一维数组的反转*/
public void swap(int[] arr) {
for(int start=0,end=arr.length-1;start<arr.length/2;start++,end--){
int temp;
temp=arr[start];
arr[start]=arr[end];
arr[end]=temp;
}
}
/**此方法用于处理二维数组的反转*/
public void horizontaSwap() {
for(int i=0;i<datas.length;i++) {
swap(datas[i]);
}
}
/**此方法用于处理二维数组元素顺时针旋转*/
public void clockwise() {
int [][]arr=new int[4][4];
for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++){
arr[j][3-i]=datas[i][j];
}
}
datas=arr;
}
/**此方法用于处理二维数组元素逆时针旋转*/
public void antilockwise() {
int [][]arr=new int[4][4];
for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++){
arr[3-j][i]=datas[i][j];
}
}
datas=arr;
}
/**此方法用于处理数据左移动*/
public void moveToLeft(int flag) {
int[][]arr=new int[4][4];
for(int i=0,n;i<datas.length;i++) {
n=0;
for(int j=0;j<datas[i].length;j++){
if(datas[i][j]!=0) {
arr[i][n]=datas[i][j];
n++;
}
}
}
datas=arr;
//合并元素后,后续元素前移,并在末尾补0
for(int i=0;i<datas.length;i++) {
for(int x = 0;x<3;x++) {
if(datas[i][x]==datas[i][x+1]) {
datas[i][x]*=2;
if(flag==1) {//判断是否真正左移
score+=datas[i][x]; //计算得分
}
for(int j = x+1;j<3;j++) {//后续元素前移,并在末尾补0.
datas[i][j]=datas[i][j+1];
}
datas[i][3]=0;
}
}
}
}
/**此方法用于处理数据右移动*/
public void moveToRight(int flag) {
horizontaSwap();
moveToLeft(flag);
horizontaSwap();
}
/**此方法用于处理数据上移动*/
public void moveToTop(int flag) {
antilockwise();
moveToLeft(flag);
clockwise();
}
/**此方法用于处理数据下移动*/
public void moveToBottom(int flag) {
clockwise();
moveToLeft(flag);
antilockwise();
}
/**此方法用于拷贝二维数组的数据*/
public void copyArray(int[][]src,int[][]dest) {
for(int i=0;i<src.length;i++) {
for(int j=0;j<src[i].length;j++) {
dest[i][j]=src[i][j];
}
}
}
/**此方法用于整合四种移动的判定*/
public void check() {
if(checkLeft()==false&&checkRight()==false&&checkTop()==false&&checkBottom()==false) {
loseflag = 0;
paintView();
}
}
/**此方法用于判断是否可以左移*/
public boolean checkLeft() {
//创建新数组用于备份原数组数据
int[][] arr=new int[4][4];
//将原数组数据拷贝到新数组中
copyArray(datas,arr);
//调用左移动方法,对原数组数据进行左移动
moveToLeft(2);
//false:不可以移动;true:可以移动
boolean flag=false;
//使用移动后的数据与原数组数据逐个进行比对,并使用flag变量记录
lo:for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++) {
if(datas[i][j]!=arr[i][j]) {
flag=true;
break lo;
}
}
}
//恢复原数组数据
copyArray(arr,datas);
//返回结果信息
return flag;
}
/**此方法用于判断是否可以右移*/
public boolean checkRight() {
int[][] arr=new int[4][4];
copyArray(datas,arr);
moveToRight(2);
boolean flag=false;
lo:for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++) {
if(datas[i][j]!=arr[i][j]) {
flag=true;
break lo;
}
}
}
copyArray(arr,datas);
return flag;
}
/**此方法用于判断是否可以上移*/
public boolean checkTop() {
int[][] arr=new int[4][4];
copyArray(datas,arr);
moveToTop(2);
boolean flag=false;
lo:for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++) {
if(datas[i][j]!=arr[i][j]) {
flag=true;
break lo;
}
}
}
copyArray(arr,datas);
return flag;
}
/**此方法用于判断是否可以下移*/
public boolean checkBottom() {
int[][] arr=new int[4][4];
copyArray(datas,arr);
moveToBottom(2);
boolean flag=false;
lo:for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++) {
if(datas[i][j]!=arr[i][j]) {
flag=true;
break lo;
}
}
}
copyArray(arr,datas);
return flag;
}
/**键盘松开时所触发的方法*/
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
/**此方法用于从空白的位置,随机产生2号数字块*/
public void createNum() {
int[]arrayI=new int[16];
int[]arrayJ=new int[16];
int w=0;
for(int i=0;i<datas.length;i++) {
for(int j=0;j<datas[i].length;j++) {
if(datas[i][j]==0) {
arrayI[w]=i;
arrayJ[w]=j;
w++;
}
}
}
if(w!=0) {
Random r=new Random();
int index =r.nextInt(w);//生成[0,w)的随机数
int x=arrayI[index];
int y=arrayJ[index];
datas[x][y]=2;
}
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==item1) {
theme ="flower";
}else if(e.getSource()==item2) {
theme ="gold";
}else if(e.getSource()==item3) {
theme ="blackWhite";
}
//重新绘制界面
paintView();
}
}
如果我们想要使程序在没有java环境下也可以运行,我们可以将程序用exe4j软件将Java程序打包转换为.exe的可执行文件。我使用的是Java12,exe4j8.0版本。具体打包过程可见如何将 java 项目打包成exe可执行文件_码猿小菜鸡的博客-CSDN博客
【打包成exe可执行文件后】
【游戏不同皮肤界面】