1 Star 0 Fork 5

吃饭不如学习 / alphaGo

forked from changjiuxiong / alphaGo 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
jgoboard-3.4.2.js 64.48 KB
一键复制 编辑 原始数据 按行查看 历史
changjiuxiong 提交于 2020-07-22 13:23 . first commit
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248
/* jgoboard by Joonas Pihlajamaa, slightly modified by Christopher Clark */
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
//var request = require('superagent');
var C = require('./constants');
/**
* Automatic div module.
* @module autodiv
*/
function parseMarkup(str) {
var lines = str.split('\n'), data = [];
// Handle div contents as diagram contents
for(var i = 0, len = lines.length; i < len; ++i) {
var elems = [], line = lines[i];
for(var j = 0, len2 = line.length; j < len2; ++j) {
switch(line[j]) {
case '.':
elems.push({type: C.CLEAR}); break;
case 'o':
elems.push({type: C.WHITE}); break;
case 'x':
elems.push({type: C.BLACK}); break;
case ' ':
break; // ignore whitespace
default: // assume marker
if(!elems.length) break; // no intersection yet
// Append to mark so x123 etc. are possible
if(elems[elems.length - 1].mark)
elems[elems.length - 1].mark += line[j];
else
elems[elems.length - 1].mark = line[j];
}
}
if(elems.length) data.push(elems);
}
return data;
}
// Array of loaded boards
//var boards = [];
// Available attributes:
// data-jgostyle: Evaluated and used as board style
// data-jgosize: Used as board size unless data-jgosgf is defined
// data-jgoview: Used to define viewport
function process(JGO, div) {
// Handle special jgo-* attributes
var style, width, height, TL, BR; // last two are viewport
if(div.getAttribute('data-jgostyle')) {
/*jshint evil:true */
style = eval(div.getAttribute('data-jgostyle'));
} else style = JGO.BOARD.medium;
if(div.getAttribute('data-jgosize')) {
var size = div.getAttribute('data-jgosize');
if(size.indexOf('x') != -1) {
width = parseInt(size.substring(0, size.indexOf('x')));
height = parseInt(size.substr(size.indexOf('x')+1));
} else width = height = parseInt(size);
}
//if(div.getAttribute('data-jgosgf'))
var data = parseMarkup(div.innerHTML);
div.innerHTML = '';
if(!width) { // Size still missing
if(!data.length) return; // no size or data, no board
height = data.length;
width = data[0].length;
}
var jboard = new JGO.Board(width, height);
var jsetup = new JGO.Setup(jboard, style);
if(div.getAttribute('data-jgoview')) {
var tup = div.getAttribute('data-jgoview').split('-');
TL = jboard.getCoordinate(tup[0]);
BR = jboard.getCoordinate(tup[1]);
} else {
TL = new JGO.Coordinate(0,0);
BR = new JGO.Coordinate(width-1, height-1);
}
jsetup.view(TL.i, TL.j, width-TL.i, height-TL.j);
var c = new JGO.Coordinate();
for(c.j = TL.j; c.j <= BR.j; ++c.j) {
for(c.i = TL.i; c.i <= BR.i; ++c.i) {
var elem = data[c.j - TL.j][c.i - TL.i];
jboard.setType(c, elem.type);
if(elem.mark) jboard.setMark(c, elem.mark);
}
}
jsetup.create(div);
}
/**
* Find all div elements with class 'jgoboard' and initialize them.
*/
exports.init = function(document, JGO) {
var matches = document.querySelectorAll('div.jgoboard');
for(var i = 0, len = matches.length; i < len; ++i)
process(JGO, matches[i]);
};
},{"./constants":4}],2:[function(require,module,exports){
'use strict';
var Coordinate = require('./coordinate');
var C = require('./constants');
var util = require('./util');
/**
* Go board class for storing intersection states. Also has listeners that
* are notified on any changes to the board via setType() and setMark().
*
* @param {int} width The width of the board
* @param {int} [height] The height of the board
* @constructor
*/
var Board = function(width, height) {
this.width = width;
if(height !== undefined)
this.height = height;
else { //noinspection JSSuspiciousNameCombination
this.height = this.width;
}
this.listeners = [];
this.shading = [];
this.stones = [];
this.marks = [];
this.shades = false;
// Initialize stones and marks
for(var i=0; i<this.width; ++i) {
var stoneArr = [], markArr = [];
for(var j=0; j<this.height; ++j) {
stoneArr.push(C.CLEAR);
markArr.push(C.MARK.NONE);
}
this.stones.push(stoneArr);
this.marks.push(markArr);
}
};
/**
* Add listener to the board. Listeners are passed an event object with
* event type ('type' or 'mark'), coordinate and board members, and new
* and old values as newVal and oldVal. Event object should be
* considered read only.
*
* @param {function} func A listener callback.
*/
Board.prototype.addListener = function(func) {
this.listeners.push(func);
};
/**
* Remove listener from the board.
*
* @param {function} func A listener callback.
*/
Board.prototype.removeListener = function(func) {
var index = this.listeners.indexOf(func);
if(index != -1) this.listeners.splice(index, 1);
};
/**
* Create coordinate from "J18" type of notation that depend from board size.
*
* @param {string} s The coordinate string.
*/
Board.prototype.getCoordinate = function(s) {
return new Coordinate(C.COORDINATES.indexOf(s.toUpperCase().substr(0,1)),
this.height - parseInt(s.substr(1)));
};
/**
* Make a human readable "J18" type string representation of the coordinate.
*
* @param {Coordinate} c Coordinate.
* @returns {string} representation.
*/
Board.prototype.toString = function(c) {
return C.COORDINATES[c.i] + (this.height-c.j);
};
/**
* Simple iteration over all coordinates.
*
* @param {func} func The iterator method, which is called with the
* coordinate, type and mark parameters.
* @param {int} [i1] Column start.
* @param {int} [j1] Row start.
* @param {int} [i2] Colunm end.
* @param {int} [j2] Row end.
*/
Board.prototype.each = function(func, i1, j1, i2, j2) {
var c = new Coordinate();
if(i1 === undefined) i1 = 0;
if(j1 === undefined) j1 = 0;
if(i2 === undefined) i2 = this.width-1;
if(j2 === undefined) j2 = this.height-1;
for(c.j=j1; c.j<=j2; c.j++)
for(c.i=i1; c.i<=i2; c.i++)
func(c.copy(), this.stones[c.i][c.j], this.marks[c.i][c.j]);
};
/**
* Clear board.
*/
Board.prototype.clear = function() {
this.each(function(c) {
this.setType(c, C.CLEAR);
this.setMark(c, C.MARK.NONE);
}.bind(this));
};
/**
* Set the intersection type at given coordinate(s).
*
* @param {Object} c A Coordinate or Array of them.
* @param {Object} t New type, e.g. CLEAR, BLACK, ...
*/
Board.prototype.setType = function(c, t) {
if(c instanceof Coordinate) {
var old = this.stones[c.i][c.j];
if(old == t) return; // no change
this.stones[c.i][c.j] = t;
var ev = { type: 'type', coordinate: c, board: this,
oldVal: old, newVal: t };
this.listeners.forEach(function(l) { l(ev); });
} else if(c instanceof Array) {
for(var i=0, len=c.length; i<len; ++i)
this.setType(c[i], t); // use ourself to avoid duplicate code
}
};
/**
* Set the intersection mark at given coordinate(s).
*
* @param {Object} c A Coordinate or Array of them.
* @param {Object} m New mark, e.g. MARK.NONE, MARK.TRIANGLE, ...
*/
Board.prototype.setMark = function(c, m) {
if(c instanceof Coordinate) {
var old = this.marks[c.i][c.j];
if(old == m) return; // no change
this.marks[c.i][c.j] = m;
var ev = { type: 'mark', coordinate: c, board: this,
oldVal: old, newVal: m };
this.listeners.forEach(function(l) { l(ev); });
} else if(c instanceof Array) {
for(var i=0, len=c.length; i<len; ++i)
this.setMark(c[i], m); // use ourself to avoid duplicate code
}
};
/**
* Get the intersection type(s) at given coordinate(s).
*
* @param {Object} c A Coordinate or an Array of them.
* @returns {Object} Type or array of types.
*/
Board.prototype.getType = function(c) {
var ret;
if(c instanceof Coordinate) {
ret = this.stones[c.i][c.j];
} else if(c instanceof Array) {
ret = [];
for(var i=0, len=c.length; i<len; ++i)
ret.push(this.stones[c[i].i][c[i].j]);
}
return ret;
};
/**
* Get the intersection mark(s) at given coordinate(s).
*
* @param {Object} c A Coordinate or an Array of them.
* @returns {Object} Mark or array of marks.
*/
Board.prototype.getMark = function(c) {
var ret;
if(c instanceof Coordinate) {
ret = this.marks[c.i][c.j];
} else if(c instanceof Array) {
ret = [];
for(var i=0, len=c.length; i<len; ++i)
ret.push(this.marks[c[i].i][c[i].j]);
}
return ret;
};
/**
* Get neighboring coordinates on board.
*
* @param {Coordinate} c The coordinate
* @returns {Array} The array of adjacent coordinates of given type (may be an empty array)
*/
Board.prototype.getAdjacent = function(c) {
var coordinates = [], i = c.i, j = c.j;
if(i>0)
coordinates.push(new Coordinate(i-1, j));
if(i+1<this.width)
coordinates.push(new Coordinate(i+1, j));
if(j>0)
coordinates.push(new Coordinate(i, j-1));
if(j+1<this.height)
coordinates.push(new Coordinate(i, j+1));
return coordinates;
};
/**
* Filter coordinates based on intersection type.
*
* @param {Object} c An array of Coordinates.
* @param {Object} t A type filter (return only matching type).
* @returns {Object} Object with attributes 'type' and 'mark', array or false.
*/
Board.prototype.filter = function(c, t) {
var ret = [];
for(var i=0, len=c.length; i<len; ++i)
if(this.stones[c[i].i][c[i].j] == t)
ret.push(c);
return ret;
};
/**
* Check if coordinates contain given type.
*
* @param {Object} c An array of Coordinates.
* @param {Object} t A type filter (return only matching type).
* @returns {bool} True or false.
*/
Board.prototype.hasType = function(c, t) {
for(var i=0, len=c.length; i<len; ++i)
if(this.stones[c[i].i][c[i].j] == t)
return true;
return false;
};
/**
* Search all intersections of similar type, return group and edge coordinates.
*
* @param {Coordinate} coord The coordinate from which to start search.
* @param {int} [overrideType] Treat current coordinate as this type.
* @returns {Object} Two arrays of coordinates in members 'group' and 'neighbors'.
*/
Board.prototype.getGroup = function(coord, overrideType) {
var type = overrideType || this.getType(coord), seen = {},
group = [coord.copy()], neighbors = [],
queue = this.getAdjacent(coord);
seen[coord.toString()] = true;
while(queue.length) {
var c = queue.shift();
if(c.toString() in seen)
continue; // seen already
else
seen[c.toString()] = true; // seen now
if(this.getType(c) == type) { // check if type is correct
group.push(c);
queue = queue.concat(this.getAdjacent(c)); // add prospects
} else
neighbors.push(c);
}
return {group: group, neighbors: neighbors};
};
/**
* Get a raw copy of board contents. Will not include any listeners!
*
* @returns {Object} Board contents.
*/
Board.prototype.getRaw = function() {
return {
width: this.width,
height: this.height,
stones: util.extend({}, this.stones),
marks: util.extend({}, this.marks)
};
};
/**
* Set a raw copy of board contents. Will not change or call any listeners!
*
* @param {Object} raw Board contents.
*/
Board.prototype.setRaw = function(raw) {
this.width = raw.width;
this.height = raw.height;
this.stones = raw.stones;
this.marks = raw.marks;
};
/**
* Calculate impact of a move on board. Returns a data structure outlining
* validness of move (success & errorMsg) and possible captures and ko
* coordinate.
*
* @param {Board} jboard Board to play the move on (stays unchanged).
* @param {Coordinate} coord Coordinate to play or null for pass.
* @param {int} stone Stone to play - BLACK or WHITE.
* @param {Coordinate} [ko] Coordinate of previous ko.
* @returns {Object} Move result data structure.
*/
Board.prototype.playMove = function(coord, stone, ko) {
var oppType = (stone == C.BLACK ? C.WHITE : C.BLACK),
captures = [], adjacent;
if(!coord) // pass
return { success: true, captures: [], ko: false };
if(this.getType(coord) != C.CLEAR)
return { success: false,
errorMsg: 'Cannot play on existing stone!' };
if(ko && coord.equals(ko))
return { success: false,
errorMsg: 'Cannot retake ko immediately!' };
adjacent = this.getAdjacent(coord); // find adjacent coordinates
for(var i=0; i<adjacent.length; i++) {
var c = adjacent[i];
if(this.getType(c) == oppType) { // potential capture
var g = this.getGroup(c);
if(this.filter(g.neighbors, C.CLEAR).length == 1)
captures = captures.concat(g.group);
}
}
// Suicide not allowed
if(captures.length === 0 &&
!this.hasType(this.getGroup(coord, stone).neighbors, C.CLEAR))
return { success: false,
errorMsg: 'Suicide is not allowed!' };
// Check for ko. Note that captures were not removed so there should
// be zero liberties around this stone in case of a ko.
if(captures.length == 1 && this.filter(adjacent, C.CLEAR).length === 0)
return { success: true, captures: captures, ko: captures[0].copy() };
return { success: true, captures: captures, ko: false };
};
module.exports = Board;
},{"./constants":4,"./coordinate":5,"./util":13}],3:[function(require,module,exports){
'use strict';
var C = require('./constants');
var Coordinate = require('./coordinate');
var Stones = require('./stones');
var util = require('./util');
/**
* Create a jGoBoard canvas object.
*
* @param {Object} elem Container HTML element or its id.
* @param {Object} opt Options object.
* @param {Object} images Set of images (or false values) for drawing.
* @constructor
*/
var Canvas = function(elem, opt, images) {
/* global document */
if(typeof elem === 'string')
elem = document.getElementById(elem);
var canvas = document.createElement('canvas'), i, j;
var padLeft = opt.edge.left ? opt.padding.normal : opt.padding.clipped,
padRight = opt.edge.right ? opt.padding.normal : opt.padding.clipped,
padTop = opt.edge.top ? opt.padding.normal : opt.padding.clipped,
padBottom = opt.edge.bottom ? opt.padding.normal : opt.padding.clipped;
this.marginLeft = opt.edge.left ? opt.margin.normal : opt.margin.clipped;
this.marginRight = opt.edge.right ? opt.margin.normal : opt.margin.clipped;
this.marginTop = opt.edge.top ? opt.margin.normal : opt.margin.clipped;
this.marginBottom = opt.edge.bottom ? opt.margin.normal : opt.margin.clipped;
this.boardWidth = padLeft + padRight +
opt.grid.x * opt.view.width;
this.boardHeight = padTop + padBottom +
opt.grid.y * opt.view.height;
this.width = canvas.width =
this.marginLeft + this.marginRight + this.boardWidth;
this.height = canvas.height =
this.marginTop + this.marginBottom + this.boardHeight;
this.listeners = {'click': [], 'mousemove': [], 'mouseout': []};
/**
* Get board coordinate based on screen coordinates.
* @param {number} x Coordinate.
* @param {number} y Coordinate.
* @returns {Coordinate} Board coordinate.
*/
this.getCoordinate = function(pageX, pageY) {
var bounds = canvas.getBoundingClientRect(),
scaledX = (pageX - bounds.left) * canvas.width / (bounds.right - bounds.left),
scaledY = (pageY - bounds.top) * canvas.height / (bounds.bottom - bounds.top);
return new Coordinate(
Math.floor((scaledX-this.marginLeft-padLeft)/opt.grid.x) + opt.view.xOffset,
Math.floor((scaledY-this.marginTop-padTop)/opt.grid.y) + opt.view.yOffset);
}.bind(this);
// Click handler will call all listeners passing the coordinate of click
// and the click event
canvas.onclick = function(ev) {
var c = this.getCoordinate(ev.clientX, ev.clientY),
listeners = this.listeners.click;
for(var l=0; l<listeners.length; l++)
listeners[l].call(this, c.copy(), ev);
}.bind(this);
// Move handler will call all listeners passing the coordinate of move
// whenever mouse moves over a new intersection
canvas.onmousemove = function(ev) {
if(!this.listeners.mousemove.length) return;
var c = this.getCoordinate(ev.clientX, ev.clientY),
listeners = this.listeners.mousemove;
var lastMove = new Coordinate(-1, -1);
if(c.i < this.opt.view.xOffset ||
c.i >= this.opt.view.xOffset + this.opt.view.width)
c.i = -1;
if(c.j < this.opt.view.yOffset ||
c.j >= this.opt.view.yOffset + this.opt.view.height)
c.j = -1;
if(lastMove.equals(c))
return; // no change
else
lastMove = c.copy();
for(var l=0; l<listeners.length; l++)
listeners[l].call(this, c.copy(), ev);
}.bind(this);
// Mouseout handler will again call all listeners of that event, no
// coordinates will be passed of course, only the event
canvas.onmouseout = function(ev) {
var listeners = this.listeners.mouseout;
for(var l=0; l<listeners.length; l++)
listeners[l].call(this, ev);
}.bind(this);
elem.appendChild(canvas);
this.ctx = canvas.getContext('2d');
this.opt = util.extend({}, opt); // make a copy just in case
this.stones = new Stones(opt, images);
this.images = images;
// Fill margin with correct color
this.ctx.fillStyle = opt.margin.color;
this.ctx.fillRect(0, 0, canvas.width, canvas.height);
if(this.images.board) {
// Prepare to draw board with shadow
this.ctx.save();
this.ctx.shadowColor = opt.boardShadow.color;
this.ctx.shadowBlur = opt.boardShadow.blur;
this.ctx.shadowOffsetX = opt.boardShadow.offX;
this.ctx.shadowOffsetX = opt.boardShadow.offY;
var clipTop = opt.edge.top ? 0 : this.marginTop,
clipLeft = opt.edge.left ? 0 : this.marginLeft,
clipBottom = opt.edge.bottom ? 0 : this.marginBottom,
clipRight = opt.edge.right ? 0 : this.marginRight;
// Set clipping to throw shadow only on actual edges
this.ctx.beginPath();
this.ctx.rect(clipLeft, clipTop,
canvas.width - clipLeft - clipRight,
canvas.height - clipTop - clipBottom);
this.ctx.clip();
this.ctx.drawImage(this.images.board, 0, 0,
this.boardWidth, this.boardHeight,
this.marginLeft, this.marginTop,
this.boardWidth, this.boardHeight);
// Draw lighter border around the board to make it more photography
this.ctx.strokeStyle = opt.border.color;
this.ctx.lineWidth = opt.border.lineWidth;
this.ctx.beginPath();
this.ctx.rect(this.marginLeft, this.marginTop,
this.boardWidth, this.boardHeight);
this.ctx.stroke();
this.ctx.restore(); // forget shadow and clipping
}
// Top left center of grid (not edge, center!)
this.gridTop = this.marginTop + padTop + opt.grid.y / 2;
this.gridLeft = this.marginLeft + padLeft + opt.grid.x / 2;
this.ctx.strokeStyle = opt.grid.color;
var smt = this.opt.grid.smooth; // with 0.5 there will be full antialias
// Draw vertical gridlines
for(i=0; i<opt.view.width; i++) {
if((i === 0 && opt.edge.left) || (i+1 == opt.view.width && opt.edge.right))
this.ctx.lineWidth = opt.grid.borderWidth;
else
this.ctx.lineWidth = opt.grid.lineWidth;
this.ctx.beginPath();
this.ctx.moveTo(smt + this.gridLeft + opt.grid.x * i,
smt + this.gridTop - (opt.edge.top ? 0 : opt.grid.y / 2 + padTop/2));
this.ctx.lineTo(smt + this.gridLeft + opt.grid.x * i,
smt + this.gridTop + opt.grid.y * (opt.view.height - 1) +
(opt.edge.bottom ? 0 : opt.grid.y / 2 + padBottom/2));
this.ctx.stroke();
}
// Draw horizontal gridlines
for(i=0; i<opt.view.height; i++) {
if((i === 0 && opt.edge.top) || (i+1 == opt.view.height && opt.edge.bottom))
this.ctx.lineWidth = opt.grid.borderWidth;
else
this.ctx.lineWidth = opt.grid.lineWidth;
this.ctx.beginPath();
this.ctx.moveTo(smt + this.gridLeft - (opt.edge.left ? 0 : opt.grid.x / 2 + padLeft/2),
smt + this.gridTop + opt.grid.y * i);
this.ctx.lineTo(smt + this.gridLeft + opt.grid.x * (opt.view.width - 1) +
(opt.edge.right ? 0 : opt.grid.x / 2 + padRight/2),
smt + this.gridTop + opt.grid.y * i);
this.ctx.stroke();
}
if(opt.stars.points) { // If star points
var step = (opt.board.width - 1) / 2 - opt.stars.offset;
// 1, 4, 5, 8 and 9 points are supported, rest will result in randomness
for(j=0; j<3; j++) {
for(i=0; i<3; i++) {
if(j == 1 && i == 1) { // center
if(opt.stars.points % 2 === 0)
continue; // skip center
} else if(i == 1 || j == 1) { // non-corners
if(opt.stars.points < 8)
continue; // skip non-corners
} else { // corners
if(opt.stars.points < 4)
continue; // skip corners
}
var x = (opt.stars.offset + i * step) - opt.view.xOffset,
y = (opt.stars.offset + j * step) - opt.view.yOffset;
if(x < 0 || y < 0 || x >= opt.view.width || y >= opt.view.height)
continue; // invisible
this.ctx.beginPath();
this.ctx.arc(smt + this.gridLeft + x * opt.grid.x,
smt + this.gridTop + y * opt.grid.y,
opt.stars.radius, 2*Math.PI, false);
this.ctx.fillStyle = opt.grid.color;
this.ctx.fill();
}
}
}
this.ctx.font = opt.coordinates.font;
this.ctx.fillStyle = opt.coordinates.color;
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
// Draw horizontal coordinates
for(i=0; i<opt.view.width; i++) {
if(opt.coordinates && opt.coordinates.top)
this.ctx.fillText(C.COORDINATES[i + opt.view.xOffset],
this.gridLeft + opt.grid.x * i,
this.marginTop / 2);
if(opt.coordinates && opt.coordinates.bottom)
this.ctx.fillText(C.COORDINATES[i + opt.view.xOffset],
this.gridLeft + opt.grid.x * i,
canvas.height - this.marginBottom / 2);
}
// Draw vertical coordinates
for(i=0; i<opt.view.height; i++) {
if(opt.coordinates && opt.coordinates.left)
this.ctx.fillText(''+(opt.board.height-opt.view.yOffset-i),
this.marginLeft / 2,
this.gridTop + opt.grid.y * i);
if(opt.coordinates && opt.coordinates.right)
this.ctx.fillText(''+(opt.board.height-opt.view.yOffset-i),
canvas.width - this.marginRight / 2,
this.gridTop + opt.grid.y * i);
}
// Store rendered board in another canvas for fast redraw
this.backup = document.createElement('canvas');
this.backup.width = canvas.width;
this.backup.height = canvas.height;
this.backup.getContext('2d').drawImage(canvas,
0, 0, canvas.width, canvas.height,
0, 0, canvas.width, canvas.height);
// Clip further drawing to board only
this.ctx.beginPath();
this.ctx.rect(this.marginLeft, this.marginTop, this.boardWidth, this.boardHeight);
this.ctx.clip();
// Fix Chromium bug with image glitches unless they are drawn once
// https://code.google.com/p/chromium/issues/detail?id=469906
if(this.images.black) this.ctx.drawImage(this.images.black, 10, 10);
if(this.images.white) this.ctx.drawImage(this.images.white, 10, 10);
if(this.images.shadow) this.ctx.drawImage(this.images.shadow, 10, 10);
// Sucks but works
this.restore(this.marginLeft, this.marginTop, this.boardWidth, this.boardHeight);
};
/**
* Restore portion of canvas.
*/
Canvas.prototype.restore = function(x, y, w, h) {
x = Math.floor(x);
y = Math.floor(y);
this.ctx.drawImage(this.backup, x, y, w, h, x, y, w, h);
};
/**
* Get X coordinate based on column.
* @returns {number} Coordinate.
*/
Canvas.prototype.getX = function(i) {
return this.gridLeft + this.opt.grid.x * i;
};
/**
* Get Y coordinate based on row.
* @returns {number} Coordinate.
*/
Canvas.prototype.getY = function(j) {
return this.gridTop + this.opt.grid.y * j;
};
/**
* Redraw canvas portion using a board.
*
* @param {Board} jboard Board object.
* @param {number} i1 Starting column to be redrawn.
* @param {number} j1 Starting row to be redrawn.
* @param {number} i2 Ending column to be redrawn (inclusive).
* @param {number} j2 Ending row to be redrawn (inclusive).
*/
Canvas.prototype.draw = function(jboard, i1, j1, i2, j2) { i1 = Math.max(i1, this.opt.view.xOffset);
j1 = Math.max(j1, this.opt.view.yOffset);
i2 = Math.min(i2, this.opt.view.xOffset + this.opt.view.width - 1);
j2 = Math.min(j2, this.opt.view.yOffset + this.opt.view.height - 1);
if(i2 < i1 || j2 < j1)
return; // nothing to do here
var x = this.getX(i1 - this.opt.view.xOffset) - this.opt.grid.x,
y = this.getY(j1 - this.opt.view.yOffset) - this.opt.grid.y,
w = this.opt.grid.x * (i2 - i1 + 2),
h = this.opt.grid.y * (j2 - j1 + 2);
this.ctx.save();
this.ctx.beginPath();
this.ctx.rect(x, y, w, h);
this.ctx.clip(); // only apply redraw to relevant area
this.restore(x, y, w, h); // restore background
// Expand redrawn intersections while keeping within viewport
i1 = Math.max(i1-1, this.opt.view.xOffset);
j1 = Math.max(j1-1, this.opt.view.yOffset);
i2 = Math.min(i2+1, this.opt.view.xOffset + this.opt.view.width - 1);
j2 = Math.min(j2+1, this.opt.view.yOffset + this.opt.view.height - 1);
var isLabel = /^[a-zA-Z1-9]/;
// Stone radius derived marker size parameters
var stoneR = this.opt.stone.radius,
clearW = stoneR * 1.5, clearH = stoneR * 1.2, clearFunc;
// Clear grid for labels on clear intersections before casting shadows
if(this.images.board) { // there is a board texture
clearFunc = function(ox, oy) {
this.ctx.drawImage(this.images.board,
ox - this.marginLeft - clearW / 2, oy - this.marginTop - clearH / 2, clearW, clearH,
ox - clearW / 2, oy - clearH / 2, clearW, clearH);
}.bind(this);
} else { // no board texture
this.ctx.fillStyle = this.opt.margin.color;
clearFunc = function(ox, oy) {
this.ctx.fillRect(ox - clearW / 2, oy - clearH / 2, clearW, clearH);
}.bind(this);
}
// Clear board grid under markers when needed
jboard.each(function(c, type, mark) {
// Note: Use of smt has been disabled here for clear results
var ox = this.getX(c.i - this.opt.view.xOffset);
var oy = this.getY(c.j - this.opt.view.yOffset);
if(type == C.CLEAR && mark && isLabel.test(mark))
clearFunc(ox, oy);
}.bind(this), i1, j1, i2, j2); // provide iteration limits
// After the board has been cleared, but before shadows and stones have been drawn,
// draw in the shading if any exists, Chris Clark
var shadeSize = 8.5;
this.ctx.lineWidth = this.opt.grid.borderWidth;
if (jboard.shades) {
jboard.each(function(c, type) {
var i = c.i + c.j * 19;
var strength = jboard.shades[i];
var color = "rgba(255," + Math.floor((1.0-strength)*255) + ",0,0.7)";
this.ctx.fillStyle = color;
var ox = this.getX(c.i - this.opt.view.xOffset);
var oy = this.getY(c.j - this.opt.view.yOffset);
// We need to redraw the lines to avoid some weird coloring affect that occur because
// (I think?) cause by the clear function removing them
this.ctx.beginPath();
this.ctx.moveTo(ox, oy - shadeSize);
this.ctx.lineTo(ox, oy + shadeSize);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.moveTo(ox- shadeSize, oy);
this.ctx.lineTo(ox + shadeSize, oy);
this.ctx.stroke();
this.ctx.fillRect(ox - shadeSize, oy - shadeSize,shadeSize * 2, shadeSize * 2);
}.bind(this), i1, j1, i2, j2)
}
// Shadows
jboard.each(function(c, type) {
var ox = this.getX(c.i - this.opt.view.xOffset);
var oy = this.getY(c.j - this.opt.view.yOffset);
if(type == C.BLACK || type == C.WHITE) {
this.stones.drawShadow(this.ctx,
this.opt.shadow.xOff + ox,
this.opt.shadow.yOff + oy);
}
}.bind(this), i1, j1, i2, j2); // provide iteration limits
// Stones and marks
jboard.each(function(c, type, mark) {
var ox = (this.getX(c.i - this.opt.view.xOffset));
var oy = (this.getY(c.j - this.opt.view.yOffset));
var markColor;
switch(type) {
case C.BLACK:
case C.DIM_BLACK:
this.ctx.globalAlpha = type == C.BLACK ? 1 : this.opt.stone.dimAlpha;
this.stones.drawStone(this.ctx, type, ox, oy);
markColor = this.opt.mark.blackColor; // if we have marks, this is the color
break;
case C.WHITE:
case C.DIM_WHITE:
this.ctx.globalAlpha = type == C.WHITE ? 1 : this.opt.stone.dimAlpha;
this.stones.drawStone(this.ctx, type, ox, oy);
markColor = this.opt.mark.whiteColor; // if we have marks, this is the color
break;
default:
this.ctx.globalAlpha=1;
markColor = this.opt.mark.clearColor; // if we have marks, this is the color
}
// Common settings to all markers
this.ctx.lineWidth = this.opt.mark.lineWidth;
this.ctx.strokeStyle = markColor;
this.ctx.font = this.opt.mark.font;
this.ctx.fillStyle = markColor;
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
if(mark) this.stones.drawMark(this.ctx, mark, ox, oy);
}.bind(this), i1, j1, i2, j2); // provide iteration limits
this.ctx.restore(); // also restores globalAlpha
};
/**
* Add an event listener to canvas (click) events. The callback will be
* called with 'this' referring to Canvas object, with coordinate and
* event as parameters. Supported event types are 'click', 'mousemove',
* and 'mouseout'. With 'mouseout', there is no coordinate parameter for
* callback.
*
* @param {String} event The event to listen to, e.g. 'click'.
* @param {function} callback The callback.
*/
Canvas.prototype.addListener = function(event, callback) {
this.listeners[event].push(callback);
};
module.exports = Canvas;
},{"./constants":4,"./coordinate":5,"./stones":12,"./util":13}],4:[function(require,module,exports){
'use strict';
var util = require('./util');
/**
* Enum for intersection types. Aliased in JGO namespace, e.g. JGO.BLACK.
* @enum
* @readonly
*/
exports.INTERSECTION = {
CLEAR: 0,
/** Black stone */
BLACK: 1,
/** White stone */
WHITE: 2,
/** Semi-transparent black stone */
DIM_BLACK: 3,
/** Semi-transparent white stone */
DIM_WHITE: 4
};
// Alias all objects into globals
util.extend(exports, exports.INTERSECTION);
/**
* Enum for marker types.
* @readonly
* @enum
*/
exports.MARK = {
/** No marker ('') */
NONE: '',
/** Selected intersection */
SELECTED: '^',
/** Square */
SQUARE: '#',
/** Triangle */
TRIANGLE: '/',
/** Circle */
CIRCLE: '0',
/** Cross */
CROSS: '*',
/** Black territory */
BLACK_TERRITORY: '-',
/** White territory */
WHITE_TERRITORY: '+'
};
/**
* Board coordinate array.
* @constant
*/
exports.COORDINATES = 'ABCDEFGHJKLMNOPQRSTUVWXYZ'.split('');
},{"./util":13}],5:[function(require,module,exports){
'use strict';
var SGFLetters = 'abcdefghijklmnopqrstuvwxyz'.split('');
/**
* Create a helper class to create coordinates from (1,2) (zero-based),
* 'ah' type of input. You can create a coordinate with no arguments, in
* which case it defaults to (0,0), or with one argument, in which case it
* tries to parse 'ai' type of string coordinate, or with two arguments, (i,j).
* 'J18' style coordinates depend on board size due to number running from
* bottom, so those need to be instantiated from Board.getCoordinate.
*
* @param {int} [i] Column or SGF-style string.
* @param {int} [j] Row.
* @constructor
*/
var Coordinate = function(i, j) {
if(i !== undefined) {
if(j !== undefined) {
this.i = i;
this.j = j;
} else { // try to parse coordinates from first parameter
this.i = 0;
this.j = 0;
if(typeof i != 'string')
return;
// assume SGF-type coordinate
i = i.toLowerCase();
this.i = SGFLetters.indexOf(i.substr(0,1));
this.j = SGFLetters.indexOf(i.substr(1));
}
} else { // called without both parameters
this.i = 0;
this.j = 0;
}
};
/**
* Compare with another coordinate.
*
* @param {Coordinate} Coordinate.
* @returns {boolean} true if equal, false if not.
*/
Coordinate.prototype.equals = function(c) {
return (c.i == this.i) && (c.j == this.j);
};
/**
* Make an SGF-type 'ai' string representation of the coordinate.
*
* @returns {string} String representation.
*/
Coordinate.prototype.toString = function() {
return SGFLetters[this.i] + SGFLetters[this.j];
};
/**
* Make a copy of this coordinate.
*
* @returns {Coordinate} A copy of this coordinate.
*/
Coordinate.prototype.copy = function() {
return new Coordinate(this.i, this.j);
};
module.exports = Coordinate;
},{}],6:[function(require,module,exports){
'use strict';
var JGO = require('./constants'); // base for JGO object
JGO.Coordinate = require('./coordinate');
JGO.Canvas = require('./canvas');
JGO.Node = require('./node');
JGO.Notifier = require('./notifier');
JGO.Record = require('./record');
JGO.Setup = require('./setup');
JGO.Stones = require('./stones');
JGO.Board = require('./board');
JGO.util = require('./util');
JGO.sgf = require('./sgf');
JGO.auto = require('./auto');
module.exports = JGO;
},{"./auto":1,"./board":2,"./canvas":3,"./constants":4,"./coordinate":5,"./node":7,"./notifier":8,"./record":9,"./setup":10,"./sgf":11,"./stones":12,"./util":13}],7:[function(require,module,exports){
'use strict';
var util = require('./util');
var C = require('./constants');
/**
* Helper class to store node information, apply and revert changes easily.
*
* @param {Board} jboard Board object to make changes on.
* @param {Node} parent Parent node or null if no parent.
* @param {Object} info Node information - ko coordinate, comment, etc.
* @constructor
*/
var Node = function(jboard, parent, info) {
this.jboard = jboard;
this.parent = parent;
this.info = info ? util.extend({}, info) : {};
this.children = [];
this.changes = [];
if(parent) {
parent.children.push(this); // register child
this.info.captures = util.extend({}, parent.info.captures);
} else {
this.info.captures = {1: 0, 2: 0}; // C.BLACK, C.WHITE
}
};
/**
* Helper method to clear parent node's markers. Created to achieve SGF like
* stateless marker behavaior.
*/
Node.prototype.clearParentMarks = function() {
if(!this.parent)
return;
for(var i=this.parent.changes.length-1; i>=0; i--) {
var item = this.parent.changes[i];
if('mark' in item)
this.setMark(item.c, C.MARK.NONE);
}
};
/**
* Helper method to make changes to a board while saving them in the node.
*
* @param {Object} c Coordinate or array of them.
* @param {int} val Type.
*/
Node.prototype.setType = function(c, val) {
if(c instanceof Array) {
for(var i=0, len=c.length; i<len; ++i)
this.setType(c[i], val); // avoid repeating ourselves
return;
}
// Store both change and previous value to enable reversion
this.changes.push({c: c.copy(), type: val, old: this.jboard.getType(c)});
this.jboard.setType(c, val);
};
/**
* Helper method to make changes to a board while saving them in the node.
*
* @param {Object} c Coordinate or array of them.
* @param {int} val Mark.
*/
Node.prototype.setMark = function(c, val) {
if(c instanceof Array) {
for(var i=0, len=c.length; i<len; ++i)
this.setMark(c[i], val); // avoid repeating ourselves
return;
}
// Store both change and previous value to enable reversion
this.changes.push({c: c.copy(), mark: val, old: this.jboard.getMark(c)});
this.jboard.setMark(c, val);
};
/**
* Apply changes of this node to board.
*/
Node.prototype.apply = function() {
for(var i=0; i<this.changes.length; i++) {
var item = this.changes[i];
if('type' in item)
this.jboard.setType(item.c, item.type);
else
this.jboard.setMark(item.c, item.mark);
}
};
/**
* Revert changes of this node to board.
*/
Node.prototype.revert = function() {
for(var i=this.changes.length-1; i>=0; i--) {
var item = this.changes[i];
if('type' in item)
this.jboard.setType(item.c, item.old);
else
this.jboard.setMark(item.c, item.old);
}
};
module.exports = Node;
},{"./constants":4,"./util":13}],8:[function(require,module,exports){
'use strict';
/**
* A change notifier class that can listen to changes in a Board and keep
* multiple Canvas board views up to date.
*
* @param {Board} jboard The board to listen to.
* @constructor
*/
var Notifier = function(jboard) {
this.updateScheduled = false; // set on first change
this.canvases = []; // canvases to notify on changes
this.board = jboard;
this.changeFunc = function(ev) {
var coord = ev.coordinate;
if(this.updateScheduled) { // update already scheduled
this.min.i = Math.min(this.min.i, coord.i);
this.min.j = Math.min(this.min.j, coord.j);
this.max.i = Math.max(this.max.i, coord.i);
this.max.j = Math.max(this.max.j, coord.j);
return;
}
this.min = coord.copy();
this.max = coord.copy();
this.updateScheduled = true;
setTimeout(function() { // schedule update in the end
for(var c=0; c<this.canvases.length; c++)
this.canvases[c].draw(this.board, this.min.i, this.min.j,
this.max.i, this.max.j);
this.updateScheduled = false; // changes updated, scheduled function run
}.bind(this), 0);
}.bind(this);
this.board.addListener(this.changeFunc);
};
/**
* Change the board to listen to. Notifier will stop listening previous board,
* start listening new one and trigger full redraw of canvas.
*
* @param {Board} board New board to listen to.
*/
Notifier.prototype.changeBoard = function(board) {
this.board.removeListener(this.changeFunc);
this.board = board;
this.board.addListener(this.changeFunc);
// There might be a scheduled update timeout, but ignore that, it will
// just redraw a portion of new board
for(var c=0; c<this.canvases.length; c++)
this.canvases[c].draw(this.board, 0, 0, this.board.width, this.board.height);
};
/**
* Add a canvas to notify list.
*
* @param {Canvas} jcanvas The canvas to add.
*/
Notifier.prototype.addCanvas = function(jcanvas) {
this.canvases.push(jcanvas);
};
module.exports = Notifier;
},{}],9:[function(require,module,exports){
'use strict';
var Board = require('./board');
var Node = require('./node');
/**
* Create a go game record that can handle plays and variations. A Board
* object is created that will reflect the current position in game record.
*
* @param {int} width Board width.
* @param {int} height Board height.
* @constructor
*/
var Record = function(width, height) {
this.jboard = new Board(width, height ? height : width);
this.root = this.current = null;
this.info = {}; // game information
};
/**
* Get board object.
*
* @returns {Board} Board object.
*/
Record.prototype.getBoard = function() {
return this.jboard;
};
/**
* Get current node.
*
* @returns {Node} Current node.
*/
Record.prototype.getCurrentNode = function() {
return this.current;
};
/**
* Get root node.
*
* @returns {Node} Root node.
*/
Record.prototype.getRootNode = function() {
return this.root;
};
/**
* Create new empty node under current one.
*
* @param {bool} clearParentMarks True to clear parent node marks.
* @param {Object} info Node information - ko coordinate, comment, etc.
* @returns {Node} New, current node.
*/
Record.prototype.createNode = function(clearParentMarks, options) {
var node = new Node(this.jboard, this.current, options);
if(clearParentMarks)
node.clearParentMarks();
if(this.root === null)
this.root = node;
return (this.current = node);
};
/**
* Advance to the next node in the game tree.
*
* @param {int} [variation] parameter to specify which variation to select, if there are several branches.
* @returns {Node} New current node or null if at the end of game tree.
*/
Record.prototype.next = function(variation) {
if(this.current === null)
return null;
if(!variation)
variation = 0;
if(variation >= this.current.children.length)
return null;
this.current = this.current.children[variation];
this.current.apply(this.jboard);
return this.current;
};
/**
* Back up a node in the game tree.
*
* @returns {Node} New current node or null if at the beginning of game tree.
*/
Record.prototype.previous = function() {
if(this.current === null || this.current.parent === null)
return null; // empty or no parent
this.current.revert(this.jboard);
this.current = this.current.parent;
return this.current;
};
/**
* Get current variation number (zero-based).
*
* @returns {int} Current variations.
*/
Record.prototype.getVariation = function() {
if(this.current === null || this.current.parent === null)
return 0;
return this.current.parent.children.indexOf(this.current);
};
/**
* Go to a variation. Uses previous() and next().
*
* @param {int} [variation] parameter to specify which variation to select, if there are several branches.
*/
Record.prototype.setVariation = function(variation) {
if(this.previous() === null)
return null;
return this.next(variation);
};
/**
* Get number of variations for current node.
*
* @returns {int} Number of variations.
*/
Record.prototype.getVariations = function() {
if(this.current === null || this.current.parent === null)
return 1;
return this.current.parent.children.length; // "nice"
};
/**
* Go to the beginning of the game tree.
*
* @returns {Node} New current node.
*/
Record.prototype.first = function() {
this.current = this.root;
this.jboard.clear();
if(this.current !== null)
this.current.apply(this.jboard);
return this.current;
};
/**
* Create a snapshot of current Record state. Will contain board state and
* current node.
*
* @returns Snapshot to be used with restoreSnapshot().
*/
Record.prototype.createSnapshot = function() {
return {jboard: this.jboard.getRaw(), current: this.current};
};
/**
* Restore the Record to the state contained in snapshot. Use only if you
* REALLY know what you are doing, this is mainly for creating Record
* quickly from SGF.
*
* @param {Object} raw Snapshot created with createSnapshot().
*/
Record.prototype.restoreSnapshot = function(raw) {
this.jboard.setRaw(raw.jboard);
this.current = raw.current;
};
/**
* Normalize record so the longest variation is the first.
*
* @param {Node} node The node to start with. Defaults to root if unset.
* @returns int Length of longest subsequence.
*/
Record.prototype.normalize = function(node) {
var i, len, maxLen = 0, maxI = 0;
if(!node) node = this.getRootNode();
for(i=0; i<node.children.length; i++) {
len = this.normalize(node.children[i]);
if(maxLen < len) { maxLen = len; maxI = i; }
}
if(maxI) { // If needed, swap longest first
i = node.children[0];
node.children[0] = node.children[maxI];
node.children[maxI] = i;
}
return maxLen + 1; // longest subsequence plus this
};
module.exports = Record;
},{"./board":2,"./node":7}],10:[function(require,module,exports){
'use strict';
var Notifier = require('./notifier');
var Canvas = require('./canvas');
var util = require('./util');
/**
* Setup helper class to make creating Canvases easy.
*
* @param {Board} jboard Board object to listen to.
* @param {Object} boardOptions Base board options like BOARD.large.
* @constructor
*/
var Setup = function(board, boardOptions) {
var defaults = {
margin: {color:'white'},
edge: {top:true, bottom:true, left:true, right:true},
coordinates: {top:true, bottom:true, left:true, right:true},
stars: {points: 0 },
board: {width:board.width, height:board.height},
view: {xOffset:0, yOffset:0, width:board.width, height:board.height}
};
if(board.width == board.height) {
switch(board.width) { // square
case 9:
defaults.stars.points=5;
defaults.stars.offset=2;
break;
case 13:
case 19:
defaults.stars.points=9;
defaults.stars.offset=3;
break;
}
}
this.board = board; // board to follow
this.notifier = new Notifier(this.board); // notifier to canvas
this.options = util.extend(defaults, boardOptions); // clone
};
/**
* View only a portion of the whole board.
*
* @param {int} xOff The X offset.
* @param {int} yOff The Y offset.
* @param {int} width The width.
* @param {int} height The height.
*/
Setup.prototype.view = function(xOff, yOff, width, height) {
this.options.view.xOffset = xOff;
this.options.view.yOffset = yOff;
this.options.view.width = width;
this.options.view.height = height;
this.options.edge.left = (xOff === 0);
this.options.edge.right = (xOff+width == this.options.board.width);
this.options.edge.top = (yOff === 0);
this.options.edge.bottom = (yOff+height == this.options.board.height);
};
/**
* Replace default options (non-viewport related)
*
* @param {Object} options The new options.
*/
Setup.prototype.setOptions = function(options) {
util.extend(this.options, options);
};
/**
* Get {@link Notifier} object created by this class. Can be used to
* change the board the canvas listens to.
*
* @returns {Notifier} Canvas notifier.
*/
Setup.prototype.getNotifier = function() {
return this.notifier;
};
/**
* Create Canvas based on current settings. When textures are used,
* image resources need to be loaded, so the function returns and
* asynchronously call readyFn after actual initialization.
*
* @param {Object} elemId The element id or HTML Node where to create the canvas in.
* @param {function} readyFn Function to call with canvas once it is ready.
*/
Setup.prototype.create = function(elemId, readyFn) {
var options = util.extend({}, this.options); // create a copy
var createCallback = function(images) {
var jcanvas = new Canvas(elemId, options, images);
jcanvas.draw(this.board, 0, 0, this.board.width-1, this.board.height-1);
// Track and group later changes with Notifier
this.notifier.addCanvas(jcanvas);
if(readyFn) readyFn(jcanvas);
}.bind(this);
if(this.options.textures) // at least some textures exist
util.loadImages(this.options.textures, createCallback);
else // blain BW board
createCallback({black:false,white:false,shadow:false,board:false});
};
module.exports = Setup;
},{"./canvas":3,"./notifier":8,"./util":13}],11:[function(require,module,exports){
'use strict';
/**
* SGF loading module.
* @module sgf
*/
var Coordinate = require('./coordinate');
var Record = require('./record');
var C = require('./constants');
var ERROR; // error holder for sgfParse etc.
var fieldMap = {
'AN': 'annotator',
'CP': 'copyright',
'DT': 'date',
'EV': 'event',
'GN': 'gameName',
'OT': 'overtime',
'RO': 'round',
'RE': 'result',
'RU': 'rules',
'SO': 'source',
'TM': 'time',
'PC': 'location',
'PB': 'black',
'PW': 'white',
'BR': 'blackRank',
'WR': 'whiteRank',
'BT': 'blackTeam',
'WT': 'whiteTeam'
};
/*
* Helper function to handle single coordinates as well as coordinate lists.
*
* @param {object} propValues A property value array containing a mix of coordinates (aa) and lists (aa:bb)
* @returns {array} An array of Coordinate objects matching the given property values.
*/
function explodeSGFList(propValues) {
var coords = [];
for(var i=0, len=propValues.length; i<len; i++) {
var val = propValues[i];
if(val.indexOf(':') == -1) { // single coordinate
coords.push(new Coordinate(val));
} else {
var tuple = val.split(':'), c1, c2, coord;
c1 = new Coordinate(tuple[0]);
c2 = new Coordinate(tuple[1]);
coord = new Coordinate();
for(coord.i=c1.i; coord.i<=c2.i; ++coord.i)
for(coord.j=c1.j; coord.j<=c2.j; ++coord.j)
coords.push(coord.copy());
}
}
return coords;
}
function sgfMove(node, name, values, moveMarks) {
var coord, player, opponent, play;
if(name == 'B') {
player = C.BLACK;
opponent = C.WHITE;
} else if('W') {
player = C.WHITE;
opponent = C.BLACK;
}
coord = (values[0].length == 2) ? new Coordinate(values[0]) : null;
// Apparently, IGS wants to use outside-board coordinates for pass
if(coord.i >= node.jboard.width || coord.j >= node.jboard.height)
coord = null; // Thank you, IGS
play = node.jboard.playMove(coord, player);
node.info.captures[player] += play.captures.length; // tally captures
if(moveMarks && play.ko)
node.setMark(play.ko, C.MARK.SQUARE);
if(play.success && coord !== null) {
node.setType(coord, player); // play stone
node.setType(play.captures, C.CLEAR); // clear opponent's stones
if(moveMarks)
node.setMark(coord, C.MARK.CIRCLE);
} else ERROR = play.error;
return play.success;
}
function sgfSetup(node, name, values) {
var setupMap = {'AB': C.BLACK, 'AW': C.WHITE, 'AE': C.CLEAR};
node.setType(explodeSGFList(values), setupMap[name]);
return true;
}
function sgfMarker(node, name, values) {
var markerMap = {
'TW': C.MARK.WHITE_TERRITORY,
'TB': C.MARK.BLACK_TERRITORY,
'CR': C.MARK.CIRCLE,
'TR': C.MARK.TRIANGLE,
'MA': C.MARK.CROSS,
'SQ': C.MARK.SQUARE
};
node.setMark(explodeSGFList(values), markerMap[name]);
return true;
}
function sgfComment(node, name, values) {
node.info.comment = values[0];
return true;
}
function sgfHandicap(node, name, values) {
node.info.handicap = values[0];
return true;
}
function sgfLabel(node, name, values) {
for(var i=0; i<values.length; i++) {
var v = values[i], tuple = v.split(':');
node.setMark(new Coordinate(tuple[0]), tuple[1]);
}
return true;
}
function sgfInfo(node, name, values) {
var field = fieldMap[name];
node.info[field] = values[0];
return true;
}
var SGFProperties = {
'B': sgfMove,
'W': sgfMove,
'AB': sgfSetup,
'AW': sgfSetup,
'AE': sgfSetup,
'C': sgfComment,
'LB': sgfLabel,
'HA': sgfHandicap,
'TW': sgfMarker,
'TB': sgfMarker,
'CR': sgfMarker,
'TR': sgfMarker,
'MA': sgfMarker,
'SQ': sgfMarker,
'AN': sgfInfo,
'CP': sgfInfo,
'DT': sgfInfo,
'EV': sgfInfo,
'GN': sgfInfo,
'OT': sgfInfo,
'RO': sgfInfo,
'RE': sgfInfo,
'RU': sgfInfo,
'SO': sgfInfo,
'TM': sgfInfo,
'PC': sgfInfo,
'PB': sgfInfo,
'PW': sgfInfo,
'BR': sgfInfo,
'WR': sgfInfo,
'BT': sgfInfo,
'WT': sgfInfo
};
/*
* Parse SGF string into object tree representation:
*
* tree = { sequence: [ <node(s)> ], leaves: [ <subtree(s), if any> ] }
*
* Each node is an object containing property identifiers and associated values in array:
*
* node = {'B': ['nn'], 'C': ['This is a comment']}
*
* @param {String} sgf The SGF in string format, whitespace allowed.
* @returns {Object} Root node or false on error. Error stored to ERROR variable.
*/
function parseSGF(sgf) {
var tokens, i, len, token, // for loop vars
lastBackslash = false, // flag to note if last string ended in escape
bracketOpen = -1, // the index where bracket opened
processed = [];
if('a~b'.split(/(~)/).length === 3) {
tokens = sgf.split(/([\[\]\(\);])/); // split into an array at '[', ']', '(', ')', and ';', and include separators in array
} else { // Thank you IE for not working
var blockStart = 0, delimiters = '[]();';
tokens = [];
for(i=0, len=sgf.length; i<len; ++i) {
if(delimiters.indexOf(sgf.charAt(i)) !== -1) {
if(blockStart < i)
tokens.push(sgf.substring(blockStart, i));
tokens.push(sgf.charAt(i));
blockStart = i+1;
}
}
if(blockStart < i) // leftovers
tokens.push(sgf.substr(blockStart, i));
}
// process through tokens and push everything into processed, but merge stuff between square brackets into one element, unescaping escaped brackets
// i.e. ['(', ';', 'C', '[', 'this is a comment containing brackets ', '[', '\\', ']', ']'] becomes:
// ['(', ';', 'C', '[', 'this is a comment containing brackets []]']
// after this transformation, it's just '(', ')', ';', 'ID', and '[bracket stuff]' elements in the processed array
for(i=0, len=tokens.length; i<len; ++i) {
token = tokens[i];
if(bracketOpen == -1) { // handling elements outside property values (i.e. square brackets)
token = token.replace(/^\s+|\s+$/g, ''); // trim whitespace, it is irrelevant here
if(token == '[') // found one
bracketOpen = i;
else if(token !== '') // we are outside brackets, so just push everything nonempty as it is into 'processed'
processed.push(token);
} else { // bracket is open, we're now looking for a ] without preceding \
if(token != ']') { // a text segment
lastBackslash = (token.charAt(token.length-1) == '\\'); // true if string ends in \
} else { // a closing bracket
if(lastBackslash) { // it's escaped - we continue
lastBackslash = false;
} else { // it's not escaped - we close the segment
processed.push(tokens.slice(bracketOpen, i+1).join('').replace(/\\\]/g, ']')); // push the whole thing including brackets, and unescape the inside closing brackets
bracketOpen = -1;
}
}
}
}
// basic error checking
if(processed.length === 0) {
ERROR = 'SGF was empty!';
return false;
} else if(processed[0] != '(' || processed[1] != ';' || processed[processed.length-1] != ')') {
ERROR = 'SGF did not start with \'(;\' or end with \')\'!';
return false;
}
// collect 'XY', '[AB]', '[CD]' sequences (properties) in a node into {'XY': ['AB', 'CD']} type of associative array
// effectively parsing '(;GM[1]FF[4];B[pd])' into ['(', {'GM': ['1'], 'FF': ['4']}, {'B': ['pd']}, ')']
// start again with 'tokens' and process into 'processed'
tokens = processed;
processed = [];
var node, propertyId = ''; // node under construction, and current property identifier
// the following code is not strict on format, so let's hope it's well formed
for(i=0, len=tokens.length; i<len; ++i) {
token = tokens[i];
if(token == '(' || token == ')') {
if(node) { // flush and reset node if necessary
if(propertyId !== '' && node[propertyId].length === 0) { // last property was missing value
ERROR = 'Missing property value at ' + token + '!';
return false;
}
processed.push(node);
node = undefined;
}
processed.push(token); // push this token also
} else if(token == ';') { // new node
if(node) { // flush if necessary
if(propertyId !== '' && node[propertyId].length === 0) { // last property was missing value
ERROR = 'Missing property value at ' + token + '!';
return false;
}
processed.push(node);
}
node = {};
propertyId = ''; // initialize the new node
} else { // it's either a property identifier or a property value
if(token.indexOf('[') !== 0) { // it's property identifier
if(propertyId !== '' && node[propertyId].length === 0) { // last property was missing value
ERROR = 'Missing property value at ' + token + '!';
return false;
}
if(token in node) { // duplicate key
ERROR = 'Duplicate property identifier ' + token + '!';
return false;
}
propertyId = token;
node[propertyId] = []; // initialize new property with empty value array
} else { // it's property value
if(propertyId === '') { // we're missing the identifier
ERROR = 'Missing property identifier at ' + token + '!';
return false;
}
node[propertyId].push(token.substring(1, token.length-1)); // remove enclosing brackets
}
}
}
tokens = processed;
// finally, construct a game tree from '(', ')', and sequence arrays - each leaf is {sequence: [ <list of nodes> ], leaves: [ <list of leaves> ]}
var stack = [], currentRoot = {sequence: [], leaves: []}, lastRoot; // we know first element already: '('
for(i=1, len=tokens.length; i<len-1; ++i) {
token = tokens[i];
if(token == '(') { // enter a subleaf
if(currentRoot.sequence.length === 0) { // consecutive parenthesis without node sequence in between will throw an error
ERROR = 'SGF contains a game tree without a sequence!';
return false;
}
stack.push(currentRoot); // save current leaf for when we return
currentRoot = {sequence: [], leaves: []};
} else if(token == ')') { // return from subleaf
if(currentRoot.sequence.length === 0) { // consecutive parenthesis without node sequence in between will throw an error
ERROR = 'SGF contains a game tree without a sequence!';
return false;
}
lastRoot = currentRoot;
currentRoot = stack.pop();
currentRoot.leaves.push(lastRoot);
} else { // if every '(' is not followed by exactly one array of nodes (as it should), this code fails
currentRoot.sequence.push(token);
}
}
if(stack.length > 0) {
ERROR = 'Invalid number of parentheses in the SGF!';
return false;
}
return currentRoot;
}
/*
* Apply SGF nodes recursively to create a game tree.
* @returns true on success, false on error. Error message in ERROR.
*/
function recurseRecord(jrecord, gameTree, moveMarks) {
for(var i=0; i<gameTree.sequence.length; i++) {
var node = gameTree.sequence[i],
jnode = jrecord.createNode(true); // clear parent marks
for(var key in node) {
if(node.hasOwnProperty(key)) {
if(!(key in SGFProperties))
continue;
if(!SGFProperties[key](jnode, key, node[key], moveMarks)) {
ERROR = 'Error while parsing node ' + key + ': ' + ERROR;
return false;
}
}
}
}
for(i=0; i<gameTree.leaves.length; i++) {
var subTree = gameTree.leaves[i], snapshot;
snapshot = jrecord.createSnapshot();
if(!recurseRecord(jrecord, subTree, moveMarks))
return false; // fall through on errors
jrecord.restoreSnapshot(snapshot);
}
return true;
}
/*
* Convert game tree to a record.
* @returns {Object} Record or false on failure. Error stored in ERROR.
*/
function gameTreeToRecord(gameTree, moveMarks) {
var jrecord, root = gameTree.sequence[0], width = 19, height = 19;
if('SZ' in root) {
var size = root.SZ[0];
if(size.indexOf(':') != -1) {
width = parseInt(size.substring(0, size.indexOf(':')));
height = parseInt(size.substr(size.indexOf(':')+1));
} else width = height = parseInt(size);
}
jrecord = new Record(width, height);
if(!recurseRecord(jrecord, gameTree, moveMarks))
return false;
jrecord.first(); // rewind to start
return jrecord;
}
/**
* Parse SGF and return {@link Record} object(s).
*
* @param {String} sgf The SGF file as a string.
* @param {bool} moveMarks Create move and ko marks in the record.
* @returns {Object} Record object, array of them, or string on error.
*/
exports.load = function(sgf, moveMarks) {
var gameTree = parseSGF(sgf);
if(gameTree.sequence.length === 0) { // potentially multiple records
var ret = [];
if(gameTree.leaves.length === 0)
return 'Empty game tree!';
for(var i=0; i<gameTree.leaves.length; i++) {
var rec = gameTreeToRecord(gameTree, moveMarks);
if(!rec)
return ERROR;
ret.push(rec); // return each leaf as separate record
}
return ret;
}
return gameTreeToRecord(gameTree, moveMarks);
};
},{"./constants":4,"./coordinate":5,"./record":9}],12:[function(require,module,exports){
'use strict';
var C = require('./constants');
/**
* Create a jGoBoard stones object. This is a facility that can draw
* stones and markers on a HTML5 canvas. Only used internally by the
* library.
*
* @param {Object} options Options array.
* @constructor
*/
var Stones = function(options, images) {
this.stoneR = options.stone.radius;
this.gridX = options.grid.x;
this.gridY = options.grid.x;
this.markX = this.stoneR * 1.1;
this.markY = this.stoneR * 1.1;
this.circleR = this.stoneR * 0.5;
this.triangleR = this.stoneR * 0.9;
this.images = images;
};
Stones.prototype.drawStone = function(ctx, type, ox, oy, scale) {
if(!scale) scale = 1;
var stone = (type == C.BLACK || type == C.DIM_BLACK) ? this.images.black : this.images.white;
if(!stone) { // BW
ctx.fillStyle = (type == C.WHITE) ? '#FFFFFF' : '#000000';
ctx.beginPath();
ctx.arc(ox, oy, this.stoneR*scale, 2*Math.PI, false);
ctx.fill();
if(type == C.WHITE) {
ctx.strokeStyle = '#000000';
ctx.stroke();
}
} else {
// round x, y for crisp rendering
ctx.drawImage(stone, 0, 0, stone.width, stone.height,
Math.round(ox - stone.width / 2 * scale),
Math.round(oy - stone.height / 2 * scale),
stone.width * scale, stone.height * scale);
}
};
Stones.prototype.drawShadow = function(ctx, ox, oy, scale) {
var shadow = this.images.shadow;
if(!shadow) return;
if(!scale) scale = 1;
ctx.drawImage(shadow, 0, 0, shadow.width, shadow.height,
Math.round(ox - shadow.width / 2 * scale),
Math.round(oy - shadow.height / 2 * scale),
shadow.width * scale, shadow.height * scale);
};
Stones.prototype.drawMark = function(ctx, mark, ox, oy) {
switch(mark) {
case C.MARK.SQUARE:
ctx.beginPath();
ctx.rect(ox - this.markX / 2, oy - this.markY / 2, this.markX, this.markY);
ctx.stroke();
break;
case C.MARK.CROSS:
ctx.beginPath();
ctx.moveTo(ox - this.markX / 2, oy + this.markY / 2);
ctx.lineTo(ox + this.markX / 2, oy - this.markY / 2);
ctx.moveTo(ox - this.markX / 2, oy - this.markY / 2);
ctx.lineTo(ox + this.markX / 2, oy + this.markY / 2);
ctx.stroke();
break;
case C.MARK.TRIANGLE:
ctx.beginPath();
for(var r=0; r<3; r++) {
ctx.moveTo(ox + this.triangleR * Math.cos(Math.PI * (0.5 + 2*r/3)),
oy - this.triangleR * Math.sin(Math.PI * (0.5 + 2*r/3)));
ctx.lineTo(ox + this.triangleR * Math.cos(Math.PI * (0.5 + 2*(r+1)/3)),
oy - this.triangleR * Math.sin(Math.PI * (0.5 + 2*(r+1)/3)));
}
ctx.stroke();
break;
case C.MARK.CIRCLE:
ctx.beginPath();
ctx.arc(ox, oy, this.circleR, 2*Math.PI, false);
ctx.stroke();
break;
case C.MARK.BLACK_TERRITORY:
ctx.globalAlpha=1;
this.drawStone(ctx, C.BLACK, ox, oy, 0.5);
break;
case C.MARK.WHITE_TERRITORY:
ctx.globalAlpha=1;
this.drawStone(ctx, C.WHITE, ox, oy, 0.5);
break;
case C.MARK.SELECTED:
ctx.globalAlpha=0.5;
ctx.fillStyle = '#8080FF';
//ctx.beginPath();
ctx.fillRect(ox - this.gridX / 2, oy - this.gridY / 2,
this.gridX, this.gridY);
break;
default: // Label
// For clear intersections, grid is cleared before shadow cast
ctx.fillText(mark, ox, oy);
break;
}
};
module.exports = Stones;
},{"./constants":4}],13:[function(require,module,exports){
'use strict';
/**
* Utility function module.
* @module util
*/
var Coordinate = require('./coordinate');
/**
* Load images and defined by object and invoke callback when completed.
*
* @param {Object} sources A dictionary of sources to load.
* @param {function} callback A callback function to call with image dict.
*/
exports.loadImages = function(sources, callback) {
var images = {}, imagesLeft = 0;
for(var src in sources) // count non-false properties as images
if(sources.hasOwnProperty(src) && sources[src])
imagesLeft++;
var countdown = function() {
if(--imagesLeft <= 0) {
callback(images);
}
};
for(src in sources) { // load non-false properties to images object
if(sources.hasOwnProperty(src) && sources[src]) {
/* global Image */
images[src] = new Image();
images[src].onload = countdown;
images[src].src = sources[src];
}
}
};
/**
* Helper function to create coordinates for standard handicap placement.
*
* @param {int} size Board size (9, 13, 19 supported).
* @param {itn} num Number of handicap stones.
* @returns {Array} Array of Coordinate objects.
*/
exports.getHandicapCoordinates = function(size, num) {
// Telephone dial style numbering
var handicapPlaces = [[], [], [3,7], [3,7,9], [1,3,7,9], [1,3,5,7,9],
[1,3,4,6,7,9], [1,3,4,5,6,7,9], [1,2,3,4,6,7,8,9],
[1,2,3,4,5,6,7,8,9]];
var places = handicapPlaces[num], offset = (size <= 9 ? 2 : 3),
step = (size - 1) / 2 - offset, coords = [];
if(places) {
for(var n=0; n<places.length; n++) {
var i = (places[n]-1) % 3, j = Math.floor((places[n]-1) / 3);
coords.push(new Coordinate(offset+i*step, offset+j*step));
}
}
return coords;
};
/**
* Deep extend an object.
*
* @function extend
* @param {Object} dest Destination object to extend.
* @param {Object} src Source object which properties will be copied.
* @returns {Object} Extended destination object.
*/
exports.extend = function(dest, src) {
for(var key in src) {
if(src.hasOwnProperty(key)) {
if(typeof src[key] === 'object') {
if(!dest[key] || (typeof dest[key] !== 'object'))
dest[key] = {}; // create/overwrite if necessary
exports.extend(dest[key], src[key]);
} else dest[key] = src[key];
}
}
return dest;
};
},{"./coordinate":5}],14:[function(require,module,exports){
'use strict';
var JGO = require('./JGO');
window.JGO = JGO; // expose as global object
},{"./JGO":6}]},{},[14]);
JavaScript
1
https://gitee.com/FuckWork/alphaGo.git
git@gitee.com:FuckWork/alphaGo.git
FuckWork
alphaGo
alphaGo
master

搜索帮助