Initial commit.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { Card, ClientMessage, GameView, PlayerView } from '../types'
|
||||
import { CardView } from './CardView'
|
||||
|
||||
interface Props {
|
||||
view: GameView
|
||||
you: PlayerView
|
||||
send: (msg: ClientMessage) => void
|
||||
}
|
||||
|
||||
// ArrangePhase lets the player order their deck for battle. Leftmost card
|
||||
// fights first; food cards buff the next pet to their right... i.e. foods
|
||||
// apply "down" to the next pet later in the order. Drag cards or use the
|
||||
// arrow buttons to reorder, then lock in.
|
||||
export function ArrangePhase({ view, you, send }: Props) {
|
||||
const [order, setOrder] = useState<Card[]>(you.deck ?? [])
|
||||
const dragIndex = useRef<number | null>(null)
|
||||
const locked = you.ready
|
||||
|
||||
// If the server-side deck changes (shouldn't during arrange, but be safe),
|
||||
// resync.
|
||||
useEffect(() => {
|
||||
setOrder(you.deck ?? [])
|
||||
}, [you.deck])
|
||||
|
||||
function move(from: number, to: number) {
|
||||
if (to < 0 || to >= order.length) return
|
||||
setOrder((o) => {
|
||||
const next = [...o]
|
||||
const [c] = next.splice(from, 1)
|
||||
next.splice(to, 0, c)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
// Which pets do the foods land on? Compute buff per card for preview.
|
||||
const bonuses = new Map<string, number>()
|
||||
{
|
||||
let pendingApples = 0
|
||||
for (const c of order) {
|
||||
if (c.kind === 'food') {
|
||||
if (c.food === 'apple') pendingApples++
|
||||
} else {
|
||||
bonuses.set(c.id, pendingApples)
|
||||
pendingApples = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
const trailingFoods = (() => {
|
||||
let n = 0
|
||||
for (let i = order.length - 1; i >= 0 && order[i].kind === 'food'; i--) n++
|
||||
return n
|
||||
})()
|
||||
|
||||
const opponent = view.players.find((p) => p.seat !== view.youSeat)
|
||||
|
||||
if (locked) {
|
||||
return (
|
||||
<div className="centered">
|
||||
<h2>Order locked in ⚔️</h2>
|
||||
<p className="muted">
|
||||
Waiting for {opponent?.name ?? 'your opponent'} to arrange their deck…
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="arrange">
|
||||
<div className="shop-status">
|
||||
<span className="status-hot">Arrange your battle line</span>
|
||||
</div>
|
||||
<p className="hint">
|
||||
The <strong>leftmost</strong> card fights first. Food cards power up the
|
||||
next pet to their <strong>right</strong>.
|
||||
{trailingFoods > 0 && (
|
||||
<span className="warn-text">
|
||||
{' '}
|
||||
⚠️ {trailingFoods} food card{trailingFoods > 1 ? 's' : ''} at the end
|
||||
will be wasted!
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="arrange-row">
|
||||
<div className="arrange-marker">⚔️ first</div>
|
||||
{order.map((c, i) => (
|
||||
<div
|
||||
key={c.id}
|
||||
className="arrange-card"
|
||||
draggable
|
||||
onDragStart={() => (dragIndex.current = i)}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault()
|
||||
if (dragIndex.current !== null && dragIndex.current !== i) {
|
||||
move(dragIndex.current, i)
|
||||
dragIndex.current = i
|
||||
}
|
||||
}}
|
||||
onDragEnd={() => (dragIndex.current = null)}
|
||||
>
|
||||
<CardView card={c} bonus={bonuses.get(c.id) ?? 0} />
|
||||
<div className="arrange-arrows">
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
disabled={i === 0}
|
||||
onClick={() => move(i, i - 1)}
|
||||
aria-label="move earlier"
|
||||
>
|
||||
◀
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-ghost btn-sm"
|
||||
disabled={i === order.length - 1}
|
||||
onClick={() => move(i, i + 1)}
|
||||
aria-label="move later"
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<button
|
||||
className="btn btn-primary btn-big"
|
||||
onClick={() => send({ type: 'arrange', order: order.map((c) => c.id) })}
|
||||
>
|
||||
Lock in & battle ⚔️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user