Witam, ostatnio zacząłem uczyć się języka C++ z kursu „Od Zera Do Gier Kodera” i doszedłem do programu kółko krzyżyk.
main.cpp
#include <iostream>
#include <conio.h>
#include "game.h"
main()
{
StartGry();
for(;;)
{
RysujPlansze();
if (g_StanGry == GS_MOVE)
{
unsigned uNumerPola;
std::cin>>uNumerPola;
Ruch(uNumerPola);
}
else if (g_StanGry == GS_WON || g_StanGry == GS_DRAW) break;
}
getch();
}
game.cpp
// Właściwy kod gry
#include <iostream>
#include <ctime>
#include "game.h"
// zmienne
//--------
// plansza w postaci tablicy 3x3
FIELD g_aPlansza[3][3] = { { FLD_EMPTY, FLD_EMPTY, FLD_EMPTY },
{ FLD_EMPTY, FLD_EMPTY, FLD_EMPTY },
{ FLD_EMPTY, FLD_EMPTY, FLD_EMPTY } };
// stan gry (nie rozpoczęta, ruch gracza, wygrana lub remis)
GAMESTATE g_StanGry = GS_NOTSTARTED;
// znak aktualnego gracza (kółko lub krzyżyk)
SIGN g_AktualnyGracz;
//----------------------------------------------------------------------------------------
// funkcje
//--------
// funkcja wywoływana na starcie gry
bool StartGry()
{
// najpierw sprawdzamy, czy gra już nie trwa;
// jeżeli tak, to funkcja kończy się porażką
if (g_StanGry != GS_NOTSTARTED) return false;
// losujemy gracza, który będzie zaczynał
srand (static_cast<unsigned>(time(NULL)));
g_AktualnyGracz = (rand() % 2 == 0 ? SGN_CIRCLE : SGN_CROSS);
// ustawiamy stan gry na ruch graczy
g_StanGry = GS_MOVE;
// wszystko się udało, zatem zwracamy true
return true;
}
// rysowanie (albo raczej wypisywanie) pola gry
bool RysujPlansze()
{
// także sprawdzamy, czy aby na pewno gra rozpoczęłas ię
if (g_StanGry == GS_NOTSTARTED) return false;
// czyścimy okienko konsoli przy pomocy polecenia systemowego CLS
system ("cls");
// pokazujemy szyld tytułowy
std::cout << " KOLKO I KRZYZYK " << std::endl;
std::cout << "---------------------" << std::endl;
std::cout << std::endl;
// następnie wypisujemy właściwą planszę
// oczywiście, czynimy to przy pomocy dwóch pętli for
std::cout << " -----" << std::endl;
for (int i = 0; i < 3; ++i)
{
// lewa część ramki
std::cout << " |";
// wiersz
for (int j = 0; j < 3; ++j)
{
// sprawdzamy, czy dane pole jest puste
if (g_aPlansza[i][j] == FLD_EMPTY)
// wtedy wyświetlamy jego numerek
// tak, żeby użytkownik wiedział, jaką liczbę wpisać, gdy będzie
// chciał postawić na nim kółko lub krzyżyk :)
std::cout << i * 3 + j + 1;
else
// jeżeli natomiast pole jest zajęte,
// wyświetlamy odpowiadający mu znak;
// korzystamy tu ze sztuczki z odpowiednimi wartościami stałych typu
// FIELD; dzięki niej unikamy dodatkowego if'a (tudzież operatora ?:)
std::cout << static_cast<char>(g_aPlansza[i][j]);
}
// prawa część ramki
std::cout << "|" << std::endl;
}
std::cout << " -----" << std::endl;
std::cout << std::endl;
// wreszcie, pokazujemy dolny komunikat
// jest on albo prośbą o podanie ruchu, albo informacją o stanie gry
// wszystko zależy o tegoż stanu, a zatem stosujemy instrukcję switch
switch (g_StanGry)
{
case GS_MOVE:
// ruch gracza, a więc gra trwa
// musimy więc poprosić o ruch
std::cout << "Podaj numer pola, w ktorym" << std::endl;
std::cout << "chcesz postawic ";
std::cout << (g_AktualnyGracz == SGN_CIRCLE ? "kolko" : "krzyzyk") << ": ";
break;
case GS_WON:
// gra zakończona, ktoś wygrał :)
// wyświetlamy odpowiednią informację
std::cout << "Wygral gracz stawiajacy ";
std::cout << (g_AktualnyGracz == SGN_CIRCLE ? "kolka" : "krzyzyki") << "!";
break;
case GS_DRAW:
// może też się zdarzyć remis
// także pokazujemy wiadomość
std::cout << "Remis!";
break;
}
// wszystko poszło OK, zatem powiadamiamy o tym
return true;
}
// wykonanie ruchu i sprawdzenie jego rezultatu
bool Ruch(unsigned uNumerPola)
{
// znowuż musimy sprawdzić stan gry
if (g_StanGry != GS_MOVE) return false;
// następnie sprawdzamy, czy numer pola mieści się w przedziale <1; 9>
if (!(uNumerPola >= 1 && uNumerPola <= 9)) return false;
// jeśli doszliśmy dotąd, to wszystko jest w porządku i możemy wykonać ruch;
// przeliczamy więc numer pola na indeksy tablicy i przypisujemy znak gracza;
// (tylko wtedy, gdy pole jest puste)
// po raz któryś-tam z kolei używamy przy tym sztuczki z wartościami enumów
unsigned uY = (uNumerPola - 1) / 3;
unsigned uX = (uNumerPola - 1) % 3;
if (g_aPlansza[uY][uX] == FLD_EMPTY)
g_aPlansza[uY][uX] = static_cast<FIELD>(g_AktualnyGracz);
else
// jeżeli próbowano dwukrotnie postawić znak w tym samym miejscu,
// to niestety nie możemy na to pozwolić :)
return false;
// no i teraz następuje najbardziej zakręcona część programu :))
// (taaak, poprzednia linijka wcale nią nie była :DD)
// musimy mianowicie określić stan gry na podstawie planszy;
// mogło się przecież zdarzyć, że któryś gracz ułożył linię poziomą, pionową
// lub ukosną i wygrał, prawda? :) tutaj musimy to określić
// można to zrobić na kilka sposobów: najprostszy do wymyślenia, ale najbardziej
// pogmatwany jest zwykły ciąg instrukcji warunkowych, kontrolujących każde pole...
// sprytniejszą metodą jest jednak użycie tablicy przeglądowej i pętli for
const int LINIE[][3][2] = { { { 0, 0 }, { 0, 1 }, { 0, 2 } }, // górna pozioma
{ { 1, 0 }, { 1, 1 }, { 1, 2 } }, // środkowa pozioma
{ { 2, 0 }, { 2, 1 }, { 2, 2 } }, // dolna pozioma
{ { 0, 0 }, { 1, 0 }, { 2, 0 } }, // lewa pionowa
{ { 0, 1 }, { 1, 1 }, { 2, 1 } }, // środkowa pionowa
{ { 0, 2 }, { 1, 2 }, { 2, 2 } }, // prawa pionowa
{ { 0, 0 }, { 1, 1 }, { 2, 2 } }, // przekątna backslashowa
{ { 2, 0 }, { 1, 1 }, { 0, 2 } } }; // przekątna slashowa
FIELD Pole, ZgodnePole;
unsigned uLiczbaZgodnychPol;
for (int i = 0; i < 8; ++i)
{
// i przebiega po kolejnych możliwych liniach (jest ich osiem)
// zerujemy zmienne pomocnicze
Pole = ZgodnePole = FLD_EMPTY; // obie zmienne zostaną ustawione na FLD_EMPTY
uLiczbaZgodnychPol = 0;
for (int j = 0; j < 3; ++j)
{
// j przebiega po trzech polach w każdej linii
// pobieramy rzeczone pole
// to zdecydowanie najbardziej pogmatwane wyrażenie :)
Pole = g_aPlansza[LINIE[i][j][0]][LINIE[i][j][1]];
// jeżeli sprawdzane pole różni się od tego, które ma się zgadzać...
if (Pole != ZgodnePole)
{
// to zmieniamy zgadzane pole na to aktualne
ZgodnePole = Pole;
uLiczbaZgodnychPol = 1;
}
else
// jeśli natomiast oba pola się zgadzają, no to inkrementujemy
// licznik takich zgodnych pól
++uLiczbaZgodnychPol;
}
// teraz sprawdzamy, czy udało nam się zgodzić linię
if (uLiczbaZgodnychPol == 3 && ZgodnePole != FLD_EMPTY)
{
// jeżeli tak, no to ustawiamy stan gry na wygraną
g_StanGry = GS_WON;
// przerywamy pętlę i funkcję
return true;
}
}
// istnieje jeszcze możliwość, że nastąpił remis
// wtedy funkcja dojdzie tutaj i cała plansza będzie pokryta krzyżykami i kółkami
// sprawdzamy to przy pomocy odpowiedniej pętli, zliczającej zapełnione pola
// jeżeli jest ich tyle, ile wszystkich, no to kończymy remisem
unsigned uLiczbaZapelnionychPol = 0;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
if (g_aPlansza[i][j] != FLD_EMPTY)
++uLiczbaZapelnionychPol;
if (uLiczbaZapelnionychPol == 3*3)
{
// ustawiamy stan gry na remis
g_StanGry = GS_DRAW;
// kończymy funkcję
return true;
}
// oczywiście, może się zdarzyć, iż gra po prostu toczy się dalej
// w takim wypadku zmieniamy jedynie aktualnego gracza
g_AktualnyGracz = (g_AktualnyGracz == SGN_CIRCLE ? SGN_CROSS : SGN_CIRCLE);
// zwracamy true
return true;
}
game.h
enum SIGN {SGN_CIRCLE = 'O', SGN_CROSS = 'X'};
enum FIELD {FLD_EMPTY = 0, //puste;
FLD_CIRCLE = SGN_CIRCLE,
GLD_CROSS = SGN_CROSS};
enum GAMESTATE {GS_NOTSTARTED,
GS_MOVE,
GS_WON,
GS_DRAW};
extern GAMESTATE g_StanGry;
bool StartGry();
bool RysujPlansze ();
bool Ruch(unsigned);
I mam problem ze zrozumieniem jednej linijki, mianowicie:
Pole = g_aPlansza[LINIE[i][j][0]][LINIE[i][j][1]];
Nie rozumiem jak to do g_aPlansza jest przypisywana jakaś liczba przez [LINIE[i][j][0]][LINIE[i][j][1]]
Dlatego proszę o jak najbardziej dokładne wytłumaczenie. Pozdrawiam