From a408a43cd105b89a8d44292e2ef69802a5660497 Mon Sep 17 00:00:00 2001 From: Stephen Enders Date: Sat, 23 Jan 2021 11:52:32 -0500 Subject: Move source files to src folder We'll be making a few source files it'll be useful to have them organized at least somewhat from our non-src files --- src/helper.cpp | 265 ++++++++++++++++++++++++++++++++++++++++++ src/helper.hpp | 104 +++++++++++++++++ src/random.cpp | 27 +++++ src/random.hpp | 19 +++ src/timedLatch.cpp | 38 ++++++ src/timedLatch.hpp | 22 ++++ src/ur.cpp | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 809 insertions(+) create mode 100644 src/helper.cpp create mode 100644 src/helper.hpp create mode 100644 src/random.cpp create mode 100644 src/random.hpp create mode 100644 src/timedLatch.cpp create mode 100644 src/timedLatch.hpp create mode 100644 src/ur.cpp (limited to 'src') diff --git a/src/helper.cpp b/src/helper.cpp new file mode 100644 index 0000000..8d6f3ce --- /dev/null +++ b/src/helper.cpp @@ -0,0 +1,265 @@ +#include "helper.hpp" +#include + +std::shared_ptr> +loadTextures(const char* path) +{ + int sprite_width = SPRITE_SIZE, sprite_height = SPRITE_SIZE; + sf::Image textureAtlas; + if (!textureAtlas.loadFromFile(path)) { + std::cerr << "Unable to load textures from file: " << path << std::endl; + throw std::runtime_error("Unable to load spritesheet"); + } + + textureAtlas.createMaskFromColor(GLOBAL_MASK); + auto imageSize = textureAtlas.getSize(); + + auto textures = std::make_shared>(); + + for (int y = 0; y < imageSize.y; y += sprite_height) { + for (int x = 0; x < imageSize.x; x += sprite_width) { + sf::Texture t; + t.loadFromImage(textureAtlas, + sf::IntRect(x, y, sprite_width, sprite_height)); + textures->push_back(t); + } + } + return textures; +} + +// increment through the textures +inline int +next(int* p, int max) +{ + int i = *p; + (*p) = (i + 1) % max; + return i; +} + +std::shared_ptr> +createBoard(std::shared_ptr> textures) +{ + auto sprites = std::make_shared>(); + sf::Texture& star_texture = (*textures)[STAR_TILE]; + int blank_idx = 0; + int sp_idx = 0; + // p1 pieces + // p1 star + { + sf::Sprite s; + s.setTexture(star_texture); + s.setPosition(pos(3, 5)); + sprites->push_back(s); + } + // p1 start + for (int i = 0; i < 3; i++) { + sf::Texture& t = (*textures)[P1_BOARD_TILES[next(&sp_idx, 2)]]; + sf::Sprite s; + s.setTexture(t); + s.setPosition(pos(4 + i, 5)); + sprites->push_back(s); + } + // p1 end + { + sf::Sprite goal; + goal.setTexture((*textures)[P1_END]); + goal.setPosition(pos(8, 5)); + sprites->push_back(goal); + + sf::Sprite end_star; + end_star.setTexture(star_texture); + end_star.setPosition(pos(9, 5)); + sprites->push_back(end_star); + + sf::Texture& t = (*textures)[P1_BOARD_TILES[next(&sp_idx, 2)]]; + sf::Sprite s; + s.setTexture(t); + s.setPosition(pos(10, 5)); + sprites->push_back(s); + } + // center pieces + for (int i = 0; i < 8; i++) { + sf::Sprite s; + if (i == 3) { + s.setTexture(star_texture); + } else { + sf::Texture& t = (*textures)[BLANK_TILES[next(&blank_idx, 3)]]; + s.setTexture(t); + } + s.setPosition(pos(3 + i, 4)); + sprites->push_back(s); + } + // p2 pieces + // p2 star + { + sf::Sprite s; + s.setTexture(star_texture); + s.setPosition(pos(3, 3)); + sprites->push_back(s); + } + // p2 start + for (int i = 0; i < 3; i++) { + sf::Texture& t = (*textures)[P2_BOARD_TILES[next(&sp_idx, 2)]]; + sf::Sprite s; + s.setTexture(t); + s.setPosition(pos(4 + i, 3)); + sprites->push_back(s); + } + // p2 end + { + sf::Sprite goal; + goal.setTexture((*textures)[P2_END]); + goal.setPosition(pos(8, 3)); + sprites->push_back(goal); + + sf::Sprite end_star; + end_star.setTexture(star_texture); + end_star.setPosition(pos(9, 3)); + sprites->push_back(end_star); + + sf::Texture& t = (*textures)[P2_BOARD_TILES[next(&sp_idx, 2)]]; + sf::Sprite end_tile; + end_tile.setTexture(t); + end_tile.setPosition(pos(10, 3)); + sprites->push_back(end_tile); + } + + return sprites; +} + +sf::Font +loadFont() +{ + sf::Font font; + if (!font.loadFromFile("./res/DejaVuSansMono.ttf")) { + std::cerr << "Unable to load font" << std::endl; + throw std::runtime_error("Unable to load font"); + } + return font; +} + +std::shared_ptr +createPiece(int id, sf::Texture& texture) +{ + + sf::Sprite s(texture); + auto p = std::make_shared(); + p->id = id; + p->sprite = s; + + return p; +} + +std::shared_ptr +createPlayer(sf::Texture& texture) +{ + std::shared_ptr player = std::make_shared(); + player->score = 0; + player->pieces = std::make_shared>(); + for (int i = 0; i < NUM_PIECES; i++) { + player->pieces->push_back(*createPiece(i + 1, texture)); + } + + return player; +} + +std::shared_ptr> +createAllDice(sf::Texture& die0Texture, sf::Texture& die1Texture) +{ + auto dice = std::make_shared>(); + + // create dice, even 0 odds 1 + // there are 8 dice results int total + // 4 potential 0s + // 4 potential 1s + // The dice will be rendered in pairs + // [0, 1] will be the left most die + // [2, 3] will be the second + // ... and so on + // Since a die can only have 2 results (0 or 1) + // To simplify how we will be placing them, the results of the roll + // will dictate which value each die gets + for (int i = 0; i < 8; i++) { + if (i % 2 == 0) { + sf::Sprite s; + s.setTexture(die0Texture); + struct dice_t die = { 0, true, s }; + dice->push_back(die); + } else { + sf::Sprite s; + s.setTexture(die1Texture); + struct dice_t die = { 1, false, s }; + dice->push_back(die); + } + } + + return dice; +} + +std::shared_ptr> +createRollSprites(sf::Texture& t1, sf::Texture& t2) +{ + auto sprites = std::make_shared>(); + + sprites->push_back(sf::Sprite(t1)); + sprites->push_back(sf::Sprite(t2)); + + return sprites; +} + +void +makeNum(sf::Sprite* sprite_ptr, + int num, + std::shared_ptr> textures) +{ + sf::Texture& t = (*textures)[NUMS_TILES[num]]; + sprite_ptr->setTexture(t); +}; + +bool +clickedPiece(sf::Vector2i mousePosition, struct piece_t* piece) +{ + return piece->sprite.getGlobalBounds().contains(mousePosition.x, + mousePosition.y); +} + +bool +canMovePiece(struct piece_t* piece, + int roll, + std::shared_ptr> myPieces, + std::shared_ptr> enemyPieces) +{ + int next = piece->position + roll; + + // rolled passed the exit + if (next > EXIT_SPACE) { + return false; + } + + // colliding with another piece + for (struct piece_t& p : (*myPieces)) { + // cannot move onto your own piece + if (p.id != piece->id && p.position == next) { + return false; + } + } + + // can't attack in safe square + for (struct piece_t& p : (*enemyPieces)) { + // cannot move onto a protected enemy piece + if (next == SAFE_SPACE && p.position == SAFE_SPACE) { + return false; + } + } + + return true; +} + +// This function takes in an row and col we want to put the sprite in +// and translate it to a real position in the view +// This is because we've ZOOMed in an that adjusts the entire view. +sf::Vector2f +pos(float c, float r) +{ + return { c * SPRITE_SIZE, r * SPRITE_SIZE }; +} diff --git a/src/helper.hpp b/src/helper.hpp new file mode 100644 index 0000000..3e7ffbb --- /dev/null +++ b/src/helper.hpp @@ -0,0 +1,104 @@ +#ifndef UR_HELPER_H +#define UR_HELPER_H + +#include +#include +#include + +// BOARD LAYOUT [0, 1, 2, 3](start) [4,5,6,7,8,9,10,11](middle), [12,13](end) + +static const unsigned int SPRITE_SIZE = 16; +static const unsigned int NUM_PIECES = 7; +static const unsigned int SAFE_SPACE = 7; // 0-indexed +static const unsigned int EXIT_SPACE = 14; // final space + 1 +static const float ZOOM = 0.5f; +static const float SPRITE_ROWS = 9.f; +static const float SPRITE_COLS = 14.f; +static const float SCR_W = SPRITE_SIZE / ZOOM * SPRITE_COLS / ZOOM; +static const float SCR_H = SPRITE_SIZE / ZOOM * SPRITE_ROWS / ZOOM; +static const int P1_PIECE = 19; +static const int P2_PIECE = 18; +static const int P1_BOARD_TILES[2] = { 0, 1 }; +static const int P2_BOARD_TILES[2] = { 2, 3 }; +static const int STAR_TILE = 4; +static const int BLANK_TILES[3] = { 5, 6, 7 }; +static const int P1_END = 22; +static const int P2_END = 23; +static const int DIE_0 = 17; +static const int DIE_1 = 16; +static const int NUMS_TILES[8] = { 8, 9, 10, 11, 12, 13, 14, 15 }; +static const int ROLL_TILES[2] = { 20, 21 }; + +static const char* TITLE = "Royal Game of Ur"; +static const sf::Color GLOBAL_MASK(255, 0, 255, 255); + +enum GameState +{ + WAITING, + ROLLING, + PLACING, + GAME_OVER +}; + +struct piece_t +{ + int id; + int position; + sf::Sprite sprite; +}; + +struct player_t +{ + int score; + std::shared_ptr> pieces; +}; + +struct dice_t +{ + int value; + bool show; + sf::Sprite sprite; +}; + +std::shared_ptr> +loadTextures(const char* path); + +std::shared_ptr> +createBoard(std::shared_ptr> textures); + +sf::Font +loadFont(); + +std::shared_ptr +createPlayer(sf::Texture& pieceTexture); + +std::shared_ptr +createPiece(int id, sf::Texture& texture); + +std::shared_ptr> +createAllDice(sf::Texture& die0Texture, sf::Texture& die1Texture); + +std::shared_ptr> +createRollSprites(sf::Texture& t1, sf::Texture& t2); + +void +makeNum(sf::Sprite* sprite_ptr, + int num, + std::shared_ptr> textures); + +bool +clickedPiece(sf::Vector2i mousePosition, std::shared_ptr piece); + +bool +canMovePiece(std::shared_ptr piece, + int roll, + std::shared_ptr> myPieces, + std::shared_ptr> enemyPieces); + +std::vector +getLegalMoves(std::shared_ptr activePlayer, + std::shared_ptr opponent); + +sf::Vector2f +pos(float c, float r); +#endif diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..e94b191 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,27 @@ +#include "random.hpp" +#include +#include + +namespace ur { + +Random::Random(int min, int max) +{ + this->min = min; + this->max = max; + // setup the random stuff + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + this->engine = std::default_random_engine(seed); + + // setup distribution + float range = float(max - min); + this->distribution = + std::normal_distribution(range / 2.f, range / 4.f); +} + +int +Random::next() +{ + return std::round(this->distribution(this->engine)); +} + +} diff --git a/src/random.hpp b/src/random.hpp new file mode 100644 index 0000000..e74267a --- /dev/null +++ b/src/random.hpp @@ -0,0 +1,19 @@ +#ifndef UR_RANDOM_H +#define UR_RANDOM_H + +#include +namespace ur { +class Random +{ +public: + Random(int min, int max); + int next(); + +private: + int min; + int max; + std::default_random_engine engine; + std::normal_distribution distribution; +}; +} +#endif diff --git a/src/timedLatch.cpp b/src/timedLatch.cpp new file mode 100644 index 0000000..66714a5 --- /dev/null +++ b/src/timedLatch.cpp @@ -0,0 +1,38 @@ +#include "timedLatch.hpp" + +namespace ur { + +TimedLatch::TimedLatch(sf::Time duration) +{ + this->duration = duration; + this->clock = sf::Clock(); + this->isStarted = false; +}; + +void +TimedLatch::start() +{ + this->clock.restart(); + this->isStarted = true; +}; + +bool +TimedLatch::is_running() +{ + return this->isStarted && this->clock.getElapsedTime() < duration; +}; + +bool +TimedLatch::is_completed() +{ + + return this->isStarted && this->clock.getElapsedTime() >= duration; +}; + +void +TimedLatch::reset() +{ + this->isStarted = false; +}; + +} diff --git a/src/timedLatch.hpp b/src/timedLatch.hpp new file mode 100644 index 0000000..77c7d49 --- /dev/null +++ b/src/timedLatch.hpp @@ -0,0 +1,22 @@ +#ifndef UR_TIMEDLATCH_H +#define UR_TIMEDLATCH_H +#include + +namespace ur { +class TimedLatch +{ +public: + TimedLatch(sf::Time duration); + void start(); + void reset(); + bool is_running(); + bool is_completed(); + +private: + bool isStarted; + sf::Time duration; + sf::Clock clock; // internal sfml clock to manage time +}; +} + +#endif diff --git a/src/ur.cpp b/src/ur.cpp new file mode 100644 index 0000000..dc61eb3 --- /dev/null +++ b/src/ur.cpp @@ -0,0 +1,334 @@ +#include "helper.hpp" +#include "random.hpp" +#include "timedLatch.hpp" +#include +#include +#include +#include + +const char* TEXTURE_PATH = "./res/ur.png"; +const float PAD = 32.f; +const float PIECE_PAD = 8.f; +const float TEXT_OFFSET = 8.f; +const sf::Color BG_COLOR = sf::Color(66, 47, 81, 255); +const sf::Color SEMI_TRANSPARENT = sf::Color(255, 255, 255, 128); + +ur::Random dice_rand(0, 1); // 50/50 random +GameState state = GameState::WAITING; +GameState prev_state = GameState::WAITING; +struct piece_t* grabbed_piece = nullptr; +sf::Vector2f grabbed_piece_origin; + +int turn_roll = 0; +bool mouse_left_locked = false; + +inline void +change_state(GameState next) +{ + std::cout << "GameState == " << next << std::endl; + prev_state = state; + state = next; +} + +// p1 = false, p2 = true +bool turn_tracker = false; +int rolling_frame = 0; + +inline void +next(int* i, int max) +{ + (*i) = ((*i) + 1) % max; +} + +inline void +next_turn() +{ + turn_tracker = !turn_tracker; + change_state(GameState::WAITING); +} + +inline bool +p1_turn() +{ + return !turn_tracker; +} + +inline bool +p2_turn() +{ + return turn_tracker; +} + +inline void +render_dice(sf::RenderWindow* window, + std::shared_ptr> dice, + std::shared_ptr> roll_sprites, + std::shared_ptr> textures, + sf::Sprite* roll_result, + ur::TimedLatch* animation_timer, + ur::TimedLatch* animation_frame_timer) +{ + + if (animation_timer->is_completed()) { + animation_timer->reset(); + change_state(GameState::PLACING); + int rolls[4] = { + dice_rand.next(), dice_rand.next(), dice_rand.next(), dice_rand.next() + }; + // draw roll result + for (int i = 0; i < 8; i++) { + auto& die = (*dice)[i]; + die.show = die.value == rolls[i / 2]; + } + // set roll result + for (int r : rolls) + turn_roll += r; + } + + // draw dice + int dice_r, dice_c, roll_r, roll_c; + if (p1_turn()) { + dice_r = 6; + dice_c = 11; + roll_r = dice_r + 2; + roll_c = dice_c; + } else { + dice_r = 1; + dice_c = 11; + roll_r = dice_r - 1; + roll_c = dice_c; + } + + if (state == GameState::PLACING) { + // draw roll text + makeNum(roll_result, turn_roll, textures); + window->draw(*roll_result); + } else if (state == GameState::ROLLING) { + // if completed update dice sprites + if (animation_frame_timer->is_completed()) { + // iterate over each pair of dice sprites + // and show whichever matches the roll + for (int i = 0; i < 8; i += 2) { + int result = dice_rand.next(); + (*dice)[i].show = result == 0; + (*dice)[i + 1].show = result == 1; + } + } + // make sure we're started! + // note - this should come after the completed check otherwise we'll always + // restart it + if (!animation_frame_timer->is_running()) { + animation_frame_timer->start(); + } + + } else { + // draw initial values + // draw the 0s + int c = dice_c, r = dice_r; + for (int i = 0; i < 8; i += 2) { + auto& die = (*dice)[i]; + die.sprite.setPosition(pos(c++, r)); + window->draw(die.sprite); + if (i == 2) { + c = dice_c; + r += 1; + } + } + } + + int c = dice_c, r = dice_r; + int i = 0; + for (auto& die : (*dice)) { + if (die.show) { + die.sprite.setPosition(pos(c++, r)); + window->draw(die.sprite); + if (++i == 2) { + c = dice_c; + r += 1; + } + } + } + c = roll_c, r = roll_r; + for (auto& s : (*roll_sprites)) { + s.setPosition(pos(c++, r)); + } +} + +int +main() +{ + const std::shared_ptr> textures = + loadTextures(TEXTURE_PATH); + + const std::shared_ptr> board = createBoard(textures); + + const std::shared_ptr p1 = + createPlayer((*textures)[P1_PIECE]); + + const std::shared_ptr p2 = + createPlayer((*textures)[P2_PIECE]); + + const std::shared_ptr> roll_sprites = + createRollSprites((*textures)[ROLL_TILES[0]], (*textures)[ROLL_TILES[1]]); + + const std::shared_ptr> dice = + createAllDice((*textures)[DIE_0], (*textures)[DIE_1]); + + sf::Sprite p1Score; + p1Score.setPosition(pos(0, SPRITE_ROWS - 1)); + makeNum(&p1Score, 0, textures); + + sf::Sprite p2Score; + p2Score.setPosition(pos(0, 0)); + makeNum(&p2Score, 0, textures); + + // init piece positions + int p_num = (SPRITE_COLS / 2) - (p1->pieces->size() / 2) - 1; + for (auto& p : *(p1->pieces)) { + p.sprite.setPosition(pos(p_num++, SPRITE_ROWS - 1)); + } + + p_num = (SPRITE_COLS / 2) - (p2->pieces->size() / 2) - 1; + for (auto& p : *(p2->pieces)) { + p.sprite.setPosition(pos(p_num++, 0)); + } + + sf::Sprite roll_result; + roll_result.setPosition(pos(12, 4)); + + sf::RenderWindow window(sf::VideoMode(SCR_W, SCR_H), TITLE); + window.setFramerateLimit(60); + window.setVerticalSyncEnabled(true); + + sf::View view(window.getDefaultView()); + view.zoom(ZOOM); + view.setSize(view.getSize() * ZOOM); + view.setCenter(view.getSize() / 2.f); + + ur::TimedLatch rolling_animation_timer(sf::seconds(3)); + ur::TimedLatch rolling_animation_frame_pause_timer(sf::milliseconds(100)); + + while (window.isOpen()) { + + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed || + sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) { + window.close(); + } + + if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left) && + !mouse_left_locked) { + mouse_left_locked = true; + // check rolling button click + window.setView(view); + auto mPos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); + + // was roll clicked + if (state == GameState::WAITING) { + for (auto& s : (*roll_sprites)) { + // zoom sprite bounds + if (s.getGlobalBounds().contains(mPos)) { + std::cout << "Roll!" << std::endl; + // setup for rolling + rolling_animation_timer.start(); + change_state(GameState::ROLLING); + for (auto& rs : (*roll_sprites)) { + rs.setColor(SEMI_TRANSPARENT); + } + for (int i = 0; i < 8; i++) { + (*dice)[i].show = i % 2 == 0; // only show the 0s + } + break; + } + } + } else if (state == GameState::PLACING) { + // is a piece being clicked + std::shared_ptr> pieces; + if (p1_turn()) { + pieces = p1->pieces; + } else { + pieces = p2->pieces; + } + + for (auto& p : (*pieces)) { + if (p.sprite.getGlobalBounds().contains(mPos)) { + grabbed_piece = &p; + grabbed_piece_origin = grabbed_piece->sprite.getPosition(); + break; + } + } + } + window.setView(window.getDefaultView()); // reset back to main view + } else if (!sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) { + mouse_left_locked = false; + if (state == GameState::PLACING && grabbed_piece != nullptr) { + // did the piece drop into place + bool in_place = false; + sf::FloatRect intersect; + for (auto& s : *(board)) { + if (s.getGlobalBounds().intersects( + grabbed_piece->sprite.getGlobalBounds(), intersect)) { + if (intersect.width > SPRITE_SIZE / 2 && + intersect.height > SPRITE_SIZE / 2) { + grabbed_piece->sprite.setPosition(s.getPosition()); + in_place = true; + break; + } + } + } + + if (in_place) { + next_turn(); + turn_roll = 0; + for (auto& s : (*roll_sprites)) { + s.setColor(sf::Color::White); + } + } else { + grabbed_piece->sprite.setPosition(grabbed_piece_origin); + } + grabbed_piece = nullptr; + } + } + } + + window.clear(BG_COLOR); + window.setView(view); + + for (auto s : *(board)) { + window.draw(s); + } + + auto mPos = window.mapPixelToCoords(sf::Mouse::getPosition(window)); + if (grabbed_piece != nullptr) { + float x = mPos.x - (grabbed_piece->sprite.getGlobalBounds().width / 2); + float y = mPos.y - (grabbed_piece->sprite.getGlobalBounds().height / 2); + grabbed_piece->sprite.setPosition(x, y); + } + // draw unused pieces + for (auto& p : *(p1->pieces)) { + window.draw(p.sprite); + } + for (auto& p : *(p2->pieces)) { + window.draw(p.sprite); + } + + render_dice(&window, + dice, + roll_sprites, + textures, + &roll_result, + &rolling_animation_timer, + &rolling_animation_frame_pause_timer); + for (auto& s : (*roll_sprites)) { + window.draw(s); + } + + window.draw(p1Score); + window.draw(p2Score); + window.setView(window.getDefaultView()); + window.display(); + } + + return EXIT_SUCCESS; +} -- cgit v1.2.3-54-g00ecf