icoa-cli 2.19.647 → 2.19.649
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/commands/ai4ctf.js +1 -1
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/ctf4vla.js +1 -1
- package/dist/commands/duck.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/learn.js +1 -1
- package/dist/lib/learn-cup.js +1 -0
- package/dist/lib/term-video.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{copyFileSync as e,existsSync as o,mkdirSync as t,readFileSync as n}from"node:fs";import{dirname as a,join as r}from"node:path";import chalk from"chalk";import{playSavedReplay as s,resolveEngineDir as l,saveJudgeReplay as c}from"../commands/battle.js";import{getConfig as i}from"./config.js";import{learnPost as u}from"./learn-contest.js";import{DEFAULT_PVP_BOT 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=",g=(e,o,t)=>{const n=(()=>{const e=(i().language||"en").toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("th")?"th":"en"})();return"zh"===n?o:"th"===n?t??e:e},y=(e,o,t={},n=2e4)=>u(`/api/icoa/learn/cup/${e}`,{token:o.toUpperCase(),...t},n),b=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","决赛","รอบชิงชนะเลิศ"]},$=e=>{const o=h[e];return o?g(o[0],o[1],o[2]):`R${e}`},m=e=>({1:"R64",2:"R32",3:"R16",4:"QF",5:"SF",6:"F"}[e]??`R${e}`),f=(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))),w=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} (${g("no bot","没交 bot","ไม่มี bot")})`):(e.me?chalk.bold.yellow(o):chalk.white(o))+t};function k(e,o){switch(e){case"no_cup":return g("No cup with that passcode — check it with your teacher.","没有这个口令的杯赛 —— 跟老师核对一下口令。","ไม่มีถ้วยที่ใช้รหัสนี้ — ลองถามครูอีกครั้ง");case"closed":return g("Registration is closed — the bracket has started.","报名已截止 —— 对阵已经开始。","ปิดรับสมัครแล้ว — การแข่งขันเริ่มแล้ว");case"full":return g("The cup is full (64 players).","杯赛满了(64 人)。","ถ้วยเต็มแล้ว (64 คน)");case"same_device":return g("This computer already has a player in this cup — one person, one computer, one entry.","这台电脑已经有人报名了这个杯赛 —— 一人、一台电脑、一个名额。","คอมพิวเตอร์เครื่องนี้มีผู้เล่นในถ้วยนี้แล้ว — หนึ่งคน หนึ่งเครื่อง หนึ่งสิทธิ์");case"nick_taken":return g("That nickname is taken — pick another.","这个昵称被用了 —— 换一个。","ชื่อเล่นนี้มีคนใช้แล้ว — เลือกชื่ออื่น");case"bad_nick":return g("Nickname: 2–12 letters, digits, _ or - (no spaces, not your real name).","昵称:2–12 个字母、数字、_ 或 -(不要空格,不要真名)。","ชื่อเล่น: ตัวอักษรอังกฤษ ตัวเลข _ หรือ - จำนวน 2–12 ตัว (ไม่มีช่องว่าง ไม่ใช้ชื่อจริง)");case"locked":return g("Bots were locked when round 1 started — you play with the version shown above.","第一轮开始时 bot 已锁定 —— 比赛用的是上面显示的那个版本。","bot ถูกล็อกตอนเริ่มรอบแรกแล้ว — ใช้เวอร์ชันที่แสดงด้านบน");case"has_slot":return g("You already have a number — it stays yours.","你已经有号码了,不能再换。","คุณมีหมายเลขแล้ว — เปลี่ยนไม่ได้");case"wrong_course":return g("The cup is for BattleAI tokens (battleai-48).","杯赛只对 BattleAI 课程 token(battleai-48)开放。","ถ้วยนี้สำหรับ token ของ BattleAI (battleai-48) เท่านั้น");default:return o||g("The server did not answer — try again.","服务器没有回应 —— 再试一次。","เซิร์ฟเวอร์ไม่ตอบ — ลองอีกครั้ง")}}const v=(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 _(e,o){const t=e.a?.slot===o,[n,a]=t?[e.wins_a,e.wins_b]:[e.wins_b,e.wins_a],r=e.winner_slot===o,s="tiebreak"===e.decided_by?g(" (tie-break game)","(加赛一局定胜负)"," (เกมตัดสิน)"):"value"===e.decided_by?g(" (army value)","(按兵力价值)"," (ตัดสินด้วยมูลค่ากองทัพ)"):"coin"===e.decided_by?g(" (coin toss)","(抽签)"," (เสี่ยงทาย)"):"walkover"===e.decided_by?g(" (walkover)","(对手弃权)"," (ชนะบาย)"):"admin"===e.decided_by?g(" (decided by the teacher)","(老师判定)"," (ครูตัดสิน)"):"",l=null!=n&&null!=a?` ${n}-${a}`:"";return(r?chalk.green(`✅ ${g("won","赢","ชนะ")}${l}`):chalk.red(`❌ ${g("lost","输","แพ้")}${l}`))+chalk.gray(s)}function j(e){console.log();const o="open"===e.state?g("registration open — waiting for the teacher to start","报名中 —— 等老师开赛","เปิดรับสมัคร — รอครูเริ่มการแข่งขัน"):"done"===e.state?g("finished","已结束","จบแล้ว"):`${$(e.round)} · ${"judging"===e.round_state?g("playing now","正在判","กำลังแข่ง"):"done"===e.round_state?g("done — next round when the teacher starts it","本轮结束 —— 等老师开下一轮","จบรอบแล้ว — รอครูเริ่มรอบถัดไป"):e.round_state}`;console.log(chalk.bold.white(` 🏆 ${e.label||e.cup}`)+chalk.gray(` · ${o} · ${e.players} ${g("players","人","คน")}`));const t=e.me;if(!t)return;const n=t.slot;if(console.log(chalk.white(` ${g("You","你","คุณ")}: `)+chalk.bold.yellow(t.nick)+chalk.gray(n?` #${n}`:` · ${g("no number yet","还没选号","ยังไม่ได้เลือกหมายเลข")}`)+chalk.gray(t.locked_version?` · 🔒 bot v${t.locked_version}`:t.forfeit?` · ${g("no bot — out","没交 bot,弃权","ไม่มี bot — ตกรอบ")}`:t.versions.length?` · bot v${t.versions[t.versions.length-1].version}`:` · ${g("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 r=!t.forfeit;for(let o=1;o<=6;o++){const t=chalk.cyan(f(m(o),4)),s=e.bracket[o-1]?.find(e=>e.a?.slot===n||e.b?.slot===n);if(s){const e=s.a?.slot===n?s.b:s.a,o="done"===s.state?_(s,n):"judging"===s.state?chalk.yellow(`⚖ ${g("playing…","正在判…","กำลังแข่ง…")}`):"error"===s.state?chalk.yellow(g("⚖ judge hiccup — the teacher will re-run it","⚖ 判分出了点问题,老师会重判","⚖ ระบบตัดสินขัดข้อง — ครูจะสั่งแข่งใหม่")):chalk.gray(g("waiting","等待","รอ")),a=s.replays?chalk.gray(` 🎬 replay ${s.id} 1`):"";if(console.log(` ${t} vs ${w(e)} ${o}${a}`),"done"===s.state&&s.winner_slot!==n){r=!1,console.log(chalk.gray(g(" ↳ Your run ends here. Keep watching: bracket, and replay any game."," ↳ 你的比赛到这里结束。可以继续看:bracket 看对阵,replay 看任何一局。"," ↳ เส้นทางของคุณจบที่นี่ ดูต่อได้: bracket ดูสาย และ replay ดูเกมไหนก็ได้")));break}continue}if(!r)break;const l=1<<o-1,c=(n-1>>o<<o)+((n-1>>o-1)%2==0?l:0)+1,i=[];for(let o=c;o<c+l;o++){if(a.has(o))continue;const t=v(e,o);t&&i.push(t)}const u=i.slice(0,3).map(e=>w(e)),d=i.length>3?chalk.gray(" +"+(i.length-3)):"",p=chalk.gray(l>1?` (#${c}–${c+l-1})`:` (#${c})`);"open"===e.state&&1===o?console.log(` ${t} vs ${i.length?u.join(chalk.gray(" · ")):chalk.gray(g("a bot, drawn when round 1 starts","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่มรอบแรก"))}${p}`):console.log(` ${t} ${chalk.gray(g("next: one of","可能对上:","อาจเจอ:"))} ${i.length?u.join(chalk.gray(" · "))+d:chalk.gray("open"===e.state?g("bots, drawn at the start","开赛时抽到的 bot","bot ที่จะสุ่มตอนเริ่ม"):g("(to be decided)","(待定)","(ยังไม่รู้)"))}${p}`)}if("done"===e.state&&e.champion_slot){const o=v(e,e.champion_slot);console.log(chalk.bold.yellow(` 🏆 ${g("Champion","冠军","แชมป์")}: `)+(o?w(o):"?")+(o&&o.slot===n?chalk.bold.green(g(" — that is you! 🎉"," —— 就是你!🎉"," — คือคุณ! 🎉")):""))}}function x(e,o){const t=e.bracket[o-1];if(t){console.log(chalk.bold.white(` ${$(o)}`)+chalk.gray(` · ${t.length} ${g("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:"—",r=e.b?"filler"===e.b.kind?`🤖${e.b.nick}`:e.b.nick:"—",s=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(f(`#${e.id}`,6))}${l(f(a,14),o)} ${chalk.cyan(f(n,5))} ${l(f(r,14),t)}`+(e.replays?chalk.gray(" 🎬"):"")+(s?chalk.yellow(g(" ← you"," ← 你"," ← คุณ")):""))}console.log(chalk.gray(g(" replay <match#> <game#> — e.g. replay 41 1"," replay <场号> <局号> —— 例如 replay 41 1"," replay <เลขนัด> <เลขเกม> — เช่น replay 41 1")))}else console.log(chalk.gray(g(` ${$(o)} has not started yet.`,` ${$(o)}还没开始。`,` ${$(o)} ยังไม่เริ่ม`)))}function N(e){const o=e.entrants.filter(e=>"player"===e.kind);console.log(chalk.bold.white(` ${o.length} ${g("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(f(e,20))).join(""));console.log(chalk.gray(g(" ✓ = bot uploaded"," ✓ = 已上传 bot"," ✓ = อัปโหลด bot แล้ว")))}function R(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 C(e){const o=e.me,t=e=>chalk.bold.green(e),n=(e,o)=>console.log(` ${e?"✅":"⬜"} ${e?chalk.gray(o):chalk.white(o)}`),a=!!o?.versions.length;console.log(),n(!!o,g(`joined as ${o?.nick??"?"}`,`已报名,昵称 ${o?.nick??"?"}`,`สมัครแล้ว ชื่อ ${o?.nick??"?"}`)),n(!!o?.slot,o?.slot?g(`your number: #${o.slot}`,`你的号码:#${o.slot}`,`หมายเลขของคุณ: #${o.slot}`):g("pick a number 1–64: pick 17 (or pick random)","选一个 1–64 的号码:pick 17(或 pick random)","เลือกหมายเลข 1–64: pick 17 (หรือ pick random)")),n(a||!!o?.locked_version,o?.locked_version?g(`bot locked: v${o.locked_version}`,`bot 已锁定:v${o.locked_version}`,`bot ถูกล็อก: v${o.locked_version}`):a?g(`bot uploaded: v${o.versions[o.versions.length-1].version} (up again to replace it before the start)`,`已上传 bot:v${o.versions[o.versions.length-1].version}(开赛前可以再 up 换掉)`,`อัปโหลด bot แล้ว: v${o.versions[o.versions.length-1].version} (ก่อนเริ่มแข่ง up ใหม่เพื่อเปลี่ยนได้)`):g("upload your bot: up (no bot by round 1 = you forfeit)","上传 bot:up(第一轮开始还没交 = 弃权)","อัปโหลด bot: up (ไม่มี bot ตอนเริ่มรอบแรก = สละสิทธิ์)")),n("open"!==e.state,"open"===e.state?g("wait for the teacher to start round 1","等老师开第一轮","รอครูเริ่มรอบแรก"):g("the bracket has started","对阵已开始","การแข่งขันเริ่มแล้ว"));const r=o?.slot?a||"open"!==e.state?"path":"up":"pick 17";console.log(chalk.gray(" ▸ ")+chalk.gray(g("next: ","下一步敲 ","พิมพ์ต่อไป: "))+t(r)+chalk.gray(" ▸ ")+t("guide")+chalk.gray(g(" the full sheet"," 完整说明"," คำอธิบายทั้งหมด")))}function T(e){console.log(chalk.gray(g(` 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 A(e){const o=e=>chalk.bold.green(e),t=e=>chalk.gray(e);console.log(),console.log(chalk.bold.white(g(" 🏆 The cup — how it works"," 🏆 杯赛怎么玩"," 🏆 ถ้วย — เล่นอย่างไร")));const n=[["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","no bot.py yet? this puts the 15-knob starter there","还没有 bot.py?这条命令放一个 15 个旋钮的起步 bot","ยังไม่มี bot.py? คำสั่งนี้จะสร้าง bot เริ่มต้นแบบ 15 knobs ให้"],["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,a,r,s]of n)console.log(` ${o(f(e,13))}${t(g(a,r,s))}`);console.log(),console.log(chalk.bold.white(g(" Rules"," 规则"," กติกา")));for(const[e,o,n]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(t(` · ${g(e,o,n)}`));console.log(t(` ${g("Big bracket","大屏对阵图","สายการแข่งขัน")}: ${p}${e}`))}async function K(e){for(;;){const o=await e(chalk.white(g(" 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(" "+k("bad_nick")))}}async function B(){if(o(d))return!0;const n=await l();if(!n)return!1;const s=r(n,"bots","knob_bot.py");return!!o(s)&&(t(a(d),{recursive:!0}),e(s,d),!0)}export async function runCup(e,t,a){const r=a.trim().replace(/^(?:join|enter|进)\s+/i,"").toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,8);if(!r){const o=await y("status",e);if(console.log(),console.log(chalk.bold.white(g(" 🏆 Cup"," 🏆 杯赛"," 🏆 ถ้วย"))+chalk.gray(g(" — the 64-player knock-out your teacher runs."," —— 老师组织的 64 强淘汰赛。"," — การแข่งแบบแพ้คัดออก 64 คนที่ครูจัด"))),o.ok&&o.data?.cups.length)for(const e of o.data.cups)console.log(` ${chalk.bold.cyan(f(e.cup,7))} ${chalk.white(f(e.label||"",22))} ${chalk.gray(`${g("you are","你是","คุณคือ")} ${e.nick}`)}`);else o.ok||console.log(chalk.yellow(" "+k(o.code,o.message)));return console.log(chalk.gray(" ▸ ")+chalk.bold.green(g("cup <PASSCODE>","cup <口令>","cup <รหัส>"))+chalk.gray(g(" the passcode comes from your teacher, e.g. cup BKK26"," 口令由老师给,例如 cup BKK26"," รหัสได้จากครู เช่น cup BKK26"))),"back"}let l=null;const i=await y("status",e,{cup:r});if(i.ok&&i.data)l=i.data;else{if("not_member"!==i.code)return console.log(chalk.yellow(" "+k(i.code,i.message))),"back";{const o=await y("join",e,{cup:r,probe:!0});if(!o.ok||!o.data)return console.log(chalk.yellow(" "+k(o.code,o.message))),"back";if(console.log(chalk.white(` ${g("Entering","进入","กำลังเข้า")} `)+chalk.bold.cyan(r)+chalk.gray(` ${o.data.label||""} · ${o.data.players} ${g("players","人","คน")}`)),"open"!==o.data.state)return console.log(chalk.yellow(" "+k("closed"))+chalk.gray(` ${p}${r}`)),"back";for(let o=0;o<4&&!l;o++){const o=await K(t);if(null===o)return"back";const n=await y("join",e,{cup:r,nick:o});if(n.ok&&n.data)l=n.data;else if(console.log(chalk.yellow(" "+k(n.code,n.message))),"same_device"===n.code||"closed"===n.code||"full"===n.code)return"back"}if(!l)return"back";A(r)}}const u=chalk.bold.magenta(`icoa cup ${r}> `),h=async()=>{const o=await y("status",e,{cup:r});return o.ok&&o.data?(l=o.data,!0):(console.log(chalk.yellow(" "+k(o.code,o.message))),!1)};for(C(l),j(l),T(r);;){const a=await t(u);if(null===a)return"back";const i=a.trim(),$=i.toLowerCase();if(b.has($))return"back";if("menu"===$)return"menu";if(!i||/^(?:path|me|route|路线|เส้นทาง|status)$/i.test(i)){await h()&&j(l),T(r);continue}if(/^(?:help|\?|帮助|ช่วย)$/i.test(i)){await h(),C(l),T(r);continue}if(/^(?:guide|说明|คู่มือ)$/i.test(i)){A(r),T(r);continue}if(/^(?:web|url|网页)$/i.test(i)){console.log(chalk.white(` ${p}${r}`)+chalk.gray(g(" — open on the projector or a phone"," —— 投影或手机打开"," — เปิดบนโปรเจกเตอร์หรือมือถือ")));continue}if(/^(?:who|players|名单|ผู้เล่น)$/i.test(i)){await h()&&N(l);continue}const m=/^(?:bracket|round|对阵|สาย)(?:\s+(\d))?$/i.exec(i);if(m){await h()&&x(l,m[1]?Number(m[1]):Math.max(1,l.round));continue}const f=/^(?:pick|选|เลือก)(?:\s+(random|\d+))?$/i.exec(i);if(f){if(!f[1]){console.log(chalk.gray(g(" pick 17 — a number 1–64, or pick random"," pick 17 —— 1–64 的号码,或 pick random"," pick 17 — หมายเลข 1–64 หรือ pick random")));continue}const o="random"===f[1].toLowerCase(),t=await y("pick",e,o?{cup:r,random:!0}:{cup:r,slot:Number(f[1])});if(t.ok&&t.data){l=t.data;const e=t.data.how??"";console.log(chalk.green(" ✓ ")+chalk.white(g(`Your number is #${t.data.picked}`,`你的号码是 #${t.data.picked}`,`หมายเลขของคุณคือ #${t.data.picked}`))+chalk.gray("random_after_2_clashes"===e?g(" — second clash, so the system drew it for you"," —— 第二次撞号,系统帮你抽的"," — ชนเป็นครั้งที่สอง ระบบจึงสุ่มให้"):"random_asked"===e?g(" — drawn at random"," —— 随机抽的"," — สุ่มให้"):"")),C(l)}else if("taken"===t.code){if(console.log(chalk.yellow(g(" That number is taken — pick another one. Next clash: the system picks for you."," 这个号码被占了 —— 再选一个。再撞一次就由系统随机给。"," หมายเลขนี้มีคนใช้แล้ว — เลือกใหม่ ถ้าชนอีกครั้งระบบจะสุ่มให้"))),await h()){const e=R(l);console.log(chalk.gray(` ${g("free","空号","ว่าง")}: ${e.slice(0,24).join(" ")}${e.length>24?" …":""}`))}}else console.log(chalk.yellow(" "+k(t.code,t.message)));continue}if(/^(?:template|模板)$/i.test(i)){o(d)?console.log(chalk.gray(` ${g("Already there","已经有了","มีอยู่แล้ว")}: ${d}`)):await B()?console.log(chalk.green(" ✓ ")+chalk.white(`${d}`)+chalk.gray(g(" — now type up"," —— 接着敲 up"," — พิมพ์ up ต่อได้เลย"))):console.log(chalk.yellow(g(" 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}const w=/^(?:up|upload|submit|上传|อัปโหลด)(?:\s+(.+))?$/i.exec(i);if(w){const t=(w[1]??"").trim()||d;if(!o(t)){console.log(chalk.yellow(` ${g("Not found","找不到","ไม่พบไฟล์")}: ${t}`)+chalk.gray(g(" — type template first"," —— 先敲 template"," — พิมพ์ template ก่อน")));continue}let a="";try{a=n(t,"utf8")}catch{console.log(chalk.yellow(` ${g("Could not read","读不了","อ่านไฟล์ไม่ได้")}: ${t}`));continue}const s=await y("upload",e,{cup:r,code:a},3e4);if(!s.ok||!s.data){console.log(chalk.yellow(" "+k(s.code,s.message)));continue}console.log(s.data.unchanged?chalk.gray(g(` Identical to v${s.data.version} — nothing new.`,` 和 v${s.data.version} 一字不差,没有新版本。`,` เหมือนกับ v${s.data.version} ทุกตัวอักษร — ไม่มีเวอร์ชันใหม่`)):chalk.green(" ✓ ")+chalk.white(g(`Uploaded as v${s.data.version} — this is your cup bot unless you upload again before the start.`,`已上传为 v${s.data.version} —— 开赛前不再上传,比赛就用它。`,`อัปโหลดเป็น v${s.data.version} แล้ว — นี่คือ bot ที่ใช้แข่ง เว้นแต่จะอัปโหลดใหม่ก่อนเริ่ม`))),await h(),C(l);continue}const v=/^(?:replay|回放|รีเพลย์)(?:\s+(\d+))?(?:\s+(\d+))?$/i.exec(i);if(v){if(!v[1]){console.log(chalk.gray(g(" 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 o=await y("replay",e,{cup:r,match_id:Number(v[1]),game:Number(v[2]??1)},3e4);if(!o.ok||!o.data){console.log(chalk.yellow(" "+k(o.code,o.message)));continue}const t=o.data,n=0===t.seat_a?{blue:t.a,red:t.b}:1===t.seat_a?{blue:t.b,red:t.a}:null,a="a"===t.winner?t.a:"b"===t.winner?t.b:null;if(console.log(chalk.gray(` #${v[1]} ${g("game","第","เกม")} ${v[2]??1}${g(""," 局","")}${t.tiebreak?g(" (tie-break)","(加赛)"," (เกมตัดสิน)"):""} · ${t.map} · `)+(n?`🟦 ${chalk.bold.white(n.blue)}${chalk.gray(" vs ")}🟥 ${chalk.bold.white(n.red)}`:`${t.a} vs ${t.b}`)+chalk.gray(` · ${g("winner","胜者","ผู้ชนะ")}: `)+(a?chalk.bold.white(a):chalk.gray(g("draw","平局","เสมอ")))),!c(t.replay,n)){console.log(chalk.yellow(g(" Could not save the replay."," 回放存不下来。"," บันทึกรีเพลย์ไม่ได้")));continue}console.log(chalk.gray(g(" space = pause · ← → = step · ↑ ↓ = speed · q = back to the cup"," 空格 = 暂停 · ← → = 单步 · ↑ ↓ = 速度 · q = 回到杯赛"," space = หยุด · ← → = ทีละขั้น · ↑ ↓ = ความเร็ว · q = กลับไปที่ถ้วย"))),await s()||console.log(chalk.yellow(g(" 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(g(` Not a cup command: ${i}`,` 不是杯赛命令:${i}`,` ไม่ใช่คำสั่งของถ้วย: ${i}`))),T(r)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function colorMode(t,o,n){if(!n)return"none";const e=`${t.LC_ALL||""}${t.LC_CTYPE||""}${t.LANG||""}`;return e&&!/utf-?8/i.test(e)||o<60?"none":"Apple_Terminal"===t.TERM_PROGRAM?"256":/truecolor|24bit/i.test(t.COLORTERM||"")||t.WT_SESSION||"vscode"===t.TERM_PROGRAM?"truecolor":"256"}export function videoSize(t){const o=Math.max(32,Math.min(96,t-4));let n=Math.round(9*o/16);return n%2&&(n+=1),{w:o,h:n}}const t=[0,95,135,175,215,255],o=t=>t<48?0:t<115?1:Math.min(5,Math.floor((t-35)/40));export function rgbTo256(n,e,r){const i=o(n),c=o(e),s=o(r),u=t[i],a=t[c],$=t[s],l=Math.round((n+e+r)/3),f=l>238?23:Math.max(0,Math.round((l-8)/10)),M=8+10*f;return(n-M)**2+(e-M)**2+(r-M)**2<(n-u)**2+(e-a)**2+(r-$)**2?232+f:16+36*i+6*c+s}export function encodeFrame(t,o,n,e,r){const i=[];if("none"===r)return i;for(let c=0;c+1<e;c+=2){let e="",s="",u="";for(let i=0;i<n;i++){const a=o+3*(c*n+i),$=a+3*n;let l,f;"truecolor"===r?(l=`38;2;${248&t[a]};${248&t[a+1]};${248&t[a+2]}`,f=`48;2;${248&t[$]};${248&t[$+1]};${248&t[$+2]}`):(l=`38;5;${rgbTo256(t[a],t[a+1],t[a+2])}`,f=`48;5;${rgbTo256(t[$],t[$+1],t[$+2])}`),l===s&&f===u||(e+=`[${l};${f}m`,s=l,u=f),e+="▀"}i.push(` ${e}[0m`)}return i}const n=t=>new Promise(o=>setTimeout(o,t));export async function playFrames(t,o,e,r){const{w:i,h:c,n:s,fps:u}=o,a=process.stdout;if("none"===e||0===s)return void(s&&a.write(`${r(s-1)}\n`));const $=c/2+1,l=i*c*3;a.write("[?25l");try{for(let o=0;o<s;o++){const s=Date.now(),f=encodeFrame(t,o*l,i,c,e);a.write(`${o?`[${$}A`:""}${f.join("\n")}\n[2K${r(o)}\n`);const M=1e3/u-(Date.now()-s);M>0&&await n(M)}}finally{a.write("[0m[?25h")}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icoa-cli",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.649",
|
|
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",
|