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 - [x] ends game upon 2 consecutive passes
- [ ] calculates estimated score at game end - [ ] calculates estimated score at game end
- [ ] compares board groups to most common dead shapes - [ ] 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 - [ ] displays game record as string
stretch goals stretch goals

View file

@ -1,5 +1,9 @@
@import url('https://fonts.googleapis.com/css?family=La+Belle+Aurore|Raleway:300|Raleway:600'); @import url('https://fonts.googleapis.com/css?family=La+Belle+Aurore|Raleway:300|Raleway:600');
html {
font-size: 12px;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
@ -39,7 +43,7 @@ body {
"player" "player"
"record" "record"
"submit"; "submit";
font: 14px 'La Belle Aurore', cursive; font-family: 'La Belle Aurore', cursive;
min-height: 0; min-height: 0;
} }
@ -83,7 +87,6 @@ h4 {
#player-meta input[type="button"] { #player-meta input[type="button"] {
margin: .25em; margin: .25em;
height: 100%;
} }
div[data-player-meta] { div[data-player-meta] {
@ -176,6 +179,14 @@ content {
#game-hud p { #game-hud p {
font-size: 130%; 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 { .player-pos#black-pos {
@ -222,7 +233,7 @@ content {
} }
.bowl[data-turn] { .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 { .caps-space {
@ -401,20 +412,27 @@ td .dot .seki {
} }
@media only screen and (min-width: 560px) { @media only screen and (min-width: 560px) {
#player-meta { #player-meta {
flex-direction: row; flex-direction: row;
} }
div[data-player-meta] { div[data-player-meta] {
width: 50%; width: 50%;
} }
#menu { #menu {
grid-template-columns: 60vw; grid-template-columns: 60vw;
grid-template-rows: auto auto 60vw auto; grid-template-rows: auto auto 60vw auto;
font-size: 14px;
} }
} }
@media only screen and (min-width: 500px) { @media only screen and (min-width: 500px) {
html {
font-size: 14px;
}
.player-pos { .player-pos {
height: 14vh; 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 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> 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. 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> 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.
For now, Browser Go only supports games on a small board. 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> 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> <p id="game-record"></p>
</div> </div>

View file

@ -12,7 +12,6 @@ const DOTS_DATA = {
'0': 'none', '0': 'none',
'1': 'black', '1': 'black',
'd': 'dame', 'd': 'dame',
's': 'seki'
} }
const RANKS = [ const RANKS = [
@ -49,13 +48,15 @@ const HANDI_REC = {
const gameState = { const gameState = {
winner: null, winner: null,
turn: 1, // turn logic depends on handicap stones 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 komi: null, // komi depends on handicap stones + player rank
handicap: null, handicap: null,
boardSize: 9, boardSize: 9,
playerState: { playerState: {
bCaptures: null, bCaptures: null,
wCaptures: null wCaptures: null,
bScore: null,
wScore: null
}, },
gameMeta: { // declared at game start and not editable after gameMeta: { // declared at game start and not editable after
date: null // contains metadata date: null // contains metadata
@ -188,8 +189,12 @@ class Point {
} }
} }
cycleTerritory = () => { cycleTerritory = () => {
this.groupMembers.forEach(pt => { console.log(this);
switch (pt.territory) { if (this.stone) {
this.groupMembers.forEach(pt => pt.territory = pt.territory * -1);
} else {
this.groupMembers.forEach(pt => {
switch (pt.territory) {
case 1: case 1:
pt.territory = -1; pt.territory = -1;
break; break;
@ -199,8 +204,9 @@ class Point {
case 'd': case 'd':
pt.territory = 1; pt.territory = 1;
break; break;
} }
}) });
}
} }
} }
// could use this Array to iterate through and create // could use this Array to iterate through and create
@ -235,15 +241,8 @@ const gameHudEl = document.querySelector('#game-hud p');
/*----- event listeners -----*/ /*----- 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); document.getElementById('board').addEventListener('mousemove', hoverPreview);
// click on board to play move
document.getElementById('board').addEventListener('click', clickBoard); 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('white-bowl').addEventListener('click',clickPass);
document.getElementById('black-bowl').addEventListener('click',clickPass); document.getElementById('black-bowl').addEventListener('click',clickPass);
document.getElementById('kifu').addEventListener('click', clickMenu); 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('click', clickUpdatePlayerMeta);
document.getElementById('player-meta').addEventListener('change', clickUpdatePlayerMeta); document.getElementById('player-meta').addEventListener('change', clickUpdatePlayerMeta);
document.querySelector('input[name="komi-suggest"]').addEventListener('click', clickKomiSuggestion); document.querySelector('input[name="komi-suggest"]').addEventListener('click', clickKomiSuggestion);
gameHudEl.addEventListener('click', clickSubmitScore); gameHudEl.addEventListener('click', clickGameHud);
/*----- functions -----*/ /*----- functions -----*/
@ -303,12 +302,12 @@ function clickKomiSuggestion() {
renderMenu(); renderMenu();
} }
function clickSubmitScore() { function clickGameHud() {
if (gameState.pass > 1 && !gameState.winner) calculateWinner(); if (gameState.pass > 1 && !gameState.winner) calculateWinner();
if (gameState.pass < 0) confirmResign();
} }
function clickSubmitStart() { function clickSubmitStart() {
gameState.playerMeta.b.name = blackNameInputEl.value; gameState.playerMeta.b.name = blackNameInputEl.value;
gameState.playerMeta.w.name = whiteNameInputEl.value; gameState.playerMeta.w.name = whiteNameInputEl.value;
initGame(); initGame();
@ -344,6 +343,14 @@ function clickMenu() {
clickUpdatePlayerMeta(); clickUpdatePlayerMeta();
} }
function startMenu() {
modalEl.style.visibility = 'visible';
changeUpdateKomi();
changeUpdateHandicap();
clickUpdatePlayerMeta();
}
function clickCloseMenu(evt) { function clickCloseMenu(evt) {
evt.stopPropagation(); evt.stopPropagation();
if (evt.target.className === "modal") modalEl.style.visibility = 'hidden'; if (evt.target.className === "modal") modalEl.style.visibility = 'hidden';
@ -354,14 +361,19 @@ function clickResign(evt) {
} }
function playerResign() { function playerResign() {
// display confirmation message // display confirmation message\
if (!confirm('Do you want to resign?')) return; 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.gameRecord.push(`${STONES_DATA[gameState.turn]}: resign`);
gameState.winner = STONES_DATA[gameState.turn * -1]; gameState.winner = STONES_DATA[gameState.turn * -1];
endGame(); endGame();
} }
function hoverPreview(evt) { function hoverPreview(evt) {
evt.stopPropagation(); evt.stopPropagation();
if (gameState.pass > 1 || gameState.winner) return; 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)}` 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() { function init() {
gameState.gameMeta.date = getDate();
gameState.komi = 5.5; // get komi from player input
// startMenu();
gameState.winner = null; gameState.winner = null;
gameState.pass = null; gameState.pass = null;
// gameState.komi = ; // get komi from player input
// gameState.handicap = ; // get handicap from player input // gameState.handicap = ; // get handicap from player input
gameState.turn = gameState.handicap ? -1 : 1; gameState.turn = gameState.handicap ? -1 : 1;
gameState.boardSize = 9; gameState.boardSize = 9;
gameState.playerState.bCaptures = 0; gameState.playerState.bCaptures = 0;
gameState.playerState.wCaptures = 0; gameState.playerState.wCaptures = 0;
gameState.gameMeta.date = getDate();
// get any future meta from player input // get any future meta from player input
// gameState.playerMeta.b // get from player input // gameState.playerMeta.b // get from player input
// gameState.playerMeta.w // get from player input // gameState.playerMeta.w // get from player input
@ -511,12 +524,19 @@ function render() {
function renderMessage() { function renderMessage() {
if (gameState.winner && gameState.pass < 2) { 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`; gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by resignation`;
} }
else if (gameState.winner && gameState.pass > 1) { else if (gameState.winner && gameState.pass > 1) {
gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by `; gameHudEl.style.visibility = 'visible';
} else { 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' gameHudEl.textContent = 'click to finalize game'
} else {
gameHudEl.style.visibility = 'hidden';
} }
} }
@ -560,31 +580,69 @@ function renderPreview(hoverPoint) {
} }
function calculateWinner() { 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() { function endGameSetTerritory() {
boardState.forEach(pt => { // boardState.forEach(pt => {
pt.territory = pt.stone ? pt.stone : 'd' // pt.territory = pt.stone ? pt.stone : 'd'
}); // });
let emptyPoints = boardState.filter(pt => !pt.stone); let emptyPoints = boardState.filter(pt => !pt.stone);
emptyPoints.forEach(pt => pt.joinGroup()); emptyPoints.forEach(pt => pt.joinGroup());
emptyPointSetTerritory(emptyPoints); emptyPointSetTerritory(emptyPoints);
boardState.filter(pt => { // boardState.filter(pt => {
return pt.groupMembers.length < 6 && pt.stone // return pt.groupMembers.length < 6 && pt.stone
}).forEach(pt => pt.territory = pt.stone * -1); // }).forEach(pt => pt.territory = pt.stone * -1);
} }
function emptyPointSetTerritory(emptyPoints) { function emptyPointSetTerritory(emptyPoints) {
// let dame = emptyPoints.filter(pt => pt.checkNeighbors().some(nbr => nbr.territory === 1) emptyPoints.filter(pt => !pt.territory && pt.checkNeighbors().filter(nbr => nbr.stone !== 0))
// && pt.checkNeighbors().some(nbr => nbr.territory === -1)); .forEach(pt => {
// dame.forEach(pt => pt.territory = 'd'); console.log(pt);
// emptyPoints.filter(pt => pt.territory = 0) 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() { function endGame() {
if (!gameState.winner) if (!gameState.winner) endGameSetTerritory()
endGameSetTerritory()
// join all remaining groups // join all remaining groups
// check remaining groups life // check remaining groups life
@ -593,15 +651,15 @@ function endGame() {
// compare spaces to rotations of deadShapes[...] // compare spaces to rotations of deadShapes[...]
// 'd' if empty spaces // 'd' if empty spaces
render(); // return dead group suggestion
// return dead group suggestion // users can flip status of any dead group overlay( 1, -1 )
// users can flip status of any dead group overlay( 1, -1 ) // confirm state
// confirm state
// calculate score = points in overlay for each player + captures // calculate score = points in overlay for each player + captures
// render final board state with dead groups removed // render final board state with dead groups removed
// log game record // log game record
// stringify according to .sgf format // stringify according to .sgf format
// log as text // log as text
render();
} }
// game-end // game-end