"Ħmistax", jew "Ħmistax" huwa eżempju kbir ta 'logħba loġika sempliċi li hija popolari mad-dinja kollha. Sabiex issolvi l-puzzle, għandek bżonn tirranġa l-kwadri bin-numri fl-ordni, mill-iżgħar għall-akbar. Mhux faċli, imma interessanti.
Fit-tutorja tal-lum, aħna nuruk kif tiżviluppa Fifteen f'Java 8 b'Eclipse. Biex tiżviluppa l-UI, se nużaw l-API Swing.
Infakkrukom:għall-qarrejja kollha ta '"Habr" - skont ta' 10 rublu meta tirreġistra fi kwalunkwe kors ta 'Skillbox billi tuża l-kodiċi promozzjonali "Habr".
F'dan l-istadju, trid tiddefinixxi l-proprjetajiet:
Daqs - id-daqs tal-playing field;
nbTiles - in-numru ta' tikketti fil-qasam. nbTiles = daqs * daqs - 1;
Il-madum huwa tikketta, li hija firxa unidimensjonali ta 'numri interi. Kull waħda mit-tikketti se tirċievi valur uniku fil-medda [0, nbTiles]. Żero jindika kwadru vojt;
blankPos - il-pożizzjoni tal-kwadru vojt.
Loġika tal-logħob
Għandna bżonn niddefinixxu metodu ta 'reset użat biex inizjalizza pożizzjoni ġdida tal-logħba. Dan huwa kif nissettjaw valur għal kull element tal-firxa tat-tikketta. Ukoll, allura npoġġu blankPos fl-aħħar pożizzjoni tal-firxa.
Għandna bżonn ukoll metodu ta 'shuffle biex tħawwad il-firxa ta' tikketti. Aħna ma ninkludux tikketta vojta fil-proċess tat-tħaffir biex inħalluha fil-pożizzjoni oriġinali tagħha.
Peress li nofs biss il-pożizzjonijiet tal-bidu possibbli tal-puzzle għandhom soluzzjoni, ir-riżultat tal-shuffle għandu jiġi ċċekkjat biex jiġi żgurat li t-tqassim kurrenti huwa ġeneralment solvubbli. Biex tagħmel dan, aħna niddefinixxu l-metodu isSolvable.
Jekk speck partikolari jkun preċedut minn speck b'valur ogħla, dan jitqies bħala inverżjoni. Meta t-tikketta vojta tkun f'postha, in-numru ta 'inverżjonijiet għandu jkun ugwali biex il-puzzle jista' jiġi solvut. Allura ngħoddu n-numru ta 'inverżjonijiet u nirritornaw veru jekk in-numru huwa ugwali.
Sussegwentement, huwa importanti li tiddefinixxi metodu isSolved biex tivverifika jekk l-idejn tagħna tal-Logħba Ta' Ħmistax ġietx solvuta. L-ewwel inħarsu lejn fejn tinsab it-tikketta vojta. Jekk fil-pożizzjoni inizjali, allura t-tqassim attwali huwa ġdid, mhux solvut qabel. Aħna mbagħad loop permezz tal-madum f'ordni inversa, u jekk il-valur tat-tikketta huwa differenti mill-indiċi +1 korrispondenti, nirritornaw falza. Inkella, wasal iż-żmien li terġa 'lura vera fl-aħħar tal-metodu minħabba li l-puzzle diġà ġie solvut.
Metodu ieħor li jeħtieġ li jiġi definit huwa newGame. Huwa meħtieġ li tinħoloq istanza ġdida tal-logħba. Biex tagħmel dan, irrisettjaw il-bord tal-logħob, imbagħad naħdmuh u nkomplu sakemm il-pożizzjoni tal-logħba tkun riżolvabbli.
Hawn eżempju ta 'kodiċi bil-loġika ewlenija tat-tikketti:
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;
}
Fl-aħħarnett, għandek bżonn tipprogramma l-moviment tat-tikketti fil-firxa. Dan il-kodiċi se jissejjaħ aktar tard permezz ta 'callback biex jirrispondi għall-moviment tal-cursor. Il-logħba tagħna se tappoġġja movimenti multipli tal-madum fl-istess ħin. Għalhekk, wara li nkunu kkonvertijna l-pożizzjoni ppressata fuq l-iskrin għal tikketta, niksbu l-pożizzjoni ta 'tikketta vojta u nfittxu direzzjoni ta' moviment biex tappoġġja bosta mill-movimenti tagħha fl-istess ħin.
Hawn kodiċi eżempju:
// 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;
L-iżvilupp tal-UI bi Swing API
Wasal iż-żmien għall-interface. L-ewwel nieħdu l-klassi Jpanel. Imbagħad niġbdu tikketti fuq il-qasam - biex nikkalkulaw id-daqs ta 'kull wieħed, se nużaw id-dejta speċifikata fil-parametru tal-kostruttur tal-logħba:
Marġni huwa wkoll parametru stabbilit fil-kostruttur tal-logħba.
Issa rridu niddefinixxu l-metodu drawGrid biex niġbdu l-grilja u l-ispecks fuq l-iskrin. Aħna nanalizzaw il-firxa ta 'tikketti u nikkonvertiw il-koordinati f'koordinati tal-interface tal-utent. Imbagħad niġbed kull tag bin-numru korrispondenti fiċ-ċentru:
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);
}
}
Fl-aħħarnett, aħna se jegħlbu l-metodu paintComponent, li huwa derivat mill-klassi JPane. Imbagħad nużaw il-metodu drawGrid u mbagħad il-metodu drawStartMessage biex nuru messaġġ li jqanqalna nikklikkjaw biex nibdew il-logħba:
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);
}
Twieġeb għall-azzjonijiet tal-utent fl-IU
Sabiex il-logħba tmexxi l-kors tagħha, huwa meħtieġ li jiġu mmaniġġjati l-azzjonijiet tal-utent fl-UI. Biex tagħmel dan, aħna nżidu l-implimentazzjoni ta 'MouseListener fuq Jpanel u l-kodiċi biex iċaqlaq it-tikketti, diġà muri hawn fuq:
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();
}
});
Il-kodiċi jitqiegħed fil-kostruttur tal-klassi GameOfFifteen. Fl-aħħar nett, insejħu l-metodu newGame biex tibda logħba ġdida.
Kodiċi tal-logħba sħiħa
L-aħħar pass qabel ma tara l-logħba fl-azzjoni huwa li tpoġġi l-biċċiet kollha tal-kodiċi flimkien. Hawn x'jiġri:
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);
});
}
}
Fl-aħħarnett, ejja nilagħbu!
Wasal iż-żmien li tniedi l-logħba u tittestjaha fl-azzjoni. Il-qasam għandu jidher bħal dan:
Ejja nippruvaw insolvu l-puzzle. Jekk kollox mar tajjeb, niksbu dan: