diff --git a/src-ui/changes.html b/src-ui/changes.html index b1447d1dc..ca3c86a8f 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/orbital.png b/src-ui/img/orbital.png new file mode 100644 index 000000000..612de1227 Binary files /dev/null and b/src-ui/img/orbital.png differ diff --git a/src-ui/js/ui/KeyPopup.js b/src-ui/js/ui/KeyPopup.js index 0239d2540..d79ea4d74 100644 --- a/src-ui/js/ui/KeyPopup.js +++ b/src-ui/js/ui/KeyPopup.js @@ -218,7 +218,8 @@ ui.keypopup = { nibunnogo: [4, 0], mintonette: [10, 0], balloon: [10, 0], - tilecity: [10, 0] + tilecity: [10, 0], + orbital: [124, 0] }, //--------------------------------------------------------------------------- @@ -880,6 +881,7 @@ ui.keypopup = { }, generate_trainstations: function(mode) { + var orbital = ui.puzzle.pid === "orbital"; this.generate_main( [ "0", @@ -893,8 +895,8 @@ ui.keypopup = { "8", "9", " ", - ["-", "?"], - ["q", "╋"] + ["-", orbital ? "●" : "?"], + ["q", orbital ? "○" : "╋"] ], 4 ); diff --git a/src-ui/js/ui/Misc.js b/src-ui/js/ui/Misc.js index eefcd50bc..11d03aa2f 100755 --- a/src-ui/js/ui/Misc.js +++ b/src-ui/js/ui/Misc.js @@ -174,6 +174,7 @@ function toBGimage(pid) { "nurimisaki", "nuriuzu", "oneroom", + "orbital", "ovotovata", "oyakodori", "patchwork", diff --git a/src-ui/list.html b/src-ui/list.html index 40d3eca81..0cf7a6516 100644 --- a/src-ui/list.html +++ b/src-ui/list.html @@ -183,6 +183,7 @@

パズルの種類のリスト
  • +
  • diff --git a/src/pzpr/variety.js b/src/pzpr/variety.js index 26cc8ad50..2910e0025 100755 --- a/src/pzpr/variety.js +++ b/src/pzpr/variety.js @@ -310,6 +310,7 @@ ovotovata: [0, 0, "Ovotovata", "Ovotovata", "country"], oneroom: [0, 0, "ワンルームワンドア", "One Room One Door", "heyawake"], onsen: [0, 0, "温泉めぐり", "Onsen-meguri", "country"], + orbital: [0, 0, "オービタル", "Orbital", "nagenawa"], oyakodori: [0, 0, "おやこどり", "Oyakodori", "kaero"], paintarea: [1, 0, "ペイントエリア", "Paintarea"], parquet: [0, 0, "Parquet", "Parquet"], diff --git a/src/res/failcode.en.json b/src/res/failcode.en.json index f99ff4796..477150cc4 100644 --- a/src/res/failcode.en.json +++ b/src/res/failcode.en.json @@ -625,6 +625,7 @@ "lpNoNum.onsen": "A loop has no numbers.", "lpNoNum.pipelink": "A loop has no numbers.", "lpNumGt2.onsen": "A loop has more than one number.", + "lpNumGt2.orbital": "A loop encloses more than one black circle.", "lpPlNum.pipelink": "A loop has multiple kinds of number.", "lpSepNum.pipelink": "A kind of number is in different loops.", "lrAcrossArrow.nagare": "The line doesn't go straight in the arrow's direction.", @@ -734,6 +735,7 @@ "nmNoLine": "A number is not connected to another number.", "nmNoMove.bonsan": "A circle doesn't start any line.", "nmNoMove.rectslider": "A shaded cell doesn't start any line.", + "nmNoOrbit.orbital": "A black circle is not inside a loop.", "nmNoSideShade.tasquare": "No shaded cells are adjacent to square marks.", "nmNotConsecNeighbors.ripple": "A number is not the neighbor of its consecutive numbers.", "nmNotConseq.kouchoku": "Equal letters are not connected directly.", @@ -745,6 +747,7 @@ "nmNumberNe.sukoro": "The number of numbers placed in four adjacent cells is not equal to the number.", "nmNumberNe.sukororoom": "The number of numbers placed in four adjacent cells is not equal to the number.", "nmNumberNe.view": "The number of numbers placed in four adjacent cells is not equal to the number.", + "nmOrbitNe.orbital": "A number does not indicate the amount of white circles used by the loop.", "nmOutOfBk.oyakodori": "A bird is outside a nest.", "nmOutOfBk.yosenabe": "A filling isn't in a crock.", "nmOutOfHole.herugolf": "A ball doesn't cup in.", @@ -753,15 +756,16 @@ "nmOutsideTren.tren": "A number is not contained inside a 1x2 or 1x3 block.", "nmPillowGt.shugaku": "The number of pillows around the number is wrong.", "nmPillowLt.shugaku": "The number of pillows around the number is wrong.", + "nmPlOrbit.orbital": "A black circle is inside multiple loops.", "nmProduct.factors": "A number of room is not equal to the product of these numbers.", "nmRange.kakuru": "A number is larger than 9.", "nmRange.sananko": "A number is larger than 3.", "nmRange.trainstations": "A number is out of range.", "nmSame2x2.kazunori": "There is a 2x2 block of the same number.", "nmSame2x2.snakepit": "A snake loops back on itself.", - "nmShadeEq.smullyan": "The number of shaded cells in a shaded number's domain is equal to the number.", "nmShade5Ne.lookair": "The number is not equal to the number of shaded cells in the cell and the four adjacent cells.", "nmShadeDiagNe.context": "The number of shaded cells diagonally adjacent to a shaded number is not correct.", + "nmShadeEq.smullyan": "The number of shaded cells in a shaded number's domain is equal to the number.", "nmShadeGt.interbd": "The number of shaded cells around a number is not correct.", "nmShadeGt.kaidan": "The number of circles around a number is not correct.", "nmShadeGt.kuromenbun": "The number of shaded cells around an area is not correct.", diff --git a/src/variety/nagenawa.js b/src/variety/nagenawa.js index 3715e7883..d5094cb43 100644 --- a/src/variety/nagenawa.js +++ b/src/variety/nagenawa.js @@ -7,7 +7,7 @@ } else { pzpr.classmgr.makeCustom(pidlist, classbase); } -})(["nagenawa", "ringring"], { +})(["nagenawa", "ringring", "orbital"], { //--------------------------------------------------------- // マウス入力系 "MouseEvent@nagenawa": { @@ -19,6 +19,35 @@ "MouseEvent@ringring": { inputModes: { edit: ["info-line"], play: ["line", "peke", "info-line"] } }, + "MouseEvent@orbital": { + inputModes: { + edit: ["number", "circle-unshade", "info-line"], + play: ["line", "peke", "info-line"] + }, + mouseinput: function() { + if (this.inputMode === "circle-unshade") { + this.inputIcebarn(); + } else { + this.common.mouseinput.call(this); + } + }, + getNewNumber: function(cell, val) { + if (this.btn === "left" && val === -1) { + return -3; + } else if (this.btn === "left" && val === -2) { + return 0; + } else if (this.btn === "left" && val === cell.getmaxnum()) { + return -1; + } else if (this.btn === "right" && val === 0) { + return -2; + } else if (this.btn === "right" && val === -1) { + return cell.getmaxnum(); + } + + val += this.btn === "left" ? 1 : -1; + return val < -3 ? -1 : val; + } + }, MouseEvent: { mouseinput_auto: function() { if (this.puzzle.playmode) { @@ -45,6 +74,8 @@ if (this.mousestart) { this.inputblock(); } + } else if (this.pid === "orbital") { + this.inputqnum(); } } }, @@ -61,9 +92,24 @@ //--------------------------------------------------------- // キーボード入力系 - "KeyEvent@nagenawa": { + "KeyEvent@nagenawa,orbital": { enablemake: true }, + "KeyEvent@orbital#1": { + keyinput: function(ca) { + if (ca === "q") { + var cell = this.cursor.getc(); + cell.setQues(cell.ques !== 6 ? 6 : 0); + this.prev = cell; + cell.draw(); + return; + } else if (ca === "w") { + ca = "s1"; + } + + this.key_inputqnum(ca); + } + }, //--------------------------------------------------------- // 盤面管理系 @@ -73,7 +119,38 @@ }, minnum: 0 }, - "Border@ringring": { + "Cell@orbital": { + maxnum: function() { + return (this.board.rows + this.board.cols - 2) << 1; + }, + noLP: function(dir) { + return this.isNum(); + }, + getNum: function() { + return this.ice() ? -3 : this.qnum; + }, + setNum: function(val) { + if (val === -3) { + this.setQues(6); + } else { + this.setQues(0); + this.setQnum(val); + } + }, + posthook: { + qnum: function(val) { + if (val !== -1 && this.ques === 6) { + this.setQues(0); + } + }, + ques: function(val) { + if (val === 6) { + this.setQnum(-1); + } + } + } + }, + "Border@ringring,orbital": { enableLineNG: true }, Board: { @@ -85,7 +162,14 @@ LineGraph: { enabled: true, - isLineCross: true + isLineCross: true, + setExtraData: function(component) { + this.common.setExtraData.call(this, component); + component.bounds = null; + } + }, + "LineGraph@orbital": { + makeClist: true }, "AreaRoomGraph@nagenawa": { @@ -117,6 +201,8 @@ this.drawBorders(); } else if (pid === "ringring") { this.drawQuesCells(); + } else if (this.pid === "orbital") { + this.drawCircledNumbers(); } this.drawLines(); @@ -130,6 +216,25 @@ "Graphic@ringring": { drawTarget: function() {} }, + "Graphic@orbital": { + hideHatena: true, + fontShadecolor: "white", + numbercolor_func: "fixed_shaded", + getCircleStrokeColor: function(cell) { + if (!cell.ice()) { + return null; + } + var error = cell.error || cell.qinfo; + return error === 1 || error === 4 ? this.errcolor1 : this.quescolor; + }, + getCircleFillColor: function(cell) { + if (!cell.isNum()) { + return null; + } + var error = cell.error || cell.qinfo; + return error === 1 || error === 4 ? this.errcolor1 : this.quescolor; + } + }, //--------------------------------------------------------- // URLエンコード/デコード処理 @@ -197,11 +302,19 @@ count = 0; } } - //if(count>0){ cm += count.toString(36);} - this.outbstr += cm; } }, + "Encode@orbital": { + decodePzpr: function() { + this.decodeIce(); + this.decodeNumber16(); + }, + encodePzpr: function() { + this.encodeIce(); + this.encodeNumber16(); + } + }, //--------------------------------------------------------- "FileIO@nagenawa": { decodeData: function() { @@ -240,17 +353,50 @@ }); } }, + "FileIO@orbital": { + decodeData: function() { + this.decodeCell(function(cell, ca) { + if (ca === "#") { + cell.ques = 6; + } else if (ca === "-") { + cell.qnum = -2; + } else if (ca !== ".") { + cell.qnum = +ca; + } + }); + this.decodeBorderLine(); + }, + encodeData: function() { + this.encodeCell(function(cell) { + if (cell.ques === 6) { + return "# "; + } else if (cell.qnum === -2) { + return "- "; + } else if (cell.qnum >= 0) { + return cell.qnum + " "; + } else { + return ". "; + } + }); + this.encodeBorderLine(); + } + }, //--------------------------------------------------------- // 正解判定処理実行部 AnsCheck: { checklist: [ "checkLineExist", - "checkLineOnShadeCell@ringring", + "checkLineOnShadeCell@ringring,orbital", "checkOverLineCount@nagenawa", "checkBranchLine", "checkDeadendLine+", "checkLessLineCount@nagenawa", "checkAllLoopRect", + "checkMultipleOrbit@orbital", + "checkMultiplePlanets@orbital", + "checkOrbitNumber@orbital", + "checkOrbitExists@orbital", + "checkAllCirclePassed@orbital", "checkUnreachedUnshadeCell+@ringring" ], @@ -283,8 +429,8 @@ bd = this.board; var paths = bd.linegraph.components; for (var r = 0; r < paths.length; r++) { - var borders = paths[r].getedgeobjs(); - if (this.isLoopRect(borders)) { + var component = paths[r]; + if (this.getComponentBounds(component)) { continue; } @@ -292,14 +438,23 @@ if (this.checkOnly) { break; } - paths[r].setedgeerr(1); + component.setedgeerr(1); } if (!result) { this.failcode.add("lnNotRect"); bd.border.setnoerr(); } }, - isLoopRect: function(borders) { + getComponentBounds: function(component) { + var bounds = component.bounds; + if (bounds === null) { + var borders = component.getedgeobjs(); + component.bounds = this.calculateComponentBounds(borders); + return component.bounds; + } + return bounds; + }, + calculateComponentBounds: function(borders) { var bd = this.board; var x1 = bd.maxbx, x2 = bd.minbx, @@ -319,6 +474,17 @@ y2 = borders[i].by; } } + + /* All coordinates must be even numbers, otherwise this can't be a cell rectangle */ + if ((x1 & x2 & y1 & y2 & 1) === 0) { + return false; + } + + var expected = x2 - x1 + (y2 - y1); + if (borders.length !== expected) { + return false; + } + for (var i = 0; i < borders.length; i++) { var border = borders[i]; if ( @@ -330,7 +496,157 @@ return false; } } - return true; + return { x1: x1, x2: x2, y1: y1, y2: y2 }; + } + }, + "AnsCheck@orbital": { + checkOrbitExists: function() { + var orbits = this.getOrbitData(); + + this.checkAllCell(function(cell) { + return cell.isNum() && !orbits[cell.id]; + }, "nmNoOrbit"); + }, + checkLineOnShadeCell: function() { + this.checkAllCell(function(cell) { + return cell.isNum() && cell.lcnt > 0; + }, "lnOnShade"); + }, + checkMultiplePlanets: function() { + var bd = this.board, + paths = bd.linegraph.components; + for (var r = 0; r < paths.length; r++) { + paths[r]._id = r; + } + + var orbits = this.getOrbitData(); + var reverse = {}; + var result = true; + + for (var id in orbits) { + var count = orbits[id].length; + if (count !== 1) { + continue; + } + + var cell = bd.cell[+id]; + + var loop = orbits[id][0]; + var loopid = loop._id + ""; + if (loopid in reverse) { + result = false; + if (this.checkOnly) { + break; + } + loop.setedgeerr(1); + cell.seterr(1); + reverse[loopid].seterr(1); + } else { + reverse[loopid] = cell; + } + } + + if (!result) { + this.failcode.add("lpNumGt2"); + this.board.border.setnoerr(); + } + }, + checkMultipleOrbit: function() { + var orbits = this.getOrbitData(); + var result = true; + + for (var id in orbits) { + var count = orbits[id].length; + if (count === 1) { + continue; + } + + result = false; + if (this.checkOnly) { + break; + } + this.board.cell[+id].seterr(1); + for (var x = 0; x < count; x++) { + orbits[id][x].setedgeerr(1); + } + } + if (!result) { + this.failcode.add("nmPlOrbit"); + this.board.border.setnoerr(); + } + }, + checkOrbitNumber: function() { + var orbits = this.getOrbitData(); + var result = true; + + for (var id in orbits) { + if (orbits[id].length !== 1) { + continue; + } + var cell = this.board.cell[+id]; + if (!cell.isValidNum()) { + continue; + } + + var circles = orbits[id][0].clist.filter(function(o) { + return o.ice(); + }); + if (circles.length === cell.getNum()) { + continue; + } + + result = false; + if (this.checkOnly) { + break; + } + this.board.cell[+id].seterr(1); + orbits[id][0].setedgeerr(1); + } + if (!result) { + this.failcode.add("nmOrbitNe"); + this.board.border.setnoerr(); + } + }, + + getOrbitData: function() { + if (this._info.orbits) { + return this._info.orbits; + } + + var ret = {}; + var bd = this.board; + var paths = bd.linegraph.components; + for (var r = 0; r < paths.length; r++) { + var component = paths[r]; + var bounds = this.getComponentBounds(component); + if (!bounds) { + continue; + } + + var cells = bd.cellinside(bounds.x1, bounds.y1, bounds.x2, bounds.y2); + cells.each(function(cell) { + if (!cell.isNum()) { + return; + } + + var id = cell.id + ""; + if (!(id in ret)) { + ret[id] = []; + } + ret[id].push(component); + }); + } + + return (this._info.orbits = ret); + }, + + checkAllCirclePassed: function() { + this.checkAllCell(function(cell) { + return cell.lcnt === 0 && cell.ice(); + }, "lnIsolate"); } + }, + "FailCode@orbital": { + lnIsolate: "lnIsolate.dotchi" } }); diff --git a/test/script/nagenawa.js b/test/script/nagenawa.js index be209ac62..fbd7890b5 100644 --- a/test/script/nagenawa.js +++ b/test/script/nagenawa.js @@ -35,5 +35,9 @@ ui.debug.addDebugData("nagenawa", { inputs: [ /* 回答入力はicebarn, countryと同じなので省略 */ /* 問題入力はcountryと同じなので省略 */ + { + input: ["newboard,2,2", "editmode", "mouse,rightx2,3,3"], + result: "pzprv3/nagenawa/2/2/1/0 0 /0 0 /4 . /. . /0 /0 /0 0 /0 0 /0 0 /" + } ] }); diff --git a/test/script/orbital.js b/test/script/orbital.js new file mode 100644 index 000000000..93892e2ff --- /dev/null +++ b/test/script/orbital.js @@ -0,0 +1,78 @@ +/* orbital.js */ + +ui.debug.addDebugData("orbital", { + url: "6/6/88o100s0t4s.m", + failcheck: [ + [ + "brNoLine", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /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 0 0 /0 0 0 0 0 0 /0 0 0 0 0 0 /0 0 0 0 0 0 /" + ], + [ + "lnOnShade", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /0 0 0 0 0 /0 0 0 0 0 /1 1 1 0 0 /0 0 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 /1 0 0 1 0 0 /1 0 0 1 0 0 /1 0 0 1 0 0 /" + ], + [ + "lnBranch", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /0 0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /1 1 1 1 1 /0 0 0 0 0 /1 1 1 1 1 /0 0 0 0 0 0 /0 0 0 0 0 0 /0 0 0 0 0 0 /1 0 1 0 0 1 /1 0 1 0 0 1 /" + ], + [ + "lnNotRect", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 1 /0 0 0 1 1 /0 0 0 0 0 /1 1 1 0 0 /1 1 0 0 0 /1 1 0 0 0 /1 0 0 0 0 1 /1 0 0 1 0 0 /1 0 0 1 0 0 /0 0 0 0 0 0 /1 0 1 0 0 0 /" + ], + [ + "nmNoOrbit", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 1 /0 0 0 1 0 /0 0 0 1 0 /1 1 1 1 1 /1 1 0 0 0 /1 1 0 0 0 /1 0 0 0 0 1 /1 0 0 1 1 1 /1 0 0 0 0 1 /0 0 0 0 0 0 /1 0 1 0 0 0 /" + ], + [ + "lpNumGt2", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 1 /0 0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /0 0 0 0 0 /1 1 1 1 1 /1 0 0 0 0 1 /1 0 0 0 0 1 /1 0 0 0 0 1 /1 0 0 0 0 1 /1 0 0 0 0 1 /" + ], + [ + "nmPlOrbit", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /0 1 1 0 0 /1 1 1 1 0 /0 0 0 0 0 /1 1 1 1 0 /0 1 1 0 0 /0 0 0 0 0 /0 1 0 1 0 0 /1 1 0 1 1 0 /1 1 0 1 1 0 /0 1 0 1 0 0 /0 0 0 0 0 0 /" + ], + [ + "nmOrbitNe", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /0 1 1 1 0 /0 0 0 0 0 /0 0 0 0 0 /0 1 1 1 0 /0 0 0 0 0 /0 0 0 0 0 /0 1 0 0 1 0 /0 1 0 0 1 0 /0 1 0 0 1 0 /0 0 0 0 0 0 /0 0 0 0 0 0 /" + ], + [ + "lnIsolate", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 0 /0 0 0 1 1 /0 0 0 0 0 /1 1 1 1 0 /0 0 0 0 0 /0 0 0 1 1 /1 0 0 0 1 0 /1 0 0 1 1 1 /1 0 0 1 1 1 /0 0 0 1 0 1 /0 0 0 1 0 1 /" + ], + [ + "lnDeadEnd", + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 0 /0 0 0 1 1 /0 0 0 0 0 /1 1 1 1 0 /0 0 0 0 0 /1 1 0 1 1 /1 0 0 0 1 0 /1 0 0 1 1 1 /1 0 0 1 1 1 /0 0 0 1 0 1 /1 0 0 1 0 1 /" + ], + [ + null, + "pzprv3/orbital/6/6/. # . . . . /# . . . # # /. . 4 . . . /. # . . . . /. . . . - . /# # # . . . /1 1 1 1 0 /0 0 0 1 1 /0 0 0 0 0 /1 1 1 1 0 /1 1 0 0 0 /1 1 0 1 1 /1 0 0 0 1 0 /1 0 0 1 1 1 /1 0 0 1 1 1 /0 0 0 1 0 1 /1 0 1 1 0 1 /" + ] + ], + inputs: [ + { + input: [ + "newboard,2,2", + "editmode", + "mouse,leftx2,3,1", + "mouse,leftx3,1,3", + "mouse,leftx4,3,3" + ], + result: "pzprv3/orbital/2/2/. # /- 0 /0 /0 /0 0 /" + }, + { + input: [ + "cursor,1,3", + "key,q", + "cursor,3,3", + "key,3", + "editmode,circle-unshade", + "mouse,left,1,1" + ], + result: "pzprv3/orbital/2/2/# # /# 3 /0 /0 /0 0 /" + }, + { + input: ["playmode", "mouse,left,1,3,1,1,3,1,3,3"], + result: "pzprv3/orbital/2/2/# # /# 3 /1 /0 /1 0 /" + } + ] +});