feat(uno): enable drag-and-drop playing of cards to discard pile
- Add visual highlight for discard zone when card is dragged over it - Implement logic to detect valid plays against current discard pile - Trigger play execution (including wild color picker) when dropped on discard - Refactor `endCardDrag` to handle both reordering and playing paths
This commit is contained in:
parent
f829ecf93b
commit
85dff94613
|
|
@ -699,7 +699,17 @@ export default class UnoGame extends Phaser.Scene {
|
|||
slotIndicator.setDepth(D.card - 1);
|
||||
this.transientObjs.push(slotIndicator);
|
||||
|
||||
this.dragState = { cardIdx: handIdx, offsetX, offsetY, prevX: card.x, shadow, slotIndicator, insertIdx, spread, handStartX, handCenterY };
|
||||
const discardHighlight = this.add.graphics();
|
||||
discardHighlight.lineStyle(3, COLORS.accent, 0.9);
|
||||
discardHighlight.strokeRoundedRect(-CARD_W / 2 - 6, -CARD_H / 2 - 6, CARD_W + 12, CARD_H + 12, 12);
|
||||
discardHighlight.fillStyle(COLORS.accent, 0.18);
|
||||
discardHighlight.fillRoundedRect(-CARD_W / 2 - 6, -CARD_H / 2 - 6, CARD_W + 12, CARD_H + 12, 12);
|
||||
discardHighlight.setPosition(DISCARD_POS.x, DISCARD_POS.y);
|
||||
discardHighlight.setDepth(D.card + 1);
|
||||
discardHighlight.setAlpha(0);
|
||||
this.transientObjs.push(discardHighlight);
|
||||
|
||||
this.dragState = { cardIdx: handIdx, offsetX, offsetY, prevX: card.x, shadow, slotIndicator, discardHighlight, insertIdx, spread, handStartX, handCenterY, overDiscard: false };
|
||||
|
||||
card.setDepth(D.card + 10);
|
||||
this.tweens.add({ targets: card, scaleX: 1.08, scaleY: 1.08, duration: 120, ease: 'Cubic.easeOut' });
|
||||
|
|
@ -718,12 +728,50 @@ export default class UnoGame extends Phaser.Scene {
|
|||
card.setRotation(tiltRad);
|
||||
ds.prevX = pointer.x;
|
||||
|
||||
const newInsertIdx = Phaser.Math.Clamp(Math.round((card.x - ds.handStartX) / ds.spread), 0, Math.max(0, n - 1));
|
||||
if (newInsertIdx !== ds.insertIdx) {
|
||||
ds.insertIdx = newInsertIdx;
|
||||
this.tweens.killTweensOf(ds.slotIndicator);
|
||||
this.tweens.add({ targets: ds.slotIndicator, x: ds.handStartX + newInsertIdx * ds.spread, duration: 100, ease: 'Cubic.easeOut' });
|
||||
this._updateNonDraggedCards();
|
||||
const overDiscard = this._isDiscardDropTarget(card.x, card.y);
|
||||
if (overDiscard !== ds.overDiscard) {
|
||||
ds.overDiscard = overDiscard;
|
||||
if (overDiscard) {
|
||||
this.tweens.killTweensOf(ds.slotIndicator);
|
||||
ds.slotIndicator.setAlpha(0);
|
||||
this._settleNonDraggedCards();
|
||||
this.tweens.add({ targets: ds.discardHighlight, alpha: 1, duration: 150 });
|
||||
} else {
|
||||
ds.slotIndicator.setAlpha(1);
|
||||
this._updateNonDraggedCards();
|
||||
this.tweens.add({ targets: ds.discardHighlight, alpha: 0, duration: 150 });
|
||||
}
|
||||
}
|
||||
|
||||
if (!overDiscard) {
|
||||
const newInsertIdx = Phaser.Math.Clamp(Math.round((card.x - ds.handStartX) / ds.spread), 0, Math.max(0, n - 1));
|
||||
if (newInsertIdx !== ds.insertIdx) {
|
||||
ds.insertIdx = newInsertIdx;
|
||||
this.tweens.killTweensOf(ds.slotIndicator);
|
||||
this.tweens.add({ targets: ds.slotIndicator, x: ds.handStartX + newInsertIdx * ds.spread, duration: 100, ease: 'Cubic.easeOut' });
|
||||
this._updateNonDraggedCards();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isDiscardDropTarget(cardX, cardY) {
|
||||
if (!this.isLocalTurn() || this.gs.phase !== 'play') return false;
|
||||
const me = this.gs.players[0];
|
||||
const handCard = me.hand[this.dragState.cardIdx];
|
||||
if (!handCard || !isLegalPlay(this.gs, handCard, me.hand)) return false;
|
||||
return Math.abs(cardX - DISCARD_POS.x) < CARD_W * 1.2 && Math.abs(cardY - DISCARD_POS.y) < CARD_H * 1.2;
|
||||
}
|
||||
|
||||
_settleNonDraggedCards() {
|
||||
const ds = this.dragState;
|
||||
const n = this.gs.players[0].hand.length;
|
||||
let k = 0;
|
||||
for (let j = 0; j < n; j++) {
|
||||
if (j === ds.cardIdx) continue;
|
||||
const cardObj = this.localHandCards[j];
|
||||
this.tweens.killTweensOf(cardObj);
|
||||
this.tweens.add({ targets: cardObj, x: ds.handStartX + k * ds.spread, rotation: 0, scaleX: 1, scaleY: 1, duration: 100, ease: 'Cubic.easeOut' });
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -752,6 +800,32 @@ export default class UnoGame extends Phaser.Scene {
|
|||
const card = this.localHandCards[ds.cardIdx];
|
||||
const hand = this.gs.players[0].hand;
|
||||
const n = hand.length;
|
||||
|
||||
this.tweens.killTweensOf(ds.discardHighlight);
|
||||
ds.discardHighlight.setAlpha(0);
|
||||
|
||||
// ── Play path: dropped on discard pile ───────────────────────────────────
|
||||
if (ds.overDiscard) {
|
||||
const handCard = hand[ds.cardIdx];
|
||||
this.tweens.killTweensOf(card);
|
||||
card.setScale(1);
|
||||
card.setRotation(0);
|
||||
ds.shadow.setAlpha(0);
|
||||
this._settleNonDraggedCards();
|
||||
this.dragState = null;
|
||||
if (handCard.isWild) {
|
||||
this.pendingWildPlayCardId = handCard.id;
|
||||
this.openColorPicker((color) => {
|
||||
this.pendingWildPlayCardId = null;
|
||||
this.executeLocalPlay(handCard.id, color);
|
||||
});
|
||||
} else {
|
||||
this.executeLocalPlay(handCard.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Reorder path: dropped back in hand ───────────────────────────────────
|
||||
const finalIdx = Phaser.Math.Clamp(Math.round((card.x - ds.handStartX) / ds.spread), 0, n - 1);
|
||||
|
||||
this.tweens.killTweensOf(card);
|
||||
|
|
|
|||
Loading…
Reference in New Issue