Kółko i krzyżyk.

0

Witam.

Otóż mam oto taki programik "Kółko i krzyżyk"

main.cpp

 
#include <iostream> 
#include <conio.h> 
#include "game.h" 


void main()
{
    // rozpoczynamy grę
    StartGry();
    
    // pętla nieskończona - częsty element w grach
    // wyjdziemy z niej przy pomocy break w odpowiednim momencie
    for(;; )
    {
        // rysujemy planszę
        RysujPlansze();
        
        // sprawdzamy stan gry i czynimy odpowiednie działania
        if( g_StanGry == GS_MOVE )
        {
            // ruch któregoś gracza
            // pobieramy go, więc i wywołujemy funkcję Ruch()
            unsigned uNumerPola;
            std::cin >> uNumerPola;
            Ruch( uNumerPola );
        }
        else if( g_StanGry == GS_WON || g_StanGry == GS_DRAW )
        // koniec gry (wygraną lub remisem)
        // wtedy przerywamy pętlę
             break;
        
    }
    
    // czekamy na dowolny klawisz
    getch();
}

game.h

// Plik nagłówkowy właściwego kodu gry

// zabezpieczenie przez wielokrotnym dołączeniem
#pragma once

```cpp

// typy
//-----

// znak - czyli kółko lub krzyżyk :)
enum SIGN { SGN_CIRCLE = 'O', SGN_CROSS = 'X' };

// pojedyncze pole (może być puste, mieć kółko lub krzyżyk)
enum FIELD { FLD_EMPTY = 0, // puste
FLD_CIRCLE = SGN_CIRCLE, // kółko
FLD_CROSS = SGN_CROSS }; // krzyżyk

// stan gry
enum GAMESTATE { GS_NOTSTARTED, // nie rozpoczęta
GS_MOVE, // ruch gracza
GS_WON, // wygrana gracza
GS_DRAW }; // remis

// deklaracje zmiennych
//---------------------

// zmienna określająca stan gry musi być widoczna także na zewnątrz
// modułu game.cpp; poniższa deklaracja czyni zadość tej potrzebie

// stan gry
extern GAMESTATE g_StanGry;

// prototypy funkcji
//------------------

// rozpoczęcie gry
bool StartGry();

// narysowanie planszy
bool RysujPlansze();

// wykonanie ruchu
bool Ruch( unsigned );


i game.cpp

```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;
    
    
    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;
} 

I zadanie do niego brzmi:"W naszej grze w kółko i krzyżyk jest ukryta pewna usterka. Objawia się
wtedy, gdy gracz wpisze coś innego niż liczbę jako numer pola. Spróbuj naprawić
ten błąd; niech program reaguje tak samo, jak na wartość spoza przedziału
<1; 9>"

Pierwszą myślą było rzutowanie, ale jakoś mnie nie przekonało. W internecie wyczytałem o ciekawej funkcji atoi(), która konwertuje wartość zapisaną w łańcuchu napisów w liczbe całkowitą. Czy będzie to poprawne rozwiązanie? Ktoś może ma inny pomysł?

0

Wczytywanie w C++ odbywa się za pomocą obiektu cin więc wystarczy użyć opcji szukania.
Kontrola jest prymitywnie prosta:

int x;
if(cin>>x)
  {
   cout<<"poprawnie wprowadzono"<<endl;
  }
else
  {
   cin.clear();
   while(cin.get()!='\n') {}
   cout<<"blad wprowadzenia"<<endl;
  }
0

Zgadza się, jednakże program ma reagować tak samo jakby to była liczba z przedziału 1;9 czyli przypisać znak "O" bądź "X" do numeru pola.

0

Masz na dodatek problemy z czytaniem ze zrozumieniem, przeczytaj uważnie: - "... niech program reaguje tak samo, jak na wartość spoza przedziału <1; 9>"

0

Masz rację, źle zinterpretowałem treść zadania.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.