Strona główna » Visual Studio

Tag: Visual Studio

Bartosz Gala autor bloga devhobby.eu – pragnę powitać w serwisie programowanie z kotem na kolanach. Zapraszam do przeczytania moich artykułów o Visual Studio na blogu.

Projekt: tworzymy własną grę (7): Koniec gry i tablica wyników

Pierwsza część projektu             Poprzednia część projektu      

W tym odcinku kończy się przygoda z główną częścią gry, do której będziemy w następnych odcinkach dopisywać nowe funkcjonalności. Dodam dzisiaj jedną metodę do klasy Player oraz stworze osobną klasę obsługującą tylko i wyłącznie obsługę wyników gry w tym tabelę wyników i zapisywanie/odczyt wyników do pliku.

Tablica wyników

Najpierw nowa klasa, która obsłuży tablicę wyników. Zdecydowałem się umieścić ją osobno, bo to bardzo uniwersalny kawałek kodu, który może się przydać w innych projektach. Poniżej link do kodu, który zaraz będę omawiać: 

ScoreBoard.h + ScoreBoard.cpp

W tej klasie bardzo dużo pracy wykonuje konstruktor i destruktor. To w konstruktorze dochodzi do odczytu danych z pliku do tablicy wyników lub stworzenia pustej tablicy oraz następuje sortowanie wyników od najmniejszego, do największego. Zdecydowałem się na sortowanie „bąbelkowe” – jest to najprostsze i najszybsze w zapisie sortowanie, a w wypadku tylko 10 pozycji nie ustępuje szybkością innym, bardziej zaawansowanym algorytmom. Zastosowałem tu też najwygodniejszy i zarazem najbezpieczniejszy zapis kodu służący do odczytu/zapisu całej tablicy: for(auto& i: tab) i = 0– wypełnianie po kolei wszystkich komórek tablicy tab liczbą zero. Polecam zapamiętać ten zapis, bywa bardzo przydatny. Tablica wyników tworzona jest tylko dla wysokiej trudności gry, oczywiście można to zmienić albo na tej samej tablicy, albo tworząc dodatkowy plik wyników, lub zapisując dwie tablice wyników w jednym pliku, co możliwe, że w dalszej części tworzenia projektu zostanie wprowadzone.

Metoda showScoreBoard odpowiada za wyświetlanie wyników na ekranie oraz jeśli wynik gracza mieści się w pierwszej dziesiątce zapisuje ten wynik na ostatnim miejscu tablicy (czyli nadpisuje wynik, który opuszcza tabelę). 

W destruktorze dochodzi do zapisu tablicy wyników do pliku, a jeśli nie ma takiego pliku jest on tworzony i zapisywany. Zastosowana jest najprostsza forma zapisu pliku, bardzo użyteczna w przypadku małych ilości danych.

Koniec gry

Pora uzupełnić grę o zakończenie. Do klasy Player dodaje metodę o wdzięcznej nazwie gameOver i używam jej jako wyjście z funkcji startGame (plik game.cpp linia 40)  return p_player->gameOver(); zamykając w ten sposób główną pętlę gry. Jeszcze kilka drobnych poprawek, które z łatwością można znaleźć przy pomocy GitHuba i gra w wersji beta już gotowa (dla niektórych firm to już wersja Release, a cała reszta to DLC 😉 )

Link do aktualnej wersji gry na GitHub.

W następnej części zatrzymam się jeszcze nad kodem i postaram się go w miarę własnej wiedzy „udoskonalić”, szczególnie że w międzyczasie pisania sam nauczyłem się kilku nowych rzeczy, które warto by zastosować, oraz usłyszałem kilka uwag do kodu, które wypada wziąć pod uwagę. Tak więc, zamiast pisać DLC biorę się za Patch 0.2 🙂 

 

Inne:

W kodzie czasem w komentarzach używam na początku określenia TODO: – to bardzo wygodna opcja, dla osób korzystających z reShapera, taki komentarz podświetla się na niebiesko, oraz zaznacza na pasku bocznym reShapera, co bardzo pomaga odnaleźć takie miejsce.

Przyznam się do grzechu… uparłem się, zrobić interfejs graficzny gry przy pomocy biblioteki windows.h tak jak „Microsoft to wymyślił”. Wiem, że są łatwiejsze i bardziej eleganckie metody i że ten sposób to pisanie w C, a nie C++, ale niestety, założyłem się, że dam radę, więc nie mam wyjścia. Jak skończę, to opiszę moją walkę w osobnym artykule.

 

Następna część projektu

 

Projekt: tworzymy własną grę w (6)-2: Spacerem po grze

Pierwsza część projektu      Poprzednia część projektu

Dokończenie pierwszej części artykułu. Dzisiaj opiszę wreszcie obiecywany ruch gracza, oraz gra uzyska status alfa1, co znaczy, że będzie grywalna z wykorzystaniem podstawowych funkcji. Dzisiejszym zadaniem jest:

Poruszanie w grze.

W tym odcinku zostanie dodany ruch postaci pomiędzy obszarami wraz z pełną informacją opisową dla gracza. Na sam początek gra informuje gracza o tym, gdzie może iść, przy okazji przypominając zasady sterowania. Sprawdzone zostanie to, które drzwi są otwarte, więc można z nich skorzystać.

Następnie zostanie z klawiatury odczytana decyzja gracza i przekazana do metody klasy Player. Można to zrobić na dwa, w gruncie rzeczy identyczne sposoby: Klasycznie wykorzystując komendy if – else if:

lub wykorzystując operator ?:

Oba te sposoby są poprawne i działają identycznie. Dobrze jest znać oba te sposoby zapisu. Na tym etapie sprawdzane jest jaki klawisz został wciśnięty, analizowane są zarówno duże, jak i małe litery, oraz wykluczana jest możliwość przejścia przez drzwi zamknięte.

Na zakończenie należy dodać funkcjonalność, która wykorzysta tę informację do przemieszczenia gracza, dopisując odpowiednią metodę do klasy Player:

Oczywiście trzeba wpisać jeszcze wywołanie do funkcji i metody w główną pętlę gry. Po wykonaniu ruchu wszystko zaczyna się od nowa… albo kończy. Jeszcze brakuje właściwego zakończenia gry, ale ten moduł zostanie dopisany w następnej części. Można już poruszać się po obszarze gry, sprawdzić, czy opisy pomieszczeń, zamykanie drzwi i inne elementy działają. W następnej części: wynik gry oraz zapis i odczyt pliku. Dzięki temu uzyskamy w pełni grywalną grę, do której będzie można już tylko dodawać kolejne funkcjonalności. 

Link do projektu na GitHub (W kodzie „wykomentowane” są części kodu, które będą dołączone i omówione w następnej części).

Następna część projektu.

Visualki – ReShaper, pierwsze wrażenie.

Jeśli ktoś zapyta mnie, jaki addon do Visual Studio uważam za niezbędny, to wskażę go bez chwili zawahania się. Tak więc dzisiaj chcę napisać kilka słów o dodatku, o którym bardzo chcę za jakiś czas (gdy go znacznie lepiej poznam)  napisać dłuższy artykuł. Oto:

ReShaper C++ for Visual Studio.

Narzędzie znanej i dość lubianej, szczególnie w środowisku programistów Java firmy Jet Brains, na pierwszy rzut oka tylko powiela wbudowane funkcje Visual Studio, takie jak InteliSense. Na samym początku podchodziłem do tego rozszerzenia dość nieufnie i bez entuzjazmu. Jednak po kilku dniach korzystania moje zdanie o tym produkcie zmieniło się całkowicie. ReShaper nawet jeszcze dość nieudolnie wykorzystywany, czasem wręcz (o zgrozo) wspierany myszką, bardzo mocno przyśpiesza samo „klepanie” kodu, wspaniale pokazuje błędy i stylistyczne potknięcia, a co najważniejsze dla amatora: wspiera „ładne” kodowanie, dbając o spójność nazewnictwa, potrafi wskazać wiele złych stylistycznie miejsc kodu, podpowiada ciekawe rozwiązania. 

Dodatkowo jest to nieoceniony przyjaciel przy re faktoringu kodu. Pozwala pewnie zmieniać nazwy zmiennych, klas czy funkcji w całym kodzie, przerabiać w kilka sekund całe bloki kodu i jednocześnie uniknąć głupich błędów i literówek. Opcje takie jak Rearrange code, potrafią wprowadzić co prawda zarówno sporo porządku, jak i totalny chaos w kodzie, ale czasem warto spróbować, jak wygląda porządek, według automatycznych skryptów :). Obecnie powoli nabieram wprawy w wykorzystywaniu podstawowych funkcji rozszerzenia, jednak obiecuje, że ten wpis to tylko „zajawka” kolejnych, już zdecydowanie praktycznych artykułów o tym narzędziu.

Narzędzie możemy pobrać na stronie producenta i przez 30 dni cieszyć się darmową wersją. Potem musimy albo zapłacić 89 euro za pierwszy rok (lub 8.9 za miesiąc), albo sprawdzić, czy nie możemy się ubiegać o licencję darmową lub zniżkową, w ramach programów edukacyjnych lub Open Source. 

Osobiście bardzo polecam przetestowanie ReShapera, bo na prawdę warto.

Tym razem wyjątkowo: Ważne skróty klawiaturowe dla ReShaper:

  • Alt + Enter          szybka naprawa, “Swiss Army knife”
  • Alt + Insert         generowanie kodu
  • Ctrl + Shift + R     kontekstowy refactoring
  • Ctrl + N             nawigacja/wyszukiwanie
  • Ctrl + Alt + G       nawigacja, zależnie od kontekstu
  • Alt + P              parametry metod (wszystkie przeciążenia)
  • Ctrl + Alt + H       hierarchia obiektów, dziedziczenia, itp
  • Ctrl + F11           struktura pliku: sortowanie ręczne, refaktoring, otaczanie fragmentów,
  • Ctrl + Space         uzupełnianie składni, dodatkowo podpowiada nazwy zmiennych
  • Ctrl + Shift + Space kontekstowe podpowiedzi
 

Projekt: tworzymy własną grę w (6)-1: przygotowanie obszaru gry.

Pierwsza część projektu      Poprzednia część projektu

Tym razem artykuł zabrał mi ciut więcej czasu… po pierwsze święta, ale przede wszystkim dość przełomowy moment, podczas którego trzeba by omówić bardzo dużo kodu, za dużo jak na jeden wpis. Tak więc po raz pierwszy dzielę jeden wpis na dwie dość nierozerwalne części i umieszczę je w odstępie tylko kilku dni od siebie. Dzisiaj pozmieniamy to i owo w klasach, i przedstawimy reguły gry obowiązujące w pierwszej, uproszczonej wersji. Niestety w brew obietnicą z poprzedniego wpisu, poruszanie się wprowadzimy dopiero w drugiej części tego artykułu.

Przygotowanie obszaru gry.

Najpierw dodamy nowe metody do klasy player. Nie są one konieczne, ale bardzo uproszczą kod zarówno teraz, jak i później, podczas dodawania „eventów” do gry. Nowe metody to bardzo proste odmiany seterów, jednak nie ustawiają one nową wartość zmiennej, a ją modyfikują:

We właściwym kodzie obie metody zostaną zapisane w jednej linijce, co prawda przy obecnych kompilatorach nie ma to większego znaczenia, ale wizualnie będzie wyglądać znacznie ładniej.

Teraz informacja o celu gry. Jest wyświetlana tylko raz, na samym początku rozgrywki i jeśli gracz, mimo ostrzeżenia nie zapamięta opisu wyjścia, będzie się poruszać po świecie gry zupełnie po omacku.

Zaraz po opisie, w tej samej funkcji wprowadzam  zmiany podczas wejścia na obszar, oraz to tutaj, w następnym artykule dopisze wszystkie pozostałe wywołania funkcji w grze. Dodaje też metodę:  void Player::change_addOne_PlayerRounds() { playerRounds++; }Jak wynika z kodu, jest to metoda klasy Player (i tam ją też zapisuje). Na końcu pojawia się górny „pasek statusu” gry, na którym wyświetlane są najważniejsze parametry gracza.

Przy okazji znalazłem błąd w pliku player.cpp: w konstruktorze były ustawione w 2 ostatnich linijkach do zapisu komórki tablicy 4,5 zamiast 6,7, co powodowało wyjście poza zakres w aktualnie pisanym kodzie i błąd dopiero podczas wykonywania programu. 

Na koniec najdłuższa funkcja całej gry: wyświetlanie opisu aktualnego obszaru gry. Pierwsza część opisowa nie wymaga większego wyjaśnienia. Część „graficzna” przedstawia szkic pola gry z ukazaniem tego, które drzwi są otwarte, a które zamknięte. Osoby chętne, mogą wprowadzić trzeci poziom trudności, w którym opis graficzny nie będzie wyświetlany (to 2-3 linijek kodu więcej). Na sam koniec, kilka linijek opisu wpływu pomieszczenia na gracza. 

Link do funkcji opisującej obszar.

 

Proszę pamiętać, że przez samą zmianę treści napisów, można bardzo poważnie zmienić klimat gry. Ja koncentruje się na bardzo sterylnych i krótkich opisach, jednak nic nie stoi na przeszkodzie, aby to zmienić samemu. Można też w ramach wprawy tworzyć grę np. po angielsku.

Wyjątkowo w tym odcinku nie podaje linka do GitHuba z kodem końcowym, dopiero w następnym odcinku umieszczę tam kod wstępnie grywalnej gry (alfa 1). Jednocześnie zachęcam do samodzielnego eksperymentowania i połączenia podanych dzisiaj fragmentów kodu w całość.

Zapraszam do następnej części projektu już wkrótce.

Projekt: tworzymy własną grę (5) – pierwsze uruchomienie szkieletu gry.

Pierwsza część projektu             Poprzednia część projektu      

Dzisiaj po raz pierwszy gra da oznaki życia. Jednak zanim do tego dojdzie, czeka nas sporo kodowania, ale najpierw kilka słów na boku:

Podążając za moim projektem, możesz się sporo sam nauczyć. Mój kod nie jest doskonały, sam jestem świeżym samoukiem, więc zachęcam do zastanowienia się, jak napisać to lepiej, przetestowania swojego pomysłu i podzielenia się tym z innymi. Nie chodzi mi tu oczywiście, o zmiany w stylu „dodaj using namespace” lub „zapisz zmienne nie jako jakasZmienna, ale jakas_zmienna”, tylko o zmiany dające realną poprawę jakości kodu i gwarantuje ci, że jeśli się postarasz, to ci się uda. Tak więc śmiało, zachęcam do dawania w komentarzach własnych poprawek, linków do gista, i dyskusji, bo w ten sposób można nauczyć się wiele, wiele więcej.

Opuszczam pliki klasy i tworzyę osobną parę plików: „game.h” i „game.cpp”, gdzie przenoszę się z naszego głównego pliku z pisaniem całej głównej części gry. Plik z funkcją main() najlepiej, jeżeli nie zawiera nic więcej, a sama funkcja zawiera jak najmniej kodu. Tak jest ładniej i przejrzyściej. Od tego momentu w głównym pliku nie zmieni się już prawie nic… no, może poza opcjonalnym (obiecanym w zeszłym odcinku) wyniku działania Ctrl+C, Ctrl+V 🙂 i ustawianiem wielkości świata gry.

Pierwsza sprawa to przekazanie do funkcji wszystkich danych, czyli wskaźnika do „tablicy świata” i „gracza”. Oczywiście będzie to wymagało dołączenia (#include) plików klas zarówno do pliku .h, jak i .cpp, inaczej zostanie utracony zasięg i może dojść do sytuacji (przy braku dołączenia klas w pliku .cpp), że błędy zaczną się sypać dopiero w momencie kompilacji. Należy też pamiętać, że program operuje na wskaźnikach, więc składnia: obiekt.metoda nie zadziała i trzeba użyć składni w_obiekt -> metoda.  Przypominam, że bardzo dużo informacji zawarłem w komentarzach w kodzie, chwilami jest nawet mniej kodu niż komentarza. W ten sposób można się bardzo łatwo wdrażać w rozumienie czyjegoś kodu (chociaż nie wyobrażam sobie tak „obłożonego komentarzami kodu w programie komercyjnym). To tyle spraw „organizacyjnych, teraz przechodzimy pisania kodu.

Pierwsze menu.

Malutka funkcja mająca na celu jedynie wyświetlenie tekstu powitalno-informacyjnego oraz pozwolić użytkownikowi wybrać trudność gry. Oczywiście, można by jeszcze w funkcji main() zaimplementować ustawianie wielkości świata gry, ale tę funkcjonalność zostawim sobie na odcinek, w którym gra będzie uzyskiwać ostatnie szlify.

Funkcja na Gist (długi kod, więc jak wcześniej zamieszczam link, a nie wstawiam okienko)

Następnie kilka linijek kodu, które zamykają wszystkie wyjścia poza teren gry i nazywają  pomieszczenia. Kodu, jak myślę, nikomu nie trzeba wyjaśniać.

void lockArea(Player* p_player, World** p_p_worldArr)
{
int p = 1; //numerator pokojow do opisu
const int xM = p_player->get_sizeX(); //wyciagamy z obiektu rozmiar gry, mozemy zastosowac odwolanie do metody w kodzie za kazdym razem,
const int yM = p_player->get_sizeY(); //ale tak bedzie to bardziej przejrzyste
for (int i = 0; i < xM; i++)
for (int j = 0; j < yM; j++)
{
p_p_worldArr[i][j].set_infoCell(p); //wprowadzamy przy okazji mumer pokoju (reszte roboty robi metoda set_infoCell()
p++;
//blokujemy drzwi zewnetrzne
if (i == 0) p_p_worldArr[i][j].set_exitCell(0, false); //zamykamy drzwi na "polnocy"
if (j == (yM – 1)) p_p_worldArr[i][j].set_exitCell(1, false); //zamykamy drzwi na "wschodzie"
if (i == (xM – 1)) p_p_worldArr[i][j].set_exitCell(2, false); //zamykamy drzwi na "poludniu"
if (j == 0) p_p_worldArr[i][j].set_exitCell(3, false); //zamykamy drzwi na "zachodzie"
}
}

view raw
game.cpp
hosted with ❤ by GitHub

Jeszcze implementacja wysokiej trudności gry. Różnica w wysokości zdrowia, puli czasu oraz, przede wszystkim zamykają się w każdym pokoju jedne drzwi. Na razie zostawiam sobie (głównie do testów) otwarte wszystkie drzwi na obwodzie świata (poza oczywiście wcześniej zamkniętymi drzwiami „na zewnątrz”. Na tym etapie, może się sporadycznie zdarzyć, że gra będzie niemożliwa do ukończenia. Jednak nie przejmuje się tym, ponieważ w finalnej grze takie zagrożenie zniknie, dzięki możliwości zdobycia kluczy otwierających pojedyncze drzwi. 

void hardMode(Player *p_player, World** p_p_worldArr)
//zamykamy jedno losowe przejscie w kazdym pokoju, z wylaczeniem pokojow zewnetrznych
//taki sposob losowania i ustawiania parametrow, pozwala na to, ze sporo drzwi bedzie nie do przejscia tylko w jedna strone.
{
for (int i = 1; i < (p_player->get_sizeX() – 1); i++)
for (int j = 1; j < (p_player->get_sizeY() – 1); j++)
{
int tmp = (rand() % 4); //wartosc tymczasowa, wykorzystywana w nastepnej linijce do okreslenia ktore drzwi zamknac
p_p_worldArr[i][j].set_exitCell(tmp, false);
}
}

view raw
game.cpp
hosted with ❤ by GitHub

Na zakończenie dodam kolejną funkcję, która rozwijana będzie dopiero w najbliższym odcinku. 

int gameLoop(Player * p_player, World ** p_p_worldArr)
{
std::cout << "Tu bedziemy grali!!" << std::endl;
_getch();
return 0;
}

view raw
game.cpp
hosted with ❤ by GitHub

Na koniec, jak obiecywałem, zupełnie nie wyjaśniając wklejam, wyszukany fragment kodu z Stack Overflow, odpowiada on za powiększenie okienka konsoli Windows, i tu uwaga: nie mam pojęcia, na których kompilatorach to zadziała, ale jest to część opcjonalna, tylko dla dalszej wygody.  Czyste małpie kopiuj-wklej w akcji. Do działania tego kodu potrzebujemy dołączyć bibliotekę windows.h odpowiadającą za zarządzanie oknami w systemie windows.

#include <windows.h>
…….
HWND console = GetConsoleWindow(); //zmiana wielkosci okna konsoli copy-paste – wazne, ze dziala, nie wazne jak 😉 (windows.h lib);)
RECT r;
GetWindowRect(console, &r);
MoveWindow(console, r.left, r.top, 900, 600, TRUE); // tu ustawiamy wysokosc i szerokosc okna konsoli … koniec copy-paste.

view raw
Main.cpp
hosted with ❤ by GitHub

A teraz zostało tylko skompilować i cieszyć się pierwszymi znaczkami wyświetlonymi na konsoli. W następnej części zajmiemy się ruchem postaci, będzie troszkę kodu bardzo czułego na jakiekolwiek błędy, oraz kilka słów o tym, jak sobie radzić z opisywaniem zjawisk przestrzennych w kodzie. Poniżej link do całego (na obecny moment) projektu na Git hubie. Jeśli korzystasz z Visual Studio 17, to na 100% się kompiluje 🙂 W wypadku innych kompilatorów sprawdź zaznaczone elementy i usuń, lub zmień na kompatybilne. (albo poczekaj, po napisaniu całej gry spróbuje ją poprawić tak, żeby kompilowała się również dla Linuksa, co jednocześnie powinno zlikwidować większość problemów.) 

Commit dla artykułu na GitHub.

Następna część projektu

Projekt: tworzymy własną grę (4) – gracz, czyli kolejny obiekt.

Pierwsza część projektu        Poprzednia część projektu

Pora przyśpieszyć 🙂 

Dane Gracza.

Obiekt klasy Player, jak ją nazwałem, ma „wiedzieć” o wszelkich sprawach jego dotyczących. Musi przechować informację o aktualnym położeniu, lokacji startowej i końcowej, swojego zdrowia, czasu, jaki pozostał do końca gry i każdą inną informację, która ma znaczenie przez cały czas przebiegu gry. Zaczynam od przechowywania wszystkich współrzędnych. Jest tego troszkę, bo aż sześć zmiennych, po dwie współrzędne dla wejścia, wyjścia i obecnej lokacji. Możemy śmiało co prawda opisać to osobnymi zmiennymi, ale dużo klarowniej będzie skupić te wszystkie dane w jedną tablicę. Tu też po raz pierwszy nasze settery i gettery przestaną pełnić tylko funkcję izolacji zmiennych, ale też pozwolą nam zrozumieć, co to znaczy interfejs klasy. Read more