"ืงื•ื•ื™ื˜ืœ" ืื™ืŸ ื“ื–ืฉืึทื•ื•ืึท - ื•ื•ื™ ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ืึท ืคื•ืœ-ืคืœืขื“ื–ืฉื“ ืฉืคึผื™ืœ

"ืงื•ื•ื™ื˜ืœ" ืื™ืŸ ื“ื–ืฉืึทื•ื•ืึท - ื•ื•ื™ ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ืึท ืคื•ืœ-ืคืœืขื“ื–ืฉื“ ืฉืคึผื™ืœ

"ืคื•ืคืฆืŸ" ืึธื“ืขืจ "ืคื•ืคืฆืŸ" ืื™ื– ืึท ื•ื™ืกื’ืขืฆื™ื™ื›ื ื˜ ื‘ื™ื™ึทืฉืคึผื™ืœ ืคื•ืŸ ืึท ืคึผืฉื•ื˜ ืœืึธื’ื™ืง ืฉืคึผื™ืœ ื•ื•ืึธืก ืื™ื– ืคืึธืœืงืก ืื™ื‘ืขืจ ื“ื™ ื•ื•ืขืœื˜. ืื™ืŸ ืกื“ืจ ืฆื• ืกืึธืœื•ื•ืข ื“ื™ ืจืขื˜ืขื ื™ืฉ, ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ืฆื•ืœื™ื™ื’ืŸ ื“ื™ ืกืงื•ื•ืขืจื– ืžื™ื˜ ื ื•ืžืขืจืŸ ืื™ืŸ ืกื“ืจ, ืคื•ืŸ ืงืœืขื ืกื˜ืขืจ ืฆื• ื’ืจืขืกื˜ืŸ. ืขืก ืื™ื– ื ื™ืฉื˜ ื’ืจื™ื ื’, ืึธื‘ืขืจ ืขืก ืื™ื– ื˜ืฉื™ืงืึทื•ื•ืข.

ืื™ืŸ ื”ื™ื™ึทื ื˜ ืก ื˜ื•ื˜ืึธืจื™ืึทืœ ืžื™ืจ ื•ื•ืขื˜ ื•ื•ื™ื™ึทื–ืŸ ืื™ืจ ื•ื•ื™ ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ Fifteen ืื™ืŸ Java 8 ืžื™ื˜ Eclipse. ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ื“ื™ ื•ื™ ืžื™ืจ ื•ื•ืขืœืŸ ื ื•ืฆืŸ ื“ื™ Swing API.

ืžื™ืจ ื“ืขืจืžืึธื ืขืŸ ืื™ืจ: ืคึฟืึทืจ ืึทืœืข ืœื™ื™ืขื ืขืจ ืคื•ืŸ "ื”ื‘ืจ" - ืึท ืึทืจืึธืคึผืจืขื›ืขื ืขืŸ ืคื•ืŸ 10 ืจื•ื‘ืœ ื•ื•ืขืŸ ืื™ืจ ืคืึทืจืฉืจื™ื™ึทื‘ืŸ ืื™ืŸ ืงื™ื™ืŸ ืกืงื™ืœื‘ืึธืงืก ืงื•ืจืก ื ื™ืฆืŸ ื“ื™ ืคึผืขืจืžืึธื•ืฉืึทื ืึทืœ ืงืึธื“ "ื”ื‘ืจ".

Skillbox ืจืขืงืึทืžืขื ื“ื–: ื‘ื™ืœื“ื•ื ื’ืงืจื™ื™ื– ืึธื ืœื™ื™ืŸ ืงื•ืจืก "ืคืึทืš ื“ื–ืฉืึทื•ื•ืึท ื“ืขื•ื•ืขืœืึธืคึผืขืจ".

ืฉืคึผื™ืœ ืคึผืœืึทืŸ

ืื™ืŸ ื“ืขื ื‘ื™ื ืข ืื™ืจ ื“ืึทืจืคึฟืŸ ืฆื• ื“ืขืคื™ื ื™ืจืŸ ื“ื™ ืคึผืจืึธืคึผืขืจื˜ื™ืขืก:

  • ื’ืจื™ื™ืก - ื“ื™ ื’ืจื™ื™ืก ืคื•ืŸ ื“ืขื ืฉืคึผื™ืœ ืคืขืœื“;
  • nbTiles - ื ื•ืžืขืจ ืคื•ืŸ ื˜ืึทื’ืก ืื™ืŸ ื“ืขื ืคืขืœื“. ื ื‘ื˜ื™ืœืขืก = ื’ืจื™ื™ืก * ื’ืจื™ื™ืก - 1;
  • ื˜ื™ืœืขืก ืื™ื– ืึท ืงื•ื•ื™ื˜ืœ ื•ื•ืึธืก ืื™ื– ืึท ืื™ื™ืŸ-ื“ื™ืžืขื ืฉืึทื ืึทืœ ืžืขื ื’ืข ืคื•ืŸ โ€‹โ€‹โ€‹โ€‹ื™ื ื˜ืึทื“ื–ืฉืขืจื–. ื™ืขื“ืขืจ ืคื•ืŸ ื“ื™ ื˜ืึทื’ืก ื•ื•ืขื˜ ื‘ืึทืงื•ืžืขืŸ ืึท ื™ื™ื ืฆื™ืง ื•ื•ืขืจื˜ ืื™ืŸ ื“ื™ ืงื™ื™ื˜ [0, nbTiles]. ื ื•ืœ ื™ื ื“ื™ืงื™ื™ืฅ ืึท ืœื™ื™ื“ื™ืง ืงื•ื•ืึทื“ืจืึทื˜;
  • 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;

ืžื™ืจ ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ื•ื™ ื ื™ืฆืŸ 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);
}

ืจืขืึทื’ื™ืจืŸ ืฆื• ื‘ืึทื ื™ืฆืขืจ ืึทืงืฉืึทื ื– ืื™ืŸ ื“ื™ ื•ื™

ื›ึผื“ื™ ื“ื™ ืฉืคึผื™ืœ ื–ืึธืœ ืœื•ื™ืคืŸ, ืขืก ืื™ื– ื ื™ื™ื˜ื™ืง ืฆื• ืคึผืจืึธืฆืขืก ื‘ืึทื ื™ืฆืขืจ ืึทืงืฉืึทื ื– ืื™ืŸ ื“ื™ ื•ื™. ืฆื• ื˜ืึธืŸ ื“ืึธืก, ืœื™ื™ื’ืŸ ื“ื™ ื™ืžืคึผืœืึทืžืขื ื˜ื™ื™ืฉืึทืŸ ืคื•ืŸ 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);
    });
  }
 
 
}

ืฆื•ื ืกื•ืฃ, ืœืึธืžื™ืจ ืฉืคึผื™ืœืŸ!

ืขืก ืื™ื– ืฆื™ื™ื˜ ืฆื• ืงืึทื˜ืขืจ ื“ื™ ืฉืคึผื™ืœ ืื•ืŸ ืคึผืจื•ื‘ื™ืจืŸ ืขืก ืื™ืŸ ืงืึทืžืฃ. ื“ื™ ืคืขืœื“ ื–ืึธืœ ืงื•ืงืŸ ื•ื•ื™ ื“ืึธืก:

"ืงื•ื•ื™ื˜ืœ" ืื™ืŸ ื“ื–ืฉืึทื•ื•ืึท - ื•ื•ื™ ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ืึท ืคื•ืœ-ืคืœืขื“ื–ืฉื“ ืฉืคึผื™ืœ

ื–ืืœ ืก ืคึผืจื•ื‘ื™ืจืŸ ืฆื• ืกืึธืœื•ื•ืข ื“ื™ ืจืขื˜ืขื ื™ืฉ. ืื•ื™ื‘ ืึทืœืฅ ืื™ื– ื’ื•ื˜, ืžื™ืจ ื‘ืึทืงื•ืžืขืŸ ื“ืขื:

"ืงื•ื•ื™ื˜ืœ" ืื™ืŸ ื“ื–ืฉืึทื•ื•ืึท - ื•ื•ื™ ืฆื• ืึทื ื˜ื•ื•ื™ืงืœืขืŸ ืึท ืคื•ืœ-ืคืœืขื“ื–ืฉื“ ืฉืคึผื™ืœ

ืึทื– ืก ืึทืœืข. ื”ืึธื˜ ืื™ืจ ื“ืขืจื•ื•ืึทืจื˜ืŸ ืžืขืจ? ๐Ÿ™‚

Skillbox ืจืขืงืึทืžืขื ื“ื–:

ืžืงื•ืจ: www.habr.com

ืงื•ื™ืคืŸ ืคืึทืจืœืึธื–ืœืขืš ื”ืึธืกื˜ื™ื ื’ ืคึฟืึทืจ ื–ื™ื™ื˜ืœืขืš ืžื™ื˜ DDoS ืฉื•ืฅ, VPS VDS ืกืขืจื•ื•ืขืจืก ๐Ÿ”ฅ ืงื•ื™ืคื˜ ืคืึทืจืœืขืกืœืขื›ืข ื•ื•ืขื‘ื–ื™ื™ื˜ืœ ื”ืึธืกื˜ื™ื ื’ ืžื™ื˜ DDoS ืฉื•ืฅ, VPS VDS ืกืขืจื•ื•ืขืจืก | ProHoster