
«Viisakus» vĂ”i «Viiekas» on suurepĂ€rane nĂ€ide lihtsast loogika mĂ€ngust, mis on populaarsuselt ĂŒle kogu maailma. Probleemi lahendamiseks tuleb numbrikastid paigutada jĂ€rjestikku, alates vĂ€iksemast kuni suuremani. See ei ole lihtne, aga huvitav.
KĂ€esolevas Ă”petuses nĂ€itame, kuidas luua «Viiekas» Java 8-ga Eclipseâis. Kasutame kasutajaliidese arendamiseks Swing API-d.
Tuletame meelde: kĂ”igile «Habra» lugejatele â 10 000 rubla soodustus, kui registreerite end Skillboxi mis tahes kursusele promokoodi «Habr» abil.
Skillbox soovitab: Hariduslik veebikursus .
MĂ€ngu disainimine
Selles etapis tuleb mÀÀratleda omadused:
- Size â mĂ€nguvĂ€lja suurus;
- nbTiles â mĂ€ngunuppude arv mĂ€nguvĂ€ljal. nbTiles = size*size â 1;
- Tiles â nupp, mis kujutab endast ĂŒhemÔÔtmelist tĂ€isarvude massiivi. Iga nupp saab ainulaadse vÀÀrtuse vahemikus [0, nbTiles]. Null tĂ€histab tĂŒhja kasti;
- blankPos â tĂŒhja kasti positsioon.
MĂ€ngu loogika
Tuleb mÀÀratleda nullimismeetod (reset), mida kasutatakse uue mÀngupositsiooni algatamiseks. Nii paneme vÀÀrtuse igasse mÀngunupu massiivi elemendisse. Siis paigutame blankPos massiivi viimasesse positsiooni.
Samuti on vajalik shuffle meetod mĂ€ngunuppude massiivi segamiseks. Me ei kaasata tĂŒhja nuppu segamisse, et hoida see endises positsioonis.
Kuna vaid pool vĂ”imalikest mĂ”istatuste algpositsioonidest on lahendatavad, tuleb segunemise tulemus kontrollida, et veenduda, kas praegune asetuse olukord on ĂŒleĂŒldse lahendatav. Selleks mÀÀratleme meetodi isSolvable.
Kui konkreetse viiese enne on viies, mille vÀÀrtus on suurem, loetakse seda inversiooniks. Kui tĂŒhi viies on oma kohas, peab inversioonide arv olema paarisarv, et mĂ”istatus oleks lahendatav. Seega loendame inversioonide arvu ja tagastame true, kui arv on paarisarv.
SeejĂ€rel on oluline mÀÀratleda meetod isSolved, et kontrollida, kas meie mĂ€ngu Game Of Fifteen asetuse olek on lahendatud. Esiteks vaatame, kus asub tĂŒhi viies. Kui algasendis, siis on praegune asetuse olek uus, varem lahendamata. SeejĂ€rel lĂ€bime plaadid vastupidises jĂ€rjestuses ja kui viie vÀÀrtus erineb vastava indeksi +1-st, tagastame false. Kui mitte, on lĂ”puks meetodis aeg tagastada true, sest mĂ”istatus on juba lahendatud.
Teine meetod, mille mÀÀratlemine on vajalik, on newGame. See on vajalik uue mÀngu eksemplari loomiseks. Selleks lÀhtestame mÀnguvÀlja, seejÀrel segame selle ja jÀtkame, kuni mÀngupositsioon on lahendatav.
Siin on nÀide koodist, kus on peamine loogika viiekesi mÀngus:
private void newGame() {
do {
reset(); // lÀhtesta algses olekus
shuffle(); // sega
} while(!isSolvable()); // tee, kuni ruudustik 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 pĂŒsivast mĂ€ngust on lahendatavad/
// Iga kord, kui plaat eelneb kÔrgema vÀÀrtusega plaadile, loetakse see
// pöördeks. Meie juhul, kui tĂŒhja plaat on lahendatud positsioonis,
// peab pöördepunkte olema paarisarv, et mÀng 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ĂŒhja plaat ei ole lahendatud positsioonis ==>
return false;
for (int i = nbTiles - 1; i >= 0; i--) {
if (tiles[i] != i + 1)
return false;
}
return true;
}LĂ”puks tuleb programmeerida klotside liikumine massiivis. Seda koodi kutsutakse hiljem vĂ€lja tagasikutsumise (callback) teel, et reageerida hiirekursori liikumisele. Meie mĂ€ng toetab mitme klotsi liikumist samaaegselt. Seega, pĂ€rast seda, kui oleme muundanud ekraanil vajutatud positsiooni klotsiks, saame tĂŒhja klotsi positsiooni ja otsime liikumissuunda, et toetada selle korraga mitut liikumist.
Siin on koodi nÀ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 UI-d Swing API abil
On aeg tegeleda liidese kujundamisega. Esiteks vĂ”etakse klass Jpanel. SeejĂ€rel joonistame klotse mĂ€nguvĂ€ljakule â iga klotsi mÔÔtmete arvutamiseks kasutame mĂ€ngu konstruktori parameetrites mÀÀratletud andmeid:
gridSize = (dim - 2 * margin);
tileSize = gridSize / size;Margin on samuti parameeter, mis on mÀÀratud mÀngu konstruktori kaudu.
NĂŒĂŒd tuleb mÀÀrata meetod drawGrid, et joonistada vĂ”rk ja klotsid ekraanile. AnalĂŒĂŒsime klotside massiivi ja muundame koordinaadid kasutajaliidese koordinaatideks. SeejĂ€rel joonistame iga klotsi keskmesse vastava numbri:
private void drawGrid(Graphics2D g) {
for (int i = 0; i < tiles.length; i++) {
// me konverteerime 1D koordinadid 2D koordinatideks vastavalt 2D massiivi suurusele
int r = i / size;
int c = i % size;
// me konverteerime sisendkoordinadid UI-s
int x = margin + c * tileSize;
int y = margin + r * tileSize;
// kontrollime erijuhtumit tĂŒhja plaadi jaoks
if(tiles[i] == 0) {
if (gameOver) {
g.setColor(FOREGROUND_COLOR);
drawCenteredString(g, "u2713", x, y);
}
continue;
}
// teiste plaatide jaoks
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 ĂŒle kirjutame paintComponent meetodi, mis pĂ€rineb JPane klassist. SeejĂ€rel kasutame meetodit drawGrid ja seejĂ€rel meetodit drawStartMessage, 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) {
// keskenda string s antud ruudule (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);
}Kasutaja tegevusele reageerimine kasutajaliideses
Kuna mĂ€ng peab sujuvalt kulgema, on vajalik kasutaja tegevuste töötlemine kasutajaliideses. Selleks lisame Jpanelile MouseListener'i ja koodi viie tĂŒkki liigutamiseks, nagu on juba ĂŒlal nĂ€idatud:
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// kasulik kasutaja liikumiseks ruudustikul hiireklikkide kaudu
// on aeg rakendada kasutajate interaktsiooni, et liikuda plaatide vahel mÀngu lahendamiseks!
if (gameOver) {
newGame();
} else {
// vÔtame kliki positsiooni
int ex = e.getX() - margin;
int ey = e.getY() - margin;
// klikk ruudustikus?
if (ex gridSize || ey gridSize)
return;
// vÔtame positsiooni ruudustikus
int c1 = ex / tileSize;
int r1 = ey / tileSize;
// vĂ”tame tĂŒhja raku positsiooni
int c2 = blankPos % size;
int r2 = blankPos / size;
// muundame 1D koordinaatideks
int clickPos = r1 * size + c1;
int dir = 0;
// otsime suunda mitme plaadi liigutamiseks 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) {
// liigume plaatidega 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();
}
// uuesti joonistame paneeli
repaint();
}
});Kood asetatakse klassi GameOfFifteen konstruktorisse. LÔpus kutsume vÀlja meetodi newGame, et alustada uut mÀngu.
MÀngu tÀiskood
Viimane samm enne mÀngu nÀgemist tegevuses on kÔik koodi elemendid kokku panna. Siin on, mida saate:
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 Pusle MĂ€ngu Java 8 ja Swingiga
// KĂŒsimuste korral lugege julgelt kommentaare ;)
public class GameOfFifteen extends JPanel { // meie ruudustik joonistatakse spetsiaalses paneelis
// Meie 15 mÀngu 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 erivÀrvi
// Plaatide segamiseks mÔeldud juhuslik 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 serv
private int margin;
// Ruudustiku UI suurus
private int gridSize;
private boolean gameOver; // tÔene, kui mÀng on lÀbi, vale vastupidiselt
public GameOfFifteen(int size, int dim, int mar) {
this.size = size;
dimension = dim;
margin = mar;
// initsialiseerime plaadid
nbTiles = size * size - 1; // -1, sest me ei arvesta tĂŒhja plaati
tiles = new int[size * size];
// arvutame ruudustiku 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) {
// kasutatakse, et lasta kasutajatel ruudustikku interakteeruda hiireklÔpsudega
// on aeg rakendada kasutajatega interaktsiooni, et liikuda plaatidega mÀngu lahendamiseks!
if (gameOver) {
newGame();
} else {
// saame klÔpsuse asukoha
int ex = e.getX() - margin;
int ey = e.getY() - margin;
// klÔps ruudustikus?
if (ex gridSize || ey gridSize)
return;
// saame asukoha ruudustikus
int c1 = ex / tileSize;
int r1 = ey / tileSize;
// saame tĂŒhja ruudu asukoha
int c2 = blankPos % size;
int r2 = blankPos / size;
// konverteerime 1D koordinaatides
int clickPos = r1 * size + c1;
int dir = 0;
// otsime suunda mitme plaadi liigutamiseks 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();
}
// joonistame paneeli uuesti
repaint();
}
});
newGame();
}
private void newGame() {
do {
reset(); // lÀhtestame algolekusse
shuffle(); // segame
} while(!isSolvable()); // teeme seda seni, kuni ruudustik 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 pooled pusle permutatsioonid on lahendatavad.
// Iga kord, kui plaat on eelneva suurema vÀÀrtusega plaadi ees, loetakse see
// tagasi pöördumisel. Meie juhul, kui tĂŒhja plaat on lahendatud positsioonis,
// peavad inversioonide arv olema paaris, et pusle 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ĂŒhja plaat ei ole lahendatud positsioonis == false
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 Pusle MĂ€ng");
frame.setResizable(false);
frame.add(new GameOfFifteen(4, 550, 30), BorderLayout.CENTER);
frame.pack();
// keskele ekraanile
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}LÔpuks saame mÀngida!
NĂŒĂŒd on Ă”ige aeg mĂ€ng kĂ€ivitada ja vaadata, kuidas see toimib. MĂ€nguvĂ€li peaks vĂ€lja nĂ€gema jĂ€rgmiselt:

Proovime mÔistatust lahendada. Kui kÔik lÀheb hÀsti, siis saame jÀrgmise tulemuse:

Ja kĂ”ik! Kas te ootasite rohkem? đ
Skillbox soovitab:
- Praktiline kursus .
- Rakenduslik veebikursus .
- Kaheaastane praktiline kursus .
Allikas: habr.com
