// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-//////////////////////////////////////////////////////////////////////////////                                                                        //// Ishido: Javascript                                                     ////                                                                        //// Copyright 2008-2010, Andrew D. Birrell                                 ////                                                                        //////////////////////////////////////////////////////////////////////////////// Cached DOM elements, static after Init(), for programming convenience// All (except board) have position:absolute, relative to board//var board;                    // entire board, loading variant: IMGvar board1;                   // entire board, variant 1: IMGvar board2;                   // entire board, variant 2: IMGvar stones;                   // stones: DIV containing Array(96) of IMGvar undoer;                   // undo click area: IMGvar flasher;                  // flasher for celebrating four-waysvar flasherBase = Object();   // original position of the flashervar touchStone;               // stone waiting to be played: IMGvar touchBase = Object();     // original position of touchStonevar scoreDisplay;             // scoreboard: DIV containing varying textvar pouchDisplay;             // pouch: DIV containing Array(72) of IMGvar iPhone = false;           // iPhone has restricted user gestures// Skin//var boardSet = -1;var stoneDir = "missing";// The stones (played, waiting, and in pouch).  Values are [0..35]//var contents = new Array();   // stones on the board, by square numbervar newStone;                 // visible stone waiting to be placedvar pouch = new Array();      // stones in the (hidden) pouchvar stonesLeft;               // count of stones in the pouch// Scoring//var score;                    // current total pointsvar fourWays;                 // count of 4-waysvar multiplier;               // doubles on each 4-wayvar highScore = 0;// Undo object.  Contains (square, stonesLeft, score, fourWays, multiplier)//var undoState = null;         // single-level undo// Animation after squareClick; inhibits other clicks until done//var animating = false;// Drag state if user is moving touchStone around//var dragging = false;var dragStartM = Object();    // initial mouse coordinates at touchStonevar dragStartO = Object();    // initial touchStone coordinatesfunction getCookie(key) {	// If there's a cookie named "key" return its value, else null	var regExp = new RegExp("(^|.*; )" + key + "=");	var value = document.cookie.replace(regExp, "");	if (value == document.cookie) return null;	return decodeURIComponent(value.replace(/;.*$/, ""));}function setCookie(key, value) {	// Set the persistent cookie named "key" to "value"	document.cookie = key + "=" + encodeURIComponent(value) +		"; expires=Thu, 31 Dec 2099 23:59:59 GMT";}function stoneUrl(stone) {	// Return the URL for given stone, or blank for -1	return (stone < 0 ? "blank" : (stoneDir + "/stoneB" + stone)) + ".png";}function placeStone(stone, square) {	// Place given design of stone (or blank) at given square	var img = document.getElementById("stone" + square);	img.src = stoneUrl(stone);	contents[square] = stone;}function showScore() {	// Display the score and relevant messages	scoreDisplay.innerHTML = "Score: " + score +		"<BR>Four-ways: " + fourWays +		"<BR>High: " + highScore +		(stonesLeft < 0 ?			"<BR><BR>Click here to start again." :			"");}function matchOne(status, stone, row, col) {	// Compare given stone with contents of (row,col) and update status	// accordingly.	if (row < 0 || row >= 8 || col < 0 || col >= 12) return;	var b = contents[row * 12 + col];	if (b < 0) return;	var color = (Math.floor(stone/6) == Math.floor(b/6));	var symbol = (stone-6*Math.floor(stone/6) == b-6*Math.floor(b/6));	if (color) status.byColor++;	if (symbol) status.bySymbol++;	if (color || symbol) status.matches++; else status.mismatch = true;}function match(stone, square) {	// If placing the stone here is legal, return the number of matches;	// else return 0	if (contents[square] >= 0) return 0;	var row = Math.floor(square / 12);	var col = square - row * 12;	var status = new Object();	status.byColor = 0;	status.bySymbol = 0;	status.matches = 0;	status.mismatch = false;	matchOne(status, stone, row, col-1);	if (!status.mismatch) matchOne(status, stone, row, col+1);	if (!status.mismatch) matchOne(status, stone, row-1, col);	if (!status.mismatch) matchOne(status, stone, row+1, col);	if (status.mismatch || status.matches == 0) {		return 0;	} else if (status.matches == 1) {		return 1;	} else if (status.matches == 2) {		return (status.byColor >= 1 && status.bySymbol >= 1 ? 2 : 0);	} else if (status.matches == 3) {		return (status.byColor >= 1 && status.bySymbol >= 1 ? 3 : 0);	} else { // Four-way		return (status.byColor >= 2 && status.bySymbol >= 2 ? 4 : 0);	}}function startMove() {	// Start a new move with next stone from the pouch, known to exist.	// Return true iff the new stone can legally be played.	if (stonesLeft <= 0) Alert("Incorrect call of \"startMove\"");	stonesLeft--;	newStone = pouch[stonesLeft];	touchStone.src = stoneUrl(newStone);	touchStone.style.cursor = "pointer";	var mini = document.getElementById("mini" + stonesLeft);	mini.src = "blank.png";	for (var i = 0; i < 96; i++) if (match(newStone, i)) return true;	return false;}function startCelebrating() {	// Start the celebration	flasher.style.visibility = "visible";	setTimeout(stopCelebrating, 300);}function stopCelebrating() {	// Cancel any celebration	flasher.style.visibility = "hidden";}function celebrate(x, y) {	// Visual celebration of four-way creation.	// The relevant square is at (x,y) relative to square 0;	// flasherBase positions flasher at square 0 relative to board.	flasher.style.left = "" + (x + flasherBase.x) + "px";	flasher.style.top = "" + (y + flasherBase.y) + "px";	setTimeout(startCelebrating, 200);}function acceptMove(square, matches) {	// Accept user's attempt to place current stone at square	// A: move the stone to the new square	placeStone(newStone, square);	touchStone.src = stoneUrl(-1);	touchStone.style.cursor = "default";	newStone = -1;	// B: turn on the undo machinery	undoState = new Object();	undoState.square = square;	undoState.stonesLeft = stonesLeft;	undoState.score = score;	undoState.fourWays = fourWays;	undoState.multiplier = multiplier;	var row = Math.floor(square / 12);	var col = square - row * 12;	var sqLeft = col * touchStone.width;	var sqTop = row * touchStone.height;	undoer.style.left = "" + (stones.offsetLeft + sqLeft) + "px";	undoer.style.top = "" + (stones.offsetTop + sqTop) + "px";	undoer.style.display = "block";	// C: do the accounting	if (row > 0 && row < 7 && col > 0 && col < 11) {		score += multiplier * (matches == 1 ? 1 :			(matches == 2 ? 2 : (matches == 3 ? 4 : 8)));		if (matches == 4) {			fourWays++;			if (fourWays == 1) score += 25;			else if (fourWays == 2) score += 50;			else if (fourWays == 3) score += 100;			else if (fourWays == 4) score += 200;			else if (fourWays == 5) score += 400;			else if (fourWays == 6) score += 600;			else if (fourWays == 7) score += 800;			else if (fourWays == 8) score += 1000;			else if (fourWays == 9) score += 5000;			else if (fourWays == 10) score += 10000;			else if (fourWays == 11) score += 25000;			else score += 50000;			multiplier *= 2;			celebrate(sqLeft, sqTop);		}		showScore();	}	// D: proceed to next move	if (stonesLeft == 0 || !startMove()) {		// end of game		if (stonesLeft == 2) score += 100;		else if (stonesLeft == 1) score += 500;		else if (stonesLeft == 0) score += 1000;		stonesLeft = -1; // flag for showScore and scoreClick		showScore();		if (score > highScore) {			highScore = score; // don't display it yet			setCookie("highScore", "" + highScore);		}		scoreDisplay.style.cursor = "pointer";	}}function chooseOne(srce, n) {	// Choose randomly one of n non-negative elements in srce.	// Replaces the element with -1 and returns the value it had	var r = Math.floor(n * Math.random());	for (var i = 0; true; i++) {		var value = srce[i];		if (value == null) {			alert("Missing value in 1-of-" + n);			return -1;		} else if (value >= 0) {			if (r == 0) { srce[i] = -1; return value; }			r--;		}	}}function chooseStarter(colors, symbols, stones, i, square) {	// Choose a random starter stone, with one of the remaining colors	// and one of the remaining symbols.	// Places the chosen stone at "square" and removes it from "stones"	var color = chooseOne(colors, i);	var symbol = chooseOne(symbols, i);	var stone = color * 6 + symbol;	stones[stone] = -1;	placeStone(stone, square);}function erase() {	// Erase the board an start a new game	for (var i = 0; i < 96; i++) placeStone(-1, i);	var stones = new Array();	for (var i = 0; i < 36; i++) {		stones[i] = i;		stones[i+36] = i;	}	var colors = new Array();	var symbols = new Array();	for (var i = 0; i < 6; i++) colors[i] = symbols[i] = i;	chooseStarter(colors, symbols, stones, 6, 0);	chooseStarter(colors, symbols, stones, 5, 11);	chooseStarter(colors, symbols, stones, 4, 41);	chooseStarter(colors, symbols, stones, 3, 54);	chooseStarter(colors, symbols, stones, 2, 84);	chooseStarter(colors, symbols, stones, 1, 95);	stonesLeft = 66;	for (var i = 0; i < stonesLeft; i++) {		pouch[i] = chooseOne(stones, stonesLeft-i);		var mini = document.getElementById("mini" + i);		mini.src = "mini.png";	}	undoState = null;	undoer.style.display = "none";	score = 0;	fourWays = 0;	multiplier = 1;	showScore();	scoreDisplay.style.cursor = "default";	startMove();}function animatedMove(square, matches) {	// Constructor for animated move from touchStone to square	this.square = square;	this.matches = matches;	var row = Math.floor(square / 12);	var col = square - row * 12;	var destX = stones.offsetLeft + col * touchStone.width;	var destY = stones.offsetTop + row * touchStone.height;	this.dx = (destX - touchBase.x) / 10;	this.dy = (destY - touchBase.y) / 10;	this.step = 0;	animating = true;	this.move();}animatedMove.prototype.move = function () {	// Move one step in the animation	this.step++;	if (this.step <= 10) {		var left = touchBase.x + this.step * this.dx;		var top = touchBase.y + this.step * this.dy;		touchStone.style.left = "" + left + "px";		touchStone.style.top = "" + top + "px";		var me = this;		setTimeout(function() { me.move(); }, 30);	} else {		acceptMove(this.square, this.matches);		animating = false;		touchStone.style.left = "" + touchBase.x + "px";		touchStone.style.top = "" + touchBase.y + "px";	}}function startDrag(event) {	// Start dragging the touchStone from its current position (which	// isn't its base if we're doing an undo).	dragStartM.x = event.clientX;	dragStartM.y = event.clientY;	dragStartO.x = touchStone.offsetLeft;	dragStartO.y = touchStone.offsetTop;	// Nudge the stone to give the user some feedback from the click.	touchStone.style.left = "" + (touchStone.offsetLeft-2) + "px";	touchStone.style.top = "" + (touchStone.offsetTop-2) + "px";	document.onmousemove = dragMove;	document.onmouseout = dragOut;	dragging = true;}function stopDrag() {	// Terminate the drag and move touchStone back to its base position	dragging = false;	document.onmousemove = null;	document.onmouseout = null;	touchStone.style.left = "" + touchBase.x + "px";	touchStone.style.top = "" + touchBase.y + "px";}function dragClick(event) {	// Respond to click in touchStone element.	// This can start or stop the drag, as appropriate.	if (!event) event = window.event; // IE versus the rest	if (dragging) {		var x = touchStone.offsetLeft + touchStone.height/2;		var y = touchStone.offsetTop + touchStone.width/2;		var col = Math.floor((x - stones.offsetLeft) / touchStone.width);		var row = Math.floor((y - stones.offsetTop) / touchStone.height);		if (col >= 0 && col < 12 && row >= 0 && row < 8) {			var square = row * 12 + col;			var matches = match(newStone, square);			if (matches > 0) {				acceptMove(square, matches);				stopDrag();			}		} else if (col > 12) stopDrag(); 	} else if (!iPhone && !animating && newStone >= 0) {		startDrag(event);	}	return false;}function dragMove(event) {	// Respond to mouse movement while dragging	if (!event) event = window.event; // IE versus the rest	if (dragging) {		var newLeft = Math.max(0, Math.min(board.width-touchStone.width,			dragStartO.x + event.clientX - dragStartM.x - 2));		var newTop = Math.max(0, Math.min(board.height-touchStone.height,			dragStartO.y + event.clientY - dragStartM.y - 2));		touchStone.style.left = "" + newLeft + "px";		touchStone.style.top = "" + newTop + "px";	}	return false;}function dragOut(event) {	// Respond to mouse leaving its target element.	// Terminate the drag if the mouse leaves the window.  Note that it	// can transiently leave the touchStone element during fast dragging,	// which does not terminate the drag.	if (!event) event = window.event; // IE versus the rest	if (dragging && !event.relatedTarget && !event.toElement) stopDrag();	return false;}function squareClick(square) {	// Respond to click in given square on the main board	if (!animating && newStone >= 0) {		// click on empty square		var matches = match(newStone, square);		if (matches > 0)  {//			new animatedMove(square, matches);			acceptMove(square, matches);		}	}}function undoClick(event) {	// Respond to click in the "undo" area	if (!event) event = window.event; // IE versus the rest	if (!animating && undoState) {		// A: move the stone back to the touchstone		newStone = contents[undoState.square];		placeStone(-1, undoState.square);		touchStone.src = stoneUrl(newStone);		touchStone.style.cursor = "pointer";		// B: fix up the accounting		stonesLeft = undoState.stonesLeft;		score = undoState.score;		fourWays = undoState.fourWays;		multiplier = undoState.multiplier;		scoreDisplay.style.cursor = "default";		showScore();		if (stonesLeft > 0) {			var mini = document.getElementById("mini" + (stonesLeft - 1));			mini.src = "mini.png";		}		// C: turn on the dragging machinery		if (!iPhone) {			touchStone.style.left = "" + undoer.offsetLeft + "px";			touchStone.style.top = "" + undoer.offsetTop + "px";			startDrag(event);		} // on iPhone, undo just jumps the undone stone back to touchBase		// D: turn off the undo machinery		undoState = null;		undoer.style.display = "none";	}}function scoreClick() {	// Respond, perhaps, to click in scoreboard area	if (stonesLeft < 0) erase();}function setStones(dir) {	if (dir != stoneDir) {		stoneDir = dir;		for (var i = 0; i < 96; i++) {			var s = contents[i];			if (s >= 0) placeStone(s, i);		}		if (newStone >= 0) touchStone.src = stoneUrl(newStone);		var bb = document.getElementById("boardBtn");		bb.src = stoneDir + "/boards.png";		var hb = document.getElementById("helpBtn");		hb.src = stoneDir + "/help.png";	}}function setBoardSet(n) {	if (n < 0 || n >= 4) n = 0;	if (n != boardSet) {		if (boardSet >= 0) setCookie("boardSet", "" + n);		boardSet = n;		board.style.display = "none";		if (boardSet == 0 || boardSet == 1) {			board1.style.display = "inline";			board = board1;		} else {			board2.style.display = "inline";			board = board2;		}		if (boardSet == 1 || boardSet == 2) {			setStones("stones");		} else {			setStones("stones2");		}	}}function boardBtnClick() {	setBoardSet(boardSet+1);}function helpBtnClick() {	var helpTxt = document.getElementById("helpTxt");	helpTxt.style.display = "block";}function helpClose() {	var helpTxt = document.getElementById("helpTxt");	helpTxt.style.display = "none";}function delayedInit() {	// Respond to onload event, after admiring the splash screen	var bCookie = getCookie("boardSet");	setBoardSet((bCookie == null ? 1 : parseInt(bCookie)));	var splash = document.getElementById("splash");	splash.style.display = "none";	var hCookie = getCookie("highScore");	highScore = (hCookie == null ? 0 : parseInt(hCookie));	erase();}function init() {	// Respond to onload event	var agent = navigator.userAgent;	iPhone = agent && agent.match(/iPod;|iPhone;/i);	board = document.getElementById("loading");	board1 = document.getElementById("board1");	board2 = document.getElementById("board2");	stones = document.getElementById("stones");	flasher = document.getElementById("flasher");	flasherBase.x = flasher.offsetLeft;	flasherBase.y = flasher.offsetTop;	undoer = document.getElementById("undoer");	touchStone = document.getElementById("touchStone");	touchBase.x = touchStone.offsetLeft;	touchBase.y = touchStone.offsetTop;	scoreDisplay = document.getElementById("scoreDisplay");	pouchDisplay = document.getElementById("pouchDisplay");	if (!iPhone) {		// "target" attribute isn't allowed in HTML 4.01, but is in the DOM;		// we want a new page, because "back" reloads the game in IE		document.getElementById("listIndex").target = "_new";		document.getElementById("listJS").target = "_new";		document.getElementById("listCSS").target = "_new";	}	setTimeout(delayedInit, 1000);}
