mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 12:28:54 +00:00
db7093bbe5813d0abcf7fa0d125af678ff771256
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
db7093bbe5 |
fix(web): make the game comfortable to play on a phone and in both themes
Applies the findings of the three UI/UX reviews. The two that cost a player something every single turn: - The countdown reddened during the opponent's turn. It read the deadline, which the server rewrites on every handover, and never asked whose turn it was — a panic spike, every turn, about nothing the player could act on. It now mutes when the clock is not theirs, and signals urgency by thickening as well as reddening, because green to red is the pair a red-green colour blindness cannot separate. - On a phone the keyboard closed on every turn and could not be reopened. `disabled` on the focused word field blurs it, and the recovering focus() ran on a WebSocket message, which iOS will not open a keyboard for. The field stays editable and the submit button carries the turn instead; focus is asked for inside the gesture that submitted. A draft aimed at a syllable the game has moved past is now corrected rather than kept. The board was also not holding together as the game grew. `min-height` on the shell left nothing with a definite height to shrink against, so the chain kept its full height and the page got taller every turn, walking the word field and the chat off the bottom of the screen. The shell now has a real height, the chain gives up its space first with a floor so it cannot vanish, and `main` scrolls only as a last resort. Online play had one true dead end: nothing on the pre-room screen showed the connection at all, so a server that was down left an enabled button and a screen that never changed — while each impatient tap sent a real CreateRoom until the rate limiter answered. The badge is there now, requests disable their button, a stall says so, and the lobby draws the room's refusals beside the button that produced them instead of in a chat panel below the fold. A dropped player gets a retry rather than waiting out the backoff. An invite link asks a first-time guest for a name instead of seating them as "Người chơi" with no way to fix it. Accessibility and palette: - The turn change is announced. It is the state the whole game hangs on and it reached nobody who was not looking at that line. - --accent darkened to #12692f: accent text on its own tint measured 4.29:1. Every existing pair improves, white-on-accent included. - --border-strong added for the boundary of anything you type into or press, which needs 3:1 and had 1.47:1 light, 1.78:1 dark. - One focus ring, one 44px icon-button recipe, spacing and type scales, safe-area gutters, per-route titles, a heading per screen, a skip link, and reduced motion that stops the connection dot pulsing rather than flickering it a hundred thousand times a frame. Tests follow the two interactions that changed shape: resign and kick confirm inline with a second press, because confirm() blocks the frame loop the countdown runs on and hesitating could cost the turn it was protecting. joinRoomSeated waits for the seat before anybody speaks — the server replays a seat only the conversation from when it joined, so the old helper was racing the handshake. Verified: 189 unit tests, 40 end-to-end, no type errors, and the palette and layout measured at 360/400/768/1280 in both themes. |
||
|
|
e19b083dc0 |
feat(online): seat the chat beside the game and score the series
A room screen wide enough for it is two columns now: the game or the lobby on one side and the room's conversation on the other, so neither has to be scrolled past to reach the other. A phone keeps one column, the game first, with the chat folded behind an unread count while a game is on. One chat panel spans both phases by staying in the same place in the markup — a remounted panel reopened having read nothing, and handed the reader the lobby's conversation back as unread mail. The room also keeps the score of the series. PlayerSlot carries the games a seat has won since the room opened, credited before the lobby is broadcast so the players see it the moment a game ends, and shown in the lobby and beside each player's score on the board. It belongs to the seat rather than to the room, because vacating one is exactly when the name on it stops meaning the same person. The conversation reads as a log — one line per message, "name: text", each seat in its own colour — rather than as a stack of bubbles, which stops being legible once four people are talking. ChatMessage carries the seat that spoke, so a line is coloured by what the server said rather than by matching display names, and a line whose seat has been vacated is in nobody's colour. The board's word button gets a test id of its own: it and the chat's send button both read "Gửi", and the two are now on screen together. |
||
|
|
4e2e9e1433 |
feat(online)!: seat two to four players in a room
A room holds up to four people and needs two to start. Both numbers are server constants sent to the client in RoomState, so the lobby draws whatever the server allows and widening a room is a server change alone. Failing a turn eliminates that player rather than ending the game. The syllable and the used words survive them, the turn passes to whoever is next, and the last player standing wins. Two seats is that same rule seen from close up, which is why there is one implementation of it and not two. A dead end still costs the first player to face it their own clock, as before: they get their turn, and lose it. Everyone behind them has already seen that board, so they go out together rather than each sitting out a turn limit they cannot use — which leaves the player who closed the position standing, the same outcome two players get. A player who is knocked out keeps their seat. They watch the rest of the game, chat included, with only the word input gone, and everybody lands back in the same lobby when it ends. The result screen is the whole table, ranked by who outlasted whom, with each score reported beside the place rather than deciding it. The turn clock is deliberately not paused for a seat that has dropped, so a player who loses their connection on their own turn loses it the way anybody else would. Their reconnect window decides only whether they are still in the game afterwards. Any number of windows can be open at once, settled by one timer armed for the nearest of them. Starting waits for every guest, not merely the first: a room of four that began on one yes would have dealt three people a turn they never agreed to. Kicking names a seat and is still refused on a player who is ready, and on the owner's own — leaving is what an owner who wants out does, and it hands the room on. Joining stays a lobby thing: a room with a game running turns a latecomer away even with seats going spare, because there is no way to hand somebody a game already in progress. BREAKING CHANGE: RoomState, TurnUpdate and GameOver lose the fields that could only ever describe a second player, OpponentLeft is retired in favour of presence on RoomState, and suggestions move to the new PlayerEliminated — they describe the position that beat a player, which by the end of a longer game is nobody else's position. ProtocolVersion goes to 2, so a client built against 1 is refused with a readable error rather than decoding a frame that now means something else. |
||
|
|
d481a093ec |
feat(online): make a room a lobby with an owner and a ready-up
A room used to be a wrapper around one game: joining started it, and the room died with it unless both players accepted a rematch inside thirty seconds. It is now a lobby that outlives its games. Whoever created the room owns it and the other seat is the guest. The guest readies and the owner starts; the owner has no readiness of their own, because starting is the same statement. A finished game returns both to the lobby, where the next one is agreed exactly as the last was — the readiness that started a game is spent with it. A guest takes their readiness back before leaving, which is deliberate friction: a player the owner is waiting on should have to say so before walking away. The owner can free the seat of a guest who is not ready, and not of one who is — readiness is a commitment, not an inconvenience. An owner who leaves hands the room to whoever is left, unreadied, because they are the one who starts now. Something has to bound a room that outlives its games: the last player out closes it, as does ten minutes in a lobby nobody started a game in. A dropped connection is still not a player leaving — the seat is held for the reconnect window in the lobby as well as mid-game, so a refresh no longer costs somebody their room, and a resume lands in the lobby it left. The rematch handshake is retired, and RoomCreated and RoomJoined go with it. All three described part of what RoomState now describes in full, and three messages for one lobby is three ways for a client to hold a view of it the server never had. One snapshot per recipient, broadcast from the one place that knows an input is finished, so no handler can forget to send it. |