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

  1. Draw the window.
  2. Create a menu.
  3. Create all blank cards.
  4. Create a card (2 or 4) at random.
  5. Keyboard events listening (up, down, left, right – click listening).
  6. According to the direction of the keyboard, process the number movement merge.
  7. Add success and failure determination.
  8. 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

  1. Let’s set num to 0 by default in the Card class
  2. 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.
  3. 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.
  4. 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

  1. Move from line 2, because you don’t need to move the first line.
  2. As long as the number on the card is not 0, it means to move.
  3. 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.
  4. If the current card has the same number as the previous card, it is merged.
  5. 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!