]> git.cworth.org Git - lmno.games/commitdiff
anagrams: Add a bunch of keybindings to make things keyboard friendly
authorCarl Worth <cworth@cworth.org>
Sun, 8 Mar 2026 13:13:38 +0000 (09:13 -0400)
committerCarl Worth <cworth@cworth.org>
Sun, 8 Mar 2026 13:13:38 +0000 (09:13 -0400)
The most significant is that just typing a word starts a claim
implicitly. But we also add "space" for drawing a new letter from the
bag, and Y/N for voting.

anagrams/anagrams.jsx

index 52036c0d71dce3200a05f6863ffb7f0a673ed1dd..0d2397da4aa239112c5709d3f6d0102fa286e3d7 100644 (file)
@@ -258,6 +258,18 @@ class Game extends React.Component {
     if (is_me && data.timeout_ms > 0) {
       this._start_claim_timer(data.timeout_ms, data.warning_ms);
     }
+
+    /* If a letter was queued from a keyboard-initiated claim, take it. */
+    if (is_me && this._pending_key) {
+      const pending = this._pending_key;
+      this._pending_key = null;
+      const tile = this.state.center.find(
+        t => t.letter === pending && !this.state.revealing[t.id]
+      );
+      if (tile) {
+        this.take_letter(tile);
+      }
+    }
   }
 
   receive_claim_end(data) {
@@ -510,15 +522,36 @@ class Game extends React.Component {
    *****************************************************/
 
   on_key_down(e) {
-    if (!this.state.claim_active) return;
-
     /* Ignore if typing in an input field. */
     if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return;
+    if (!this.state.joined || this.state.game_over) return;
 
     const key = e.key.toUpperCase();
 
-    if (key === "BACKSPACE" || key === "DELETE") {
-      /* Return the last letter from the rack. */
+    /* Vote: Y to accept, N to reject. */
+    if (this.state.vote_pending && !this.state.my_vote) {
+      if (key === "Y") { e.preventDefault(); this.vote(true); return; }
+      if (key === "N") { e.preventDefault(); this.vote(false); return; }
+    }
+
+    /* Spacebar: deal a letter from the bag. */
+    if (e.key === " " && !this.state.claim_player) {
+      e.preventDefault();
+      if (this.state.bag_remaining > 0) {
+        this.deal_letter();
+      }
+      return;
+    }
+
+    /* Escape: cancel claim. */
+    if (key === "ESCAPE" && this.state.claim_active) {
+      e.preventDefault();
+      this.cancel_claim();
+      return;
+    }
+
+    /* Backspace/Delete: return last letter during claim. */
+    if ((key === "BACKSPACE" || key === "DELETE") && this.state.claim_active) {
       e.preventDefault();
       const rack = this.state.claim_rack;
       if (rack.length > 0) {
@@ -527,7 +560,8 @@ class Game extends React.Component {
       return;
     }
 
-    if (key === "ENTER") {
+    /* Enter: submit word during claim. */
+    if (key === "ENTER" && this.state.claim_active) {
       e.preventDefault();
       if (this.state.claim_rack.length >= 4) {
         this.submit_word();
@@ -535,13 +569,18 @@ class Game extends React.Component {
       return;
     }
 
-    if (key === "ESCAPE") {
+    /* Everything below requires a letter key. */
+    if (!/^[A-Z]$/.test(key)) return;
+
+    /* If not claiming yet, start a claim and queue the letter. */
+    if (!this.state.claim_active) {
+      if (this.state.claiming || this.state.claim_player) return;
       e.preventDefault();
-      this.cancel_claim();
+      this._pending_key = key;
+      this.start_claim();
       return;
     }
 
-    if (!/^[A-Z]$/.test(key)) return;
     e.preventDefault();
 
     /* Find a matching letter in the center and claim it. */