diff --git a/.gitignore b/.gitignore index 699531e..09b4cd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # EN: The following is a list of files/catalogues, which should be ommited from commiting to the git repository. # PL: W tym pliku zawarta została lista plików/ścieżek, które powinny zostać pominięte w commitach do repozytorium git. +Chinczyk188/.tablica_wynikow.csv.old # EN: Visual Studio-related directories. # PL: Katalogi związane z Visual Studio. diff --git a/Chinczyk188/Board.cpp b/Chinczyk188/Board.cpp new file mode 100644 index 0000000..fba0ed3 --- /dev/null +++ b/Chinczyk188/Board.cpp @@ -0,0 +1,177 @@ +#include "Board.hpp" +#include +#include +#include +#include "Engine.hpp" + +void Board::initVariables() { + + // Ustaw wartości na domyślne. + this->window = nullptr; + + // Wczytaj tło - bitmapę planszy. + // loadFromFile() zwraca True w przypadku pomyślnego + // wczytania tekstury. + if (!this->boardTexture.loadFromFile("res/sprites/board.png")) { + + // Jeśli nie uda nam się wczytać tekstury: + std::cerr << "Uwaga: Nie udalo sie wczytac " + "wymaganej tekstury planszy z \"res/sprites/board.png\"!\n" + "Upewnij sie, ze plik .exe jest we wlasciwym katalogu, " + "a gra zostala w pelni wypakowana wraz z folderem \"res\".\n"; + + sf::sleep(sf::seconds(10)); + this->window->close(); + } + +} + +void Board::initWindow() { + + // Dwuwymiarowy, wektor typu (u)nsigned int. + sf::Vector2u textureSize = this->boardTexture.getSize(); + + // Aby okno zmieściło się na większości wyświetlaczy, + // sprawmy, aby zajmowało ono około 450x450 pikseli, + // czyli dokładnie połowę z rozdzielczości tekstury planszy. + this->videoMode.width = textureSize.x / 2; + this->videoMode.height = textureSize.y / 2; + + // Stwórz okno. + this->window = new sf::RenderWindow( + this->videoMode, + "Okno planszy - Chinczyk188", + sf::Style::Default); + +} + +void Board::initBoard() { + + // Wczytaj wymaganą teksturę planszy + this->boardSprite.setTexture(this->boardTexture); + + // Ustaw pozycję grafiki. + this->boardSprite.setPosition(0.0f, 0.0f); +} + +void Board::initView() { + + // Ze względu na to, że długość krawędzi okna jest dwa razy mniejsza + // od tekstury planszy, powinniśmy to zrównoważyć powiększając + // widok o tą samą wartość. + sf::Vector2f viewSize( + static_cast(2 * this->videoMode.width), + static_cast(2 * this->videoMode.height) + ); + + this->view.setSize(viewSize); + this->view.setCenter(viewSize.x / 2.0f, viewSize.y / 2.0f); + + // Ustawiamy widok na wyznaczone wartości. + this->window->setView(this->view); + +} + +void Board::handleResize(unsigned int newWidth, unsigned int newHeight) { + + // Aby zapobiec sytuacji, w której po zmianie rozmiaru + // okno byłoby większe od rozdzielczości ekranu, + // niech krawędź okna wynosi minimum z nowej szerokości i wysokości, + // zachowując przy tym proporcję 1:1 planszy. + unsigned int newSize = std::min(newWidth, newHeight); + this->window->setSize(sf::Vector2u(newSize, newSize)); + +} + +bool Board::running() const { + + // Akcesor dla isOpen() + return this->window->isOpen(); + +} + +void Board::pollEvents() { + + while (this->window->pollEvent(this->ev)) { + + switch (this->ev.type) { + + case sf::Event::Closed: + this->window->close(); + break; + + case sf::Event::Resized: + this->handleResize(this->ev.size.width, this->ev.size.height); + break; + + case sf::Event::KeyPressed: + if (this->ev.key.code == sf::Keyboard::Escape) + this->window->close(); + break; + + } + } + +} + +void Board::update() { + + // Obecnie wrapper dla metody + // pollEvents(), nasłuchiwanie wydarzeń. + this->pollEvents(); + +} + +bool Board::manualUpdate() { + + this->update(); + this->render(); + + bool retval = this->window->pollEvent(this->ev); + this->lastEvent = this->ev; + + return retval; +} + +void Board::render() { + + // Mechanizm renderowania + + // Czyszczenie ekranu + this->window->clear(sf::Color::White); + // Rysowanie tła + this->window->draw(this->boardSprite); + + + + // Mechanizm wyświetlania + // W celu wyświetlenia na ekranie wyniku renderowania + this->window->display(); + +} + +void Board::updateAndRender() { + + this->render(); + this->update(); + +} + +void Board::runAsThread() { + + //this->thread = std::thread([this]() {this->updateAndRender();}); + //this->threads.push_back(std::thread([this]() { this->updateAndRender(); })); + // this->thread = std::thread(&Board::updateAndRender, this); + //this->threads.front().launch(); + +} + +void Board::closeWindow() { + + this->window->close(); + +} + +void Board::run() { + +} \ No newline at end of file diff --git a/Chinczyk188/Board.hpp b/Chinczyk188/Board.hpp new file mode 100644 index 0000000..3021fb7 --- /dev/null +++ b/Chinczyk188/Board.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class Board { + + private: + + // Wskaźnik na okno. + sf::RenderWindow* window; + sf::VideoMode videoMode; + + sf::Texture boardTexture; + sf::Sprite boardSprite; + + public: + + // Aby przekazać je do Engine + sf::Event ev; + sf::View view; + + // Konstruktor, destruktor + void closeWindow(); + + // Akcesor + bool running() const; + + //Board(): thread(&Board::updateAndRender, this) {}; + + // Metody klasy + void pollEvents(); + void update(); + void render(); + void updateAndRender(); + void run(); + bool manualUpdate(); + sf::Event lastEvent; + //std::vector threads; + //std::vector threads; + //sf::Thread thread; + void runAsThread(); + + // Sterowanie oknem + void initVariables(); + void initWindow(); + void initBoard(); + void initView(); + void handleResize(unsigned int newWidth, unsigned int newHeight); + +}; \ No newline at end of file diff --git a/Chinczyk188/Chinczyk188.vcxproj b/Chinczyk188/Chinczyk188.vcxproj index d979855..500fa90 100644 --- a/Chinczyk188/Chinczyk188.vcxproj +++ b/Chinczyk188/Chinczyk188.vcxproj @@ -102,14 +102,16 @@ Level3 true - SFML_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;SFML_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true $(ProjectDir)res\SFML\include;%(AdditionalIncludeDirectories) + stdcpp17 + stdc17 Console true - sfml-graphics-s-d.lib;opengl32.lib;freetype.lib;sfml-window-s-d.lib;winmm.lib;gdi32.lib;sfml-system-s-d.lib;%(AdditionalDependencies) + sfml-graphics-s-d.lib;opengl32.lib;freetype.lib;sfml-window-s-d.lib;winmm.lib;gdi32.lib;sfml-system-s-d.lib;sfml-audio-s-d.lib;openal32.lib;flac.lib;ogg.lib;vorbis.lib;vorbisenc.lib;vorbisfile.lib;%(AdditionalDependencies) $(ProjectDir)res\SFML\lib;%(AdditionalLibraryDirectories) @@ -119,21 +121,36 @@ true true true - SFML_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;SFML_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true $(ProjectDir)res\SFML\include;%(AdditionalIncludeDirectories) + stdc17 + stdcpp17 Console true true true - sfml-graphics-s.lib;opengl32.lib;freetype.lib;sfml-window-s.lib;winmm.lib;gdi32.lib;sfml-system-s.lib;%(AdditionalDependencies) + sfml-graphics-s.lib;opengl32.lib;freetype.lib;sfml-window-s.lib;winmm.lib;gdi32.lib;sfml-system-s.lib;sfml-audio-s.lib;openal32.lib;flac.lib;ogg.lib;vorbis.lib;vorbisenc.lib;vorbisfile.lib;%(AdditionalDependencies) $(ProjectDir)res\SFML\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + diff --git a/Chinczyk188/Chinczyk188.vcxproj.filters b/Chinczyk188/Chinczyk188.vcxproj.filters index 56d4d70..11ba313 100644 --- a/Chinczyk188/Chinczyk188.vcxproj.filters +++ b/Chinczyk188/Chinczyk188.vcxproj.filters @@ -18,5 +18,40 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/Chinczyk188/Engine.cpp b/Chinczyk188/Engine.cpp new file mode 100644 index 0000000..d38d8cf --- /dev/null +++ b/Chinczyk188/Engine.cpp @@ -0,0 +1,215 @@ +#include "Engine.hpp" +#include +#include +#include +#include +#include +#include + +const char* colorNames[] = {"czerwony", "niebieski", "zolty", "zielony"}; + +Engine::Engine(): currentPlayerIndex(0) { + if (!this->dicerollBuffer.openFromFile("res/audio/wardoctor17_diceroll.ogg")) { + std::cerr << "Nie udalo sie zaladowac efektu dzwiekowego rzutu kostka.\n\n"; + this->dicerollLoaded = false; + } + + if (!this->pawnmoveBuffer.openFromFile("res/audio/PeteBarry_pawnmove.ogg")) { + std::cerr << "Nie udalo sie zaladowac efektu dzwiekowego ruchu pionkow.\n\n"; + this->pawnmoveLoaded = false; + } +} + +void Engine::addPlayer(const std::string& name, unsigned int seed, short color) { + this->players.emplace_back(name, seed, color); +} + +void Engine::initializeGame() { + int totalPawnsPerPlayer = 4; // 4 pionki na gracza + for (auto& player: players) { + player.initializePawns(totalPawnsPerPlayer); + } +} + +void Engine::startGame(Board board) { + initializeGame(); + this->board = board; +} + +void Engine::nextTurn() { + + this->turnCounter++; + std::cout << "-------------------- \n" + << this->turnCounter << "\n" + << "-------------------- \n"; + + Player& currentPlayer = players[currentPlayerIndex]; + std::cout << "Ruch gracza " << currentPlayer.getName() + << " (" << colorNames[currentPlayer.getColor()] << ").\n"; + + // Rzut kostką + if (dicerollLoaded) this->dicerollBuffer.play(); + int diceRoll = rollDice(currentPlayer.getSeed(), currentPlayer.getRollCount()); + std::cout << "Wylosowano " << diceRoll << ".\n"; + currentPlayer.incrementRollCount(); + sf::sleep(sf::milliseconds(1000)); + + // Wybieranie pionka + bool pickAPlace = false; + // Wylosowanie szóstki to specjalny przypadek + if (diceRoll == 6) { + if (currentPlayer.pawnsActive == 0) { + // Musi wyjść pionkiem + std::cout << currentPlayer.getName() << " wychodzi pierwszym pionkiem z bazy.\n"; + currentPlayer.pawnsAtBase--; + currentPlayer.pawnsActive++; + this->pawnmoveBuffer.play(); + sf::sleep(sf::milliseconds(2000)); + } else if (currentPlayer.pawnsActive > 0 && currentPlayer.pawnsActive < 4) { + + // Może wyjść z bazy albo ruszyć pionek + char choice; + + std::cout << "Co chcesz zrobic?\n"; + std::cout << "a) Wyjsc pionkiem z bazy\n" + << "b) Wykonac ruch wyciagnietym wczesniej pionkiem\n" + << "> "; + + while (true) { + std::cin >> choice; + choice |= 32; + + if (choice == 'a') { + + pickAPlace = false; + std::cout << currentPlayer.getName() << " wychodzi " + << currentPlayer.pawnsActive + 1 << ". pionkiem z bazy.\n"; + currentPlayer.pawnsAtBase--; + currentPlayer.pawnsActive++; + this->pawnmoveBuffer.play(); + sf::sleep(sf::milliseconds(2000)); + break; + + } else if (choice == 'b') { + + if (currentPlayer.pawnsActive > 1) { + pickAPlace = true; + } else { + std::cout << currentPlayer.getName() << " rusza jedyny pionek " + << diceRoll << " pol do przodu.\n"; + this->pawnmoveBuffer.play(); + sf::sleep(sf::milliseconds(2000)); + } + break; + + } else std::cout << "Podaj jedna z wymienionych odpowiedzi!\n> "; + } + + } else if (currentPlayer.pawnsActive == 4) { + // Musi ruszyć pionek, o ile może + // TODO + pickAPlace = true; + this->pawnmoveBuffer.play(); + } + } else { + // Musi ruszyć któryś z pionków + pickAPlace = true; + if (currentPlayer.pawnsActive == 1) { + // Jedyny pionek możemy ruszyć za niego + pickAPlace = false; + std::cout << currentPlayer.getName() << " rusza jedyny pionek " + << diceRoll << " pol do przodu.\n"; + this->pawnmoveBuffer.play(); + sf::sleep(sf::milliseconds(2000)); + } + + } + + if (currentPlayer.pawnsActive > 1 && pickAPlace) { + std::cout << "Wybierz prosze pionek na planszy ktorym chcesz wykonac ruch.\n"; + } + + // Użytkownik wybiera pionek na planszy, którym chce się ruszyć + int pickStatus = 0; + if (currentPlayer.pawnsActive == 0 || !pickAPlace) pickStatus = 8; // pomiń pętlę + while (pickStatus != 8) { + + while (board.manualUpdate()) { + + sf::Event currentEvent = board.lastEvent; + std::cout << "this-evtype: " << currentEvent.type << "\n"; + switch (currentEvent.type) { + + case sf::Event::Closed: + board.closeWindow(); + // Zamknięcie okna powinno wyjść z tej pętli + pickStatus = 8; + break; + + case sf::Event::MouseButtonPressed: + std::cout << "pretend i'm selecting a piece\n"; + pickStatus = 4; + break; + + case sf::Event::MouseButtonReleased: + std::cout << "pretend i'm releasing a piece\n"; + this->pawnmoveBuffer.play(); + sf::sleep(sf::seconds(1.0f)); + if (pickStatus == 4) pickStatus = 8; + break; + + case sf::Event::Resized: + std::cout << "resized triggered: " << currentEvent.size.width << " " << currentEvent.size.height << "\n"; + board.handleResize(currentEvent.size.width, currentEvent.size.height); + break; + + } + } + } + + // TEST + // currentPlayer.tryMovingPawn(0, 0); + + // if (!pickAPlace) { + // std::cout << "Brak ruchu.\n"; + // } + + if (currentPlayer.hasWon()) { + // Zostanie wykorzystane do zwiększenia liczby wygranych + this->winnerNickname = currentPlayer.getName(); + announceWinner(currentPlayer); + } else { + // Iteruj po wektorze z graczami + currentPlayerIndex = (currentPlayerIndex + 1) % players.size(); + } + + sf::sleep(sf::milliseconds(1000)); + std::cout << "\n"; + return; +} + +// Deterministycznie (lub nie) generuj kolejne rzuty kostką +int Engine::rollDice(unsigned int seed, unsigned int rollNumber) { + // Podane ziarno gracza oraz numer losowania zostaną użyte + // do wylosowania liczby od 1 do 6. + std::mt19937 rng(seed + rollNumber); + std::uniform_int_distribution dist(1, 6); + + return dist(rng); +} + +bool Engine::isGameOver() const { + for (const auto& player: players) { + if (player.hasWon()) { + return true; + } + } + return false; +} + +void Engine::announceWinner(const Player& player) const { + std::cout << "\n\n--------------------\n" + << "Koniec gry!\n" + << player.getName() + << " (" << colorNames[player.getColor()] << ") wygrywa!\n"; +} \ No newline at end of file diff --git a/Chinczyk188/Engine.hpp b/Chinczyk188/Engine.hpp new file mode 100644 index 0000000..b9929ee --- /dev/null +++ b/Chinczyk188/Engine.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "Board.hpp" +#include "Player.hpp" +#include +#include +#include +#include +#include +#include +#include + +class Engine { + public: + Engine(); + + // Przygotowanie zmiennych + void addPlayer(const std::string& name, unsigned int seed, short color); + void initializeGame(); + + // Pętla gry + void startGame(Board board); + bool isGameOver() const; + + // Logika gry + void nextTurn(); + int rollDice(unsigned int seed, unsigned int moveNumber); + + // Audio + bool dicerollLoaded = true; + bool pawnmoveLoaded = true; + sf::Music dicerollBuffer; + sf::Music pawnmoveBuffer; + + // Pomocnicze + std::string winnerNickname = ""; + unsigned int turnCounter = 0; + + // Z Game + Board board; + + private: + std::vector players; + short currentPlayerIndex; + + // Pomocnicze + void announceWinner(const Player& player) const; +}; \ No newline at end of file diff --git a/Chinczyk188/Game.cpp b/Chinczyk188/Game.cpp index 82ed9dc..2c69fe7 100644 --- a/Chinczyk188/Game.cpp +++ b/Chinczyk188/Game.cpp @@ -1,71 +1,410 @@ #include "Game.hpp" +#include +#include #include +#include +#include +#include +#include // sprawdzenie, czy plik istnieje +#include "ssp.hpp" // nagłówkowy parser plików .csv +#include "Engine.hpp" + +void Game::printGameWelcomeText() const { + + std::cout << "\n" + " .o88b. db db d888888b d8b db .o88b. d88888D db db db dD db .d888b. .d888b.\n" + "d8P Y8 88 88 `88' 888o 88 d8P Y8 YP d8' `8b d8' 88 ,8P' o88 88 8D 88 8D\n" + "8P 88ooo88 88 88V8o 88 8P d8' `8bd8' 88,8P 88 `VoooY' `VoooY'\n" + "8b 88~~~88 88 88 V8o88 8b d8' 88 88`8b 88 .d~~~b. .d~~~b.\n" + "Y8b d8 88 88 .88. 88 V888 Y8b d8 d8' db 88 88 `88. 88 88 8D 88 8D\n" + " `Y88P' YP YP Y888888P VP V8P `Y88P' d88888P YP YP YD VP `Y888P' `Y888P'\n" + "\n"; + + std::cout << "Witaj w Chinczyku!\nAutor: B.Z. (2 EF-DI, L01, 177188)\n"; + std::cout << "Kod zrodlowy: https://gitea.7o7.cx/sherl/Chinczyk188 na licencji GPLv3\n"; + std::cout << "Instrukcja gry powinna zostac dolaczona do tej kopii gry, w przeciwnym razie\n" + "jest ona dostepna w repozytorium git.\n\n"; + +} + +void Game::readLeaderboards(std::string leaderboardLocation) { + + try { + + // Spróbuj wczytać plik z zapisanymi wynikami + ss::parser p{leaderboardLocation, " "}; + + for (const auto& [csvName, csvWins]: p.iterate()) { + this->leaderboardName.push_back(csvName); + this->leaderboardWins.push_back(csvWins); + } + + } + catch (ss::exception& e) { + + // Jeżeli nie udało się wczytać pliku, spróbuj wczytać kopię zapasową + if (std::filesystem::exists("." + leaderboardLocation + ".old")) { + + ss::parser p{"." + leaderboardLocation + ".old", " "}; + + for (const auto& [csvName, csvWins]: p.iterate()) { + this->leaderboardName.push_back(csvName); + this->leaderboardWins.push_back(csvWins); + } + + // Przekopiuj po cichu (verbose=false) dane do pliku głównego + this->dumpLeaderboards(leaderboardLocation, false); + + return; + + } + + std::cerr << "Wystapil blad przy probie wczytania listy wynikow. \n" + "Gra sprobuje naprawic blad poprzez utworzenie nowego pliku z lista wynikow.\n" + "Nacisnij CTRL+C w przeciagu 10 sekund aby anulowac i wyjsc z programu.\n"; + sf::sleep(sf::seconds(15)); + + // Utwórz plik + std::ofstream leaderboardFile; + leaderboardFile.open(leaderboardLocation); + leaderboardFile << "\n"; + leaderboardFile.close(); + + // Przykładowe dane + this->leaderboardName.push_back("Anonymous"); + this->leaderboardWins.push_back(1); + + } + +} + +void Game::dumpLeaderboards(std::string leaderboardLocation, bool verbose) const { + + // Otwórz strumień wyjściowy do ścieżki leaderboardLocation + std::ofstream leaderboardFile; + leaderboardFile.open(leaderboardLocation); + for (int i = 0; i < (this->leaderboardName.size() & this->leaderboardWins.size()); i++) { + // Zapisz dane z wektora w formacie csv, spacja to separator + leaderboardFile << this->leaderboardName[i] << " " << this->leaderboardWins[i] << "\n"; + } + leaderboardFile.close(); + + // Zmienna verbose decyduje o tym, czy chcemy wyświetlić komunikat o zapisie + if (verbose) std::cout << "Zapisano dane do pliku " << leaderboardLocation << ".\n"; + +} + +unsigned int Game::updateLeaderboards(std::string winnerNickname) { + + bool updated = false; + unsigned int totalWins = 0; + + for (int i = 0; i < (this->leaderboardName.size() & this->leaderboardWins.size()); i++) { + + // Szukaj nick we wczytanych danych z pliku CSV + if (this->leaderboardName[i] == winnerNickname) { + + totalWins = ++this->leaderboardWins[i]; + updated = true; + break; + + } + + } + + if (!updated) { + // Jeżeli to pierwsza wygrana tego gracza, to dodaj go do wektora + this->leaderboardName.push_back(winnerNickname); + this->leaderboardWins.push_back(1); + totalWins = 1; + } + + // Zapisz po cichu zmiany do obu plików CSV + this->dumpLeaderboards(LEADERBOARD_FILE, false); + this->dumpLeaderboards(std::string(".") + LEADERBOARD_FILE + std::string(".old"), false); + + return totalWins; +} + +void Game::printLeaderboards() const { + + // Zamienia dwa wektory w parę wektorów + // https://stackoverflow.com/a/18479184 + std::vector> vectorPair(this->leaderboardName.size()); + for (unsigned int i = 0; i < vectorPair.size(); i++) { + vectorPair[i] = std::make_pair(this->leaderboardName[i], this->leaderboardWins[i]); + } + + // Sortuje malejąco parę wektorów biorąc pod uwagę wartości w tym drugim + // https://stackoverflow.com/a/279878 + std::sort(vectorPair.begin(), vectorPair.end(), [](auto &left, auto &right) { + return left.second > right.second; + }); + + for (const auto& [name, wins]: vectorPair) { + std::cout << "- " << name << ", " << wins << " wygranych\n"; + } + +} + +void Game::playStartTune() { + + // Jeżli plik welcome_alt.ogg istnieje, załaduj go, a potem odtwórz. + if (!this->soundBuffer1.openFromFile("res/audio/welcome_alt.ogg")) + return; + + // Aby dało się usłyszeć dźwięk, nie może on wyjść poza zakres (out of scope). + // Z tego powodu zapisujemy odnośnik do bufora w pamięci programu (w klasie Game). + this->soundBuffer1.play(); + +} void Game::initVariables() { + // Ustaw wartości na domyślne. this->window = nullptr; + this->leaderboardLocation = LEADERBOARD_FILE; + + // Wczytaj tło - bitmapę planszy. + // loadFromFile() zwraca True w przypadku pomyślnego + // wczytania tekstury. + if (!this->boardTexture.loadFromFile("res/sprites/board.png")) { + + // Jeśli nie uda nam się wczytać tekstury: + std::cerr << "Uwaga: Nie udalo sie wczytac " + "wymaganej tekstury planszy z \"res/sprites/board.png\"!\n" + "Upewnij sie, ze plik .exe jest we wlasciwym katalogu, " + "a gra zostala w pelni wypakowana wraz z folderem \"res\".\n"; + + sf::sleep(sf::seconds(10)); + this->window->close(); + } + + // Spróbuj wczytać listę wyników z pliku + this->readLeaderboards(leaderboardLocation); + // Zapisz po cichu (stąd false) kopię zapasową listy wyników + this->dumpLeaderboards("." + leaderboardLocation + ".old", false); } void Game::initWindow() { - this->videoMode = sf::VideoMode::getDesktopMode(); - this->videoMode.width /= 2; - this->videoMode.height /= 2; + // Dwuwymiarowy, wektor typu (u)nsigned int. + sf::Vector2u textureSize = this->boardTexture.getSize(); - this->window = new sf::RenderWindow(this->videoMode, "Chinczyk188", sf::Style::Default); + // Aby okno zmieściło się na większości wyświetlaczy, + // sprawmy, aby zajmowało ono około 450x450 pikseli, + // czyli dokładnie połowę z rozdzielczości tekstury planszy. + this->videoMode.width = textureSize.x / 2; + this->videoMode.height = textureSize.y / 2; + + // Stwórz okno. + this->window = new sf::RenderWindow( + this->videoMode, + "Okno planszy - Chinczyk188", + sf::Style::Default); + +} + +void Game::initBoard() { + + // Wczytaj wymaganą teksturę planszy + this->boardSprite.setTexture(this->boardTexture); + + // Ustaw pozycję grafiki. + this->boardSprite.setPosition(0.0f, 0.0f); +} + +void Game::initView() { + + // Ze względu na to, że długość krawędzi okna jest dwa razy mniejsza + // od tekstury planszy, powinniśmy to zrównoważyć powiększając + // widok o tą samą wartość. + sf::Vector2f viewSize( + static_cast(2 * this->videoMode.width), + static_cast(2 * this->videoMode.height) + ); + + this->view.setSize(viewSize); + this->view.setCenter(viewSize.x / 2.0f, viewSize.y / 2.0f); + + // Ustawiamy widok na wyznaczone wartości. + this->window->setView(this->view); + +} + +void Game::handleResize(unsigned int newWidth, unsigned int newHeight) { + + // Aby zapobiec sytuacji, w której po zmianie rozmiaru + // okno byłoby większe od rozdzielczości ekranu, + // niech krawędź okna wynosi minimum z nowej szerokości i wysokości, + // zachowując przy tym proporcję 1:1 planszy. + unsigned int newSize = std::min(newWidth, newHeight); + this->window->setSize(sf::Vector2u(newSize, newSize)); } Game::Game() { + //: + // thread(std::thread([this]() { + // while (this->running()) { + // this->update(); + // this->render(); + // sf::sleep(sf::milliseconds(100)); + // } })) { - this->initVariables(); - this->initWindow(); + // Konstruktor klasy. Klasę Game tworzymy poprzez + // ustawienie domyślnych wartości i stworzenie okna. + + this->board.initVariables(); + this->board.initWindow(); + this->board.initBoard(); + this->board.initView(); // widok musi być zainicjalizowany po oknie + + // Puść melodyjkę + this->playStartTune(); + + // Wyświetl witający tekst + this->printGameWelcomeText(); } Game::~Game() { - delete this->window; + // W destruktorze zapisujemy dane i usuwamy okno. + this->dumpLeaderboards(); + delete this->window; // alternatywnie: this->window->close(); } const bool Game::running() const { - return this->window->isOpen(); + // Akcesor dla isOpen() + //return this->window->isOpen(); + return this->board.running(); } void Game::pollEvents() { while (this->window->pollEvent(this->ev)) { + switch (this->ev.type) { + case sf::Event::Closed: this->window->close(); break; - case sf::Event::KeyPressed: - if (this->ev.key.code == sf::Keyboard::Escape) this->window->close(); + + case sf::Event::Resized: + this->handleResize(this->ev.size.width, this->ev.size.height); break; + + case sf::Event::KeyPressed: + if (this->ev.key.code == sf::Keyboard::Escape) + this->window->close(); + break; + } } + } void Game::update() { + // Obecnie wrapper dla metody + // pollEvents(), nasłuchiwanie wydarzeń. this->pollEvents(); } void Game::render() { - // EN: Render logic - // PL: Mechanizm renderowania - this->window->clear(sf::Color(255, 0, 0, 255)); + // Mechanizm renderowania + + // Czyszczenie ekranu + this->window->clear(sf::Color::White); + // Rysowanie tła + this->window->draw(this->boardSprite); - // PL: Mechanizm wyświetlania + // Mechanizm wyświetlania // W celu wyświetlenia na ekranie wyniku renderowania this->window->display(); +} + +void Game::run() { + + Engine engine; + board.updateAndRender(); + + // this->update(); + // this->render(); + + int numPlayers = 2; + std::cout << "Podaj prosze liczbe graczy (2-4):\n> "; + std::cin >> numPlayers; + std::cout << "\n"; + + if (numPlayers < 2) numPlayers = 2; + if (numPlayers > 4) numPlayers = 4; + + const char* colorNames[] = {"czerwony", "niebieski", "zolty", "zielony"}; + // Uzyskaj dane o użytkownikach + for (int i = 0; i < numPlayers; i++) { + + std::string name; + std::string str_seed = std::to_string(std::time(nullptr)); // czas + + std::cout << "Wpisz nazwe gracza " << (i + 1) << " (" << colorNames[i] << "): "; + std::cin >> name; + + #ifndef CHINCZYK188_IGNORE_USER_SEED + // Jeżeli nie została zdefiniowana flaga do ignorowania ziarna użytkownika + // (deterministyczne losowanie), to pozwól na wprowadzanie ziaren. + std::cout << "Wpisz ziarno gracza " << (i + 1) << " (" << colorNames[i] << "): "; + std::cin >> str_seed; + #endif + std::cout << "\n"; + + short color = static_cast(i); + unsigned int seed = std::hash{}(str_seed); + engine.addPlayer(name, seed, color); + + board.updateAndRender(); + //this->update(); + //this->render(); + } + + // Przekaż ev i window, aby obsługiwać zdarzenia + // w Engine + engine.startGame(this->board); + + // Główna pętla gry + while (board.running()) { + + board.updateAndRender(); + // this->update(); // do threadów + //this->render(); + + if (!engine.isGameOver()) { + engine.nextTurn(); + } else { + + // updateLeaderboard() inkrementuje i zwraca liczbę wygranych przekazanego gracza + std::cout << "To jego(/jej) " + << updateLeaderboards(engine.winnerNickname) << ". wygrana!\n"; + + std::cout << "--------------------\n\n" + << "Statystyki:\n"; + this->printLeaderboards(); + std::cout << "\n"; + + sf::sleep(sf::seconds(10.0f)); + board.closeWindow(); + + } + + } } \ No newline at end of file diff --git a/Chinczyk188/Game.hpp b/Chinczyk188/Game.hpp index a349fc9..8bf6e92 100644 --- a/Chinczyk188/Game.hpp +++ b/Chinczyk188/Game.hpp @@ -1,22 +1,59 @@ #pragma once +#define LEADERBOARD_FILE "tablica_wynikow.csv" +#include "Board.hpp" +#include #include +#include +#include +#include class Game { + private: + + // Wskaźnik na okno. sf::RenderWindow* window; sf::VideoMode videoMode; - sf::Event ev; + sf::Texture boardTexture; + sf::Sprite boardSprite; + + std::vector leaderboardName; + std::vector leaderboardWins; + sf::Music soundBuffer1; + Board board; + + // Metody void initVariables(); void initWindow(); + void initBoard(); + void initView(); + void playStartTune(); + void handleResize(unsigned int newWidth, unsigned int newHeight); public: + + // Aby przekazać je do Engine + sf::Event ev; + std::string leaderboardLocation; + sf::View view; + + // Konstruktor, destruktor Game(); - virtual ~Game(); + ~Game(); + // Akcesor const bool running() const; - void pollEvents(); + // Metody klasy + void pollEvents(); void update(); void render(); + void printGameWelcomeText() const; + void readLeaderboards(std::string leaderboardLocation); + void dumpLeaderboards(std::string leaderboardLocation = LEADERBOARD_FILE, bool verbose = true) const; + unsigned int updateLeaderboards(std::string winnerNickname); + void printLeaderboards() const; + void run(); + }; \ No newline at end of file diff --git a/Chinczyk188/Makefile b/Chinczyk188/Makefile index 84568e9..11e0206 100644 --- a/Chinczyk188/Makefile +++ b/Chinczyk188/Makefile @@ -3,13 +3,14 @@ # EN: Warning: mingw-w64-x86_64-gcc-14.1.0-3 is the last version of gcc, which supports wildcards ("*.cpp"). # PL: Uwaga: mingw-w64-x86_64-gcc-14.1.0-3 to ostatnia wspierana wersja gcc, w której można stosować wildcard'y ("*.cpp"). CC = "C:\\msys64\\mingw64\\bin\\g++.exe" -DEPS = -lsfml-graphics-s -lsfml-window-s -lsfml-system-s -lopengl32 -lwinmm -lgdi32 -DSFML_STATIC -#LINK = -L. +DEPS = -lsfml-graphics-s -lsfml-window-s -lsfml-audio-s -lopenal32 -lflac -lvorbisenc -lvorbisfile -lvorbis -logg -lsfml-system-s -lopengl32 -lwinmm -lgdi32 -lpthread +LINK = -L. -Lres/SFML/lib-mingw/ +FLAGS = -DSFML_STATIC # -DCHINCZYK188_IGNORE_USER_SEED OUTPUT = output.exe CPPSTD = c++17 default: - $(CC) -g "*.cpp" $(DEPS) $(LINK) -std=$(CPPSTD) -static -static-libgcc -fno-keep-inline-dllexport -o $(OUTPUT) + $(CC) -g "*.cpp" $(LINK) $(DEPS) -std=$(CPPSTD) $(FLAGS) -static -static-libgcc -fno-keep-inline-dllexport -Os -s -Wl,--build-id=none -o $(OUTPUT) run: default $(OUTPUT) diff --git a/Chinczyk188/Pawn.cpp b/Chinczyk188/Pawn.cpp new file mode 100644 index 0000000..6d1e087 --- /dev/null +++ b/Chinczyk188/Pawn.cpp @@ -0,0 +1,59 @@ +#include "Pawn.hpp" + +// Konstruktor +Pawn::Pawn() { + + this->position = -1; + this->grid_x = -1; + this->grid_y = -1; + +} + +// Gettery i settery +int Pawn::getRelativePosition() const { + + return this->position; + +} + +void Pawn::setRelativePosition(int position) { + + this->position = position; + +} + +int Pawn::move(int fields) { + + switch (fields) { + + case -1: + // Przenieś do bazy + // 0 = ok + return 0; + + case 0: + // Wstaw na planszę + // 0 = ok + return 0; + + default: + // Sprawdź: + // a) czy da się wejść na to miejsce + // b) czy są pionki do zbicia + // c) czy są inne pionki tego samego gracza + + return 0; + + } + +} + +bool Pawn::isAtBase() const { + + return true; + +} + +void Pawn::sendToBase() { + +} \ No newline at end of file diff --git a/Chinczyk188/Pawn.hpp b/Chinczyk188/Pawn.hpp new file mode 100644 index 0000000..e48052e --- /dev/null +++ b/Chinczyk188/Pawn.hpp @@ -0,0 +1,22 @@ +#pragma once + +class Pawn { + public: + Pawn(); + + // Gettery i settery + int getRelativePosition() const; + void setRelativePosition(int position); + + bool isAtBase() const; + bool isActive() const; + bool isAtHome() const; + void sendToBase(); + + int move(int fields); + + private: + int position; // -1 oznacza pionek w bazie + int grid_x; + int grid_y; +}; \ No newline at end of file diff --git a/Chinczyk188/Player.cpp b/Chinczyk188/Player.cpp new file mode 100644 index 0000000..5f91efa --- /dev/null +++ b/Chinczyk188/Player.cpp @@ -0,0 +1,62 @@ +#include "Player.hpp" + +Player::Player(const std::string& name, unsigned int seed, short color): + name(name), seed(seed), color(color), pawnsFinished(0) {} + +const std::string& Player::getName() const { + return name; +} + +unsigned int Player::getSeed() const { + return seed; +} + +void Player::initializePawns(int totalPawns) { + pawns.resize(totalPawns); +} + +int Player::getRollCount() const { + return this->rollCount; +} + +void Player::incrementRollCount() { + this->rollCount++; +} + +short Player::getColor() const { + return this->color; +} + +short Player::tryMovingPawn(short pawnID, short fields) { + // this->pawnsFinished = 4; // do testowania, wymuszania zakończenia gry + return 0; +} + +void Player::movePawn(int pawnIndex, int steps) { + if (pawnIndex < 0 || pawnIndex >= pawns.size()) return; + + // TODO: zaimplementować logikę ruchu pionków + Pawn& pawn = pawns[pawnIndex]; + if (pawn.isAtBase()) { + if (steps == 6) { + pawn.setRelativePosition(1); + } + } else { + int newPosition = pawn.getRelativePosition() + steps; + // TODO: sprawdź pozycję pionków + pawn.setRelativePosition(newPosition); + + if (newPosition >= 51/* "koniec", do przerobienia */) { + pawn.setRelativePosition(51/* "koniec" */); + pawnsFinished++; + } + } +} + +std::vector& Player::getPawns() { + return pawns; +} + +bool Player::hasWon() const { + return pawnsFinished == pawns.size(); +} \ No newline at end of file diff --git a/Chinczyk188/Player.hpp b/Chinczyk188/Player.hpp new file mode 100644 index 0000000..56b3333 --- /dev/null +++ b/Chinczyk188/Player.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include "Pawn.hpp" + +class Player { + public: + Player(const std::string& name, unsigned int seed, short color); + + // Akcesory + const std::string& getName() const; + unsigned int getSeed() const; + int getRollCount() const; + short getColor() const; + + void incrementRollCount(); + + // Zarządzanie pionkami + void initializePawns(int totalPawns); + void movePawn(int pawnIndex, int steps); + short tryMovingPawn(short pawnID, short fields); + std::vector& getPawns(); + short pawnsAtBase = 4; + short pawnsActive = 0; + short pawnsAtHome = 0; + + bool hasWon() const; + + private: + std::string name; + unsigned int seed; + std::vector pawns; + short color; + + int rollCount = 0; + + int pawnsFinished; // to nie to samo, co pawnsAtHome +}; \ No newline at end of file diff --git a/Chinczyk188/main.cpp b/Chinczyk188/main.cpp index 1d6dbed..d8e69c2 100644 --- a/Chinczyk188/main.cpp +++ b/Chinczyk188/main.cpp @@ -4,11 +4,9 @@ int main() { Game game; - while(game.running()) { + while (game.running()) { - game.update(); - - game.render(); + game.run(); } diff --git a/Chinczyk188/res/audio/PeteBarry_pawnmove.ogg b/Chinczyk188/res/audio/PeteBarry_pawnmove.ogg new file mode 100644 index 0000000..edfde0e Binary files /dev/null and b/Chinczyk188/res/audio/PeteBarry_pawnmove.ogg differ diff --git a/Chinczyk188/res/audio/wardoctor17_diceroll.ogg b/Chinczyk188/res/audio/wardoctor17_diceroll.ogg new file mode 100644 index 0000000..e423c79 Binary files /dev/null and b/Chinczyk188/res/audio/wardoctor17_diceroll.ogg differ diff --git a/Chinczyk188/res/audio/welcome_alt.mp3 b/Chinczyk188/res/audio/welcome_alt.mp3 deleted file mode 100644 index e369290..0000000 Binary files a/Chinczyk188/res/audio/welcome_alt.mp3 and /dev/null differ diff --git a/Chinczyk188/res/audio/welcome_alt.ogg b/Chinczyk188/res/audio/welcome_alt.ogg new file mode 100644 index 0000000..792dbe5 Binary files /dev/null and b/Chinczyk188/res/audio/welcome_alt.ogg differ diff --git a/Chinczyk188/res/sprites/board_placesOverlay.png b/Chinczyk188/res/sprites/board_placesOverlay.png new file mode 100644 index 0000000..8ca0809 Binary files /dev/null and b/Chinczyk188/res/sprites/board_placesOverlay.png differ