2019-08-02 21:14:23 +00:00
|
|
|
/*----- constants -----*/
|
|
|
|
// game state object {gameMeta object, playerMeta object, turn, pass, gameRecord, bCaptures, wCaptures}
|
2019-08-03 06:42:49 +00:00
|
|
|
const STONES_DATA = {
|
2019-08-03 04:19:31 +00:00
|
|
|
'-1': 'white',
|
2019-08-03 06:42:49 +00:00
|
|
|
'0': 'none',
|
2019-08-03 04:19:31 +00:00
|
|
|
'1': 'black',
|
|
|
|
'k': 'ko'
|
|
|
|
}
|
|
|
|
|
2019-08-03 06:42:49 +00:00
|
|
|
const DOTS_DATA = {
|
2019-08-03 04:19:31 +00:00
|
|
|
'-1': 'white',
|
2019-08-03 06:42:49 +00:00
|
|
|
'0': 'none',
|
2019-08-03 04:19:31 +00:00
|
|
|
'1': 'black',
|
|
|
|
'd': 'dame',
|
|
|
|
's': 'seki'
|
|
|
|
}
|
|
|
|
|
2019-08-06 22:25:58 +00:00
|
|
|
const RANKS = ['30k', '29k', '28k', '27k', '26k', '25k', '24k', '23k', '22k', '21k', '20k',
|
2019-08-06 21:13:51 +00:00
|
|
|
'19k', '18k', '17k', '16k', '15k', '14k', '13k', '12k', '11k', '10k',
|
|
|
|
'9k', '8k', '7k', '6k', '5k', '4k', '3k', '2k', '1k',
|
|
|
|
'1d', '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d']
|
|
|
|
|
2019-08-02 21:14:23 +00:00
|
|
|
const gameState = {
|
|
|
|
winner: null,
|
2019-08-03 06:42:49 +00:00
|
|
|
turn: 1, // turn logic depends on handicap stones
|
2019-08-02 21:14:23 +00:00
|
|
|
pass: null,
|
2019-08-06 18:22:43 +00:00
|
|
|
komi: null, // komi depends on handicap stones + player rank
|
2019-08-02 21:14:23 +00:00
|
|
|
handicap: null,
|
2019-08-03 04:19:31 +00:00
|
|
|
boardSize: 9,
|
2019-08-02 21:14:23 +00:00
|
|
|
playerState: {
|
|
|
|
bCaptures: null,
|
|
|
|
wCaptures: null
|
|
|
|
},
|
|
|
|
gameMeta: { // declared at game start and not editable after
|
2019-08-03 04:19:31 +00:00
|
|
|
date: null // contains metadata
|
2019-08-02 21:14:23 +00:00
|
|
|
},
|
|
|
|
playerMeta: { // editable during game
|
|
|
|
b: {
|
|
|
|
name: null,
|
2019-08-06 22:25:58 +00:00
|
|
|
rank: 21,
|
2019-08-03 04:19:31 +00:00
|
|
|
rankCertain: false
|
2019-08-02 21:14:23 +00:00
|
|
|
},
|
|
|
|
w: {
|
|
|
|
name: null,
|
2019-08-06 22:25:58 +00:00
|
|
|
rank: 21,
|
2019-08-02 21:14:23 +00:00
|
|
|
rankCertain: false
|
|
|
|
},
|
|
|
|
},
|
2019-08-03 04:19:31 +00:00
|
|
|
groups: {},
|
2019-08-02 21:14:23 +00:00
|
|
|
gameRecord : []
|
|
|
|
}
|
2019-07-26 17:17:23 +00:00
|
|
|
|
2019-08-03 04:19:31 +00:00
|
|
|
|
|
|
|
// deadShapes{}
|
2019-07-26 17:17:23 +00:00
|
|
|
|
2019-08-02 21:14:23 +00:00
|
|
|
// index represents handicap placement, eg handiPlace[1] = { (3, 3), (7, 7) }
|
|
|
|
const handiPlace = [ 0,
|
2019-08-03 04:19:31 +00:00
|
|
|
[ [ 3, 3 ], [ 7, 7 ] ],
|
|
|
|
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ] ],
|
|
|
|
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ], [ 7, 3 ] ] ];
|
|
|
|
|
|
|
|
/*----- app's state (variables) -----*/
|
2019-08-03 22:01:50 +00:00
|
|
|
let boardState;
|
|
|
|
|
|
|
|
|
2019-08-03 04:19:31 +00:00
|
|
|
// define initial game state
|
|
|
|
|
2019-08-03 06:42:49 +00:00
|
|
|
class Point {
|
|
|
|
constructor(x, y) {
|
|
|
|
this.pos = [ x, y ]
|
|
|
|
this.stone = 0; // this is where move placement will go 0, 1, -1 'k'
|
2019-08-04 02:21:02 +00:00
|
|
|
this.legal;
|
2019-08-06 21:13:51 +00:00
|
|
|
this.territory;
|
2019-08-05 17:50:06 +00:00
|
|
|
this.capturing = [];
|
2019-08-06 06:44:07 +00:00
|
|
|
this.groupMembers = [];
|
2019-08-03 06:42:49 +00:00
|
|
|
this.neighbors = {
|
|
|
|
top: {},
|
|
|
|
btm: {},
|
|
|
|
lft: {},
|
|
|
|
rgt: {}
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
2019-08-03 06:42:49 +00:00
|
|
|
this.neighbors.top = x > 1 ? [ x - 1, y ] : null;
|
|
|
|
this.neighbors.btm = x < gameState.boardSize ? [ x + 1, y ] : null;
|
|
|
|
this.neighbors.rgt = y > 1 ? [ x, y - 1 ] : null;
|
|
|
|
this.neighbors.lft = y < gameState.boardSize ? [ x, y + 1 ] : null;
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
|
|
|
checkNeighbors = () => {
|
|
|
|
let neighborsArr = [];
|
|
|
|
for ( let neighbor in this.neighbors ) {
|
|
|
|
let nbr = this.neighbors[neighbor];
|
|
|
|
// neighbor exists it's point is stored as { rPos, cPos}
|
|
|
|
if ( nbr !== null ) {
|
2019-08-06 06:44:07 +00:00
|
|
|
neighborsArr.push(boardState.find( pt => pt.pos[0] === nbr[0] && pt.pos[1] === nbr[1] ))
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// returns array of existing neighbors to calling function
|
|
|
|
return neighborsArr;
|
|
|
|
}
|
2019-08-05 17:50:06 +00:00
|
|
|
getLiberties = () => {
|
2019-08-06 06:44:07 +00:00
|
|
|
let neighborsArr = this.checkNeighbors().filter(pt => pt.stone === 0);
|
2019-08-05 17:50:06 +00:00
|
|
|
return neighborsArr; //checked
|
2019-08-06 06:44:07 +00:00
|
|
|
// return all liberties;
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
2019-08-06 06:44:07 +00:00
|
|
|
joinGroup = () => {
|
|
|
|
this.groupMembers.push(this);
|
|
|
|
let frns = this.checkNeighbors().filter(nbr => nbr.stone === this.stone);
|
|
|
|
for (let frn of frns) {
|
|
|
|
this.groupMembers.push(frn);
|
|
|
|
}
|
|
|
|
// this.groupMembers = Array.from(new Set(this.groupMembers));
|
2019-08-06 21:13:51 +00:00
|
|
|
if (!this.groupMembers.length) return;
|
2019-08-06 06:44:07 +00:00
|
|
|
for (let grpMem in this.groupMembers) {
|
2019-08-06 21:13:51 +00:00
|
|
|
debugger;
|
2019-08-06 06:44:07 +00:00
|
|
|
this.groupMembers = Array.from(new Set(this.groupMembers.concat(this.groupMembers[grpMem].groupMembers)));
|
|
|
|
}
|
|
|
|
for (let grpMem in this.groupMembers) {
|
|
|
|
this.groupMembers[grpMem].groupMembers = Array.from(new Set(this.groupMembers[grpMem].groupMembers.concat(this.groupMembers)));
|
2019-08-05 17:50:06 +00:00
|
|
|
}
|
2019-08-06 06:44:07 +00:00
|
|
|
}
|
|
|
|
checkCapture = () => {
|
|
|
|
let tempCaptures = [];
|
|
|
|
let opps = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn * -1
|
|
|
|
&& nbr.getLiberties().every(liberty => liberty === this));
|
2019-08-05 20:56:54 +00:00
|
|
|
for (let opp of opps) {
|
2019-08-06 06:44:07 +00:00
|
|
|
if (opp.groupMembers.every(stone => stone.getLiberties().filter(liberty => liberty !== this).length === 0)) {
|
|
|
|
this.capturing = this.capturing.concat(opp.groupMembers);
|
|
|
|
};
|
2019-08-05 20:56:54 +00:00
|
|
|
}
|
2019-08-06 21:13:51 +00:00
|
|
|
this.capturing = Array.from(new Set(this.capturing));
|
2019-08-05 17:50:06 +00:00
|
|
|
return this.capturing;
|
2019-08-03 23:59:56 +00:00
|
|
|
}
|
2019-08-05 17:50:06 +00:00
|
|
|
checkGroup = () => { // liberty is true when called by move false when called by check Capture
|
|
|
|
let frns = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn);
|
2019-08-06 06:44:07 +00:00
|
|
|
for (let frn in frns) {
|
|
|
|
if (frns[frn].getLiberties().filter(liberty => liberty !== this).length) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
2019-08-04 03:43:57 +00:00
|
|
|
// could use this Array to iterate through and create
|
2019-08-04 00:20:52 +00:00
|
|
|
// let boardCreator = new Array(gameState.boardSize).fill(gameState.boardSize);
|
2019-08-03 22:01:50 +00:00
|
|
|
// boardState [point objects-contain overlay] lastState (created from boardState)
|
2019-08-03 21:15:17 +00:00
|
|
|
|
2019-08-03 22:01:50 +00:00
|
|
|
// 'k' represents komi, in-game integers represent move previews,
|
|
|
|
// 'chk', 'hold', 'x' and 'l' represent points checked during checkLegalMove run
|
|
|
|
// game-end integer represent points of territory, 'd' represents dame,
|
2019-08-03 06:42:49 +00:00
|
|
|
|
2019-08-03 21:15:17 +00:00
|
|
|
|
2019-08-03 22:01:50 +00:00
|
|
|
/*----- cached element references -----*/
|
2019-08-05 23:13:23 +00:00
|
|
|
const whiteCapsEl = document.getElementById("white-caps");
|
|
|
|
const blackCapsEl = document.getElementById("black-caps");
|
|
|
|
const modalEl = document.querySelector('.modal');
|
2019-08-06 18:22:43 +00:00
|
|
|
const komiSliderEl = document.querySelector('input[name="komi-slider"]');
|
|
|
|
const handiSliderEl = document.querySelector('input[name="handicap-slider"]');
|
2019-08-06 22:25:58 +00:00
|
|
|
const blackRankEl = document.getElementById("black-rank");
|
|
|
|
const blackRankUpEl = document.getElementById("black-rank-up");
|
|
|
|
const blackRankDownEl = document.getElementById("black-rank-down");
|
|
|
|
const whiteRankEl = document.getElementById("white-rank");
|
|
|
|
const whiteRankUpEl = document.getElementById("black-rank-up");
|
|
|
|
const whiteRankDownEl = document.getElementById("black-rank-down");
|
2019-08-06 23:36:39 +00:00
|
|
|
const blackNameInputEl = document.querySelector("input[name='black-name']")
|
|
|
|
const whiteNameInputEl = document.querySelector("input[name='white-name']")
|
|
|
|
const blackNameDisplayEl = document.querySelector("h4#black-player-name");
|
|
|
|
const whiteNameDisplayEl = document.querySelector("h4#white-player-name");
|
2019-08-06 22:25:58 +00:00
|
|
|
|
2019-08-04 03:43:57 +00:00
|
|
|
// store modal #menu for displaying game info
|
2019-08-03 22:01:50 +00:00
|
|
|
// store
|
|
|
|
|
|
|
|
|
|
|
|
/*----- 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)
|
2019-08-03 23:59:56 +00:00
|
|
|
document.getElementById('board').addEventListener('mousemove', hoverPreview);
|
2019-08-03 22:01:50 +00:00
|
|
|
// click on board to play move
|
2019-08-06 21:13:51 +00:00
|
|
|
document.getElementById('board').addEventListener('click', clickBoard);
|
2019-08-03 22:01:50 +00:00
|
|
|
// ::hover-over on either bowl for pass, one-level undo options (CSS implementation)
|
|
|
|
// click on menu items
|
|
|
|
// click on kifu to display game menu
|
2019-08-05 23:13:23 +00:00
|
|
|
document.getElementById('white-bowl').addEventListener('click',clickPass);
|
|
|
|
document.getElementById('black-bowl').addEventListener('click',clickPass);
|
|
|
|
document.getElementById('kifu').addEventListener('click', clickMenu);
|
|
|
|
document.getElementById('white-caps-space').addEventListener('click', clickResign);
|
|
|
|
document.getElementById('black-caps-space').addEventListener('click', clickResign);
|
|
|
|
modalEl.addEventListener('click', clickCloseMenu);
|
2019-08-06 18:22:43 +00:00
|
|
|
komiSliderEl.addEventListener('change', changeUpdateKomi);
|
|
|
|
handiSliderEl.addEventListener('change', changeUpdateHandicap);
|
2019-08-06 22:25:58 +00:00
|
|
|
document.getElementById('player-meta').addEventListener('click', clickUpdatePlayerMeta);
|
|
|
|
document.getElementById('player-meta').addEventListener('change', clickUpdatePlayerMeta);
|
2019-08-05 23:13:23 +00:00
|
|
|
|
2019-08-03 22:01:50 +00:00
|
|
|
|
|
|
|
/*----- functions -----*/
|
2019-08-03 06:42:49 +00:00
|
|
|
init();
|
|
|
|
|
|
|
|
let findPointFromIdx = (arr) => boardState.find( point => point.pos[0] === arr[0] && point.pos[1] === arr[1] );
|
2019-08-03 04:19:31 +00:00
|
|
|
|
2019-08-06 18:22:43 +00:00
|
|
|
function changeUpdateKomi() {
|
|
|
|
document.getElementById('komi').textContent = komiSliderEl.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function changeUpdateHandicap() {
|
|
|
|
document.getElementById('handicap').textContent = handiSliderEl.value;
|
|
|
|
}
|
|
|
|
|
2019-08-06 22:25:58 +00:00
|
|
|
function clickUpdatePlayerMeta(evt) {
|
|
|
|
switch (evt.target.id) {
|
|
|
|
case 'black-rank-up':
|
|
|
|
gameState.playerMeta.b.rank++;
|
|
|
|
break;
|
|
|
|
case 'black-rank-down':
|
|
|
|
gameState.playerMeta.b.rank--;
|
|
|
|
break;
|
|
|
|
case 'white-rank-up':
|
|
|
|
gameState.playerMeta.w.rank++;
|
|
|
|
break;
|
|
|
|
case 'white-rank-down':
|
|
|
|
gameState.playerMeta.w.rank--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
blackRankEl.textContent = RANKS[gameState.playerMeta.b.rank];
|
|
|
|
whiteRankEl.textContent = RANKS[gameState.playerMeta.w.rank];
|
2019-08-06 23:36:39 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function clickKomiSuggestion() {
|
|
|
|
//
|
2019-08-06 22:25:58 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 23:36:39 +00:00
|
|
|
function clickSubmitStart() {
|
|
|
|
|
|
|
|
gameState.playerMeta.b.name = blackNameInputEl.value;
|
|
|
|
gameState.playerMeta.w.name = whiteNameInputEl.value;
|
|
|
|
blackNameDisplayEl.textContent = gameState.playerMeta.b.name;
|
|
|
|
whiteNameDisplayEl.textContent = gameState.playerMeta.w.name;
|
|
|
|
}
|
|
|
|
|
2019-08-05 23:13:23 +00:00
|
|
|
function clickPass(evt) {
|
|
|
|
if (evt.target.parentElement.id === `${STONES_DATA[gameState.turn]}-bowl`) playerPass();
|
|
|
|
}
|
|
|
|
|
|
|
|
function playerPass() {
|
|
|
|
// display confirmation message
|
|
|
|
clearKo();
|
|
|
|
clearCaptures();
|
|
|
|
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: pass`)
|
|
|
|
gameState.pass++;
|
|
|
|
if (gameState.pass === 2) return endGame();
|
|
|
|
gameState.turn*= -1;
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
|
|
|
|
function clickMenu() {
|
|
|
|
modalEl.style.visibility = 'visible';
|
2019-08-06 18:22:43 +00:00
|
|
|
changeUpdateKomi();
|
|
|
|
changeUpdateHandicap();
|
2019-08-06 22:25:58 +00:00
|
|
|
clickUpdatePlayerMeta();
|
2019-08-05 23:13:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function clickCloseMenu(evt) {
|
|
|
|
evt.stopPropagation();
|
2019-08-06 18:22:43 +00:00
|
|
|
if (evt.target.className === "modal") modalEl.style.visibility = 'hidden';
|
2019-08-05 23:13:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function clickResign(evt) {
|
|
|
|
if (evt.target.parentElement.id === `${STONES_DATA[gameState.turn]}-caps-space`) playerResign();
|
|
|
|
}
|
|
|
|
|
|
|
|
function playerResign() {
|
|
|
|
// display confirmation message
|
2019-08-06 23:36:39 +00:00
|
|
|
if (!confirm('Do you want to resign?')) return;
|
|
|
|
|
|
|
|
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: resign`);
|
|
|
|
gameState.winner = STONES_DATA[gameState.turn * -1];
|
2019-08-05 23:13:23 +00:00
|
|
|
endGame();
|
|
|
|
}
|
|
|
|
|
2019-08-03 23:59:56 +00:00
|
|
|
function hoverPreview(evt) {
|
2019-08-04 05:40:45 +00:00
|
|
|
evt.stopPropagation();
|
2019-08-06 21:13:51 +00:00
|
|
|
if (gameState.pass > 1 || gameState.winner) return;
|
2019-08-03 23:59:56 +00:00
|
|
|
// renders preview stone if move is legal
|
2019-08-03 21:15:17 +00:00
|
|
|
let hover = [ parseInt(evt.target.closest('td').id[0]), parseInt(evt.target.closest('td').id[2]) ];
|
2019-08-03 06:42:49 +00:00
|
|
|
let point = findPointFromIdx(hover);
|
2019-08-03 23:59:56 +00:00
|
|
|
if (checkLegal(point)) {
|
2019-08-04 02:21:02 +00:00
|
|
|
point.legal = true; // legal
|
2019-08-05 18:37:03 +00:00
|
|
|
renderPreview(point);
|
2019-08-03 23:59:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkLegal(point) {
|
2019-08-04 02:21:02 +00:00
|
|
|
clearOverlay();
|
2019-08-03 21:15:17 +00:00
|
|
|
// first step in logic: is point occupied, or in ko
|
2019-08-03 23:59:56 +00:00
|
|
|
if (point.stone) return false;
|
2019-08-05 17:50:06 +00:00
|
|
|
// if point is not empty check if liberties
|
2019-08-06 06:44:07 +00:00
|
|
|
if (point.getLiberties().length < 1) {
|
2019-08-05 17:50:06 +00:00
|
|
|
//if no liberties check if enemy group has liberties
|
2019-08-06 06:44:07 +00:00
|
|
|
if ( point.checkCapture().length ) return true;
|
2019-08-03 23:59:56 +00:00
|
|
|
//if neighboring point is not empty check if friendly group is alive
|
2019-08-06 06:44:07 +00:00
|
|
|
if (point.checkGroup()) return true;
|
2019-08-04 02:21:02 +00:00
|
|
|
return false;
|
2019-08-03 21:15:17 +00:00
|
|
|
}
|
2019-08-03 23:59:56 +00:00
|
|
|
return true;
|
2019-08-03 04:19:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 02:21:02 +00:00
|
|
|
function clearOverlay() { //legal and check
|
|
|
|
for (let point in boardState) {
|
2019-08-04 04:17:23 +00:00
|
|
|
point = boardState[point];
|
|
|
|
point.legal = false;
|
2019-08-04 02:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-04 03:43:57 +00:00
|
|
|
function resolveCaptures(point) {
|
2019-08-06 07:00:00 +00:00
|
|
|
if(!point.capturing.length) {
|
2019-08-05 23:13:23 +00:00
|
|
|
point.checkCapture();
|
|
|
|
}
|
|
|
|
if(point.capturing.length) {
|
2019-08-05 17:50:06 +00:00
|
|
|
point.capturing.forEach(cap => {
|
2019-08-05 23:13:23 +00:00
|
|
|
gameState.playerState[gameState.turn > 0 ? 'bCaptures' : 'wCaptures']++;
|
2019-08-06 06:44:07 +00:00
|
|
|
cap.groupMembers = [];
|
2019-08-06 07:00:00 +00:00
|
|
|
cap.stone = checkKo(point, cap) ? 'k' : 0;
|
2019-08-05 17:50:06 +00:00
|
|
|
})
|
2019-08-04 03:43:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 07:00:00 +00:00
|
|
|
function checkKo(point, cap) {
|
2019-08-06 21:13:51 +00:00
|
|
|
if (!point.getLiberties().length && cap.checkNeighbors().filter(stone => stone.stone === gameState.turn * -1)
|
|
|
|
&& point.capturing.length === 1) return true;
|
2019-08-04 04:17:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 21:13:51 +00:00
|
|
|
function clickBoard(evt) {
|
2019-08-04 05:40:45 +00:00
|
|
|
evt.stopPropagation();
|
2019-08-06 21:13:51 +00:00
|
|
|
if (gameState.pass > 1 || gameState.winner) return;
|
2019-08-03 06:42:49 +00:00
|
|
|
// checks for placement and pushes to cell
|
2019-08-03 23:59:56 +00:00
|
|
|
let placement = [ parseInt(evt.target.closest('td').id[0]), parseInt(evt.target.closest('td').id[2]) ];
|
2019-08-03 06:42:49 +00:00
|
|
|
let point = findPointFromIdx(placement);
|
2019-08-03 04:19:31 +00:00
|
|
|
//checks that this placement was marked as legal
|
2019-08-03 23:59:56 +00:00
|
|
|
if ( !checkLegal(point) ) return;
|
2019-08-05 23:13:23 +00:00
|
|
|
clearKo();
|
|
|
|
clearPass();
|
2019-08-04 03:43:57 +00:00
|
|
|
resolveCaptures(point);
|
2019-08-05 23:13:23 +00:00
|
|
|
point.stone = gameState.turn;
|
2019-08-06 06:44:07 +00:00
|
|
|
point.joinGroup();
|
2019-08-05 18:37:03 +00:00
|
|
|
clearCaptures();
|
|
|
|
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: ${point.pos}`)
|
2019-08-03 06:42:49 +00:00
|
|
|
gameState.turn*= -1;
|
|
|
|
render();
|
2019-08-03 04:19:31 +00:00
|
|
|
}
|
2019-08-04 04:17:23 +00:00
|
|
|
|
2019-08-05 23:13:23 +00:00
|
|
|
function clearKo() {
|
2019-08-04 04:17:23 +00:00
|
|
|
for (let point in boardState) {
|
|
|
|
point = boardState[point];
|
|
|
|
point.stone = point.stone === 'k' ? 0 : point.stone;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-05 23:13:23 +00:00
|
|
|
function clearPass() {
|
|
|
|
gameState.pass = 0;
|
|
|
|
}
|
|
|
|
|
2019-08-05 17:50:06 +00:00
|
|
|
function clearCaptures() {
|
|
|
|
for (let point in boardState) {
|
|
|
|
point = boardState[point];
|
|
|
|
point.capturing = [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 22:25:58 +00:00
|
|
|
function getDate() {
|
|
|
|
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)}`
|
|
|
|
}
|
|
|
|
"YYYY-MM-DD"
|
2019-08-03 04:19:31 +00:00
|
|
|
function init() {
|
|
|
|
gameState.winner = null;
|
|
|
|
gameState.pass = null;
|
|
|
|
// gameState.komi = ; // get komi from player input
|
|
|
|
// gameState.handicap = ; // get handicap from player input
|
2019-08-03 23:59:56 +00:00
|
|
|
// gameState.turn = gameState.handicap ? -1 : 1;
|
2019-08-03 04:19:31 +00:00
|
|
|
gameState.playerState.bCaptures = 0;
|
|
|
|
gameState.playerState.wCaptures = 0;
|
2019-08-06 22:25:58 +00:00
|
|
|
gameState.gameMeta.date = getDate();
|
2019-08-03 04:19:31 +00:00
|
|
|
// get any future meta from player input
|
|
|
|
// gameState.playerMeta.b // get from player input
|
|
|
|
// gameState.playerMeta.w // get from player input
|
|
|
|
gameState.gameRecord = []; // clear game record from previous game
|
|
|
|
// gameState.boardState // create board from user input
|
2019-08-03 22:01:50 +00:00
|
|
|
|
2019-08-03 04:19:31 +00:00
|
|
|
//need init player meta
|
2019-08-03 22:01:50 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
];
|
2019-08-05 17:50:06 +00:00
|
|
|
// testing board state for moves at [32]
|
|
|
|
gameState.turn = 1;
|
|
|
|
|
2019-08-06 06:44:07 +00:00
|
|
|
// 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;
|
2019-08-05 23:13:23 +00:00
|
|
|
|
|
|
|
|
2019-08-05 18:37:03 +00:00
|
|
|
clearCaptures();
|
2019-08-05 17:50:06 +00:00
|
|
|
// end testing board state
|
2019-08-03 06:42:49 +00:00
|
|
|
render();
|
2019-08-03 04:19:31 +00:00
|
|
|
};
|
2019-08-02 21:14:23 +00:00
|
|
|
|
2019-08-03 06:42:49 +00:00
|
|
|
function render(hoverPoint) {
|
2019-08-06 21:13:51 +00:00
|
|
|
if (gameState.winner || gameState.pass > 1) {
|
|
|
|
renderTerritory();
|
2019-08-06 23:36:39 +00:00
|
|
|
renderMessage();
|
2019-08-06 21:13:51 +00:00
|
|
|
}
|
2019-08-05 18:37:03 +00:00
|
|
|
gameState.gameRecord.length? renderTurn() : renderFirstTurn();
|
2019-08-03 06:42:49 +00:00
|
|
|
renderBoard();
|
2019-08-04 04:38:31 +00:00
|
|
|
renderCaps();
|
2019-08-05 18:37:03 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 23:36:39 +00:00
|
|
|
function renderMessage() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-06 21:13:51 +00:00
|
|
|
function renderTerritory() {
|
|
|
|
boardState.forEach(val => {
|
|
|
|
let stoneElem = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).childNodes[1].childNodes[0];
|
|
|
|
stoneElem.setAttribute("data-dot", DOTS_DATA[val.territory]);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-08-05 18:37:03 +00:00
|
|
|
function renderFirstTurn() {
|
|
|
|
document.getElementById(`${STONES_DATA[gameState.turn]}-bowl`).toggleAttribute('data-turn');
|
|
|
|
}
|
|
|
|
function renderTurn() {
|
2019-08-06 21:13:51 +00:00
|
|
|
if (gameState.winner || gameState.pass > 1) document.querySelectorAll(`.bowl`).forEach(bowl => {
|
|
|
|
bowl.removeAttribute('data-turn');
|
|
|
|
bowl.toggleAttribute('data-turn');
|
|
|
|
|
|
|
|
});
|
|
|
|
document.querySelectorAll(`.bowl`).forEach(bowl => bowl.toggleAttribute('data-turn'));
|
2019-08-03 06:42:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function renderBoard() {
|
|
|
|
boardState.forEach(val => {
|
2019-08-05 23:13:23 +00:00
|
|
|
let stoneElem = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).childNodes[1];
|
2019-08-05 17:50:06 +00:00
|
|
|
stoneElem.setAttribute("data-stone", STONES_DATA[val.stone]);
|
2019-08-03 06:42:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-08-04 04:38:31 +00:00
|
|
|
function renderCaps() {
|
2019-08-05 23:13:23 +00:00
|
|
|
blackCapsEl.textContent = gameState.playerState.bCaptures;
|
|
|
|
whiteCapsEl.textContent = gameState.playerState.wCaptures;
|
2019-08-04 04:38:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-03 06:42:49 +00:00
|
|
|
function renderPreview(hoverPoint) {
|
|
|
|
boardState.forEach(val => {
|
2019-08-05 23:13:23 +00:00
|
|
|
let dot = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).childNodes[1].childNodes[0];
|
2019-08-04 02:21:02 +00:00
|
|
|
dot.setAttribute("data-dot", val.legal === true && val.pos[0] === hoverPoint.pos[0] && val.pos[1] === hoverPoint.pos[1] ? DOTS_DATA[gameState.turn] : DOTS_DATA[0]);
|
2019-08-03 06:42:49 +00:00
|
|
|
|
|
|
|
})
|
2019-08-05 23:13:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 21:13:51 +00:00
|
|
|
function endGameSetTerritory() {
|
|
|
|
boardState.forEach(pt => {
|
2019-08-06 23:36:39 +00:00
|
|
|
pt.territory = pt.stone ? pt.stone : 'd'
|
2019-08-06 21:13:51 +00:00
|
|
|
});
|
2019-08-06 23:36:39 +00:00
|
|
|
boardState.filter(pt => {
|
|
|
|
return pt.groupMembers.length < 6 && pt.stone
|
|
|
|
}).forEach(pt => pt.territory = pt.stone * -1);
|
2019-08-06 21:13:51 +00:00
|
|
|
}
|
|
|
|
|
2019-08-05 23:13:23 +00:00
|
|
|
function endGame() {
|
2019-08-06 23:36:39 +00:00
|
|
|
if (!gameState.winner)
|
2019-08-06 21:13:51 +00:00
|
|
|
endGameSetTerritory()
|
|
|
|
|
|
|
|
// join all remaining groups
|
|
|
|
// check remaining groups life
|
2019-08-06 23:36:39 +00:00
|
|
|
|
2019-08-06 21:13:51 +00:00
|
|
|
// search empty spaces on board for deadShapes
|
|
|
|
// compare spaces to rotations of deadShapes[...]
|
|
|
|
// 'd' if empty spaces
|
2019-08-05 23:13:23 +00:00
|
|
|
|
2019-08-06 21:13:51 +00:00
|
|
|
render();
|
|
|
|
// return dead group suggestion
|
|
|
|
// users can flip status of any dead group overlay( 1, -1 )
|
|
|
|
// confirm state
|
|
|
|
// calculate score = points in overlay for each player + captures
|
|
|
|
// render final board state with dead groups removed
|
|
|
|
// log game record
|
|
|
|
// stringify according to .sgf format
|
|
|
|
// log as text
|
2019-08-03 06:42:49 +00:00
|
|
|
}
|
2019-07-26 17:17:23 +00:00
|
|
|
|
2019-08-02 21:14:23 +00:00
|
|
|
// functions
|
|
|
|
// initialize game
|
|
|
|
// set handicap stones
|
|
|
|
// render
|
|
|
|
// render board
|
|
|
|
//render moves
|
|
|
|
//render preview
|
|
|
|
// render captures
|
|
|
|
// render player turn marker
|
|
|
|
// game-end
|
|
|
|
// render dead group suggestion
|
|
|
|
// render territory counts
|
|
|
|
// checkLegalMove
|
|
|
|
// clear overlay
|
|
|
|
// if move is not '0', move is illegal (opposing player or 'k' for ko)
|
|
|
|
// iterate through neighboring points in clockwise order
|
|
|
|
// if anyone is '0' move is legal - call render preview
|
|
|
|
// if neighboring point is opposing player
|
|
|
|
// cycle through opposing player group marking points as overlay: 'chk' when checked and
|
|
|
|
// overlay: 'hold' if they are neighboring points of opposing player color
|
|
|
|
// if any neighboring point is '0' terminate cycle and move to next neighboring point of original move
|
|
|
|
// if there are unchecked points of 'hold' return
|
|
|
|
// if no boardState: 0 points, move is legal overlay: 'l'
|
|
|
|
// set all 'chk' to 'x' to represent stones that will be captured upon move
|
2019-08-02 00:10:56 +00:00
|
|
|
// if neighboring point is player's
|
|
|
|
// cycle through player group marking points as overlay: 'chk' || 'hold'
|
|
|
|
// if any neighboring point is '0' ternminate cycle and mark point as 'l'
|
|
|
|
// set move
|
|
|
|
// if checkLegalMove has returned '0' i2llegal move message?
|
|
|
|
// if move state is 'l'
|
|
|
|
// push boardState to lastState
|
|
|
|
// push 'l' move to boardState
|
|
|
|
// resolve captures
|
|
|
|
// for all 'x' in overlay
|
|
|
|
// count number and add to playerCaptures
|
|
|
|
// set boardState to '0'
|
|
|
|
// pass--
|
|
|
|
// push move to game record
|
|
|
|
// game record: [ 0: handicapStones Obj, 1: 1stMove([moveState[],moveState[][])]
|
|
|
|
// pass() pass++ and player turn to other player
|
|
|
|
// gameEnd when pass = 2
|
|
|
|
// search empty spaces on board for deadShapes
|
|
|
|
// compare spaces to rotations of deadShapes[...]
|
|
|
|
// 'd' if empty spaces
|
|
|
|
// return dead group suggestion
|
|
|
|
// users can flip status of any dead group overlay( 1, -1 )
|
|
|
|
// confirm state
|
|
|
|
// calculate score = points in overlay for each player + captures
|
|
|
|
// render final board state with dead groups removed
|
|
|
|
// log game record
|
|
|
|
// stringify according to .sgf format
|
|
|
|
// log as text
|