Skip to content

Instantly share code, notes, and snippets.

@NachiaVivias
Last active October 24, 2025 13:58
Show Gist options
  • Select an option

  • Save NachiaVivias/c100a57da91c147e094dab862970278e to your computer and use it in GitHub Desktop.

Select an option

Save NachiaVivias/c100a57da91c147e094dab862970278e to your computer and use it in GitHub Desktop.
Procon36 brief visualizer
# include <Siv3D.hpp> // Siv3D v0.6.15
// #define ForWeb
static inline String SampleProblemJson = UR"({"problem":{"field":{"size":4,"entities":[[6,3,4,0],[1,5,3,5],[2,7,0,6],[1,2,7,4]]}}})";
static inline String SampleSolutionJson = UR"({"ops": [{"n": 2,"x": 0,"y": 0},{"n": 3,"x": 1,"y": 1},{"n": 2,"x": 2,"y": 1},{"n": 2,"x": 2,"y": 2},{"n": 2,"x": 2,"y": 2}]})";
struct VerifyResult {
bool isOk;
String msg;
};
struct Operation {
int64 y;
int64 x;
int64 r;
static Operation FromJson(JSON json) {
Operation res;
res.x = json[U"x"].get<int32>();
res.y = json[U"y"].get<int32>();
res.r = json[U"n"].get<int32>();
return res;
}
JSON toJson() {
JSON res;
res[U"x"] = x;
res[U"y"] = y;
res[U"n"] = r;
return res;
}
bool inRange(int64 a, int64 b) {
return x <= a && a < x + r && y <= b && b < y + r;
}
};
struct Board {
Grid<int32> grid;
auto width() const { return grid.width(); }
auto height() const { return grid.height(); }
Size size() const { return Size(width(), height()); }
Board appliedOperation(const Operation& op) {
Board res = *this;
for (auto [i, j] : step(Size{ op.r, op.r })) res.grid[Point(op.x + i, op.y + j)] = grid[Point(op.x + j, op.y + (op.r - 1 - i))];
return res;
}
Array<Operation> allCandidateOperations() const {
Array<Operation> res;
for (int32 r = 2; r <= width() && r <= height(); r++) {
for (int32 x = 0; x + r <= width(); x++) {
for (int32 y = 0; y + r <= height(); y++) {
res.push_back({ y,x,r });
}
}
}
return res;
}
int32 numAdjacentPairs() const {
int32 ans = 0;
for (auto p : step(Size(width(), height() - 1))) if (grid[p] == grid[p + Point{ 0, 1 }]) ans += 1;
for (auto p : step(Size(width() - 1, height()))) if (grid[p] == grid[p + Point{ 1, 0 }]) ans += 1;
return ans;
}
int32 sumManhattanDist() const {
Array<std::array<Point, 2>> entityCenterPos(size().area() / 2);
for (auto p : step(size())) {
entityCenterPos[grid[p]][0] = p;
std::swap(entityCenterPos[grid[p]][0], entityCenterPos[grid[p]][1]);
}
int32 ans = 0;
for (auto [u, v] : entityCenterPos) {
Point d = u - v;
ans += std::abs(d.x) + std::abs(d.y) - 1;
}
return ans;
}
};
struct TextReadWrap {
struct BadFormatError { String msg; };
TextReader cin;
int32 r = 0;
TextReadWrap(String fname) : cin(fname) {}
void badFormat(String msg) { throw BadFormatError{ U"Problem format error ({}) on line {}"_fmt(msg, r) }; }
String readLine() { r++; return cin.readLine().value_or_eval([&]() -> String { badFormat(U"line read failed"); }); }
Array<int64> readLineSplitInt(int32 num, int64 low, int64 high) {
auto sp = readLine().split(U' ');
if ((int32)sp.size() < num) badFormat(U"integer token missing");
size_t i = 0;
return sp.map([&](String f) -> int64 {
auto k = ParseIntOpt<int64>(f).value_or_eval([&]() -> int64 { badFormat(U"integer parse error"); });
if (k < low || high < k) badFormat(U"value range is invalid #{}"_fmt(i));
i++;
return k; });
}
};
struct Problem {
Board board;
auto width() const { return board.width(); }
auto height() const { return board.height(); }
int32 numEntity() const { return int32(width() * height() / 2); }
static Problem LoadFromTextFormat(String fname) {
TextReadWrap cin{ fname };
Problem res;
int32 n = (int32)cin.readLineSplitInt(1, 2, 1000)[0], m = n * n / 2;
res.board.grid.assign(n, n, -1);
Array<int32> cnt(m);
for (int32 i = 0; i < n; i++) {
auto g = cin.readLineSplitInt(n, 0, m - 1);
for (int32 j : step((int32)n)) {
res.board.grid[Point{ j, i }] = (int32)g[j];
}
}
return res;
}
static Problem FromJson(JSON json) {
Problem res;
json = json[U"problem"][U"field"];
int32 n = json[U"size"].get<int32>();
auto g = json[U"entities"];
res.board.grid.resize({ n, n });
for (auto p : step(Size(n, n))) res.board.grid[p] = g[p.y][p.x].get<int32>();
return res;
}
VerifyResult verify(bool strict = false) {
if (strict && width() != height()) return { false, U"width != height" };
auto w = width();
auto h = height();
if (w < 4) return { false, U"width < 4" };
if (h < 4) return { false, U"height < 4" };
if (w * h % 2 != 0) return { false, U"(number of entity) % 2 != 0" };
Array<int32> occurrence(numEntity());
for (auto p : step(Size(w, h))) {
auto v = board.grid[p];
if (!(0 <= v && v < numEntity())) return { false, U"entity id out of range" };
occurrence[v]++;
}
for (auto a : step(numEntity())) if (occurrence[a] != 2) return { false, U"(entity {} occurrence) != 2"_fmt(a) };
return { true, U"" };
}
};
struct Solution {
Array<Operation> ops;
int32 operationCount() const { return (int32)ops.size(); }
Operation get(size_t i) const { return ops[i]; }
static Solution LoadFromTextFormat(String fname) {
TextReadWrap cin{ fname };
Solution res;
int32 n = (int32)(cin.readLineSplitInt(1, 2, 100000)[0]);
res.ops = step(n).map([&](auto) -> Operation {
auto g = cin.readLineSplitInt(n, 0, 1000);
return Operation{ g[0], g[1], g[2] };
});
return res;
}
static Solution FromJson(JSON json) {
Solution res;
for (auto op : json[U"ops"].arrayView()) res.ops.push_back(Operation::FromJson(op));
return res;
}
JSON toJson() const {
Array<JSON> opsJson = ops.map([&](Operation op) -> JSON { return op.toJson(); });
JSON res;
res[U"ops"] = opsJson;
return res;
}
VerifyResult verify(const Problem& problem) {
int32 x_max = (int32)problem.width();
int32 y_max = (int32)problem.height();
int32 i = 0;
for (auto& op : ops) {
if (op.r < 1) return { false, U"op.r < 0 at ops[{}]"_fmt(i) };
if (op.r >= x_max) return { false, U"op.r >= width at ops[{}]"_fmt(i) };
if (op.r >= y_max) return { false, U"op.r >= height at ops[{}]"_fmt(i) };
if (op.x < 0) return { false, U"rotation area range error(0) at ops[{}]"_fmt(i) };
if (op.x + op.r - 1 >= x_max) return { false, U"rotation area range error(1) at ops[{}]"_fmt(i) };
if (op.y < 0) return { false, U"rotation area range error(2) at ops[{}]"_fmt(i) };
if (op.y + op.r - 1 >= y_max) return { false, U"rotation area range error(3) at ops[{}]"_fmt(i) };
i++;
}
return { true, U"" };
}
};
struct SolutionScore {
bool isValid;
int32 numPair;
int32 numOperation;
};
struct SolutionProgress {
Problem task;
Solution sol;
Array<Board> allBoard;
void setSolution(
Problem _task,
Solution _sol
) {
task = std::move(_task);
sol = std::move(_sol);
allBoard.resize(sol.operationCount() + 1);
allBoard[0] = task.board;
for (size_t i = 0; i < sol.operationCount(); i++) {
allBoard[i + 1] = allBoard[i].appliedOperation(sol.get(i));
}
}
int32 sceneCount() const {
return sol.operationCount() + 1;
}
};
Array<ColorF> GetColorSet(int32 ty) {
Array<ColorF> colors;
if (ty == 0) {
colors.push_back(Palette::Red);
colors.push_back(Palette::Orange);
colors.push_back(Palette::Blue);
colors.push_back(Palette::Cyan);
}
if (ty == 1) {
colors.push_back(Palette::Darkred);
colors.push_back(Palette::Darkgoldenrod);
colors.push_back(Palette::Darkblue);
colors.push_back(Palette::Darkcyan);
}
return colors;
}
SolutionProgress MakeSolutionProgress(Problem problem, Solution solution) {
SolutionProgress res;
res.task = problem;
res.sol = solution;
res.allBoard = {};
res.allBoard.push_back(res.task.board);
for (auto& op : res.sol.ops) {
res.allBoard.push_back(res.allBoard.back().appliedOperation(op));
}
return res;
}
ColorF RandomEntityColor() {
return ColorF(Random(), Random(), Random(), 1.0);
}
String PROBLEM_PATH = U"problem.json";
String SOLUTION_PATH = U"solution.json";
String SOLUTION_DIRECTORY = U"sols";
bool ReloadFromJson(Problem& problem, Solution& solution, JSON problemJson, JSON solutionJson) {
Problem problem_tmp;
Solution solution_tmp;
try {
problem_tmp = Problem::FromJson(problemJson);
solution_tmp = Solution::FromJson(solutionJson);
}
catch (...) {
Console << U"a file is not correctly saved";
return false;
}
auto pverify = problem_tmp.verify();
if (!pverify.isOk) {
Console << pverify.msg;
return false;
}
auto sverify = solution_tmp.verify(problem_tmp);
if (!sverify.isOk) {
Console << sverify.msg;
return false;
}
std::swap(problem_tmp, problem);
std::swap(solution_tmp, solution);
return true;
}
bool ReloadFromJsonString(Problem& problem, Solution& solution, String problemJsonString, String solutionJsonString) {
JSON jsonProblem;
JSON jsonSolution;
try {
jsonProblem = JSON::Parse(problemJsonString);
jsonSolution = JSON::Parse(solutionJsonString);
return ReloadFromJson(problem, solution, jsonProblem, jsonSolution);
}
catch (...) {
Console << U"json load error";
return false;
}
}
bool ReloadFromFile(Problem& problem, Solution& solution) {
try {
const JSON jsonProblem = JSON::Load(PROBLEM_PATH);
const JSON jsonSolution = JSON::Load(SOLUTION_PATH);
return ReloadFromJson(problem, solution, jsonProblem, jsonSolution);
}
catch (...) {
Console << U"a file is not correctly saved";
return false;
}
}
void VisualizerMain()
{
Problem problem;
Solution solution;
SolutionProgress digest;
Grid<Vec2> animOffset(problem.width(), problem.height(), Vec2());
Grid<int32> animHighlight(problem.width(), problem.height(), 0);
int32 id = 0;
double animFrame = 0.0;
double animSpeed = 0.0;
bool animating = false;
double sliderVal = 0.0;
bool bad_load = true;
double auto_anim_speed = 0.0;
double uiAreaBorderY = 50.0;
RectF boardDisplayRect;
Array<String> uiPhaseId = { U"Board", U"Graph", U"File" };
int32 uiPhase = 0;
// phase 0 : board
int32 maxOpSize = 1;
Array<int32> opCountBySize;
Array<int32> opCountBySizeAll;
Grid<double> visualHeat;
const double heatDuration = 0.5;
Array<ColorF> entityColor;
// phase 1 : statistic graph
RectF graph1Rect = RectF(50.0, uiAreaBorderY + 20.0, 550.0, 350.0).movedBy(Vec2(0.5, 0.5));
Array<double> matchGraphData;
Array<double> distGraphData;
// phase 2 : file import
#ifdef ForWeb
TextEditState problemFileTextArea;
TextEditState solutionFileTextArea;
#else
TextAreaEditState problemFileTextArea;
TextAreaEditState solutionFileTextArea;
#endif
RectF problemFileAreaRect = RectF(50.0, uiAreaBorderY + 20.0, 550.0, 200.0);
RectF solutionFileAreaRect = RectF(50.0, uiAreaBorderY + 230.0, 550.0, 200.0);
int32 jsonLoadStatus = 0;
bool flagMarkPairs = true;
bool flagColorByDistance = true;
bool flagConnectPairs = false;
bool flagEntityId = false;
bool flagHeat = true;
bool switchUI = true;
auto UpdateTurnId = [&](int32 newId) {
if (newId < id) {
for (auto& h : visualHeat) h = 0.0;
}
id = newId;
opCountBySize.assign(maxOpSize, 0);
for (int32 i = 0; i < id; i++) {
opCountBySize[solution.ops[i].r - 1] += 1;
}
if (id != int32(digest.sol.operationCount())) {
auto op = digest.sol.ops[id];
}
else {
auto_anim_speed = 0.0;
animSpeed = 0.0;
animating = false;
}
animFrame = 0.0;
sliderVal = (double)id / digest.sol.operationCount();
};
auto when_reloaded = [&]() -> void {
digest = MakeSolutionProgress(problem, solution);
auto board_size = problem.board.size();
id = 0;
animFrame = 0.0;
animSpeed = 0.0;
animating = false;
boardDisplayRect = RectF(50.0, uiAreaBorderY + 5.0, 500.0, 500.0);
maxOpSize = (int32)std::min(board_size.x, board_size.y);
opCountBySize.assign(maxOpSize, 0);
opCountBySizeAll = opCountBySize;
for (auto& op : solution.ops) opCountBySizeAll[op.r - 1] += 1;
visualHeat.resize(board_size);
entityColor.resize(board_size.area() / 2);
for (auto& c : entityColor) c = RandomEntityColor();
matchGraphData = digest.allBoard.map([&](const Board& b) -> double { return (double)b.numAdjacentPairs() / digest.task.numEntity(); });
double maxDist = 0.0;
{
auto [n, m] = board_size;
for (int32 i : step(n)) for (int32 j : step(m)) maxDist += std::abs(m - 1 - j * 2);
for (int32 i : step(n)) for (int32 j : step(m)) maxDist += std::abs(n - 1 - i * 2);
maxDist /= 2.0;
}
maxDist -= problem.numEntity();
distGraphData = digest.allBoard.map([&](const Board& b) -> double { return (double)b.sumManhattanDist() / maxDist; });
UpdateTurnId(0);
};
auto reload = [&]() -> void {
bad_load = !ReloadFromFile(problem, solution);
if (!bad_load) when_reloaded();
};
#ifdef ForWeb
Font font{ 30 };
Font boldFont{ 30 };
#else
Font font{ 30 };
Font boldFont{ 30, Typeface::Bold };
#endif
//reload();
ReloadFromJsonString(problem, solution, SampleProblemJson, SampleSolutionJson);
when_reloaded();
bad_load = false;
while (System::Update())
{
// control R,L arrow keys
{
if (KeyRight.down()) {
if (animating) {
animSpeed = 0.0; animFrame = 0.0;
animating = false;
if (id + 1 < int32(digest.sceneCount())) {
UpdateTurnId(id + 1);
}
}
if (id + 1 < int32(digest.sceneCount())) {
animSpeed = 0.04;
animating = true;
}
}
if (KeyLeft.down()) {
if (animating) {
animSpeed = 0.0; animFrame = 0.0;
animating = false;
}
else {
if (id - 1 >= 0) UpdateTurnId(id - 1);
animating = false;
}
}
}
// control number keys
{
int32 input_number_key = 0;
if (Key1.down()) input_number_key = 1;
if (Key2.down()) input_number_key = 2;
if (Key3.down()) input_number_key = 3;
if (Key4.down()) input_number_key = 4;
if (Key5.down()) input_number_key = 5;
if (Key6.down()) input_number_key = 6;
if (Key7.down()) input_number_key = 7;
if (Key8.down()) input_number_key = 8;
if (Key9.down()) input_number_key = 9;
input_number_key *= 2;
if (input_number_key != 0 && KeyShift.pressed()) input_number_key -= 1;
if (input_number_key != 0) {
if (input_number_key <= 2) {
auto_anim_speed = 0.0;
}
else {
auto_anim_speed = (double)(digest.sceneCount() - 1) * 0.5 / 60.0 / Pow(1.5, 18 - input_number_key);
auto_anim_speed = Max(auto_anim_speed, 0.005);
animSpeed = auto_anim_speed;
animating = true;
}
}
}
// animation step
int32 prevTurnStep = animFrame <= 0.0 ? id : id + 1;
{
if (animSpeed >= 1.0) {
animFrame = 0.0;
UpdateTurnId(Clamp<int32>(id + (int32)Round(animSpeed), 0, digest.sceneCount() - 1));
animSpeed = auto_anim_speed;
animating = auto_anim_speed != 0.0;
}
else {
animFrame += animSpeed;
if (animFrame > 1.0) {
animSpeed = auto_anim_speed;
animFrame = 0.0;
animating = auto_anim_speed != 0.0;
UpdateTurnId(id + 1);
}
}
}
int32 newTurnStep = animFrame <= 0.0 ? id : id + 1;
// tick visual heat
{
double delta = Scene::DeltaTime();
for (auto& h : visualHeat) {
h -= delta;
if (h < 0.0) h = 0.0;
}
// heaten
for (auto i = prevTurnStep; i < newTurnStep; i++) {
auto op = digest.sol.ops[i];
for (auto x = op.x; x < op.x + op.r; x++) {
for (auto y = op.y; y < op.y + op.r; y++) {
visualHeat[Point(x, y)] = heatDuration;
}
}
}
}
if (uiPhase == 0) {
boardDisplayRect.drawFrame();
// Array<ColorF> colors = GetColorSet(1);
Array<ColorF> colors = GetColorSet(0);
Array<ColorF> colorsHighlighted = GetColorSet(0);
// draw
{
// bool anim_enabled = animSpeed <= 0.2 && animating;
auto viewport = ScopedViewport2D(boardDisplayRect.asRect());
RectF drawArea{ Vec2::Zero(), boardDisplayRect.size };
double massDisplayWidth = std::max(drawArea.w / digest.task.width(), drawArea.h / digest.task.height()) * 0.95;
Vec2 gridAreaSize = Vec2(digest.task.width(), digest.task.height()) * massDisplayWidth;
RectF gridArea{ drawArea.center() - gridAreaSize / 2.0, gridAreaSize };
Vec2 displayMassSize{ massDisplayWidth, massDisplayWidth };
double textSize = massDisplayWidth * 0.45;
Optional<Operation> animatingOp = none;
if (0.0 < animFrame) animatingOp = digest.sol.ops[id];
Vec2 opCenterInDisplay = Vec2::Zero();
if (animatingOp.has_value()) {
Vec2 opCenter = Vec2(animatingOp->x + animatingOp->r * 0.5, animatingOp->y + animatingOp->r * 0.5);
opCenterInDisplay = gridArea.tl() + displayMassSize * opCenter;
}
// calc pair mark
Array<int32> pairMarks(digest.task.numEntity(), 0);
{
auto& grid = digest.allBoard[id].grid;
int32 req = 0;
req += 1;
for (const auto& [x, y] : step(digest.task.board.size())) {
if (0 < x && grid[Point(x - 1, y)] == grid[Point(x, y)]) {
pairMarks[grid[Point(x, y)]] += 1;
}
if (0 < y && grid[Point(x, y - 1)] == grid[Point(x, y)]) {
pairMarks[grid[Point(x, y)]] += 1;
}
}
if (animFrame > 0.0) {
auto& nxgrid = digest.allBoard[id + 1].grid;
req += 1;
for (const auto& [x, y] : step(digest.task.board.size())) {
if (0 < x && nxgrid[Point(x - 1, y)] == nxgrid[Point(x, y)]) {
pairMarks[nxgrid[Point(x, y)]] += 1;
}
if (0 < y && nxgrid[Point(x, y - 1)] == nxgrid[Point(x, y)]) {
pairMarks[nxgrid[Point(x, y)]] += 1;
}
}
}
for (auto& a : pairMarks) {
a = (a == req ? 0 : -1);
}
}
Array<std::array<Vec2, 2>> entityCenterPos(problem.numEntity());
Grid<Polygon> massAfterRotate(problem.board.size());
// calc mass position
for (auto p : step(problem.board.size())) {
bool massRotating = animatingOp.has_value() && animatingOp->inRange(p.x, p.y);
RectF rect = RectF(gridArea.tl() + displayMassSize * Vec2(p), displayMassSize);
Polygon beforeRotate = rect.asPolygon();
Polygon afterRotate = beforeRotate;
if (massRotating) afterRotate.rotateAt(opCenterInDisplay, 0.5 * Math::Pi * animFrame);
auto center = afterRotate.centroid();
auto entityId = digest.allBoard[id].grid[p];
entityCenterPos[entityId][0] = center;
std::swap(entityCenterPos[entityId][0], entityCenterPos[entityId][1]);
massAfterRotate[p] = afterRotate;
}
double minDistMass = 1.0;
double maxDistMass = gridAreaSize.length();
// mass background
for (int rotated_priority : step(2)) {
for (auto p : step(problem.board.size())) {
bool massRotating = animatingOp.has_value() && animatingOp->inRange(p.x, p.y);
if ((massRotating && rotated_priority == 0) || (!massRotating && rotated_priority == 1)) continue;
Polygon afterRotate = massAfterRotate[p];
auto center = afterRotate.centroid();
auto entityId = digest.allBoard[id].grid[p];
ColorF entityPaintColor = entityColor[entityId].withAlpha(0.25);
if (flagColorByDistance) {
double distEntity = entityCenterPos[entityId][0].distanceFrom(entityCenterPos[entityId][1]);
double distanceScore = 0.0;
if (distEntity > massDisplayWidth * (1.0 + 1.0e-4)) {
distanceScore = Min(1.0, 0.2 + 0.8 * Clamp((distEntity - massDisplayWidth) / (maxDistMass - minDistMass), 0.0, 1.0));
}
entityPaintColor = ColorF(Palette::Black).lerp(Palette::Yellow, distanceScore * 0.8);
}
afterRotate.draw(entityPaintColor);
}
}
// entity id number
if (flagEntityId) {
for (auto p : step(problem.board.size())) {
auto entityId = digest.allBoard[id].grid[p];
Polygon afterRotate = massAfterRotate[p];
auto center = afterRotate.centroid();
boldFont(ToString(entityId)).drawAt(textSize, center, Palette::White);
}
}
// draw pair mark
if (flagMarkPairs) {
for (auto i : step(problem.numEntity())) {
if (pairMarks[i] >= 0) {
Vec2 p0 = entityCenterPos[i][0];
Vec2 p1 = entityCenterPos[i][1];
double markWidth = massDisplayWidth * 0.25;
Vec2 normal = Line(p0, p1).normal() * -markWidth;
Polygon mark = Polygon::Correct(Array<Vec2>({ p0 - normal, p0 + normal, p1 + normal, p1 - normal }))[0];
mark.append(Circle(p1, markWidth).asPolygon());
mark.append(Circle(p0, markWidth).asPolygon());
mark.draw(ColorF(Palette::Limegreen).withAlpha(0.3));
}
}
}
// pairs segment
if (flagConnectPairs) {
for (auto i : step(problem.numEntity())) {
Line(entityCenterPos[i][0], entityCenterPos[i][1]).draw(2.0, entityColor[i]);
}
}
// heat
if (flagHeat) {
for (auto [x, y] : step(Size(problem.width(), problem.height()))) {
RectF rect = RectF(gridArea.tl() + displayMassSize * Vec2(x, y), displayMassSize);
rect.draw(Palette::Red.withAlpha((int32)(255 * 0.8 * visualHeat[Point(x, y)])));
}
}
}
// drawText
{
double ypos = uiAreaBorderY + 3.0;
double xpos = 570.0;
double maxHist = Max(1.0, (double)*std::max_element(opCountBySizeAll.begin(), opCountBySizeAll.end()));
for (int32 i = 2; i <= maxOpSize; i++) {
double margin = 1.0;
double h = 20.0;
double hist_x = xpos + 65.0;
RectF rg = { Vec2{ hist_x, ypos + margin }, Vec2{ 100.0, h - margin * 2 } };
RectF hist1 = rg;
RectF hist2 = rg;
hist1.w *= opCountBySizeAll[i - 1] / maxHist;
hist2.w *= opCountBySize[i - 1] / maxHist;
hist1.draw(Palette::White.withAlpha(64));
hist2.draw(Palette::Orange);
font(U"{}:"_fmt(i, i)).draw(h - margin * 2, Arg::rightCenter(Vec2(xpos + 24.0, ypos + h * 0.5)));
font(U"{}"_fmt(opCountBySize[i - 1])).draw(18.0, Arg::rightCenter(Vec2(xpos + 60.0, ypos + h * 0.5)));
ypos += h;
}
}
}
else if (uiPhase == 1) {
graph1Rect.drawFrame(0.0, 2.0, Palette::White.withAlpha(0), Palette::White);
const int32 wn = matchGraphData.size();
int32 memori = 100;
if (wn <= 500) memori = 50;
if (wn <= 200) memori = 20;
if (wn <= 100) memori = 10;
Array<Vec2> curve(wn), curve2(wn);
Array<double> relX = step(wn).asArray().map([&](auto i) { return (double)i / (curve.size() - 1); });
for (auto i : step(wn)) {
curve[i] = graph1Rect.getRelativePoint(relX[i], 1.0 - matchGraphData[i]);
}
for (auto i : step(wn)) {
curve2[i] = graph1Rect.getRelativePoint(relX[i], 1.0 - distGraphData[i]);
}
for (int32 i = 0; i < curve.size(); i += memori) {
font(ToString(i)).draw(16.0, graph1Rect.getRelativePoint(relX[i], 1.0) + Vec2(2.0, 0.0));
}
for (int32 i = 10; i <= 100; i+=10) {
font(ToString(i) + U"%").draw(16.0, Arg::leftCenter(graph1Rect.getRelativePoint(1.0, 1.0 - i / 100.0) + Vec2(5.0, 0.0)));
}
for (auto i : step(curve.size())) if (1 <= i) Line(curve2[i], curve2[i - 1]).draw(3.0, Palette::Yellow.withAlpha(96));
for (auto i : step(curve.size())) if (1 <= i) Line(curve[i], curve[i - 1]).draw(3.0, Palette::Lime.withAlpha(96));
for (auto i : step(curve.size())) curve[i].asCircle(2.0).draw(Palette::Lime);
for (int32 i = memori; i < curve.size(); i += memori) {
Line(graph1Rect.getRelativePoint(relX[i], 0.0), graph1Rect.getRelativePoint(relX[i], 1.0)).draw();
}
for (int32 i = 1; i < 10; i += 1) {
Line(graph1Rect.getRelativePoint(0.0, i * 0.1), graph1Rect.getRelativePoint(1.0, i * 0.1)).draw();
}
Line(graph1Rect.getRelativePoint(relX[id], 0.0), graph1Rect.getRelativePoint(relX[id], 1.0)).draw(Palette::Yellow);
}
else if (uiPhase == 2) {
font(U"Problem file").draw(problemFileAreaRect.tl());
font(U"Solution file").draw(solutionFileAreaRect.tl());
if (SimpleGUI::Button(U"clear", problemFileAreaRect.tl() + Vec2(220.0, 0.0))) {
problemFileTextArea.clear();
}
if (SimpleGUI::Button(U"clear", solutionFileAreaRect.tl() + Vec2(220.0, 0.0))) {
solutionFileTextArea.clear();
}
#ifdef ForWeb
SimpleGUI::TextBox(problemFileTextArea, problemFileAreaRect.tl() + Vec2(0.0, 40.0), problemFileAreaRect.size.x, 1000000);
SimpleGUI::TextBox(solutionFileTextArea, solutionFileAreaRect.tl() + Vec2(0.0, 40.0), solutionFileAreaRect.size.x, 1000000);
#else
SimpleGUI::TextArea(problemFileTextArea, problemFileAreaRect.tl() + Vec2(0.0, 40.0), problemFileAreaRect.size - Vec2(0.0, 40.0), 1000000);
SimpleGUI::TextArea(solutionFileTextArea, solutionFileAreaRect.tl() + Vec2(0.0, 40.0), solutionFileAreaRect.size - Vec2(0.0, 40.0), 1000000);
#endif
if (SimpleGUI::Button(U"load", solutionFileAreaRect.bl() + Vec2(0.0, 20.0))) {
if (!ReloadFromJsonString(problem, solution, problemFileTextArea.text, solutionFileTextArea.text)) {
jsonLoadStatus = -1;
}
else {
jsonLoadStatus = 1;
when_reloaded();
}
}
if (jsonLoadStatus == 1) {
font(U"Success").draw(solutionFileAreaRect.bl() + Vec2(100.0, 20.0), Palette::Limegreen);
}
else if (jsonLoadStatus == -1) {
font(U"Failed").draw(solutionFileAreaRect.bl() + Vec2(100.0, 20.0), Palette::Orange);
}
}
// top
{
double xpos = 10.0;
double yline = 10.0;
//if (SimpleGUI::Button(U"load", Vec2(xpos, yline), 80.0)) { reload(); } xpos += 100.0;
if (bad_load) {
font(U"FAILED TO LOAD").draw(Vec2(xpos, yline), Palette::Orange); xpos += 250.0;
}
else {
String turnText = U"{} / {}"_fmt(id, solution.operationCount());
RectF reg = font(turnText).region(Arg::leftCenter(Vec2(xpos, (yline + uiAreaBorderY) / 2)));
font(turnText).draw(Arg::leftCenter(Vec2(xpos, (yline + uiAreaBorderY) / 2)));
font(U"turns").draw(20.0, Arg::bottomLeft(reg.br() + Vec2(5.0, 0.0)));
xpos += 250.0;
}
int32 numAdjacent = digest.allBoard[id].numAdjacentPairs();
double prop = (double)numAdjacent / digest.task.numEntity();
RectF gaugeOutRect = RectF::FromPoints(Vec2(xpos, yline), Vec2(boardDisplayRect.tr().x, uiAreaBorderY));
RectF gaugeRect = RectF::FromPoints(gaugeOutRect.getRelativePoint(0.0, 0.1), gaugeOutRect.getRelativePoint(1.0, 0.9));
RectF gaugeActiveRect = RectF(gaugeRect.pos, gaugeRect.size * Vec2(prop, 1.0));
gaugeRect.stretched(2.0).draw(Palette::Gray);
gaugeRect.draw(Palette::Black);
gaugeActiveRect.draw(Palette::Darkgreen);
font(U"{} / {} Pairs"_fmt(numAdjacent, digest.task.numEntity())).draw(20.0, Arg::rightCenter(gaugeRect.rightCenter() - Vec2(10.0, 0.0))); xpos += 250.0;
Line(Vec2(0.0, uiAreaBorderY), Vec2(1000.0, uiAreaBorderY)).movedBy(Vec2(0.0, 0.5)).draw(Palette::White);
}
// UI controller
if (switchUI) {
RectF UIControlArea = RectF::FromPoints(Vec2(560.0, 10.0), Vec2(790.0, 50.0));
double xpos = UIControlArea.x;
double margin = 6.0;
for (auto i : step(uiPhaseId.size())) {
double wid = font(uiPhaseId[i]).region(18.0).w;
RectF usearea = RectF(Vec2(xpos, UIControlArea.y), Vec2(wid + margin * 2.0, UIControlArea.h));
RectF hitbox = usearea.stretched(-2.0);
if (i == uiPhase) {
hitbox.draw(Palette::Yellow.withAlpha(128));
}
font(uiPhaseId[i]).drawAt(18.0, usearea.center());
xpos += usearea.w;
if (hitbox.leftClicked()) uiPhase = int32(i);
}
}
// Slider
{
if (SimpleGUI::Slider(sliderVal, Vec2(0.0, 560.0), 600.0)) {
int32 nextTurnId = (int32)Round(sliderVal * solution.operationCount());
UpdateTurnId(nextTurnId);
}
}
// check boxes
if (switchUI && uiPhase == 0) {
double cb_max_width = 0.0;
auto txSize = font(U"[Space] : show/hide").region(15.0).size;
cb_max_width = Max(cb_max_width, txSize.x);
auto cbSize_1 = SimpleGUI::CheckBoxRegion(U"Connect Pairs", Vec2::Zero()).size;
cb_max_width = Max(cb_max_width, cbSize_1.x);
auto cbSize_2 = SimpleGUI::CheckBoxRegion(U"Mark Pairs", Vec2::Zero()).size;
cb_max_width = Max(cb_max_width, cbSize_2.x);
auto cbSize_3 = SimpleGUI::CheckBoxRegion(U"Distance Color", Vec2::Zero()).size;
cb_max_width = Max(cb_max_width, cbSize_3.x);
auto cbSize_4 = SimpleGUI::CheckBoxRegion(U"Entity ID", Vec2::Zero()).size;
cb_max_width = Max(cb_max_width, cbSize_4.x);
auto cbSize_5 = SimpleGUI::CheckBoxRegion(U"Heat", Vec2::Zero()).size;
cb_max_width = Max(cb_max_width, cbSize_5.x);
Vec2 cb_pos = Scene::Rect().br();
cb_pos.x -= cb_max_width;
font(U"[Space] : show/hide").draw(15.0, Arg::bottomLeft(cb_pos));
cb_pos.y -= txSize.y;
cb_pos.y -= cbSize_1.y;
SimpleGUI::CheckBox(flagConnectPairs, U"Connect Pairs", cb_pos);
cb_pos.y -= cbSize_2.y;
SimpleGUI::CheckBox(flagMarkPairs, U"Mark Pairs", cb_pos);
cb_pos.y -= cbSize_3.y;
SimpleGUI::CheckBox(flagColorByDistance, U"Distance Color", cb_pos);
cb_pos.y -= cbSize_4.y;
SimpleGUI::CheckBox(flagEntityId, U"Entity ID", cb_pos);
cb_pos.y -= cbSize_5.y;
SimpleGUI::CheckBox(flagHeat, U"Heat", cb_pos);
}
if (KeySpace.down()) {
switchUI = !switchUI;
}
}
}
void Main() {
VisualizerMain();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment