opengenerals/processor/logic/GameBoard.cpp
2024-02-13 01:06:07 +08:00

345 lines
7.8 KiB
C++

#include "GameBoard.h"
#include <algorithm>
#include <queue>
GameBoard::GameBoard(pos_t w, pos_t h, const InitInfo& info)
: n(info.n)
, t(0)
, players(info.n + 1)
, w(w)
, h(h)
, view_radius(1)
, board(w * h)
, halfturn_id(0) {
for (std::uint8_t i = 1; i <= n; ++i) {
players[i].id = i;
players[i].team = info.player_team[i];
t = std::max(t, players[i].team);
}
diff_tiles.resize(t + 1);
cover.resize(t + 1, std::vector(h, std::vector<std::uint8_t>(w)));
}
Tile& GameBoard::at(pos_t x, pos_t y) {
return board[x * w + y];
}
const Tile& GameBoard::at(pos_t x, pos_t y) const {
return board[x * w + y];
}
void GameBoard::setOwner(pos_t x, pos_t y, Player p) {
auto flip = [&](int team, Point pos) {
if (!team)
return;
auto pr = diff_tiles[team].insert(pos);
if (!pr.second)
diff_tiles[team].erase(pr.first);
};
auto t1 = players[at(x, y).owner].team, t2 = players[p].team;
if (t1 != t2) {
pos_t sx = x - std::min(x, view_radius),
tx = x + std::min<pos_t>(h - x, view_radius),
sy = y - std::min(y, view_radius),
ty = y + std::min<pos_t>(w - y, view_radius);
for (pos_t xx = sx; xx <= tx; xx++) {
for (pos_t yy = sy; yy <= ty; yy++) {
cover[t1][xx][yy]--;
if (!cover[t1][xx][yy])
flip(t1, Point(xx, yy));
if (!cover[t2][xx][yy])
flip(t2, Point(xx, yy));
cover[t2][xx][yy]++;
}
}
}
at(x, y).owner = p;
updatedPosition(x, y);
}
bool GameBoard::attack(const PlayerMove& o) {
if (!o.isValid(w, h))
return false;
auto& sc_tile = at(o.x, o.y);
auto& tt_tile = at(o.tx(), o.ty());
if (sc_tile.owner != o.player)
return false;
if (sc_tile.unit <= 1)
return false;
if (tt_tile.type == TileType::Mountain)
return false;
auto moving_unit = o.half ? sc_tile.unit - 1 : sc_tile.unit / 2;
sc_tile.unit -= moving_unit;
auto isfriend = isTeammate(o.player, tt_tile.owner);
auto delta
= isfriend ? moving_unit + tt_tile.unit : moving_unit - tt_tile.unit;
updatedPosition(o.x, o.y);
updatedPosition(o.ty(), o.ty());
tt_tile.unit = std::abs(delta);
if (delta > 0 && (!isfriend || tt_tile.type != TileType::Capital)) {
if (!isfriend && tt_tile.type == TileType::Capital)
capitalCaptured(tt_tile.owner, o.player);
setOwner(o.tx(), o.ty(), o.player);
}
return true;
}
void GameBoard::capitalCaptured(Player tt, Player sc) {
for (pos_t x = 0; x < h; ++x) {
for (pos_t y = 0; y < w; ++y) {
auto& tile = at(x, y);
if (tile.owner != tt)
continue;
updatedPosition(x, y);
if (tile.type == TileType::Capital) {
tile.type = TileType::Stronghold;
continue;
}
setOwner(x, y, sc);
tile.unit = std::max(tile.unit / 2, 1);
}
}
new_defeated_players.push_back(tt);
players[tt].is_defeated = 1;
}
bool GameBoard::isTeammate(Player x, Player y) const {
return teamOf(x) == teamOf(y);
}
void GameBoard::turnUpdate() {
for (pos_t x = 0; x < h; ++x) {
for (pos_t y = 0; y < w; ++y) {
auto& tile = at(x, y);
if (tile.owner == neutral_player)
continue;
if (tile.type == TileType::Stronghold
|| tile.type == TileType::Capital)
tile.unit += 1;
if (tile.type == TileType::Swamp) {
if (tile.unit > 0)
tile.unit -= 1;
if (tile.unit == 0)
setOwner(x, y, neutral_player);
}
}
}
}
void GameBoard::roundUpdate() {
for (pos_t x = 0; x < h; ++x) {
for (pos_t y = 0; y < w; ++y) {
auto& tile = at(x, y);
if (tile.owner == neutral_player)
continue;
tile.unit += 1;
}
}
}
void GameBoard::updatedPosition(pos_t x, pos_t y) {
updated_tiles.emplace(x, y);
}
Team GameBoard::teamOf(Player x) const {
return players[x].team;
}
bool GameBoard::isDefeated(Player x) const {
return players[x].is_defeated;
}
std::uint8_t GameBoard::numPlayers() const {
return n;
}
std::uint8_t GameBoard::numTeams() const {
return t;
}
void GameBoard::appendOrderQueue(const PlayerMove& p) {
if (!players[p.player].is_defeated)
players[p.player].orders.emplace_back(p);
}
void GameBoard::clearOrderQueue(Player p) {
players[p].orders.clear();
}
void GameBoard::popOrderQueue(Player p) {
players[p].orders.pop_front();
}
void GameBoard::setOffline(Player p) {
new_offline_players.push_back(p);
}
void GameBoard::offline(Player p) {
if (players[p].is_defeated)
return;
std::vector<std::vector<std::uint8_t>> vis(h, std::vector<std::uint8_t>(w));
std::queue<Point> q;
for (pos_t i = 0; i < h; i++) {
for (pos_t j = 0; j < w; j++) {
if (at(i, j).type == TileType::Capital && at(i, j).owner == p) {
vis[i][j] = 1;
q.emplace(i, j);
}
}
}
Player v = neutral_player;
while (!q.empty()) {
pos_t x = q.front().x, y = q.front().y;
q.pop();
for (int i = 0; i < 4; i++) {
pos_t xx = x + direction_dx[i], yy = y + direction_dy[i];
Tile& tile = at(xx, yy);
if (xx >= h || yy >= w || tile.type == TileType::Mountain
|| tile.type != TileType::Stronghold || vis[xx][yy])
continue;
if (tile.type == TileType::Capital) {
if (players[tile.owner].team != players[p].team)
continue;
v = tile.owner;
break;
}
vis[xx][yy] = 1;
q.emplace(xx, yy);
}
if (v != neutral_player)
break;
}
for (pos_t i = 0; i < h; i++) {
for (pos_t j = 0; j < w; j++) {
Tile& tile = at(i, j);
if (tile.owner == p) {
setOwner(i, j, v);
if (tile.type == TileType::Capital)
tile.type = TileType::Stronghold;
}
}
}
new_defeated_players.push_back(p);
players[p].is_defeated = 1;
}
std::vector<TeamRanking> GameBoard::leaderboard() const {
std::vector<TeamRanking> res(t);
std::vector<PlayerRanking> prank(n);
for (Player i = 1; i <= n; ++i) {
prank[i - 1].player = i;
prank[i - 1].is_defeated = players[i].is_defeated;
}
for (Team i = 1; i <= t; ++i)
res[i - 1].team = i;
for (pos_t x = 0; x < h; ++x) {
for (pos_t y = 0; y < w; ++y) {
auto& tile = at(x, y);
if (tile.owner == neutral_player)
continue;
prank[tile.owner - 1].land += 1;
prank[tile.owner - 1].unit += tile.unit;
}
}
for (const auto& p : prank)
res[teamOf(p.player) - 1].players.push_back(p);
for (auto& team : res) {
for (const auto& p : team.players) {
team.land += p.land;
team.unit += p.unit;
}
sort(team.players.begin(),
team.players.end(),
[](const PlayerRanking& a, const PlayerRanking& b) {
return a.unit != b.unit ? a.unit > b.unit
: a.player < b.player;
});
}
sort(res.begin(),
res.end(),
[](const TeamRanking& a, const TeamRanking& b) {
return a.unit != b.unit ? a.unit > b.unit : a.team < b.team;
});
return res;
}
TeamViewDiff GameBoard::teamViewDiff(Team team) {
TeamViewDiff res;
res.team = team;
for (const auto& pos : updated_tiles) {
if (cover[team][pos.x][pos.y])
diff_tiles[team].insert(pos);
}
for (const auto& pos : diff_tiles[team]) {
auto x = pos.x, y = pos.y;
res.diffs.emplace_back(x, y, cover[team][x][y] ? at(x, y) : Tile());
}
diff_tiles[team].clear();
return res;
}
std::vector<Tile> GameBoard::getViewOf(Team team) const {
std::vector<Tile> res(h * w);
for (pos_t x = 0; x < h; x++) {
for (pos_t y = 0; y < w; y++) {
if (cover[team][x][y])
res[x * w + y] = at(x, y);
}
}
return res;
}
GameState GameBoard::tick() {
halfturn_id++;
for (int _i = 1; _i <= n; _i++) {
int i = (halfturn_id & 1) ? _i : n - _i + 1;
if (!players[i].orders.empty()) {
if (!attack(players[i].orders.front()))
players[i].orders.clear();
}
}
if (!(halfturn_id % 50))
roundUpdate();
if (!(halfturn_id & 1))
turnUpdate();
for (auto p : new_offline_players)
offline(p);
new_offline_players.clear();
GameState res;
res.halfturn_id = halfturn_id;
res.defeated_players = new_defeated_players;
new_defeated_players.clear();
for (Player i = 1; i <= n; i++) {
if (!players[i].is_defeated)
res.alive_players.push_back(i);
}
res.leaderboard = leaderboard();
res.diffs.resize(t);
for (Team i = 1; i <= t; i++)
res.diffs[i] = teamViewDiff(i);
updated_tiles.clear();
return res;
}
PlayerState::PlayerState() {
is_defeated = false;
}