Skip to content

Instantly share code, notes, and snippets.

@NachiaVivias
Last active December 23, 2024 08:00
Show Gist options
  • Select an option

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

Select an option

Save NachiaVivias/af94dde3af1934c3e232e064fb7fcfcd to your computer and use it in GitHub Desktop.
Procon35 brief visualiser

OpenSiv3D のソースコード 1 枚 です。

ファイルから読み込み

実行ファイルと同じ場所に problem.json と solution.jsonを配置し、問題/回答ID欄 (= load の左の 2 枠)を空にすると、それらのファイルから読み込みます。

アニメーション

キーボード操作です。

左右矢印キーで 1 手戻る/進みます。

数字キーで自動アニメーションを再生・スピード調節ができます。

速い順に 9 , Shift+9 , 8 , Shift+8 , ... ,2 で、 1 を押すと次の 1 手の後で停止します。スピードは 9 のときの速度を基準とした倍率で決まります。

#include <Siv3D.hpp> // Siv3D v0.6.14
namespace Procon35 {
using BoardElement = int8;
using Pattern = Grid<BoardElement>;
using Board = Grid<int8>;
const int32 FixedPatternsCount = 25;
const int32 OperationNumberLimit = 1'000'000;
inline bool PatternAccess(const Pattern& pattern, int32 y, int32 x) {
return pattern.inBounds(y, x) ? bool(pattern[y][x]) : false;
}
struct VerifyResult {
bool isOk;
String msg;
};
struct Problem {
Board boardStart;
Board boardGoal;
Array<Pattern> patterns;
int32 height() const { return int32(boardStart.height()); }
int32 width() const { return int32(boardGoal.width()); }
VerifyResult verify(bool strong, bool doThrow);
JSON toJson() const;
static Problem FromJson(JSON json);
int32 numAllPatterns() const { return int32(patterns.size()); }
int32 numGeneralPatterns() const { return numAllPatterns() - FixedPatternsCount; }
};
struct Operation {
int32 patternId = 0;
int32 x = 0;
int32 y = 0;
int32 direction = 0;
static Operation Make(int32 patternId, int32 x, int32 y, int32 direction) {
return Operation{ .patternId = patternId, .x = x, .y = y, .direction = direction };
}
};
struct Solution {
Array<Operation> operations;
int32 operationCount() const { return int32(operations.size()); }
JSON toJson() const;
VerifyResult verify(const Problem& problem);
static Solution FromJson(JSON json);
static Solution FromBinary(const Array<unsigned char>& src);
};
struct JudgeResult {
bool isCorrect() const;
String getMessage() const;
int32 resultCode;
};
JudgeResult Judge(const Problem& problem, const Solution& solution);
String ToString(const Board& board, int32 style = 0);
}
namespace Procon35 {
Board BoardOperate(const Board& board, const Pattern& pattern, int32 posY, int32 posX, int32 direction);
Board BoardOperate(const Board& board, const Problem& problem, Operation operation);
Grid<Point> BoardOperateMoving(const Board& board, const Pattern& pattern, int32 posY, int32 posX, int32 direction);
Grid<Point> BoardOperateMoving(const Board& board, const Problem& problem, Operation operation);
}
namespace Procon35 {
Array<Pattern> GetDefaultPatterns();
}
namespace Procon35 {
struct CompressedBoard {
uint16 h;
uint16 w;
Array<uint64> data;
static CompressedBoard Generate(const Board& src);
Board restore() const;
struct Accessor {
Array<uint64>::const_iterator beg;
int32 offset = 0;
BoardElement operator[](int32 x) const;
};
Accessor operator[](int32 y) const;
};
struct SolutionProgress {
Solution solution;
Array<Board> midState;
};
struct NaiveCompressedSolutionProgress {
Solution solution;
Array<CompressedBoard> midState;
Board getRestoredBoard(int32 idx) const;
};
SolutionProgress MakeSolutionProgress(const Problem& problem, const Solution& solution);
NaiveCompressedSolutionProgress MakeNaiveCompressedSolutionProgress(const Problem& problem, const Solution& solution);
class CompressedSolutionProcess {
struct ProcessPoint {
Board board;
Array<int32> usage;
};
struct CompressedProcessPoint {
CompressedBoard board;
Array<int32> usage;
};
Problem m_problem;
Solution m_solution;
Array<CompressedProcessPoint> m_midState;
int32 m_sampleRate = 1;
CompressedSolutionProcess(Problem problem, Solution solution, int32 sampleRate);
ProcessPoint getRestoredBoard(int32 idx) const;
};
}
namespace Procon35 {
Board BoardOperate(const Board& board, const Pattern& pattern, int32 posY, int32 posX, int32 direction) {
Board res(board.size());
int32 w = int32(board.width());
int32 h = int32(board.height());
// 上
if (direction == 0) {
auto nxpos = Array<int32>(w, 0);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, x - posX)) {
res[nxpos[x]++][x] = board[y][x];
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, x - posX)) {
res[nxpos[x]++][x] = board[y][x];
}
}
// 下
else if (direction == 1) {
auto nxpos = Array<int32>(w, h - 1);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, h - 1 - y - posY, x - posX)) {
res[nxpos[x]--][x] = board[h - 1 - y][x];
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, h - 1 - y - posY, x - posX)) {
res[nxpos[x]--][x] = board[h - 1 - y][x];
}
}
// 左
else if (direction == 2) {
auto nxpos = Array<int32>(h, 0);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, x - posX)) {
res[y][nxpos[y]++] = board[y][x];
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, x - posX)) {
res[y][nxpos[y]++] = board[y][x];
}
}
// 右
else if (direction == 3) {
auto nxpos = Array<int32>(h, w - 1);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, w - 1 - x - posX)) {
res[y][nxpos[y]--] = board[y][w - 1 - x];
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, w - 1 - x - posX)) {
res[y][nxpos[y]--] = board[y][w - 1 - x];
}
}
else {
throw Error(U"Problem::BoardOperate \n direction value error");
}
return res;
}
Board BoardOperate(const Board& board, const Problem& problem, Operation operation) {
return BoardOperate(board, problem.patterns[operation.patternId], operation.y, operation.x, operation.direction);
}
Grid<Point> BoardOperateMoving(const Board& board, const Pattern& pattern, int32 posY, int32 posX, int32 direction) {
Grid<Point> res(board.size());
int32 w = int32(board.width());
int32 h = int32(board.height());
// 上
if (direction == 0) {
auto nxpos = Array<int32>(w, 0);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, x - posX)) {
res[y][x] = Point(x, nxpos[x]++);
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, x - posX)) {
res[y][x] = Point(x, nxpos[x]++);
}
}
// 下
else if (direction == 1) {
auto nxpos = Array<int32>(w, h - 1);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, h - 1 - y - posY, x - posX)) {
res[h - 1 - y][x] = Point(x, nxpos[x]--);
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, h - 1 - y - posY, x - posX)) {
res[h - 1 - y][x] = Point(x, nxpos[x]--);
}
}
// 左
else if (direction == 2) {
auto nxpos = Array<int32>(h, 0);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, x - posX)) {
res[y][x] = Point(nxpos[y]++, y);
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, x - posX)) {
res[y][x] = Point(nxpos[y]++, y);
}
}
// 右
else if (direction == 3) {
auto nxpos = Array<int32>(h, w - 1);
for (auto [x, y] : step(board.size())) if (!PatternAccess(pattern, y - posY, w - 1 - x - posX)) {
res[y][w - 1 - x] = Point(nxpos[y]--, y);
}
for (auto [x, y] : step(board.size())) if (PatternAccess(pattern, y - posY, w - 1 - x - posX)) {
res[y][w - 1 - x] = Point(nxpos[y]--, y);
}
}
else {
throw Error(U"Problem::BoardOperate \n direction value error");
}
return res;
}
Grid<Point> BoardOperateMoving(const Board& board, const Problem& problem, Operation operation) {
return BoardOperateMoving(board, problem.patterns[operation.patternId], operation.y, operation.x, operation.direction);
}
}
namespace Procon35 {
Array<Pattern> GetDefaultPatterns() {
Array<Pattern> result;
for (int32 n : {1, 2, 4, 8, 16, 32, 64, 128, 256}) {
// Ⅰ:すべてのセルが1
{
Pattern pat(n, n, 1);
result.push_back(std::move(pat));
}
// Ⅱ:偶数行のセルが1 で,奇数行のセルが0
if (n != 1) {
Pattern pat(n, n, 0);
for (int32 y : step(n)) for (int32 x : step(n)) if (y % 2 == 0) pat[y][x] = 1;
result.push_back(std::move(pat));
}
// Ⅲ:偶数列のセルが1 で,奇数列のセルが0
if (n != 1) {
Pattern pat(n, n, 0);
for (int32 y : step(n)) for (int32 x : step(n)) if (x % 2 == 0) pat[y][x] = 1;
result.push_back(std::move(pat));
}
}
if (result.size() != FixedPatternsCount) throw Error(U"Problem::GetDefaultPatterns error : # of fixed patterns should be {}"_fmt(FixedPatternsCount));
return result;
}
}
namespace Procon35 {
CompressedBoard CompressedBoard::Generate(const Board& src) {
int32 h = int32(src.height());
int32 w = int32(src.width());
auto data = Array<uint64>(int32(h) * int32(w) / 32 + 1);
for (int32 y : step(h)) for (int32 x : step(w)) {
int32 p = y * w + x;
data[p >> 5] |= uint64(src[y][x]) << ((p & 31) << 1);
}
return CompressedBoard{ .h = uint16(h), .w = uint16(w), .data = std::move(data) };
}
Board CompressedBoard::restore() const {
Board res(w, h);
for (int32 y : step(int32(h))) for (int32 x : step(int32(w))) {
int32 p = y * w + x;
res[y][x] = (data[p >> 5] >> ((p & 31) << 1)) & 3;
}
return res;
}
CompressedBoard::Accessor CompressedBoard::operator[](int32 y) const {
return Accessor{ .beg = data.begin(), .offset = y * int32(w) };
}
BoardElement CompressedBoard::Accessor::operator[](int32 x) const {
int32 p = offset + x;
return (beg[p >> 5] >> ((p & 31) << 1)) & 3;
}
Board NaiveCompressedSolutionProgress::getRestoredBoard(int32 idx) const {
return midState[idx].restore();
}
SolutionProgress MakeSolutionProgress(const Problem& problem, const Solution& solution) {
SolutionProgress res;
res.solution = solution;
res.midState.push_back(problem.boardStart);
for (auto& operation : solution.operations) {
res.midState.push_back(BoardOperate(res.midState.back(), problem, operation));
}
return res;
}
NaiveCompressedSolutionProgress MakeNaiveCompressedSolutionProgress(const Problem& problem, const Solution& solution) {
NaiveCompressedSolutionProgress res;
res.solution = solution;
Board currentBoard = problem.boardStart;
res.midState.push_back(CompressedBoard::Generate(currentBoard));
for (auto& operation : solution.operations) {
currentBoard = BoardOperate(currentBoard, problem, operation);
res.midState.push_back(CompressedBoard::Generate(currentBoard));
}
return res;
}
CompressedSolutionProcess::CompressedSolutionProcess(Problem problem, Solution solution, int32 sampleRate)
: m_problem(std::move(problem))
, m_solution(std::move(solution))
, m_sampleRate(sampleRate)
{
Board currentBoard = m_problem.boardStart;
Array<int32> currentUsage(m_problem.numAllPatterns(), 0);
m_midState.push_back(CompressedProcessPoint{
.board = CompressedBoard::Generate(currentBoard),
.usage = currentUsage });
int32 t = 0;
for (auto& operation : solution.operations) {
currentBoard = BoardOperate(currentBoard, m_problem, operation);
currentUsage[operation.patternId] += 1;
t += 1;
if (t % m_sampleRate == 0) {
m_midState.push_back(CompressedProcessPoint{
.board = CompressedBoard::Generate(currentBoard),
.usage = currentUsage });
}
}
}
CompressedSolutionProcess::ProcessPoint CompressedSolutionProcess::getRestoredBoard(int32 idx) const {
int32 tid = idx / m_sampleRate;
int32 t = tid * m_sampleRate;
auto board = m_midState[tid].board.restore();
auto usage = m_midState[tid].usage;
while (t < idx) {
board = BoardOperate(board, m_problem, m_solution.operations[t]);
usage[m_solution.operations[t].patternId] += 1;
}
return ProcessPoint{ .board = std::move(board), .usage = std::move(usage) };
}
}
namespace Procon35 {
VerifyResult Problem::verify(bool strong, bool doThrow) {
String msgHeader = U"Problem::verify error : ";
if (doThrow) {
auto res = verify(strong, false);
if (!res.isOk) throw Error(msgHeader + res.msg);
return res;
}
auto myError = [](String s) -> VerifyResult { return VerifyResult{ false, std::move(s) }; };
if (strong) {
// 盤面の大きさを確認。
const int32 MinWidth = 32;
const int32 MaxWidth = 256;
const int32 MinHeight = 32;
const int32 MaxHeight = 256;
if (width() < MinWidth) return myError(U"width < {} ( = {} )"_fmt(MinWidth, width()));
if (MaxWidth < width()) return myError(U"width > {} ( = {} )"_fmt(MaxWidth, width()));
if (height() < MinHeight) return myError(U"height < {} ( = {} )"_fmt(MinHeight, height()));
if (MaxHeight < height()) return myError(U"height > {} ( = {} )"_fmt(MaxHeight, height()));
if (boardGoal.size().x != width()) return myError(U"boardGoal.size().x != width() ({} != {})"_fmt(boardGoal.size().x, width()));
if (boardGoal.size().y != width()) return myError(U"boardGoal.size().y != height() ({} != {})"_fmt(boardGoal.size().y, height()));
}
{
int32 minMassElem = 0;
int32 maxMassElem = 9;
if (strong) maxMassElem = 3;
Array<int32> freq(10, 0);
for (auto [y, x] : step(Size(height(), width()))) {
int32 val = boardStart[y][x];
if (val < minMassElem || maxMassElem < val) {
return myError(U"boardStart[{}][{}] = {} is out of range [{}, {}]"_fmt(
y, x, val, minMassElem, maxMassElem
));
}
freq[val] += 1;
}
int32 boardSize = width() * height();
if (strong) {
for (int32 t = minMassElem; t <= maxMassElem; t++) {
if (freq[t] * 10 < boardSize) {
return myError(U"element {} occurs too infrequently : occurence = {}, size = {}"_fmt(
t, freq[t], boardSize
));
}
}
}
if (width() == 0) return myError(U"width = 0");
if (height() == 0) return myError(U"height = 0");
std::map<BoardElement, int32> elementSetStart;
std::map<BoardElement, int32> elementSetGoal;
for (auto [y, x] : step(Size(height(), width()))) {
elementSetStart[boardStart[y][x]] += 1;
elementSetGoal[boardGoal[y][x]] += 1;
}
auto elementSetStartVec = Array(elementSetStart.begin(), elementSetStart.end());
auto elementSetGoalVec = Array(elementSetStart.begin(), elementSetStart.end());
//Console << elementSetStartVec;
//Console << elementSetGoalVec;
if (elementSetStartVec != elementSetGoalVec) return myError(U"The board is not solvable.");
}
return VerifyResult{ true, U"OK" };
}
JSON Problem::toJson() const {
auto BoardToJson = [](Board g, int32 h, int32 w) -> JSON {
return JSON(step(h).map([&](int32 y) -> JSON {
String res;
for (int32 x : step(w)) res.push_back(U'0' + g[y][x]);
return JSON(res);
}).asArray());
};
JSON json;
json[U"board"][U"width"] = width();
json[U"board"][U"height"] = height();
json[U"board"][U"start"] = BoardToJson(boardStart, height(), width());
json[U"board"][U"goal"] = BoardToJson(boardGoal, height(), width());
int32 generalPatternCount = int32(patterns.size()) - FixedPatternsCount;
json[U"general"][U"n"] = generalPatternCount;
json[U"general"][U"patterns"] = JSON(Array<JSON>(generalPatternCount));
for (int32 gp : step(generalPatternCount)) {
int32 p = FixedPatternsCount + gp;
auto& pattern = patterns[p];
JSON patternJson;
patternJson[U"p"] = p;
patternJson[U"width"] = int32(pattern.width());
patternJson[U"height"] = int32(pattern.height());
patternJson[U"cells"] = BoardToJson(pattern, int32(pattern.height()), int32(pattern.width()));
json[U"general"][U"patterns"][gp] = patternJson;
}
return json;
}
Problem Problem::FromJson(JSON json) {
int32 w = json[U"board"][U"width"].get<int32>();
int32 h = json[U"board"][U"height"].get<int32>();
Problem res;
res.boardStart.assign(w, h, 0);
auto boardStartJson = json[U"board"][U"start"];
for (int32 y : step(h)) {
String s = boardStartJson[y].getString();
for (int32 x : step(w)) res.boardStart[y][x] = char8(s[x] - U'0');
}
res.boardGoal.assign(w, h, 0);
auto boardGoalJson = json[U"board"][U"goal"];
for (int32 y : step(h)) {
String s = boardGoalJson[y].getString();
for (int32 x : step(w)) res.boardGoal[y][x] = char8(s[x] - U'0');
}
res.patterns = GetDefaultPatterns();
int32 generalPatternCount = json[U"general"][U"n"].get<int32>();
res.patterns.resize(FixedPatternsCount + generalPatternCount);
for (int32 idx : step(generalPatternCount)) {
auto tgjson = json[U"general"][U"patterns"][idx];
int32 p = tgjson[U"p"].get<int32>();
int32 pw = tgjson[U"width"].get<int32>();
int32 ph = tgjson[U"height"].get<int32>();
Pattern pat(pw, ph);
for (int32 y : step(ph)) {
String s = tgjson[U"cells"][y].getString();
for (int32 x : step(pw)) pat[y][x] = char8(s[x] - U'0');
}
res.patterns[p] = pat;
}
return res;
}
JSON Solution::toJson() const {
JSON json;
json[U"n"] = operationCount();
json[U"ops"] = operations.map([](const Operation& op) -> JSON {
JSON json;
json[U"p"] = op.patternId;
json[U"x"] = op.x;
json[U"y"] = op.y;
json[U"s"] = op.direction;
return json;
});
return json;
}
VerifyResult Solution::verify(const Problem& problem) {
String msgHeader = U"Solution::verify error : ";
if (OperationNumberLimit <= operations.size()) {
return { false, msgHeader + U"too many operations" };
}
for (size_t i = 0; i < operations.size(); i++) {
auto op = operations[i];
if (op.patternId < 0 || problem.numAllPatterns() <= op.patternId) {
return { false, msgHeader + U"pattern_id[{}] out of range"_fmt(i) };
}
if (op.x < -256 && 256 < op.x) {
return { false, msgHeader + U"x[{}] out of range"_fmt(i) };
}
if (op.y < -256 && 256 < op.y) {
return { false, msgHeader + U"y[{}] out of range"_fmt(i) };
}
if (op.direction < 0 && 4 <= op.direction) {
return { false, msgHeader + U"direction[{}] out of range"_fmt(i) };
}
}
return { true, U"OK" };
}
Solution Solution::FromJson(JSON json) {
Solution res;
int32 n = json[U"n"].get<int32>();
JSON ops = json[U"ops"];
res.operations = step(n).map([&ops](int32 i) -> Operation {
Operation res;
res.patternId = ops[i][U"p"].get<int32>();
res.x = ops[i][U"x"].get<int32>();
res.y = ops[i][U"y"].get<int32>();
res.direction = ops[i][U"s"].get<int32>();
return res;
});
return res;
}
Solution Solution::FromBinary(const Array<unsigned char>& src) {
size_t ptr = 0;
auto gc = [&]() -> uint32 {
if (src.size() <= ptr) throw Error(U"Format error");
return uint32(src[ptr++]);
};
Solution res;
uint32 num = 0;
for (auto a_ : step(4)) num = 256 * num + gc();
if (num > OperationNumberLimit) throw Error(U"Too many operations");
for (auto i : step(num)) {
uint32 c = 0;
for (auto a_ : step(4)) num = 256 * num + gc();
Operation buf;
buf.patternId = int32(c >> 22);
buf.x = int32((c >> 12) & 0x3ff) - 512;
buf.patternId = int32((c >> 2) & 0x3ff) - 512;
buf.direction = int32(c & 0x3);
if (!InRange(buf.x, -256, 256)) throw Error(U"X[{}] out of range"_fmt(i));
if (!InRange(buf.y, -256, 256)) throw Error(U"X[{}] out of range"_fmt(i));
res.operations.push_back(buf);
}
return res;
}
bool JudgeResult::isCorrect() const { return resultCode == 0; }
String JudgeResult::getMessage() const {
switch (resultCode) {
case 0: return U"OK : Correct";
case 1: return U"NG : Wrong final state";
case 2: return U"NG : Limit exceeded";
}
return U"Undefined verdict";
}
JudgeResult Judge(const Problem& problem, const Solution& solution) {
if (solution.operationCount() > OperationNumberLimit) return JudgeResult{ 2 };
auto board = problem.boardStart;
for (auto& operation : solution.operations) {
board = BoardOperate(board, problem, operation);
}
if (board != problem.boardGoal) return JudgeResult{ 1 };
return JudgeResult{ 0 };
}
String ToString(const Board& board, int32 style) {
if (style == 0) {
String res;
for (int32 y : step(int32(board.height()))) {
for (int32 x : step(int32(board.width()))) {
res += s3d::ToString(board[y][x]);
}
res += U"\n";
}
return res;
}
throw Error(U" ToString argument error ");
}
}
Optional<JSON> GetProblem(String task_id, String path) {
String token = U"TOKEN-IS-UNUSED";
URL url = U"https://nachia.tech/procon35_aftergame/task/" + task_id + U"/problem";
auto GetHeader = [&]() -> HashTable<String, String> {
return HashTable<String, String>{ { U"Procon-Token", token } };
};
auto GetTask = [&]() -> Optional<JSON> {
auto header = GetHeader();
// try
if (const auto response = SimpleHTTP::Get(url, header, path)) {
if (response.isOK()) {
const JSON data = JSON::Load(path);
return data;
}
return none;
}
else {
Console << U"Bad request";
return none;
}
};
return GetTask();
}
Optional<JSON> GetSolution(String sub_id, String path) {
String token = U"TOKEN-IS-UNUSED";
URL url = U"https://nachia.tech/procon35_aftergame/submission/" + sub_id + U"/download";
auto GetHeader = [&]() -> HashTable<String, String> {
return HashTable<String, String>{ { U"Procon-Token", token } };
};
auto GetTask = [&]() -> Optional<JSON> {
auto header = GetHeader();
// try
if (const auto response = SimpleHTTP::Get(url, header, path)) {
if (response.isOK()) {
const JSON data = JSON::Load(path);
return data;
}
return none;
}
else {
Console << U"Bad request";
return none;
}
};
return GetTask();
}
Array<ColorF> GetColorSet(int32 ty) {
Array<ColorF> colors;
if (ty == 0) {
colors.push_back(Palette::Red);
colors.push_back(Palette::Green);
colors.push_back(Palette::Blue);
colors.push_back(Palette::Cyan);
}
if (ty == 1) {
colors.push_back(Palette::Darkred);
colors.push_back(Palette::Darkgreen);
colors.push_back(Palette::Darkblue);
colors.push_back(Palette::Darkcyan);
}
return colors;
}
String PROBLEM_PATH = U"problem.json";
String SOLUTION_PATH = U"solution.json";
String PROBLEM_LOAD_PATH = U"problem_tmp.json";
String SOLUTION_LOAD_PATH = U"solution_tmp.json";
bool ReloadFromServer(String taskid, String subid, Procon35::Problem& problem, Procon35::Solution& solution) {
JSON jsonProblem; {
auto buf = GetProblem(taskid, PROBLEM_LOAD_PATH);
if (!buf.has_value()) {
Console << U"problem error";
return false;
}
jsonProblem = buf.value();
}
JSON jsonSolution; {
auto buf = GetSolution(subid, SOLUTION_LOAD_PATH);
if (!buf.has_value()) {
Console << U"submission error";
return false;
}
jsonSolution = buf.value();
}
Procon35::Problem problem_tmp;
Procon35::Solution solution_tmp;
try {
problem_tmp = Procon35::Problem::FromJson(jsonProblem);
solution_tmp = Procon35::Solution::FromJson(jsonSolution);
}
catch (...) {
Console << U"a file is not correctly saved";
return false;
}
auto pverify = problem_tmp.verify(false, false);
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 ReloadFromFile(Procon35::Problem& problem, Procon35::Solution& solution) {
Procon35::Problem problem_tmp;
Procon35::Solution solution_tmp;
try {
const JSON jsonProblem = JSON::Load(PROBLEM_PATH);
const JSON jsonSolution = JSON::Load(SOLUTION_PATH);
problem_tmp = Procon35::Problem::FromJson(jsonProblem);
solution_tmp = Procon35::Solution::FromJson(jsonSolution);
}
catch (...) {
Console << U"a file is not correctly saved";
return false;
}
auto pverify = problem_tmp.verify(false, false);
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;
}
void VisualizeSolution() {
using namespace Procon35;
Problem problem;
Solution solution;
NaiveCompressedSolutionProgress 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;
RectF boardDisplayRect = RectF(50.0, 50.0, 500.0, 500.0);
double displayMassWidth = boardDisplayRect.w / problem.width();
double displayMassHeight = boardDisplayRect.h / problem.height();
Vec2 displayMassSize = Vec2(displayMassWidth, displayMassHeight);
TextEditState taskideditor{ U"17" };
TextEditState subideditor{ U"31" };
Array<int32> dirCount{ 4 };
Array<int32> patCount{ 300 };
auto UpdateTurnId = [&](int32 newId) {
id = newId;
if (id != int32(digest.solution.operationCount())) {
auto op = digest.solution.operations[id];
auto moving = BoardOperateMoving(digest.getRestoredBoard(id), problem, op);
for (auto [x, y] : step(Size(problem.width(), problem.height()))) {
if (PatternAccess(problem.patterns[op.patternId], y - op.y, x - op.x)) {
animHighlight[y][x] = 1;
}
else {
animHighlight[y][x] = 0;
}
animOffset[y][x] = Vec2(moving[y][x] - Point(x, y)) * displayMassSize;
}
}
else {
auto_anim_speed = 0.0;
animSpeed = 0.0;
animating = false;
}
animFrame = 0.0;
sliderVal = (double)id / digest.solution.operationCount();
dirCount.assign(4, 0);
patCount.assign(300, 0);
for (int32 i = 0; i < id; i++) {
auto op = digest.solution.operations[i];
dirCount[op.direction] += 1;
patCount[Min(op.patternId, 25)] += 1;
}
};
auto reload = [&]() -> void {
String taskid = taskideditor.text;
String subid = subideditor.text;
if (taskid == U"" || subid == U"") {
bad_load = !ReloadFromFile(problem, solution);
}
else {
bad_load = !ReloadFromServer(taskid, subid, problem, solution);
}
if (bad_load) return;
digest = MakeNaiveCompressedSolutionProgress(problem, solution);
animOffset = Grid<Vec2>(problem.width(), problem.height(), Vec2());
animHighlight = Grid<int32>(problem.width(), problem.height(), 0);
id = 0;
animFrame = 0.0;
animSpeed = 0.0;
animating = false;
boardDisplayRect = RectF(50.0, 50.0, 500.0, 500.0);
displayMassWidth = boardDisplayRect.w / problem.width();
displayMassHeight = boardDisplayRect.h / problem.height();
displayMassSize = Vec2(displayMassWidth, displayMassHeight);
UpdateTurnId(0);
};
Font font{ 30 };
reload();
while (System::Update())
{
{
double xpos = 10.0;
double yline = 10.0;
SimpleGUI::TextBox(taskideditor, Vec2(xpos, yline), 80.0); xpos += 85.0;
SimpleGUI::TextBox(subideditor, Vec2(xpos, yline), 80.0); xpos += 85.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 += 300.0;
}
else {
font(U"{} / {}"_fmt(id, solution.operationCount())).draw(Vec2(xpos, yline)); xpos += 300.0;
}
}
{
if (SimpleGUI::Slider(sliderVal, Vec2(0.0, 560.0), 600.0)) {
int32 nextTurnId = Round(sliderVal * solution.operationCount());
UpdateTurnId(nextTurnId);
}
}
boardDisplayRect.drawFrame();
// control R,L arrow keys
{
if (KeyRight.down()) {
if (animating) {
animSpeed = 0.0; animFrame = 0.0;
animating = false;
if (id + 1 < int32(digest.midState.size())) {
UpdateTurnId(id + 1);
}
}
if (id + 1 < int32(digest.midState.size())) {
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 (!taskideditor.active && !subideditor.active) {
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)solution.operationCount() * 0.2 / 60.0 / Pow(1.8, 18 - input_number_key);
auto_anim_speed = Max(auto_anim_speed, 0.005);
animSpeed = auto_anim_speed;
animating = true;
}
}
}
if (animSpeed >= 1.0) {
animFrame = 0.0;
UpdateTurnId(Clamp<int32>(id + (int32)Round(animSpeed), 0, solution.operationCount()));
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);
}
}
// Array<ColorF> colors = GetColorSet(1);
Array<ColorF> colors = GetColorSet(0);
Array<ColorF> colorsHighlighted = GetColorSet(0);
// draw
{
bool anim_enabled = animSpeed <= 0.2 && animating;
for (auto [x, y] : step(Size(problem.width(), problem.height()))) {
RectF rect = RectF(boardDisplayRect.tl() + displayMassSize * Vec2(x, y), displayMassSize);
auto elem = digest.midState[id][y][x];
auto color = (elem == problem.boardGoal[y][x]) ? colors[elem] : colorsHighlighted[elem];
if (anim_enabled) rect = rect.moveBy(animOffset[y][x] * animFrame);
if (!(anim_enabled && animHighlight[y][x])) {
rect.draw(color);
}
}
for (auto [x, y] : step(Size(problem.width(), problem.height()))) {
RectF rect = RectF(boardDisplayRect.tl() + displayMassSize * Vec2(x, y), displayMassSize);
auto elem = digest.midState[id][y][x];
auto color = (elem == problem.boardGoal[y][x]) ? colors[elem] : colorsHighlighted[elem];
if (anim_enabled) rect = rect.moveBy(animOffset[y][x] * animFrame);
if (anim_enabled && animHighlight[y][x]) {
rect.draw(color);
rect.drawFrame(6.0, 0.0, Palette::Yellow);
}
}
}
// drawText
{
double ypos = 50.0;
double xpos = 570.0;
Array<String> dirText = { U"U", U"D", U"L", U"R" };
for (int32 d = 0; d < 4; d++) {
font(U"{} : {}"_fmt(dirText[d], dirCount[d])).draw(Vec2(xpos, ypos));
ypos += 40.0;
}
ypos += 30.0;
font(U"fixed").draw(18.0, Vec2(xpos, ypos));
ypos += 25.0;
font(U"{}"_fmt(patCount[0])).draw(15.0, Arg::topRight(Vec2(xpos + 50.0, ypos)));
ypos += 20.0;
for (int32 d = 0; d < 8; d++) {
font(U"{}"_fmt(patCount[d * 3 + 1])).draw(15.0, Arg::topRight(Vec2(xpos + 50.0, ypos)));
font(U"{}"_fmt(patCount[d * 3 + 2])).draw(15.0, Arg::topRight(Vec2(xpos + 120.0, ypos)));
font(U"{}"_fmt(patCount[d * 3 + 3])).draw(15.0, Arg::topRight(Vec2(xpos + 190.0, ypos)));
ypos += 20.0;
}
ypos += 10.0;
font(U"general").draw(18.0, Vec2(xpos, ypos));
ypos += 20.0;
font(U"{}"_fmt(patCount[25])).draw(15.0, Arg::topRight(Vec2(xpos + 50.0, ypos)));
}
}
}
void Main() {
VisualizeSolution();
}
{"startsAt":1729304712,"board":{"width":6,"height":4,"start":["220103","213033","022103","322033"],"goal":["000000","111222","222233","333333"]},"general":{"n":2,"patterns":[{"p":25,"width":4,"height":2,"cells":["0111","1001"]},{"p":26,"width":2,"height":2,"cells":["10","01"]}]}}
{"n":21,"ops":[{"p":1,"x":0,"y":0,"s":2},{"p":24,"x":4,"y":1,"s":2},{"p":4,"x":0,"y":1,"s":2},{"p":22,"x":1,"y":3,"s":1},{"p":0,"x":0,"y":2,"s":2},{"p":22,"x":2,"y":-255,"s":0},{"p":0,"x":1,"y":0,"s":2},{"p":22,"x":2,"y":2,"s":1},{"p":1,"x":1,"y":2,"s":2},{"p":22,"x":5,"y":3,"s":1},{"p":24,"x":4,"y":1,"s":2},{"p":1,"x":2,"y":1,"s":2},{"p":1,"x":2,"y":2,"s":2},{"p":1,"x":3,"y":0,"s":2},{"p":22,"x":4,"y":2,"s":1},{"p":1,"x":3,"y":2,"s":2},{"p":22,"x":5,"y":-255,"s":0},{"p":0,"x":4,"y":1,"s":2},{"p":0,"x":4,"y":3,"s":2},{"p":23,"x":5,"y":2,"s":0},{"p":1,"x":5,"y":0,"s":0}]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment