"Tag" дар Java - чӣ гуна як бозии мукаммалро таҳия кардан мумкин аст

"Tag" дар Java - чӣ гуна як бозии мукаммалро таҳия кардан мумкин аст

"понздаҳ" ё "понздаҳ" намунаи аълои бозии мантиқии оддӣ аст, ки дар тамоми ҷаҳон маъмул аст. Барои ҳалли муаммо, шумо бояд квадратҳоро бо рақамҳо аз хурдтарин ба калонтар тартиб диҳед. Ин осон нест, аммо ҷолиб аст.

Дар дарси имрӯза мо ба шумо нишон медиҳем, ки чӣ тавр ба Fifteen дар Java 8 бо Eclipse таҳия кардан мумкин аст. Барои таҳияи UI мо Swing API-ро истифода хоҳем бурд.

Мо ба шумо хотиррасон мекунем: барои ҳамаи хонандагони "Habr" - тахфифи 10 000 рубл ҳангоми номнавис шудан ба курсҳои Skillbox бо истифода аз рамзи таблиғотии "Habr".

Skillbox тавсия медиҳад: Курси онлайни таълимӣ "Касби Java Developer".

Тарҳрезии бозӣ

Дар ин марҳила шумо бояд хосиятҳоро муайян кунед:

  • Андоза — андозаи майдони бозӣ;
  • nbTiles — шумораи барчаспҳо дар майдон. nbTiles = андоза* андоза - 1;
  • Тилейҳо тегест, ки массиви якченакаи бутун мебошад. Ҳар яке аз тегҳо дар диапазон [0, nbTiles] арзиши беназир мегиранд. Сифр майдони холиро нишон медиҳад;
  • blankPos - мавқеи майдони холӣ.

Мантиқи бозӣ

Мо бояд усули аз нав барқароркуниро муайян кунем, ки барои оғоз кардани мавқеи нави бозӣ истифода мешавад. Бо ин роҳ мо барои ҳар як элементи массиви барчаспҳо арзиш муқаррар мекунем. Хуб, пас мо blankPos -ро дар мавқеи охирини массив ҷойгир мекунем.

Барои омехта кардани массиви тегҳо ба мо инчунин усули омехта лозим аст. Мо теги холиро ба раванди омехта дохил намекунем, то он дар ҳамон ҷой монад.

Азбаски танҳо нисфи мавқеъҳои имконпазири ибтидоии муаммо ҳалли худро доранд, шумо бояд натиҷаи омехтаро тафтиш кунед, то боварӣ ҳосил кунед, ки тарҳбандии ҷорӣ ҳатто ҳалшаванда аст. Барои ин мо усули isSolvable -ро муайян мекунем.

Агар пеш аз як тег дорои теги баландтар бошад, он инверсия ҳисобида мешавад. Вақте ки ҷои холӣ ҷойгир аст, шумораи инверсияҳо бояд баробар бошанд, то муаммо ҳалшаванда бошад. Ҳамин тавр, мо шумораи инверсияҳоро ҳисоб мекунем ва агар адад ҷуфт бошад, ҳақиқиро бармегардонем.

Пас муҳим аст, ки усули isSolved -ро муайян кунед, то тафтиш кунед, ки оё тарҳбандии Game Of Fifteen мо ҳал шудааст. Пеш аз ҳама, мо мебинем, ки ҷои холӣ дар куҷост. Агар дар мавқеи ибтидоӣ бошад, пас мутобиқсозии ҷорӣ нав аст, қаблан қарор дода нашудааст. Сипас, мо сафолҳоро бо тартиби баръакс такрор мекунем ва агар арзиши тег аз индекси мувофиқ +1 фарқ кунад, мо бардурӯғ бармегардем. Дар акси ҳол, дар охири усул вақти бозгашти ҳақиқӣ расидааст, зеро муаммо аллакай ҳал шудааст.

Усули дигаре, ки бояд муайян карда шавад, newGame аст. Барои сохтани як мисоли нави бозӣ лозим аст. Барои ин, мо майдони бозиро аз нав танзим мекунем, сипас онро омехта мекунем ва то он даме, ки мавқеи бозӣ ҳалшаванда аст, идома медиҳем.

Ин аст рамзи намунавӣ бо мантиқи калидии тег:

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;
}

Ниҳоят, шумо бояд ҳаракати тегҳоро дар массив барномарезӣ кунед. Ин код баъдтар тавассути занги бозгашт даъват карда мешавад, то ба ҳаракати курсор посух диҳад. Бозии мо дар як вақт ҳаракатҳои сершумори сафолҳоро дастгирӣ мекунад. Хамин тавр, пас аз он ки мавкеи пахшшударо дар экран ба тег табдил додем, мо мавкеи теги холиро мегирем ва самти харакатро мечустем, то дар як вакт якчанд харакати онро дастгири кунад.

Дар ин ҷо як рамзи мисол аст:

// 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;

Мо UI-ро бо истифода аз Swing API таҳия мекунем

Вақти он расидааст, ки дар интерфейс кор кунед. Аввал мо синфи Jpanel-ро мегирем. Сипас, мо барчаспҳоро дар майдон мекашем - барои ҳисоб кардани андозаи ҳар як, мо маълумотеро, ки дар параметри созандаи бозӣ нишон дода шудааст, истифода мебарем:

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

Маржа инчунин як параметрест, ки дар созандаи бозӣ муқаррар карда шудааст.

Акнун ба мо лозим аст, ки усули drawGrid-ро барои кашидани тор ва нуқтаҳо дар экран муайян кунем. Мо массиви тегҳоро таҳлил мекунем ва координатҳоро ба координатҳои интерфейси корбар табдил медиҳем. Сипас ҳар як нуқтаро бо рақами мувофиқ дар марказ кашед:

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);
  }
}

Дар охир, биёед усули paintComponent-ро, ки аз синфи JPane гирифта шудааст, бекор кунем. Сипас, мо усули drawGrid ва пас аз он усули drawStartMessage -ро истифода мебарем, то паёмеро нишон диҳад, ки моро барои оғози бозӣ клик мекунад:

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);
}

Вокуниш ба амалҳои корбар дар UI

Барои он ки бозӣ ҷараёни худро иҷро кунад, амалҳои корбарро дар UI коркард кардан лозим аст. Барои ин, татбиқи MouseListener дар Jpanel ва рамзи интиқоли нуқтаҳои, ки аллакай дар боло нишон дода шудааст, илова кунед:

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();
  }
});

Мо кодро дар конструктори синфи GameOfFifteen ҷойгир мекунем. Дар ниҳоят, мо усули newGame-ро барои оғози бозии нав даъват мекунем.

Рамзи пурраи бозӣ

Қадами охирин пеш аз дидани бозӣ дар амал ин якҷоя кардани ҳама унсурҳои код мебошад. Ин аст он чизе ки рӯй медиҳад:

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);
    });
  }
 
 
}

Дар охир, биёед бозӣ кунем!

Вақти он расидааст, ки бозӣ оғоз кунед ва онро дар амал санҷед. Майдон бояд чунин бошад:

"Tag" дар Java - чӣ гуна як бозии мукаммалро таҳия кардан мумкин аст

Биёед кӯшиш кунем, ки муамморо ҳал кунем. Агар ҳама чиз хуб бошад, мо инро мегирем:

"Tag" дар Java - чӣ гуна як бозии мукаммалро таҳия кардан мумкин аст

Ҳамааш ҳамин. Оё шумо бештар интизор будед? 🙂

Skillbox тавсия медиҳад:

Манбаъ: will.com

Илова Эзоҳ