Piszemy grę w Visual Studio C++: Kółko i krzyżyk.

informatyka logo
Strona główna  Strona szkoły  Plan lekcji  Bieżące zastępstwa  Biblioteka 

III LO Tarnów
eDziennik III LO Tarnów
MS Visual Studio C++ 2008

Gra: Kółko i Krzyżyk
Visual Studio C++


Zasady gry są raczej znane wszystkim. Poniższy kod źródłowy zawiera implementację gry w planszy 20x20 pól. Gra się kończy po ustawieniu w jednej linii pięciu jednakowych znaków. Można przerobić tak kod, że zwiększymy rozmiar planszy oraz ilość jednakowych znaków decydujących o wygranej. W przedstawionej wersji, gracze na przemian wstawiają myszką swoje znaki. Można pokusić się na sterowanie klawiaturą, ale to już zostawiam Tobie czytelniku.
 
kliknij na rysunku, aby pobrać kod źródłowy (Visual Studio C++ 2008)
gra kółko i krzyżyk visual studio c++
 
Krok 1: Przygotowujemy formatkę, strukturę i stałe dla gry
 
Na formatce umieszczamy komponent typu panel. Na nim będziemy wyświetlać grafikę. Oczywiści możemy zrobić to bezpośredni na formatce, ale tak też można;) A tak na prawdę, to wolne miejsce na formatce można wykorzystać do wyświetlania opcji gry czy też jej stanu. Wstępnie nie musimy zabiegać o rozmiar formatki jak i panelu. Wymiary ustawimy w kodzie programu, tak aby się automatycznie wyliczały do przyjętych parametrów planszy.
 
kółko i krzyżyk gra visual studio c++ formatka gry
 
Podpinamy bibliotekę graficzną oraz deklarujemy stałe i strukturę pojedynczego pola mapy gry

	using namespace System::Drawing;
	
	/// </summary>
	const int szerokosc=20;//wymiary kafla mapy w pikselach
	const int wysokosc=20;
	const int MaxWIERSZY=20;//ilość pól
	const int MaxKOLUMN=20;
	const int SUKCES=5;//ilośc znaków wygrywających
	const int KRZYZ=1;
	const int KOLKO=2;
	bool jakiGracz=true;//zaczyna krzyż
	int suma=0;//licznik sparwdzający czy osiągnięto sukces
        //struktura danych dla kazdego pola planszy gry
	value struct Pole{
			 bool odwiedzone;
			 bool zamaluj;
			 int zawartosc;//0 puste; 1- krzyż, 2- kółko
		};

Kilka słów o strukturze przeznaczonej do przechowywania danych o każdym polu planszy gry


 
Krok 2: Deklarujemy dynamiczną tablice planszy gry oraz obiekt graficzny na którym będziemy rysować grę

#pragma endregion
    array<Pole,2>^ Mapa;//tablica planszy gry 0-puste, 1-krzyż, 2- kółko
    Graphics^ plansza;


 
Krok 3: Piszemy funkcje zerującą poszczególne pola planszy

		void ZerujTablice(){
			 for(int w=0;w<MaxWIERSZY;w++)
				 for(int k=0;k<MaxKOLUMN;k++)
				     {
			                 Mapa[w,k].odwiedzone=false;
					 Mapa[w,k].zamaluj=false;
					 Mapa[w,k].zawartosc=0;
			             }
		         return;}//koniec ZerujTablice

Krok 4: Inicjujemy grę, czyli start aplikacji
 
Najwygodniej do ustalenia wszystkich początkowych parametrów gry nadaje się zdarzenie "ładowania" aplikacji do sytemu Windows. W momencie uruchomienia aplikacji przeliczamy wymagane rozmiary planszy oraz inicjujemy "zerowy" stan gry.

	private: System::Void Form1_Load(System::Object^  sender, System::EventArgs^  e) {
	                         //dopasuj szerokośc i wysokość formatki do planszy gry
				 Width=MaxKOLUMN*szerokosc+8;//dodaj na grubość ramek pionowych okna
				 Height=MaxWIERSZY*wysokosc+29;//dodaj na grubość paska tytułowego i ramek poziomych
				 //teraz wymiary panelu
				 panel1->Width=MaxKOLUMN*szerokosc+1;
				 panel1->Height=MaxWIERSZY*wysokosc+1;
				 //przydziel pamięć na dynamiczna tablice planszy
				 Mapa= gcnew array<Pole,2>(MaxWIERSZY,MaxKOLUMN);
				 //stowarzysz obiekt graficzny z panelem
				 plansza=panel1->CreateGraphics();
				 //ustaw poczatkowe wartości
				 ZerujTablice();
			 }

Możemy skompilować program. Powinniśmy otrzymać okno aplikacji o wymiarach dostosowanych do przyjętych rozmiarów planszy gry
 
Krok 5: Funkcja rysująca planszę gry
 
Ta funkcja będzie rysować planszę gry w momencie inicjacji aplikacji oraz będzie odświeżać grafikę przy na przykład przysłonięciu okna naszej aplikacji inną aplikacją czy tez zmianie stanu gry gdy któryś z graczy wygrał. W każdym bądź razie w kodzie programu będziemy ją wywoływać w kilku miejscach

void RysujPlansze(){
  plansza->Clear(Color::White);//białe tło
  Pen^ Olowek=gcnew Pen(System::Drawing::Color::Aqua);
  Olowek->Width=1;
  //rysuj kratkę
  for(int w=0;w<=MaxWIERSZY;w++)plansza->DrawLine(Olowek,0,w*wysokosc,szerokosc*MaxKOLUMN,w*wysokosc);
  for(int k=0;k<=MaxWIERSZY;k++)plansza->DrawLine(Olowek,k*szerokosc,0,k*szerokosc,wysokosc*MaxWIERSZY);
  //odswież krzyże i kółka
  Olowek->Width=3;
  for(int w=0;w<MaxWIERSZY;w++)
    for(int k=0;k<MaxKOLUMN;k++){
       //rysuj krzyże
       if(Mapa[w,k].zawartosc==KRZYZ){
	    Olowek->Color=System::Drawing::Color::Navy;
	    //pokaz ściezke wygranej
	    if(Mapa[w,k].zamaluj)Olowek->Color=System::Drawing::Color::Red;
	    plansza->DrawLine(Olowek,k*szerokosc,w*wysokosc,k*szerokosc+szerokosc,w*wysokosc+wysokosc);
	    plansza->DrawLine(Olowek,k*szerokosc+szerokosc,w*wysokosc,k*szerokosc,w*wysokosc+wysokosc);
	    }//koniec krzyży
       //rysuj kółka
       if(Mapa[w,k].zawartosc==KOLKO){
	   Olowek->Color=System::Drawing::Color::Green;
	   //pokaz ściezke wygranej
 	   if(Mapa[w,k].zamaluj)Olowek->Color=System::Drawing::Color::Red;
           plansza->DrawArc(Olowek,k*szerokosc,w*wysokosc,szerokosc,wysokosc,0,360);
	   }//koniec kółek
     }				 
return;}//koniec rysuj plansze

Aby plansza była odrysowywana przez system musimy umieścić ja w zdarzeniu Paint właściciela grafiki, czyli w naszym przypadku komponentu panel
 
kółko i krzyżyk gra visual studio c++ paint odświeżenie grafiki
 
Kod programu uzupełniamy o taką instrukcję

private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {
				 RysujPlansze();
		 }

Możemy skompilować program. Powinniśmy otrzymać widok jak poniżej
 
gra kółko i krzyżyk visual studio c++ plansza gry
 
Krok 6: Rysujemy znaki wstawiane przez graczy
 
Użyjemy do tego myszki, a więc musimy umieć odczytać współrzędne kliknięcia myszką w planszę. Współrzędne te musimy zamienić na wiersz i kolumnę dynamicznej tablicy, w której mamy zapisaną mapę gry. Odczyt wiersza i kolumny to część całkowita z dzielenia odpowiedniej współrzędnej X lub Y przez szerokość (dla X) i wysokość (dla Y). Funkcja, która to zrealizuje może wyglądać jak poniżej

 void WstawZnak(int k,int w){
       if(Mapa[w,k].zawartosc!=0)return;//wyskocz jak zajęte
       bool fRuch=false;
       if(jakiGracz){Mapa[w,k].zawartosc=KRZYZ;fRuch=true;}//wstaw krzyż
		 else{Mapa[w,k].zawartosc=KOLKO;fRuch=true;}//wstaw kółko
       Pen^ Olowek=gcnew Pen(System::Drawing::Color::Navy);
       Olowek->Width=3;
	 if(!jakiGracz){
		  Olowek->Color=System::Drawing::Color::Green;
		  plansza->DrawArc(Olowek,k*szerokosc,w*wysokosc,szerokosc,wysokosc,0,360);}
	  else{
	          plansza->DrawLine(Olowek,k*szerokosc,w*wysokosc,k*szerokosc+szerokosc,w*wysokosc+wysokosc);
		  plansza->DrawLine(Olowek,k*szerokosc+szerokosc,w*wysokosc,k*szerokosc,w*wysokosc+wysokosc);
	     }
       //zamien ruch graczy   
       if(fRuch)jakiGracz=!jakiGracz;
       return;
}//koniec WstawZnak

Wstawianie znaku wywołamy w zdarzeniu kliknięcia klawiszem myszki w panelu

private: System::Void panel1_MouseDown(System::Object^  sender, 
                                       System::Windows::Forms::MouseEventArgs^  e) {
				       // odczytaj pozycje klikniecia myszy w kolumnach i wierszach
				       int wspK= e->X/szerokosc;
				       int wspW= e->Y/wysokosc;
				       WstawZnak(wspK,wspW);
 			 }

Skompilowanie programu daje już efekt wstawiania kółek i krzyży
 
kólko i krzyżyk gra visual studio c++ rysowanie znaków
 
Krok 7 (ostatni): Zliczamy punkty
 
Musimy sprawdzić stany pionów, poziomów i przekątnych czy spełniony jest warunek wygrania gry. Najlepiej funkcję sprawdzania jest podpiąć pod zdarzenia klikania w planszę. Czyli umieścić we wcześniejszej funkcji WstawZnak(int k,int w). Dodatkowa funkcja sprawdzająca musi zawierać element rysowania ciągu symboli wygrywających. Ja w kodzie programu przyjąłem czerwony kolor zaznaczający taki ciąg. Funkcja również musi zawierać metodę wyświetlającą komunikat o wygraniu i pytanie czy powtórzyć grę czy zamknąć program. Oczywiście jest to propozycja rozwiązania, którą zawsze można zmodyfikować.
 
Kod poniższej funkcji na pewno można zrealizować rekurencyjnie, poniższe rozwiązanie stosuje pętle sprawdzające w zadanych kierunkach: obie przekątne, pion i poziom.

	void SkanujPlansze(int aK, int aW,int& aSuma, int Symbol){
			int StareK=aK;
			int StareW=aW;
            if(aSuma<SUKCES){
                        aSuma=0;
			CzyscOdwiedziny();			
			//Skanuj w kierunku SE-NW
			while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				if(!Mapa[aW,aK].odwiedzone){
					aSuma++;
			        Mapa[aW,aK].odwiedzone=true;
			        Mapa[aW,aK].zamaluj=true;
				    }
			   aW--;
			   aK--;
			   if(aK<0)break;
			   if(aW<0)break;
			  };
			//Skanuj w kierunku NW-SE
			aW=StareW;
			aK=StareK;
			while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				if(!Mapa[aW,aK].odwiedzone){
					aSuma++;
			        Mapa[aW,aK].odwiedzone=true;
			        Mapa[aW,aK].zamaluj=true;
				    }
			   aW++;
			   aK++;
			   if(aK>MaxKOLUMN-1)break;
			   if(aW>MaxWIERSZY-1)break;
			  };
			};
			//skanuj po drugiej przekątnej
			if(aSuma<SUKCES){
                        aSuma=0;
			CzyscOdwiedziny();
                         aW=StareW;
			 aK=StareK;
			  //Skanuj w kierunku NE-SW
			  while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				  if(!Mapa[aW,aK].odwiedzone){
					  aSuma++;
			          Mapa[aW,aK].odwiedzone=true;
			          Mapa[aW,aK].zamaluj=true;
				     }
			   aW++;
			   aK--;
			   if(aK<0)break;
			   if(aW>MaxWIERSZY-1)break;
			  };
			  //Skanuj w kierunku SW-NE
			  aW=StareW;
			  aK=StareK;
			  while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				  if(!Mapa[aW,aK].odwiedzone){
					  aSuma++;
					  Mapa[aW,aK].odwiedzone=true;
			          Mapa[aW,aK].zamaluj=true;
				     }
			   aW--;
			   aK++;
			   if(aK>MaxKOLUMN-1)break;
			   if(aW<0)break;
			  };
			}
			//nie ma sukcesu na przekątnej to szukaj w poziomie
			if(aSuma<SUKCES){
                        aSuma=0;
                        CzyscOdwiedziny();
                        //Skanuj w kierunku W-E
			 aW=StareW;
			 aK=StareK;
			 while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				 if(!Mapa[aW,aK].odwiedzone){
					  aSuma++;
					  Mapa[aW,aK].odwiedzone=true;
					  Mapa[aW,aK].zamaluj=true;
				     }
			   aK++;
			   if(aK>MaxKOLUMN-1)break;
			  };
			 //Skanuj w kierunku E-W
			 aW=StareW;
			 aK=StareK;
			 while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				 if(!Mapa[aW,aK].odwiedzone){
					 aSuma++;
					 Mapa[aW,aK].odwiedzone=true;
					 Mapa[aW,aK].zamaluj=true;
				    }
			   aK--;
			   if(aK<0)break;
			  };
			};//koniec skanowania w poziomie
			
			//nie ma sukcesu w poziomie, to szukaj w pionie
			if(aSuma<SUKCES){
                         aSuma=0;
			 CzyscOdwiedziny();
			 //Skanuj w kierunku N-S
			 aW=StareW;
			 aK=StareK;
			 while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				 if(!Mapa[aW,aK].odwiedzone){
					 aSuma++;
			                 Mapa[aW,aK].odwiedzone=true;
				         Mapa[aW,aK].zamaluj=true;
				    }
			   aW++;
			   if(aW>MaxWIERSZY-1)break;
			  };
			 //Skanuj w kierunku S-N
			 aW=StareW;
			 aK=StareK;
			 while(Mapa[aW,aK].zawartosc==Symbol && aSuma<SUKCES){
				 if(!Mapa[aW,aK].odwiedzone){
					  aSuma++;
			                  Mapa[aW,aK].odwiedzone=true;
			                  Mapa[aW,aK].zamaluj=true;
				      }
			   aW--;
			   if(aW<0)break;
			  };
			};//koniec skanowania w pionie
			if(aSuma!=SUKCES){CzyscOdwiedziny();}
		          else{
			   String^ wygral;
			   if(jakiGracz){wygral="Wygrał X\nPowtórzyć grę? (Zaczyna O)";}
			            else{wygral="Wygrał O\nPowtórzyć grę? (Zaczyna X)";}
			   //rysuj ścieżkę wygranej
			   RysujPlansze();
			   //pokaz komunikat
			   if(MessageBox::Show(wygral,"Koniec rozgrywki", 
			                   MessageBoxButtons::YesNo,MessageBoxIcon::Question)
				           ==System::Windows::Forms::DialogResult::No)Close();
			   else{ZerujTablice();
				    RysujPlansze();
				    aSuma=0;}
			   ;}
			 
           return;//
  }//koniec skanuj plansze

Modyfikujemy funkcję WstawZnak, czyli dopisujemy te linijki, które są wyróżnione zielonym kolorem.

 void WstawZnak(int k,int w){
       if(Mapa[w,k].zawartosc!=0)return;//wyskocz jak zajęte
       bool fRuch=false;
       if(jakiGracz){Mapa[w,k].zawartosc=KRZYZ;fRuch=true;}//wstaw krzyż
		 else{Mapa[w,k].zawartosc=KOLKO;fRuch=true;}//wstaw kółko
       Pen^ Olowek=gcnew Pen(System::Drawing::Color::Navy);
       Olowek->Width=3;
	 if(!jakiGracz){
		  Olowek->Color=System::Drawing::Color::Green;
		  plansza->DrawArc(Olowek,k*szerokosc,w*wysokosc,szerokosc,wysokosc,0,360);}
	  else{
	          plansza->DrawLine(Olowek,k*szerokosc,w*wysokosc,k*szerokosc+szerokosc,w*wysokosc+wysokosc);
		  plansza->DrawLine(Olowek,k*szerokosc+szerokosc,w*wysokosc,k*szerokosc,w*wysokosc+wysokosc);
	     }
       
//modyfikacja funkcji suma=0; if(jakiGracz){SkanujPlansze(k,w,suma,KRZYZ);} else{SkanujPlansze(k,w,suma,KOLKO);}
//zamien ruch graczy if(fRuch)jakiGracz=!jakiGracz; return; }//koniec WstawZnak

Po skompilowaniu powinniśmy uzyskać taki efekt działania aplikacji jak na pierwszym rysunku. Aplikacja nie ma warunku sprawdzającego remisu w przypadku gdy żaden z graczy nie uzyskał wymaganej ilości symboli w jednej linii i nie ma już wolnych pól dla kolejnego ruchu. Nie posiada również metody zapisu do pliku stanu i wyników gry. Wyzwaniem jest napisanie AI dla rozgrywającego komputera...
 
Adam Lasko Zbylitowska Góra 8.10.2012




strona główna
Delphi
symulacje fizyczne Delphi
Saper w Delphi7
VS C++ 2008 Express
Wstęp do środowiska VS C++ 2008
Kólko i krzyżyk gra w visual studio c++
Delphi, VS C++
Wstęp do algorytmów

Tworzenie witryn www aplikacja google
Walki robotów
diversity motorola solutions polska walki robotów

III LO Tarnów
2012-2014©Adam Lasko Zbylitowska Góra