Browse Source

Basic AI that plays against itself, restart buttons

Kajetan Johannes Hammerle 1 year ago
parent
commit
a55fbc8229
1 changed files with 450 additions and 111 deletions
  1. 450 111
      Main.cpp

+ 450 - 111
Main.cpp

@@ -1,6 +1,7 @@
 #include <cmath>
 #include <cstdio>
 
+#include "GLFW/glfw3.h"
 #include "data/Array.h"
 #include "data/ArrayList.h"
 #include "data/HashMap.h"
@@ -21,8 +22,15 @@ static Buffer fontBuffer;
 static VertexBuffer vertexBuffer;
 static VertexBuffer fontVertexBuffer;
 static Texture fontTexture;
+static Random rng(0);
+static int restartButton = 0;
+static int restartMapButton = 0;
+static int turns = 0;
 
 enum class Resource { NOTHING, CLAY, ORE, WHEAT, SHEEP, WOOD };
+static const char* RESOURCE_NAME[] = {"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),
@@ -81,10 +89,28 @@ static void addString(const char* s, const Vector2& pos, float size,
     }
 }
 
+static int getQualityOfNumber(int number) {
+    if(number < 2 || number > 12 || number == 7) {
+        return 0;
+    }
+    return number < 7 ? number - 1 : 13 - number;
+}
+
 struct Player {
     int id = -1;
     Color4 color;
     HashMap<int, int> resources;
+    int settlements = 5;
+    int paths = 15;
+    int cities = 4;
+
+    Player() {
+        giveResource(Resource::CLAY, 0);
+        giveResource(Resource::ORE, 0);
+        giveResource(Resource::SHEEP, 0);
+        giveResource(Resource::WHEAT, 0);
+        giveResource(Resource::WOOD, 0);
+    }
 
     int getResource(Resource r) const {
         const int* i = resources.search(static_cast<int>(r));
@@ -92,9 +118,82 @@ struct Player {
     }
 
     void placeStartSettlement();
+
+    void giveResource(Resource r, int amount) {
+        LOG_INFO(StringBuffer<256>("Player ")
+                     .append(id)
+                     .append(" gets ")
+                     .append(amount)
+                     .append(" ")
+                     .append(RESOURCE_NAME[static_cast<int>(r)]));
+        int* i = resources.search(static_cast<int>(r));
+        if(i == nullptr) {
+            resources.tryEmplace(static_cast<int>(r), amount);
+        } else {
+            *i += amount;
+        }
+        LOG_INFO(StringBuffer<256>(resources));
+    }
+
+    void removeResource(Resource r, int amount) {
+        LOG_INFO(StringBuffer<256>("Player ")
+                     .append(id)
+                     .append(" pays ")
+                     .append(amount)
+                     .append(" ")
+                     .append(RESOURCE_NAME[static_cast<int>(r)]));
+        int* i = resources.search(static_cast<int>(r));
+        if(i == nullptr) {
+            return;
+        }
+        *i -= amount;
+        LOG_INFO(StringBuffer<256>(resources));
+    }
+
+    void takeTurn();
+
+    bool swap() {
+        int max = 0;
+        int maxResource = 0;
+        for(const auto& entry : resources.entries()) {
+            if(entry.value > max) {
+                max = entry.value;
+                maxResource = entry.getKey();
+            }
+        }
+
+        int min = max;
+        int minResource = 0;
+        for(const auto& entry : resources.entries()) {
+            if(entry.value < min) {
+                min = entry.value;
+                minResource = entry.getKey();
+            }
+        }
+
+        if(max >= 4) {
+            removeResource(static_cast<Resource>(maxResource), 4);
+            giveResource(static_cast<Resource>(minResource), 1);
+            return true;
+        }
+        return false;
+    }
+
+    int getPoints() const;
+
+    void reset() {
+        removeResource(Resource::CLAY, getResource(Resource::CLAY));
+        removeResource(Resource::ORE, getResource(Resource::ORE));
+        removeResource(Resource::SHEEP, getResource(Resource::SHEEP));
+        removeResource(Resource::WHEAT, getResource(Resource::WHEAT));
+        removeResource(Resource::WOOD, getResource(Resource::WOOD));
+        settlements = 5;
+        paths = 15;
+        cities = 4;
+    }
 };
 
-static Array<Player, 1> players;
+static Array<Player, 4> players;
 static int currentPlayer = 0;
 
 struct Hexagon {
@@ -156,10 +255,11 @@ struct Hexagon {
     }
 };
 
-static Array<Hexagon, 19> hexagons;
+static Array<Hexagon, 19> gHexagons;
 
 struct Corner {
     int player = -1;
+    bool city = false;
     Vector2 mid;
     ArrayList<int, 3> hexagons;
     ArrayList<int, 3> paths;
@@ -168,13 +268,32 @@ struct Corner {
         if(player < 0 || player >= players.getLength()) {
             return;
         }
-        addSquare(mid, 0.05f, players[player].color);
+        addSquare(mid, 0.075f * (static_cast<float>(city) + 1.0f),
+                  players[player].color);
     }
 
     bool validForSettlement() const;
+    bool doesAnyPathBelongToPlayer(int player) const;
+
+    bool isFree() const {
+        return player < 0 || player >= players.getLength();
+    }
+
+    void reset() {
+        player = -1;
+        city = false;
+    }
+
+    int getQuality() {
+        int q = 0;
+        for(int h : hexagons) {
+            q += getQualityOfNumber(gHexagons[h].number);
+        }
+        return q;
+    }
 };
 
-static Array<Corner, 54> corners;
+static Array<Corner, 54> gCorners;
 
 struct Path {
     int player = -1;
@@ -184,6 +303,9 @@ struct Path {
     int cornerB = -1;
 
     void addToBuffer() const {
+        if(player < 0 || player >= players.getLength()) {
+            return;
+        }
         Vector2 diff = to - from;
         Vector2 normal(diff[1], -diff[0]);
         normal.normalize();
@@ -193,8 +315,8 @@ struct Path {
         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));
+        addTriangle(a, c, b, players[player].color);
+        addTriangle(c, d, b, players[player].color);
     }
 
     Vector2 getMid() const {
@@ -202,34 +324,35 @@ struct Path {
     }
 
     const Corner& getOtherCorner(const Corner& c) const {
-        if(&(corners[cornerA]) == &c) {
-            return corners[cornerB];
-        } else if(&(corners[cornerB]) == &c) {
-            return corners[cornerA];
+        if(&(gCorners[cornerA]) == &c) {
+            return gCorners[cornerB];
+        } else if(&(gCorners[cornerB]) == &c) {
+            return gCorners[cornerA];
         }
         return c;
     }
-};
 
-static List<Path> gPaths;
+    bool isFree() const {
+        return player < 0 || player >= players.getLength();
+    }
 
-static int getQualityOfNumber(int number) {
-    if(number < 2 || number > 12 || number == 7) {
-        return 0;
+    void reset() {
+        player = -1;
     }
-    return number < 7 ? number - 1 : 13 - number;
-}
+};
+
+static List<Path> gPaths;
 
 static int findBestCorner() {
     int current = -1;
     int quality = 0;
-    for(int i = 0; i < corners.getLength(); i++) {
-        if(!corners[i].validForSettlement()) {
+    for(int i = 0; i < gCorners.getLength(); i++) {
+        if(!gCorners[i].validForSettlement()) {
             continue;
         }
         int newQuality = 0;
-        for(int h : corners[i].hexagons) {
-            newQuality += getQualityOfNumber(hexagons[h].number);
+        for(int h : gCorners[i].hexagons) {
+            newQuality += getQualityOfNumber(gHexagons[h].number);
         }
         if(newQuality > quality) {
             quality = newQuality;
@@ -245,37 +368,167 @@ void Player::placeStartSettlement() {
         LOG_ERROR("Cannot place start settlement");
         return;
     }
-    corners[c].player = id;
+    gCorners[c].player = id;
+    settlements--;
+
+    while(true) {
+        int p =
+            gCorners[c].paths[rng.next(0, gCorners[c].paths.getLength() - 1)];
+        if(gPaths[p].player == -1) {
+            gPaths[p].player = id;
+            paths--;
+            return;
+        }
+    }
+}
+
+static List<int> getPossiblePaths(int player) {
+    List<int> paths;
+    for(int i = 0; i < gPaths.getLength(); i++) {
+        if(!gPaths[i].isFree()) {
+            continue;
+        }
+        const Corner& cornerA = gCorners[gPaths[i].cornerA];
+        const Corner& cornerB = gCorners[gPaths[i].cornerB];
+        if(cornerA.player == player || cornerB.player == player ||
+           cornerA.doesAnyPathBelongToPlayer(player) ||
+           cornerB.doesAnyPathBelongToPlayer(player)) {
+            paths.add(i);
+        }
+    }
+    return paths;
+}
+
+static List<int> getPossibleCorners(int player) {
+    List<int> corners;
+    for(int i = 0; i < gCorners.getLength(); i++) {
+        if(!gCorners[i].isFree()) {
+            continue;
+        }
+        if(gCorners[i].doesAnyPathBelongToPlayer(player) &&
+           gCorners[i].validForSettlement()) {
+            corners.add(i);
+        }
+    }
+    return corners;
+}
+
+static List<int> getPossibleCityCorners(int player) {
+    List<int> corners;
+    for(int i = 0; i < gCorners.getLength(); i++) {
+        if(gCorners[i].player == player && !gCorners[i].city) {
+            corners.add(i);
+        }
+    }
+    return corners;
+}
+
+void Player::takeTurn() {
+    while(true) {
+        int clay = getResource(Resource::CLAY);
+        int ore = getResource(Resource::ORE);
+        int sheep = getResource(Resource::SHEEP);
+        int wheat = getResource(Resource::WHEAT);
+        int wood = getResource(Resource::WOOD);
+        if(wood >= 1 && clay >= 1 && wheat >= 1 && sheep >= 1 &&
+           settlements > 0) {
+            LOG_INFO("I can build a settlement.");
+            List<int> possibleCorners = getPossibleCorners(id);
+            if(possibleCorners.getLength() >= 1) {
+                int index = rng.next(0, possibleCorners.getLength() - 1);
+                gCorners[possibleCorners[index]].player = id;
+                removeResource(Resource::CLAY, 1);
+                removeResource(Resource::WOOD, 1);
+                removeResource(Resource::SHEEP, 1);
+                removeResource(Resource::WHEAT, 1);
+                LOG_INFO("I build a settlement.");
+                settlements--;
+                continue;
+            }
+        }
+        if(wood >= 1 && clay >= 1 && paths > 0) {
+            LOG_INFO("I can build a path.");
+            List<int> possiblePaths = getPossiblePaths(id);
+            if(possiblePaths.getLength() >= 1 &&
+               getPossibleCorners(id).getLength() == 0) {
+                int index = rng.next(0, possiblePaths.getLength() - 1);
+                gPaths[possiblePaths[index]].player = id;
+                removeResource(Resource::CLAY, 1);
+                removeResource(Resource::WOOD, 1);
+                LOG_INFO("I build a path.");
+                paths--;
+                continue;
+            }
+        }
+        if(ore >= 3 && wheat >= 2 && cities > 0) {
+            LOG_INFO("I can build a city.");
+            List<int> possibleCityCorners = getPossibleCityCorners(id);
+            if(possibleCityCorners.getLength() >= 1) {
+                int bestIndex = -1;
+                int quality = -1;
+                for(int index : possibleCityCorners) {
+                    int newQuality = gCorners[index].getQuality();
+                    if(newQuality > quality) {
+                        bestIndex = index;
+                        quality = newQuality;
+                    }
+                }
+                gCorners[bestIndex].city = true;
+                removeResource(Resource::ORE, 3);
+                removeResource(Resource::WHEAT, 2);
+                LOG_INFO("I build a city.");
+                cities--;
+                settlements++;
+                continue;
+            }
+        }
+        if(swap()) {
+            continue;
+        }
+        break;
+    }
+}
+
+bool Corner::doesAnyPathBelongToPlayer(int playerId) const {
+    for(int p : paths) {
+        if(gPaths[p].player == playerId) {
+            return true;
+        }
+    }
+    return false;
 }
 
 bool Corner::validForSettlement() const {
-    if(player != -1) {
+    if(!isFree()) {
         return false;
     }
     for(int p : paths) {
-        if(gPaths[p].getOtherCorner(*this).player != -1) {
+        if(!gPaths[p].getOtherCorner(*this).isFree()) {
             return false;
         }
     }
     return true;
 }
 
+int Player::getPoints() const {
+    return (4 - cities) * 2 + 5 - settlements;
+}
+
 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;
+        gHexagons[i].resource = Resource::WHEAT;
+        gHexagons[i + 4].resource = Resource::WOOD;
+        gHexagons[i + 8].resource = Resource::SHEEP;
     }
     for(int i = 0; i < 3; i++) {
-        hexagons[i + 12].resource = Resource::ORE;
-        hexagons[i + 15].resource = Resource::CLAY;
+        gHexagons[i + 12].resource = Resource::ORE;
+        gHexagons[i + 15].resource = Resource::CLAY;
     }
-    hexagons[18].resource = Resource::NOTHING;
+    gHexagons[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);
+    for(int i = 0; i < gHexagons.getLength(); i++) {
+        std::swap(gHexagons[i].resource,
+                  gHexagons[rng.next(i, gHexagons.getLength() - 1)].resource);
     }
 }
 
@@ -286,17 +539,17 @@ static bool invalidNumbers(int a, int b) {
 }
 
 static bool invalidNumbersExist() {
-    for(const Corner& c : corners) {
+    for(const Corner& c : gCorners) {
         if(c.hexagons.getLength() == 2) {
-            int numberA = hexagons[c.hexagons[0]].number;
-            int numberB = hexagons[c.hexagons[1]].number;
+            int numberA = gHexagons[c.hexagons[0]].number;
+            int numberB = gHexagons[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;
+            int numberA = gHexagons[c.hexagons[0]].number;
+            int numberB = gHexagons[c.hexagons[1]].number;
+            int numberC = gHexagons[c.hexagons[2]].number;
             if(invalidNumbers(numberA, numberB) ||
                invalidNumbers(numberA, numberC) ||
                invalidNumbers(numberB, numberC)) {
@@ -310,93 +563,94 @@ static bool invalidNumbersExist() {
 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) {
+    for(Hexagon& h : gHexagons) {
         if(h.resource != Resource::NOTHING) {
             h.number = numbers[index++];
+        } else {
+            h.number = 0;
         }
     }
-    Random r(1);
-    for(int i = 0; i < hexagons.getLength(); i++) {
-        if(hexagons[i].resource == Resource::NOTHING) {
+    for(int i = 0; i < gHexagons.getLength(); i++) {
+        if(gHexagons[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);
+        int ni = rng.next(i, gHexagons.getLength() - 1);
+        while(gHexagons[ni].resource == Resource::NOTHING) {
+            ni = rng.next(i, gHexagons.getLength() - 1);
         }
-        std::swap(hexagons[i].number, hexagons[ni].number);
+        std::swap(gHexagons[i].number, gHexagons[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 a = rng.next(0, gHexagons.getLength() - 1);
+        while(gHexagons[a].resource == Resource::NOTHING) {
+            a = rng.next(0, gHexagons.getLength() - 1);
         }
 
-        int b = r.next(0, hexagons.getLength() - 1);
-        while(hexagons[b].resource == Resource::NOTHING) {
-            b = r.next(0, hexagons.getLength() - 1);
+        int b = rng.next(0, gHexagons.getLength() - 1);
+        while(gHexagons[b].resource == Resource::NOTHING) {
+            b = rng.next(0, gHexagons.getLength() - 1);
         }
 
-        std::swap(hexagons[a].number, hexagons[b].number);
+        std::swap(gHexagons[a].number, gHexagons[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<float>(i - 1),
-                                        -RADIUS * 2.0f - LINE_LENGTH);
-        hexagons[i + 16].mid = mid - Vector2(WIDTH * static_cast<float>(i - 1),
-                                             RADIUS * 2.0f + LINE_LENGTH);
+        gHexagons[i].mid = mid - Vector2(WIDTH * static_cast<float>(i - 1),
+                                         -RADIUS * 2.0f - LINE_LENGTH);
+        gHexagons[i + 16].mid = mid - Vector2(WIDTH * static_cast<float>(i - 1),
+                                              RADIUS * 2.0f + LINE_LENGTH);
     }
     for(int i = 3; i < 7; i++) {
-        hexagons[i].mid = mid - Vector2(WIDTH * (static_cast<float>(i) - 4.5f),
-                                        -RADIUS - LINE_LENGTH * 0.5f);
-        hexagons[i + 9].mid =
+        gHexagons[i].mid = mid - Vector2(WIDTH * (static_cast<float>(i) - 4.5f),
+                                         -RADIUS - LINE_LENGTH * 0.5f);
+        gHexagons[i + 9].mid =
             mid - Vector2(WIDTH * (static_cast<float>(i) - 4.5f),
                           RADIUS + LINE_LENGTH * 0.5f);
     }
     for(int i = 7; i < 12; i++) {
-        hexagons[i].mid =
+        gHexagons[i].mid =
             mid - Vector2(WIDTH * static_cast<float>(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();
+    for(int i = 0; i < gHexagons.getLength(); i++) {
+        gCorners[i].mid = gHexagons[i].getLeftTopCorner();
+        gCorners[i + 19].mid = gHexagons[i].getLeftBottomCorner();
+    }
+    gCorners[38].mid = gHexagons[0].getTopCorner();
+    gCorners[39].mid = gHexagons[1].getTopCorner();
+    gCorners[40].mid = gHexagons[2].getTopCorner();
+    gCorners[41].mid = gHexagons[16].getBottomCorner();
+    gCorners[42].mid = gHexagons[17].getBottomCorner();
+    gCorners[43].mid = gHexagons[18].getBottomCorner();
+    gCorners[44].mid = gHexagons[0].getRightTopCorner();
+    gCorners[45].mid = gHexagons[0].getRightBottomCorner();
+    gCorners[46].mid = gHexagons[3].getRightTopCorner();
+    gCorners[47].mid = gHexagons[3].getRightBottomCorner();
+    gCorners[48].mid = gHexagons[7].getRightTopCorner();
+    gCorners[49].mid = gHexagons[7].getRightBottomCorner();
+    gCorners[50].mid = gHexagons[12].getRightTopCorner();
+    gCorners[51].mid = gHexagons[12].getRightBottomCorner();
+    gCorners[52].mid = gHexagons[16].getRightTopCorner();
+    gCorners[53].mid = gHexagons[16].getRightBottomCorner();
 }
 
 static int findCorner(const Vector2& mid) {
-    for(int i = 0; i < corners.getLength(); i++) {
-        if((mid - corners[i].mid).squareLength() < 0.00001f) {
+    for(int i = 0; i < gCorners.getLength(); i++) {
+        if((mid - gCorners[i].mid).squareLength() < 0.00001f) {
             return i;
         }
     }
     return -1;
 }
 
-static void initHexagonCorners() {
-    for(Hexagon& h : hexagons) {
+static void initHexagongCorners() {
+    for(Hexagon& h : gHexagons) {
         h.corners[0] = findCorner(h.getTopCorner());
         h.corners[1] = findCorner(h.getBottomCorner());
         h.corners[2] = findCorner(h.getLeftTopCorner());
@@ -404,12 +658,12 @@ static void initHexagonCorners() {
         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) {
+    for(int i = 0; i < gHexagons.getLength(); i++) {
+        for(int c = 0; c < gHexagons[c].corners.getLength(); c++) {
+            if(gHexagons[i].corners[c] == -1) {
                 LOG_WARNING("Could not find a hexagon corner");
             } else {
-                if(corners[hexagons[i].corners[c]].hexagons.add(i)) {
+                if(gCorners[gHexagons[i].corners[c]].hexagons.add(i)) {
                     LOG_WARNING("Corner hexagon overflow");
                 }
             }
@@ -428,15 +682,15 @@ static bool doesPathExist(const Path& p) {
 }
 
 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() >=
+    for(int i = 0; i < gCorners.getLength(); i++) {
+        for(int k = 0; k < gCorners.getLength(); k++) {
+            if(i == k || (gCorners[i].mid - gCorners[k].mid).length() >=
                              LINE_LENGTH * 1.01f) {
                 continue;
             }
             Path p;
-            p.from = corners[i].mid;
-            p.to = corners[k].mid;
+            p.from = gCorners[i].mid;
+            p.to = gCorners[k].mid;
             if(doesPathExist(p)) {
                 continue;
             }
@@ -448,13 +702,13 @@ static void initPaths() {
 }
 
 static void initCornerPaths() {
-    for(int c = 0; c < corners.getLength(); c++) {
+    for(int c = 0; c < gCorners.getLength(); c++) {
         for(int i = 0; i < gPaths.getLength(); i++) {
             Vector2 mid = gPaths[i].getMid();
-            if((corners[c].mid - mid).length() >= RADIUS) {
+            if((gCorners[c].mid - mid).length() >= RADIUS) {
                 continue;
             }
-            if(corners[c].paths.add(i)) {
+            if(gCorners[c].paths.add(i)) {
                 LOG_WARNING("Corner path overflow");
             }
             if(gPaths[i].cornerA == -1) {
@@ -462,23 +716,28 @@ static void initCornerPaths() {
             } else if(gPaths[i].cornerB == -1) {
                 gPaths[i].cornerB = c;
             } else {
-                LOG_WARNING("Path got too much corners");
+                LOG_WARNING("Path got too much gCorners");
             }
         }
     }
     for(const Path& p : gPaths) {
         if(p.cornerA == -1 || p.cornerB == -1) {
-            LOG_WARNING("Path is missing corners");
+            LOG_WARNING("Path is missing gCorners");
         }
     }
 }
 
 static void initPlayers() {
+    static const Color4 PLAYER_COLOR[] = {
+        Color4(255, 0, 0, 255), Color4(0, 0, 255, 255),
+        Color4(255, 128, 0, 255), Color4(255, 255, 255, 255)};
     for(int i = 0; i < players.getLength(); i++) {
         players[i].id = i;
+        players[i].color = PLAYER_COLOR[i];
     }
-    players[0].color = Color4(255, 0, 0, 255);
+}
 
+static void placeStartSettlement() {
     for(int i = 0; i < players.getLength(); i++) {
         players[i].placeStartSettlement();
     }
@@ -491,22 +750,23 @@ static void init() {
     initResources();
     initHexagonMid();
     initCornerMid();
-    initHexagonCorners();
+    initHexagongCorners();
     initPaths();
     initCornerPaths();
     initNumbers();
     initPlayers();
+    placeStartSettlement();
 }
 
 static void buildBuffer() {
     buffer.clear();
-    for(const Hexagon& h : hexagons) {
+    for(const Hexagon& h : gHexagons) {
         h.addToBuffer();
     }
     for(const Path& p : gPaths) {
         p.addToBuffer();
     }
-    for(const Corner& c : corners) {
+    for(const Corner& c : gCorners) {
         c.addToBuffer();
     }
     vertexBuffer.setData(buffer, GL::BufferUsage::DYNAMIC);
@@ -514,11 +774,23 @@ static void buildBuffer() {
 
 static void buildFontBuffer() {
     fontBuffer.clear();
-    for(const Hexagon& h : hexagons) {
+    for(const Hexagon& h : gHexagons) {
         h.addStringToBuffer();
     }
-    addString(StringBuffer<16>(currentPlayer), Vector2(-0.9f, 0.9f), 0.1f,
-              players[currentPlayer].color);
+    for(int i = 0; i < players.getLength(); i++) {
+        StringBuffer<16> s(players[i].getPoints());
+        if(i == currentPlayer) {
+            s.append(" <");
+        }
+        addString(s,
+                  Vector2(-0.9f, 0.9f) -
+                      Vector2(0.0f, 0.1f * static_cast<float>(i)),
+                  0.1f, players[i].color);
+    }
+
+    addString(StringBuffer<16>(turns), Vector2(0.6f, 0.9f), 0.1f,
+              Color4(255, 255, 255, 255));
+
     fontVertexBuffer.setData(fontBuffer, GL::BufferUsage::DYNAMIC);
 }
 
@@ -527,25 +799,88 @@ static void rebuild() {
     buildFontBuffer();
 }
 
-static int waitTicks = 0;
+static void giveResources(int dice) {
+    for(const Hexagon& h : gHexagons) {
+        if(h.number != dice) {
+            continue;
+        }
+        for(int c : h.corners) {
+            const Corner& corner = gCorners[c];
+            if(corner.player < 0 || corner.player >= players.getLength()) {
+                continue;
+            }
+            players[corner.player].giveResource(h.resource, 1 + corner.city);
+        }
+    }
+}
 
 static void doTurn() {
+    int dice = rng.next(1, 6) + rng.next(1, 6);
+    LOG_INFO(StringBuffer<256>("Dice: ").append(dice));
+    giveResources(dice);
+    players[currentPlayer].takeTurn();
+}
+
+static int waitTicks = 0;
+
+static bool checkForWinner() {
+    bool won = false;
+    for(const Player& p : players) {
+        if(p.getPoints() >= 10) {
+            return true;
+        }
+    }
+    return won;
+}
+
+static void restart() {
+    for(Corner& c : gCorners) {
+        c.reset();
+    }
+    for(Path& p : gPaths) {
+        p.reset();
+    }
+    for(Player& p : players) {
+        p.reset();
+    }
+    currentPlayer = 0;
+    turns = 0;
+    placeStartSettlement();
+}
+
+static void restartMap() {
+    restart();
+    initResources();
+    initNumbers();
 }
 
 static void tick() {
+    if(Window::Controls::wasReleased(restartButton)) {
+        restart();
+    }
+    if(Window::Controls::wasReleased(restartMapButton)) {
+        restartMap();
+    }
+
     waitTicks++;
-    if(waitTicks < 100) {
+    if(waitTicks < 1) {
         return;
     }
     waitTicks = 0;
 
+    if(checkForWinner()) {
+        return;
+    }
+
+    turns++;
     doTurn();
-    currentPlayer = (currentPlayer + 1) % players.getLength();
     rebuild();
+    currentPlayer = (currentPlayer + 1) % players.getLength();
 }
 
 static void render(float lag) {
     GL::setViewport(Window::getSize()[0], Window::getSize()[1]);
+    GL::clear();
     shader.use();
     vertexBuffer.draw(vertexBuffer.getSize() /
                       static_cast<int>(sizeof(Vector2) + sizeof(Color4)));
@@ -588,6 +923,10 @@ int main() {
     vertexBuffer.init(VertexBuffer::Attributes().addFloat(2).addColor4());
     fontVertexBuffer.init(
         VertexBuffer::Attributes().addFloat(2).addFloat(2).addColor4());
+    restartButton = Window::Controls::add("Restart");
+    Window::Controls::bindKey(restartButton, GLFW_KEY_R);
+    restartMapButton = Window::Controls::add("Restart Map");
+    Window::Controls::bindKey(restartMapButton, GLFW_KEY_M);
     init();
     rebuild();
     Window::show();