2024-02-11 00:34:05 +08:00

307 lines
7.3 KiB
C++

#include "terrain.h"
#include <assert.h>
#include <functional>
#include <random>
using namespace std;
BadImportTerrainConfig::BadImportTerrainConfig(size_t pt): pt(pt) {}
const char* BadImportTerrainConfig::what() const noexcept {
return ("ImportTerrainConfig " + to_string(pt)).c_str();
}
const char* BadCapitalAssign::what() const noexcept {
return "Bad Capital Assign";
}
inline namespace {
mt19937 rnd(19260817);
enum class VertexType : std::uint8_t {
Ordinary,
Mountain,
Capital,
Cut
};
bool ban(VertexType t) {
return t == VertexType::Mountain || t == VertexType::Capital;
}
template<typename T>
void upmin(T& x, const T& y) {
if (y < x)
x = y;
}
bool findCut(pos_t w, pos_t h, vector<vector<VertexType>>& vertex_type) {
int idx = 0;
vector<vector<int>> dfn(h, vector<int>(w)), low = dfn;
function<void(pos_t, pos_t, pos_t, pos_t, bool)> tarjan =
[&](pos_t x, pos_t y, pos_t fx, pos_t fy, bool rt) {
dfn[x][y] = low[x][y] = ++idx;
int ch = 0;
for (int i = 0; i < 4; i++) {
const pos_t xx = x + direction_dx[i], yy = y + direction_dy[i];
if (xx >= h || yy >= w)
continue;
if (vertex_type[xx][yy] == VertexType::Capital)
vertex_type[x][y] = VertexType::Cut;
if (ban(vertex_type[xx][yy]) || (xx == fx && yy == fy))
continue;
if (!dfn[xx][yy]) {
ch++;
tarjan(xx, yy, x, y, 0);
upmin(low[x][y], low[xx][yy]);
if (!rt && low[xx][yy] >= dfn[x][y])
vertex_type[x][y] = VertexType::Cut;
} else {
upmin(low[x][y], dfn[xx][yy]);
}
}
if (rt && ch >= 2)
vertex_type[x][y] = VertexType::Cut;
};
bool flag = false;
for (pos_t i = 0; i < h; i++) {
for (pos_t j = 0; j < w; j++) {
if (!ban(vertex_type[i][j]) && !dfn[i][j]) {
if (flag)
return false;
flag = 1;
tarjan(i, j, -1, -1, 1);
}
}
}
return true;
};
pair<bool, vector<Point>> generateCapital(
pos_t w,
pos_t h,
vector<vector<VertexType>>& vertex_type,
int r) {
vector<Point> res(r);
for (int i = 0; i <= r; i++) {
if (!findCut(w, h, vertex_type))
return {false, vector<Point>()};
if (i == r)
break;
vector<Point> options;
for (pos_t x = 0; x < h; x++) {
for (pos_t y = 0; y < w; y++) {
if (vertex_type[x][y] == VertexType::Ordinary)
options.emplace_back(x, y);
if (vertex_type[x][y] == VertexType::Cut)
vertex_type[x][y] = VertexType::Ordinary;
}
}
if (options.empty())
return {false, {}};
res[i] = options[rnd() % options.size()];
vertex_type[res[i].x][res[i].y] = VertexType::Capital;
}
return {true, res};
}
} // namespace
GameBoard ImportedTerrain::makeGameBoard(const InitInfo& init_info) {
auto perm = [](std::uint8_t n) {
vector<std::uint8_t> vec(n);
iota(vec.begin(), vec.end(), 0);
shuffle(vec.begin(), vec.end(), rnd);
return vec;
};
GameBoard g(w, h, init_info);
for (pos_t i = 0; i < h; i++) {
for (pos_t j = 0; j < w; j++) {
g.at(i, j).type = tiles[i][j].type;
g.at(i, j).unit = tiles[i][j].unit;
}
}
auto arrange = [&perm, &g](const vector<Player>& players,
const vector<vector<Point>>& capitals,
vector<Player>& rest) {
vector<Point> caps;
for (const auto& v : capitals) {
for (const auto& p : v)
caps.push_back(p);
if (caps.size() >= players.size())
break;
}
auto C = perm(players.size()), D = perm(caps.size());
for (size_t j = D.size(); j < C.size(); j++)
rest.push_back(players[C[j]]);
for (size_t j = 0; j < min(C.size(), D.size()); j++) {
g.at(caps[D[j]].x, caps[D[j]].y).type = TileType::Capital;
g.at(caps[D[j]].x, caps[D[j]].y).owner = players[C[j]];
}
};
vector<vector<Player>> players(g.numTeams());
for (int i = 1, n = g.numPlayers(); i <= n; i++)
players[g.teamOf(i) - 1].push_back(i);
auto A = perm(players.size()), B = perm(capitals.size() - 1);
vector<Player> rest1;
for (size_t i = B.size(); i < A.size(); i++) {
for (auto id : players[A[i]])
rest1.push_back(id);
}
for (size_t i = 0; i < min(A.size(), B.size()); i++)
arrange(players[A[i]], capitals[B[i]], rest1);
vector<Player> rest2;
arrange(rest1, capitals.back(), rest2);
for (pos_t x = 0; x < h; x++) {
for (pos_t y = 0; y < w; y++) {
if (g.at(x, y).type == TileType::Capital) {
for (int i = 0; i < 4; i++) {
const pos_t xx = x + direction_dx[i],
yy = y + direction_dy[i];
if (xx >= h || yy >= w)
continue;
if (g.at(xx, yy).type == TileType::Capital)
throw BadCapitalAssign();
}
}
}
}
int T = 5;
while (T--) {
vector<vector<VertexType>> vertex_type(
h,
vector<VertexType>(w, VertexType::Ordinary));
for (pos_t i = 0; i < h; i++) {
for (pos_t j = 0; j < w; j++) {
if (g.at(i, j).type == TileType::Mountain)
vertex_type[i][j] = VertexType::Mountain;
if (g.at(i, j).type == TileType::Capital)
vertex_type[i][j] = VertexType::Capital;
}
}
auto pr = generateCapital(w, h, vertex_type, rest2.size());
if (!pr.first)
continue;
for (size_t i = 0; i < rest2.size(); i++) {
g.at(pr.second[i].x, pr.second[i].y).type = TileType::Capital;
g.at(pr.second[i].x, pr.second[i].y).owner = rest2[i];
}
return g;
}
throw BadCapitalAssign();
}
ImportedTerrain importTerrain(const ImportTerrainConfig& in) {
ImportedTerrain t;
t.w = in.w;
t.h = in.h;
t.tiles.resize(t.h, vector<ImportedTerrain::TerrainTile>(t.w));
size_t pt = 0;
auto error = [&]() { throw BadImportTerrainConfig(pt); };
auto view = [&](char c) {
return pt < in.value.size() && in.value[pt] == c ? (pt++, true) : false;
};
auto jump = [&](char c) {
if (!view(c))
error();
};
auto read = [&](int l = -99999, int r = 99999) {
bool neg = view('-');
if (!(pt < in.value.size() && isdigit(in.value[pt])))
error();
std::int32_t x = 0;
while (pt < in.value.size() && isdigit(in.value[pt])) {
x = x * 10 + (in.value[pt] ^ '0');
if (x > 99999)
error();
pt++;
}
if (neg)
x = -x;
if (x < l || x > r)
error();
return x;
};
if (t.w < 5 || t.h < 5)
error();
vector<vector<vector<Point>>> vec(27, vector<vector<Point>>(101));
vector<vector<VertexType>> vertex_type(
t.h,
vector<VertexType>(t.w, VertexType::Ordinary));
for (pos_t i = 0; i < t.h; i++) {
for (pos_t j = 0; j < t.w; j++) {
auto& p = t.tiles[i][j];
if (view(' ')) {
p.type = TileType::Blank;
} else if (view('m')) {
p.type = TileType::Mountain;
vertex_type[i][j] = VertexType::Mountain;
} else if (view('n')) {
p.type = TileType::Blank;
p.unit = read();
} else if (view('s')) {
p.type = TileType::Swamp;
} else if (view('g')) {
p.type = TileType::Blank;
bool tag = 1;
int team = (pt < in.value.size() && isupper(in.value[pt])
? in.value[pt++] - 'A'
: (tag = view(' '), 26));
int priority
= (tag && pt < in.value.size() && isdigit(in.value[pt])
? read(1, 99)
: 100);
vec[team][priority].emplace_back(i, j);
} else {
p.type = TileType::Stronghold;
p.unit = read();
}
if (i + 1 < t.h || j + 1 < t.w)
jump(',');
}
}
if (pt != in.value.size())
error();
if (!findCut(t.w, t.h, vertex_type))
error();
t.capitals.emplace_back();
for (int i = 0; i <= 26; i++) {
for (int j = 100; j >= 1; j--) {
if (!vec[i][j].empty()) {
t.capitals.back().emplace_back().swap(vec[i][j]);
}
}
if (!t.capitals.back().empty() && i != 26) {
t.capitals.emplace_back();
}
}
return t;
}