Bu yazıda mayın tarlası oyununda mayın olmayan yere tıklandığında mayınsız bölgenin otomatik açılması işlemini, mayına basıldığında tüm oyunun açılmasını ve gerekli renklendirmeleri yapacağız.
1. Bölümde ekranı oluşturmuş ve mayınları rastgele yerleştirmiştik. Şimdi bir önceki makalede yer alan CSS kodlarını mayın tuşunun durumuna göre aşağıdaki gibi güncelleyelim:
<style>
.MineGameCanvas {
width:500px;
height:500px;
}
.MineButton {
float:left;
width:25px;
height:25px;
}
.MineButtonPressed {
float:left;
width:25px;
height:25px;
pointer-events:none;
background-color:gray;
border:inset;
}
.MineButtonT {
float:left;
width:25px;
height:25px;
background-color:red;
pointer-events:none;
}
.MineButtonT2 {
float: left;
width: 25px;
height: 25px;
background-color: orange;
pointer-events:none;
}
</style>
Burada mayına basılması durumu için "MineButtonT" sınıfı, mayına basılıp oyun sonlandığında diğer mayınları göstermek içi "MineButtonT2" ve boş mayın tarlası alanlarını göstermek için "MineButtonPressed" sınıfları eklenmiştir. Bir diğer önemli nokta ise "pointer-events:none" bilgisidir. Bununla tuşun olay yakalayıcıları etkisizleştirilmektedir (tuş disable yapılmaktadır).
Mayın taraması işleminin ve diğer ek fonksiyonların çalıştırılması için javascript kodu aşağıdaki gibi güncellenmiştir:
<script type="text/javascript">
var mineCount = 50;
var arr = [];
var width = 20;
var height = 20;
var possibility = mineCount / (width * height);
var BoomClass = "MineButtonT";
var BoomClassShow = "MineButtonT2";
var MinePressed = "MineButtonPressed";
var MineSweeperWrapper = function () {
this.HasMine = false,
this.IsEnabled = true,
this.IsPressed = false,
this.IsFlagged = false,
this.MineButton = null
}
function ButtonPressed(sender) {
var btNumx = $(sender).attr("btNumx");
var btNumy = $(sender).attr("btNumy");
var x = parseInt(btNumx);
var y = parseInt(btNumy);
if (arr[x][y].HasMine) {
arr[x][y].IsPressed = true;
OpenAll();
} else {
EvaluateSituation(x, y);
}
}
function PressMine(X, Y) {
arr[X][Y].IsEnabled = false;
$(arr[X][Y].MineButton).toggleClass(MinePressed);
}
function EvaluateSituation(X, Y) {
var stack = [];
var neighborCount = 0;
if (ControlButtonHasMine(X, Y + 1, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X, Y - 1, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X + 1, Y, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X + 1, Y - 1, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X + 1, Y + 1, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X - 1, Y, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X - 1, Y - 1, stack)) {
neighborCount++;
}
if (ControlButtonHasMine(X - 1, Y + 1, stack)) {
neighborCount++;
}
if (arr[X][Y].IsEnabled)
PressMine(X, Y);
if (neighborCount > 0) {
$(arr[X][Y].MineButton).val(neighborCount);
} else {
while (stack.length > 0) {
var neighbour = stack.pop();
EvaluateSituation(neighbour.X, neighbour.Y);
}
}
}
function ControlButtonHasMine(X, Y, stack) {
if (X >= 0 && X < width && Y >= 0 && Y < height) {
if (arr[X][Y].IsEnabled) {
stack.push(arr[X][Y]);
if (!arr[X][Y].HasMine) {
return false;
} else {
return true;
}
}
}
}
function OpenAll() {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
if (arr[x][y].HasMine) {
if (!arr[x][y].IsPressed)
$(arr[x][y].MineButton).toggleClass(BoomClassShow);
else
$(arr[x][y].MineButton).toggleClass(BoomClass);
} else {
if (arr[x][y].IsEnabled) {
$(arr[x][y].MineButton).toggleClass(MinePressed);
arr[x][y].IsEnabled = false;
}
}
arr[x][y].IsEnabled = false;
}
}
}
$(document).ready(function () {
arr = CreateArray(width, height, 0);
PlantMines();
GenerateMineField();
});
function GenerateMineField() {
var canvas = $(".MineGameCanvas");
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
var button = $('<input id="Button-' + x + '-' + y + '" type="button" value="" class ="MineButton" btNumx="' + x + '" btNumy="' + y + '" onClick="ButtonPressed(this)"/>');
arr[x][y].MineButton = button;
arr[x][y].X = x;
arr[x][y].Y = y;
canvas.append(button);
}
}
}
function CreateArray(witdh, height, defaultValue) {
var arr = [];
for (var x = 0; x < width; x++) {
arr[x] = [];
for (var y = 0; y < height; y++) {
arr[x][y] = new MineSweeperWrapper();
}
}
return arr;
}
function PlantMines() {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
if (!arr[x][y].HasMine) {
var deger = Math.random();
if (deger <= possibility) {
arr[x][y].HasMine = true;
mineCount--;
if (mineCount == 0)
return;
}
else {
arr[x][y].HasMine = false;;
}
}
}
}
if (mineCount > 0)
PlantMines();
}
</script>
Bir önceki makalede anlatılmayan ve yeni eklenen fonksiyonların işlevleri şöyledir:
* PressMine fonksiyonu bir tuşa basıldığında tuş ile ilgili gerekli sınıf değişiklikleri ve durum değişikliklerini yapmaktadır.
* EvaluateSituation fonksiyonu mayın olmayan boş bir alana tıklandığında mayınsız alanı tarama ve mayınları sayma işlemlerinin yerine getirildiği recursive fonksiyondur.
* ControlButtonHasMine fonksiyonu tuş üzerinde mayın olup olmadığını ve kontrol edilen tuşun geçerli bir tuş olup olmadığını kontrol eden fonksiyondur. Ayrıca tuş çevresinde yer alan komşu tuşların eğer kontrol edilmemişlerse yığına eklenme işlemi de burada yapılmaktadır. Yığın içinde yer alan komşu tuşlar EvaluateSituation fonsiyonu içinde tekrar çağrılarak değerlendirilmektedir.
* OpenAll fonksiyonu mayına basılıp oyun sonlandığında bütün mayın alanını açık hale getiren fonksiyondur.
Bu makalede önemli noktalardan ilki javascript sınıf tanımlamasıdır:
var MineSweeperWrapper = function () {
this.HasMine = false,
this.IsEnabled = true,
this.IsPressed = false,
this.IsFlagged = false,
this.MineButton = null
}
Yukarıda görüldüğü gibi constructer olarak kullanılan bir fonksiyon üzerinden tanımlama yapılmaktadır. Sınıf ve arr dizisi kullanılarak tarama işleminin hızlı yapılması sağlanmaktadır. Diğer durumda DOM üzerinden işlem yapmak daha zordur.
MineSweeperWrapper sınıfı gerekli algoritmik alanları ve ilgili DOM nesnesine referans içermektedir. Bu alanlardan "HasMine" tuş üzerinde mayın olup olmadığı bilgisini, "IsEnabled" tuşun kullanılabilir olup olmadığı bilgisini, "IsPressed" basılan tuşun işaret bilgisini, "IsFlagged" tuşa bayrak konulup konulmadığı bilgisini (sonraki makalede işlenecektir) ve son olarak "MineButton" DOM nesnesine referans bilgisini sağlamaktadır.
Bu yazıda diğer önemli nokta ise yığın (stack) kullanımıdır. Yığınlara kasik diziler ile aynı şekilde tanımlanırken yığına ekleme için push(), yığında çıkarma için pop() fonksiyonları kullanılmaktadır.
Yukarıdaki kodların çalıştırılması sonucu elde edilen sonuç aşağıdaki gibidir: