User:Writ Keeper/common.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | The accompanying .css page for this skin is at User:Writ Keeper/common.css. |
//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);
}