Jump to content

User:Writ Keeper/common.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Writ Keeper (talk | contribs) at 07:54, 4 February 2025 (fx). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//only do anything if on the freecell homepage
if (mw.config.get("wgPageName") == "User:Writ_Keeper/freecell")
{
	var pastGameStates = [];
	var cardImageMap = {
		C2:"/media/wikipedia/commons/6/69/2C.svg",
		C3:"/media/wikipedia/commons/7/70/3C.svg",
		C4:"/media/wikipedia/commons/2/25/4C.svg",
		C5:"/media/wikipedia/commons/6/6d/5C.svg",
		C6:"/media/wikipedia/commons/6/62/6C.svg",
		C7:"/media/wikipedia/commons/6/68/7C.svg",
		C8:"/media/wikipedia/commons/9/94/8C.svg",
		C9:"/media/wikipedia/commons/6/63/9C.svg",
		C10:"/media/wikipedia/commons/c/c7/10C.svg",
		C11:"/media/wikipedia/commons/1/11/JC.svg",
		C12:"/media/wikipedia/commons/9/9e/QC.svg",
		C13:"/media/wikipedia/commons/e/e1/KC.svg",
		C1:"/media/wikipedia/commons/e/eb/AC.svg",
		D2:"/media/wikipedia/commons/f/fb/2D.svg",
		D3:"/media/wikipedia/commons/5/57/3D.svg",
		D4:"/media/wikipedia/commons/c/c7/4D.svg",
		D5:"/media/wikipedia/commons/d/d9/5D.svg",
		D6:"/media/wikipedia/commons/c/cf/6D.svg",
		D7:"/media/wikipedia/commons/5/5a/7D.svg",
		D8:"/media/wikipedia/commons/8/80/8D.svg",
		D9:"/media/wikipedia/commons/0/09/9D.svg",
		D10:"/media/wikipedia/commons/8/83/10D.svg",
		D11:"/media/wikipedia/commons/3/33/JD.svg",
		D12:"/media/wikipedia/commons/6/63/QD.svg",
		D13:"/media/wikipedia/commons/0/06/KD.svg",
		D1:"/media/wikipedia/commons/6/6d/AD.svg",
		H2:"/media/wikipedia/commons/9/9d/2H.svg",
		H3:"/media/wikipedia/commons/3/3a/3H.svg",
		H4:"/media/wikipedia/commons/6/6f/4H.svg",
		H5:"/media/wikipedia/commons/0/03/5H.svg",
		H6:"/media/wikipedia/commons/a/a2/6H.svg",
		H7:"/media/wikipedia/commons/e/e1/7H.svg",
		H8:"/media/wikipedia/commons/3/34/8H.svg",
		H9:"/media/wikipedia/commons/e/e0/9H.svg",
		H10:"/media/wikipedia/commons/8/8a/10H.svg",
		H11:"/media/wikipedia/commons/1/15/JH.svg",
		H12:"/media/wikipedia/commons/d/d2/QH.svg",
		H13:"/media/wikipedia/commons/e/e5/KH.svg",
		H1:"/media/wikipedia/commons/8/87/AH.svg",
		S2:"/media/wikipedia/commons/d/de/2S.svg",
		S3:"/media/wikipedia/commons/c/ce/3S.svg",
		S4:"/media/wikipedia/commons/c/cc/4S.svg",
		S5:"/media/wikipedia/commons/a/ac/5S.svg",
		S6:"/media/wikipedia/commons/1/1f/6S.svg",
		S7:"/media/wikipedia/commons/a/a1/7S.svg",
		S8:"/media/wikipedia/commons/3/36/8S.svg",
		S9:"/media/wikipedia/commons/3/31/9S.svg",
		S10:"/media/wikipedia/commons/1/16/10S.svg",
		S11:"/media/wikipedia/commons/d/d9/JS.svg",
		S12:"/media/wikipedia/commons/3/35/QS.svg",
		S13:"/media/wikipedia/commons/5/5c/KS.svg",
		S1:"/media/wikipedia/commons/2/27/AS.svg"
	}

	function deal() 
	{
		pastGameStates = [];
		$(".cardHolder").empty();
		let rng = window.crypto || window.msCrypto;
		let deckSize = 52;
		let rngArray = new Uint16Array(deckSize);
		let rejectArray = new Uint16Array(1);
		let maxInt = 65536;
		let deck = new Array(deckSize);
		for(let i = 0; i < deck.length; i++)
		{
			deck[i] = i;
		}
		rng.getRandomValues(rngArray);
		for(let i=(deck.length - 1); i >= 1; i--)
		{
			//discard values greater than the maximum multiple of i+1 below maxInt; these values would skew the distribution
			while(rngArray[i] >= Math.floor(maxInt/(i+1)) * (i+1))
			{
				rng.getRandomValues(rejectArray);
				rngArray[i] = rejectArray[0];
			}
			let tempStorage = deck[i];
			let randomIndex = rngArray[i]%(i+1);
			deck[i] = deck[randomIndex];
			deck[randomIndex] = tempStorage;
			deckSize--;
		}
		let cardIndex = 0;
		
		$("div.cardHolder").droppable();
		//now deck is shuffled, deal
		for(let i = 1; i <= 4; i++)
		{
			for(let j = 0; j < 7; j++)
			{
				addCardToColumn(deck[cardIndex], i);
				cardIndex++;
			}
		}
		for(let i = 5; i <= 8; i++)
		{
			for(let j = 0; j < 6; j++)
			{
				addCardToColumn(deck[cardIndex], i);
				cardIndex++;
			}
		}
		for(let columnIndex = 1; columnIndex <= 8; columnIndex++)
		{
			refreshDraggability(columnIndex);
		}
	}
	function recordGameState()
	{
		let gameState = {};
		gameState.freecells = [];
		gameState.homecells = [];
		gameState.columns = [[],[],[],[],[],[],[],[]];
		
		$("#freecellContainer [id^='freecell']").each(function(ind, el){
			let cardData = $(el).children("div.card").data("card");
			if(typeof cardData == "undefined")
				gameState.freecells.push(-1);
			else gameState.freecells.push(cardData);
		});
		$("#freecellContainer [id^='homecell']").each(function(ind, el){
			let cardData = $(el).children("div.card").data("card");
			if(typeof cardData == "undefined")
				gameState.freecells.push(-1);
			else gameState.freecells.push(cardData);
		});
		$("#board [id^='fcColumn']").each(function(ind, el) {
			$(el).find("div.card").each(function(ind2, card) {
				gameState.columns[ind].push($(card).data("card"));
			});
		});
		return gameState;
	}
	function createCardData(card)
	{
		let cardSuit = (Math.floor(card/13)%4);
		switch(cardSuit)
		{
			case 0:
				cardSuit = "C";
				break;
			case 1:
				cardSuit = "D";
				break;
			case 2: 
				cardSuit = "H";
				break;
			case 3:
				cardSuit = "S";
				break;
			default:
				cardSuit = null;
		}
		let cardRank = card%13 + 1
		return {"card":card, "suit":cardSuit, "rank":cardRank};
	}
	function undo()
	{
		gameState = pastGameStates.pop();
		if(gameState != null)
		{
			$(".cardHolder").empty();
			for(let i = 0; i < 4; i++)
			{
				if(gameState.homecells[i] != -1)
				{
					let homeCardData = createCardData(gameState.homecells[i]);
					let newCard = document.createElement("div");
					let newCardImg = document.createElement("img");
					$(newCard).addClass("card");
					$(newCard).data(homeCardData);
					$(newCardImg).attr("src", cardImageMap[""+homeCardData.suit+homeCardData.rank]);
					$(newCardImg).addClass("cardImg");
					$("#homecell" + (i+1)).append(newCard);
					$(newCard).addClass("headCard");
					$(newCard).droppable()
					$(newCard).draggable({revert:attemptCardDrop, greedy:true, disabled:true,revertDuration:0, zIndex:500, stop:cleanCard});
					$(newCard).append(newCardImg);
				}
				
				if(gameState.freecells[i] != -1)
				{
					let freeCardData = createCardData(gameState.freecells[i]);
					let newCard = document.createElement("div");
					let newCardImg = document.createElement("img");
					$(newCard).addClass("card");
					$(newCard).data(freeCardData);
					$(newCardImg).attr("src", cardImageMap[""+freeCardData.suit+freeCardData.rank]);
					$(newCardImg).addClass("cardImg");
					$("#freecell" + (i+1)).append(newCard);
					$(newCard).addClass("headCard");
					$(newCard).droppable({"disabled":true});
					$(newCard).draggable({revert:attemptCardDrop, greedy:true,revertDuration:0, zIndex:500, stop:cleanCard});
					$(newCard).append(newCardImg);
				}
			}
			for(let column = 0; column < 8; column++)
			{
				let columnArray = gameState.columns[column];
				for(let i = 0; i < columnArray.length; i++)
				{
					addCardToColumn(columnArray[i], column+1);
				}
			}
			for(let columnIndex = 1; columnIndex <= 8; columnIndex++)
			{
				refreshDraggability(columnIndex);
			}
		}
	}
	function autoplay()
	{
		setTimeout(function()
		{
			let homeCells = {"C":[0,0],"D":[0,0],"H":[0,0],"S":[0,0]};
			let firstFree = 0;
			for(let i = 1; i <= 4; i++)
			{
				if($("#homecell"+ i + " div.card").length > 0)
				{
					let homeData = $("#homecell"+i + " div.card").data();
					homeCells[homeData.suit] = [homeData.rank, i];
				}
				else if(firstFree == 0)
				{
					firstFree = i;
				}
			}
			let hasMoved = false;
			let columnIndex = 1;
			while(columnIndex <= 8 && !hasMoved)
			{
				let targetCard = $("#fcColumn" + columnIndex + " div.card").last();
				if(targetCard != null)
				{
					let targetCardData = targetCard.data();
					let targetOppositeColor = oppositeColorSuits(targetCardData.suit);
					if(homeCells[targetCardData.suit][0] == targetCardData.rank - 1 
					&& homeCells[sameColorSuit(targetCardData.suit)][0] >= targetCardData.rank - 3 
					&& homeCells[targetOppositeColor[0]][0] >= targetCardData.rank - 2
					&& homeCells[targetOppositeColor[1]][0] >= targetCardData.rank - 2)
					{
						let homeContainer = null;
						if(homeCells[targetCardData.suit][1] > 0)
						{
							homeContainer = $("#homecell" + homeCells[targetCardData.suit][1]);
						}
						else
						{
							homeContainer = $("#homecell" + firstFree);
						}
						hasMoved = true;
						targetCard.detach;
						homeContainer.append(targetCard);
						targetCard.removeClass("tailCard");
						targetCard.addClass("headCard");
						targetCard.draggable("option","disabled",true);
						refreshDraggability(columnIndex);
					}
				}
				columnIndex++;
			}
			if(hasMoved)
			{
				autoplay();
			}
		}, 400);
	}
	function sameColorSuit(suit)
	{
		switch(suit)
		{
			case "C":
				return "S";
			case "D":
				return "H";
			case "H":
				return "D";
			case "S":
				return "C";
			default: return null;
		}
	}
	function oppositeColorSuits(suit)
	{
		switch(suit)
		{
			case "C":
			case "S":
				return ["D","H"];
			case "D":
			case "H":
				return ["S","C"];
			default: return null;
		}
	}
	function refreshDraggability(column)
	{
		if(column == null)
		{
			return;
		}
		let cards = $("#fcColumn" + column + " div.card");
		if(cards.length == 0)
		{
			$("#fcColumn" + column).droppable("option","disabled",false);
			return;
		}
		else 
		{
			$("#fcColumn" + column).droppable("option","disabled",true);
			cards.droppable("option","disabled",true);
			$(cards[cards.length-1]).draggable("option","disabled",false);
			$(cards[cards.length-1]).droppable("option","disabled",false);
			let index = cards.length - 2;
			let allPrevInOrder = true;
			while(index >= 0 && allPrevInOrder) 
			{
				let lowerCard = $(cards[index+1]).data();
				let upperCard = $(cards[index]).data();
				if(allPrevInOrder && lowerCard.rank == upperCard.rank - 1 && isAlternatingSuit(lowerCard.suit, upperCard.suit))
				{
					$(cards[index]).draggable("option","disabled",false);
				}
				else
				{
					allPrevInOrder = false;
				}
				index--;
			}
		}
		
	}
	function isAlternatingSuit(lowerSuit, upperSuit)
	{
		if(upperSuit == "D" || upperSuit == "H")
		{
			return (lowerSuit == "C" || lowerSuit == "S");
		}
		return (lowerSuit == "D" || lowerSuit == "H");
	}
	function addCardToColumn(card, column)
	{
		let cardData = createCardData(card);
		let newCard = document.createElement("div");
		let newCardImg = document.createElement("img");
		$(newCard).addClass("card");
		$(newCard).data(cardData);
		$(newCardImg).attr("src", cardImageMap[""+cardData.suit+cardData.rank]);
		$(newCardImg).addClass("cardImg");
		if($("#fcColumn" + column).children().length == 0)
		{
			$("#fcColumn" + column).append(newCard);
			$(newCard).addClass("headCard");
		}
		else 
		{
			$("#fcColumn" + column).find("div").droppable("option","disabled",true);
			$("#fcColumn" + column).find("div").last().append(newCard);
			$(newCard).addClass("tailCard");
		}
		$(newCard).droppable()
		$(newCard).draggable({revert:attemptCardDrop, greedy:true, disabled:true,revertDuration:0, zIndex:500, stop:cleanCard});
		$(newCard).append(newCardImg);
	}
	function cleanCard()
	{
		$(this).css("top","");
		$(this).css("left","");
	}
	function attemptCardDrop(target) 
	{
		if (target == null || !target)
		{
			return true;
		}
		let originalColumn = $(this).parents("div[id^='fcColumn']");
		if(originalColumn.length > 0)
		{
			originalColumn = originalColumn.attr("id").match(/[1-8]/)[0];
		}
		else 
		{
			originalColumn = null;
		}
		let numCards = $(this).find("div.card").length + 1;
		let targetParent = $(target).parents("div.cardHolder");
		let targetInfo = null;
		let emptyTarget = false;
		if(targetParent.length > 0)
		{
			targetInfo = targetParent.prop("id").match(/([A-Za-z]+)(\d)/);
			emptyTarget = false;
		}
		else 
		{
			targetInfo = $(target).prop("id").match(/([A-Za-z]+)(\d)/);
			emptyTarget = true;
		}
		let topCardInfo = $(this).data();
		let targetCardInfo = $(target).data();
		
		if(targetInfo[1] == "homecell")
		{
			if(numCards == 1)
			{
				if((emptyTarget && topCardInfo.rank == 1) ||
					(targetCardInfo.suit == topCardInfo.suit && targetCardInfo.rank == topCardInfo.rank - 1))
				{
					pastGameStates.push(recordGameState());
					$(this).detach;
					if(emptyTarget)
					{
						$(target).append($(this));
					}
					else 
					{
						targetParent.empty();
						targetParent.append($(this));
					}
					$(this).removeClass("tailCard");
					$(this).addClass("headCard");
					$(this).draggable("option","disabled",true);
					refreshDraggability(originalColumn);
					autoplay();
				}
			}
		}
		else if(targetInfo[1] == "freecell")
		{
			if(numCards == 1 && emptyTarget)
			{
				pastGameStates.push(recordGameState());
				$(this).detach;
				$(target).append($(this));
				$(this).removeClass("tailCard");
				$(this).addClass("headCard");
				refreshDraggability(originalColumn);
				autoplay();
			}
		}
		else
		{
			if(numCards <= calculateCardCount(emptyTarget))
			{
				if(emptyTarget || (isAlternatingSuit(topCardInfo.suit, targetCardInfo.suit) && topCardInfo.rank == targetCardInfo.rank-1))
				{
					pastGameStates.push(recordGameState());
					$(this).detach;
					$(target).append($(this));
					if(emptyTarget)
					{
						$(this).removeClass("tailCard");
						$(this).addClass("headCard");
					}
					else
					{
						$(target).droppable("option","disabled",true);
						$(this).addClass("tailCard");
						$(this).removeClass("headCard");
					}
					refreshDraggability(originalColumn);
					autoplay();
				}
			}
		}
		return true;
	}
	function calculateCardCount(emptyTarget)
	{
		let count = 1;
		count += (4 - $("#freecellContainer div[id^='freecell'] img").length);
		let emptyColumns = $("#board div.cardHolder:empty").length;
		if(emptyTarget)
		{
			emptyColumns--;
		}
		count = count * (2 ** emptyColumns);
		return count;
	}
	
	function init()
	{
		$(document).on("keydown", function(event) 
		{
			if(event.key == "F2")
			{
				deal();
				return false;
			}
			else if(event.key == "z" && event.ctrlKey)
			{
				undo();
				return false;
			}
			else return true;
		});
		deal();
	}
	$(document).ready(init);
}