"Viisteist" Java's — kuidas arendada tĂ€ielikku mĂ€ngu

"Viisteist" Java's — kuidas arendada tĂ€ielikku mĂ€ngu

«Viisteist», vĂ”i «Viisteistk» on suurepĂ€rane nĂ€ide lihtsast loogikapallist, mis on populaarne ĂŒle kogu maailma. MĂ”istatuse lahendamiseks tuleb numbrilised ruudud paigutada Ă”igesse jĂ€rjestusse, alates vĂ€iksemast kuni suuremani. See pole lihtne, kuid on huvitav.

KÀesolevas Ôpetuses nÀitame, kuidas arendada «Viisteist» Java 8 ja Eclipse'i abil. Kasutame kasutajaliidese loomiseks Swing API-d.

Tuletame meelde: kĂ”igile «Habr» lugejatele – 10 000 rubla allahindlus igale Skillboxi kursusele, kasutades sooduskoodi «Habr».

Skillbox soovitab: Haridusalane veebikursus «Java-arendaja amet».

MĂ€ngu kavandamine

Selles etapis tuleb mÀÀratleda omadused:

  • Size — mĂ€nguvĂ€lja suurus;
  • nbTiles — ruutude arv mĂ€nguvĂ€ljal. nbTiles = size*size — 1;
  • Tiles — viisteist, mis on ĂŒhe mÔÔtmega tĂ€isarvude massiiv. Igal viisteistk on ainulaadne vÀÀrtus vahemikus [0, nbTiles]. Null tĂ€histab tĂŒhja ruutu;
  • blankPos — tĂŒhja ruudu positsioon.

MĂ€ngu loogika

Peab mÀÀratlema nullimise (reset) meetodi, mida kasutatakse uue mÀnguasendi initsialiseerimiseks. Nii seadistame vÀÀrtuse iga viisteistk massiivi elemendi jaoks. SeejÀrel paneme blankPos massiivi viimasesse positsiooni.

Vajame ka segamise meetodit, et viisteiste massiivi segada. JĂ€tame tĂŒhja viisteistk segamisprotsessist vĂ€lja, et see jÀÀks oma kohale.

Kuna ainult pool vÔimalikest mÀnguasenditest on lahendatavad, tuleb kontrollida saadud segamise tulemust, et veenduda, et praegune paigutus on tÔesti lahendatav. Selleks mÀÀratleme meetodi isSolvable.

Kui konkreetsele viisteistk'le eelneb viisteistk, mille vÀÀrtus on kĂ”rgem, loetakse seda inversiooniks. Kui tĂŒhja viisteistk on oma kohas, peab inversioonide arv olema paarisarv, et mĂ”istatus oleks lahendatav. Seega loeme inversioonide arvu ja tagastame true, kui arv on paarisarv.

SeejĂ€rel on oluline mÀÀratleda meetod isSolved, et kontrollida, kas meie viisteistk paigutus on lahendatud. Esiteks vaatame, kus asub tĂŒhja viisteistk. Kui algasendis, siis on praegune paigutus uus, mitte kunagi lahendatud. SeejĂ€rel vaatame ĂŒle plaatide vÀÀrtused vastupidises jĂ€rjekorras ning kui viisteistk'i vÀÀrtus erineb vastavast indeksist +1, tagastame false. Vastupidiselt tagastame meetodi lĂ”pus true, kuna mĂ”istatus on juba lahendatud.

Teine meetod, mida tuleb mÀÀratleda, on newGame. Seda vajatakse uue mÀngu eksemplari loomiseks. Selleks lÀhtestame mÀnguvÀlja, segame seda ja jÀtkame, kuni mÀngupositsioon on lahendatav.

Siin on koodinĂ€ide, mis sisaldab peamist loogikat viiekĂŒmnese mĂ€ngu jaoks:

private void newGame() {
  do {
    reset(); // lÀhtesta algseisund
    shuffle(); // sega
  } while(!isSolvable()); // jÀtka, kuni ruut on lahendatav
 
  gameOver = false;
}
 
private void reset() {
  for (int i = 0; i  1) {
    int r = RANDOM.nextInt(n--);
    int tmp = tiles[r];
    tiles[r] = tiles[n];
    tiles[n] = tmp;
  }
}
 
// Ainult pool puzzle permutatsioonidest on lahendatavad/
// Iga kord, kui plaat on eelnevalt kÔrgema vÀÀrtusega plaadi ees,
// loetakse see pöördeks. Meie puhul, kui tĂŒhi plaat on lahendatud positsioonis,
// peavad pöörded olema paarisarv, et puzzle oleks lahendatav
private boolean isSolvable() {
  int countInversions = 0;
 
  for (int i = 0; i < nbTiles; i++) {
    for (int j = 0; j  tiles[i])
        countInversions++;
    }
  }
 
  return countInversions % 2 == 0;
}
 
private boolean isSolved() {
  if (tiles[tiles.length - 1] != 0) // kui tĂŒhi plaat ei ole lahendatud positsioonis == mitte lahendatud
    return false;
 
  for (int i = nbTiles - 1; i >= 0; i--) {
    if (tiles[i] != i + 1)
      return false;
  }
 
  return true;
}

LĂ”puks tuleb programmeerida viiekĂŒmnese mĂ€ngu liikuvad plaadid massiivis. Seda koodi kutsutakse hiljem tagasi (callback), et reageerida kursori liikumisele. Meie mĂ€ng toetab mitme plaadi liikumist samaaegselt. Seega, pĂ€rast selle leidmist, et vajutatud positsioon ekraanil vastab viiekĂŒmnesele plaadile, leiame tĂŒhja plaadi positsiooni ja otsime liikumise suunda, et toetada mitme plaadi liikumist samaaegselt.

Siin on koodinÀide:

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

Arendame kasutajaliidese Swing API-l

On aeg tegeleda liidesega. Alustame Jpanel klassist. Siis joonistame mĂ€ngulaudadele viiekĂŒmneseid — iga plaadi suuruse arvutamiseks kasutame andmeid, mis on mÀÀratud mĂ€ngu konstruktoriparameetris:

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

Margin on samuti paramater, mis on mÀÀratud mÀngu konstruktoris.

NĂŒĂŒd tuleb mÀÀratleda meetod drawGrid, et joonistada ruut ja viiekĂŒmnesed ekraanile. AnalĂŒĂŒsime viiekĂŒmneste massiivi ja muundame koordinaadid kasutajaliidese koordinaatideks. SeejĂ€rel joonistame iga viiekĂŒmnese vastava numbri keskele:

private void drawGrid(Graphics2D g) {
  for (int i = 0; i < tiles.length; i++) {
    // me konverteerime 1D koordinaadid 2D koordinaatideks, arvestades 2D massiivi suurust
    int r = i / size;
    int c = i % size;
    // me konverteerime UI koordinaatideks
    int x = margin + c * tileSize;
    int y = margin + r * tileSize;
 
    // kontrollime, kas on eriline juhtum tĂŒhja plaadi puhul
    if(tiles[i] == 0) {
      if (gameOver) {
        g.setColor(FOREGROUND_COLOR);
        drawCenteredString(g, "u2713", x, y);
      }
 
      continue;
    }
 
    // teiste plaatide puhul
    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);
  }
}

LĂ”puks ĂŒletame paintComponent meetodi, mis pĂ€rineb JPane klassist. Seega kasutame drawGrid meetodit, ning seejĂ€rel drawStartMessage meetodit, et kuvada sĂ”num, mis kutsub klikkima mĂ€ngu alustamiseks:

private void drawStartMessage(Graphics2D g) {
  if (gameOver) {
    g.setFont(getFont().deriveFont(Font.BOLD, 18));
    g.setColor(FOREGROUND_COLOR);
    String s = "Kliki, et alustada uut mÀngu";
    g.drawString(s, (getWidth() - g.getFontMetrics().stringWidth(s)) / 2,
        getHeight() - margin);
  }
}
 
private void drawCenteredString(Graphics2D g, String s, int x, int y) {
  // tsenterime stringi s antud plaadi (x,y) osas
  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);
}

Reageerime kasutaja toimingutele UI-s

Kuna mÀng peab oma rada kÀima, on vajalik kasutaja toimingute töötlemine UI-s. Selleks lisame MouseListeneri rakenduse Jpanelile ja koodi, et viia viieplaate, nagu eelnevalt nÀidatud:

addMouseListener(new MouseAdapter() {
  @Override
  public void mousePressed(MouseEvent e) {
    // vÔimaldab kasutajatel suhelda ruudustiku kaudu klikkides
    // on aeg rakendada kasutaja interaktsiooni plaatide liigutamiseks mÀngu lahendamiseks!
    if (gameOver) {
      newGame();
    } else {
      // saadakse klikikoht
      int ex = e.getX() - margin;
      int ey = e.getY() - margin;
 
      // klikiti ruudustikus?
      if (ex  gridSize || ey  gridSize)
        return;
 
      // saadakse positsioon ruudustikus
      int c1 = ex / tileSize;
      int r1 = ey / tileSize;
 
      // saadakse tĂŒhja ruudu positsioon
      int c2 = blankPos % size;
      int r2 = blankPos / size;
 
      // me konverteerime 1D koordinaati
      int clickPos = r1 * size + c1;
 
      int dir = 0;
 
      // otsime suunda mitme plaadi liikumiseks korraga
      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) {
        // liigutame plaate suunas
        do {
          int newBlankPos = blankPos + dir;
          tiles[blankPos] = tiles[newBlankPos];
          blankPos = newBlankPos;
        } while(blankPos != clickPos);
 
        tiles[blankPos] = 0;
      }
 
      // kontrollime, kas mÀng on lahendatud
      gameOver = isSolved();
    }
 
    // uuendame paneeli
    repaint();
  }
});

Kood loome klassi GameOfFifteen konstruktoris. LĂ”pus kutsume ĂŒles meetodi newGame, et alustada uut mĂ€ngu.

TÀielik mÀngu kood

Viimane samm enne mÀngu nÀgemist on kokku koguda kÔik koodi elemendid. Siin on, mis meid ootab:

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;
 
// Loome 15-tĂŒkki mĂ”istatus mĂ€ngu Java 8 ja Swingiga
// Kui teil on kĂŒsimusi, siis looge julgelt kommentaare ;)
public class GameOfFifteen extends JPanel { // meie ruudustik joonistatakse spetsiaalses paneelis
 
  // Meie 15-tĂŒkki mĂ€ngu eksemplari suurus
  private int size;
  // Plaatide arv
  private int nbTiles;
  // Ruudustiku UI mÔÔtmed
  private int dimension;
  // Esimene vÀrv
  private static final Color FOREGROUND_COLOR = new Color(239, 83, 80); // kasutame suvalist vÀrvi
  // Plaatide segamiseks suvaline objekt
  private static final Random RANDOM = new Random();
  // Plaatide salvestamine 1D tÀisarvude massiivi
  private int[] tiles;
  // Plaadi suurus UI-s
  private int tileSize;
  // TĂŒhja plaadi asukoht
  private int blankPos;
  // Ruudustiku servad raamile
  private int margin;
  // Ruudustiku UI suurus
  private int gridSize;
  private boolean gameOver; // tÔene, kui mÀng on lÀbi, vale vastasel juhul
 
  public GameOfFifteen(int size, int dim, int mar) {
    this.size = size;
    dimension = dim;
    margin = mar;
    
    // algatame plaadid
    nbTiles = size * size - 1; // -1, sest me ei arvesse tĂŒhja plaati
    tiles = new int[size * size];
    
    // arvutame ruudustiku suuruse ja plaadi suuruse
    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) {
        // kasutame, et lasta kasutajatel ruudustikus klÔpsata
        // on aeg rakendada kasutajatega suhtlemist, et liikuda plaate mÀngu lahendamiseks!
        if (gameOver) {
          newGame();
        } else {
          // saadud klÔpsu positsioon
          int ex = e.getX() - margin;
          int ey = e.getY() - margin;
          
          // klÔps ruudustikus?
          if (ex  gridSize  || ey  gridSize)
            return;
          
          // saadud positsioon ruudustikus
          int c1 = ex / tileSize;
          int r1 = ey / tileSize;
          
          // saadud tĂŒhja ruumi positsioon
          int c2 = blankPos % size;
          int r2 = blankPos / size;
          
          // teisendame 1D koordinaatidesse
          int clickPos = r1 * size + c1;
          
          int dir = 0;
          
          // otsime suunda, et mitu plaati korraga liikuda
          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) {
            // liigutame plaate suunas
            do {
              int newBlankPos = blankPos + dir;
              tiles[blankPos] = tiles[newBlankPos];
              blankPos = newBlankPos;
            } while(blankPos != clickPos);
            
            tiles[blankPos] = 0;
          }
          
          // kontrollime, kas mÀng on lahendatud
          gameOver = isSolved();
        }
        
        // uuendame paneeli
        repaint();
      }
    });
    
    newGame();
  }
 
  private void newGame() {
    do {
      reset(); // lÀhtestamine algseisundisse
      shuffle(); // segamine
    } while(!isSolvable()); // teeme, kuni ruut on lahendatav
    
    gameOver = false;
  }
 
  private void reset() {
    for (int i = 0; i  1) {
      int r = RANDOM.nextInt(n--);
      int tmp = tiles[r];
      tiles[r] = tiles[n];
      tiles[n] = tmp;
    }
  }
 
  // Ainult pool mÔistatuse permutatsioonidest on lahendatavad.
  // Iga kord, kui plaat eelneb kÔrgema vÀÀrtusega plaadile, siis loetakse
  // selleks inversioon. Meie puhul peab lahendatud positsiooniga
  // tĂŒhja plaadi puhul olema inversioonide arv paaris, et mĂ”istatus oleks lahendatav
  private boolean isSolvable() {
    int countInversions = 0;
    
    for (int i = 0; i < nbTiles; i++) {
      for (int j = 0; j  tiles[i])
          countInversions++;
      }
    }
    
    return countInversions % 2 == 0;
  }
 
  private boolean isSolved() {
    if (tiles[tiles.length - 1] != 0) // kui tĂŒhi plaat ei ole lahendatud positsioonis ==> pole lahendatud
      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  {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setTitle("15 Plaadi MĂ€ng");
      frame.setResizable(false);
      frame.add(new GameOfFifteen(4, 550, 30), BorderLayout.CENTER);
      frame.pack();
      // keskendame ekraanil
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
    });
  }
 
 
}

LÔpuks mÀngime!

On just Ôige aeg mÀngu kÀivitamiseks ja selle toimimist kontrollimiseks. VÀli peaks vÀlja nÀgema jÀrgmiselt:

"Viisteist" Java's — kuidas arendada tĂ€ielikku mĂ€ngu

PĂŒĂŒame mĂ”istatusi lahendada. Kui kĂ”ik lĂ€heb hĂ€sti, saame sellise tulemuse:

"Viisteist" Java's — kuidas arendada tĂ€ielikku mĂ€ngu

Nii ongi. Kas ootasite rohkem? 🙂

Skillbox soovitab:

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster