"Tag" nyob rau hauv Java - yuav ua li cas los tsim ib tug tag nrho-fledged kev ua si

"Tag" nyob rau hauv Java - yuav ua li cas los tsim ib tug tag nrho-fledged kev ua si

"Tib tsib" los yog "Tib tsib" yog ib qho piv txwv zoo ntawm kev ua si logic yooj yim uas nrov thoob plaws ntiaj teb. Yuav kom daws tau qhov puzzle, koj yuav tsum npaj cov squares nrog cov lej nyob rau hauv kev txiav txim, los ntawm qhov tsawg tshaj plaws mus rau loj tshaj plaws. Nws tsis yooj yim, tab sis nws nthuav.

Hauv kev qhia niaj hnub no peb yuav qhia koj yuav ua li cas txhim kho Kaum tsib hauv Java 8 nrog dab noj hnub. Txhawm rau tsim UI peb yuav siv Swing API.

Peb nco qab: rau txhua tus neeg nyeem Habr - 10 ruble luv nqi thaum tso npe rau hauv ib chav kawm Skillbox siv Habr promo code.

Skillbox pom zoo: Kev kawm online chav kawm "Profession Java developer".

Game tsim

Hauv theem no koj yuav tsum tau txhais cov khoom:

  • Loj - qhov loj ntawm qhov chaw ua si;
  • nbTiles - tus naj npawb ntawm cov cim npe hauv thaj teb. nbTiles = size*size - 1;
  • Pobzeb yog ib daim ntawv uas yog ib qho ntawm ib sab ntawm cov lej. Txhua lub cim npe yuav tau txais tus nqi tshwj xeeb hauv qhov ntau [0, nbTiles]. Zero qhia ib qho square khoob;
  • blankPos - txoj hauj lwm ntawm qhov khoob square.

Kev ua si logic

Peb yuav tsum tau txhais ib txoj kev pib dua siv los pib qhov kev ua si tshiab. Txoj kev no peb teeb tus nqi rau txhua qhov ntawm cov cim npe array. Zoo, ces peb tso blankPos nyob rau hauv txoj hauj lwm kawg ntawm array.

Peb kuj xav tau ib txoj kev shuffle los shuffle lub array ntawm tag. Peb tsis suav nrog cov ntawv khoob hauv cov txheej txheem shuffling thiaj li yuav tawm hauv tib txoj haujlwm.

Txij li thaum tsuas yog ib nrab ntawm qhov ua tau pib txoj haujlwm ntawm cov duab dhos ua si muaj kev daws teeb meem, koj yuav tsum tau kuaj xyuas qhov tshwm sim ntawm kev sib tsoo kom paub tseeb tias qhov kev teeb tsa tam sim no tseem tuaj yeem daws tau. Txhawm rau ua qhov no, peb txhais cov txheej txheem issolvable.

Yog tias ib qho cim npe ua ntej los ntawm lub cim nrog tus nqi siab dua, nws raug suav tias yog kev hloov pauv. Thaum qhov chaw khoob nyob rau hauv qhov chaw, tus naj npawb ntawm inversions yuav tsum yog txawm rau cov puzzle kom daws tau. Yog li peb suav tus naj npawb ntawm inversions thiab rov qab muaj tseeb yog tias tus lej txawm.

Nws yog ib qho tseem ceeb uas yuav tsum tau txhais cov txheej txheem isSolved los xyuas seb peb Qhov Kev Ua Si Ntawm Kaum Tsib layout puas daws tau. Ua ntej peb saib qhov chaw khoob. Yog hais tias nyob rau hauv thawj txoj hauj lwm, ces tam sim no kev sib raug zoo yog tshiab, tsis tau txiav txim siab yav tas los. Peb mam li rov qab los ntawm cov vuas nyob rau hauv qhov kev txiav txim rov qab, thiab yog hais tias tus nqi ntawm lub cim yog txawv los ntawm tus coj index +1, peb rov qab cuav. Txwv tsis pub, thaum kawg ntawm txoj kev nws yog lub sij hawm rov qab los qhov tseeb vim hais tias cov puzzle twb tau daws lawm.

Lwm txoj kev uas yuav tsum tau txhais yog newGame. Nws yuav tsum tau tsim ib qho piv txwv tshiab ntawm qhov kev ua si. Txhawm rau ua qhov no, peb rov pib qhov chaw ua si, tom qab ntawd rub nws thiab txuas ntxiv mus kom txog thaum qhov kev ua si tau daws tau.

Ntawm no yog ib qho piv txwv code nrog lub ntsiab logic ntawm tag:

private void newGame() {
  do {
    reset(); // reset in initial state
    shuffle(); // shuffle
  } while(!isSolvable()); // make it until grid be solvable
 
  gameOver = false;
}
 
private void reset() {
  for (int i = 0; i < tiles.length; i++) {
    tiles[i] = (i + 1) % tiles.length;
  }
 
  // we set blank cell at the last
  blankPos = tiles.length - 1;
}
 
private void shuffle() {
  // don't include the blank tile in the shuffle, leave in the solved position
  int n = nbTiles;
 
  while (n > 1) {
    int r = RANDOM.nextInt(n--);
    int tmp = tiles[r];
    tiles[r] = tiles[n];
    tiles[n] = tmp;
  }
}
 
// Only half permutations of the puzzle are solvable/
// Whenever a tile is preceded by a tile with higher value it counts
// as an inversion. In our case, with the blank tile in the solved position,
// the number of inversions must be even for the puzzle to be solvable
private boolean isSolvable() {
  int countInversions = 0;
 
  for (int i = 0; i < nbTiles; i++) {
    for (int j = 0; j < i; j++) {
      if (tiles[j] > tiles[i])
        countInversions++;
    }
  }
 
  return countInversions % 2 == 0;
}
 
private boolean isSolved() {
  if (tiles[tiles.length - 1] != 0) // if blank tile is not in the solved position ==> not solved
    return false;
 
  for (int i = nbTiles - 1; i >= 0; i--) {
    if (tiles[i] != i + 1)
      return false;
  }
 
  return true;
}

Thaum kawg, koj yuav tsum tau ua qhov kev txav ntawm cov cim npe hauv array. Cov lej no yuav raug hu tom qab los ntawm kev hu rov qab los teb rau tus cursor txav. Peb qhov kev ua si yuav txhawb nqa ntau lub pobzeb txav tib lub sijhawm. Yog li, tom qab peb tau hloov txoj hauj lwm nias ntawm qhov screen rau hauv ib lub cim, peb tau txais txoj hauj lwm ntawm qhov khoob tag thiab nrhiav kev taw qhia ntawm kev txav mus los txhawb nqa ntau yam ntawm nws cov kev taw qhia tib lub sijhawm.

Nov yog ib qho piv txwv code:

// get position of the click
int ex = e.getX() - margin;
int ey = e.getY() - margin;
 
// click in the grid ?
if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
  return;
 
// get position in the grid
int c1 = ex / tileSize;
int r1 = ey / tileSize;
 
// get position of the blank cell
int c2 = blankPos % size;
int r2 = blankPos / size;
 
// we convert in the 1D coord
int clickPos = r1 * size + c1;
 
int dir = 0;
 
// we search direction for multiple tile moves at once
if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
  dir = (r1 - r2) > 0 ? size : -size;
else if (r1 == r2 && Math.abs(c1 - c2) > 0)
  dir = (c1 - c2) > 0 ? 1 : -1;
 
if (dir != 0) {
  // we move tiles in the direction
  do {
    int newBlankPos = blankPos + dir;
    tiles[blankPos] = tiles[newBlankPos];
    blankPos = newBlankPos;
  } while(blankPos != clickPos);
 
tiles[blankPos] = 0;

Peb tsim UI siv Swing API

Nws yog lub sijhawm los ua haujlwm ntawm lub interface. Ua ntej peb kawm Jpanel. Tom qab ntawd peb kos cov cim npe rau ntawm daim teb - txhawm rau xam qhov ntau thiab tsawg ntawm txhua tus, peb yuav siv cov ntaub ntawv teev tseg hauv qhov kev ua si constructor parameter:

gridSize = (dim  -  2 * margin);
tileSize = gridSize / size;

Margin kuj yog ib qho parameter teev nyob rau hauv lub game constructor.

Tam sim no peb yuav tsum tau txhais txoj kev drawGrid los kos daim phiaj thiab cov pob ntawm qhov screen. Peb txheeb xyuas cov array ntawm cov cim npe thiab hloov cov kev tswj hwm rau hauv cov neeg siv interface tswj xyuas. Tom qab ntawd kos txhua qhov chaw nrog tus lej sib thooj hauv qhov chaw:

private void drawGrid(Graphics2D g) {
  for (int i = 0; i < tiles.length; i++) {
    // we convert 1D coords to 2D coords given the size of the 2D Array
    int r = i / size;
    int c = i % size;
    // we convert in coords on the UI
    int x = margin + c * tileSize;
    int y = margin + r * tileSize;
 
    // check special case for blank tile
    if(tiles[i] == 0) {
      if (gameOver) {
        g.setColor(FOREGROUND_COLOR);
        drawCenteredString(g, "u2713", x, y);
      }
 
      continue;
    }
 
    // for other tiles
    g.setColor(getForeground());
    g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
    g.setColor(Color.BLACK);
    g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
    g.setColor(Color.WHITE);
 
    drawCenteredString(g, String.valueOf(tiles[i]), x , y);
  }
}

Thaum kawg, cia peb hla txoj kev paintComponent, uas muab los ntawm JPane chav kawm. Peb mam li siv txoj kev drawGrid, ua raws li txoj kev drawStartMessage los tso saib cov lus qhia kom peb nyem los pib qhov kev ua si:

private void drawStartMessage(Graphics2D g) {
  if (gameOver) {
    g.setFont(getFont().deriveFont(Font.BOLD, 18));
    g.setColor(FOREGROUND_COLOR);
    String s = "Click to start new game";
    g.drawString(s, (getWidth() - g.getFontMetrics().stringWidth(s)) / 2,
        getHeight() - margin);
  }
}
 
private void drawCenteredString(Graphics2D g, String s, int x, int y) {
  // center string s for the given tile (x,y)
  FontMetrics fm = g.getFontMetrics();
  int asc = fm.getAscent();
  int desc = fm.getDescent();
  g.drawString(s,  x + (tileSize - fm.stringWidth(s)) / 2,
      y + (asc + (tileSize - (asc + desc)) / 2));
}
 
@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2D = (Graphics2D) g;
  g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  drawGrid(g2D);
  drawStartMessage(g2D);
}

Reacting rau cov neeg siv kev ua hauv UI

Txhawm rau kom qhov kev ua si khiav nws cov chav kawm, nws yog qhov yuav tsum tau ua cov neeg siv ua haujlwm hauv UI. Txhawm rau ua qhov no, ntxiv qhov kev siv ntawm MouseListener ntawm Jpanel thiab cov cai rau txav chaw, twb tau qhia saum toj no:

addMouseListener(new MouseAdapter() {
  @Override
  public void mousePressed(MouseEvent e) {
    // used to let users to interact on the grid by clicking
    // it's time to implement interaction with users to move tiles to solve the game !
    if (gameOver) {
      newGame();
    } else {
      // get position of the click
      int ex = e.getX() - margin;
      int ey = e.getY() - margin;
 
      // click in the grid ?
      if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
        return;
 
      // get position in the grid
      int c1 = ex / tileSize;
      int r1 = ey / tileSize;
 
      // get position of the blank cell
      int c2 = blankPos % size;
      int r2 = blankPos / size;
 
      // we convert in the 1D coord
      int clickPos = r1 * size + c1;
 
      int dir = 0;
 
      // we search direction for multiple tile moves at once
      if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
        dir = (r1 - r2) > 0 ? size : -size;
      else if (r1 == r2 && Math.abs(c1 - c2) > 0)
        dir = (c1 - c2) > 0 ? 1 : -1;
 
      if (dir != 0) {
        // we move tiles in the direction
        do {
          int newBlankPos = blankPos + dir;
          tiles[blankPos] = tiles[newBlankPos];
          blankPos = newBlankPos;
        } while(blankPos != clickPos);
 
        tiles[blankPos] = 0;
      }
 
      // we check if game is solved
      gameOver = isSolved();
    }
 
    // we repaint panel
    repaint();
  }
});

Peb tso cai rau hauv tus tsim qauv ntawm GameOfFifteen chav kawm. Thaum kawg, peb hu txoj kev tshiabGame los pib qhov kev ua si tshiab.

Full game code

Cov kauj ruam kawg ua ntej pom qhov kev ua si hauv kev nqis tes ua yog muab tag nrho cov ntsiab lus ua ke. Nov yog qhov tshwm sim:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
 
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
 
// We are going to create a Game of 15 Puzzle with Java 8 and Swing
// If you have some questions, feel free to read comments ;)
public class GameOfFifteen extends JPanel { // our grid will be drawn in a dedicated Panel
 
  // Size of our Game of Fifteen instance
  private int size;
  // Number of tiles
  private int nbTiles;
  // Grid UI Dimension
  private int dimension;
  // Foreground Color
  private static final Color FOREGROUND_COLOR = new Color(239, 83, 80); // we use arbitrary color
  // Random object to shuffle tiles
  private static final Random RANDOM = new Random();
  // Storing the tiles in a 1D Array of integers
  private int[] tiles;
  // Size of tile on UI
  private int tileSize;
  // Position of the blank tile
  private int blankPos;
  // Margin for the grid on the frame
  private int margin;
  // Grid UI Size
  private int gridSize;
  private boolean gameOver; // true if game over, false otherwise
 
  public GameOfFifteen(int size, int dim, int mar) {
    this.size = size;
    dimension = dim;
    margin = mar;
    
    // init tiles
    nbTiles = size * size - 1; // -1 because we don't count blank tile
    tiles = new int[size * size];
    
    // calculate grid size and tile size
    gridSize = (dim - 2 * margin);
    tileSize = gridSize / size;
    
    setPreferredSize(new Dimension(dimension, dimension + margin));
    setBackground(Color.WHITE);
    setForeground(FOREGROUND_COLOR);
    setFont(new Font("SansSerif", Font.BOLD, 60));
    
    gameOver = true;
    
    addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent e) {
        // used to let users to interact on the grid by clicking
        // it's time to implement interaction with users to move tiles to solve the game !
        if (gameOver) {
          newGame();
        } else {
          // get position of the click
          int ex = e.getX() - margin;
          int ey = e.getY() - margin;
          
          // click in the grid ?
          if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
            return;
          
          // get position in the grid
          int c1 = ex / tileSize;
          int r1 = ey / tileSize;
          
          // get position of the blank cell
          int c2 = blankPos % size;
          int r2 = blankPos / size;
          
          // we convert in the 1D coord
          int clickPos = r1 * size + c1;
          
          int dir = 0;
          
          // we search direction for multiple tile moves at once
          if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
            dir = (r1 - r2) > 0 ? size : -size;
          else if (r1 == r2 && Math.abs(c1 - c2) > 0)
            dir = (c1 - c2) > 0 ? 1 : -1;
            
          if (dir != 0) {
            // we move tiles in the direction
            do {
              int newBlankPos = blankPos + dir;
              tiles[blankPos] = tiles[newBlankPos];
              blankPos = newBlankPos;
            } while(blankPos != clickPos);
            
            tiles[blankPos] = 0;
          }
          
          // we check if game is solved
          gameOver = isSolved();
        }
        
        // we repaint panel
        repaint();
      }
    });
    
    newGame();
  }
 
  private void newGame() {
    do {
      reset(); // reset in intial state
      shuffle(); // shuffle
    } while(!isSolvable()); // make it until grid be solvable
    
    gameOver = false;
  }
 
  private void reset() {
    for (int i = 0; i < tiles.length; i++) {
      tiles[i] = (i + 1) % tiles.length;
    }
    
    // we set blank cell at the last
    blankPos = tiles.length - 1;
  }
 
  private void shuffle() {
    // don't include the blank tile in the shuffle, leave in the solved position
    int n = nbTiles;
    
    while (n > 1) {
      int r = RANDOM.nextInt(n--);
      int tmp = tiles[r];
      tiles[r] = tiles[n];
      tiles[n] = tmp;
    }
  }
 
  // Only half permutations of the puzzle are solvable.
  // Whenever a tile is preceded by a tile with higher value it counts
  // as an inversion. In our case, with the blank tile in the solved position,
  // the number of inversions must be even for the puzzle to be solvable
  private boolean isSolvable() {
    int countInversions = 0;
    
    for (int i = 0; i < nbTiles; i++) {
      for (int j = 0; j < i; j++) {
        if (tiles[j] > tiles[i])
          countInversions++;
      }
    }
    
    return countInversions % 2 == 0;
  }
 
  private boolean isSolved() {
    if (tiles[tiles.length - 1] != 0) // if blank tile is not in the solved position ==> not solved
      return false;
    
    for (int i = nbTiles - 1; i >= 0; i--) {
      if (tiles[i] != i + 1)
        return false;      
    }
    
    return true;
  }
 
  private void drawGrid(Graphics2D g) {
    for (int i = 0; i < tiles.length; i++) {
      // we convert 1D coords to 2D coords given the size of the 2D Array
      int r = i / size;
      int c = i % size;
      // we convert in coords on the UI
      int x = margin + c * tileSize;
      int y = margin + r * tileSize;
      
      // check special case for blank tile
      if(tiles[i] == 0) {
        if (gameOver) {
          g.setColor(FOREGROUND_COLOR);
          drawCenteredString(g, "u2713", x, y);
        }
        
        continue;
      }
      
      // for other tiles
      g.setColor(getForeground());
      g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
      g.setColor(Color.BLACK);
      g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
      g.setColor(Color.WHITE);
      
      drawCenteredString(g, String.valueOf(tiles[i]), x , y);
    }
  }
 
  private void drawStartMessage(Graphics2D g) {
    if (gameOver) {
      g.setFont(getFont().deriveFont(Font.BOLD, 18));
      g.setColor(FOREGROUND_COLOR);
      String s = "Click to start new game";
      g.drawString(s, (getWidth() - g.getFontMetrics().stringWidth(s)) / 2,
          getHeight() - margin);
    }
  }
 
  private void drawCenteredString(Graphics2D g, String s, int x, int y) {
    // center string s for the given tile (x,y)
    FontMetrics fm = g.getFontMetrics();
    int asc = fm.getAscent();
    int desc = fm.getDescent();
    g.drawString(s,  x + (tileSize - fm.stringWidth(s)) / 2,
        y + (asc + (tileSize - (asc + desc)) / 2));
  }
 
  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D) g;
    g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    drawGrid(g2D);
    drawStartMessage(g2D);
  }
 
  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setTitle("Game of Fifteen");
      frame.setResizable(false);
      frame.add(new GameOfFifteen(4, 550, 30), BorderLayout.CENTER);
      frame.pack();
      // center on the screen
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
    });
  }
 
 
}

Thaum kawg, cia peb ua si!

Nws yog lub sijhawm los tso qhov kev ua si thiab sim nws hauv kev nqis tes ua. Lub teb yuav tsum zoo li no:

"Tag" nyob rau hauv Java - yuav ua li cas los tsim ib tug tag nrho-fledged kev ua si

Cia peb sim daws qhov puzzle. Yog tias txhua yam mus zoo, peb tau txais qhov no:

"Tag" nyob rau hauv Java - yuav ua li cas los tsim ib tug tag nrho-fledged kev ua si

Yog tag nrho. Koj puas tau xav ntau dua? 🙂

Skillbox pom zoo:

Tau qhov twg los: www.hab.com

Ntxiv ib saib