/*
The contents of this file are (C) Paul Stephens 2005. All Rights reserved. 
You may use this software free of charge on non profit-making websites, provided that this
copyright notice is left intact.

This software must not be used on revenue-generating websites, or distributed with a commerical
package, except by agreement with the copyright holder.

For details contact paul@paulspages.co.uk.

Version 1.2.2 6th April 2005
*/

//alert("bgmain v1.2")

function bgBoard(boardID, playcolour, imgpath, horizOrientation, povPlayer) {
var i, j, k, ki
var imgp = (imgpath == null) ? "" : imgpath
this.imgpath = imgp
this.base64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
this.playcols = new Array()
this.playnames= new Array()
this.boardID = boardID
this.timeOut = null
this.undoString = ""
this.curLabels = 0


// 'Point of view' player number (i.e. the one who is playing towards the bottom of the board
// This is made variable to help with importing games from GNU etc.
this.onsrcs = new Array()
this.offsrcs = new Array()
this.diceImgNames = new Array()
this.diceImgNames[1] = new Array()
this.diceImgNames[2] = new Array()

this.povPlayer = (povPlayer == null) ? 1 : povPlayer * 1
this.povColour = "w"  // Colour to use for POV player (default white)
if (playcolour != null) {
	if (playcolour.toUpperCase() == 'B') {this.povColour = "b"}
}



this.showGNUPositionID = bg_showGNUPositionID
this.setPOV = bg_setPOV
this.setPOV(this.povPlayer, this.povColour)

// Horizontal board layout - default is top-left
// NOTE - overridden later if constructor param is 'r'
this.horizOrientation = 'l' 

// Working variables for animated moves
this.xtarget = 0
this.ytarget = 0
this.xstep   = 4
this.ystep   = 4
this.xdir = 0
this.ydir = 0
this.pieceMoving = null
this.animDelay = 10
this.animInProgress = false
this.animCallback = ""
this.animCallbackDelay = 500
this.player2hit = 0
this.ID2hit = ""
this.stepMode = false
this.demoMode = false

// Move queue properties
this.moveQueue = new Array()
this.mqwork = new Array()
this.playPointer = 0
this.qActive = false
this.qPlayer = 0
this.mqCallback = ""
this.moveLegendDisp = null // If pointed to a display element, animated moves will highlight current move
this.stepMode = false
this.hasMoved = false // flag for use by calling processes - set true by movePiece() method


// DIMENSION SETTINGS - change these to work with a different board/piece layout
// Board dimension configuration is now in a separate external script for easy dev
this.config = bg_config
this.config()

// Generate container and board image HTML
document.write('<div id="' + boardID + 'maindiv" style="position:relative;width:' + this.boardWidth + ';height:' + this.boardHeight + ';">')
document.write('<img src="' + imgp + 'board.jpg">')

k = boardID + 'cubeDisp'
document.write('<div id="' + k + '" style="background-image:url(' + imgp + 'cube.gif);background-repeat:no-repeat;position:absolute;text-align:center;left:' 
	+ this.cubeX + ';top:' + this.cubeY[0] + ';width:' + this.cubeSize + ';height:' + 
	this.cubeSize + ';' + this.cubeFont + ';">' + 
	'64</div>')
this.cubeDisp = document.getElementById(k)
// preload cube offer image
document.write('<img src="' + imgp + 'cubeoffer.gif" style="display:none">')

// Note that the cube status properties are duplicated int the bg_game object
// These versions are maintained by this object's showCube() method in case anyone wants 
// to use them
this.cubeval = 1
this.cubewith = 0

this.setHoriz = bg_setHoriz

// background-image:url(' + imgp + 'cube.gif);


this.diceimgs = new Array()
this.diceimgs[1] = new Array()
this.diceimgs[2] = new Array()
k = boardID + 'dice10'
document.write('<img id="' + k + '" src="' + this.diceImgNames[1][0] + '" style="position:absolute;left:' + this.dice1x + ';top:' + this.dicey + ';">')
this.diceimgs[1][0] = document.getElementById(k)
k = boardID + 'dice11'
document.write('<img id="' + k + '" src="' + this.diceImgNames[1][0] + '" style="position:absolute;left:' + (this.dice1x + this.dicesize + (this.dicesize/2)) + ';top:' + this.dicey + ';">')
this.diceimgs[1][1] = document.getElementById(k)

k = boardID + 'dice20'
document.write('<img id="' + k + '" src="' + this.diceImgNames[2][0] + '" style="position:absolute;left:' + this.dice2x + ';top:' + this.dicey + ';">')
this.diceimgs[2][0] = document.getElementById(k)
k = boardID + 'dice21'
document.write('<img id="' + k + '" src="' + this.diceImgNames[2][0] + '" style="position:absolute;left:' + (this.dice2x + this.dicesize + (this.dicesize/2)) + ';top:' + this.dicey + ';">')
this.diceimgs[2][1] = document.getElementById(k)

// Preload other dice images

for (j = 1; j < 3; j++) {
	for (i = 1; i < 7; i++) {
		document.write('<img src="' + this.diceImgNames[j][i] + '" id="' + this.boardID + "xd" + j + i + '" style="display:none">')
	}
}

this.labels = new Array()
j = this.ylower + this.lowerlabelOffset 
for (i = 1; i < 13; i ++) {
	k = boardID + 'lb' + i
	document.write('<span id="' + k + '" style="position:absolute;text-align:center;width:' + this.labelWidth + ';left:' + this.pointx[i] + ';top:' + j + ';' + this.labelFont + '">&nbsp;</span>')
	this.labels[i] = document.getElementById(k)
}
j = this.yupper - this.upperlabelOffset // SET UPPER LABELS VERTICAL OFFSET HERE
for (i = 13; i < 25; i ++) {
	k = boardID + 'l' + i
	document.write('<span id="' + k + '" style="position:absolute;text-align:center;width:' + this.labelWidth + ';left:' + this.pointx[i] + ';top:' + j + ';' + this.labelFont + '">&nbsp;</span>')
	this.labels[i] = document.getElementById(k)
}

// Switch horizontal orientation if specified.
if (horizOrientation != null) {
	if (horizOrientation.toLowerCase() == 'r') {  // play from top-right 
		this.setHoriz('r')
	}
}

for (i=1; i < 25; i++) {
	this.points[i] = new Array() 
	this.points[i].owner = 0
}
// Arrays for holding pieces while on the bar
this.bar = new Array()
this.bar[1] = new Array()
this.bar[1].owner = 0
this.bar[1].ypos = this.bar1ypos 
this.bar[2] = new Array()
this.bar[2].owner = 0
this.bar[2].ypos = this.bar2ypos

// Arrays for holding pieces that have been born off
this.off = new Array()
this.off[1] = new Array()
this.off[1].owner = 0
this.off[1].ypos = this.uppertrayYpos
this.off[2] = new Array()
this.off[2].owner = 0
this.off[2].ypos = this.lowertrayYpos

// Image objects to hold piece displays 
this.pieces = new Array()
this.pieces[1] = new Array()
this.pieces[2] = new Array()

this.pimages = new Array()
this.pimages[1] = new Array()
this.pimages[2] = new Array()


for (j = 1; j < 3; j++) {
	for (i = 0; i < 15; i++) {
		k = boardID + 'p' + this.playcols[j] + i
		ki = boardID + 'pi' + this.playcols[j] + i
		document.write('<img src="' + this.onsrcs[j] + '" id="' + k + '" style="position:absolute;width:24left:0;top:0;display:;z-index:10">')
//		document.write('<span id="' + k + '" style="position:absolute;left:0;top:0;display:;z-index:10"><img src="' + this.onsrcs[j] + '" id="' + ki + '"></span>')

		this.pieces[j][i] = document.getElementById(k)
//		this.pimages[j][i] = document.getElementById(ki)
		this.off[j][this.off[j].length] = this.pieces[j][i]
	}
	this.off[j].owner = j
}
document.write("</div>")

// hook up methods
this.movePiece = bg_movePiece
this.doMoves = bg_doMoves
this.doM2 = bg_doM2
this.showLabels = bg_showLabels
this.genGNUPositionID = bg_genGNUPositionID
this.aniMove = bg_aniMove
this.move2Bar = bg_move2Bar
this.showDice = bg_showDice
this.genGNUforward = bg_genGNUforward
this.genGNUbackward = bg_genGNUbackward
this.pipCount = bg_pipCount
this.showCube = bg_showCube
this.undoMoves = bg_undoMoves
//this.showLabels(1)
this.resetBoard = bg_resetBoard
this.flipHoriz = bg_flipHoriz
this.calibrate = bg_calibrate
this.cb2 = bg_cb2
this.cb3 = bg_cb3
}

function bg_calibrate(callBack, target, min, max) {
//alert("Click OK to calibrate")
this.cb_callback = callBack
this.cb_target = target
this.cb_min = min
this.cb_max = max
this.cb_results = new Array()
this.cb_imgObj = this.pieces[1][0]
this.cb_imgObj.oldSrc = this.cb_imgObj.src
this.cb_imgObj.src = this.imgpath + "pb.gif"
this.cb_imgObj.oldX = parseInt(this.cb_imgObj.style.left)
this.cb_imgObj.oldY = parseInt(this.cb_imgObj.style.top)
this.cb_imgObj.style.left = this.cb_imgObj.style.top = 0
this.cb_averagemoves = 0
this.cb_stop = false
this.cb_count1 = -1
this.cb_count2 = 0
setTimeout(this.boardID + ".cb3()", 0)

}


function bg_cb2() {
var x = parseInt(this.cb_imgObj.style.left), y = parseInt(this.cb_imgObj.style.top)
if (this.cb_stop) {
	setTimeout(this.boardID + ".cb3()", 0)
	return
} 
this.cb_imgObj.style.left = x + 2
this.cb_imgObj.style.top = y + 2
this.cb_count2++
setTimeout(this.boardID + ".cb2()", 10)
}

function bg_cb3() {
if (this.cb_count1 >= 0) {
	this.cb_results[this.cb_count1] = this.cb_count2
}
this.cb_count1++
if (this.cb_count1 <= 4) {  // Do another test
	//alert("Done a test " + this.cb_imgObj.style.left)
	this.cb_count2 = 0
	this.cb_imgObj.style.left = this.cb_imgObj.style.top = 0
	this.cb_stop = false
	setTimeout(this.boardID + ".cb2()", 0)
	setTimeout(this.boardID + ".cb_stop = true", 1000)
	return
}
// End of run
var i, totmoves = 0, newStep = 0
for (i = 0; i < this.cb_results.length; i++) {
	totmoves += this.cb_results[i]
}
this.cb_averagemoves = Math.floor(totmoves/this.cb_results.length)
this.cb_imgObj.src = this.cb_imgObj.oldSrc
this.cb_imgObj.style.left = this.cb_imgObj.oldX
this.cb_imgObj.style.top = this.cb_imgObj.oldY

newStep = Math.round(this.cb_target/this.cb_averagemoves)
newStep = (newStep < this.cb_min) ? this.cb_min : newStep
newStep = (newStep > this.cb_max) ? this.cb_max : newStep
this.xstep = this.ystep = newStep
// alert("Calibration: " + " average " + this.cb_averagemoves + " moves per second, step set to " + this.xstep + ", calling " + this.cb_callback)
setTimeout(this.cb_callback, 0)
}




function bg_setPOV(player, colour) {
var i, j
var opp = (player == 1) ? 2 : 1
var oppcolour = (colour == "w") ? "b" : "w"
this.playcols[player] = (colour.toLowerCase() == "w") ? "w" : "b"
this.playcols[opp] = (this.playcols[player] == "w") ? "b" : "w"
this.playnames[player] = (this.playcols[player] == "w") ? "White" : "Red"
this.playnames[opp] = (this.playcols[opp] == "b") ? "Red" : "White" 
this.povPlayer = player
this.povColour = colour
this.onsrcs[1] = this.imgpath + 'p' + this.playcols[1] + ".gif"
this.onsrcs[2] = this.imgpath + 'p' + this.playcols[2] + ".gif"
this.offsrcs[1] = this.imgpath + 'p' + this.playcols[1] + "s.gif"
this.offsrcs[2] = this.imgpath + 'p' + this.playcols[2] + "s.gif"

for (j = 1; j < 3; j++) {
	for (i=0; i < 7; i++) {
		this.diceImgNames[j][i] = this.imgpath + 'd' + this.playcols[j] + i + ".gif"
	}
}
//alert(this.diceImgNames[1] + " " + this.diceImgNames[2])
}

function bg_flipHoriz(d) {
// Warning - this function is dangerous!
// It doesn't check whether there are any moves in progress
// It's best used only when called by a controller (e.g. the Game object)
// which knows what's going on
if (d == null) {
	d = (this.horizOrientation == 'r') ? 'l' : 'r'
} else {
	d = (d.toLowerCase() == "l") ? "l" : "r"
}
var x = this.genGNUPositionID(1)
this.setHoriz(d)
this.showGNUPositionID (x, 1)
}


function bg_setHoriz(dr) {
var i, j, k, jl, kl
dr = dr.toLowerCase()
if (dr != this.horizOrientation) {
	for (i = 1; i < 7; i++) {
		j = this.pointx[i]
		jl = this.labels[i]
		k = 13 - i
		this.pointx[i] = this.pointx[k]
		this.labels[i] = this.labels[k]	
		this.pointx[k] = j
		this.labels[k] = jl
	}
	for (i = 13; i < 19; i++) {
		j = this.pointx[i]
		jl = this.labels[i]
		k = 37 - i
		this.pointx[i] = this.pointx[k]	
		this.labels[i] = this.labels[k]
		this.pointx[k] = j
		this.labels[k] = jl
	}
	this.horizOrientation = (this.horizOrientation == 'r') ? 'l' : 'r'
}
this.pointx[0] = (this.horizOrientation == 'r') ? this.rightSideOff	: this.leftSideOff
}


function bg_resetBoard() {
//	this.showGNUPositionID("4HPwATDgc/ABMA")
	this.showGNUPositionID("AAAAAAAAAAAAAA")
	this.showCube(0,0)
	this.showDice(1,0,0)
	this.showDice(2,0,0)
}	

function bg_showCube(plr, cubeVal, offer) {
var trayNo = (plr == 0) ? 0 : (plr == this.povPlayer) ? 2 : 1
cubeVal = (cubeVal == null) ? 1 : cubeVal 
if (offer == null) {offer = false}
var x = (cubeVal < 2) ? 64 : cubeVal	
this.cubeDisp.innerHTML = x
this.cubeDisp.style.top = this.cubeY[trayNo]
if (offer) {
	this.cubeDisp.style.backgroundImage = "url(" + this.imgpath + "cubeoffer.gif)"
} else {
	this.cubeDisp.style.backgroundImage = "url(" + this.imgpath + "cube.gif)"
}
this.cubewith = plr
this.cubeval = (cubeVal < 2) ? 1 : cubeVal
this.hasMoved = true
}

function bg_pipCount(plr) {
var x = 0, i, j = 24
var opp = (this.povPlayer == 1) ? 2 : 1
if (plr == opp) { // Not the POV player - go forward, with descending pips per point
	for (i = 1; i < 25; i++) {
		if (this.points[i].owner == opp) {
			x += (this.points[i].length * j)
		}
		j--
	}
} else { // POV Player - backward, descending pips per point
	for (i = 24; i > 0; i--) {
		if (this.points[i].owner == this.povPlayer) {
			x+= (this.points[i].length * j)
		}
		j--
	}
}
x += (this.bar[plr].length * 25)
return x
}

function bg_showDice(player, d0, d1) {
var valid = true
if (player < 1 || player > 2 || d0 < 0 || d0 > 6 || d1 < 0 || d1 > 6) {valid = false}
if (!valid) {
	alert("Invalid call to showDice, parameters are " + player + ", " + d0 + ", " + d1)
	return -1
}
var side = (player == this.povPlayer) ? 2 : 1
//this.diceimgs[side][0].src = this.imgpath + "dw" + d0 + ".gif"
// this.diceimgs[side][1].src = this.imgpath + "dw" + d1 + ".gif"
this.diceimgs[side][0].src = this.diceImgNames[side][d0]
this.diceimgs[side][1].src = this.diceImgNames[side][d1]
//alert ("Showdice " + this.diceimgs[side][0].src + " " + this.diceimgs[side][1].src)
}

function bg_showGNUPositionID (str, plr) {
if (plr == null) {
	plr = 2
}
if (str.length != 14) {
	alert(str + " is not a valid GNU backgammon position ID")
	return false
}
var i, j, k, m, n, x = "", y = "", z = "", h, d = 0
// Decode characters into binary strings
for (i = 0; i < 14; i++) {
	j = str.substr(i,1)
	k = this.base64tab.indexOf(j)
	if (k < 0) {
		alert("Invalid character " + j + " in position ID string")
		return false
	}
	d = 0
	for (n = 32; n >= 1; n/=2) {
		z += Math.floor(k/n)
		k = k % n
	}

}
z = z.substr(0,80)
for (j = 7; j < 80; j+=8) {
	for (i = j; i > (j - 8); i--) {
		y += z.substr(i,1)	
	}
}

// Clear board by moving all pieces to the off location
/*
for (j = 1; j < 3; j++) {
	for (i = 1; i < 25; i++) {
		if (this.points[i].owner == j) {
			m = (j == this.povPlayer) ? i : (25 - i) // Reverse point nos for 1st player
			n = this.points[i].length
			for (k = 0; k < n; k++) {
				//alert("Moving " + this.playnames[j] + " from point " + m + " to off.")
				this.movePiece(j, m, 0, 1)
			}
		}
	}
	if (this.bar[j].length > 0) {
		n = this.bar[j].length
		for (k = 0; k < n; k++) {
			this.movePiece(j, 25, 0, 1)
		}
	}
}
*/

// reset point arrays
for (i=1; i < 25; i++) {
	this.points[i].length = 0
	this.points[i].owner = 0
}

for (i=1; i<3; i++) {
	this.bar[i].length = 0
	this.bar[i].owner = 0
	this.off[i].length = 0
	this.off[i].owner = 0
}

// Put all pieces in the off tray.
// This method ignores the pieces' current location, so is safe against
// aborted moves in the previous play
var p2move, trayNo 
for (i = 1; i < 3; i++) {
	this.off[i].owner = i
	trayNo = (i == this.povPlayer) ? 2 : 1
	for (j = 0; j < 15; j++) {
		p2move = this.pieces[i][j]
		p2move.style.top = this.off[trayNo].ypos + (this.off[i].length * this.sideheight)		
		this.off[i][this.off[i].length] = p2move
		p2move.src = this.offsrcs[i]
		p2move.style.left = this.pointx[0]
	}
}

// Work through decoded string, moving peices from the off tray
i = 1; j = 0
// Player 

do {
	x = y.substr(j,1)
	if (x == "0") {i++}
	if (x == "1") {
		this.movePiece(plr, 0, i)
	}
	j++
}
while (i < 25)
do {
	x = y.substr(j,1)
	if (x == "0") {i++}
	if (x == "1") {
		this.movePiece(plr, 0, 25)
	}
	j++
}
while (i < 26) 

// Opponent

plr = (plr == 1) ? 2 : 1

i = 1
do {
	x = y.substr(j,1)
	if (x == "0") {i++}
	if (x == "1") {
		this.movePiece(plr, 0, i)
	}
	j++
}
while (i < 25)
do {
	x = y.substr(j,1)
	if (x == "0") {i++}
	if (x == "1") {
		this.movePiece(plr, 0, 25)
	}
	j++
}
while (i < 26) 

return y

}

function XXXXXbg_showGNUPositionID (str, plr) {
// NEW VERSION UNDER DEVELOPMENT
if (plr == null) {
	plr = 2
}
if (str.length != 14) {
	alert(str + " is not a valid GNU backgammon position ID")
	return false
}
var i, j, k, m, n, x = "", y = "", z = "", h, d = 0
// Decode characters into binary strings
for (i = 0; i < 14; i++) {
	j = str.substr(i,1)
	k = this.base64tab.indexOf(j)
	if (k < 0) {
		alert("Invalid character " + j + " in position ID string")
		return false
	}
	d = 0
	for (n = 32; n >= 1; n/=2) {
		z += Math.floor(k/n)
		k = k % n
	}

}
z = z.substr(0,80)
for (j = 7; j < 80; j+=8) {
	for (i = j; i > (j - 8); i--) {
		y += z.substr(i,1)	
	}
}

// Faster version
// This one bypasses the standard move mechanism - instead it 
// works through the decoded posID bitmap, grabbing images straight from the pieces[]
// array and sticking them onto points. It doesn't clear the board first, or worry
// about having multiple pieces temporarily in the same location. Everything gets
// sorted in the end.

// reset point arrays
for (i=1; i < 25; i++) {
	this.points[i].length = 0
	this.points[i].owner = 0
}

for (i=1; i<3; i++) {
	this.bar[i].length = 0
	this.bar[i].owner = 0
	this.off[i].length = 0
	this.off[i].owner = 0
}

// Work through decoded string in two passes

var lcontrol = 1
var piecePointer , player , tp, toPointArray, pointInit , p2move 
var stackoffset

do {
	i = 1; j = 0
	piecePointer = 0; player = plr;  pointInit = false; p2move = null
	// Decoded string has a 0 for each point on the board (including the bar as point 25)
	// This pattern is repeated twice, first for the player, then the opponent
	// Point numbers are for each player's POV (so run 1-25 for each player)
	// Any 1s preceding a zero indicate a piece for the current player on that point
	// So 110010 means 'two pieces on the one point and one piece on the three point'
	do {
		x = y.substr(j,1)
		if (x == "0") { // end of point i - move on to next
			i++
			pointInit = false
		} 
		if (x == "1") { // this player has a piece on point i (from this player's perspective)
			if (!pointInit) {
				tp = (player == this.povPlayer) ? i : 25 - i
				toPointArray = this.points[tp]
				toPointArray.owner = player
				pointInit = true
			}
			p2move = this.pieces[player][piecePointer++]
			p2move.src = this.onsrcs[player]
			stackoffset = Math.floor(toPointArray.length/6)
			ey = (tp > 12) ? (stackoffset * this.pieceheight /2) + this.yupper + ((toPointArray.length - (stackoffset * 6)) * this.pieceheight) : this.ylower - (stackoffset * this.pieceheight /2) - ((toPointArray.length - (stackoffset * 6)) * this.pieceheight)
			ex = this.pointx[tp]
			p2move.style.top = ey
			p2move.style.left = ex
			p2move.style.zIndex = 10 + (stackoffset * 10)
			toPointArray[toPointArray.length] = p2move
			if (!confirm(piecePointer + ", tp=" + tp)) {return}
		}
		j++
	}
	while (i < 25)
	do {  // bar (point 25) - special process
		x = y.substr(j,1)
		if (x == "0") { // end of point i - move on to next
			i++
			pointInit = false
		} 
		if (x == "1") {
			if (!pointInit) {
				toPointArray = this.bar[player]
				toPointArray.owner = player
				pointInit = true
			}
			p2move = this.pieces[player][piecePointer++]
			p2move.src = this.onsrcs[player]
			tp = 25
			stackoffset = Math.floor(this.bar[player].length/4)
			ey = this.bar[player].ypos + (stackoffset * this.pieceheight /2) + ((this.bar[player].length - (stackoffset * 4)) * this.pieceheight)	
			ex = this.pointx[tp]
			p2move.style.top = ey
			p2move.style.left = ex
			p2move.style.zIndex = 10 + (stackoffset * 10)
			toPointArray[toPointArray.length] = p2move
		}
		j++
	}
	while (i < 26) 
	// now move any unused pieces to the off tray
	toPointArray = this.off[player]
	tp = 0
	ex = this.pointx[tp]
	for (i = piecePointer; i < 15; i++) {
		p2move = this.pieces[player][i]
		p2move.src = this.offsrcs[player]
		ey = this.off[player].ypos + (this.off[player].length * this.sideheight)
		p2move.style.top = ey
		p2move.style.left = ex
		p2move.style.zIndex = 10 
		toPointArray[toPointArray.length] = p2move
	}
	
	plr = (plr == 1) ? 2 : 1  // Toggle to opponent for second pass
	lcontrol++
} while (lcontrol <3)

return y

}


function bg_genGNUPositionID (plr) {
if (plr == null) {plr = 2}
// This method generates a GNU backgammon position string
// for the specified player (1=opponent, 2=board POV)
var i, j, x = "", y = "", z = "", h, op
op = (plr == 1) ? 2 : 1
// alert("plr is " + plr + ", op is " + op)

// 1st pass - generate a "1" for each piece the specified player (plr) has on each
// point, plus a "0" for every point. The bar is counted as point 25

// Player
if (plr == this.povPlayer) {
	x += this.genGNUforward(plr)
} else {
	x += this.genGNUbackward(plr)
}

if (this.bar[plr].length > 0) {
	for (j=0; j < this.bar[plr].length; j++) {
		x += "1"
	} 
} 
x += "0"

// Now do the same for the other player. 
// Work in opposite direction

if (plr != this.povPlayer) {
	x += this.genGNUforward(op)
} else {
	x += this.genGNUbackward(op)
}

if (this.bar[op].length > 0) {
	for (j=0; j < this.bar[op].length; j++) {
		x += "1"
	} 
} 
x += "0"

if (x.length < 80) {
	for (j = x.length; j < 81; j++) {
		x += "0"
	}
}
// OK, now convert it to "little endian" format
for (j = 7; j < 80; j+=8) {
	for (i = j; i > (j - 8); i--) {
		y += x.substr(i,1)	
	}
}
// OK (phew!) now convert it to Base64 6-bit values
// Pad out to 84 bytes for conversion to 14 six-bit values
y += "0000"
for (j = 0; j < 84; j += 6) {
	h = (y.substr(j,1) * 32) + (y.substr(j+1,1) * 16) + (y.substr(j+2,1) * 8) + (y.substr(j+3,1) * 4) + (y.substr(j+4,1) * 2) + (y.substr(j+5,1) * 1)
	z += this.base64tab.substr(h,1)
}
return z
}

function bg_genGNUforward(plr) {
var i, j, x = ""
for (i=1; i < 25; i++) {
	if (this.points[i].owner == plr) {
		for (j = 0; j < this.points[i].length; j++) {
			x += "1"
		}
	} 
	x += "0"
}
return x
}

function bg_genGNUbackward(plr) {
var i, j, x = ""
for (i=24; i > 0; i--) {
	if (this.points[i].owner == plr) {
		for (j = 0; j < this.points[i].length; j++) {
			x += "1"
		}
		
	} 
	x += "0"
}
return x
}

function bg_showLabels(player) {
var i, j
if (player == null) {player = this.curLabels}
this.curLabels = player
if (player == 0) {
	for (i = 1; i < 25; i++) {
		this.labels[i].innerHTML = ""
	}
	return
}

if (player == this.povPlayer) {
	for (i = 1; i < 25; i++) {
		this.labels[i].innerHTML = i
	}
} else {
	j = 24
	for (i = 1; i < 25; i++) {
		this.labels[i].innerHTML = j
		j--
	} 
}
}


function bg_undoMoves(movetype, mqCallback) {
// This function undoes the last set of moves made by bg_doMoves().
// It won't work if there's been a showGNUPositionID() call since the last bg_doMoves().
if (this.undoString == "") {
	if (mqCallback != null) {
		setTimeout(mqCallback, 0) 
	}
	return
}
var xs = 0, xe = 0, xm = 0, xi = 0, i
var movestr = this.undoString

// The undo string was assembled by the previous bg_doMoves() call.
// It contains a reverse version of each move that was played, e.g. 
// original move = 13/8, reverse move = 8/13

// These are stored in the order that the original moves were played,
// so the order of play has to be reversed.

// The string may contain extra moves, representing reverse versions of pieces that were moved 
// to the bar. These are preceded by "^", and use the opponent player's numbering.
// So if 13/8 sent the opponent to the bar, the undo string will contain
// ^25/17 8/13 

// This function creates a new move string containing the moves in reverse order of 
// play, and feeds it to bg_doMoves().

// bg_doMoves() recognises moves that begin with ^, and applies them to the opponent player. 

// Extract the undoString into an array, then feed them backwards into a new movestring 
this.moveQueue.length = 0
while (xs < movestr.length) {
	xm = movestr.indexOf("/", xs) // search for middle of next move pair
	if (xm < 0) {break} // no more moves in string
	xs = movestr.lastIndexOf(" ", xm) // Search back from midpoint for space
	if (xs < 0) {xs = 0} // this was first item in string
	xe = movestr.indexOf(" ", xm) // search forward for space
	if (xe < 0) {xe = movestr.length -1} // this was last item in string
	this.moveQueue[this.moveQueue.length] = movestr.substring(xs, xe+1)
	xs = xe+1
}
movestr = ""
for (i = (this.moveQueue.length - 1); i >= 0; i--) {
	movestr += this.moveQueue[i].toString() + " "
}
this.doMoves(this.qPlayer, movestr, movetype, mqCallback)
}


function bg_doMoves(player, movestr, movetype, mqCallback) {
// this function creates a queue of moves from a move string (e.g. "24/21 13/10 6/3 6/3").
// In non-animated modes it then performs the moves synchronously.
// In animated mode it kicks off a timeout chain to animate the moves.
// mqCallback (optional) holds the function call to return control after the moves are done

if (player < 1 || player > 2) {
	alert("Invalid player no. (" + player + ") supplied to domoves()")
	return -1
}
if (movestr == "" || movestr.indexOf("/") < 1) {
	//alert("Invalid move string (" + movestr + ") supplied to domoves()")
	return -1
}
// modes (movetype values) are:
// 0 - animated
// 1 - immediate
// 2 - data only (no graphic update) - used for script mapping
// default is animated.
if (movetype == null) {movetype = 0}
// mqCallback is the function to be called (via timeout) after this queue has been processed
this.mqCallback = (mqCallback == null) ? "" : mqCallback
//alert("doMoves() has set callback to " + this.mqCallback)


var xs = 0, xe = 0, xm = 0, xi = 0, xj = 0, xk = 0, xm = "", xt = ""
var m1 = new Array(), m2, m3
this.undoString = ""

this.moveQueue.length = 0

movestr = bg_cleanupMoveStr(movestr)
// bg_cleanupMoveStr makes movestr safe for splitting on " "
this.moveQueue = movestr.split(" ")
this.qPlayer = player
this.qActive = true
this.playPointer = 0
var i = 0, x = 0, y, z, done = false, xplayer, xmq

// If the move mode is 1 (immediate) or 2 (no visible move), 
// do the moves in a loop
//alert("Move queue is " + this.moveQueue + ", move type is " + movetype + ", callback is " + this.mqCallback)
if (movetype != 0) {

	for (i = 0; i < this.moveQueue.length; i++) {
		xplayer = player
		if (this.moveQueue[i].indexOf("^") >= 0) {
			xplayer = (player == 1) ? 2 : 1
			//alert("Undoing move to bar - moveQueue entry is " + this.moveQueue[i])
			this.moveQueue[i] = bg_replace(this.moveQueue[i],"^", "")
		}
		xmq = bg_replace(this.moveQueue[i],"bar", "25")
		xmq = bg_replace(xmq,"off", "0")
		this.mqwork = xmq.split("/")
		//alert(this.mqwork.length)
		this.movePiece(xplayer, parseInt(this.mqwork[0]), parseInt(this.mqwork[1]), movetype)
	}
	if (this.mqCallback != "") {
		this.timeOut = setTimeout(this.mqCallback, 0)
		this.mqCallback = ""
	}
	this.qActive = false
	return
}
// Animated moves - more complex!
this.timeOut = setTimeout(this.boardID + ".doM2()", 0)
}

function bg_cleanupMoveStr(str) {
// This function takes a move string (e.g. "24/18 18/8") and converts special GNU and other
// constructs to plain moves, e.g.:
// "6/off" to "6/0"
// "bar/23" to "25/23"
// "24/22(2)" to "24/22 24/22"
// "24/22*/18" to "24/22* 22/18"
var i, j, k, m, n

//str = bg_replace(str, "off", "0")
//str = bg_replace(str, "bar", "25")
// Conver "x/y(2)" format to "x/y x/y"
while (str.indexOf("(") >= 0) {
	i = str.indexOf("(")
	p = str.indexOf(")", i)
	j = parseInt(str.substring(i+1, p))
	m = str.lastIndexOf(" ", i)
	n = str.substring(m+1, i) // move string
	q = " "
	for (k = 0; k < j; k++) {
		q += n + " "
	}
	str = str.substring(0, m) + q + str.substring(p+1)
}
// 
str = bg_btrim(str)  // strip leading and trailing spaces
// reduce multiple embedded spaces to single space, e.g. "6/1      5/4" to "6/1 5/4"
str = bg_1space(str)
// convert "24/18*/16*" format
var xstr = new Array(), m1 = new Array(), xt, m2
xstr = str.split(" ")
str = ""
for (i=0; i < xstr.length; i++) {
	m1=xstr[i].split("/")
	if (m1.length > 2) {
		// string contains at least 2 "/" markers
		xt = ""
		for (m2=0; m2< m1.length - 1; m2++) {
			xt += m1[m2] + "/" + m1[m2+1] + " " 
		}
		str += xt
	} else {
		str += xstr[i] + " "
	}
}
str = bg_replace(str, "*", "")
return bg_btrim(str)
}




function bg_doM2() {

if (this.playPointer >= this.moveQueue.length) { // End of queue
	if (this.mqCallback != "") {
		this.timeOut = setTimeout(this.mqCallback, 0)
		this.mqCallback = ""
	}
	this.qActive = false
	//alert(this.undoString)
	return
}

var x, i, s, j, tPlayer = this.qPlayer, xmq
if (this.moveQueue[this.playPointer].indexOf("^") >= 0) {
	tPlayer = (this.qPlayer == 1) ? 2 : 1
	this.moveQueue[this.playPointer] = bg_replace(this.moveQueue[this.playPointer],"^", "")
}
xmq = bg_replace(this.moveQueue[this.playPointer],"bar", "25")
xmq = bg_replace(xmq,"off", "0")

this.mqwork = xmq.split("/")

// Do highlighted move string display (shows current move in string)
// but not for moves called from command steps (i.e. game plays only)

// OK, taking a flyer here. The call to movePiece is made before the animated move string display,
// even when the move is animated.
// This banks on the rest of this function completing execution before the animation chain
// ends and calls back to this function. It should be OK, since the animation involves
// timeouts, even for a very short move.


var moveRtnString = this.movePiece(tPlayer, parseInt(this.mqwork[0]), parseInt(this.mqwork[1]), 0, null, this.boardID + ".doM2()")
if (moveRtnString.indexOf("^") >= 0) {
	if (this.moveQueue[this.playPointer].indexOf("*") < 0) {
		this.moveQueue[this.playPointer]= bg_btrim(this.moveQueue[this.playPointer]) + "*"
	}
}
this.undoString += moveRtnString + " "
if (this.moveLegendDisp != null && this.stepMode == false) {
	s = (this.demoMode == false) ? "Plays " : "Demo: "; 		
	j = this.playPointer 
	for (i = 0; i < this.moveQueue.length; i++) {
		if (i == j) {
			s+='<span class="moveLegendDisp">'
		}
		s+= this.moveQueue[i] + " "
		if (i == j) {
			s+='</span>'
		}
	}
	this.moveLegendDisp.innerHTML = s
}

// Call movePiece in animated (0) mode
// movePiece kicks off an asynchronous timeout chain, 
// with a callback to this function when the chain ends
this.playPointer++ // increment pointer for next time round
}

function bg_movePiece(player, frompoint, topoint, movetype, initDelay, animCallback) {
var rtnstring = topoint.toString() + "/" + frompoint.toString()
//alert(animCallback)
if (player < 1 || player > 2) {
  alert("Error invalid player number (" + player + ")")
  return -1
}
if (frompoint < 0 || frompoint > 25) {
  alert("Error invalid from point (" + frompoint + ")")
  return -1
}
if (topoint < 0 || topoint > 25) {
  alert("Error invalid to point (" + topoint + ")")
  return -1
}
if (movetype == null) {movetype = 1}
if (initDelay == null) {initDelay = this.animDelay}
this.animCallback = (animCallback == null) ? "" : animCallback
// point 0 = bar, point 25 = off
// reverse point locations for player 2 (i.e. their 24 becomes system's 1)
var fp, tp, opponent, fromPointArray, toPointArray, p2move, rtnval = 0, id2hit = "none"
var sx, sy, ex, ey, stackoffset = 0
opponent = (player == 1) ? 2 : 1
var trayNo = (player == this.povPlayer) ? 2 : 1
switch (frompoint) {
case 25:
	fromPointArray = this.bar[player]
	fp = 25
	break
case 0:
	fromPointArray = this.off[player]
	fp = 0
	break
default:
	fp = (player == this.povPlayer) ? frompoint : 25 - frompoint
	fromPointArray = this.points[fp]
}
switch (topoint) {
case 25:
	toPointArray = this.bar[player]
	tp = 25
	stackoffset = Math.floor(this.bar[player].length/this.barstackthreshold)
	ey = this.bar[trayNo].ypos + (stackoffset * this.pieceheight /2) + ((this.bar[player].length - (stackoffset * this.barstackthreshold)) * this.pieceheight)	
	break
case 0:
	toPointArray = this.off[player]
	ey = this.off[trayNo].ypos + (this.off[player].length * this.sideheight)
	tp = 0
	break
default:
	tp = (player == this.povPlayer) ? topoint : 25 - topoint
	toPointArray = this.points[tp]
	stackoffset = Math.floor(toPointArray.length/this.pointstackthreshold)
	ey = (tp > 12) ? (stackoffset * this.pieceheight /2) + this.yupper + ((toPointArray.length - (stackoffset * this.pointstackthreshold)) * this.pieceheight) : this.ylower - (stackoffset * this.pieceheight /2) - ((toPointArray.length - (stackoffset * this.pointstackthreshold)) * this.pieceheight)
}
ex = this.pointx[tp]
if (fromPointArray.owner != player) {
	alert("Illegal move - " + this.playnames[player] + " does not have a piece to move from their " + frompoint + " point.")
	alert(player + " " + frompoint + " " + topoint + " " + this.povPlayer) 
	return -1
} 
if ((toPointArray.owner != player) && (toPointArray.length > 1)) { 
	alert("Illegal move - the " + topoint + " point is blocked.")
	return -1
}
// OK, move is legal - calculate start and end positions of piece to move
// first find which image object (piece) to move - take highest-numbered one
p2move = fromPointArray[fromPointArray.length - 1]
// Starting (current) coordinates of selected piece image
sx = parseInt(p2move.style.left)
sy = parseInt(p2move.style.top)

//alert(p2move.id)
if (tp == 0) {
	p2move.src = this.offsrcs[player]
} else {
	p2move.src = this.onsrcs[player]
}

this.restingZindex = 10 + (stackoffset * 10)
//alert(p2move.style.zIndex + " " + stackoffset)

// If this is a hit, send opponent to the bar
if (toPointArray.owner == opponent) {
	// This is a hit - move opponent's (sole) piece off this point

	// Note recursive calls to movePiece - be careful to keep all variables in this function local!
	// Final param '2' means "don't physically move the image" - that's done by
	// the animation routine, which calls this.move2Bar() when it's finished moving the
	// main piece (because it finds a non-zero value in this.player2hit)

	// If movetype = 1 (instant move), do it now, otherwise queue the move for later (or never)
	var xrtnstring = rtnstring
	if (movetype == 1) {
		this.movePiece(opponent, 25 - topoint, 25, 1)
	} else {
		id2hit = toPointArray[0].id	
		this.player2hit = opponent
		this.ID2hit = toPointArray[0].id
		this.movePiece(opponent, 25 - topoint, 25, 2)
	}
	this.animCallback = (animCallback == null) ? "" : animCallback
	// Restore local variable after recursive all, add hit-off move to value 
	rtnstring = " ^25/" + (25 - topoint).toString() + " " + xrtnstring
	rtnval = (25 - topoint)
	// Still some manual tidying to do though
	ey = (tp > 12) ? (ey - this.pieceheight) : (ey + this.pieceheight)
}


// Tidy up leaving position
var fl = fromPointArray.length
fromPointArray[fl -1] = null
fl--
fromPointArray.length = fl
if (fromPointArray.length == 0) {
	fromPointArray.owner = 0 // point is now empty
}

	//alert(movetype)
// Testing - just reposition the item - put animated move in later
if (movetype < 2) {
if (movetype == 1) {
	//alert(p2move.id + " " + ex + " " + ey)
	p2move.style.top = ey
	p2move.style.left = ex
	p2move.style.zIndex = this.restingZindex
} else {
	this.xtarget = ex
	this.ytarget = ey
	this.pieceMoving = p2move
	this.pieceMoving.style.zIndex = 100
	this.xdir = (ex > sx) ? 1 : (ex < sx) ? -1 : 0
	this.ydir = (ey > sy) ? 1 : (ey < sy) ? -1 : 0
	if (this.xdir != 0 || this.ydir != 0) {
		this.animInProgress = true
		this.timeOut = setTimeout(this.boardID + ".aniMove()", initDelay)
		//var nc = this.boardID + ".aniMove('" + p2move.id + "'," + this.xtarget + "," + this.ytarget + "," +  this.xdir + "," +  this.ydir + "," +  this.xstep + "," +  this.ystep + ")"
		//alert(nc)
		//this.timeOut = setTimeout(nc, initDelay)
	}
}
}
toPointArray[toPointArray.length] = p2move
toPointArray.owner = player
this.hasMoved = true
return rtnstring
}

function bg_move2Bar() {
// This method initiates an animated move of a piece image from a point to the bar.

if (this.player2hit < 1 || this.player2hit > 2) {
	this.player2hit = 0
	this.animInProgress = false
	return -1
}
var ex, ey, sx, sy, toPointArray, tp, stackoffset, player = this.player2hit 
var p2move = document.getElementById(this.ID2hit)
toPointArray = this.bar[player]
tp = 25
stackoffset = Math.floor(this.bar[player].length/5)
ey = this.bar[player].ypos + (stackoffset * this.pieceheight /2) + ((this.bar[player].length - (stackoffset * 5)) * this.pieceheight)	
ex = this.pointx[tp]
sx = parseInt(p2move.style.left)
sy = parseInt(p2move.style.top)
this.xtarget = ex
this.ytarget = ey
this.pieceMoving = p2move
this.pieceMoving.style.zIndex = 100
this.xdir = (ex > sx) ? 1 : (ex < sx) ? -1 : 0
this.ydir = (ey > sy) ? 1 : (ey < sy) ? -1 : 0
this.player2hit = 0 // Cancel further hits!
this.timeOut = setTimeout(this.boardID + ".aniMove()", 0)
}

function bg_aniMove() {

var z, a, xl = parseInt(this.pieceMoving.style.left), xt = parseInt(this.pieceMoving.style.top)
var x = (this.xtarget - xl)
var y = (this.ytarget - xt)

if (x == 0 && y ==0) {
	this.pieceMoving.style.zIndex = this.restingZindex
	if (this.player2hit == 0) {
		this.animInProgress = false
		if (this.animCallback != "") {
		//alert("this.animCallback is " + this.animCallback)

			this.timeOut = setTimeout(this.animCallback, this.animCallbackDelay)
			this.animCallback = ""
		}
	} else {
		this.move2Bar()
	}	
	return
}
//alert("Before: " + this.pieceMoving.style.left + " " + this.pieceMoving.style.top)
a = Math.abs(x)
z = (this.xstep > a) ? a : this.xstep
this.pieceMoving.style.left = xl + (this.xdir * z)
a = Math.abs(y)
z = (this.ystep > a) ? a : this.ystep
this.pieceMoving.style.top = xt + (this.ydir * z)
//alert("After: " + this.pieceMoving.style.left + " " + this.pieceMoving.style.top)
this.timeOut = setTimeout(this.boardID + ".aniMove()", this.animDelay)
}




function bgGame(gameID, pageFileName, board, dataID, rollcontrolsArea, playcontrolsArea, 
		promptdisparea, afterPlaycontrolsArea, speedcontrolsArea, matchControlsArea, rollDispID, 
	moveDispID, preComDispID, postComDispID, pageTitleDispID, gameTitleDispID,
		playerName1DispID, playerName2DispID, gameIntroDispID, 
		analyseMoveDisp, analyseCubeDisp, matchdataDisp, gameStatsDisp, 
		isDev, devForm, matchData) {
this.ready = false		
var i
this.gameID = gameID
this.pageFileName = pageFileName
this.board = board
this.data  = document.getElementById(dataID)
this.isDev = (isDev == null) ? false : isDev
this.isMatch = (matchData == null) ? false : true
if (isDev) {
	if (devForm == null || devForm == "") {
		alert("Game constructor Fatal error - dev form ID must be supplied for dev configuration")
		return -1
	}
	this.devForm = document.getElementById(devForm)
}
this.timeOut = null
this.rollcontrolsArea = document.getElementById(rollcontrolsArea)
this.playcontrolsArea = document.getElementById(playcontrolsArea)
this.promptDispArea = (promptdisparea == null) ? null : document.getElementById(promptdisparea)
this.promptDispAreaonValue = this.promptDispArea

this.afterPlaycontrolsArea = document.getElementById(afterPlaycontrolsArea)
this.speedcontrolsArea = (speedcontrolsArea == null) ? null : document.getElementById(speedcontrolsArea)
this.matchControlsArea = (matchControlsArea == null) ? null : document.getElementById(matchControlsArea)
this.rollDisp = (rollDispID == null) ? null : document.getElementById(rollDispID)
this.moveDisp = (moveDispID == null) ? null : document.getElementById(moveDispID)
if (this.moveDisp != null) {this.board.moveLegendDisp = this.moveDisp}
this.preComDisp = (preComDispID == null) ? null : document.getElementById(preComDispID)
this.postComDisp = (postComDispID == null) ? null : document.getElementById(postComDispID)
this.pageTitleDisp = (pageTitleDispID == null) ? null : document.getElementById(pageTitleDispID)
this.gameTitleDisp = (gameTitleDispID == null) ? null : document.getElementById(gameTitleDispID)
this.gameTitleDisp = (gameTitleDispID == null) ? null : document.getElementById(gameTitleDispID)
this.playerName1Disp = (playerName1DispID == null) ? null : document.getElementById(playerName1DispID)
this.playerName2Disp = (playerName2DispID == null) ? null : document.getElementById(playerName2DispID)
this.gameIntroDisp = (gameIntroDispID == null) ? null : document.getElementById(gameIntroDispID)
this.analyseMoveDisp = (analyseMoveDisp == null) ? null : document.getElementById(analyseMoveDisp)
this.analyseCubeDisp = (analyseCubeDisp == null) ? null : document.getElementById(analyseCubeDisp)
this.matchdataDisp = (matchdataDisp == null) ? null : document.getElementById(matchdataDisp)
this.gameStatsDisp = (gameStatsDisp == null) ? null : document.getElementById(gameStatsDisp)
this.wait4play = document.getElementById("wait4play")

this.playerNames = new Array()
this.playerNames[1] = ""
this.playerNames[2] = ""
this.gamestats = ""
this.plays = new Array()
this.gameTitle = ""
this.pageTitle = ""
this.gameIntro = ""
this.curplay = -1
this.cubeval = 1
this.cubewith = 0
this.playstatus = 0
this.autoplay = false
this.hasPlayed = false
this.curStep = 0
this.autodelay = 700
this.stepsPaused = false
this.devHasLoadedToForm = false
this.devSkipPlayZero = false
this.showIDInStats = true
this.hidePageTitleInMatches = false
this.sourceType = ""
this.menuScript = ""

//this.playcontrolsArea.innerHTML = xh


// Method hookups
this.start = bgg_start
this.parseData = bgg_parseData
this.nextPlay = bgg_nextPlay
this.playMove = bgg_playMove
this.playEnded = bgg_playEnded
this.startPlay = bgg_startPlay
this.prevRoll = bgg_prevRoll
this.dispStats = bgg_dispStats
this.autoStart = bgg_autoStart
this.autoNext = bgg_autoNext
this.processCommand = bgg_processCommand
this.init = bgg_init
this.mapScript = bgg_mapScript
this.getPosID = bgg_getPosID
this.applyPosID = bgg_applyPosID
this.listPlays = bgg_listPlays
this.jumpToPlay = bgg_jumpToPlay
this.hideIntro = bgg_hideIntro
this.genJumpControl = bgg_genJumpControl
this.reloadJumpControl = bgg_reloadJumpControl
this.reInitGame = bgg_reInitGame
this.stopAutoPlay = bgg_stopAutoPlay
this.processPlayButton = bgg_processPlayButton
this.parseSteps = bgg_parseSteps
this.nextStep = bgg_nextStep
this.disablePlayControls = bgg_disablePlayControls
this.replay = bgg_replay
this.setAnimDelay = bgg_setAnimDelay
this.gotoTitle = bgg_gotoTitle
this.setPause = bgg_setPause
this.gotoPlay = bgg_gotoPlay
this.releasePause = bgg_releasePause
this.disableAllControls = bgg_disableAllControls
this.restoreAllControls = bgg_restoreAllControls
this.demoMove = bgg_demoMove 
this.demoMove2 = bgg_demoMove2 
this.demoMove3 = bgg_demoMove3 
this.demoMove4 = bgg_demoMove4 
this.showMoveAnalysis = bgg_showMoveAnalysis
this.flipHoriz = bgg_flipHoriz
this.gotoGame = bgg_gotoGame
this.gotoMenu = bgg_gotoMenu
this.genMenuPage = bgg_genMenuPage
this.setPrompts = bgg_setPrompts
this.genXMLFromPage = bgd_genXMLFromPage
this.convDemoTags = bgg_convDemoTags
this.setBoardForSteps = bgg_setBoardForSteps
this.processMatchData = bgg_processMatchData

// Dev method hookups
if (isDev) {
	this.loadPlayToForm = bgd_loadPlayToForm
	this.loadPlayFromForm = bgd_loadPlayFromForm
	this.loadTitlesToForm = bgd_loadTitlesToForm
	this.loadTitlesFromForm = bgd_loadTitlesFromForm
	this.genScriptFromPage = bgd_genScriptFromPage
	this.bgg2mat = bgd_bgg2mat
	//this.genPlayoutStepsFromGame = bgd_genPlayoutStepsFromGame
}


// Match data (if present)
this.matchArray = new Array()
this.matchLength = 0
this.matchScript = ""
this.matchTitle = ""
this.matchDescription = ""
this.curGameNo = 0
this.firstGameElement = 5 // UPDATE THIS IF ARRAY LAYOUT CHANGES!


if (this.isMatch) {
	this.processMatchData(matchData)
}

this.parseData(false)
//alert("Before mapScript() - play 0 posID is " + this.plays[0].posID)
this.mapScript()
//alert("After mapScript() - play 0 posID is " + this.plays[0].posID)
//alert("Before applyPosID() - GNU pos ID for player 1 is " + this.board.genGNUPositionID (1))
this.applyPosID(this.plays[0].posID)
//alert("After applyPosID() - GNU pos ID for player 1 is " + this.board.genGNUPositionID (1))

// If the page is running from a match file, override any menu script value set by game script
if (this.isMatch) {
	if (this.curGameNo == 0) {
		this.menuScript = this.matchArray[2]
	} else {
		this.menuScript = this.matchArray[1]
	}
	if (this.hidePageTitleInMatches) {
		//alert("hiding titles")
		bg_dispTextValue(this.pageTitleDisp, "") 	
	}
}


// Generate control buttons

// Game navigation buttons (prev/next, autoplay, jump-to-play control)
var xh = ""

xh += '<input class="stdcontrol" type="button" id="' + this.gameID + 'prevbutton" title="go to the previous play" value=" < " onclick="' + this.gameID + '.gotoPlay(-1)">'
xh += '<input class="stdcontrol" type="button" id="' + this.gameID + 'nextbutton" title="go to the next play" value=" > " onclick="' + this.gameID + '.gotoPlay(+1)">'
xh += '<input class="stdcontrol" type="button" id="' + this.gameID + 'autobutton" title="play automatically" value="autoplay" onclick="' + this.gameID + '.autoStart()">'
xh += '&nbsp;'
xh += this.genJumpControl()

this.rollcontrolsArea.innerHTML = xh
this.prevButton = document.getElementById(this.gameID + 'prevbutton')
this.prevButton.disabled = true
this.isPaused = false
this.nextButton = document.getElementById(this.gameID + 'nextbutton')
this.nextButton.disabled = true
this.autoButton = document.getElementById(this.gameID + 'autobutton')
this.autoButton.disabled = true
this.jumpSelect = document.getElementById(this.gameID + 'jumpSelect')
this.jumpSelect.disabled = true
this.goButton = document.getElementById(this.gameID + 'gobutton')
this.goButton.disabled = true
xh = ""


// Play control (Play button exposed after dice roll, before moves)
xh += '<input class="stdcontrol" type="button" style="display:none" title="play the moves" id="' + this.gameID + 'playbutton" value=" play " onclick="' + this.gameID + '.playMove()">'
this.playcontrolsArea.innerHTML = xh
this.playButton = document.getElementById(this.gameID + 'playbutton')
this.playButton.disabled = true


// Afterplay controls (replay/next)
xh = ""
xh += '<input class="stdcontrol" type="button" title="replay the moves" style="display:none" id="' + this.gameID + 'replaybutton" value="replay" onclick="' + this.gameID + '.replay()">'
xh += '<input class="stdcontrol" type="button" title="go to the next play" style="display:none" id="' + this.gameID + 'next2button" value="next" onclick="' + this.gameID + '.nextPlay()">'
this.afterPlaycontrolsArea.innerHTML = xh
this.replayButton = document.getElementById(this.gameID + 'replaybutton')
this.replayButton.disabled = true
this.next2Button = document.getElementById(this.gameID + 'next2button')
this.next2Button.disabled = true

// Match navigation and back-to-menu
xh = ""
xh += '<input class="stdcontrol" type="button" id="' + this.gameID + 'prevgamebutton" title="go to the previous game in this match" value="<<" onclick="' + this.gameID + '.gotoGame(-1)">'

xh+= '<input type="button" id="' + this.gameID + 'gotomenubutton" title="Return to the match listing" class="stdcontrol" value="home" onclick="' + this.gameID + '.gotoMenu()">'

xh += '<input class="stdcontrol" type="button" id="' + this.gameID + 'nextgamebutton" title="go to the next game in this match" value=">>" onclick="' + this.gameID + '.gotoGame(+1)">'
this.matchControlsArea.innerHTML = xh
this.matchControlsArea.style.display = ""
this.prevgamebutton = document.getElementById(this.gameID + 'prevgamebutton')
this.gotomenubutton = document.getElementById(this.gameID + 'gotomenubutton')
this.nextgamebutton = document.getElementById(this.gameID + 'nextgamebutton')


// Vector array for disabling/enabling all controls
this.allControls = new Array(this.prevgamebutton, this. prevButton, this.nextButton, this.nextgamebutton, 
				this.autoButton, this.jumpSelect, this.gotomenubutton,
				this.goButton, this.playButton, this.replayButton, this.next2Button)

for (i=0; i < this.allControls.length; i++) {
	this.allControls[i].prevDisabled = this.allControls[i].disabled
}


if (this.speedcontrolsArea != null) {
	this.speedcontrolsArea = document.getElementById(speedcontrolsArea)
	xh = ""
	xh += '<INPUT type="radio" title="Slow animation speed" name="' + this.gameID + 'bgg_speed" checked onclick="' + this.gameID + '.setAnimDelay(10)"> Fast '
	xh += '<INPUT type="radio" title="Medium  animation speed" name="' + this.gameID + 'bgg_speed" onclick="' + this.gameID + '.setAnimDelay(30)"> Medium '
	xh += '<INPUT type="radio" title="Fast animation speed" name="' + this.gameID + 'bgg_speed" onclick="' + this.gameID + '.setAnimDelay(60)"> Slow '
	xh += '&nbsp;<INPUT type="checkbox" title="Wait for you to press the play button before showing moves" checked id="' + this.gameID + 'wait4play">Wait before playing'

	this.speedcontrolsArea.innerHTML += xh
	this.wait4play = document.getElementById(this.gameID + 'wait4play')
} else {
	// If no speed controls area defined, create dummy 'wait for plays' object  
	// with permanently-true .checked property
	this.wait4play = new Object()
	this.wait4play.checked = true
}

this.jumpSelect.style.display = ""
this.preComDisp.style.display = ""
this.ready = true
// start() triggered by pgCheckStart() (page download complete checker)
// this.start()
//this.listPlays()
}


function bgg_processMatchData(matchData) {
/*
matchArray element layout is:
0 - game no. (1 to matchlength) to play in this page OR 0 = use this match fle as a menu
1 - match file name
2 - parent menu file name
3 - match title
4 - Match description (e.g. "Match to 7 points"
5 onwards - game file names
So add 4 to game number to find the right file name.
*/
var matchTemp = ""
this.matchArray = matchData.split("::")
this.curGameNo = parseInt(this.matchArray[0])
this.matchScript = this.matchArray[1]
this.matchTitle = this.matchArray[3]
this.matchDescription = this.matchArray[4]
//alert("V2 Match is " + (this.matchArray.length - 4) + " games long, " + this.matchArray + "\nlast element is " + this.matchArray[this.matchArray.length -1])
this.matchLength = this.matchArray.length - this.firstGameElement
// Update match display area.
if (this.matchArray[3] != "") {
	//matchTemp = this.matchArray[3] + "<br>"
}
if (this.curGameNo == 0) {
	// Generate a menu script for this page using the contents of the menu file
	this.data.value = this.genMenuPage()
	matchTemp += this.matchArray[4]
	bg_dispTextValue(this.matchdataDisp, "")
} else {		
	matchTemp += this.matchArray[4] + "<br>Game " + this.matchArray[0] + " of " + this.matchLength
	bg_dispTextValue(this.matchdataDisp, matchTemp) 
}
}


function bgg_setPrompts(off) {
if (off) {
	bg_dispTextValue(this.promptDispArea, "")
	this.promptDispArea = null
} else {
	this.promptDispArea = this.promptDispAreaonValue
}
}


function bgg_gotoGame(val) {
if (!this.isMatch) {return}
var qs = (this.pageFileName.substr(this.pageFileName.length -1, 1) == "&") ? "s=" : "?s="
var x = this.curGameNo + val
if (x > 0 && x <= this.matchLength) {
	location.href = this.pageFileName + qs + this.matchScript + "&g=" + x
}
}


function bgg_gotoMenu() {
var qs = (this.pageFileName.substr(this.pageFileName.length -1, 1) == "&") ? "s=" : "?s="
location.href= "../match_listing.php"
/*
if (this.menuScript != "") {
	location.href = this.pageFileName + qs + this.menuScript
} else {
	location.href = this.pageFileName
}
*/
}


function bgg_flipHoriz(d) {
if (this.goButton.disabled == true) {
	alert("Sorry - the board direction cannot be changed while a play is in progress")
	return
}
if (d == null) {
	d = (this.board.horizOrientation == 'r') ? 'l' : 'r'
} else {
	d = (d.toLowerCase() == "l") ? "l" : "r"
}
this.board.flipHoriz(d)
this.board.showLabels()
}

function bgg_setPause(toOn) {
this.curplay++
var cp=this.plays[this.curplay]
var intro = (cp.title == "") ? "" : "<b>" + cp.title + "</b><br>"
intro += '<a href="javascript:' + this.gameID + '.releasePause()">(Click here to play)</a>'
if (this.preComDisp != null) {this.preComDisp.innerHTML = intro}	
}

function bgg_releasePause() {
this.curplay --
this.nextPlay()
}


function bgg_gotoPlay(offset) {
clearTimeout(this.timeOut)
var newPos = (this.curplay + offset)
if (newPos < 0 || newPos >= this.plays.length) {return}
var cp = this.plays[newPos]
// if the new play is a steps sequence, put it into pause mode
this.curplay = newPos -1
// Cancel any animated move queues
clearTimeout(this.timeOut)
if (this.isDev) {
	this.loadPlayToForm(newPos, cp)
	this.devHasLoadedToForm = true
}
if (cp.command == "steps") {
	this.setPause(true)
} else {
	this.nextPlay()
}
}

function bgg_gotoTitle(title) {
var i, found = false
for (i = 0; i < this.plays.length; i++) {
	if (this.plays[i].title == title) {
		found = true
		break
	}
}
if (found) {
	this.curplay = i
	this.gotoPlay(0)
} else {
	alert("Error - play title " + title + " not found")
}
}


function bgg_setAnimDelay(delay){
	this.board.animDelay = delay
}

function bgg_replay() {
this.curplay--
this.nextPlay()
// old version
//this.disablePlayControls()
//this.startPlay(this.autodelay)
}

function bgg_demoMove(obj, ignoreCheck) {
if (this.board.demoMode || this.autoplay) {return} // multiple clicks while demo in progress
if (this.playstatus > 0 && obj.parentNode == this.preComDisp && ignoreCheck == null) {
	alert("Sorry, pre-play example moves can't be demonstrated once the play has been made.")
	return
}
this.board.demoMode = true
var movestring = obj.innerHTML, cp = this.plays[this.curplay]
this.disableAllControls()
this.board.doMoves(cp.player, movestring, 0, this.gameID + ".demoMove2()")
}

function bgg_demoMove2(obj) {
setTimeout(this.gameID + ".demoMove3()", 0)
}

function bgg_demoMove3(obj) {
this.board.undoMoves(1, this.gameID + ".demoMove4()")
this.board.demoMode = false


this.restoreAllControls()

}

function bgg_demoMove4(obj) {
if (this.board.moveLegendDisp != null) {
	this.board.moveLegendDisp.innerHTML = ""
}
}
/*
function bgg_demoMove3(obj) {
this.board.undoMoves(0, this.gameID + ".demoMove4()")
}

function bgg_demoMove4(obj) {
this.board.demoMode = false
this.restoreAllControls()
}
*/
function bgg_disableAllControls() {
var i
for (i=0; i < this.allControls.length; i++) {
	this.allControls[i].prevDisabled = this.allControls[i].disabled
	this.allControls[i].disabled = true
}
}

function bgg_restoreAllControls() {
var i
for (i=0; i < this.allControls.length; i++) {
	this.allControls[i].disabled = this.allControls[i].prevDisabled
}
}

function bgg_disablePlayControls() {
this.playButton.disabled = true
this.playcontrolsArea.style.display="none"
this.prevButton.disabled = true
this.nextButton.disabled = true
this.goButton.disabled   = true
this.jumpSelect.disabled = true
if (!this.autoplay) {this.autoButton.disabled = true}
}

function bgg_reInitGame() {
this.stopAutoPlay()
this.pageTitle = this.gameTitle = this.gameIntro = this.playerNames[1] = this.playerNames[2] = ""
this.parseData(false)
this.mapScript()
this.applyPosID(this.plays[0].posID)
this.reloadJumpControl()
this.timeOut = setTimeout(this.gameID + ".nextPlay()", 0)
}

function bgg_processPlayButton() {
if (this.playstatus < 2) {
	this.playMove()
} else {
	this.startPlay(this.autodelay)
}
}


function bgg_hideIntro() {
	this.gameIntroDisp.style.display = "none"
	// IE display bug patch
	this.jumpSelect.style.display = ""
	this.timeOut = setTimeout(this.gameID + ".nextPlay()", 0)
}

function bgg_init() {
	//this.board.resetBoard()
	this.curplay = -1
	this.nextPlay()
}

function bgg_jumpToPlay() {
var x = document.getElementById(this.gameID + "jumpSelect")
var y = parseInt(x.options[x.selectedIndex].value) - 1
this.stopAutoPlay()
this.curplay = (y)
this.playstatus = 2
this.nextPlay()
}

function bgg_stopAutoPlay(cncl) {
if (cncl == null) {clearTimeout(this.timeOut)}
this.autoplay = false
this.autoButton.disabled = false
this.autoButton.value = "autoplay"
this.playButton.style.display = ""
//this.playcontrolsArea.style.display = ""
}

function bgg_genJumpControl() {
var i, j, cp
// inline style sheet is patch to fix IE bug - IE displays the control even though there's 
// an opaque element layered above it.
j = '<select class="jumpselect" title="go directly to a different play" style="display:none;" id="' + this.gameID + 'jumpSelect" onchange="' + this.gameID + '.jumpToPlay()">'
for (i = 0; i < this.plays.length; i++) {
	cp = this.plays[i]
	if (cp.rollNo > 0 || (cp.command != "" && cp.title != "")) {
		j += '<option value="' + i + '">' 
		if (cp.rollNo > 0)	{j += "Roll " + cp.rollNo} 
		if (cp.title != "") {
			j += (cp.rollNo > 0) ? ": " : ""
			j += cp.title
		}
		j += '</option>'
	}
}
j += '</select>'
j += '<input type="button" title="go to the selected play" id="' + this.gameID + 'gobutton" class="stdcontrol" value="Go" onclick="' + this.gameID + '.jumpToPlay()">'
return j
}

function bgg_reloadJumpControl() {
// separate function so that dev version can reload jump select control after restarting with new script
var i, cp
var el
this.jumpSelect.innerHTML = ""

for (i = 0; i < this.plays.length; i++) {	
	cp = this.plays[i]
	if (cp.rollNo > 0 || (cp.command != "" && cp.title != "")) {
		el = document.createElement("OPTION")
		el.value = i.toString()
		if (cp.rollNo > 0)	{el.text = "Roll " + cp.rollNo} 
		if (cp.title != "") {
			el.text += (cp.rollNo > 0) ? ": " : ""
			el.text += cp.title
		}
		this.jumpSelect.options.add(el);
	}
}
}


function bgg_autoStart() {
// This function starts and stops the autoplay process
if (! this.autoplay) {
	this.autoplay = true
	this.disablePlayControls()
	this.playButton.style.display = "none"
	this.playcontrolsArea.style.display = "none"
	this.autoButton.value = "stop autoplay"
	if (this.playstatus < 2) {
		this.startPlay(this.autodelay)
	} else {
		this.nextPlay(this.autodelay)
	}
} else {
	this.stopAutoPlay(true)
}
}

function bgg_autoNext() {
if (this.curplay < (this.plays.length -1)) {
	this.timeOut = setTimeout(this.gameID + ".nextPlay(" + this.autodelay + ")", 2000)
} else {
	this.autoplay = false
	this.autoButton.value = "autoplay"
	//alert("Game over!")
}
}


function bgg_dispStats(clear) {
if (clear == null) {clear = 'y'}
var plr = this.board.povPlayer
var opp = (plr == 1) ? 2 : 1
var d1, d2
if (this.playerName1Disp != null) {
	if (clear == 'n') {
		d1 = "&nbsp;"
	} else {
		d1 = this.playerNames[opp] + " <span style='font-weight:normal'>pips " + this.board.pipCount(opp) 
		if (this.showIDInStats ) {
			d1 += " ID " + this.board.genGNUPositionID(opp) 	
		}
		d1 += "</span>"
	}
	this.playerName1Disp.innerHTML = d1
}
if (this.playerName2Disp != null) {
	if (clear == 'n') {
		d2 = "&nbsp;"
	} else {
		d2 = this.playerNames[plr] + " <span style='font-weight:normal'>pips " + this.board.pipCount(plr)
		if (this.showIDInStats) {
			d2 += " ID " + this.board.genGNUPositionID(plr) 	
		}
		d2 += "</span>"
	}
	this.playerName2Disp.innerHTML = d2
}
}


function bgg_prevRoll() {
if (this.curplay <= 0) {return}
this.curplay -= 2
this.nextPlay()
}

function bgg_start() {
this.timeOut = setTimeout(this.gameID + ".nextPlay()", 0)
}

function bgg_mapScript(devRecalc) {
// This function works silently through the game script, pre-setting any empty position IDs
// This allows the page to jump forward before all intervening rolls/commands have been played
// First play always has a posID (defaulted to reset board)
devRecalc = (devRecalc == null) ? false : true // Force recalc of non lockPos posIDs in dev mode
var i, cp, lastPos, j
this.applyPosID(this.plays[0].posID)
lastPos = this.plays[0].posID
j = (this.plays.length -1)
for (i = 0; i < this.plays.length; i++) {

	cp = this.plays[i]
	// If this play doesn't have a pre-set posID, update it to the last one generated
	cp.posID = (cp.posID.length < 15) ? lastPos : cp.posID
	// Look ahead to see if it needs to calculate pos for next play
	if (i < j) {
		if (this.plays[i+1].posID.length < 15 || (devRecalc && !this.plays[i+1].lockPos)) {
			if (cp.command == "") {
				this.board.doMoves(cp.player, cp.moves, 1, null)
			} else {
				this.curplay = i
				this.processCommand(true)
			}
			// Generate posID for start of next play, so player is the other one
			// from this play!
			lastPos = this.getPosID(this.plays[i+1].player)
		}
	}
}
this.curplay = -1
this.applyPosID(this.plays[0].posID)
}

function bgg_getPosID(plr) {
// This function generates a 15-plus game play position string, comprising
// Player no, GNU PosID, cube owner (if any), cube value (if any)
var x = plr.toString()
x+= this.board.genGNUPositionID(plr)
if (this.board.cubewith > 0) {
	x+= this.board.cubewith.toString()
	x+= this.board.cubeval.toString()
	}
return x
}

function bgg_applyPosID(posID) {
this.board.showGNUPositionID(posID.substring(1,15), parseInt(posID.substr(0,1))) 
if (posID.length > 15) {
	this.board.cubewith = parseInt(posID.substr(15, 1))
	this.board.cubeval  = parseInt(posID.substring(16))
	this.board.showCube(this.board.cubewith, this.board.cubeval)
} else {
	this.board.showCube(0,0)
}
}

function bgg_parseData(listplays) {
if (listplays == null) {listplays = false}
this.plays.length = 0
var s = this.data.value 
var su = s.toLowerCase()
var dfPlayer = 1, dfColour = "w"
var i, j, start, end, x, y, z, ind, w, playObj, p, pu

// Match data 
// When the game file source was a match file (e.g. a .mat), the converter will have
// generated a script containing the match menu data, plus a game if the game no. is > 0
i = su.indexOf("[matchdata")
if (i >= 0) {
	//alert(s.substring(s.indexOf("]", i) + 1, s.indexOf("[/matchdata", i)))
	this.isMatch = true
	this.processMatchData(s.substring(s.indexOf("]", i) + 1, s.indexOf("[/matchdata", i)))
	if (this.curGameNo == 0) { 
		// new menu page script has been generated by processMatchData, so reload vars
		s = this.data.value 
		su = s.toLowerCase()		
	}
}
i = su.indexOf("[pagetitle")
if (i >= 0) {
	this.pageTitle = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[gametitle")
if (i >= 0) {
	this.gameTitle = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[sourcetype")
if (i >= 0) {
	this.sourceType = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[menuscript")
if (i >= 0) {
	this.menuScript = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[gameintro")
if (i >= 0) {
	this.gameIntro = s.substring(s.indexOf("]", i) + 1, s.indexOf("[/gameintro", i))
}
// if a gameintro has been supplied, convert it to the first play of script
if (this.gameIntro != "") {
	this.plays[0] = new bggPlay()
	playObj = this.plays[0]
	playObj.player = 1
	playObj.title = "Intro"
	playObj.command = "steps"
	playObj.posID = "14HPwATDgc/ABMA"

	var xs = "[step]dice::100[/step][step]dice::200[/step][step]labels::0[/step][step]say::" + this.gameIntro + "[/step]"
	this.parseSteps(playObj, xs)
	this.devSkipPlayZero = true	
} else {
	this.devSkipPlayZero = false	
}


i = su.indexOf("[horiz")
if (i >= 0) {
	this.board.setHoriz(su.substr(i+6,1))
}
i = su.indexOf("[playername1")
if (i >= 0) {
	this.playerNames[1] = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[playername2")
if (i >= 0) {
	this.playerNames[2] = s.substring(s.indexOf(" ", i) + 1, s.indexOf("]", i))
}
i = su.indexOf("[gamestats")
if (i >= 0) {
	this.gamestats = s.substring(s.indexOf(" ", i) + 1, s.indexOf("[/gamestats", i))
}

i = su.indexOf("[povplr")
if (i >= 0) {
	dfPlayer = parseInt(su.substr(i+7,1))
}
i = su.indexOf("[povclr")
if (i >= 0) {
	dfColour = su.substr(i+7,1)
}
this.board.setPOV(dfPlayer, dfColour)


// Now parse the plays into play objects
start = 0; end = 0
do {
	start = su.indexOf("[play]", end)
	if (start < 0) {break}
	end = su.indexOf("[/play]", start)
	end = (end < 0) ? su.length -1 : end + 8
	//if (end < 0) {end = su.length -1}
	p = s.substring(start, end -1)
	//if (!confirm(p)) {return}
	pu = p.toLowerCase()
	x = this.plays.length
	this.plays[x] = new bggPlay()
	playObj = this.plays[x]
	
	i = pu.indexOf("[posid")
	if (i >= 0) {
		playObj.posID = p.substring(p.indexOf(" ", i) + 1, p.indexOf("]", i))
	}
	i = pu.indexOf("[lockpos")
	if (i >= 0) {
		playObj.lockPos = true
	}
	i = pu.indexOf("[player")
	if (i >= 0) {
		playObj.player = parseInt(p.substr(i + 7,1))
	}
	i = pu.indexOf("[dice")
	if (i >= 0) {
		playObj.dice = p.substring(p.indexOf(" ", i) + 1, p.indexOf("]", i))
	}
	i = pu.indexOf("[analysemove")
	if (i >= 0) {
		i = p.indexOf("]", i) 
		playObj.analyseMove = p.substring(i + 1, pu.indexOf("[/analysemove", i))
	}
	i = pu.indexOf("[analysecube")
	if (i >= 0) {
		i = p.indexOf("]", i) 
		playObj.analyseCube = p.substring(i + 1, pu.indexOf("[/analysecube", i))
	}
	i = pu.indexOf("[move")
	if (i >= 0) {
		playObj.moves = p.substring(p.indexOf(" ", i) + 1, p.indexOf("]", i))
		//if (!confirm(p)) {return}
	}
	i = pu.indexOf("[title")
	if (i >= 0) {
		playObj.title = p.substring(p.indexOf(" ", i) + 1, p.indexOf("]", i))
	}
	i = pu.indexOf("[noprompts")
	if (i >= 0) {
		playObj.noprompts = true
	}
	i = pu.indexOf("[wait")
	if (i >= 0) {
		playObj.wait = true
	}
	i = pu.indexOf("[command")
	if (i >= 0) {
		playObj.command = pu.substring(p.indexOf(" ", i) + 1, p.indexOf("]", i))
	}
	i = pu.indexOf("[precom")
	if (i >= 0) {
		i = p.indexOf("]", i) 
		playObj.precomment = p.substring(i + 1, p.indexOf("[/pre", i))
	}
	i = pu.indexOf("[postcom")
	if (i >= 0) {
		i = p.indexOf("]", i) 
		playObj.postcomment = p.substring(i + 1, p.indexOf("[/pos", i))
	}


	i = pu.indexOf("[steps")
	if (i >= 0) {
		i = pu.indexOf("]", i) + 1
		j = pu.indexOf("[/steps", i)
		if (j < 0) {
			alert("Fatal error in script parser - missing [/steps tag")
			return -1		
		}
		this.parseSteps(playObj, p.substring(i, j))
	}	
	start = end
}
while (start >= 0)
// Update any static page elements that have been specified
if (this.pageTitle != "" && this.pageTitleDisp != null) {
	this.pageTitleDisp.innerHTML = this.pageTitle
}
if (this.gameTitle != "" && this.gameTitleDisp != null) {
	this.gameTitleDisp.innerHTML = this.gameTitle
}

if (this.playerNames[1] != "" && this.playerName1Disp != null) {
	this.playerName1Disp.innerHTML = this.playerNames[1]
}
if (this.playerNames[2] != "" && this.playerName2Disp != null) {
	this.playerName2Disp.innerHTML = this.playerNames[2]
}

var ix = (this.devSkipPlayZero) ? 1 : 0
if (this.plays[ix].posID.length < 15) {
	// default starting position
	this.plays[ix].posID = this.plays[ix].player + "4HPwATDgc/ABMA"
}
if (this.isDev) {
	this.loadTitlesToForm()
}
// Set roll numbers for dice plays (i.e. ignoring command plays)
var xr = 0
for (i = 0; i < this.plays.length; i++) {
	if (this.plays[i].command == "") {
		xr++
		this.plays[i].rollNo = xr
	}
}

if (listplays) {this.listPlays()}


}

function bgg_parseSteps(playObj, s) {
var i, j, k, start = 0, end = 0, thisStep = "", msq, msq_cmd, msq_s1, msq_wait, cmd
var steps = playObj.steps
steps.length = 0
var su = s.toLowerCase()
do {
	start = su.indexOf("[step]", end)
	if (start < 0) {break}
	j = su.indexOf("]", start) + 1
	end = su.indexOf("[/step]", start)
	if (end < 0) {
		alert("Fatal error in script parser - missing [/step] tag")
		return -1
	}
	thisStep = s.substring(j, end)
		
	j = thisStep.indexOf("::")
	cmd = bg_btrim(thisStep.substring(0, j))
	if (cmd.substr(0, 7) == "moveseq") {
		// Move sequence - convert into a series of move and wait commands
		msq_cmd = "move" + cmd.substring(7, cmd.indexOf(" "))
		msq_wait = cmd.substring(cmd.indexOf(" ") + 1)
		msq = new Array()
		msq = thisStep.substring(j+2).split(",")
		for (i = 0; i < msq.length; i++) {
			if (msq[i] != "") {
				k = steps.length
				steps[k] = new Array()
				steps[k][0] = msq_cmd
				steps[k][1] = msq[i]
				k = steps.length
				steps[k] = new Array()
				steps[k][0] = "wait"
				steps[k][1] = msq_wait				
			}				
		}
	} else {
		k = steps.length
		steps[k] = new Array()
		if (j >= 0) {
			steps[k][0] = bg_btrim(thisStep.substring(0, j))
			steps[k][1] = bg_btrim(thisStep.substring(j + 2))
		} else {
			steps[k][0] = bg_btrim(thisStep)
			steps[k][1] = ""
		}
	}
	start = end
	//if (!confirm(steps[k][0] + " " + steps[k][1])) {document.reload()}
} while (start >= 0)
return 0
}


function bgg_listPlays() {
var playObj, i, ix
document.write("<br><b>Game script:</b><br>")
document.write("Page title: " + this.pageTitle + "<br>")
document.write("Game title: " + this.gameTitle + "<br>")
document.write("Player 1: " + this.playerNames[1] + "<br>")
document.write("Player 2: " + this.playerNames[2] + "<br>")
document.write("<br>Plays: <br>")
for (i = 0; i < this.plays.length; i++) {
	playObj = this.plays[i]
	document.write("<br><b>Play no: " + (i + 1) + "</b><br>")
	document.write("Title: " + playObj.title + "<br>")
	document.write("Pos ID: " + playObj.posID)
	if (playObj.posID != "") {
		document.write(" (Player " + playObj.posID.substr(0,1) + ", GNU position ID " + playObj.posID.substr(1,14))
		if (playObj.posID.length >= 16) {
			document.write(", cube with player " + playObj.posID.substr(15,1))			
		}
		if (playObj.posID.length > 16) {
			document.write(", value  " + playObj.posID.substr(16))			
		}
		document.write(")")
	}
	document.write("<br>")		
	document.write("Roll No: " + playObj.rollNo + "<br>")
	document.write("Player: " + playObj.player + "<br>")
	document.write("Dice: " + playObj.dice + "<br>")
	document.write("Moves: " + playObj.moves + "<br>")
	document.write("Command: " + playObj.command + "<br>")
	if (playObj.steps.length > 0) {
		document.write('Steps:<br><table width="500" border="1">')
		for (ix = 0; ix < playObj.steps.length; ix++) {
			document.write('<tr><td width="250">' + playObj.steps[ix][0] + '</td><td>' + (playObj.steps[ix][1] + "&nbsp;") + '</td></tr>')
		}
		document.write('</table>')	
	}				
	document.write("Precomment: " + playObj.precomment + "<br>")
	document.write("Postcomment: " + playObj.postcomment + "<br>")
}	
}


function bgg_processCommand(nodisp) {
if (nodisp == null) {nodisp = false}
this.board.stepMode = true
//alert("processComand")
var cp = this.plays[this.curplay]
var cmd = cp.command.toLowerCase()
var op, x, y, z, p1
if (cmd == "") {return -1}
if (cmd.indexOf("double") >= 0) {
	op = (cp.player == 1) ? 2 : 1
	z = parseInt(cmd.substring(cmd.lastIndexOf(" ")))
	if (!nodisp) {
		this.board.showCube(op, z, true)
		if (this.moveDisp != null) {
			p1 = (this.playerNames[cp.player] == "") ? "Player " + cp.player : this.playerNames[cp.player]
			bg_dispTextValue(this.rollDisp, p1 + " offers the cube at " + z)
			//this.rollDisp.innerHTML = p1 + " offers the cube at " + z
		}
	}
	return
} 
if (cmd.indexOf("take") >= 0) {
	x = cp.player
	z = parseInt(cmd.substring(cmd.lastIndexOf(" ")))
	this.board.cubewith = x
	this.board.cubeval = z
	//alert(this.board.cubeval)
	if (!nodisp) {
		this.board.showCube(x, z, false)
		if (this.moveDisp != null) {
			p1 = (this.playerNames[cp.player] == "") ? "Player " + cp.player : this.playerNames[cp.player]
			//this.rollDisp.innerHTML = p1 + " takes the cube at " + z
			bg_dispTextValue(this.rollDisp, p1 + " takes the cube at " + z)
		}
	}
	return
}
if (cmd.indexOf("drop") >= 0) {
	x = cp.player
	if (!nodisp) {
		if (this.moveDisp != null) {
			z = (this.board.cubeval > 1) ? " points." : " point."
			p1 = (this.playerNames[cp.player] == "") ? "Player " + cp.player : this.playerNames[cp.player]
			//this.moveDisp.innerHTML = p1 + " passes the cube and forfeits the game at " + this.board.cubeval + z 
			bg_dispTextValue(this.rollDisp, p1 + " passes the cube and forfeits the game at " + this.board.cubeval + z)

		}
	}
	return
}
if (cmd.indexOf("steps") >= 0) {
	if (!nodisp) {
		//this.preComDisp.innerHTML = ""
		bg_dispTextValue(this.preComDisp, "")
		this.disablePlayControls()
		// put in other steps preparation (i.e. make play button non-display)	
	}
	this.curStep = 0
	// Call steps processor even in nodisp (script mapping) mode
	// - in nodisp mode, nextStep() will execute only moves, in immediate mode
	this.stepsPaused = false
	this.nextStep(nodisp)
	return
}
// default behaviour - display the command text
if (!nodisp) {
	if (this.moveDisp != null) {
			p1 = (this.playerNames[cp.player] == "") ? "Player " + cp.player : this.playerNames[cp.player]
			//this.moveDisp.innerHTML = p1 + " " + cmd
			bg_dispTextValue(this.moveDisp, p1 + " " + cmd)
	}
}
return
}

function bgg_nextStep (nodisp) {
// Paused mode - stop before next step
if (this.isPaused) {
	this.prevButton.disabled = false
	this.nextButton.disabled = false
	//this.initButton.disabled = false
	this.goButton.disabled   = false
	this.jumpSelect.disabled = false
	if (!this.autoplay) {this.autoButton.disabled = true}
	// IE 6 fell over ('unspecified error') trying to show the style class versioin of the paused message, 
	// and in fact falls over on a lot of container elements (.g. <font></font>), 
	// so it's back to old technology!
	// Mozilla showed it fine.
//	this.preComDisp.innerHTML += '<p id="sp" class="stepsPausedMessage">(Paused - please press || to continue)</p>'
	this.preComDisp.innerHTML += '<!--paused--><br><br><font color="red"><i>(Paused - please press || to continue)</i></font>'
	this.stepsPaused = true
	return
}
this.board.stepMode = true
// vars i, j, k are re-useable on each pass
var cp = this.plays[this.curplay], i, j, k, n = this.gameID + ".nextStep(" + nodisp + ")"
var cf1, cf2 , vx
var cubeparams = new Array()
var steps, cmd, txt, nm
steps = cp.steps

// This function works in two modes, controlled by the nodisp parameter.

// When nodisp == false, it processes commands that lead to timeouts, with
// callbacks to itself. This causes the queued actions to be performed asynchronously
// to the function that called this one, after this function has returned to it

// When nodisp == true (script mapping mode), it processes the entire step set within
// a single call, synchronous with the calling function (i.e. mapScript(). 

do { // eternal outer loop for all steps - broken by breaks and returns

	if (this.curStep >= steps.length) {
		if (!nodisp) {
			this.timeOut = setTimeout(this.gameID + ".playEnded()", 0)
		}
		return
	}
	// Process current step
	cmd = steps[this.curStep][0].toLowerCase() 
	txt = steps[this.curStep][1]
	this.curStep++
	
	// inner loop - actually a form of select case
	// must be broken every time through
	// Note that all commands are expected to start in ch 0 of the cmd field.
	// leading spaces are OK in the script though, as the parser bg_btrims() them
	// before loading them into the cmd fields.
	do {
		j = cmd.indexOf("move")
		if (j == 0) {
			if (!nodisp && cmd.substr(j+5, 1) != "1") {
				// Animated moves - include queued callback to this function as final param to doMoves()
				this.board.doMoves( parseInt(cmd.substr(j+4,1)) , bg_btrim(txt), 0, n)
				return  // return to calling function leaving animated moves to execute asynchronously
			} else {
				// Immediate moves - execute doMoves() then call back to this function
				this.board.doMoves(parseInt(cmd.substr(j+4,1)), bg_btrim(txt), 1, null)
				break // stay in this function
			}
		}
		j = cmd.indexOf("setboard")
		if (j == 0) {
			this.setBoardForSteps(bg_btrim(txt))  
			break
		}
		j = cmd.indexOf("setpos")
		if (j == 0) {
			this.applyPosID(bg_btrim(txt))  
			break
		}
		j = cmd.indexOf("cube")
		if (j == 0) {
			cubeparams = txt.split(",")
			cubeparams[2] = (cubeparams[2] == "false") ? null : true
			this.board.showCube(cubeparams[0], cubeparams[1], cubeparams[2]) 
			break
		}
		if (!nodisp) { // ignore non-move steps in nodisp (script mapping) mode
			j = cmd.indexOf("wait")
			if (j == 0) {
				this.timeOut = setTimeout(n, parseInt(txt))
				return 
			}
			j = cmd.indexOf("say")
			if (j == 0) {
				//bg_dispTextValue(this.preComDisp, txt, true)
				// Adds [demo] tag support - use with care!
				bg_dispTextValue(this.preComDisp, this.convDemoTags(txt, true), true)
				//if (this.preComDisp != null) {this.preComDisp.innerHTML += txt}
				break 
			}
			//alert(":" + cmd)
			j = cmd.indexOf("clearmoved")
			if (j == 0) {
				this.board.hasMoved = false
				break
			}
			j = cmd.indexOf("clearfrom")
			if (j == 0) {
				// set strings to upper case, because IE changes HTML tags to upper
				cf1 = this.preComDisp.innerHTML.toUpperCase()
				cf2 = txt.toUpperCase()
				i = cf1.indexOf(cf2)
				// alert(this.preComDisp.innerHTML + " " + txt + i)
				bg_dispTextValue(this.preComDisp, this.preComDisp.innerHTML.substring(0, (i + txt.length)))
				/*
				if (this.preComDisp != null && i >= 0) {
					this.preComDisp.innerHTML = this.preComDisp.innerHTML.substring(0, (i + txt.length)) 
				 }
				 */
				break
			}
			j = cmd.indexOf("clear")
			if (j == 0) {
				bg_dispTextValue(this.preComDisp,"")
				//if (this.preComDisp != null) {this.preComDisp.innerHTML = ""}
				break
			}
			j = cmd.indexOf("labels")
			if (j == 0) {
				this.board.showLabels(parseInt(txt))
				break
			}
			j = cmd.indexOf("pausebutton")
			if (j == 0) {
				if (this.preComDisp != null) {
					this.preComDisp.innerHTML += "<!--bggpause--><input class='stdcontrol' type='button' value='" + txt + "' onclick='this.style.display=" + '"none";' + this.gameID + ".nextStep()'>"
					return
				}
				break
			}
			j = cmd.indexOf("linktotitle")
			if (j == 0) {
				if (this.preComDisp != null) {
					vx =  '<a href="javascript:' + 
					this.gameID + '.gotoTitle(' + "'" + txt.substr(0, txt.indexOf("^")) + "'" +
					')">' + txt.substring(txt.indexOf("^") + 1) +  '</a>'
					bg_dispTextValue(this.preComDisp,vx, true)
				}
				break
			}

			j = cmd.indexOf("dispstats")
			if (j == 0) {
				this.dispStats(bg_btrim(txt))
				break
			}
			j = cmd.indexOf("dice")
			if (j == 0) {
				this.board.showDice(parseInt(txt.substr(0,1)),parseInt(txt.substr(1,1)), parseInt(txt.substr(2,1)))
				break
			}
			j = cmd.indexOf("horiz")
			if (j == 0) {
				this.board.flipHoriz(cmd.substr(j+5,1))
				break
			}
			j = cmd.indexOf("fliphoriz")
			if (j == 0) {
				this.board.flipHoriz(null)
				break
			}
			j = cmd.indexOf("exit")
			if (j == 0) {
				// exit command breaks the steps chain with no timeout call to another function
				// it must only be used to end menu pages (i.e. ones with linktotitle steps),
				// otherwise the page will halt. 
				// NOTE - the navigation buttons will be left disabled after this call, so 
				// the preceding page MUST contain linktotitle entries.
				return
							}
		}  // !nodisp
		// default behaviour
		break
	} while (2 == 2) // inner fake loop (really a select case) - must be broken on first pass every time
} while (2 == 2) // main loop through steps - broken by breaks and returns

}

function bgg_nextPlay(launchdelay) {
// This function moves to the next play in the script sequence.
// It's separated from bgg_startPlay() (which actually initiates the playing process) so
// that bgg_startPlay() can be called directly in replay mode

// This function is called by the autoplay mechanism, and by the next play button

var cp
/*
if (this.rollDisp != null) {this.rollDisp.innerHTML = ""}
if (this.moveDisp != null) {this.moveDisp.innerHTML = ""}
if (this.preComDisp != null) {this.preComDisp.innerHTML = ""}
if (this.postComDisp != null) {this.postComDisp.innerHTML = ""}
*/
bg_dispTextValue(this.rollDisp, "")
bg_dispTextValue(this.moveDisp, "")
bg_dispTextValue(this.preComDisp, "")
bg_dispTextValue(this.postComDisp, "")
bg_dispTextValue(this.promptDispArea, "")

this.playcontrolsArea.style.display = "none"
this.afterPlaycontrolsArea.style.display = "none"
this.playstatus = 0

if (this.curplay >= 0 && this.playstatus < 2) { 
	// This is a jump to the next play, without having executed the current one's moves.
	// If the next play doesn't have a position ID value, do the current
	// play's moves in non-animated mode, to build the position
	if (this.plays[this.curplay + 1].posID.length < 15) {
		cp = this.plays[this.curplay]
		this.board.doMoves(cp.player, cp.moves, 1, null)
	}
}
this.curplay++
this.hasPlayed = false
this.startPlay(launchdelay)
}

function bgg_startPlay(launchdelay) {
// This function prepares the board for the next play or command.
// If a launchdelay value has been set (autoplay mode), it sets a timeout for playMove(), 
// which does the actual play. If not, it ends, and the page waits for the 
// user to call playMove() via the play button.
this.afterPlaycontrolsArea.style.display = "none"
this.playcontrolsArea.style.display = "none"

var p1, p2 , op, protext
if (this.curplay < 0 || this.curplay >= this.plays.length) {
	alert("Invalid call to startPlay() method - current play number (" + this.curplay + ") is < zero or greater than the number of plays")
	return -1
}
var cp = this.plays[this.curplay]
var isCommand = (cp.command != "")
// Either update posID, or set board to pre-installed value if present
if (cp.posID.length < 15) {
	cp.posID = this.getPosID(cp.player)
} else {
	// CHECK POSSIBILITY OF NOT DOING THIS WHEN THE PREVIOUS MOVE HAS BEEN FULLY PLAYED
	this.applyPosID(cp.posID)
}
// hasMoved flag used to show/hide replay button after play
// it's set true by the board object's movePiece() method whenever a piece is moved
this.board.hasMoved = false

if (this.isDev && !this.hasPlayed && !this.devHasLoadedToForm) {
	this.loadPlayToForm(this.curplay, cp)
}
if (this.preComDisp != null) {
	// new demo support
	//protext = bg_replace(cp.precomment, "[demo]", '<span class="movedemo" onmouseover="this.style.fontWeight=' + "'bold'" + '" onmouseout="this.style.fontWeight=' + "'normal'" + '" title="Click here to demonstrate this move" onclick="' + this.gameID + '.demoMove(this)">' )
	//protext = bg_replace(protext, "[/demo]", "</span>")
	// bg_dispTextValue(this.preComDisp, protext)
	bg_dispTextValue(this.preComDisp, this.convDemoTags(cp.precomment))
	//this.preComDisp.innerHTML = protext	
}

if (this.analyseMoveDisp !=null) {
	if (cp.analyseMove == "") {
		this.analyseMoveDisp.innerHTML = ""
	} else {
		this.analyseMoveDisp.innerHTML = this.showMoveAnalysis(cp.analyseMove, false, this.gameID)
	}
}
if (this.analyseCubeDisp !=null) {
	if (cp.analyseCube == "") {
		this.analyseCubeDisp.innerHTML = ""
	} else {
		this.analyseCubeDisp.innerHTML = bgg_showCubeAnalysis(cp.analyseCube)
	}
}

if (this.postComDisp != null) {
	if (this.playstatus < 2) {
		//this.postComDisp.innerHTML = ""
		bg_dispTextValue(this.postComDisp, "")
	}
}

if (isCommand) {
	this.processCommand() 
	if (cp.command.toLowerCase() != "steps") { // steps command sequences do their own timeout chaining
		this.timeOut = setTimeout(this.gameID + ".playEnded()", 0)
	} 
} else {  // It's a roll
	this.board.showLabels(cp.player)
	this.dispStats()
	if (this.rollDisp != null) {
		var d0 = bg_btrim(cp.dice), d1 = d0.substr(0,1), d2 = d0.substr(1,1)
		op = (cp.player == 1) ? 2 : 1
		this.board.showDice(op, 0, 0)
		p1 = (this.playerNames[cp.player] == "") ? "Player " + cp.player : this.playerNames[cp.player]
		bg_dispTextValue(this.rollDisp, "Roll " + cp.rollNo + ": " + p1 + " rolls " + d1 + " " + d2)
		//this.rollDisp.innerHTML = "Roll " + cp.rollNo + ": " + p1 + " rolls " + d1 + " " + d2 
		if (bg_btrim(cp.moves) != "") {
		} else {
			bg_dispTextValue(this.moveDisp, " - no move available")
			//this.moveDisp.innerHTML = " - no move available"
		}	
		this.board.showDice(cp.player, parseInt(d1), parseInt(d2))
	}

		if ((!this.autoplay) && (this.playstatus < 2) && (this.wait4play.checked) && (bg_btrim(cp.moves) != "")) {
			this.playcontrolsArea.style.display = ""
			//this.initButton.disabled = false 

			this.playButton.disabled = (launchdelay == null) ? false : true	
			this.playButton.style.display = ""
			this.autoButton.disabled = false
			this.jumpSelect.disabled = false
			this.goButton.disabled = false
			if (!cp.noprompts) {
				bg_dispTextValue(this.promptDispArea, "Press 'Play' to continue")			
			}
			this.prevButton.disabled = (this.curplay > 0 ) ? false : true
			this.nextButton.disabled = (this.curplay < (this.plays.length -1)) ? false : true
			this.next2Button.disabled = (this.curplay < (this.plays.length -1)) ? false : true
		}
	this.playstatus = 0
	if (!this.wait4play.checked || bg_btrim(cp.moves) == "") {launchdelay = this.autodelay}
	if (launchdelay != null) {
		this.timeOut = setTimeout(this.gameID + ".playMove()", launchdelay)
	} 
}
this.hasPlayed = true
}

function bgg_playMove() {
var p1 
this.disablePlayControls()
bg_dispTextValue(this.promptDispArea, "")

var cp = this.plays[this.curplay]
this.playstatus = 1
if (this.analyseMoveDisp !=null && cp.analyseMove != "") {
	this.analyseMoveDisp.innerHTML = this.showMoveAnalysis(cp.analyseMove, true)
}
if (bg_btrim(cp.moves) != "") {
	if (this.moveDisp != null  && bg_btrim(cp.moves) != "") {
		bg_dispTextValue(this.moveDisp, "Plays " + cp.moves)
		//this.moveDisp.innerHTML = "Plays " + cp.moves
	}
	//alert("Calling doMoves()for play " + this.curplay)
	this.board.doMoves(cp.player, cp.moves, 0, this.gameID + ".playEnded()")
} else {
	if (this.moveDisp != null) {
	}
	this.timeOut = setTimeout(this.gameID + ".playEnded()", 0)
}
}

function bgg_playEnded() {
//alert("Play ended for roll " + this.curplay)
var cp = this.plays[this.curplay]
this.devHasLoadedToForm = false
if (!this.board.stepMode) {
	this.dispStats()
}
if (!this.autoplay || this.curplay == (this.plays.length -1)) {
	//this.initButton.disabled = false
	this.autoButton.disabled = false
	//alert(this.board.hasMoved)
	this.replayButton.style.display = (this.board.hasMoved == true) ? "" : "none"
	this.replayButton.disabled = false
	this.prevButton.disabled = false
	this.nextButton.disabled = (this.curplay >= (this.plays.length -1)) ? true : false
	this.next2Button.style.display = ""
	this.next2Button.disabled = (this.curplay >= (this.plays.length -1)) ? true : false
	this.goButton.disabled   = false
	this.jumpSelect.disabled = false
	if (!cp.noprompts) {
		bg_dispTextValue(this.promptDispArea, "Press 'Next' for the next step, or 'Replay' to show the moves again")
	}
}

if (this.gameStatsDisp != null) {
	if (this.curplay == (this.plays.length -1) && this.gamestats != "") {
		this.gameStatsDisp.innerHTML = "<pre>" + this.gamestats + "</pre>"
	} else {
		this.gameStatsDisp.innerHTML = ""
	}
}


this.board.stepMode = false
var cp = this.plays[this.curplay]
bg_dispTextValue(this.postComDisp, cp.postcomment)

if (!this.autoplay) {this.afterPlaycontrolsArea.style.display = ""}
this.playstatus = 2
if (this.autoplay) {
	setTimeout(this.gameID + ".autoNext()", this.board.animDelay * 50)
}
}

function bgg_showMoveAnalysis(d, highlight, gameID) {
// Returns formatted HTML from analysis data depending on source type
switch (this.sourceType) {
	case "GNU match": 
		return bgg_showMoveAnalysis_GNUMatch(d, highlight, gameID)
		break
	default:
		return bgg_showMoveAnalysis_default(d)
}
}

function bgg_showMoveAnalysis_default(d) {
var lines = new Array(), i, j, k
lines = d.split("\n")
var op = 'Move analysis:<table border="0">' 
for (i=0; i < (lines.length -1); i++) {
	op += "<tr><td class='moveanalysisother'>" + bg_replace(lines[i], " ", "&nbsp;") + "</td></tr>"
}
op += "</table>"
return op
}

function bgg_showMoveAnalysis_GNUMatch(d, highlight, gameID) {
highlight = (highlight == null) ? false : highlight
var lines = new Array(), i, j, k, kx
lines = d.split("\n")
var op = (gameID == null) ? 'Move analysis:<table border="0">' : 'Move analysis (click on a move to see a demonstration):<table border="0">'
for (i=2; i < (lines.length -1); i+= 2) {
	if (highlight) {
		j = (lines[i].substr(0,1) == "*") ? "moveanalysischosen" : "moveanalysisother"
	} else {
		j = "moveanalysisother"
	} 
	k = bg_btrim(lines[i].substring(5, lines[i].indexOf("MWC:"))) + " " + lines[i].substring(lines[i].indexOf("MWC:"))


	if (k.indexOf("-ply") >= 0) {
		k = bg_replace(k, "Cubeful", "Cf") ; k = bg_replace(k, "Cubeless", "Cl")
		if (k.indexOf("%)") > 0) {
			kx = bg_btrim(k.substring(k.lastIndexOf("(")+1, k.lastIndexOf(")")))
			k = k.substring(0, k.lastIndexOf("(")) + kx
		}
		if (!highlight) {
			k = bg_replace(k, "-ply", 'p<span style="color:yellow" onmouseover="this.style.fontWeight=' + "'bold'" + '" onmouseout="this.style.fontWeight=' + "'normal'" + '" title="Click here to demonstrate this move" onclick="' + gameID + '.demoMove(this)">' )
			k = bg_replace(k, "MWC:", "</span>MWC:"	)
		} else {
			k = bg_replace(k, "-ply", "p")
		}
	}
//	op += bg_replace("<tr><td class='" + j + "'><pre>" + k + "<br>" + lines[i+1] + "</pre></td></tr>", "\n", "")
//	op += "<tr><td class='" + j + "'><pre>" + k + "<br>" + lines[i+1] + "</pre></td></tr>"
	op += "<tr><td class='" + j + "'>" + k + "<br>" + bg_replace(lines[i+1].substring(4), " ", "&nbsp;")  + "</td></tr>"
}
//if (!confirm(op + op.indexOf("\n"))) {return}
op += "</table>"
return op
}



function bgg_showCubeAnalysis(d) {
var lines = new Array(), i, j
lines = d.split("\n")
var op = 'Cube analysis:<table border="0">'
for (i=2; i < lines.length; i++) {
	op += "<tr><td class='cubeanalysis'>" + bg_replace(lines[i], " ", "&nbsp;") + "</td></tr>"
}
op += "</table>"
return op
}


function bggPlay() {
this.title = ""
this.rollNo = 0
this.player = 0
this.posID = "" // starting posID (including player and optional cube)
this.lockPos = false // lock posID against recalc in dev mode
this.dice = "" // dice command
this.moves = ""
this.precomment = ""
this.postcomment = ""
this.wait = false
this.command = ""
this.analyseMove = ""
this.analyseCube = ""
this.steps = new Array()
this.noprompts = false
}

// FREE-STANDING FUNCTIONS

function bg_btrim(str) {
var s = 0, e
if (str == "" || str == null) {return str}
while (str.substr(s,1) == " ") {
	s++
	if (s == str.length) {return ""}
} 
e = str.length - 1
while (str.substr(e,1) == " ") {
	e--
}
return str.substring(s, e +1)
}

function bg_1space(str) {
var i, n, op="", pc="*"
for (i=0; i < str.length; i++) {
	n = str.substr(i,1)
	if (n != " " || n != pc) {
		op += n
	}
	pc = n
}
return op
}

function bgg_convDemoTags(txt, ignoreCheck) {
	txt = bg_replace(txt, "[demo]", '<span class="movedemo" onmouseover="this.style.fontWeight=' + "'bold'" + '" onmouseout="this.style.fontWeight=' + "'normal'" + '" title="Click here to demonstrate this move" onclick="' + this.gameID + '.demoMove(this,' + ignoreCheck + ')">' )
	return bg_replace(txt, "[/demo]", "</span>")
}

function bgg_setBoardForSteps(txt) {

// This function allows a single [step]setboard::[/step] command to set up the board, dice etc.
// Parameter is a comma-delimited string, contents:
// posID (BGR format),dice, labels, stats, horiz 
// All elements are optional - if not specified, the item isn't changed (but there must always be 3 commas!)
// Example - [step]setboard::,162,1,n,[/step]
// This leaves the board's position unchanged, but rolls dice 62 for player 1, shows player 1 labels,
// and switches off the stats display.
var i, cmds = new Array(), x
cmds = txt.split(",")
if (cmds.length < 5) {  // Allow trailing commas to be omitted
	for (i = (cmds.length -1); i < 5; i++) {
		cmds[i] = ""
	}
}
if (cmds.length != 5) {
	alert("Invalid call to setBoardForSteps - command list contains " + cmds.length + " items (should be 5)")
	return
}
//alert("SetBoardz: " + cmds.length + " " + cmds)

if (cmds[0] != "") { // Pos ID including optional cube
	this.applyPosID(bg_btrim(cmds[0]))
}
if (cmds[1] != "") { // Dice - enter '100' to switch dice off
	x = bg_btrim(cmds[1])
	i = (parseInt(x.substr(0,1)) == 1) ? 2 : 1
	this.board.showDice(i, 0, 0)
	this.board.showDice(parseInt(x.substr(0,1)),parseInt(x.substr(1,1)), parseInt(x.substr(2,1)))
}
if (cmds[2] != "") { // Labels - enter '0' to switch labels off
	this.board.showLabels(parseInt(cmds[2]))
}
if (cmds[3] != "") { // Stats - enter 'n to switch stats off, 'y' to switch them on
	this.dispStats(bg_btrim(cmds[3]))
}
if (cmds[4] != "") { // Horizontal orientation - enter 'l' or 'r'
	this.board.flipHoriz(bg_btrim(cmds[4]))
}
}

function bg_replace(str, rep, wth) {
var op = ""
var x = str.length, i, j, s, e 
if (x < 1) {return ""}
x = rep.length
if (x < 1) {return str}
s = 0 
do {
	e = str.indexOf(rep, s)
	if (e < 0) {
		op += str.substring(s)
		break
	}
	op += str.substring(s, e) + wth
	s = e + x
} while (s < str.length)
return op
}

function bg_dispTextValue(dispObj, txt, append, autohide) {
append = (append == null) ? false : append
autohide = (autohide == null) ? true : autohide
if (dispObj != null) {
	if (append) {
		dispObj.innerHTML += txt
	} else {
		dispObj.innerHTML = txt	
	}
	if (autohide && dispObj.innerHTML == "") {
		dispObj.style.display = "none"
	} else {
		dispObj.style.display = ""
	}
}
}

function bgg_genMenuPage() {
var n = "]\n", ary = this.matchArray
var qs = (this.pageFileName.substr(this.pageFileName.length -1, 1) == "&") ? "s=" : "?s="
var i, j = 1, op = "[pagetitle " + this.matchTitle + n + "[gametitle " + this.matchDescription +  n
var brk = (this.matchArray.length < 17) ? "<br><br>" : "<br>"
op += "[play][player1][noprompts][title ---Menu---][posID 1AAAAAAAAAAAAAA][command steps]"
op += "[steps][step]labels::0[/step][step]dispstats::n[/step]"
op += "[step]say::<b>MATCH MENU</b><br><br>"
for (i = this.firstGameElement; i < ary.length; i++) {
	if (ary[i] != "") {
		op += '<a href="' + this.pageFileName + qs + ary[1] + '&g=' + j + '">Game ' + j + '</a>' + brk
		j++
	}
}
op += "<br><b>Press 'Menu' to return to the previous menu</b>[/step][/steps][/play]"
//alert(op)
return op
}

function bgd_genXMLFromPage() {
var f = this.devForm, n = "]\n"
var op = '<?xml version="1.0" encoding="ISO-8859-1"?>\n'
op += '<game xmlns="http://www.paulspages.co.uk/bgreplay/">\n' 
var cp, j = "", i, ix
if (this.pageTitle != "") {
	op += "<pagetitle>" + bgd_prep4XML(this.pageTitle) + "</pagetitle>\n"
}
if (this.gameTitle != "") {
	op += "<gametitle>" + bgd_prep4XML(this.gameTitle) + "</gametitle>\n"
}
if (this.gameIntro != "") {
	op += "<gameintro>" + bgd_prep4XML(this.gameIntro) + "</gameintro>\n"
}
if (this.sourceType != "") {
	op += "<sourcetype>" + this.sourceType + "</sourcetype>\n"
}
if (this.menuScript != "") {
	op += "<menuscript>" + this.menuScript + "</menuscript>\n"
}
if (this.playerNames[1] != "") {
	op += "<playername1>" + bgd_prep4XML(this.playerNames[1]) + "</playername1>\n"
}
if (this.playerNames[2] != "") {
	op += "<playername2>" + bgd_prep4XML(this.playerNames[2]) + "</playername2>\n"
}
op += "<horiz>" + this.board.horizOrientation + "</horiz><povplr>" + this.board.povPlayer + "</povplr><povclr>" + this.board.povColour + "</povclr>\n"
var ix = (this.devSkipPlayZero) ? 1 : 0
for (i = ix; i < this.plays.length; i++) {
	cp = this.plays[i]
	op += "<play>\n"
	if (cp.player != 0) {op += "<player>" + cp.player + "</player>"}
	if (cp.title != "") {op += "<title>" + bgd_prep4XML(cp.title) + "</title>"}
	if (cp.posID != "") {op += "<posID>" + cp.posID + "</posID>"}
	if (cp.lockPos) {op += "<lockpos/>\n"}
	if (cp.noprompts) {op += "<noprompts/>\n"}		
	if (cp.dice != "") {op += "<dice>" + cp.dice + "</dice>"}
	if (cp.moves != "") {op += "<move>" + cp.moves + "</move>\n"}
	if (cp.command != "") {op += "<command>" + cp.command + "</command>\n"}
	if (cp.command.toLowerCase() == "steps") {
		j = "<steps>\n"
		for (ix=0; ix < cp.steps.length; ix++) {
			j+= "<step>" + bgd_prep4XML(cp.steps[ix][0]) + "::" + bgd_prep4XML(cp.steps[ix][1]) + "</step>\n"
		}
		j += "</steps>\n"
		op += j
	}
	if (cp.wait) {op += "<wait></wait>\n"}
	if (cp.precomment != "") {op += "<precomment>" + bgd_prep4XML(cp.precomment) + "</precomment>\n"}
	if (cp.postcomment != "") {op += "<postcomment>" + bgd_prep4XML(cp.postcomment) + "</postcomment>\n"}
	if (cp.analyseMove != "") {op += "<analysemove>" + bgd_prep4XML(cp.analyseMove) + "</analysemove>\n"}
	if (cp.analyseCube != "") {op += "<analysecube>" + bgd_prep4XML(cp.analyseCube) + "</analysecube>\n"}

	op += "</play>\n"
}
if (this.gamestats != "") {
	op += "<gamestats>" + bgd_prep4XML(this.gamestats) + "</gamestats>\n"
}
op += "</game>"
return op
}

function bgd_prep4XML (s) {
s = bg_replace(s, "&", "&amp;")
s = bg_replace(s, "<", "&lt;")

return s
}

