āϜāĻžāĻ­āĻžāϤ⧇ "āĻŸā§āϝāĻžāĻ—" - āϕ⧀āĻ­āĻžāĻŦ⧇ āĻāĻ•āϟāĻŋ āĻĒā§‚āĻ°ā§āĻŖāĻžāĻ™ā§āĻ— āϗ⧇āĻŽ āĻŦāĻŋāĻ•āĻžāĻļ āĻ•āϰāĻž āϝāĻžāϝāĻŧ

āϜāĻžāĻ­āĻžāϤ⧇ "āĻŸā§āϝāĻžāĻ—" - āϕ⧀āĻ­āĻžāĻŦ⧇ āĻāĻ•āϟāĻŋ āĻĒā§‚āĻ°ā§āĻŖāĻžāĻ™ā§āĻ— āϗ⧇āĻŽ āĻŦāĻŋāĻ•āĻžāĻļ āĻ•āϰāĻž āϝāĻžāϝāĻŧ

"āĻĒāύ⧇āϰ⧋" āĻŦāĻž "āĻĒāύ⧇āϰ⧋" āĻāĻ•āϟāĻŋ āϏāĻžāϧāĻžāϰāĻŖ āϞāϜāĻŋāĻ• āϗ⧇āĻŽā§‡āϰ āĻāĻ•āϟāĻŋ āϚāĻŽā§ŽāĻ•āĻžāϰ āωāĻĻāĻžāĻšāϰāĻŖ āϝāĻž āϏāĻžāϰāĻž āĻŦāĻŋāĻļā§āĻŦ⧇ āϜāύāĻĒā§āϰāĻŋāϝāĻŧāĨ¤ āϧāĻžāρāϧāĻžāϟāĻŋ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ, āφāĻĒāύāĻžāϕ⧇ āϛ⧋āϟ āĻĨ⧇āϕ⧇ āĻŦāĻĄāĻŧ āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇ āϏāĻ‚āĻ–ā§āϝāĻž āϏāĻš āĻŦāĻ°ā§āĻ—āĻ•ā§āώ⧇āĻ¤ā§āϰāϗ⧁āϞāĻŋ āϏāĻžāϜāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤ āĻāϟāĻž āϏāĻšāϜ āύāϝāĻŧ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāϟāĻž āφāĻ•āĻ°ā§āώāĻŖā§€āϝāĻŧ.

āφāϜāϕ⧇āϰ āϟāĻŋāωāĻŸā§‹āϰāĻŋāϝāĻŧāĻžāϞ⧇ āφāĻŽāϰāĻž āĻĻ⧇āĻ–āĻžāĻŦ āĻ•āĻŋāĻ­āĻžāĻŦ⧇ Eclipse āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ Java 8-āĻ Fifteen āĻĄā§‡āϭ⧇āϞāĻĒ āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤ UI āĻĄā§‡āϭ⧇āϞāĻĒ āĻ•āϰāϤ⧇ āφāĻŽāϰāĻž Swing API āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻŦāĨ¤

āφāĻŽāϰāĻž āĻŽāύ⧇ āĻ•āϰāĻŋāϝāĻŧ⧇ āĻĻāĻŋāĻšā§āĻ›āĻŋ: "Habr"-āĻāϰ āϏāĻ•āϞ āĻĒāĻžāĻ āĻ•āĻĻ⧇āϰ āϜāĻ¨ā§āϝ - "Habr" āĻĒā§āϰāϚāĻžāϰāĻŽā§‚āϞāĻ• āϕ⧋āĻĄ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āϝ⧇āϕ⧋āύ⧋ Skillbox āϕ⧋āĻ°ā§āϏ⧇ āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ 10 āϰ⧁āĻŦ⧇āϞ āĻ›āĻžāĻĄāĻŧāĨ¤

Skillbox āϏ⧁āĻĒāĻžāϰāĻŋāĻļ āĻ•āϰ⧇: āĻļāĻŋāĻ•ā§āώāĻžāĻ—āϤ āĻ…āύāϞāĻžāχāύ āϕ⧋āĻ°ā§āϏ "āĻĒ⧇āĻļāĻž āϜāĻžāĻ­āĻž āĻŦāĻŋāĻ•āĻžāĻļāĻ•āĻžāϰ⧀".

āϖ⧇āϞāĻžāϰ āύāĻ•āĻļāĻž

āĻāχ āĻĒāĻ°ā§āϝāĻžāϝāĻŧ⧇ āφāĻĒāύāĻžāϕ⧇ āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝāϗ⧁āϞāĻŋ āϏāĻ‚āĻœā§āĻžāĻžāϝāĻŧāĻŋāϤ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇:

  • āφāĻ•āĻžāϰ - āϖ⧇āϞāĻžāϰ āĻŽāĻžāϠ⧇āϰ āφāĻ•āĻžāϰ;
  • nbTiles — āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇āϰ āĻŸā§āϝāĻžāϗ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻžāĨ¤ nbTiles = āφāĻ•āĻžāϰ * āφāĻ•āĻžāϰ - 1;
  • āϟāĻžāχāϞāϏ āĻšāϞ āĻāĻ•āϟāĻŋ āĻŸā§āϝāĻžāĻ— āϝāĻž āĻĒā§‚āĻ°ā§āĻŖāϏāĻ‚āĻ–ā§āϝāĻžāϰ āĻāĻ•-āĻŽāĻžāĻ¤ā§āϰāĻŋāĻ• āĻ…ā§āϝāĻžāϰ⧇āĨ¤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŸā§āϝāĻžāĻ— āĻĒāϰāĻŋāϏāϰ⧇ āĻāĻ•āϟāĻŋ āĻ…āύāĻ¨ā§āϝ āĻŽāĻžāύ āĻĒāĻžāĻŦ⧇ [0, nbTiles]āĨ¤ āĻļā§‚āĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻ–āĻžāϞāĻŋ āĻŦāĻ°ā§āĻ— āύāĻŋāĻ°ā§āĻĻ⧇āĻļ āĻ•āϰ⧇;
  • blankPos — āĻ–āĻžāϞāĻŋ āĻŦāĻ°ā§āĻ—āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύāĨ¤

āϖ⧇āϞāĻž āϝ⧁āĻ•ā§āϤāĻŋ

āφāĻŽāĻžāĻĻ⧇āϰ āĻāĻ•āϟāĻŋ āύāϤ⧁āύ āϗ⧇āĻŽ āĻĒāϜāĻŋāĻļāύ āĻļ⧁āϰ⧁ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻāĻ•āϟāĻŋ āϰāĻŋāϏ⧇āϟ āĻĒāĻĻā§āϧāϤāĻŋ āϏāĻ‚āĻœā§āĻžāĻžāϝāĻŧāĻŋāϤ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇āĨ¤ āĻāχāĻ­āĻžāĻŦ⧇ āφāĻŽāϰāĻž āĻŸā§āϝāĻžāĻ— āĻ…ā§āϝāĻžāϰ⧇āϰ āĻĒā§āϰāϤāĻŋāϟāĻŋ āωāĻĒāĻžāĻĻāĻžāύ⧇āϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻŽāĻžāύ āϏ⧇āϟ āĻ•āϰāĻŋāĨ¤ āφāĻšā§āĻ›āĻž, āϤāĻžāĻšāϞ⧇ āφāĻŽāϰāĻž āĻ…ā§āϝāĻžāϰ⧇āϰ āĻļ⧇āώ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇ blankPos āϰāĻžāĻ–āĻŋāĨ¤

āĻŸā§āϝāĻžāĻ—āϗ⧁āϞāĻŋāϰ āĻ…ā§āϝāĻžāϰ⧇ āĻāϞ⧋āĻŽā§‡āϞ⧋ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āφāĻŽāĻžāĻĻ⧇āϰ āĻāĻ•āϟāĻŋ āĻļāĻžāĻĢ⧇āϞ āĻĒāĻĻā§āϧāϤāĻŋāϰāĻ“ āĻĒā§āϰāϝāĻŧā§‹āϜāύāĨ¤ āφāĻŽāϰāĻž āĻ–āĻžāϞāĻŋ āĻŸā§āϝāĻžāĻ—āϟāĻŋāϕ⧇ āĻāĻ•āχ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇ āϰāĻžāĻ–āĻžāϰ āϜāĻ¨ā§āϝ āĻļāĻžāĻĢāϞāĻŋāĻ‚ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāϤ⧇ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰāĻŋ āύāĻžāĨ¤

āϝ⧇āĻšā§‡āϤ⧁ āϧāĻžāρāϧāĻžāϟāĻŋāϰ āϏāĻŽā§āĻ­āĻžāĻŦā§āϝ āĻĒā§āϰāĻžāϰāĻŽā§āĻ­āĻŋāĻ• āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇āϰ āĻŽāĻžāĻ¤ā§āϰ āĻ…āĻ°ā§āϧ⧇āĻ•āϟāĻŋāϰ āĻāĻ•āϟāĻŋ āϏāĻŽāĻžāϧāĻžāύ āϰāϝāĻŧ⧇āϛ⧇, āϤāĻžāχ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϞ⧇āφāωāϟāϟāĻŋ āĻāĻŽāύāĻ•āĻŋ āϏāĻŽāĻžāϧāĻžāύāϝ⧋āĻ—ā§āϝ āĻ•āĻŋāύāĻž āϤāĻž āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰāϤ⧇ āφāĻĒāύāĻžāϕ⧇ āĻĢāϞāĻžāĻĢāϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻĢāϞāĻžāĻĢāϞ āĻĒāϰ⧀āĻ•ā§āώāĻž āĻ•āϰāϤ⧇ āĻšāĻŦ⧇āĨ¤ āĻāϟāĻŋ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ, āφāĻŽāϰāĻž isSolvable āĻĒāĻĻā§āϧāϤāĻŋ āϏāĻ‚āĻœā§āĻžāĻžāϝāĻŧāĻŋāϤ āĻ•āϰāĻŋāĨ¤

āϝāĻĻāĻŋ āĻāĻ•āϟāĻŋ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŸā§āϝāĻžāϗ⧇āϰ āφāϗ⧇ āĻāĻ•āϟāĻŋ āωāĻšā§āϚ āĻŽāĻžāύ⧇āϰ āĻāĻ•āϟāĻŋ āĻŸā§āϝāĻžāĻ— āĻĨāĻžāϕ⧇ āϤāĻŦ⧇ āĻāϟāĻŋ āĻāĻ•āϟāĻŋ āĻŦāĻŋāĻĒāϰ⧀āϤ āĻšāĻŋāϏāĻžāĻŦ⧇ āĻŦāĻŋāĻŦ⧇āϚāĻŋāϤ āĻšāϝāĻŧāĨ¤ āϝāĻ–āύ āĻ–āĻžāϞāĻŋ āĻ¸ā§āĻĨāĻžāύāϟāĻŋ āĻĨāĻžāϕ⧇, āϤāĻ–āύ āϧāĻžāρāϧāĻžāϟāĻŋ āϏāĻŽāĻžāϧāĻžāύāϝ⧋āĻ—ā§āϝ āĻšāĻ“āϝāĻŧāĻžāϰ āϜāĻ¨ā§āϝ āĻŦāĻŋāĻĒāϰ⧀āϤ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž āĻ…āĻŦāĻļā§āϝāχ āϏāĻŽāĻžāύ āĻšāϤ⧇ āĻšāĻŦ⧇āĨ¤ āϤāĻžāχ āφāĻŽāϰāĻž āĻŦāĻŋāĻĒāϰ⧀āϤ āϏāĻ‚āĻ–ā§āϝāĻž āĻ—āĻŖāύāĻž āĻ•āϰāĻŋ āĻāĻŦāĻ‚ āϏāĻ‚āĻ–ā§āϝāĻžāϟāĻŋ āĻœā§‹āĻĄāĻŧ āĻšāϞ⧇ āϏāĻ¤ā§āϝ āĻĢ⧇āϰāϤ āĻĻāĻŋāχāĨ¤

āφāĻŽāĻžāĻĻ⧇āϰ āϗ⧇āĻŽ āĻ…āĻĢ āĻĢāĻŋāĻĢāϟāĻŋāύ āϞ⧇āφāωāϟāϟāĻŋ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇ āĻ•āĻŋāύāĻž āϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻž āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ isSolved āĻĒāĻĻā§āϧāϤāĻŋāϟāĻŋ āϏāĻ‚āĻœā§āĻžāĻžāϝāĻŧāĻŋāϤ āĻ•āϰāĻž āϗ⧁āϰ⧁āĻ¤ā§āĻŦāĻĒā§‚āĻ°ā§āĻŖāĨ¤ āĻĒā§āϰāĻĨāĻŽā§‡ āφāĻŽāϰāĻž āĻĻ⧇āĻ–āĻŋ āĻ–āĻžāϞāĻŋ āϜāĻžāϝāĻŧāĻ—āĻžāϟāĻž āϕ⧋āĻĨāĻžāϝāĻŧāĨ¤ āϝāĻĻāĻŋ āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ• āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇ āĻĨāĻžāϕ⧇, āϤāĻžāĻšāϞ⧇ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻĒā§āϰāĻžāĻ¨ā§āϤāĻŋāĻ•āĻ•āϰāĻŖāϟāĻŋ āύāϤ⧁āύ, āĻĒā§‚āĻ°ā§āĻŦ⧇ āϏāĻŋāĻĻā§āϧāĻžāĻ¨ā§āϤ āύ⧇āĻ“āϝāĻŧāĻž āĻšāϝāĻŧāύāĻŋāĨ¤ āϤāĻžāϰāĻĒāϰ⧇ āφāĻŽāϰāĻž āĻŦāĻŋāĻĒāϰ⧀āϤ āĻ•ā§āϰāĻŽā§‡ āϟāĻžāχāϞāϏ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻ•āϰāĻŋ āĻāĻŦāĻ‚ āϝāĻĻāĻŋ āĻŸā§āϝāĻžāϗ⧇āϰ āĻŽāĻžāύ āϏāĻ‚āĻļā§āϞāĻŋāĻˇā§āϟ āϏ⧂āϚāĻ• +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;

āφāĻŽāϰāĻž āϏ⧁āχāĻ‚ 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 āĻ•ā§āϞāĻžāϏ āĻĨ⧇āϕ⧇ āωāĻĻā§āĻ­ā§‚āϤāĨ¤ āϤāĻžāϰāĻĒāϰ⧇ āφāĻŽāϰāĻž āĻĄā§āϰāĻ—ā§āϰāĻŋāĻĄ āĻĒāĻĻā§āϧāϤāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻŋ, āϤāĻžāϰāĻĒāϰ⧇ āĻĄā§āϰāĻ¸ā§āϟāĻžāĻ°ā§āϟ āĻŽā§‡āϏ⧇āϜ āĻĒāĻĻā§āϧāϤāĻŋ āĻ…āύ⧁āϏāϰāĻŖ āĻ•āϰ⧇ āĻāĻ•āϟāĻŋ āĻŦāĻžāĻ°ā§āϤāĻž āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ āĻ•āϰāĻŋ āϝāĻž āφāĻŽāĻžāĻĻ⧇āϰ āϗ⧇āĻŽ āĻļ⧁āϰ⧁ āĻ•āϰāϤ⧇ āĻ•ā§āϞāĻŋāĻ• āĻ•āϰāϤ⧇ āĻ…āύ⧁āϰ⧋āϧ āĻ•āϰ⧇:

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 āĻ•ā§āϞāĻžāϏ⧇āϰ āĻ•āύāĻ¸ā§āĻŸā§āϰāĻžāĻ•ā§āϟāϰ⧇ āϕ⧋āĻĄāϟāĻŋ āϰāĻžāĻ–āĻŋāĨ¤ āĻāϕ⧇āĻŦāĻžāϰ⧇ āĻļ⧇āώ⧇, āφāĻŽāϰāĻž āĻāĻ•āϟāĻŋ āύāϤ⧁āύ āϗ⧇āĻŽ āĻļ⧁āϰ⧁ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ 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