From: Benjamin Auder Date: Tue, 31 May 2022 17:09:18 +0000 (+0200) Subject: First draft of Hex game X-Git-Url: https://git.auder.net/variants/img/cross.svg?a=commitdiff_plain;h=d621e620e7b568df94c53611f6c71ab318f4ffe3;p=xogo.git First draft of Hex game --- diff --git a/app.js b/app.js index 6036d49..17fe770 100644 --- a/app.js +++ b/app.js @@ -130,7 +130,9 @@ function toggleStyle(event, obj) { let options; function prepareOptions() { options = {}; - let optHtml = V.Options.select.map(select => { return ` + let optHtml = ""; + if (V.Options.select) { + optHtml += V.Options.select.map(select => { return `
@@ -147,9 +149,10 @@ function prepareOptions() {
`; - }).join(""); - optHtml += V.Options.check.map(check => { - return ` + }).join(""); + } + if (V.Options.check) { + optHtml += V.Options.check.map(check => { return `
`; - }).join(""); - if (V.Options.styles.length >= 1) { + }).join(""); + } + if (V.Options.input) { + optHtml += V.Options.input.map(input => { return ` +
+ +
`; + }).join(""); + } + if (V.Options.styles) { optHtml += '
'; let i = 0; const stylesLength = V.Options.styles.length; diff --git a/base_rules.js b/base_rules.js index 75545c2..20ff25e 100644 --- a/base_rules.js +++ b/base_rules.js @@ -290,19 +290,20 @@ export default class ChessRules { return fen; } + static FenEmptySquares(count) { + // if more than 9 consecutive free spaces, break the integer, + // otherwise FEN parsing will fail. + if (count <= 9) + return count; + // Most boards of size < 18: + if (count <= 18) + return "9" + (count - 9); + // Except Gomoku: + return "99" + (count - 18); + } + // Position part of the FEN string getPosition() { - const format = (count) => { - // if more than 9 consecutive free spaces, break the integer, - // otherwise FEN parsing will fail. - if (count <= 9) - return count; - // Most boards of size < 18: - if (count <= 18) - return "9" + (count - 9); - // Except Gomoku: - return "99" + (count - 18); - }; let position = ""; for (let i = 0; i < this.size.y; i++) { let emptyCount = 0; @@ -312,7 +313,7 @@ export default class ChessRules { else { if (emptyCount > 0) { // Add empty squares in-between - position += format(emptyCount); + position += C.FenEmptySquares(emptyCount); emptyCount = 0; } position += this.board2fen(this.board[i][j]); @@ -320,7 +321,7 @@ export default class ChessRules { } if (emptyCount > 0) // "Flush remainder" - position += format(emptyCount); + position += C.FenEmptySquares(emptyCount); if (i < this.size.y - 1) position += "/"; //separate rows } @@ -789,16 +790,20 @@ export default class ChessRules { chessboard.style.top = newY + "px"; const newR = {x: newX, y: newY, width: newWidth, height: newHeight}; const pieceWidth = this.getPieceWidth(newWidth); - for (let i=0; i < this.size.x; i++) { - for (let j=0; j < this.size.y; j++) { - if (this.g_pieces[i][j]) { - // NOTE: could also use CSS transform "scale" - this.g_pieces[i][j].style.width = pieceWidth + "px"; - this.g_pieces[i][j].style.height = pieceWidth + "px"; - const [ip, jp] = this.getPixelPosition(i, j, newR); - // Translate coordinates to use chessboard as reference: - this.g_pieces[i][j].style.transform = - `translate(${ip - newX}px,${jp - newY}px)`; + // NOTE: next "if" for variants which use squares filling + // instead of "physical", moving pieces + if (this.g_pieces) { + for (let i=0; i < this.size.x; i++) { + for (let j=0; j < this.size.y; j++) { + if (this.g_pieces[i][j]) { + // NOTE: could also use CSS transform "scale" + this.g_pieces[i][j].style.width = pieceWidth + "px"; + this.g_pieces[i][j].style.height = pieceWidth + "px"; + const [ip, jp] = this.getPixelPosition(i, j, newR); + // Translate coordinates to use chessboard as reference: + this.g_pieces[i][j].style.transform = + `translate(${ip - newX}px,${jp - newY}px)`; + } } } } diff --git a/common.css b/common.css index 6f8472c..7096ff8 100644 --- a/common.css +++ b/common.css @@ -303,12 +303,14 @@ piece.hidden { height: 100%; } +/* Default squares colors (can be overriden or unused) */ .dark-square { fill: #b58863; } .light-square { fill: #f0d9b5; } + .in-shadow { filter: brightness(50%); } diff --git a/variants/Hex/class.js b/variants/Hex/class.js index f1e3a83..ec14a06 100644 --- a/variants/Hex/class.js +++ b/variants/Hex/class.js @@ -1,5 +1,78 @@ +// https://www.boardspace.net/hex/english/Rules%20-%20HexWiki.htm +export default class HexRules extends ChessRules { + + static get Options() { + return { + input: [ + { + label: "Board size", + type: "number", + defaut: 11, + variable: "bsize" + } + ], + check: [ + { + label: "Swap", + defaut: true, + variable: "swap" + } + ] + }; + } + + get hasReserve() { + return false; + } + + get noAnimate() { + return true; + } + + doClick(coords) { + if ( + this.board[coords.x][coords.y] != "" && + (!this.swap || this.movesCount >= 2) + ) { + return null; + } + let res = new Move({ + start: {x: coords.x, y: coords.y}, + appear: [ + new PiPo({ + x: coords.x, + y: coords.y, + c: this.turn, + p: 'p' + }) + ], + vanish: [] + }); + if (this.board[coords.x][coords.y] != "") { + res.vanish.push( + new PiPo({ + x: coords.x, + y: coords.y, + c: C.GetOppCol(this.turn), + p: 'p' + }) + ); + } + return res; + } + + genRandInitFen() { + // NOTE: size.x == size.y (square boards) + const emptyCount = C.FenEmptySquares(this.size.x.repeat); + return (emptyCount + "/").repeat(this.size.x).slice(0, -1); + } + + getPieceWidth(rwidth) { + return (rwidth / this.size.y); //TODO + } + + // TODO getSvgChessboard() { - const flipped = (this.playerColor == 'b'); let board = ` specify per variant in CSS file - getSquareColorClass(i, j) { - return ((i+j) % 2 == 0 ? "light-square": "dark-square"); + setupPieces() { + // TODO: just scan board and get IDs, and addClass "bg-white" or "bg-black" + } + + // TODO (NOTE: no flip here, always same view) + getPixelPosition(i, j, r) { + if (i < 0 || j < 0) + return [0, 0]; //piece vanishes + let x, y; + const sqSize = r.width / this.size.y; + const flipped = (this.playerColor == 'b'); + const x = (flipped ? this.size.y - 1 - j : j) * sqSize, + y = (flipped ? this.size.x - 1 - i : i) * sqSize; + return [r.x + x, r.y + y]; + } + + initMouseEvents() { + const mousedown = (e) => { + if (e.touches && e.touches.length > 1) + e.preventDefault(); + const cd = this.idToCoords(e.target.id); + if (cd) { + const move = this.doClick(cd); + if (move) + this.playPlusVisual(move); + } + }; + + if ('onmousedown' in window) + document.addEventListener("mousedown", mousedown); + if ('ontouchstart' in window) + document.addEventListener("touchstart", mousedown, {passive: false}); + } + + get size() { + return { + x: this.bsize, + y: this.bsize, + ratio: 1.630118 + }; + } + + pieces() { + return { + 'p': { + "class": "pawn", + } + }; + } + + play(move) { + super.playOnBoard(move); + } + + // TODO: + getCurrentScore(move) { + const oppCol = C.GetOppCol(this.turn); + // Search for connecting path of opp color: TODO + // ... + if (path found) + return (oppCol == "w" ? "1-0" : "0-1"); + return "*"; + } + + playVisual(move) { + move.vanish.forEach(v => { +// TODO: just get ID, and remClass "bg-white" or "bg-black" (in CSS: TODO) + }); + move.appear.forEach(a => { +// TODO: just get ID, and addClass "bg-white" or "bg-black" (in CSS: TODO) +// this.g_pieces[a.x][a.y] = document.createElement("piece"); +// this.g_pieces[a.x][a.y].classList.add(this.pieces()[a.p]["class"]); +// this.g_pieces[a.x][a.y].classList.add(a.c == "w" ? "white" : "black"); +// this.g_pieces[a.x][a.y].style.width = pieceWidth + "px"; +// this.g_pieces[a.x][a.y].style.height = pieceWidth + "px"; + }); } -// TODO: generalize base_rules.js to not assume size.x == width and size.y == height (not true here). +}; diff --git a/variants/Hex/style.css b/variants/Hex/style.css new file mode 100644 index 0000000..91ea8fd --- /dev/null +++ b/variants/Hex/style.css @@ -0,0 +1,3 @@ +.neutral-square { + fill: #eaeded; +}