From 980538bca7fa2c2c2570fd6bff4d131fded80c4e Mon Sep 17 00:00:00 2001 From: Lennard Sprong Date: Wed, 10 Jun 2026 08:44:03 +0200 Subject: [PATCH] Add Elastic Link --- src-ui/changes.html | 2 +- src-ui/js/ui/KeyPopup.js | 6 +- src-ui/list.html | 1 + src/pzpr/variety.js | 1 + src/res/failcode.en.json | 4 + src/variety-common/Answer.js | 17 +++ src/variety/country.js | 19 --- src/variety/elasticlink.js | 275 +++++++++++++++++++++++++++++++++++ src/variety/kaidan.js | 16 -- src/variety/nuriloop.js | 15 -- test/script/elasticlink.js | 44 ++++++ 11 files changed, 348 insertions(+), 52 deletions(-) create mode 100644 src/variety/elasticlink.js create mode 100644 test/script/elasticlink.js diff --git a/src-ui/changes.html b/src-ui/changes.html index d87e4b6c7..5e625afbc 100644 --- a/src-ui/changes.html +++ b/src-ui/changes.html @@ -33,13 +33,13 @@
Latest types (all types)
diff --git a/src-ui/js/ui/KeyPopup.js b/src-ui/js/ui/KeyPopup.js index c79794bfa..e06a5057c 100644 --- a/src-ui/js/ui/KeyPopup.js +++ b/src-ui/js/ui/KeyPopup.js @@ -261,7 +261,8 @@ ui.keypopup = { golemgrad: [10, 0], topo: [10, 10], soulmates: [10, 10], - landmeasure: [10, 0] + landmeasure: [10, 0], + elasticlink: [10, 0] }, //--------------------------------------------------------------------------- @@ -526,6 +527,9 @@ ui.keypopup = { if (pid === "familyphoto") { itemlist.push(["q", "●"]); } + if (pid === "elasticlink") { + itemlist.push(["q", "○"]); + } if ( pid === "icelom" || pid === "icelom2" || diff --git a/src-ui/list.html b/src-ui/list.html index 3457d6e30..d80dc82ed 100644 --- a/src-ui/list.html +++ b/src-ui/list.html @@ -239,6 +239,7 @@

パズルの種類のリスト
  • +
  • diff --git a/src/pzpr/variety.js b/src/pzpr/variety.js index 2b09a54ed..ddac83b18 100755 --- a/src/pzpr/variety.js +++ b/src/pzpr/variety.js @@ -161,6 +161,7 @@ doubleback: [0, 0, "Double Back", "Double Back", "country"], easyasabc: [0, 0, "ABCプレース", "Easy as ABC"], edamame: [0, 0, "Edamame", "Edamame", "kaidan"], + elasticlink: [0, 0, "Elastic Link", "Elastic Link"], energywalk: [0, 0, "Energy Walk", "Energy Walk", "icewalk"], evolmino: [0, 0, "シンカミノ", "Evolmino"], factors: [0, 0, "因子の部屋", "Rooms of Factors"], diff --git a/src/res/failcode.en.json b/src/res/failcode.en.json index c6973b09d..4dbeee42c 100644 --- a/src/res/failcode.en.json +++ b/src/res/failcode.en.json @@ -565,6 +565,7 @@ "laLenNe": "The length of a line is wrong.", "laLoop": "A line forms a loop.", "laMoveOver.herugolf": "You make a bogey or more.", + "laNoCurve": "A line doesn't turn.", "laOnBorder": "There is a line across a border.", "laOnHole.herugolf": "A line goes through a hole.", "laOnIce.oyakodori": "A line goes through a nest cell.", @@ -668,6 +669,7 @@ "lnLengthGt3": "A line is longer than 3 cells.", "lnLengthLt.pencils": "A line is shorter than the connected pencil.", "lnLengthLt3": "A line is shorter than 3 cells.", + "lnLengthNe.elasticlink": "A number doesn't overlap a segment of the correct length.", "lnLengthNe3.wittgen": "A block does not cover exactly 3 cells.", "lnLenLt.anglers": "A line is shorter than the number.", "lnLenLt.reflect": "The lines passing a triangle are too short.", @@ -833,6 +835,7 @@ "nmMoveNe.tren": "A block cannot move in the correct number of spaces.", "nmNoLine.amibo": "No bar connects to a white circle.", "nmNoLine.coffeemilk": "A white or black circle doesn't have a line.", + "nmNoLine.elasticlink": "A number or circle doesn't have a line.", "nmNoLine.firefly": "There is a lonely firefly.", "nmNoLine.ichimaga": "A circle doesn't start any line.", "nmNoLine.kusabi": "A circle is not connected to another object.", @@ -946,6 +949,7 @@ "routeLenLoop": "The route does not contain a loop.", "routeIgnoreCP.nurimaze": "There is a circle out of the shortest route from S to G.", "routePassDeadEnd.nurimaze": "There is a triangle on the shortest route from S to G.", + "segAlternate": "Two connected segments don't have a length difference of exactly 1.", "segBlackEq.balance": "Segments through a black circle are equal.", "segDiff.geradeweg": "Segments have different length.", "segLong.balance": "A segment is too long.", diff --git a/src/variety-common/Answer.js b/src/variety-common/Answer.js index d12d4e1fd..49173d232 100644 --- a/src/variety-common/Answer.js +++ b/src/variety-common/Answer.js @@ -448,6 +448,23 @@ pzpr.classmgr.makeCommon({ } }, + checkLoop: function() { + var paths = this.board.linegraph.components; + for (var r = 0; r < paths.length; r++) { + var path = paths[r]; + if (path.circuits === 0) { + continue; + } + + this.failcode.add("laLoop"); + if (this.checkOnly) { + break; + } + this.board.border.setnoerr(); + path.setedgeerr(1); + } + }, + //--------------------------------------------------------------------------- // ans.checkConnectLineCount() ○などがないセルから出ている線の本数について判定する //--------------------------------------------------------------------------- diff --git a/src/variety/country.js b/src/variety/country.js index ab737b251..ce94f18de 100644 --- a/src/variety/country.js +++ b/src/variety/country.js @@ -1858,25 +1858,6 @@ return cell1.lcnt === 1 && cell2.lcnt === 1; }, "lnDeadEndAround"); }, - checkLoop: function() { - var bd = this.board; - var paths = bd.linegraph.components; - for (var r = 0; r < paths.length; r++) { - if ( - paths[r].clist.some(function(cell) { - return cell.lcnt !== 2; - }) - ) { - continue; - } - this.failcode.add("laLoop"); - if (this.checkOnly) { - break; - } - this.board.border.setnoerr(); - paths[r].setedgeerr(1); - } - }, checkLinesInRoom: function() { var bd = this.board; var paths = bd.linegraph.components; diff --git a/src/variety/elasticlink.js b/src/variety/elasticlink.js new file mode 100644 index 000000000..5891cac19 --- /dev/null +++ b/src/variety/elasticlink.js @@ -0,0 +1,275 @@ +(function(pidlist, classbase) { + if (typeof module === "object" && module.exports) { + module.exports = [pidlist, classbase]; + } else { + pzpr.classmgr.makeCustom(pidlist, classbase); + } +})(["elasticlink"], { + MouseEvent: { + inputModes: { + edit: ["mark-circle", "number", "empty", "clear"], + play: ["border", "subline"] + }, + autoedit_func: "qnum", + autoplay_func: "line", + + mouseinput_other: function() { + if (this.inputMode === "mark-circle") { + this.inputIcebarn(); + } + } + }, + + KeyEvent: { + enablemake: true, + keyinput: function(ca) { + if (ca === "q" || ca === "o") { + var cell = this.cursor.getc(); + cell.setQues(cell.ques !== 6 ? 6 : 0); + this.prev = cell; + cell.draw(); + } else { + this.key_inputqnum(ca); + } + } + }, + Cell: { + noLP: function() { + return this.isEmpty(); + }, + maxnum: function() { + var bd = this.board; + return Math.max(bd.cols, bd.rows); + }, + isLineShapeEndpoint: function() { + return this.ice(); + }, + getSegment: function(horiz) { + var llist = new this.klass.PieceList(); + var cell; + if (horiz) { + for ( + cell = this; + cell.adjborder.right.isLine(); + cell = cell.adjacent.right + ) { + llist.add(cell.adjborder.right); + } + for ( + cell = this; + cell.adjborder.left.isLine(); + cell = cell.adjacent.left + ) { + llist.add(cell.adjborder.left); + } + } else { + for ( + cell = this; + cell.adjborder.top.isLine(); + cell = cell.adjacent.top + ) { + llist.add(cell.adjborder.top); + } + for ( + cell = this; + cell.adjborder.bottom.isLine(); + cell = cell.adjacent.bottom + ) { + llist.add(cell.adjborder.bottom); + } + } + return llist; + } + }, + Board: { + hasborder: 1 + }, + Border: { + enableLineNG: true + }, + + LineGraph: { + enabled: true, + makeClist: true + }, + + //--------------------------------------------------------- + // 画像表示系 + Graphic: { + irowake: true, + + gridcolor_type: "LIGHT", + + numbercolor_func: "fixed", + + fontsizeratio: 0.65 /* 丸数字 */, + + paint: function() { + this.drawBGCells(); + this.drawGrid(); + this.drawLines(); + + this.drawPekes(); + + this.drawCircledNumbers(); + + this.drawChassis(); + + this.drawTarget(); + }, + + getBGCellColor: function(cell) { + return cell.ques === 7 ? "black" : this.getBGCellColor_error1(cell); + }, + getCircleFillColor: function(cell) { + return cell.ice() ? "rgba(255,255,255,0.5)" : null; + }, + getCircleStrokeColor: function(cell) { + return cell.ice() ? "black" : null; + } + }, + + Encode: { + decodePzpr: function(type) { + this.decodeIce(); + this.decodeNumber16(); + this.decodeEmpty(); + }, + encodePzpr: function(type) { + this.encodeIce(); + this.encodeNumber16(); + this.encodeEmpty(); + } + }, + //--------------------------------------------------------- + FileIO: { + decodeData: function() { + this.decodeCell(function(cell, ca) { + if (ca === "#") { + cell.ques = 7; + return; + } + if (ca.charAt(0) === "O") { + cell.ques = 6; + ca = ca.substr(1); + } + if (ca === "-") { + cell.qnum = -2; + } else if (+ca > 0) { + cell.qnum = +ca; + } + }); + this.decodeBorderLine(); + }, + encodeData: function() { + this.encodeCell(function(cell) { + if (cell.ques === 7) { + return "# "; + } + + var ca = ""; + if (cell.ques === 6) { + ca += "O"; + } + + if (cell.qnum === -2) { + ca += "-"; + } else if (cell.qnum > 0) { + ca += cell.qnum.toString(); + } + + if (ca === "") { + ca = "."; + } + return ca + " "; + }); + this.encodeBorderLine(); + } + }, + + AnsCheck: { + checklist: [ + "checkBranchLine", + "checkCrossLine", + "checkLoop", + + "checkNoCurveLine", + "checkSegmentLength", + "checkSegmentAlternating", + + "checkDeadendConnectLine+", + "checkNoLineObject" + ], + + checkNoCurveLine: function() { + this.checkLineShape(function(path) { + if (path.cells[1].isnull) { + return false; + } + + return path.ccnt === 0; + }, "laNoCurve"); + }, + + checkDeadendConnectLine: function() { + this.checkAllCell(function(cell) { + return !cell.ice() && cell.lcnt === 1; + }, "lnDeadEnd"); + }, + checkNoLineObject: function() { + this.checkAllCell(function(cell) { + return (cell.ice() || cell.isNum()) && cell.lcnt === 0; + }, "nmNoLine"); + }, + + checkSegmentAlternating: function() { + var bd = this.board; + for (var c = 0; c < bd.cell.length; c++) { + var cell = bd.cell[c]; + if (!cell.isLineCurve()) { + continue; + } + var horiz = cell.getSegment(true); + var vert = cell.getSegment(false); + + if (Math.abs(horiz.length - vert.length) === 1) { + continue; + } + + this.failcode.add("segAlternate"); + if (this.checkOnly) { + break; + } + bd.border.setnoerr(); + cell.seterr(1); + horiz.seterr(1); + vert.seterr(1); + } + }, + + checkSegmentLength: function() { + var bd = this.board; + for (var c = 0; c < bd.cell.length; c++) { + var cell = bd.cell[c]; + if (cell.lcnt === 0 || !cell.isValidNum()) { + continue; + } + var horiz = cell.getSegment(true); + var vert = cell.getSegment(false); + + if (horiz.length === cell.qnum || vert.length === cell.qnum) { + continue; + } + + this.failcode.add("lnLengthNe"); + if (this.checkOnly) { + break; + } + bd.border.setnoerr(); + cell.seterr(1); + horiz.seterr(1); + vert.seterr(1); + } + } + } +}); diff --git a/src/variety/kaidan.js b/src/variety/kaidan.js index 82f21ac75..ef31c3c20 100644 --- a/src/variety/kaidan.js +++ b/src/variety/kaidan.js @@ -1077,22 +1077,6 @@ return this.isIndividualObject(clist, function(cell) { return cell.qans === 1 && cell.path ? cell.path.id : -1; }); - }, - checkLoop: function() { - var paths = this.board.linegraph.components; - for (var r = 0; r < paths.length; r++) { - var path = paths[r]; - if (path.circuits === 0) { - continue; - } - - this.failcode.add("laLoop"); - if (this.checkOnly) { - break; - } - this.board.border.setnoerr(); - path.setedgeerr(1); - } } }, "AnsCheck@takoyaki,edamame#2": { diff --git a/src/variety/nuriloop.js b/src/variety/nuriloop.js index a672ac637..1ed606e29 100644 --- a/src/variety/nuriloop.js +++ b/src/variety/nuriloop.js @@ -214,21 +214,6 @@ return cell.lcnt === 1 && cell.qnum !== 0; }, "lnDeadEnd"); }, - checkLoop: function() { - var bd = this.board; - var paths = bd.linegraph.components; - for (var r = 0; r < paths.length; r++) { - if (paths[r].circuits === 0) { - continue; - } - this.failcode.add("laLoop"); - if (this.checkOnly) { - break; - } - this.board.border.setnoerr(); - paths[r].setedgeerr(1); - } - }, checkNoLineObject: function() { this.checkAllCell(function(cell) { return cell.qnum === 0 && cell.lcnt === 0; diff --git a/test/script/elasticlink.js b/test/script/elasticlink.js new file mode 100644 index 000000000..0f8721109 --- /dev/null +++ b/test/script/elasticlink.js @@ -0,0 +1,44 @@ +/* elasticlink.js */ + +ui.debug.addDebugData("elasticlink", { + url: "5/5/8040c.q1l3k", + failcheck: [ + [ + "lnBranch", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 1 1 0 /0 0 0 0 /0 0 0 0 /0 1 1 0 /0 0 0 0 /0 0 0 1 0 /0 0 0 1 0 /0 0 1 1 0 /0 1 0 0 0 /" + ], + [ + "lnCross", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /0 1 1 0 /0 0 0 0 /0 1 1 0 /0 0 0 0 /0 1 0 0 0 /0 0 0 1 0 /0 0 1 1 0 /0 1 1 0 0 /" + ], + [ + "laLoop", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /0 1 1 1 /0 0 0 0 /0 1 1 1 /0 0 0 0 /0 0 0 0 0 /0 1 0 0 1 /0 1 0 0 1 /0 0 0 0 0 /" + ], + [ + "laNoCurve", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 1 0 0 0 /0 1 0 0 0 /0 1 0 0 0 /0 1 0 0 0 /" + ], + [ + "lnLengthNe", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 1 1 /0 0 1 1 /0 0 0 0 0 /0 0 0 0 0 /0 0 1 0 0 /0 0 0 0 1 /" + ], + [ + "segAlternate", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /1 1 1 1 /0 0 0 0 /0 0 0 0 /1 0 1 1 /0 0 0 0 0 /1 0 0 0 1 /1 0 0 0 1 /1 0 0 0 1 /" + ], + [ + "lnDeadEnd", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /1 0 0 0 /0 0 1 1 /0 0 0 0 /0 0 0 0 /1 0 1 1 /1 0 0 0 0 /1 0 1 0 1 /0 0 0 0 1 /0 0 0 0 1 /" + ], + [ + "nmNoLine", + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /0 0 0 0 /0 0 1 1 /1 0 0 0 /0 0 0 0 /1 0 1 1 /0 1 0 0 0 /0 1 1 0 1 /1 0 0 0 1 /1 0 0 0 1 /" + ], + [ + null, + "pzprv3/elasticlink/5/5/- O . . . /. . . . . /. . O1 . . /. . . . 3 /. O O . . /1 0 0 0 /0 0 1 1 /1 0 0 0 /0 0 0 0 /0 0 1 1 /1 0 0 0 0 /1 0 1 0 1 /0 1 0 0 1 /0 1 0 0 1 /" + ] + ], + inputs: [] +});