icoa-cli 2.19.651 → 2.19.652
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/learn-cup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as o}from"node:fs";import chalk from"chalk";import{playSavedReplay as t,resolveEngineDir as n,saveJudgeReplay as a}from"../commands/battle.js";import{getConfig as s}from"./config.js";import{learnPost as r}from"./learn-contest.js";import{DEFAULT_PVP_BOT as l,printKnobs as i,setKnob as c,writeTemplate as u}from"./learn-pvp.js";export const CUP_RE=/^\/?(?:cup|cups|杯赛|ถ้วย)(?:\s+(.*))?$/i;const d="https://practice.icoa2026.au/assets/translations/cup/index.html?c=",b=()=>{const e=(s().language||"en").toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("th")?"th":"en"},p=(e,o,t)=>{const n=b();return"zh"===n?o:"th"===n?t??e:e},y=(e,o,t={},n=2e4)=>r(`/api/icoa/learn/cup/${e}`,{token:o.toUpperCase(),...t},n),g=new Set(["back","b","quit","q","exit","card","lesson","卡片"]),h={1:["Round of 64","64 强","รอบ 64 ทีม"],2:["Round of 32","32 强","รอบ 32 ทีม"],3:["Round of 16","16 强","รอบ 16 ทีม"],4:["Quarter-final","8 强","รอบ 8 ทีม"],5:["Semi-final","半决赛","รอบรองชนะเลิศ"],6:["Final","决赛","รอบชิงชนะเลิศ"]},k=e=>{const o=h[e];return o?p(o[0],o[1],o[2]):`R${e}`},m=e=>"knobs"===e?p("🔧 KNOBS cup","🔧 旋钮杯","🔧 ถ้วย KNOBS"):"code"===e?p("✍ CODE cup","✍ 代码杯","✍ ถ้วย CODE"):"",$=e=>"knobs"===e?p("this cup accepts only the 15-knob bot: template knobs, then set <KNOB> <value>, then up","这个杯赛只收 15 个旋钮的 bot:template knobs,再 set <旋钮> <值>,再 up","ถ้วยนี้รับเฉพาะ bot แบบ 15 knobs: template knobs แล้ว set <KNOB> <ค่า> แล้ว up"):"code"===e?p("any Python bot — write your own act(u) (the knob bot is fine too)","任何 Python bot 都可以 —— 自己写 act(u)(旋钮 bot 也行)","รับ bot Python แบบใดก็ได้ — เขียน act(u) เองได้ (bot แบบ knobs ก็ได้)"):"",f=e=>({1:"R64",2:"R32",3:"R16",4:"QF",5:"SF",6:"F"}[e]??`R${e}`),w=(e,o)=>e+" ".repeat(Math.max(0,o-(e=>{let o=0;for(const t of e){const e=t.codePointAt(0)??0;e>=3633&&e<=3642&&3634!==e&&3635!==e||e>=3655&&e<=3662||(o+=e>65535||e>=11904&&e<=42191||e>=44032&&e<=55203||e>=63744&&e<=64255?2:1)}return o})(e))),v=e=>{if(!e)return chalk.gray("—");const o="filler"===e.kind?`🤖 ${e.nick}`:e.nick,t=chalk.gray(` #${e.slot}`);return e.forfeit?chalk.gray(`${o} #${e.slot} (${p("no bot","没交 bot","ไม่มี bot")})`):(e.me?chalk.bold.yellow(o):chalk.white(o))+t};function _(e,o){switch(e){case"no_cup":return p("No cup with that passcode — check it with your teacher.","没有这个口令的杯赛 —— 跟老师核对一下口令。","ไม่มีถ้วยที่ใช้รหัสนี้ — ลองถามครูอีกครั้ง");case"closed":return p("Registration is closed — the bracket has started.","报名已截止 —— 对阵已经开始。","ปิดรับสมัครแล้ว — การแข่งขันเริ่มแล้ว");case"full":return p("The cup is full (64 players).","杯赛满了(64 人)。","ถ้วยเต็มแล้ว (64 คน)");case"same_device":return p("This computer already has a player in this cup — one person, one computer, one entry.","这台电脑已经有人报名了这个杯赛 —— 一人、一台电脑、一个名额。","คอมพิวเตอร์เครื่องนี้มีผู้เล่นในถ้วยนี้แล้ว — หนึ่งคน หนึ่งเครื่อง หนึ่งสิทธิ์");case"nick_taken":return p("That nickname is taken — pick another.","这个昵称被用了 —— 换一个。","ชื่อเล่นนี้มีคนใช้แล้ว — เลือกชื่ออื่น");case"bad_nick":return p("Nickname: 2–12 letters, digits, _ or - (no spaces, not your real name).","昵称:2–12 个字母、数字、_ 或 -(不要空格,不要真名)。","ชื่อเล่น: ตัวอักษรอังกฤษ ตัวเลข _ หรือ - จำนวน 2–12 ตัว (ไม่มีช่องว่าง ไม่ใช้ชื่อจริง)");case"locked":return p("Bots were locked when round 1 started — you play with the version shown above.","第一轮开始时 bot 已锁定 —— 比赛用的是上面显示的那个版本。","bot ถูกล็อกตอนเริ่มรอบแรกแล้ว — ใช้เวอร์ชันที่แสดงด้านบน");case"has_slot":return p("You already have a number — it stays yours.","你已经有号码了,不能再换。","คุณมีหมายเลขแล้ว — เปลี่ยนไม่ได้");case"wrong_course":return p("The cup is for BattleAI tokens (battleai-48).","杯赛只对 BattleAI 课程 token(battleai-48)开放。","ถ้วยนี้สำหรับ token ของ BattleAI (battleai-48) เท่านั้น");default:return o||p("The server did not answer — try again.","服务器没有回应 —— 再试一次。","เซิร์ฟเวอร์ไม่ตอบ — ลองอีกครั้ง")}}const N=(e,o)=>{const t=e.entrants.find(e=>e.slot===o);return t?{slot:o,nick:t.nick,kind:t.kind,level:t.level,forfeit:0,me:e.me?.slot===o}:null};function O(e,o){const t=e.a?.slot===o,[n,a]=t?[e.wins_a,e.wins_b]:[e.wins_b,e.wins_a],s=e.winner_slot===o,r="tiebreak"===e.decided_by?p(" (tie-break game)","(加赛一局定胜负)"," (เกมตัดสิน)"):"value"===e.decided_by?p(" (army value)","(按兵力价值)"," (ตัดสินด้วยมูลค่ากองทัพ)"):"coin"===e.decided_by?p(" (coin toss)","(抽签)"," (เสี่ยงทาย)"):"walkover"===e.decided_by?p(" (walkover)","(对手弃权)"," (ชนะบาย)"):"admin"===e.decided_by?p(" (decided by the teacher)","(老师判定)"," (ครูตัดสิน)"):"",l=null!=n&&null!=a?` ${n}-${a}`:"";return(s?chalk.green(`✅ ${p("won","赢","ชนะ")}${l}`):chalk.red(`❌ ${p("lost","输","แพ้")}${l}`))+chalk.gray(r)}function B(e){console.log();const o="open"===e.state?p("registration open — waiting for the teacher to start","报名中 —— 等老师开赛","เปิดรับสมัคร — รอครูเริ่มการแข่งขัน"):"done"===e.state?p("finished","已结束","จบแล้ว"):`${k(e.round)} · ${"judging"===e.round_state?p("playing now","正在判","กำลังแข่ง"):"done"===e.round_state?p("done — next round when the teacher starts it","本轮结束 —— 等老师开下一轮","จบรอบแล้ว — รอครูเริ่มรอบถัดไป"):e.round_state}`;console.log(chalk.bold.white(` 🏆 ${e.label||e.cup}`)+(e.division?chalk.bold.cyan(` · ${m(e.division)}`):"")+chalk.gray(` · ${o} · ${e.players} ${p("players","人","คน")}`));const t=e.me;if(!t)return;const n=t.slot;if(console.log(chalk.white(` ${p("You","你","คุณ")}: `)+chalk.bold.yellow(t.nick)+chalk.gray(n?` #${n}`:` · ${p("no number yet","还没选号","ยังไม่ได้เลือกหมายเลข")}`)+chalk.gray(t.locked_version?` · 🔒 bot v${t.locked_version}`:t.forfeit?` · ${p("no bot — out","没交 bot,弃权","ไม่มี bot — ตกรอบ")}`:t.versions.length?` · bot v${t.versions[t.versions.length-1].version}`:` · ${p("no bot uploaded yet","还没上传 bot","ยังไม่ได้อัปโหลด bot")}`)),!n)return;const a=function(e){const o=new Set;for(const t of e.bracket)for(const e of t)if("done"===e.state)for(const t of[e.a,e.b])t&&t.slot!==e.winner_slot&&o.add(t.slot);return o}(e);let s=!t.forfeit;for(let o=1;o<=6;o++){const t=chalk.cyan(w(f(o),4)),r=e.bracket[o-1]?.find(e=>e.a?.slot===n||e.b?.slot===n);if(r){const e=r.a?.slot===n?r.b:r.a,o="done"===r.state?O(r,n):"judging"===r.state?chalk.yellow(`⚖ ${p("playing…","正在判…","กำลังแข่ง…")}`):"error"===r.state?chalk.yellow(p("⚖ judge hiccup — the teacher will re-run it","⚖ 判分出了点问题,老师会重判","⚖ ระบบตัดสินขัดข้อง — ครูจะสั่งแข่งใหม่")):chalk.gray(p("waiting","等待","รอ")),a=r.replays?chalk.gray(` 🎬 replay ${r.id} 1`):"";if(console.log(` ${t} vs ${v(e)} ${o}${a}`),"done"===r.state&&r.winner_slot!==n){s=!1,console.log(chalk.gray(p(" ↳ Your run ends here. Keep watching: bracket, and replay any game."," ↳ 你的比赛到这里结束。可以继续看:bracket 看对阵,replay 看任何一局。"," ↳ เส้นทางของคุณจบที่นี่ ดูต่อได้: bracket ดูสาย และ replay ดูเกมไหนก็ได้")));break}continue}if(!s)break;const l=1<<o-1,i=(n-1>>o<<o)+((n-1>>o-1)%2==0?l:0)+1,c=[];for(let o=i;o<i+l;o++){if(a.has(o))continue;const t=N(e,o);t&&c.push(t)}const u=c.slice(0,3).map(e=>v(e)),d=c.length>3?chalk.gray(" +"+(c.length-3)):"",b=chalk.gray(l>1?` (#${i}–${i+l-1})`:` (#${i})`);"open"===e.state&&1===o?console.log(` ${t} vs ${c.length?u.join(chalk.gray(" · ")):chalk.gray(p("a bot, drawn when round 1 starts","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่มรอบแรก"))}${b}`):console.log(` ${t} ${chalk.gray(p("next: one of","可能对上:","อาจเจอ:"))} ${c.length?u.join(chalk.gray(" · "))+d:chalk.gray("open"===e.state?p("bots, drawn at the start","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่ม"):p("(to be decided)","(待定)","(ยังไม่รู้)"))}${b}`)}if("done"===e.state&&e.champion_slot){const o=N(e,e.champion_slot);console.log(chalk.bold.yellow(` 🏆 ${p("Champion","冠军","แชมป์")}: `)+(o?v(o):"?")+(o&&o.slot===n?chalk.bold.green(p(" — that is you! 🎉"," —— 就是你!🎉"," — คือคุณ! 🎉")):""))}}function C(e,o){const t=e.bracket[o-1];if(t){console.log(chalk.bold.white(` ${k(o)}`)+chalk.gray(` · ${t.length} ${p("matches","场","นัด")}`));for(const e of t){const o="done"===e.state&&null!=e.winner_slot&&e.winner_slot===e.a?.slot,t="done"===e.state&&null!=e.winner_slot&&e.winner_slot===e.b?.slot,n="done"===e.state?null!=e.wins_a?`${e.wins_a}-${e.wins_b}`:"walkover"===e.decided_by?"w/o":"—":"judging"===e.state?"⚖":"…",a=e.a?"filler"===e.a.kind?`🤖${e.a.nick}`:e.a.nick:"—",s=e.b?"filler"===e.b.kind?`🤖${e.b.nick}`:e.b.nick:"—",r=e.a?.me||e.b?.me,l=(o,t)=>t?chalk.bold.green(o):"done"===e.state?chalk.gray(o):chalk.white(o);console.log(` ${chalk.gray(w(`#${e.id}`,6))}${l(w(a,14),o)} ${chalk.cyan(w(n,5))} ${l(w(s,14),t)}`+(e.replays?chalk.gray(" 🎬"):"")+(r?chalk.yellow(p(" ← you"," ← 你"," ← คุณ")):""))}console.log(chalk.gray(p(" replay <match#> <game#> — e.g. replay 41 1"," replay <场号> <局号> —— 例如 replay 41 1"," replay <เลขนัด> <เลขเกม> — เช่น replay 41 1")))}else console.log(chalk.gray(p(` ${k(o)} has not started yet.`,` ${k(o)}还没开始。`,` ${k(o)} ยังไม่เริ่ม`)))}function K(e){const o=e.entrants.filter(e=>"player"===e.kind);console.log(chalk.bold.white(` ${o.length} ${p("players","人报名","ผู้เล่น")}`));const t=o.map(e=>`${e.nick}${e.slot?`#${e.slot}`:""}${e.has_bot?" ✓":""}`);for(let e=0;e<t.length;e+=3)console.log(" "+t.slice(e,e+3).map(e=>chalk.white(w(e,20))).join(""));console.log(chalk.gray(p(" ✓ = bot uploaded"," ✓ = 已上传 bot"," ✓ = อัปโหลด bot แล้ว")))}function S(e){const o=new Set(e.entrants.map(e=>e.slot).filter(e=>!!e)),t=[];for(let e=1;e<=64;e++)o.has(e)||t.push(e);return t}function T(o){const t=o.me,n=e=>chalk.bold.green(e),a=(e,o)=>console.log(` ${e?"✅":"⬜"} ${e?chalk.gray(o):chalk.white(o)}`),s=!!t?.versions.length;console.log(),o.division&&console.log(` ${chalk.bold.cyan(m(o.division))}${chalk.gray(` — ${$(o.division)}`)}`),a(!!t,p(`joined as ${t?.nick??"?"}`,`已报名,昵称 ${t?.nick??"?"}`,`สมัครแล้ว ชื่อ ${t?.nick??"?"}`)),a(!!t?.slot,t?.slot?p(`your number: #${t.slot}`,`你的号码:#${t.slot}`,`หมายเลขของคุณ: #${t.slot}`):p("pick a number 1–64: pick 17 (or pick random)","选一个 1–64 的号码:pick 17(或 pick random)","เลือกหมายเลข 1–64: pick 17 (หรือ pick random)")),a(s||!!t?.locked_version,t?.locked_version?p(`bot locked: v${t.locked_version}`,`bot 已锁定:v${t.locked_version}`,`bot ถูกล็อก: v${t.locked_version}`):s?p(`bot uploaded: v${t.versions[t.versions.length-1].version} (up again to replace it before the start)`,`已上传 bot:v${t.versions[t.versions.length-1].version}(开赛前可以再 up 换掉)`,`อัปโหลด bot แล้ว: v${t.versions[t.versions.length-1].version} (ก่อนเริ่มแข่ง up ใหม่เพื่อเปลี่ยนได้)`):"knobs"===o.division?p("make and upload your knob bot: template knobs → set WOMBATS 6 → up (no bot by round 1 = you forfeit)","做好并上传旋钮 bot:template knobs → set WOMBATS 6 → up(第一轮开始还没交 = 弃权)","สร้างและอัปโหลด bot แบบ knobs: template knobs → set WOMBATS 6 → up (ไม่มี bot ตอนเริ่มรอบแรก = สละสิทธิ์)"):p("upload your bot: up (no bot by round 1 = you forfeit)","上传 bot:up(第一轮开始还没交 = 弃权)","อัปโหลด bot: up (ไม่มี bot ตอนเริ่มรอบแรก = สละสิทธิ์)")),a("open"!==o.state,"open"===o.state?p("wait for the teacher to start round 1","等老师开第一轮","รอครูเริ่มรอบแรก"):p("the bracket has started","对阵已开始","การแข่งขันเริ่มแล้ว"));const r=t?.slot?s||"open"!==o.state?"path":"knobs"!==o.division||e(l)?"up":"template knobs":"pick 17";console.log(chalk.gray(" ▸ ")+chalk.gray(p("next: ","下一步敲 ","พิมพ์ต่อไป: "))+n(r)+chalk.gray(" ▸ ")+n("guide")+chalk.gray(p(" the full sheet"," 完整说明"," คำอธิบายทั้งหมด")))}function j(e,o){"knobs"===o&&console.log(chalk.gray(p(" 🔧 template knobs · knobs (see them) · set <KNOB> <value> · up"," 🔧 template knobs · knobs 看旋钮 · set <旋钮> <值> · up"," 🔧 template knobs · knobs (ดู knobs) · set <KNOB> <ค่า> · up"))),console.log(chalk.gray(p(` Enter = my path · pick N · up · bracket [round] · who · replay <match#> <game#> · web · guide · card (${e})`,` 回车 = 我的路线 · pick N 选号 · up 上传 · bracket [轮] · who · replay <场号> <局号> · web · guide · card 回卡片 (${e})`,` Enter = เส้นทางของฉัน · pick N · up · bracket [รอบ] · who · replay <นัด> <เกม> · web · guide · card (${e})`)))}function x(e,o){const t=e=>chalk.bold.green(e),n=e=>chalk.gray(e);console.log(),console.log(chalk.bold.white(p(" 🏆 The cup — how it works"," 🏆 杯赛怎么玩"," 🏆 ถ้วย — เล่นอย่างไร"))),o&&(console.log(` ${chalk.bold.cyan(m(o))}${n(` — ${$(o)}`)}`),console.log(n(p(" There are two cups today, each with its own passcode: 🔧 KNOBS (only the 15 knob values) and ✍ CODE (your own Python). You may enter both — one bot in each."," 今天有两个杯赛,各有自己的口令:🔧 旋钮杯(只改 15 个旋钮的值)和 ✍ 代码杯(自己写 Python)。两个都可以参加 —— 每个杯赛各交一个 bot。"," วันนี้มีสองถ้วย แต่ละถ้วยมีรหัสของตัวเอง: 🔧 KNOBS (เปลี่ยนเฉพาะค่า 15 knobs) และ ✍ CODE (เขียน Python เอง) เข้าได้ทั้งสองถ้วย — ถ้วยละหนึ่ง bot"))));const a=[["pick 17","choose your number 1–64. Taken? pick again; the 2nd clash → the system draws one for you","选 1–64 的号码。被占了就再选;第二次撞号由系统随机给一个","เลือกหมายเลข 1–64 ถ้ามีคนใช้แล้วให้เลือกใหม่ ชนครั้งที่ 2 ระบบสุ่มให้"],["pick random","let the system draw a free number","让系统随机给一个空号","ให้ระบบสุ่มหมายเลขว่างให้"],["up","upload ~/.icoa/battleai/bot.py (the same bot as in room). Again = replace it, until round 1 starts","上传 ~/.icoa/battleai/bot.py(和 room 里同一个 bot)。开赛前可以反复上传替换","อัปโหลด ~/.icoa/battleai/bot.py (bot เดียวกับใน room) อัปโหลดซ้ำเพื่อเปลี่ยนได้จนกว่ารอบแรกจะเริ่ม"],["template knobs","puts the 15-knob starter at bot.py (already have one? template knobs force; the old file becomes bot.py.bak)","把 15 个旋钮的起步 bot 放到 bot.py(已经有了?template knobs force,旧文件留作 bot.py.bak)","วาง bot เริ่มต้นแบบ 15 knobs ไว้ที่ bot.py (มีอยู่แล้ว? template knobs force ไฟล์เดิมจะเป็น bot.py.bak)"],["knobs","list the 15 knobs and their values","列出 15 个旋钮和现在的值","แสดง knobs ทั้ง 15 ตัวและค่าปัจจุบัน"],["set WOMBATS 6","change one knob value in bot.py (no editor needed)","改 bot.py 里一个旋钮的值(不用编辑器)","เปลี่ยนค่า knob หนึ่งตัวใน bot.py (ไม่ต้องเปิด editor)"],["Enter","your path: this round, who you may meet next, the road to the final","我的路线:这一轮对手、下一轮可能遇到谁、到决赛的路","เส้นทางของคุณ: คู่แข่งรอบนี้ ใครที่อาจเจอต่อไป และทางสู่รอบชิง"],["bracket 2","one round, every match (no number = the current round)","看某一轮的全部对阵(不写 = 当前轮)","ดูทุกนัดของรอบหนึ่ง (ไม่ใส่เลข = รอบปัจจุบัน)"],["replay 41 1","watch match #41, game 1 (any match, any player)","看第 41 场的第 1 局(任何一场都能看)","ดูนัด #41 เกมที่ 1 (ดูได้ทุกนัด)"],["who","everyone in the cup and their numbers","所有报名的人和号码","ผู้เล่นทุกคนและหมายเลข"],["web","the address of the big bracket page (projector / phone)","大屏 / 手机看的对阵图网址","ที่อยู่หน้าสายการแข่งขัน (โปรเจกเตอร์ / มือถือ)"]];for(const[e,o,s,r]of a)console.log(` ${t(w(e,16))}${n(p(o,s,r))}`);console.log(),console.log(chalk.bold.white(p(" Rules"," 规则"," กติกา")));for(const[e,o,t]of[["64 numbers. Numbers nobody picked get graded bots (gauntlet levels, spread across the bracket).","64 个号码。没人选的号码由分档 bot 补上(挑战台等级,分散在整个对阵里)。","64 หมายเลข หมายเลขที่ไม่มีใครเลือกจะใส่ bot ตามระดับ (กระจายทั่วสาย)"],["Knock-out: R64 → R32 → R16 → QF → SF → Final. The teacher starts every round.","单败淘汰:64 → 32 → 16 → 8 → 4 → 决赛。每一轮由老师开始。","แพ้คัดออก: 64 → 32 → 16 → 8 → 4 → ชิง ครูเป็นคนเริ่มทุกรอบ"],["A match = one game on each of the 3 maps; more game wins goes through.","一场 = 3 张图各一局,赢得局数多的晋级。","หนึ่งนัด = 3 แผนที่ แผนที่ละ 1 เกม ชนะมากกว่าได้ไปต่อ"],["Level after 3 games → one tie-break game; still level → higher army value.","3 局打平 → 加赛一局;还是平 → 比兵力价值。","เสมอหลัง 3 เกม → เกมตัดสิน 1 เกม ยังเสมอ → เทียบมูลค่ากองทัพ"],["Your bot is LOCKED when round 1 starts. No bot by then = you forfeit.","第一轮开始时 bot 锁定。那时还没上传 = 弃权。","bot จะถูกล็อกตอนเริ่มรอบแรก ถ้ายังไม่อัปโหลด = สละสิทธิ์"],["One person, one computer, one entry: a second account on the same computer is refused.","一人、一台电脑、一个名额:同一台电脑的第二个账号进不来。","หนึ่งคน หนึ่งเครื่อง หนึ่งสิทธิ์: บัญชีที่สองบนเครื่องเดียวกันจะถูกปฏิเสธ"]])console.log(n(` · ${p(e,o,t)}`));console.log(n(` ${p("Big bracket","大屏对阵图","สายการแข่งขัน")}: ${d}${e}`))}async function A(e){for(;;){const o=await e(chalk.white(p(" Nickname for the cup (2–12 letters/digits, not your real name; empty = cancel): "," 杯赛昵称(2–12 个字母/数字,不要真名;直接回车 = 取消):"," ชื่อเล่นในถ้วย (2–12 ตัวอักษร/ตัวเลข ไม่ใช่ชื่อจริง; Enter เปล่า = ยกเลิก): ")));if(null===o)return null;const t=o.trim();if(!t)return null;if(/^[A-Za-z0-9_-]{2,12}$/.test(t))return t;console.log(chalk.yellow(" "+_("bad_nick")))}}export async function runCup(s,r,h,k={}){const f=h.trim().replace(/^(?:join|enter|进)\s+/i,"").toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,8);if(!f){const e=await y("status",s);if(console.log(),console.log(chalk.bold.white(p(" 🏆 Cup"," 🏆 杯赛"," 🏆 ถ้วย"))+chalk.gray(p(" — the 64-player knock-out your teacher runs."," —— 老师组织的 64 强淘汰赛。"," — การแข่งแบบแพ้คัดออก 64 คนที่ครูจัด"))),e.ok&&e.data?.cups.length)for(const o of e.data.cups)console.log(` ${chalk.bold.cyan(w(o.cup,7))} ${chalk.white(w(o.label||"",22))} ${chalk.cyan(w(m(o.division),14))}${chalk.gray(`${p("you are","你是","คุณคือ")} ${o.nick}`)}`);else e.ok||console.log(chalk.yellow(" "+_(e.code,e.message)));return console.log(chalk.gray(" ▸ ")+chalk.bold.green(p("cup <PASSCODE>","cup <口令>","cup <รหัส>"))+chalk.gray(p(" the passcode comes from your teacher, e.g. cup BKK26"," 口令由老师给,例如 cup BKK26"," รหัสได้จากครู เช่น cup BKK26"))),"back"}let v=null;const N=await y("status",s,{cup:f});if(N.ok&&N.data)v=N.data;else{if("not_member"!==N.code)return console.log(chalk.yellow(" "+_(N.code,N.message))),"back";{const e=await y("join",s,{cup:f,probe:!0});if(!e.ok||!e.data)return console.log(chalk.yellow(" "+_(e.code,e.message))),"back";if(console.log(chalk.white(` ${p("Entering","进入","กำลังเข้า")} `)+chalk.bold.cyan(f)+chalk.gray(` ${e.data.label||""} · ${e.data.players} ${p("players","人","คน")}`)),e.data.division&&console.log(` ${chalk.bold.cyan(m(e.data.division))}${chalk.gray(` — ${$(e.data.division)}`)}`),"open"!==e.data.state)return console.log(chalk.yellow(" "+_("closed"))+chalk.gray(` ${d}${f}`)),"back";for(let e=0;e<4&&!v;e++){const e=await A(r);if(null===e)return"back";const o=await y("join",s,{cup:f,nick:e});if(o.ok&&o.data)v=o.data;else if(console.log(chalk.yellow(" "+_(o.code,o.message))),"same_device"===o.code||"closed"===o.code||"full"===o.code)return"back"}if(!v)return"back";x(f,v.division)}}const O=chalk.bold.magenta(`icoa cup ${f}> `),E=async()=>{const e=await y("status",s,{cup:f});return e.ok&&e.data?(v=e.data,!0):(console.log(chalk.yellow(" "+_(e.code,e.message))),!1)};for(T(v),B(v),j(f,v.division);;){const h=await r(O);if(null===h)return"back";const m=h.trim(),$=m.toLowerCase();if(g.has($))return"back";if("menu"===$)return"menu";if(!m||/^(?:path|me|route|路线|เส้นทาง|status)$/i.test(m)){await E()&&B(v),j(f,v.division);continue}if(/^(?:help|\?|帮助|ช่วย)$/i.test(m)){await E(),T(v),j(f,v.division);continue}if(/^(?:guide|说明|คู่มือ)$/i.test(m)){x(f,v.division),j(f,v.division);continue}if(/^(?:web|url|网页)$/i.test(m)){console.log(chalk.white(` ${d}${f}`)+chalk.gray(p(" — open on the projector or a phone"," —— 投影或手机打开"," — เปิดบนโปรเจกเตอร์หรือมือถือ")));continue}if(/^(?:who|players|名单|ผู้เล่น)$/i.test(m)){await E()&&K(v);continue}const w=/^(?:bracket|round|对阵|สาย)(?:\s+(\d))?$/i.exec(m);if(w){await E()&&C(v,w[1]?Number(w[1]):Math.max(1,v.round));continue}const N=/^(?:pick|选|เลือก)(?:\s+(random|\d+))?$/i.exec(m);if(N){if(!N[1]){console.log(chalk.gray(p(" pick 17 — a number 1–64, or pick random"," pick 17 —— 1–64 的号码,或 pick random"," pick 17 — หมายเลข 1–64 หรือ pick random")));continue}const e="random"===N[1].toLowerCase(),o=await y("pick",s,e?{cup:f,random:!0}:{cup:f,slot:Number(N[1])});if(o.ok&&o.data){v=o.data;const e=o.data.how??"";console.log(chalk.green(" ✓ ")+chalk.white(p(`Your number is #${o.data.picked}`,`你的号码是 #${o.data.picked}`,`หมายเลขของคุณคือ #${o.data.picked}`))+chalk.gray("random_after_2_clashes"===e?p(" — second clash, so the system drew it for you"," —— 第二次撞号,系统帮你抽的"," — ชนเป็นครั้งที่สอง ระบบจึงสุ่มให้"):"random_asked"===e?p(" — drawn at random"," —— 随机抽的"," — สุ่มให้"):"")),T(v)}else if("taken"===o.code){if(console.log(chalk.yellow(p(" That number is taken — pick another one. Next clash: the system picks for you."," 这个号码被占了 —— 再选一个。再撞一次就由系统随机给。"," หมายเลขนี้มีคนใช้แล้ว — เลือกใหม่ ถ้าชนอีกครั้งระบบจะสุ่มให้"))),await E()){const e=S(v);console.log(chalk.gray(` ${p("free","空号","ว่าง")}: ${e.slice(0,24).join(" ")}${e.length>24?" …":""}`))}}else console.log(chalk.yellow(" "+_(o.code,o.message)));continue}if(/^(?:knobs?|旋钮)$/i.test(m)||/^set$/i.test(m)){if(!e(l)){console.log(chalk.yellow(p(" No bot.py yet — "," 还没有 bot.py —— "," ยังไม่มี bot.py — "))+chalk.bold.green("template knobs"));continue}i(o(l,"utf8"));continue}const A=/^(?:set|改)\s+(.+)$/i.exec(m);if(A){if(!e(l)){console.log(chalk.yellow(p(" No bot.py yet — "," 还没有 bot.py —— "," ยังไม่มี bot.py — "))+chalk.bold.green("template knobs"));continue}c(o(l,"utf8"),A[1],p(" next: up uploads it to the cup"," 下一步:up 上传到杯赛"," ขั้นต่อไป: up เพื่ออัปโหลดเข้าถ้วย"));continue}const R=/^(?:template|模板)(?:\s+(knobs?|code|新手|高手|旋钮|代码))?(?:\s+(force|覆盖))?$/i.exec(m);if(R){const e=/^(?:code|高手|代码)$/i.test(R[1]??"")?"code":"knobs";if("code"===e&&"knobs"===v.division){console.log(chalk.yellow(p(" This is the 🔧 KNOBS cup — it takes only the knob bot. Type template knobs (custom code goes in the ✍ CODE cup)."," 这是 🔧 旋钮杯 —— 只收旋钮 bot。请敲 template knobs(自己写的代码去 ✍ 代码杯)。"," นี่คือถ้วย 🔧 KNOBS — รับเฉพาะ bot แบบ knobs พิมพ์ template knobs (โค้ดที่เขียนเองให้ส่งในถ้วย ✍ CODE)")));continue}if(!await n()){console.log(chalk.yellow(p(" The arena engine is not on this computer yet: type card, then battle once (it downloads the engine), then come back."," 这台电脑还没有竞技场引擎:先 card 回卡片,进一次 battle(会下载引擎),再回来。"," เครื่องนี้ยังไม่มี engine: พิมพ์ card แล้วเข้า battle หนึ่งครั้ง (จะดาวน์โหลด engine) แล้วกลับมา")));continue}await u(e,!!R[2],"zh"===b())&&console.log(chalk.gray(" ▸ ")+chalk.bold.green("knobs"===e?"set WOMBATS 6":"up")+chalk.gray("knobs"===e?p(" change a knob, then up"," 改一个旋钮,然后 up"," เปลี่ยน knob แล้ว up"):p(" after you edit bot.py"," 改好 bot.py 之后"," หลังจากแก้ bot.py")));continue}const P=/^(?:up|upload|submit|上传|อัปโหลด)(?:\s+(.+))?$/i.exec(m);if(P){const t=(P[1]??"").trim()||l;if(!e(t)){console.log(chalk.yellow(` ${p("Not found","找不到","ไม่พบไฟล์")}: ${t}`)+chalk.gray(p(" — type template first"," —— 先敲 template"," — พิมพ์ template ก่อน")));continue}let n="";try{n=o(t,"utf8")}catch{console.log(chalk.yellow(` ${p("Could not read","读不了","อ่านไฟล์ไม่ได้")}: ${t}`));continue}const a=await y("upload",s,{cup:f,code:n,lang:b()},3e4);if(!a.ok||!a.data){console.log(chalk.yellow(" "+_(a.code,a.message)));continue}console.log(a.data.unchanged?chalk.gray(p(` Identical to v${a.data.version} — nothing new.`,` 和 v${a.data.version} 一字不差,没有新版本。`,` เหมือนกับ v${a.data.version} ทุกตัวอักษร — ไม่มีเวอร์ชันใหม่`)):chalk.green(" ✓ ")+chalk.white(p(`Uploaded as v${a.data.version} — this is your cup bot unless you upload again before the start.`,`已上传为 v${a.data.version} —— 开赛前不再上传,比赛就用它。`,`อัปโหลดเป็น v${a.data.version} แล้ว — นี่คือ bot ที่ใช้แข่ง เว้นแต่จะอัปโหลดใหม่ก่อนเริ่ม`))),await E(),T(v);continue}const D=/^(?:replay|回放|รีเพลย์)(?:\s+(\d+))?(?:\s+(\d+))?$/i.exec(m);if(D){if(!D[1]){console.log(chalk.gray(p(" replay <match#> <game#> — e.g. replay 41 1 (match numbers are in bracket and your path)"," replay <场号> <局号> —— 例如 replay 41 1(场号在 bracket 和你的路线里)"," replay <เลขนัด> <เลขเกม> — เช่น replay 41 1 (เลขนัดอยู่ใน bracket และเส้นทางของคุณ)")));continue}const e=await y("replay",s,{cup:f,match_id:Number(D[1]),game:Number(D[2]??1)},3e4);if(!e.ok||!e.data){console.log(chalk.yellow(" "+_(e.code,e.message)));continue}const o=e.data,n=0===o.seat_a?{blue:o.a,red:o.b}:1===o.seat_a?{blue:o.b,red:o.a}:null,r="a"===o.winner?o.a:"b"===o.winner?o.b:null;if(console.log(chalk.gray(` #${D[1]} ${p("game","第","เกม")} ${D[2]??1}${p(""," 局","")}${o.tiebreak?p(" (tie-break)","(加赛)"," (เกมตัดสิน)"):""} · ${o.map} · `)+(n?`🟦 ${chalk.bold.white(n.blue)}${chalk.gray(" vs ")}🟥 ${chalk.bold.white(n.red)}`:`${o.a} vs ${o.b}`)+chalk.gray(` · ${p("winner","胜者","ผู้ชนะ")}: `)+(r?chalk.bold.white(r):chalk.gray(p("draw","平局","เสมอ")))),!a(o.replay,n)){console.log(chalk.yellow(p(" Could not save the replay."," 回放存不下来。"," บันทึกรีเพลย์ไม่ได้")));continue}console.log(chalk.gray(p(" space = pause · ← → = step · ↑ ↓ = speed · q = back to the cup"," 空格 = 暂停 · ← → = 单步 · ↑ ↓ = 速度 · q = 回到杯赛"," space = หยุด · ← → = ทีละขั้น · ↑ ↓ = ความเร็ว · q = กลับไปที่ถ้วย"))),await t(k.interactive)||console.log(chalk.yellow(p(" The replay is saved, but the viewer needs Python 3.12 and the arena engine: run python setup, open battle once, then replay again."," 回放已存在本机,但播放器需要 Python 3.12 和竞技场引擎:先 python setup,再进一次 battle,回来再 replay。"," บันทึกรีเพลย์แล้ว แต่ตัวเล่นต้องใช้ Python 3.12 และ engine: รัน python setup แล้วเข้า battle หนึ่งครั้ง จากนั้น replay อีกครั้ง")));continue}console.log(chalk.gray(p(` Not a cup command: ${m}`,` 不是杯赛命令:${m}`,` ไม่ใช่คำสั่งของถ้วย: ${m}`))),j(f,v.division)}}
|
|
1
|
+
import{existsSync as e,readFileSync as o}from"node:fs";import chalk from"chalk";import{playSavedReplay as t,resolveEngineDir as n,saveJudgeReplay as a}from"../commands/battle.js";import{getConfig as s,saveConfig as r}from"./config.js";import{learnPost as i}from"./learn-contest.js";import{DEFAULT_PVP_BOT as l,printKnobs as c,setKnob as u,writeTemplate as d}from"./learn-pvp.js";export const CUP_RE=/^\/?(?:cup|cups|杯赛|ถ้วย)(?:\s+(.*))?$/i;const p="https://practice.icoa2026.au/assets/translations/cup/index.html?c=",b=()=>{const e=(s().language||"en").toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("th")?"th":"en"},y=(e,o,t)=>{const n=b();return"zh"===n?o:"th"===n?t??e:e},g=(e,o,t={},n=2e4)=>i(`/api/icoa/learn/cup/${e}`,{token:o.toUpperCase(),...t},n),h=new Set(["back","b","quit","q","exit","card","lesson","卡片"]),k={1:["Round of 64","64 强","รอบ 64 ทีม"],2:["Round of 32","32 强","รอบ 32 ทีม"],3:["Round of 16","16 强","รอบ 16 ทีม"],4:["Quarter-final","8 强","รอบ 8 ทีม"],5:["Semi-final","半决赛","รอบรองชนะเลิศ"],6:["Final","决赛","รอบชิงชนะเลิศ"]},$=e=>{const o=k[e];return o?y(o[0],o[1],o[2]):`R${e}`},m=e=>"knobs"===e?y("🔧 KNOBS cup","🔧 旋钮杯","🔧 ถ้วย KNOBS"):"code"===e?y("✍ CODE cup","✍ 代码杯","✍ ถ้วย CODE"):"",f=e=>"knobs"===e?y("this cup accepts only the 15-knob bot: template knobs, then set <KNOB> <value>, then up","这个杯赛只收 15 个旋钮的 bot:template knobs,再 set <旋钮> <值>,再 up","ถ้วยนี้รับเฉพาะ bot แบบ 15 knobs: template knobs แล้ว set <KNOB> <ค่า> แล้ว up"):"code"===e?y("any Python bot — write your own act(u) (the knob bot is fine too)","任何 Python bot 都可以 —— 自己写 act(u)(旋钮 bot 也行)","รับ bot Python แบบใดก็ได้ — เขียน act(u) เองได้ (bot แบบ knobs ก็ได้)"):"",v=e=>({1:"R64",2:"R32",3:"R16",4:"QF",5:"SF",6:"F"}[e]??`R${e}`),w=(e,o)=>e+" ".repeat(Math.max(0,o-(e=>{let o=0;for(const t of e){const e=t.codePointAt(0)??0;e>=3633&&e<=3642&&3634!==e&&3635!==e||e>=3655&&e<=3662||(o+=e>65535||e>=11904&&e<=42191||e>=44032&&e<=55203||e>=63744&&e<=64255?2:1)}return o})(e))),_=e=>{if(!e)return chalk.gray("—");const o="filler"===e.kind?`🤖 ${e.nick}`:e.nick,t=chalk.gray(` #${e.slot}`);return e.forfeit?chalk.gray(`${o} #${e.slot} (${y("no bot","没交 bot","ไม่มี bot")})`):(e.me?chalk.bold.yellow(o):chalk.white(o))+t};function N(e){if(!e?.versions.length)return null;const o=e.chosen_version;return o&&e.versions.some(e=>e.version===o)?o:e.versions[e.versions.length-1].version}function O(e){const o=e.me;if(!o?.versions.length)return void console.log(chalk.gray(y(" No versions yet — up uploads your bot as v1."," 还没有版本 —— up 上传后就是 v1。"," ยังไม่มีเวอร์ชัน — up เพื่ออัปโหลดเป็น v1")));const t=o.locked_version??N(o);console.log(),console.log(chalk.bold.white(y(" Your versions in this cup"," 你在这个杯赛的版本"," เวอร์ชันของคุณในถ้วยนี้")));for(const e of o.versions){const n=(e.ts||"").replace("T"," ").slice(5,16),a=e.version===t;console.log(` ${a?o.locked_version?"🔒":"★":" "} ${a?chalk.bold.green(`v${e.version}`):chalk.white(`v${e.version}`)}`+chalk.gray(` ${n} UTC · ${e.sha.slice(0,8)}`)+(a?chalk.green(o.locked_version?y(" ← locked, this one plays"," ← 已锁定,比赛用它"," ← ล็อกแล้ว ตัวนี้ลงแข่ง"):y(" ← your entry"," ← 正式参赛版本"," ← ตัวที่ส่งแข่ง")):""))}!o.locked_version&&"open"===e.state&&o.versions.length>1&&console.log(chalk.gray(" ▸ ")+chalk.bold.green(`use ${(o.versions.find(e=>e.version!==t)??o.versions[0]).version}`)+chalk.gray(y(" make another version your entry (until round 1 starts)"," 把另一个版本设为正式参赛版本(第一轮开始前都能换)"," เปลี่ยนเวอร์ชันที่ส่งแข่ง (เปลี่ยนได้จนกว่ารอบแรกจะเริ่ม)")))}function B(e,o){switch(e){case"no_cup":return y("No cup with that passcode — check it with your teacher.","没有这个口令的杯赛 —— 跟老师核对一下口令。","ไม่มีถ้วยที่ใช้รหัสนี้ — ลองถามครูอีกครั้ง");case"closed":return y("Registration is closed — the bracket has started.","报名已截止 —— 对阵已经开始。","ปิดรับสมัครแล้ว — การแข่งขันเริ่มแล้ว");case"full":return y("The cup is full (64 players).","杯赛满了(64 人)。","ถ้วยเต็มแล้ว (64 คน)");case"same_device":return y("This computer already has a player in this cup — one person, one computer, one entry.","这台电脑已经有人报名了这个杯赛 —— 一人、一台电脑、一个名额。","คอมพิวเตอร์เครื่องนี้มีผู้เล่นในถ้วยนี้แล้ว — หนึ่งคน หนึ่งเครื่อง หนึ่งสิทธิ์");case"nick_taken":return y("That nickname is taken — pick another.","这个昵称被用了 —— 换一个。","ชื่อเล่นนี้มีคนใช้แล้ว — เลือกชื่ออื่น");case"bad_nick":return y("Nickname: 2–12 letters, digits, _ or - (no spaces, not your real name).","昵称:2–12 个字母、数字、_ 或 -(不要空格,不要真名)。","ชื่อเล่น: ตัวอักษรอังกฤษ ตัวเลข _ หรือ - จำนวน 2–12 ตัว (ไม่มีช่องว่าง ไม่ใช้ชื่อจริง)");case"locked":return y("Bots were locked when round 1 started — you play with the version shown above.","第一轮开始时 bot 已锁定 —— 比赛用的是上面显示的那个版本。","bot ถูกล็อกตอนเริ่มรอบแรกแล้ว — ใช้เวอร์ชันที่แสดงด้านบน");case"has_slot":return y("You already have a number — it stays yours.","你已经有号码了,不能再换。","คุณมีหมายเลขแล้ว — เปลี่ยนไม่ได้");case"wrong_course":return y("The cup is for BattleAI tokens (battleai-48).","杯赛只对 BattleAI 课程 token(battleai-48)开放。","ถ้วยนี้สำหรับ token ของ BattleAI (battleai-48) เท่านั้น");default:return o||y("The server did not answer — try again.","服务器没有回应 —— 再试一次。","เซิร์ฟเวอร์ไม่ตอบ — ลองอีกครั้ง")}}const T=(e,o)=>{const t=e.entrants.find(e=>e.slot===o);return t?{slot:o,nick:t.nick,kind:t.kind,level:t.level,forfeit:0,me:e.me?.slot===o}:null};function C(e,o){const t=e.a?.slot===o,[n,a]=t?[e.wins_a,e.wins_b]:[e.wins_b,e.wins_a],s=e.winner_slot===o,r="tiebreak"===e.decided_by?y(" (tie-break game)","(加赛一局定胜负)"," (เกมตัดสิน)"):"value"===e.decided_by?y(" (army value)","(按兵力价值)"," (ตัดสินด้วยมูลค่ากองทัพ)"):"coin"===e.decided_by?y(" (coin toss)","(抽签)"," (เสี่ยงทาย)"):"walkover"===e.decided_by?y(" (walkover)","(对手弃权)"," (ชนะบาย)"):"admin"===e.decided_by?y(" (decided by the teacher)","(老师判定)"," (ครูตัดสิน)"):"",i=null!=n&&null!=a?` ${n}-${a}`:"";return(s?chalk.green(`✅ ${y("won","赢","ชนะ")}${i}`):chalk.red(`❌ ${y("lost","输","แพ้")}${i}`))+chalk.gray(r)}function x(e){console.log();const o="open"===e.state?y("registration open — waiting for the teacher to start","报名中 —— 等老师开赛","เปิดรับสมัคร — รอครูเริ่มการแข่งขัน"):"done"===e.state?y("finished","已结束","จบแล้ว"):`${$(e.round)} · ${"judging"===e.round_state?y("playing now","正在判","กำลังแข่ง"):"done"===e.round_state?y("done — next round when the teacher starts it","本轮结束 —— 等老师开下一轮","จบรอบแล้ว — รอครูเริ่มรอบถัดไป"):e.round_state}`;console.log(chalk.bold.white(` 🏆 ${e.label||e.cup}`)+(e.division?chalk.bold.cyan(` · ${m(e.division)}`):"")+chalk.gray(` · ${o} · ${e.players} ${y("players","人","คน")}`));const t=e.me;if(!t)return;const n=t.slot;if(console.log(chalk.white(` ${y("You","你","คุณ")}: `)+chalk.bold.yellow(t.nick)+chalk.gray(n?` #${n}`:` · ${y("no number yet","还没选号","ยังไม่ได้เลือกหมายเลข")}`)+chalk.gray(t.locked_version?` · 🔒 bot v${t.locked_version}`:t.forfeit?` · ${y("no bot — out","没交 bot,弃权","ไม่มี bot — ตกรอบ")}`:t.versions.length?` · bot v${t.versions[t.versions.length-1].version}`:` · ${y("no bot uploaded yet","还没上传 bot","ยังไม่ได้อัปโหลด bot")}`)),!n)return;const a=function(e){const o=new Set;for(const t of e.bracket)for(const e of t)if("done"===e.state)for(const t of[e.a,e.b])t&&t.slot!==e.winner_slot&&o.add(t.slot);return o}(e);let s=!t.forfeit;for(let o=1;o<=6;o++){const t=chalk.cyan(w(v(o),4)),r=e.bracket[o-1]?.find(e=>e.a?.slot===n||e.b?.slot===n);if(r){const e=r.a?.slot===n?r.b:r.a,o="done"===r.state?C(r,n):"judging"===r.state?chalk.yellow(`⚖ ${y("playing…","正在判…","กำลังแข่ง…")}`):"error"===r.state?chalk.yellow(y("⚖ judge hiccup — the teacher will re-run it","⚖ 判分出了点问题,老师会重判","⚖ ระบบตัดสินขัดข้อง — ครูจะสั่งแข่งใหม่")):chalk.gray(y("waiting","等待","รอ")),a=r.replays?chalk.gray(` 🎬 replay ${r.id} 1`):"";if(console.log(` ${t} vs ${_(e)} ${o}${a}`),"done"===r.state&&r.winner_slot!==n){s=!1,console.log(chalk.gray(y(" ↳ Your run ends here. Keep watching: bracket, and replay any game."," ↳ 你的比赛到这里结束。可以继续看:bracket 看对阵,replay 看任何一局。"," ↳ เส้นทางของคุณจบที่นี่ ดูต่อได้: bracket ดูสาย และ replay ดูเกมไหนก็ได้")));break}continue}if(!s)break;const i=1<<o-1,l=(n-1>>o<<o)+((n-1>>o-1)%2==0?i:0)+1,c=[];for(let o=l;o<l+i;o++){if(a.has(o))continue;const t=T(e,o);t&&c.push(t)}const u=c.slice(0,3).map(e=>_(e)),d=c.length>3?chalk.gray(" +"+(c.length-3)):"",p=chalk.gray(i>1?` (#${l}–${l+i-1})`:` (#${l})`);"open"===e.state&&1===o?console.log(` ${t} vs ${c.length?u.join(chalk.gray(" · ")):chalk.gray(y("a bot, drawn when round 1 starts","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่มรอบแรก"))}${p}`):console.log(` ${t} ${chalk.gray(y("next: one of","可能对上:","อาจเจอ:"))} ${c.length?u.join(chalk.gray(" · "))+d:chalk.gray("open"===e.state?y("bots, drawn at the start","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่ม"):y("(to be decided)","(待定)","(ยังไม่รู้)"))}${p}`)}if("done"===e.state&&e.champion_slot){const o=T(e,e.champion_slot);console.log(chalk.bold.yellow(` 🏆 ${y("Champion","冠军","แชมป์")}: `)+(o?_(o):"?")+(o&&o.slot===n?chalk.bold.green(y(" — that is you! 🎉"," —— 就是你!🎉"," — คือคุณ! 🎉")):""))}}function S(e,o){const t=e.bracket[o-1];if(t){console.log(chalk.bold.white(` ${$(o)}`)+chalk.gray(` · ${t.length} ${y("matches","场","นัด")}`));for(const e of t){const o="done"===e.state&&null!=e.winner_slot&&e.winner_slot===e.a?.slot,t="done"===e.state&&null!=e.winner_slot&&e.winner_slot===e.b?.slot,n="done"===e.state?null!=e.wins_a?`${e.wins_a}-${e.wins_b}`:"walkover"===e.decided_by?"w/o":"—":"judging"===e.state?"⚖":"…",a=e.a?"filler"===e.a.kind?`🤖${e.a.nick}`:e.a.nick:"—",s=e.b?"filler"===e.b.kind?`🤖${e.b.nick}`:e.b.nick:"—",r=e.a?.me||e.b?.me,i=(o,t)=>t?chalk.bold.green(o):"done"===e.state?chalk.gray(o):chalk.white(o);console.log(` ${chalk.gray(w(`#${e.id}`,6))}${i(w(a,14),o)} ${chalk.cyan(w(n,5))} ${i(w(s,14),t)}`+(e.replays?chalk.gray(" 🎬"):"")+(r?chalk.yellow(y(" ← you"," ← 你"," ← คุณ")):""))}console.log(chalk.gray(y(" replay <match#> <game#> — e.g. replay 41 1"," replay <场号> <局号> —— 例如 replay 41 1"," replay <เลขนัด> <เลขเกม> — เช่น replay 41 1")))}else console.log(chalk.gray(y(` ${$(o)} has not started yet.`,` ${$(o)}还没开始。`,` ${$(o)} ยังไม่เริ่ม`)))}function j(e){const o=e.entrants.filter(e=>"player"===e.kind);console.log(chalk.bold.white(` ${o.length} ${y("players","人报名","ผู้เล่น")}`));const t=o.map(e=>`${e.nick}${e.slot?`#${e.slot}`:""}${e.has_bot?" ✓":""}`);for(let e=0;e<t.length;e+=3)console.log(" "+t.slice(e,e+3).map(e=>chalk.white(w(e,20))).join(""));console.log(chalk.gray(y(" ✓ = bot uploaded"," ✓ = 已上传 bot"," ✓ = อัปโหลด bot แล้ว")))}function K(e){const o=new Set(e.entrants.map(e=>e.slot).filter(e=>!!e)),t=[];for(let e=1;e<=64;e++)o.has(e)||t.push(e);return t}function E(o){const t=o.me,n=e=>chalk.bold.green(e),a=(e,o)=>console.log(` ${e?"✅":"⬜"} ${e?chalk.gray(o):chalk.white(o)}`),s=!!t?.versions.length;console.log(),o.division&&console.log(` ${chalk.bold.cyan(m(o.division))}${chalk.gray(` — ${f(o.division)}`)}`),a(!!t,y(`joined as ${t?.nick??"?"}`,`已报名,昵称 ${t?.nick??"?"}`,`สมัครแล้ว ชื่อ ${t?.nick??"?"}`)),a(!!t?.slot,t?.slot?y(`your number: #${t.slot}`,`你的号码:#${t.slot}`,`หมายเลขของคุณ: #${t.slot}`):y("pick a number 1–64: pick 17 (or pick random)","选一个 1–64 的号码:pick 17(或 pick random)","เลือกหมายเลข 1–64: pick 17 (หรือ pick random)"));const r=N(t),i=t?.versions.length??0;a(s||!!t?.locked_version,t?.locked_version?y(`bot locked: v${t.locked_version}`,`bot 已锁定:v${t.locked_version}`,`bot ถูกล็อก: v${t.locked_version}`):s?y(`your entry: v${r} (${i} version${1===i?"":"s"}) — up = new version · versions = list · use N = switch`,`正式参赛版本:v${r}(共 ${i} 个版本)—— up = 新版本 · versions = 列表 · use N = 换版本`,`ตัวที่ส่งแข่ง: v${r} (มี ${i} เวอร์ชัน) — up = เวอร์ชันใหม่ · versions = รายการ · use N = เปลี่ยน`):"knobs"===o.division?y("make and upload your knob bot: template knobs → set WOMBATS 6 → up (no bot by round 1 = you forfeit)","做好并上传旋钮 bot:template knobs → set WOMBATS 6 → up(第一轮开始还没交 = 弃权)","สร้างและอัปโหลด bot แบบ knobs: template knobs → set WOMBATS 6 → up (ไม่มี bot ตอนเริ่มรอบแรก = สละสิทธิ์)"):y("upload your bot: up (no bot by round 1 = you forfeit)","上传 bot:up(第一轮开始还没交 = 弃权)","อัปโหลด bot: up (ไม่มี bot ตอนเริ่มรอบแรก = สละสิทธิ์)")),a("open"!==o.state,"open"===o.state?y("wait for the teacher to start round 1","等老师开第一轮","รอครูเริ่มรอบแรก"):y("the bracket has started","对阵已开始","การแข่งขันเริ่มแล้ว"));const c=t?.slot?s||"open"!==o.state?"path":"knobs"!==o.division||e(l)?"up":"template knobs":"pick 17";console.log(chalk.gray(" ▸ ")+chalk.gray(y("next: ","下一步敲 ","พิมพ์ต่อไป: "))+n(c)+chalk.gray(" ▸ ")+n("guide")+chalk.gray(y(" the full sheet"," 完整说明"," คำอธิบายทั้งหมด")))}function R(e,o){"knobs"===o&&console.log(chalk.gray(y(" 🔧 template knobs · knobs (see them) · set <KNOB> <value> · up"," 🔧 template knobs · knobs 看旋钮 · set <旋钮> <值> · up"," 🔧 template knobs · knobs (ดู knobs) · set <KNOB> <ค่า> · up"))),console.log(chalk.gray(y(` Enter = my path · pick N · up · versions · use N · bracket [round] · who · replay <match#> <game#> · web · guide · lang en|zh|th · card (${e})`,` 回车 = 我的路线 · pick N 选号 · up 上传 · versions 版本 · use N 选版本 · bracket [轮] · who · replay <场号> <局号> · web · guide · lang en|zh|th · card 回卡片 (${e})`,` Enter = เส้นทางของฉัน · pick N · up · versions · use N · bracket [รอบ] · who · replay <นัด> <เกม> · web · guide · lang en|zh|th · card (${e})`)))}function z(e,o){const t=e=>chalk.bold.green(e),n=e=>chalk.gray(e);console.log(),console.log(chalk.bold.white(y(" 🏆 The cup — how it works"," 🏆 杯赛怎么玩"," 🏆 ถ้วย — เล่นอย่างไร"))),o&&(console.log(` ${chalk.bold.cyan(m(o))}${n(` — ${f(o)}`)}`),console.log(n(y(" There are two cups today, each with its own passcode: 🔧 KNOBS (only the 15 knob values) and ✍ CODE (your own Python). You may enter both — one bot in each."," 今天有两个杯赛,各有自己的口令:🔧 旋钮杯(只改 15 个旋钮的值)和 ✍ 代码杯(自己写 Python)。两个都可以参加 —— 每个杯赛各交一个 bot。"," วันนี้มีสองถ้วย แต่ละถ้วยมีรหัสของตัวเอง: 🔧 KNOBS (เปลี่ยนเฉพาะค่า 15 knobs) และ ✍ CODE (เขียน Python เอง) เข้าได้ทั้งสองถ้วย — ถ้วยละหนึ่ง bot"))));const a=[["pick 17","choose your number 1–64. Taken? pick again; the 2nd clash → the system draws one for you","选 1–64 的号码。被占了就再选;第二次撞号由系统随机给一个","เลือกหมายเลข 1–64 ถ้ามีคนใช้แล้วให้เลือกใหม่ ชนครั้งที่ 2 ระบบสุ่มให้"],["pick random","let the system draw a free number","让系统随机给一个空号","ให้ระบบสุ่มหมายเลขว่างให้"],["up","upload ~/.icoa/battleai/bot.py as a NEW version (v1, v2, v3 …); the newest becomes your entry. Old versions are kept","把 ~/.icoa/battleai/bot.py 上传成一个新版本(v1、v2、v3…),最新的自动成为正式参赛版本;旧版本都保留","อัปโหลด ~/.icoa/battleai/bot.py เป็นเวอร์ชันใหม่ (v1, v2, v3 …) ตัวล่าสุดจะเป็นตัวที่ส่งแข่ง เวอร์ชันเก่ายังเก็บไว้"],["versions","list every version you uploaded; ★ = your entry","列出你上传过的所有版本;★ = 正式参赛版本","แสดงทุกเวอร์ชันที่อัปโหลด ★ = ตัวที่ส่งแข่ง"],["use 2","make v2 your entry instead (any version, as often as you like, until round 1 starts)","改用 v2 作为正式参赛版本(任意版本、换几次都行,第一轮开始前有效)","ใช้ v2 เป็นตัวที่ส่งแข่งแทน (เวอร์ชันใดก็ได้ เปลี่ยนกี่ครั้งก็ได้ จนกว่ารอบแรกจะเริ่ม)"],["lang th","switch the language: lang en · lang zh · lang th","切换语言:lang en · lang zh · lang th","เปลี่ยนภาษา: lang en · lang zh · lang th"],["template knobs","puts the 15-knob starter at bot.py (already have one? template knobs force; the old file becomes bot.py.bak)","把 15 个旋钮的起步 bot 放到 bot.py(已经有了?template knobs force,旧文件留作 bot.py.bak)","วาง bot เริ่มต้นแบบ 15 knobs ไว้ที่ bot.py (มีอยู่แล้ว? template knobs force ไฟล์เดิมจะเป็น bot.py.bak)"],["knobs","list the 15 knobs and their values","列出 15 个旋钮和现在的值","แสดง knobs ทั้ง 15 ตัวและค่าปัจจุบัน"],["set WOMBATS 6","change one knob value in bot.py (no editor needed)","改 bot.py 里一个旋钮的值(不用编辑器)","เปลี่ยนค่า knob หนึ่งตัวใน bot.py (ไม่ต้องเปิด editor)"],["Enter","your path: this round, who you may meet next, the road to the final","我的路线:这一轮对手、下一轮可能遇到谁、到决赛的路","เส้นทางของคุณ: คู่แข่งรอบนี้ ใครที่อาจเจอต่อไป และทางสู่รอบชิง"],["bracket 2","one round, every match (no number = the current round)","看某一轮的全部对阵(不写 = 当前轮)","ดูทุกนัดของรอบหนึ่ง (ไม่ใส่เลข = รอบปัจจุบัน)"],["replay 41 1","watch match #41, game 1 (any match, any player)","看第 41 场的第 1 局(任何一场都能看)","ดูนัด #41 เกมที่ 1 (ดูได้ทุกนัด)"],["who","everyone in the cup and their numbers","所有报名的人和号码","ผู้เล่นทุกคนและหมายเลข"],["web","the address of the big bracket page (projector / phone)","大屏 / 手机看的对阵图网址","ที่อยู่หน้าสายการแข่งขัน (โปรเจกเตอร์ / มือถือ)"]];for(const[e,o,s,r]of a)console.log(` ${t(w(e,16))}${n(y(o,s,r))}`);console.log(),console.log(chalk.bold.white(y(" Rules"," 规则"," กติกา")));for(const[e,o,t]of[["64 numbers. Numbers nobody picked get graded bots (gauntlet levels, spread across the bracket).","64 个号码。没人选的号码由分档 bot 补上(挑战台等级,分散在整个对阵里)。","64 หมายเลข หมายเลขที่ไม่มีใครเลือกจะใส่ bot ตามระดับ (กระจายทั่วสาย)"],["Knock-out: R64 → R32 → R16 → QF → SF → Final. The teacher starts every round.","单败淘汰:64 → 32 → 16 → 8 → 4 → 决赛。每一轮由老师开始。","แพ้คัดออก: 64 → 32 → 16 → 8 → 4 → ชิง ครูเป็นคนเริ่มทุกรอบ"],["A match = one game on each of the 3 maps; more game wins goes through.","一场 = 3 张图各一局,赢得局数多的晋级。","หนึ่งนัด = 3 แผนที่ แผนที่ละ 1 เกม ชนะมากกว่าได้ไปต่อ"],["Level after 3 games → one tie-break game; still level → higher army value.","3 局打平 → 加赛一局;还是平 → 比兵力价值。","เสมอหลัง 3 เกม → เกมตัดสิน 1 เกม ยังเสมอ → เทียบมูลค่ากองทัพ"],["Your ENTRY version (★ in versions) is LOCKED when round 1 starts. No bot by then = you forfeit.","第一轮开始时锁定你的正式参赛版本(versions 里的 ★)。那时还没上传 = 弃权。","เวอร์ชันที่ส่งแข่ง (★ ใน versions) จะถูกล็อกตอนเริ่มรอบแรก ถ้ายังไม่อัปโหลด = สละสิทธิ์"],["One person, one computer, one entry: a second account on the same computer is refused.","一人、一台电脑、一个名额:同一台电脑的第二个账号进不来。","หนึ่งคน หนึ่งเครื่อง หนึ่งสิทธิ์: บัญชีที่สองบนเครื่องเดียวกันจะถูกปฏิเสธ"]])console.log(n(` · ${y(e,o,t)}`));console.log(n(` ${y("Big bracket","大屏对阵图","สายการแข่งขัน")}: ${p}${e}`))}async function A(e){for(;;){const o=await e(chalk.white(y(" Nickname for the cup (2–12 letters/digits, not your real name; empty = cancel): "," 杯赛昵称(2–12 个字母/数字,不要真名;直接回车 = 取消):"," ชื่อเล่นในถ้วย (2–12 ตัวอักษร/ตัวเลข ไม่ใช่ชื่อจริง; Enter เปล่า = ยกเลิก): ")));if(null===o)return null;const t=o.trim();if(!t)return null;if(/^[A-Za-z0-9_-]{2,12}$/.test(t))return t;console.log(chalk.yellow(" "+B("bad_nick")))}}export async function runCup(s,i,k,$={}){const v=k.trim().replace(/^(?:join|enter|进)\s+/i,"").toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,8);if(!v){const e=await g("status",s);if(console.log(),console.log(chalk.bold.white(y(" 🏆 Cup"," 🏆 杯赛"," 🏆 ถ้วย"))+chalk.gray(y(" — the 64-player knock-out your teacher runs."," —— 老师组织的 64 强淘汰赛。"," — การแข่งแบบแพ้คัดออก 64 คนที่ครูจัด"))),e.ok&&e.data?.cups.length)for(const o of e.data.cups)console.log(` ${chalk.bold.cyan(w(o.cup,7))} ${chalk.white(w(o.label||"",22))} ${chalk.cyan(w(m(o.division),14))}${chalk.gray(`${y("you are","你是","คุณคือ")} ${o.nick}`)}`);else e.ok||console.log(chalk.yellow(" "+B(e.code,e.message)));return console.log(chalk.gray(" ▸ ")+chalk.bold.green(y("cup <PASSCODE>","cup <口令>","cup <รหัส>"))+chalk.gray(y(" the passcode comes from your teacher, e.g. cup BKK26"," 口令由老师给,例如 cup BKK26"," รหัสได้จากครู เช่น cup BKK26"))),"back"}let _=null;const N=await g("status",s,{cup:v});if(N.ok&&N.data)_=N.data;else{if("not_member"!==N.code)return console.log(chalk.yellow(" "+B(N.code,N.message))),"back";{const e=await g("join",s,{cup:v,probe:!0});if(!e.ok||!e.data)return console.log(chalk.yellow(" "+B(e.code,e.message))),"back";if(console.log(chalk.white(` ${y("Entering","进入","กำลังเข้า")} `)+chalk.bold.cyan(v)+chalk.gray(` ${e.data.label||""} · ${e.data.players} ${y("players","人","คน")}`)),e.data.division&&console.log(` ${chalk.bold.cyan(m(e.data.division))}${chalk.gray(` — ${f(e.data.division)}`)}`),"open"!==e.data.state)return console.log(chalk.yellow(" "+B("closed"))+chalk.gray(` ${p}${v}`)),"back";for(let e=0;e<4&&!_;e++){const e=await A(i);if(null===e)return"back";const o=await g("join",s,{cup:v,nick:e});if(o.ok&&o.data)_=o.data;else if(console.log(chalk.yellow(" "+B(o.code,o.message))),"same_device"===o.code||"closed"===o.code||"full"===o.code)return"back"}if(!_)return"back";z(v,_.division)}}const T=chalk.bold.magenta(`icoa cup ${v}> `),C=async()=>{const e=await g("status",s,{cup:v});return e.ok&&e.data?(_=e.data,!0):(console.log(chalk.yellow(" "+B(e.code,e.message))),!1)};for(E(_),x(_),R(v,_.division);;){const k=await i(T);if(null===k)return"back";const m=k.trim(),f=m.toLowerCase();if(h.has(f))return"back";if("menu"===f)return"menu";if(!m||/^(?:path|me|route|路线|เส้นทาง|status)$/i.test(m)){await C()&&x(_),R(v,_.division);continue}if(/^(?:help|\?|帮助|ช่วย)$/i.test(m)){await C(),E(_),R(v,_.division);continue}if(/^(?:guide|说明|คู่มือ)$/i.test(m)){z(v,_.division),R(v,_.division);continue}if(/^(?:web|url|网页)$/i.test(m)){console.log(chalk.white(` ${p}${v}`)+chalk.gray(y(" — open on the projector or a phone"," —— 投影或手机打开"," — เปิดบนโปรเจกเตอร์หรือมือถือ")));continue}if(/^(?:who|players|名单|ผู้เล่น)$/i.test(m)){await C()&&j(_);continue}const w=/^(?:bracket|round|对阵|สาย)(?:\s+(\d))?$/i.exec(m);if(w){await C()&&S(_,w[1]?Number(w[1]):Math.max(1,_.round));continue}const N=/^(?:pick|选|เลือก)(?:\s+(random|\d+))?$/i.exec(m);if(N){if(!N[1]){console.log(chalk.gray(y(" pick 17 — a number 1–64, or pick random"," pick 17 —— 1–64 的号码,或 pick random"," pick 17 — หมายเลข 1–64 หรือ pick random")));continue}const e="random"===N[1].toLowerCase(),o=await g("pick",s,e?{cup:v,random:!0}:{cup:v,slot:Number(N[1])});if(o.ok&&o.data){_=o.data;const e=o.data.how??"";console.log(chalk.green(" ✓ ")+chalk.white(y(`Your number is #${o.data.picked}`,`你的号码是 #${o.data.picked}`,`หมายเลขของคุณคือ #${o.data.picked}`))+chalk.gray("random_after_2_clashes"===e?y(" — second clash, so the system drew it for you"," —— 第二次撞号,系统帮你抽的"," — ชนเป็นครั้งที่สอง ระบบจึงสุ่มให้"):"random_asked"===e?y(" — drawn at random"," —— 随机抽的"," — สุ่มให้"):"")),E(_)}else if("taken"===o.code){if(console.log(chalk.yellow(y(" That number is taken — pick another one. Next clash: the system picks for you."," 这个号码被占了 —— 再选一个。再撞一次就由系统随机给。"," หมายเลขนี้มีคนใช้แล้ว — เลือกใหม่ ถ้าชนอีกครั้งระบบจะสุ่มให้"))),await C()){const e=K(_);console.log(chalk.gray(` ${y("free","空号","ว่าง")}: ${e.slice(0,24).join(" ")}${e.length>24?" …":""}`))}}else console.log(chalk.yellow(" "+B(o.code,o.message)));continue}const A=/^lang(?:\s+(\S+))?$/i.exec(m);if(A){const e=(A[1]??"").toLowerCase().slice(0,2);if(!["en","zh","th"].includes(e)){console.log(chalk.gray(` ${y("language","语言","ภาษา")}: `)+chalk.white(b())+chalk.gray(" · ")+["en","zh","th"].map(e=>chalk.bold.green(`lang ${e}`)).join(chalk.gray(" / ")));continue}r({language:e}),console.log(chalk.green(" ✓ ")+chalk.gray("zh"===e?"语言 → ":"th"===e?"ภาษา → ":"language → ")+chalk.white(e)),await C()&&E(_),R(v,_.division);continue}if(/^(?:versions?|版本|เวอร์ชัน)$/i.test(m)){await C()&&O(_);continue}const P=/^(?:use|用|ใช้)(?:\s+v?(\d+))?$/i.exec(m);if(P){if(!P[1]){await C()&&O(_);continue}const e=await g("use",s,{cup:v,version:Number(P[1])});if(!e.ok||!e.data){console.log(chalk.yellow(" "+("no_version"===e.code?y(`You have no v${P[1]} in this cup — type versions.`,`你在这个杯赛没有 v${P[1]} —— 敲 versions 看列表。`,`คุณไม่มี v${P[1]} ในถ้วยนี้ — พิมพ์ versions`):B(e.code,e.message))));continue}_=e.data,console.log(chalk.green(" ✓ ")+chalk.white(y(`v${P[1]} is now your entry — it is locked when round 1 starts.`,`v${P[1]} 现在是正式参赛版本 —— 第一轮开始时锁定。`,`ตอนนี้ v${P[1]} เป็นตัวที่ส่งแข่ง — จะถูกล็อกตอนเริ่มรอบแรก`))),O(_);continue}if(/^(?:knobs?|旋钮)$/i.test(m)||/^set$/i.test(m)){if(!e(l)){console.log(chalk.yellow(y(" No bot.py yet — "," 还没有 bot.py —— "," ยังไม่มี bot.py — "))+chalk.bold.green("template knobs"));continue}c(o(l,"utf8"));continue}const Y=/^(?:set|改)\s+(.+)$/i.exec(m);if(Y){if(!e(l)){console.log(chalk.yellow(y(" No bot.py yet — "," 还没有 bot.py —— "," ยังไม่มี bot.py — "))+chalk.bold.green("template knobs"));continue}u(o(l,"utf8"),Y[1],y(" next: up uploads it to the cup"," 下一步:up 上传到杯赛"," ขั้นต่อไป: up เพื่ออัปโหลดเข้าถ้วย"));continue}const D=/^(?:template|模板)(?:\s+(knobs?|code|新手|高手|旋钮|代码))?(?:\s+(force|覆盖))?$/i.exec(m);if(D){const e=/^(?:code|高手|代码)$/i.test(D[1]??"")?"code":"knobs";if("code"===e&&"knobs"===_.division){console.log(chalk.yellow(y(" This is the 🔧 KNOBS cup — it takes only the knob bot. Type template knobs (custom code goes in the ✍ CODE cup)."," 这是 🔧 旋钮杯 —— 只收旋钮 bot。请敲 template knobs(自己写的代码去 ✍ 代码杯)。"," นี่คือถ้วย 🔧 KNOBS — รับเฉพาะ bot แบบ knobs พิมพ์ template knobs (โค้ดที่เขียนเองให้ส่งในถ้วย ✍ CODE)")));continue}if(!await n()){console.log(chalk.yellow(y(" The arena engine is not on this computer yet: type card, then battle once (it downloads the engine), then come back."," 这台电脑还没有竞技场引擎:先 card 回卡片,进一次 battle(会下载引擎),再回来。"," เครื่องนี้ยังไม่มี engine: พิมพ์ card แล้วเข้า battle หนึ่งครั้ง (จะดาวน์โหลด engine) แล้วกลับมา")));continue}await d(e,!!D[2],"zh"===b())&&console.log(chalk.gray(" ▸ ")+chalk.bold.green("knobs"===e?"set WOMBATS 6":"up")+chalk.gray("knobs"===e?y(" change a knob, then up"," 改一个旋钮,然后 up"," เปลี่ยน knob แล้ว up"):y(" after you edit bot.py"," 改好 bot.py 之后"," หลังจากแก้ bot.py")));continue}const W=/^(?:up|upload|submit|上传|อัปโหลด)(?:\s+(.+))?$/i.exec(m);if(W){const t=(W[1]??"").trim()||l;if(!e(t)){console.log(chalk.yellow(` ${y("Not found","找不到","ไม่พบไฟล์")}: ${t}`)+chalk.gray(y(" — type template first"," —— 先敲 template"," — พิมพ์ template ก่อน")));continue}let n="";try{n=o(t,"utf8")}catch{console.log(chalk.yellow(` ${y("Could not read","读不了","อ่านไฟล์ไม่ได้")}: ${t}`));continue}const a=await g("upload",s,{cup:v,code:n,lang:b()},3e4);if(!a.ok||!a.data){console.log(chalk.yellow(" "+B(a.code,a.message)));continue}console.log(a.data.unchanged?chalk.gray(y(` Identical to v${a.data.version} — nothing new.`,` 和 v${a.data.version} 一字不差,没有新版本。`,` เหมือนกับ v${a.data.version} ทุกตัวอักษร — ไม่มีเวอร์ชันใหม่`)):chalk.green(" ✓ ")+chalk.white(y(`Uploaded as v${a.data.version} — it is now your entry. Older versions are kept: versions · use N.`,`已上传为 v${a.data.version} —— 现在它是正式参赛版本。旧版本都保留:versions 看列表 · use N 换回去。`,`อัปโหลดเป็น v${a.data.version} แล้ว — ตอนนี้เป็นตัวที่ส่งแข่ง เวอร์ชันเก่ายังอยู่: versions · use N`))),await C(),E(_);continue}const F=/^(?:replay|回放|รีเพลย์)(?:\s+(\d+))?(?:\s+(\d+))?$/i.exec(m);if(F){if(!F[1]){console.log(chalk.gray(y(" replay <match#> <game#> — e.g. replay 41 1 (match numbers are in bracket and your path)"," replay <场号> <局号> —— 例如 replay 41 1(场号在 bracket 和你的路线里)"," replay <เลขนัด> <เลขเกม> — เช่น replay 41 1 (เลขนัดอยู่ใน bracket และเส้นทางของคุณ)")));continue}const e=await g("replay",s,{cup:v,match_id:Number(F[1]),game:Number(F[2]??1)},3e4);if(!e.ok||!e.data){console.log(chalk.yellow(" "+B(e.code,e.message)));continue}const o=e.data,n=0===o.seat_a?{blue:o.a,red:o.b}:1===o.seat_a?{blue:o.b,red:o.a}:null,r="a"===o.winner?o.a:"b"===o.winner?o.b:null;if(console.log(chalk.gray(` #${F[1]} ${y("game","第","เกม")} ${F[2]??1}${y(""," 局","")}${o.tiebreak?y(" (tie-break)","(加赛)"," (เกมตัดสิน)"):""} · ${o.map} · `)+(n?`🟦 ${chalk.bold.white(n.blue)}${chalk.gray(" vs ")}🟥 ${chalk.bold.white(n.red)}`:`${o.a} vs ${o.b}`)+chalk.gray(` · ${y("winner","胜者","ผู้ชนะ")}: `)+(r?chalk.bold.white(r):chalk.gray(y("draw","平局","เสมอ")))),!a(o.replay,n)){console.log(chalk.yellow(y(" Could not save the replay."," 回放存不下来。"," บันทึกรีเพลย์ไม่ได้")));continue}console.log(chalk.gray(y(" space = pause · ← → = step · ↑ ↓ = speed · q = back to the cup"," 空格 = 暂停 · ← → = 单步 · ↑ ↓ = 速度 · q = 回到杯赛"," space = หยุด · ← → = ทีละขั้น · ↑ ↓ = ความเร็ว · q = กลับไปที่ถ้วย"))),await t($.interactive)||console.log(chalk.yellow(y(" The replay is saved, but the viewer needs Python 3.12 and the arena engine: run python setup, open battle once, then replay again."," 回放已存在本机,但播放器需要 Python 3.12 和竞技场引擎:先 python setup,再进一次 battle,回来再 replay。"," บันทึกรีเพลย์แล้ว แต่ตัวเล่นต้องใช้ Python 3.12 และ engine: รัน python setup แล้วเข้า battle หนึ่งครั้ง จากนั้น replay อีกครั้ง")));continue}console.log(chalk.gray(y(` Not a cup command: ${m}`,` 不是杯赛命令:${m}`,` ไม่ใช่คำสั่งของถ้วย: ${m}`))),R(v,_.division)}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.652",
|
|
4
4
|
"description": "ICOA CLI — AI-native competition runtime of the International Cyber Olympiad in AI: learn, practise, and compete in AI & cybersecurity from the terminal (AI4CTF · CTF4AI · CTF4EAI)",
|
|
5
5
|
"author": "Charlie Zhu",
|
|
6
6
|
"type": "module",
|