[C#,WinForms] Gra Układanka
Dzisiaj przedstawię proces tworzenia prostej gry, którą nazwałem „Układanką”. Jeżeli jesteś początkującym programistą i chcesz napisać pseudoużyteczny program z wykożystaniem Visual Studio i Windows Forms – ten wpis jest właśnie dla Ciebie. Jeżeli jesteś bardziej doświadczonym programistą, chcesz się trochę pośmiać / przeczytać dzienną normę kiepskiego kodu / powytykać kilka błędów i z wielką satysfakcją wyszydzić autora, bądź też pomóc zrozumieć autorowi jego błędy (niepotrzebne skreślić) – też możesz przeczytać 😉 Do dzieła!
Program będzie się składał z dziewięcu przycisków, z których jeden jest niewidoczny. Widoczne przyciski będą elementami układanki. Na przyciskach umieścimy losowo, bez powtórzeń cyfry od 1 do 8. Pozwolimy graczowi przesuwać elementy układanki w ten sposób, że jeśli zostanie naciśnięty element bezpośrednio sąsiadujący z wolnym polem, to przenosi się on na to pole. Zadaniem gracza jest uparządkowanie elementów według cyfr w kolejności rosnącej.
Uruchamiamy Visual Studio i wybieramy nowy projekt Windows Forms. We właściwościach formy zmienimy FormBorderStyle
na Fixed3D
, aby użyszkodnik nie mógł zmienić rozmiaru okienka. MaximizeBox
ustawiamy na false – przycisk maksymalizacji jest niedostępny. Właściwości Text
zmieniamy na UKŁADANKA, co spowoduje wyświetlenie tego napisu na górnej belce okienka. ShowIcon
ustawiamy na false, BackColor
na PowderBlue
(pozwalam wybrać inny :-D, kwestia gustu). Na formie umieszczmy 9 buttonów.
Od czasu do czasu warto rzucić okiem na wygenerowany kod w pliku Form1.Designer.cs
, aby wiedzieć co w trawie piszczy 🙂 Dla przycisku w lewym dolnym rogu ustawiamy właściwość Visible
na false.
Tworzymy pole będące dwuwymiarową tablicą typu Button, w której przechowywane będą elementy naszej układanki (przyciski)
private Button[,] buttonsTab;
Inicjalizujemy tablicę w konstruktorze Form1
buttonsTab=new Button[,] { {button1, button2, button3}, {button4, button5, button6}, {button7, button8, button9} };
Następnie zajmiemy się napisaniem metody, która sprawi, że na przyciskach będziemy widzieli losowe cyferki od 1 do 8 bez powtórzeń.
//przypisujemy przyciskom losowe cyfry bez powtórzeń private void losujCyfryNaPrzyciskach() { int index; Random rand = new Random(); //lista liczb, z której będziemy losować liczby i umieszczać je na przyciskach List<int> liczby = new List<int>() {1,2,3,4,5,6,7,8 }; //tablica przycisków, kóre sa widoczne na początku gry (wszystkie bez 7) Button[] widocznePrzyciski = new Button[]{button1,button2,button3,button4,button5,button6,button8,button9 }; foreach (Button b in widocznePrzyciski) //dla 8 widocznych przycisków { //losujemy wartość od 0 do liczności listy liczb index = rand.Next(0, liczby.Count); //liczbę o wylosowanym indeksie zamieniamy na string i wstawiamy do właściwości Text przycisku b.Text = liczby[index].ToString(); //usuwamy z listy liczbę o wylosowanym indeksie, aby nie wylosować jej ponownie liczby.RemoveAt(index); } }
Metodę wywołujemy w konstruktorze Form1
po metodzie InitializeComponent()
.
W konstruktorze wywołamy jeszcze metodę, która doda obsługę zdarzenia Click do naszych buttonów.
//dodajemy obsługę zdarzenia Click dla każdego z przycisków private void dodajObslugeClickPrzyciskow() { foreach (Button b in buttonsTab) { //po kliknięcu któregokolwiek przycisku zostanie wywołana metoda o nazwie button_Click b.Click += new System.EventHandler(this.button_Click); } }
Kolejna metoda, którą wywołamy w konstruktorze Form1
. Tym razem możemy zrealizować nasze estetyczne fantazje.
private void zmienFont_i_ColorPrzyciskow() { foreach (Button b in buttonsTab) { //tekst na przyciskach będzie miał nową czcionkę, rozmiar 25 b.Font = new Font(FontFamily.GenericMonospace, 25); //zmieniamy kolory tekstu i tła przycisków b.BackColor = Color.Azure; b.ForeColor = Color.CornflowerBlue; } }
I dochodzimy do serca naszego programu – metoda która zostanie wykonana po kliknięciu przycisku tj. elementu naszej układanki. Używamy kilku mniejszych, czekających na napisanie metod. W metodzie dowiemy się, który przycisk został kliknięty oraz jaką pozycje w buttonsTab
ma ten przycisk. Sprawdzimy, czy sąsiaduje on z przyciskiem niewidocznym i gdy zajdzie taka potrzeba przesówamy element.
private void button_Click(object sender, EventArgs e) { //zmienne, w których zapiszemy pozycję klikniętego przycisku w buttonsTab int index1, index2; //tworzymy zmienną b i przypisujemy do niej referencję do przycisku, który wywołał zdarzenie Click Button b = sender as Button; Button niewidoczny; //będzie przechowywał referencję do niewidocznego przycisku ustawIndeksyPrzycisku(out index1, out index2, b); //ustawia index1 i index2 //jeśli kliknięty przycisk nie sąsiaduje z niewidocznym, to metoda kończy swoje działanie if (!czyJestPrzyNiewidocznym(index1, index2, out niewidoczny)) return; else { //zamienieamy miejscami kliknięty przycisk i niewidoczny przycisk (przysunięcie elementu układanki) zamianaNiewidocznego(b, niewidoczny); if (sprCzyKoniec()) //sprawdzamy czy układanka została już ułożona { MessageBox.Show("WYGRAŁEŚ!!!"); //nagroda dla zwycięzcy //przywracamy układanke do stanu początkowego foreach(Button but in buttonsTab) { but.Visible = true; } button7.Visible = false; losujCyfryNaPrzyciskach(); } } }
Teraz zabieramy się za pisanie brakujących funkcji.
Poniższa metoda rozpozna po nazwie, który przycisk został wciśnięty i przypisze wartości zmiennym index1
oraz index2
(zadeklarowanym wewnątrz metody button_Click
), które informują o indeksach przycisku w tablicy przycisków buttonsTab
. Targa mną silne przeczucie, że ta metoda nie wygląda najlepiej. Może nie powinno jej być wogóle, a problem powinien zostać rozwiązany z użyciem bardziej wyrafinowanego sposobu… Cóż, w każdym bądź razie działa.
private void ustawIndeksyPrzycisku(out int index1,out int index2,Button b) { index1 = 0; index2 = 0; if (b.Name == "button1") { index1 = 0; index2 = 0; } else if (b.Name == "button2") { index1 = 0; index2 = 1; } else if (b.Name == "button3") { index1 = 0; index2 = 2; } else if (b.Name == "button4") { index1 = 1; index2 = 0; } else if (b.Name == "button5") { index1 = 1; index2 = 1; } else if (b.Name == "button6") { index1 = 1; index2 = 2; } else if (b.Name == "button7") { index1 = 2; index2 = 0; } else if (b.Name == "button8") { index1 = 2; index2 = 1; } else if (b.Name == "button9") { index1 = 2; index2 = 2; } }
Następna metoda sprawdza, czy wciśnięty przycisk sąsiaduje z przyciskiem niewidocznym (wolnym polem, na które można przesunąć element układanki). Sprawdzamy, czy przycisk po lewej stronie (buttonsTab[index1, index2 - 1]
w naszej tablicy) jest niewidoczny. Jednak wcześniej dowiadujemy się, czy tablica posiada element o indeksach [index1, index2 - 1]
, wykorzystując metodę czyIndeksySaPoprawne()
, którą napiszemy później.
Analogicznie postępujemy z przyciskiem po prawej stronie, na dole i na górze.
private bool czyJestPrzyNiewidocznym(int index1, int index2, out Button niewidoczny) { if (czyIndeksySaPoprawne(index1, index2 - 1)) //po lewej { if (buttonsTab[index1, index2 - 1].Visible == false) { niewidoczny = buttonsTab[index1, index2 - 1]; return true; } } if (czyIndeksySaPoprawne(index1, index2 + 1))//po prawej { if (buttonsTab[index1, index2 + 1].Visible == false) { niewidoczny = buttonsTab[index1, index2 + 1]; return true; } } if (czyIndeksySaPoprawne(index1 - 1, index2))//na górze { if (buttonsTab[index1 - 1, index2].Visible == false) { niewidoczny = buttonsTab[index1 - 1, index2]; return true; } } if (czyIndeksySaPoprawne(index1 + 1, index2))//na dole { if (buttonsTab[index1 + 1, index2].Visible == false) { niewidoczny = buttonsTab[index1 + 1, index2]; return true; } } niewidoczny = null; return false; }
Metoda, która sprawdzi, czy indeksy są poprawne dla naszej tablicy 3×3 (buttonsTab
)
private bool czyIndeksySaPoprawne(int index1, int index2) { if (index1 < 0 || index1 > 2 || index2 < 0 || index2 > 2) { return false; } return true; }
Metoda realizująca „przesunięcie elementu układanki”. Kliknięty przycisk zamienia się tekstem z niewidocznym i sam się ukrywa.
private void zamianaNiewidocznego(Button b,Button niewidoczny) { b.Visible = false; niewidoczny.Text = b.Text.ToString(); b.Text = "x"; niewidoczny.Visible = true; }
Sprawdzamy, czy wysiłki gracza coś dały, tzn. czy elementy są już ułożone w odpowiedniej kolejności.
private bool sprCzyKoniec() { int i=0; foreach (Button b in buttonsTab) { i++; if ( b.Text == "x" || b.Text==i.ToString()) continue; else return false; } return true; //jeśli elementy są ułożone w odpowiedniej kolejności zwracamy true }
I tak kończy się nasza przygoda z dzielną układanką, podstępnymi elementami, okrótną tablicą, piękną formą i biednym programistą.
Gotowy projekt można ściągnąć TUTAJ.
Metoda ustawIndeksyPrzycisku mogłaby wyglądać tak:
private void ustawIndeksyPrzycisku(out int index1, out int index2, Button b)
{
int x = b.Name[b.Name.Length – 1] – ‚0’ – 1;
index1 = x / 3;
index2 = x % 3;
}
Może nie najładniejszy sposób, ale lepszy od ręcznego sprawdzania.
Rzeczywiście, teraz jest krótsza i lepiej wygląda. Przy okazji dowiedziałem się jak szybko zamienić char na int. Dzięki! 😉