په جاوا کې "ټاګ" - د بشپړې لوبې رامینځته کولو څرنګوالی

په جاوا کې "ټاګ" - د بشپړې لوبې رامینځته کولو څرنګوالی

"پنځلس" یا "پنځلس" د ساده منطق لوبې یوه غوره بیلګه ده چې په ټوله نړۍ کې مشهوره ده. د معما د حل کولو لپاره، تاسو اړتیا لرئ چې چوکونه د شمیرو سره په ترتیب سره تنظیم کړئ، له کوچني څخه تر لوی پورې. دا اسانه نه ده، مګر دا په زړه پورې ده.

د نن ورځې په ټیوټوریل کې به موږ تاسو ته وښیو چې څنګه په جاوا 8 کې د Eclipse سره پنځلس رامینځته کړو. د UI پراختیا لپاره موږ به د سوینګ API وکاروو.

موږ یادونه کوو: د ټولو هابر لوستونکو لپاره - د 10 روبل تخفیف کله چې د هابر پرومو کوډ په کارولو سره د مهارت بکس کوم کورس کې نوم لیکنه وکړئ.

Skillbox وړاندیز کوي: تعلیمي آنلاین کورس "د جاوا پرمخ وړونکی مسلک".

د لوبې ډیزاین

پدې مرحله کې تاسو اړتیا لرئ چې ځانګړتیاوې تعریف کړئ:

  • اندازه - د لوبې ډګر اندازه؛
  • nbTiles - په ساحه کې د ټګونو شمیر. nbTiles = اندازه * اندازه - 1؛
  • ټایلونه یو ټاګ دی چې د انټیجرونو یو اړخیزه لړۍ ده. هر ټګ به په حد کې یو ځانګړی ارزښت ترلاسه کړي [0، nbTiles]. صفر یو خالي مربع ته اشاره کوي؛
  • blankPos - د خالي مربع موقعیت.

د لوبې منطق

موږ اړتیا لرو د بیا تنظیم کولو میتود تعریف کړو چې د نوي لوبې موقعیت پیل کولو لپاره کارول کیږي. په دې توګه موږ د ټاګونو د هر عنصر لپاره ارزښت ټاکو. ښه، بیا موږ د صف په وروستي موقعیت کې blankPos ځای په ځای کوو.

موږ د ټاګونو سرې بدلولو لپاره د شفل میتود ته هم اړتیا لرو. موږ د شفل کولو پروسې کې خالي ټاګ شامل نه کوو ترڅو په ورته موقعیت کې پریږدو.

څرنګه چې د معما د ممکنه پیل شوي پوستونو یوازې نیمایي حل لري، تاسو اړتیا لرئ چې د بدلیدونکي بدلون پایله وګورئ ترڅو ډاډ ترلاسه کړئ چې اوسنی ترتیب حتی د حل وړ دی. د دې کولو لپاره، موږ د حل وړ میتود تعریف کوو.

که چیرې یو ځانګړی ټاګ د لوړ ارزښت سره د ټاګ څخه مخکې وي، دا یو انعطاف ګڼل کیږي. کله چې خالي ځای په ځای وي، د معما شمیر باید حتی د حل وړ وي. نو موږ د انعطاف شمیره شمیرو او ریښتیا بیرته راستنیږو که شمیره مساوي وي.

بیا دا مهمه ده چې د حل شوي میتود تعریف کړئ ترڅو وګورئ چې ایا زموږ د پنځلسو لوبو ترتیب حل شوی که نه. لومړی موږ ګورو چې خالي ځای چیرته دی. که په لومړني حالت کې وي، نو اوسنی سمون نوی دی، مخکې پریکړه نه ده شوې. موږ بیا د ټایلونو له لارې په برعکس ترتیب تکرار کوو، او که د ټاګ ارزښت د اړونده شاخص +1 څخه توپیر ولري، موږ غلط بیرته راګرځوو. که نه نو ، د میتود په پای کې دا د ریښتیني راستنیدو وخت دی ځکه چې پہیلی لا دمخه حل شوې.

بله طریقه چې باید تعریف شي نوې لوبه ده. دا اړینه ده چې د لوبې نوې بیلګه جوړه کړئ. د دې کولو لپاره، موږ د لوبې ساحه بیا تنظیم کړه، بیا یې بدله کړه او تر هغه وخته پورې دوام ورکړئ چې د لوبې موقعیت د حل وړ وي.

دلته د ټاګ کلیدي منطق سره کوډ مثال دی:

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;

موږ د سوینګ API په کارولو سره UI ته وده ورکوو

دا د انٹرفیس کار کولو وخت دی. لومړی موږ د 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);
  }
}

په نهایت کې ، راځئ چې د پینټ اجزا میتود له پامه غورځوو ، کوم چې د 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 کې د کاروونکي عملونه پروسس کړئ. د دې کولو لپاره ، په Jpanel کې د MouseListener پلي کول او د حرکت ځایونو لپاره کوډ اضافه کړئ ، دمخه پورته ښودل شوي:

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 ټولګي جوړونکي کې ځای په ځای کوو. په پای کې، موږ د نوې لوبې پیل کولو لپاره د نوي لوبې میتود غږ کوو.

د لوبې بشپړ کوډ

په عمل کې د لوبې لیدلو دمخه وروستی ګام د کوډ ټول عناصر یوځای کول دي. دلته څه پیښیږي:

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

په پای کې، راځئ چې لوبه وکړو!

دا وخت دی چې لوبه پیل کړئ او په عمل کې یې ازموینه وکړئ. ساحه باید داسې ښکاري:

په جاوا کې "ټاګ" - د بشپړې لوبې رامینځته کولو څرنګوالی

راځئ هڅه وکړو چې معما حل کړو. که هرڅه سم وي، موږ دا ترلاسه کوو:

په جاوا کې "ټاګ" - د بشپړې لوبې رامینځته کولو څرنګوالی

بس نور څه نه. ایا تاسو نور تمه درلوده؟ 🙂

Skillbox وړاندیز کوي:

سرچینه: www.habr.com

Add a comment