Trie — follow prefixes
แต่ละขอบแทนตัวอักษร เริ่มจาก anchor แล้วเดินต่อเฉพาะ prefix ที่ยังมีในพจนานุกรม กิ่งที่เป็นไปไม่ได้ถูกตัดทันที
- Exact word / prefix
- O(L)
- Build
- O(ΣL)
- Memory
- O(number of nodes)
สร้าง Computer Science Capstone ที่เล่นได้จริง: เปรียบเทียบดัชนีห้าแบบ ตรวจสอบทุก legal move แล้วประกอบนโยบาย AI จากความน่าจะเป็น การเตรียมตาถัดไป ความเสี่ยงตอบโต้ และการควบคุมกระดาน
จะรีบเอาคะแนนสูงสุด ลากคำไปหา Triple Word เก็บตัวดีไว้รอตาหน้า หรือกั๊กช่อง anchor จนคู่แข่งหาคำสบาย ๆ ไม่ได้? ลองวางกระดาน ใส่ Rack แล้วให้ดัชนีทั้งห้าแบบเสนอคำตอบ จากนั้นลองจับผิดมันดู—เพราะคำที่เร็วและแรงที่สุดอาจไม่ใช่คำที่ฉลาดที่สุดเสมอไป
แต่ละขอบแทนตัวอักษร เริ่มจาก anchor แล้วเดินต่อเฉพาะ prefix ที่ยังมีในพจนานุกรม กิ่งที่เป็นไปไม่ได้ถูกตัดทันที
ตัวอักษรถูกแปลงเป็น frequency signature ทำให้ LISTEN และ SILENT เปิด bucket เดียวกัน จากนั้นใช้ subset ของ rack ดึงกลุ่มคำก่อนตรวจ anchor และคำตัด
นักศึกษาบูรณาการคณิตศาสตร์ไม่ต่อเนื่อง โครงสร้างข้อมูล ฐานข้อมูล ความน่าจะเป็น AI ที่อธิบายได้ API และวิศวกรรมประสิทธิภาพเป็น Game Engine ที่สาธิต วัดผล และปกป้องเหตุผลการออกแบบได้ ผลงานที่เล่นได้จึงเป็นทั้งชิ้นงาน Capstone และหลักฐานของทุกการตัดสินใจเชิงวิศวกรรม
ออกแบบการแทนพจนานุกรม Rack และกระดานให้กำจัด candidate ที่เป็นไปไม่ได้ตั้งแต่ต้น แล้ววิเคราะห์ต้นทุนจริงของ prefix traversal, recursion, subset generation และ move enumeration แบบ output-sensitive
การ insert CAT เริ่มจาก root แล้วเดินหรือสร้าง edge C, A และ T จากนั้น mark T เป็น terminal การ insert CAR ใช้ C และ A ร่วมกัน สร้าง R แล้ว mark terminal การค้นคำสำเร็จเมื่อ edge ครบทุกตัวและ node สุดท้ายเป็น terminal ส่วน prefix search ไม่ต้องตรวจ terminal
INSERT(word):
node = root
for ch in word:
node = node.child.get_or_create(ch)
node.terminal = true
CONTAINS(word):
follow every edge; return final.terminalที่ช่องว่าง traversal แตกกิ่งเฉพาะ child ที่มีใน rack และผ่าน cross-check mask ส่วนช่องที่มีตัวอยู่แล้วจะเดินตามตัวนั้นโดยไม่ใช้ rack ดังนั้น state ประกอบด้วย Trie node, ตำแหน่งกระดาน, multiset ที่เหลือ, ตัวที่วาง และสถานะว่าครอบ anchor แล้วหรือยัง
EXTEND(node, square, rack):
if square is fixed: follow its one edge
else for child in node.children:
if rack has child AND crossMask allows child:
consume tile; recurse
emit only terminal states covering an anchorComplexity: exact lookup เป็น O(L) แต่ move generation เป็น output-sensitive ขึ้นกับจำนวน anchor A, branching b, rack states และคำตอบ Z ไม่ใช่เพียง O(L)
Set, multiset, equivalence relation, combinatorics และทฤษฎีบทมูลฐานของเลขคณิตอธิบายว่าเหตุใด anagram จึงมี identity ร่วมกัน และเหตุใด Rack เจ็ดตัวจึงมีครอบครัวทางเลือกแบบ exponential ที่ยังมีขอบเขต
กำหนดจำนวนเฉพาะต่างกันให้แต่ละตัว A=2, B=3, C=5, …, Z=101 แล้วคูณ prime ของทุกตัว ทฤษฎีการแยกตัวประกอบเฉพาะทำให้ anagram มีผลคูณเดียวกัน เช่น ABBA และ BABA เท่ากับ 2²×3²=36 ส่วน multiset อื่นให้จำนวนเต็มอื่น
primeKey("LISTEN")
= p[L] × p[I] × p[S] × p[T] × p[E] × p[N]
= primeKey("SILENT")วิธีนี้เหมาะสำหรับการสอน แต่ผลคูณ 64-bit อาจ overflow และการ mod ด้วยขนาดตารางทำให้เกิด collision จึงยังต้องเปรียบเทียบ signature ต้นฉบับเมื่อ key ชนกัน
Engine เก็บจำนวนตัวอักษร 26 ค่า เนื่องจาก count ต่อตัวมีขนาดเล็ก จึง pack count 4-bit สองค่าต่อหนึ่ง byte ทำให้ 26 ค่าเป็น key 13 bytes ที่ไม่เสียข้อมูล จากนั้น FNV-1a 64-bit เลือก slot ใน open-addressed table, ใช้ linear probing เมื่อชน และเปรียบเทียบครบทั้ง 13 bytes ก่อนยอมรับ hit
offset = 1469598103934665603
for byte in packedSignature:
hash = (hash XOR byte) × 1099511628211
slot = hash AND (capacity − 1)
while occupied AND key != storedKey:
slot = (slot + 1) AND (capacity − 1)Average: probe O(1) เมื่อควบคุม load factor; Worst case: O(M) หาก clustering บังคับให้ไล่ตาราง
Blank มีคะแนนศูนย์แต่แทน A–Z ได้ จึงไม่ควรผูกกับ prime เดียว สำหรับ subset ที่มี blank ตัวสร้าง candidate แตก 26 ทาง เพิ่ม count ของตัวแทน pack signature แล้วเก็บใน set หากมีสอง blank จะแตกเป็นคู่ตัวอักษร จากนั้น deduplicate count vector ที่เหมือนกัน ตอนวางจริง move ต้องบันทึกว่าตัวใดมาจาก blank เพื่อให้คะแนนยังเป็นศูนย์แม้กระดานแสดงตัวอักษรที่เลือก
ค่า median แบบ warm ใน persistent worker เดียวกัน วัดเฉพาะการสร้าง subset; full-word lookup ยังใช้ packed database key
| Rack | Prime | Packed | Speedup |
|---|---|---|---|
| AEINRST | 3.917 µs | 9.935 µs | 2.536× |
| AABBCDE | 2.742 µs | 6.770 µs | 2.470× |
| AAAAAAA | 1.008 µs | 5.657 µs | 5.612× |
ทุก Engine คืน candidate set ตรงกัน เวลาเป็น warm uncached median รวมสร้าง subset, lookup, แตก bucket, deduplicate และ sort แต่ยังไม่รวมการวางบนกระดาน
| Rack | Words | Prime | Trie | Prime faster |
|---|---|---|---|---|
| AEINRST | 343 | 141.986 µs | 1,283.597 µs | 9.040× |
| AABBCDE | 42 | 22.478 µs | 226.228 µs | 10.064× |
| AAAAAAA | 1 | 6.428 µs | 13.447 µs | 2.092× |
Warm uncached median หน่วย microseconds; 1,000 iterations × 7 repeats ค่ายิ่งต่ำยิ่งดี ทุกแถวตรวจ candidate-set equivalence แบบ exact ก่อนจับเวลา
| Rack | Words | Prime Product | Compact/Numba Packed | Python Memory Hash | SQLite Hash | Packed Trie |
|---|---|---|---|---|---|---|
| AEINRST | 343 | 145.223 | 157.805 | 528.645 | 661.396 | 1,258.651 |
| AABBCDE | 42 | 22.556 | 28.167 | 229.290 | 287.744 | 216.450 |
| AAAAAAA | 1 | 6.498 | 11.426 | 20.364 | 31.195 | 13.226 |
Leave-one-out เริ่มจาก rack เต็ม ตัดหนึ่งตำแหน่ง บันทึก multiset ที่เหลือ แล้วเรียกซ้ำ สำหรับ BEGIN ชั้นแรกได้ EGIN, BGIN, BEIN, BEGN และ BEGI เมื่อทำต่อจะได้ subset ขนาดเล็กทุกชุด ต้อง canonicalize ด้วยการ sort หรือ frequency count เพราะการตัดตัวซ้ำคนละตำแหน่งไม่ควรสร้างงานซ้ำ
LEAVE-ONE-OUT(tiles, seen):
key ← canonical(tiles)
if key in seen: return
seen.add(key); HASH-LOOKUP(key)
for each distinct position i:
recurse(tiles without i, seen)กำหนดตำแหน่ง rack เป็น 0…R−1 จำนวนเต็ม mask m เลือกตำแหน่ง i เมื่อ bit i เป็น 1 การวนจาก 1 ถึง 2ᴿ−1 สร้าง position subset ที่ไม่ว่างครบหนึ่งครั้ง จากนั้น canonical signature รวมกรณีที่ต่างกันเพียงสลับตัวอักษรซ้ำ
for mask = 1 .. (1 << R) - 1:
counts = [0] × 26; blanks = 0
for i = 0 .. R - 1:
if mask & (1 << i):
add rack[i] to counts or blanks
signatures.add(counts, blanks)| วิธี | จุดเด่น | ข้อควรระวัง |
|---|---|---|
| Leave-one-out | เห็น recursion และ memoization ชัด | เกิด state ซ้ำหากไม่มี canonical key |
| Bit mask | สั้น เป็น iterative และแบ่งงานง่าย | มี position duplicate ก่อน deduplicate |
| Count-state DP | ทำงานบน multiset โดยตรง | index ซับซ้อนขึ้น |
Hash pipeline: subset → รวมตัวที่ตรึงบนกระดาน → signature O(L) → bucket lookup เฉลี่ย O(1) → ตรวจ anchor, blank, premium และคำตัดทุกคำ
หลังออกแบบ key นักศึกษาต้องตัดสินใจว่าจะจัดเก็บอย่างไร รวม anagram อย่างไร รับประกัน integrity อย่างไร และแยกฐานข้อมูลตอน build ออกจาก runtime index ที่เน้นการอ่านอย่างไร
พจนานุกรม SOWPODS ที่ normalize แล้วมี 267,752 คำ แต่มีเพียง 237,740 signatures เพราะ anagram อยู่ใน equivalence class เดียวกัน หากเก็บหนึ่งแถวต่อหนึ่งคำจะทำ key ซ้ำ ฐานข้อมูลจึงเก็บหนึ่งแถวต่อ signature และรวมคำใน bucket ที่คั่นด้วย zero byte
anagrams| Column | Type | หน้าที่ |
|---|---|---|
| signature | BLOB PK | packed count vector 13 bytes |
| words | BLOB NOT NULL | กลุ่ม anagram คั่นด้วย NUL |
| word_count | INTEGER | ขนาด bucket และ diagnostics |
| min_length | INTEGER | กรองความยาวเบื้องต้น |
metadata| Column | Type | ตัวอย่าง |
|---|---|---|
| key | TEXT PK | schema, source, words |
| value | TEXT | warin.scrabble.hash-lexicon.v1 |
ค่า SHA-256 ผูก binary index เข้ากับ input ที่ normalize แล้วอย่างแน่นอน หาก manifest ไม่ตรง ระบบปฏิเสธแทนการใช้พจนานุกรมคนละชุดโดยไม่แจ้ง
ปกติ SQLite มี integer rowid ซ่อนอยู่และมี primary-key index แยก แต่ signature BLOB เป็นทั้ง identity และ access path อยู่แล้ว WITHOUT ROWID จึงเก็บแถวใน primary-key B-tree ลด identity และ lookup ที่ซ้ำซ้อน
SQLite durable, portable และ rebuild ง่าย แต่ B-tree lookup เป็น O(log S) ไม่ใช่ hash จริง เมื่อ worker เริ่มทำงานจึงโหลดทุกแถวเข้า compact NumPy arrays และ open-addressed table SQLite เป็น source of truth ส่วน in-memory tableเป็น hot read path
ทุก mode ใช้กระดาน Rack พจนานุกรม และตัวตรวจตำแหน่ง/คะแนน PHP ชุดเดียวกัน เปลี่ยนเฉพาะวิธีดึง candidate การ probe แบบ O(1) โดยเฉลี่ยหนึ่งครั้งไม่ได้ทำให้ Solver ทั้งระบบเป็น O(1)
ต้นไม้ prefix แบบ binary อ่านอย่างเดียว ตัด prefix ที่เป็นไปไม่ได้ตั้งแต่ anchor การตรวจคำยาว L เป็น O(L) และ mode นี้ไม่ query SQLite ตอนทำงาน
ลายเซ็นจำนวนตัวอักษร 13 byte ชี้ไปยัง anagram bucket โดย SQLite ใช้ primary-key B-tree: O(log S) เมื่อมี S signatures แล้วจึงแตก bucket เป็นคำ
โหลด SQLite ครั้งเดียวเข้า dictionary ที่รันค้าง การ probe เฉลี่ย O(1) และเทียบ key 13 byte จริงเพื่อจัดการ collision
fast path ตั้งต้นใช้ key แบบ immutable ใน NumPy, slot int32 แบบ open addressing และ compile การสร้าง subset กับ batch probe ด้วย Numba
กำหนด A=2, B=3, … แล้วเปลี่ยนคำเป็นผลคูณที่แน่นอน จำนวนเฉพาะเปรียบเหมือนแม่สีที่แบ่งย่อยไม่ได้ การคูณผสมตัวอักษรโดยยังรักษาว่าแต่ละสีปรากฏกี่ครั้ง ลำดับหายไป—LISTEN และ SILENT กลายเป็นเงารูปเดียวกันทางคณิตศาสตร์—แต่การแยกตัวประกอบเฉพาะยังถอด multiset กลับได้ ทฤษฎีบทนามธรรมจึงกลายเป็นดัชนีที่ทั้งสง่างามและรันได้จริง Rack 7 ตัวใช้ uint64/Numba ส่วนคำยาวบนกระดานใช้จำนวนเต็ม exact ของ Python
เวลา solve ทั้งหมดยังรวมการสร้าง candidate Trie ของกลุ่ม Hash, anchor traversal, ตรวจคำตัด, คิดคะแนน, sort และ API overhead ส่วน In-memory และ Prime initialize แบบ lazy จึงควรรายงาน cold และ warm แยกกัน
กระดานสังเกตได้ แต่ Rack คู่แข่งและการจั่วในอนาคตเป็นตัวแปรแฝง การสุ่มแบบไม่คืนกลับ ความน่าจะเป็นแบบมีเงื่อนไข และ expected value เปลี่ยนข้อมูลที่ไม่สมบูรณ์เป็นปริมาณที่ตรวจและจำลองได้
เราไม่เห็น rack คู่แข่ง Engine จึงไม่แต่ง rack ขึ้นเอง แต่หักตัวอักษรบนกระดานและ rack ของเราออกจากจำนวนตัวมาตรฐาน แล้วประเมินว่าตัวที่ต้องใช้ตอบโต้จะปรากฏในตัวอย่าง 7 ตัวแบบไม่คืนกลับหรือไม่
คะแนนกติกาจริง รวม premium, คำตัด และ bingo bonus
Risk EV = P(reply) × counter score
ตรวจ S, ES, ED, ING และ hook รูปแบบอื่น
Setup EV = P(draw) × future score
ให้คุณค่ากับ leave และ hook สำหรับตาหน้า
ลงโทษ anchor ใหม่ที่พาไปหา Double/Triple Word
Move กลายเป็น feature vector ที่อธิบายได้ นักศึกษาเลือกเพิ่มหรือตัด heuristic ตรวจสอบ policy ที่เกิดขึ้น แล้วใช้ข้อมูล self-play เรียนและประเมินน้ำหนักโดยไม่ซ่อนกระบวนการตัดสินใจ
การหยิบตัวอักษรไม่เป็นอิสระ เพราะหยิบ S หนึ่งตัวแล้วโอกาส S ตัวต่อไปเปลี่ยน Binomial จึงเป็นเพียง approximation ส่วน Hypergeometric นับ rack ขนาด d จาก unseen N ตัวที่มี useful tile K ตัวได้ตรงกับการสุ่มแบบไม่คืนกลับ
P(X=x) = C(K,x) C(N−K,d−x) / C(N,d)
P(X≥1) = 1 − C(N−K,d) / C(N,d)สำหรับ extension หลายตัว เช่น ING Engine ใช้ multivariate count โดย I, N และ G ที่ต้องใช้ต้องมีครบ และ blank เป็นทรัพยากรทดแทน
การตอบโต้ 40 คะแนนที่มีโอกาส 0.20 ให้ Risk EV 8 ไม่ได้ทำนายว่าคู่แข่งจะได้ 8 คะแนน แต่หมายถึงโจทย์เทียบเท่าที่เล่นซ้ำเสียเฉลี่ย 8 คะแนน Variance ยังสำคัญ: policy แบบ risk-neutral เปรียบเทียบค่าเฉลี่ย ส่วน risk-averse อาจลงโทษ tail loss หรือ conditional value-at-risk
แต่ละ move กลายเป็น feature vector ได้แก่ immediate score, hook risk, setup value, opening risk, anchor denial, rack leave และ premium exposure จากนั้น linear policy ที่กำหนดด้วยมือใช้จัดอันดับ ทุก term มองเห็นได้ นักศึกษาจึง audit ได้ว่าทำไม HARN ชนะ HARNS
เก็บ feature vector และผลจาก self-play แล้วใช้ linear regression ทำนาย score difference, logistic regression ทำนาย win probability หรือ ranking model เรียน pairwise preference ต้อง split ตามเกมเต็ม ไม่ใช่ราย move เพื่อกัน leakage, standardize features ก่อนเรียน coefficient, เปรียบเทียบกับ hand policy และรายงาน confidence interval ข้ามหลาย seed
Anchor คือช่องว่างที่ติดกับตัวเดิม ตัวอักษรแนวตั้งฉากสร้าง pattern เช่น L_B ยอมรับเฉพาะ A ดังนั้น mask มีเพียงหนึ่ง bit mask กว้างเปิดทางเลือก ส่วน mask แคบช่วยกั๊กทางลง
allowed(anchor) = letters making
every perpendicular word legal
L _ B → allowed = { A }AI คำนวณโอกาสที่คู่แข่งไม่มีตัวใน allowed set ของ constrained anchor แต่ละช่อง แล้วเฉลี่ยเป็น Anchor Denial ส่วน 1-Tile Pass ตรวจ union ทั้งกระดาน ทั้งสองค่าไม่ใช่โอกาส Pass ที่แน่นอน เพราะอาจยังมีคำที่ลงหลายตัว
อ่านค่าแบบนี้: denial สูงหมายถึงประตูเปิดยาก ส่วน opening risk ต่ำหมายถึงประตูนั้นไม่พาไปยังช่องโบนัสสำคัญ
มองการกด Solve หนึ่งครั้งเป็นข้อความที่เดินทางในระบบกระจายขนาดเล็ก: Browser serialize สถานะกระดาน, PHP gateway ตรวจสอบและส่งต่อ และ persistent Python service ทำ indexed search นักศึกษาไล่ request ข้าม process และ container แยก transmission latency ออกจาก computation time ออกแบบ timeout/retry policy และรักษาความหมายที่ต่างกันระหว่าง “ไม่มี legal move” กับ “service ติดต่อไม่ได้”
กระดาน render จาก state model เดียว: 225 ช่อง, Rack สองชุด, คะแนน, turn, bag และ history Event handler ต้องแก้ state ก่อนแล้วจึง render การแยก state ออกจาก HTML ป้องกันไม่ให้กระดานที่มองเห็นกลายเป็นฐานข้อมูลที่ไม่น่าเชื่อถือ ส่วน responsive CSS รักษารูปทรงสี่เหลี่ยมและลำดับการอ่านทั้ง desktop และ mobile
state → render(board, rack, score)
user event → validate → update state → renderการกด Solve เดินผ่าน idle → loading → success, empty result หรือ failure ต้องกันการส่งซ้ำระหว่าง loading แยก “ไม่มีคำลง” ออกจาก network error และแสดงหลักฐานจาก server แทนการคำนวณซ้ำไม่ตรงกันใน Browser ทุก control ต้องมี label ใช้ keyboard ได้ และเห็น focus ชัดเจน
fetch(request)
.then(validateHTTP)
.then(renderMoves)
.catch(renderFailure)POST /pages/scrabble-api.php
Content-Type: application/json
{
"board": [15 strings × 15 cells],
"rack": "AEINRST",
"limit": 20,
"engine": "trie" | "sqlite-hash" | "memory-hash"
| "compact-numba" | "prime-product",
"adversarial": true
}response คืน identity ของ lexicon, timing, engine statistics, unseen probabilities และรายการ moves แต่ละ move มีพิกัด ทิศทาง ตัวที่วางใหม่ คะแนน และ heuristic decomposition เมื่อร้องขอ
GET /health
→ status, backend, signatures,
table_bytes, startup_ms
POST /candidates
{ "board": [...], "rack": "AEINRST" }
→ words, hash_lookups, hash_hits,
signature_ms, hash_query_ms,
worker_ms, cache_hitFastAPI ตรวจว่ากระดานมี 15 แถว ASCII และ rack ยาว 1–7 ตัว worker โหลด 237,740 signatures ครั้งเดียวตอน lifespan startup และ JIT-compile Numba probe ครั้งเดียว จากนั้น request ปกติไม่ rebuild index
200 success · 422 invalid client data · 500 internal failure · 502 gateway cannot obtain a valid worker response.
แยกวัด serialization, network, signature generation, hash lookup, placement และ rendering เพราะ wall time ไม่เท่ากับ algorithm time
ตรวจความยาวและชุดตัวอักษรทุก external boundary อย่าเชื่อ limit ใน browser เพราะ client ส่ง HTTP เองได้
ใช้ finite timeout, health check และ fallback ที่ระบุชัด อย่าแปลง worker failure เป็น “ไม่มีคำลง” เพราะ failure กับ empty result เป็นคนละ state
สร้างห้องทดลองเป็นหน้าเว็บแบบมี state โดยไม่ซ่อนกลไกการทดลอง: semantic HTML กำหนดความหมายให้กระดาน ตัวควบคุม และตารางผล, responsive CSS ทำให้ 225 ช่องยังใช้งานได้หลายขนาดจอ, JavaScript ดูแล turn, rack และ pending request ส่วน Fetch เชื่อม state เหล่านี้กับ API นักศึกษาต้อง validate input ทั้ง client และ server แสดงสถานะ loading, error, empty และ success ให้ชัด ป้องกันการส่งซ้ำ และทำให้ keyboard กับ screen reader ใช้งานได้เทียบเท่า pointer
board array เป็น source of truth เหตุการณ์จาก click และ keyboard ต้องแก้ model ก่อน แล้ว render function จึงสร้าง tile, score และ controls จาก state เดียวกัน การแยกหน้าที่ model กับ DOM ป้องกันกรณีช่องดูเหมือนมีตัวอักษรแต่ Solver ได้รับช่องว่าง ระหว่าง Fetch ใช้ pending flag ปิดการกด Solve ซ้ำ และใช้ AbortController ยกเลิก request เก่าเมื่อกระดานเปลี่ยน
event → validate → update state → render
solve → pending → fetch → parse → render result
error → restore controls → explain failureInterface ที่แข็งแรงต้องแยก idle, editing, loading, success, no-move, validation-error และ service-error โดย HTTP 200 ที่ได้ array ว่างไม่ใช่ HTTP 502 การแสดงทั้งสองอย่างว่า “ไม่มีคำลง” ทำให้นักศึกษาเข้าใจระบบผิด ส่วนพารามิเตอร์ภาษา Engine ที่เลือก และค่าฟอร์มควรมีพฤติกรรมที่คาดเดาได้เมื่อย้อนหน้า เปลี่ยนภาษา หรือ refresh
แจ้ง rack, ตัวอักษร และรูปทรงกระดานที่ผิดได้ทันที
เป็นขอบเขตตัดสินจริง ห้ามเชื่อ state จาก browser โดยตรง
ประกาศ progress และ error ด้วย focus management กับ live region
รักษาคะแนน พิกัด และการ sort ไม่ใช่เพียงย่อหน้าตา
ไล่ Trie nodes และนับ unique subset signatures
พิสูจน์การแทนด้วย prime product และนับ unique multiset ของ Rack
normalize bucket schema แล้วเทียบ query plan และ storage
ตรวจ Hypergeometric ด้วย simulation และ confidence interval
เปิด–ปิด heuristic ทีละส่วน ทำ self-play และวัดความไวของ policy
แยก API latency จาก solver time และทดสอบ failure semantics
ออกแบบ UI state, ตรวจ input และทดสอบ responsive interaction ที่ใช้ keyboard ได้
ผมสอน Design and Analysis of Data Structures and Algorithms มาหลายปี และไม่อยากให้นักศึกษาเบื่อกับสูตรหรือโค้ดที่แยกจากโลกจริง จึงออกแบบบทเรียนเป็น game engine ที่จับต้องได้: ทุกโครงสร้างข้อมูลต้องค้นคำที่ลงได้จริง คำนวณคะแนน และอธิบายต้นทุนของการค้นให้เห็นตรงหน้า
SILENT และ LISTEN ถูกแปลงเป็นลายเซ็นความถี่ตัวอักษรเดียวกัน การสร้างลายเซ็นเป็น O(n) ส่วนการเปิด bucket ใน hash table เป็น O(1) โดยเฉลี่ย
สำหรับ BEGIN เราตัดตัวอักษรแบบ recursive แล้วค้น EGIN, BGIN, BEIN … จนถึง GIN หรือ IN เมื่อ rack มีเพียง 7 ตัว จำนวน subset จึงมีขอบเขตและเตรียมไว้ก่อนตรวจตำแหน่งได้
การพบใน hash บอกเพียงว่าคำใดใช้ multiset ที่มีได้ Engine ยังต้องตรวจ anchor ทิศทาง ช่องโบนัส blank และคำตัดทุกคำก่อนถือว่าเป็น legal move
เส้นทาง Hash ปัจจุบันต้องสร้าง signatures, query SQLite, แตก word buckets, สร้าง candidate Trie ชั่วคราว แล้วจึงเข้า board solver เดียวกัน ขณะที่ Packed Trie เริ่ม prune จาก anchor บนกระดานได้ทันที จึงมักชนะด้วย constant factor ที่ต่ำกว่า O(1) ในที่นี้หมายถึงการเปิด bucket หนึ่งครั้งโดยเฉลี่ย ไม่ใช่เวลาของการค้น Scrabble ทั้งกระบวนการ
มันดึงกลุ่ม anagram ได้โดยตรง ทำให้ subset selection ตรวจสอบและสอนได้ง่าย และอาจชนะการไล่ candidate ของ Trie เมื่อ rack สร้างคำได้หลากหลาย จาก microbenchmark 1,000 รอบในเครื่องนี้ in-memory Hash เร็วกว่า 2.03× สำหรับ AEINRST แต่เพียง 1.27× สำหรับ CARTSAA; เมื่อรวมการตรวจตำแหน่งจริง Trie ยังอาจเร็วกว่า
คลิกช่องแล้วพิมพ์ A-Z เพื่อสร้างโจทย์เอง ตัวพิมพ์เล็กในสถานะกระดานสงวนไว้แทน blank ที่วางแล้ว
ห้องทดลองเว็บนี้ต่อยอดแนวคิดที่นักศึกษา 4 คนร่วมบันทึกไว้: ทำให้ฝึก Scrabble ได้โดยไม่ต้องรอคู่เล่น ใช้ดัชนีคำเพื่อให้การตรวจคำตอบสนองรวดเร็ว และทำให้ตาของ AI ตรวจสอบได้เป็นลำดับตั้งแต่สร้าง candidate, validate, คิดคะแนน จนถึงเลือก move

รายงานเริ่มจากปัญหาการเรียนรู้ที่จับต้องได้: คู่เล่นมีจำกัดทำให้ฝึกคำศัพท์ไม่ต่อเนื่อง บอทจึงเปลี่ยนเกมเป็นกิจกรรมฝึกภาษาอังกฤษที่เข้าใช้ได้ทุกเวลา
ทีมใช้ Figma ออกแบบ UI ร่วมกัน ก่อนพัฒนากระดาน ถุงตัวอักษร การลากวาง การตรวจคำ และคะแนนแบบ real-time เป็นเกม Unity/C#
Sequence model แยกคำผิดออกจากตาที่ถูกต้อง และให้ move ของผู้เล่นกับ AI ผ่าน validation, scoring และ game-end check ชุดเดียวกัน ซึ่งเป็น invariant ที่เว็บรุ่นนี้ยังรักษาไว้
แบบสอบถามเบื้องต้น 35 คนในรายงานนักศึกษาระบุว่า มากกว่า 80% มองว่าเกมช่วยคำศัพท์ใหม่ การจดจำ และความสนุก ผลนี้เป็นหลักฐานที่รายงานโดยโครงงาน ไม่ใช่ controlled trial อิสระ และเป็นแรงตั้งต้นให้ทดลองในชั้นเรียนแบบทำซ้ำได้
ที่มาของเนื้อหา: สรุปจากรายงาน Capstone ของนักศึกษาเรื่อง “เกมสแครบเบิล / Scrabble game” โดยจงใจไม่นำคำกล่าวด้านความเร็วมาใช้ซ้ำ จนกว่าเว็บรุ่นนี้จะวัดด้วยข้อมูลและ workload เดียวกันอย่างยุติธรรม