diff --git a/src-ui/changes.html b/src-ui/changes.html index 55b4c7620..b0c564794 100644 --- a/src-ui/changes.html +++ b/src-ui/changes.html @@ -33,13 +33,13 @@
Latest types (all types)
diff --git a/src-ui/img/balancelink.png b/src-ui/img/balancelink.png new file mode 100644 index 000000000..172d452ac Binary files /dev/null and b/src-ui/img/balancelink.png differ diff --git a/src-ui/js/ui/KeyPopup.js b/src-ui/js/ui/KeyPopup.js index bcf4f5cd0..eff2f8f82 100644 --- a/src-ui/js/ui/KeyPopup.js +++ b/src-ui/js/ui/KeyPopup.js @@ -268,7 +268,8 @@ ui.keypopup = { slovak: [136, 10], gravel: [120, 0], seiza: [4, 0], - heyajirimisaki: [10, 0] + heyajirimisaki: [10, 0], + balancelink: [139, 0] }, //--------------------------------------------------------------------------- @@ -445,6 +446,8 @@ ui.keypopup = { this.generate_mrokmrno(mode); } else if (type === 138) { this.generate_jatahoku(); + } else if (type === 139) { + this.generate_balancelink(); } else if (type === 5339) { this.generate_swslither(); } @@ -855,6 +858,21 @@ ui.keypopup = { 4 ); }, + generate_balancelink: function() { + this.generate_main( + [ + ["1", { text: "●" }], + ["2", { text: "◆" }], + ["3", { text: "▲" }], + ["4", { text: "■" }], + ["5", { text: "★" }], + ["6", { text: "▼" }], + ["-", { text: "?" }], + " " + ], + 3 + ); + }, generate_bdwalk: function() { this.generate_main( [ diff --git a/src-ui/js/ui/Misc.js b/src-ui/js/ui/Misc.js index 58db7f5dd..cc379217c 100755 --- a/src-ui/js/ui/Misc.js +++ b/src-ui/js/ui/Misc.js @@ -113,6 +113,7 @@ function toBGimage(pid) { "araf", "archipelago", "balance", + "balancelink", "balloon", "batten", "battleship", diff --git a/src-ui/list.html b/src-ui/list.html index fcb985bc1..f90543212 100644 --- a/src-ui/list.html +++ b/src-ui/list.html @@ -244,6 +244,7 @@

パズルの種類のリスト
  • +
  • diff --git a/src/pzpr/variety.js b/src/pzpr/variety.js index c25e66dc6..a8bdf2dc3 100755 --- a/src/pzpr/variety.js +++ b/src/pzpr/variety.js @@ -101,6 +101,7 @@ arukone: [0, 0, "アルコネ", "Arukone", "numlin"], ayeheya: [0, 1, "∀人∃HEYA", "Ayeheya", "heyawake"], balance: [0, 0, "Balance Loop", "Balance Loop"], + balancelink: [0, 0, "バランスリンク", "Balance Link"], balloon: [0, 0, "風船箱", "Balloon Box"], barns: [1, 0, "バーンズ", "Barns"], batten: [0, 0, "Battenberg Painting", "Battenberg Painting"], diff --git a/src/res/failcode.en.json b/src/res/failcode.en.json index ae11df39f..ceb075243 100644 --- a/src/res/failcode.en.json +++ b/src/res/failcode.en.json @@ -624,6 +624,7 @@ "lcInvDirW.firefly": "Fireflies are connected without a line starting from point.", "lcInvWhite.wblink": "Two white circles are connected.", "lcIsolate.arukone": "A line doesn't connect any alphabet.", + "lcIsolate.balancelink": "A line doesn't connect to any symbol.", "lcIsolate.balloon": "A string doesn't connect to any number.", "lcIsolate.kusabi": "A line isn't connected to any circle.", "lcIsolate.uturns": "A line doesn't connect two gray circles.", @@ -632,12 +633,14 @@ "lcLenInvNe.kusabi": "The legs of a U have different lengths.", "lcNotKusabi.kusabi": "A line is not U-shaped.", "lcOnNum.anglers": "A line goes through a symbol.", + "lcOnNum.balancelink": "A line goes through a symbol.", "lcOnNum.arukone": "A line goes through an alphabet.", "lcOnNum.kusabi": "A line goes through a circle.", "lcOnNum.uturns": "A line goes through a gray circle.", "lcOnNum": "A line goes through a number.", "lcSameNum.ichimaga": "Same numbers are connected each other.", "lcTripleNum.arukone": "Three or more alphabets are connected.", + "lcTripleNum.balancelink": "Three or more symbols are connected.", "lcTripleNum.kusabi": "Three or more objects are connected.", "lcTripleNum.wblink": "Three or more objects are connected.", "lcTripleNum": "Three or more numbers are connected.", @@ -707,6 +710,7 @@ "lnNoTip.pencils": "A line is not connected to a pencil tip.", "lnNotRect.nagenawa": "There is a non-rectangle loop.", "lnNotSq.tajmahal": "A segment doesn't form a square.", + "lnNotSymm.balancelink": "A line is not symmetrical.", "lcNotUTurns.uturns": "A line does not turn twice in the same direction between circles.", "lcNotUTurns.vertigo": "A line turns in the wrong direction.", "lnOnCenter.midloop": "A circle is not the center point of a line.", @@ -791,6 +795,7 @@ "nmCircleNe.slovak": "The amount of circles is not equal to the amount of numbers in the surrounding cells.", "nmConnBarWrong": "The number of lines connected to a shaded cell is wrong.", "nmConnDiff.arukone": "Different alphabets are connected.", + "nmConnDiff.balancelink": "Different symbols are connected.", "nmConnDiff.kouchoku": "Different letters are connected directly.", "nmConnDiff.numlin": "Different numbers are connected.", "nmConnected.bonsan": "There are connected circles.", @@ -858,6 +863,7 @@ "nmMixed.aquapelago": "A group contains different numbers.", "nmMoveNe.tren": "A block cannot move in the correct number of spaces.", "nmNoLine.amibo": "No bar connects to a white circle.", + "nmNoLine.balancelink": "A symbol doesn't have a line.", "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.", diff --git a/src/variety-common/Graphic.js b/src/variety-common/Graphic.js index 7335796df..b6583505a 100644 --- a/src/variety-common/Graphic.js +++ b/src/variety-common/Graphic.js @@ -2300,39 +2300,22 @@ pzpr.classmgr.makeCommon({ } }, - fillStar: function(g, px, py, sizeX, sizeY) { - // 星を描画するときの頂点の位置 - var starXOffset = [ - 0, - 0.235, - 0.95, - 0.38, - 0.588, - 0, - -0.588, - -0.38, - -0.95, - -0.235 - ]; - var starYOffset = [ - -1, - -0.309, - -0.309, - 0.124, - 0.809, - 0.4, - 0.809, - 0.124, - -0.309, - -0.309 - ]; + pathStar: function(g, px, py, sizeX, sizeY, factor) { + if (!factor) { + factor = 0.38; + } g.beginPath(); g.moveTo(px, py - sizeY); for (var p = 1; p < 10; p++) { - g.lineTo(px + sizeX * starXOffset[p], py + sizeY * starYOffset[p]); + var starY = Math.cos(p * Math.PI * 0.2) * (p & 1 ? factor : 1), + starX = Math.sin(p * Math.PI * 0.2) * (p & 1 ? factor : 1); + g.lineTo(px + sizeX * starX, py + sizeY * -starY); } g.closePath(); + }, + fillStar: function(g, px, py, sizeX, sizeY, factor) { + this.pathStar(g, px, py, sizeX, sizeY, factor); g.fill(); }, diff --git a/src/variety/balancelink.js b/src/variety/balancelink.js new file mode 100644 index 000000000..eb5a20821 --- /dev/null +++ b/src/variety/balancelink.js @@ -0,0 +1,357 @@ +(function(pidlist, classbase) { + if (typeof module === "object" && module.exports) { + module.exports = [pidlist, classbase]; + } else { + pzpr.classmgr.makeCustom(pidlist, classbase); + } +})(["balancelink"], { + //--------------------------------------------------------- + // マウス入力系 + MouseEvent: { + inputModes: { + edit: ["number", "clear", "empty", "info-line"], + play: ["line", "peke", "info-line"] + }, + autoedit_func: "qnum", + autoplay_func: "line" + }, + + //--------------------------------------------------------- + // キーボード入力系 + KeyEvent: { + enablemake: true, + keyinput: function(ca) { + if (ca === "q") { + var cell = this.cursor.getc(); + cell.setQues(cell.ques !== 7 ? 7 : 0); + this.prev = cell; + cell.draw(); + } else { + this.key_inputqnum(ca); + } + }, + getNewNumber: function(cell, ca, cur) { + if (ca === "BS" || ca === " ") { + return -1; + } else if (ca === "-") { + return -2; + } else if (ca >= "1" && ca <= "6") { + return cur === +ca ? cur + 6 : +ca; + } + return null; + } + }, + + //--------------------------------------------------------- + // 盤面管理系 + Cell: { + maxnum: 12, + noLP: function() { + return this.isEmpty(); + } + }, + Border: { + enableLineNG: true + }, + Board: { + hasborder: 1 + }, + + LineGraph: { + enabled: true, + makeClist: true + }, + + //--------------------------------------------------------- + // 画像表示系 + Graphic: { + gridcolor_type: "LIGHT", + + numbercolor_func: "qnum", + + irowake: true, + + paint: function() { + this.drawBGCells(); + this.drawGrid(); + + this.drawPekes(); + this.drawQuesMarks(); + this.drawLines(); + + this.drawChassis(); + + this.drawTarget(); + }, + + drawQuesMarks: function() { + var g = this.vinc("cell_mark", "auto"); + + g.lineWidth = Math.max(this.cw / 18, 2); + var rsize = this.cw * 0.35; + + var triy = 0.867 * rsize, + trix = rsize, + sqsize = rsize * 0.75; + var clist = this.range.cells; + for (var i = 0; i < clist.length; i++) { + var cell = clist[i]; + + g.vid = "c_mk_" + cell.id; + g.strokeStyle = this.getQuesNumberColor(cell); + g.fillStyle = g.strokeStyle; + var px = cell.bx * this.bw, + py = cell.by * this.bh; + var value = cell.qnum > 6 ? cell.qnum - 6 : cell.qnum; + switch (value) { + case 1: + if (cell.qnum > 6) { + g.fillCircle(px, py, rsize); + } else { + g.strokeCircle(px, py, rsize); + } + continue; + case 2: + g.beginPath(); + g.setOffsetLinePath( + px, + py, + 0, + -rsize, + rsize, + 0, + 0, + rsize, + -rsize, + 0, + true + ); + break; + case 3: + g.beginPath(); + g.setOffsetLinePath( + px, + py, + 0, + -triy, + -trix, + triy, + trix, + triy, + true + ); + break; + case 4: + if (cell.qnum > 6) { + g.fillRectCenter(px, py, sqsize, sqsize); + } else { + g.strokeRectCenter(px, py, sqsize, sqsize); + } + continue; + case 5: + this.pathStar(g, px, py, rsize, rsize, 0.6); + break; + case 6: + g.beginPath(); + g.setOffsetLinePath( + px, + py, + 0, + triy, + -trix, + -triy, + trix, + -triy, + true + ); + break; + default: + g.vhide(); + continue; + } + if (cell.qnum > 6) { + g.fill(); + } else { + g.stroke(); + } + } + }, + getBGCellColor: function(cell) { + return cell.ques === 7 ? "black" : this.getBGCellColor_error1(cell); + } + }, + + //--------------------------------------------------------- + // URLエンコード/デコード処理 + Encode: { + decodePzpr: function() { + var bd = this.board; + this.genericDecodeNumber16(bd.cell.length, function(c, val) { + var cell = bd.cell[c]; + if (val === 0) { + cell.ques = 7; + } else { + cell.qnum = val; + } + }); + }, + encodePzpr: function() { + var bd = this.board; + this.genericEncodeNumber16(bd.cell.length, function(c) { + var cell = bd.cell[c]; + return cell.ques === 7 ? 0 : cell.qnum; + }); + } + }, + //--------------------------------------------------------- + FileIO: { + decodeData: function() { + this.decodeCellQnum(); + this.decodeBorderLine(); + }, + encodeData: function() { + this.encodeCellQnum(); + this.encodeBorderLine(); + }, + + decodeCellQnum: function() { + this.decodeCell(function(cell, ca) { + if (ca === "#") { + cell.ques = 7; + } else if (ca !== ".") { + cell.qnum = +ca; + } + }); + }, + encodeCellQnum: function() { + this.encodeCell(function(cell) { + if (cell.ques === 7) { + return "# "; + } else if (cell.qnum !== -1) { + return cell.qnum + " "; + } else { + return ". "; + } + }); + } + }, + + //--------------------------------------------------------- + // 正解判定処理実行部 + AnsCheck: { + checklist: [ + "checkLineExist+", + "checkBranchLine", + "checkCrossLine", + "checkTripleObject", + "checkLineOnShaded", + "checkLinkSymmetry", + "checkLineOverLetter", + "checkLinkSameNumber", + "checkDeadendConnectLine+", + "checkDisconnectLine", + "checkNoLineObject+" + ], + + checkLineOnShaded: function() { + for (var c = 0; c < this.board.cell.length; c++) { + var cell = this.board.cell[c]; + if (cell.ques !== 7 || cell.lcnt === 0) { + continue; + } + + this.failcode.add("lnOnShade"); + if (this.checkOnly) { + break; + } + cell.seterr(1); + } + }, + + checkLinkSymmetry: function() { + var self = this; + this.checkLineShape(function(path) { + var cell1 = path.cells[0], + cell2 = path.cells[1]; + + if (!cell1.isNum() || cell1.qnum !== cell2.qnum) { + return false; + } + + if (path.objs <= 2) { + return false; + } + + var map = self.getSymmetryMap(path.dir1, path.dir2); + if (!map) { + return true; + } + + var prev1 = cell1, + prev2 = cell2; + for (var i = 0; i <= path.objs.length / 2; i++) { + var j = path.objs.length - (i + 1); + + var link1 = path.objs[i], + link2 = path.objs[j]; + + var next1 = + link1.sidecell[0] === prev1 + ? link1.sidecell[1] + : link1.sidecell[0], + next2 = + link2.sidecell[0] === prev2 + ? link2.sidecell[1] + : link2.sidecell[0]; + + var dir1 = prev1.getdir(next1, 2), + dir2 = prev2.getdir(next2, 2); + + if (map[dir1] !== dir2) { + return true; + } + + prev1 = next1; + prev2 = next2; + } + + return false; + }, "lnNotSymm"); + }, + + getSymmetryMap: function(dir1, dir2) { + switch (Math.min(dir1, dir2) + "," + Math.max(dir1, dir2)) { + case "1,1": // up, up + case "2,2": // dn, dn + case "3,4": // lt, rt + return { 1: 1, 2: 2, 3: 4, 4: 3 }; + + case "3,3": // lt, lt + case "4,4": // rt, rt + case "1,2": // up, dn + return { 1: 2, 2: 1, 3: 3, 4: 4 }; + + case "1,3": // up, lt + case "2,4": // dn, rt + return { 1: 3, 2: 4, 3: 1, 4: 2 }; + + case "1,4": // up, rt + case "2,3": // dn, lt + return { 1: 4, 2: 3, 3: 2, 4: 1 }; + + default: + throw Error("Unknown symmetry for " + dir1 + "," + dir2); + } + }, + + checkLinkSameNumber: function() { + this.checkSameObjectInRoom( + this.board.linegraph, + function(cell) { + return Math.max(cell.qnum, -1); + }, + "nmConnDiff" + ); + } + } +}); diff --git a/test/script/balancelink.js b/test/script/balancelink.js new file mode 100644 index 000000000..53a8e47d6 --- /dev/null +++ b/test/script/balancelink.js @@ -0,0 +1,61 @@ +/* balancelink.js */ + +ui.debug.addDebugData("balancelink", { + url: "5/5/0k1gbmb01gbib", + failcheck: [ + [ + "brNoLine", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /" + ], + [ + "lnBranch", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 0 /1 1 1 1 /0 0 0 0 /0 0 0 0 /0 0 0 0 0 /0 0 0 1 0 /1 0 0 0 1 /1 0 0 0 1 /" + ], + [ + "lnCross", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 0 /1 1 1 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 0 /0 1 0 0 0 /1 1 0 1 0 /1 0 0 0 0 /" + ], + [ + "lcTripleNum", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 1 /0 0 0 0 /0 0 0 0 /1 1 1 1 /0 0 0 0 0 /0 0 0 0 1 /0 0 0 0 1 /0 0 0 0 1 /" + ], + [ + "lnOnShade", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 1 0 0 /0 0 0 0 /0 0 1 0 /0 0 0 0 /0 0 0 0 0 /0 0 1 0 0 /0 0 1 0 0 /0 0 0 0 0 /", + { skiprules: true } + ], + [ + "lnNotSymm", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 1 /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 /" + ], + [ + "lnNotSymm", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 0 0 /0 1 1 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 0 /0 1 0 0 0 /0 0 0 1 0 /0 0 0 0 0 /" + ], + [ + "lcOnNum", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 0 1 1 /0 1 0 0 /0 0 0 0 /0 0 0 0 /0 0 0 0 0 /0 0 1 0 0 /0 1 0 0 0 /0 1 0 0 0 /" + ], + [ + "nmConnDiff", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /1 0 0 0 /0 0 0 0 /1 0 0 0 /0 0 0 0 /0 0 0 0 0 /1 0 0 0 0 /1 0 0 0 0 /0 0 0 0 0 /" + ], + [ + "lnDeadEnd", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /1 0 1 0 /0 1 0 1 /0 0 0 0 /1 1 1 1 /0 0 0 0 0 /1 0 1 0 0 /0 1 0 1 0 /0 0 0 0 0 /" + ], + [ + "lcIsolate", + "pzprv3/balancelink/5/5/# . . . . /. 1 . . . /. . . . . /. . # 1 . /11 . . . 11 /0 1 1 1 /0 0 1 0 /0 0 1 0 /0 0 0 1 /1 1 1 1 /0 1 0 0 1 /0 0 1 1 1 /0 0 0 0 1 /0 0 0 0 0 /" + ], + [ + "nmNoLine", + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 0 0 0 /0 1 0 0 /0 0 1 0 /0 0 0 0 /1 1 1 1 /0 0 0 0 0 /0 0 1 0 0 /0 0 0 1 0 /0 0 0 0 0 /" + ], + [ + null, + "pzprv3/balancelink/5/5/# . . . . /. 1 . 11 . /. . . . . /. 11 # 1 . /11 . . . 11 /0 1 1 1 /0 0 1 0 /0 1 0 0 /0 0 0 1 /1 1 1 1 /0 1 0 0 1 /0 0 1 0 1 /0 1 0 0 1 /0 0 0 0 0 /" + ] + ], + inputs: [] +});