| 1 | import { useEffect, useRef } from 'react'; |
| 2 | import type { Prefs } from '../game/prefs'; |
| 3 | import { WIND_EN, WIND_NAMES } from '../game/tiles'; |
| 4 | import type { SeatId } from '../game/types'; |
| 5 | import type { NetSession } from '../net/session'; |
| 6 | import { Bi, BiText } from './Bi'; |
| 7 | import { useLang, useT } from './lang'; |
| 8 | |
| 9 | const SEATS: SeatId[] = [0, 1, 2, 3]; |
| 10 | |
| 11 | /** |
| 12 | * The room before the deal: who is here, who is sitting where, and — for the |
| 13 | * host — the button that starts it. Seats nobody takes go to the computer, so |
| 14 | * two friends and two bots is a game, not a wait. |
| 15 | */ |
| 16 | export function OnlineLobby({ session, prefs }: { session: NetSession; prefs: Prefs }) { |
| 17 | const t = useT(); |
| 18 | const lang = useLang(); |
| 19 | const seats = session.seats; |
| 20 | const me = session.room.me.id; |
| 21 | |
| 22 | // Walking in unseated: take the first free chair rather than making everyone |
| 23 | // pick one. Moving is one tap on another seat — including one somebody |
| 24 | // already holds, which is how two people share a hand. |
| 25 | const claimed = useRef(false); |
| 26 | useEffect(() => { |
| 27 | if (claimed.current || session.mySeat !== null) return; |
| 28 | if (seats.some((s) => s.ids.length === 0)) { |
| 29 | claimed.current = true; |
| 30 | session.claimSeat(null); |
| 31 | } |
| 32 | }, [session, seats]); |
| 33 | |
| 34 | const seated = seats.filter((s) => s.ids.length > 0).length; |
| 35 | |
| 36 | return ( |
| 37 | <div className="lobby"> |
| 38 | <h1>{lang === 'en' ? 'Taiwanese Mahjong' : '台灣麻將'}</h1> |
| 39 | <p className="sub"> |
| 40 | {t('線上對戰', 'online game', ' · ')} · {session.playerCount}{' '} |
| 41 | {t('人在房', 'in the room')} |
| 42 | </p> |
| 43 | <p className="hint"> |
| 44 | <BiText |
| 45 | zh="把這頁的連結傳給朋友,他們一開就進同一房。" |
| 46 | en="Share this page's invite link — anyone who opens it lands at this table." |
| 47 | /> |
| 48 | </p> |
| 49 | |
| 50 | <div className="pick-grid"> |
| 51 | {SEATS.map((i) => { |
| 52 | const slot = seats[i]; |
| 53 | const mine = slot.ids.includes(me); |
| 54 | const held = slot.ids.length; |
| 55 | return ( |
| 56 | <button |
| 57 | key={i} |
| 58 | className={`btn big-btn ${mine ? '' : 'ghost'}`} |
| 59 | onClick={() => !mine && session.claimSeat(i)} |
| 60 | > |
| 61 | {t(WIND_NAMES[i], WIND_EN[i])} |
| 62 | <em> |
| 63 | {held === 0 |
| 64 | ? t('空位,開局時由電腦代打', 'open — a bot if nobody sits', ' · ') |
| 65 | : mine |
| 66 | ? `${slot.name || t('你', 'you')}${ |
| 67 | held > 1 ? ` +${held - 1}` : '' |
| 68 | } · ${t('你', 'you')}` |
| 69 | : `${slot.name}${held > 1 ? ` +${held - 1}` : ''} · ${t('可同坐', 'join them')}`} |
| 70 | </em> |
| 71 | </button> |
| 72 | ); |
| 73 | })} |
| 74 | </div> |
| 75 | |
| 76 | <div className="lobby-buttons"> |
| 77 | {session.isHost ? ( |
| 78 | <button |
| 79 | className="btn big-btn" |
| 80 | disabled={seated === 0} |
| 81 | onClick={() => session.startOnline(prefs.rules)} |
| 82 | > |
| 83 | <Bi |
| 84 | zh="開局" |
| 85 | en="Deal" |
| 86 | sub={ |
| 87 | seated < 4 |
| 88 | ? t(`${4 - seated} 個電腦`, `${4 - seated} bots fill in`) |
| 89 | : undefined |
| 90 | } |
| 91 | /> |
| 92 | </button> |
| 93 | ) : ( |
| 94 | <p className="hint">{t('等房主開局', 'waiting for the host to deal', ' · ')}</p> |
| 95 | )} |
| 96 | </div> |
| 97 | {session.err && <p className="hint">{session.err}</p>} |
| 98 | </div> |
| 99 | ); |
| 100 | } |