#include #include #include "data/Array.h" #include "data/ArrayList.h" #include "data/HashMap.h" #include "data/List.h" #include "math/Vector.h" #include "rendering/Shader.h" #include "rendering/Texture.h" #include "rendering/VertexBuffer.h" #include "rendering/Window.h" #include "utils/Color.h" #include "utils/Logger.h" #include "utils/Random.h" static Shader shader; static Shader fontShader; static Buffer buffer; static Buffer fontBuffer; static VertexBuffer vertexBuffer; static VertexBuffer fontVertexBuffer; static Texture fontTexture; enum class Resource { NOTHING, CLAY, ORE, WHEAT, SHEEP, WOOD }; static const Color4 RESOURCE_COLOR[] = { Color4(160, 120, 0, 255), Color4(220, 96, 0, 255), Color4(128, 128, 128, 255), Color4(255, 200, 0, 255), Color4(40, 200, 40, 255), Color4(0, 120, 0, 255)}; static const Vector2 MIN_BORDER(-0.9f, -0.9f); static const Vector2 MAX_BORDER(0.9f, 0.9f); static const Vector2 AREA = MAX_BORDER - MIN_BORDER; static const float RADIUS = AREA[0] / (5.0f * 2.0f * cosf(30.0f * static_cast(M_PI) / 180.0f)); static const float LINE_LENGTH = RADIUS * sinf(30.0f * static_cast(M_PI) / 180.0f) * 2.0f; static const float WIDTH = AREA[0] / 5.0f; static void addTriangle(const Vector2 a, const Vector2 b, const Vector2 c, const Color4& color) { buffer.add(a).add(color); buffer.add(b).add(color); buffer.add(c).add(color); } static void addSquare(const Vector2 mid, float size, const Color4& color) { size *= 0.5f; Vector2 a = mid + Vector2(-size, size); Vector2 b = mid + Vector2(size, size); Vector2 c = mid + Vector2(-size, -size); Vector2 d = mid + Vector2(size, -size); addTriangle(a, b, c, color); addTriangle(b, d, c, color); } static void addString(const char* s, const Vector2& pos, float size, const Color4& color) { int index = 0; Vector2 topLeft = pos; while(s[index] != '\0') { Vector2 topRight = topLeft + Vector2(size, 0.0f); Vector2 bottomLeft = topLeft + Vector2(0.0f, -size); Vector2 bottomRight = topRight + Vector2(0.0f, -size); int c = s[index]; float minX = static_cast(c % 16) / 16.0f + 1.0f / 128.0f; float maxX = minX + 6.0f / 128.0f; float minY = static_cast(c / 16) / 16.0f; float maxY = minY + 1.0f / 16.0f; fontBuffer.add(topLeft).add(Vector2(minX, minY)).add(color); fontBuffer.add(bottomLeft).add(Vector2(minX, maxY)).add(color); fontBuffer.add(topRight).add(Vector2(maxX, minY)).add(color); fontBuffer.add(bottomLeft).add(Vector2(minX, maxY)).add(color); fontBuffer.add(topRight).add(Vector2(maxX, minY)).add(color); fontBuffer.add(bottomRight).add(Vector2(maxX, maxY)).add(color); topLeft[0] += size; index++; } } struct Player { int id = -1; Color4 color; HashMap resources; int getResource(Resource r) const { const int* i = resources.search(static_cast(r)); return i == nullptr ? 0 : *i; } void placeStartSettlement(); }; static Array players; static int currentPlayer = 0; struct Hexagon { Resource resource = Resource::NOTHING; Vector2 mid; Array corners; int number = 0; Hexagon() : corners(-1) { } void addToBuffer() const { Color4 color = RESOURCE_COLOR[static_cast(resource)]; float angle = 2.0f * static_cast(M_PI) / 6.0f; for(int i = 0; i < 6; i++) { Vector2 a(sinf(angle * static_cast(i)) * RADIUS, cosf(angle * static_cast(i)) * RADIUS); Vector2 b(sinf(angle * static_cast(i + 1)) * RADIUS, cosf(angle * static_cast(i + 1)) * RADIUS); addTriangle(a + mid, b + mid, mid, color); } } void addStringToBuffer() const { if(number == 0) { return; } StringBuffer<16> s(number); constexpr float SIZE = 0.1f; addString(s, mid + Vector2(-SIZE * static_cast(s.getLength()), SIZE) * 0.5f, SIZE, Color4(0, 0, 0, 0)); } Vector2 getTopCorner() const { return mid + Vector2(0.0f, RADIUS); } Vector2 getBottomCorner() const { return mid - Vector2(0.0f, RADIUS); } Vector2 getLeftTopCorner() const { return mid - Vector2(WIDTH, -LINE_LENGTH) * 0.5f; } Vector2 getLeftBottomCorner() const { return mid - Vector2(WIDTH, LINE_LENGTH) * 0.5f; } Vector2 getRightTopCorner() const { return mid + Vector2(WIDTH, LINE_LENGTH) * 0.5f; } Vector2 getRightBottomCorner() const { return mid + Vector2(WIDTH, -LINE_LENGTH) * 0.5f; } }; static Array hexagons; struct Corner { int player = -1; Vector2 mid; ArrayList hexagons; ArrayList paths; void addToBuffer() const { if(player < 0 || player >= players.getLength()) { return; } addSquare(mid, 0.05f, players[player].color); } bool validForSettlement() const; }; static Array corners; struct Path { int player = -1; Vector2 from; Vector2 to; int cornerA = -1; int cornerB = -1; void addToBuffer() const { Vector2 diff = to - from; Vector2 normal(diff[1], -diff[0]); normal.normalize(); normal *= 0.01f; Vector2 a = from + normal; Vector2 b = from - normal; Vector2 c = to + normal; Vector2 d = to - normal; addTriangle(a, c, b, Color4(255, 255, 255, 255)); addTriangle(c, d, b, Color4(255, 255, 255, 255)); } Vector2 getMid() const { return (to + from) * 0.5f; } const Corner& getOtherCorner(const Corner& c) const { if(&(corners[cornerA]) == &c) { return corners[cornerB]; } else if(&(corners[cornerB]) == &c) { return corners[cornerA]; } return c; } }; static List gPaths; static int getQualityOfNumber(int number) { if(number < 2 || number > 12 || number == 7) { return 0; } return number < 7 ? number - 1 : 13 - number; } static int findBestCorner() { int current = -1; int quality = 0; for(int i = 0; i < corners.getLength(); i++) { if(!corners[i].validForSettlement()) { continue; } int newQuality = 0; for(int h : corners[i].hexagons) { newQuality += getQualityOfNumber(hexagons[h].number); } if(newQuality > quality) { quality = newQuality; current = i; } } return current; } void Player::placeStartSettlement() { int c = findBestCorner(); if(c == -1) { LOG_ERROR("Cannot place start settlement"); return; } corners[c].player = id; } bool Corner::validForSettlement() const { if(player != -1) { return false; } for(int p : paths) { if(gPaths[p].getOtherCorner(*this).player != -1) { return false; } } return true; } static void initResources() { for(int i = 0; i < 4; i++) { hexagons[i].resource = Resource::WHEAT; hexagons[i + 4].resource = Resource::WOOD; hexagons[i + 8].resource = Resource::SHEEP; } for(int i = 0; i < 3; i++) { hexagons[i + 12].resource = Resource::ORE; hexagons[i + 15].resource = Resource::CLAY; } hexagons[18].resource = Resource::NOTHING; Random r(0); for(int i = 0; i < hexagons.getLength(); i++) { std::swap(hexagons[i].resource, hexagons[r.next(i, hexagons.getLength() - 1)].resource); } } static bool invalidNumbers(int a, int b) { a = a == 8 ? 6 : a; b = b == 8 ? 6 : b; return a == b; } static bool invalidNumbersExist() { for(const Corner& c : corners) { if(c.hexagons.getLength() == 2) { int numberA = hexagons[c.hexagons[0]].number; int numberB = hexagons[c.hexagons[1]].number; if(invalidNumbers(numberA, numberB)) { return true; } } else if(c.hexagons.getLength() == 3) { int numberA = hexagons[c.hexagons[0]].number; int numberB = hexagons[c.hexagons[1]].number; int numberC = hexagons[c.hexagons[2]].number; if(invalidNumbers(numberA, numberB) || invalidNumbers(numberA, numberC) || invalidNumbers(numberB, numberC)) { return true; } } } return false; } static void initNumbers() { int index = 0; int numbers[] = {2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 3, 4, 5, 6, 8, 9, 10, 11}; for(Hexagon& h : hexagons) { if(h.resource != Resource::NOTHING) { h.number = numbers[index++]; } } Random r(1); for(int i = 0; i < hexagons.getLength(); i++) { if(hexagons[i].resource == Resource::NOTHING) { continue; } int ni = r.next(i, hexagons.getLength() - 1); while(hexagons[ni].resource == Resource::NOTHING) { ni = r.next(i, hexagons.getLength() - 1); } std::swap(hexagons[i].number, hexagons[ni].number); } while(invalidNumbersExist()) { int a = r.next(0, hexagons.getLength() - 1); while(hexagons[a].resource == Resource::NOTHING) { a = r.next(0, hexagons.getLength() - 1); } int b = r.next(0, hexagons.getLength() - 1); while(hexagons[b].resource == Resource::NOTHING) { b = r.next(0, hexagons.getLength() - 1); } std::swap(hexagons[a].number, hexagons[b].number); } } static void initHexagonMid() { Vector2 mid = MIN_BORDER + AREA * 0.5f; for(int i = 0; i < 3; i++) { hexagons[i].mid = mid - Vector2(WIDTH * static_cast(i - 1), -RADIUS * 2.0f - LINE_LENGTH); hexagons[i + 16].mid = mid - Vector2(WIDTH * static_cast(i - 1), RADIUS * 2.0f + LINE_LENGTH); } for(int i = 3; i < 7; i++) { hexagons[i].mid = mid - Vector2(WIDTH * (static_cast(i) - 4.5f), -RADIUS - LINE_LENGTH * 0.5f); hexagons[i + 9].mid = mid - Vector2(WIDTH * (static_cast(i) - 4.5f), RADIUS + LINE_LENGTH * 0.5f); } for(int i = 7; i < 12; i++) { hexagons[i].mid = mid - Vector2(WIDTH * static_cast(i - 9), 0.0f); } } static void initCornerMid() { for(int i = 0; i < hexagons.getLength(); i++) { corners[i].mid = hexagons[i].getLeftTopCorner(); corners[i + 19].mid = hexagons[i].getLeftBottomCorner(); } corners[38].mid = hexagons[0].getTopCorner(); corners[39].mid = hexagons[1].getTopCorner(); corners[40].mid = hexagons[2].getTopCorner(); corners[41].mid = hexagons[16].getBottomCorner(); corners[42].mid = hexagons[17].getBottomCorner(); corners[43].mid = hexagons[18].getBottomCorner(); corners[44].mid = hexagons[0].getRightTopCorner(); corners[45].mid = hexagons[0].getRightBottomCorner(); corners[46].mid = hexagons[3].getRightTopCorner(); corners[47].mid = hexagons[3].getRightBottomCorner(); corners[48].mid = hexagons[7].getRightTopCorner(); corners[49].mid = hexagons[7].getRightBottomCorner(); corners[50].mid = hexagons[12].getRightTopCorner(); corners[51].mid = hexagons[12].getRightBottomCorner(); corners[52].mid = hexagons[16].getRightTopCorner(); corners[53].mid = hexagons[16].getRightBottomCorner(); } static int findCorner(const Vector2& mid) { for(int i = 0; i < corners.getLength(); i++) { if((mid - corners[i].mid).squareLength() < 0.00001f) { return i; } } return -1; } static void initHexagonCorners() { for(Hexagon& h : hexagons) { h.corners[0] = findCorner(h.getTopCorner()); h.corners[1] = findCorner(h.getBottomCorner()); h.corners[2] = findCorner(h.getLeftTopCorner()); h.corners[3] = findCorner(h.getLeftBottomCorner()); h.corners[4] = findCorner(h.getRightTopCorner()); h.corners[5] = findCorner(h.getRightBottomCorner()); } for(int i = 0; i < hexagons.getLength(); i++) { for(int c = 0; c < hexagons[c].corners.getLength(); c++) { if(hexagons[i].corners[c] == -1) { LOG_WARNING("Could not find a hexagon corner"); } else { if(corners[hexagons[i].corners[c]].hexagons.add(i)) { LOG_WARNING("Corner hexagon overflow"); } } } } } static bool doesPathExist(const Path& p) { Vector2 mid = p.getMid(); for(const Path& po : gPaths) { if((mid - po.getMid()).squareLength() < 0.0001f) { return true; } } return false; } static void initPaths() { for(int i = 0; i < corners.getLength(); i++) { for(int k = 0; k < corners.getLength(); k++) { if(i == k || (corners[i].mid - corners[k].mid).length() >= LINE_LENGTH * 1.01f) { continue; } Path p; p.from = corners[i].mid; p.to = corners[k].mid; if(doesPathExist(p)) { continue; } gPaths.add(p); } } LOG_INFO( StringBuffer<256>("Got ").append(gPaths.getLength()).append(" paths")); } static void initCornerPaths() { for(int c = 0; c < corners.getLength(); c++) { for(int i = 0; i < gPaths.getLength(); i++) { Vector2 mid = gPaths[i].getMid(); if((corners[c].mid - mid).length() >= RADIUS) { continue; } if(corners[c].paths.add(i)) { LOG_WARNING("Corner path overflow"); } if(gPaths[i].cornerA == -1) { gPaths[i].cornerA = c; } else if(gPaths[i].cornerB == -1) { gPaths[i].cornerB = c; } else { LOG_WARNING("Path got too much corners"); } } } for(const Path& p : gPaths) { if(p.cornerA == -1 || p.cornerB == -1) { LOG_WARNING("Path is missing corners"); } } } static void initPlayers() { for(int i = 0; i < players.getLength(); i++) { players[i].id = i; } players[0].color = Color4(255, 0, 0, 255); for(int i = 0; i < players.getLength(); i++) { players[i].placeStartSettlement(); } for(int i = players.getLength() - 1; i >= 0; i--) { players[i].placeStartSettlement(); } } static void init() { initResources(); initHexagonMid(); initCornerMid(); initHexagonCorners(); initPaths(); initCornerPaths(); initNumbers(); initPlayers(); } static void buildBuffer() { buffer.clear(); for(const Hexagon& h : hexagons) { h.addToBuffer(); } for(const Path& p : gPaths) { p.addToBuffer(); } for(const Corner& c : corners) { c.addToBuffer(); } vertexBuffer.setData(buffer, GL::BufferUsage::DYNAMIC); } static void buildFontBuffer() { fontBuffer.clear(); for(const Hexagon& h : hexagons) { h.addStringToBuffer(); } addString(StringBuffer<16>(currentPlayer), Vector2(-0.9f, 0.9f), 0.1f, players[currentPlayer].color); fontVertexBuffer.setData(fontBuffer, GL::BufferUsage::DYNAMIC); } static void rebuild() { buildBuffer(); buildFontBuffer(); } static int waitTicks = 0; static void doTurn() { } static void tick() { waitTicks++; if(waitTicks < 100) { return; } waitTicks = 0; doTurn(); currentPlayer = (currentPlayer + 1) % players.getLength(); rebuild(); } static void render(float lag) { GL::setViewport(Window::getSize()[0], Window::getSize()[1]); shader.use(); vertexBuffer.draw(vertexBuffer.getSize() / static_cast(sizeof(Vector2) + sizeof(Color4))); fontShader.use(); fontTexture.bindTo(0); GL::enableBlending(); fontVertexBuffer.draw( vertexBuffer.getSize() / static_cast(sizeof(Vector2) * 2 + sizeof(Color4))); (void)lag; } static bool shouldRun() { return !Window::shouldClose(); } int main() { Error e = Window::open( Window::Options(4, 6, IntVector2(400, 300), false, "Catan Simulator")); if(e.has()) { e.message.printLine(); return 0; } e = shader.compile("resources/vertex.vs", "resources/fragment.fs"); if(e.has()) { e.message.printLine(); return 0; } e = fontShader.compile("resources/font.vs", "resources/font.fs"); if(e.has()) { e.message.printLine(); return 0; } e = fontTexture.load("resources/font8x8.png", 1); if(e.has()) { e.message.printLine(); return 0; } vertexBuffer.init(VertexBuffer::Attributes().addFloat(2).addColor4()); fontVertexBuffer.init( VertexBuffer::Attributes().addFloat(2).addFloat(2).addColor4()); init(); rebuild(); Window::show(); Window::run(10'000'000); return 0; }