diff --git a/Chinczyk188/Board.cpp b/Chinczyk188/Board.cpp index 785d809..063a6e8 100644 --- a/Chinczyk188/Board.cpp +++ b/Chinczyk188/Board.cpp @@ -1,4 +1,5 @@ #include "Board.hpp" +#include "Utils.hpp" #include #include #include @@ -23,6 +24,20 @@ void Board::initVariables() { this->window->close(); } + // Podobnie, wczytaj czcionkę Ubuntu. + if (!this->Ubuntu.loadFromFile("res/fonts/Ubuntu-Regular.ttf")) { + + // Jeśli nie uda nam się wczytać czcionki: + std::cerr << "Uwaga: Nie udalo sie wczytac " + "wymaganej czcionki Ubuntu z \"res/fonts/Ubuntu.ttf\"!\n" + "Upewnij sie, ze plik .exe jest we wlasciwym katalogu, " + "a gra zostala w pelni wypakowana wraz z folderem \"res\".\n"; + + this->smartSleep(10000); + this->window->close(); + + } + } void Board::initWindow() { @@ -141,7 +156,13 @@ void Board::render() { // Rysowanie tła this->window->draw(this->boardSprite); + for (sf::CircleShape pawn: pieces) { + this->window->draw(pawn); + } + for (sf::Text pawnText: piecesText) { + this->window->draw(pawnText); + } // Mechanizm wyświetlania // W celu wyświetlenia na ekranie wyniku renderowania @@ -230,7 +251,7 @@ void Board::selectAField(short &pickStatus, short &field) { mapped = this->window->mapPixelToCoords(mousePosition, this->view); if (mapped.x >= 0 && mapped.x <= 1100 && mapped.y >= 0 && mapped.y <= 1100) { // Wyznacz ponownie field, na wypadek jeśli użytkownik zmienił zdanie - field = static_cast(mapped.y / 100) * 11 + static_cast(mapped.x / 100); + field = XYToField(mapped.x, mapped.y); } pickStatus = 8; } @@ -246,6 +267,56 @@ void Board::selectAField(short &pickStatus, short &field) { } +/** + * @brief Draws a pawn (or, optionally, multiple). + * + * @param[in] color Pawn color + * @param[in] field Field in fields matrix to draw in + * @param[in] howMany (Optional, default: 1) Piece count overlay + */ +void Board::drawPawn(short color, short field, short howMany) { + + sf::CircleShape circle(40.f); + cords3 outlineColor = color2RGBOutline(color); + cords3 fillerColor = color2RGBFiller(color); + cords2 XY = fieldToXY(field); + circle.setOutlineColor(sf::Color(outlineColor.x - 20, outlineColor.y - 20, outlineColor.z - 20)); + circle.setFillColor( sf::Color( fillerColor.x - 20, fillerColor.y - 20, fillerColor.z - 20)); + circle.setPosition(XY.x - 50 + 10, XY.y - 50 + 10); + circle.setOutlineThickness(5); + this->pieces.push_back(circle); + + if (howMany > 1) { + sf::Text number(std::to_string(howMany), Ubuntu); + number.setCharacterSize(30); + number.setFillColor(sf::Color::Black); + number.setPosition(XY.x, XY.y); + this->piecesText.push_back(number); + } + + return; + +} + +void Board::drawPawns() { + /*this->pieces.clear(); + this->piecesText.clear();*/ + for (int i = 0; i < 121; i++) { + // Wyznacz kolor i zlicz pionki + short color = -1; + short pawnsFound = 0; + for (int j = 0; j < 4; j++) { + if (fields[i][j] != 0) { + color = fields[i][j] - 4; + pawnsFound++; + } + } + if (pawnsFound != 0) { + this->drawPawn(color, i, pawnsFound); + } + } +} + void Board::closeWindow() { this->window->close(); diff --git a/Chinczyk188/Board.hpp b/Chinczyk188/Board.hpp index 5b9c045..bc235f1 100644 --- a/Chinczyk188/Board.hpp +++ b/Chinczyk188/Board.hpp @@ -15,10 +15,15 @@ class Board { sf::VideoMode videoMode; sf::Texture boardTexture; - sf::Sprite boardSprite; + sf::Sprite boardSprite; + sf::Font Ubuntu; - // void _thread_wait_for_str_cin(bool &done, std::string &text); - // void _thread_wait_for_str_getline(bool& done, std::string& text); + // Rysowanie obiektów na ekranie + void drawPawn(short color, short field, short howMany = 1); + + // Obiekty przechowujące pionki i tekst w celu renderowania + std::vector pieces{}; + std::vector piecesText{}; public: @@ -26,6 +31,8 @@ class Board { sf::Event ev; sf::View view; + short fields[121][4] = {{}}; + // Konstruktor, destruktor void closeWindow(); @@ -39,6 +46,7 @@ class Board { void updateAndRender(); void run(); bool manualUpdate(); + void drawPawns(); sf::Event lastEvent; // Sterowanie oknem diff --git a/Chinczyk188/Engine.cpp b/Chinczyk188/Engine.cpp index 62ffdf3..2578dae 100644 --- a/Chinczyk188/Engine.cpp +++ b/Chinczyk188/Engine.cpp @@ -1,4 +1,5 @@ #include "Engine.hpp" +#include "Utils.hpp" #include #include #include @@ -33,12 +34,19 @@ void Engine::initializeGame() { int totalPawnsPerPlayer = 4; // 4 pionki na gracza for (auto& player: players) { player.initializePawns(totalPawnsPerPlayer); + short playerColor = player.getColor(); + for (int i = 0; i < 4; i++) { + // 3. najmniej znaczący bit informuje o zajęciu pola + // Ponadto przyjmujemy, że jeśli tylko 0 miejsce jest + // zajęte, to możemy wyświetlić + this->board.fields[getPawnInitialPosition(playerColor, i)][0] = playerColor + 4; + } } } void Engine::startGame(Board board) { - initializeGame(); this->board = board; + initializeGame(); } void Engine::nextTurn() { @@ -54,6 +62,7 @@ void Engine::nextTurn() { // Rysuj pionki // ... + this->board.drawPawns(); // Rzut kostką if (dicerollLoaded) this->dicerollBuffer.play(); @@ -72,9 +81,7 @@ void Engine::nextTurn() { // 0 pionków na planszy - musi wyjść pionkiem case 0: std::cout << currentPlayer.getName() << " wychodzi pierwszym pionkiem z bazy.\n"; - currentPlayer.hasLeftBase = true; - currentPlayer.pawnsAtBase--; - currentPlayer.pawnsActive++; + this->spawnPiece(currentPlayer.getColor(), 0); this->pawnmoveBuffer.play(); this->board.smartSleep(2000); break; @@ -100,8 +107,7 @@ void Engine::nextTurn() { pickAPlace = false; std::cout << currentPlayer.getName() << " wychodzi " << currentPlayer.pawnsActive + 1 << ". pionkiem z bazy.\n"; - currentPlayer.pawnsAtBase--; - currentPlayer.pawnsActive++; + this->spawnPiece(currentPlayer.getColor(), currentPlayer.pawnsActive + 1); this->pawnmoveBuffer.play(); this->board.smartSleep(2000); break; @@ -113,6 +119,11 @@ void Engine::nextTurn() { } else { std::cout << currentPlayer.getName() << " rusza jedyny pionek " << diceRoll << " pol do przodu.\n"; + for (int i = 0; i < 4; i++) { + short* relativePawns = currentPlayer.getRelativePawns(); + if (relativePawns[i] >= 0) + this->movePiece(currentPlayer.getColor(), relPosToField(currentPlayer.getColor(), relativePawns[i]), diceRoll); + } this->pawnmoveBuffer.play(); this->board.smartSleep(2000); } @@ -168,13 +179,26 @@ void Engine::nextTurn() { } // Użytkownik wybiera pionek na planszy, którym chce się ruszyć + bool moveResult = false; short pickStatus = 0; short selectedField = -1; if (!pickAPlace) pickStatus = 8; // pomiń pętlę - while (pickStatus != 8) { + while (!moveResult) { - this->board.selectAField(pickStatus, selectedField); + while (pickStatus != 8) { + this->board.selectAField(pickStatus, selectedField); + + } + + if (pickAPlace) { + moveResult = this->movePiece(currentPlayer.getColor(), selectedField, diceRoll); + if (!moveResult) std::cout << "Nie mozna wykonac tego ruchu.\n"; + } else { + // Nadpisz moveResult jeżeli gracz nie wybiera pionka + moveResult = true; + } + } std::cout << "(Engine.cpp) selected: " << selectedField << "\n"; @@ -192,6 +216,8 @@ void Engine::nextTurn() { // std::cout << "Brak ruchu.\n"; // } + + if (currentPlayer.hasWon()) { // Zostanie wykorzystane do zwiększenia liczby wygranych @@ -219,6 +245,110 @@ void Engine::nextTurn() { return; } +/** + * @brief Put the piece onto the board + * + * @param[in] color Player's color + * @param[in] pawnId Unique pawn number + * + * @return Returns true on success, false on failure + */ + +short Engine::spawnPiece(short color, short pawnId) { + short * relativePieces = players[color].getRelativePawns(); + if (relativePieces[pawnId] == -1) { + players[color].sendToBoard(pawnId); + this->board.fields[relPosToField(color, relativePieces[pawnId])][pawnId] = color + 4; + return true; + } + return false; +} + +/** + * @brief Take pawns down + * + * @param[in] field Field inside field matrix + * + * @return Pawns taken down + */ +short Engine::takePawns(short color, short field) { + // Zbij wszystkie możliwe pionki + // usuwając je z pola $field, a następnie + // przenosząc je do bazy. + + short counter = 0; + + // Nie można zbić pionka w polu startowym. + if (field == relPosToField(color, 0)) { + return -1; + } + for (int i = 0; i < 4; i++) { + if (this->board.fields[field][i] > 0) { + + counter++; + + // Zidentyfikuj kolor na podstawie wartości w $fields + short color = this->board.fields[field][i] - 4; + + // Zaktualizuj liczbę pionków gracza + this->players[color].pawnsActive -= 1; + this->players[color].pawnsAtBase += 1; + + // Ustaw pionek na odpowiednie miejsce w bazie + this->board.fields[getPawnInitialPosition(color, i)][i] = color + 4; + // i usuń go z planszy + this->board.fields[field][i] = 0; + + } + } + return counter; +} + +/** + * @brief Try moving a piece + * + * @param[in] color Player color + * @param[in] field Selected field + * @param[in] steps How many steps to move + * + * @return Returns true on success, false on failure. + */ +bool Engine::movePiece(short color, short field, short steps) { // podajemy color dla sanity-checku, czy użytkownik nie chce ruszyć nie swojego pionka + for (int i = 0; i < 4; i++) { + if (this->board.fields[field][i] == color + 4) { + // Znaleźliśmy pionek do przesunięcia, teraz pytanie: + // - czy możemy się ruszyć do przodu o $steps kroków + short relPos = fieldToRelPos(color, field); + if (relPos + steps < 44) { + short newField = relPosToField(color, relPos + steps); + bool isNewFieldOccupied = false; + short occupyingColor = -1; + // - czy tam, gdzie chcemy przenieść pionek gracza + // inny gracz nie ma swojego + for (int j = 0; j < 4; j++) { + if (this->board.fields[newField][j] != 0) { + isNewFieldOccupied = true; + occupyingColor = this->board.fields[newField][j] - 4; + break; + } + } + // - jeśli ma, musimy go (/je) zbić + if (isNewFieldOccupied) takePawns(occupyingColor, newField); + // - w końcu, dopisujemy pionek bieżącego gracza + this->board.fields[field][i] = color + 4; + this->players[this->currentPlayerIndex].unsafeMovePiece(i, steps); + // - po przesunięciu jednego z pionków kończymy pętlę, ponieważ + // nie chcemy przesuwać kolejnych pionków + return true; + } + // jeżeli nie jesteśmy w stanie przesunąć jednego pionka o $steps pozycji, + // to nie będziemy mogli przesunąć żadnego innego pionka + else return false; + } + } + return false; +} + // 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 diff --git a/Chinczyk188/Engine.hpp b/Chinczyk188/Engine.hpp index 2aadfce..f5e05a2 100644 --- a/Chinczyk188/Engine.hpp +++ b/Chinczyk188/Engine.hpp @@ -24,6 +24,9 @@ class Engine { // Logika gry void nextTurn(); int rollDice(unsigned int seed, unsigned int moveNumber); + short spawnPiece(short color, short pawnId); + short takePawns(short color, short field); + bool movePiece(short color, short field, short steps); // Audio bool dicerollLoaded = true; diff --git a/Chinczyk188/Makefile b/Chinczyk188/Makefile index 11e0206..3d0e6c1 100644 --- a/Chinczyk188/Makefile +++ b/Chinczyk188/Makefile @@ -3,7 +3,7 @@ # 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-audio-s -lopenal32 -lflac -lvorbisenc -lvorbisfile -lvorbis -logg -lsfml-system-s -lopengl32 -lwinmm -lgdi32 -lpthread +DEPS = -lsfml-graphics-s -lsfml-window-s -lsfml-audio-s -lopenal32 -lfreetype -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 diff --git a/Chinczyk188/Player.cpp b/Chinczyk188/Player.cpp index e66d600..151eebf 100644 --- a/Chinczyk188/Player.cpp +++ b/Chinczyk188/Player.cpp @@ -59,4 +59,31 @@ std::vector& Player::getPawns() { bool Player::hasWon() const { return pawnsFinished == pawns.size(); +} + +short* Player::getRelativePawns() { + return this->relativePawns; +} + +void Player::sendToBase(short pawnNumber) { + this->relativePawns[pawnNumber] = -1; + this->pawnsActive--; + this->pawnsAtBase++; +} + +void Player::sendToBoard(short pawnNumber) { + this->hasLeftBase = true; + this->relativePawns[pawnNumber] = 0; + this->pawnsActive++; + this->pawnsAtBase--; +} + +void Player::unsafeMovePiece(short pawnNumber, short steps) { + // to dopełnia obecną w Engine.cpp movePiece() + // metoda jest "unsafe" ponieważ nie sprawdza danych, tylko ufa movePiece() + this->relativePawns[pawnNumber] += steps; + if (this->relativePawns[pawnNumber] >= 40) { + this->pawnsActive--; + this->pawnsAtHome++; + } } \ No newline at end of file diff --git a/Chinczyk188/Player.hpp b/Chinczyk188/Player.hpp index c594086..0ea36da 100644 --- a/Chinczyk188/Player.hpp +++ b/Chinczyk188/Player.hpp @@ -12,6 +12,7 @@ class Player { unsigned int getSeed() const; int getRollCount() const; short getColor() const; + short* getRelativePawns(); void incrementRollCount(); @@ -19,6 +20,9 @@ class Player { void initializePawns(int totalPawns); void movePawn(int pawnIndex, int steps); short tryMovingPawn(short pawnID, short fields); + void sendToBase(short pawnNumber); + void sendToBoard(short pawnNumber); + void unsafeMovePiece(short pawnNumber, short steps); std::vector& getPawns(); short pawnsAtBase = 4; short pawnsActive = 0; @@ -32,6 +36,7 @@ class Player { unsigned int seed; std::vector pawns; short color; + short relativePawns[4] = {-1}; int rollCount = 0; diff --git a/Chinczyk188/Utils.cpp b/Chinczyk188/Utils.cpp index e738e9f..a1c7747 100644 --- a/Chinczyk188/Utils.cpp +++ b/Chinczyk188/Utils.cpp @@ -195,4 +195,126 @@ short green_path[44] = { 17, 28, 39, 50, 51, 52, 53, 54, 65, 64, 63, 62, 61 -}; \ No newline at end of file +}; + +short pawnInitialPositions[4][4] = { + { + 99, 100, 110, 111 + }, + { + 9, 10, 20, 21 + }, + { + 0, 1, 11, 12 + }, + { + 108, 109, 119, 120 + } +}; + +short colors_RGB[4][2][3] = { + // czerwony + { + // obwódka + { + 192, 67, 67 + }, + // wypełnienie + { + 255, 166, 166 + } + }, + // niebieski + { + // obwódka + { + 102, 175, 187 + }, + // wypełnienie + { + 187, 233, 255 + } + }, + // żółty + { + // obwódka + { + 211, 211, 30 + }, + // wypełnienie + { + 255, 251, 141 + } + }, + // zielony + { + // obwódka + { + 102, 187, 103 + }, + // wypełnienie + { + 187, 255, 200 + } + } +}; + +/** + * @brief Convert field in fields matrix to relative position in paths matrix + * + * @param[in] color Player's color + * @param[in] field Field in the matrix + * + * @return Position inside paths relative to player's color + */ +short fieldToRelPos(short color, short field) { + for (int i = 0; i < 44; i++) { + if (paths[color][i] == field) { + return i; + } + } return -1; +} + +/** + * @brief Convert relative position in paths matrix to a field in fields matrix + * + * @param[in] color Player's color + * @param[in] field Field in the matrix + * + * @return Field in fields matrix relative to player's color + */ +short relPosToField(short color, short relPos) { + if (relPos >= 0 && relPos < 44) return paths[color][relPos]; + else return -1; +} + +/** + * @brief Gets the pawn initial position. Wrapper for pawnInitialPositions[][]. + * + * @param[in] color Player's color + * @param[in] pawnNumber Unique pawn number inside fields matrix + * + * @return The pawn initial position. + */ +short getPawnInitialPosition(short color, short pawnNumber) { + return pawnInitialPositions[color][pawnNumber]; +} + +cords3 color2RGBOutline(short color) { + return cords3(colors_RGB[color][0][0], colors_RGB[color][0][1], colors_RGB[color][0][2]); +} + +cords3 color2RGBFiller(short color) { + return cords3(colors_RGB[color][1][0], colors_RGB[color][1][1], colors_RGB[color][1][2]); +} + +cords2 fieldToXY(short field) { + cords2 XY; + XY.x = (field % 11) * 100 + 50; + XY.y = static_cast(field / 11) * 100 + 50; + return XY; +} + +short XYToField(short x, short y) { + return static_cast(y / 100) * 11 + static_cast(x / 100); +} \ No newline at end of file diff --git a/Chinczyk188/Utils.hpp b/Chinczyk188/Utils.hpp index d4b12bf..f9fa57a 100644 --- a/Chinczyk188/Utils.hpp +++ b/Chinczyk188/Utils.hpp @@ -2,7 +2,29 @@ #include #include +struct cords2 { + cords2(): x(0), y(0) {}; + cords2(short x, short y): x(x), y(y) {}; + short x; + short y; +}; + +struct cords3 { + cords3(): x(0), y(0), z(0) {}; + cords3(short x, short y, short z): x(x), y(y), z(z) {}; + short x; + short y; + short z; +}; + std::string removeSpaces(std::string str); std::string removeWhitespace(std::string str); void _thread_wait_for_str_cin(bool &done, std::string &text); -void _thread_wait_for_str_getline(bool& done, std::string& text); \ No newline at end of file +void _thread_wait_for_str_getline(bool& done, std::string& text); +short fieldToRelPos(short color, short field); +short relPosToField(short color, short relPos); +short getPawnInitialPosition(short color, short pawnNumber); +cords3 color2RGBOutline(short color); +cords3 color2RGBFiller(short color); +cords2 fieldToXY(short field); +short XYToField(short x, short y); diff --git a/Chinczyk188/res/fonts/Ubuntu-Regular.ttf b/Chinczyk188/res/fonts/Ubuntu-Regular.ttf new file mode 100644 index 0000000..f98a2da Binary files /dev/null and b/Chinczyk188/res/fonts/Ubuntu-Regular.ttf differ