pull in client-side go app
modify build script to create working .xdc
This commit is contained in:
parent
f8cfd28213
commit
ee9a6222fa
21 changed files with 1451 additions and 65 deletions
6
build.sh
Executable file
6
build.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
rm dist/webxdc-go.xdc 2> /dev/null
|
||||
# idk why zipping from project dir retains src/ prefix but it does
|
||||
# and this makes the resultant .xdc unreadable to client
|
||||
(cd src; zip -9 --recurse-paths "../dist/webxdc-go.xdc" .)
|
BIN
dist/myapp.xdc
vendored
BIN
dist/myapp.xdc
vendored
Binary file not shown.
BIN
dist/webxdc-go.xdc
vendored
Normal file
BIN
dist/webxdc-go.xdc
vendored
Normal file
Binary file not shown.
|
@ -3,7 +3,7 @@
|
|||
"webxdc-dev": "^0.9.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "zip -9 --recurse-paths dist/myapp.xdc src",
|
||||
"build": "./build.sh",
|
||||
"dev": "webxdc-dev run --port 4000 src"
|
||||
}
|
||||
}
|
||||
|
|
BIN
src/audio/go_loud.wav
Normal file
BIN
src/audio/go_loud.wav
Normal file
Binary file not shown.
BIN
src/audio/go_loud_2.wav
Normal file
BIN
src/audio/go_loud_2.wav
Normal file
Binary file not shown.
BIN
src/audio/go_loud_3.wav
Normal file
BIN
src/audio/go_loud_3.wav
Normal file
Binary file not shown.
BIN
src/audio/go_loud_4.wav
Normal file
BIN
src/audio/go_loud_4.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft.wav
Normal file
BIN
src/audio/go_soft.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_2.wav
Normal file
BIN
src/audio/go_soft_2.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_3.wav
Normal file
BIN
src/audio/go_soft_3.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_4.wav
Normal file
BIN
src/audio/go_soft_4.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_5.wav
Normal file
BIN
src/audio/go_soft_5.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_6.wav
Normal file
BIN
src/audio/go_soft_6.wav
Normal file
Binary file not shown.
BIN
src/audio/go_soft_7.wav
Normal file
BIN
src/audio/go_soft_7.wav
Normal file
Binary file not shown.
BIN
src/images/black-stones-bowl.jpg
Normal file
BIN
src/images/black-stones-bowl.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
BIN
src/images/board.png
Normal file
BIN
src/images/board.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 217 KiB |
BIN
src/images/white-stones-bowl.jpg
Normal file
BIN
src/images/white-stones-bowl.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
162
src/index.html
162
src/index.html
|
@ -1,67 +1,103 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<link rel="stylesheet" href="style.css" type="text/css" />
|
||||
<script src="webxdc.js"></script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
<form>
|
||||
<input id="input" type="text" placeholder="Message" autofocus required />
|
||||
<input type="submit" onclick="sendMsg(); return false;" value="Send" />
|
||||
<script defer src="main.js"></script>
|
||||
<title>webxdc go</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="modal">
|
||||
<div>
|
||||
<form id="menu">
|
||||
<div id="game-meta">
|
||||
<h1 class="menu-heading">webxdc 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">Komi:</span><span id="komi"></span></div>
|
||||
<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>
|
||||
<input type="range" min="0" max="9" 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">9 x 9<br>
|
||||
<input type="radio" name="board-size" value="13">13 x 13<br>
|
||||
<input type="radio" name="board-size" value="19" checked>19 x 19
|
||||
</div>
|
||||
</div>
|
||||
<div id="player-meta">
|
||||
<div data-player-meta="black">
|
||||
<h4 class="menu-heading">Black</h4>
|
||||
<span class="menu-heading">Name:</span><input type="text" name="black-name">
|
||||
<div class="menu-line">
|
||||
<span class="menu-heading">Rank:</span><span id="black-rank">9k</span><input type="button" id="black-rank-up" value="▲"><input type="button" id="black-rank-down" value="▼">
|
||||
</div>
|
||||
<div class="menu-line">
|
||||
<input type="checkbox" name="black-rank-certain"><label for="black-rank-certain">Rank Certainty</label>
|
||||
</div>
|
||||
</div>
|
||||
<div data-player-meta="white">
|
||||
<h4 class="menu-heading">White</h4>
|
||||
<span class="menu-heading">Name:</span><input type="text" name="white-name">
|
||||
<div class="line">
|
||||
<span class="menu-heading">Rank:</span><span id="white-rank">9k</span><input type="button" id="white-rank-up" value="▲"><input type="button" id="white-rank-down" value="▼">
|
||||
</div>
|
||||
<div class="menu-line">
|
||||
<input type="checkbox" name="white-rank-certain"><label for="white-rank-certain">Rank Certainty</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="game-record-space">
|
||||
<p id="instructions">
|
||||
<span class="menu-heading">Welcome to webxdc Go!</span>
|
||||
<br><br>
|
||||
If this is your first time playing Go, please see
|
||||
<a href="https://www.youtube.com/watch?v=gECcsSeRcNo" target="_blank">this great tutorial video.</a><br><br>
|
||||
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. 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.
|
||||
</p>
|
||||
<p id="game-record"></p>
|
||||
</div>
|
||||
<div id="game-update-space">
|
||||
<input type="submit" name="komi-suggest" value="Suggest Komi"><input type="submit" name="game-start" value="Start!">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<p id="output"></p>
|
||||
<p><em><small id="deviceName"></small></em></p>
|
||||
<script>
|
||||
|
||||
var El = function (tag, text) {
|
||||
var el = document.createElement(tag);
|
||||
el.innerText = text || '';
|
||||
return el;
|
||||
};
|
||||
|
||||
// handle past and future state updates
|
||||
window.webxdc.setUpdateListener(function (update) {
|
||||
var output = document.getElementById('output');
|
||||
// when appending content to an element with output.innerHTML +=
|
||||
// that content is implicitly parsed, making it possible for messages
|
||||
// to be interpreted as scripts. Creating elements directly,
|
||||
// injecting content as plain text, and appending them to the DOM
|
||||
// is a much safer practice.
|
||||
[
|
||||
El('strong', update.payload.name + ':'),
|
||||
El('span', update.payload.msg),
|
||||
El('br'),
|
||||
].forEach(function (item) {
|
||||
output.appendChild(item);
|
||||
});
|
||||
});
|
||||
|
||||
function sendMsg() {
|
||||
msg = document.getElementById("input").value;
|
||||
info = 'someone typed "' + msg + '"';
|
||||
document.getElementById("input").value = '';
|
||||
|
||||
// send new updates
|
||||
window.webxdc.sendUpdate({
|
||||
payload: {
|
||||
name: window.webxdc.selfName,
|
||||
msg,
|
||||
},
|
||||
info,
|
||||
}, info);
|
||||
}
|
||||
|
||||
(function () {
|
||||
window.deviceName.innerText = 'this is ' + window.webxdc.selfName;
|
||||
})()
|
||||
</script>
|
||||
</body>
|
||||
</div>
|
||||
</div>
|
||||
<content>
|
||||
<div id="white-pos" class="player-pos">
|
||||
<div id="white-bowl" class="bowl"><p>Pass?</p><div id="white-stone-image" class="stone-image"></div></div>
|
||||
<div id="white-player-space" class="name-space">
|
||||
<h4 id="white-player-name">Browser Go</h4>
|
||||
<div id="white-caps-space" class="caps-space"><p>Resign?</p><p id="white-caps"></p></div>
|
||||
</div>
|
||||
<div id="game-hud">
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="board-container">
|
||||
<div id="board-space">
|
||||
<table id="board">
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="black-pos" class="player-pos">
|
||||
<div id="black-bowl" class="bowl"><p>Pass?</p><div id="black-stone-image" class="stone-image"></div></div>
|
||||
<div id="black-player-space" class="name-space">
|
||||
<h4 id="black-player-name">by oxaliq</h4>
|
||||
<div id="black-caps-space" class="caps-space"><p>Resign?</p><p id="black-caps"></p></div>
|
||||
</div>
|
||||
<div id="kifu">
|
||||
</div>
|
||||
</div>
|
||||
</content>
|
||||
</body>
|
||||
</html>
|
||||
|
|
834
src/main.js
Normal file
834
src/main.js
Normal file
|
@ -0,0 +1,834 @@
|
|||
/*----- constants -----*/
|
||||
const STONES_DATA = {
|
||||
'-1': 'white',
|
||||
'0': 'none',
|
||||
'1': 'black',
|
||||
'k': 'ko'
|
||||
};
|
||||
|
||||
const DOTS_DATA = {
|
||||
'-1': 'white',
|
||||
'0': 'none',
|
||||
'1': 'black',
|
||||
'd': 'dame',
|
||||
};
|
||||
|
||||
const RANKS = [
|
||||
'30k', '29k', '28k', '27k', '26k', '25k', '24k', '23k', '22k', '21k', '20k',
|
||||
'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'
|
||||
];
|
||||
// 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 PLACEMENT_SOUNDS = {
|
||||
soft: [
|
||||
'audio/go_soft.wav',
|
||||
'audio/go_soft_2.wav',
|
||||
'audio/go_soft_3.wav',
|
||||
'audio/go_soft_4.wav',
|
||||
'audio/go_soft_5.wav',
|
||||
'audio/go_soft_6.wav',
|
||||
'audio/go_soft_7.wav'
|
||||
],
|
||||
loud: [
|
||||
'audio/go_loud.wav',
|
||||
'audio/go_loud_2.wav',
|
||||
'audio/go_loud_3.wav',
|
||||
'audio/go_loud_4.wav',
|
||||
]
|
||||
};
|
||||
|
||||
const gameState = { // pre-init values (render prior to any player input)
|
||||
winner: null,
|
||||
turn: null, // turn logic depends on handicap stones
|
||||
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: null,
|
||||
playerState: {
|
||||
bCaptures: null,
|
||||
wCaptures: null,
|
||||
bScore: null,
|
||||
wScore: null
|
||||
},
|
||||
gameMeta: { // declared at game start and not editable after
|
||||
date: null, // contains metadata
|
||||
start: false
|
||||
},
|
||||
playerMeta: { // editable during game
|
||||
b: {
|
||||
name: null,
|
||||
rank: null,
|
||||
rankCertain: false
|
||||
},
|
||||
w: {
|
||||
name: null,
|
||||
rank: null,
|
||||
rankCertain: false
|
||||
},
|
||||
},
|
||||
groups: {},
|
||||
gameRecord : []
|
||||
};
|
||||
|
||||
// index represents handicap placement for different board-sizes, eg handiPlace['9][1] = { (3, 3), (7, 7) }
|
||||
// last array in each property also used for hoshi rendering
|
||||
const HANDI_PLACE = {
|
||||
'9' : [
|
||||
0, 0,
|
||||
[[ 7, 3 ], [ 3, 7 ] ],
|
||||
[ [ 7, 7 ], [ 7, 3 ], [ 3, 7 ] ],
|
||||
[ [ 3, 3 ], [ 7, 7 ], [ 3, 7 ], [ 7, 3 ] ]
|
||||
],
|
||||
'13' : [
|
||||
0, 0,
|
||||
[ [ 4, 10 ], [ 10, 4 ] ],
|
||||
[ [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 7, 7 ], [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 7, 4 ], [ 4, 7 ], [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 7, 7 ], [ 7, 4 ], [ 4, 7 ], [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 10, 7 ], [ 7, 4 ], [ 7, 10 ], [ 4, 7 ], [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
[ [ 7, 7 ], [ 10, 7 ], [ 7, 4 ], [ 7, 10 ], [ 4, 7 ], [ 4, 4 ], [ 10, 10 ], [ 4, 10 ], [ 10, 4] ],
|
||||
],
|
||||
'19' : [
|
||||
0, 0,
|
||||
[ [ 4, 16 ], [ 16, 4 ] ],
|
||||
[ [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 10, 10 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 10, 4 ], [ 4, 10 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 10, 10 ], [ 10, 4 ], [ 4, 10 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 16, 10 ], [ 10, 4 ], [ 10, 16 ], [ 4, 10 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
[ [ 10, 10 ], [ 16, 10 ], [ 10, 4 ], [ 10, 16 ], [ 4, 10 ], [ 4, 4 ], [ 16, 16 ], [ 4, 16 ], [ 16, 4] ],
|
||||
]
|
||||
};
|
||||
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.pos = [ x, y ];
|
||||
this.stone = 0; // this is where move placement will go 0, 1, -1, also contains ko: 'k'
|
||||
this.legal = true;
|
||||
this.territory = null;
|
||||
this.capturing = [];
|
||||
this.groupMembers = [ this ];
|
||||
this.neighbors = {
|
||||
top: {},
|
||||
btm: {},
|
||||
lft: {},
|
||||
rgt: {}
|
||||
};
|
||||
this.neighbors.top = x > 1 ? [ x - 1, y ] : null;
|
||||
this.neighbors.btm = x < gameState.boardSize ? [ x + 1, y ] : null;
|
||||
this.neighbors.rgt = y < gameState.boardSize ? [ x, y + 1 ] : null;
|
||||
this.neighbors.lft = y > 1 ? [ x, y - 1 ] : null;
|
||||
}
|
||||
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 ) {
|
||||
neighborsArr.push(boardState.find(pt => pt.pos[0] === nbr[0] && pt.pos[1] === nbr[1]));
|
||||
}
|
||||
};
|
||||
// returns array of existing neighbors to calling function
|
||||
return neighborsArr;
|
||||
}
|
||||
getLiberties = () => {
|
||||
let neighborsArr = this.checkNeighbors().filter(pt => pt.stone === 0);
|
||||
return neighborsArr;
|
||||
}
|
||||
joinGroup = () => {
|
||||
this.groupMembers = this.groupMembers.filter(grp => grp.stone === this.stone);
|
||||
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));
|
||||
for (let grpMem in this.groupMembers) {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
checkCapture = () => {
|
||||
let opps = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn * -1
|
||||
&& nbr.getLiberties().every(liberty => liberty === this));
|
||||
for (let opp of opps) {
|
||||
if (opp.groupMembers.every(stone => stone.getLiberties().filter(liberty => liberty !== this).length === 0)) {
|
||||
this.capturing = this.capturing.concat(opp.groupMembers);
|
||||
};
|
||||
}
|
||||
this.capturing = Array.from(new Set(this.capturing));
|
||||
return this.capturing;
|
||||
}
|
||||
checkGroup = () => { // liberty is true when called by move false when called by check Capture
|
||||
let frns = this.checkNeighbors().filter(nbr => nbr.stone === gameState.turn);
|
||||
for (let frn in frns) {
|
||||
if (frns[frn].groupMembers.find(stone => stone.getLiberties().find(liberty => liberty !== this))) return true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cycleTerritory = () => {
|
||||
if (this.stone) {
|
||||
this.groupMembers.forEach(pt => pt.territory = pt.territory * -1);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*----- app's state (variables) -----*/
|
||||
let boardState = [];
|
||||
|
||||
|
||||
/*----- cached element references -----*/
|
||||
const whiteCapsEl = document.getElementById('white-caps');
|
||||
const blackCapsEl = document.getElementById('black-caps');
|
||||
const modalEl = document.querySelector('.modal');
|
||||
const komiSliderEl = document.querySelector('input[name="komi-slider"]');
|
||||
const handiSliderEl = document.querySelector('input[name="handicap-slider"]');
|
||||
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');
|
||||
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');
|
||||
const gameHudEl = document.querySelector('#game-hud p');
|
||||
const dateEl = document.getElementById('date');
|
||||
const boardSizeEl = document.getElementById('board-size-radio');
|
||||
const komiDisplayEl = document.getElementById('komi');
|
||||
const handiDisplayEl = document.getElementById('handicap');
|
||||
const boardEl = document.querySelector('#board tbody');
|
||||
const gameStartEl = document.querySelector('input[name="game-start"]');
|
||||
const komiSuggestEl = document.querySelector('input[name="komi-suggest"]');
|
||||
const soundPlayerEl = new Audio();
|
||||
const boardSizeRadioEls = [
|
||||
document.querySelectorAll('input[name="board-size"')[0],
|
||||
document.querySelectorAll('input[name="board-size"')[1],
|
||||
document.querySelectorAll('input[name="board-size"')[2]
|
||||
];
|
||||
|
||||
/*----- event listeners -----*/
|
||||
document.getElementById('board').addEventListener('mousemove', hoverPreview);
|
||||
document.getElementById('board').addEventListener('click', clickBoard);
|
||||
document.getElementById('white-bowl').addEventListener('click',clickPass);
|
||||
document.getElementById('black-bowl').addEventListener('click',clickPass);
|
||||
document.getElementById('kifu').addEventListener('click', clickMenuOpen);
|
||||
document.getElementById('white-caps-space').addEventListener('click', clickResign);
|
||||
document.getElementById('black-caps-space').addEventListener('click', clickResign);
|
||||
modalEl.addEventListener('click', clickCloseMenu);
|
||||
komiSliderEl.addEventListener('change', changeUpdateKomi);
|
||||
handiSliderEl.addEventListener('change', changeUpdateHandicap);
|
||||
document.getElementById('player-meta').addEventListener('click', clickUpdatePlayerMeta);
|
||||
document.getElementById('player-meta').addEventListener('change', clickUpdatePlayerMeta);
|
||||
komiSuggestEl.addEventListener('click', clickKomiSuggestion);
|
||||
gameHudEl.addEventListener('click', clickGameHud);
|
||||
boardSizeEl.addEventListener('click', clickBoardSize);
|
||||
gameStartEl.addEventListener('click', clickSubmitStart);
|
||||
|
||||
/*----- FUNCTIONS ----------------------------------*/
|
||||
/*----- init functions -----*/
|
||||
init();
|
||||
|
||||
function init() {
|
||||
gameState.gameMeta.date = getDate();
|
||||
gameState.komi = 5.5;
|
||||
gameState.handicap = 0;
|
||||
gameState.winner = null;
|
||||
gameState.pass = null;
|
||||
gameState.boardSize = 19;
|
||||
gameState.playerState.bCaptures = 0;
|
||||
gameState.playerMeta.b.rank = 21;
|
||||
gameState.playerState.wCaptures = 0;
|
||||
gameState.playerMeta.w.rank = 21;
|
||||
gameState.gameRecord = [];
|
||||
boardState = [];
|
||||
gameState.gameMeta.start = false;
|
||||
startMenu();
|
||||
};
|
||||
|
||||
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)}`;
|
||||
}
|
||||
|
||||
function startMenu() {
|
||||
modalEl.style.visibility = 'visible';
|
||||
renderMenu();
|
||||
}
|
||||
function clickSubmitStart(evt) {
|
||||
if (gameState.gameMeta.start) return init();
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
gameState.playerMeta.b.name = blackNameInputEl.value || 'black';
|
||||
gameState.playerMeta.w.name = whiteNameInputEl.value || 'white';
|
||||
modalEl.style.visibility = 'hidden';
|
||||
return initGame();
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
gameState.winner = null;
|
||||
gameState.pass = null;
|
||||
gameState.turn = gameState.handicap ? -1 : 1;
|
||||
gameState.gameMeta.start = true;
|
||||
initBoard();
|
||||
renderBoardInit();
|
||||
renderGame();
|
||||
}
|
||||
|
||||
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() {
|
||||
if (gameState.handicap < 2) return;
|
||||
HANDI_PLACE[gameState.boardSize][gameState.handicap].forEach(pt => {
|
||||
if (!pt) return;
|
||||
let handi = findPointFromIdx(pt);
|
||||
handi.stone = 1;
|
||||
handi.joinGroup();
|
||||
});
|
||||
}
|
||||
|
||||
/*----- meta functions -----*/
|
||||
|
||||
// include save function
|
||||
// unpack existing gamerecords
|
||||
// globalGameRecord = JSON.parse(localStorage.getItem('browser-go-saved-games'));
|
||||
// append current game record to globalGameRecord - globalGameRecord.gameName = gameState
|
||||
// stringify all stored gamerecords JSON.stringify(globalGameRecord)
|
||||
// localStorage.clear()
|
||||
// localStorage.setItem('browser-go-saved-games')
|
||||
|
||||
// load function
|
||||
// unpack existing gamerecords - globalGameRecord = JSON.parse(localStorage.getItem('browser-))
|
||||
// display each game record name - Object.keys(globalGameRecord).forEach( ... )
|
||||
// upon user selection initgame from gameState meta data
|
||||
|
||||
// undo last move
|
||||
// set up gameState var to track number of 'misclicks'
|
||||
// after every move JSON.stringify(localGameRecord)
|
||||
// localStorage.clear()
|
||||
// localStorage.setItem()
|
||||
// on undo click - load JSON.parse(localStorage.getItem())
|
||||
// reset gameState
|
||||
|
||||
// plus general purpose
|
||||
|
||||
function findPointFromIdx(arr) {
|
||||
return pointFromIdx = boardState.find( point => point.pos[0] === arr[0] && point.pos[1] === arr[1] );
|
||||
}
|
||||
|
||||
function changeUpdateKomi(evt) {
|
||||
evt.stopPropagation();
|
||||
komiDisplayEl.textContent = komiSliderEl.value;
|
||||
gameState.komi = komiSliderEl.value;
|
||||
renderMenu();
|
||||
}
|
||||
|
||||
function changeUpdateHandicap(evt) {
|
||||
evt.stopPropagation();
|
||||
handiDisplayEl.textContent = handiSliderEl.value !== 1 ? handiSliderEl.value : 0;
|
||||
gameState.handicap = handiSliderEl.value !== 1 ? handiSliderEl.value : 0;
|
||||
renderMenu();
|
||||
}
|
||||
|
||||
function clickUpdatePlayerMeta(evt) {
|
||||
evt.stopPropagation();
|
||||
if (evt.target.id) {
|
||||
switch (evt.target.id) {
|
||||
case 'black-rank-up':
|
||||
if (gameState.playerMeta.b.rank < RANKS.length - 1) gameState.playerMeta.b.rank++;
|
||||
break;
|
||||
case 'black-rank-down':
|
||||
if (gameState.playerMeta.b.rank > 0) gameState.playerMeta.b.rank--;
|
||||
break;
|
||||
case 'white-rank-up':
|
||||
if (gameState.playerMeta.w.rank < RANKS.length - 1) gameState.playerMeta.w.rank++;
|
||||
break;
|
||||
case 'white-rank-down':
|
||||
if (gameState.playerMeta.w.rank > 0) 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;
|
||||
renderMenu();
|
||||
|
||||
}
|
||||
|
||||
function clickBoardSize(evt) {
|
||||
evt.stopPropagation();
|
||||
gameState.boardSize = boardSizeRadioEls.find(el => el.checked === true).value;
|
||||
renderMenu();
|
||||
}
|
||||
|
||||
function clickKomiSuggestion(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
if (gameState.gameMeta.start) {
|
||||
gameState.playerMeta.b.name = blackNameInputEl.value || 'black';
|
||||
gameState.playerMeta.w.name = whiteNameInputEl.value || 'white';
|
||||
modalEl.style.visibility = 'hidden';
|
||||
return;
|
||||
}
|
||||
let sugg = KOMI_REC[gameState.boardSize][Math.abs(gameState.playerMeta.w.rank - gameState.playerMeta.b.rank)];
|
||||
let handi = HANDI_REC[gameState.boardSize][Math.abs(gameState.playerMeta.w.rank - gameState.playerMeta.b.rank)];
|
||||
gameState.komi = sugg;
|
||||
gameState.handicap = handi;
|
||||
renderMenu();
|
||||
}
|
||||
|
||||
function clickCloseMenu(evt) {
|
||||
evt.stopPropagation();
|
||||
if (evt.target.className === "modal" && gameState.gameMeta.start) modalEl.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/*----- gameplay functions -----*/
|
||||
|
||||
function clickBoard(evt) {
|
||||
evt.stopPropagation();
|
||||
if (gameState.pass > 1 || gameState.winner) return editTerritory(evt);
|
||||
// checks for placement and pushes to cell
|
||||
let placement = [ parseInt(evt.target.closest('td').id.split('-')[0]), parseInt(evt.target.closest('td').id.split('-')[1]) ];
|
||||
let point = findPointFromIdx(placement);
|
||||
//checks that this placement was marked as legal
|
||||
if ( !checkLegal(point) ) return null;
|
||||
clearKo();
|
||||
clearPass();
|
||||
resolveCaptures(point);
|
||||
point.stone = gameState.turn;
|
||||
point.joinGroup();
|
||||
playSound(point);
|
||||
clearCaptures();
|
||||
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: ${point.pos}`);
|
||||
gameState.turn*= -1;
|
||||
renderGame();
|
||||
}
|
||||
|
||||
function clearKo() {
|
||||
for (let point in boardState) {
|
||||
point = boardState[point];
|
||||
point.stone = point.stone === 'k' ? 0 : point.stone;
|
||||
}
|
||||
}
|
||||
|
||||
function clearPass() {
|
||||
gameState.pass = 0;
|
||||
}
|
||||
|
||||
function resolveCaptures(point) {
|
||||
if(!point.capturing.length) {
|
||||
point.checkCapture();
|
||||
}
|
||||
if(point.capturing.length) {
|
||||
point.capturing.forEach(cap => {
|
||||
gameState.playerState[gameState.turn > 0 ? 'bCaptures' : 'wCaptures']++;
|
||||
cap.stone = checkKo(point) ? 'k' : 0;
|
||||
cap.groupMembers = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkLegal(point) {
|
||||
clearOverlay();
|
||||
// first step in logic: is point occupied, or in ko
|
||||
if (point.stone) return false;
|
||||
// if point is not empty check if liberties
|
||||
if (point.getLiberties().length < 1) {
|
||||
//if no liberties check if enemy group has liberties
|
||||
if ( point.checkCapture().length ) return true;
|
||||
//if neighboring point is not empty check if friendly group is alive
|
||||
if (point.checkGroup()) return true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearOverlay() {
|
||||
for (let point in boardState) {
|
||||
point = boardState[point];
|
||||
point.legal = false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
function checkKo(point) { // currently prevents snapback // capturing point has no liberties and is only capturing one stone and
|
||||
if (!point.getLiberties().length && point.capturing.length === 1 && !point.checkNeighbors().some(stone => stone.stone === gameState.turn)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function playSound(point) { //plays louder sounds for tenuki and for captures
|
||||
if (point.capturing.length || (gameState.boardSize === 19 && gameState.gameRecord.length > 90 && point.groupMembers.length === 1)
|
||||
|| (gameState.boardSize === 13 && gameState.gameRecord.length > 40 && point.groupMembers.length === 1)) {
|
||||
soundPlayerEl.src = PLACEMENT_SOUNDS.loud[Math.floor(Math.random() * 5)];
|
||||
soundPlayerEl.play();
|
||||
} else {
|
||||
soundPlayerEl.src = PLACEMENT_SOUNDS.soft[Math.floor(Math.random() * 8)];
|
||||
soundPlayerEl.play();
|
||||
}
|
||||
}
|
||||
|
||||
function clearCaptures() {
|
||||
for (let point in boardState) {
|
||||
point = boardState[point];
|
||||
point.capturing = [];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return renderGame();
|
||||
}
|
||||
|
||||
function clickMenuOpen() {
|
||||
modalEl.style.visibility = 'visible';
|
||||
renderMenu();
|
||||
}
|
||||
|
||||
function hoverPreview(evt) {
|
||||
evt.stopPropagation();
|
||||
if (gameState.pass > 1 || gameState.winner) return;
|
||||
// renders preview stone if move is legal
|
||||
let hover = evt.target.closest('td').id.split('-');
|
||||
hover = [parseInt(hover[0]), parseInt(hover[1])];
|
||||
let point = findPointFromIdx(hover);
|
||||
if (checkLegal(point)) {
|
||||
point.legal = true; // legal
|
||||
renderPreview(point);
|
||||
}
|
||||
}
|
||||
|
||||
/*----- render functions ----------------------*/
|
||||
/*----- meta render -----*/
|
||||
|
||||
function renderMenu() {
|
||||
dateEl.textContent = gameState.gameMeta.date;
|
||||
if (gameState.gameMeta.start) {
|
||||
gameStartEl.value = "New Game";
|
||||
komiSuggestEl.value = "Close Menu";
|
||||
}
|
||||
renderKomiSlider();
|
||||
renderHandiSlider();
|
||||
renderBoardSizeRadio();
|
||||
blackRankEl.textContent = RANKS[gameState.playerMeta.b.rank];
|
||||
whiteRankEl.textContent = RANKS[gameState.playerMeta.w.rank];
|
||||
}
|
||||
|
||||
function renderKomiSlider() {
|
||||
komiSliderEl.value = gameState.komi;
|
||||
komiDisplayEl.textContent = gameState.komi;
|
||||
if (gameState.gameMeta.start) komiSliderEl.setAttribute('disabled', true);
|
||||
}
|
||||
|
||||
function renderHandiSlider() {
|
||||
handiSliderEl.value = gameState.handicap;
|
||||
handiDisplayEl.textContent = gameState.handicap;
|
||||
if (gameState.gameMeta.start) handiSliderEl.setAttribute('disabled', true);
|
||||
}
|
||||
|
||||
function renderBoardSizeRadio() {
|
||||
boardSizeEl.value = gameState.boardSize;
|
||||
if (gameState.gameMeta.start) boardSizeRadioEls.forEach(el => el.setAttribute('disabled', true));
|
||||
}
|
||||
|
||||
/*----- game render -----*/
|
||||
|
||||
function renderBoardInit() {
|
||||
renderClearBoard();
|
||||
renderBoardTableRows();
|
||||
renderHoshi();
|
||||
renderBoardTableStyle();
|
||||
}
|
||||
|
||||
function renderClearBoard() {
|
||||
boardEl.innerHTML = '';
|
||||
boardEl.classList = '';
|
||||
}
|
||||
|
||||
function renderBoardTableRows() {
|
||||
let i = 1;
|
||||
while (i <= gameState.boardSize) {
|
||||
let tableRow = document.createElement('tr');
|
||||
tableRow.id = `row-${i}`;
|
||||
tableRow.innerHTML = renderBoardTableCells(i);
|
||||
boardEl.appendChild(tableRow);
|
||||
i++;
|
||||
}
|
||||
boardEl.classList = `board-${gameState.boardSize}x${gameState.boardSize}`;
|
||||
}
|
||||
|
||||
// iterator ^ becomes x index ̌
|
||||
function renderBoardTableCells(x) {
|
||||
let y = 1;
|
||||
let cells = '';
|
||||
while (y <= gameState.boardSize) {
|
||||
let newCell = `
|
||||
<td id="${x}-${y}">
|
||||
<div class="stone">
|
||||
<div class="dot"></div>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
cells = cells + newCell;
|
||||
y++;
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
function renderHoshi() { // gets hoshi placement from handiplace const and adds a class to dot elem
|
||||
let hoshi = HANDI_PLACE[gameState.boardSize].slice(-1);
|
||||
hoshi = hoshi[0];
|
||||
hoshi.forEach(star => {
|
||||
let boardPt = document.getElementById(`${star[0]}-${star[1]}`).getElementsByClassName('stone')[0];
|
||||
boardPt.className += ' hoshi';
|
||||
});
|
||||
}
|
||||
|
||||
function renderBoardTableStyle() {
|
||||
document.querySelectorAll('#board-space td[id^="1-"]').forEach(pt => pt.className += 'top ');
|
||||
document.querySelectorAll(`#board-space td[id^="${gameState.boardSize}-"]`).forEach(pt => pt.className += 'btm ');
|
||||
document.querySelectorAll('#board-space td[id$="-1"]').forEach(pt => pt.className += 'lft ');
|
||||
document.querySelectorAll(`#board-space td[id$="-${gameState.boardSize}"]`).forEach(pt => pt.className += 'rgt ');
|
||||
}
|
||||
|
||||
function renderGame() {
|
||||
if (gameState.winner || gameState.pass > 1) {
|
||||
renderTerritory();
|
||||
renderMessage();
|
||||
}
|
||||
blackNameDisplayEl.textContent =
|
||||
`${gameState.playerMeta.b.name},
|
||||
${RANKS[gameState.playerMeta.b.rank]}`;
|
||||
whiteNameDisplayEl.textContent =
|
||||
`${gameState.playerMeta.w.name},
|
||||
${RANKS[gameState.playerMeta.w.rank]}`;
|
||||
gameState.gameRecord.length ? renderTurn() : renderFirstTurn();
|
||||
renderBoardState();
|
||||
renderCaps();
|
||||
}
|
||||
|
||||
function renderFirstTurn() {
|
||||
document.getElementById(`${STONES_DATA[gameState.turn]}-bowl`).toggleAttribute('data-turn');
|
||||
}
|
||||
|
||||
function renderTurn() {
|
||||
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'));
|
||||
}
|
||||
|
||||
function renderBoardState() {
|
||||
boardState.forEach(val => {
|
||||
let stoneElem = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).getElementsByClassName('stone')[0];
|
||||
stoneElem.setAttribute("data-stone", STONES_DATA[val.stone]);
|
||||
});
|
||||
}
|
||||
|
||||
function renderCaps() {
|
||||
blackCapsEl.textContent = gameState.playerState.bCaptures;
|
||||
whiteCapsEl.textContent = gameState.playerState.wCaptures;
|
||||
}
|
||||
|
||||
function renderPreview(hoverPoint) {
|
||||
boardState.forEach(val => {
|
||||
let dot = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).getElementsByClassName('dot')[0];
|
||||
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]);
|
||||
});
|
||||
}
|
||||
|
||||
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.style.visibility = 'visible';
|
||||
gameHudEl.style.cursor = 'default';
|
||||
gameHudEl.textContent = `${gameState.playerMeta[gameState.winner === 1 ? 'b' : 'w'].name || STONES_DATA[gameState.winner]} won by ${Math.abs(gameState.playerState.wScore - gameState.playerState.bScore)}`;
|
||||
} else if (gameState.pass > 1) {
|
||||
gameHudEl.style.visibility = 'visible';
|
||||
gameHudEl.textContent = 'finalize game';
|
||||
} else {
|
||||
gameHudEl.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
function renderTerritory() {
|
||||
boardState.forEach(val => {
|
||||
let stoneElem = document.getElementById(`${val.pos[0]}-${val.pos[1]}`).getElementsByClassName('dot')[0];
|
||||
stoneElem.setAttribute("data-dot", DOTS_DATA[val.territory]);
|
||||
});
|
||||
}
|
||||
|
||||
/*----- endgame functions -----*/
|
||||
|
||||
function clickResign(evt) {
|
||||
if (evt.target.parentElement.id === `${STONES_DATA[gameState.turn]}-caps-space`) playerResign();
|
||||
}
|
||||
|
||||
function playerResign() {
|
||||
// display confirmation message
|
||||
gameState.pass = -1;
|
||||
gameHudEl.style.visibility = "visible";
|
||||
gameHudEl.textContent = "Do you want to resign?";
|
||||
}
|
||||
|
||||
function clickGameHud() {
|
||||
if (gameState.pass > 1 && !gameState.winner) calculateWinner();
|
||||
if (gameState.pass < 0) confirmResign();
|
||||
}
|
||||
|
||||
function confirmResign() {
|
||||
gameState.gameRecord.push(`${STONES_DATA[gameState.turn]}: resign`);
|
||||
gameState.winner = STONES_DATA[gameState.turn * -1];
|
||||
endGame();
|
||||
}
|
||||
|
||||
function endGame() {
|
||||
if (!gameState.winner) endGameSetTerritory();
|
||||
renderGame();
|
||||
}
|
||||
|
||||
function editTerritory(evt) {
|
||||
let placement = [ parseInt(evt.target.closest('td').id.split('-')[0]), parseInt(evt.target.closest('td').id.split('-')[1]) ];
|
||||
let point = findPointFromIdx(placement);
|
||||
point.cycleTerritory();
|
||||
renderGame();
|
||||
}
|
||||
|
||||
function calculateWinner() {
|
||||
let whiteTerritory = boardState.reduce((acc, pt) => {
|
||||
if (pt.territory === -1 && pt.stone !== -1) {
|
||||
return acc = acc + (pt.stone === 0 ? 1 : 2);
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
let blackTerritory = boardState.reduce((acc, pt) => {
|
||||
if (pt.territory === 1 && pt.stone !== 1) {
|
||||
return acc + (pt.stone === 0 ? 1 : 2);
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
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)}`);
|
||||
renderGame();
|
||||
}
|
||||
|
||||
function endGameSetTerritory() {
|
||||
let emptyPoints = boardState.filter(pt => !pt.stone);
|
||||
emptyPoints.forEach(pt => pt.joinGroup());
|
||||
emptyPointSetTerritory(emptyPoints);
|
||||
groupsMarkDeadLive();
|
||||
// reviseTerritory();
|
||||
}
|
||||
|
||||
function groupsMarkDeadLive() {
|
||||
boardState.filter(pt => (!pt.territory ))
|
||||
.forEach(pt => {
|
||||
if (pt.groupMembers.some(grpMem => {
|
||||
return grpMem.checkNeighbors().some(nbr => nbr.territory === pt.stone && nbr.stone === 0);
|
||||
})) {
|
||||
pt.groupMembers.forEach(grpMem => grpMem.territory = pt.stone);
|
||||
}
|
||||
});
|
||||
boardState.filter(pt => (!pt.territory)).forEach(pt => {
|
||||
pt.territory = pt.stone * -1;
|
||||
});
|
||||
}
|
||||
|
||||
function emptyPointSetTerritory(emptyPoints) {
|
||||
emptyPoints.filter(pt => !pt.territory && pt.checkNeighbors().filter(nbr => nbr.stone !== 0))
|
||||
.forEach(pt => {
|
||||
let b = pt.groupMembers.reduce((acc, grpMem) => {
|
||||
let bNbr = grpMem.checkNeighbors().filter(nbr => nbr.stone === 1).length;
|
||||
return acc + bNbr;
|
||||
}, 0);
|
||||
let w = pt.groupMembers.reduce((acc, grpMem) => {
|
||||
let wNbr = grpMem.checkNeighbors().filter(nbr => nbr.stone === -1).length;
|
||||
return acc + wNbr;
|
||||
}, 0);
|
||||
pt.groupMembers.forEach(grp => {
|
||||
if (Math.abs(b - w) < 4 && b && w) grp.territory = 'd';
|
||||
else grp.territory = b > w ? 1 : -1;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function reviseTerritory() {
|
||||
// count eyes
|
||||
// for each group marked live get liberties
|
||||
//
|
||||
}
|
510
src/style.css
Normal file
510
src/style.css
Normal file
|
@ -0,0 +1,510 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 12px;
|
||||
background: radial-gradient(farthest-corner at 55% 40%, rgb(150, 200, 220) 0%, rgb(97, 166, 194) 65%, rgb(70,100,120) 90%, rgb(40, 80, 90) 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
height: vh;
|
||||
width: vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
/* display: none; */
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
}
|
||||
|
||||
#menu {
|
||||
position: relative;
|
||||
background-color: rgb(250, 2250, 255, 0.9);
|
||||
padding: 1vmin;
|
||||
display: grid;
|
||||
grid-template-columns: 60vw;
|
||||
grid-template-rows: auto auto 60vw auto;
|
||||
grid-template-areas:
|
||||
"meta"
|
||||
"player"
|
||||
"record"
|
||||
"submit";
|
||||
font-family: 'La Belle Aurore', cursive;
|
||||
min-height: 0;
|
||||
max-height: 100vh;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
#menu .menu-subblock {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: .25em;
|
||||
}
|
||||
|
||||
#game-meta {
|
||||
grid-area: meta;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-heading, content, #instructions, div[data-player-meta] label {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 140%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
margin: .25em;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
#player-meta {
|
||||
grid-area: player;
|
||||
display: flex;
|
||||
justify-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#player-meta span[id$="rank"] {
|
||||
margin: 0 2em;
|
||||
}
|
||||
|
||||
#player-meta input[type="button"] {
|
||||
margin: .25em;
|
||||
}
|
||||
|
||||
#player-meta * .menu-line {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: baseline;
|
||||
justify-items: flex-start;
|
||||
}
|
||||
|
||||
div[data-player-meta] {
|
||||
width: 100%;
|
||||
justify-self: stretch;
|
||||
}
|
||||
|
||||
div[data-player-meta] input[type="text"] {
|
||||
width: 90%;
|
||||
justify-self: stretch;
|
||||
}
|
||||
|
||||
div[data-player-meta] input {
|
||||
margin: 1vmin;
|
||||
}
|
||||
|
||||
#confirm {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
div[data-player-meta] label {
|
||||
margin: .25em;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
#game-record-space {
|
||||
grid-area: record;
|
||||
}
|
||||
|
||||
#instructions {
|
||||
padding: .5em;
|
||||
line-height: 1.5;
|
||||
overflow: scroll;
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#instructions, #game-record{
|
||||
border: 2px solid black;
|
||||
height: 1;
|
||||
}
|
||||
|
||||
#game-record {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#game-update-space {
|
||||
grid-area: submit;
|
||||
margin: .5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
content {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: space-between !important;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.player-pos {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-around;
|
||||
flex: 5;
|
||||
height: 9vmin;
|
||||
}
|
||||
|
||||
#game-hud p {
|
||||
font-size: 130%;
|
||||
width: 100%;
|
||||
order: 0;
|
||||
width: 10vh;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
padding: 1vh;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.player-pos#black-pos {
|
||||
align-items: flex-start;
|
||||
flex-direction: row-reverse;
|
||||
justify-self: flex-end;
|
||||
}
|
||||
|
||||
#kifu {
|
||||
order: 0;
|
||||
height: 10vh;
|
||||
width: 8vh;
|
||||
background-color: #FFF;
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
.bowl {
|
||||
order: -1;
|
||||
margin: 4vh;
|
||||
height: 15vh;
|
||||
width: 15vh;
|
||||
/* border: solid black; */
|
||||
border-radius: 50%;
|
||||
background-color: rgb(116, 48, 17);
|
||||
background: radial-gradient(farthest-corner at 48% 54%, rgba(30, 5, 0, 0.25) 0%, rgba(30, 5, 0, 0.45) 2%, rgba(30, 5, 0, 0.75) 32%, rgb(0,0,0,0.85)35%, rgb(116,48,17) 48%, rgb(140, 60, 40) 52%, rgb(100, 40, 5) 55%, rgb(116, 48, 17) 58%, rgb(140,60,40) 65%, rgb(100, 40, 5) 80%, rgb(80, 20, 0) 90%);
|
||||
box-shadow: -1vmin 2vmin 1.5vmin rgba(83, 53, 35, 0.61);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bowl p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stone-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#white-stone-image {
|
||||
background-image: url(../images/white-stones-bowl.jpg);
|
||||
}
|
||||
|
||||
#black-stone-image {
|
||||
background-image: url(../images/black-stones-bowl.jpg);
|
||||
}
|
||||
|
||||
.bowl[data-turn]:hover p {
|
||||
display: block;
|
||||
color: #FFF;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
padding: .5em;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.bowl[data-turn] {
|
||||
box-shadow: 0 0 3vh 3vh rgb(255, 175, 2);
|
||||
}
|
||||
|
||||
.caps-space {
|
||||
color: #FFF;
|
||||
margin: 1vh;
|
||||
height: 10vh;
|
||||
width: 10vh;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(farthest-side at 49% 52%, rgb(150, 75, 50) 0%, rgb(116,48,17) 35%, rgb(116,48,17) 64%, rgb(80, 20, 0) 65%, rgb(175, 140, 95) 70%, rgb(120, 50, 40) 80%, rgb(80, 20, 0) 95%, rgb(175, 140, 95) 100%);
|
||||
box-shadow: -0.5vmin 1vmin 1vmin rgba(83, 53, 35, 0.61);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.caps-space :first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bowl[data-turn] + .name-space .caps-space:hover :first-child {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
padding: .5em;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.name-space {
|
||||
order: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name-space h4 {
|
||||
font-size: 120%;
|
||||
color: rgb(255,240,230);
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
padding: 0.25em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#board-container {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-areas: 100%;
|
||||
grid-template-columns: 100%;
|
||||
grid-template-areas:
|
||||
"board";
|
||||
}
|
||||
|
||||
#board-space tbody {
|
||||
background: radial-gradient(farthest-corner at 55% 40%, rgba(244, 230, 120, 0.75) 0%, rgba(234, 178, 78, 0.5) 65%, rgba(200, 160, 90, 0.45) 90%, rgba(200, 140, 90, 0.45) 100%);
|
||||
background-size: cover;
|
||||
padding: 1vmin;
|
||||
}
|
||||
|
||||
#board-space {
|
||||
grid-area: board;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-image: url(../images/board.png);
|
||||
z-index: 1;
|
||||
box-shadow: -2vmin 4vmin 3vmin rgba(145, 92, 23, 0.5);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
#board-space table {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#board-space td {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
border-radius: 50% solid black;
|
||||
color: black;
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#board-space .board-9x9 td {
|
||||
height: 9vmin;
|
||||
width: 9vmin;
|
||||
}
|
||||
|
||||
#board-space .board-13x13 td {
|
||||
height: 7vmin;
|
||||
width: 7vmin;
|
||||
}
|
||||
|
||||
#board-space .board-19x19 td {
|
||||
height: 5vmin;
|
||||
width: 5vmin;
|
||||
}
|
||||
|
||||
#board-space td.top {
|
||||
background: conic-gradient( rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%);
|
||||
}
|
||||
|
||||
#board-space td.btm {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
}
|
||||
|
||||
#board-space td.lft {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
}
|
||||
|
||||
#board-space td.rgt {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
}
|
||||
|
||||
#board-space td.top.lft {
|
||||
background: conic-gradient( rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%);
|
||||
}
|
||||
|
||||
#board-space td.top.rgt {
|
||||
background: conic-gradient( rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76% );
|
||||
}
|
||||
|
||||
#board-space td.btm.lft {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
}
|
||||
|
||||
#board-space td.btm.rgt {
|
||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
||||
}
|
||||
|
||||
.stone.hoshi {
|
||||
background: radial-gradient(circle farthest-corner at center, #000 0%, #000 14%, rgba(0,0,0,0) 15%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
td .stone {
|
||||
width: 85%;
|
||||
height: 85%;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
td .stone .dot {
|
||||
width: 35%;
|
||||
height: 35%;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td .stone[data-stone="ko"] {
|
||||
background-color: transparent;
|
||||
border: 1vmin solid rgba(200,20,50,0.8);
|
||||
border-radius: 0%;
|
||||
}
|
||||
td .stone[data-stone="white"] {
|
||||
background: radial-gradient(farthest-side at 55% 40%, white 0%, rgb(200,200,200) 65%, rgb(100,100,100) 90%, rgb(68, 50, 0) 100%);
|
||||
box-shadow: -.25vmin .5vmin .5vmin rgba(145, 92, 23, 0.5);
|
||||
}
|
||||
td .stone[data-stone="black"] {
|
||||
background-color: black;
|
||||
background: radial-gradient(farthest-side at 55% 40%, rgb(220,220,220) 0%, rgb(60,60,60) 45%, rgb(15,15,15) 90%, rgb(5, 5, 0) 100%);
|
||||
box-shadow: -.25vmin .5vmin .5vmin rgba(145, 92, 23, 0.75);
|
||||
}
|
||||
|
||||
td .stone[data-stone="none"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
td .dot[data-dot="white"] {
|
||||
background-color: white;
|
||||
}
|
||||
td .dot[data-dot="black"] {
|
||||
background-color: black;
|
||||
}
|
||||
td .dot[data-dot="none"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
td .dot[data-dot="dame"] {
|
||||
background-color: purple;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 591px) {
|
||||
|
||||
#player-meta {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div[data-player-meta] {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
|
||||
@media only screen and (min-width: 500px) {
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.player-pos {
|
||||
height: 14vh;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 590px) {
|
||||
|
||||
#board-space .board-19x19 td {
|
||||
height: 3.5vh;
|
||||
width: 3.5vh;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 570px) {
|
||||
|
||||
#board-space .board-9x9 td {
|
||||
height: 7.5vh;
|
||||
width: 7.5vh;
|
||||
}
|
||||
|
||||
#board-space .board-13x13 td {
|
||||
height: 5vh;
|
||||
width: 5vh;
|
||||
}
|
||||
|
||||
.bowl {
|
||||
order: -1;
|
||||
margin: 3vh;
|
||||
height: 10vh;
|
||||
width: 10vh;
|
||||
}
|
||||
|
||||
.caps-space {
|
||||
color: #FFF;
|
||||
margin: 2vh;
|
||||
height: 7vh;
|
||||
width: 7vh;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
|
||||
content {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
#menu {
|
||||
grid-template-columns: 50vw;
|
||||
grid-template-rows: auto auto 50vw auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 900px) {
|
||||
|
||||
#menu {
|
||||
grid-template-columns: 55vh;
|
||||
grid-template-rows: auto auto 55vh auto;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue