From 605f7e280ad303c7c7dd636796f6958e789e0008 Mon Sep 17 00:00:00 2001 From: Lennard Sprong Date: Mon, 27 Nov 2023 15:22:10 +0100 Subject: [PATCH] Add Kissing Polyominoes --- src-ui/changes.html | 2 +- src-ui/img/kissing.png | Bin 0 -> 141 bytes src-ui/js/ui/Misc.js | 1 + src-ui/list.html | 1 + src/pzpr/variety.js | 7 + src/res/failcode.en.json | 3 + src/variety/statuepark.js | 334 +++++++++++++++++++++++++++++++++++++- test/script/kissing.js | 61 +++++++ 8 files changed, 400 insertions(+), 9 deletions(-) create mode 100644 src-ui/img/kissing.png create mode 100644 test/script/kissing.js diff --git a/src-ui/changes.html b/src-ui/changes.html index 729abeb38..b0ff02830 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/kissing.png b/src-ui/img/kissing.png new file mode 100644 index 0000000000000000000000000000000000000000..54c67c336bdff8be0434d65e880d3f073c56b7c8 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~wg8_H*Z)3= z|37?Mwzkk0C}QgA;us<^H8~+AB_V-@k&VrunbDbnsX@qsb;1@04IQQx42ljtTq{Z~ kSQe~HIHKEdG$4kFA;yQ{;5tV?3!niEp00i_>zopr0Px%-#Q*>R literal 0 HcmV?d00001 diff --git a/src-ui/js/ui/Misc.js b/src-ui/js/ui/Misc.js index 28fb7baa9..764116590 100755 --- a/src-ui/js/ui/Misc.js +++ b/src-ui/js/ui/Misc.js @@ -142,6 +142,7 @@ function toBGimage(pid) { "ladders", "kaidan", "kaisu", + "kissing", "kropki", "kuroclone", "lollipops", diff --git a/src-ui/list.html b/src-ui/list.html index d4e3eee1d..6a6e0043a 100644 --- a/src-ui/list.html +++ b/src-ui/list.html @@ -370,6 +370,7 @@

パズルの種類のリスト
  • +
  • diff --git a/src/pzpr/variety.js b/src/pzpr/variety.js index 471dc3600..f3b00b294 100755 --- a/src/pzpr/variety.js +++ b/src/pzpr/variety.js @@ -213,6 +213,13 @@ kakuru: [0, 0, "カックル", "Kakuru"], kazunori: [0, 0, "かずのりのへや", "Kazunori Room"], kinkonkan: [1, 0, "キンコンカン", "Kin-Kon-Kan"], + kissing: [ + 0, + 0, + "Kissing Polyominoes", + "Kissing Polyominoes", + "statuepark" + ], koburin: [0, 0, "コブリン", "Koburin", "yajilin"], kouchoku: [0, 0, "交差は直角に限る", "Kouchoku"], kramma: [0, 0, "快刀乱麻", "KaitoRamma", "kramma"], diff --git a/src/res/failcode.en.json b/src/res/failcode.en.json index aa548b5c3..ce81f6843 100644 --- a/src/res/failcode.en.json +++ b/src/res/failcode.en.json @@ -61,6 +61,7 @@ "bdPassStar.tentaisho": "A line goes over a star.", "bdUnequal.antmill": "A cross clue does not overlap exactly 1 shaded cell.", "bdUnused.fillomino": "A given border does not divide two regions.", + "bdUnused.kissing": "A bar is not adjacent to two different blocks.", "bdUnused.lohkous": "There is an unused border.", "bdUnused.mirrorbk": "A mirror does not divide two regions.", "bdUnused.tren": "A border is not adjacent to a block.", @@ -358,6 +359,7 @@ "ceUnused.kinkonkan": "A mirror is unused.", "ciNotOnCnr.loute": "A circle is not at the corner of an area.", "circleNotPromontory.kurodoko": "A circle is not a dead end.", + "circleShade.kissing": "A crossed cell is shaded.", "circleShade": "A white circle is shaded.", "circleUnshade.snake": "A circle is not shaded.", "circleUnshade": "A black circle is not shaded.", @@ -386,6 +388,7 @@ "csGt1.takoyaki": "A line has more than one circle in the middle.", "csGt2": "The size of a mass of shaded cells is over two.", "csGt4": "The size of a mass of shaded cells is over four.", + "csGtLimit.kissing": "Two blocks are adjacent in an invalid location.", "csLoop.parquet": "There is a loop of shaded cells.", "csLt1.nothree": "A dot doesn't overlap a shaded cell.", "csLt1.takoyaki": "A line has no circle in the middle.", diff --git a/src/variety/statuepark.js b/src/variety/statuepark.js index 38905100e..debe42f3f 100644 --- a/src/variety/statuepark.js +++ b/src/variety/statuepark.js @@ -2,13 +2,21 @@ // statuepark.js // -(function(pidlist, classbase) { +(function(classbase) { + var pidlist = [ + "statuepark", + "statuepark-aux", + "pentopia", + "battleship", + "pentatouch", + "kissing" + ]; if (typeof module === "object" && module.exports) { module.exports = [pidlist, classbase]; } else { pzpr.classmgr.makeCustom(pidlist, classbase); } -})(["statuepark", "statuepark-aux", "pentopia", "battleship", "pentatouch"], { +})({ MouseEvent: { use: true, inputModes: { @@ -24,13 +32,19 @@ if (this.notInputted() && this.mousestart) { this.inputqcmp(); } - } else if (this.puzzle.editmode && this.mousestart) { - if (this.pid === "pentatouch") { + } else if (this.puzzle.editmode) { + if (this.pid === "kissing") { + if (this.mousestart || this.mousemove) { + this.inputborder(); + } else if (this.mouseend && this.notInputted()) { + this.inputempty(); + } + } else if (this.pid === "pentatouch" && this.mousestart) { this.inputcrossMark(); - } else { + } else if (this.mousestart) { this.inputqnum(); } - if (this.notInputted()) { + if (this.mousestart && this.getbank()) { if (this.btn === "left") { this.inputpiece(); } else { @@ -215,6 +229,12 @@ play: ["shade", "unshade", "clear", "completion"] } }, + "MouseEvent@kissing": { + inputModes: { + edit: ["completion", "border", "empty"], + play: ["shade", "unshade", "clear", "completion"] + } + }, KeyEvent: { enablemake: true @@ -397,6 +417,9 @@ "Board@pentatouch": { hascross: 1 }, + "Board@kissing": { + hasborder: 1 + }, Bank: { enabled: true, @@ -482,6 +505,28 @@ } }, + "Bank@kissing": { + exceedPieceSize: -1, + rebuildExtraData: function() { + var bd = this.puzzle.board; + var minsize = bd.rows * bd.cols + 1; + var maxsize = 0; + + for (var i = 0; i < this.pieces.length; i++) { + var piece = this.pieces[i]; + var size = 0; + for (var j = 0; j < piece.str.length; j++) { + if (piece.str[j] === "1") { + size++; + } + } + minsize = Math.min(size, minsize); + maxsize = Math.max(size, maxsize); + } + this.exceedPieceSize = minsize + maxsize; + } + }, + "Bank@battleship": { defaultPreset: function() { return this.presets[1].constant; @@ -714,6 +759,20 @@ } } }, + "Cell@kissing": { + allowShade: function() { + return this.isValid(); + }, + allowUnshade: function() { + return this.isValid(); + }, + posthook: { + qans: function() { + this.drawaround(); + } + } + }, + "Cell@battleship": { numberAsObject: true, minnum: 0, @@ -909,6 +968,12 @@ "AreaUnshadeGraph@statuepark": { enabled: true }, + "AreaShadeGraph@kissing": { + relation: { "cell.qans": "node", "border.ques": "separator" }, + isedgevalidbylinkobj: function(border) { + return !border.isBorder(); + } + }, "Graphic@statuepark": { enablebcolor: true, @@ -926,8 +991,9 @@ shadecolor: "rgb(80, 80, 80)", bgcellcolor_func: "qsub1", - crosssize: 0.15, - + crosssize: 0.15 + }, + "Graphic@pentatouch,kissing#1": { drawTarget: function() { var show = this.puzzle.editmode && this.puzzle.cursor.bankpiece !== null; this.drawCursor(true, show); @@ -947,6 +1013,10 @@ } else if (this.pid === "pentopia") { this.drawArrowCombinations(); this.drawHatenas(); + } else if (this.pid === "kissing") { + this.drawBorders(); + this.drawXCells(); + this.drawDotCells(); } this.drawChassis(); @@ -1063,6 +1133,144 @@ } }, + "Graphic@kissing": { + shadecolor: "#777", + trialcolor: "rgb(255, 160, 0)", + + drawXCells: function() { + var g = this.vinc("cell_x", "auto", true); + + var rsize = this.cw * 0.2; + var clist = this.range.cells; + for (var i = 0; i < clist.length; i++) { + var cell = clist[i]; + + g.vid = "c_x_" + cell.id; + var px = cell.bx * this.bw, + py = cell.by * this.bh; + if (cell.isEmpty()) { + g.strokeStyle = this.quescolor; + g.lineWidth = 2; + g.strokeCross(px, py, rsize); + } else { + g.vhide(); + } + } + }, + drawBorders: function() { + this.vinc("border", "auto", true); + var g = this.context; + + var blist = this.range.borders; + for (var i = 0; i < blist.length; i++) { + var border = blist[i], + color = this.getBorderColor(border); + + g.vid = "b_bd_" + border.id; + if (!!color) { + var px = border.bx * this.bw, + py = border.by * this.bh; + var lx = this.bw * 0.2, + ly = this.bh * 0.2; + g.fillStyle = color; + if (border.isVert()) { + this.fillCapsule(g, px, py, lx, this.bh - ly); + } else { + this.fillCapsule(g, px, py, this.bw - lx, ly); + } + } else { + g.vhide(); + } + } + }, + fillCapsule: function(g, x, y, w, h) { + if (w > h) { + var rads = (90 * Math.PI) / 180, + rade = (270 * Math.PI) / 180; + + g.beginPath(); + g.moveTo(x + w - h, y - h); + g.arc(x + w - h, y, h, rads, rade, true); + g.lineTo(x + h - w, y + h); + g.arc(x + h - w, y, h, rade, rads, true); + g.lineTo(x + w - h, y - h); + g.fill(); + } else { + var rads = (0 * Math.PI) / 180, + rade = (180 * Math.PI) / 180; + + g.beginPath(); + g.moveTo(x - w, y + w - h); + g.arc(x, y + w - h, w, rads, rade, true); + g.lineTo(x + w, y + h - w); + g.arc(x, y + h - w, w, rade, rads, true); + g.lineTo(x - w, y + w - h); + g.fill(); + } + }, + drawShadedCells: function() { + this.vinc("cell_shaded", "crispEdges"); + var g = this.context; + var clist = this.range.cells; + + var radius = 0.75; + + for (var i = 0; i < clist.length; i++) { + var cell = clist[i], + color = this.getShadedCellColor(cell); + var px = cell.bx, + py = cell.by; + + var sizes = {}; + for (var dir in cell.adjacent) { + sizes[dir] = + !cell.adjborder[dir].isBorder() && cell.adjacent[dir].isShade() + ? 1 + : radius; + } + + g.vid = "c_fulls_h_" + cell.id; + if (!!color) { + g.fillStyle = color; + + var gap = sizes.left === 1 ? 0 : 1; + var left = px - sizes.left, + right = px + sizes.right, + top = py - radius, + bottom = py + radius; + g.fillRect( + left * this.bw + gap, + top * this.bh, + (right - left) * this.bw - gap, + (bottom - top) * this.bh + ); + } else { + g.vhide(); + } + + g.vid = "c_fulls_v_" + cell.id; + if (!!color) { + g.fillStyle = color; + var px = cell.bx, + py = cell.by; + + var left = px - radius, + right = px + radius, + top = py - sizes.top, + bottom = py + sizes.bottom; + g.fillRect( + left * this.bw + 1, + top * this.bh, + (right - left) * this.bw - 1, + (bottom - top) * this.bh + ); + } else { + g.vhide(); + } + } + } + }, + "Graphic@statuepark-aux": { paint: function() { this.drawShadedCells(); @@ -1389,6 +1597,23 @@ } }, + "Encode@kissing": { + decodePzpr: function(type) { + if (this.outbstr[0] !== "/") { + this.decodeBorder(); + } + if (this.outbstr[0] !== "/") { + this.decodeEmpty(); + } + this.decodePieceBank(); + }, + encodePzpr: function(type) { + this.encodeBorder(); + this.encodeEmpty(); + this.encodePieceBank(); + } + }, + FileIO: { decodeData: function() { this.decodePieceBank(); @@ -1461,6 +1686,38 @@ } }, + "FileIO@kissing": { + decodeData: function() { + this.decodePieceBank(); + this.decodeBorderQues(); + this.decodeCell(function(cell, ca) { + if (ca === "x") { + cell.ques = 7; + } else if (ca === "#") { + cell.qans = 1; + } else if (ca === "+") { + cell.qsub = 1; + } + }); + this.decodePieceBankQcmp(); + }, + encodeData: function() { + this.encodePieceBank(); + this.encodeBorderQues(); + this.encodeCell(function(cell) { + if (cell.ques === 7) { + return "x "; + } else if (cell.qans) { + return "# "; + } else if (cell.qsub) { + return "+ "; + } + return ". "; + }); + this.encodePieceBankQcmp(); + } + }, + "AnsCheck@statuepark": { checklist: [ "checkUnshadeOnCircle", @@ -1483,6 +1740,67 @@ }, "circleShade"); } }, + "AnsCheck@kissing": { + checklist: [ + "checkUnshadeOnCircle", + "checkPieceSize", + "checkSeparators", + "checkBankPiecesAvailable", + "checkBankPiecesInvalid", + "checkBankPiecesUsed" + ], + + checkPieceSize: function() { + // A separate check for pieces that are far too large, + // which probably indicates two pieces being merged together. + var exceed = this.board.bank.exceedPieceSize; + this.checkAllArea( + this.board.sblkmgr, + function(w, h, a, n) { + return a < exceed; + }, + "csGtLimit" + ); + }, + + checkSeparators: function() { + for (var id = 0; id < this.board.border.length; id++) { + var border = this.board.border[id]; + if (!border.isBorder()) { + continue; + } + var cell1 = border.sidecell[0], + cell2 = border.sidecell[1]; + if (cell1.isnull || cell2.isnull) { + continue; + } + if (cell1.isShade() && cell2.isShade() && cell1.sblk !== cell2.sblk) { + continue; + } + + this.failcode.add("bdUnused"); + if (this.checkOnly) { + break; + } + if (cell1.sblk) { + cell1.sblk.clist.seterr(1); + } else { + cell1.seterr(1); + } + if (cell2.sblk) { + cell2.sblk.clist.seterr(1); + } else { + cell2.seterr(1); + } + } + }, + + checkUnshadeOnCircle: function() { + this.checkAllCell(function(cell) { + return cell.isShade() && !cell.isValid(); + }, "circleShade"); + } + }, "AnsCheck@pentopia,battleship,pentatouch#1": { checkShadeDiagonal: function() { diff --git a/test/script/kissing.js b/test/script/kissing.js new file mode 100644 index 000000000..1d5a95a0b --- /dev/null +++ b/test/script/kissing.js @@ -0,0 +1,61 @@ +/* kissing.js */ + +ui.debug.addDebugData("kissing", { + url: "7/5/0840408008000g00000//t", + failcheck: [ + [ + "bankLt", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . . x . /# # # . . . . /# # # . . . # /. . # # # # # /. . . # . # . /0 0 0 0 0 /" + ], + [ + "bankGt", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . # x . /# # # . # . . /# # # . # . # /. . # . # # # /# # # . . # . /0 0 0 0 0 /" + ], + [ + "bankInvalid", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . . x . /# # # . . . . /# # # . # . # /. . # # # # # /. . . # . # . /0 0 0 0 0 /" + ], + [ + "bdUnused", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . . x . /# # . . . . . /# # # # . . . /. . # . # # . /# # # . # # . /0 0 0 0 0 /" + ], + [ + "bdUnused", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# . . # # x . /# # # . # # . /# # # . . . # /# . # . . . # /. # # # . # # /0 0 0 0 0 /" + ], + [ + "csGtLimit", + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . # x . /# # # . # . . /# # # . # . # /. # # . # # # /. . # # . . # /0 0 0 0 0 /" + ], + [ + null, + "pzprv3/kissing/5/7/t/0 0 0 0 0 0 /1 0 0 0 0 0 /1 0 0 0 0 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 /0 0 1 0 0 0 0 /0 0 0 0 0 0 0 /# # . . # x . /# # # . # . . /# # # . # . # /. . # . # # # /. # # # . # . /0 0 0 0 0 /" + ] + ], + inputs: [ + { + input: [ + "newboard,3,3", + "editmode", + "mouse,left,1,3", + "mouse,left,4,0,4,4" + ], + result: + "pzprv3/kissing/3/3/p/0 1 /0 1 /0 0 /0 0 0 /0 0 0 /. . . /x . . /. . . /0 0 0 0 0 0 0 0 0 0 0 0 /" + }, + { + input: ["playmode", "mouse,left,1,3,5,3,5,1", "mouse,right,1,1,1,5"], + result: + "pzprv3/kissing/3/3/p/0 1 /0 1 /0 0 /0 0 0 /0 0 0 /+ . # /x # # /+ . . /0 0 0 0 0 0 0 0 0 0 0 0 /" + }, + { + result: function(puzzle, assert) { + var b1 = puzzle.board.getc(3, 3).sblk; + var b2 = puzzle.board.getc(5, 3).sblk; + var b3 = puzzle.board.getc(5, 1).sblk; + assert.equal(b2, b3); + assert.notEqual(b1, b2); + } + } + ] +});