[C++] Gra w statki - obiektowo


(Kanaliaon) #1

Witam, napisałem w C++ grę w statki obiektowo.

1 gracz, komputer ustawia losowo statki a my strzelamy.

Plansza : 16x16, rozmieszczanie statków: losowe (w dużej części udało mi się uniknąć krzyżowania i stykania statków), chociaż warunki na krzyżowanie i stykanie nie sa jeszcze idealne, sporadycznie programowi zdarza się stykać statki...

Statki : 2 czteromasztowce i 2 trójmasztowce, więcej nie dałem ze względu na rozbudowane warunki dot. nie krzyżowania się statków każdego z każdym...

Osobna klasa na maszty (każdy maszt wie, jakiego jest typu), plansze, 4-masztowce i 3-masztowce

Gra działa pod Windowsem i Linuxem (wystarczy zmienić jedna zmienna boolowską - ta pod "using namespace...")

Opcja testowania (widac statki) i gry (nie widać statków) - również zmiana jednej zmiennej na początku kodu.

Graficzna sygnalizacja trafienia (zmiana znaku masztu na "o") oraz zatopienia statku (zmiana znaków w obrębie statku na "\" )

Zabezpieczenie przed wprowadzeniem złych współrzędnych.

Program po każdym ruchu odświeża plansze i wypisuje ilość pływających statków.

Warunek końca programu: wszystkie statki zestrzelone lub użytkownik wciśnie "0".

Po zatopieniu wszystkich statków program wyświetla liczbę wykonanych ruchów (strzał 2 razy w to samo pole traktowany jest jako 2 ruchy itd).

Numerowanie linii w pionie i poziomie.

Szansa na trafny strzał: ok. 5,5% (obsadzenie planszy: 14/256 pol)

Szczątkowy polimorfizm.

Kod: http://wklej.org/id/217640/

Binarka: http://www.sendspace.pl/file/1860026a91867a9452c7f1b (skompilowane pod kątem Windowsa, na Linuxie wyświetlanie może się rozjeżdżać - trzeba by przekompilować ze zmianą zmiennej w 27 linii kodu).

Czekam na opinie :slight_smile:


(Piotr24 Power) #2

Świetna robota, prosty kod, ładnie zrobione przełączanie Windows / Linux, tryb testowy...

Należą Ci się gratulacje...

Przeglądam jeszcze kod, ale pięknie zrobione...

Zaobserwowałem jedną rzecz:

Podczas gry, gdy zatopisz statek dopiero w następnej "turze" rysowania zmienia się "o" na "/" wcześniej masz np. 4 x "o" zamiast "/"... Dzieje się tak nie za każdym razem, a licznik (w porównaniu do zmiany znaku na planszy) działa prawidłowo i pokazuje w tym momencie ilość pozostałych statków, gdy na planszy kiedy np. strzelasz do 3 masztowca (i zestrzeliłeś go, ale masz nadal jako nie do końca zestrzelony, bo plansza mimo kolejnego kółka nie zrobiła zmiany znaku) strzelasz dalej (myśląc, że to 4-ro masztowiec) masz puste trafienie, a znaki zmieniają się na trafione.

Wydaje mi się, że to błąd gdzieś w sprawdzaniu czy jest zatopiony, lecz ja nie powiem Ci dziś dokładnie gdzie jest błąd... Jutro zerknę na kod dokładnie i poprawie...

Tu masz zobrazowane:

Na 1. screenie widzisz, że statek to 3-ój masztowiec...

statkibd1b.png

Na 2. screenie widzisz, że zatopiłem cały statek, a on nie zmienił się na "/", lecz liczba pozostałych statków była podana dobrze...

statkibd1.png

Na 3. screenie widzisz, że przy kolejnym ruchu statek zmienił się na "/", choć powinien być taki na screenie numer 2.

statkibd3.png


(Fiołek) #3

OOP(jeśli to można tak nazwać :wink: ) do czegoś tak prostego to przesada, wskaźniki "***" zamiast dwuwymiarowej lub ciągłej tablicy(co jest o wiele lepszym rozwiązaniem).

DRY(funkcja statek::CzyPlywam powinna być zdefiniowana tylko raz, bo i tak niczym się nie różnią jej "wersje")!

Jeśli już stosować "polimorfizm" to statek::CzyPlywam powinna być czysto wirtualna i klasa statek powinna być abstrakcyjna.

Zamiast zmiennej "Linux" powinno być makro preprocesora i kompilacja warunkowa.

Niejednolite nazewnictwo, do tego mieszanie pl/ang. O ile sam komentarze piszę po polsku, a kod po angielsku, o tyle nie lubię jeśli ktoś kod pisze i po polsku, i po angielsku.

To tyle wytykania błędów.

Podoba mi się zastosowanie "?:" zamiast if-else. Zaciemnia kod, ale w sumie jest dość fajna ^^.

Zacznij pisać v2 stosując tylko jedno-(lub dwu-, jak wolisz)wymiarową tablicę charów, brak klas, tylko kilka pomocniczych funkcji i losowanie wszystkich statków. Do tego wyświetlanie planszy "na raz" :slight_smile:

To tyle rad z mojej strony.


(Kanaliaon) #4

Tak, wiem skąd to wynika (z tego, że do funkcji ruch przesyłam naraz tylko jeden z dwóch czteromasztowców i jeden z dwóch trójmasztowców, jak wyślę wszystkie statki do tej funkcji naraz, to kod się skomplikuje strasznie... Czyli robię tak na przemian: raz obsługa statku 1,3 a potem 2,4 i dlatego czasami "symulacja zatopienia / " odbywa się w następnym ruchu., bo np. w ruchu obsługującym statki 1,3 zatopimy statek 2 który jest obsługiwany w następnym ruchu...

W sumie próbowałem to naprawić, ale z niezbyt dobrym skutkiem...

@Fiołek:

To miał być kod obiektowy + wskaźniki. Takie było zadanie. Na tablicach myślę by się to dało łatwiej przeprowadzić... Po prostu tablica dwuwymiarowa 16x16 i kilka funkcji do operacji na niej...

Słusznie, dzięki.


(Piotr24 Power) #5

Dobrze jest... Grunt, że wszystko działa...

Naprawisz ten błąd pewnie niedługo :wink:

Pozdrawiam, Power :slight_smile:


(somekind) #6

Dlaczego?

OOP da się zastosować wszędzie, czy jakikolwiek inny paradygmat lepiej odzwierciedla rzeczywistość?

Moim zdaniem sekwencje if-else są w pewnym momencie dużo mniej czytelne niż operator ?. I zajmują 2 do 4 razy więcej miejsca.

Kwestia przyzwyczajenia do tych znaczków po prostu :slight_smile:

Hmm...

Opinia jest jedna - to nie jest obiektowo. Wiem, że użyłeś klas, ale to za mało, żeby mówić o OOP. Dobrze zaprojektowana klasa dobrze realizuje jedno zadanie. U Ciebie wszystko jest pomieszane, występują niepotrzebne klasy, są złamane reguły DRY, SRP i chyba KISS, może jeszcze parę innych.

Cała gra powinna kręcić się wokół klasy Plansza. Plansza raczej składa się z Pól (nie masztów - niby nic, ale dziwnie brzmi).

Pole może być w jednym z kilku stanów. Ty obecnie stany swoich masztów określasz przy użyciu liczby typu int i jakichś magicznych wartości... Nie sądzisz, że w tym wypadku lepszy byłby typ wyliczeniowy?

Plansza powinna mieć zatem tablicę Pól oraz drugą tablicę Statków. Bo Statki to drugi rodzaj obiektów powiązanych z Planszą. Plansza MA Pola i MA Statki. Oprócz tego Plansza powinna umieć losowo umieścić na sobie statki oraz określić efekt strzału użytkownika - zatem powinna mieć dwie publiczne metody do tych czynności. Jak określić efekt strzału? Ja bym spytał (delegował) o to Statki ze swojej listy. Bo jeśli nie trafiliśmy w statek, to znaczy, że jest pudło. Jeśli zaś trafiliśmy, to albo wciąż pływa albo jest zatopiony - stwierdzenie tego to zadanie statku.

Co do Statków - dobrze zaprojektowana klasa dobrze realizuje jedno zadanie. Czym różnią się klasy Torpedowiec i Pancernik od siebie pod względem zachowań? Niczym. Na dodatek większość kodu mają identyczną - programowanie metodą kopiuj-wklej jest bardzo złe... Wystarczyłaby jedna klasa Statek i kolejny enum określający typ statku czyli liczbę jego masztów.

Do tego potrzebna jest także klasa interfejsu z użytkownikiem potrafiąca pobrać od niego poprawne wartości oraz wyświetlić planszę na ekranie.

I jeszcze takie coś - cały program w jednym pliku to nie jest dobry pomysł.


(Fiołek) #7

Da się, ale dla tak prostych projektów jest to przerost formy nad treścią. Tutaj o wiele lepiej sprawdziłoby się programowanie strukturalne. Pisze się o wiele szybciej i kod jest zwięźlejszy.

Czy potrzeba w tym projekcie rozdzielać się na pole i na statki? O wiele bardziej czytelna byłaby jedna tablica charów [16][16] z polami: . - puste, o - statek, / - zatopione + ewentualnie x - pudło. Do tego kilka funkcji(sprawdzająca czy trafiono, pobierająca dane, losująca planszę) i zmiennych z danymi(ile strzałów, ile celnych, ile statków zostało).


(somekind) #8

Ale jeśli chcemy się nauczyć programowania obiektowego, to chyba nie tylko możemy ale i powinniśmy zaczynać od prostych projektów :slight_smile:

W takim razie powiedz od jak trudnego projektu jest sens programować obiektowo?

AFAIK programowanie obiektowe to także rodzaj strukturalnego. Chyba o proceduralne Ci chodzi?

Tylko, że gdy będziemy chcieli np. dodać gracza komputerowego, który będzie grał przeciwko nam albo zrobić wersję graficzną tej gry, to wersję proceduralną trzeba będzie napisać od nowa, a obiektową tylko rozbudować.

Myślę, że jeśli nie rozdzielimy, to obiektowo nie będzie.


(Fiołek) #9

Jeśli się uczyć obiektowości to na małych projektach, ale takich gdzie obiektowość jest wskazana i ułatwia pisanie(albo chociaż nie utrudnia).

Możliwe, nie jestem dobry w rozróżnianiu paradygmatów.

Taką wersje i tak trzebaby było przerabiać, bo ciężko cokolwiek tu zmienić a tym bardziej dodać.