Pazar, Kasım 21, 2010

ASP .Net ile Tic Tac Toe

Geçenlerde bir arkadaşımla msn'de sunucu tabanlı bir dille Tic Tac Toe yazılabilir mi,yazılamaz mı, yazılırsa nasıl olur şeklinde muhabbet etmiştik. İkimizinde hem fikir olduğumuz nokta böyle bir oyunu JavaScript ile yazmanın daha mantıklı olduğuydu ama meraktan sunucu tabanlı bir dille nasıl olabileceğini konuştuk. Bende bu aralar ASP .Net ile haşir neşir olduğumdan ve daha öncede Tic Tac Toe ile ilgili tecrübem olduğunda yazmaya karar verdim.

C++ ile yaptığım versiyonunda tek bir bilgisayarda iki kişi tarafından (fareyi sırayla kullanarak) oynanabiliyordu. Buna ufak bir yapay zeka(AI) yazarak bilgisayara karşı oynayabilecek şekilde yaptım.

Kodlar

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace TicTacToe
{
public partial class Default : System.Web.UI.Page
{
//Hangi şekillerde karakterden 3 tane aynı hizaya gelebilir
private static int[,] ihtimaller = new int[,] { {0,1,2}, {3,4,5},
{6,7,8}, {0,3,6},
{1,4,7}, {2,5,8},
{0,4,8}, {2,4,6}};

protected void Page_Load(object sender, EventArgs e)
{
int[] harita = haritaOlustur();

if (Request.QueryString["hucre"] != null)
{
int index = Convert.ToInt32(Request.QueryString["hucre"]);
harita[index] = 1;
bilgisayarOyna();
}
else if (Request.QueryString["yeniOyun"] != null)
{
Session.Clear();
Response.Redirect("Default.aspx");
}

/*kazananVarMi() fonksiyonu 3 farklı sayı döndürebilir:
0 => Kazanan yok
1=>Oyuncu kazandı
2=>Bilgisayar Kazandı*/
int kazanan = kazananVarMi();
if (kazanan > 0)
{
string strKazanan = "<center><span style='font-size:35pt;font-weight:bold'>";
strKazanan += (kazanan == 1 ? "Sen Kazandın" : "Bilgisayar Kazandı")
+ "</span></center>";
Response.Write(strKazanan);
}
else if (berabereMi())
{
string strKazanan = "<center><span style='font-size:35pt;font-weight:bold'>"
+ "BERABERE</span></center>";
Response.Write(strKazanan);
}
else
{
tabloOlustur(harita);
Session["harita"] = harita;
}


}

private void tabloOlustur(int[] pHarita)
{
int sayac = 0;
for (int i = 0; i < 3; i++)
{
TableRow satir = new TableRow();
for (int k = 0; k < 3; k++)
{
TableCell hucre = new TableCell();
hucre.Width = 75;
hucre.Height = 75;
hucre.HorizontalAlign = HorizontalAlign.Center;
hucre.Attributes.Add("onMouseOver", "this.bgColor='#00c0ff'");
hucre.Attributes.Add("onMouseOut", "this.bgColor=''");
if (Convert.ToUInt32(pHarita[sayac]) == 0)
hucre.Text = "<a href='Default.aspx?hucre=" + sayac.ToString() + "'>Tıkla</a>";
else if (Convert.ToUInt32(pHarita[sayac]) == 1)
hucre.Text = "<span style='font-size:35pt;font-weight:bold'>X</span>";
else
hucre.Text = "<span style='font-size:35pt;font-weight:bold'>O</span>";

satir.Cells.Add(hucre);
sayac++;
}
tablo.Rows.Add(satir);
}
}

private int[] haritaOlustur()
{
int[] harita = new int[9];
if (Session["harita"] != null)
{
harita = (int[])Session["harita"];
}
else
{
for (int i = 0; i < 9; i++)
harita[i]=0;
}

return harita;
}

private int kazananVarMi()
{
int[] harita = haritaOlustur();
int durum = 0;
int hucre1 = 0, hucre2 = 0, hucre3 = 0;
for (int i = 0; i < 8; i++)
{
hucre1 = ihtimaller[i, 0];
hucre2 = ihtimaller[i, 1];
hucre3 = ihtimaller[i, 2];

if (harita[hucre1] != 0 && harita[hucre2] != 0 && harita[hucre3] != 0)
{
if (harita[hucre1] == harita[hucre2] && harita[hucre1] == harita[hucre3])
durum = harita[hucre1];
}
}

return durum;
}

private bool berabereMi()
{
//Eğer harita dizisinde değeri 0 olan eleman yoksa oyun Berabere bitmiş demektir
int[] harita = haritaOlustur();
for (int i = 0; i < 9; i++)
{
if (harita[i] == 0)
return false;
}

return true;
}

private void bilgisayarOyna()
{
int hucre1 = 0, hucre2 = 0, hucre3 = 0;
bool durum = false;
int[] harita = haritaOlustur();
for (int i = 0; i < 8; i++)
{
hucre1 = ihtimaller[i,0];
hucre2 = ihtimaller[i,1];
hucre3 = ihtimaller[i,2];

//Bilgisayarın tek hamlede kazanma ihtimali var mı ?
if (harita[hucre1] == 2 && harita[hucre2] == 2 && harita[hucre3] == 0)
{
durum = true;
harita[hucre3] = 2;
break;
}
else if (harita[hucre1] == 2 && harita[hucre3] == 2 && harita[hucre2] == 0)
{
durum = true;
harita[hucre2] = 2;
break;
}
else if (harita[hucre2] == 2 && harita[hucre3] == 2 && harita[hucre1] == 0)
{
durum = true;
harita[hucre1] = 2;
break;
}
//Karşıdaki oyuncunun tek hamlede kazanma ihtimali var mı ?
else if (harita[hucre1] == 1 && harita[hucre2] == 1 && harita[hucre3] == 0)
{
durum = true;
harita[hucre3] = 2;
break;
}
else if (harita[hucre1] == 1 && harita[hucre3] == 1 && harita[hucre2] == 0)
{
durum = true;
harita[hucre2] = 2;
break;
}
else if (harita[hucre2] == 1 && harita[hucre3] == 1 && harita[hucre1] == 0)
{
durum = true;
harita[hucre1] = 2;
break;
}
}

/*Oyunun bitme ihtimali yoksa rasgele oynar.
*Aslında burayıda nasıl oynayacağını detaylandıracak
*kodlar yazılarak daha akıllı hale getirilebilir
*/
if (!durum)
harita[bosHucreBul()] = 2;
}

private int bosHucreBul()
{
Random sayi = new Random();
int hucre = sayi.Next(0, 9);
int[] harita = haritaOlustur();

/*BerabereMi fonksiyonu boş hücre varmı diye kontrol eder.
Eğer kontrole bunu da eklemezsek ve hiç boş hücre yoksa
Sonsuz döngüye girer*/
while (harita[hucre] != 0 && !berabereMi())
{
hucre = sayi.Next(0, 9);
}

return hucre;
}
}
}



Ekran Görüntüsü

Proje Olarak indirmek için aşağıdaki linki kullanabilirsiniz. Visual Studio 2010 ile hazırlanmıştır. İsteyen için PHP versiyonu da mevcut :)
İNDİR

Salı, Kasım 16, 2010

PHP $this İşaretçisinin Adını Değiştirmek

Başlıktada belirttiğim gibi "$this" işaretçisi'nin adını değiştirmek mümkün mü?
Hangi amaçla böyle bir şeye ihtiyaç duyulur şuan için bir fikrim yok ama ceviz.net'in PHP bölümünde böyle bir başlıkla karşılaştım. Konuya buradan ulaşabilirsiniz.


Konu hakkındaki ilk yorumum böyle bir şeyin olamayacağı yönündeydi. Sonuçta bu ad php yorumlayıcısı tarafından belirlenmiş anahtar kelimeydi. Ama biraz düşününce yolu biraz dolandırarak çok basit bir yöntemle yapmak mümkün olabilirdi. Sınıf içinde $this işaretçisine referans olacak bir değişken tanımlayıp, sınıf içindeki değişkenlere yeni tanımladığımız değişkenle erişmek mümkün. Aşağıdaki ufak örnek daha iyi anlamaya yardımcı olabilir.


<?php
class Birsey
{
public $mesaj;
private $bu;//Yeni işaretçi olacak

function __constructor()
{
//Yeni değişkeni $this'e referans olarak ayarlıyoruz
$this->bu = &$this;
}

function birseyYap($mesaj)
{
//Yeni işaretçi ile sınıf değişkenlerine erişiyoruz
$bu->mesaj = $mesaj;
echo $bu->mesaj;
}
}

$nesne = new Birsey();
$nesne->birseyYap("Merhaba Dünya");
?>

Pazar, Kasım 07, 2010

SDL Per Pixel Çarpışma Algoritması

Daha önce Oyunlarda Basit AI(Yapay Zeka) Uygulamaları yazımda Rectangle çarpışma algoritmasından bahsetmiştim. Bu algoritmanın mantığında resmi dik dörtgen olarak algılayıp o sınırlar içine başka bir resim girerse çarpışma var demektir. Aşağıdaki resimlerdeki gibi.











Burada şöyle bir sorun çıkar karşımıza. Nesnelerimiz her zaman bu şekilde dik dörtgen olmaya bilir. Yuvarlak,üçgen, çokgen veya belli bir biçimi olmaya bilir. Bu durumda rectangle algoritması yukarıda belirttiğim gibi resmin etrafında bir çerçeve varmış gibi farz eder ve başka bir resim o çerçevenin sınırları içerisine girerse çarpışma algılar. Aşağıdaki resimlerdeki gibi.












Halbuki ikinci resimde tuğla ile kaya arasında temas yok. Yani çarpışma henüz gerçekleşmedi. Kayaya deydiği anda çarpışma var olarak algılatmamız lazım. İş de bu tip durumlar için Per Pixel Çarpışma algoritmalarına ihtiyaç duyarız.

Per Pixel algoritmalarında öncelikle bilmemiz gereken şey arka planı şeffaf(transparan) resimler kullanmamız gerektiği. Bu algoritmada Rectangle Çarpışma algoritmasında olduğu gibi yine resmin sınırları bir çerçeve gibi belirlenir(resimlerdeki yeşil ve kırmızı arkaplanları ben anlaşılması açısından koydum). Başka bir resim bu sınır içerisine girdiğinde her iki resim içinde kesişen yerlerdeki pixeller taranmaya başlar. Her iki resim içinde taranmakta olan mevcut pixel transparan değilse çarpışma var demektir.












İlk resimde resmin sınırlarına girmiş durumda ama henüz bir temas yok. İkinci resimde ise temas var ve çarpışma algılanmış durumda. Bu işlemi gerçekleştiren kod aşağıdaki şekilde:

SDL_Rect Resim::temasAlani(SDL_Rect rect)
{
SDL_Rect temasRect;
temasRect.x = rect.x - this->getX();
temasRect.y = rect.y - this->getY();
temasRect.w = rect.w;
temasRect.h = rect.h;

return temasRect;
}

bool Resim::perPixelCarpismaKontrol(Resim* hedef)
{
bool durum = false;

int x1 = Maximum(getResimKoord().x, hedef->getResimKoord().x);
int y1 = Maximum(getResimKoord().y, hedef->getResimKoord().y);

int x2 = Minimum(getResimKoord().x + resimW, hedef->getResimKoord().x + hedef->resimW);
int y2 = Minimum(getResimKoord().y + resimH, hedef->getResimKoord().y + hedef->resimH);

int width = x2 - x1;
int height = y2 - y1;

SDL_Rect carpismaRect = {0,0,0,0};

//Eğer width ve height 0 dan büyükse ise resimler temas halinde demektir
if(width > 0 && height > 0)
{
//Ne kadarlık bir alanın temas halinde olduğu
carpismaRect.x = x1;
carpismaRect.y = y1;
carpismaRect.w = width;
carpismaRect.h = height;

/*İlgili resmin hangi koordinatlarında kesişme olduğunu öğreniyoruz
Bu sayede resmin tamamını değil sadece kesişen kısmı kontrol ediyoruz*/
SDL_Rect kaynakRect = temasAlani(carpismaRect);
SDL_Rect hedefRect = hedef->temasAlani(carpismaRect);

//pixel pixel resim taramasına başlıyorum
for(int y = 0; y <= carpismaRect.h; y++)
{
for(int x = 0; x <= carpismaRect.w; x++)
{
if(GetAlphaXY(this, kaynakRect.x + x, kaynakRect.y + y) &&
GetAlphaXY(hedef, hedefRect.x + x, hedefRect.y + y))
durum = true;
}
}
}
else
durum = false;

return durum;
}


Yukarıdaki kodlarda temasAlani() fonksiyonuna dikkat çekmek istiyorum.Ne kadarlık bir alanda temas olduğunu öğrendikten sonra temasAlani() fonksiyonu ile temasın ilgili resmin hangi koordinatlarında yani hangi pixeller arasında olduğunu öğreniyoruz. Bu sayede sadece o alandaki pixellerin şeffaflığını kontrol etmiş oluyoruz.












Aşağıda örnek uygulama anlamanıza yardımcı olacaktır. Farkı görmeniz açısından hem Rectangle hemde Per Pixel çarpışma algoritmalarını beraber verdim.











Not: Program Code::Blocks kullanarak GCC ile derlenmiştir. Proje klasörünün içinde çalıştırılabilir(executable) halleri vardır. SDL'yi kurmadan deneyebilmeniz için.

Linux(Ubuntu) için Kaynak Kod: SDL_Per_Pixel
Windows için Kaynak Kod: SDL_Per_Pixel