debug territory count at endgame

This commit is contained in:
Sorrel Bri 2019-08-07 12:45:58 -07:00
parent 11c17f6d0e
commit 201407b8ea
4 changed files with 125 additions and 48 deletions

View file

@ -15,7 +15,8 @@ a working game of go for a 9x9 board that
- [x] ends game upon 2 consecutive passes
- [ ] calculates estimated score at game end
- [ ] compares board groups to most common dead shapes
- [ ] allows users to override dead group estimates and submit finalized score to game record
- [x] allows users to override dead group estimates
- [ ] allows users to submit finalized score to game record
- [ ] displays game record as string
stretch goals

View file

@ -1,5 +1,9 @@
@import url('https://fonts.googleapis.com/css?family=La+Belle+Aurore|Raleway:300|Raleway:600');
html {
font-size: 12px;
}
* {
box-sizing: border-box;
margin: 0;
@ -39,7 +43,7 @@ body {
"player"
"record"
"submit";
font: 14px 'La Belle Aurore', cursive;
font-family: 'La Belle Aurore', cursive;
min-height: 0;
}
@ -83,7 +87,6 @@ h4 {
#player-meta input[type="button"] {
margin: .25em;
height: 100%;
}
div[data-player-meta] {
@ -176,6 +179,14 @@ content {
#game-hud p {
font-size: 130%;
width: 50%;
order: 0;
width: 10vh;
background-color: rgba(0,0,0,0.3);
padding: 1vh;
color: #fff;
cursor: pointer;
visibility: hidden;
}
.player-pos#black-pos {
@ -222,7 +233,7 @@ content {
}
.bowl[data-turn] {
box-shadow: 0 0 3vh 3vh rgb(31, 255, 2);
box-shadow: 0 0 3vh 3vh rgb(255, 175, 2);
}
.caps-space {
@ -401,20 +412,27 @@ td .dot .seki {
}
@media only screen and (min-width: 560px) {
#player-meta {
flex-direction: row;
}
div[data-player-meta] {
width: 50%;
}
#menu {
grid-template-columns: 60vw;
grid-template-rows: auto auto 60vw auto;
font-size: 14px;
}
}
@media only screen and (min-width: 500px) {
html {
font-size: 14px;
}
.player-pos {
height: 14vh;
}

View file

@ -50,8 +50,8 @@
To begin a game enter player names and ranks above, then click "Suggest Komi" Browser Go will calculate the appropriate komi based on AGA guidelines.
To override Browser Go's suggestion, use the sliders above. Be sure to check the 'rank certainty' box if you're club-rated.<br><br>
When the game begins, click on a legal point on the board to make a move. The active player's bowl will be highlighted. To pass, click on your bowl.
This will only be possible on your turn. To resign click on your capture tray. To view the game record or adjust player settings, click the kifu on black's side.<br><br>
For now, Browser Go only supports games on a small board.
This will only be possible on your turn. To resign click on your capture tray. After the game ends, groups and territory will display Browser Go's estimate for final state.
Simply click on a group to change a group between live and dead, or a point between territory and dame.
I've got great things planned for the future, though! Lookout for new releases on <a href="https://github.com/sorrelbri/browser-go">the GitHub page!</a></p>
<p id="game-record"></p>
</div>

View file

@ -12,7 +12,6 @@ const DOTS_DATA = {
'0': 'none',
'1': 'black',
'd': 'dame',
's': 'seki'
}
const RANKS = [
@ -49,13 +48,15 @@ const HANDI_REC = {
const gameState = {
winner: null,
turn: 1, // turn logic depends on handicap stones
pass: null,
pass: null, // -1 represents state in which resignation has been submitted, not confirmed
komi: null, // komi depends on handicap stones + player rank
handicap: null,
boardSize: 9,
playerState: {
bCaptures: null,
wCaptures: null
wCaptures: null,
bScore: null,
wScore: null
},
gameMeta: { // declared at game start and not editable after
date: null // contains metadata
@ -188,6 +189,10 @@ class Point {
}
}
cycleTerritory = () => {
console.log(this);
if (this.stone) {
this.groupMembers.forEach(pt => pt.territory = pt.territory * -1);
} else {
this.groupMembers.forEach(pt => {
switch (pt.territory) {
case 1:
@ -200,7 +205,8 @@ class Point {
pt.territory = 1;
break;
}
})
});
}
}
}
// could use this Array to iterate through and create
@ -235,15 +241,8 @@ const gameHudEl = document.querySelector('#game-hud p');
/*----- event listeners -----*/
// input listeners for player names, ranks, rank certainty (editable during game)
//input lister for handicap + komi (only editable pre-game)
// ::hover-over on board to preview move (with legal move logic)
document.getElementById('board').addEventListener('mousemove', hoverPreview);
// click on board to play move
document.getElementById('board').addEventListener('click', clickBoard);
// ::hover-over on either bowl for pass, one-level undo options (CSS implementation)
// click on menu items
// click on kifu to display game menu
document.getElementById('white-bowl').addEventListener('click',clickPass);
document.getElementById('black-bowl').addEventListener('click',clickPass);
document.getElementById('kifu').addEventListener('click', clickMenu);
@ -255,7 +254,7 @@ handiSliderEl.addEventListener('change', changeUpdateHandicap);
document.getElementById('player-meta').addEventListener('click', clickUpdatePlayerMeta);
document.getElementById('player-meta').addEventListener('change', clickUpdatePlayerMeta);
document.querySelector('input[name="komi-suggest"]').addEventListener('click', clickKomiSuggestion);
gameHudEl.addEventListener('click', clickSubmitScore);
gameHudEl.addEventListener('click', clickGameHud);
/*----- functions -----*/
@ -303,12 +302,12 @@ function clickKomiSuggestion() {
renderMenu();
}
function clickSubmitScore() {
function clickGameHud() {
if (gameState.pass > 1 && !gameState.winner) calculateWinner();
if (gameState.pass < 0) confirmResign();
}
function clickSubmitStart() {
gameState.playerMeta.b.name = blackNameInputEl.value;
gameState.playerMeta.w.name = whiteNameInputEl.value;
initGame();
@ -344,6 +343,14 @@ function clickMenu() {
clickUpdatePlayerMeta();
}
function startMenu() {
modalEl.style.visibility = 'visible';
changeUpdateKomi();
changeUpdateHandicap();
clickUpdatePlayerMeta();
}
function clickCloseMenu(evt) {
evt.stopPropagation();
if (evt.target.className === "modal") modalEl.style.visibility = 'hidden';
@ -354,14 +361,19 @@ function clickResign(evt) {
}
function playerResign() {
// display confirmation message
if (!confirm('Do you want to resign?')) return;
// display confirmation message\
gameState.pass = -1;
gameHudEl.style.visibility = "visible";
gameHudEl.textContent = "Do you want to resign?";
}
function confirmResign() {
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: resign`);
gameState.winner = STONES_DATA[gameState.turn * -1];
endGame();
}
function hoverPreview(evt) {
evt.stopPropagation();
if (gameState.pass > 1 || gameState.winner) return;
@ -477,15 +489,16 @@ function getDate() {
return `${d.getFullYear()}-${String(d.getMonth()+1).charAt(-1)||0}${String(d.getMonth()+1).charAt(-0)}-${String(d.getDate()).charAt(-1)||0}${String(d.getDate()+1).charAt(-0)}`
}
function init() {
gameState.gameMeta.date = getDate();
gameState.komi = 5.5; // get komi from player input
// startMenu();
gameState.winner = null;
gameState.pass = null;
// gameState.komi = ; // get komi from player input
// gameState.handicap = ; // get handicap from player input
gameState.turn = gameState.handicap ? -1 : 1;
gameState.boardSize = 9;
gameState.playerState.bCaptures = 0;
gameState.playerState.wCaptures = 0;
gameState.gameMeta.date = getDate();
// get any future meta from player input
// gameState.playerMeta.b // get from player input
// gameState.playerMeta.w // get from player input
@ -511,12 +524,19 @@ function render() {
function renderMessage() {
if (gameState.winner && gameState.pass < 2) {
gameHudEl.style.visibility = 'visible';
gameHudEl.style.cursor = 'default';
gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by resignation`;
}
else if (gameState.winner && gameState.pass > 1) {
gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by `;
} else {
gameHudEl.style.visibility = 'visible';
gameHudEl.style.cursor = 'default';
gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by ${Math.abs(gameState.playerState.wScore - gameState.playerState.bScore)}`;
} else if (gameState.pass > 1) {
gameHudEl.style.visibility = 'visible';
gameHudEl.textContent = 'click to finalize game'
} else {
gameHudEl.style.visibility = 'hidden';
}
}
@ -560,31 +580,69 @@ function renderPreview(hoverPoint) {
}
function calculateWinner() {
// debugger;
let whiteTerritory = boardState.reduce((acc, pt) => {
if (pt.territory === -1 && pt.stone !== -1) {
return acc = acc + (pt.stone === 0 ? 1 : 2);
}
return acc;
}, 0);
console.log(whiteTerritory);
let blackTerritory = boardState.reduce((acc, pt) => {
if (pt.territory === 1 && pt.stone !== 1) {
return acc + (pt.stone === 0 ? 1 : 2);
}
return acc;
}, 0);
console.log(blackTerritory);
gameState.playerState.wScore =
gameState.playerState.wCaptures
+ (gameState.komi < 0 ? gameState.komi * -1 : 0)
+ whiteTerritory;
gameState.playerState.bScore =
gameState.playerState.bCaptures
+ (gameState.komi > 0 ? gameState.komi : 0)
+ blackTerritory;
gameState.winner = gameState.playerState.wScore > gameState.playerState.bScore ? -1 : 1;
gameState.gameRecord.push(`${STONES_DATA[gameState.winner]}: +${Math.abs(gameState.playerState.wScore - gameState.playerState.bScore)}`)
render();
}
function endGameSetTerritory() {
boardState.forEach(pt => {
pt.territory = pt.stone ? pt.stone : 'd'
});
// boardState.forEach(pt => {
// pt.territory = pt.stone ? pt.stone : 'd'
// });
let emptyPoints = boardState.filter(pt => !pt.stone);
emptyPoints.forEach(pt => pt.joinGroup());
emptyPointSetTerritory(emptyPoints);
boardState.filter(pt => {
return pt.groupMembers.length < 6 && pt.stone
}).forEach(pt => pt.territory = pt.stone * -1);
// boardState.filter(pt => {
// return pt.groupMembers.length < 6 && pt.stone
// }).forEach(pt => pt.territory = pt.stone * -1);
}
function emptyPointSetTerritory(emptyPoints) {
// let dame = emptyPoints.filter(pt => pt.checkNeighbors().some(nbr => nbr.territory === 1)
// && pt.checkNeighbors().some(nbr => nbr.territory === -1));
// dame.forEach(pt => pt.territory = 'd');
// emptyPoints.filter(pt => pt.territory = 0)
emptyPoints.filter(pt => !pt.territory && pt.checkNeighbors().filter(nbr => nbr.stone !== 0))
.forEach(pt => {
console.log(pt);
let b = pt.groupMembers.reduce((acc, rdcPt) => {
let bNbr = rdcPt.checkNeighbors().filter(nbr => nbr.stone === 1).length;
return acc + bNbr;
}, 0);
let w = pt.groupMembers.reduce((acc, rdcPt) => {
debugger;
let wNbr = rdcPt.checkNeighbors().filter(nbr => nbr.stone === -1).length;
return acc + wNbr;
}, 0);
pt.groupMembers.forEach(grp => {
if (Math.abs(b - w) < 4) grp.territory = 'd'
else grp.territory = b > w ? 1 : -1;
})
});
}
function endGame() {
if (!gameState.winner)
endGameSetTerritory()
if (!gameState.winner) endGameSetTerritory()
// join all remaining groups
// check remaining groups life
@ -593,7 +651,6 @@ function endGame() {
// compare spaces to rotations of deadShapes[...]
// 'd' if empty spaces
render();
// return dead group suggestion
// users can flip status of any dead group overlay( 1, -1 )
// confirm state
@ -602,6 +659,7 @@ function endGame() {
// log game record
// stringify according to .sgf format
// log as text
render();
}
// game-end