• วิศวกรซอฟต์แวร์ที่มีประสบการณ์ 15 ปี แชร์ประสบการณ์ พัฒนาเกมไพ่จากวัยเด็กด้วยภาษา Go
  • ตอนพัฒนา “Truco” โดยไม่มี LLM (Large Language Model) ต้องแก้ทุกปัญหาด้วยตนเองทั้งหมด ไม่ว่าจะเป็น การออกแบบ UI และการดีพลอยแบบ serverless จนใช้เวลา 3 เดือน
  • ตอนทำ “Escoba” ใช้ LLM ช่วย แปลงโค้ดแบ็กเอนด์และเร่งความเร็วในการพัฒนา อย่างมาก โดย พรอมป์ครั้งเดียวก็ใช้งานได้เกือบทั้งหมด
  • ช่วงท้ายของบทความมี ตัวอย่าง Tic-Tac-Toe พร้อมไกด์แบบ ทีละขั้นตอน สำหรับสร้างเกมด้วย Go backend, การแปลงเป็น WASM และการเชื่อมกับ React เพื่อให้ใครก็เริ่มทำเกมเองได้
  • แต่ ฟรอนต์เอนด์ React และการจัดการสถานะเกมบน WASM ก็ยังต้องดีบักและลงมือทำเองอยู่ดี

บทนำ

  • วิศวกรซอฟต์แวร์ที่มีประสบการณ์ 15 ปีตระหนักว่าตัวเองยังไม่เคยสร้างและเผยแพร่เกมจริง ๆ มาก่อน
  • จึงตัดสินใจพัฒนาเกมไพ่เกมหนึ่งที่เคยเล่นกับเพื่อน ๆ ในอาร์เจนตินาตอนเด็กด้วยภาษา Go

Truco: 3 เดือนโดยไม่มี LLM

  • เริ่มพัฒนาเกมไพ่ชื่อ Truco ตั้งแต่วันที่ 18 มิถุนายน 2024 โดยใช้ Go backend และเขียนฟรอนต์เอนด์ด้วยความรู้ React เพียงเล็กน้อย
  • ความท้าทายใหญ่ที่สุดคือ การทำ UI และเพื่อหลีกเลี่ยงการต้องมีเซิร์ฟเวอร์ จึงใช้ TinyGo เพื่อ transpile เป็น WASM (WebAssembly) แล้วนำไฟล์ static ไปดีพลอยบน GitHub Pages
  • ในช่วงที่ยังไม่มี LLM ผู้เขียนต้องค้นหารายละเอียดทุกอย่างเองและลองผิดลองถูกอยู่ตลอด จนใช้เวลาราว 3 เดือนกว่าจะเสร็จ
  • ไม่มีเป้าหมายด้านโฆษณาหรือรายได้ แค่ต้องการทำเกมนี้ให้เสร็จ และแม้จะเปิดตัวมาแล้ว 1 ปี ก็ยังมีคนเล่นอย่างต่อเนื่อง

Escoba: 3 วันเมื่อมี LLM

  • หนึ่งปีต่อมา ระหว่างเดินทางไปอาร์เจนตินาเพื่อเยี่ยมครอบครัว เขาได้สอนหลานเล่นเกมไพ่ยอดนิยมอันดับสองชื่อ Escoba
  • ครั้งนี้เขาใช้ LLM (Claude) โดยคัดลอกแบ็กเอนด์ของ Truco มาก่อน จากนั้นอธิบายกติกาของ Escoba ผ่านพรอมป์และขอให้รีแฟกเตอร์โค้ด
  • แค่พรอมป์แรกก็ได้ผลลัพธ์ที่เกือบสมบูรณ์แล้ว เหลือเพียงบั๊กเล็กน้อยและฟีเจอร์เสริมบางส่วนที่ต้องแก้ด้วยมือ
  • ส่วนฟรอนต์เอนด์ยังต้องลงมือทำและดีบักเองอีกหลายวัน ข้อจำกัดของ LLM, ทักษะ React และสภาพแวดล้อมที่ค่อนข้างแปลกซึ่งให้ WASM เป็นตัวจัดการสถานะเกม ล้วนเป็นความท้าทาย

ทีละขั้นตอน: วิธีสร้างเกมของตัวเอง

การพัฒนาแบ็กเอนด์

  • แบ็กเอนด์แบบ turn-based สามารถออกแบบฟังก์ชันการทำงานได้อย่างชัดเจน
  • หากต้องการคงโครงสร้างแบบ serverless ไว้ การหลีกเลี่ยงรูปแบบที่ผู้เล่นหลายคนต้องเล่นกันแบบคนต่อคนถือเป็นทางเลือกที่สมจริง หากไม่มีเซิร์ฟเวอร์เชิงพาณิชย์รองรับ

การพัฒนาฟรอนต์เอนด์

  • ฟรอนต์เอนด์ต้องรองรับงานต่อไปนี้
    • ขอสร้าง GameState ใหม่จากแบ็กเอนด์
    • แสดงสถานะบน UI
    • มีอินเทอร์เฟซให้เลือกการกระทำที่ถูกต้อง
    • เมื่อมีการกระทำ ให้ส่งคำสั่งไปยังแบ็กเอนด์
    • หากถึงตาของบอท ให้ร้องขอไปยังแบ็กเอนด์

การแปลงแบ็กเอนด์เป็น WASM

  • หากต้องการ build โค้ด Go ให้เป็น WASM ให้ใช้ GOARCH=wasm GOOS=js go build
  • อาจมีปัญหาเรื่องขนาดไบนารีที่ใหญ่ จึงใช้ TinyGo เพื่อลดขนาด
  • เพื่อ export ฟังก์ชันที่จะเชื่อมกับฟรอนต์เอนด์ ต้องเขียน entry point แยกใน Go (เช่น main_wasm.go) แล้วแยกการ build ตามกรณี
  • ในฟังก์ชันหลักต้องบล็อกด้วย select {} เพื่อไม่ให้โปรแกรมจบทันที

การเชื่อมข้อมูลระหว่างแบ็กเอนด์กับฟรอนต์เอนด์

  • struct แบบยืดหยุ่นของ Go อย่าง GameState ไม่สามารถ serialize/deserialize จาก WASM ได้โดยตรง
  • จึงจำเป็นต้อง แลกเปลี่ยนข้อมูลทั้งหมดในรูปแบบ JSON
  • โดยอ้างอิงเอกสารของ TinyGo ทั้งอินพุตและเอาต์พุตจะต้องส่งผ่านการ serialize เป็น JSON

อินเทอร์เฟซระหว่างฟรอนต์เอนด์กับแบ็กเอนด์

  • ฝั่งฟรอนต์เอนด์จะเรียกใช้ฟังก์ชันของ backend โดยตรง
  • GameState ถูกจัดการอยู่ภายใน WASM เท่านั้น ฟรอนต์เอนด์ไม่สามารถ mutation ได้ และ backend คือแหล่งข้อมูลจริงเพียงหนึ่งเดียวเสมอ
  • หลัง recompile WASM ต้องนำไฟล์ใหม่มาแทนที่ และมีตัวอย่างการใช้ Makefile เพื่อทำงานอัตโนมัติ

สภาพแวดล้อมการรัน WASM

  • ในการรัน ต้องใส่ wasm_exec.js ไว้ใน head และใช้สคริปต์ดังกล่าวเพื่อสร้าง instance แล้วจึงรัน

บทสรุป

  • การสร้างเกมเป็นประสบการณ์ที่สนุก และแนวทางที่ใช้ Go, WASM และ React ร่วมกันก็เป็นสิ่งที่ใคร ๆ ลองทำตามได้
  • แม้ ความช่วยเหลือจาก LLM จะเพิ่มผลิตภาพได้มาก แต่ ทักษะด้านฟรอนต์เอนด์และประสบการณ์ในการดีบัก ก็ยังสำคัญอยู่เสมอ
  • ใคร ๆ ก็สามารถ ลองพัฒนาเกมด้วยตัวเอง ด้วยโครงสร้างแบบนี้ได้ จึงน่าลองทำดู

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น