This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Bu tanınmış bulmaca, 1870’lerde Amerika’da popüler oldu. Bulmacanın amacı, tahtadaki karoları yatay ve dikey olarak kaydırarak sıralamaktır. Bulmaca, karoların karıştırıldığı bir düzenden başlar.
Bulmacanın en yaygın sürümünde karoların üzerinde 1–15 sayıları bulunur. Ancak karoları bir görüntünün parçaları hâline getirerek bulmacayı biraz daha zorlaştırabilirsiniz. Başlamadan önce bulmacayı çözmeyi deneyin. Boş kareye komşu bir karoyu tıklayarak boş konuma kaydırın.
game.project ayarlar dosyasını açın ve oyunun boyutlarını 512⨉512 olarak ayarlayın. Bu boyutlar, kullanacağınız görüntüyle eşleşecektir.

Sonraki adım, bulmacaya uygun bir görüntü indirmektir. Kare biçiminde herhangi bir görüntü seçin, ancak 512’ye 512 piksel olacak şekilde ölçeklendirdiğinizden emin olun. Görüntü aramakla uğraşmak istemiyorsanız şunu kullanabilirsiniz:

Görüntüyü indirin, ardından projenizin main klasörüne sürükleyin.
Defold, bulmaca tahtasını görselleştirmek için ideal olan yerleşik bir karo haritası (Tilemap) bileşeni (component) içerir. Karo haritaları, tek tek karoları ayarlamanıza ve okumanıza olanak tanır; bu proje için gereken de budur.
Ancak karo haritasını oluşturmadan önce, karo haritasının karo görüntülerini alacağı bir karo kaynağına (Tilesource) ihtiyacınız vardır.
main klasörüne sağ tıklayın ve New ▸ Tile Source seçeneğini seçin. Yeni dosyaya monalisa.tilesource adını verin.
Karonun Width ve Height özelliklerini 128 olarak ayarlayın. Bu işlem, 512⨉512 piksellik görüntüyü 16 karoya böler. Karoları karo haritasına yerleştirdiğinizde 1–16 olarak numaralandırılırlar.

Ardından main klasörüne sağ tıklayın ve New ▸ Tile Map seçeneğini seçin. Yeni dosyaya “grid.tilemap” adını verin.
Defold, ızgaranın başlangıç durumunu hazırlamanızı gerektirir. Bunun için “layer1” katmanını seçin ve başlangıç noktasının hemen sağ üstüne 4⨉4 boyutunda bir karo ızgarası boyayın. Karolar için hangi değerleri ayarladığınız pek önemli değildir. Birazdan bu karoların içeriğini otomatik olarak ayarlayan kodu yazacaksınız.

main.collection koleksiyon (collection) dosyasını açın. Outline görünümündeki kök düğüme sağ tıklayın ve Add Game Object seçeneğini seçin. Yeni oyun nesnesinin (game object) Id özelliğini “game” olarak ayarlayın.
Oyun nesnesine sağ tıklayın ve Add Component File seçeneğini seçin. grid.tilemap dosyasını seçin. Id özelliğini “tilemap” olarak ayarlayın.
Oyun nesnesine sağ tıklayın ve Add Component ▸ Label seçeneğini seçin. Etiketin (label) Id özelliğini “done”, Text özelliğini ise “Well done” olarak ayarlayın. Etiketi karo haritasının merkezine taşıyın.
Etiketin ızgaranın üzerine çizildiğinden emin olmak için Z konumunu 1 olarak ayarlayın.

Ardından bulmaca mantığı için bir Lua betik (script) dosyası oluşturun: main klasörüne sağ tıklayın ve New ▸ Script seçeneğini seçin. Yeni dosyaya “game.script” adını verin.
Sonra main.collection içindeki “game” adlı oyun nesnesine sağ tıklayın ve Add Component File seçeneğini seçin. game.script dosyasını seçin.
Oyunu çalıştırın. Izgarayı çizdiğiniz hâliyle ve üzerinde “Well done” iletisini gösteren etiketi görmelisiniz.
Artık bütün parçalar yerli yerinde; öğreticinin geri kalanında bulmaca mantığını oluşturacağız.
Betik, tahtadaki karoların karo haritasından ayrı kendi temsilini tutacaktır. Bunun nedeni, üzerinde işlem yapmayı kolaylaştırabilmemizdir. Karoları 2 boyutlu bir dizide saklamak yerine, bir Lua tablosunda tek boyutlu bir liste olarak saklayacağız. Liste, ızgaranın sol üst köşesinden başlayıp sağ alt köşesine kadar sırayla karo numaralarını içerir:
-- The completed board looks like this:
self.board = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}
Böyle bir karo listesini alıp karo haritamıza çizen kod oldukça basittir, ancak listedeki konumu x ve y konumuna dönüştürmesi gerekir:
-- 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) -- <1>
local x = i - (math.ceil(i/4) - 1) * 4
tilemap.set_tile("#tilemap","layer1",x,y,t[i])
end
end
Bir test init() işlevi oluşturarak işlevin amaçlandığı gibi çalışıp çalışmadığını denetleyebilirsiniz:
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
Karolar bir Lua tablosunda liste olarak tutulduğunda sıralarını karıştırmak çok kolaydır. Kod yalnızca listedeki her öğeyi dolaşır ve her karonun yerini rastgele seçilen başka bir karoyla değiştirir:
-- 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)
local n = #t
for i = 1, n - 1 do
t = swap(t, i, math.random(i, n))
end
return t
end
Devam etmeden önce 15 bulmacası hakkında dikkate almanız gereken bir nokta var: karo sırasını yukarıdaki gibi rastgele belirlerseniz bulmacanın çözülmesinin imkânsız olma olasılığı %50’dir.
Bu kötü bir haberdir, çünkü oyuncuya çözülemeyen bir bulmaca sunmayı kesinlikle istemezsiniz.
Neyse ki bir düzenin çözülebilir olup olmadığını belirlemek mümkündür. Bunun nasıl yapıldığına bakalım:
4⨉4 boyutundaki bir bulmacada bir düzenin çözülebilir olup olmadığını belirlemek için iki bilgi gerekir:
Düzendeki “terslik” (inversion) sayısı. Bir karonun, kendisinden daha küçük numaralı başka bir karodan önce gelmesine terslik denir. Örneğin {1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 11, 10, 13, 14, 15, 0} listesinde 3 terslik vardır:
(Çözülmüş bulmaca durumunda terslik sayısının sıfır olduğuna dikkat edin)
Boş karenin bulunduğu satır (listede 0 ile gösterilir).
Bu iki sayı, aşağıdaki işlevlerle hesaplanabilir:
-- 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 -- <1>
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) -- <1>
local x = i - (math.ceil(i/4) - 1) * 4
return x,y
end
end
end
Bu iki sayıyla artık bir bulmaca durumunun çözülebilir olup olmadığını söylemek mümkündür. 4⨉4 boyutundaki bir tahta durumu şu koşullarda çözülebilirdir:
Kurallara uygun her hamle, bir parçayı boş kareyle yatay veya dikey olarak yer değiştirerek hareket ettirir.
Bir parçayı yatay olarak hareket ettirmek, ne terslik sayısını ne de boş karenin bulunduğu satırın numarasını değiştirir.
Ancak bir parçayı dikey olarak hareket ettirmek, terslik sayısının tek veya çift olma durumunu değiştirir (tekten çifte veya çiftten teke). Aynı şekilde boş karenin bulunduğu satırın numarasının tek veya çift olma durumunu da değiştirir.
Örneğin:

Bu hamleyle karoların sırası şu durumdan:
{ ... 0, 11, 2, 13, 6 ... }
şu duruma değişir:
{ ... 6, 11, 2, 13, 0 ... }
Toplam terslik sayısı aşağıdaki şekilde 1 azalır:
Dikey bir kaydırmada terslik sayısı ±1 veya ±3 değişebilir.
Dikey bir kaydırmada boş karenin satır numarası ±1 değişebilir.
Bulmacanın son durumunda boş kare sağ alt köşededir (tek numaralı 1. satır) ve terslik sayısı çift bir değer olan 0’dır. Kurallara uygun her hamle, bu iki değeri ya olduğu gibi bırakır (yatay hamle) ya da tek veya çift olma durumlarını değiştirir (dikey hamle). Kurallara uygun hiçbir hamle, terslik sayısını ve boş karenin satır numarasını tek, tek veya çift, çift hâline getiremez.
Dolayısıyla bu iki sayının ikisinin de tek veya ikisinin de çift olduğu bir bulmaca durumunu çözmek imkânsızdır.
Çözülebilirliği denetleyen kod şöyledir:
-- 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
Şimdi geriye yalnızca bulmacayı etkileşimli hâle getirmek kaldı.
Yukarıda oluşturulan işlevleri kullanarak çalışma sırasındaki tüm hazırlıkları yapan bir init() işlevi oluşturun:
function init(self)
msg.post(".", "acquire_input_focus") -- <1>
math.randomseed(socket.gettime()) -- <2>
self.board = scramble({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}) -- <3>
while not solvable(self.board) do -- <4>
self.board = scramble(self.board)
end
draw(self.board) -- <5>
self.done = false -- <6>
msg.post("#done", "disable") -- <7>
end
/input/game.input_bindings dosyasını açın ve yeni bir Mouse Trigger ekleyin. Eylemin adını “press” olarak ayarlayın:

Betiğe dönün ve bir on_input() işlevi oluşturun.
-- Deal with user input
function on_input(self, action_id, action)
if action_id == hash("press") and action.pressed and not self.done then -- <1>
local x = math.ceil(action.x / 128) -- <2>
local y = math.ceil(action.y / 128)
local ex, ey = find(self.board, 0) -- <3>
if math.abs(x - ex) + math.abs(y - ey) == 1 then -- <4>
self.board = swap(self.board, (4-ey)*4+ex, (4-y)*4+x) -- <5>
draw(self.board) -- <6>
end
ex, ey = find(self.board, 0)
if inversions(self.board) == 0 and ex == 4 then -- <7>
self.done = true
msg.post("#done", "enable")
end
end
end
İşte bu kadar! İşiniz bitti, bulmaca oyunu tamamlandı!
Başvuru için betik kodunun tamamı aşağıdadır:
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