<commit_before>
"use strict";

function Board (options) {

	this.pieces = [];
	this.unlockedPieces = [];
	this.unlockingEffects = [];

	var colors = [
		{color: 0}, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
		{color: 0}, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
		{color: 0}, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
		{color: 0}, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
		{color: 0}, {color: 0}, {color: 0}, undefined, undefined, undefined, undefined, undefined
	];
	for (var i = colors.length - 1; i >= 0; i--) {
		if (colors[i])
			this.pieces[i] = new Piece(colors[i]);
	};
	this.makePiecesFall();
	options.pieceCycle[0] = new Piece({color: 0, key:true});

	this.pieceCycle = options.pieceCycle;
	this.pieceCycleIndex = 0;
	this.dropperPieceA = undefined;
	this.dropperPieceB = undefined;

	this.playerPosition = Math.floor((8-1)/2); // Ahrg! I want to use Board.size, but it isn't defined yet...
	this.playerOrientation = 0;

	this.serverStateCounter = 0;

	this.chargeDropper();
};

Board.size = {x:8, y:18};
Board.numColors = 4;
Board.nonKeyToKeyRatio = 7;

Board.coordToIndex = function (x, y) {

	return x + y * Board.size.x;
};

Board.indexToCoord = function (index) {

	return new Coord({x:index % Board.size.x, y:Math.floor(index / Board.size.x)});
};

Board.prototype.moveLeft = function () {

	this.playerPosition = Math.max(0, this.playerPosition - 1);

	this.animateDropper();
};

Board.prototype.moveRight = function () {

	this.playerPosition = Math.min(Board.size.x - (this.playerOrientation % 2 ? 1 : 2), this.playerPosition + 1);

	this.animateDropper();
};

Board.prototype.rotate = function () {

	this.playerOrientation = ((this.playerOrientation + 1) % 4);

	this.preventDropperFromStickingOutAfterRotation();

	this.animateDropper();
};

Board.prototype.preventDropperFromStickingOutAfterRotation = function () {

	// If the orientation is horizontal, and the pieces were at the right wall, now making the last one stick out...
	if	(!(this.playerOrientation % 2) && this.playerPosition >= Board.size.x - 1) {

		// ...move the pair up just against the wall.
		this.playerPosition = Board.size.x - 2;
	}
};

Board.prototype.drop = function () {

	var coords = this.getDropperCoordinates();

	var aPos = Board.coordToIndex(coords.a.x, coords.a.y);
	var bPos = Board.coordToIndex(coords.b.x, coords.b.y);

	// Make sure the board space is not used, and is not outside the board.
	if (
		this.pieces[aPos] ||
		this.pieces[bPos] ||
		coords.a.x < 0 || coords.a.x > Board.size.x-1 ||
		coords.b.x < 0 || coords.b.x > Board.size.x-1
	) {
		return false;
	}

	// Add the pieces.
	this.pieces[aPos] = this.dropperPieceA;
	this.pieces[bPos] = this.dropperPieceB
	this.chargeDropper();

	this.applyGameLogic();

	return true;
};

Board.prototype.getDropperCoordinates = function () {

	/* Player Orientations:

		ab	a.	ba	b.
		..	b.	..	a.
	*/

	return {
		a: {
			x: this.playerPosition + (this.playerOrientation == 2 ? 1 : 0),
			y: (this.playerOrientation == 3 ? 1 : 0)
		},
		b: {
			x: this.playerPosition + (this.playerOrientation == 0 ? 1 : 0),
			y: (this.playerOrientation == 1 ? 1 : 0)
		},
	};
}

Board.prototype.chargeDropper = function () {

	this.dropperPieceA = new Piece(this.getNextPieceInCycle(0));
	this.dropperPieceB = new Piece(this.getNextPieceInCycle(1));
	this.consumePiecesFromCycle(2);

	// Set the orientation back to horiz. or vert., but not backwards or upside-down.
	this.playerOrientation %= 2;

	this.animateDropper();
};

Board.prototype.consumePiecesFromCycle = function (count) {

	this.pieceCycleIndex = (this.pieceCycleIndex + count) % this.pieceCycle.length;
};

Board.prototype.getNextPieceInCycle = function (index) {

	return this.pieceCycle[(this.pieceCycleIndex + index) % this.pieceCycle.length];
};

Board.prototype.animateDropper = function () {

	var coords = this.getDropperCoordinates();

	var timePerPieceWidths = 50;

	this.dropperPieceA.animation.add(new Animation({
		to: coords.a,
		duration: timePerPieceWidths
	}));
	this.dropperPieceB.animation.add(new Animation({
		to: coords.b,
		duration: timePerPieceWidths
	}));
};

Board.prototype.applyGameLogic = function () {

	this.makePiecesFall();
	this.unlockChains();
};

Board.prototype.makePiecesFall = function (fallAnimationStartTime) {

	fallAnimationStartTime = fallAnimationStartTime || new Date().getTime();

	/*

	This might seem like an awful lot of code for something as simple as
	making the pieces fall.

	Turns out it isn't that simple...

	I need to set the animation properly so it is initiated only when a
	piece *starts* falling. That makes it tricky to do it in multiple
	passes. I also want a slight delay in the animation for each
	consecutive piece in a falling block. That means I need to keep track
	of wether the line of pieces is broken.

	If you have a better solution, I'm happy to see it.

	*/

	// For each collumn.
	for (var x = 0; x < Board.size.x; ++x) {

		// Start at the bottom.
		var yPut = Board.size.y - 1;

		// Search for a space that can be filled from above.
		while (yPut && this.pieces[Board.coordToIndex(x, yPut)]) {
			--yPut;
		}

		var yGet = yPut - 1;

		var numConsecutive = 0;

		// For the whole collumn...
		collumnLoop:
		while (yGet >= 0){

			// Search for a piece to put in the empty space.
			while (!this.pieces[Board.coordToIndex(x, yGet)]) {

				--yGet;

				numConsecutive = 0;

				if (yGet < 0) {
					break collumnLoop;
				}
			}

			var getPos = Board.coordToIndex(x, yGet);
			var putPos = Board.coordToIndex(x, yPut);

			// Move the piece.
			this.pieces[putPos] = this.pieces[getPos];
			this.pieces[getPos] = undefined;

			// Animate it.
			var timePerPieceHeight = 100;
			this.pieces[putPos].animation.add(new Animation({
				to: {x: x, y: yPut},
				startTime: fallAnimationStartTime,
				delay: numConsecutive * 50,
				duration: Math.sqrt(yPut - yGet) * timePerPieceHeight,
				interpolation: "easeInQuad"
			}));
			++numConsecutive;

			// Raise the put/put-positions.
			--yGet;
			--yPut;
		}
	}

};

Board.prototype.unlockChains = function () {

	var foundChains = false;
	for (var i = this.pieces.length - 1; i >= 0; i--) {

		// Look for keys.
		if (this.pieces[i] && this.pieces[i].key) {

			var matchingNeighborPositions = this.matchingNeighborsOfPosition(i);

			// If there is at least one pair in the chain...
			if (matchingNeighborPositions.length) {

				foundChains = true;

				// As soon as everything has stopped falling...
				var unlockEffectStartTime = this.maxAnimationEndTime();

				// ...Start the unlocking effect.
				var unlockEffectDuration = this.unLockChainRecursively(i, unlockEffectStartTime);

				// Fill up the gaps left by the chains, right after the unlocking effect is finished.
				this.makePiecesFall(unlockEffectStartTime + unlockEffectDuration);
			}
		}
	};

	if (foundChains) {

		// New chains might have formed.
		this.unlockChains();
	}
};

Board.prototype.maxAnimationEndTime = function () {

	var maxAnimationEndTime = new Date().getTime();

	for (var i = this.pieces.length - 1; i >= 0; i--) {

		if (this.pieces[i]) {
			maxAnimationEndTime = Math.max(maxAnimationEndTime, this.pieces[i].animation.getLast().getEndTime());
		}
	}

	return maxAnimationEndTime;
}

Board.prototype.unLockChainRecursively = function (position, unlockEffectStartTime) {

	// Must search for neighbors before removing the piece matching against.
	var matchingNeighborPositions = this.matchingNeighborsOfPosition(position);

	var unlockedPiece = this.pieces[position];

	// Another branch of the chain might have reached here before.
	if (!unlockedPiece) {
		return 0;
	}

	// Move the piece from the play field to the queue of pieces waiting for the unlocking effect.
	unlockedPiece.unlockEffectStartTime = unlockEffectStartTime;
	this.unlockedPieces.push(unlockedPiece);
	this.pieces[position] = undefined;

	// For all matching neighbors...
	var unlockEffectDelayTime = 25;
	var longestChainDuration = 0;
	for (var i = matchingNeighborPositions.length - 1; i >= 0; i--) {

		// Recurse.
		var chainDuration = this.unLockChainRecursively(matchingNeighborPositions[i], unlockEffectStartTime + unlockEffectDelayTime)

		longestChainDuration = Math.max(longestChainDuration, chainDuration);
	};

	return unlockEffectDelayTime + longestChainDuration;
};

Board.prototype.matchingNeighborsOfPosition = function (position) {

	if (!this.pieces[position]) {
		return [];
	}

	var neighborPositions = [];

	var coord = Board.indexToCoord(position);

	// Right
	if (coord.x < Board.size.x - 1) {
		neighborPositions.push(position + 1)
	}

	// Left
	if (coord.x > 0) {
		neighborPositions.push(position - 1);
	}

	// Down
	if (coord.y < Board.size.y - 1) {
		neighborPositions.push(position + Board.size.x);
	}

	// Up
	if (coord.y > 0) {
		neighborPositions.push(position - Board.size.x);
	}

	var matchingNeighborPositions = [];
	var color = this.pieces[position].color;
	for (var i = neighborPositions.length - 1; i >= 0; i--) {

		var neighborPosition = neighborPositions[i];

		if (this.pieces[neighborPosition] && this.pieces[neighborPosition].color == color) {

			matchingNeighborPositions.push(neighborPosition);
		}
	};

	return matchingNeighborPositions;
};

Board.generatePieceCycle = function () {

	// Create list of all colors.
	var baseColors = [];
	var keyColors = [];
	for (var i = 0; i < Board.numColors; ++i) {
		baseColors[i] = {color: i, key: false};
		keyColors[i] = {color: i, key: true};
	}

	// Create a list of all pieces in the proper ratios.
	var properRatio = keyColors;
	for (var i = 0; i < Board.nonKeyToKeyRatio; ++i) {
		properRatio = properRatio.concat(baseColors);
	}

	// Repeat the colors so there is a long cycle.
	var pieces = [];
	for (var i = 0; i < 32; ++i) {

		// Shuffle the group of keys and colors separately, so the whole cycle gets the keys/colors evenly distributed.
		this.fisherYatesArrayShuffle(properRatio);

		pieces = pieces.concat(properRatio);
	}

	return pieces;
};

// http://stackoverflow.com/questions/2450954/how-to-randomize-a-javascript-array
Board.fisherYatesArrayShuffle = function (myArray) {

	var i = myArray.length;
	if ( i == 0 ) return false;
	while ( --i ) {
		var j = Math.floor( Math.random() * ( i + 1 ) );
		var tempi = myArray[i];
		var tempj = myArray[j];
		myArray[i] = tempj;
		myArray[j] = tempi;
	 }
}

Board.prototype.draw = function (context, currentTime, center, scale) {

	// Draw the unlocking effects.
	var doneUnlockingEffectIndices = [];
	for (var i = this.unlockingEffects.length - 1; i >= 0; i--) {

		if (!this.unlockingEffects[i].isDone(currentTime)) {

			this.unlockingEffects[i].draw(
				context,
				currentTime,
				center,
				scale
			);

		} else {

			doneUnlockingEffectIndices.push(i);
		};
	};

	// Remove the unlocking effects when they are done.
	for (var i = doneUnlockingEffectIndices.length - 1; i >= 0; i--) {

		this.unlockingEffects.splice(doneUnlockingEffectIndices[i], 1);
	};

	// Draw the unlocked pieces, queued for unlocking effects.
	var donePieceIndices = [];
	for (var i = 0, length = this.unlockedPieces.length; i < length; ++i) {

		var piece = this.unlockedPieces[i];

		if (piece.unlockEffectStartTime > currentTime) {

			// The piece should still be visible, so draw like normal.
			piece.draw(
				context,
				currentTime,
				center,
				scale
			);

		} else {

			// Remove it from the unlocking queue.
			donePieceIndices.push(i);

			// Start the unlocking effects.
			this.unlockingEffects.push(new UnlockingEffect(piece));
		}
	}

	// Remove the unlocked pieces from the unlocking effect queue.
	for (var i = donePieceIndices.length - 1; i >= 0; i--) {

		this.unlockedPieces.splice(donePieceIndices[i], 1);
	};

	// Draw the board pieces.
	for (var i = 0, length = this.pieces.length; i < length; ++i) {

		var piece = this.pieces[i];
		if (piece !== undefined) {

			piece.draw(
				context,
				currentTime,
				center,
				scale
			);
		}
	}

	// Draw the dropper pieces.
	this.dropperPieceA.draw(
		context,
		currentTime,
		center,
		scale
	);
	this.dropperPieceB.draw(
		context,
		currentTime,
		center,
		scale
	);
};

<commit_msg>Fixed bug when unlocking multiple chains directly after a drop/fall.<commit_after>
"use strict";

function Board (options) {

	this.pieces = [];
	this.unlockedPieces = [];
	this.unlockingEffects = [];

	// var colors = [
	// 	{color: 1}, {color: 1}, {color: 1}, undefined, undefined, undefined, undefined, undefined,
	// 	{color: 0}, {color: 0}, {color: 0}, undefined, undefined, undefined, undefined, undefined
	// ];
	// for (var i = colors.length - 1; i >= 0; i--) {
	// 	if (colors[i])
	// 		this.pieces[i] = new Piece(colors[i]);
	// };
	// this.makePiecesFall();
	// options.pieceCycle[0] = new Piece({color: 0, key:true});
	// options.pieceCycle[1] = new Piece({color: 1, key:true});

	this.pieceCycle = options.pieceCycle;
	this.pieceCycleIndex = 0;
	this.dropperPieceA = undefined;
	this.dropperPieceB = undefined;

	this.playerPosition = Math.floor((8-1)/2); // Ahrg! I want to use Board.size, but it isn't defined yet...
	this.playerOrientation = 0;

	this.serverStateCounter = 0;

	this.chargeDropper();
};

Board.size = {x:8, y:18};
Board.numColors = 4;
Board.nonKeyToKeyRatio = 7;

Board.coordToIndex = function (x, y) {

	return x + y * Board.size.x;
};

Board.indexToCoord = function (index) {

	return new Coord({x:index % Board.size.x, y:Math.floor(index / Board.size.x)});
};

Board.prototype.moveLeft = function () {

	this.playerPosition = Math.max(0, this.playerPosition - 1);

	this.animateDropper();
};

Board.prototype.moveRight = function () {

	this.playerPosition = Math.min(Board.size.x - (this.playerOrientation % 2 ? 1 : 2), this.playerPosition + 1);

	this.animateDropper();
};

Board.prototype.rotate = function () {

	this.playerOrientation = ((this.playerOrientation + 1) % 4);

	this.preventDropperFromStickingOutAfterRotation();

	this.animateDropper();
};

Board.prototype.preventDropperFromStickingOutAfterRotation = function () {

	// If the orientation is horizontal, and the pieces were at the right wall, now making the last one stick out...
	if	(!(this.playerOrientation % 2) && this.playerPosition >= Board.size.x - 1) {

		// ...move the pair up just against the wall.
		this.playerPosition = Board.size.x - 2;
	}
};

Board.prototype.drop = function () {

	var coords = this.getDropperCoordinates();

	var aPos = Board.coordToIndex(coords.a.x, coords.a.y);
	var bPos = Board.coordToIndex(coords.b.x, coords.b.y);

	// Make sure the board space is not used, and is not outside the board.
	if (
		this.pieces[aPos] ||
		this.pieces[bPos] ||
		coords.a.x < 0 || coords.a.x > Board.size.x-1 ||
		coords.b.x < 0 || coords.b.x > Board.size.x-1
	) {
		return false;
	}

	// Add the pieces.
	this.pieces[aPos] = this.dropperPieceA;
	this.pieces[bPos] = this.dropperPieceB
	this.chargeDropper();

	this.applyGameLogic();

	return true;
};

Board.prototype.getDropperCoordinates = function () {

	/* Player Orientations:

		ab	a.	ba	b.
		..	b.	..	a.
	*/

	return {
		a: {
			x: this.playerPosition + (this.playerOrientation == 2 ? 1 : 0),
			y: (this.playerOrientation == 3 ? 1 : 0)
		},
		b: {
			x: this.playerPosition + (this.playerOrientation == 0 ? 1 : 0),
			y: (this.playerOrientation == 1 ? 1 : 0)
		},
	};
}

Board.prototype.chargeDropper = function () {

	this.dropperPieceA = new Piece(this.getNextPieceInCycle(0));
	this.dropperPieceB = new Piece(this.getNextPieceInCycle(1));
	this.consumePiecesFromCycle(2);

	// Set the orientation back to horiz. or vert., but not backwards or upside-down.
	this.playerOrientation %= 2;

	this.animateDropper();
};

Board.prototype.consumePiecesFromCycle = function (count) {

	this.pieceCycleIndex = (this.pieceCycleIndex + count) % this.pieceCycle.length;
};

Board.prototype.getNextPieceInCycle = function (index) {

	return this.pieceCycle[(this.pieceCycleIndex + index) % this.pieceCycle.length];
};

Board.prototype.animateDropper = function () {

	var coords = this.getDropperCoordinates();

	var timePerPieceWidths = 50;

	this.dropperPieceA.animation.add(new Animation({
		to: coords.a,
		duration: timePerPieceWidths
	}));
	this.dropperPieceB.animation.add(new Animation({
		to: coords.b,
		duration: timePerPieceWidths
	}));
};

Board.prototype.applyGameLogic = function () {

	this.makePiecesFall();
	this.unlockChains();
};

Board.prototype.makePiecesFall = function (fallAnimationStartTime) {

	fallAnimationStartTime = fallAnimationStartTime || new Date().getTime();

	/*

	This might seem like an awful lot of code for something as simple as
	making the pieces fall.

	Turns out it isn't that simple...

	I need to set the animation properly so it is initiated only when a
	piece *starts* falling. That makes it tricky to do it in multiple
	passes. I also want a slight delay in the animation for each
	consecutive piece in a falling block. That means I need to keep track
	of wether the line of pieces is broken.

	If you have a better solution, I'm happy to see it.

	*/

	// For each collumn.
	for (var x = 0; x < Board.size.x; ++x) {

		// Start at the bottom.
		var yPut = Board.size.y - 1;

		// Search for a space that can be filled from above.
		while (yPut && this.pieces[Board.coordToIndex(x, yPut)]) {
			--yPut;
		}

		var yGet = yPut - 1;

		var numConsecutive = 0;

		// For the whole collumn...
		collumnLoop:
		while (yGet >= 0){

			// Search for a piece to put in the empty space.
			while (!this.pieces[Board.coordToIndex(x, yGet)]) {

				--yGet;

				numConsecutive = 0;

				if (yGet < 0) {
					break collumnLoop;
				}
			}

			var getPos = Board.coordToIndex(x, yGet);
			var putPos = Board.coordToIndex(x, yPut);

			// Move the piece.
			this.pieces[putPos] = this.pieces[getPos];
			this.pieces[getPos] = undefined;

			// Animate it.
			var timePerPieceHeight = 100;
			this.pieces[putPos].animation.add(new Animation({
				to: {x: x, y: yPut},
				startTime: fallAnimationStartTime,
				delay: numConsecutive * 50,
				duration: Math.sqrt(yPut - yGet) * timePerPieceHeight,
				interpolation: "easeInQuad"
			}));
			++numConsecutive;

			// Raise the put/put-positions.
			--yGet;
			--yPut;
		}
	}

};

Board.prototype.unlockChains = function () {

	var foundChains = false;
	var maxUnlockEffectDuration = 0;
	for (var i = this.pieces.length - 1; i >= 0; i--) {

		// Look for keys.
		if (this.pieces[i] && this.pieces[i].key) {

			var matchingNeighborPositions = this.matchingNeighborsOfPosition(i);

			// If there is at least one pair in the chain...
			if (matchingNeighborPositions.length) {

				foundChains = true;

				// As soon as everything has stopped falling...
				var unlockEffectStartTime = this.maxAnimationEndTime();

				// ...Start the unlocking effect.
				var unlockEffectDuration = this.unLockChainRecursively(i, unlockEffectStartTime);

				maxUnlockEffectDuration = Math.max(maxUnlockEffectDuration, unlockEffectDuration);
			}
		}
	};

	if (foundChains) {

		// Fill up the gaps left by the chains, right after the unlocking effect is finished.
		this.makePiecesFall(this.maxAnimationEndTime() + maxUnlockEffectDuration);

		// New chains might have formed.
		this.unlockChains();
	}
};

Board.prototype.maxAnimationEndTime = function () {

	var maxAnimationEndTime = new Date().getTime();

	for (var i = this.pieces.length - 1; i >= 0; i--) {

		if (this.pieces[i]) {
			maxAnimationEndTime = Math.max(maxAnimationEndTime, this.pieces[i].animation.getLast().getEndTime());
		}
	}

	return maxAnimationEndTime;
}

Board.prototype.unLockChainRecursively = function (position, unlockEffectStartTime) {

	// Must search for neighbors before removing the piece matching against.
	var matchingNeighborPositions = this.matchingNeighborsOfPosition(position);

	var unlockedPiece = this.pieces[position];

	// Another branch of the chain might have reached here before.
	if (!unlockedPiece) {
		return 0;
	}

	// Move the piece from the play field to the queue of pieces waiting for the unlocking effect.
	unlockedPiece.unlockEffectStartTime = unlockEffectStartTime;
	this.unlockedPieces.push(unlockedPiece);
	this.pieces[position] = undefined;

	// For all matching neighbors...
	var unlockEffectDelayTime = 25;
	var longestChainDuration = 0;
	for (var i = matchingNeighborPositions.length - 1; i >= 0; i--) {

		// Recurse.
		var chainDuration = this.unLockChainRecursively(matchingNeighborPositions[i], unlockEffectStartTime + unlockEffectDelayTime)

		longestChainDuration = Math.max(longestChainDuration, chainDuration);
	};

	return unlockEffectDelayTime + longestChainDuration;
};

Board.prototype.matchingNeighborsOfPosition = function (position) {

	if (!this.pieces[position]) {
		return [];
	}

	var neighborPositions = [];

	var coord = Board.indexToCoord(position);

	// Right
	if (coord.x < Board.size.x - 1) {
		neighborPositions.push(position + 1)
	}

	// Left
	if (coord.x > 0) {
		neighborPositions.push(position - 1);
	}

	// Down
	if (coord.y < Board.size.y - 1) {
		neighborPositions.push(position + Board.size.x);
	}

	// Up
	if (coord.y > 0) {
		neighborPositions.push(position - Board.size.x);
	}

	var matchingNeighborPositions = [];
	var color = this.pieces[position].color;
	for (var i = neighborPositions.length - 1; i >= 0; i--) {

		var neighborPosition = neighborPositions[i];

		if (this.pieces[neighborPosition] && this.pieces[neighborPosition].color == color) {

			matchingNeighborPositions.push(neighborPosition);
		}
	};

	return matchingNeighborPositions;
};

Board.generatePieceCycle = function () {

	// Create list of all colors.
	var baseColors = [];
	var keyColors = [];
	for (var i = 0; i < Board.numColors; ++i) {
		baseColors[i] = {color: i, key: false};
		keyColors[i] = {color: i, key: true};
	}

	// Create a list of all pieces in the proper ratios.
	var properRatio = keyColors;
	for (var i = 0; i < Board.nonKeyToKeyRatio; ++i) {
		properRatio = properRatio.concat(baseColors);
	}

	// Repeat the colors so there is a long cycle.
	var pieces = [];
	for (var i = 0; i < 32; ++i) {

		// Shuffle the group of keys and colors separately, so the whole cycle gets the keys/colors evenly distributed.
		this.fisherYatesArrayShuffle(properRatio);

		pieces = pieces.concat(properRatio);
	}

	return pieces;
};

// http://stackoverflow.com/questions/2450954/how-to-randomize-a-javascript-array
Board.fisherYatesArrayShuffle = function (myArray) {

	var i = myArray.length;
	if ( i == 0 ) return false;
	while ( --i ) {
		var j = Math.floor( Math.random() * ( i + 1 ) );
		var tempi = myArray[i];
		var tempj = myArray[j];
		myArray[i] = tempj;
		myArray[j] = tempi;
	 }
}

Board.prototype.draw = function (context, currentTime, center, scale) {

	// Draw the unlocking effects.
	var doneUnlockingEffectIndices = [];
	for (var i = this.unlockingEffects.length - 1; i >= 0; i--) {

		if (!this.unlockingEffects[i].isDone(currentTime)) {

			this.unlockingEffects[i].draw(
				context,
				currentTime,
				center,
				scale
			);

		} else {

			doneUnlockingEffectIndices.push(i);
		};
	};

	// Remove the unlocking effects when they are done.
	for (var i = doneUnlockingEffectIndices.length - 1; i >= 0; i--) {

		this.unlockingEffects.splice(doneUnlockingEffectIndices[i], 1);
	};

	// Draw the unlocked pieces, queued for unlocking effects.
	var donePieceIndices = [];
	for (var i = 0, length = this.unlockedPieces.length; i < length; ++i) {

		var piece = this.unlockedPieces[i];

		if (piece.unlockEffectStartTime > currentTime) {

			// The piece should still be visible, so draw like normal.
			piece.draw(
				context,
				currentTime,
				center,
				scale
			);

		} else {

			// Remove it from the unlocking queue.
			donePieceIndices.push(i);

			// Start the unlocking effects.
			this.unlockingEffects.push(new UnlockingEffect(piece));
		}
	}

	// Remove the unlocked pieces from the unlocking effect queue.
	for (var i = donePieceIndices.length - 1; i >= 0; i--) {

		this.unlockedPieces.splice(donePieceIndices[i], 1);
	};

	// Draw the board pieces.
	for (var i = 0, length = this.pieces.length; i < length; ++i) {

		var piece = this.pieces[i];
		if (piece !== undefined) {

			piece.draw(
				context,
				currentTime,
				center,
				scale
			);
		}
	}

	// Draw the dropper pieces.
	this.dropperPieceA.draw(
		context,
		currentTime,
		center,
		scale
	);
	this.dropperPieceB.draw(
		context,
		currentTime,
		center,
		scale
	);
};

<|endoftext|>


<commit_before>
/**
  End-to-end tests of a small pbf extract.

  The vancouver_canada.osm.pbf extract will be automatically downloaded before testing.
  @see: ./pretest.sh for more details, or run manually to download file.
**/

var fs = require('fs'),
    path = require('path'),
    colors = require('colors'),
    tmp = require('tmp'),
    deep = require('deep-diff'),
    streams = require('../'),
    sink = require('through2-sink');
    _ = require('lodash');

var tmpfile = tmp.fileSync({ postfix: '.json' }).name,
    pbfPath = path.resolve(__dirname) + '/vancouver_canada.osm.pbf',
    expectedPath = path.resolve(__dirname) + '/fixtures/vancouver.extract.expected.json';

var results = [];

console.log(pbfPath);

streams.pbfParser({ file: pbfPath })
  .pipe( streams.docConstructor() )
  .pipe( streams.tagMapper() )
  .pipe( streams.docDenormalizer() )
  .pipe( streams.addressExtractor() )
  .pipe( streams.categoryMapper( streams.config.categoryDefaults ) )
  .pipe( streams.dbMapper() )
  .pipe( sink.obj(function (doc) {
    results.push(doc);
  }) )
  .on('error', function error(err) {
    console.error('YIKES! Test failed with the following error: ', err.message);
    process.exit();
  })
  .on('finish', function assert(){

    var actual = results;
    var expected = JSON.parse( fs.readFileSync( expectedPath, { encoding: 'utf8' } ) );

    actual = _.sortBy(actual, ['_id', '_type']);
    expected = _.sortBy(expected, ['_id', '_type']);

    fs.writeFileSync('actual_output.json', JSON.stringify(actual, null, 2));
    fs.writeFileSync('expected_output.json', JSON.stringify(expected, null, 2));

    var i = 0;
    var countDiff = 0;
    var countSame = 0;

    while(i<actual.length) {
      var d = deep.diff(actual[i], expected[i]);
      if (d) {
        countDiff++;
      }
      else {
        countSame++;
      }
      i++;
    }
    var diff = deep.diff( actual, expected );

    fs.unlinkSync('actual_output.json');
    fs.unlinkSync('expected_output.json');

    if( diff ){
      //console.log( JSON.stringify(diff, null, 2) );
      console.log('actual count:', actual.length);
      console.log('expected count:', expected.length);
      console.log('matching count:', colors.green(countSame));
      console.log('difference count:', colors.red(countDiff));
      console.error( colors.red( 'end-to-end tests failed :(' ) );
      console.error( 'contents of', tmpfile, 'do not match expected:', expectedPath );
      process.exit(1);
    }

    console.error( colors.green( 'end-to-end tests passed :)' ) );
  });<commit_msg>Fix variable declaration<commit_after>
/**
  End-to-end tests of a small pbf extract.

  The vancouver_canada.osm.pbf extract will be automatically downloaded before testing.
  @see: ./pretest.sh for more details, or run manually to download file.
**/

var fs = require('fs'),
    path = require('path'),
    colors = require('colors'),
    tmp = require('tmp'),
    deep = require('deep-diff'),
    streams = require('../'),
    sink = require('through2-sink'),
    _ = require('lodash');

var tmpfile = tmp.fileSync({ postfix: '.json' }).name,
    pbfPath = path.resolve(__dirname) + '/vancouver_canada.osm.pbf',
    expectedPath = path.resolve(__dirname) + '/fixtures/vancouver.extract.expected.json';

var results = [];

console.log(pbfPath);

streams.pbfParser({ file: pbfPath })
  .pipe( streams.docConstructor() )
  .pipe( streams.tagMapper() )
  .pipe( streams.docDenormalizer() )
  .pipe( streams.addressExtractor() )
  .pipe( streams.categoryMapper( streams.config.categoryDefaults ) )
  .pipe( streams.dbMapper() )
  .pipe( sink.obj(function (doc) {
    results.push(doc);
  }) )
  .on('error', function error(err) {
    console.error('YIKES! Test failed with the following error: ', err.message);
    process.exit();
  })
  .on('finish', function assert(){

    var actual = results;
    var expected = JSON.parse( fs.readFileSync( expectedPath, { encoding: 'utf8' } ) );

    actual = _.sortBy(actual, ['_id', '_type']);
    expected = _.sortBy(expected, ['_id', '_type']);

    fs.writeFileSync('actual_output.json', JSON.stringify(actual, null, 2));
    fs.writeFileSync('expected_output.json', JSON.stringify(expected, null, 2));

    var i = 0;
    var countDiff = 0;
    var countSame = 0;

    while(i<actual.length) {
      var d = deep.diff(actual[i], expected[i]);
      if (d) {
        countDiff++;
      }
      else {
        countSame++;
      }
      i++;
    }
    var diff = deep.diff( actual, expected );

    fs.unlinkSync('actual_output.json');
    fs.unlinkSync('expected_output.json');

    if( diff ){
      //console.log( JSON.stringify(diff, null, 2) );
      console.log('actual count:', actual.length);
      console.log('expected count:', expected.length);
      console.log('matching count:', colors.green(countSame));
      console.log('difference count:', colors.red(countDiff));
      console.error( colors.red( 'end-to-end tests failed :(' ) );
      console.error( 'contents of', tmpfile, 'do not match expected:', expectedPath );
      process.exit(1);
    }

    console.error( colors.green( 'end-to-end tests passed :)' ) );
  });
<|endoftext|>


<commit_before>"use strict";
/*jshint node: true*/
/*eslint-env node*/

const
    fs = require("fs"),
    path = require("path"),

    defaultContent = {
        version: 1,
        serve: {
            httpPort: 8000
        },
        host: {
            wscript: false,
            java: false
        },
        browsers: {},
        metrics: {
            coverage: {
                statements: 90,
                functions: 90,
                branches: 90,
                lines: 90
            },
            maintainability: 70
        }
    },

    contentMigrationPath = [
        content => {
            /*
             * from: selenium.browsers: [name]
             * to: browsers: { name: { type: "selenium" } }
             */
            content.browsers = {};
            content.selenium.browsers.forEach(name => {
                content.browsers[name] = {
                    type: "selenium"
                };
            });
            delete content.selenium; // No more needed
        }
    ];

module.exports = class ConfigFile {

    constructor () {
        this.read();
    }

    getFileName () {
        if (!this._fileName) {
            this._fileName = path.join(__dirname, "../tmp/config.json");
        }
    }

    read () {
        let fileName = this.getFileName();
        if (fs.existsSync(fileName)) {
            this.content = JSON.parse(fs.readFileSync(fileName).toString());
            this.savedJSON = JSON.stringify(this.content); // Ignore formatting
            this._checkVersion();
        } else {
            this.content = defaultContent;
            this.savedJSON = "";
        }
    }

    _checkVersion () {
        var currentVersion = this.content.version || 0;
        if (currentVersion < contentMigrationPath.length) {
            contentMigrationPath.slice(currentVersion).forEach(migrate => migrate(this.content));
        }
        this.content.version = contentMigrationPath.length;
    }

    isNew () {
        return !this.savedJSON;
    }

    isModified () {
        return JSON.stringify(this.content) !== this.savedJSON;
    }

    save () {
        if (this.isModified()) {
            let json = JSON.stringify(this.content);
            fs.writeFileSync(this.getFileName(), json);
            this.savedJSON = json;
        }
    }

};
<commit_msg>Fix fileName<commit_after>"use strict";
/*jshint node: true*/
/*eslint-env node*/

const
    fs = require("fs"),
    path = require("path"),

    defaultContent = {
        version: 1,
        serve: {
            httpPort: 8000
        },
        host: {
            wscript: false,
            java: false
        },
        browsers: {},
        metrics: {
            coverage: {
                statements: 90,
                functions: 90,
                branches: 90,
                lines: 90
            },
            maintainability: 70
        }
    },

    contentMigrationPath = [
        content => {
            /*
             * from: selenium.browsers: [name]
             * to: browsers: { name: { type: "selenium" } }
             */
            content.browsers = {};
            content.selenium.browsers.forEach(name => {
                content.browsers[name] = {
                    type: "selenium"
                };
            });
            delete content.selenium; // No more needed
        }
    ];

module.exports = class ConfigFile {

    constructor () {
        this.read();
    }

    getFileName () {
        if (!this._fileName) {
            this._fileName = path.join(__dirname, "../tmp/config.json");
        }
        return this._fileName;
    }

    read () {
        let fileName = this.getFileName();
        if (fs.existsSync(fileName)) {
            this.content = JSON.parse(fs.readFileSync(fileName).toString());
            this.savedJSON = JSON.stringify(this.content); // Ignore formatting
            this._checkVersion();
        } else {
            this.content = defaultContent;
            this.savedJSON = "";
        }
    }

    _checkVersion () {
        var currentVersion = this.content.version || 0;
        if (currentVersion < contentMigrationPath.length) {
            contentMigrationPath.slice(currentVersion).forEach(migrate => migrate(this.content));
        }
        this.content.version = contentMigrationPath.length;
    }

    isNew () {
        return !this.savedJSON;
    }

    isModified () {
        return JSON.stringify(this.content) !== this.savedJSON;
    }

    save () {
        if (this.isModified()) {
            let json = JSON.stringify(this.content);
            fs.writeFileSync(this.getFileName(), json);
            this.savedJSON = json;
        }
    }

};
<|endoftext|>


<commit_before>(function(ns) {
  var prevState = ns.prevState || [];
  var currentState = [];
  var domGUID = ns.domGUID = ns.domGUID || 0;
  var nodeTypes = [];

  $('body').contents().each(function processNodes() {
    var selfNode = this;
    var jqThis = $(this);
    var val = "";
    var childs = [];
    var childIDs = [];
    var thisUID = -1;

    if(this.domDeltaID) { thisUID = this.domDeltaID; }
    else {
      thisUID = this.domDeltaID = ns.domGUID++;
      val += "ADDED NODE: " + this.domDeltaID + "; ";
    }

    if(nodeTypes.indexOf(this.nodeType) == -1) nodeTypes.push(this.nodeType);

    var tempstyle = window.getComputedStyle(this);
    var style = null;

    // Deep copy the computed style in case the function returns the same object
    if(tempstyle) {
      style = {};
      $.each(tempstyle, function(key, value) {
        style[key] = value;
      });
    }

    if(jqThis.contents().length > 0) {
      // Make sure children are processed first so we can get a UID for each of them
      jqThis.contents().each(processNodes);

      jqThis.contents().each(function() {
        childs.push(this);
        childIDs.push(this.domDeltaID);
      });
    }

    // Record the state of affairs for the node
    currentState[thisUID] = {style: style, children:childs, childrenIDs:childIDs, dom:this, classList: this.className ? this.className.split(' ') : null };
  });

  console.log("NodeTypes", nodeTypes);
  console.log("Previous State", ns.prevState);
  console.log("Current State", currentState);

  if(prevState.length > 0) {
    var testsRan = 0;
    console.log("Calculating Diff...");
    for(var i=0,end=currentState.length; i!=end; ++i) {
      // See if the UID exists in the prev state
      if(!(i in prevState)) {
        console.log("Added element: ", currentState[i]);
        continue;
      }

      if(currentState[i]) {}
      else { console.log("POSSIBLY REMOVED: Undefined current state element"); continue; }

      var dom = currentState[i].dom;
      var style = currentState[i].style;
      var prevStyle = prevState[i].style;
      var classList = currentState[i].classList;
      var prevClassList = prevState[i].classList;

      // Find class add/removals
      if(classList) {
      	if(prevClassList) {
      		// Adds
      		$.each(classList, function(index, className) {
      			if(className.trim().length > 0) {
	      			if(prevClassList.indexOf(className) == -1) {
	      				console.log("Added class to: ", dom, "Class: ", className);
	      			}
      			}
      		});
      		// Removes
      		$.each(prevClassList, function(index, className) {
      			if(className.trim().length > 0) {
	      			if(classList.indexOf(className) == -1) {
	      				console.log("Removed class from: ", dom, "Class: ", className);
	      			}
      			}
      		});
      	}

      	else {
      		console.log("Added classes to: ", dom, "Classes: ", classList);
      	}
      }

      else {
      	if(prevClassList) {
      		console.log("Removed classes from: ", dom, "Classes: ", classList);
      	}
      }

      // Make sure a style was generated for our item in question
      if(style && prevStyle) {
        // Find changed and added properties
        $.each(style, function(key, value) {
          if(key in prevStyle) {
            if(!(prevStyle[key] === style[key])) {
              console.log("DOM Node:", dom, "Property", key, "PREV: ", prevStyle[key], "NEW: ", style[key]);
            }
          }

          else {
            console.log("DOM Node:", dom, "New Property", key, "NEW: ", style[key]);
          }

          testsRan++;
        });

        // Find removed properties
        $.each(prevStyle, function(key, value) {
          if(!(key in style)) {
            console.log("DOM Node:", dom, "Removed Property", key, "NEW: ", style[key]);
          }
        });
      }

      // Now diff children additions/removals
      $.each(currentState[i].childrenIDs, function(index, value) {
        if(prevState[i].childrenIDs.indexOf(value) == -1) {
          console.log("Added child to: ", dom, "Child: ", currentState[i].children[index]);
        }
      });
      $.each(prevState[i].childrenIDs, function(index, value) {
        if(currentState[i].childrenIDs.indexOf(value) == -1) {
          console.log("Removed child from: ", dom, "Child: ", prevState[i].children[index]);
        }
      });
    }

    console.log("Tested " + testsRan + " properties for differences.");
  }

  else {
    console.log("First frame captured, run again after making some changes to see diff");
  }

  ns.prevState = currentState;
  ns.domGUID = domGUID;

})(window.top);
<commit_msg>Removed strict equality<commit_after>(function(ns) {
  var prevState = ns.prevState || [];
  var currentState = [];
  var domGUID = ns.domGUID = ns.domGUID || 0;
  var nodeTypes = [];

  $('body').contents().each(function processNodes() {
    var selfNode = this;
    var jqThis = $(this);
    var val = "";
    var childs = [];
    var childIDs = [];
    var thisUID = -1;

    if(this.domDeltaID) { thisUID = this.domDeltaID; }
    else {
      thisUID = this.domDeltaID = ns.domGUID++;
      val += "ADDED NODE: " + this.domDeltaID + "; ";
    }

    if(nodeTypes.indexOf(this.nodeType) == -1) nodeTypes.push(this.nodeType);

    var tempstyle = window.getComputedStyle(this);
    var style = null;

    // Deep copy the computed style in case the function returns the same object
    if(tempstyle) {
      style = {};
      $.each(tempstyle, function(key, value) {
        style[key] = value;
      });
    }

    if(jqThis.contents().length > 0) {
      // Make sure children are processed first so we can get a UID for each of them
      jqThis.contents().each(processNodes);

      jqThis.contents().each(function() {
        childs.push(this);
        childIDs.push(this.domDeltaID);
      });
    }

    // Record the state of affairs for the node
    currentState[thisUID] = {style: style, children:childs, childrenIDs:childIDs, dom:this, classList: this.className ? this.className.split(' ') : null };
  });

  console.log("NodeTypes", nodeTypes);
  console.log("Previous State", ns.prevState);
  console.log("Current State", currentState);

  if(prevState.length > 0) {
    var testsRan = 0;
    console.log("Calculating Diff...");
    for(var i=0,end=currentState.length; i!=end; ++i) {
      // See if the UID exists in the prev state
      if(!(i in prevState)) {
        console.log("Added element: ", currentState[i]);
        continue;
      }

      if(currentState[i]) {}
      else { console.log("POSSIBLY REMOVED: Undefined current state element"); continue; }

      var dom = currentState[i].dom;
      var style = currentState[i].style;
      var prevStyle = prevState[i].style;
      var classList = currentState[i].classList;
      var prevClassList = prevState[i].classList;

      // Find class add/removals
      if(classList) {
      	if(prevClassList) {
      		// Adds
      		$.each(classList, function(index, className) {
      			if(className.trim().length > 0) {
	      			if(prevClassList.indexOf(className) == -1) {
	      				console.log("Added class to: ", dom, "Class: ", className);
	      			}
      			}
      		});
      		// Removes
      		$.each(prevClassList, function(index, className) {
      			if(className.trim().length > 0) {
	      			if(classList.indexOf(className) == -1) {
	      				console.log("Removed class from: ", dom, "Class: ", className);
	      			}
      			}
      		});
      	}

      	else {
      		console.log("Added classes to: ", dom, "Classes: ", classList);
      	}
      }

      else {
      	if(prevClassList) {
      		console.log("Removed classes from: ", dom, "Classes: ", classList);
      	}
      }

      // Make sure a style was generated for our item in question
      if(style && prevStyle) {
        // Find changed and added properties
        $.each(style, function(key, value) {
          if(key in prevStyle) {
            if((prevStyle[key] != style[key])) {
              console.log("DOM Node:", dom, "Property", key, "PREV: ", prevStyle[key], "NEW: ", style[key]);
            }
          }

          else {
            console.log("DOM Node:", dom, "New Property", key, "NEW: ", style[key]);
          }

          testsRan++;
        });

        // Find removed properties
        $.each(prevStyle, function(key, value) {
          if(!(key in style)) {
            console.log("DOM Node:", dom, "Removed Property", key, "NEW: ", style[key]);
          }
        });
      }

      // Now diff children additions/removals
      $.each(currentState[i].childrenIDs, function(index, value) {
        if(prevState[i].childrenIDs.indexOf(value) == -1) {
          console.log("Added child to: ", dom, "Child: ", currentState[i].children[index]);
        }
      });
      $.each(prevState[i].childrenIDs, function(index, value) {
        if(currentState[i].childrenIDs.indexOf(value) == -1) {
          console.log("Removed child from: ", dom, "Child: ", prevState[i].children[index]);
        }
      });
    }

    console.log("Tested " + testsRan + " properties for differences.");
  }

  else {
    console.log("First frame captured, run again after making some changes to see diff");
  }

  ns.prevState = currentState;
  ns.domGUID = domGUID;

})(window.top);
<|endoftext|>


<commit_before>import 'babel-polyfill';
import { assert,
		 expect,
		 should } from "chai";
import Fenix  	  from "../src/Fenix";

let urlBase = "http://jsonplaceholder.typicode.com"
let sources = {
	"posts": {
		methods: '*'
	},
	"comments": {
		methods: '*'
	}
}

let params = { urlBase, sources };
let api = new Fenix(params);

describe('fenix', () => {

	it('find one', () => {

		let id = 1;

		api.posts(id).then(data => {
			assert.typeOf(data, 'object');
			assert.equal(id, data.id);
		});

    });

	it('find multiple', () => {

		api.posts().then(data => {
			expect(data).to.be.instanceof(Array);
		});

    });
});<commit_msg>cache test<commit_after>import 'babel-polyfill';
import { assert,
		 expect,
		 should } from "chai";
import Fenix  	  from "../src/Fenix";

let urlBase = "http://jsonplaceholder.typicode.com"
let sources = {
	"posts": {
		methods: '*'
	},
	"comments": {
		methods: 'GET',
		cache: {
			expires: "30"
		}
	}
};

let params = { urlBase, sources };
let api = new Fenix(params);

describe('fenix', () => {

	it('find one', async function() {

		let id = 1;

		let { data: post } = await api.posts(id);

		assert.typeOf(post, 'object');
		assert.equal(id, post.id);

    });

	it('find multiple', async function() {

		let { data: posts } = await api.posts();

		expect(posts).to.be.instanceof(Array);

    });

    it('cache saved', async function() {

		let id = 1;
		let cacheIndex = `${urlBase}/comments/${id}`;

		// Certify that cache entry is not there
		expect(api._cache[cacheIndex]).to.be.undefined;

		// First fetch
		let { data: comment } = await api.comments(id);

		// Get cache entry
		let cacheEntry = api._cache[cacheIndex];

		// Cache entry is there ...
		expect(cacheEntry).not.to.be.undefined;
		// and has the same id
		expect(cacheEntry).to.have.deep.property('res.data.id', id);

		// Second fetch
		let { data: commentNew } = await api.comments(id);

		// Is the same object, no fetch processed
		expect(commentNew).to.equal(cacheEntry.res.data);

    });
});<|endoftext|>


<commit_before><commit_msg>fixed issue with already stored dbs<commit_after><|endoftext|>


<commit_before>/* Javascript for PollBlock. */
function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

function CheckPollUtil (runtime, element, pollType) {
    var self = this;

    this.init = function() {
        // Initialization function used for both Poll Types
        this.voteUrl = runtime.handlerUrl(element, 'vote');
        this.tallyURL = runtime.handlerUrl(element, 'get_results');
        this.downloadUrl = runtime.handlerUrl(element, 'download_results');
        console.log(this.downloadUrl);

        this.submit = $('input[type=button]', element);

        this.answers = $('input[type=radio]', element);

        this.checkAnswers = $('input[type=checkbox]', element); //get the array of checkboxes
        this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());  //modify handlebar!!

        this.viewResultsButton = $('.view-results-button', element);
        this.viewResultsButton.click(this.getResults);

        // If the submit button doesn't exist, the user has alread
        // selected a choice. Render results instead of initializing machinery.
        var max_submissions = parseInt($('.poll-max-submissions', element).text());
        var current_count = parseInt($('.poll-current-count', element).text());

        if (max_submissions == current_count) {
            self.onSubmit({'success': true});
            return false;

        }
        if (pollType != "checkpoll") {
            if (! self.submit.length) {
                self.onSubmit({'success': true});
                return false;
            }
        }

        if (max_submissions > 1 && current_count > 0) {
            $('.poll-submissions-count', element).show();
        }
        return true;
    };

    this.checkPollInit = function(){
        // Initialization function for Survey Blocks

        // If the user is unable to vote, disable input.
        if (! $('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
            return
        }

        self.checkAnswers.bind("change.enableSubmit", self.verifyAll);

        self.submit.click(function () {
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify({choices: self.checkPollChoices(), username: self.getUserName()}),
                success: self.onSubmit
            })
        });
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        self.verifyAll();
    };

    this.getUserName = function () {
        return "noname";
        var edxInfo = getCookie("edx-user-info").replace(/\\054/g, ',').replace(/\\"/g, '"');
        if (edxInfo.charAt(0) == '"')
            edxInfo = edxInfo.substring(1);
        if (edxInfo.charAt(edxInfo.length-1) == '"')
            edxInfo = edxInfo.substring(0,edxInfo.length-1);
        var username = JSON.parse(edxInfo).username;
        return username;
    }

    this.checkPollChoices = function () {
        var choices = [];
        self.checkAnswers.each(function(index, el) {
            if (el.checked) {
                el = $(el);
                choices.push(el.val()); //maybe element is needed
            }
        });
        return choices;
    };

    this.pollInit = function(){
        // Initialization function for PollBlocks.
        var selector = 'input[name=choice]:checked';
        var radio = $(selector, element);
        self.submit.click(function () {
            // We can't just use radio.selector here because the selector
            // is mangled if this is the first time this XBlock is added in
            // studio.
            radio = $(selector, element);
            var choice = radio.val();
            var thanks = $('.poll-voting-thanks', element);
            thanks.addClass('poll-hidden');
            // JQuery's fade functions set element-level styles. Clear these.
            thanks.removeAttr('style');
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify({"choice": choice,  username: self.getUserName()}),
                success: self.onSubmit
            });
        });
        // If the user has already reached their maximum submissions, all inputs should be disabled.
        if (!$('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
        }
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        var answers = $('input[type=radio]', element);
        if (! radio.val()) {
            answers.bind("change.enableSubmit", self.enableSubmit);
        } else if ($('div.poll-block', element).data('can-vote')) {
            self.enableSubmit();
        }
    };

    this.surveyInit = function () {
        // Initialization function for Survey Blocks

        // If the user is unable to vote, disable input.
        if (! $('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
            return
        }
        self.answers.bind("change.enableSubmit", self.verifyAll);
        self.submit.click(function () {
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify(self.surveyChoices()),
                success: self.onSubmit
            })
        });
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        self.verifyAll();
    };

    this.surveyChoices = function () {
        // Grabs all selections for survey answers, and returns a mapping for them.
        var choices = {};
        self.answers.each(function(index, el) {
            el = $(el);
            choices[el.prop('name')] = $(self.checkedElement(el), element).val();
        });
        return choices;
    };

    this.checkedElement = function (el) {
        // Given the DOM element of a radio, get the selector for the checked element
        // with the same name.
        return "input[name='" + el.prop('name') + "']:checked"
    };

    this.verifyAll = function () {
        // Verify that all questions have an answer selected.
        var doEnable = true;

        if (pollType == "checkpoll") {
            var temp = [];
            //seems that this function should be the same
            self.checkAnswers.each(function (index, el) {
                if (el.checked) {
                    el = $(el);
                    temp.push(el.val()); //maybe element is needed
                }

            });

            if (! temp.length) {
                doEnable = false;
                return false
            }
        } else {
            self.answers.each(function (index, el) {
                if (!$(self.checkedElement($(el)), element).length) {
                    doEnable = false;
                    return false
                }
            });
        }

        if (doEnable){
            self.enableSubmit();
        }
    };

    this.onSubmit = function (data) {
        // Fetch the results from the server and render them.
        if (!data['success']) {
            alert(data['errors'].join('\n'));
        }
        var can_vote = data['can_vote'];
        $('.poll-current-count', element).text(data['submissions_count']);
        if (data['max_submissions'] > 1) {
            $('.poll-submissions-count', element).show();
        }
        if ($('div.poll-block', element).data('private')) {
            // User may be changing their vote. Give visual feedback that it was accepted.
            var thanks = $('.poll-voting-thanks', element);
            thanks.removeClass('poll-hidden');
            thanks.fadeOut(0).fadeIn('slow', 'swing');
            $('.poll-feedback-container', element).removeClass('poll-hidden');
            if (can_vote) {
                $('input[name="poll-submit"]', element).val('Ответить заново');
            } else {
                $('input', element).attr('disabled', true)
            }
            return;
        }
        // Used if results are not private, to show the user how other students voted.
        self.getResults();
    };

    this.getResults = function () {
        // Used if results are not private, to show the user how other students voted.
        $.ajax({
            // Semantically, this would be better as GET, but we can use helper
            // functions with POST.
            type: "POST",
            url: self.tallyURL,
            data: JSON.stringify({}),
            success: function (data) {
                $('div.poll-block', element).html(self.resultsTemplate(data));
                this.downloadResultsButton = $('.download-results-button', element);
                //this.downloadResultsButton.click(this.downloadResults);
                //this.downloadResultsButton.on( "click", this.downloadResults);
                $('div.poll-block', element).on('click', 'a.download-results-button', function() {
                    self.downloadResults();
                });

            }
        })
    };

    this.downloadFile = function(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    this.checkPollToCsv = function (data) {
        var csvFile = "информация о курсе,вопрос,имя пользователя,id пользователя";
        var shortAnswers = [];
        for (var i = 0; i < data['answers'].length; i++) {
            csvFile += "," + data['answers'][i][1]['label'];
            shortAnswers.push(data['answers'][i][0]);
        }
        csvFile += "\n";

        var course_info = data['course_info'];
        var question = data['question'];
        var question = data['question'];

        for (var i = 0; i < data['detailed_tally'].length; i++) {
            csvFile += course_info + "," + question + ",";
            csvFile += data['detailed_tally'][i]['username'] + "," + data['detailed_tally'][i]['user_id'];

            if (pollType == "checkpoll") {
                for (var j = 0; j < shortAnswers.length; j++) {
                    if (data['detailed_tally'][i]['choices'].indexOf(shortAnswers[j]) != -1) {
                        csvFile += ",+";
                    } else {
                        csvFile += ",-";
                    }
                }
            } else if (pollType == "poll"){
                for (var j = 0; j < shortAnswers.length; j++) {
                    if (data['detailed_tally'][i]['choice'] == shortAnswers[j]) {
                        csvFile += ",+";
                    } else {
                        csvFile += ",-";
                    }
                }
            }
            csvFile += "\n";
        }
        return csvFile;
    }

    this.downloadResults = function () {
        // Used if results are not private, to show the user how other students voted.
        $.ajax({
            // Semantically, this would be better as GET, but we can use helper
            // functions with POST.
            type: "POST",
            url: self.downloadUrl,
            data: JSON.stringify({}),
            success: function (data) {
                self.downloadFile(data['course_info']+'.csv', self.checkPollToCsv(data));
            }
        })
    };

    this.enableSubmit = function () {
        // Enable the submit button.
        self.submit.removeAttr("disabled");
        self.checkAnswers.unbind("change.enableSubmit");  //HERE IS THE BUGGG
        self.answers.unbind("change.enableSubmit");
    };

    var run_init = this.init();
    if (run_init) {
        var init_map = {'poll': self.pollInit, 'survey': self.surveyInit, 'checkpoll': self.checkPollInit};
        init_map[pollType]()
    }

}

function CheckPollBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'checkpoll');
}

function PollBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'poll');
}

function SurveyBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'survey');
}
<commit_msg>Remove cookie usage<commit_after>/* Javascript for PollBlock. */
function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

function CheckPollUtil (runtime, element, pollType) {
    var self = this;

    this.init = function() {
        // Initialization function used for both Poll Types
        this.voteUrl = runtime.handlerUrl(element, 'vote');
        this.tallyURL = runtime.handlerUrl(element, 'get_results');
        this.downloadUrl = runtime.handlerUrl(element, 'download_results');
        console.log(this.downloadUrl);

        this.submit = $('input[type=button]', element);

        this.answers = $('input[type=radio]', element);

        this.checkAnswers = $('input[type=checkbox]', element); //get the array of checkboxes
        this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());  //modify handlebar!!

        this.viewResultsButton = $('.view-results-button', element);
        this.viewResultsButton.click(this.getResults);

        // If the submit button doesn't exist, the user has alread
        // selected a choice. Render results instead of initializing machinery.
        var max_submissions = parseInt($('.poll-max-submissions', element).text());
        var current_count = parseInt($('.poll-current-count', element).text());

        if (max_submissions == current_count) {
            self.onSubmit({'success': true});
            return false;

        }
        if (pollType != "checkpoll") {
            if (! self.submit.length) {
                self.onSubmit({'success': true});
                return false;
            }
        }

        if (max_submissions > 1 && current_count > 0) {
            $('.poll-submissions-count', element).show();
        }
        return true;
    };

    this.checkPollInit = function(){
        // Initialization function for Survey Blocks

        // If the user is unable to vote, disable input.
        if (! $('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
            return
        }

        self.checkAnswers.bind("change.enableSubmit", self.verifyAll);
        self.checkAnswers.bind("change", self.verifyAll);

        self.submit.click(function () {
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify({choices: self.checkPollChoices(), username: self.getUserName()}),
                success: self.onSubmit
            })
        });
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        self.verifyAll();
    };

    this.getUserName = function () {
        return "noname";
        var edxInfo = getCookie("edx-user-info").replace(/\\054/g, ',').replace(/\\"/g, '"');
        if (edxInfo.charAt(0) == '"')
            edxInfo = edxInfo.substring(1);
        if (edxInfo.charAt(edxInfo.length-1) == '"')
            edxInfo = edxInfo.substring(0,edxInfo.length-1);
        var username = JSON.parse(edxInfo).username;
        return username;
    }

    this.checkPollChoices = function () {
        var choices = [];
        self.checkAnswers.each(function(index, el) {
            if (el.checked) {
                el = $(el);
                choices.push(el.val()); //maybe element is needed
            }
        });
        return choices;
    };

    this.pollInit = function(){
        // Initialization function for PollBlocks.
        var selector = 'input[name=choice]:checked';
        var radio = $(selector, element);
        self.submit.click(function () {
            // We can't just use radio.selector here because the selector
            // is mangled if this is the first time this XBlock is added in
            // studio.
            radio = $(selector, element);
            var choice = radio.val();
            var thanks = $('.poll-voting-thanks', element);
            thanks.addClass('poll-hidden');
            // JQuery's fade functions set element-level styles. Clear these.
            thanks.removeAttr('style');
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify({"choice": choice,  username: self.getUserName()}),
                success: self.onSubmit
            });
        });
        // If the user has already reached their maximum submissions, all inputs should be disabled.
        if (!$('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
        }
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        var answers = $('input[type=radio]', element);
        if (! radio.val()) {
            answers.bind("change.enableSubmit", self.enableSubmit);
        } else if ($('div.poll-block', element).data('can-vote')) {
            self.enableSubmit();
        }
    };

    this.surveyInit = function () {
        // Initialization function for Survey Blocks

        // If the user is unable to vote, disable input.
        if (! $('div.poll-block', element).data('can-vote')) {
            $('input', element).attr('disabled', true);
            return
        }
        self.answers.bind("change.enableSubmit", self.verifyAll);
        self.submit.click(function () {
            $.ajax({
                type: "POST",
                url: self.voteUrl,
                data: JSON.stringify(self.surveyChoices()),
                success: self.onSubmit
            })
        });
        // If the user has refreshed the page, they may still have an answer
        // selected and the submit button should be enabled.
        self.verifyAll();
    };

    this.surveyChoices = function () {
        // Grabs all selections for survey answers, and returns a mapping for them.
        var choices = {};
        self.answers.each(function(index, el) {
            el = $(el);
            choices[el.prop('name')] = $(self.checkedElement(el), element).val();
        });
        return choices;
    };

    this.checkedElement = function (el) {
        // Given the DOM element of a radio, get the selector for the checked element
        // with the same name.
        return "input[name='" + el.prop('name') + "']:checked"
    };

    this.verifyAll = function () {
        console.log("Verify_all");
        // Verify that all questions have an answer selected.
        var doEnable = true;

        if (pollType == "checkpoll") {
            var temp = [];
            //seems that this function should be the same
            self.checkAnswers.each(function (index, el) {
                if (el.checked) {
                    el = $(el);
                    temp.push(el.val()); //maybe element is needed
                }

            });

            if (! temp.length) {
                doEnable = false;
                return false
            }
        } else {
            self.answers.each(function (index, el) {
                if (!$(self.checkedElement($(el)), element).length) {
                    doEnable = false;
                    return false
                }
            });
        }

        if (doEnable){
            self.enableSubmit();
        }
    };

    this.onSubmit = function (data) {
        // Fetch the results from the server and render them.
        if (!data['success']) {
            alert(data['errors'].join('\n'));
        }
        var can_vote = data['can_vote'];
        $('.poll-current-count', element).text(data['submissions_count']);
        if (data['max_submissions'] > 1) {
            $('.poll-submissions-count', element).show();
        }
        if ($('div.poll-block', element).data('private')) {
            // User may be changing their vote. Give visual feedback that it was accepted.
            var thanks = $('.poll-voting-thanks', element);
            thanks.removeClass('poll-hidden');
            thanks.fadeOut(0).fadeIn('slow', 'swing');
            $('.poll-feedback-container', element).removeClass('poll-hidden');
            if (can_vote) {
                $('input[name="poll-submit"]', element).val('Ответить заново');
            } else {
                $('input', element).attr('disabled', true)
            }
            return;
        }
        // Used if results are not private, to show the user how other students voted.
        self.getResults();
    };

    this.getResults = function () {
        // Used if results are not private, to show the user how other students voted.
        $.ajax({
            // Semantically, this would be better as GET, but we can use helper
            // functions with POST.
            type: "POST",
            url: self.tallyURL,
            data: JSON.stringify({}),
            success: function (data) {
                $('div.poll-block', element).html(self.resultsTemplate(data));
                this.downloadResultsButton = $('.download-results-button', element);
                //this.downloadResultsButton.click(this.downloadResults);
                //this.downloadResultsButton.on( "click", this.downloadResults);
                $('div.poll-block', element).on('click', 'a.download-results-button', function() {
                    self.downloadResults();
                });

            }
        })
    };

    this.downloadFile = function(filename, text) {
        var element = document.createElement('a');
        element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        element.setAttribute('download', filename);

        element.style.display = 'none';
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    this.checkPollToCsv = function (data) {
        var csvFile = "информация о курсе,вопрос,имя пользователя,id пользователя";
        var shortAnswers = [];
        for (var i = 0; i < data['answers'].length; i++) {
            csvFile += "," + data['answers'][i][1]['label'];
            shortAnswers.push(data['answers'][i][0]);
        }
        csvFile += "\n";

        var course_info = data['course_info'];
        var question = data['question'];
        var question = data['question'];

        for (var i = 0; i < data['detailed_tally'].length; i++) {
            csvFile += course_info + "," + question + ",";
            csvFile += data['detailed_tally'][i]['username'] + "," + data['detailed_tally'][i]['user_id'];

            if (pollType == "checkpoll") {
                for (var j = 0; j < shortAnswers.length; j++) {
                    if (data['detailed_tally'][i]['choices'].indexOf(shortAnswers[j]) != -1) {
                        csvFile += ",+";
                    } else {
                        csvFile += ",-";
                    }
                }
            } else if (pollType == "poll"){
                for (var j = 0; j < shortAnswers.length; j++) {
                    if (data['detailed_tally'][i]['choice'] == shortAnswers[j]) {
                        csvFile += ",+";
                    } else {
                        csvFile += ",-";
                    }
                }
            }
            csvFile += "\n";
        }
        return csvFile;
    }

    this.downloadResults = function () {
        // Used if results are not private, to show the user how other students voted.
        $.ajax({
            // Semantically, this would be better as GET, but we can use helper
            // functions with POST.
            type: "POST",
            url: self.downloadUrl,
            data: JSON.stringify({}),
            success: function (data) {
                self.downloadFile(data['course_info']+'.csv', self.checkPollToCsv(data));
            }
        })
    };

    this.enableSubmit = function () {
        // Enable the submit button.
        self.submit.removeAttr("disabled");
        self.checkAnswers.unbind("change.enableSubmit");  //HERE IS THE BUGGG
        self.answers.unbind("change.enableSubmit");
    };

    this.disableSubmit = function () {
        // Enable the submit button.
        self.submit.prop("disabled", true);
        self.checkAnswers.bind("change.enableSubmit");  //HERE IS THE BUGGG
        self.answers.bind("change.enableSubmit");
    };

    var run_init = this.init();
    if (run_init) {
        var init_map = {'poll': self.pollInit, 'survey': self.surveyInit, 'checkpoll': self.checkPollInit};
        init_map[pollType]()
    }

}

function CheckPollBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'checkpoll');
}

function PollBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'poll');
}

function SurveyBlock(runtime, element) {
    new CheckPollUtil(runtime, element, 'survey');
}
<|endoftext|>


<commit_before>/* eslint-disable prefer-arrow-callback */
import {expect} from 'chai';
import fs from 'fs';
import {exec} from 'child_process';

describe('Smoke test for the executable script', function() {
  beforeEach(() => {
    fs.writeFileSync(
      'test/test-data.js',
      'var foo = 10;\n' +
      '[1, 2, 3].map(function(x) { return x*x });'
    );
  });

  afterEach(() => {
    fs.unlinkSync('test/test-data.js');
    if (fs.existsSync('test/output.js')) {
      fs.unlinkSync('test/output.js');
    }
  });

  describe('when valid input and output file given', function() {
    it('transforms input file to output file', done => {
      exec('node ./bin/index.js -t let,arrow test/test-data.js -o test/output.js', (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('');
        expect(stdout).to.equal('');

        expect(fs.readFileSync('test/output.js').toString()).to.equal(
          'const foo = 10;\n' +
          '[1, 2, 3].map(x => { return x*x });'
        );
        done();
      });
    });
  });

  describe('when no input/output files given', () => {
    it('reads STDIN and writes STDOUT', done => {
      exec('node ./bin/index.js -t let,arrow < test/test-data.js > test/output.js', (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('');
        expect(stdout).to.equal('');

        expect(fs.readFileSync('test/output.js').toString()).to.equal(
          'const foo = 10;\n' +
          '[1, 2, 3].map(x => { return x*x });'
        );
        done();
      });
    });
  });

  describe('when invalid transform name given', () => {
    it('exits with error message', done => {
      exec('node ./bin/index.js --transform blah test/test-data.js', (error, stdout, stderr) => {
        expect(error).not.to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('Unknown transform "blah".\n');
        expect(stdout).to.equal('');

        expect(fs.existsSync('test/output.js')).to.equal(false);
        done();
      });
    });
  });

  describe('when transform generates warnings', () => {
    beforeEach(() => {
      fs.writeFileSync(
        'test/test-data-warnings.js',
        'if (true) { var x = 10; }\n x = 12;\n'
      );
    });

    afterEach(() => {
      fs.unlinkSync('test/test-data-warnings.js');
    });

    it('logs warnings to STDERR', done => {
      exec('node ./bin/index.js --transform let test/test-data-warnings.js', (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('test/test-data-warnings.js:\n1:  warning  Unable to transform var  (let)\n');
        expect(stdout).to.equal('if (true) { var x = 10; }\n x = 12;\n');
        done();
      });
    });
  });
});
<commit_msg>Use named constants for system test file names<commit_after>/* eslint-disable prefer-arrow-callback */
import {expect} from 'chai';
import fs from 'fs';
import {exec} from 'child_process';

const INPUT_FILE = 'test/test-data.js';
const INPUT_WARNINGS_FILE = 'test/test-data-warnings.js';
const OUTPUT_FILE = 'test/output.js';

describe('Smoke test for the executable script', function() {
  beforeEach(() => {
    fs.writeFileSync(
      INPUT_FILE,
      'var foo = 10;\n' +
      '[1, 2, 3].map(function(x) { return x*x });'
    );
  });

  afterEach(() => {
    fs.unlinkSync(INPUT_FILE);
    if (fs.existsSync(OUTPUT_FILE)) {
      fs.unlinkSync(OUTPUT_FILE);
    }
  });

  describe('when valid input and output file given', function() {
    it('transforms input file to output file', done => {
      exec(`node ./bin/index.js -t let,arrow ${INPUT_FILE} -o ${OUTPUT_FILE}`, (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('');
        expect(stdout).to.equal('');

        expect(fs.readFileSync(OUTPUT_FILE).toString()).to.equal(
          'const foo = 10;\n' +
          '[1, 2, 3].map(x => { return x*x });'
        );
        done();
      });
    });
  });

  describe('when no input/output files given', () => {
    it('reads STDIN and writes STDOUT', done => {
      exec(`node ./bin/index.js -t let,arrow < ${INPUT_FILE} > ${OUTPUT_FILE}`, (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('');
        expect(stdout).to.equal('');

        expect(fs.readFileSync(OUTPUT_FILE).toString()).to.equal(
          'const foo = 10;\n' +
          '[1, 2, 3].map(x => { return x*x });'
        );
        done();
      });
    });
  });

  describe('when invalid transform name given', () => {
    it('exits with error message', done => {
      exec(`node ./bin/index.js --transform blah ${INPUT_FILE}`, (error, stdout, stderr) => {
        expect(error).not.to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal('Unknown transform "blah".\n');
        expect(stdout).to.equal('');

        expect(fs.existsSync(OUTPUT_FILE)).to.equal(false);
        done();
      });
    });
  });

  describe('when transform generates warnings', () => {
    beforeEach(() => {
      fs.writeFileSync(
        INPUT_WARNINGS_FILE,
        'if (true) { var x = 10; }\n x = 12;\n'
      );
    });

    afterEach(() => {
      fs.unlinkSync(INPUT_WARNINGS_FILE);
    });

    it('logs warnings to STDERR', done => {
      exec(`node ./bin/index.js --transform let ${INPUT_WARNINGS_FILE}`, (error, stdout, stderr) => {
        expect(error).to.equal(null); // eslint-disable-line no-null/no-null
        expect(stderr).to.equal(`${INPUT_WARNINGS_FILE}:\n1:  warning  Unable to transform var  (let)\n`);
        expect(stdout).to.equal('if (true) { var x = 10; }\n x = 12;\n');
        done();
      });
    });
  });
});
<|endoftext|>