add user score submission

This commit is contained in:
Sorrel Bri 2019-08-06 23:13:23 -07:00
parent 64282bd55d
commit 11c17f6d0e
4 changed files with 195 additions and 163 deletions

View file

@ -2,35 +2,35 @@
#### Minimum Deliverable Product #### Minimum Deliverable Product
a working game of go for a 9x9 board that a working game of go for a 9x9 board that
* displays well on mobile or desktop - [x] displays well on mobile or desktop
* initiates a game with suggested handicap and komi according to rank input - [x] initiates a game with suggested handicap and komi according to rank input
* * displays how to play in open screen - [x] displays how to play in open screen
* lets the user know whose turn it is - [x] lets the user know whose turn it is
* lets the user know which moves are legal and calculates those accordingly - [x] lets the user know which moves are legal and calculates those accordingly
* * logs ko - [x] logs ko
* * implement a search algorithm to avoid moving into dead space - [x] implement a search algorithm to avoid moving into dead space
* correctly removes captured stones and adds them to capturing player's score - [x] correctly removes captured stones and adds them to capturing player's score
* logs game record - [x] logs game record
* allows players to pass or resign - [x] allows players to pass or resign
* * 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 - [ ] allows users to override dead group estimates and submit finalized score to game record
* displays game record as string - [ ] displays game record as string
stretch goals stretch goals
* uses stone placement GUI for resign and pass - [x] uses stone placement GUI for resign and pass
* maintains a one move game state history for 'undo mismove' - [ ] maintains a one move game state history for 'undo mismove'
* converts string to .sgf format - [ ] converts string to .sgf format
* allows users to edit game info mid game - [x] allows users to edit game info mid game
* add stone placement sounds - [ ] add stone placement sounds
superstretch goals superstretch goals
* allows users to select board size (9x9, 13x13, 19x19) - [x] allows users to select board size (9x9, 13x13, 19x19)
* allows users to load .sgf main lines - [ ] allows users to load .sgf main lines
* allow for responsivity in the form of - [ ] allow for responsivity in the form of
* * 9x9 games simply stretch with screen size - - [ ] 9x9 games simply stretch with screen size
* * larger games allow small displays one click to zoom before running legal move calculations and move placement - - [ ] larger games allow small displays one click to zoom before running legal move calculations and move placement
<!-- describe go with images of game--> <!-- describe go with images of game-->

View file

@ -57,7 +57,11 @@ body {
.menu-heading, content, #instructions, div[data-player-meta] label { .menu-heading, content, #instructions, div[data-player-meta] label {
font-family: 'Raleway', sans-serif; font-family: 'Raleway', sans-serif;
font-weight: 300; }
h1 {
font-size: 140%;
font-weight: 600;
} }
h4 { h4 {

View file

@ -17,11 +17,18 @@
<div> <div>
<form id="menu"> <form id="menu">
<div id="game-meta"> <div id="game-meta">
<h1 class="menu-heading">Browser Go</h1>
<div class="menu-subblock"><span class="menu-heading">Date:</span><span id="date"></span></div> <div class="menu-subblock"><span class="menu-heading">Date:</span><span id="date"></span></div>
<div class="menu-subblock"><span class="menu-heading">Komi:</span><span id="komi"></span></div> <div class="menu-subblock"><span class="menu-heading">Komi:</span><span id="komi"></span></div>
<input type="range" min="-21.5" max="7.5" step="1" value="5.5" name="komi-slider"> <input type="range" min="-21.5" max="7.5" step="1" value="5.5" name="komi-slider">
<div class="menu-subblock"><span class="menu-heading">Handicap:</span><span id="handicap"></span></div> <div class="menu-subblock"><span class="menu-heading">Handicap:</span><span id="handicap"></span></div>
<input type="range" min="0" max="5" step="1" value="0" name="handicap-slider"> <input type="range" min="0" max="5" step="1" value="0" name="handicap-slider">
<div id="board-size-radio" class="menu-subblock">
<p class="menu-heading">Board Size</p>
<input type="radio" name="board-size" value="9" checked>9 x 9<br>
<input type="radio" name="board-size" value="13">13 x 13<br>
<input type="radio" name="board-size" value="19">19 x 19
</div>
</div> </div>
<div id="player-meta"> <div id="player-meta">
<div data-player-meta="black"> <div data-player-meta="black">

View file

@ -15,10 +15,36 @@ const DOTS_DATA = {
's': 'seki' 's': 'seki'
} }
const RANKS = ['30k', '29k', '28k', '27k', '26k', '25k', '24k', '23k', '22k', '21k', '20k', const RANKS = [
'30k', '29k', '28k', '27k', '26k', '25k', '24k', '23k', '22k', '21k', '20k',
'19k', '18k', '17k', '16k', '15k', '14k', '13k', '12k', '11k', '10k', '19k', '18k', '17k', '16k', '15k', '14k', '13k', '12k', '11k', '10k',
'9k', '8k', '7k', '6k', '5k', '4k', '3k', '2k', '1k', '9k', '8k', '7k', '6k', '5k', '4k', '3k', '2k', '1k',
'1d', '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d'] '1d', '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d'
];
// index corresponds to difference in player rank
const KOMI_REC = {
'9': [
5.5, 2.5, -0.5, -3.5, -6.5, -9.5, 12.5, 15.5, 18.5, 21.5
],
'13': [
5.5, 0.5, -5.5, 0.5, -5.5, 0.5, -5.5, 0.5, -5.5, 0.5
],
'19': [
7.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
]
}
const HANDI_REC = {
'9': [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
'13': [
0, 0, 0, 2, 2, 3, 3, 4, 4, 5
],
'19': [
0, 0, 2, 3, 4, 5, 6, 7, 8, 9
]
}
const gameState = { const gameState = {
winner: null, winner: null,
@ -54,13 +80,39 @@ const gameState = {
// deadShapes{} // deadShapes{}
// index represents handicap placement, eg handiPlace[1] = { (3, 3), (7, 7) } // index represents handicap placement, eg handiPlace[1] = { (3, 3), (7, 7) }
const handiPlace = [ 0, const HANDI_PLACE = {
[ [ 3, 3 ], [ 7, 7 ] ], '9' : [
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ] ], 0, 0,
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ], [ 7, 3 ] ] ]; [ [ 7, 3 ], [ 3, 7 ] ],
[ [ 7, 7 ], [ 7, 3 ], [ 3, 7 ] ],
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ], [ 7, 3 ] ]
],
'13' : [
0, 0,
[ [ 4, 9 ], [ 9, 4 ] ],
[ [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 7, 7 ], [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 7, 4 ], [ 4, 7 ], [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 7, 7 ], [ 7, 4 ], [ 4, 7 ], [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 9, 7 ], [ 7, 4 ], [ 7, 9 ], [ 4, 7 ], [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
[ [ 7, 7 ], [ 9, 7 ], [ 7, 4 ], [ 7, 9 ], [ 4, 7 ], [ 4, 4 ], [ 9, 9 ], [ 4, 9 ], [ 9, 4] ],
],
'19' : [
0, 0,
[ [ 4, 16 ], [ 16, 4 ] ],
[ [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 9, 9 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 9, 4 ], [ 4, 9 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 9, 9 ], [ 9, 4 ], [ 4, 9 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 16, 9 ], [ 9, 4 ], [ 9, 16 ], [ 4, 9 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
[ [ 9, 9 ], [ 16, 9 ], [ 9, 4 ], [ 9, 16 ], [ 4, 9 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
]
};
/*----- app's state (variables) -----*/ /*----- app's state (variables) -----*/
let boardState; let boardState = [];
// define initial game state // define initial game state
@ -131,11 +183,25 @@ class Point {
checkGroup = () => { // liberty is true when called by move false when called by check Capture checkGroup = () => { // liberty is true when called by move false when called by check Capture
let frns = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn); let frns = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn);
for (let frn in frns) { for (let frn in frns) {
console.log(frns[frn]);
if (frns[frn].groupMembers.find(stone => stone.getLiberties().find(liberty => liberty !== this))) return true; if (frns[frn].groupMembers.find(stone => stone.getLiberties().find(liberty => liberty !== this))) return true;
continue; continue;
} }
} }
cycleTerritory = () => {
this.groupMembers.forEach(pt => {
switch (pt.territory) {
case 1:
pt.territory = -1;
break;
case -1:
pt.territory = 'd';
break;
case 'd':
pt.territory = 1;
break;
}
})
}
} }
// could use this Array to iterate through and create // could use this Array to iterate through and create
// let boardCreator = new Array(gameState.boardSize).fill(gameState.boardSize); // let boardCreator = new Array(gameState.boardSize).fill(gameState.boardSize);
@ -188,6 +254,8 @@ komiSliderEl.addEventListener('change', changeUpdateKomi);
handiSliderEl.addEventListener('change', changeUpdateHandicap); 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);
gameHudEl.addEventListener('click', clickSubmitScore);
/*----- functions -----*/ /*----- functions -----*/
@ -204,6 +272,7 @@ function changeUpdateHandicap() {
} }
function clickUpdatePlayerMeta(evt) { function clickUpdatePlayerMeta(evt) {
if (evt.target.id) {
switch (evt.target.id) { switch (evt.target.id) {
case 'black-rank-up': case 'black-rank-up':
gameState.playerMeta.b.rank++; gameState.playerMeta.b.rank++;
@ -218,6 +287,7 @@ function clickUpdatePlayerMeta(evt) {
gameState.playerMeta.w.rank--; gameState.playerMeta.w.rank--;
break; break;
} }
}
if (evt.target.name = 'black-rank-certain') gameState.playerMeta.b.rankCertain = !gameState.playerMeta.b.rankCertain; if (evt.target.name = 'black-rank-certain') gameState.playerMeta.b.rankCertain = !gameState.playerMeta.b.rankCertain;
if (evt.target.name = 'white-rank-certain') gameState.playerMeta.w.rankCertain = !gameState.playerMeta.w.rankCertain; if (evt.target.name = 'white-rank-certain') gameState.playerMeta.w.rankCertain = !gameState.playerMeta.w.rankCertain;
blackRankEl.textContent = RANKS[gameState.playerMeta.b.rank]; blackRankEl.textContent = RANKS[gameState.playerMeta.b.rank];
@ -226,15 +296,30 @@ function clickUpdatePlayerMeta(evt) {
} }
function clickKomiSuggestion() { function clickKomiSuggestion() {
// let sugg = KOMI_REC[Math.abs(gameState.playerMeta.w.rank - gameState.playerMeta.b.rank)];
let handi = HANDI_REC[Math.abs(gameState.playerMeta.w.rank - gameState.playerMeta.b.rank)];
gameState.komi = sugg;
gameState.handicap = handi;
renderMenu();
}
function clickSubmitScore() {
if (gameState.pass > 1 && !gameState.winner) calculateWinner();
} }
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();
}
function renderMenu() {
komiSliderEl.value = sugg;
blackNameDisplayEl.textContent = gameState.playerMeta.b.name; blackNameDisplayEl.textContent = gameState.playerMeta.b.name;
whiteNameDisplayEl.textContent = gameState.playerMeta.w.name; whiteNameDisplayEl.textContent = gameState.playerMeta.w.name;
blackRankEl.textContent = RANKS[gameState.playerMeta.b.rank];
whiteRankEl.textContent = RANKS[gameState.playerMeta.w.rank];
} }
function clickPass(evt) { function clickPass(evt) {
@ -324,6 +409,13 @@ function resolveCaptures(point) {
} }
} }
function editTerritory(evt) {
let placement = [ parseInt(evt.target.closest('td').id[0]), parseInt(evt.target.closest('td').id[2]) ];
let point = findPointFromIdx(placement);
point.cycleTerritory();
render();
}
function checkKo(point, cap) { function checkKo(point, cap) {
if (!point.getLiberties().length && cap.checkNeighbors().filter(stone => stone.stone === gameState.turn * -1) if (!point.getLiberties().length && cap.checkNeighbors().filter(stone => stone.stone === gameState.turn * -1)
&& point.capturing.length === 1) return true; && point.capturing.length === 1) return true;
@ -331,7 +423,7 @@ function checkKo(point, cap) {
function clickBoard(evt) { function clickBoard(evt) {
evt.stopPropagation(); evt.stopPropagation();
if (gameState.pass > 1 || gameState.winner) return; if (gameState.pass > 1 || gameState.winner) return editTerritory(evt);
// checks for placement and pushes to cell // checks for placement and pushes to cell
let placement = [ parseInt(evt.target.closest('td').id[0]), parseInt(evt.target.closest('td').id[2]) ]; let placement = [ parseInt(evt.target.closest('td').id[0]), parseInt(evt.target.closest('td').id[2]) ];
let point = findPointFromIdx(placement); let point = findPointFromIdx(placement);
@ -366,6 +458,20 @@ function clearCaptures() {
} }
} }
function initBoard() {
let i = 0;
while (i < gameState.boardSize * gameState.boardSize) {
let point = new Point( Math.floor(i / gameState.boardSize) + 1, i % gameState.boardSize + 1)
boardState.push(point);
i++;
}
// initHandi();
}
function initHandi() {
// HANDI_PLACE[gameState.handicap]
}
function getDate() { function getDate() {
let d = new Date; let d = new Date;
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)}`
@ -375,7 +481,8 @@ function init() {
gameState.pass = null; gameState.pass = null;
// gameState.komi = ; // get komi from player input // 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.playerState.bCaptures = 0; gameState.playerState.bCaptures = 0;
gameState.playerState.wCaptures = 0; gameState.playerState.wCaptures = 0;
gameState.gameMeta.date = getDate(); gameState.gameMeta.date = getDate();
@ -386,105 +493,9 @@ function init() {
// gameState.boardState // create board from user input // gameState.boardState // create board from user input
//need init player meta //need init player meta
initBoard();
boardState = [ new Point(1,1), new Point(1,2), new Point(1,3), new Point(1,4), new Point(1,5), new Point(1,6), new Point(1,7), new Point(1,8), new Point(1,9),
new Point(2,1), new Point(2,2), new Point(2,3), new Point(2,4), new Point(2,5), new Point(2,6), new Point(2,7), new Point(2,8), new Point(2,9),
new Point(3,1), new Point(3,2), new Point(3,3), new Point(3,4), new Point(3,5), new Point(3,6), new Point(3,7), new Point(3,8), new Point(3,9),
new Point(4,1), new Point(4,2), new Point(4,3), new Point(4,4), new Point(4,5), new Point(4,6), new Point(4,7), new Point(4,8), new Point(4,9),
new Point(5,1), new Point(5,2), new Point(5,3), new Point(5,4), new Point(5,5), new Point(5,6), new Point(5,7), new Point(5,8), new Point(5,9),
new Point(6,1), new Point(6,2), new Point(6,3), new Point(6,4), new Point(6,5), new Point(6,6), new Point(6,7), new Point(6,8), new Point(6,9),
new Point(7,1), new Point(7,2), new Point(7,3), new Point(7,4), new Point(7,5), new Point(7,6), new Point(7,7), new Point(7,8), new Point(7,9),
new Point(8,1), new Point(8,2), new Point(8,3), new Point(8,4), new Point(8,5), new Point(8,6), new Point(8,7), new Point(8,8), new Point(8,9),
new Point(9,1), new Point(9,2), new Point(9,3), new Point(9,4), new Point(9,5), new Point(9,6), new Point(9,7), new Point(9,8), new Point(9,9)
];
// testing board state for moves at [32] // testing board state for moves at [32]
gameState.turn = 1; gameState.turn = 1;
// boardState[1].stone = 1;
// boardState[1].joinGroup();
// boardState[4].stone = 1;
// boardState[4].joinGroup();
// boardState[5].stone = 1;
// boardState[5].joinGroup();
// boardState[6].stone = 1;
// boardState[6].joinGroup();
// boardState[9].stone = 1;
// boardState[9].joinGroup();
// boardState[10].stone = -1;
// boardState[10].joinGroup();
// boardState[11].stone = 1;
// boardState[11].joinGroup();
// boardState[13].stone = -1;
// boardState[13].joinGroup();
// boardState[14].stone = -1;
// boardState[14].joinGroup();
// boardState[15].stone = -1;
// boardState[15].joinGroup();
// boardState[16].stone = 1;
// boardState[16].joinGroup();
// boardState[18].stone = 1;
// boardState[18].joinGroup();
// boardState[19].stone = -1;
// boardState[19].joinGroup();
// boardState[20].stone = 1;
// boardState[20].joinGroup();
// boardState[21].stone = 1;
// boardState[21].joinGroup();
// boardState[22].stone = 1;
// boardState[22].joinGroup();
// boardState[23].stone = -1;
// boardState[23].joinGroup();
// boardState[24].stone = 1;
// boardState[24].joinGroup();
// boardState[25].stone = 1;
// boardState[25].joinGroup();
// boardState[27].stone = 1;
// boardState[27].joinGroup();
// boardState[28].stone = -1;
// boardState[28].joinGroup();
// boardState[29].stone = -1;
// boardState[29].joinGroup();
// boardState[30].stone = -1;
// boardState[30].joinGroup();
// boardState[31].stone = -1;
// boardState[31].joinGroup();
// boardState[33].stone = -1;
// boardState[33].joinGroup();
// boardState[34].stone = 1;
// boardState[34].joinGroup();
// boardState[36].stone = 1;
// boardState[36].joinGroup();
// boardState[37].stone = -1;
// boardState[37].joinGroup();
// boardState[38].stone = 1;
// boardState[38].joinGroup();
// boardState[39].stone = 1;
// boardState[39].joinGroup();
// boardState[40].stone = 1;
// boardState[40].joinGroup();
// boardState[41].stone = -1;
// boardState[41].joinGroup();
// boardState[42].stone = 1;
// boardState[42].joinGroup();
// boardState[46].stone = 1;
// boardState[46].joinGroup();
// boardState[56].stone = 1;
// boardState[56].joinGroup();
// boardState[57].stone = 1;
// boardState[57].joinGroup();
// boardState[65].stone = -1;
// boardState[65].joinGroup();
// boardState[66].stone = -1;
// boardState[66].joinGroup();
// boardState[67].stone = 1;
// boardState[67].joinGroup();
// boardState[74].stone = -1;
// boardState[75].stone = -1;
// boardState[76].stone = 1;
clearCaptures();
// end testing board state
render(); render();
}; };
@ -499,7 +510,14 @@ function render() {
} }
function renderMessage() { function renderMessage() {
if (gameState.winner) gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name} won by resignation` if (gameState.winner && gameState.pass < 2) {
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.textContent = 'click to finalize game'
}
} }
function renderTerritory() { function renderTerritory() {
@ -541,16 +559,29 @@ function renderPreview(hoverPoint) {
}) })
} }
function calculateWinner() {
}
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());
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) {
// 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)
}
function endGame() { function endGame() {
if (!gameState.winner) if (!gameState.winner)
endGameSetTerritory() endGameSetTerritory()
@ -573,17 +604,7 @@ function endGame() {
// log as text // log as text
} }
// functions
// initialize game
// set handicap stones
// render
// render board
//render moves
//render preview
// render captures
// render player turn marker
// game-end // game-end
// render dead group suggestion
// render territory counts // render territory counts
// checkLegalMove // checkLegalMove
// clear overlay // clear overlay