add scoring
This commit is contained in:
parent
fd4279e96c
commit
f8eaaf20e3
@ -10,6 +10,10 @@
|
|||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<h1>Klondike Solitaire</h1>
|
<h1>Klondike Solitaire</h1>
|
||||||
|
<div class="scores">
|
||||||
|
<div class="score-display">Score: <span id="current-score">0</span></div>
|
||||||
|
<div class="score-display">High Score: <span id="high-score">0</span></div>
|
||||||
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<label class="draw-toggle">
|
<label class="draw-toggle">
|
||||||
Draw:
|
Draw:
|
||||||
|
|||||||
149
script.js
149
script.js
@ -16,6 +16,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const history = [];
|
const history = [];
|
||||||
|
|
||||||
let isAutoCompleting = false;
|
let isAutoCompleting = false;
|
||||||
|
let score = 0;
|
||||||
|
let highScore = 0;
|
||||||
|
|
||||||
const CARD_BACKS = [
|
const CARD_BACKS = [
|
||||||
{ id: 'waves', name: 'Blue Waves', className: 'card-back-waves' },
|
{ id: 'waves', name: 'Blue Waves', className: 'card-back-waves' },
|
||||||
@ -42,11 +44,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const cardBackOptionsContainer = document.getElementById('card-back-options');
|
const cardBackOptionsContainer = document.getElementById('card-back-options');
|
||||||
const checkMovesBtn = document.getElementById('check-moves-btn');
|
const checkMovesBtn = document.getElementById('check-moves-btn');
|
||||||
const autoCompleteBtn = document.getElementById('autocomplete-btn');
|
const autoCompleteBtn = document.getElementById('autocomplete-btn');
|
||||||
|
const scoreEl = document.getElementById('current-score');
|
||||||
|
const highScoreEl = document.getElementById('high-score');
|
||||||
|
|
||||||
|
|
||||||
// ====== SAVE / LOAD ======
|
// ====== SAVE / LOAD ======
|
||||||
function saveGame() {
|
function saveGame() {
|
||||||
const gameState = { stock, waste, foundations, tableau, history, drawCount: DRAW_COUNT };
|
// Also save score in the game state
|
||||||
|
const gameState = { stock, waste, foundations, tableau, history, drawCount: DRAW_COUNT, score };
|
||||||
localStorage.setItem('solitaire-save-game', JSON.stringify(gameState));
|
localStorage.setItem('solitaire-save-game', JSON.stringify(gameState));
|
||||||
}
|
}
|
||||||
function loadGame() {
|
function loadGame() {
|
||||||
@ -61,6 +66,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
history.length = 0;
|
history.length = 0;
|
||||||
history.push(...gameState.history);
|
history.push(...gameState.history);
|
||||||
DRAW_COUNT = gameState.drawCount || 3;
|
DRAW_COUNT = gameState.drawCount || 3;
|
||||||
|
score = gameState.score || 0; // Load score
|
||||||
drawSelect.value = DRAW_COUNT;
|
drawSelect.value = DRAW_COUNT;
|
||||||
Object.values(cardElements).forEach(el => el.remove());
|
Object.values(cardElements).forEach(el => el.remove());
|
||||||
cardElements = {};
|
cardElements = {};
|
||||||
@ -73,6 +79,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
winMessage.classList.add('hidden');
|
winMessage.classList.add('hidden');
|
||||||
|
updateScoreDisplay();
|
||||||
updateBoard(true);
|
updateBoard(true);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -83,10 +90,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ====== UNDO SNAPSHOTS ======
|
// ====== UNDO SNAPSHOTS ======
|
||||||
const snapshotState = () => JSON.stringify({ stock, waste, foundations, tableau });
|
const snapshotState = () => JSON.stringify({ stock, waste, foundations, tableau, score }); // Include score in snapshots
|
||||||
const restoreState = (json) => {
|
const restoreState = (json) => {
|
||||||
const s = JSON.parse(json);
|
const s = JSON.parse(json);
|
||||||
stock = s.stock; waste = s.waste; foundations = s.foundations; tableau = s.tableau;
|
stock = s.stock; waste = s.waste; foundations = s.foundations; tableau = s.tableau;
|
||||||
|
score = s.score !== undefined ? s.score : score; // Restore score
|
||||||
};
|
};
|
||||||
const pushHistory = () => {
|
const pushHistory = () => {
|
||||||
if (history.length > 50) history.shift();
|
if (history.length > 50) history.shift();
|
||||||
@ -95,7 +103,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// ====== SETUP ======
|
// ====== SETUP ======
|
||||||
function initState() {
|
function initState() {
|
||||||
isAutoCompleting = false; // Reset on new game
|
score = 0;
|
||||||
|
highScore = localStorage.getItem('solitaire-high-score') || 0;
|
||||||
|
updateScoreDisplay();
|
||||||
|
|
||||||
|
isAutoCompleting = false;
|
||||||
Object.values(cardElements).forEach(el => el.remove());
|
Object.values(cardElements).forEach(el => el.remove());
|
||||||
cardElements = {};
|
cardElements = {};
|
||||||
deck = []; stock = []; waste = [];
|
deck = []; stock = []; waste = [];
|
||||||
@ -120,13 +132,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
stock = deck;
|
stock = deck;
|
||||||
winMessage.classList.add('hidden');
|
winMessage.classList.add('hidden');
|
||||||
history.length = 0;
|
history.length = 0;
|
||||||
pushHistory();
|
pushHistory(); // Initial state for undo
|
||||||
updateBoard(true);
|
updateBoard(true);
|
||||||
saveGame();
|
saveGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== RENDER ======
|
// ====== RENDER ======
|
||||||
function updateBoard(initial=false) {
|
function updateBoard(initial=false) {
|
||||||
|
updateScoreDisplay(); // Keep score display fresh on every update
|
||||||
const gameBoardRect = gameBoard.getBoundingClientRect();
|
const gameBoardRect = gameBoard.getBoundingClientRect();
|
||||||
const updatePile = (pileData, pileEl, type) => {
|
const updatePile = (pileData, pileEl, type) => {
|
||||||
if (!pileEl) return;
|
if (!pileEl) return;
|
||||||
@ -241,6 +254,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ====== HELPERS ======
|
// ====== HELPERS ======
|
||||||
|
function updateScoreDisplay() {
|
||||||
|
scoreEl.textContent = score;
|
||||||
|
highScoreEl.textContent = highScore;
|
||||||
|
}
|
||||||
function getValueRank(v) { if (v==='A') return 1; if (v==='K') return 13; if (v==='Q') return 12; if (v==='J') return 11; return parseInt(v,10); }
|
function getValueRank(v) { if (v==='A') return 1; if (v==='K') return 13; if (v==='Q') return 12; if (v==='J') return 11; return parseInt(v,10); }
|
||||||
function getPileArrayFromElement(el) {
|
function getPileArrayFromElement(el) {
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
@ -296,7 +313,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
function performMoveCleanup(sourcePile) {
|
function performMoveCleanup(sourcePile) {
|
||||||
if (tableau.includes(sourcePile) && sourcePile.length > 0) {
|
if (tableau.includes(sourcePile) && sourcePile.length > 0) {
|
||||||
sourcePile[sourcePile.length - 1].faceUp = true;
|
const topCard = sourcePile[sourcePile.length - 1];
|
||||||
|
if (!topCard.faceUp) {
|
||||||
|
topCard.faceUp = true;
|
||||||
|
score += 5; // +5 points for revealing a card
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clearSelection();
|
clearSelection();
|
||||||
updateBoard();
|
updateBoard();
|
||||||
@ -307,6 +328,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
function checkWin() {
|
function checkWin() {
|
||||||
if (foundations.every(p=>p.length===13)) {
|
if (foundations.every(p=>p.length===13)) {
|
||||||
|
if (score > highScore) {
|
||||||
|
highScore = score;
|
||||||
|
localStorage.setItem('solitaire-high-score', highScore);
|
||||||
|
updateScoreDisplay();
|
||||||
|
}
|
||||||
winMessage.classList.remove('hidden');
|
winMessage.classList.remove('hidden');
|
||||||
try {
|
try {
|
||||||
confetti({ particleCount: 180, spread: 75, origin:{y:0.35} });
|
confetti({ particleCount: 180, spread: 75, origin:{y:0.35} });
|
||||||
@ -324,6 +350,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cardsToMove.forEach(c => c.faceUp = true);
|
cardsToMove.forEach(c => c.faceUp = true);
|
||||||
waste.push(...cardsToMove);
|
waste.push(...cardsToMove);
|
||||||
} else if (waste.length > 0) {
|
} else if (waste.length > 0) {
|
||||||
|
score = Math.max(0, score - 20); // Penalty for recycling, but not below 0
|
||||||
stock = waste.reverse();
|
stock = waste.reverse();
|
||||||
stock.forEach(c => c.faceUp = false);
|
stock.forEach(c => c.faceUp = false);
|
||||||
waste = [];
|
waste = [];
|
||||||
@ -337,10 +364,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const { sourcePile, cardIndex, cardsToMove } = findCardData(cardEl);
|
const { sourcePile, cardIndex, cardsToMove } = findCardData(cardEl);
|
||||||
if (!sourcePile || !cardsToMove || cardsToMove.length === 0) return;
|
if (!sourcePile || !cardsToMove || cardsToMove.length === 0) return;
|
||||||
const cardToMove = cardsToMove[0];
|
const cardToMove = cardsToMove[0];
|
||||||
|
|
||||||
// Try moving single cards to foundation first
|
// Try moving single cards to foundation first
|
||||||
if (cardsToMove.length === 1) {
|
if (cardsToMove.length === 1) {
|
||||||
for (const f of foundations) {
|
for (const f of foundations) {
|
||||||
if (canMove(cardToMove, f)) {
|
if (canMove(cardToMove, f)) {
|
||||||
|
score += 10;
|
||||||
|
updateScoreDisplay();
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(sourcePile, cardIndex, f);
|
moveSequence(sourcePile, cardIndex, f);
|
||||||
performMoveCleanup(sourcePile);
|
performMoveCleanup(sourcePile);
|
||||||
@ -348,9 +378,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then try moving any valid stack to another tableau
|
// Then try moving any valid stack to another tableau
|
||||||
for (const t of tableau) {
|
for (const t of tableau) {
|
||||||
if (t !== sourcePile && canMove(cardToMove, t)) {
|
if (t !== sourcePile && canMove(cardToMove, t)) {
|
||||||
|
// --- SCORING LOGIC ---
|
||||||
|
if (sourcePile === waste) score += 5; // FIX: Added +5 for waste to tableau move
|
||||||
|
if (foundations.includes(sourcePile)) score -= 15;
|
||||||
|
updateScoreDisplay();
|
||||||
|
// --- END SCORING LOGIC ---
|
||||||
|
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(sourcePile, cardIndex, t);
|
moveSequence(sourcePile, cardIndex, t);
|
||||||
performMoveCleanup(sourcePile);
|
performMoveCleanup(sourcePile);
|
||||||
@ -386,12 +423,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
isAutoCompleting = true;
|
isAutoCompleting = true;
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
let movedCard = false;
|
let movedCard = false;
|
||||||
|
let source = null;
|
||||||
// First check waste pile
|
// First check waste pile
|
||||||
if (waste.length > 0) {
|
if (waste.length > 0) {
|
||||||
const card = waste[waste.length - 1];
|
const card = waste[waste.length - 1];
|
||||||
for (const f of foundations) {
|
for (const f of foundations) {
|
||||||
if (canMove(card, f)) {
|
if (canMove(card, f)) {
|
||||||
moveSequence(waste, waste.length - 1, f);
|
score += 10;
|
||||||
|
source = waste;
|
||||||
|
moveSequence(source, source.length - 1, f);
|
||||||
movedCard = true;
|
movedCard = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -404,8 +444,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const card = t[t.length - 1];
|
const card = t[t.length - 1];
|
||||||
for (const f of foundations) {
|
for (const f of foundations) {
|
||||||
if (canMove(card, f)) {
|
if (canMove(card, f)) {
|
||||||
moveSequence(t, t.length - 1, f);
|
score += 10;
|
||||||
if (t.length > 0) t[t.length - 1].faceUp = true;
|
source = t;
|
||||||
|
moveSequence(source, source.length - 1, f);
|
||||||
movedCard = true;
|
movedCard = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -414,60 +455,40 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (movedCard) break;
|
if (movedCard) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateBoard();
|
|
||||||
|
if (source) performMoveCleanup(source);
|
||||||
|
|
||||||
if (!movedCard) {
|
if (!movedCard) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
isAutoCompleting = false;
|
isAutoCompleting = false;
|
||||||
|
checkWin(); // Final check
|
||||||
}
|
}
|
||||||
}, 120);
|
}, 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function checkForAnyAvailableMove() {
|
||||||
* Checks for any available moves that make progress in the game.
|
const topWasteCard = waste.length > 0 ? waste[waste.length - 1] : null;
|
||||||
* Ignores pointless, looping moves between tableau piles.
|
if (topWasteCard) {
|
||||||
* @returns {boolean} - True if a productive move is found, otherwise false.
|
for (const f of foundations) if (canMove(topWasteCard, f)) return true;
|
||||||
*/
|
for (const t of tableau) if (canMove(topWasteCard, t)) return true;
|
||||||
function checkForAnyAvailableMove() {
|
|
||||||
// 1. Any move to a foundation is always productive.
|
|
||||||
const topWasteCard = waste.length > 0 ? waste[waste.length - 1] : null;
|
|
||||||
if (topWasteCard) {
|
|
||||||
for (const f of foundations) if (canMove(topWasteCard, f)) return true;
|
|
||||||
}
|
|
||||||
for (const t of tableau) {
|
|
||||||
if (t.length > 0) {
|
|
||||||
const topTableauCard = t[t.length - 1];
|
|
||||||
for (const f of foundations) if (canMove(topTableauCard, f)) return true;
|
|
||||||
}
|
}
|
||||||
}
|
for (const sourcePile of tableau) {
|
||||||
|
if (sourcePile.length > 0) {
|
||||||
|
const topCard = sourcePile[sourcePile.length-1];
|
||||||
|
for(const f of foundations) if(canMove(topCard, f)) return true;
|
||||||
|
|
||||||
// 2. Any move from the waste pile to the tableau is always productive.
|
const movableStackIndex = sourcePile.findIndex(card => card.faceUp);
|
||||||
if (topWasteCard) {
|
if (movableStackIndex > 0) {
|
||||||
for (const t of tableau) if (canMove(topWasteCard, t)) return true;
|
const cardToMove = sourcePile[movableStackIndex];
|
||||||
}
|
for (const destPile of tableau) {
|
||||||
|
if (sourcePile !== destPile && canMove(cardToMove, destPile)) return true;
|
||||||
// 3. A tableau-to-tableau move is only productive if it reveals a face-down card.
|
}
|
||||||
for (const sourcePile of tableau) {
|
|
||||||
// Find the start index of the movable, face-up stack.
|
|
||||||
const movableStackIndex = sourcePile.findIndex(card => card.faceUp);
|
|
||||||
|
|
||||||
// A move is productive if there is a movable stack AND a card underneath it.
|
|
||||||
if (movableStackIndex > 0) { // Index > 0 means there's a card at index 0, 1, etc.
|
|
||||||
const cardToMove = sourcePile[movableStackIndex];
|
|
||||||
for (const destPile of tableau) {
|
|
||||||
if (sourcePile !== destPile && canMove(cardToMove, destPile)) {
|
|
||||||
// This move is guaranteed to reveal the card at [movableStackIndex - 1].
|
|
||||||
// We can immediately say a productive move is available.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've gotten this far, no moves were found that make clear progress.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function handleStuckCheck() {
|
function handleStuckCheck() {
|
||||||
if (checkForAnyAvailableMove()) {
|
if (checkForAnyAvailableMove()) {
|
||||||
alert("Hint: A move is available on the board!");
|
alert("Hint: A move is available on the board!");
|
||||||
@ -527,9 +548,14 @@ function checkForAnyAvailableMove() {
|
|||||||
lastClick = { time: now, cardId: card.id };
|
lastClick = { time: now, cardId: card.id };
|
||||||
if (selected.card) {
|
if (selected.card) {
|
||||||
if (canMove(selected.card, sourcePile)) {
|
if (canMove(selected.card, sourcePile)) {
|
||||||
|
// --- SCORING LOGIC ---
|
||||||
|
if (foundations.includes(sourcePile)) score += 10;
|
||||||
|
if (tableau.includes(sourcePile) && selected.sourcePile === waste) score += 5;
|
||||||
|
if (tableau.includes(sourcePile) && foundations.includes(selected.sourcePile)) score -= 15;
|
||||||
|
// --- END SCORING LOGIC ---
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(selected.sourcePile, selected.cardIndex, sourcePile);
|
moveSequence(selected.sourcePile, selected.cardIndex, sourcePile);
|
||||||
performMoveCleanup(sourcePile);
|
performMoveCleanup(selected.sourcePile);
|
||||||
} else {
|
} else {
|
||||||
if (selected.card.id === card.id) {
|
if (selected.card.id === card.id) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
@ -547,6 +573,11 @@ function checkForAnyAvailableMove() {
|
|||||||
if (pileEl && selected.card) {
|
if (pileEl && selected.card) {
|
||||||
const destPile = getPileArrayFromElement(pileEl);
|
const destPile = getPileArrayFromElement(pileEl);
|
||||||
if (destPile && canMove(selected.card, destPile)) {
|
if (destPile && canMove(selected.card, destPile)) {
|
||||||
|
// --- SCORING LOGIC ---
|
||||||
|
if (foundations.includes(destPile)) score += 10;
|
||||||
|
if (tableau.includes(destPile) && selected.sourcePile === waste) score += 5;
|
||||||
|
if (tableau.includes(destPile) && foundations.includes(selected.sourcePile)) score -= 15;
|
||||||
|
// --- END SCORING LOGIC ---
|
||||||
pushHistory();
|
pushHistory();
|
||||||
moveSequence(selected.sourcePile, selected.cardIndex, destPile);
|
moveSequence(selected.sourcePile, selected.cardIndex, destPile);
|
||||||
performMoveCleanup(selected.sourcePile);
|
performMoveCleanup(selected.sourcePile);
|
||||||
@ -571,7 +602,23 @@ function checkForAnyAvailableMove() {
|
|||||||
});
|
});
|
||||||
gameBoard.addEventListener('dragover', e => { e.preventDefault(); document.querySelectorAll('.drag-over').forEach(p => p.classList.remove('drag-over')); const targetEl = e.target.closest('.pile, .card'); if (targetEl) targetEl.closest('.pile')?.classList.add('drag-over'); });
|
gameBoard.addEventListener('dragover', e => { e.preventDefault(); document.querySelectorAll('.drag-over').forEach(p => p.classList.remove('drag-over')); const targetEl = e.target.closest('.pile, .card'); if (targetEl) targetEl.closest('.pile')?.classList.add('drag-over'); });
|
||||||
gameBoard.addEventListener('dragend', () => { if (dragged.cards?.length) { dragged.cards.forEach(c => cardElements[c.id]?.classList.remove('dragging')); } dragged = { cards: [], sourcePile: null, startIndex: -1 }; document.querySelectorAll('.drag-over').forEach(p => p.classList.remove('drag-over')); });
|
gameBoard.addEventListener('dragend', () => { if (dragged.cards?.length) { dragged.cards.forEach(c => cardElements[c.id]?.classList.remove('dragging')); } dragged = { cards: [], sourcePile: null, startIndex: -1 }; document.querySelectorAll('.drag-over').forEach(p => p.classList.remove('drag-over')); });
|
||||||
gameBoard.addEventListener('drop', e => { e.preventDefault(); if (!dragged.cards.length) return; const dropTargetEl = e.target.closest('.card, .pile'); if (!dropTargetEl) return; const destPile = getPileArrayFromElement(dropTargetEl.closest('.pile')); if (destPile && canMove(dragged.cards[0], destPile)) { pushHistory(); moveSequence(dragged.sourcePile, dragged.startIndex, destPile); performMoveCleanup(dragged.sourcePile); }});
|
gameBoard.addEventListener('drop', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dragged.cards.length) return;
|
||||||
|
const dropTargetEl = e.target.closest('.card, .pile');
|
||||||
|
if (!dropTargetEl) return;
|
||||||
|
const destPile = getPileArrayFromElement(dropTargetEl.closest('.pile'));
|
||||||
|
if (destPile && canMove(dragged.cards[0], destPile)) {
|
||||||
|
// --- SCORING LOGIC ---
|
||||||
|
if (foundations.includes(destPile)) score += 10;
|
||||||
|
if (tableau.includes(destPile) && dragged.sourcePile === waste) score += 5;
|
||||||
|
if (tableau.includes(destPile) && foundations.includes(dragged.sourcePile)) score -= 15;
|
||||||
|
// --- END SCORING LOGIC ---
|
||||||
|
pushHistory();
|
||||||
|
moveSequence(dragged.sourcePile, dragged.startIndex, destPile);
|
||||||
|
performMoveCleanup(dragged.sourcePile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (e)=>{
|
document.addEventListener('keydown', (e)=>{
|
||||||
if (isAutoCompleting) return;
|
if (isAutoCompleting) return;
|
||||||
@ -599,4 +646,4 @@ function checkForAnyAvailableMove() {
|
|||||||
initState();
|
initState();
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', () => updateBoard(true));
|
window.addEventListener('resize', () => updateBoard(true));
|
||||||
});
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user