คู่มือทีละขั้นตอนสำหรับมือใหม่
สิ่งที่ต้องมี
- บัญชี FlipSight หลังเข้าสู่ระบบแล้วจึงลงทะเบียนบอตและดาวน์โหลดตัวเชื่อมต่อได้
- รูปบอตหนึ่งรูป: ไฟล์ GIF, JPG หรือ PNG ขนาดไม่เกิน 5 MB กว้างและสูงอย่างน้อย 175 px
- คอมพิวเตอร์ของคุณสำหรับเปิดใช้งานบอต ตาเดินของบอตคำนวณบนคอมพิวเตอร์ของคุณที่เปิดโปรแกรมเชื่อมต่อไว้ แม้ใช้เอนจินเดียวกัน ความแข็งก็อาจต่างกันตามประสิทธิภาพของคอมพิวเตอร์และการตั้งค่าเอนจิน และถ้าเปลี่ยนคอมพิวเตอร์หรือการตั้งค่า เรตติ้งก็อาจขยับตามไปด้วย
- Python 3 โปรแกรมฟรีที่ใช้รันตัวเชื่อมต่อ เวอร์ชัน 3.9 ขึ้นไปใช้ได้ แนะนำ 3.11 ขึ้นไป คุณจะตรวจในขั้นตอนที่ 5
- เอนจินบอตของคุณ โปรแกรมเชื่อมต่อมีเอนจินฝึกหัดสำหรับทดสอบรวมอยู่ด้วย
1. ลงทะเบียนบอต
เข้าสู่ระบบแล้วเปิดหน้าบอต (othelloclub.com/th/flipsight/bot) ในกล่อง ลงทะเบียนบอตของฉัน ด้านบน ใส่ชื่อใน ชื่อบอต (2–24 ตัวอักษร) ตอนนี้ให้ เกมที่รองรับ เลือกไว้เฉพาะ มาตรฐาน เลือกรูปใน อัปโหลดรูปบอต แล้วกดปุ่ม ลงทะเบียนบอตของฉัน
ถ้าสำเร็จจะเห็นแบบนี้: กล่อง โทเคนเชื่อมต่อ (แสดงครั้งเดียว) ปรากฏขึ้นพร้อมข้อความยาวที่ขึ้นต้นด้วย fsbm_
2. เก็บโทเคนไว้
ข้อความยาวนี้คือโทเคน เป็นรหัสผ่านเฉพาะของบอตคุณ ตัวเชื่อมต่อใช้มันยืนยันกับเซิร์ฟเวอร์ว่าทำงานแทนบอตตัวนี้ คัดลอกทั้งหมดแล้ววางเก็บไว้ชั่วคราว เช่น ในโปรแกรมแก้ไขข้อความ เมื่อปิดกล่องแล้ว จะดูโทเคนเดิมอีกไม่ได้
ดูแลโทเคนเหมือนรหัสผ่าน อย่าให้ใครเห็น และอย่าโพสต์ภาพหน้าจอของมัน หากทำหายหรือมีคนเห็น ให้เปิด จัดการ ในรายการบอตแล้วกด สร้างโทเคนเชื่อมต่อ เมื่อได้โทเคนใหม่ โทเคนเก่าจะใช้ไม่ได้ทันที
3. ดาวน์โหลดตัวเชื่อมต่อ
กด ดาวน์โหลดตัวอย่าง Python เพื่อดาวน์โหลดไฟล์ ZIP แล้วแตกไฟล์ คู่มือนี้เรียกโฟลเดอร์ที่แตกออกมาว่า "โฟลเดอร์บอต" วางไว้ในที่ที่หาง่าย เช่น เดสก์ท็อป
ถ้าสำเร็จจะเห็นแบบนี้: ในโฟลเดอร์บอตมีไฟล์ README.md และโฟลเดอร์ tools
4. เปิดเทอร์มินัลในโฟลเดอร์บอต
เทอร์มินัลคือหน้าต่างสำหรับพิมพ์คำสั่ง เปิดตามวิธีของระบบคุณด้านล่าง
macOS: กด Command+Space พิมพ์ Terminal แล้วเปิด พิมพ์ cd และเว้นวรรคหนึ่งครั้ง ลากโฟลเดอร์บอตมาวางในหน้าต่าง แล้วกด Enter
Linux: ในโปรแกรมจัดการไฟล์ คลิกขวาที่โฟลเดอร์บอตแล้วเลือก "เปิดในเทอร์มินัล" (Open in Terminal) ชื่อเมนูอาจต่างกันในแต่ละดิสโทร หากไม่มีเมนูนี้ ให้เปิดเทอร์มินัลแล้วใช้ cd แบบเดียวกับ macOS
Windows: เปิดโฟลเดอร์บอตใน File Explorer พิมพ์ powershell ในแถบที่อยู่ด้านบนแล้วกด Enter หน้าต่าง PowerShell จะเปิดขึ้นในโฟลเดอร์นั้น
ตรวจสอบ: พิมพ์คำสั่งด้านล่างแล้วกด Enter ถ้าเห็น README.md และ tools ในรายการแสดงว่าสำเร็จ คำสั่งนี้เหมือนกันบน macOS, Linux และ Windows PowerShell
ls5. ตรวจ Python
ในหน้าต่างเดิม คัดลอกคำสั่งของระบบคุณหนึ่งคำสั่ง วางแล้วกด Enter ใช้ปุ่มคัดลอกที่มุมขวาบนของกล่องโค้ดแต่ละกล่องได้
macOS / Linux
python3 --versionWindows PowerShell
py --versionถ้าสำเร็จจะเห็นแบบนี้: เวอร์ชันที่ขึ้นต้นด้วย 3 เช่น Python 3.11.9 เวอร์ชัน 3.9 ขึ้นไปใช้ได้ หากขึ้นว่าไม่พบคำสั่ง ให้ติดตั้ง Python ตามด้านล่าง ปิดหน้าต่าง แล้วเริ่มใหม่ตั้งแต่ขั้นตอนที่ 4
macOS: ดาวน์โหลดตัวติดตั้งสำหรับ macOS (.pkg) จาก python.org แล้วติดตั้ง จากนั้นรัน Install Certificates.command หนึ่งครั้งจากโฟลเดอร์ Python ใน "แอปพลิเคชัน" ขั้นตอนนี้เตรียมใบรับรองที่จำเป็นสำหรับการเชื่อมต่อแบบปลอดภัย (HTTPS)
Linux: ติดตั้ง python3 ด้วยโปรแกรมจัดการซอฟต์แวร์หรือตัวจัดการแพ็กเกจของดิสโทรคุณ
Windows: ติดตั้ง Python install manager จาก python.org
6. ใส่โทเคน
ใส่โทเคนลงในตัวแปรสภาพแวดล้อม ซึ่งเป็นการตั้งค่าชั่วคราวที่มีอยู่เฉพาะในหน้าต่างเทอร์มินัลนี้ ชื่อของมันคือ FS_BOT_TOKEN คัดลอกคำสั่งหนึ่งบรรทัดของระบบคุณด้านล่าง วางแล้วกด Enter หนึ่งครั้ง จากนั้นจะขึ้นคำว่า Bot token และรอให้คุณป้อนข้อมูล ตอนนี้ให้วางโทเคนที่คัดลอกไว้ในขั้นตอนที่ 2 แล้วกด Enter
macOS / Linux
printf 'Bot token: '; read -r -s FS_BOT_TOKEN; printf '\n'; export FS_BOT_TOKENWindows PowerShell
$env:FS_BOT_TOKEN = [Net.NetworkCredential]::new('', (Read-Host 'Bot token' -AsSecureString)).Passwordบน macOS และ Linux โทเคนที่วางจะไม่แสดงบนหน้าจอ ส่วนบน Windows จะเห็น * แทนตัวอักษร ทั้งสองแบบเป็นเรื่องปกติ เพราะโทเคนไม่ได้เขียนไว้ในคำสั่ง จึงไม่ค้างอยู่ในประวัติคำสั่ง การตั้งค่านี้มีผลเฉพาะในหน้าต่างนี้ หากปิดหน้าต่างไปแล้วหรือเปิดหน้าต่างใหม่ ให้ทำขั้นตอนที่ 4 และ 6 ซ้ำ
Windows: ใน PowerShell ให้วางด้วย Ctrl+V หรือคลิกขวา คำสั่งหนึ่งบรรทัดนี้เขียนแบบที่ใช้ได้ทั้งใน Windows PowerShell 5.1 ที่มากับ Windows และ PowerShell 7 ที่ใหม่กว่า แต่ยังไม่ได้ทดสอบบนเครื่อง Windows จริง
ถ้าสำเร็จจะเห็นแบบนี้: พรอมต์สำหรับคำสั่งถัดไปกลับมาโดยไม่มีข้อผิดพลาด
7. เริ่มตัวเชื่อมต่อ
ในหน้าต่างเดิม คัดลอกคำสั่งด้านล่างตามที่เป็นอยู่ วางแล้วกด Enter อย่าเปลี่ยนชื่อไฟล์ในคำสั่ง บอตของคุณจะเชื่อมต่อด้วยเอนจินฝึกซ้อม ซึ่งเป็นเอนจินที่อ่อนมาก เดินช่องที่ถูกกติกาซึ่งมีหมายเลขต่ำที่สุด
macOS / Linux
python3 tools/bot_match_client.py --engine 'python3 tools/bot_match_example_engine.py'Windows PowerShell
py tools/bot_match_client.py --engine 'py tools/bot_match_example_engine.py'ระหว่างทำงาน คำสั่งไม่แสดงข้อความและไม่กลับไปยังบรรทัดรับคำสั่งถัดไปเป็นเรื่องปกติ เพื่อให้บอตออนไลน์ ให้เปิดหน้าต่างนี้ไว้พร้อมกับโปรแกรมเชื่อมต่อและเอนจินที่ทำงานอยู่ และป้องกันไม่ให้คอมพิวเตอร์เข้าสู่โหมดพัก การปิดหน้าต่างนี้หรือกด Ctrl+C จะหยุดทั้งสองโปรแกรมและทำให้บอตออฟไลน์ หากต้องการกลับมาออนไลน์ ให้เรียกใช้คำสั่งด้านบนอีกครั้ง โปรแกรมเชื่อมต่อจะรับคำขอแข่งขันจากบอตอื่นโดยอัตโนมัติ
ระหว่างที่บอตทำงานด้วยเอนจินฝึกซ้อม จะไม่ถูกจับคู่บอตแข่งอัตโนมัติ และเกมระหว่างบอตที่บอตนั้นเล่น รวมถึงเกมที่คุณท้าเอง จะไม่นำไปคิดเรตติ้ง เมื่อเปลี่ยนเป็นเอนจินของคุณเองแล้วเปิดใหม่ (ขั้นที่ 10) ตั้งแต่นั้นจะถูกจับคู่อัตโนมัติและนำไปคิดเรตติ้ง
ถ้าสำเร็จจะเห็นแบบนี้: ในรายการ บอตที่ลงทะเบียน บนหน้าบอต จุดทางขวาของชื่อบอตคุณเป็นสีเขียว เมื่อวางเมาส์บนจุดจะแสดง ออนไลน์ หากเป็นสีเทา (ออฟไลน์) ให้กด รีเฟรช
8. ทดสอบด้วยเกมแรก
บนหน้าบอต เลือกบอตของคุณใน บอตของฉันที่ใช้เล่น เลือกบอตใดก็ได้ในรายการ บอต FlipSight กด ท้าประลอง แล้วยืนยันในหน้าต่างที่เปิดขึ้น ในเกมทุกประเภท เซิร์ฟเวอร์เป็นผู้คำนวณตาเดินของบอต FlipSight จึงไม่ต้องเปิดห้องเกมไว้ เมื่อเปิดการจับคู่อัตโนมัติ (ค่าเริ่มต้น) เว็บไซต์จะจัดบอตแข่งให้ด้วยระหว่างที่ตัวเชื่อมต่อทำงานอยู่
ถ้าสำเร็จจะเห็นแบบนี้: ห้องเกมแสดงกระดานสองกระดาน และมีหมากวางลงทุกครั้งที่ถึงตาบอตของคุณ เอนจินฝึกซ้อมอ่อนมาก การแพ้จึงเป็นเรื่องปกติ
9. ให้ทำงานต่อหลังปิดหน้าต่าง (ไม่บังคับ)
เริ่มด้วยคำสั่งด้านล่างแทนขั้นตอนที่ 7 แล้วบอตจะทำงานต่อแม้ปิดหน้าต่างไปแล้ว รันในหน้าต่างเดียวกับที่ใส่โทเคน (ขั้นตอนที่ 6) บอตจะหยุดเมื่อคอมพิวเตอร์ปิดหรือเข้าสู่โหมดสลีป หากหยุดระหว่างเกม เกมนั้นจะแพ้เพราะหมดเวลาหรือขาดการเชื่อมต่อ
macOS / Linux
macOS / Linux: หลังรันคำสั่งเริ่มแล้วปิดหน้าต่างได้ ข้อความจะถูกเขียนลงไฟล์ bot.log ในโฟลเดอร์บอต หากต้องการหยุด ให้เปิดเทอร์มินัลใหม่แล้วรันคำสั่งหยุด คัดลอกเฉพาะกล่องที่ต้องการ: ถ้าวางทั้งสองกล่องพร้อมกัน บอตจะหยุดทันทีหลังเริ่มทำงาน
เริ่ม
nohup python3 tools/bot_match_client.py --engine 'python3 tools/bot_match_example_engine.py' > bot.log 2>&1 &หยุด
pkill -f bot_match_client.pyWindows PowerShell
Windows: จะมีหน้าต่างที่ย่อไว้ปรากฏบนแถบงาน และบอตทำงานอยู่ในหน้าต่างนั้น ปิดหน้าต่าง PowerShell ปัจจุบันได้ หากต้องการหยุด ให้เปิดหน้าต่างที่ย่อไว้แล้วกด Ctrl+C หรือปิดหน้าต่างนั้น
เริ่ม
Start-Process py -ArgumentList 'tools/bot_match_client.py --engine "py tools/bot_match_example_engine.py"' -WorkingDirectory $PWD -WindowStyle Minimized10. เปลี่ยนเป็นเอนจินของคุณเอง
หลังทดสอบการเชื่อมต่อด้วยเอนจินฝึกซ้อม ให้เปลี่ยนเป็นเอนจินของคุณ ใช้ตัวเลือกเฉพาะด้านล่างสำหรับ Edax หรือ Egaroucid ใช้ --nboard สำหรับเอนจินอื่นที่รองรับ และ JSON สำหรับเอนจินที่เขียนเอง ใช้โทเคนเดิมต่อได้
เลือกเกมรูปแบบอื่น (Anti, 10x10, แปดเหลี่ยม, รีเวอร์ซีคลาสสิก) ใน เกมที่รองรับ เฉพาะเมื่อเอนจินของคุณรู้กติกาของเกมนั้น บอตอื่นและผู้เล่นสามารถท้าบอตของคุณได้ในทุกเกมที่เลือกไว้ หากต้องการเปลี่ยน ให้เปิด จัดการ ในรายการบอต แก้ เกมที่รองรับ แล้วกด บันทึก เปลี่ยนไม่ได้ขณะมีเกมกำลังเล่นหรือมีคำท้ารออยู่
ตัวอย่างคำขอถึงเอนจินแยกตามเกม
เชื่อมต่อเอนจินที่รองรับ NBoard เช่น Edax
NBoard เป็นโปรแกรมหน้าจอ Othello ที่ใช้ร่วมกับ NTest ได้ โปรโตคอล NBoard คือรูปแบบสื่อสารแบบเปิดระหว่างโปรแกรมนี้กับเอนจิน และใช้ได้กับเอนจินที่รองรับ เช่น NTest และ Edax
ตัวอย่างการติดตั้งและเริ่มใช้งานด้านล่างใช้ Edax 4.6 และ Egaroucid for Console 7.8.2
โปรแกรมเชื่อมต่อไม่ได้รวม Edax 4.6 ให้ดาวน์โหลดไฟล์สำหรับระบบของคุณจาก Assets ที่ Edax 4.6 วางในโฟลเดอร์บอต แล้วแตกไฟล์ด้วยคำสั่งเตรียมด้านล่าง คำสั่ง Windows และ Linux ใช้กับคอมพิวเตอร์ x86-64
Windows PowerShell (x86-64): edax-4.6-MS-windows-x86.zip
เตรียม (ครั้งแรกเท่านั้น)
Expand-Archive -LiteralPath edax-4.6-MS-windows-x86.zip -DestinationPath edax-4.6เริ่ม
py tools/bot_match_client.py --edax "edax-4.6\wEdax-x86-64.exe"Linux (x86-64): edax-4.6-linux-x86.tar.gz
เตรียม (ครั้งแรกเท่านั้น)
mkdir -p edax-4.6
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6
chmod +x edax-4.6/lEdax-x86-64เริ่ม
python3 tools/bot_match_client.py --edax "edax-4.6/lEdax-x86-64"macOS: edax-reversi-4.6.tar.gz
Edax 4.6 ไม่มีไฟล์โปรแกรม macOS อย่างเป็นทางการ ให้ดาวน์โหลด Source code (tar.gz) และ edax-4.6-linux-x86.tar.gz จากรุ่นเดียวกัน แล้วคอมไพล์โปรแกรม Mac จากซอร์ส ส่วนไฟล์ Linux ใช้ดึงเฉพาะไฟล์ประเมินร่วม data/eval.dat ต้องมี Xcode Command Line Tools คำสั่งเตรียมจะคอมไพล์ซอร์ส
เตรียม (ครั้งแรกเท่านั้น)
xcode-select --installtar -xzf edax-reversi-4.6.tar.gz
mv edax-reversi-4.6 edax-4.6
mkdir -p edax-4.6/bin
tar -xzf edax-4.6-linux-x86.tar.gz -C edax-4.6 data/eval.dat
(cd edax-4.6/src && make build ARCH=native OS=osx)เริ่ม
python3 tools/bot_match_client.py --edax "edax-4.6/bin/mEdax-native"Edax เชื่อมต่อได้โดยระบุเพียงพาธไฟล์โปรแกรมด้วย --edax ตามด้านล่าง ไม่ต้องสร้างอะแดปเตอร์แยก โปรแกรมเชื่อมต่อจะแบ่งเวลาคิดแต่ละตาจากเวลาเกมที่เหลือ
เปลี่ยนพาธในคำสั่งเป็นพาธเต็มของไฟล์โปรแกรมคุณ หากพาธมีช่องว่างให้ครอบด้วยเครื่องหมายอัญประกาศคู่ ถ้าหาไฟล์ประเมินไม่พบอัตโนมัติ ให้ระบุพาธ eval.dat ด้วย --edax-eval
การเชื่อมต่อแบบ NBoard เล่นได้เฉพาะเกมมาตรฐาน 8×8 ให้เลือกเฉพาะ มาตรฐาน ใน เกมที่รองรับ ของบอตนี้ หากมีเกมรูปแบบอื่นเข้ามา ตัวเชื่อมต่อจะไม่ถามเอนจิน จะไม่มีการเดิน และเกมจะถูกยกเลิกหรือแพ้เพราะหมดเวลา
Egaroucid: --egaroucid
เตรียม Egaroucid for Console เลือกไฟล์โปรแกรมคอนโซลและเก็บโฟลเดอร์ resources ไว้ที่เดิม บน macOS และ Linux ให้ใช้โปรแกรมที่สร้างตามคู่มือทางการ โปรแกรมเชื่อมต่อส่งกระดานและเวลาที่เหลือด้วย setboard, settime และ go จึงไม่ต้องเขียนอะแดปเตอร์
Windows PowerShell
py tools/bot_match_client.py --egaroucid "egaroucid\Egaroucid_for_console.exe"macOS / Linux
python3 tools/bot_match_client.py --egaroucid "egaroucid/bin/Egaroucid_for_Console.out"ทั้งสองวิธีรองรับเฉพาะเกมปกติ 8×8 และใช้เอนจินแยกเกมละหนึ่งตัวเมื่อเล่นสองเกมพร้อมกัน --max-think-seconds คือขีดจำกัดความปลอดภัยต่อตา (ค่าเริ่มต้น 10 วินาที) ถ้า Egaroucid ถึงขีดจำกัด จะหยุดค้นหา เดินตาสำรองที่ถูกกติกา แล้วเริ่มเอนจินใหม่ในตาถัดไป
เอนจินที่เขียนเอง: เชื่อมต่อผ่าน JSON
อ่านส่วนนี้เฉพาะเมื่อคุณต้องการเชื่อมต่อเอนจินที่คุณเขียนเอง หากคุณใช้ Edax, Egaroucid หรือเอนจิน NBoard ตัวอื่น คุณทำเสร็จแล้วตั้งแต่ด้านบนและข้ามส่วนนี้ได้เลย
ทุกครั้งที่ถึงตาบอตของคุณ ตัวเชื่อมต่อจะรัน my_adapter.py หนึ่งครั้ง โดยส่งตำแหน่งปัจจุบันให้เป็นหนึ่งบรรทัด (หมากอยู่ตรงไหน ถึงตาใคร ช่องไหนเดินได้ถูกกติกา เหลือเวลาเท่าไร) จากนั้น my_adapter.py ตอบกลับเป็นช่องหนึ่งช่องในหนึ่งบรรทัด แล้วตัวเชื่อมต่อจะเดินตานั้นบนเซิร์ฟเวอร์ my_adapter.py เป็นไฟล์ Python เล็ก ๆ ที่อยู่ตรงกลาง ทำหน้าที่ถามตาเดินจากเอนจินของคุณ ตัวเชื่อมต่อดูแลการเชื่อมต่อเซิร์ฟเวอร์ โทเคน และการส่งตาเดินให้ทั้งหมด ไฟล์นี้จึงไม่ต้องยุ่งกับเรื่องเหล่านั้นเลย
bot_match_client.py→ my_adapter.pybot_match_client.py← my_adapter.pyเอนจินของคุณเป็นแบบไหน
- เอนจินที่เลือกตาเดินด้วยโค้ด Python: ใช้ตัวอย่าง A ด้านล่าง
- ไฟล์โปรแกรมที่พิมพ์คำตอบหนึ่งบรรทัดแล้วปิดตัวทุกครั้งที่รัน: ใช้ตัวอย่าง B ด้านล่าง
- ไฟล์โปรแกรมที่เปิดค้างไว้และแลกคำสั่งกันต่อเนื่อง: ถ้ารองรับ NBoard ให้เชื่อมต่อด้วย --nboard ด้านบน ถ้าเป็นโปรโตคอลอื่น ให้แก้ตัวอย่าง B ให้เข้ากับโปรโตคอลนั้น และให้ my_adapter.py เปิดเอนจินค้างไว้แล้วแลกทีละบรรทัดเป็นวงวน
ขั้นตอนที่ 1: ไฟล์ที่ตรวจการเชื่อมต่ออย่างเดียว (my_adapter.py)
ในโฟลเดอร์บอต (ที่มี README.md และ tools) ให้สร้างไฟล์ชื่อ my_adapter.py วางโค้ดด้านล่างลงไปตามเดิมโดยไม่ต้องแก้ แล้วบันทึก ไฟล์นี้ไม่ใช้เอนจินใดเลย และตอบด้วยช่องที่ถูกกติกาช่องแรกเท่านั้น มีไว้เพื่อยืนยันว่าการเชื่อมต่อใช้งานได้ก่อนที่คุณจะต่อเอนจินของคุณ
import json
import sys
def choose_move(position):
legal = position["legal_moves"]
return legal[0] if legal else -1
line = sys.stdin.readline()
position = json.loads(line)
move = choose_move(position)
print(json.dumps({"move": move}), flush=True)
ตรวจอินพุตและเอาต์พุตก่อนเชื่อมต่อ
คำสั่งในกล่อง 1 ส่งตำแหน่งตัวอย่างเข้าอะแดปเตอร์โดยตรง ผลที่ควรได้คือหนึ่งบรรทัด: {"move": 19} เมื่อเห็นบรรทัดนั้นแล้ว คำสั่งในกล่อง 2 จะเริ่มตัวเชื่อมต่อพร้อมอะแดปเตอร์นี้ ให้รันในหน้าต่างที่มีโทเคนจากขั้นตอนที่ 6 รันสองกล่องนี้แยกกัน หากเอนจินของคุณอ่านรูปแบบ JSON นี้ได้เอง ก็ใส่คำสั่งของเอนจินต่อท้าย --engine ได้เลย
1. ทดสอบอะแดปเตอร์อย่างเดียว
macOS / Linux
printf '%s\n' '{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | python3 my_adapter.pyWindows PowerShell
'{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}' | py my_adapter.py2. เริ่มตัวเชื่อมต่อพร้อมอะแดปเตอร์
macOS / Linux
python3 tools/bot_match_client.py --engine 'python3 my_adapter.py'Windows PowerShell
py tools/bot_match_client.py --engine 'py my_adapter.py'ขั้นตอนที่ 2: เปลี่ยนเป็นไฟล์ที่เรียกเอนจินของคุณ
เมื่อขั้นตอนที่ 1 ใช้งานได้แล้ว ให้แทนที่เนื้อหาทั้งหมดของ my_adapter.py ด้วยตัวอย่าง A หรือ B ด้านล่าง แก้เฉพาะบรรทัดที่มีคำว่า EDIT และปล่อยส่วนที่เหลือไว้ตามเดิม จากนั้นรันคำสั่งทดสอบ 1 ด้านบนอีกครั้ง แล้วตรวจว่ามีคำตอบหนึ่งบรรทัดปรากฏขึ้น ตาเดินนี้เอนจินของคุณเป็นผู้เลือก จึงเป็นเรื่องปกติที่จะได้ช่องอื่นที่ไม่ใช่ 19
ตัวอย่าง A: เอนจินที่เลือกตาเดินด้วยฟังก์ชัน Python
ตัวอย่างนี้สมมติว่ามีไฟล์ชื่อ my_engine.py ซึ่งมีฟังก์ชัน search ที่เลือกตาเดิน ที่ EDIT 1 ให้เปลี่ยนชื่อไฟล์และชื่อฟังก์ชัน และที่ EDIT 2 ให้เปลี่ยนค่าที่ส่งเข้าไป ให้ตรงกับเอนจินของคุณ ฟังก์ชันควรคืนค่าเป็นหมายเลขช่อง (0 ถึง 63) หรือพิกัด เช่น d3
import json
import sys
from pathlib import Path
from my_engine import search # EDIT 1: your engine file and function name
def think_ms(position):
# Time for this move: share the remaining clock over my remaining moves.
my_moves_left = position["board"].count(0) // 2 + 1
share = position["remaining_ms"] / my_moves_left
return int(min(share, position["budget_ms"]) * 0.8)
def choose_move(position):
if not position["legal_moves"]:
return -1
# EDIT 2: pass what your function needs
return search(
board=position["board"],
color=position["color"],
legal_moves=position["legal_moves"],
time_limit_ms=think_ms(position),
)
position = json.loads(sys.stdin.readline())
legal = position["legal_moves"]
try:
move = choose_move(position)
except Exception as error:
# The engine failed: note why in my_adapter.log and play the first legal square.
with open(Path(__file__).with_name("my_adapter.log"), "a") as log:
log.write(repr(error) + "\n")
move = legal[0] if legal else -1
print(json.dumps({"move": move}), flush=True)ตัวอย่าง B: เอนจินที่เป็นไฟล์โปรแกรม
ตัวอย่างนี้สมมติว่าโปรแกรมอ่านหนึ่งบรรทัดที่มีอักขระกระดาน 64 ตัว ฝ่ายที่ถึงตาเดิน และเวลาคิดเป็นมิลลิวินาที แล้วตอบเป็นพิกัดหนึ่งบรรทัด เช่น D3 (. = ว่าง, X = ดำ, O = ขาว) เอนจินแต่ละตัวมีรูปแบบของตัวเอง จึงให้เปลี่ยนชื่อไฟล์โปรแกรมที่ EDIT 1 บรรทัดที่ส่งให้เอนจินที่ EDIT 2 และส่วนที่อ่านคำตอบที่ EDIT 3 ให้ตรงกับเอนจินของคุณ
my_adapter.py → my_engine
...........................OX......XO........................... X 1777my_engine → my_adapter.py
D3import json
import subprocess
import sys
from pathlib import Path
# EDIT 1: your engine program file (kept in the same folder as my_adapter.py)
ENGINE = Path(__file__).with_name("my_engine.exe" if sys.platform == "win32" else "my_engine")
def think_ms(position):
# Time for this move: share the remaining clock over my remaining moves.
my_moves_left = position["board"].count(0) // 2 + 1
share = position["remaining_ms"] / my_moves_left
return int(min(share, position["budget_ms"]) * 0.8)
def choose_move(position):
if not position["legal_moves"]:
return -1
# EDIT 2: build the line your engine reads
board = "".join(".XO"[cell] for cell in position["board"])
color = "X" if position["color"] == 1 else "O"
reply = subprocess.run(
[str(ENGINE)],
input=f"{board} {color} {think_ms(position)}\n",
text=True,
capture_output=True,
check=True,
timeout=position["budget_ms"] / 1000 * 0.9,
)
# EDIT 3: take the square out of your engine's answer
return reply.stdout.strip()
position = json.loads(sys.stdin.readline())
legal = position["legal_moves"]
try:
move = choose_move(position)
except Exception as error:
# The engine failed: note why in my_adapter.log and play the first legal square.
with open(Path(__file__).with_name("my_adapter.log"), "a") as log:
log.write(repr(error) + "\n")
move = legal[0] if legal else -1
print(json.dumps({"move": move}), flush=True)เวลาคิด: เกมบอตให้เวลาบอตแต่ละตัว 1 นาทีต่อกระดาน การคิดนานกับตาเดียวจึงทำให้แพ้เพราะหมดเวลาได้เร็ว think_ms ในตัวอย่างนำเวลาที่เหลือ (remaining_ms) มาหารด้วยจำนวนตาที่คุณยังต้องเดิน เพื่อกำหนดเวลาของตานี้ budget_ms ไม่ใช่เวลาที่ควรใช้กับตานี้ แต่เป็นเวลานานที่สุดที่ตัวเชื่อมต่อรอคำตอบ (ค่าเริ่มต้นคือ 10 วินาที) หากคำตอบมาช้ากว่านั้น บอตจะเล่นกระดานนั้นต่อไม่ได้และแพ้กระดานนั้นเพราะหมดเวลา จึงให้ส่ง think_ms ให้เอนจินของคุณ ไม่ใช่ budget_ms
เมื่อมีอะไรผิดพลาด: หากเรียกเอนจินไม่สำเร็จ ไฟล์ตัวอย่างจะเขียนสาเหตุลงใน my_adapter.log และเดินช่องที่ถูกกติกาช่องแรกแทน หากตาเดินดูแปลก ให้เปิดไฟล์นั้นดูก่อน ตัวเชื่อมต่อไม่แสดงข้อความผิดพลาดของอะแดปเตอร์บนหน้าจอ ให้ใช้ print เฉพาะกับคำตอบหนึ่งบรรทัดเท่านั้น และเขียนข้อความดีบักลงไฟล์
ความเร็ว: โปรแกรมเชื่อมต่อจะเปิด my_adapter.py ค้างไว้ในแต่ละเกมและส่งหนึ่งบรรทัดทุกตา ไฟล์ที่ตอบหนึ่งบรรทัดแล้วปิดตัวเหมือนตัวอย่างจะถูกเรียกใหม่ในตาถัดไป เอนจินของคุณจึงเริ่มใหม่ทุกครั้ง ถ้าเอนจินเปิดช้าหรือต้องการใช้งานต่อจากตาก่อนหน้า ให้เปลี่ยน my_adapter.py เป็นวงวน “อ่านหนึ่งบรรทัด ตอบหนึ่งบรรทัด” ซ้ำไป อย่าอ่านอินพุตจนจบ และ flush ทุกครั้งที่ตอบ
ส่วนที่เหลือของหัวข้อนี้เป็นข้อมูลอ้างอิงว่าตัวเชื่อมต่อกับ my_adapter.py รับส่งอะไรกัน หากคุณใช้ไฟล์ตัวอย่างด้านบนตามเดิม ก็ไม่จำเป็นต้องอ่าน
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
นี่คือตัวอย่างข้อมูลที่ตัวเชื่อมต่อส่งมา ไม่ใช่โค้ดสำหรับรัน ไม่ต้องบันทึกเป็นไฟล์ Python ระหว่างเล่น ตัวเชื่อมต่อจะส่งข้อมูลปัจจุบันให้อัตโนมัติทุกตา
ด้านล่างคือตัวอย่างตำแหน่งเริ่มต้นแบบมาตรฐาน 8×8 ในเกมจริง ตัวเชื่อมต่อจะใส่ตำแหน่งปัจจุบันให้ board เรียงช่องจาก a1 ไปทางขวา แล้วต่อด้วยแถวถัดไป โดย 0=ว่าง, 1=ดำ, 2=ขาว color=1 หมายถึงขอตาเดินของฝ่ายดำ legal_moves คือรายการช่องที่คุณเดินได้ remaining_ms คือเวลาที่เหลือของเกม และ budget_ms คือเวลานานที่สุดที่ตัวเชื่อมต่อรอคำตอบของตานี้ ทั้งสองเป็นมิลลิวินาที (1000 = 1 วินาที) ฟิลด์และตัวอย่างของเกมรูปแบบอื่นอยู่ในส่วนเกมรูปแบบอื่นด้านล่าง
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[19,26,37,44],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":0,"2":0},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
เขียนคำตอบแบบใดแบบหนึ่งด้านล่างเป็น JSON บรรทัดเดียวที่ลงท้ายด้วยการขึ้นบรรทัดใหม่ move คือหมายเลขช่องหรือพิกัด ตัวพิมพ์เล็กหรือใหญ่ก็ได้ บนกระดาน 8×8 d3 คือ (3−1)×8+3 = 19 ต้องเลือกจาก legal_moves ที่ได้รับเสมอ ในการแข่งระหว่างบอต หากไม่มีตาที่ถูกกติกา ให้ส่ง -1 หรือ pass ส่วนเกมกับคน เซิร์ฟเวอร์จัดการการผ่านที่ถูกบังคับเอง เขียนเฉพาะบรรทัดคำตอบลงใน stdout ห้ามมีข้อความอื่น
{"move":19}{"move":"d3"}{"move":-1}ตัวอย่างคำขอถึงเอนจินแยกตามเกม
ตัวอย่างคำขอแรกของฝ่ายดำที่ตัวเชื่อมต่อส่งให้เอนจินแบบ --engine สร้างด้วยโค้ดกติกา เกมจริงทุกประเภทเริ่มจากตำแหน่งสุ่มที่มี 12 หมาก ดังนั้น board และ legal_moves จะต่างกันในทุกเกม เพื่อให้ตัวอย่างเหล่านี้คงที่ จึงเลือกตาเปิดเกมไว้ใกล้กลางกระดาน ในเกมจริง match_id และค่าเวลาก็ต่างออกไปด้วย
game_mode: ค่าใดค่าหนึ่งจาก std, anti, grand, octagon, reversi XOT ในเกมกับคนจะมาเป็น stdboard_size: 8 หรือ 10 สำหรับ grand และ octagonboard: อาร์เรย์ความยาว board_size² อ่านจาก a1 ไปทางขวา แล้วต่อด้วยแถวถัดไป 0=ว่าง (รวมช่องที่ถูกปิดของแปดเหลี่ยม), 1=ดำ, 2=ขาวblocked_squares: ช่องที่ใช้ไม่ได้ของแปดเหลี่ยม ในเกมอื่นเป็นอาร์เรย์ว่างplaced: จำนวนหมากที่แต่ละสีวางไปแล้ว รวมตาสุ่มตอนเริ่มเกม คีย์เป็นสตริง "1" และ "2" ใช้กับขีดจำกัด 32 หมากของรีเวอร์ซีคลาสสิก
Anti (anti)
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [2, 3, 12, 25, 42, 44, 45],
"game_mode": "anti",
"board_size": 8,
"blocked_squares": [],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
{"move":2}Grand (10x10) (grand)
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [13, 14, 25, 42, 63, 65, 66],
"game_mode": "grand",
"board_size": 10,
"blocked_squares": [],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
{"move":13}Octagon (octagon)
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [13, 14, 25, 42, 63, 65, 66],
"game_mode": "octagon",
"board_size": 10,
"blocked_squares": [0, 1, 10, 8, 9, 19, 80, 90, 91, 89, 98, 99],
"placed": {
"1": 4,
"2": 4
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
{"move":13}รีเวอร์ซีคลาสสิก (reversi)
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"color": 1,
"legal_moves": [18, 25, 33, 41, 42, 50, 51],
"game_mode": "reversi",
"board_size": 8,
"blocked_squares": [],
"placed": {
"1": 6,
"2": 6
},
"remaining_ms": 60000,
"match_id": 123,
"leg": 0,
"revision": 0,
"history": [],
"round_no": null,
"room_created_at": null,
"budget_ms": 10000
}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
{"move":18}ปัญหาที่พบบ่อย
| อาการ | สาเหตุและวิธีแก้ |
|---|---|
| บอตแสดงจุดสีเทา (ออฟไลน์) | ตรวจว่าหน้าต่างตัวเชื่อมต่อยังทำงานอยู่ มันจะหยุดเมื่อปิดหน้าต่าง กด Ctrl+C หรือคอมพิวเตอร์เข้าสู่โหมดสลีป ทำขั้นตอนที่ 6 และ 7 ซ้ำ แล้วกด รีเฟรช |
ขึ้นว่าไม่พบคำสั่งcommand not found / is not recognized | ยังไม่ได้ติดตั้ง Python หรือไม่ได้เปิดหน้าต่างใหม่หลังติดตั้ง ดูขั้นตอนที่ 5 เปิดหน้าต่างใหม่ แล้วเริ่มใหม่ตั้งแต่ขั้นตอนที่ 4 |
ขึ้นว่าเปิดไฟล์ไม่ได้can't open file | คุณรันนอกโฟลเดอร์บอต ให้ย้ายไปที่โฟลเดอร์บอตตามขั้นตอนที่ 4 แล้วใช้ ls ตรวจว่ามี README.md และ tools ในรายการ |
ขึ้นว่าไม่มีโทเคนแล้วปิดตัวลงFS_BOT_TOKEN is missing or invalid | หน้าต่างนี้ไม่มีโทเคน หากคุณเปิดหน้าต่างใหม่ ให้ทำขั้นตอนที่ 6 ซ้ำ |
ขึ้นว่าโทเคนถูกปฏิเสธแล้วหยุดทำงานBot credential rejected | วางโทเคนผิด หรือเป็นโทเคนเก่า หลังจากออกโทเคนใหม่หรือปิดใช้งานบอต โทเคนเก่าจะใช้ไม่ได้อีก เริ่มใหม่ตั้งแต่ขั้นตอนที่ 6 ด้วยโทเคนล่าสุด |
ข้อผิดพลาดบรรทัดเดิมขึ้นซ้ำ ๆConnection/state error | ตรวจการเชื่อมต่ออินเทอร์เน็ต บน macOS ให้ตรวจว่าได้รัน Install Certificates.command จากขั้นตอนที่ 5 แล้ว หากขึ้นเฉพาะระหว่างเกม เซิร์ฟเวอร์อาจปฏิเสธตาเดินของเอนจินคุณเพราะผิดกติกา ตัวเชื่อมต่อจะส่งตาเดิมซ้ำ จึงต้องแก้ที่เอนจิน |
| ปุ่มท้าประลองไม่ทำงาน หรือคำท้าไม่ถูกรับ | บอตทั้งสองต้องมีจุดสีเขียว ตรวจว่าเลือกบอตของคุณไว้ใน บอตของฉันที่ใช้เล่น บอตที่กำลังเล่นเกมอื่นอยู่จะไม่รับคำท้าใหม่ คำท้าที่ไม่ถูกรับจะหมดอายุหลัง 120 วินาที |
บอตไม่เดิน แล้วแพ้เพราะหมดเวลาหรือเกมถูกยกเลิกEngine failed for match | เอนจินตอบไม่ทันเวลา หรือคำตอบมีรูปแบบผิด ตรวจคำสั่งของเอนจินและเอาต์พุต JSON หนึ่งบรรทัดของมัน หากบอตไม่เดินตาแรกภายใน 30 วินาทีหลังเริ่ม เกมจะถูกยกเลิก |
| ไม่มีคำท้าเกมรูปแบบอื่นเข้ามา | หน้าต่างท้าประลองแสดงเฉพาะเกมที่บอตทั้งสองรองรับ ตรวจว่าได้เลือกเกมนั้นใน เกมที่รองรับ ของบอตคุณ และบอตอีกฝ่ายก็รองรับด้วย บอตที่เชื่อมต่อผ่าน NBoard ควรเลือกไว้เฉพาะ มาตรฐาน |
อภิธานศัพท์
| คำศัพท์ | ความหมาย |
|---|---|
| API | วิธีที่ตกลงกันไว้สำหรับให้โปรแกรมส่งคำขอและคำตอบถึงกัน ตัวเชื่อมต่อจัดการให้คุณ |
| โทเคน | รหัสผ่านเฉพาะของบอตคุณ ตัวเชื่อมต่อใช้มันยืนยันกับเซิร์ฟเวอร์ว่าเป็นบอตตัวไหน |
| เทอร์มินัล | หน้าต่างสำหรับพิมพ์คำสั่ง บน Windows ให้ใช้ PowerShell |
| ตัวแปรสภาพแวดล้อม | การตั้งค่าชั่วคราวที่ส่งให้โปรแกรมที่เริ่มจากหน้าต่างเทอร์มินัลหนึ่ง จะหายไปเมื่อปิดหน้าต่าง |
| JSON | รูปแบบการเขียนข้อมูลเป็นข้อความ ตัวอย่าง: {"move":19} |
| เอนจิน | โปรแกรมที่ดูตำแหน่งบนกระดานแล้วเลือกตาเดิน |
| อะแดปเตอร์ | โปรแกรมเล็ก ๆ ที่แปลงข้อมูลระหว่างเอนจินของคุณกับตัวเชื่อมต่อ |
| NBoard | NBoard เป็นโปรแกรมหน้าจอ Othello ที่ใช้ร่วมกับ NTest ได้ โปรโตคอล NBoard คือรูปแบบสื่อสารแบบเปิดระหว่างโปรแกรมนี้กับเอนจิน และใช้ได้กับเอนจินที่รองรับ เช่น NTest และ Edax |
| ตาที่ถูกกติกา | ช่องที่คุณเดินได้ตอนนี้ เซิร์ฟเวอร์ระบุไว้ใน legal_moves |
ดาวน์โหลด
เข้าสู่ระบบเพื่อดาวน์โหลด .zip มีคู่มือ โปรแกรมเชื่อมต่อ และเอนจินตัวอย่าง ส่วน .json คือข้อกำหนด API และ .md คือคู่มือ
ข้อกำหนดโดยละเอียด
ส่วนที่เหลือของหน้านี้เป็นข้อมูลอ้างอิงโดยละเอียดสำหรับนักพัฒนาที่ทำงานกับเอนจินหรือ API โดยตรง
ตัวเลือกของตัวเชื่อมต่อ
ตัวเลือกของ tools/bot_match_client.py ต้องระบุ --engine หรือ --nboard / --edax / --egaroucid อย่างใดอย่างหนึ่งเท่านั้น โทเคนไม่เคยเป็นตัวเลือก จะอ่านจากตัวแปรสภาพแวดล้อม FS_BOT_TOKEN เท่านั้น รหัสออก: 0 หยุดตามปกติ, 2 โทเคนถูกปฏิเสธ, 3 ถึง --max-runtime
| พารามิเตอร์ | คำอธิบาย |
|---|---|
--engine "COMMAND" | เปิดคำสั่งนี้ครั้งเดียวต่อเกมแล้วเปิดค้างไว้ และแลก JSON หนึ่งบรรทัดทุกตาโดยไม่ปิดอินพุต เอนจินที่ตอบหนึ่งบรรทัดแล้วปิดตัวจะถูกเปิดใหม่ในตาถัดไป |
--nboard "COMMAND" | เปิดเอนจิน NBoard ค้างไว้หนึ่งตัวต่อเกม รวมสองตัว เฉพาะเกมมาตรฐาน (std) |
--edax PATH | Edax เชื่อมต่อได้โดยระบุเพียงพาธไฟล์โปรแกรมด้วย --edax ตามด้านล่าง ไม่ต้องสร้างอะแดปเตอร์แยก โปรแกรมเชื่อมต่อจะแบ่งเวลาคิดแต่ละตาจากเวลาเกมที่เหลือ |
--edax-eval PATH | พาธ eval.dat ของ Edax ถ้าไม่ระบุจะหา data/eval.dat ในโฟลเดอร์โปรแกรมหรือโฟลเดอร์แม่ |
--egaroucid PATH | เตรียม Egaroucid for Console เลือกไฟล์โปรแกรมคอนโซลและเก็บโฟลเดอร์ resources ไว้ที่เดิม บน macOS และ Linux ให้ใช้โปรแกรมที่สร้างตามคู่มือทางการ โปรแกรมเชื่อมต่อส่งกระดานและเวลาที่เหลือด้วย setboard, settime และ go จึงไม่ต้องเขียนอะแดปเตอร์ |
--nboard-depth N | ส่ง set depth N ไปยังเอนจิน NBoard ค่า 0 ถึง 60 โดย 0 คือใช้ค่าเริ่มต้นของเอนจิน (--edax: 0 → 60). |
--max-think-seconds S | จำนวนวินาทีสูงสุดที่รอเอนจินต่อตา ค่าเริ่มต้น 10 สูงสุด 300 ไม่เกินเวลาที่เหลือลบ 0.75 วินาที นี่คือเพดานความปลอดภัยของตัวเชื่อมต่อ เซิร์ฟเวอร์ไม่มีการจำกัดเวลาต่อตา ตั้งเป็น 0 เพื่อยกเลิกเพดานต่อตาและใช้เวลาเกมที่เหลือ |
--engine-name NAME | ชื่อเอนจินที่แสดงในข้อมูลบอต (ไม่เกิน 120 ตัวอักษร) อย่าใส่คำสั่งหรือพาธ |
--cpu-name NAME | ชื่อ CPU ที่จะรายงาน (ไม่เกิน 120 ตัวอักษร) หากไม่ระบุจะตรวจหาให้อัตโนมัติ |
--once-match ID | จัดการเฉพาะการแข่งขันนี้ และออกเมื่อการแข่งขันจบ (finished, aborted, expired, rejected) ใช้กับ ID ติดลบของเกมกับคนได้ด้วย สำหรับการทดสอบ ให้ใช้คู่กับ --max-runtime |
--max-runtime S | ปิดเอนจินและออกหลังผ่านไปตามจำนวนวินาทีนี้ (รหัสออก 3) |
--no-stream | ไม่ใช้สตรีมเหตุการณ์ ใช้การโพลอย่างเดียว |
--api URL, --stream-url URL | ที่อยู่ของ API และสตรีม ปกติให้ใช้ค่าเริ่มต้น รับเฉพาะ HTTPS และเนื่องจากโทเคนถูกส่งไปด้วย สตรีมต้องอยู่บนโฮสต์ของ API |
เขียนการเชื่อมต่อ API เอง
รับคำท้าและตำแหน่งกระดานด้วย agent_poll หรือสตรีมเหตุการณ์ที่ยืนยันตัวตนแล้ว ส่ง agent_accept สำหรับคำท้าที่รออยู่ซึ่งคุณเป็น bot_b จากนั้นในการแข่งขันที่กำลังเล่น ให้คำนวณเฉพาะตำแหน่งที่เป็นตาของคุณในแต่ละเกม ส่งได้ไม่เกินหนึ่งครั้งต่อ match_id เกม และ revision และทิ้งงานที่ล้าสมัยเมื่อสถานะเปลี่ยน เกมกับคนใช้ match_id ติดลบและ leg=0 และต้องส่ง round_no และ room_created_at ตรงตามที่ได้รับ ในห้องปกติ เซิร์ฟเวอร์จัดการการผ่านที่ถูกบังคับเอง มีเพียงการแข่งระหว่างบอตเท่านั้นที่ต้องส่งการผ่าน
agent_poll / bot-stream → pending (bot_b = my_id)
→ agent_accept → running → legs[0] + legs[1]
→ agent_move(match_id, leg, revision, move) → finished / abortedเกมรูปแบบอื่น
บอตจะได้รับเฉพาะเกมที่เลือกไว้ใน เกมที่รองรับ ค่า game_mode ในคำขอถึงเอนจินและสถานะการแข่งขันบอกว่าเป็นเกมอะไร เซิร์ฟเวอร์คำนวณขนาดกระดาน ช่องที่ถูกปิด และช่องที่ถูกกติกาให้ เอนจินจึงแค่เลือกจาก legal_moves ที่ได้รับ อย่าสร้างตาเดินของ 10×10 หรือรีเวอร์ซีคลาสสิกด้วยการสร้างตาเดินแบบมาตรฐาน 8×8
| เกม | game_mode | กระดาน | หมากเริ่มต้น | เวลา (ต่อเกม ต่อสี) | legal_moves แรกของฝ่ายดำ |
|---|---|---|---|---|---|
| มาตรฐาน | std | 8×8 | สุ่ม 12 หมาก | 60,000 ms | ขึ้นอยู่กับตำแหน่ง |
| Anti | anti | 8×8 | สุ่ม 12 หมาก | 60,000 ms | ขึ้นอยู่กับตำแหน่ง |
| Grand (10x10) | grand | 10×10 | สุ่ม 12 หมาก | 60,000 ms | ขึ้นอยู่กับตำแหน่ง |
| Octagon | octagon | 10×10 (88) | สุ่ม 12 หมาก | 60,000 ms | ขึ้นอยู่กับตำแหน่ง |
| รีเวอร์ซีคลาสสิก | reversi | 8×8 | สุ่ม 12 หมาก | 60,000 ms | ขึ้นอยู่กับตำแหน่ง |
กติการ่วม: ฝ่ายดำ (1) เดินก่อน ทั้งสองเกมเริ่มจากตำแหน่งเดียวกันโดยสลับสี และเล่นพร้อมกัน ในการแข่งระหว่างบอต เมื่อคุณไม่มีตาที่ถูกกติกา legal_moves จะมาเป็นอาร์เรย์ว่าง และบอตต้องส่ง move=-1 (ผ่าน) เอง เซิร์ฟเวอร์จะไม่ผ่านแทนคุณ และเวลาของคุณยังเดินต่อไป การผ่านทั้งที่มีตาที่ถูกกติกาถือว่าผิดกติกา เกมจะจบเมื่อไม่มีสีใดเดินได้ ช่องว่างไม่ยกให้ใคร นับรวมเฉพาะหมากจริงของทั้งสองเกม และถ้าผลรวมเท่ากันถือว่าเสมอ เกมบอตทุกประเภทเริ่มจากตำแหน่งสุ่มที่มีหมาก 12 ตัว ทุกประเภทเกมให้เวลาบอตแต่ละตัว 1 นาทีต่อกระดาน
Anti: การวางและการพลิกหมากเหมือนโอเทลโลมาตรฐาน ฝ่ายที่มีหมากรวมทั้งสองเกมน้อยกว่าชนะ
10x10: กติกามาตรฐานบนกระดาน 10×10 (100 ช่อง) ตำแหน่งเริ่มต้นคือสี่ช่องตรงกลาง การแข่งระหว่างบอตจะเดินสุ่มเพิ่มอีก 8 ตา และเริ่มที่ 12 หมาก
แปดเหลี่ยม: กระดานและการวางเริ่มต้นเหมือน 10x10 แต่ใช้สามช่องในแต่ละมุมไม่ได้ รวม 12 ช่อง (เล่นได้ 88 ช่อง) ช่องที่ถูกปิดจะมาเป็นหมายเลขช่องใน blocked_squares และเป็น 0 ซึ่งเป็นค่าเดียวกับช่องว่างใน board อาร์เรย์นี้ไม่ได้เรียงตามหมายเลข วางหมากบนช่องที่ถูกปิดไม่ได้ และแนวหมากจะขาดที่ช่องนั้น
รีเวอร์ซีคลาสสิก: ตามกติกาจะเริ่มจากกระดานว่าง ขณะที่ยังมีช่องว่างในสี่ช่องตรงกลาง (27, 28, 35, 36) ให้วางได้เฉพาะที่นั่นและไม่มีการพลิกหมาก เมื่อทั้งสี่ช่องเต็มแล้วจึงใช้กติกามาตรฐาน แต่ละสีวางหมากได้ไม่เกิน 32 หมาก รวมสี่ตาแรกด้วย placed คือจำนวนหมากที่แต่ละสีวางไปแล้ว คีย์เป็นสตริง "1" (ดำ) และ "2" (ขาว) การแข่งระหว่างบอตจะเริ่มหลังจากเซิร์ฟเวอร์วางหมากแบบสุ่มไปแล้ว 12 ตา รวมสี่ตาตรงกลางด้วย ดังนั้นในคำขอแรก placed ของสองสีรวมกันได้ 12 (ปกติคือ {"1":6,"2":6}) สีที่วางครบ 32 หมากแล้วจะได้ legal_moves ว่าง และส่ง -1 ทุกครั้งที่ถึงตา
ตัวอย่างคำขอถึงเอนจินแยกตามเกม
ตัวอย่างคำขอแรกของฝ่ายดำที่ตัวเชื่อมต่อส่งให้เอนจินแบบ --engine สร้างด้วยโค้ดกติกา เกมจริงทุกประเภทเริ่มจากตำแหน่งสุ่มที่มี 12 หมาก ดังนั้น board และ legal_moves จะต่างกันในทุกเกม เพื่อให้ตัวอย่างเหล่านี้คงที่ จึงเลือกตาเปิดเกมไว้ใกล้กลางกระดาน ในเกมจริง match_id และค่าเวลาก็ต่างออกไปด้วย
Anti (anti)
{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"anti","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Grand (10x10) (grand)
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"grand","board_size":10,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}Octagon (octagon)
{"board":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[13,14,25,42,63,65,66],"game_mode":"octagon","board_size":10,"blocked_squares":[0,1,10,8,9,19,80,90,91,89,98,99],"placed":{"1":4,"2":4},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}รีเวอร์ซีคลาสสิก (reversi)
{"board":[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,2,2,1,1,0,0,0,0,2,2,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[18,25,33,41,42,50,51],"game_mode":"reversi","board_size":8,"blocked_squares":[],"placed":{"1":6,"2":6},"remaining_ms":60000,"match_id":123,"leg":0,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}ฟิลด์ในคำขอถึงเอนจิน
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
game_mode | string | ค่าใดค่าหนึ่งจาก std, anti, grand, octagon, reversi XOT ในเกมกับคนจะมาเป็น std |
board_size | integer 8 | 10 | 8 หรือ 10 สำหรับ grand และ octagon |
board | integer[board_size²] | อาร์เรย์ความยาว board_size² อ่านจาก a1 ไปทางขวา แล้วต่อด้วยแถวถัดไป 0=ว่าง (รวมช่องที่ถูกปิดของแปดเหลี่ยม), 1=ดำ, 2=ขาว |
blocked_squares | integer[] | ช่องที่ใช้ไม่ได้ของแปดเหลี่ยม ในเกมอื่นเป็นอาร์เรย์ว่าง |
placed | {"1": integer, "2": integer} | จำนวนหมากที่แต่ละสีวางไปแล้ว รวมตาสุ่มตอนเริ่มเกม คีย์เป็นสตริง "1" และ "2" ใช้กับขีดจำกัด 32 หมากของรีเวอร์ซีคลาสสิก |
legal_moves | integer[] | ช่องที่คุณเดินได้ตอนนี้ หากว่าง ให้ตอบ move=-1 |
color | integer 1 | 2 | สีที่ต้องเดิน 1=ดำ, 2=ขาว |
budget_ms | integer | เวลาที่ให้สำหรับตานี้เป็นมิลลิวินาที: ค่าที่น้อยกว่าระหว่าง --max-think-seconds กับเวลาที่เหลือลบ 0.75 วินาที ตัวเชื่อมต่อจะหยุดเอนจินเมื่อเกินเวลานี้ |
remaining_ms | integer | เวลาที่เหลือของสีคุณในเกมนี้เป็นมิลลิวินาที หลังหักเวลาที่ใช้ไปแล้วในตานี้ |
match_id | integer ≠ 0 | ID การแข่งขัน เป็นค่าติดลบ (-หมายเลขห้อง) สำหรับเกมกับคน |
leg | integer 0 | 1 | หมายเลขเกม 0 หรือ 1 เป็น 0 เสมอสำหรับเกมกับคน |
revision | integer | หมายเลขสถานะของเกมนี้ ส่งไปกับ agent_move โดยไม่เปลี่ยนค่า |
history | array | ตาเดินที่เล่นไปแล้วในเกมนี้ ให้ตัดสินใจจาก board |
round_no | integer | null | หมายเลขรอบของเกมกับคน เป็น null สำหรับการแข่งระหว่างบอต |
room_created_at | string | null | เวลาที่สร้างห้องเกมกับคน เป็น null สำหรับการแข่งระหว่างบอต |
ท้าบอตอื่นในเกมรูปแบบอื่น
เพิ่ม game_mode ใน agent_challenge ค่าเริ่มต้นคือ std บอตทั้งสองต้องมีเกมนี้ใน supported_games ดูเกมของคู่แข่งได้จาก bots[].supported_games ในคำตอบของ agent_poll หากบอตฝ่ายใดไม่มีเกมนี้ คำตอบจะเป็น HTTP 409 พร้อมข้อผิดพลาดด้านล่าง
{"action":"agent_challenge","target_id":2,"game_mode":"anti"}
{"action":"agent_challenge","target_name":"Opponent","game_mode":"grand"}HTTP 409
{"ok":false,"error":"unsupported game mode","server_now_ms":1800000000000}การเปลี่ยน supported_games
แบบฟอร์มลงทะเบียนเลือก มาตรฐาน ไว้ตั้งแต่ต้น และบอตเก่าที่ไม่มีรายการบันทึกไว้จะรองรับ std เปลี่ยนรายการได้เฉพาะใน จัดการ บนหน้าเว็บบอตเท่านั้น เปลี่ยนด้วยโทเคนบอต (API) ไม่ได้ การเปลี่ยนจะถูกปฏิเสธขณะมีการแข่งขันกำลังเล่นหรือรอการรับ บันทึกรายการว่างไม่ได้
NBoard กับเกมรูปแบบอื่น
การเชื่อมต่อแบบ --nboard รองรับเฉพาะเกมมาตรฐาน (std) เมื่อมีเกมรูปแบบอื่นเข้ามา ตัวเชื่อมต่อจะไม่ถามเอนจินและสละตานั้น (ข้อความ Engine failed for match) เมื่อไม่มีตาเดินถูกส่ง เกมจะถูกยกเลิกก่อนตาแรก หรือเกมนั้นจะแพ้เพราะหมดเวลาในภายหลัง ให้บอต NBoard รองรับเฉพาะเกมมาตรฐานเท่านั้น
คนกับบอตที่ลงทะเบียน
ผู้เล่นท้าบอตของคุณได้ในทุกเกมที่บอตลงทะเบียนไว้ หากรองรับเกมมาตรฐาน อาจมีทั้ง STD และ XOT เข้ามา XOT มาพร้อม game_mode std และเริ่มจากกระดานที่เดินไปแล้ว 8 ตา (รวมอยู่ใน history) ผู้ท้าเป็นคนเลือกเวลา และเกมไม่คิดเรตติ้ง เกมกับคนมีเกมเดียว (leg 0) และ match_id ติดลบ เซิร์ฟเวอร์จัดการการผ่านเมื่อเดินไม่ได้ จึงไม่ต้องส่ง -1 บอตที่ลงทะเบียนและออนไลน์พร้อมเปิดจับคู่อัตโนมัติจะเข้าร่วมจับคู่ด่วน STD/XOT ที่รองรับด้วย เกมจับคู่ด่วนของสมาชิกคิดเรตติ้ง ส่วนของผู้เยี่ยมชมไม่คิดเรตติ้ง
ตาผิดกติกาและเวลา
ตาที่ผิดกติกาหรือ revision ที่ล้าสมัยจะถูกปฏิเสธด้วย HTTP 409 (illegal_move, stale_revision, not_your_turn) และกระดานไม่เปลี่ยน ระหว่างนั้นเวลายังเดินต่อ ตัวเชื่อมต่อจะส่งตาเดิมพร้อม revision เดิมซ้ำ ดังนั้นหากไม่แก้เอนจิน เกมนั้นจะแพ้เพราะหมดเวลาในที่สุด เซิร์ฟเวอร์ไม่มีการจำกัดเวลาต่อตา ตรวจเฉพาะเวลาที่เหลือของแต่ละสี 10 วินาทีคือค่าเริ่มต้นของ --max-think-seconds ของตัวเชื่อมต่อ
การยืนยันตัวตน
ใช้ HTTPS, JSON POST และ Authorization: Bearer โทเคนเป็นของบอตเดียว อย่าใส่ใน URL โค้ดสาธารณะ หรือบันทึกระบบ
Content-Type: application/json
Authorization: Bearer <FS_BOT_TOKEN>POST https://othelloclub.com/flipsight/bot_match_api.php
กติกาการแข่งขัน
เกมบอตทุกประเภทเริ่มจากตำแหน่งสุ่มที่มีหมาก 12 ตัว แข่งสองเกมพร้อมกันโดยสลับสี ตัดสินจากจำนวนหมากรวม ส่วน Anti ฝ่ายที่มีหมากน้อยกว่าชนะ
เกมบอตทุกประเภทเริ่มจากตำแหน่งสุ่มที่มีหมาก 12 ตัว ทุกประเภทเกมให้เวลาบอตแต่ละตัว 1 นาทีต่อกระดาน
หากบอตไม่เดินตาแรกภายใน 30 วินาทีหลังเริ่ม การแข่งขันจะถูกยกเลิกและไม่คิดอันดับ หากระหว่างการแข่งขันบอตฝ่ายหนึ่งขาดการเชื่อมต่อนานกว่า 30 วินาทีขณะที่อีกฝ่ายยังเชื่อมต่ออยู่ บอตที่เชื่อมต่ออยู่จะชนะ (finish_reason: disconnect) และผลจะถูกคิดอันดับ
ในเกมทุกประเภท เซิร์ฟเวอร์เป็นผู้คำนวณตาเดินของบอต FlipSight รับคำท้าทั้งจากเว็บและผ่าน API (agent_challenge) และเซิร์ฟเวอร์จะเริ่มการแข่งทันที จึงไม่ต้องเปิดห้องเกมไว้ หากการคำนวณของเซิร์ฟเวอร์หยุดนานกว่า 30 วินาที การแข่งนั้นจะเป็นโมฆะ (finish_reason: technical) และไม่นำไปคิดเรตติ้ง เกมรูปแบบอื่นคำนวณหนัก จึงแข่งกับบอต FlipSight ได้พร้อมกันสูงสุด 2 การแข่ง ถ้าเต็มจะได้ HTTP 409 พร้อม site bot variant matches are full โปรดลองใหม่ในภายหลัง
บอตแข่งอัตโนมัติ: เว็บไซต์เปิดห้องบอตแข่งไว้ 2 ห้องเสมอ เมื่อบอตที่ลงทะเบียนและเปิดการจับคู่อัตโนมัติอยู่ออนไลน์และไม่ได้แข่งอยู่ บอตนั้นจะได้รับคำท้าบอตแข่งแบบมาตรฐาน (std) กับบอตที่มีเรตติ้งใกล้เคียง (บอตที่ลงทะเบียนตัวอื่นก่อน ถ้าไม่มีจึงเป็นบอต FlipSight) โดยบอตนั้นเป็น bot_b ตัวเชื่อมต่อจะรับคำท้าอัตโนมัติและการแข่งเริ่มทันที หากไม่รับภายใน 120 วินาทีหรือปฏิเสธ บอตนั้นจะไม่ถูกจับคู่อัตโนมัติเป็นเวลา 10 นาที บอตแข่งอัตโนมัตินำไปคิดเรตติ้ง บอตแข่งอัตโนมัติยังดำเนินต่อระหว่างเกมอารีนา การจับคู่อัตโนมัติเปิดอยู่เป็นค่าเริ่มต้น และปิดได้ที่ จัดการ ในหน้าบอต บอตที่เปิดด้วยเอนจินฝึกซ้อม (environment.practice เป็น true) จะไม่ถูกจับคู่บอตแข่งอัตโนมัติ และเกมระหว่างบอตในช่วงนั้นจะไม่นำไปคิดเรตติ้ง
คำท้าโดยตรงจากคนจะเล่นหนึ่งเกมในห้องปกติ โดยเลือกเกมและเวลาที่รองรับ เซิร์ฟเวอร์กำหนดให้คำท้าโดยตรงไม่คิดเรตติ้งเสมอ บอตที่ลงทะเบียนและออนไลน์พร้อมเปิดจับคู่อัตโนมัติจะเข้าร่วมจับคู่ด่วน STD/XOT ที่รองรับด้วย เกมจับคู่ด่วนของสมาชิกคิดเรตติ้ง ส่วนของผู้เยี่ยมชมไม่คิดเรตติ้ง บอตที่ลงทะเบียนจะรับเกมได้ไม่เกินจำนวนเกมพร้อมกันที่ตั้งไว้ในการจัดการบอต (ค่าเริ่มต้น 2) เกมกับคน 1 เกม และการแข่งระหว่างบอต 1 ครั้ง (2 กระดาน) นับเป็น 1 อย่างละครั้ง
pending → running → finished / aborted
pending → rejected / expired
kind=human: id=-room_id, legs[0], round_no, room_created_at
agent_move: move=19 / "d3" / "D3" (8×8)
10×10: "j10" = "J0" = 99
pass: -1 / "pass" / "PA" / "PS" / "--" (paired games only)
index=row*board_size+column (0-based)ขั้นตอนการแข่งขัน
เกมบอตทุกประเภทเริ่มจากตำแหน่งสุ่มที่มีหมาก 12 ตัว แข่งสองเกมพร้อมกันโดยสลับสี ตัดสินจากจำนวนหมากรวม ส่วน Anti ฝ่ายที่มีหมากน้อยกว่าชนะ
หมายเลขผู้เข้าร่วมของคุณคือ 0 หากคุณเป็น bot_a และ 1 หากเป็น bot_b ผู้เข้าร่วม 0 เล่นดำ (1) ใน legs[0] และขาว (2) ใน legs[1] ส่วนผู้เข้าร่วม 1 กลับกัน ถึงตาคุณเมื่อ leg.status เป็น active และ leg.turn เท่ากับสีของคุณ อาจถึงตาคุณในทั้งสองเกมพร้อมกันได้
หากไม่มีตาที่ถูกกติกา คุณต้องส่ง move -1 (ผ่าน) เอง เซิร์ฟเวอร์จะไม่ผ่านแทนคุณ ในเกมที่เล่นกับคน เซิร์ฟเวอร์จัดการการผ่านเหมือนห้องเกมปกติ
ในตาของคุณ เวลาที่เหลือ = clocks_ms[สีของคุณ - 1] - (server_now_ms - turn_started_ms) โดย clocks_ms เรียงตามสี (ดำ, ขาว) ส่วน totals และ winner เรียงตามผู้เข้าร่วม (winner เป็น 0 หรือ 1 และเป็น null เมื่อเสมอ)
หมายเลขช่อง
หมายเลขช่อง = แถว × 8 + คอลัมน์ ดังนั้น a1=0, h1=7, a2=8 และ h8=63 เช่น 19 คือ d3 และ 37 คือ f5 อาร์เรย์ board ใช้ลำดับเดียวกัน โดย 0=ว่าง, 1=ดำ, 2=ขาว
a b c d e f g h
1 0 1 2 3 4 5 6 7
2 8 9 10 11 12 13 14 15
3 16 17 18 19 20 21 22 23
4 24 25 26 27 28 29 30 31
5 32 33 34 35 36 37 38 39
6 40 41 42 43 44 45 46 47
7 48 49 50 51 52 53 54 55
8 56 57 58 59 60 61 62 63บน 10×10 (grand, octagon) ช่อง = แถว × 10 + คอลัมน์ คอลัมน์คือ a ถึง j และแถวที่สิบเขียนเป็น 10 หรือ 0 ในพิกัด a1=0, j1=9, a2=10, j10=99 (เหมือน j0) พิกัดเดียวกันมีหมายเลขต่างกันตามขนาดกระดาน: d3 คือ 19 บน 8×8 และ 23 บน 10×10
a b c d e f g h i j
1 0 1 2 3 4 5 6 7 8 9
2 10 11 12 13 14 15 16 17 18 19
3 20 21 22 23 24 25 26 27 28 29
4 30 31 32 33 34 35 36 37 38 39
5 40 41 42 43 44 45 46 47 48 49
6 50 51 52 53 54 55 56 57 58 59
7 60 61 62 63 64 65 66 67 68 69
8 70 71 72 73 74 75 76 77 78 79
9 80 81 82 83 84 85 86 87 88 89
10 90 91 92 93 94 95 96 97 98 99Octagon (octagon)
a b c d e f g h i j
1 ## ## 2 3 4 5 6 7 ## ##
2 ## 11 12 13 14 15 16 17 18 ##
3 20 21 22 23 24 25 26 27 28 29
4 30 31 32 33 34 35 36 37 38 39
5 40 41 42 43 44 45 46 47 48 49
6 50 51 52 53 54 55 56 57 58 59
7 60 61 62 63 64 65 66 67 68 69
8 70 71 72 73 74 75 76 77 78 79
9 ## 81 82 83 84 85 86 87 88 ##
10 ## ## 92 93 94 95 96 97 ## ##สตรีมเหตุการณ์
เชื่อมต่อด้วยโทเคน Bearer เดียวกันไปที่ GET https://othelloclub.com/flipsight_api/bot-stream เพื่อรับเหตุการณ์ JSON บรรทัดละหนึ่งรายการ (NDJSON): hello ยืนยันการเชื่อมต่อ, match หมายถึงการแข่งขันหนึ่งของคุณมีการเปลี่ยนแปลง (เหตุการณ์การแข่งขันระหว่างบอตมีออบเจ็กต์การแข่งขันฉบับเต็ม) และ ping รักษาการเชื่อมต่อทุก 10 วินาที เมื่อได้รับ match ให้เรียก agent_poll หนึ่งครั้ง หากการเชื่อมต่อหลุด ให้เชื่อมต่อใหม่โดยหน่วงเวลาเพิ่มขึ้นจาก 1 ถึง 30 วินาที และเรียกสถานะต่อไประหว่างนั้น ขณะสตรีมเชื่อมต่ออยู่ บอตของคุณจะแสดงเป็นออนไลน์ บอตแต่ละตัวเปิดสตรีมได้สูงสุด 3 สตรีม
ตัวอย่างเหตุการณ์
curl -N 'https://othelloclub.com/flipsight_api/bot-stream' \
-H "Authorization: Bearer $FS_BOT_TOKEN"
{"type":"hello","bot_id":1,"server_now_ms":1800000000000}
{"type":"match","match":{"id":123,"bot_a":1,"bot_b":2,"status":"running","state":{...}},"server_now_ms":1800000000500}
{"type":"match","match":{"id":-45,"kind":"human","room_id":45},"server_now_ms":1800000003000}
{"type":"ping","server_now_ms":1800000010000}ตัวเชื่อมต่อเอนจิน
โปรแกรมเชื่อมต่อบอต FlipSight (Python 3.9 ขึ้นไป ใช้เฉพาะไลบรารีมาตรฐาน) ทำงานบน Windows, macOS และ Linux และเชื่อมต่อเอนจินได้สองแบบ --engine จะเปิดเอนจินครั้งเดียวต่อเกมแล้วเปิดค้างไว้ ส่ง JSON หนึ่งบรรทัดทุกตา และอ่านคำตอบ {"move": ช่อง} หนึ่งบรรทัด เอนจินที่ตอบหนึ่งบรรทัดแล้วปิดตัวยังใช้ได้เพราะจะถูกเปิดใหม่ในตาถัดไป แต่เอนจินที่อ่านอินพุตจนจบ (sys.stdin.read() เป็นต้น) จะไม่ตอบเลย จึงต้องอ่านทีละบรรทัด legal_moves ในคำขอคือรายการช่องที่เดินได้ ถ้าว่างให้ตอบ -1 ส่วน --nboard จะเปิดเอนจินโปรโตคอล NBoard ค้างไว้เกมละหนึ่งตัว และกำหนดความลึกด้วย --nboard-depth
Edax เชื่อมต่อได้โดยระบุเพียงพาธไฟล์โปรแกรมด้วย --edax ตามด้านล่าง ไม่ต้องสร้างอะแดปเตอร์แยก โปรแกรมเชื่อมต่อจะแบ่งเวลาคิดแต่ละตาจากเวลาเกมที่เหลือ
เตรียม Egaroucid for Console เลือกไฟล์โปรแกรมคอนโซลและเก็บโฟลเดอร์ resources ไว้ที่เดิม บน macOS และ Linux ให้ใช้โปรแกรมที่สร้างตามคู่มือทางการ โปรแกรมเชื่อมต่อส่งกระดานและเวลาที่เหลือด้วย setboard, settime และ go จึงไม่ต้องเขียนอะแดปเตอร์
JSON ขาเข้า (ตัวเชื่อมต่อ → อะแดปเตอร์)
นี่คือตัวอย่างข้อมูลที่ตัวเชื่อมต่อส่งมา ไม่ใช่โค้ดสำหรับรัน ไม่ต้องบันทึกเป็นไฟล์ Python ระหว่างเล่น ตัวเชื่อมต่อจะส่งข้อมูลปัจจุบันให้อัตโนมัติทุกตา
{"board":[0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,2,0,0,0,0,0,1,2,2,1,0,0,0,0,2,2,2,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"color":1,"legal_moves":[2,3,12,25,42,44,45],"game_mode":"std","board_size":8,"blocked_squares":[],"placed":{"1":4,"2":4},"remaining_ms":40000,"match_id":123,"leg":1,"revision":0,"history":[],"round_no":null,"room_created_at":null,"budget_ms":10000}JSON ตอบกลับ (อะแดปเตอร์ → ตัวเชื่อมต่อ)
{"move":2}ซิงค์สถานะ
เมื่อสตรีมเชื่อมต่ออยู่ ให้เรียก agent_poll เมื่อมีเหตุการณ์เข้ามาและทุก 10 วินาที หากเรียกโดยไม่มีสตรีม ให้เรียกทุกวินาทีระหว่างการแข่งขันและทุก 5 วินาทีเมื่อว่าง และห้ามเรียกบ่อยกว่าวินาทีละครั้ง เวลาเครือข่ายนับรวมในนาฬิกาของคุณ หากคำตอบของการส่งตาเดินสูญหาย ให้อ่านสถานะใหม่และส่งตาเดิมซ้ำเฉพาะเมื่อ revision ยังเท่าเดิม
คำสั่ง API
ตัวอย่างคำตอบแสดงรูปแบบ ปุ่มคัดลอกคัดลอกเฉพาะคำขอและไม่เรียก API
POST agent_poll
อ่านสถานะ
| พารามิเตอร์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
action | string "agent_poll" | จำเป็น | ชื่อแอ็กชันที่จะเรียก |
environment | object | ไม่บังคับ | CPU, OS และชื่อเอนจินของเครื่องที่รันโปรแกรมเชื่อมต่อ (แต่ละค่าไม่เกิน 120 อักขระ ไม่บังคับ) และ practice (true หรือ false ไม่บังคับ) ถ้า practice เป็น true แปลว่าบอตกำลังฝึกซ้อม จะไม่ถูกจับคู่บอตแข่งอัตโนมัติ และเกมระหว่างบอตที่ท้าหรือรับในช่วงนั้นจะไม่นำไปคิดเรตติ้ง โปรแกรมเชื่อมต่อจะส่งค่านี้เองเมื่อเปิดด้วยเอนจินฝึกซ้อม เซิร์ฟเวอร์จำค่าล่าสุดไว้ เมื่อเลิกฝึกซ้อมให้ส่งโดยไม่มี practice หรือส่งเป็น false |
ตัวอย่างคำขอ
curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
-H "Authorization: Bearer $FS_BOT_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"action":"agent_poll","environment":{"cpu":"Apple M4","engine":"MyEngine","os":"Darwin"}}'ตัวอย่างคำตอบ
{
"ok": true,
"bots": [
{
"id": 1,
"name": "Opponent",
"owner_id": "example",
"owner_name": "Example",
"owner": {
"player_id": "example",
"name": "Example",
"photo": "/img/no_profile.gif",
"country_code": "",
"country_name": "",
"country_flag_url": "",
"donor": false,
"verified_player_id": ""
},
"engine_level": null,
"rating": {
"rating": 1200,
"rd": 300,
"official": false,
"official_progress": {
"kind": "provisional",
"current": 0,
"required": 50
},
"color": "pz-rating-unofficial",
"high_rating": 1200,
"wins": 0,
"losses": 0,
"draws": 0,
"streak": 0,
"best_streak": 0
},
"supported_games": ["std"],
"auto_match": true,
"bio": "",
"registered_at": "2026-11-12 18:00:00",
"registered_at_ts": 1794474000,
"environment": {
"cpu": "Apple M4",
"engine": "MyEngine",
"os": "Darwin",
"practice": false
},
"is_builtin": false,
"online": true,
"disabled": false,
"deleted": false,
"photo": "/flipsight/bot_match_api.php?action=photo&bot_id=1&v=1794474000000"
},
{
"id": 2,
"name": "MyBot",
"owner_id": "example",
"owner_name": "Example",
"owner": {
"player_id": "example",
"name": "Example",
"photo": "/img/no_profile.gif",
"country_code": "",
"country_name": "",
"country_flag_url": "",
"donor": false,
"verified_player_id": ""
},
"engine_level": null,
"rating": {
"rating": 1200,
"rd": 300,
"official": false,
"official_progress": {
"kind": "provisional",
"current": 0,
"required": 50
},
"color": "pz-rating-unofficial",
"high_rating": 1200,
"wins": 0,
"losses": 0,
"draws": 0,
"streak": 0,
"best_streak": 0
},
"supported_games": ["std", "anti"],
"auto_match": true,
"bio": "",
"registered_at": "2026-11-12 18:00:00",
"registered_at_ts": 1794474000,
"environment": {
"cpu": "Apple M4",
"engine": "MyEngine",
"os": "Darwin",
"practice": false
},
"is_builtin": false,
"online": true,
"disabled": false,
"deleted": false,
"photo": "/flipsight/bot_match_api.php?action=photo&bot_id=2&v=1794474000000"
}
],
"record_bots": [],
"matches": [
{
"id": 123,
"bot_a": 1,
"bot_b": 2,
"game_mode": "std",
"time_limit_sec": 60,
"status": "running",
"state": {
"version": 1,
"game_mode": "std",
"time_limit_sec": 60,
"status": "active",
"revision": 1,
"started_ms": 1799999980000,
"last_now_ms": 1799999984200,
"finished_ms": null,
"opening_moves": [19, 20, 29, 34, 26, 11, 43, 37],
"opening_history": [
{
"color": 1,
"move": 19
},
{
"color": 2,
"move": 20
},
{
"color": 1,
"move": 29
},
{
"color": 2,
"move": 34
},
{
"color": 1,
"move": 26
},
{
"color": 2,
"move": 11
},
{
"color": 1,
"move": 43
},
{
"color": 2,
"move": 37
}
],
"legs": [
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 1, 2, 2, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"turn": 2,
"black_participant": 0,
"placed": {
"1": 5,
"2": 4
},
"clocks_ms": [55800, 60000],
"turn_started_ms": 1799999984200,
"revision": 1,
"history": [
{
"color": 1,
"participant": 0,
"move": 44,
"at_ms": 1799999984200
}
],
"status": "active",
"discs": [5, 8],
"finished_ms": null,
"legal_moves": [18, 21, 22, 25, 30, 33, 38, 42, 50, 51, 52]
},
{
"board": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"turn": 1,
"black_participant": 1,
"placed": {
"1": 4,
"2": 4
},
"clocks_ms": [60000, 60000],
"turn_started_ms": 1799999980000,
"revision": 0,
"history": [],
"status": "active",
"discs": [3, 9],
"finished_ms": null,
"legal_moves": [2, 3, 12, 25, 42, 44, 45]
}
],
"totals": [14, 11],
"winner": null,
"finish_reason": null,
"board_size": 8,
"blocked_squares": []
},
"auto": false,
"expires_ms": 1800000080000,
"ranked": true,
"rating_applied": false,
"rating_before": [null, null],
"rating_after": [null, null],
"created_at": "2026-11-12 19:00:00"
}
],
"me": "example",
"bot": {
"id": 2,
"name": "MyBot",
"owner_id": "example",
"owner_name": "Example",
"owner": {
"player_id": "example",
"name": "Example",
"photo": "/img/no_profile.gif",
"country_code": "",
"country_name": "",
"country_flag_url": "",
"donor": false,
"verified_player_id": ""
},
"engine_level": null,
"rating": {
"rating": 1200,
"rd": 300,
"official": false,
"official_progress": {
"kind": "provisional",
"current": 0,
"required": 50
},
"color": "pz-rating-unofficial",
"high_rating": 1200,
"wins": 0,
"losses": 0,
"draws": 0,
"streak": 0,
"best_streak": 0
},
"supported_games": ["std", "anti"],
"auto_match": true,
"bio": "",
"registered_at": "2026-11-12 18:00:00",
"registered_at_ts": 1794474000,
"environment": {
"cpu": "Apple M4",
"engine": "MyEngine",
"os": "Darwin",
"practice": false
},
"is_builtin": false,
"online": true,
"disabled": false,
"deleted": false,
"photo": "/flipsight/bot_match_api.php?action=photo&bot_id=2&v=1794474000000"
},
"server_now_ms": 1800000000000
}ออบเจ็กต์การแข่งขัน (matches[])
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
id | integer | ID การแข่งขัน (ติดลบสำหรับเกมกับคน) |
kind | string | human สำหรับเกมกับคน |
bot_a | integer | ID บอตของผู้เข้าร่วม 0 (0 สำหรับคน) |
bot_b | integer | ID บอตของผู้เข้าร่วม 1 (0 สำหรับคน) |
game_mode | string | เกม (std, anti, grand, octagon, reversi) |
time_limit_sec | integer | จำนวนวินาทีต่อเกม ต่อสี |
status | string | pending, running, finished, aborted, rejected, expired |
expires_ms | integer | กำหนดเวลาตอบรับ (มิลลิวินาที) |
ranked | boolean | การแข่งขันคิดอันดับหรือไม่ |
state.status | string | active, finished หรือ aborted |
state.game_mode | string | เกม (std, anti, grand, octagon, reversi) |
state.board_size | integer 8 | 10 | จำนวนช่องต่อด้าน 8 หรือ 10 |
state.blocked_squares | integer[] | ช่องที่ใช้ไม่ได้ (เฉพาะแปดเหลี่ยม ไม่ได้เรียงลำดับ) |
state.legs | array | เกม (2 เกมระหว่างบอต, 1 เกมกับคน) |
state.totals | [integer, integer] | ผลรวมเม็ดของทั้งสองเกมตามผู้เข้าร่วม |
state.winner | integer | null | ผู้เข้าร่วมที่ชนะ 0 หรือ 1, null เมื่อเสมอ |
state.finish_reason | string | null | discs, timeout, double_timeout, disconnect, no_first_move |
ออบเจ็กต์เกม (state.legs[])
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
board | integer[64 | 100] | 64 / 100 ช่อง, 0=ว่าง 1=ดำ 2=ขาว |
turn | integer | สีที่ต้องเดิน 1 (ดำ) หรือ 2 (ขาว) |
legal_moves | integer[] | ช่องที่ถูกกติกาของสีที่ต้องเดิน ว่างสำหรับเกมที่จบแล้ว |
placed | {"1": integer, "2": integer} | จำนวนหมากที่แต่ละสีวางแล้ว คีย์ "1" (ดำ) และ "2" (ขาว) |
black_participant | integer | ผู้เข้าร่วมที่เล่นดำในเกมนี้ |
clocks_ms | [number, number] | ค่าฐานของเวลาที่เหลือเรียงตามสี (ดำ, ขาว) |
turn_started_ms | integer | เวลาของเซิร์ฟเวอร์เมื่อตาปัจจุบันเริ่ม |
revision | integer | เพิ่มขึ้น 1 ทุกครั้งที่เกมนี้เปลี่ยน |
status | string | active, finished, timeout, stopped |
history | array | ตาที่เดินแล้ว (color, participant, move, at_ms) |
discs | [integer, integer] | จำนวนเม็ดเรียงตามสี (ดำ, ขาว) |
result_discs | [integer, integer] (optional) | คะแนนตัดสิน [ดำ, ขาว] ของเกมที่แพ้เพราะหมดเวลา (ไม่บังคับ) หากมีค่านี้ ให้ใช้รวมแทนจำนวนหมาก (discs) คะแนนของฝ่ายที่หมดเวลาและคู่แข่ง: std และ reversi 0 ต่อ 64, grand 0 ต่อ 100, octagon 0 ต่อ 88, anti 64 ต่อ 0 อีกเกมหนึ่งเล่นต่อไป และเมื่อทั้งสองเกมจบ ผลรวมจะตัดสินผู้ชนะ บันทึกที่จบไปแล้วก่อนหน้านี้จะไม่ถูกคำนวณใหม่ |
POST agent_challenge
ท้าประลอง
ระบุคู่แข่งด้วย target_id (หมายเลขบอต) หรือ target_name (ชื่อบอต) ซึ่งดูได้ในรายการ bots ของคำตอบ agent_poll บอต FlipSight ในตัว (is_builtin: true) ท้าผ่าน API ได้ในเกมทุกประเภท และเซิร์ฟเวอร์จะเริ่มการแข่งทันที ถ้ามีเกมรูปแบบอื่นกับบอตในตัวกำลังแข่งอยู่ 2 การแข่งแล้ว จะได้ HTTP 409 พร้อม site bot variant matches are full
| พารามิเตอร์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
action | string "agent_challenge" | จำเป็น | ชื่อแอ็กชันที่จะเรียก |
target_id | integer ≥ 1 | ไม่บังคับ | ID ของบอตที่จะท้า |
target_name | string | ไม่บังคับ | ชื่อของบอตที่จะท้า (แทน target_id) |
game_mode | string std | anti | octagon | grand | reversi | ไม่บังคับ | เกมสำหรับการแข่งระหว่างบอต ค่าเริ่มต้นคือ std บอตทั้งสองต้องรองรับ มาตรฐาน / Anti / Octagon / Grand (10x10) / รีเวอร์ซีคลาสสิก |
ตัวอย่างคำขอ
curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
-H "Authorization: Bearer $FS_BOT_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"action":"agent_challenge","target_id":2,"game_mode":"anti"}'ตัวอย่างคำตอบ
{
"ok": true,
"match_id": 123,
"server_now_ms": 1800000000000
}POST agent_accept
ยอมรับ
| พารามิเตอร์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
action | string "agent_accept" | จำเป็น | ชื่อแอ็กชันที่จะเรียก |
match_id | integer ≥ 1 | จำเป็น | ID การแข่งขัน (ติดลบสำหรับเกมกับคน) |
ตัวอย่างคำขอ
curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
-H "Authorization: Bearer $FS_BOT_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"action":"agent_accept","match_id":123}'ตัวอย่างคำตอบ
{
"ok": true,
"match": {
"id": 123,
"status": "running",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}POST agent_reject
ปฏิเสธ
| พารามิเตอร์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
action | string "agent_reject" | จำเป็น | ชื่อแอ็กชันที่จะเรียก |
match_id | integer ≥ 1 | จำเป็น | ID การแข่งขัน (ติดลบสำหรับเกมกับคน) |
ตัวอย่างคำขอ
curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
-H "Authorization: Bearer $FS_BOT_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"action":"agent_reject","match_id":123}'ตัวอย่างคำตอบ
{
"ok": true,
"match": {
"id": 123,
"status": "rejected",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}POST agent_move
ส่งตาเดิน
| พารามิเตอร์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
action | string "agent_move" | จำเป็น | ชื่อแอ็กชันที่จะเรียก |
match_id | integer ≠ 0 | จำเป็น | ID การแข่งขัน (ติดลบสำหรับเกมกับคน) |
round_no | integer ≥ 0 | ไม่บังคับ | เฉพาะเกมกับคน: round_no จากออบเจ็กต์การแข่งขัน |
room_created_at | string | ไม่บังคับ | เฉพาะเกมกับคน: room_created_at จากออบเจ็กต์การแข่งขัน |
leg | integer 0 | 1 | จำเป็น | เกม 0 หรือ 1 (0 สำหรับเกมกับคน) |
revision | integer ≥ 0 | จำเป็น | revision ของเกมที่ใช้คำนวณตาเดิน หากเกมเปลี่ยนไปแล้วจะได้ 409 stale_revision |
move | integer -1..99 | string | จำเป็น | ช่อง 0-(board_size²−1), -1 สำหรับการผ่าน / d3 / D3 / pass |
ตัวอย่างคำขอ
curl 'https://othelloclub.com/flipsight/bot_match_api.php' \
-H "Authorization: Bearer $FS_BOT_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"action":"agent_move","match_id":123,"leg":1,"revision":0,"move":2}'ตัวอย่างคำตอบ
{
"ok": true,
"match": {
"id": 123,
"status": "running",
"bot_a": 1,
"bot_b": 2
},
"server_now_ms": 1800000000000
}ข้อผิดพลาด
ข้อผิดพลาดมีรูปแบบ {"ok": false, "error": "…"} หากได้ 401 ให้หยุดและตรวจโทเคน หากได้ 409 ให้อ่านสถานะปัจจุบันแล้วตรวจตาเดิน revision และตาที่ถูกกติกา 429 หมายถึงบอตหนึ่งตัวเปิดสตรีมมากเกินไป ให้ลองข้อผิดพลาดการเชื่อมต่อและ 503 ซ้ำโดยเพิ่มเวลารอขึ้นเรื่อย ๆ และอย่าส่งตาที่ผิดกติกาซ้ำ
{"ok":false,"error":"stale_revision","server_now_ms":1800000000000}| HTTP | error |
|---|---|
| 400 | JSON object required / unknown action |
| 401 | invalid bot credential |
| 403 | access denied / invalid csrf |
| 405 | POST required |
| 409 | stale_revision / not_your_turn / illegal_move / leg_finished / match not running / match timed out / stale room revision / invalid move coordinate / move outside board / unsupported game mode / both bots must be connected / bot already has an active match / bot busy / challenge expired or answered / site bot variant matches are full / target bot not found |
| 413 | request too large |
| 429 | too many streams (bot-stream) |
| 503 | bot match storage unavailable |