Test Platformu - Bölüm 3

Yeni bir sayıda tekrar merhaba sevgili Plazma okurları. Plazma'nın ilk sayısından beri süregelen "Test Platformu" bu sayıda artık daha da detaylı bir biçimde devam ediyor. Önceki sayıları okumamış başlangıç seviyesindeki programcılar bu sayıda zorlanabilirler. Her sayı birbirinin devamı niteliğinde olduğu için bu bölümü okumadan önce bölüm 1 ve bölüm 2'yi okumanızı tavsiye ediyorum.

Bu Bölümde Neler Anlatılacak?

• Bu yazı dizisinin misyonu Commodore 64 için PC destekli yazılım geliştirmeyle ilgili yöntemler sunmak olduğu için bu sayıda öncelikle JavaScript'ten bir "VIC Simülatörü" nasıl hazırlanır onu göreceğiz. Bu VIC Simülatörü, Commodore 64'ün ekran çipi olan VIC'in hafızadaki byte dizilişini ekrana nasıl yansıttığıyla ilgili bir simülasyon yapacak. Bu sayede bir efekti Commodore 64'te programlamaya başlamadan JavaScript'ten çeşitli testler yapabileceğiz.

• VIC Simülatörünü kullanarak bir nokta basma rutini hazırlayacağız. Bu rutin ekranda verilen x ve y koordinatına nokta koyacak.

• Aynı örnek içerisinde basit bir sinüs, kosinüs hesabıyla ekrana tam sınırlarına denk gelecek şekilde bir elips çizdireceğiz.

• Daha sonra Commodore 64 üzerinde grafik ekranı açıp ekrana nokta basma işleminin nasıl yapıldığını kod örnekleriyle göreceğiz.

• Sonraki adımda VIC Simülatöründe denediğimiz elips çizdirme rutininin nasıl Commodore 64'e aktarılabileceğini inceleyeceğiz.

• En son olarak da bu son yaptığımız işlemin Commodore 64'e aktarma işleminin eksik, zayıf yönlerini ve alternatiflerini tartışacağız. Ancak diğer yöntemleri sonraki bölümlere saklayacağız.

VIC Simülatörü

Çok uzun bir zamandır VIC simülatörü türü bir şeyi JavaScript'ten yapmak istiyordum. Aslında C++ ve .NET'ten hazırladığım benzer birçok program olmasına rağmen, JavaScript'in scripting dili olması ve dolayısıyla derlenmeden çalışabilmesi, gittiğim her yerde herkesin PC'sinde kolaylıkla çalışabiliyor olmamı sağlaması gibi avantajlarından dolayı JavaScript'te de benzer bir şeyler yapmayı hedeflemiştim. Ancak JavaScript'ten VIC simülatörü yapmak o kadar da kolay değil. Gelin ne olduğunu ve nasıl yapıldığını inceleyelim.

VIC

simülasyonu tam olarak nedir?

Commodore 64'ün tek renk grafik modu 320x200 çözünürlükten, yani toplam 64000 pikselden oluşur. Ancak PC'de 13h (320x200 256 renk) gibi modlarda çalışmaya alışmış insanlara Commodore'daki hafıza yapısı başlangıçta epey bir ters gelecektir. Çünkü 13h modunda her bir piksel bir byte'a denk gelirken ve byte değerleri renkleri simgelerken, Commodore'da 1 byte'ın her biti 1 pikseli sembolize edecek şekilde 1 byte = 8 piksel'e denk gelir. Örneğin 197 byte değerini ele alalım:

197 = %11000101

Yani bunun ekrandaki görüntüsü X'ler dolu o'lar boş pikselleri sembolize edecek olursa;

XXoooXoX

şeklinde olacaktır. Ekrana örnek olarak ard arda bir dolu bir boş piksel çizmek istediğimiz durumda ise tersten bir hesap yaparak byte değerine ulaşabiliriz.

XoXoXoXo -> %10101010 = 170

Bir diğer konu byte'ların nasıl dizileceği konusu. Yani her 8 piksellik alan hangi sırayla ekrana basılıyor. Bu da Commodore'da programlama ile yeni tanışan insanların genelde sıkıntıya düştüğü bir başka konu.

Dizilim öncelikle aşağıya doğru başlar. Yani 8 pikseli temsil eden bir byte, diğerinin altına yerleşir. Ancak bu tüm ekran için değil yalnızca 8x8 piksellik bir alan için geçerlidir.

Commodore'un 320x200 çözünürlüğündeki ekranı aşağıdaki resimde de göründüğü gibi 8x8 piksellik karakter alanları halinde 40x25'lik 1000 adet parçaya bölünmüştür.

Parçalardan her biri ise 8x8 pikselden oluşmaktadır. İşte bu her 8x8'lik alan hafızada peş peşe gelen 8 byte ile ifade edilir. Örnek olarak ekrana yazılan bir A harfi şu şekilde bytelara çevrilebilir.

Görüldüğü üzere 8x8'lik bir alanı bu şekilde peş peşe 8 byte ile ifade edebiliyoruz. Peki hafızada bundan sonra yazılacak 9. byte ekranda nereye denk gelecek? Bu karakterin bitişiğindeki karakter alanının en üst 8 pikseline denk gelecek. Yani biz peş peşe iki kez şekildeki sayıları yazacak olursak;

24, 60, 102, 126, 102, 102, 0, 24, 60, 102, 126, 102, 102, 0

Bu 16 byte bize yan yana iki A harfi gösterecektir. Bu durumun tek bir istisnası olabilir. O da bu byteların satır sonuna, yani 40. karaktere denk gelmeleri durumudur. Bu durumda ise bir alttaki karakter satırından devam eder byte dizilimi. Eğer byteların 320x200'lük alandaki koordinatlarını ifade edecek olursak aşağıdaki gibi bir sonuçla karşılaşırız. Buradaki parantez içindeki değerler sırasıyla piksel cinsinden x ve y değerleridir. Unutulmaması gereken şey, her bir byte'ın verilen x koordinatı değerinden itibaren 8 pikseli kapsadığıdır.

byte:0 -> (0,0)

byte:1 -> (0,1)

byte:2 -> (0,2)

byte:3 -> (0,3)

byte:4 -> (0,4)

byte:5 -> (0,5)

byte:6 -> (0,6)

byte:7 -> (0,7)

byte:8 -> (8,0)

byte:9 -> (8,1)

byte:10 -> (8,2)

byte:11 -> (8,3)

byte:12 -> (8,4)

byte:13 -> (8,5)

byte:14 -> (8,6)

byte:15 -> (8,7)

byte:16 -> (16,0)

byte:17 -> (16,1)

byte:18 -> (16,2)

byte:19 -> (16,3)

byte:20 -> (16,4)

byte:21 -> (16,5)

byte:22 -> (16,6)

byte:23 -> (16,7)

byte:312 -> (312,0)

byte:313 -> (312,1)

byte:314 -> (312,2)

byte:315 -> (312,3)

byte:316 -> (312,4)

byte:317 -> (312,5)

byte:318 -> (312,6)

byte:319 -> (312,7)

byte:320 -> (0,8)

byte:321 -> (0,9)

byte:322 -> (0,10)

byte:323 -> (0,11)

byte:324 -> (0,12)

byte:325 -> (0,13)

byte:326 -> (0,14)

byte:327 -> (0,15)

byte:328 -> (8,8)

byte:329 -> (8,9)

byte:330 -> (8,10)

byte:331 -> (8,11)

byte:332 -> (8,12)

byte:333 -> (8,13)

byte:334 -> (8,14)

byte:335 -> (8,15)

byte:63992 -> (312,192)

byte:63993 -> (312,193)

byte:63994 -> (312,194)

byte:63995 -> (312,195)

byte:63996 -> (312,196)

byte:63997 -> (312,197)

byte:63998 -> (312,198)

byte:63999 -> (312,199)

Eğer yukarıdaki tablo kafanızı karıştırdıysa size daha rahat anlaşılır bir model ile açıklayayım. Örneğin karşınızda raflar var ve her rafın üstünde yan yana duran bir sürü su kabı var. Siz aynı rafta duran su kaplarının hepsini hortumlarla birbirlerine bağlıyorsunuz. Kısacası biri dolduktan sonra fazla gelen suyu yanındakine aktarıyor. Her rafın en sağındaki kap bir alttaki rafın en solundaki kaba hortumla bağlı.

Siz en üst rafın en solunda duran kaba su doldurmaya başladıkça 8 birimlik kap dolduğunda yanındaki kaba, o da yanındakine aktarıyor.

En son bir raftaki tüm kaplar dolunca ise alt rafın en solundaki kaptan devam ediyor.

Buradaki her kap 8x8'lik bir karakter alanına denk geliyor ve 8 byte alabiliyor. Eğer her rafta toplam 40 kap olur ve 25 tane de rafımız olursa alın size Commodore'un ekranının hafıza dizilimi. Tek farkı bardaklar Commodore'da tersten doluyor. Yani yukarıdan aşağıya.

VIC simülatörü ise bu hafıza dizilimini PC'de simüle ederek bize hiç Commodore ya da Commodore emülasyonu kullanmadan test ortamı sağlayacak bir araç olacak.

"VIC Simülatörü"nü JavaScript'ten

yapmanın zor olan yanı ve çözümü nedir?

Aslında flash, java ve benzeri tarayıcı teknolojilerini kullanmaksızın basit bir yöntemle JavaScript'ten 320x200'lük alanın tamamını simüle etmek hemen hemen imkansızdır. Çünkü bu 64000 pixellik bir alan demektir ve HTML yapı itibariyle 64000 nesne ya da 64000 parçaya bölünmüş bir tabloyu kolay kolay kaldıramaz. Kaldırsa da çok ama çok yavaş olur. Peki ne yapmak lazım? Hadi bu yukarıdaki raf, bardak modelini ele alalım. Biz eğer her bir bardağı birer resim olarak kullanacak olursak bu yalnızca 40x25 = 1000 resim eder ki bu HTML için oldukça makul bir sayı. Peki kaç farklı resim oluşturmamız gerekir bu durumda? 2^64 = 18,446,744,073,709,551,616 adet resim oluşturmamız gerekir. Hmmm... O zaman bu olayı birazcık optimize etmemiz gerekecek. Tarayıcıya 1000 resim yerine 8000 resim yüklemek onu yaklaşık 8 kat yavaşlatacaktır. Peki bu resim sayısında yeterli bir fark yaratacak mı? Bu durumda her 1 byte 1 resme denk gelecek yani 2^8 = 256 adet resim oluşturmamız yeterli olacak. 8 kat hız kaybederek resim sayısını 72,057,594,037,927,936'de 1'e düşürdük. Fena sayılmaz.

Yukarıdaki paragraf elbette ki biraz espri içeriyor. Bu "niye her bir byte bir resme karşılık geliyor?" sorusuna yanıt olsun diye yazdığım, benim düşünce metodumu anlatan bir paragraf. Ancak sonuç olarak her 8 pixel, yani her 1 byte'ı 1 resime denk getirmemiz lazım. Peki bu resimleri nasıl oluşturacağız? 256 her ne kadar aşırı büyük bir sayı değilse de inanın oturup tek tek Photoshop'ta 256 resmi pixellemek istemezsiniz. Bunun için ben PHP kullandım. Çünkü PHP'nin grafik kütüphanesi olan GD2 çok basit bir kullanıma sahip. Sizler de PHP kurup, php.ini'nin içerisinden ";extension=php_gd2.dll" satırının başındaki noktalı virgülü kaldıracak olursanız aşağıdaki scripti çalıştırarak ihtiyacımız olan 256 resmi oluşturabilirsiniz.

Not: Php klasörünün içinde "extensions" isminde bir klasör ve altında "php_gd2.dll" olması gerekiyor. Eğer bu dll yoksa php.net sitesinden en son sürümün installer'ını değil, zipli paketini indirecek olursanız extension'lar da beraberinde gelecektir.

gifolustur.php

<?php ob_start();

@mkdir("i");

for($i = 0; $i < 256; $i++)

header("Content-type: image/gif");

$resimDosyasi = @imagecreate(8, 1);

$renk1 = imagecolorallocate($resimDosyasi, 0, 0, 0); // siyah

$renk2 = imagecolorallocate($resimDosyasi, 255, 255, 255); // beyaz for($bit = 0; $bit < 8; $bit++)

if($i & pow(2, 7 - $bit))

imagesetpixel($resimDosyasi, $bit, 0, $renk2);

else imagesetpixel($resimDosyasi, $bit, 0, $renk1);

imagegif($resimDosyasi, "./i/".$i.".gif");

imagedestroy($resimDosyasi);

ob_end_flush();

Bu PHP dosyasını çalıştırmak için bir web server kurulu olması şart değil. İsterseniz doğrudan konsol penceresinden de scripti tetikleyebilirsiniz. Örnek olarak "VicSimulator" klasörünün "D" sabit diskinde kök dizinde durduğunu varsayalım.

Bu şekilde bir çıktı almanız gerekir. Eğer "php" komutunu bulamaz ve hata verirse bu durumda php.exe'yi yer aldığı klasörle birlikte çift tırnak işareti içine alarak verirseniz sorun kalmayacaktır. Örnek:

"c:\php\php.exe" gifolustur.php

Ancak her zaman "gifolustur.php" dosyasının bulunduğu klasörde olmaya dikkat edin. Çünkü bu script çalıştırıldığı dizine "i" isimli bir klasör açıp içersine 0'dan 255'e kadar numerik olarak isimlendirilmiş gif dosyaları yaratır.

Peki neden daha açıklayıcı olan "images" yerine yalnızca "i" seçtik klasör ismi olarak? Çünkü birazdan oluşturacağımız HTML dosyasında resimler bu klasörden okunacak ve klasör ismi 8000 kez tekrar edeceği için oluşturulacak HTML'in toplam uzunluğunu azaltmak ve çıktıyı hızlandırmak için tek harf kullanmayı tercih ettik. Bu tür ufak ayrıntılar sonucu oldukça etkiler. Aslında en hızlısı resimleri doğrudan kök dizine koymak olacaktır ama düzen için bu kadar taviz verilebilir.

PHP'ye çok uzaksanız, bir türlü yukardaki scripti çalıştıramadıysanız, Windows'un security ayarları scripte engel oluyor ve nasıl düzelticeğinizi bilmiyorsanız ya da yalnızca PHP kurmaya/ kullanmaya üşeniyorsanız bu durumda derginin içersinde yer alan hazır gifleri kullanabilirsiniz.

VIC Simülatörü'nün JavaScript Kodları

Sırada iş VIC Simülatörü'nü yazmaya geldi. Kolaylıkla kullanılabilmesi ve kopyala&yapıştır işleriyle uğraştırmamak için kodları ayrı bir JS uzantılı dosyada tutacağız. JS uzantılı dosyalar kolaylıkla HTML'lere linklenebilir ve kullanılabilirler.

vicsim.js var VICHaritasi = new Array(8000);

function ekraniTemizle()

for(i = 0; i < VICHaritasi.length; i++)

VICHaritasi[i] = 0;

function ekraniCiz()

alert("VIC haritasının oluşturulması biraz zaman alabilir.

\nHarita oluşturulurken sabırlı olun.");

cikti = "<table width=320 height=200 border=0 cellspacing=0 cellpadding=0>";

for (y = 0; y < 25; y++)

cikti += "<tr>";

for (x = 0; x < 40; x++)

cikti += "<td>";

for (i = 0; i < 8; i++)

haritaIndeksi = y * 320 + x * 8 + i;

cikti += "<img src=i/" + (VICHaritasi[haritaIndeksi] & 255) + ".gif>";

if(i != 7)

cikti += "<br>";

cikti += "</td>";

cikti += "</tr>";

cikti += "</table>";

document.body.innerHTML = cikti;

ekraniTemizle();

İşte VIC simülatörü yapmak bu kadar kolay. Bu kodu açıklamaya ihtiyaç duymuyorum. Sonuç olarak ne yaptığını bilmeniz önemli. Kodun en başında global olarak tanımlanan "VICHaritasi" dizisi 8000 bytelık bir dizi. ekraniTemizle() fonksiyonu bu diziyi 0 değerleriyle dolduruyor ve ekraniCiz ise bu değerleri VIC'in çalışma prensibine uygun bir şekilde simüle ederek uygun gif parçaları ile bir tablo halinde ekrana basıyor. ekraniTemizle() fonksiyonu bu scriptin sonunda çağrılırken ekraniCiz() fonksiyonu bu scriptin linklendiği HTML dosyasından kullanılmak üzere hazırlandı.

Şimdi bu yukarıdaki scripti kullanan bir HTML örneği görelim.

vicsim.htm

<html>

<head>

<title>VIC Simülatörü</title>

<script src="vicsim.js"></script>

</head>

<body bgcolor="#cccccc"></body>

<script> function nokta(x,y)

adres = ((y >> 3) * 320) + (y & 7) + (x & 0x1f8);

byteDegeri = Math.pow(2, 7 - (x & 7));

VICHaritasi[adres] = VICHaritasi[adres] | byteDegeri;

function ekraniOlustur()

for(i = 0; i < 256; i++)

x = 160 + 160 * Math.cos(i / 128 * Math.PI);

y = 100 + 100 * Math.sin(i / 128 * Math.PI);

nokta(x,y);

ekraniOlustur();

ekraniCiz();

</script>

</html>

HTML'in çıktısı:

Öncelikle ekrana aşağıdaki uyarı mesajı gelir.

Daha sonra ekrana bir elips çizilir.

Bu örnekte ne yaptık? Standart HTML yapısını çıkaracak olursak iki bölge bizi ilgilendiriyor. Birincisi

<script src="vicsim.js"></script>

satırı. Bu satırda az önce oluşturduğumuz "vicsim.js" dosyasını HTML'e ekliyoruz. Diğer önemli bölge ise

<script> function nokta(x,y)

adres = ((y >> 3) * 320) + (y & 7) + (x & 0x1f8);

byteDegeri = Math.pow(2, 7 - (x & 7));

VICHaritasi[adres] = VICHaritasi[adres] | byteDegeri;

function ekraniOlustur()

for(i = 0; i < 256; i++)

x = 160 + 160 * Math.cos(i / 128 * Math.PI);

y = 100 + 100 * Math.sin(i / 128 * Math.PI);

nokta(x,y);

ekraniOlustur();

ekraniCiz();

</script>

Bu script taginin yer aldığı alan. Burada iki yeni fonksiyon tanımlıyoruz. Bunlardan birincisi verilen (x,y) koordinatına nokta basan bir rutin. Elbette ki bu rutin VICHaritasi dizisini kullanıyor. Yani aslında bu fonksiyonu çağırarak ekrana henüz bir şey çizmiyoruz. Bunun yerine noktayı VICHaritasi dizisindeki ilgili byte'ları değiştirerek koyuyoruz.

Diğer fonksiyonumuz ise ekraniOlustur() fonksiyonu. Bu fonksiyon ise ekrana çizilecek şeyin belirlendiği kısım. Bu örnekte ekrana tam sınırlarına denk gelecek bir elips çizdiriyoruz. Öncelikle sinüs, cosinüs fonksiyonları yardımıyla elipsin çevresini oluşturan (x,y) değerleri elde ediliyor, daha sonra az öncesinde yaratılmış nokta(x,y) fonksiyonu çağırılarak noktalar VICHaritasi dizisindeki yerlerini alıyorlar.

Son olarak ekraniOlustur() fonksiyonu çağırıldıktan sonra önceki JS dosyasında bulunan ekraniCiz() çağırılarak sonucu HTML içerisinde görüntülüyoruz.

İşin özeti bu. Burada sinüs, cosinüs kullanılarak çember ya da elips nasıl çizdirilir bunu açıklamayı yersiz buluyorum. Formül olarak zaten açık bir şekilde duruyor. 160 ve 100 nereden geldi diyecek olursanız 320 ve 200'ün yarısı yani ekranın orta koordinatları. 256 adım sayısı ve 128 ise bu adım sayısının yarısı. Aslında 128 yerine 256'yı olduğu şekilde kullanmak gerekir ancak derece -> radyan dönüşümünde kullanılan bir kısaltma ile (i / 256 * 2 * pi) yerine (i / 128 * pi) şeklinde kısalttığımız için 128 değerini kullandık.

Gelelim en can alıcı noktaya. nokta(x,y) fonksiyonu. Buradaki adres ve byteDegeri'ni nasıl hesaplıyoruz? Gelin birlikte buna kafa yoralım.

Elimizde bir x ve bir y değeri var. Bizim ulaşmak istediğimiz 8000'lik dizinin kaçıncı elemanına denk geldiği ve bu elemanın değerini nasıl etkilediğini (hangi bitini set ettiğini) bulmak. Öncelikle şunu biliyoruz. Ekranın en sol üstünden yani dizinin ilk elemanından itibaren byte'lar birer birer arttıkça y'de 8'lik alanda birer birer artıp her 8'den sonra yeniden 0'a dönüyor. Bunu yine bardak, raf örneğini inceleyerek anlayabilirsiniz. İlk byte'da y koordinatı 0, ikincide 1, üçüncüde 2....sekizincide 7 ve dokuzuncuda yine 0... Peki bu nereye kadar böyle devam ediyor? 40. karaktere kadar yani 40x8=320. byte'a kadar. 320. byte'da y koordinatı 8 atlayarak 8 ile 15 arası değerler almaya başlıyor. 640'da 16-23, 960'da 24-31 şeklinde devam ediyor bu. Peki bunu nasıl formülize edeceğiz? Madem "her 8'de bir" diyoruz öncelikle bu 8'deki kırılma noktasını ilkokuldan kalma yöntemle bölme işlemi yaparak inceleyelim.

Buradaki s sonuç ve k kalan'ı temsil ediyor. Peki bu iki parametreyi nasıl formülize edeceğiz?

Elbette ki aşağıdaki şekilde.

ys = int(y / 8)

yk = y mod 8

Yani "ys", y / 8 işleminden çıkan sonucun tam sayı kısmı ve "yk" ise 8 ile bölümden kalan yani matematiksel ifadesiyle "mod 8". Peki bu "ys" ve "yk" bize neyi veriyor? Hadi bir tablo yaparak görelim sonucu.

işlem: y/8 y: 0 ys: 0 yk: 0 y: 1 ys: 0 yk: 1 y: 2 ys: 0 yk: 2 y: 7 ys: 0 yk: 7 y: 8 ys: 1 yk: 0 y: 9 ys: 1 yk: 1 y: 10 ys: 1 yk: 2 y: 11 ys: 1 yk: 3 y: 15 ys: 1 yk: 7 y: 16 ys: 2 yk: 0 y: 17 ys: 2 yk: 1 y: 198 ys: 24 yk: 6 y: 199 ys: 24 yk: 7

Hmmm, işte bu tablodaki değerler bir şeyleri ortaya çıkarıyor. Dokümanın en başında verdiğim tabloya bakarsanız ve az önce bahsettiğim gibi 320'de bir y'de 8'lik bir artış olduğunu düşünecek olursak hafızadaki doğru adresi bulmak için "ys" parametresini 320 ile çarpmak ve "yk" ile toplamak gerekiyor. Bu durumda formülümüz;

yi = ys * 320 + yk

Haline dönüşüyor. Buradaki "yi" bize dizinin y koordinatına bağlı indeksini veriyor. Ne yazık ki işimiz bitmedi. Bir de x koordinatımız var. Ama x koordinatında işimiz biraz daha kolay. Tek bir noktayı unutmamak koşuluyla! x koordinatındaki 1 pixellik ilerleme y koordinatında olduğu gibi hafızada 1 byte'lık ilerlemeye denk gelmiyor. 8 pixel aynı byte üzerinde yer alıyor. Yani x = 0 ve x = 7 değerleri aslında bize aynı byte'ı veriyor. Ancak bu byte'da değiştirmemiz gereken bit değerleri değişiyor. Şimdilik yalnızca hafıza adresini bulmaya çalıştığımıza göre x = 0 ve x = 7 bizim için aynı şeyi ifade ediyor. Bu ne demektir? x'i 8'e bölebiliriz. Böylece;

xs = int(x / 8)

xk = x mod 8 işlem: x/8 x: 0 xs:0 xk:0 x: 1 xs:0 xk:1 x: 2 xs:0 xk:2 x: 7 xs:0 xk:7 x: 8 xs:1 xk:0 x: 9 xs:1 xk:1 x: 15 xs:1 xk:0 x: 16 xs:2 xk:1 x: 17 xs:2 xk:2 x: 318 xs:39 xk:6 x: 319 xs:39 xk:7

"xs" parametresinde 0'dan 39'a kadar byte değerlerini elde ediyoruz. Ekran adreslerinde bir sırada toplam 40 byte var. Ancak unutmamamız gereken hafızada bu byte'ların peş peşe gelmediği 8'er aralıklarla dizildiği. Çünkü aralarda aynı karakter alanında yer alan diğer y koordinatlarına ait değerler duruyor. Peki 8'er 8'er gittiğine göre ne yapmak lazım? Tabii ki bu "xs" değerini 8 ile çarpmak lazım.

xi = xs * 8

Hadi artık bu kadar uzun işlemden sonra şu adres indeksini bulalım.

ai = yi + xi

İşte bu kadar. Artık dizinin kaçıncı elemanına erişmemiz gerektiğini biliyoruz. Ama hala ona hangi değeri vermemiz gerektiğini bilmiyoruz. Bunu bulmak için "xk" parametresini kullanacağız. Peki "xk"nın 0'dan 7'ye değişim göstermesini ekranda nasıl görmemiz lazım?

xk = 0 -> Xooooooo -> bd = %10000000 = 128 = 2^7 xk = 1 -> oXoooooo -> bd = %01000000 = 64 = 2^6 xk = 2 -> ooXooooo -> bd = %00100000 = 32 = 2^5 xk = 3 -> oooXoooo -> bd = %00010000 = 16 = 2^4 xk = 4 -> ooooXooo -> bd = %00001000 = 8 = 2^3 xk = 5 -> oooooXoo -> bd = %00000100 = 4 = 2^2 xk = 6 -> ooooooXo -> bd = %00000010 = 2 = 2^1 xk = 7 -> oooooooX -> bd = %00000001 = 1 = 2^0

Bu değerleri tablolayabiliriz. Ama sonuç olarak bunu da formülüze etmek mümkün. Şöyle ki bd = 2 ^ (7 - xk)

Evet "bd" yani byte değerini 2'nin (7-xk). üssünü alarak bulabiliyoruz.

Uff puff. Peki başka bir şey kaldı mı? Ne yazık ki evet... Ama ufak bir şey. Biz bu byte'a doğrudan bir değer verdiğimizde byte'ın eski değerini silmiş oluyoruz. Bu durumda ekrana basmak istediğimiz bir nokta, aynı byte'a denk gelen diğer 7 noktayı silmiş oluyor. Bunu engellemek için OR'lamak lazım. Matematikteki "veya" ifadesini bir hatırlayalım.

%11010001

%10001010

%11011011

Byte'ın eski değeriyle "bd" değerini OR'ladığımız takdirde eğer o piksel daha önceden basılmışsa bir değişim olmayacak, basılmamışsa basılacak ve diğer bitler durumdan etkilenmeyecek. Örnek olarak byte'ın ilk ve son bitlerine daha önceden nokta konulmuş olduğunu varsayalım ve biz 3 numaralı bit'e (2^3) OR'layarak bir nokta koyalım.

%10000001 <- Orijinal değer

%00001000 <- Yeni basılan nokta

%10001001 <- Sonuç değeri

İşte bu sonuç değeri tam istediğimiz değer! Şimdi uzuuun uzun anlattığımız bunca parçalanmış formülü toparlayalım. VICHaritasi dizisine kısaca "vh" diyecek olursak;

ai = yi + xi bd = 2 ^ (7 - xk)

vh[ai] = vh[ai] OR bd

İşte formül bu. Şimdi yi ve xi'yi yerlerine koyalım.

ai = (ys * 320 + yk) + (xs * 8)

bd = 2 ^ (7 - xk)

vh[ai] = vh[ai] OR bd

Şimdi de ys, yk, xs ve xk değerlerini de yerine koyalım.

ai = (int(y / 8) * 320 + (y mod 8)) + (int(x / 8) * 8)

bd = 2 ^ (7 - (x mod 8))

vh[ai] = vh[ai] OR bd

Son hali biraz karışık gözükebilir. Bu yüzden parçalara ayırarak açıklama ihtiyacı hissettim. "nokta(x,y)" fonksiyonunun açıklanmaya başladığı yerden buraya kadar olan bölümü anlayana kadar defalarca okumanızı tavsiye ediyorum.

Şimdi de bu formülü JavaScript diline uygun hale getirelim. Öncelikle bir sayıyı 8'e böldükten sonra tam sayı kısmını almanın en basit yolu o sayının bitlerini 3 bit sağa kaydırmaktır. 3 bit sağa kaydırmak sayıyı 2^3'e bölmek anlamına gelir ki dışarıda kalan bitler küsurattır. Küsuratı atmış oluyoruz kısacası. JavaScript C tabanlı bir dil olduğu için operatörlerinin de birçoğu C ile aynıdır. Bit kaydırma işlemleri << ve >> operatörleriyle yapılır. Yani y >> 3 diyerek y'yi 8'e bölüp tam sayı kısmını almak mümkün.

Gelelim mod işlemine. Aslında "%" operatörüyle bu işi halledebiliriz ancak söz konusu olan sayı 2'nin üslerinden biriyse AND'lemek her zaman daha iyi ve daha hızlı bir yöntemdir. mod 8 demek sayının 7 ile AND'lenmesi demektir. 7 = %00000111 ile AND'leyerek 7'yi geçen sayının tekrar 0'a dönmesini sağlıyoruz. mod 8 de aynı işi görüyor.

Bir başka nokta ise "xi" parametresinde yaptığımız 8'e bölüp tam sayı kısmını alıp 8 ile çarpma işlemi. Bu işlem yalnızca sayının alt 3 bitinin sıfır olmasını sağlıyor. Aslında alt 3 bit sıfır olacak şekilde %111111000 = 504 ($1f8) sayısıyla AND'lemek de bize aynı sonucu verecektir. Burada dikkat edilmesi gereken husus x'in 0-255 değil 0-319 aralığında değiştiğidir. Bu yüzden AND'lerken 8 değil 9 bit kullanıyoruz.

AND ve OR operatörleri JavaScript'te yine C'de olduğu gibi & ve | karakterleriyle ifade ediliyorlar. Üs alma işlemi için ise Math kütüphanesinde yer alan pow(sayı, üs) fonksiyonu kullanılıyor.

Şimdi bütün bu yazılanları toparlayacak olursak formül son şeklini şu şekilde alıyor.

ai = ((y >> 3) * 320) + (y & 7) + (x & 0x1f8);

bd = Math.pow(2,7 - (x & 7));

vh[ai] = vh[ai] | bd;

Örnekteki değişkenlere çevirecek olursak;

adres = ((y >> 3) * 320) + (y & 7) + (x & 0x1f8);

byteDegeri = Math.pow(2, 7 - (x & 7));

VICHaritasi[adres] = VICHaritasi[adres] | byteDegeri;

İşte size plot rutininin açıklaması.

Evet Commodore 64'te ekrana nokta basmak o kadar da basit bir iş değildir. 64'ler dergisinde Barış Yalçınkaya'nın köşe yazısında bir okuyucunun sorusunu hatırlıyorum. Okuyucu "Commodore 64'te ekrana üçgen, kare gibi şekilleri nasıl çizebilirim?" diye çok mantıklı ve basit bir soru sormuştu. Eminim Barış bir hayli ter dökmüştür cevaplayana kadar. En son cevap olarak bir çizim programıyla hazırlanmış bir grafiğin nasıl ekrana basılacağını anlatmıştı. Ama Barış'a kesinlikle hak veriyorum. Ekrana nokta basmak bile gördüğünüz gibi "anlatmaya geldiğinde" oldukça zor bir iş.

Commodore 64'te Ekrana Nokta Basmak

Tamam, simülatörle bastık noktaları. Ama şimdi bir de bu işin Commodore'a uyarlanması var. Genellikle Commodore 8 bitlik bir bilgisayar olduğu için ilk verilen nokta basma rutini örneğinde x koordinatı 0-255 aralığında değerler alabilecek şekilde örneklenir. Ancak ben sizlere doğrudan x: 0-319 ve y: 0-199 aralığında değerler alabilecek şekilde yani 9 bitlik bir x koordinatıyla nasıl nokta basılacağını göstereceğim. ACME ile derlenebilecek kod örneği aşağıdadır.

noktakoy.a

!to "noktakoy.prg",cbm

* = $0801

!byte $0c, $08, $00, $00, $9e, $32, $30, $36, $31, $00, $00, $00

KARAKTER_EKRANI = $0400

VIC_HARITASI = $2000

* = $080d

; İç ve Dış Ekran Renklerini Belirle lda #$02 sta $d020 lda #$00 sta $d021

; Bitmap Ekran Aç lda #$18 sta $d018 lda #$c8 sta $d016 lda #$3b sta $d011

; Ekranın Arkaplan ve Çizim Renklerini Belirle ldx #$00 lda #$10

.ekrandongusu sta KARAKTER_EKRANI,x sta KARAKTER_EKRANI+$100,x sta KARAKTER_EKRANI+$200,x sta KARAKTER_EKRANI+$2e8,x inx bne .ekrandongusu

; Ana Rutin ldy #0 ; x koordinatı 9.bit lda #160 ; x koordinatı ldx #100 ; y koordinatı jsr noktakoy ; ekrana nokta bas jmp * ; sonsuz döngüye gir

; isim : noktakoy

; açıklama : ekrandaki verilen (x,y) koordinatına nokta koyar

; girdiler : a -> x koordinatı

; x -> y koordinatı

; kullanılan : $f0, $f1, $f2 noktakoy sty $f2 ldy ekranLo,x sty $f0 ldy ekranHi,x sty $f1 ldy $f2 beq .xhisifir inc $f1

.xhisisifir tax and #$f8 tay lda oraTablosu,x ora ($f0),y sta ($f0),y rts

!align 255,0,0 oraTablosu

!for i,32 {

!byte %10000000

!byte %01000000

!byte %00100000

!byte %00010000

!byte %00001000

!byte %00000100

!byte %00000010

!byte %00000001

!align 255,0,0 ekranLo

!for i,25 {

!for j,8 {

!byte (VIC_HARITASI+(320*(i-1))+(j-1))&255

!align 255,0,0 ekranHi

!for i,25 {

!for j,8 {

!byte (VIC_HARITASI+(320*(i-1))+(j-1))>>8

* = VIC_HARITASI

!fill $1f40, 0

Programın Çıktısı:

Bu örnekte dikkat edilmesi gereken bir nokta var. (Bakın ekranın ortasında duruyor :))

; Ana Rutin ldy #0 ; x koordinatı 9.bit lda #160 ; x koordinatı ldx #100 ; y koordinatı jsr noktakoy ; ekrana nokta bas

Öncelikle bu bölümde gördüğünüz register isimleri kafanızı karıştırmasın. Yani "ldx" komutu x koordinatı değerini taşır şeklinde düşünmeyin. Aksine yanlarında açıklamaları olduğu gibi ldx ile 0-199 arasındaki y koordinatı değerini y register'ına yüklüyoruz. ldy ve lda ise x koordinatı değerlerini içeriyor. Peki burada 9 biti nasıl kullandık? y registerının en sağdaki biti (0. bit) a registerının 9. biti anlamına geliyor. Yani x'e 300 gibi 255'ten büyük bir değer vermemiz gerektiğinde bunu öncelikle 9 bitlik bir ifade şeklinde yazmalıyız.

300 = %100101100

Daha sonra en soldaki biti y registerının en sağdaki biti olacak şekilde ayırıp geri kalan 8 biti de a registerına vermek gerekiyor. Yani;

ldy #%00000001 lda #%00101100

Şeklinde x'e 300 değerini verebiliyoruz. Bunu daha basit görmenizi sağlamak için 300'ü 16 bit ile ifade edip ortadan ikiye böleceğim.

300 = %100101100 = %0000000100101100 = %<00000001><00101100>

Soldaki parça Y, sağdaki ise A register'ının değerini ifade ediyor.

Dikkat etmeniz gereken nokta Y register'ının 1 değerini alması durumunda X'in değerinin 319 - 256 = 63 değerini geçmemesi gerektiğidir. Yani Y'nin her iki değeri için A'nın değer aralıklarını inceleyecek olursak;

Y = 0 -> A [0,255] Y = 1 -> A [0,63]

Olması gerekmektedir. Benzer şekilde Y koordinatının değerlerini tutan X register'ı ise 199'u geçmemelidir.

X [0,199]

Bu koşullara uyulmak suretiyle 320 x 200'lük alanda ekranın istediğiniz yerine nokta basabilirsiniz.

Şimdi bu örneği VIC Simülatörü'ndeki örneği yapmakta kullanalım.

Commodore 64'te VIC Simülatör'deki Örnek İle Aynı Sonucu Veren Programı Hazırlamak

Öncelikle elipsin çiziminde kullanılacak bazı sinüs tablolarını hazırlamamız gerekiyor. Bu konu bir önceki Test Platformu bölümünde detaylı olarak ele alınmıştı. Burada yine JavaScript kullanıyoruz.

sinus.htm

<script> var txtSinXLo = "";

var txtSinXHi = "";

var txtSinY = "";

var txtCikti = "";

for(i = 0; i < 256; i++)

sinX = 160 + Math.floor(159.9 * Math.sin(i / 128 * Math.PI));

sinXLo = sinX & 255;

sinXHi = sinX >> 8;

sinY = 100 + Math.floor(99.9 * Math.sin(i / 128 * Math.PI));

if(i != 0)

txtSinXLo += ",";

txtSinXHi += ",";

txtSinY += ",";

txtSinXLo += sinXLo;

txtSinXHi += sinXHi;

txtSinY += sinY;

txtCikti += "!align 255,0,0<br><br>";

txtCikti += "sinusTablosuXLo<br>";

txtCikti += "!byte "+txtSinXLo+"<br>";

txtCikti += "!byte "+txtSinXLo+"<br>";

txtCikti += "cosinusTablosuXLo = sinusTablosuXLo + 64<br><br>";

txtCikti += "!align 255,0,0<br><br>";

txtCikti += "sinusTablosuXHi<br>";

txtCikti += "!byte "+txtSinXHi+"<br>";

txtCikti += "!byte "+txtSinXHi+"<br>";

txtCikti += "cosinusTablosuXHi = sinusTablosuXHi + 64<br><br>";

txtCikti += "!align 255,0,0<br><br>";

txtCikti += "sinusTablosuY<br>";

txtCikti += "!byte "+txtSinY+"<br>";

txtCikti += "!byte "+txtSinY+"<br>";

txtCikti += "cosinusTablosuY = sinusTablosuY + 64<br><br>";

document.write(txtCikti);

</script>

"sinus.htm"in tarayıcıya verdiği çıktıyı kopyala-yapıştır ile "elipsciz.a" kaynak kodunun sonlarında belirtilen bölgeye yapıştırmamız gerekiyor.

elipsciz.a

!to "elipsciz.prg",cbm

* = $0801

!byte $0c, $08, $00, $00, $9e, $32, $30, $36, $31, $00, $00, $00

KARAKTER_EKRANI = $0400

VIC_HARITASI = $2000

* = $080d

; İç ve Dış Ekran Renklerini Belirle lda #$02 sta $d020 lda #$00 sta $d021

; Bitmap Ekran Aç lda #$18 sta $d018 lda #$c8 sta $d016 lda #$3b sta $d011

; Ekranın Arkaplan ve Çizim Renklerini Belirle ldx #$00 lda #$10

.ekrandongusu sta KARAKTER_EKRANI,x sta KARAKTER_EKRANI+$100,x sta KARAKTER_EKRANI+$200,x sta KARAKTER_EKRANI+$2e8,x inx bne .ekrandongusu

; Ana Rutin anarutin ldx sayac

; koordinatları al lda cosinusTablosuXHi,x sta xhi lda cosinusTablosuXLo,x sta xlo lda sinusTablosuY,x sta y ldy xhi ; x koordinatı 9.bit lda xlo ; x koordinatı ldx y ; y koordinatı jsr noktakoy ; ekrana nokta bas inc sayac ; tablo indeks sayacını 1 arttır bne anarutin ; eğer hala 255'i geçmediyse ana rutin başlangıcına git jmp * ; sonsuz döngüye gir

; isim : noktakoy

; açıklama : ekrandaki verilen (x,y) koordinatına nokta koyar

; girdiler : a -> x koordinatı

; x -> y koordinatı

; kullanılan : $f0, $f1, $f2 noktakoy sty $f2 ldy ekranLo,x sty $f0 ldy ekranHi,x sty $f1 ldy $f2 beq .xhisifir inc $f1

.xhisifir tax and #$f8 tay lda oraTablosu,x ora ($f0),y sta ($f0),y rts sayac !byte 0 xlo !byte 0 xhi !byte 0 y !byte 0

!align 255,0,0 oraTablosu

!for i,32 {

!byte %10000000

!byte %01000000

!byte %00100000

!byte %00010000

!byte %00001000

!byte %00000100

!byte %00000010

!byte %00000001

!align 255,0,0 ekranLo

!for i,25 {

!for j,8 {

!byte (VIC_HARITASI+(320*(i-1))+(j-1))&255

!align 255,0,0 ekranHi

!for i,25 {

!for j,8 {

!byte (VIC_HARITASI+(320*(i-1))+(j-1))>>8

"sinus.a"nın çıktısı buraya gelecek

* = VIC_HARITASI

!fill $1f40, 0

Programın Çıktısı:

VIC Simülatör'de Yapılan İşlemin Commodore'a Aktarılmasına İlişkin Değerlendirme

Ekrana nokta basma rutini VIC Simülatör örneğinde çok detaylı anlatıldığı için Commodore 64 versiyonunun açıklamasını yapmaya gerek duymuyorum. Uyarlama olarak tıpatıp aynısı. Elbette ki bazı ek tablolar kullanılıyor, Commodore'a özel optimizasyonlar ve mecburi tasarım değişiklikleri oluyor. Ama bunun haricinde anlatılacak pek bir şey yok. En başından beri bahsettiğim gibi, bu döküman 6510 ASM'yi kaba hatlarıyla bilen ve ACME ya da benzeri derleyicileri kullanmış kişilere hitap ediyor. Eğer 6510 ASM bilmiyorsanız, bu örnekleri anlayabilmek için öncelikle başlangıç seviyesindeki dökümanları okumanızı tavsiye ederim.

Gelelim elips örneğine. Burada VIC Simülatör'ündeki;

x = 160 + 160 * Math.cos(i / 128 * Math.PI);

y = 100 + 100 * Math.sin(i / 128 * Math.PI);

satırlarını nasıl uyarladık? Aslında çıtayı iyice yükseltmemek için kötü bir yöntem kullandık. Öyle ki bu çizdiğimiz elipsin çaplarından birini bile değiştirmek istesek "sinus.htm" dosyasını değiştirip, yeni sinüs tabloları elde edip, çıktıları "elipsciz.a" kaynak koduna yapıştırdıktan sonra programı yeniden derlememiz gerekecek. Peki neden?

Sorunu şöyle ele alalım. Commodore 64'de sinüs fonksiyonu yok. Peki sinüs fonksiyonunu nasıl uyarlayabiliriz? Aslında matematiksel olarak bunun yöntemleri var, hatta hatta çok kısa assembler kodlarıyla bile sinüs hesaplatılabiliyor. Ancak en hızlı yöntem önceden hazırlanmış bir tablo. Peki bu tabloyu hafızada nasıl tutacağız?

Sinüs fonksiyonu ne yazık ki yalnızca ondalıklı sonuçlar vermekle kalmıyor, hatta hatta yalnızca -1 ile 1 arası sonuçlar veriyor. Bu durumda byte değerleri tam sayı olduğu için küsuratları atacak olursak, tabloda yalnızca -1, 0 ve 1 değerleri bulunur. Bytelar negatif değerler de alamayacağı için muhtemelen -1 yerine 7 bit kullanarak (en soldaki bit pozitif negatif işareti belirtecek şekilde) 255 ya da 128 benzeri bir değer kullanmamız gerekecektir. En mantıklı yöntem sin(x) değil de a*sin(x)'i tablolamak. Bu durumda küsurat kaybından önemli ölçüde kurtulmuş oluyoruz. Kaybolan küsuratlar ise bu tür bir örnekte hiç önemli değil. Ancak negatif sayılarla ilgili problemimiz hala devam etmekte. Bu durumda JavaScript'deki örneğin birebir aynısını tablolayarak bu işi çözüyoruz yani a + a * sin(x)

Burada x ve y için a değerleri 160 ve 100 olduğuna göre;

160 + 160 * cos(x)

100 + 100 * sin(x)

hesaplamamız gerekiyor. Cosinüs fonksiyonu sinüs tablosunu biraz ilerden okumaya başlayarak elde edilebildiği için cosinüs ayrıca tablolanmıyor yalnızca sinüs tablosu peş peşe iki kez hafızaya konuluyor.

Burada yaşanan son sorun x koordinatlarında çıkıyor. x 255 değerini geçtiği ve tek byte ile ifade edilemediği için iki tabloya bölünmek zorunda kalıyor. Bir tablo alt 8 bitini 1 byte ile ifade ederken diğer tablo ise 9. bitini bir byte ile ifade ediyor. Bu hesaplama işleri "sinus.htm" dosyasında halledildikten sonra kaynak koda yerleştiriliyor ve basit bir döngüyle tablodaki hazır değerler okunarak ekrana pixeller yerleştiriliyor. Peki buradaki yöntem ne derece yanlış?

Aslında yöntem hemen hemen doğru. Tek eksik tabloların sabit değerlerle çarpılarak hazırlandıktan sonra olduğu gibi kullanılması. Olması gereken tabloların mümkün olan en geniş değerlerle hesaplandıktan sonra bir katsayıyla çarpılarak kullanılmasıdır. Mümkün olan en geniş katsayıya örnek vermek gerekirse buradaki formülde 8 bitlik bir tablo için en geniş "a" değeri 128'dir. Yani 128 + 128 * sin(x) şeklinde 0 - 255 arasındaki değerler elde edilebilir. 256'ya taşma önlenmek amacıyla 128 + 127.9 * sin(x) haline getirilmesi gerekebilir. Neden bu yöntem kullanılmadı?

Çünkü bu yöntem çarpma işlemi gerektiriyor ve Commodore'da çarpma işlemi yapmak da başlı başına bir meseledir. Ayrıca 8 bitlik bir sayıyla 8 bitlik bir sayıyı çarpıp 16 bit sonuç elde etmek nispeten açıklaması kolay bir durumdur. Ancak x koordinatı 9 bit olduğu için 16 bitlik bir sayıyla 8 bitlik bir sayıyı çarpan ve 24 bit sonuç veren bir rutine ihtiyaç vardır ve bu noktada işler gittikçe zorlaşıyor. Elbette ki 9 bitlik koordinat sisteminin hassasiyeti bir miktar bozularak bu işi sahte yöntemlerle çözmek mümkün. Ama şu seviyede bu tür konulara girmeyi uygun görmediğim için sabit tablolardan okunan değerleri olduğu gibi kullanmayı tercih ettim.

İşler giderek detaylanıyor ve bu sayıda da burada bırakıyoruz. Sizlere iyi beyin jimnastikleri diliyorum.

Benimle irtibata geçmek isteyenler için e-mail adresim;

skate <at> glance <dot> ws (mailto:c64hydrogen@yahoo.com)

Emir Akaydın

Skate / Glance / Clash