前言
目的:
想寫一篇面向新手的文章,將做一個基于javaSE的2048小游戲
專案介紹
這是一個簡單的小游戲,游戲的規則很簡單,你需要控制所有方塊向一個方向運動,兩個相同數字方塊撞在一起之后合并成為他們的和,每次操作之后會隨機生成一個2或者4,最終得到一個 “2048”的方塊就算勝利了,
所需技術
java基本語法、運算子與流程控制、面向物件基礎、GUI
實作思路
整體思路
將整個專案分為三層:資料層、視圖層、控制層,
- 資料層,Data類,2048游戲最主要用到的資料結構就是二維陣列,使用二維陣列保存當前的狀態,這個類中有一些方法,方法的作用是改變二維陣列的值來完成資料的上下左右移動,
- 視圖層,視圖層的作用是展示資料,它只跟資料層打交道,負責把資料畫出來,
- 控制層,它是一個監聽器,監聽鍵盤的操作,根據不同的操作來呼叫資料層里的方法,從而改變資料的值,
第一層
/**
* 這是一個物體類,可以通過一個二維陣列保存資料
*/
public class Data{
/**
* 保存核心資料
*/
int[][] Numbers;
/**
* 構造器
*/
public Data(){
Numbers = new int[4][4];
}
public Data(int[][] a ){
}
//方法,進行上下左右移動
public void right() {
}
public void left() {
}
public void up() {
}
public void down() {
}
}
第二層
import Game.Numbers;
import javax.swing.*;
import java.awt.*;
/**
* 這是視圖層,用于展示資料
*/
public class View extends JPanel {
/**
* 私有,用于展示的資料
*/
private numbers;
/**
* 構造器
* @param numbers
*/
public view(Numbers numbers){
this.numbers = numbers;
}
/**
* 根據資料畫圖
* @param g
*/
public void paint(Graphics g) {
}
/**
* 引數是想畫的資料,作用是將資料畫出來
* @param numbers
*/
public void showdata(Numbers numbers){
this.numbers = numbers;
repaint();
}
}
(這是思路,具體實作時簡化了)
第三層
package Game;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* 這是控制層,監聽鍵盤操作,做出回應,
*/
public class Control extends JPanel implements KeyListener {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
//再這里根據按下的鍵呼叫Numbers里的方法
//然后將資料交給視圖層,畫圖
}
@Override
public void keyReleased(KeyEvent e) {
}
}
具體實作方法
資料層
資料層是本專案最核心的類,而最核心的方法是資料的左移,即left(),你可能會問,為什么只有左移,那右、上、下移動呢,其實,如果我們實作了左移,只需對原二維陣列做轉置或鏡像操作,再進行左移,再轉置或鏡像回去就能實作右、上、下移動,
left()方法的實作
------->
上面是效果圖,我在這里只寫一下我的思路(不只這一種),我們將四行拆解,一行一行實作合并,先寫一個方法,把一行中的空位消除,然后,從第一個元素開始遍歷,檢測這個元素和下一個元素能否合并,如果能就合并,直到遍歷到末尾,最后再做一次空位消除操作,就得到了想要的效果,
--clear()--->
----eliminate----> ![]()
public void clear(int[] Eliminated_array) {
// 寫一個清零函式,用于清除空白
int move = 0;
for(int i = 0;i<Eliminated_array.length;i++){
if(Eliminated_array[i]==0){
move++;
continue;
}else {
if(move!=0){
Eliminated_array[i-move]=Eliminated_array[i];
Eliminated_array[i] = 0;
merge=1;
}
}
}
}
public void eliminate(int[] Eliminated_array) {
// 給定一個一維陣列,將一維陣列,格式化
clear(Eliminated_array);
//成功完成了清零操作,進行合并
for(int i = 0;i<Eliminated_array.length-1;i++){
if(Eliminated_array[i]==Eliminated_array[i+1]&&Eliminated_array[i]!=0){
Eliminated_array[i]*=2;
Eliminated_array[i+1]=0;
}
}
clear(Eliminated_array);
}
right()方法的實作
有了left()方法,right()實作起來就很簡單,只要將保存資料的二維陣列進行鏡像操作,然后呼叫left()方法即可實作,
/**
* 對二維陣列進行鏡像操作
* @return
*/
public void Mirror() {
int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
for (int i = 0; i < Numbers.length; i++) {
for (int k = 0; k < Numbers[0].length; k++) {
arr2[i][Numbers[0].length - k - 1] = Numbers[i][k];
}
}
Numbers = arr2;
}
public void right() {
Mirror();
for (int i = 0; i < 4; i++) {
eliminate(Numbers[i]);
}
Mirror();
}
up()方法的實作
將保存資料的二維陣列轉置再呼叫left()方法即可,
/**
* 對二維陣列進行轉置
*/
public void Transposition() {
// 矩陣轉置,其實很簡單,重新創建一個陣列填充即可
int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
for (int i = 0; i < Numbers.length; i++) {
int k = 0;// arr2的行標記
for (int j = 0; j < Numbers[i].length; j++) {
arr2[k][i] = Numbers[i][j];
k++;
}
}
Numbers = arr2;
}
public void up() {
Transposition();
for (int i = 0; i < 4; i++) {
eliminate(Numbers[i]);
}
Transposition();
}
down()方法的實作.
將保存資料的二維陣列轉置再呼叫right()方法即可,
public void down() {
Transposition();
right();
Transposition();
}
視圖層
第二層用作資料展示,很簡單,直接上代碼,
//View層用于展示資料
public void paint(Graphics g) {
super.paint(g);
// 先畫背景
g.setColor(new Color(0x66ccff)); // 當然是藍色了
for (int i = 0; i < 4; i++) {
for (int k = 0; k < 4; k++) {
g.fillRoundRect(25 + i * 90, 120 + k * 90, 80, 80, 15, 15);
}
}
// 再畫數字
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (data.getNumbers()[j][i]!= 0) {
int FontSize = 30;
int MoveX = 0, MoveY = 0;
switch (data.getNumbers()[j][i]) {
case 2:
g.setColor(new Color(0xeee4da));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 4:
g.setColor(new Color(0xede0c8));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 8:
g.setColor(new Color(0xf2b179));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 16:
g.setColor(new Color(0xf59563));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 32:
g.setColor(new Color(0xf67c5f));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 64:
g.setColor(new Color(0xf65e3b));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 128:
g.setColor(new Color(0xedcf72));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 256:
g.setColor(new Color(0xedcc61));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 512:
g.setColor(new Color(0xedc850));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 1024:
g.setColor(new Color(0xedc53f));
FontSize = 27;
MoveX = -15;
MoveY = 0;
break;
case 2048:
g.setColor(new Color(0xedc22e));
FontSize = 27;
MoveX = -15;
MoveY = 0;
break;
default:
g.setColor(new Color(0x000000));
break;
}
g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 10, 10);
g.setColor(new Color(0x000000));
g.setFont(new Font("Arial", Font.PLAIN, FontSize));
g.drawString(data.Numbers[j][i] + "", 25 + i * 90 + 30 + MoveX, 120 + j * 90 + 50 + MoveY);
}
}
}
score_screen.setText("分數為" + ":" + score);
}
控制層
思路
控制層用于整個游戲的流程控制,核心是監聽器,監聽器負責回應鍵盤操作,當按鍵 按下時呼叫相關的方法改變資料層的資料,在這一層中需要有score全域變數記錄當前分數,preData變數記錄前一個狀態,
亂數生成
游戲規則規定,每移動一次會在隨機地方生成一個“2”或者“4”,我們在進行流程控制時,需要判斷鍵盤按下后當前資料是否發生改變,只有資料改變才生成亂數,方法很簡單,只需記錄之前狀態,當鍵盤回應后判斷之前狀態和當前狀態是否相同即可,
這種狀態按下左鍵,資料沒有改變,不會生成亂數
是否輸掉游戲
首先判斷操作后資料是否改變,若改變則肯定還沒有輸掉,如果資料沒有改變則判斷是否滿,若未滿,則一定沒有輸,若滿,就判斷分別進行上下左右移動后資料是否改變,若都不改變,則輸掉游戲,
/**
*根據Data,來判斷當前游戲是否輸了,邏輯很簡單,首先看這一回合有沒有移動過,若移動了則還沒有輸,否則看當前是否滿了,若未滿肯定沒有輸
* 否則上下左右移動一下,如果上下左右移動都沒有變,則游戲結束
* @param data
* @return
*/
public boolean isFailed(Data data){
boolean result = true;
System.out.println("是否滿了"+data.isFull());
if(isMove){
result = false;
}
else if(!data.isFull()){
result = false;
}else {
Data d = data;
data.up();
if(!data.equals(preData)){
result = false;
}
data = d;
data.down();
if(!data.equals(preData)){
result = false;
}
data = d;
data.right();
if(!data.equals(preData)){
result = false;
}
data = d;
data.left();
if(!data.equals(preData)){
result = false;
}
data = d;
}
return result;
}
整體代碼
package Game;
import java.util.Random;
public class Data {
//這是一個物體類,可以通過一個二維陣列保存資料
/**
* 保存核心資料
*/
int[][] Numbers;
/**
* 構造器
*/
public Data(){
Numbers = new int[4][4];
}
public Data(int[][] numbers ){
Numbers=numbers;
}
public int[][] getNumbers() {
return Numbers;
}
public void eliminate(int[] Eliminated_array) {
// 給定一個一維陣列,將一維陣列,格式化
clear(Eliminated_array);
//成功完成了清零操作,進行合并
for(int i = 0;i<Eliminated_array.length-1;i++){
if(Eliminated_array[i]==Eliminated_array[i+1]&&Eliminated_array[i]!=0){
Eliminated_array[i]*=2;
Eliminated_array[i+1]=0;
}
}
clear(Eliminated_array);
}
public void clear(int[] Eliminated_array) {
// 寫一個清零函式,用于清除空白
int move = 0;
for(int i = 0;i<Eliminated_array.length;i++){
if(Eliminated_array[i]==0){
move++;
continue;
}else {
if(move!=0){
Eliminated_array[i-move]=Eliminated_array[i];
Eliminated_array[i] = 0;
}
}
}
}
/**
* 對二維陣列進行轉置
*/
public void Transposition() {
// 矩陣轉置,其實很簡單,重新創建一個陣列填充即可
int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
for (int i = 0; i < Numbers.length; i++) {
int k = 0;// arr2的行標記
for (int j = 0; j < Numbers[i].length; j++) {
arr2[k][i] = Numbers[i][j];
k++;
}
}
Numbers = arr2;
}
/**
* 對二維陣列進行鏡像操作
* @return
*/
public void Mirror() {
int[][] arr2 = new int[Numbers[0].length][Numbers[0].length];
for (int i = 0; i < Numbers.length; i++) {
for (int k = 0; k < Numbers[0].length; k++) {
arr2[i][Numbers[0].length - k - 1] = Numbers[i][k];
}
}
Numbers = arr2;
}
public void right() {
Mirror();
for (int i = 0; i < 4; i++) {
eliminate(Numbers[i]);
}
Mirror();
}
public void left() {
for (int i = 0; i < 4; i++) {
eliminate(Numbers[i]);
}
}
public void up() {
Transposition();
for (int i = 0; i < 4; i++) {
eliminate(Numbers[i]);
}
Transposition();
}
public void down() {
Transposition();
right();
Transposition();
}
public void Random_generation_2() {
Random r = new Random();
if (isFull()) {
// 先判斷一下是否滿了
return;
}
while(true){
int x = r.nextInt(4);
int y = r.nextInt(4);
//控制概率
int flag = r.nextInt(100) + 1;
if (Numbers[x][y] == 0) {
if (flag < 30) {
Numbers[x][y] = 4;
} else {
Numbers[x][y] = 2;
}
break;
}
}
}
/**
* 用于悔步
* @param a
*/
public void undo(int[][] a) {
Numbers = a;
}
/**
* 判斷有沒有滿
* @return
*/
public boolean isFull() {
for (int i = 0; i < 4; i++) {
for (int k = 0; k < 4; k++) {
if (Numbers[i][k] == 0) {
return false;
}
}
}
return true;
}
/**
* 判斷preNumber和Number 是否相等
* @return
*/
public boolean isEqual(Data d){
int[][] numbers = d.getNumbers();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (Numbers[i][j] != numbers[i][j]) {
return false;
}
}
}
return true;
}
}
package Game;
import java.awt.*;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import com.sun.glass.events.KeyEvent;
public class game extends JFrame {
Data data;
JLabel score_screen;
Data preData;
public int score;
boolean isMove;
public game() {
this.setBounds(450, 100, 400, 500);
this.setTitle("2048小游戲");
this.setLayout(null);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
Init();
// 創建顯示分數
score_screen = new JLabel();
score_screen.setBounds(100, 10, 100, 30);
score_screen.setText("分數為" + ":" + score);
this.add(score_screen);
score_screen.setVisible(true);
//加監聽器
this.addKeyListener(new KeyListener() {
@Override
public void keyTyped(java.awt.event.KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(java.awt.event.KeyEvent e) {
// TODO Auto-generated method stub
// 呼叫對應的方法即可
preData = new Data(data.getNumbers());
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
data.left();
break;
case KeyEvent.VK_RIGHT:
data.right();
break;
case KeyEvent.VK_DOWN:
data.down();
break;
case KeyEvent.VK_UP:
data.up();
break;
}
if(!data.isEqual(preData)){
isMove = true;
}else {
isMove = false;
}
if(isMove){
score++;
data.Random_generation_2();
}
//在這里判斷輸贏
System.out.println("是否輸了"+isFailed(data));
if (isFailed(data)) {
DisplayToast();
}
repaint();
}
@Override
public void keyPressed(java.awt.event.KeyEvent e) {
// TODO Auto-generated method stub
}
});
}
/**
*根據Data,來判斷當前游戲是否輸了,邏輯很簡單,首先看這一回合有沒有移動過,若移動了則還沒有輸,否則看當前是否滿了,若未滿肯定沒有輸
* 否則上下左右移動一下,如果上下左右移動都沒有變,則游戲結束
* @param data
* @return
*/
public boolean isFailed(Data data){
boolean result = true;
System.out.println("是否滿了"+data.isFull());
if(isMove){
result = false;
}
else if(!data.isFull()){
result = false;
}else {
Data d = data;
data.up();
if(!data.equals(preData)){
result = false;
}
data = d;
data.down();
if(!data.equals(preData)){
result = false;
}
data = d;
data.right();
if(!data.equals(preData)){
result = false;
}
data = d;
data.left();
if(!data.equals(preData)){
result = false;
}
data = d;
}
return result;
}
public void Init() {
//關于init與構造器的區別,構造器的東西是只用一次的,但是init可以多次用
//初始化numbers 清空撤銷用list 清空score
data = new Data();
data.Random_generation_2();
data.Random_generation_2();
preData = data;
}
//View層用于展示資料
public void paint(Graphics g) {
super.paint(g);
// 先畫背景
g.setColor(new Color(0x66ccff)); // 當然是藍色了
for (int i = 0; i < 4; i++) {
for (int k = 0; k < 4; k++) {
g.fillRoundRect(25 + i * 90, 120 + k * 90, 80, 80, 15, 15);
}
}
// 再畫數字
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (data.getNumbers()[j][i]!= 0) {
int FontSize = 30;
int MoveX = 0, MoveY = 0;
switch (data.getNumbers()[j][i]) {
case 2:
g.setColor(new Color(0xeee4da));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 4:
g.setColor(new Color(0xede0c8));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 8:
g.setColor(new Color(0xf2b179));
FontSize = 30;
MoveX = 0;
MoveY = 0;
break;
case 16:
g.setColor(new Color(0xf59563));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 32:
g.setColor(new Color(0xf67c5f));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 64:
g.setColor(new Color(0xf65e3b));
FontSize = 29;
MoveX = -5;
MoveY = 0;
break;
case 128:
g.setColor(new Color(0xedcf72));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 256:
g.setColor(new Color(0xedcc61));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 512:
g.setColor(new Color(0xedc850));
FontSize = 28;
MoveX = -10;
MoveY = 0;
break;
case 1024:
g.setColor(new Color(0xedc53f));
FontSize = 27;
MoveX = -15;
MoveY = 0;
break;
case 2048:
g.setColor(new Color(0xedc22e));
FontSize = 27;
MoveX = -15;
MoveY = 0;
break;
default:
g.setColor(new Color(0x000000));
break;
}
g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 10, 10);
g.setColor(new Color(0x000000));
g.setFont(new Font("Arial", Font.PLAIN, FontSize));
g.drawString(data.Numbers[j][i] + "", 25 + i * 90 + 30 + MoveX, 120 + j * 90 + 50 + MoveY);
}
}
}
score_screen.setText("分數為" + ":" + score);
}
public void DisplayToast() {
JOptionPane.showMessageDialog(null, "你輸了");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// 主函式就是創建物件,然后初始化
game UI = new game();
// 創建一個按鈕用于悔步,和一個JLable顯示分數
}
}
總結
第一次寫博客,肯定會有錯誤,如有發現請指出,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/290181.html
標籤:其他
