Author’s brief introduction
Author name: The programming world is clear and hidden
CSDN blog expert, engaged in software development for many years, proficient in Java, JavaScript, the blogger is also from scratch to learn and grow step by step, know the importance of learning and accumulation, like to fight with the majority of ADC to upgrade, welcome your attention, look forward to learning, growth and take off with you!
Quote:
A few days ago saw this number game occasionally, felling still quite interesting, played once, unexpectedly won’t, how to play all is to lose, is really evil door, this does not serve as a programmer, I play not to win I write myself a, line not? I wrote it myself, I want to win, I set the conditions, is to play!!
rendering
Implementation approach
- Draw the window.
- Create a menu.
- Create all blank cards.
- Create a card (2 or 4) at random.
- Keyboard events listening (up, down, left, right – click listening).
- According to the direction of the keyboard, process the number movement merge.
- Add success and failure determination.
- Handle other finishing touches.
Code implementation
Create a window
First create a game form class GameFrame, inherit to JFrame, used to display on the screen (window object), each game has a window, set the window title, size, layout, etc.
package main;
import java.awt.Color;
import javax.swing.JFrame;
/** * form class */
public class GameFrame extends JFrame {
// constructor
public GameFrame(a){
setTitle("2048");// Set the title
setSize(370.420);// Set the window size
getContentPane().setBackground(new Color(66.136.83));// Add background
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// The process exits after the shutdown
setLocationRelativeTo(null);/ / in the middle
setResizable(false);// Not allowed to be larger}}Copy the code
Create the panel container GamePanel to inherit from JPanel
package main;
import javax.swing.JFrame;
import javax.swing.JPanel;
/* * Canvas class */
public class GamePanel extends JPanel{
private JFrame mainFrame=null;
private GamePanel panel = null;
// Initialize the related parameters in the constructor
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this; }}Copy the code
Create a Main class to start the window.
package main;
/ / the Main class
public class Main {
public static void main(String[] args) {
GameFrame frame = new GameFrame();
GamePanel panel = new GamePanel(frame);
frame.add(panel);
frame.setVisible(true); }}Copy the code
Right click to execute the Main class and the window is created
Create a menu
private Font createFont(a){
return new Font("Song Of song origin",Font.BOLD,18);
}
// Create a menu
private void createMenu(a) {
/ / create a JMenuBar
JMenuBar jmb = new JMenuBar();
// get the font
Font tFont = createFont();
// Create game options
JMenu jMenu1 = new JMenu("Game");
jMenu1.setFont(tFont);
// Create help options
JMenu jMenu2 = new JMenu("Help");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("New Game");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("Quit");
jmi2.setFont(tFont);
//jmi1 jmi2 added to the menu item "Games"
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("Operation Help");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("Victory condition");
jmi4.setFont(tFont);
//jmi13 jmi4 added to the menu item "Games"
jMenu2.add(jmi3);
jMenu2.add(jmi4);
jmb.add(jMenu1);
jmb.add(jMenu2);
mainFrame.setJMenuBar(jmb);
// Add a listener
jmi1.addActionListener(this);
jmi2.addActionListener(this);
jmi3.addActionListener(this);
jmi4.addActionListener(this);
// Set directive
jmi1.setActionCommand("restart");
jmi2.setActionCommand("exit");
jmi3.setActionCommand("help");
jmi4.setActionCommand("win");
}
Copy the code
If you add this code to GamePanel, it will cause an error, so you need to implement ActionListener and rewrite the actionPerformed method.The GamePanel code is as follows:
package main;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
/* * Canvas class */
public class GamePanel extends JPanel implements ActionListener{
private JFrame mainFrame=null;
private GamePanel panel = null;
// Initialize the related parameters in the constructor
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
// Create a menu
createMenu();
}
private Font createFont(a){
return new Font("Song Of song origin",Font.BOLD,18);
}
// Create a menu
private void createMenu(a) {
/ / create a JMenuBar
JMenuBar jmb = new JMenuBar();
// get the font
Font tFont = createFont();
// Create game options
JMenu jMenu1 = new JMenu("Game");
jMenu1.setFont(tFont);
// Create help options
JMenu jMenu2 = new JMenu("Help");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("New Game");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("Quit");
jmi2.setFont(tFont);
//jmi1 jmi2 added to the menu item "Games"
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("Operation Help");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("Victory condition");
jmi4.setFont(tFont);
//jmi13 jmi4 added to the menu item "Games"
jMenu2.add(jmi3);
jMenu2.add(jmi4);
jmb.add(jMenu1);
jmb.add(jMenu2);
mainFrame.setJMenuBar(jmb);
// Add a listener
jmi1.addActionListener(this);
jmi2.addActionListener(this);
jmi3.addActionListener(this);
jmi4.addActionListener(this);
// Set directive
jmi1.setActionCommand("restart");
jmi2.setActionCommand("exit");
jmi3.setActionCommand("help");
jmi4.setActionCommand("win");
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
UIManager.put("OptionPane.buttonFont".new FontUIResource(new Font("Song Of song origin", Font.ITALIC, 18)));
UIManager.put("OptionPane.messageFont".new FontUIResource(new Font("Song Of song origin", Font.ITALIC, 18)));
if ("exit".equals(command)) {
Object[] options = { "Sure"."Cancel" };
int response = JOptionPane.showOptionDialog(this."Are you sure you want to quit?"."",
JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
if (response == 0) {
System.exit(0); }}else if("restart".equals(command)){
}else if("help".equals(command)){
JOptionPane.showMessageDialog(null."Move the keyboard up, down, left, and right, and the same numbers will merge!"."Hint!, JOptionPane.INFORMATION_MESSAGE);
}else if("win".equals(command)){
JOptionPane.showMessageDialog(null."Win if you get 2048, lose when there are no empty cards!"."Hint!, JOptionPane.INFORMATION_MESSAGE); }}}Copy the code
Create a Card
Establish a Card type
package main;
import java.awt.Graphics;
public class Card {
private int x = 0;/ / x coordinate
private int y = 0;/ / y
private int w = 80;/ / wide
private int h = 80;/ / high
private int i = 0;/ / the subscript I
private int j = 0;/ / the subscript j
private int start=10;// Offset (fixed value)
private int num=0;// Display numbers
private boolean merge=false;// Whether the current round has been merged, if so, can not continue to merge, for the current round
public Card(int i,int j){
this.i=i;
this.j=j;
}
// Calculate x and y coordinates according to I and j
private void cal(a){
this.x = start + j*w + (j+1) *5;
this.y = start + i*h + (i+1) *5;
}
// Draw method
public void draw(Graphics g) {
cal();
g.fillRoundRect(x, y, w, h, 4.4);
}
public int getNum(a) {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isMerge(a) {
return merge;
}
public void setMerge(boolean merge) {
this.merge = merge; }}Copy the code
Add parameters to GamePanel
private final int COLS=4;/ / column
private final int ROWS=4;/ / line
private Card cards[][] = new Card[ROWS][COLS];
private String gameFlag = "start";// Game state
Copy the code
Instantiate the Card object
/ / initialization
private void init(a) {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = newCard(i,j); cards[i][j]=card; }}}Copy the code
Called in the constructor
// Initialize the related parameters in the constructor
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
// Create a menu
createMenu();
/ / initialization
init();
}
Copy the code
Override the paint method in GamePanel and draw the cards in this method.
@Override
public void paint(Graphics g) {
super.paint(g);
// Draw the card
drawCard(g);
}
// Draw the card
private void drawCard(Graphics g) {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) { card = cards[i][j]; card.draw(g); }}}Copy the code
runThis black is not what we want, and we have to set different colors for different numbers, so let’s change it in Card.
/ / for color
private Color getColor(a){
Color color=null;
// Set the color according to num
switch (num) {
case 2:
color = new Color(238.244.234);
break;
case 4:
color = new Color(222.236.200);
break;
case 8:
color = new Color(174.213.130);
break;
case 16:
color = new Color(142.201.75);
break;
case 32:
color = new Color(111.148.48);
break;
case 64:
color = new Color(76.174.124);
break;
case 128:
color = new Color(60.180.144);
break;
case 256:
color = new Color(45.130.120);
break;
case 512:
color = new Color(9.97.26);
break;
case 1024:
color = new Color(242.177.121);
break;
case 2048:
color = new Color(223.185.0);
break;
default:// Default color
color = new Color(92.151.117);
break;
}
return color;
}
Copy the code
Add numeric display and color modification code, modify draw method.
// Draw method
public void draw(Graphics g) {
cal();
// Get the old color
Color oColor = g.getColor();
// Get the color to use
Color color = getColor();
// Set the brush color
g.setColor(color);
g.fillRoundRect(x, y, w, h, 4.4);
if(num! =0) {// Set the color of the word
g.setColor(new Color(125.78.51));
Font font = new Font("Song Of song origin", Font.BOLD, 35);
g.setFont(font);
// Convert to String
String text = num+"";
// Calculates the length of the font text
int wordWidth = getWordWidth(font, text);
// Calculate the x-coordinate of the font center
int sx = x+(w-wordWidth)/2;
/ / to draw
g.drawString(text, sx , y+50);
}
// Restore the brush color
g.setColor(oColor);
}
// Get the length of the font string
public static int getWordWidth(Font font, String content) {
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
int width = 0;
for (int i = 0; i < content.length(); i++) {
width += metrics.charWidth(content.charAt(i));
}
return width;
}
Copy the code
Let’s change the default number of Card and see what happens
Create a random number, 2 or 4
- Let’s set num to 0 by default in the Card class
- Because the ratio of 2 and 4 is 1:4, the number 1-5 is chosen randomly. When it is 1, it means that the number 2 will appear when it is 2, 3, 4 and 5.
- If you randomly get I, j, you get the position of the card, you cutover I, j gets the card instance, if there’s no number on the card, it’s ok, otherwise you recursively continue to get it until you get it.
- I’m just going to take the number that I just picked up and put it in my Card instance object.
The code is as follows:
// Create the number 2 or 4 on a random empty card
private void createRandomNumber(a) {
int num = 0;
Random random = new Random();
int index = random.nextInt(5) +1;// Select random numbers between 1 and 5
// Since the probability of 2 and 4 is 1 to 4, if index is 1, create the number 4, otherwise create the number 2.
if(index==1){
num = 4;
}else {
num = 2;
}
// Determine if the grid is full, stop fetching and exit
if(cardFull()){
return ;
}
// Get random cards, not empty
Card card = getRandomCard(random);
// Set the number to the card object
if(card! =null){ card.setNum(num); }}// Get random cards, not empty
private Card getRandomCard(Random random) {
int i = random.nextInt(ROWS);
int j = random.nextInt(COLS);
Card card = cards[i][j];
if(card.getNum()==0) {// If the card is blank, it is found
return card;
}
// If no blank is found, recurse
return getRandomCard(random);
}
// Check that the grid is full
private boolean cardFull(a) {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()==0) {// If one is empty, it is not full
return false; }}}return true;
}
Copy the code
Constructor, which opens the game with a default number
Add keyboard events
Remember to call this method in the construct.
// Add keyboard listener
private void createKeyListener(a) {
KeyAdapter l = new KeyAdapter() {
/ / press
@Override
public void keyPressed(KeyEvent e) {
if(!"start".equals(gameFlag)) return ;
int key = e.getKeyCode();
switch (key) {
/ / up
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
moveCard(1);/ / up
break;
/ / to the right
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
moveCard(2);/ / to the right
break;
/ / down
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
moveCard(3);/ / to the up and down
break;
/ / to the left
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
moveCard(4);/ / to the left
break; }}/ / to loosen
@Override
public void keyReleased(KeyEvent e) {}};// Add keyboard listener to main frame
mainFrame.addKeyListener(l);
}
Copy the code
Add mouse movement logic processing code
// How to move the card
protected void moveCard(int dir) {
// Clean up the card, because each move sets the merge mark, which needs to be reset
clearCard();
if(dir==1) {// Move up
moveCardTop(true);
}
// Create a new card after the move
createRandomNumber();
/ / redraw
repaint();
}
// Clean up the card, because each move sets the merge mark, which needs to be reset
private void clearCard(a) {
Card card;
for (int i = 0; i < ROWS; i++) {// I starts at 1, because I =0 does not need to be moved
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
card.setMerge(false); }}}// Move up
private boolean moveCardTop(boolean bool) {
boolean res = false;
Card card;
for (int i = 1; i < ROWS; i++) {// I starts at 1, because I =0 does not need to be moved
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()! =0) {// As long as the card is not empty, move
if(card.moveTop(cards,bool)){// Move up
res = true;// If one of them is moved or merged, res is true}}}}return res;
}
Copy the code
Add upward-moving logic to the Card class
- Move from line 2, because you don’t need to move the first line.
- As long as the number on the card is not 0, it means to move.
- According to i-1, I get the last card, and if the last card is empty, I swap the current card, and recurse, because I might have to move up.
- If the current card has the same number as the previous card, it is merged.
- If neither of the preceding two types is displayed, no operation is performed.
// The card moves up
public boolean moveTop(Card[][] cards,boolean bool) {
// Set exit conditions
if(i==0) {// It is already on the top
return false;
}
// The card above
Card prev = cards[i-1][j];
if(prev.getNum()==0) {// The last card was empty
// Move, essentially set the number
if(bool){//bool is executed only if it is true, because flase is only used to determine whether to move
prev.num=this.num;
this.num=0;
// Move (prev)
prev.moveTop(cards,bool);
}
return true;
}else if(prev.getNum()==num && ! prev.merge){// Merge operation (if merge already, do not run merge again, for course round)
if(bool){////bool The value is true
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {// The last num is different from the current num, cannot be moved, and exits
return false; }}Copy the code
See the effect
Add code in the other three directions, starting with a few codes in the GamePanel.
// Move to the right
private boolean moveCardRight(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = COLS-1; j >=0 ; j--) {//j starts at cols-1 and moves decrement from the far right
card = cards[i][j];
if(card.getNum()! =0) {// As long as the card is not empty, move
if(card.moveRight(cards,bool)){// Move to the right
res = true;// If one of them is moved or merged, res is true}}}}return res;
}
// Move down
private boolean moveCardBottom(boolean bool) {
boolean res = false;
Card card;
for (int i = ROWS-1; i >=0; i--) {// I starts from rows-1 and moves down
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()! =0) {// As long as the card is not empty, move
if(card.moveBottom(cards,bool)){/ / move
res = true;// If one of them is moved or merged, res is true}}}}return res;
}
// Move left
private boolean moveCardLeft(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 1; j < COLS ; j++) {//j starts at 1 and moves from the far left
card = cards[i][j];
if(card.getNum()! =0) {// As long as the card is not empty, move
if(card.moveLeft(cards,bool)){// Move left
res = true;// If one of them is moved or merged, res is true}}}}return res;
}
Copy the code
Add a couple of other directions to Card
// Move down
public boolean moveBottom(Card[][] cards,boolean bool) {
// Set exit conditions
if(i==3) {// It is already at the bottom
return false;
}
// The card above
Card prev = cards[i+1][j];
if(prev.getNum()==0) {// The last card was empty
// Move, essentially set the number
if(bool){//bool is executed only if it is true, because flase is only used to determine whether to move
prev.num=this.num;
this.num=0;
// Move (prev)
prev.moveBottom(cards,bool);
}
return true;
}else if(prev.getNum()==num && ! prev.merge){// Merge operation (if merge already, do not run merge again, for course round)
if(bool){////bool The value is true
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {// The last num is different from the current num, cannot be moved, and exits
return false; }}// Move to the right
public boolean moveRight(Card[][] cards,boolean bool) {
// Set exit conditions
if(j==3) {// It is already on the right
return false;
}
// The card above
Card prev = cards[i][j+1];
if(prev.getNum()==0) {// The last card was empty
// Move, essentially set the number
if(bool){//bool is executed only if it is true, because flase is only used to determine whether to move
prev.num=this.num;
this.num=0;
// Move (prev)
prev.moveRight(cards,bool);
}
return true;
}else if(prev.getNum()==num && ! prev.merge){// Merge operation (if merge already, do not run merge again, for course round)
if(bool){////bool The value is true
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {// The last num is different from the current num, cannot be moved, and exits
return false; }}// Move left
public boolean moveLeft(Card[][] cards,boolean bool) {
// Set exit conditions
if(j==0) {// It is already on the left
return false;
}
// The card above
Card prev = cards[i][j-1];
if(prev.getNum()==0) {// The last card was empty
// Move, essentially set the number
if(bool){//bool is executed only if it is true, because flase is only used to determine whether to move
prev.num=this.num;
this.num=0;
// Move (prev)
prev.moveLeft(cards,bool);
}
return true;
}else if(prev.getNum()==num && ! prev.merge){// Merge operation (if merge already, do not run merge again, for course round)
if(bool){////bool The value is true
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {// The last num is different from the current num, cannot be moved, and exits
return false; }}Copy the code
Modify the moveCard method
// How to move the card
protected void moveCard(int dir) {
// Clean up the card, because each move sets the merge mark, which needs to be reset
clearCard();
if(dir==1) {// Move up
moveCardTop(true);
}else if(dir==2) {// Move to the right
moveCardRight(true);
}else if(dir==3) {// Move down
moveCardBottom(true);
}else if(dir==4) {// Move left
moveCardLeft(true);
}
// Create a new card after the move
createRandomNumber();
/ / redraw
repaint();
}
Copy the code
This is basically the end of the game, add other ancillary things, such as restart, game win, game end, etc., but not to mention.
To see the big guy here, move the rich little hands to praise + reply + collection, can [concern] a wave of better.
Code acquisition method:
Help article [like] + [collection] + [concern] + [comment], plus my wechat: QQ283582761, I send you!