|
#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(); |
|
} |