diff --git a/css/style.css b/css/style.css index ff38c99..ab85c07 100644 --- a/css/style.css +++ b/css/style.css @@ -1,180 +1,176 @@ * { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - touch-action: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + touch-action: none; } body { - font-family: Arial, Helvetica, sans-serif; + font-family: Arial, Helvetica, sans-serif; } body, html { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } #game_view { - width: 100vw; - height: 100vh; + width: 100vw; + height: 100vh; - background: #050; - padding: 0; + background: #050; + padding: 0; - overflow: clip; + overflow: clip; } #game_header { - width: 100%; - height: 50pt; - background: #00000055; - overflow: clip; - display: flex; + width: 100%; + height: 50pt; + background: #00000055; + overflow: clip; + display: flex; } - #game_footer { - width: 100%; - height: 50pt; - background: #00000055; - overflow: clip; - display: flex; + width: 100%; + height: 50pt; + background: #00000055; + overflow: clip; + display: flex; } #game_main { - width: 100%; - height: calc(100% - 100pt); - overflow: clip; + width: 100%; + height: calc(100% - 100pt); + overflow: clip; } #game_box { - max-width: 100%; - max-height: 100%; - aspect-ratio: 1.6; - margin: auto; - background: #00000011; - position: relative; + max-width: 100%; + max-height: 100%; + aspect-ratio: 1.6; + margin: auto; + background: #00000011; + position: relative; } #top_row { - width: 100%; - height: 25%; - background: #00000011; + width: 100%; + height: 25%; + background: #00000011; } - -#game_header>div { - background-color: DodgerBlue; - color: white; - width: 200px; - margin: 5px; - text-align: center; - line-height: 60px; - font-size: 30px; +#game_header > div { + background-color: DodgerBlue; + color: white; + width: 200px; + margin: 5px; + text-align: center; + line-height: 60px; + font-size: 30px; } .card, #deal { - position: absolute; - height: 20%; - aspect-ratio: 2.5/3.5; - perspective: 1000px; + position: absolute; + height: 20%; + aspect-ratio: 2.5/3.5; + perspective: 1000px; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; - box-shadow: 0 0 5px #0009; - overflow: clip; + box-shadow: 0 0 5px #0009; + overflow: clip; } #deal { - top: 2.5%; - left: 2.5%; + top: 2.5%; + left: 2.5%; - background-image: url('../images/back.svg'); - background-size: 100%; + background-image: url("../images/back.svg"); + background-size: 100%; } .flipped { - transform: rotateY(-180deg); + transform: rotateY(-180deg); } .card_inner { - position: absolute; - width: 100%; - height: 100%; - transform-style: preserve-3d; + position: absolute; + width: 100%; + height: 100%; + transform-style: preserve-3d; } .card_front, .card_back { - position: absolute; - width: 100%; - height: 100%; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; + position: absolute; + width: 100%; + height: 100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; } .card_front { - background-color: #fff; - background-size: 100%; + background-color: #fff; + background-size: 100%; } .card_back { - transform: rotateY(180deg); - background-image: url('../images/back.svg'); - background-size: 100%; + transform: rotateY(180deg); + background-image: url("../images/back.svg"); + background-size: 100%; } /* stack */ #stack { - display: flex; - position: absolute; - height: 20%; - left: 45.5%; - right: 2.5%; - top: 2.5%; - justify-content: space-between; + display: flex; + position: absolute; + height: 20%; + left: 45.5%; + right: 2.5%; + top: 2.5%; + justify-content: space-between; } -#stack>div { - height: 100%; - aspect-ratio: 2.5/3.5; - background-color: #0012; - - border-style: solid; - border-width: 5px; - border-color: #631; +#stack > div { + height: 100%; + aspect-ratio: 2.5/3.5; + background-color: #0012; + border-style: solid; + border-width: 5px; + border-color: #631; } #tableau { - display: flex; - position: absolute; - justify-content: space-between; - height: 20%; - left: 2.5%; - right: 2.5%; + display: flex; + position: absolute; + justify-content: space-between; + height: 20%; + left: 2.5%; + right: 2.5%; } -#tableau>div { - margin-top: 2.5%; - height: 100%; - aspect-ratio: 2.5/3.5; - background-color: #0012; - - border-style: solid; - border-width: 2px; - border-color: #F75; +#tableau > div { + margin-top: 2.5%; + height: 100%; + aspect-ratio: 2.5/3.5; + background-color: #0012; + border-style: solid; + border-width: 2px; + border-color: #f75; } .card, #deal, -#tableau>div, -#stack>div { - border-radius: 0.7cqw; -} \ No newline at end of file +#tableau > div, +#stack > div { + border-radius: 0.7cqw; +} diff --git a/images/back.svg b/images/back.svg index ca9d0f7..b22eac4 100644 --- a/images/back.svg +++ b/images/back.svg @@ -1,6 +1,5 @@ - - - - - \ No newline at end of file + + + + diff --git a/index.html b/index.html index 5743ce6..487fa52 100644 --- a/index.html +++ b/index.html @@ -1,58 +1,50 @@ - - + LonelyBot - - - - - - - + + + + - +
-
-
- Solitaire bot -
-
- Score: - 0 -
+
+
+ Solitaire bot +
+
+ Score: + 0
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- +
- - - \ No newline at end of file + + diff --git a/js/game.js b/js/game.js index fc46856..d28b1e0 100644 --- a/js/game.js +++ b/js/game.js @@ -2,15 +2,15 @@ window.onload = addListeners; -const RANK_MAP = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; -const SUIT_MAP = ['♡', '♢', '♧', '♤']; +const RANK_MAP = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]; +const SUIT_MAP = ["♡", "♢", "♧", "♤"]; const N_SUITS = SUIT_MAP.length; const N_RANKS = RANK_MAP.length; const KING_RANK = N_RANKS - 1; const N_CARDS = N_SUITS * N_RANKS; const N_PILES = 7; -const N_HIDDEN_CARDS = N_PILES * (N_PILES + 1) / 2; +const N_HIDDEN_CARDS = (N_PILES * (N_PILES + 1)) / 2; const N_FULL_DECK = N_CARDS - N_HIDDEN_CARDS; const UP_SPACE = 3; @@ -28,299 +28,301 @@ const OFFSET_TIME = 70; // ♤♡♢♧♠♥♦♣ function cardId(rank, suit) { - return rank * N_SUITS + suit; + return rank * N_SUITS + suit; } - function createCardSVG(c) { - const rank_str = RANK_MAP[c.rank] - const suit_str = SUIT_MAP[c.suit] - const color = c.suit < 2 ? "#e22" : "#001" - - return ` - - ${rank_str} - ${suit_str} - - ${rank_str} - - ${rank_str} - ${suit_str} - - ` + const rank_str = RANK_MAP[c.rank]; + const suit_str = SUIT_MAP[c.suit]; + const color = c.suit < 2 ? "#e22" : "#001"; + + return ` + + ${rank_str} + ${suit_str} + + ${rank_str} + + ${rank_str} + ${suit_str} + + `; } class Card { - constructor(rank, suit) { - this.rank = rank; - this.suit = suit; - this.flipped = false; - - // private shouldn't change from the outside - this.draggable = true; - + constructor(rank, suit) { + this.rank = rank; + this.suit = suit; + this.flipped = false; + + // private shouldn't change from the outside + this.draggable = true; + + this.animating = false; + this.element = null; + + this.id = () => { + return cardId(rank, suit); + }; + + this.deleteDom = () => { + if (this.element === null) return; + this.element.remove(); + this.element = null; + }; + + this.moveToFront = () => { + if (this.element === null) return; + gameBox.appendChild(this.element); + }; + + this.createDOM = (pos_x, pos_y) => { + if (this.element !== null) return; + const c = document.createElement("div"); + c.className = "card"; + c.draggable = false; + c.innerHTML = `
+
+ ${createCardSVG(this)} +
`; + + if (this.flipped) c.firstElementChild.classList.add("flipped"); + + c.style.left = pos_x + "%"; + c.style.top = pos_y + "%"; + + c.addEventListener("transitionrun", (_) => { + this.animating = true; + }); + + const done_animate = (_) => { + c.style.removeProperty("transition"); this.animating = false; - this.element = null; + }; + c.addEventListener("transitioncancel", done_animate); + c.addEventListener("transitionend", done_animate); - this.id = () => { - return cardId(rank, suit); - }; + gameBox.appendChild(c); + c.dataset.cardId = this.id(); - this.deleteDom = () => { - if (this.element === null) return; - this.element.remove(); - this.element = null; - }; - - this.moveToFront = () => { - if (this.element === null) return; - gameBox.appendChild(this.element); - } + this.element = c; + }; - this.createDOM = (pos_x, pos_y) => { - if (this.element !== null) return; - const c = document.createElement('div'); - c.id = `card_${rank}_${suit}`; - c.className = "card"; - c.draggable = false; - c.innerHTML = `
-
- ${createCardSVG(this)} -
`; - - if (this.flipped) - c.firstElementChild.classList.add("flipped"); - - c.style.left = pos_x + "%"; - c.style.top = pos_y + "%"; - - c.addEventListener("transitionrun", (_) => { - this.animating = true; - }); - - const done_animate = (_) => { - c.style.removeProperty('transition'); - this.animating = false; - }; - - c.addEventListener("transitioncancel", done_animate); - c.addEventListener("transitionend", done_animate); - - gameBox.appendChild(c); - c.dataset.cardId = this.id(); - - this.element = c; - }; - - this.turnUp = (duration) => { - if (this.flipped) { - this.flipCard(duration); - } - } + this.turnUp = (duration) => { + if (this.flipped) { + this.flipCard(duration); + } + }; - this.flipCard = (duration) => { - this.flipped = !this.flipped; + this.flipCard = (duration) => { + this.flipped = !this.flipped; - if (this.element === null) { - return; - } + if (this.element === null) { + return; + } - // Flip card logic - let inner = this.element.firstElementChild; - if (duration > 0) { - inner.style.transition = `transform ${duration}ms` - } + // Flip card logic + let inner = this.element.firstElementChild; + if (duration > 0) { + inner.style.transition = `transform ${duration}ms`; + } - inner.classList.toggle("flipped"); - }; + inner.classList.toggle("flipped"); + }; - this.moveTo = (pos_x, pos_y, duration) => { - if (this.element === null) return; + this.moveTo = (pos_x, pos_y, duration) => { + if (this.element === null) return; - { - const currentLeft = parseFloat(this.element.style.left) || 0; - const currentTop = parseFloat(this.element.style.top) || 0; + { + const currentLeft = parseFloat(this.element.style.left) || 0; + const currentTop = parseFloat(this.element.style.top) || 0; - // this to prevent transitionend not triggered - if (Math.abs(currentLeft - pos_x) < 1e-2 && Math.abs(currentTop - pos_y) < 1e-2) { - return; - } - } + // this to prevent transitionend not triggered + if (Math.abs(currentLeft - pos_x) < 1e-2 && Math.abs(currentTop - pos_y) < 1e-2) { + return; + } + } - if (duration > 0) - this.element.style.transition = `top ${duration}ms ease-in, left ${duration}ms ease-in`; + if (duration > 0) this.element.style.transition = `top ${duration}ms ease-in, left ${duration}ms ease-in`; - if (pos_x !== null) - this.element.style.left = pos_x + "%"; + if (pos_x !== null) this.element.style.left = pos_x + "%"; - if (pos_y !== null) - this.element.style.top = pos_y + "%"; - }; + if (pos_y !== null) this.element.style.top = pos_y + "%"; + }; - this.isDraggable = () => { - return this.draggable && !this.animating; - }; + this.isDraggable = () => { + return this.draggable && !this.animating; + }; - this.goBefore = (card) => { - return this.rank == card.rank + 1 && (((this.suit ^ card.suit) & 2) === 2 || this.rank === N_RANKS); - }; - } + this.goBefore = (card) => { + return this.rank == card.rank + 1 && (((this.suit ^ card.suit) & 2) === 2 || this.rank === N_RANKS); + }; + } } class Deck { - constructor(cards, draw_step) { - this.stock = cards; + constructor(cards, draw_step) { + this.stock = cards; + this.waste = []; + this.draw_step = draw_step; + + this.peek = (n) => { + const len = this.waste.length; + const start = Math.max(len - n, 0); + return this.waste.slice(start, len); + }; + + this.pop = () => { + return this.waste.pop(); + }; + + this.deal = () => { + if (this.stock.length == 0) { + this.stock = this.waste; this.waste = []; - this.draw_step = draw_step; - - this.peek = (n) => { - const len = this.waste.length; - const start = Math.max(len - n, 0); - return this.waste.slice(start, len); - }; - - this.pop = () => { - return this.waste.pop(); - }; - - this.deal = () => { - if (this.stock.length == 0) { - this.stock = this.waste; - this.waste = []; - } else { - let removed = this.stock.splice(0, this.draw_step); - this.waste.push(...removed); - }; - }; - } + } else { + let removed = this.stock.splice(0, this.draw_step); + this.waste.push(...removed); + } + }; + } } const Pos = { - Deck: 0, - Stack: 1, - Pile: 1 + N_SUITS, - None: -1, -} + Deck: 0, + Stack: 1, + Pile: 1 + N_SUITS, + None: -1, +}; class Solitaire { - constructor(cards, draw_step) { - const hidden_cards = cards.slice(0, N_HIDDEN_CARDS); - this.piles = Array.from(Array(N_PILES), (_, i) => { - return [hidden_cards[(i + 2) * (i + 1) / 2 - 1]]; - }); + constructor(cards, draw_step) { + const hidden_cards = cards.slice(0, N_HIDDEN_CARDS); + this.piles = Array.from(Array(N_PILES), (_, i) => { + return [hidden_cards[((i + 2) * (i + 1)) / 2 - 1]]; + }); - this.hidden_piles = Array.from(Array(N_PILES), (_, i) => { - return hidden_cards.slice((i + 1) * i / 2, (i + 2) * (i + 1) / 2 - 1); - }); + this.hidden_piles = Array.from(Array(N_PILES), (_, i) => { + return hidden_cards.slice(((i + 1) * i) / 2, ((i + 2) * (i + 1)) / 2 - 1); + }); - this.deck = new Deck(cards.slice(N_HIDDEN_CARDS, N_CARDS), draw_step); - this.stack = Array.from(Array(N_SUITS), () => 0); - - this.on_deal = []; - this.on_pop_deck = []; - this.on_pop_stack = []; - this.on_push_stack = []; - this.on_reveal = []; - - this.find_origin = (card) => { - if (this.deck.peek(1).find((c) => c.id() == card.id())) { - return Pos.Deck; - } - - if (this.stack.find((rank, suit) => card.id() == cardId(rank - 1, suit))) { - return Pos.Stack + card.suit; - } - - const pos = this.piles.findIndex((pile) => pile.find((c) => c.id() == card.id())); - if (pos >= 0) { - return Pos.Pile + pos; - } - return Pos.None + this.deck = new Deck(cards.slice(N_HIDDEN_CARDS, N_CARDS), draw_step); + this.stack = Array.from(Array(N_SUITS), () => 0); + + this.on_deal = []; + this.on_pop_deck = []; + this.on_pop_stack = []; + this.on_push_stack = []; + this.on_reveal = []; + + this.find_origin = (card) => { + if (this.deck.peek(1).find((c) => c.id() == card.id())) { + return Pos.Deck; + } + + if (this.stack.find((rank, suit) => card.id() == cardId(rank - 1, suit))) { + return Pos.Stack + card.suit; + } + + const pos = this.piles.findIndex((pile) => pile.find((c) => c.id() == card.id())); + if (pos >= 0) { + return Pos.Pile + pos; + } + return Pos.None; + }; + + this.lift_card = (cards) => { + const card = cards[0]; + const res = new Array(); + if (cards.length == 1 && this.stack[card.suit] == card.rank) { + res.push(Pos.Stack + card.suit); + } + + for (let i = 0; i < N_PILES; ++i) { + const p = this.piles[i]; + const last = p[p.length - 1] || new Card(N_RANKS, 0); + if (last.goBefore(card)) { + res.push(Pos.Pile + i); } + } - this.lift_card = (cards) => { - const card = cards[0] - const res = new Array(); - if (cards.length == 1 && this.stack[card.suit] == card.rank) { - res.push(Pos.Stack + card.suit); - } - - for (let i = 0; i < N_PILES; ++i) { - const p = this.piles[i] - const last = p[p.length - 1] || new Card(N_RANKS, 0); - if (last.goBefore(card)) { - res.push(Pos.Pile + i); - } - } - - return res; + return res; + }; + + this.make_move = (card, src, dst) => { + // find the position + if (src == Pos.Deck && dst == Pos.Deck) { + this.deck.deal(); + for (const callback of this.on_deal) { + callback(); + } + return; + } + if (src == Pos.Deck) { + this.deck.pop(); + for (const callback of this.on_pop_deck) { + callback(); } + } else if (src <= N_SUITS) { + this.stack[src - 1] -= 1; + for (const callback of this.on_pop_stack) { + callback(card); + } + } - this.make_move = (card, src, dst) => { - // find the position - if (src == Pos.Deck && dst == Pos.Deck) { - this.deck.deal(); - for (const callback of this.on_deal) { callback(); } - return; - } - if (src == Pos.Deck) { - this.deck.pop(); - for (const callback of this.on_pop_deck) { callback(); } - - } else if (src <= N_SUITS) { - this.stack[src - 1] -= 1 - for (const callback of this.on_pop_stack) { callback(card); } - } - - if (dst <= N_SUITS) { - this.stack[dst - 1] += 1 - for (const callback of this.on_push_stack) { callback(card); } - } - - // from pile - let cards = [card]; - if (src >= Pos.Pile) { - src = src - Pos.Pile; - const cardPos = this.piles[src].findIndex((c) => c.id() == card.id()); - cards = this.piles[src].splice(cardPos) - - if (this.piles[src].length == 0 && this.hidden_piles[src].length > 0) { - const last = this.hidden_piles[src].pop() - this.piles[src].push(last); - for (const callback of this.on_reveal) { callback(src, last); } - } - } - - // to pile - if (dst >= Pos.Pile) { - dst = dst - Pos.Pile; - this.piles[dst].push(...cards); - } + if (dst <= N_SUITS) { + this.stack[dst - 1] += 1; + for (const callback of this.on_push_stack) { + callback(card); } - } -}; + } + + // from pile + let cards = [card]; + if (src >= Pos.Pile) { + src = src - Pos.Pile; + const cardPos = this.piles[src].findIndex((c) => c.id() == card.id()); + cards = this.piles[src].splice(cardPos); + + if (this.piles[src].length == 0 && this.hidden_piles[src].length > 0) { + const last = this.hidden_piles[src].pop(); + this.piles[src].push(last); + for (const callback of this.on_reveal) { + callback(src, last); + } + } + } + + // to pile + if (dst >= Pos.Pile) { + dst = dst - Pos.Pile; + this.piles[dst].push(...cards); + } + }; + } +} const gameBox = document.querySelector("#game_box"); // creating cards const cardArray = (() => { - const cardArray = new Array(N_CARDS); + const cardArray = new Array(N_CARDS); - for (let rank = 0; rank < N_RANKS; ++rank) { - for (let suit = 0; suit < N_SUITS; ++suit) { - let c = new Card(rank, suit); - cardArray[c.id()] = c; - } + for (let rank = 0; rank < N_RANKS; ++rank) { + for (let suit = 0; suit < N_SUITS; ++suit) { + let c = new Card(rank, suit); + cardArray[c.id()] = c; } - return cardArray; + } + return cardArray; })(); function shuffleArray(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } } let shuffledCards = [...cardArray]; @@ -329,262 +331,264 @@ shuffleArray(shuffledCards); const game = new Solitaire(shuffledCards, 3); function addListeners() { - initGame(); + initGame(); } - function getCard(rank, suit) { - return cardArray[cardId(rank, suit)]; + return cardArray[cardId(rank, suit)]; } const THRES = 0.1; const THRES_2 = THRES * THRES; -var snap_audio = new Audio('sound/snap.mp3'); - +var snap_audio = new Audio("sound/snap.mp3"); function initGame() { - let gameBoxBound = gameBox.getBoundingClientRect(); - - function getDOMPos(el) { - let bound = el.getBoundingClientRect(); - const x = (bound.left - gameBoxBound.left) / gameBoxBound.width; - const y = (bound.top - gameBoxBound.top) / gameBoxBound.height; - return [x, y]; + let gameBoxBound = gameBox.getBoundingClientRect(); + + function getDOMPos(el) { + let bound = el.getBoundingClientRect(); + const x = (bound.left - gameBoxBound.left) / gameBoxBound.width; + const y = (bound.top - gameBoxBound.top) / gameBoxBound.height; + return [x, y]; + } + + const pilePos = (function () { + let pos_stack = new Array(); + let pos_tableau = new Array(); + // not correct but whatever =)) + pos_stack.push(getDOMPos(document.querySelector("#deal"))); + + for (let s of document.querySelectorAll("#stack > div")) { + pos_stack.push(getDOMPos(s)); } - const pilePos = function () { - let pos_stack = new Array(); - let pos_tableau = new Array(); - // not correct but whatever =)) - pos_stack.push(getDOMPos(document.querySelector("#deal"))); - - for (let s of document.querySelectorAll("#stack > div")) { - pos_stack.push(getDOMPos(s)); - } - - for (let s of document.querySelectorAll("#tableau > div")) { - pos_tableau.push(getDOMPos(s)); - } - - for (let i = 0; i < N_PILES; ++i) { - let hidden = game.hidden_piles[i]; - const pos = pos_tableau[i]; - for (let j = 0; j < hidden.length; ++j) { - hidden[j].flipCard(); - hidden[j].draggable = false; - hidden[j].createDOM(pos[0] * 100, pos[1] * 100 + DOWN_SPACE * j); - } - let visible = game.piles[i]; - for (let j = 0; j < visible.length; ++j) { - visible[j].draggable = j + 1 == visible.length; - visible[j].createDOM(pos[0] * 100, pos[1] * 100 + DOWN_SPACE * hidden.length + UP_SPACE * j); - } - } - - return () => [...pos_stack, ...pos_tableau.map((p, i) => - [p[0], p[1] + (DOWN_SPACE * game.hidden_piles[i].length + UP_SPACE * game.piles[i].length) / 100] - )]; - }(); - - window.addEventListener('resize', (_) => { - gameBoxBound = gameBox.getBoundingClientRect(); - }); + for (let s of document.querySelectorAll("#tableau > div")) { + pos_tableau.push(getDOMPos(s)); + } - game.on_deal.push(() => { - for (let c of cards) { - c.deleteDom(); - } + for (let i = 0; i < N_PILES; ++i) { + let hidden = game.hidden_piles[i]; + const pos = pos_tableau[i]; + for (let j = 0; j < hidden.length; ++j) { + hidden[j].flipCard(); + hidden[j].draggable = false; + hidden[j].createDOM(pos[0] * 100, pos[1] * 100 + DOWN_SPACE * j); + } + let visible = game.piles[i]; + for (let j = 0; j < visible.length; ++j) { + visible[j].draggable = j + 1 == visible.length; + visible[j].createDOM(pos[0] * 100, pos[1] * 100 + DOWN_SPACE * hidden.length + UP_SPACE * j); + } + } - cards = [] + return () => [ + ...pos_stack, + ...pos_tableau.map((p, i) => [ + p[0], + p[1] + (DOWN_SPACE * game.hidden_piles[i].length + UP_SPACE * game.piles[i].length) / 100, + ]), + ]; + })(); + + window.addEventListener("resize", (_) => { + gameBoxBound = gameBox.getBoundingClientRect(); + }); + + game.on_deal.push(() => { + for (let c of cards) { + c.deleteDom(); + } - for (let [pos, c] of game.deck.peek(3).entries()) { - cards.push(c); - c.draggable = false; + cards = []; - c.flipCard(); - c.createDOM(TOP_DEAL, LEFT_DEAL); - c.moveToFront(); + for (let [pos, c] of game.deck.peek(3).entries()) { + cards.push(c); + c.draggable = false; - setTimeout(() => { - c.flipCard(ANIMATION_TIME); - c.moveTo(LEFT_WASTE + pos * DEAL_SPACE, TOP_DEAL, ANIMATION_TIME); - }, OFFSET_TIME * pos); - } + c.flipCard(); + c.createDOM(TOP_DEAL, LEFT_DEAL); + c.moveToFront(); - if (cards.length > 0) { - cards[cards.length - 1].draggable = true; - } - }) + setTimeout(() => { + c.flipCard(ANIMATION_TIME); + c.moveTo(LEFT_WASTE + pos * DEAL_SPACE, TOP_DEAL, ANIMATION_TIME); + }, OFFSET_TIME * pos); + } - game.on_push_stack.push((card) => { - card.moveToFront(); + if (cards.length > 0) { + cards[cards.length - 1].draggable = true; + } + }); - if (card.rank >= 2) { - cardArray[cardId(card.rank - 2, card.suit)].deleteDom(); - } + game.on_push_stack.push((card) => { + card.moveToFront(); - if (card.rank > 0) { - cardArray[cardId(card.rank - 1, card.suit)].draggable = false; - } - }) - - game.on_pop_stack.push((card) => { - if (card.rank >= 2) { - let [x, y] = pilePos()[card.suit + Pos.Stack]; - let c = cardArray[cardId(card.rank - 2, card.suit)]; - c.turnUp(); - c.draggable = false; - c.createDOM(x * 100, y * 100); - cardArray[cardId(card.rank - 1, card.suit)].moveToFront(); - } + if (card.rank >= 2) { + cardArray[cardId(card.rank - 2, card.suit)].deleteDom(); + } - if (card.rank > 0) { - cardArray[cardId(card.rank - 1, card.suit)].draggable = true; - } - }) - - game.on_pop_deck.push(() => { - cards.pop(); - if (cards.length <= 1) { - // append new stuff - cards = game.deck.peek(cards.length + 1) - - if (cards.length > 0) { - cards[0].draggable = false; - cards[0].turnUp(); - cards[0].createDOM(LEFT_WASTE, TOP_DEAL); - } - - for (let c of cards) { - c.moveToFront(); - } - } + if (card.rank > 0) { + cardArray[cardId(card.rank - 1, card.suit)].draggable = false; + } + }); + + game.on_pop_stack.push((card) => { + if (card.rank >= 2) { + let [x, y] = pilePos()[card.suit + Pos.Stack]; + let c = cardArray[cardId(card.rank - 2, card.suit)]; + c.turnUp(); + c.draggable = false; + c.createDOM(x * 100, y * 100); + cardArray[cardId(card.rank - 1, card.suit)].moveToFront(); + } - if (cards.length > 0) { - cards[cards.length - 1].draggable = true; - } - }) + if (card.rank > 0) { + cardArray[cardId(card.rank - 1, card.suit)].draggable = true; + } + }); + + game.on_pop_deck.push(() => { + cards.pop(); + if (cards.length <= 1) { + // append new stuff + cards = game.deck.peek(cards.length + 1); + + if (cards.length > 0) { + cards[0].draggable = false; + cards[0].turnUp(); + cards[0].createDOM(LEFT_WASTE, TOP_DEAL); + } + + for (let c of cards) { + c.moveToFront(); + } + } - game.on_reveal.push((src, card) => { - card.draggable = true; - card.turnUp(200); - }) + if (cards.length > 0) { + cards[cards.length - 1].draggable = true; + } + }); - function moveCard(event, card) { - const origin = game.find_origin(card); + game.on_reveal.push((src, card) => { + card.draggable = true; + card.turnUp(200); + }); - let moving_cards = [card]; + function moveCard(event, card) { + const origin = game.find_origin(card); - if (origin >= Pos.Pile) { - let p = game.piles[origin - Pos.Pile]; - let id = p.findIndex((c) => c.id() == card.id()); - moving_cards = p.slice(id); - } + let moving_cards = [card]; - if (moving_cards.some((c) => !c.isDraggable())) return; - snap_audio.play(); + if (origin >= Pos.Pile) { + let p = game.piles[origin - Pos.Pile]; + let id = p.findIndex((c) => c.id() == card.id()); + moving_cards = p.slice(id); + } - moving_cards.forEach((c) => c.moveToFront()); + if (moving_cards.some((c) => !c.isDraggable())) return; + snap_audio.play(); + moving_cards.forEach((c) => c.moveToFront()); - const dropPos = game.lift_card(moving_cards); + const dropPos = game.lift_card(moving_cards); - const [initialX, initialY] = getDOMPos(card.element); + const [initialX, initialY] = getDOMPos(card.element); - const offsetX = (event.clientX - gameBoxBound.left) / gameBoxBound.width - initialX; - const offsetY = (event.clientY - gameBoxBound.top) / gameBoxBound.height - initialY; + const offsetX = (event.clientX - gameBoxBound.left) / gameBoxBound.width - initialX; + const offsetY = (event.clientY - gameBoxBound.top) / gameBoxBound.height - initialY; - let snapped = -1; + let snapped = -1; - let curPilePos = pilePos(); + let curPilePos = pilePos(); - function distance2(x, y, u, v) { - const [dx, dy] = [x - u, y - v]; - return dx * dx + dy * dy; - } + function distance2(x, y, u, v) { + const [dx, dy] = [x - u, y - v]; + return dx * dx + dy * dy; + } - function findNear(x, y) { - for (let p of dropPos) { - if (distance2(x, y, ...curPilePos[p]) < THRES_2) { - return p; - } - } - return -1 + function findNear(x, y) { + for (let p of dropPos) { + if (distance2(x, y, ...curPilePos[p]) < THRES_2) { + return p; } + } + return -1; + } - function handleMouseMove(event) { - let x = (event.clientX - gameBoxBound.left) / gameBoxBound.width - offsetX; - let y = (event.clientY - gameBoxBound.top) / gameBoxBound.height - offsetY; + function handleMouseMove(event) { + let x = (event.clientX - gameBoxBound.left) / gameBoxBound.width - offsetX; + let y = (event.clientY - gameBoxBound.top) / gameBoxBound.height - offsetY; - let p = findNear(x, y); + let p = findNear(x, y); - if (p >= 0) { - let [u, v] = curPilePos[p]; - // const [dx, dy] = [x - u, y - v]; - // let dis2 = dx * dx + dy * dy; - // let d = Math.max(Math.sqrt(dis2) / THRES - 0.5, 0); + if (p >= 0) { + let [u, v] = curPilePos[p]; + // const [dx, dy] = [x - u, y - v]; + // let dis2 = dx * dx + dy * dy; + // let d = Math.max(Math.sqrt(dis2) / THRES - 0.5, 0); - snapped = p; + snapped = p; - // const force = d > 0 ? Math.exp(-d / 0.5) : 1; - // x -= dx * force; - // y -= dy * force; + // const force = d > 0 ? Math.exp(-d / 0.5) : 1; + // x -= dx * force; + // y -= dy * force; - moving_cards.forEach((c, idx) => c.moveTo(u * 100, v * 100 + idx * UP_SPACE, 100)); + moving_cards.forEach((c, idx) => c.moveTo(u * 100, v * 100 + idx * UP_SPACE, 100)); + } else if (snapped >= 0) { + snapped = -1; + moving_cards.forEach((c, idx) => c.moveTo(x * 100, y * 100 + idx * UP_SPACE, 100)); + } else { + moving_cards.forEach((c, idx) => { + if (!c.animating) c.moveTo(x * 100, y * 100 + idx * UP_SPACE, 0); + }); + } + } - } else if (snapped >= 0) { - snapped = -1; - moving_cards.forEach((c, idx) => c.moveTo(x * 100, y * 100 + idx * UP_SPACE, 100)); - } else { - moving_cards.forEach((c, idx) => { if (!c.animating) c.moveTo(x * 100, y * 100 + idx * UP_SPACE, 0) }); - } - } + function handleMouseUp() { + window.removeEventListener("pointermove", handleMouseMove); + + // Implement card snapping or other dragging behavior + if (snapped < 0) { + moving_cards.forEach((c, idx) => + c.moveTo(initialX * 100, initialY * 100 + idx * UP_SPACE, ANIMATION_TIME + 10 * idx) + ); + } else { + let [u, v] = curPilePos[snapped]; + moving_cards.forEach((c, idx) => c.moveTo(u * 100, v * 100 + idx * UP_SPACE, 0)); + game.make_move(card, origin, snapped); + } + } - function handleMouseUp() { - window.removeEventListener('pointermove', handleMouseMove); + window.addEventListener("pointermove", handleMouseMove); + window.addEventListener("pointerup", handleMouseUp, { once: true }); + } - // Implement card snapping or other dragging behavior - if (snapped < 0) { - moving_cards.forEach((c, idx) => c.moveTo(initialX * 100, initialY * 100 + idx * UP_SPACE, ANIMATION_TIME + 10 * idx)); - } else { - let [u, v] = curPilePos[snapped]; - moving_cards.forEach((c, idx) => c.moveTo(u * 100, v * 100 + idx * UP_SPACE, 0)); - game.make_move(card, origin, snapped); - } + let cards = []; - } + function deal() { + game.make_move(null, 0, 0); + } - window.addEventListener('pointermove', handleMouseMove); - window.addEventListener('pointerup', handleMouseUp, { once: true }); - } + function onMouseDown(event) { + if (event.which !== 1) return; - let cards = []; + const cardDOM = event.target.closest(".card"); - function deal() { - game.make_move(null, 0, 0); + if (cardDOM) { + const card = cardArray[parseInt(cardDOM.dataset.cardId)]; + moveCard(event, card); + // some how it fix the default stuff :)) + event.preventDefault(); + return; } - function onMouseDown(event) { - if (event.which !== 1) - return; - - const cardDOM = event.target.closest('.card'); - - if (cardDOM) { - const card = cardArray[parseInt(cardDOM.dataset.cardId)]; - moveCard(event, card); - // some how it fix the default stuff :)) - event.preventDefault(); - return; - } - - if (event.target.closest('#deal')) { - deal(); - event.preventDefault(); - return; - } + if (event.target.closest("#deal")) { + deal(); + event.preventDefault(); + return; } + } - gameBox.addEventListener('pointerdown', onMouseDown); -} \ No newline at end of file + gameBox.addEventListener("pointerdown", onMouseDown); +}