Commit c2566bbc authored by Kento HASEGAWA's avatar Kento HASEGAWA

Add solution viewer

parent 2dcebcae
...@@ -16,6 +16,10 @@ def webui_index(): ...@@ -16,6 +16,10 @@ def webui_index():
# return adc2019system.role.role # return adc2019system.role.role
return render_template('index.html') return render_template('index.html')
@webui.route('/view/solution')
def webui_view_solution():
return render_template('view-solution.html')
@webui.route('/part/problems') @webui.route('/part/problems')
def webui_part_problems(): def webui_part_problems():
if (adc2019system.role is not None) and (adc2019system.role.type == 'host'): if (adc2019system.role is not None) and (adc2019system.role.type == 'host'):
......
...@@ -95,6 +95,22 @@ class Host(object): ...@@ -95,6 +95,22 @@ class Host(object):
else: else:
return {'status': 'unknown request'} return {'status': 'unknown request'}
def get_solution_for_viewer(self, problem_key, solution_id):
problem = self.get_problem(problem_key)
if problem is None:
return None
problem_data = problem.get_d3json()
solution = problem.get_solution(solution_id)
if solution is None:
return None
solution_data = solution.get_d3json()
return {
'problem': problem_data,
'solution': solution_data
}
def call_api(self, method, cmd, params): def call_api(self, method, cmd, params):
if cmd == 'role': if cmd == 'role':
# サーバの役割確認 # サーバの役割確認
...@@ -109,6 +125,10 @@ class Host(object): ...@@ -109,6 +125,10 @@ class Host(object):
elif cmd == 'request/status': elif cmd == 'request/status':
request_id = float(params['request_id']) request_id = float(params['request_id'])
return self.get_request_status(request_id) return self.get_request_status(request_id)
elif cmd == 'view/solution':
problem_key = params['problem']
solution_id = params['solution']
return self.get_solution_for_viewer(problem_key, solution_id)
else: else:
return None return None
......
class ADC2019BoardViewer {
constructor(selector) {
this.container = $(selector);
this.width = this.container.width();
this.height = $(window).height();
this.svg = d3.select(selector).append('svg');
this.view = this.svg.append('g').attr('class', 'view');
this.currentTransform = null;
this.cubeResolution = 50;
this._init();
this._draw_slider();
}
draw(data){
this._draw_init();
// this._get_board(data);
this._draw_grid(data['problem']['w'], data['problem']['h']);
this._draw_solution(data);
}
_init(){
var _self = this;
if (this.currentTransform){
this.view.attr('transform', this.currentTransform);
}
}
_draw_init(){
this.view.selectAll("g").remove();
}
_draw_grid(x_cnt, y_cnt){
let _width = x_cnt * this.cubeResolution;
let _height = y_cnt * this.cubeResolution;
this.view.append("g")
.attr("class", "x axis")
.selectAll("line")
.data(d3.range(0, _width+1, this.cubeResolution))
.enter().append("line")
.attr("x1", function (d) { return d; })
.attr("y1", 0)
.attr("x2", function (d) { return d; })
.attr("y2", _height)
.attr("stroke", "#888");
this.view.append("g")
.attr("class", "y axis")
.selectAll("line")
.data(d3.range(0, _height+1, this.cubeResolution))
.enter().append("line")
.attr("x1", 0)
.attr("y1", function (d) { return d; })
.attr("x2", _width)
.attr("y2", function (d) { return d; })
.attr("stroke", "#888");
}
_draw_slider(){
let _self = this;
var zoom = d3.zoom()
.scaleExtent([0.5, 5])
.translateExtent([
[-this.width * 2, -this.height * 2],
[this.width * 2, this.height * 2]
])
.on("zoom", zoomed);
function zoomed() {
this.currentTransform = d3.event.transform;
_self.view.attr("transform", this.currentTransform);
// this.slider.property("value", d3.event.scale);
}
function slided(d) {
zoom.scaleTo(this.svg, d3.select(this).property("value"));
}
// var slider = d3.select("body").append("input")
// .datum({})
// .attr("type", "range")
// .attr("value", 1)
// .attr("min", zoom.scaleExtent()[0])
// .attr("max", zoom.scaleExtent()[1])
// .attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
// .on("input", slided);
this.svg.call(zoom).on('dblclick.zoom', null);
}
_draw_solution(data){
let cubeResolution = this.cubeResolution;
// Draw outer box
let _width = data['solution']['w'] * this.cubeResolution;
let _height = data['solution']['h'] * this.cubeResolution;
this.view.append("g")
.attr("class", "x axis")
.selectAll("line")
.data([0, _width])
.enter().append("line")
.attr("x1", function (d) { return d; })
.attr("y1", 0)
.attr("x2", function (d) { return d; })
.attr("y2", _height)
.attr("stroke", "#d00")
.attr("stroke-width", 2);
this.view.append("g")
.attr("class", "y axis")
.selectAll("line")
.data([0, _height])
.enter().append("line")
.attr("x1", 0)
.attr("y1", function (d) { return d; })
.attr("x2", _width)
.attr("y2", function (d) { return d; })
.attr("stroke", "#d00")
.attr("stroke-width", 2);
let colors = d3.schemeCategory10
// Reference
// Grid: https://bl.ocks.org/ngminhtrung/7c5721a1504f3e29a36da9ddd9e5039b
// Snap: https://bl.ocks.org/evanjmg/ea3e59e67b4256c8831d3fc80f71294b
// Nested data structure: https://codeday.me/jp/qa/20190428/720184.html
// Create data
var blocks = data['problem']['block'];
for(var bi in blocks){
let bw = blocks[bi]['w'];
let bh = blocks[bi]['h'];
let bx = data['solution']['block'][bi]['x'];
let by = data['solution']['block'][bi]['y'];
blocks[bi]['cellpos'] = Array();
for(var _h=0; _h<bh; _h++){
for(var _w=0; _w<bw; _w++){
if(blocks[bi]['cells'][_h][_w] != 0){
var _text;
if(blocks[bi]['cells'][_h][_w] == "+"){
_text = "";
}else{
_text = "" + blocks[bi]['cells'][_h][_w];
}
blocks[bi]['cellpos'].push([_w+bx, _h+by, _text]);
}
}
}
blocks[bi]['x'] = bx;
blocks[bi]['y'] = by;
}
var itemContainer = this.view.selectAll("g.itemContainer")
.data(Object.values(blocks))
.enter()
.append('g')
.attr('class', 'itemContainer')
.attr("transform", (d) => 'translate(' + d.x + ',' + d.y + ')')
.attr('data-x', (d) => d.x)
.attr('data-y', (d) => d.y);
var cellContainer = itemContainer.append('g')
.attr('class', 'cellContainer')
.attr('data-color', (d, i) => colors[i])
.attr('x', 0)
.attr('y', 0)
cellContainer.selectAll('g')
.data((d) => d.cellpos)
.enter()
.append('rect')
.attr('x', (d) => d[0] * this.cubeResolution)
.attr('y', (d) => d[1] * this.cubeResolution)
.attr('width', this.cubeResolution)
.attr('height', this.cubeResolution)
.attr('cursor', 'move')
.attr('fill', (d, i, nodes) => {
return d3.select(nodes[i].parentNode).attr('data-color');
});
cellContainer.selectAll('g')
.data((d) => d.cellpos)
.enter()
.append('text')
.attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('width', this.cubeResolution)
.attr('height', this.cubeResolution)
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('font-size', '20px')
.attr('cursor', 'move');
// .text((d) => d[2]);
let sw = data['solution']['w'];
let sh = data['solution']['h'];
var lines = Object();
for(var _h=0; _h<sh; _h++){
for(var _w=0; _w<sw; _w++){
let line_index = data['solution']['map'][_h][_w];
if(line_index == 0){
continue;
}
if(!lines.hasOwnProperty(line_index)){
lines[line_index] = Array();
}
lines[line_index].push([_w, _h, line_index]);
if((_w+1<sw) && (data['solution']['map'][_h][_w+1] == line_index)){
lines[line_index].push([_w+0.5, _h, '']);
}
if((_h+1<sh) && (data['solution']['map'][_h+1][_w] == line_index)){
lines[line_index].push([_w, _h+0.5, '']);
}
}
}
let lineColors = d3.schemePastel2
var lineItemContainer = this.view.selectAll("g.lineItemContainer")
.data(Object.values(lines))
.enter()
.append('g')
.attr('class', 'lineItemContainer');
var lineContainer = lineItemContainer.append('g')
.attr('class', 'lineContainer')
.attr('data-color', (d, i) => lineColors[i]);
lineContainer.selectAll('g')
.data((d) => d)
.enter()
.append('rect')
.attr('x', (d) => (d[0]+0.25) * this.cubeResolution)
.attr('y', (d) => (d[1]+0.25) * this.cubeResolution)
.attr('width', (this.cubeResolution*0.5))
.attr('height', (this.cubeResolution*0.5))
.attr('fill', (d, i, nodes) => {
return d3.select(nodes[i].parentNode).attr('data-color');
});
lineContainer.selectAll('g')
.data((d) => d)
.enter()
.append('text')
.attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('width', (this.cubeResolution*0.5))
.attr('height', (this.cubeResolution*0.5))
.attr('text-anchor', 'middle')
.attr('fill', 'black')
.attr('font-size', '16px')
.text((d) => d[2]);
}
// _draw_blocks(blocks){
// let cubeResolution = this.cubeResolution;
// function snapToGrid(p, r) {
// return Math.round(p / r) * r;
// }
// function dragged(d) {
// var el = d3.select(this);
// var _x = parseInt(el.attr('data-x'), 10) + d3.event.x;
// var _y = parseInt(el.attr('data-y'), 10) + d3.event.y;
// el.attr("transform", (d) => {
// return 'translate(' + snapToGrid(_x, cubeResolution) + ',' + snapToGrid(_y, cubeResolution) + ')'
// })
// }
// function dragended(d) {
// var el = d3.select(this).classed("dragging", false);
// var _x = parseInt(el.attr('data-x'), 10) + d3.event.x;
// var _y = parseInt(el.attr('data-y'), 10) + d3.event.y;
// d3.select(this)
// .attr('data-x', snapToGrid(_x, cubeResolution))
// .attr('data-y', snapToGrid(_y, cubeResolution));
// }
// function dragstarted(d) {
// var el = d3.select(this);
// el.raise().classed("dragging", true);
// }
// let colors = d3.schemeCategory10
// // Reference
// // Grid: https://bl.ocks.org/ngminhtrung/7c5721a1504f3e29a36da9ddd9e5039b
// // Snap: https://bl.ocks.org/evanjmg/ea3e59e67b4256c8831d3fc80f71294b
// // Nested data structure: https://codeday.me/jp/qa/20190428/720184.html
// var itemContainer = this.view.selectAll("g.itemContainer")
// .data(blocks)
// .enter()
// .append('g')
// .attr('class', 'itemContainer')
// .attr("transform", (d) => 'translate(' + d.x + ',' + d.y + ')')
// .attr('data-x', (d) => d.x)
// .attr('data-y', (d) => d.y)
// .call(d3.drag()
// .on("start", dragstarted)
// .on("drag", dragged)
// .on("end", dragended));
// var cellContainer = itemContainer.append('g')
// .attr('class', 'cellContainer')
// .attr('data-color', (d, i) => colors[i])
// .attr('x', 0)
// .attr('y', 0)
// cellContainer.selectAll('g')
// .data((d) => d.cells)
// .enter()
// .append('rect')
// .attr('x', (d) => d[0] * this.cubeResolution)
// .attr('y', (d) => d[1] * this.cubeResolution)
// .attr('width', this.cubeResolution)
// .attr('height', this.cubeResolution)
// .attr('cursor', 'move')
// .attr('fill', (d, i, nodes) => {
// return d3.select(nodes[i].parentNode).attr('data-color');
// });
// cellContainer.selectAll('g')
// .data((d) => d.cells)
// .enter()
// .append('text')
// .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution)
// .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution)
// .attr('width', this.cubeResolution)
// .attr('height', this.cubeResolution)
// .attr('text-anchor', 'middle')
// .attr('fill', 'white')
// .attr('font-size', '20px')
// .attr('cursor', 'move')
// .text((d) => d[2]);
// }
}
$(function(){
const board = new ADC2019BoardViewer("#board-container");
hash = location.hash.replace("#", "");
params = hash.split('/');
$.ajax({
type: "POST",
dataType: "json",
url: "/api/view/solution",
data: JSON.stringify({
"problem": params[0],
"solution": params[1]
}),
contentType: 'application/json'
}).done((d) => {
board.draw(d);
});
});
...@@ -30,7 +30,7 @@ class StatusView { ...@@ -30,7 +30,7 @@ class StatusView {
_this.container.find('.solution-detail-row td').click((e) => { _this.container.find('.solution-detail-row td').click((e) => {
var solution_id = $(e.target).parent("tr").data("solution-id"); var solution_id = $(e.target).parent("tr").data("solution-id");
var problem_name = $(e.target).parent("tr").data("problem"); var problem_name = $(e.target).parent("tr").data("problem");
var viewer_url = "/viewer/solution#" + problem_name + "/" + solution_id; var viewer_url = "/view/solution#" + problem_name + "/" + solution_id;
window.open(viewer_url, "_blank"); window.open(viewer_url, "_blank");
}) })
}); });
...@@ -59,7 +59,7 @@ class StatusView { ...@@ -59,7 +59,7 @@ class StatusView {
show: true show: true
}); });
_this.container.find('#solver-processing-modal').on('hidden.bs.modal', function(e){ _this.container.find('#solver-processing-modal').on('hidden.bs.modal', function(e){
console.log(e); // console.log(e);
_this.show_problem(); _this.show_problem();
}) })
...@@ -78,7 +78,7 @@ class StatusView { ...@@ -78,7 +78,7 @@ class StatusView {
_this.container.find('#request-status-container').html(d); _this.container.find('#request-status-container').html(d);
status = $(d).find('#request-status-value').text(); status = $(d).find('#request-status-value').text();
console.log(status); // console.log(status);
if(status == 'done'){ if(status == 'done'){
_this.request_refresh_timer = null; _this.request_refresh_timer = null;
}else{ }else{
......
<!DOCTYPE html>
<html>
<head>
<title>ADC2019 Solver System</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/adc2019.css">
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/d3.min.js"></script>
<script src="/static/js/adc2019-viewer.js"></script>
<style>
.axis path {
display: none;
}
.axis line {
stroke-opacity: 0.3;
shape-rendering: crispEdges;
}
input[type="range"] {
right: 0;
top: 0;
position: absolute;
}
svg{
width: 100%;
height: 100vh;
display: block;
}
</style>
</head>
<body>
<div id="wrapper">
<div id="control-panel-wrapper" class="container-fluid">
<div id="board-container"></div>
</div>
</div>
</body>
</html>
import datetime import datetime
import json import json
import os import os
import re
import time import time
import uuid import uuid
...@@ -12,6 +13,7 @@ class Problem(object): ...@@ -12,6 +13,7 @@ class Problem(object):
self.name = '' self.name = ''
self.size = (0, 0) self.size = (0, 0)
self.block_num = 0 self.block_num = 0
self.blocks = dict()
self.problem = '' self.problem = ''
self.status = 'Ready' self.status = 'Ready'
self.solutions = dict() self.solutions = dict()
...@@ -27,22 +29,51 @@ class Problem(object): ...@@ -27,22 +29,51 @@ class Problem(object):
with open(path, 'r') as fp: with open(path, 'r') as fp:
q_text = fp.read() q_text = fp.read()
q_lines = fp.readlines()
q_lines = q_text.splitlines()
board_size = [0, 0] board_size = [0, 0]
block_num = 0 block_num = 0
blocks = dict()
def intplus(v):
if v.isdecimal():
return int(v)
else:
return v
for _l in q_lines: li = 0
while li < len(q_lines):
_l = q_lines[li]
if "SIZE" in _l: if "SIZE" in _l:
board_size_str = _l.strip().split()[1] board_size_str = _l.strip().split()[1]
board_size = [int(v) for v in board_size_str.split('X')] board_size = [int(v) for v in board_size_str.split('X')]
if 'BLOCK_NUM' in _l: if 'BLOCK_NUM' in _l:
block_num = int(_l.strip().split()[1]) block_num = int(_l.strip().split()[1])
if 'BLOCK#' in _l:
p = r'BLOCK#([0-9]+) +([0-9]+)X([0-9]+)'
m = re.match(p, _l.strip())
bi = int(m.group(1))
bw = int(m.group(2))
bh = int(m.group(3))
blocks[bi] = {
'index': bi,
'w': bw,
'h': bh,
'cells': list()
}
for _h in range(bh):
li += 1
_l = q_lines[li].strip()
blocks[bi]['cells'].append([intplus(v.strip()) for v in _l.split(',')])
li += 1
name = os.path.splitext(os.path.basename(path))[0] name = os.path.splitext(os.path.basename(path))[0]
self.size = board_size self.size = board_size
self.block_num = block_num self.block_num = block_num
self.blocks = blocks
self.name = name self.name = name
self.problem = q_text self.problem = q_text
self.status = 'Ready' self.status = 'Ready'
...@@ -57,6 +88,15 @@ class Problem(object): ...@@ -57,6 +88,15 @@ class Problem(object):
'status': self.status 'status': self.status
} }
def get_d3json(self):
return {
'block': self.blocks,
'w': self.size[0],
'h': self.size[1],
'n': self.block_num
}
def put_solution(self, data): def put_solution(self, data):
solution = Solution(data) solution = Solution(data)
solution_id = solution.get_id() solution_id = solution.get_id()
...@@ -69,6 +109,12 @@ class Problem(object): ...@@ -69,6 +109,12 @@ class Problem(object):
def get_solutions(self): def get_solutions(self):
return self.solutions return self.solutions
def get_solution(self, solution_id):
if solution_id in self.solutions:
return self.solutions[solution_id]
else:
return None
class Solution(object): class Solution(object):
def __init__(self, data): def __init__(self, data):
...@@ -96,6 +142,51 @@ class Solution(object): ...@@ -96,6 +142,51 @@ class Solution(object):
'solution': self.solution 'solution': self.solution
} }
def get_d3json(self):
board_size = [0, 0]
block_num = 0
_lines = self.solution.splitlines()
li = 0
bw, bh = 0, 0
bmap = list()
bposition = dict()
while li < len(_lines):
_l = _lines[li].strip()
if 'SIZE' in _l:
board_size_str = _l.strip().split()[1]
board_solution_size = [int(v) for v in board_size_str.split('X')]
bw = board_solution_size[0]
bh = board_solution_size[1]
for _h in range(bh):
li += 1
_l = _lines[li].strip()
bmap.append([int(v.strip()) for v in _l.split(',')])
if 'BLOCK' in _l:
p = r'BLOCK#([0-9]+) +@\(([0-9]+), *([0-9]+)\)'
m = re.match(p, _l.strip())
bi = int(m.group(1))
bx = int(m.group(2))
by = int(m.group(3))
bposition[bi] = {
'index': bi,
'x': bx,
'y': by,
}
li += 1
return {
'w': bw,
'h': bh,
'map': bmap,
'block': bposition
}
def save(self, basedir): def save(self, basedir):
outdir = f"{basedir}/{self.problem}" outdir = f"{basedir}/{self.problem}"
if not os.path.exists(outdir): if not os.path.exists(outdir):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment