diff --git a/TESTING.md b/TESTING.md
index 0aeb5b2f..e3ffecde 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -118,6 +118,14 @@ Use following links to do testing of Penpa+. This by no means an exhaustive test
* https://puzz.link/p?mashu/13/9/013009k10a3a39310c9313693i016030j039a20
* https://puzz.link/p?mashu/v:/10/10/000000060i23601000000001i200f90000
+
+
+Mid-loop
+
+* https://puzz.link/p?midloop/10/10/13579b37b9bdfwffgfzzzzzzzzzzzzz
+* https://puzz.link/p?midloop/9/10/yfxfy7fz77fzj7fxbfx7bfx7fgfudfzgfg
+* https://puzz.link/p?midloop/10/10/tfxfh7fxfzn7bfzhfztfzjfh7ftfpft77bfzhfi
+
Maxi Loop
diff --git a/docs/js/general.js b/docs/js/general.js
index 73f16a8b..d3a8eba4 100644
--- a/docs/js/general.js
+++ b/docs/js/general.js
@@ -2933,6 +2933,19 @@ function decode_puzzlink(url) {
pu["pu_q"].number[cell] = [number, info_number[i] % 2 ? 4 : 1, "1"];
}
+ pu.mode_qa("pu_a");
+ pu.mode_set("combi");
+ pu.subcombimode("linex");
+ this.usertab_choices = ["Surface", "Composite"];
+ break;
+ case "midloop":
+ pu = new Puzzle_square(cols, rows, size);
+ pu.mode_grid("nb_grid2"); // Dashed gridlines
+ setupProblem(pu, "combi");
+
+ info_edge = puzzlink_pu.decodeMidloop();
+ puzzlink_pu.drawMidloop(pu, info_edge);
+
pu.mode_qa("pu_a");
pu.mode_set("combi");
pu.subcombimode("linex");
diff --git a/docs/js/puzzlink.js b/docs/js/puzzlink.js
index 63120a9a..817fba07 100644
--- a/docs/js/puzzlink.js
+++ b/docs/js/puzzlink.js
@@ -331,6 +331,54 @@ class Puzzlink {
}
return new_numbers;
}
+
+ decodeMidloop() {
+ // Every cell, corner and edge is a point, unless it is on the grid edge.
+ // Small even digits are white dots. Small odd digits are black dots.
+ // Large digits/characters are spacing
+ var points = {};
+ var i = 0;
+ for (var char of this.gridurl) {
+ char = parseInt(char, 36);
+ if (0 <= char && char < 16) {
+ points[i] = char % 2;
+ i += parseInt(char / 2) + 1;
+ } else {
+ i += char - 15;
+ }
+ }
+ return points;
+ }
+
+ drawMidloop(pu, info, behind_line = 2) {
+ var row_ind, col_ind, cell;
+ for (var i in info) {
+ row_ind = parseInt(i / (2 * this.cols - 1));
+ col_ind = i % (2 * this.cols - 1);
+ if (row_ind % 2 === 0 && col_ind % 2 === 0) {
+ // cell center
+ row_ind = (row_ind) / 2;
+ col_ind = (col_ind) / 2;
+ cell = pu.nx0 * (2 + row_ind) + 2 + col_ind;
+ } else if (col_ind % 2 === 0) {
+ // vertical edge
+ row_ind = (row_ind - 1) / 2;
+ col_ind = (col_ind) / 2;
+ cell = 2 * pu.nx0 * pu.ny0 + pu.nx0 * (2 + row_ind) + 2 + col_ind;
+ } else if (row_ind % 2 === 0) {
+ // horizonal edge
+ row_ind = (row_ind) / 2;
+ col_ind = (col_ind - 1) / 2;
+ cell = 3 * pu.nx0 * pu.ny0 + pu.nx0 * (2 + row_ind) + 2 + col_ind;
+ } else {
+ // corner/vertex
+ row_ind = (row_ind - 1) / 2;
+ col_ind = (col_ind - 1) / 2;
+ cell = pu.nx0 * pu.ny0 + pu.nx0 * (2 + row_ind) + 2 + col_ind;
+ }
+ pu["pu_q"].symbol[cell] = [info[i] + 1, "circle_SS", behind_line];
+ }
+ }
}
class DisjointSets {