Класичний 15 головоломки
Ця добре відома головоломка стала популярною в Америці протягом 70-х років. Мета головоломки полягає в тому, щоб організувати плитки на дошці, ковзаючи їх по горизонталі та вертикалі. Головоломка починається з тієї позиції, в якій плитки були розбиті.
йпоширеніша версія головоломки показує цифри 1-15 на плитки. Тим не менш, ви можете зробити головоломку трохи складніше, зробивши плитку частиною зображення. Перш ніж розпочати, спробуй вирішити головоломку. Натисніть на плитку, прилеглої до порожнього квадрата, щоб ковзати плитку до порожньої позиції.
Налаштування проекту
Перше, що потрібно зробити, це створити новий проект та швидко очистити. Якщо ви вже знаєте, як це зробити, чудово. Якщо ви цього не зробите, перейдіть до підручника war battles tutorial і прочитайте розділи «Налаштування проекту», виконавши «Очищення проекту».
Також є огляд редактора, який допоможе вам знайти шлях, якщо ви абсолютно нове для Defold.
Після очищення проекту відкрийте файл налаштувань game.project та встановіть розміри гри до 512 × 512. Ці розміри співпадають з зображенням, яке ви збираєтеся використовувати
.
Наступним кроком буде завантажити відповідне зображення для головоломки. Виберіть будь-яке квадратне зображення, але переконайтеся, що його масштаб дорівнює 512 на 512 пікселів. Якщо ви не хочете виходити та шукати зображення, ось одне:
Завантажте зображення, перетягніть його в папку main вашого проекту.
Представлення сітки
Defold містить вбудований компонент Tilemap, який ідеально підходить для візуалізації пазлу. Tilemaps дозволяють встановлювати та читати окремі плитки, що є усе, що вам потрібно для цього проекту.
Але перед тим, як створити tilemap, вам знадобиться Tilesource, який буде витягувати зображення з плитки.
Клацніть правою кнопкою миші папку main та виберіть New ▸ Tile Source Назвіть новий файл "monalisa.tilesource".
Встановіть властивості ширини та висоти плитки до 128. Це дозволить розділити зображення 512х512 пікселів на 16 плиток. Плитки буде пронумеровано 1-16, коли ви розмістите їх на карті.
Але перед тим, як створити tilemap, вам знадобиться Tilesource, який буде витягувати зображення з плитки.
Клацніть правою кнопкою миші папку main та виберіть New ▸ Tile Source Назвіть новий файл "monalisa.tilesource".
Встановіть властивості ширини та висоти плитки до 128. Це дозволить розділити зображення 512х512 пікселів на 16 плиток. Плитки буде пронумеровано 1-16, коли ви розмістите їх на карті.
Потім клацніть правою кнопкою миші папку main та виберіть New ▸ Tile Map. Назвіть новий файл "grid.tilemap".
Defold потребує ініціалізації сітки. Для цього виділіть шар "layer1" і намалюйте 4х4 сітці плитки лише у верхній правій долі перехрестя оординат. Це не має значення, що ви встановили плитки.
Ви напишете код трохи, який буде автоматично встановлювати вміст цих фрагментів.
Defold потребує ініціалізації сітки. Для цього виділіть шар "layer1" і намалюйте 4х4 сітці плитки лише у верхній правій долі перехрестя оординат. Це не має значення, що ви встановили плитки.
Ви напишете код трохи, який буде автоматично встановлювати вміст цих фрагментів.
* Вмбиати kнопkою пробіл
Укладання частин разом
Відкрити main.collection. Клацніть правою кнопкою миші корневий вузол у Outline та виберіть «Add Game Object». Встановіть властивість Id нового об'єкту гри на "game".
Клацніть правою кнопкою миші об'єкт гри та виберіть Add Component File Виберіть файл grid.tilemap.
Клацніть правою кнопкою миші об'єкт гри та виберітьAdd Component ▸ Label.
Встановіть ідентифікацію мітки на "done" та властивість text для "Well done". Перемістіть мітку в центрі панелі.
Встановіть позицію Z мітки на 1, щоб переконатися, що вона намальована на вершині сітки.
Клацніть правою кнопкою миші об'єкт гри та виберіть Add Component File Виберіть файл grid.tilemap.
Клацніть правою кнопкою миші об'єкт гри та виберітьAdd Component ▸ Label.
Встановіть ідентифікацію мітки на "done" та властивість text для "Well done". Перемістіть мітку в центрі панелі.
Встановіть позицію Z мітки на 1, щоб переконатися, що вона намальована на вершині сітки.
Потім створіть сkрипт Lua для логіки головоломки: клацніть правою кнопкою миші папку main та виберіть New ▸ Script. Назвіть новий файл "game.script".
Потім клацніть правою кнопкою миші на ігровій об'єкт під назвою "game" в main.collection і виберіть Add Component File. Виберіть файл game.script.
Запустіть гру. F5
Ви повинні побачити сітку, як ви її намалювали, і мітку з повідомленням "Well done" зверху.
Потім клацніть правою кнопкою миші на ігровій об'єкт під назвою "game" в main.collection і виберіть Add Component File. Виберіть файл game.script.
Запустіть гру. F5
Ви повинні побачити сітку, як ви її намалювали, і мітку з повідомленням "Well done" зверху.
Логіка головоломки
Тепер у вас є всі частини на місці, тому решту підручників буде витрачено скласти логіку головоломки.
Сkрипт буде мати власне уявлення про плитки дошки, відокремлену від плитки. Це тому, що це полегшує роботу. Замість того, щоб зберігати плитки в 2-х мірному масиві,
плитки зберігаються у вигляді одного розмірного списку в таблиці Lua. У списку міститься номер плитки послідовно, починаючи з верхнього лівого кута сітці до самого правого нижнього краю:
Сkрипт буде мати власне уявлення про плитки дошки, відокремлену від плитки. Це тому, що це полегшує роботу. Замість того, щоб зберігати плитки в 2-х мірному масиві,
плитки зберігаються у вигляді одного розмірного списку в таблиці Lua. У списку міститься номер плитки послідовно, починаючи з верхнього лівого кута сітці до самого правого нижнього краю:
-- The completed board looks like this:
self.board = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}
Код, який приймає такий список плиток і малює його на нашій плиті, досить простий, але для цього потрібно перевести позицію в список до положення x та y:
-- Draw a table list of tiles onto a 4x4 tilemap
local function draw(t)
for i=1, #t do
local y = 5 - math.ceil(i/4)
local x = i - (math.ceil(i/4) - 1) * 4
tilemap.set_tile("#tilemap","layer1",x,y,t[i])
end
end
- У табличках плитка з x-значенням 1 і y-значенням 1 розташована внизу ліворуч. Тому позиція y повинна бути перевернута.
- Ви можете перевірити, що функція працює, як передбачено, шляхом створення тесту init ():
function init(self)
-- An inverted board, for test
self.board = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
draw(self.board)
end
З плитки в списку таблиць Lua , скремблювання замовлення дуже просто. Код просто проходить через кожен елемент у списку і перемикає кожну плитку іншою випадково обраною плиткою:
-- Swap two items in a table list
local function swap(t, i, j)
local tmp = t[i]
t[i] = t[j]
t[j] = tmp
return t
end
-- Randomize the order of a the elements in a table list
local function scramble(t)
for i=1, #t do
t = swap(t, i, math.random(#t))
end
return t
end
Перш ніж перейти до наступної версії, існує ще одна річ про 15 загадок, які вам дійсно потрібно враховувати: якщо ви рандомізуєте порядок плитки, як ви робите вище, існує 50% шансів, що вирішити цю загадку неможливо.
Це погана новина, бо ви напевно не бажаєте представляти гравця головоломкою, яку неможливо вирішити.
На щастя,
можна з'ясувати, чи є розв'язувана установка чи ні. Ось як це зробити:
Це погана новина, бо ви напевно не бажаєте представляти гравця головоломкою, яку неможливо вирішити.
На щастя,
можна з'ясувати, чи є розв'язувана установка чи ні. Ось як це зробити:
Розв'язність
Для того, щоб з'ясувати, якщо становище в 4⨉4 головоломки можна вирішити, необхідні дві частини інформації:
- Кількість "інверсій" у налаштуваннях. Інверсія відбувається тоді, коли плитка передує іншою плитці з меншою кількістю на ній. Наприклад, враховуючи список {1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 11, 10, 13, 14, 15, 0}, він має 3 інверсії:
номер 12 має 11 і 10 після нього, даючи 2 інверсії.
номер 11 має 10 наступних, даючи ще 1 інверсію.
(Зверніть увагу, що вирішена головоломка має нульову інверсію)
Рядок, де порожній квадрат (позначається 0 в списку).
Ці два числа можна обчислити за допомогою наступних функцій:
-- Count the number of inversions in a list of tiles
local function inversions(t)
local inv = 0
for i=1, #t do
for j=i+1, #t do
if t[i] > t[j] and t[j] ~= 0 then
inv = inv + 1
end
end
end
return inv
end
- Зауважте, що порожній квадрат не враховує.
-- Find the x and y position of a given tile
local function find(t, tile)
for i=1, #t do
if t[i] == tile then
local y = 5 - math.ceil(i/4)
local x = i - (math.ceil(i/4) - 1) * 4
return x,y
end
end
end
- Y позиція знизу.
- Тепер, враховуючи ці два числа, можна визначити, чи готові рішення головоломки чи ні. Стан плату 4х4 можна вирішувати, якщо:
- Якщо порожній квадрат знаходиться на непарному рядку (1 або 3 підрахунку знизу), а кількість інверсій рівна.
- Якщо порожній квадрат знаходиться на рівному рядку (2 або 4 відліку знизу), а кількість інверсій непарна.
Як це працює?
Кожен легальний рух переміщує шматочок, перемикаючи його місце з порожнім квадратом, як по горизонталі, так і по вертикалі.
Переміщення шматка горизонтально не змінює кількість інверсій, а також не змінює номер рядка, де ви знайдете квадрат empy.
Переміщення шматка вертикально, однак, змінює співвідношення кількості інверсій (від непарних до рівних
або навіть з непарних). Це також змінює співвідношення порожнього квадратного рядка.
Переміщення шматка горизонтально не змінює кількість інверсій, а також не змінює номер рядка, де ви знайдете квадрат empy.
Переміщення шматка вертикально, однак, змінює співвідношення кількості інверсій (від непарних до рівних
або навіть з непарних). Це також змінює співвідношення порожнього квадратного рядка.
Наприклад:
Цей крок змінює порядок плитки з:
{ ... 0, 11, 2, 13, 6 ... }
до
{ ... 6, 11, 2, 13, 0 ... }
Нова держава додає 3 інверсії наступним чином:
Номер 6 додає 1 інверсію (номер 2 тепер після 6)
Номер 11 втрачає 1 інверсію (номер 6 тепер передує 11)
Число 13 втрачає 1 інверсію (номер 6 тепер до 13)
Номер 6 додає 1 інверсію (номер 2 тепер після 6)
Номер 11 втрачає 1 інверсію (номер 6 тепер передує 11)
Число 13 втрачає 1 інверсію (номер 6 тепер до 13)
Можливі способи зміни кількості інверсій вертикальним слайдом на ± 1 або ± 3.
Можливі способи змінення порожнього квадратного рядка вертикальним слайдом на ± 1.
У кінцевому стані головоломки порожній квадрат знаходиться у нижньому правому куті (див. 1), а кількість інверсій - рівне значення 0.
Кожен правовий рух або залишить ці два значення незайманими (горизонтальний рух) або перемикає їх полярність (вертикальний хід). Жоден правовий крок не може зробити полярність інверсій і порожнє квадратне рядок непарним, непарним чи навіть рівним.
Отже, неможливо вирішити будь-яку головоломку, в якій два числа є непарними або обидва навіть.
Ось код, який перевіряє розв'язність:
Можливі способи змінення порожнього квадратного рядка вертикальним слайдом на ± 1.
У кінцевому стані головоломки порожній квадрат знаходиться у нижньому правому куті (див. 1), а кількість інверсій - рівне значення 0.
Кожен правовий рух або залишить ці два значення незайманими (горизонтальний рух) або перемикає їх полярність (вертикальний хід). Жоден правовий крок не може зробити полярність інверсій і порожнє квадратне рядок непарним, непарним чи навіть рівним.
Отже, неможливо вирішити будь-яку головоломку, в якій два числа є непарними або обидва навіть.
Ось код, який перевіряє розв'язність:
-- Is the given table list of 4x4 tiles solvable?
local function solvable(t)
local x,y = find(t, 0)
if y % 2 == 1 and inversions(t) % 2 == 0 then
return true
end
if y % 2 == 0 and inversions(t) % 2 == 1 then
return true
end
return false
end
Введення користувача
Єдине, що залишилося робити зараз, полягає в тому, щоб робити головоломку інтерактивною.
Створіть функцію init (), яка виконує всю установку часу виконання, використовуючи функції, створені вище
Створіть функцію init (), яка виконує всю установку часу виконання, використовуючи функції, створені вище
:
function init(self)
msg.post(".", "acquire_input_focus")
math.randomseed(socket.gettime())
self.board = scramble({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0})
while not solvable(self.board) do
self.board = scramble(self.board)
end
draw(self.board)
self.done = false
msg.post("#done", "disable")
end
- Скажіть двигуну, що цей об'єкт гри повинен отримати вхідний сигнал.
- Насіння рандомізувача.
- Створіть початковий випадковий стан для дошки.
- Якщо держава є нерозв'язною, знову змагайтеся.
- Намалюйте дошку.
- Встановіть прапор завершення для відстеження виграшного стану.
- Вимкнути мітку повідомлення про завершення роботи.
- Відкрийте /input/game.input_bindings та додайте новий Trigger Mouse.
- Встановіть назву дії, щоб "press":
Поверніться до сценарію та створіть функцію on_input ().
-- Deal with user input
function on_input(self, action_id, action)
if action_id == hash("press") and action.pressed and not self.done then
local x = math.ceil(action.x / 128)
local y = math.ceil(action.y / 128)
local ex, ey = find(self.board, 0)
if math.abs(x - ex) + math.abs(y - ey) == 1 then
self.board = swap(self.board, (4-ey)*4+ex, (4-y)*4+x)
draw(self.board)
end
ex, ey = find(self.board, 0)
if inversions(self.board) == 0 and ex == 4 then
self.done = true
msg.post("#done", "enable")
end
end
end
- Якщо натискати кнопку миші, і гра все ще працює, виконайте наступне.
- Обчислити квадратику x та y, які користувач натиснув.
- Знайдіть поточне розташування порожнього (0) квадрата.
- Якщо натиснутий квадрат знаходиться на квадраті праворуч над, нижче, вліво або вправо від порожнього, виконайте такі дії:
- Переключіть плитки на клітинний квадрат і порожній.
- Перетворити оновлену плату.
- Якщо число інверсій на дошці 0, що означає, що все в правильному порядку, а порожній квадрат знаходиться в крайньому правому стовпці (він повинен бути на останній рядок для інверсії бути 0), то загадка вирішена так виконайте наступне:
- Встановіть прапорець завершення.
- Увімкнути / показати повідомлення про завершення.
- І це все! Ви закінчите, головоломка закінчена!
Повний скрипт
Ось повний код сценарію для довідки:
local function inversions(t)
local inv = 0
for i=1, #t do
for j=i+1, #t do
if t[i] > t[j] and t[j] ~= 0 then
inv = inv + 1
end
end
end
return inv
end
local function find(t, tile)
for i=1, #t do
if t[i] == tile then
local y = 5 - math.ceil(i/4)
local x = i - (math.ceil(i/4) - 1) * 4
return x,y
end
end
end
local function solvable(t)
local x,y = find(t, 0)
if y % 2 == 1 and inversions(t) % 2 == 0 then
return true
end
if y % 2 == 0 and inversions(t) % 2 == 1 then
return true
end
return false
end
local function scramble(t)
for i=1, #t do
local tmp = t[i]
local r = math.random(#t)
t[i] = t[r]
t[r] = tmp
end
return t
end
local function swap(t, i, j)
local tmp = t[i]
t[i] = t[j]
t[j] = tmp
return t
end
local function draw(t)
for i=1, #t do
local y = 5 - math.ceil(i/4)
local x = i - (math.ceil(i/4) - 1) * 4
tilemap.set_tile("#tilemap","layer1",x,y,t[i])
end
end
function init(self)
msg.post(".", "acquire_input_focus")
math.randomseed(socket.gettime())
self.board = scramble({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0})
while not solvable(self.board) do
self.board = scramble(self.board)
end
draw(self.board)
self.done = false
msg.post("#done", "disable")
end
function on_input(self, action_id, action)
if action_id == hash("press") and action.pressed and not self.done then
local x = math.ceil(action.x / 128)
local y = math.ceil(action.y / 128)
local ex, ey = find(self.board, 0)
if math.abs(x - ex) + math.abs(y - ey) == 1 then
self.board = swap(self.board, (4-ey)*4+ex, (4-y)*4+x)
draw(self.board)
end
ex, ey = find(self.board, 0)
if inversions(self.board) == 0 and ex == 4 then
self.done = true
msg.post("#done", "enable")
end
end
end
function on_reload(self)
self.done = false
msg.post("#done", "disable")
end
Подальші вправи
- Зробіть 5-55 головоломки, а потім - 6 × 5. Переконайтеся, що перевірки дозволеності працюють в цілому.
- Додайте розсувні анімації. Плитки не можуть бути переміщені окремо від плитки, тому вам доведеться вирішувати це. Можливо, окрема плитка, яка містить лише ковзання?
Немає коментарів:
Дописати коментар
Kоментарі неуkраїнсьkою видалятимуться