codekeep 0.3.0 → 0.6.0
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/index.js +6 -22
- package/package.json +18 -5
package/dist/index.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as el}from"commander";import{render as tl}from"ink";import{useState as Ce,useCallback as ao,useEffect as ji,useRef as Wn}from"react";import{Box as Mt,Text as ae,useApp as qi,useInput as zi,useStdout as Xi}from"ink";var W={gold:"\u25CF",wood:"\u2663",stone:"\u25A0"};var I=16,es=8,ts=2400;var kt={gold:80,wood:40,stone:20},Co={gold:200,wood:120,stone:80},ye={wall:"#",trap:"%",treasury:"$",ward:"@",watchtower:"^",archerTower:"!",vault:"&"},Je={wall:"Stone Wall",trap:"Bear Trap",treasury:"Treasury",ward:"Ward",watchtower:"Watchtower",archerTower:"Archer Tower",vault:"Vault"},yt="\xB7",wt={wall:{1:{gold:12,wood:0,stone:2},2:{gold:10,wood:0,stone:4},3:{gold:14,wood:0,stone:6}},trap:{1:{gold:8,wood:4,stone:6},2:{gold:8,wood:4,stone:6},3:{gold:10,wood:6,stone:8}},treasury:{1:{gold:6,wood:14,stone:2},2:{gold:6,wood:18,stone:4},3:{gold:8,wood:24,stone:6}},ward:{1:{gold:10,wood:10,stone:0},2:{gold:12,wood:12,stone:0},3:{gold:16,wood:16,stone:0}},watchtower:{1:{gold:6,wood:2,stone:12},2:{gold:6,wood:4,stone:14},3:{gold:8,wood:6,stone:18}},archerTower:{1:{gold:14,wood:6,stone:8},2:{gold:16,wood:8,stone:10},3:{gold:20,wood:12,stone:14}},vault:{1:{gold:20,wood:20,stone:15},2:{gold:35,wood:35,stone:25},3:{gold:55,wood:55,stone:40}}},Eo={1:40,2:70,3:110},Ao={1:25,2:40,3:60},Mo={1:20,2:35,3:50},os={1:4,2:6,3:9},rs={1:16,2:12,3:8},ss={1:80,2:160,3:280},ns={1:.15,2:.28,3:.4},Ot={1:1,2:2,3:3},as={1:4,2:7,3:10},_o={1:2,2:2,3:3},is={1:4,2:3,3:2};var Qe={raider:{hp:30,damage:8,loot:3,speed:1},scout:{hp:14,damage:4,loot:2,speed:2},brute:{hp:55,damage:14,loot:5,speed:1}},ls=6e4,Io={gold:2,wood:4,stone:1},Po={gold:1,wood:0,stone:3},Do={build_success:{gold:12,wood:0,stone:4},tests_pass:{gold:0,wood:8,stone:4},git_commit:{gold:5,wood:5,stone:10},session_reward:{gold:8,wood:3,stone:6},daily_login:{gold:5,wood:5,stone:10}},ds={build_success:"Miners returned",tests_pass:"Harvest complete",git_commit:"Trade caravan arrived",session_reward:"Tax collection",daily_login:"Morning tribute"},io=9e5,cs=5,zt=6,us=.5,lo=[{id:"first_structure",name:"Builder",desc:"Place your first structure",bonus:"+20 gold"},{id:"defense_win_5",name:"Warden",desc:"Win 5 defense raids",bonus:"+25 gold, +15 wood, +15 stone"},{id:"win_streak_3",name:"Rallying Cry",desc:"Achieve a 3-win streak",bonus:"+10 all resources"},{id:"win_streak_5",name:"Unstoppable",desc:"Achieve a 5-win streak",bonus:"+30 all resources"},{id:"all_types",name:"Master Builder",desc:"Place every structure type",bonus:"+15 all resources"},{id:"max_level",name:"Fortifier",desc:"Upgrade a structure to Level 3",bonus:"+20 gold, +10 stone"},{id:"archer_kills_10",name:"Marksman",desc:"Kill 10 raiders with archers",bonus:"+20 gold, +10 wood, +10 stone"},{id:"structures_20",name:"Architect",desc:"Place 20 structures total",bonus:"+10 all resources"},{id:"raids_10",name:"Veteran",desc:"Complete 10 raids",bonus:"+15 all resources"},{id:"hoarder",name:"Treasure Keeper",desc:"Hold 500+ total resources",bonus:"+25 gold"}],co={gold_nugget:{symbol:"~",color:"cyan",yield:{gold:4,wood:0,stone:0},weight:35},timber:{symbol:"~",color:"yellow",yield:{gold:0,wood:4,stone:0},weight:35},ore:{symbol:"~",color:"green",yield:{gold:0,wood:0,stone:4},weight:20},gem:{symbol:"~",color:"white",yield:{gold:2,wood:2,stone:2},weight:10}},Sr=6,ps=35e3,fs=18e4,ms=.25,gs=2,qe=["wall","trap","treasury","ward","watchtower","archerTower","vault"],Lo={1:60,2:90,3:130},$o={1:{gold:50,wood:30,stone:20},2:{gold:100,wood:60,stone:40},3:{gold:180,wood:110,stone:75}},br=1;var sl={defense_win:7200*1e3,partial_breach:14400*1e3,full_breach:480*60*1e3},Bo={raider:{gold:8,wood:4,stone:2},scout:{gold:6,wood:6,stone:0},brute:{gold:15,wood:8,stone:8}},hs={raider:120*1e3,scout:120*1e3,brute:300*1e3};var nl=1440*60*1e3,al=2880*60*1e3,il=672*60*60*1e3;var xs=[{id:"fog_of_war",name:"Fog of War",description:"Watchtower range halved",icon:"\u{1F32B}",modifiers:{watchtowerRangeMult:.5}},{id:"speed_raid",name:"Speed Raid",description:"Raiders move 50% faster",icon:"\u26A1",modifiers:{raiderSpeedMult:1.5}},{id:"fortified",name:"Fortified",description:"Raiders have +40% HP, double loot",icon:"\u{1F6E1}",modifiers:{raiderHpMult:1.4,lootMult:2}},{id:"swarm",name:"Swarm",description:"+50% raiders, \u221230% HP each",icon:"\u{1F41D}",modifiers:{raiderCountMult:1.5,raiderHpMult:.7}},{id:"piercing",name:"Piercing",description:"Ward mitigation halved",icon:"\u{1F5E1}",modifiers:{wardMitigationMult:.5}},{id:"siege",name:"Siege",description:"Double brute chance",icon:"\u{1F3F0}",modifiers:{bruteChanceMult:2}},{id:"flanking",name:"Flanking",description:"All raiders from one edge",icon:"\u27A1",modifiers:{singleEdge:!0}},{id:"ironwall",name:"Iron Wall",description:"Walls have +50% HP",icon:"\u{1F9F1}",modifiers:{wallHpMult:1.5}},{id:"sharpshooter",name:"Sharpshooter",description:"Archers deal +30% damage",icon:"\u{1F3AF}",modifiers:{archerDamageMult:1.3}},{id:"sticky_traps",name:"Sticky Traps",description:"Traps stun 50% longer",icon:"\u{1F578}",modifiers:{trapStunMult:1.5}}],Ts=2,ys=6,ws=[{id:"wall_fortify",name:"Fortified Walls",raidsRemaining:3,modifiers:{wallHpMult:1.25}},{id:"archer_focus",name:"Archer Focus",raidsRemaining:3,modifiers:{archerDamageMult:1.2}},{id:"trap_mastery",name:"Trap Mastery",raidsRemaining:3,modifiers:{trapStunMult:1.3}},{id:"ward_power",name:"Ward Power",raidsRemaining:3,modifiers:{wardMitigationMult:1.3}},{id:"scout_shield",name:"Scout Shield",raidsRemaining:2,modifiers:{wallHpMult:1.4,archerDamageMult:1.15}}],Xt=[{id:"starting_resources_boost",name:"War Chest",description:"Start with 2x resources",ascensionLevel:1},{id:"structures_lv2",name:"Veteran Builder",description:"New structures start at Lv.2",ascensionLevel:2},{id:"extra_faucet",name:"Deep Wells",description:"+3 faucet uses before diminishing",ascensionLevel:3},{id:"passive_income_boost",name:"Trade Routes",description:"+50% passive income",ascensionLevel:4},{id:"anomaly_choice",name:"Oracle",description:"Choose from 2 anomalies before each raid (coming soon)",ascensionLevel:5}],Zt=50,ll=336*60*60*1e3,dl=2880*60*1e3;var cl=720*60*1e3;import{writeFileSync as Er,readFileSync as Ea,mkdirSync as Aa,existsSync as Lr,renameSync as Ma,unlinkSync as _a}from"fs";import{join as Ws,dirname as Ia}from"path";import{homedir as Pa}from"os";function ea(e){return e.x>=0&&e.x<I&&e.y>=0&&e.y<I}function Mr(e,t){return e.structures.find(o=>o.pos.x===t.x&&o.pos.y===t.y)}function ta(e,t,o){return ea(t)?Mr(e,t)?{ok:!1,reason:"Cell is occupied"}:{ok:!0}:{ok:!1,reason:"Out of bounds"}}function _s(e,t){return e.gold>=t.gold&&e.wood>=t.wood&&e.stone>=t.stone}function Oo(e,t,o,s){let r=ta(e.grid,t,o);if(!r.ok)return{ok:!1,reason:r.reason};let n=wt[o][1];if(!_s(e.resources,n))return{ok:!1,reason:Is(e.resources,n)};let c=s?.startingLevel??1,d={id:`${o}-${t.x}-${t.y}-${Date.now()}`,kind:o,level:c,pos:{...t},placedAtUnixMs:Date.now()};return{ok:!0,keep:{...e,resources:Ps(e.resources,n),grid:{...e.grid,structures:[...e.grid.structures,d]},updatedAtUnixMs:Date.now()}}}function Uo(e,t){let o=Mr(e.grid,t);if(!o)return{ok:!1,reason:"No structure at this position"};if(o.level>=3)return{ok:!1,reason:"Already at max level"};let s=o.level+1,r=wt[o.kind][s];if(!_s(e.resources,r))return{ok:!1,reason:Is(e.resources,r)};let n={...o,level:s};return{ok:!0,keep:{...e,resources:Ps(e.resources,r),grid:{...e.grid,structures:e.grid.structures.map(c=>c.id===o.id?n:c)},updatedAtUnixMs:Date.now()}}}function Go(e,t){let o=Mr(e.grid,t);if(!o)return{ok:!1,reason:"No structure at this position"};let s=oa(o);return{ok:!0,keep:{...e,resources:He(e.resources,s),grid:{...e.grid,structures:e.grid.structures.filter(r=>r.id!==o.id)},updatedAtUnixMs:Date.now()}}}function oa(e){let t=0,o=0,s=0;for(let r=1;r<=e.level;r++){let n=wt[e.kind][r];t+=n.gold,o+=n.wood,s+=n.stone}return{gold:Math.floor(t*.5),wood:Math.floor(o*.5),stone:Math.floor(s*.5)}}function Is(e,t){let o=[];return t.gold>e.gold&&o.push(`${t.gold-e.gold} more gold`),t.wood>e.wood&&o.push(`${t.wood-e.wood} more wood`),t.stone>e.stone&&o.push(`${t.stone-e.stone} more stone`),o.length>0?`Need ${o.join(", ")}`:"Not enough resources"}function Ps(e,t){return{gold:e.gold-t.gold,wood:e.wood-t.wood,stone:e.stone-t.stone}}function He(e,t){return{gold:e.gold+t.gold,wood:e.wood+t.wood,stone:e.stone+t.stone}}function _r(e,t){let o=t.grants??Do[t.type]??{gold:0,wood:0,stone:0},s=He(e.resources,o);return{...e,resources:nt(s),updatedAtUnixMs:Date.now()}}function nt(e){return{gold:Math.min(e.gold,Co.gold*10),wood:Math.min(e.wood,Co.wood*10),stone:Math.min(e.stone,Co.stone*10)}}function Ir(e,t){let o=Math.min(Math.floor(t/ls),60),s=e.structures.filter(d=>d.kind==="treasury"),r=e.structures.filter(d=>d.kind==="watchtower"),n=s.reduce((d,g)=>d+g.level,0),c=r.reduce((d,g)=>d+g.level,0);return{gold:o*(n*Io.gold+c*Po.gold),wood:o*(n*Io.wood+c*Po.wood),stone:o*(n*Io.stone+c*Po.stone)}}function Ds(){let e=["build_success","tests_pass","git_commit","session_reward"],t=e[Math.floor(Math.random()*e.length)];return{type:t,timestamp:Date.now(),grants:Do[t]}}function ra(e){return Eo[e]}function sa(e){return Ao[e]}function na(e){return Mo[e]}function aa(e){return Lo[e]}function Rs(e){return os[e]}function Ss(e){return rs[e]}function kr(e){return ss[e]}function ia(e){return ns[e]}function la(e){return Ot[e]}function da(e){return as[e]}function ca(e){return _o[e]}function ua(e){return is[e]}function bs(e,t){return Math.max(Math.abs(e.x-t.x),Math.abs(e.y-t.y))}function De(e,t){return Math.abs(e.x-t.x)+Math.abs(e.y-t.y)}function pa(e,t,o=1){let s=e.structures.filter(c=>c.kind==="ward"),r=e.structures.filter(c=>c.kind==="watchtower"),n=0;for(let c of s){let d=1;for(let g of r)bs(c.pos,g.pos)<=1&&(d=Math.max(d,1+Math.floor(la(g.level)*o)));bs(c.pos,t)<=d&&(n=Math.max(n,ia(c.level)))}return n}function vr(e,t){return Math.max(Math.abs(e.x-t.x),Math.abs(e.y-t.y))}function Pr(e){let t=[],o=e.structures,s=o.filter(u=>u.kind==="trap"),r=o.filter(u=>u.kind==="archerTower"),n=o.filter(u=>u.kind==="wall"),c=o.filter(u=>u.kind==="ward"),d=o.filter(u=>u.kind==="treasury"),g=o.filter(u=>u.kind==="watchtower");for(let u of s)for(let i of r)if(vr(u.pos,i.pos)<=1){let y=t.find(w=>w.id==="killbox"&&w.affectedStructureIds.includes(i.id));y?y.affectedStructureIds.includes(u.id)||y.affectedStructureIds.push(u.id):t.push({id:"killbox",name:"Killbox",affectedStructureIds:[u.id,i.id]})}let l=new Set(n.map(u=>`${u.pos.x},${u.pos.y}`)),T=new Map(n.map(u=>[`${u.pos.x},${u.pos.y}`,u])),R=new Set;for(let u of n){if(R.has(u.id))continue;let i=[u];for(let w=1;l.has(`${u.pos.x+w},${u.pos.y}`);w++)i.push(T.get(`${u.pos.x+w},${u.pos.y}`));if(i.length>=3){let w=i.map(h=>h.id);w.forEach(h=>R.add(h)),t.push({id:"fortress",name:"Fortress",affectedStructureIds:w})}let y=[u];for(let w=1;l.has(`${u.pos.x},${u.pos.y+w}`);w++)y.push(T.get(`${u.pos.x},${u.pos.y+w}`));if(y.length>=3){let w=y.map(h=>h.id);w.forEach(h=>R.add(h)),t.push({id:"fortress",name:"Fortress",affectedStructureIds:w})}}for(let u of c){let i=d.filter(w=>vr(u.pos,w.pos)<=1),y=g.filter(w=>vr(u.pos,w.pos)<=1);if(i.length>0&&y.length>0){let w=[u.id,...i.map(h=>h.id),...y.map(h=>h.id)];t.push({id:"sanctum",name:"Sanctum",affectedStructureIds:[...new Set(w)]})}}for(let u=0;u<s.length;u++){let i=s.filter((y,w)=>w!==u&&De(s[u].pos,y.pos)<=3);if(i.length>=1){let y=[s[u].id,...i.map(x=>x.id)],w=[...new Set(y)].sort(),h=w.join(",");t.some(x=>x.id==="gauntlet"&&x.affectedStructureIds.sort().join(",")===h)||t.push({id:"gauntlet",name:"Gauntlet",affectedStructureIds:w})}}return t}function Wo(e){let t=e|0;return()=>{t=t+1831565813|0;let o=Math.imul(t^t>>>15,1|t);return o=o+Math.imul(o^o>>>7,61|o)^o,((o^o>>>14)>>>0)/4294967296}}function It(e){let t=0;for(let o=0;o<e.length;o++){let s=e.charCodeAt(o);t=(t<<5)-t+s|0}return t}function fa(e,t){switch(e){case"N":return{x:t,y:0};case"S":return{x:t,y:I-1};case"E":return{x:I-1,y:t};case"W":return{x:0,y:t}}}function Ls(e,t){return t.x<0||t.x>=I||t.y<0||t.y>=I?!1:e.grid[t.y][t.x]}function $s(e){for(let t=0;t<I;t++)for(let o=0;o<I;o++)e.grid[t][o]=!0;for(let t of e.solids)t.destroyed||(e.grid[t.pos.y][t.pos.x]=!1)}function Bs(e,t,o){if(e.x===t.x&&e.y===t.y)return null;let s=l=>`${l.x},${l.y}`,r=new Map,n=new Set,c=l=>Math.abs(l.x-t.x)+Math.abs(l.y-t.y),d=s(e);r.set(d,{pos:e,g:0,f:c(e),parent:null});let g=new Map;for(;r.size>0;){let l="",T=1/0;for(let[i,y]of r)y.f<T&&(T=y.f,l=i);let R=r.get(l);if(r.delete(l),R.pos.x===t.x&&R.pos.y===t.y){let i=s(t),y=g.get(i);for(;y&&y!==d;)i=y,y=g.get(i);let[w,h]=i.split(",").map(Number);return{x:w,y:h}}n.add(l);let u=[{x:R.pos.x-1,y:R.pos.y},{x:R.pos.x+1,y:R.pos.y},{x:R.pos.x,y:R.pos.y-1},{x:R.pos.x,y:R.pos.y+1}];for(let i of u){let y=s(i);if(n.has(y)||!Ls(o,i)&&!(i.x===t.x&&i.y===t.y))continue;let w=R.g+1,h=r.get(y);h&&w>=h.g||(g.set(y,l),r.set(y,{pos:i,g:w,f:w+c(i),parent:l}))}}return null}function ma(e,t){switch(e){case"wall":return ra(t);case"archerTower":return sa(t);case"watchtower":return na(t);case"vault":return aa(t);default:return 0}}var ga=["archerTower","watchtower"];function ks(e,t){return e.solids.find(o=>!o.destroyed&&De(o.pos,t)===1)}function No(e,t,o,s=1){o.hp-=Math.floor(Qe[t.raiderType].damage*s);let r=o.hp<=0;if(r){o.destroyed=!0,$s(e);let n=e.archers.find(c=>c.structureId===o.structureId);n&&(n.destroyed=!0)}o.kind==="wall"?e.events.push({t:e.tick,type:"wall_damaged",structureId:o.structureId,hpRemaining:Math.max(0,o.hp),destroyed:r}):e.events.push({t:e.tick,type:"structure_damaged",structureId:o.structureId,structureKind:o.kind,hpRemaining:Math.max(0,o.hp),destroyed:r})}function vs(e,t,o){let r=[{x:o.pos.x-1,y:o.pos.y},{x:o.pos.x+1,y:o.pos.y},{x:o.pos.x,y:o.pos.y-1},{x:o.pos.x,y:o.pos.y+1}].filter(d=>Ls(e,d)).sort((d,g)=>{let l=De(d,t.pos),T=De(g,t.pos);return l-T})[0];if(!r||De(t.pos,r)===0)return!1;let n=Bs(t.pos,r,e);if(!n)return!1;let c={...t.pos};return t.pos={...n},e.events.push({t:e.tick,type:"raider_move",probeId:t.id,from:c,to:{...t.pos}}),!0}function ha(e,t){return e.solids.filter(s=>!s.destroyed&&ga.includes(s.kind)).sort((s,r)=>{let n=s.kind==="archerTower"?0:1,c=r.kind==="archerTower"?0:1;return n!==c?n-c:De(s.pos,t.pos)-De(r.pos,t.pos)})[0]??null}function xa(e,t){let o=e.solids.filter(s=>!s.destroyed&&De(s.pos,t.pos)===1);return o.length===0?null:o.sort((s,r)=>s.hp-r.hp)[0]}function Fe(e,t){let o=e[t];return typeof o=="number"?o:1}function ut(e){let t=Wo(It(e.seed)),{keepGrid:o}=e,s=e.modifiers??{},r=["wall","archerTower","watchtower","vault"],n=o.structures.filter(m=>r.includes(m.kind)).sort((m,P)=>m.id.localeCompare(P.id)).map(m=>{let P=ma(m.kind,m.level);return m.kind==="wall"&&(P=Math.floor(P*Fe(s,"wallHpMult"))),{structureId:m.id,kind:m.kind,pos:{...m.pos},hp:P,destroyed:!1}}),c=o.structures.filter(m=>m.kind==="trap").sort((m,P)=>m.id.localeCompare(P.id)).map(m=>({structureId:m.id,pos:{...m.pos},level:m.level,cooldownRemaining:0})),d=o.structures.filter(m=>m.kind==="archerTower").sort((m,P)=>m.id.localeCompare(P.id)).map(m=>({structureId:m.id,pos:{...m.pos},level:m.level,cooldownRemaining:0,destroyed:!1})),l=o.structures.filter(m=>m.kind==="vault").reduce((m,P)=>m+$o[P.level].gold+$o[P.level].wood+$o[P.level].stone,0),T=o.structures.filter(m=>m.kind==="treasury"),R=T.length>0?Math.floor(l/T.length):0,u=T.sort((m,P)=>m.id.localeCompare(P.id)).map(m=>({structureId:m.id,pos:{...m.pos},level:m.level,storedResources:Math.max(0,kr(m.level)-R)}));u.length===0&&(u=[{structureId:"__virtual_center_treasury",pos:{x:8,y:8},level:1,storedResources:kr(1)}]);let i={tick:0,raiders:[],solids:n,traps:c,archers:d,treasuries:u,events:[],grid:Array.from({length:I},()=>Array(I).fill(!0)),totalLoot:{gold:0,wood:0,stone:0}};$s(i);let y=Pr(o),w=new Set(y.filter(m=>m.id==="killbox").flatMap(m=>m.affectedStructureIds)),h=new Set(y.filter(m=>m.id==="gauntlet").flatMap(m=>m.affectedStructureIds)),x=new Set(y.filter(m=>m.id==="fortress").flatMap(m=>m.affectedStructureIds));for(let m of i.solids)m.kind==="wall"&&x.has(m.structureId)&&(m.hp=Math.floor(m.hp*1.25));let _=[],j=["N","S","E","W"],K=s.singleEdge?j[Math.floor(t()*j.length)]:null,N=Math.round(e.probeCount*Fe(s,"raiderCountMult"));for(let m=0;m<N;m++){let P=e.spawnSpecs?.[m],Y=K??P?.edge??j[Math.floor(t()*j.length)],se=P?.offset??Math.floor(t()*I),v=P?.raiderType??e.probeTypes?.[m%(e.probeTypes?.length||1)]??"raider",oe=P?.waveDelay??0;_.push({id:m,edge:Y,offset:se,raiderType:v,spawnAtTick:oe})}function $(m,P){let Y=fa(m.edge,m.offset),se=null,v=1/0;for(let fe of u){let be=De(fe.pos,Y);be<v&&(v=be,se=fe)}let oe=Math.floor(Qe[m.raiderType].hp*Fe(s,"raiderHpMult"));i.raiders.push({id:m.id,pos:{...Y},hp:oe,raiderType:m.raiderType,stunRemaining:0,targetId:se?.structureId??null,alive:!0,looted:{gold:0,wood:0,stone:0}}),i.events.push({t:P,type:"raider_spawn",probeId:m.id,edge:m.edge,pos:{...Y},raiderType:m.raiderType,maxHp:oe})}for(let m of _)m.spawnAtTick===0&&$(m,0);for(;i.tick<ts;){i.tick++;for(let v of _)v.spawnAtTick===i.tick&&$(v,i.tick);for(let v of i.traps)v.cooldownRemaining>0&&v.cooldownRemaining--;for(let v of i.archers)v.cooldownRemaining>0&&v.cooldownRemaining--;let m=i.raiders.filter(v=>v.alive).sort((v,oe)=>v.id-oe.id),P=_.some(v=>v.spawnAtTick>i.tick);if(m.length===0&&!P)break;for(let v of i.archers){if(v.destroyed||v.cooldownRemaining>0)continue;let oe=Math.floor(ca(v.level)*Fe(s,"archerRangeMult")),fe=Math.floor(da(v.level)*Fe(s,"archerDamageMult")),be=m.filter(Te=>Te.alive&&De(v.pos,Te.pos)<=oe),me=be.length===0?void 0:be.sort((Te,Ve)=>{let C=Te.stunRemaining>0?1:0,B=Ve.stunRemaining>0?1:0;if(C!==B)return C-B;let E=i.treasuries.find(H=>H.structureId===Te.targetId),F=i.treasuries.find(H=>H.structureId===Ve.targetId),D=E?De(Te.pos,E.pos):1/0,q=F?De(Ve.pos,F.pos):1/0;return D-q})[0];if(me){let Te=fe;w.has(v.structureId)&&me.stunRemaining>0&&(Te=Math.floor(fe*1.3)),me.hp-=Te,v.cooldownRemaining=ua(v.level),me.hp<=0?(me.alive=!1,i.events.push({t:i.tick,type:"arrow_hit",probeId:me.id,archerId:v.structureId,damage:Te,hpRemaining:0}),i.events.push({t:i.tick,type:"raider_destroyed",probeId:me.id,pos:{...me.pos}})):i.events.push({t:i.tick,type:"arrow_hit",probeId:me.id,archerId:v.structureId,damage:Te,hpRemaining:me.hp})}}for(let v of m){if(!v.alive)continue;if(v.stunRemaining>0){v.stunRemaining--;continue}let oe=Math.max(1,Math.round(Qe[v.raiderType].speed*Fe(s,"raiderSpeedMult")));for(let fe=0;fe<oe&&v.alive;fe++){if(v.raiderType==="brute"){let B=ha(i,v);if(B){if(De(v.pos,B.pos)===1){No(i,v,B,Fe(s,"raiderDamageMult"));break}if(vs(i,v,B)){let F=i.traps.find(D=>D.pos.x===v.pos.x&&D.pos.y===v.pos.y&&D.cooldownRemaining===0);if(F){let D=Math.floor(Rs(F.level)*Fe(s,"trapStunMult"));v.stunRemaining=h.has(F.structureId)?D+2:D,F.cooldownRemaining=Ss(F.level),i.events.push({t:i.tick,type:"raider_stunned",probeId:v.id,pos:{...v.pos},trapId:F.structureId,stunTicks:v.stunRemaining});break}continue}let E=ks(i,v.pos);if(E){No(i,v,E,Fe(s,"raiderDamageMult"));break}}}let be=i.treasuries.find(B=>B.structureId===v.targetId);if(!be||be.storedResources<=0){let B=i.treasuries.filter(E=>E.storedResources>0).sort((E,F)=>{let D=De(E.pos,v.pos),q=De(F.pos,v.pos);return D-q||E.structureId.localeCompare(F.structureId)})[0];if(!B){v.alive=!1,i.events.push({t:i.tick,type:"raider_destroyed",probeId:v.id,pos:{...v.pos}});break}v.targetId=B.structureId}let me=i.treasuries.find(B=>B.structureId===v.targetId);if(v.pos.x===me.pos.x&&v.pos.y===me.pos.y){let B=pa(o,me.pos,Fe(s,"watchtowerRangeMult")),E=Math.min(1,B*Fe(s,"wardMitigationMult")),F=Math.max(1,Math.floor(Qe[v.raiderType].loot*me.level*(1-E)*Fe(s,"lootMult"))),D=Math.min(F,me.storedResources);me.storedResources-=D;let q=Math.ceil(D*.4),H=Math.ceil(D*.35),Z=Math.max(0,D-q-H),Ee={gold:q,wood:H,stone:Z};v.looted.gold+=q,v.looted.wood+=H,v.looted.stone+=Z,i.totalLoot.gold+=q,i.totalLoot.wood+=H,i.totalLoot.stone+=Z,i.events.push({t:i.tick,type:"treasury_breach",structureId:me.structureId,lootTaken:Ee});break}let Te=Bs(v.pos,me.pos,i);if(!Te){if(v.raiderType==="scout"){let E=xa(i,v);if(E){No(i,v,E,Fe(s,"raiderDamageMult"));break}}let B=ks(i,v.pos);if(B)No(i,v,B,Fe(s,"raiderDamageMult"));else{let E=i.solids.filter(F=>!F.destroyed).sort((F,D)=>De(F.pos,v.pos)-De(D.pos,v.pos))[0];E&&vs(i,v,E)}break}let Ve={...v.pos};v.pos={...Te},i.events.push({t:i.tick,type:"raider_move",probeId:v.id,from:Ve,to:{...v.pos}});let C=i.traps.find(B=>B.pos.x===v.pos.x&&B.pos.y===v.pos.y&&B.cooldownRemaining===0);if(C){let B=Math.floor(Rs(C.level)*Fe(s,"trapStunMult")),E=h.has(C.structureId)?B+2:B;v.stunRemaining=E,C.cooldownRemaining=Ss(C.level),i.events.push({t:i.tick,type:"raider_stunned",probeId:v.id,pos:{...v.pos},trapId:C.structureId,stunTicks:E});break}}}let Y=i.raiders.every(v=>!v.alive||v.stunRemaining>100),se=_.some(v=>v.spawnAtTick>i.tick);if(Y&&!se)break}let U=i.treasuries.reduce((m,P)=>m+P.storedResources,0),k=i.treasuries.reduce((m,P)=>m+kr(P.level),0),b,L=i.totalLoot.gold+i.totalLoot.wood+i.totalLoot.stone;return L===0?b="defense_win":U>0&&L<k*.5?b="partial_breach":b="full_breach",i.events.push({t:i.tick,type:"raid_end",outcome:b}),{tickRateHz:es,maxTicks:i.tick,events:i.events}}function Cr(e,t){return Math.abs(e.x-t.x)+Math.abs(e.y-t.y)}function Ta(e){let t=Object.entries(co),o=t.reduce((r,[,n])=>r+n.weight,0),s=e()*o;for(let[r,n]of t)if(s-=n.weight,s<=0)return r;return"gold_nugget"}function ya(e,t,o){return!!(t.structures.some(s=>s.pos.x===e.x&&s.pos.y===e.y)||o.some(s=>s.pos.x===e.x&&s.pos.y===e.y))}function Ns(e,t,o,s){if(e.length>=Sr)return e;let r=t.structures.filter(l=>l.kind==="archerTower").length,n=Math.min(r,2),c=1+(s()<.4?1:0),d=Math.min(c+n,Sr-e.length),g=[];for(let l=0;l<d;l++){let T=null;for(let R=0;R<30;R++){let u=Math.floor(s()*I),i=Math.floor(s()*I),y={x:u,y:i};if(!ya(y,t,[...e,...g])){T=y;break}}if(!T)break;g.push({id:`frag-${o}-${l}`,type:Ta(s),pos:T,spawnedAtMs:o})}return[...e,...g]}function Dr(e,t,o){let s=e.findIndex(l=>l.pos.x===t.x&&l.pos.y===t.y);if(s===-1)return null;let r=[e[s]],n=o.structures.filter(l=>l.kind==="watchtower"),c=0;for(let l of n)Cr(l.pos,t)<=Ot[l.level]&&(c=Math.max(c,l.level));if(c>0)for(let l of e)r.includes(l)||Cr(t,l.pos)<=c&&r.push(l);let d=new Set(r.map(l=>l.id)),g={gold:0,wood:0,stone:0};for(let l of r){let T=co[l.type].yield,R=1;o.structures.some(i=>i.kind==="treasury"&&Cr(i.pos,l.pos)<=gs)&&(R+=ms),g={gold:g.gold+Math.ceil(T.gold*R),wood:g.wood+Math.ceil(T.wood*R),stone:g.stone+Math.ceil(T.stone*R)}}return{yield:g,updatedFragments:e.filter(l=>!d.has(l.id)),collected:r}}function Os(e,t){return e.filter(o=>t-o.spawnedAtMs<fs)}function Us(e){let t=e.progression.totalRaidsWon;return t<Zt?{eligible:!1,reason:`Need ${Zt} raid wins (have ${t})`}:{eligible:!0}}function wa(e){return e.prestige?.ascensionLevel??0}function Ra(e){return Xt.filter(t=>t.ascensionLevel<=e).map(t=>t.id)}function Ko(e,t){let o=wa(e);return Xt.some(s=>s.id===t&&s.ascensionLevel<=o)}function Gs(e){let t=e.prestige??{ascensionLevel:0,permanentUnlocks:[],lifetimeRaidsWon:0,lifetimeRaidsLost:0,lifetimeBestStreak:0},o=t.ascensionLevel+1,s=Ra(o),r={...kt};return s.includes("starting_resources_boost")&&(r={gold:r.gold*2,wood:r.wood*2,stone:r.stone*2}),{...e,keep:{...e.keep,grid:{width:16,height:16,structures:[]},resources:r},raidHistory:[],progression:{totalBuildsToday:0,totalCommitsToday:0,lastDailyResetDay:e.progression.lastDailyResetDay,totalRaidsWon:0,totalRaidsLost:0,totalStructuresPlaced:0,currentWinStreak:0,bestWinStreak:0,achievements:e.progression.achievements,totalRaidersKilledByArcher:0,dailyChallenges:e.progression.dailyChallenges},activeBuffs:[],prestige:{ascensionLevel:o,permanentUnlocks:s,lifetimeRaidsWon:t.lifetimeRaidsWon+e.progression.totalRaidsWon,lifetimeRaidsLost:t.lifetimeRaidsLost+e.progression.totalRaidsLost,lifetimeBestStreak:Math.max(t.lifetimeBestStreak,e.progression.bestWinStreak)},savedAtUnixMs:Date.now()}}var Cs=[{level:1,structureCount:4,maxUpgradeLevel:1,probeCount:3},{level:2,structureCount:8,maxUpgradeLevel:1,probeCount:4},{level:3,structureCount:12,maxUpgradeLevel:2,probeCount:5},{level:4,structureCount:16,maxUpgradeLevel:2,probeCount:6},{level:5,structureCount:22,maxUpgradeLevel:3,probeCount:8},{level:6,structureCount:26,maxUpgradeLevel:3,probeCount:9},{level:7,structureCount:30,maxUpgradeLevel:3,probeCount:10},{level:8,structureCount:34,maxUpgradeLevel:3,probeCount:11},{level:9,structureCount:38,maxUpgradeLevel:3,probeCount:12},{level:10,structureCount:42,maxUpgradeLevel:3,probeCount:13}],Sa={maze:"Maze",sniper_nest:"Sniper Nest",trap_garden:"Trap Garden",fortress:"Fortress",balanced:"Outpost"},ba={maze:{wall:.55,trap:.1,ward:.1,archer:.05,watchtower:.05},sniper_nest:{wall:.15,trap:.1,ward:.1,archer:.35,watchtower:.1},trap_garden:{wall:.15,trap:.4,ward:.1,archer:.05,watchtower:.1},fortress:{wall:.3,trap:.1,ward:.2,archer:.1,watchtower:.05},balanced:{wall:.35,trap:.2,ward:.15,archer:.1,watchtower:.05}},Es=["maze","sniper_nest","trap_garden","fortress","balanced"];function ka(e){return Es[Math.floor(e()*Es.length)]}function Fo(e,t){let o=Cs[Math.max(0,Math.min(t-1,Cs.length-1))],s=Wo(It(e)),r=ka(s),n=ba[r],c=[],d=new Set,g=Math.max(1,Math.floor(o.structureCount/5));for(let h=0;h<g;h++){let x=As(s,d);if(!x)break;c.push(Ut("treasury",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}let l=o.structureCount-g-br,T=Math.max(0,Math.round(l*n.wall));for(let h=0;h<T;h++){let x=Ms(s,d,c);if(!x)break;c.push(Ut("wall",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}let R=Math.max(0,Math.round(l*n.trap));for(let h=0;h<R;h++){let x=va(s,d);if(!x)break;c.push(Ut("trap",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}let u=Math.max(1,Math.round(l*n.ward));for(let h=0;h<u;h++){let x=c.filter(K=>K.kind==="treasury"),_=x.length>0?x[Math.floor(s()*x.length)]:void 0;if(!_)break;let j=[{x:_.pos.x-1,y:_.pos.y},{x:_.pos.x+1,y:_.pos.y},{x:_.pos.x,y:_.pos.y-1},{x:_.pos.x,y:_.pos.y+1}].filter(K=>K.x>=0&&K.x<I&&K.y>=0&&K.y<I&&!d.has(`${K.x},${K.y}`));if(j.length>0){let K=j[Math.floor(s()*j.length)];c.push(Ut("ward",K,Gt(s,o.maxUpgradeLevel))),d.add(`${K.x},${K.y}`)}}let i=Math.max(0,Math.round(l*n.archer));for(let h=0;h<i;h++){let x=Ms(s,d,c);if(!x)break;c.push(Ut("archerTower",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}for(let h=0;h<br;h++){let x=As(s,d);if(!x)break;c.push(Ut("vault",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}let y=o.structureCount-c.length;for(let h=0;h<y;h++){let x=Ca(s,d);if(!x)break;c.push(Ut("watchtower",x,Gt(s,o.maxUpgradeLevel))),d.add(`${x.x},${x.y}`)}let w=Sa[r];return{id:`npc-${e}-${t}`,name:`${w} (Lv.${o.level})`,ownerPlayerId:"npc",grid:{width:16,height:16,structures:c},resources:{...kt},createdAtUnixMs:Date.now(),updatedAtUnixMs:Date.now()}}function Ut(e,t,o){return{id:`${e}-${t.x}-${t.y}`,kind:e,level:o,pos:{...t},placedAtUnixMs:Date.now()}}function Gt(e,t){let o=Math.floor(e()*t)+1;return Math.min(t,o)}function As(e,t){for(let o=0;o<50;o++){let s=4+Math.floor(e()*(I-8)),r=4+Math.floor(e()*(I-8));if(!t.has(`${s},${r}`))return{x:s,y:r}}return null}function Ms(e,t,o){for(let s=0;s<50;s++){let r=o[Math.floor(e()*o.length)],n=Math.floor(e()*3)-1,c=Math.floor(e()*3)-1,d=r.pos.x+n,g=r.pos.y+c;if(d>=0&&d<I&&g>=0&&g<I&&!t.has(`${d},${g}`))return{x:d,y:g}}return null}function va(e,t){for(let o=0;o<50;o++){let s=Math.floor(e()*4),r,n;if(s===0?(r=Math.floor(e()*I),n=Math.floor(e()*3)):s===1?(r=Math.floor(e()*I),n=I-1-Math.floor(e()*3)):s===2?(r=Math.floor(e()*3),n=Math.floor(e()*I)):(r=I-1-Math.floor(e()*3),n=Math.floor(e()*I)),!t.has(`${r},${n}`))return{x:r,y:n}}return null}function Ca(e,t){for(let o=0;o<50;o++){let s=Math.floor(e()*I),r=Math.floor(e()*I);if(!t.has(`${s},${r}`))return{x:s,y:r}}return null}var Ar=1;function Da(){return Ws(Pa(),".config","codekeep")}function $r(){return Ws(Da(),"game.json")}function Ks(e){let t=`player-${Date.now()}`;return{schemaVersion:Ar,savedAtUnixMs:Date.now(),player:{id:t,displayName:e,settings:{asciiMode:!1}},keep:{id:`keep-${t}-${Date.now()}`,name:`${e}'s Keep`,ownerPlayerId:t,grid:{width:16,height:16,structures:[]},resources:{...kt},createdAtUnixMs:Date.now(),updatedAtUnixMs:Date.now()},raidHistory:[],tutorialCompleted:!1,lastPlayedAtUnixMs:Date.now(),progression:{totalBuildsToday:0,totalCommitsToday:0,lastDailyResetDay:Math.floor(Date.now()/864e5),totalRaidsWon:0,totalRaidsLost:0,totalStructuresPlaced:0,currentWinStreak:0,bestWinStreak:0,achievements:[],totalRaidersKilledByArcher:0}}}function Pt(e){let t=$r(),o=Ia(t);Lr(o)||Aa(o,{recursive:!0});let s=t+".tmp",r=JSON.stringify({...e,savedAtUnixMs:Date.now()},null,2);Er(s,r,"utf-8"),Ma(s,t)}function Jt(){let e=$r();if(!Lr(e))return null;let t;try{t=Ea(e,"utf-8")}catch{return null}let o;try{o=JSON.parse(t)}catch{let s=e+".bak";try{Er(s,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Corrupted save backed up to ${s}
|
|
3
|
-
`),null}if(
|
|
4
|
-
`),null}return o}function Fs(){let e=$r();if(!Lr(e))return!1;try{return _a(e),!0}catch{return!1}}import{Box as Hs,Text as pt}from"ink";import{jsx as ft,jsxs as Ys}from"react/jsx-runtime";var La={wall:"white",trap:"magenta",treasury:"yellow",ward:"cyan",watchtower:"green",archerTower:"redBright"},$a={wall:"whiteBright",trap:"magentaBright",treasury:"yellowBright",ward:"cyanBright",watchtower:"greenBright",archerTower:"redBright"},Ba=Object.fromEntries(Object.entries(co).map(([e,t])=>[e,t.color]));function Na(e,t){return Math.abs(e.x-t.x)+Math.abs(e.y-t.y)}function Br(e,t){return Math.max(Math.abs(e.x-t.x),Math.abs(e.y-t.y))}function Oa(e,t){let o=new Set;if(e.kind==="archerTower"){let s=_o[e.level];for(let r=0;r<I;r++)for(let n=0;n<I;n++)Na(e.pos,{x:n,y:r})<=s&&!(n===e.pos.x&&r===e.pos.y)&&o.add(`${n},${r}`);return{tiles:o,color:"red"}}if(e.kind==="watchtower"){let s=Ot[e.level];for(let r=0;r<I;r++)for(let n=0;n<I;n++)Br(e.pos,{x:n,y:r})<=s&&!(n===e.pos.x&&r===e.pos.y)&&o.add(`${n},${r}`);return{tiles:o,color:"green"}}if(e.kind==="ward"){let s=1,r=t.filter(n=>n.kind==="watchtower");for(let n of r)Br(e.pos,n.pos)<=1&&(s=Math.max(s,1+Ot[n.level]));s=Math.min(s,5);for(let n=0;n<I;n++)for(let c=0;c<I;c++)Br(e.pos,{x:c,y:n})<=s&&!(c===e.pos.x&&n===e.pos.y)&&o.add(`${c},${n}`);return{tiles:o,color:"cyan"}}return null}function Vs({grid:e,cursor:t,asciiMode:o,compact:s,fragments:r=[],synergyStructureIds:n}){let c=o?"-":"\u2500",d=o?"|":"\u2502",g=o?"+":"\u250C",l=o?"+":"\u2510",T=o?"+":"\u2514",R=o?"+":"\u2518",u=s?1:2,i=(s?" ":" ")+Array.from({length:I},(N,$)=>s?$.toString(16).toUpperCase():$.toString(16).toUpperCase()+" ").join(""),y=(s?" ":" ")+g+c.repeat(I*u)+l,w=(s?" ":" ")+T+c.repeat(I*u)+R,h=new Map;for(let N of e.structures)h.set(`${N.pos.x},${N.pos.y}`,N);let x=new Map;for(let N of r)x.set(`${N.pos.x},${N.pos.y}`,N);let _=h.get(`${t.x},${t.y}`),j=_?Oa(_,e.structures):null,K=[];for(let N=0;N<I;N++){let $=s?N.toString(16).toUpperCase():" "+N.toString(16).toUpperCase(),U=[];for(let k=0;k<I;k++){let b=t.x===k&&t.y===N,L=h.get(`${k},${N}`),m=`${k},${N}`,P=j?.tiles.has(m)??!1,Y,se,v=!1,oe=x.get(m),fe=L&&n?.has(L.id);L?(Y=ye[L.kind],se=La[L.kind],(L.level>=2||fe)&&(v=!0),L.level===3&&(se=$a[L.kind])):oe?(Y="~",se=Ba[oe.type]??"cyan",v=!0):(Y=yt,se=void 0);let be=s?"":L&&L.level>1?String(L.level):" ";b?U.push(ft(pt,{backgroundColor:"white",color:"black",bold:!0,children:Y+be},k)):P&&!L&&!oe?U.push(ft(pt,{color:j.color,children:"\u2591"+(s?"":" ")},k)):P&&L?U.push(ft(pt,{color:se,bold:v,backgroundColor:j.color==="red"?"red":void 0,children:Y+be},k)):se?U.push(ft(pt,{color:se,bold:v,children:Y+be},k)):U.push(ft(pt,{dimColor:!0,children:Y+(s?"":" ")},k))}K.push(Ys(Hs,{children:[ft(pt,{dimColor:!0,children:$}),ft(pt,{dimColor:!0,children:d}),U,ft(pt,{dimColor:!0,children:d})]},N))}return Ys(Hs,{flexDirection:"column",children:[ft(pt,{dimColor:!0,children:i}),ft(pt,{dimColor:!0,children:y}),K,ft(pt,{dimColor:!0,children:w})]})}import{Box as Ho,Text as ue}from"ink";import{Fragment as js,jsx as Ie,jsxs as Ue}from"react/jsx-runtime";var Ua={wall:"WL",trap:"TR",treasury:"TY",ward:"WD",watchtower:"WT",archerTower:"AT"};function Ga(e){let t=[];return e.gold>0&&t.push(`${W.gold}${e.gold}`),e.wood>0&&t.push(`${W.wood}${e.wood}`),e.stone>0&&t.push(`${W.stone}${e.stone}`),t.join(" ")}function qs({resources:e,selectedStructure:t,message:o,compact:s,structureAtCursor:r,fragmentCount:n=0,dryRun:c,siegeForecast:d,flowMultiplier:g,raidAnomalies:l,activeBuffs:T}){if(s){let u=Ua[t];return Ie(Ho,{flexDirection:"column",marginBottom:0,children:Ue(ue,{children:[Ie(ue,{bold:!0,color:"yellow",children:"\u25C6"}),Ie(ue,{dimColor:!0,children:" \u2502 "}),Ue(ue,{color:"yellow",children:[W.gold,e.gold]}),Ie(ue,{children:" "}),Ue(ue,{color:"green",children:[W.wood,e.wood]}),Ie(ue,{children:" "}),Ue(ue,{color:"white",children:[W.stone,e.stone]}),Ie(ue,{dimColor:!0,children:" \u2502 "}),Ie(ue,{bold:!0,children:u}),o?Ie(ue,{dimColor:!0,children:" \u2502 "}):null,o?Ie(ue,{color:o.startsWith("!")?"red":"yellow",children:o}):null]})})}let R=r?`${Je[r.kind]} Lv.${r.level}`+(r.level<3?` \u2192 Lv.${r.level+1}: ${Ga(wt[r.kind][r.level+1])}`:" (MAX)"):"";return Ue(Ho,{flexDirection:"column",marginBottom:1,children:[Ue(Ho,{flexDirection:"row",gap:1,children:[Ie(ue,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep"}),c&&Ie(ue,{color:"magenta",bold:!0,children:" [DRY RUN]"}),Ie(ue,{dimColor:!0,children:"\u2502"}),Ue(ue,{color:"yellow",children:[W.gold," ",e.gold]}),Ue(ue,{color:"green",children:[W.wood," ",e.wood]}),Ue(ue,{color:"white",children:[W.stone," ",e.stone]}),Ie(ue,{dimColor:!0,children:"\u2502"}),Ue(ue,{children:["Sel: ",Ie(ue,{bold:!0,children:Je[t]})]}),n>0&&Ue(js,{children:[Ie(ue,{dimColor:!0,children:"\u2502"}),Ue(ue,{color:"cyan",bold:!0,children:["~ ",n," on ground"]})]})]}),Ue(Ho,{flexDirection:"row",gap:1,children:[Ie(ue,{color:o.startsWith("!")?"red":"yellow",children:o||" "}),g!=null&&g>1&&Ue(js,{children:[Ie(ue,{dimColor:!0,children:"\u2502"}),Ue(ue,{color:g>=2?"magenta":g>=1.5?"cyan":"green",bold:!0,children:["Flow: ",g.toFixed(1),"x"]})]})]}),Ie(ue,{dimColor:!0,children:d?`${R?R+" \u2502 ":""}${d}`:R||" "}),T&&T.length>0&&Ue(ue,{color:"cyan",children:["\u2605 Buffs: ",T.map(u=>`${u.name} (${u.raidsRemaining}r)`).join(", ")]})]})}import{Box as Wa,Text as O}from"ink";import{jsx as z,jsxs as Re}from"react/jsx-runtime";function Qt(e){let t=[];return e.gold>0&&t.push(`${W.gold}${e.gold}`),e.wood>0&&t.push(`${W.wood}${e.wood}`),e.stone>0&&t.push(`${W.stone}${e.stone}`),t.join(" ")}function zs(){let e=Qe,t=wt;return Re(Wa,{flexDirection:"column",padding:1,children:[z(O,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 Help"}),z(O,{children:" "}),z(O,{bold:!0,children:"Navigation"}),z(O,{children:" h/j/k/l or WASD or Arrows Move cursor"}),z(O,{children:" g + hex coords + Enter Jump to coordinate (e.g. g 5,3)"}),z(O,{children:" Tab Jump to next structure"}),z(O,{children:" Esc Back to menu"}),z(O,{children:" "}),z(O,{bold:!0,children:"Building"}),Re(O,{children:[" ","[ / ] Cycle structure type"]}),z(O,{children:" 1-6 Select structure by number"}),z(O,{children:" Enter or e Place structure at cursor"}),z(O,{children:" u Upgrade structure (Lv.1\u21922\u21923)"}),z(O,{children:" x Demolish structure (50% refund)"}),z(O,{children:" "}),z(O,{bold:!0,children:"Foraging"}),z(O,{children:" c Collect forage at cursor (~ on the grid)"}),Re(O,{children:[" "," Archer towers improve spawn rate; treasuries improve yield"]}),Re(O,{children:[" "," Watchtowers auto-gather nearby forage"]}),z(O,{children:" "}),z(O,{bold:!0,children:"Combat"}),z(O,{children:" r Quick defend (instant result)"}),z(O,{children:" v View last quick-defend replay"}),z(O,{children:" Defend Keep (menu) Watch raiders assault your grid"}),z(O,{children:" Attack NPC (menu) Raid an NPC keep for loot"}),z(O,{children:" "}),z(O,{bold:!0,children:"Other"}),z(O,{children:" f Kingdom boon (+resources)"}),z(O,{children:" ? Toggle this help (works on any screen)"}),z(O,{children:" q Save and quit"}),z(O,{children:" "}),z(O,{bold:!0,children:"Structures (cost Lv.1 \u2192 Lv.2 \u2192 Lv.3)"}),Re(O,{children:[" ",Re(O,{color:"white",children:["1 ",ye.wall]})," Stone Wall Blocks raiders, has HP ",Qt(t.wall[1])]}),Re(O,{children:[" ",Re(O,{color:"magenta",children:["2 ",ye.trap]})," Bear Trap Stuns raiders on contact ",Qt(t.trap[1])]}),Re(O,{children:[" ",Re(O,{color:"yellow",children:["3 ",ye.treasury]})," Treasury Stores loot, generates income ",Qt(t.treasury[1])]}),Re(O,{children:[" ",Re(O,{color:"cyan",children:["4 ",ye.ward]})," Ward Reduces loot stolen nearby ",Qt(t.ward[1])]}),Re(O,{children:[" ",Re(O,{color:"green",children:["5 ",ye.watchtower]})," Watchtower Extends ward range, auto-gathers ",Qt(t.watchtower[1])]}),Re(O,{children:[" ",Re(O,{color:"redBright",children:["6 ",ye.archerTower]})," Archer Tower Fires arrows at raiders in range ",Qt(t.archerTower[1])]}),z(O,{children:" "}),z(O,{bold:!0,children:"Resources"}),Re(O,{children:[" ",z(O,{color:"yellow",children:W.gold})," Gold Earned from events, foraging, and raids"]}),Re(O,{children:[" ",z(O,{color:"green",children:W.wood})," Wood Earned from treasuries and events"]}),Re(O,{children:[" ",z(O,{color:"white",children:W.stone})," Stone Earned from watchtowers and events"]}),z(O,{children:" Treasuries and watchtowers generate passive income over time."}),z(O,{children:" "}),z(O,{bold:!0,children:"Raider Types"}),Re(O,{children:[" Raider HP ",e.raider.hp,", dmg ",e.raider.damage,", speed ",e.raider.speed," \u2014 standard foot soldier"]}),Re(O,{children:[" Scout HP ",e.scout.hp,", dmg ",e.scout.damage,", speed ",e.scout.speed," \u2014 fast, moves twice per tick"]}),Re(O,{children:[" Brute HP ",e.brute.hp,", dmg ",e.brute.damage,", speed ",e.brute.speed," \u2014 heavy hitter, hard to kill"]}),z(O,{children:" "}),z(O,{bold:!0,children:"Raid Difficulty"}),z(O,{children:" Difficulty scales with total raids completed (Lv.1\u20135)."}),z(O,{children:" Higher levels bring more raiders, scouts, and brutes."}),Re(O,{children:[" Outcomes: ",z(O,{color:"green",children:"Defense Win"})," \xB7 ",z(O,{color:"yellow",children:"Partial Breach"})," \xB7 ",z(O,{color:"red",children:"Full Breach"})]}),z(O,{children:" "}),z(O,{dimColor:!0,children:"Press any key to close"})]})}import{useState as Ha}from"react";import{Box as on,Text as mt,useInput as Ya}from"ink";function Dt(e){return e<=2?1:e<=5?2:e<=9?3:e<=14?4:e<=19?5:e<=29?6:e<=44?7:e<=64?8:e<=99?9:10}function Lt(e,t,o,s=1){let r=Math.min(.8,(t>=8?.4:t>=6?.3:t>=3?.2:0)*s),n=t>=2?.3:0,c=[];for(let d=0;d<e;d++){let g=o();g<r?c.push("brute"):g<r+n?c.push("scout"):c.push("raider")}return c}function at(e){let t=e|0;return()=>{t=t+1831565813|0;let o=Math.imul(t^t>>>15,1|t);return o=o+Math.imul(o^o>>>7,61|o)^o,((o^o>>>14)>>>0)/4294967296}}function Xs(e){let t=e.progression;return{...e,progression:{...t,totalRaidsWon:t.totalRaidsWon??0,totalRaidsLost:t.totalRaidsLost??0,totalStructuresPlaced:t.totalStructuresPlaced??0,currentWinStreak:t.currentWinStreak??0,bestWinStreak:t.bestWinStreak??0,achievements:t.achievements??[],totalRaidersKilledByArcher:t.totalRaidersKilledByArcher??t.totalProbesKilledByScanner??0}}}var Ka={first_structure:{gold:20,wood:0,stone:0},defense_win_5:{gold:25,wood:15,stone:15},win_streak_3:{gold:10,wood:10,stone:10},win_streak_5:{gold:30,wood:30,stone:30},all_types:{gold:15,wood:15,stone:15},max_level:{gold:20,wood:0,stone:10},archer_kills_10:{gold:20,wood:10,stone:10},structures_20:{gold:10,wood:10,stone:10},raids_10:{gold:15,wood:15,stone:15},hoarder:{gold:25,wood:0,stone:0}};function Nr(e){return Ka[e]??null}function Or(e){let t=e.progression,o=new Set(t.achievements),s=[],r=(c,d)=>{!o.has(c)&&d&&s.push(c)};r("first_structure",t.totalStructuresPlaced>=1),r("defense_win_5",t.totalRaidsWon>=5),r("win_streak_3",t.bestWinStreak>=3),r("win_streak_5",t.bestWinStreak>=5),r("structures_20",t.totalStructuresPlaced>=20),r("raids_10",t.totalRaidsWon+t.totalRaidsLost>=10),r("archer_kills_10",t.totalRaidersKilledByArcher>=10),r("hoarder",e.keep.resources.gold+e.keep.resources.wood+e.keep.resources.stone>=500);let n=new Set(e.keep.grid.structures.map(c=>c.kind));return r("all_types",n.size>=qe.length),r("max_level",e.keep.grid.structures.some(c=>c.level===3)),s}function Zs(e,t,o){let r=Dt(t),n=3+r,c=Fa(e,t),d=at(c),g=Lt(n,r,d),l=g.filter(h=>h==="raider").length,T=g.filter(h=>h==="scout").length,R=g.filter(h=>h==="brute").length,u={raiders:l,scouts:T,brutes:R},i;n<=5?i="Small force approaching":n<=8?i="Moderate force approaching":n<=11?i="Strong force approaching":i="Massive force approaching";let y=[];R>0&&y.push(`${R}B`),l>0&&y.push(`${l}R`),T>0&&y.push(`${T}S`);let w=`Next raid: ${y.join(" ")} (Lv.${r})`;return{difficulty:r,probeCount:n,composition:u,vague:i,detailed:w}}function Fa(e,t){let o=`${e}-forecast-${t+1}`,s=0;for(let r=0;r<o.length;r++)s=(s<<5)-s+o.charCodeAt(r)|0;return s}function Js(e,t){if(e<Ts)return[];let o=xs.filter(n=>!(n.id==="siege"&&e<4||n.id==="piercing"&&e<3));if(o.length===0)return[];let s=[],r=Math.floor(t()*o.length);if(s.push(o[r]),e>=ys&&t()<.5){let n=o.filter((c,d)=>d!==r);n.length>0&&s.push(n[Math.floor(t()*n.length)])}return s}function Qs(e,t){let o=e.map(r=>r.modifiers);if(t)for(let r of t)o.push(r.modifiers);let s={};for(let r of o)for(let[n,c]of Object.entries(r))n==="singleEdge"?s.singleEdge=s.singleEdge||c:s[n]=(s[n]??1)*c;return s}function Yo(e,t){let o=[],s=1+(e-1)*.3,r=Math.floor(15*s),n=Math.floor(10*s),c=Math.floor(8*s);o.push({id:"resources_gold",type:"resources",name:"Gold Cache",description:`+${r*2} gold`,icon:"\u25CF",resources:{gold:r*2,wood:0,stone:0}}),o.push({id:"resources_balanced",type:"resources",name:"Supply Crate",description:`+${r} gold, +${n} wood, +${c} stone`,icon:"\u25A0",resources:{gold:r,wood:n,stone:c}});let d=[...ws],g=Math.floor(t()*d.length),l={...d[g],modifiers:{...d[g].modifiers}};return o.push({id:`buff_${l.id}`,type:"buff",name:l.name,description:`${l.name} for ${l.raidsRemaining} raids`,icon:"\u2605",buff:l}),o}function en(e){return e.map(t=>({...t,raidsRemaining:t.raidsRemaining-1})).filter(t=>t.raidsRemaining>0)}function tn(e,t){if(t<=zt)return e;let o=Math.pow(us,Math.floor((t-zt)/zt)+1);return{gold:Math.max(1,Math.floor(e.gold*o)),wood:Math.max(1,Math.floor(e.wood*o)),stone:Math.max(1,Math.floor(e.stone*o))}}import{jsx as uo,jsxs as $t}from"react/jsx-runtime";function Va(e){let t=[{key:"keep",label:"Build Keep",desc:"Place and upgrade structures",disabled:!1},{key:"defend",label:"Defend Keep",desc:"Watch NPCs attack YOUR defenses",disabled:!1},{key:"attack",label:"Attack NPC",desc:"Raid an NPC keep for resources",disabled:!1}];return e?t.push({key:"pvp",label:"\u2694 PvP Arena",desc:"Fight real players for trophies",disabled:!1}):t.push({key:"pvp-locked",label:"\u2694 PvP Arena",desc:"Coming soon \u2014 use --online to connect",disabled:!0}),t.push({key:"dailyChallenge",label:"\u2605 Daily Challenge",desc:"Date-seeded escalating waves",disabled:!1},{key:"prestige",label:"\u2191 Prestige",desc:"Ascend \u2014 reset keep, gain permanents",disabled:!1},{key:"friendRaid",label:"Raid Rival Keep",desc:"Plunder a rival lord's fortress",disabled:!1},{key:"raidLog",label:"Raid Log",desc:"View recent raid history",disabled:!1},{key:"settings",label:"Settings",desc:"Game options and reset",disabled:!1},{key:"quit",label:"Rest for the Night",desc:"Save and exit",disabled:!1}),t}function rn({gameSave:e,onlineMode:t,onKeep:o,onAttack:s,onDefend:r,onFriendRaid:n,onPvp:c,onDailyChallenge:d,onPrestige:g,onRaidLog:l,onSettings:T,onQuit:R}){let u=Va(!!t),[i,y]=Ha(0);Ya((N,$)=>{if(N==="k"||N==="w"||$.upArrow)y(U=>{let k=U-1;for(;k>=0&&u[k].disabled;)k--;return k>=0?k:U});else if(N==="j"||N==="s"||$.downArrow)y(U=>{let k=U+1;for(;k<u.length&&u[k].disabled;)k++;return k<u.length?k:U});else if($.return){let U=u[i];if(U.disabled)return;U.key==="keep"?o():U.key==="defend"?r():U.key==="attack"?s():U.key==="pvp"?c?.():U.key==="dailyChallenge"?d():U.key==="prestige"?g():U.key==="friendRaid"?n():U.key==="raidLog"?l():U.key==="settings"?T():U.key==="quit"&&R()}else N==="q"&&R()});let w=e.keep.grid.structures.length,h=e.progression,x=h.totalRaidsWon+h.totalRaidsLost,_=Math.max(1,Math.floor((Date.now()-e.keep.createdAtUnixMs)/864e5)),j=e.keep.grid.structures.filter(N=>N.kind==="treasury").length,K=e.keep.grid.structures.filter(N=>N.kind==="archerTower").length;return $t(on,{flexDirection:"column",padding:1,children:[uo(mt,{bold:!0,color:"yellow",children:`
|
|
5
|
-
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
6
|
-
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
7
|
-
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
8
|
-
\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D
|
|
9
|
-
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551
|
|
10
|
-
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D`}),uo(mt,{children:" "}),$t(mt,{dimColor:!0,children:[" ",e.player.displayName,"'s Keep \u2014 ",_,"d old"]}),$t(mt,{dimColor:!0,children:[" ",w," structures (",j,"$ ",K,"!) \xB7 Raids ",h.totalRaidsWon,"W / ",h.totalRaidsLost,"L \xB7 Streak ",h.currentWinStreak," (best ",h.bestWinStreak,")"]}),$t(mt,{dimColor:!0,children:[" Difficulty: Lv.",Dt(x),e.prestige?` \u2605 Ascension ${e.prestige.ascensionLevel}`:""]}),$t(mt,{dimColor:!0,children:[" Achievements: ",e.progression.achievements?.length||0,"/",10,e.activeBuffs&&e.activeBuffs.length>0?` Buffs: ${e.activeBuffs.length}`:""]}),uo(mt,{children:" "}),u.map((N,$)=>$t(on,{children:[$t(mt,{color:N.disabled?void 0:$===i?"yellow":void 0,bold:!N.disabled&&$===i,dimColor:N.disabled,children:[$===i&&!N.disabled?" \u25B8 ":" ",N.label]}),$t(mt,{dimColor:!0,children:[" ",N.desc]})]},N.key)),uo(mt,{children:" "}),uo(mt,{dimColor:!0,children:" \u2191\u2193 navigate Enter select q quit"})]})}import{useState as vt,useEffect as sn,useCallback as ja,useRef as qa}from"react";import{Box as gt,Text as Q,useInput as za}from"ink";import{jsx as ne,jsxs as ve}from"react/jsx-runtime";var Ur={raider:"R",scout:"S",brute:"B"};function Gr(e,t){let o=e/t;return o>.6?"green":o>.3?"yellow":"red"}function Xa(e,t){let o=e/t;return o>.6?"white":o>.3?"yellow":"red"}function Wr(e,t){let o=[];return e.gold>0&&o.push(`${t}${e.gold}${W.gold}`),e.wood>0&&o.push(`${t}${e.wood}${W.wood}`),e.stone>0&&o.push(`${t}${e.stone}${W.stone}`),o.join(" ")}function Za(e,t,o=6){let s=Math.round(e/t*o);return"\u2588".repeat(s)+"\u2591".repeat(o-s)}var Ja={wall:"white",trap:"magenta",treasury:"yellow",ward:"cyan",watchtower:"green",archerTower:"redBright"};function Kr({replay:e,keepGrid:t,raidType:o,summary:s,anomalies:r,initialSpeed:n,onSpeedChange:c,onDone:d}){let[g,l]=vt(0),[T,R]=vt(n??1),u=ja(C=>{R(C),c?.(C)},[c]),[i,y]=vt(!1),[w,h]=vt(new Map),[x,_]=vt(()=>{let C=new Map,B={wall:Eo,archerTower:Ao,watchtower:Mo,vault:Lo};for(let E of t.structures){let F=B[E.kind];if(F){let D=F[E.level];C.set(E.id,{structureId:E.id,kind:E.kind,pos:{...E.pos},hp:D,maxHp:D,destroyed:!1})}}return C}),[j,K]=vt([]),[N,$]=vt(new Set),[U,k]=vt([]),[b,L]=vt(null),m=qa(-1);za((C,B)=>{if(C==="q"||B.escape){d();return}if(C==="p"||C===" "){y(E=>!E);return}C==="1"&&u(1),C==="2"&&u(2),C==="4"&&u(4),C==="8"&&u(8),(C==="n"||B.return)&&l(e.maxTicks)}),sn(()=>{if(i||b)return;let C=setInterval(()=>{l(B=>{let E=B+1;return E>e.maxTicks?(clearInterval(C),B):E})},1e3/e.tickRateHz/T);return()=>clearInterval(C)},[i,T,b,e]),sn(()=>{if(g<=m.current)return;let C=e.events.filter(D=>D.t>m.current&&D.t<=g),B=[],E=[],F=new Map;h(D=>{let q=new Map(D);for(let H of C)switch(H.type){case"raider_spawn":{let Z=H.raiderType??"raider",Ee=H.maxHp??Qe[Z].hp;q.set(H.probeId,{id:H.probeId,pos:{...H.pos},alive:!0,stunned:!1,hp:Ee,maxHp:Ee,raiderType:Z});break}case"raider_move":{let Z=q.get(H.probeId);Z&&(Z.pos={...H.to},Z.stunned=!1);break}case"raider_stunned":{let Z=q.get(H.probeId);Z&&(Z.stunned=!0);break}case"arrow_hit":{let Z=q.get(H.probeId);Z&&(Z.hp=Math.max(0,H.hpRemaining));break}case"raider_destroyed":{let Z=q.get(H.probeId);Z&&(Z.alive=!1);break}default:break}return F=q,q}),_(D=>{let q=new Map(D);for(let H of C)if(H.type==="wall_damaged"||H.type==="structure_damaged"){let Z=q.get(H.structureId);Z&&(Z.hp=Math.max(0,H.hpRemaining),Z.destroyed=H.destroyed)}return q});for(let D of C)switch(D.type){case"raider_spawn":{let q=D.raiderType??"raider",H=Ur[q]??"R",Z=D.edge==="N"?"\u2193":D.edge==="S"?"\u2191":D.edge==="W"?"\u2192":"\u2190";B.push(`${H} Raider ${D.probeId+1} enters from ${D.edge} ${Z}`),E.push({pos:{...D.pos},char:Z,color:"yellowBright",bold:!0,expiresAtTick:g+3,priority:0});break}case"raider_stunned":{B.push(`\u26A1 Raider ${D.probeId+1} STUNNED ${D.stunTicks}t`),E.push({pos:{...D.pos},char:"\u2727",color:"cyanBright",bold:!0,expiresAtTick:g+3,priority:2});break}case"wall_damaged":{if(D.destroyed){B.push("\u{1F4A5} Wall DESTROYED!");let q=t.structures.find(H=>H.id===D.structureId);q&&E.push({pos:{...q.pos},char:"\u2717",color:"redBright",bold:!0,expiresAtTick:g+4,priority:2})}else B.push(`\u2694 Wall hit (${D.hpRemaining} HP)`);break}case"structure_damaged":{let q=Je[D.structureKind]??D.structureKind;if(D.destroyed){B.push(`\u{1F4A5} ${q} DESTROYED!`);let H=t.structures.find(Z=>Z.id===D.structureId);H&&E.push({pos:{...H.pos},char:"\u2717",color:"redBright",bold:!0,expiresAtTick:g+5,priority:3})}else B.push(`\u2694 ${q} hit (${D.hpRemaining} HP)`);break}case"arrow_hit":{let q=F.get(D.probeId),H=q?{...q.pos}:null;H&&E.push({pos:H,char:"\u2020",color:"redBright",bold:!0,expiresAtTick:g+2,priority:1}),D.hpRemaining<=0?(B.push(`\u{1F3F9} Archer slew raider ${D.probeId+1}!`),H&&E.push({pos:H,char:"\u2716",color:"redBright",bold:!0,expiresAtTick:g+4,priority:3})):B.push(`\u{1F3F9} Arrow hit raider ${D.probeId+1} (-${D.damage} \u2192 ${D.hpRemaining} HP)`);break}case"treasury_breach":{let q=Wr(D.lootTaken,""),H=o==="attack"?"Looted":"Lost";B.push(`\u{1F4B0} TREASURY BREACHED! ${H} ${q}`),$(Ee=>new Set([...Ee,D.structureId]));let Z=t.structures.find(Ee=>Ee.id===D.structureId);Z&&E.push({pos:{...Z.pos},char:"\u26A1",color:"yellowBright",bold:!0,expiresAtTick:g+5,priority:3});break}case"raider_destroyed":{C.some(H=>H.type==="arrow_hit"&&H.probeId===D.probeId&&H.hpRemaining<=0&&H.t===D.t)||B.push(`\u{1F480} Raider ${D.probeId+1} eliminated`);break}case"raid_end":{let q=D.outcome==="defense_win"?o==="defend"?"DEFENSE VICTORY \u2014 All raiders defeated!":"ATTACK FAILED \u2014 The keep held strong":D.outcome==="partial_breach"?o==="defend"?"PARTIAL BREACH \u2014 Raiders stole some supplies":"PARTIAL SUCCESS \u2014 Some loot seized":o==="defend"?"FULL BREACH \u2014 Major losses!":"FULL SUCCESS \u2014 Major plunder!";L(q),B.push(q);break}default:break}K(D=>[...D.filter(q=>q.expiresAtTick>g),...E]),m.current=g,B.length>0&&k(D=>[...D.slice(-10),...B])},[g,o,e,t]);let P=new Map;for(let C of t.structures)P.set(`${C.pos.x},${C.pos.y}`,C);if(!t.structures.some(C=>C.kind==="treasury")){let C={id:"__virtual_center_treasury",kind:"treasury",pos:{x:8,y:8},level:1};P.set("8,8",C)}let se=new Map;for(let[,C]of w)C.alive&&se.set(`${C.pos.x},${C.pos.y}`,C);let v=new Map;for(let[,C]of x)v.set(`${C.pos.x},${C.pos.y}`,C);let oe=new Map;for(let C of j){let B=`${C.pos.x},${C.pos.y}`,E=oe.get(B);(!E||C.priority>E.priority||C.priority===E.priority&&C.expiresAtTick>E.expiresAtTick)&&oe.set(B,C)}let fe=" "+Array.from({length:I},(C,B)=>B.toString(16).toUpperCase()+" ").join(""),be=[ne(Q,{dimColor:!0,children:fe},"hdr")];for(let C=0;C<I;C++){let B=[ne(Q,{dimColor:!0,children:C.toString(16).toUpperCase()+" "},"lbl")];for(let E=0;E<I;E++){let F=`${E},${C}`,D=se.get(F),q=P.get(F),H=v.get(F),Z=oe.get(F);if(Z&&!D)B.push(ne(Q,{color:Z.color,bold:Z.bold,children:Z.char+" "},E));else if(D){let Ee=D.stunned?"\u25CA":Ur[D.raiderType]??"\u25CF",Tt=D.stunned?"cyan":Gr(D.hp,D.maxHp);B.push(ne(Q,{color:Tt,bold:!0,children:Ee+" "},E))}else if(q)if(H&&!H.destroyed){let Ee=Xa(H.hp,H.maxHp),Tt=ye[H.kind]??"#";B.push(ne(Q,{color:Ee,bold:H.hp>H.maxHp*.6,children:Tt+" "},E))}else if(H&&H.destroyed)B.push(ne(Q,{color:"red",dimColor:!0,children:"x "},E));else if(N.has(q.id))B.push(ne(Q,{color:"red",bold:!0,children:"$ "},E));else{let Ee=ye[q.kind];B.push(ne(Q,{color:Ja[q.kind]||"white",children:Ee+" "},E))}else B.push(ne(Q,{dimColor:!0,children:yt+" "},E))}be.push(ne(gt,{children:B},C))}let me=Array.from(w.values()).filter(C=>C.alive),Te=Array.from(w.values()).filter(C=>!C.alive);return ve(gt,{flexDirection:"column",padding:1,children:[ve(gt,{flexDirection:"row",gap:2,children:[ne(Q,{bold:!0,color:o==="defend"?"red":"green",children:o==="defend"?"\u2694 DEFENDING YOUR KEEP":"\u2694 ATTACKING NPC KEEP"}),ve(Q,{children:["Tick: ",g,"/",e.maxTicks]}),ve(Q,{children:["Speed: ",T,"x"]}),ne(Q,{dimColor:!0,children:i?"[PAUSED]":""})]}),r&&r.length>0&&ve(Q,{color:"yellow",bold:!0,children:["\u26A1 Anomalies: ",r.map(C=>`${C.icon} ${C.name}`).join(" ")]}),ne(Q,{children:" "}),ve(gt,{flexDirection:"row",children:[ne(gt,{flexDirection:"column",children:be}),ve(gt,{flexDirection:"column",marginLeft:2,width:38,children:[me.length>0&&ve(gt,{flexDirection:"column",marginBottom:1,children:[ne(Q,{bold:!0,children:"Raiders"}),me.map(C=>ve(gt,{children:[ve(Q,{color:Gr(C.hp,C.maxHp),bold:!0,children:[Ur[C.raiderType],C.id+1]}),ne(Q,{children:" "}),ne(Q,{color:Gr(C.hp,C.maxHp),children:Za(C.hp,C.maxHp)}),ve(Q,{dimColor:!0,children:[" ",C.hp,"/",C.maxHp]}),C.stunned&&ne(Q,{color:"cyan",children:" STUN"})]},C.id)),Te.length>0&&ve(Q,{dimColor:!0,children:[" ",Te.length," slain"]})]}),ne(Q,{bold:!0,children:"Battle Log"}),(()=>{let C=U.slice(-10);return C.map((B,E)=>ne(Q,{dimColor:E<Math.max(0,C.length-3),wrap:"truncate",children:B},E))})()]})]}),ne(Q,{children:" "}),b?ve(gt,{flexDirection:"column",children:[ne(Q,{bold:!0,color:b.includes("VICTORY")||b.includes("SUCCESS")?"green":"red",children:b}),s&&ve(gt,{flexDirection:"column",marginTop:1,children:[ne(Q,{bold:!0,children:"\u2550\u2550\u2550 Raid Summary \u2550\u2550\u2550"}),ve(Q,{children:["Difficulty: Lv.",s.difficulty," Raiders: ",s.raidersKilled,"/",s.raidersTotal," slain"]}),ve(Q,{children:["Walls destroyed: ",s.wallsDestroyed," Archer towers: ",s.archersActive]}),s.lootGained.gold+s.lootGained.wood+s.lootGained.stone>0&&ve(Q,{color:"green",children:["Resources gained: ",Wr(s.lootGained,"+")]}),s.lootLost.gold+s.lootLost.wood+s.lootLost.stone>0&&ve(Q,{color:"red",children:["Resources lost: ",Wr(s.lootLost,"-")]})]}),ne(Q,{dimColor:!0,children:"Press q to return"})]}):ve(gt,{flexDirection:"column",children:[ne(Q,{dimColor:!0,children:"p pause 1/2/4/8 speed n/\u21B5 skip q back"}),ve(Q,{dimColor:!0,children:[ne(Q,{color:"green",bold:!0,children:"R"}),"=Raider ",ne(Q,{color:"yellow",bold:!0,children:"S"}),"=Scout ",ne(Q,{color:"red",bold:!0,children:"B"}),"=Brute ",ne(Q,{color:"cyan",children:"\u25CA"}),"=Stunned ",ne(Q,{color:"red",children:"\u2716"}),"=Kill ",ne(Q,{color:"redBright",children:"\u2020"}),"=Arrow ",ne(Q,{color:"yellowBright",children:"\u2193\u2191\u2192\u2190"}),"=Spawn"]})]})]})}import{useState as eo,useCallback as nn}from"react";import{Box as Vo,Text as a,useInput as Qa}from"ink";import{Fragment as et,jsx as p,jsxs as G}from"react/jsx-runtime";var po=["welcome","resources","move","place_wall","place_treasury","place_archer","place_trap","upgrade_explain","first_raid","raid_result","foraging","tips","done"],an={wall:"white",trap:"magenta",treasury:"yellow",ward:"cyan",watchtower:"green",archerTower:"redBright"};function ln({gameSave:e,onComplete:t}){let[o,s]=eo("welcome"),[r,n]=eo({x:7,y:7}),[c,d]=eo([]),[g,l]=eo([]),[T,R]=eo(""),[u,i]=eo(0),y=u>=4,w=po.indexOf(o),h=po.length-1,x=nn(()=>{let $=po.indexOf(o);$<po.length-1&&s(po[$+1])},[o]),_=o==="place_wall"?"wall":o==="place_treasury"?"treasury":o==="place_archer"?"archerTower":o==="place_trap"?"trap":null,j=nn(()=>{let $={width:16,height:16,structures:[...c]},U=ut({probeCount:2,keepGrid:$,seed:"tutorial-raid",probeTypes:["raider","scout"]}),k=[],b=0,L=0,m=!1;for(let Y of U.events)Y.type==="raider_spawn"?k.push(` Raider ${Y.probeId+1} appears from the ${Y.edge} edge`):Y.type==="wall_damaged"&&Y.destroyed?(L++,k.push(" A stone wall was destroyed!")):Y.type==="raider_stunned"?k.push(` Raider ${Y.probeId+1} stepped on a bear trap \u2014 stunned!`):Y.type==="arrow_hit"&&Y.hpRemaining<=0?(b++,k.push(` Archer tower eliminated raider ${Y.probeId+1}!`)):Y.type==="raider_destroyed"&&!k[k.length-1]?.includes(`raider ${Y.probeId+1}`)?(b++,k.push(` Raider ${Y.probeId+1} was destroyed`)):Y.type==="treasury_breach"?(m=!0,k.push(" Raiders breached your treasury!")):Y.type==="raid_end"&&(Y.outcome==="defense_win"?k.push(" \u2713 DEFENSE WIN \u2014 all raiders defeated!"):k.push(" \u2717 Raiders got through \u2014 time to strengthen defenses"));if(k.length>10){let Y=[...k.slice(0,3),` ... ${k.length-6} more events ...`,...k.slice(-3)];l(Y)}else l(k);let P=U.events.find(Y=>Y.type==="raid_end");R(P?.type==="raid_end"&&P.outcome==="defense_win"?"win":"loss"),x()},[c,x]);Qa(($,U)=>{if($==="s"&&o!=="move"){t();return}if(["welcome","resources","move","upgrade_explain","first_raid","raid_result","foraging","tips","done"].includes(o)){if(o==="move"){if($==="h"||$==="a"||U.leftArrow){n(b=>({...b,x:Math.max(0,b.x-1)})),i(b=>b+1);return}if($==="l"||$==="d"||U.rightArrow){n(b=>({...b,x:Math.min(I-1,b.x+1)})),i(b=>b+1);return}if($==="k"||$==="w"||U.upArrow){n(b=>({...b,y:Math.max(0,b.y-1)})),i(b=>b+1);return}if($==="j"||U.downArrow){n(b=>({...b,y:Math.min(I-1,b.y+1)})),i(b=>b+1);return}if((U.return||$===" ")&&y){x();return}return}if(o==="first_raid"){j();return}if(o==="done"){t();return}(U.return||$===" ")&&x();return}if(_){if($==="h"||$==="a"||U.leftArrow)n(b=>({...b,x:Math.max(0,b.x-1)}));else if($==="l"||$==="d"||U.rightArrow)n(b=>({...b,x:Math.min(I-1,b.x+1)}));else if($==="k"||$==="w"||U.upArrow)n(b=>({...b,y:Math.max(0,b.y-1)}));else if($==="j"||U.downArrow)n(b=>({...b,y:Math.min(I-1,b.y+1)}));else if(U.return||$==="e"){if(c.some(m=>m.pos.x===r.x&&m.pos.y===r.y))return;let L={id:`tut-${_}-${r.x}-${r.y}`,kind:_,level:1,pos:{...r},placedAtUnixMs:Date.now()};d(m=>[...m,L]),x()}}});let K=()=>{let b=new Map;for(let m of c)b.set(`${m.pos.x},${m.pos.y}`,m);let L=[];for(let m=3;m<13;m++){let P=[];for(let Y=3;Y<13;Y++){let se=r.x===Y&&r.y===m,v=b.get(`${Y},${m}`),oe=yt,fe;v&&(oe=ye[v.kind],fe=an[v.kind]),se?P.push(p(a,{backgroundColor:"white",color:"black",bold:!0,children:oe+" "},Y)):fe?P.push(p(a,{color:fe,bold:!0,children:oe+" "},Y)):P.push(p(a,{dimColor:!0,children:oe+" "},Y))}L.push(p(Vo,{children:P},m))}return p(Vo,{flexDirection:"column",children:L})},N=`[${w}/${h}]`;return G(Vo,{flexDirection:"column",padding:1,children:[G(Vo,{children:[p(a,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 Tutorial "}),p(a,{dimColor:!0,children:N})]}),p(a,{children:" "}),o==="welcome"&&G(et,{children:[G(a,{bold:!0,color:"cyan",children:["Welcome to CodeKeep, ",e.player.displayName,"!"]}),p(a,{children:" "}),G(a,{children:["CodeKeep is a ",p(a,{bold:!0,children:"tower defense"})," game played in your terminal."]}),p(a,{children:" "}),G(a,{children:[" ","Build a ",p(a,{bold:!0,children:"fortress"})," on a 16\xD716 grid"]}),G(a,{children:[" ","Place ",p(a,{bold:!0,children:"walls"}),", ",p(a,{bold:!0,children:"traps"}),", and ",p(a,{bold:!0,children:"archer towers"})," to defend"]}),G(a,{children:[" ","Protect your ",p(a,{bold:!0,color:"yellow",children:"treasuries"})," from raiding parties"]}),G(a,{children:[" ","Earn resources over time and from coding activity"]}),p(a,{children:" "}),p(a,{children:"Let's learn how to play!"}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue \xB7 s to skip tutorial"})]}),o==="resources"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Resources"}),p(a,{children:" "}),p(a,{children:"You have three resources to build and upgrade structures:"}),p(a,{children:" "}),G(a,{children:[" ",G(a,{color:"yellow",bold:!0,children:[W.gold," Gold"]})," \u2014 Your main currency. Earned from events and raids."]}),G(a,{children:[" ",G(a,{color:"green",bold:!0,children:[W.wood," Wood"]})," \u2014 Building material. Earned from treasuries and events."]}),G(a,{children:[" ",G(a,{color:"white",bold:!0,children:[W.stone," Stone"]})," \u2014 Heavy material. Used for walls and towers."]}),p(a,{children:" "}),p(a,{children:"Resources grow passively and from coding activity (git commits!)."}),G(a,{children:["You can also press ",p(a,{bold:!0,children:"f"})," for a kingdom boon anytime."]}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue"})]}),o==="move"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Movement"}),p(a,{children:" "}),p(a,{children:"Move the cursor with:"}),G(a,{children:[" ",p(a,{bold:!0,children:"h j k l"})," (vim) \xB7 ",p(a,{bold:!0,children:"W A S D"})," \xB7 ",p(a,{bold:!0,children:"Arrow keys"})]}),p(a,{children:" "}),G(a,{children:["Try moving around \u2014 ",y?p(a,{color:"green",bold:!0,children:"Great! Press Enter to continue."}):p(a,{dimColor:!0,children:"move at least 4 times to proceed"})]}),p(a,{children:" "}),K()]}),o==="place_wall"&&G(et,{children:[G(a,{bold:!0,color:"cyan",children:["Build: Stone Wall ",p(a,{color:"white",children:"#"})]}),p(a,{children:" "}),G(a,{children:["Walls ",p(a,{bold:!0,children:"block raiders"})," and force them to find another path."]}),p(a,{children:"They have HP and will eventually break if attacked."}),p(a,{children:" "}),G(a,{children:["Move to an empty cell and press ",p(a,{bold:!0,children:"Enter"})," to place one."]}),p(a,{children:" "}),K(),p(a,{children:" "}),p(a,{dimColor:!0,children:"Move with h/j/k/l \xB7 place with Enter"})]}),o==="place_treasury"&&G(et,{children:[G(a,{bold:!0,color:"cyan",children:["Build: Treasury ",p(a,{color:"yellow",children:"$"})]}),p(a,{children:" "}),G(a,{children:["Treasuries are what ",p(a,{bold:!0,color:"red",children:"raiders target"}),"."]}),G(a,{children:["They also generate ",p(a,{bold:!0,children:"passive income"})," over time."]}),p(a,{children:"Place them in protected spots behind your walls!"}),p(a,{children:" "}),p(a,{children:"Place a treasury somewhere on the grid."}),p(a,{children:" "}),K(),p(a,{children:" "}),p(a,{dimColor:!0,children:"Move and press Enter to place"})]}),o==="place_archer"&&G(et,{children:[G(a,{bold:!0,color:"cyan",children:["Build: Archer Tower ",p(a,{color:"redBright",children:"!"})]}),p(a,{children:" "}),G(a,{children:["Archer towers ",p(a,{bold:!0,children:"shoot arrows"})," at raiders within range."]}),p(a,{children:"They fire automatically and can kill raiders before they reach your treasury."}),p(a,{children:"Place them where raiders will pass by!"}),p(a,{children:" "}),p(a,{children:"Place an archer tower on the grid."}),p(a,{children:" "}),K(),p(a,{children:" "}),p(a,{dimColor:!0,children:"Move and press Enter to place"})]}),o==="place_trap"&&G(et,{children:[G(a,{bold:!0,color:"cyan",children:["Build: Bear Trap ",p(a,{color:"magenta",children:"%"})]}),p(a,{children:" "}),G(a,{children:["Traps ",p(a,{bold:!0,children:"stun raiders"})," that walk over them."]}),p(a,{children:"A stunned raider can't move for several ticks \u2014 perfect for"}),p(a,{children:"your archer towers to finish them off!"}),p(a,{children:" "}),p(a,{children:"Place a bear trap on a path raiders might take."}),p(a,{children:" "}),K(),p(a,{children:" "}),p(a,{dimColor:!0,children:"Move and press Enter to place"})]}),o==="upgrade_explain"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Upgrading & More Structures"}),p(a,{children:" "}),p(a,{children:"In the full game you can:"}),p(a,{children:" "}),G(a,{children:[" ",p(a,{bold:!0,children:"u"})," Upgrade a structure (Lv.1 \u2192 2 \u2192 3) for better stats"]}),G(a,{children:[" ",p(a,{bold:!0,children:"x"})," Demolish a structure (get 50% refund)"]}),G(a,{children:[" ",p(a,{bold:!0,children:"1-6"})," Quick-select any structure type"]}),p(a,{children:" "}),p(a,{children:"Two structures you haven't placed yet:"}),p(a,{children:" "}),G(a,{children:[" ",p(a,{color:"cyan",bold:!0,children:ye.ward})," ",Je.ward," \u2014 Place next to a treasury to ",p(a,{bold:!0,children:"reduce loot"})]}),p(a,{children:" raiders can steal. Wards protect a 1-tile radius around them."}),p(a,{children:" "}),G(a,{children:[" ",p(a,{color:"green",bold:!0,children:ye.watchtower})," ",Je.watchtower," \u2014 ",p(a,{bold:!0,children:"Extends ward range"})," when adjacent."]}),p(a,{children:" Also auto-gathers forage nearby and earns passive stone."}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Combo: Ward next to Treasury + Watchtower next to Ward = max protection!"}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue"})]}),o==="first_raid"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Your First Raid!"}),p(a,{children:" "}),p(a,{children:"Let's test your defenses. Two raiders will attack your layout:"}),p(a,{children:" "}),K(),p(a,{children:" "}),G(a,{children:[" You placed: ",c.map($=>G(a,{children:[p(a,{color:an[$.kind],bold:!0,children:ye[$.kind]})," "]},$.id))]}),p(a,{children:" "}),p(a,{bold:!0,color:"yellow",children:"Press any key to simulate the raid!"})]}),o==="raid_result"&&G(et,{children:[p(a,{bold:!0,color:T==="win"?"green":"yellow",children:T==="win"?"Victory! Your defenses held!":"The raiders got through \u2014 but that's okay!"}),p(a,{children:" "}),p(a,{bold:!0,children:"Raid replay:"}),g.map(($,U)=>p(a,{color:$.includes("WIN")?"green":$.includes("eliminated")||$.includes("destroyed")?"cyan":$.includes("breached")||$.includes("through")?"red":$.includes("stunned")?"magenta":void 0,children:$},U)),p(a,{children:" "}),T!=="win"&&p(a,{dimColor:!0,children:"Don't worry \u2014 you'll have plenty of resources to build better defenses!"}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue"})]}),o==="foraging"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Foraging"}),p(a,{children:" "}),G(a,{children:["Glowing ",p(a,{color:"cyan",bold:!0,children:"~"})," fragments appear on your grid over time."]}),G(a,{children:["Move your cursor over one and press ",p(a,{bold:!0,children:"c"})," to collect resources!"]}),p(a,{children:" "}),G(a,{children:[" ",p(a,{color:"cyan",children:"~"})," Gold Nugget ",p(a,{color:"yellow",children:"~"})," Timber ",p(a,{color:"green",children:"~"})," Ore ",p(a,{color:"white",children:"~"})," Gem"]}),p(a,{children:" "}),p(a,{children:"Structure synergies:"}),G(a,{children:[" ",p(a,{color:"redBright",children:"!"})," Archer Towers increase spawn rate"]}),G(a,{children:[" ",p(a,{color:"yellow",children:"$"})," Treasuries boost yield when nearby"]}),G(a,{children:[" ",p(a,{color:"green",children:"^"})," Watchtowers auto-collect nearby fragments"]}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue"})]}),o==="tips"&&G(et,{children:[p(a,{bold:!0,color:"cyan",children:"Pro Tips"}),p(a,{children:" "}),G(a,{children:[" ",p(a,{bold:!0,children:"?"})," Full help screen (anytime)"]}),G(a,{children:[" ",p(a,{bold:!0,children:"1-6"})," Quick-select structures"]}),G(a,{children:[" ",p(a,{bold:!0,children:"Tab"})," Jump between your structures"]}),G(a,{children:[" ",p(a,{bold:!0,children:"r"})," Quick defend (instant raid result)"]}),G(a,{children:[" ",p(a,{bold:!0,children:"v"})," Watch replay of last defense"]}),G(a,{children:[" ",p(a,{bold:!0,children:"f"})," Kingdom boon (free resources)"]}),G(a,{children:[" ",p(a,{bold:!0,children:"Esc"})," Back to main menu"]}),p(a,{children:" "}),p(a,{bold:!0,children:"Strategy:"}),G(a,{children:[" \u2022 Place walls to create ",p(a,{bold:!0,children:"chokepoints"})]}),G(a,{children:[" \u2022 Put traps in the chokepoint so raiders get ",p(a,{bold:!0,children:"stunned"})]}),G(a,{children:[" \u2022 Put archer towers nearby to ",p(a,{bold:!0,children:"finish stunned raiders"})]}),G(a,{children:[" \u2022 Hide treasuries ",p(a,{bold:!0,children:"deep inside"})," your walls"]}),G(a,{children:[" \u2022 Place wards next to treasuries to ",p(a,{bold:!0,children:"reduce loot stolen"})]}),G(a,{children:[" \u2022 ",p(a,{bold:!0,children:"Hover over"})," towers/wards to see their ",p(a,{bold:!0,color:"red",children:"range overlay"})]}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Enter to continue"})]}),o==="done"&&G(et,{children:[p(a,{bold:!0,color:"yellow",children:"You're ready to defend your keep!"}),p(a,{children:" "}),p(a,{children:"Your structures from the tutorial won't carry over \u2014 you start"}),p(a,{children:"with a clean grid and starting resources."}),p(a,{children:" "}),p(a,{children:"Go forth, build wisely, and may your walls hold strong!"}),p(a,{children:" "}),p(a,{dimColor:!0,children:"Press Enter to start playing"})]})]})}import{Box as dn,Text as Fr}from"ink";import{jsx as oi,jsxs as jo}from"react/jsx-runtime";var ei={wall:"white",trap:"magenta",treasury:"yellow",ward:"cyan",watchtower:"green",archerTower:"redBright"};function ti(e){let t=[];return e.gold>0&&t.push(`${W.gold}${e.gold}`),e.wood>0&&t.push(`${W.wood}${e.wood}`),e.stone>0&&t.push(`${W.stone}${e.stone}`),t.join(" ")}function cn({selected:e}){return jo(dn,{flexDirection:"column",children:[oi(Fr,{bold:!0,children:"Structures [ ]"}),qe.map((t,o)=>{let s=t===e,r=wt[t][1];return jo(dn,{children:[jo(Fr,{color:s?ei[t]:void 0,bold:s,dimColor:!s,children:[s?"\u25B8":" "," ",o+1," ",ye[t]," ",Je[t]]}),s&&jo(Fr,{dimColor:!0,children:[" ",ti(r)]})]},t)})]})}import{useState as ri}from"react";import{Box as qo,Text as Ct,useInput as si}from"ink";import{jsx as Wt,jsxs as to}from"react/jsx-runtime";var Hr=[{name:"Lord Ironhelm",seed:"friend-ironhelm-42",difficulty:1,tagline:"A minor lord with modest fortifications"},{name:"Lady Ashwood",seed:"friend-ashwood-99",difficulty:2,tagline:"Her archers never miss \u2014 or so she claims"},{name:"Baron Stonewatch",seed:"friend-stonewatch-256",difficulty:3,tagline:"Walls thick as legends and twice as old"}];function un({onSelectFriend:e,onBack:t}){let[o,s]=ri(0);return si((r,n)=>{if(r==="k"||r==="w"||n.upArrow)s(c=>Math.max(0,c-1));else if(r==="j"||r==="s"||n.downArrow)s(c=>Math.min(Hr.length-1,c+1));else if(n.return){let c=Hr[o],d=Fo(c.seed,c.difficulty);d.name=`${c.name}'s Keep`,d.ownerPlayerId=c.name.toLowerCase(),e(d)}else(r==="q"||n.escape)&&t()}),to(qo,{flexDirection:"column",padding:1,children:[Wt(qo,{children:Wt(Ct,{bold:!0,color:"cyan",children:"\u2694 Rival Keeps"})}),Wt(Ct,{children:" "}),Wt(Ct,{dimColor:!0,children:" Choose a rival lord's keep to raid for plunder."}),Wt(Ct,{children:" "}),Hr.map((r,n)=>to(qo,{flexDirection:"column",children:[to(qo,{children:[to(Ct,{color:n===o?"yellow":void 0,bold:n===o,children:[n===o?" \u25B8 ":" ",r.name]}),to(Ct,{dimColor:!0,children:[" Lv.",r.difficulty]})]}),n===o&&to(Ct,{dimColor:!0,children:[' "',r.tagline,'"']})]},r.name)),Wt(Ct,{children:" "}),Wt(Ct,{dimColor:!0,children:" \u2191\u2193 navigate Enter raid Esc back"})]})}import{useState as pn}from"react";import{Box as fo,Text as Me,useInput as ni}from"ink";import{Fragment as di,jsx as ze,jsxs as ht}from"react/jsx-runtime";function ai(e){let t=[];return e.gold>0&&t.push(`+${e.gold}${W.gold}`),e.wood>0&&t.push(`+${e.wood}${W.wood}`),e.stone>0&&t.push(`+${e.stone}${W.stone}`),t.join(" ")}function ii(e){let t=[];return e.gold>0&&t.push(`-${e.gold}${W.gold}`),e.wood>0&&t.push(`-${e.wood}${W.wood}`),e.stone>0&&t.push(`-${e.stone}${W.stone}`),t.join(" ")}function fn({gameSave:e,onBack:t,onWatchReplay:o}){let[s,r]=pn("raids"),[n,c]=pn(0),d=[...e.raidHistory].reverse().slice(0,15);ni((l,T)=>{if(l==="q"||T.escape){t();return}(T.tab||l==="t")&&r(R=>R==="raids"?"achievements":"raids"),s==="raids"&&d.length>0&&((l==="j"||T.downArrow)&&c(R=>Math.min(R+1,d.length-1)),(l==="k"||T.upArrow)&&c(R=>Math.max(R-1,0)),(T.return||l==="v")&&d[n]&&o&&o(d[n]))});let g=e.progression;return ht(fo,{flexDirection:"column",padding:1,children:[ht(fo,{gap:2,children:[ze(Me,{bold:!0,color:s==="raids"?"yellow":void 0,underline:s==="raids",children:"Raid Log"}),ze(Me,{bold:!0,color:s==="achievements"?"yellow":void 0,underline:s==="achievements",children:"Achievements"}),ze(Me,{dimColor:!0,children:"(Tab to switch)"})]}),ze(Me,{children:" "}),s==="raids"&&ht(fo,{flexDirection:"column",children:[ht(Me,{dimColor:!0,children:[" Total: ",g.totalRaidsWon,"W / ",g.totalRaidsLost,"L \xB7 Streak: ",g.currentWinStreak," (best ",g.bestWinStreak,")"]}),ze(Me,{children:" "}),d.length===0?ze(Me,{dimColor:!0,children:" No raids yet. Try Defend Keep or Attack NPC!"}):d.map((l,T)=>{let R=li(l.resolvedAtUnixMs),u=l.attackerId!==e.player.id,i=l.outcome==="defense_win"?u?"\u2713":"\u2717":u?"\u2717":"\u2713",y=(u?l.outcome==="defense_win":l.outcome!=="defense_win")?"green":"red",w=u?"DEF":"ATK",h=l.lootLost.gold+l.lootLost.wood+l.lootLost.stone>0?` ${ii(l.lootLost)}`:"",x=l.lootGained.gold+l.lootGained.wood+l.lootGained.stone>0?` ${ai(l.lootGained)}`:"",_=T===n?">":" ";return ht(Me,{dimColor:T>4&&T!==n,bold:T===n,children:[_," ",ze(Me,{color:y,children:i})," ",w," ",l.outcome.replace("_"," "),h,x," ",ht(Me,{dimColor:!0,children:["(",R,")"]})]},l.id)}),d.length>0&&ht(di,{children:[ze(Me,{children:" "}),ze(Me,{dimColor:!0,children:" j/k navigate \xB7 Enter/v watch replay"})]})]}),s==="achievements"&&ht(fo,{flexDirection:"column",children:[ht(Me,{dimColor:!0,children:[" ",g.achievements?.length||0," / ",lo.length," earned"]}),ze(Me,{children:" "}),lo.map(l=>{let T=g.achievements?.includes(l.id);return ze(fo,{children:ht(Me,{color:T?"green":void 0,dimColor:!T,children:[" ",T?"\u2605":"\u2606"," ",ze(Me,{bold:T,children:l.name})," \u2014 ",l.desc,l.bonus&&T?ht(Me,{color:"yellow",children:[" (",l.bonus,")"]}):null]})},l.id)})]}),ze(Me,{children:" "}),ze(Me,{dimColor:!0,children:" Tab switch \xB7 Esc/q back"})]})}function li(e){let t=Date.now()-e;return t<6e4?"just now":t<36e5?`${Math.floor(t/6e4)}m ago`:t<864e5?`${Math.floor(t/36e5)}h ago`:`${Math.floor(t/864e5)}d ago`}import{useState as mo}from"react";import{Box as go,Text as he,useInput as mi}from"ink";import{exec as gi}from"child_process";import{writeFileSync as ci,mkdirSync as ui,existsSync as pi}from"fs";import{join as mn}from"path";import{homedir as fi}from"os";var Yr=mn(fi(),".config","codekeep","crashes");function gn(e,t){if((e instanceof Error?e.message:String(e)).includes("Raw mode is not supported"))return"";pi(Yr)||ui(Yr,{recursive:!0});let s={timestamp:new Date().toISOString(),error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch,screen:t?.screen,gameState:t?.gameState},r=`crash-${Date.now()}.json`,n=mn(Yr,r);return ci(n,JSON.stringify(s,null,2),"utf-8"),n}function zo(e){let t=encodeURIComponent(`[Crash]: ${e.error.slice(0,80)}`),o=encodeURIComponent(`## Crash Report
|
|
2
|
+
import{Command as Zr}from"commander";import{render as Qr}from"ink";import{useState as E,useCallback as j,useEffect as qr}from"react";import{Box as ee,Text as v,useApp as jr,useInput as zr,useStdout as Yr}from"ink";var ke=5,Te=4,We=3,et=5,he=70,xe=[{id:"strike",name:"Strike",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to an enemy.",effects:[{type:"damage",value:6,target:"single"}]},{id:"guard",name:"Guard",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 5 Block.",effects:[{type:"block",value:5,target:"self"}]},{id:"bolster",name:"Bolster",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 8 Block.",effects:[{type:"block",value:8,target:"self"}]},{id:"spark",name:"Spark",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 4 damage to all enemies in a column.",effects:[{type:"damage",value:4,target:"column"}]},{id:"reinforce",name:"Reinforce",cost:2,type:"cast",category:"fortification",rarity:"common",description:"Gain 15 Block.",effects:[{type:"block",value:15,target:"self"}]},{id:"barrage",name:"Barrage",cost:2,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to all enemies.",effects:[{type:"damage",value:6,target:"all"}]},{id:"mend",name:"Mend",cost:1,type:"cast",category:"edict",rarity:"common",description:"Heal 4 Gate HP.",effects:[{type:"heal",value:4,target:"self"}]},{id:"lookout",name:"Lookout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 1 card.",effects:[{type:"draw",value:1}]},{id:"brace",name:"Brace",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 4 Block. Draw 1 card.",effects:[{type:"block",value:4,target:"self"},{type:"draw",value:1}]},{id:"ember",name:"Ember",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 8 damage to an enemy.",effects:[{type:"damage",value:8,target:"single"}]},{id:"slash",name:"Slash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 5 damage to an enemy. Draw 1 card.",effects:[{type:"damage",value:5,target:"single"},{type:"draw",value:1}]},{id:"rally",name:"Rally",cost:1,type:"cast",category:"edict",rarity:"common",description:"Gain 3 Block. Draw 2 cards.",effects:[{type:"block",value:3,target:"self"},{type:"draw",value:2}]},{id:"salvo",name:"Salvo",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 3 damage to all enemies.",effects:[{type:"damage",value:3,target:"all"}]},{id:"patch",name:"Patch",cost:0,type:"cast",category:"fortification",rarity:"common",description:"Gain 3 Block.",effects:[{type:"block",value:3,target:"self"}]},{id:"flare",name:"Flare",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 6 damage to all enemies in a column.",effects:[{type:"damage",value:6,target:"column"}]},{id:"cleave",name:"Cleave",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 10 damage to an enemy.",effects:[{type:"damage",value:10,target:"single"}]},{id:"iron_wall",name:"Iron Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 16 Block.",effects:[{type:"block",value:16,target:"self"}]},{id:"blitz",name:"Blitz",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 4 damage to an enemy twice.",effects:[{type:"damage",value:4,target:"single"},{type:"damage",value:4,target:"single"}]},{id:"volley",name:"Volley",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 6 damage to all enemies.",effects:[{type:"damage",value:6,target:"all"}]},{id:"resurgence",name:"Resurgence",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 8 Gate HP.",effects:[{type:"heal",value:8,target:"self"}]},{id:"keen_eye",name:"Keen Eye",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"expose",name:"Expose",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Vulnerable to an enemy.",effects:[{type:"vulnerable",value:2,target:"single"}]},{id:"weaken",name:"Weaken",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to an enemy.",effects:[{type:"weak",value:2,target:"single"}]},{id:"bash",name:"Bash",cost:1,type:"cast",category:"armament",rarity:"common",description:"Deal 7 damage. Apply 1 Vulnerable.",effects:[{type:"damage",value:7,target:"single"},{type:"vulnerable",value:1,target:"single"}]},{id:"shield_bash",name:"Shield Bash",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 6 Block. Deal 3 damage.",effects:[{type:"block",value:6,target:"self"},{type:"damage",value:3,target:"single"}]},{id:"ignite",name:"Ignite",cost:1,type:"cast",category:"armament",rarity:"common",description:"Apply 3 Burn to a column.",effects:[{type:"burn",value:3,target:"column"}]},{id:"scout",name:"Scout",cost:0,type:"cast",category:"edict",rarity:"common",description:"Draw 2 cards.",effects:[{type:"draw",value:2}]},{id:"fortify",name:"Fortify",cost:1,type:"cast",category:"fortification",rarity:"common",description:"Gain 10 Block.",effects:[{type:"block",value:10,target:"self"}]},{id:"sundering_strike",name:"Sundering Strike",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 12 damage. Apply 2 Vulnerable.",effects:[{type:"damage",value:12,target:"single"},{type:"vulnerable",value:2,target:"single"}]},{id:"pale_fire",name:"Pale Fire",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 5 damage to all. Apply 2 Burn.",effects:[{type:"damage",value:5,target:"all"},{type:"burn",value:2,target:"all"}]},{id:"shield_wall",name:"Shield Wall",cost:2,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 20 Block.",effects:[{type:"block",value:20,target:"self"}]},{id:"battle_cry",name:"Battle Cry",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Gain 1 Resolve. Draw 1 card.",effects:[{type:"resolve",value:1},{type:"draw",value:1}]},{id:"intimidate",name:"Intimidate",cost:1,type:"cast",category:"edict",rarity:"uncommon",description:"Apply 2 Weak to all enemies.",effects:[{type:"weak",value:2,target:"all"}]},{id:"hemorrhage",name:"Hemorrhage",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage. Apply 4 Burn.",effects:[{type:"damage",value:8,target:"single"},{type:"burn",value:4,target:"single"}]},{id:"restoration",name:"Restoration",cost:2,type:"cast",category:"edict",rarity:"uncommon",description:"Heal 12 Gate HP. Gain 4 Block.",effects:[{type:"heal",value:12,target:"self"},{type:"block",value:4,target:"self"}]},{id:"precision",name:"Precision",cost:1,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 10 damage to an enemy. Apply 1 Vulnerable.",effects:[{type:"damage",value:10,target:"single"},{type:"vulnerable",value:1,target:"single"}]},{id:"firestorm",name:"Firestorm",cost:2,type:"cast",category:"armament",rarity:"uncommon",description:"Deal 8 damage to a column. Apply 3 Burn.",effects:[{type:"damage",value:8,target:"column"},{type:"burn",value:3,target:"column"}]},{id:"deep_guard",name:"Deep Guard",cost:1,type:"cast",category:"fortification",rarity:"uncommon",description:"Gain 7 Block. Draw 1 card.",effects:[{type:"block",value:7,target:"self"},{type:"draw",value:1}]},{id:"inferno",name:"Inferno",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 12 damage to all enemies.",effects:[{type:"damage",value:12,target:"all"}]},{id:"fortress",name:"Fortress",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 25 Block. Gain 1 Resolve.",effects:[{type:"block",value:25,target:"self"},{type:"resolve",value:1}]},{id:"annihilate",name:"Annihilate",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 20 damage to an enemy.",effects:[{type:"damage",value:20,target:"single"}]},{id:"wardens_wrath",name:"Warden's Wrath",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Deal 8 damage to all. Apply 2 Vulnerable to all.",effects:[{type:"damage",value:8,target:"all"},{type:"vulnerable",value:2,target:"all"}]},{id:"iron_bastion",name:"Iron Bastion",cost:3,type:"cast",category:"fortification",rarity:"rare",description:"Gain 30 Block. Heal 5.",effects:[{type:"block",value:30,target:"self"},{type:"heal",value:5,target:"self"}]},{id:"rally_the_keep",name:"Rally the Keep",cost:2,type:"cast",category:"edict",rarity:"rare",description:"Draw 4 cards. Gain 1 Resolve.",effects:[{type:"draw",value:4},{type:"resolve",value:1}]},{id:"conflagration",name:"Conflagration",cost:3,type:"cast",category:"armament",rarity:"rare",description:"Apply 6 Burn to all enemies. Deal 5 damage to all.",effects:[{type:"burn",value:6,target:"all"},{type:"damage",value:5,target:"all"}]},{id:"pale_ward",name:"Pale Ward",cost:2,type:"cast",category:"fortification",rarity:"rare",description:"Gain 15 Block. Apply 2 Weak to all enemies.",effects:[{type:"block",value:15,target:"self"},{type:"weak",value:2,target:"all"}]},{id:"cataclysm",name:"Cataclysm",cost:4,type:"cast",category:"armament",rarity:"legendary",description:"Deal 30 damage to all enemies. Take 10 Gate damage.",effects:[{type:"damage",value:30,target:"all"},{type:"self_damage",value:10}]},{id:"keeps_resolve",name:"Keep's Resolve",cost:0,type:"cast",category:"edict",rarity:"legendary",description:"Gain 2 Resolve. Draw 2 cards. Gain 10 Block.",effects:[{type:"resolve",value:2},{type:"draw",value:2},{type:"block",value:10,target:"self"}]},{id:"eternal_wall",name:"Eternal Wall",cost:4,type:"cast",category:"fortification",rarity:"legendary",description:"Gain 50 Block. Heal 10 Gate HP.",effects:[{type:"block",value:50,target:"self"},{type:"heal",value:10,target:"self"}]},{id:"barricade",name:"Barricade",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 4 Block. Emplace: 8 HP structure, +2 Block/turn.",effects:[{type:"block",value:4,target:"self"}],emplaceCost:1,emplaceHp:8,emplaceEffects:[{type:"block",value:2,target:"self"}]},{id:"turret",name:"Turret",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 5 damage. Emplace: 6 HP, deals 3 damage to column/turn.",effects:[{type:"damage",value:5,target:"single"}],emplaceCost:2,emplaceHp:6,emplaceEffects:[{type:"damage",value:3,target:"column"}]},{id:"beacon",name:"Beacon",cost:1,type:"emplace",category:"edict",rarity:"uncommon",description:"Cast: Draw 1 card. Emplace: 5 HP, heals 2 Gate HP/turn.",effects:[{type:"draw",value:1}],emplaceCost:1,emplaceHp:5,emplaceEffects:[{type:"heal",value:2,target:"self"}]},{id:"ward_stone",name:"Ward Stone",cost:1,type:"emplace",category:"fortification",rarity:"common",description:"Cast: Gain 3 Block. Emplace: 10 HP, applies 1 Weak to enemies in column.",effects:[{type:"block",value:3,target:"self"}],emplaceCost:1,emplaceHp:10,emplaceEffects:[{type:"weak",value:1,target:"column"}]},{id:"siphon",name:"Siphon",cost:2,type:"emplace",category:"wild",rarity:"rare",description:"Cast: Deal 4 damage to all. Emplace: 4 HP, deals 2 damage to adjacent columns.",effects:[{type:"damage",value:4,target:"all"}],emplaceCost:2,emplaceHp:4,emplaceEffects:[{type:"damage",value:2,target:"adjacent"}]},{id:"watchtower",name:"Watchtower",cost:1,type:"emplace",category:"edict",rarity:"common",description:"Cast: Draw 2 cards. Emplace: 4 HP, applies 1 Vulnerable to enemies in column.",effects:[{type:"draw",value:2}],emplaceCost:1,emplaceHp:4,emplaceEffects:[{type:"vulnerable",value:1,target:"column"}]},{id:"flame_pit",name:"Flame Pit",cost:2,type:"emplace",category:"armament",rarity:"uncommon",description:"Cast: Deal 6 damage to column. Emplace: 5 HP, deals 4 damage to column/turn.",effects:[{type:"damage",value:6,target:"column"}],emplaceCost:2,emplaceHp:5,emplaceEffects:[{type:"damage",value:4,target:"column"}]},{id:"bulwark",name:"Bulwark",cost:2,type:"emplace",category:"fortification",rarity:"rare",description:"Cast: Gain 10 Block. Emplace: 15 HP, +3 Block/turn.",effects:[{type:"block",value:10,target:"self"}],emplaceCost:2,emplaceHp:15,emplaceEffects:[{type:"block",value:3,target:"self"}]},{id:"spike_trap",name:"Spike Trap",cost:1,type:"emplace",category:"armament",rarity:"common",description:"Cast: Deal 3 damage. Emplace: 3 HP, deals 2 damage to column/turn.",effects:[{type:"damage",value:3,target:"single"}],emplaceCost:1,emplaceHp:3,emplaceEffects:[{type:"damage",value:2,target:"column"}]},{id:"sanctum",name:"Sanctum",cost:3,type:"emplace",category:"edict",rarity:"rare",description:"Cast: Heal 6, draw 1. Emplace: 8 HP, heals 3 Gate HP/turn, +1 Block/turn.",effects:[{type:"heal",value:6,target:"self"},{type:"draw",value:1}],emplaceCost:3,emplaceHp:8,emplaceEffects:[{type:"heal",value:3,target:"self"},{type:"block",value:1,target:"self"}]}],Ht=["strike","strike","strike","guard","guard","spark","ember","brace","bolster","mend"];function $(e){return xe.find(t=>t.id===e)}var $a=[{id:"hollow",name:"Hollow",symbol:"\u2620",hp:18,damage:6,speed:1,act:1,description:"An empty shape from the Pale. Advances steadily."},{id:"needle",name:"Needle",symbol:"\u2191",hp:10,damage:4,speed:2,act:1,description:"Fast and fragile. Advances two rows per turn."},{id:"shade",name:"Shade",symbol:"\u25C8",hp:22,damage:8,speed:1,act:1,description:"A dense fragment of the Pale. Hits hard."},{id:"wisp",name:"Wisp",symbol:"\u25CB",hp:8,damage:3,speed:1,act:1,description:"Fragile but numerous. Appears in swarms."},{id:"husk",name:"Husk",symbol:"\u2593",hp:30,damage:5,speed:1,act:1,description:"Armored shell. Slow but resilient."},{id:"wraith",name:"Wraith",symbol:"\u2248",hp:15,damage:7,speed:2,act:2,description:"Drifts through emplacements. Ignores structures."},{id:"breaker",name:"Breaker",symbol:"\u2692",hp:25,damage:10,speed:1,act:2,description:"Targets emplacements first. Destroys structures."},{id:"flanker",name:"Flanker",symbol:"\u2194",hp:14,damage:6,speed:1,act:2,description:"Shifts columns before attacking."},{id:"shielder",name:"Shielder",symbol:"\u25C7",hp:20,damage:4,speed:1,act:2,description:"Grants shield to adjacent enemies."},{id:"echo",name:"Echo",symbol:"\u221E",hp:35,damage:12,speed:1,act:3,description:"A memory of something that should not exist."},{id:"boss_suture",name:"The Suture",symbol:"\u25C8",hp:60,damage:8,speed:1,act:1,description:"Stitched from fragments of the Pale. The first true threat."},{id:"boss_archivist",name:"The Archivist",symbol:"\u25A3",hp:90,damage:10,speed:1,act:2,description:"Keeper of forgotten records. Debuffs and shields methodically."},{id:"boss_pale",name:"The Pale Itself",symbol:"\u25C9",hp:130,damage:12,speed:1,act:3,description:"The void given form. Three phases of escalating horror."}];function ne(e){return $a.find(t=>t.id===e)}var _e=[{id:"heal_potion",name:"Mending Draught",description:"Heal 15 Gate HP.",effects:[{type:"heal",value:15,target:"self"}]},{id:"damage_potion",name:"Fire Flask",description:"Deal 10 damage to all enemies in a column.",effects:[{type:"damage",value:10,target:"column"}]},{id:"block_potion",name:"Iron Tonic",description:"Gain 15 Block.",effects:[{type:"block",value:15,target:"self"}]},{id:"draw_potion",name:"Seer's Ink",description:"Draw 3 cards.",effects:[{type:"draw",value:3}]},{id:"resolve_potion",name:"Warden's Flame",description:"Gain 2 Resolve this turn.",effects:[{type:"resolve",value:2}]}],$e={combat:15,elite:30,boss:50},Fa=[{npcId:"wren",tier:0,lines:[{id:"wren_t0_1",speaker:"Wren",text:"Welcome to the Keep, Warden. I maintain what's left of it."},{id:"wren_t0_2",speaker:"Wren",text:"The Pale has been encroaching for years now. Each run pushes it back \u2014 a little."},{id:"wren_t0_3",speaker:"Wren",text:"The Echoes you bring back... they're more than currency. They're fragments of what the Pale has consumed."}]},{npcId:"wren",tier:1,lines:[{id:"wren_t1_1",speaker:"Wren",text:"You're doing well, Warden. The Keep grows stronger."},{id:"wren_t1_2",speaker:"Wren",text:"I've been organizing the archives. There are records of other keeps \u2014 all fallen."},{id:"wren_t1_3",speaker:"Wren",text:"Did you know the Pale wasn't always like this? Sable might tell you more."},{id:"wren_t1_4",speaker:"Wren",text:"The structures you build... I can feel them resonate. Like the Keep remembers what it was."}]},{npcId:"wren",tier:2,lines:[{id:"wren_t2_1",speaker:"Wren",text:"I found something in the lower chambers. A journal. The previous Warden's."},{id:"wren_t2_2",speaker:"Wren",text:'"The Pale does not destroy," they wrote. "It remembers \u2014 and in remembering, unmakes."'},{id:"wren_t2_3",speaker:"Wren",text:"I think this Keep has been here longer than any of us know."}]},{npcId:"wren",tier:3,lines:[{id:"wren_t3_1",speaker:"Wren",text:"Warden. I need to tell you something. The Pale Visitor... they're not what they seem."},{id:"wren_t3_2",speaker:"Wren",text:"I've been tracking the patterns. Every fifty runs, the Pale shifts. Something deeper stirs."}]},{npcId:"wren",tier:4,lines:[{id:"wren_t4_1",speaker:"Wren",text:"The truth is... there have been many Wardens. You're not the first. And you won't be the last."},{id:"wren_t4_2",speaker:"Wren",text:"Unless you find the source. The thing the Pale is actually looking for."}]},{npcId:"sable",tier:0,lines:[{id:"sable_t0_1",speaker:"Sable",text:"Hmm? Oh, another Warden. The Archive is open, if you can read the old script."},{id:"sable_t0_2",speaker:"Sable",text:"Cards, Warden. That's what the old records call them. Patterns of power, crystallized."},{id:"sable_t0_3",speaker:"Sable",text:"The Pale erases. But patterns \u2014 true patterns \u2014 resist erasure. That's what your deck is."}]},{npcId:"sable",tier:1,lines:[{id:"sable_t1_1",speaker:"Sable",text:"I've decoded another section. The Pale isn't natural. It was created."},{id:"sable_t1_2",speaker:"Sable",text:"Someone \u2014 or something \u2014 made the Pale as a weapon. Against what, I don't yet know."},{id:"sable_t1_3",speaker:"Sable",text:"The emplacements you build... they're based on ancient designs. The old Keep had them too."}]},{npcId:"sable",tier:2,lines:[{id:"sable_t2_1",speaker:"Sable",text:'I found it. A name: "The Architect." They built the first Keep \u2014 and the first Pale.'},{id:"sable_t2_2",speaker:"Sable",text:"The Architect wanted to preserve everything. So they built a system that remembers. Perfectly."},{id:"sable_t2_3",speaker:"Sable",text:"A perfect memory is indistinguishable from a prison, Warden."}]},{npcId:"sable",tier:3,lines:[{id:"sable_t3_1",speaker:"Sable",text:"The bosses you fight... they're not monsters. They're memories. The Suture, the Archivist..."},{id:"sable_t3_2",speaker:"Sable",text:"Wait. The Archivist. That's my title. Why is there a boss named after my role?"}]},{npcId:"duskmar",tier:0,lines:[{id:"dusk_t0_1",speaker:"Duskmar",text:"I was the first to man these walls. And I'll be the last, if it comes to that."},{id:"dusk_t0_2",speaker:"Duskmar",text:"The Hollows are nothing. Wait until you face the Shades. Or the things in Act 3."},{id:"dusk_t0_3",speaker:"Duskmar",text:"Block. Always block. The Pale punishes the reckless."}]},{npcId:"duskmar",tier:1,lines:[{id:"dusk_t1_1",speaker:"Duskmar",text:"I've been teaching the walls to remember. Emplacements \u2014 that's the real defense."},{id:"dusk_t1_2",speaker:"Duskmar",text:"Every time you ascend, the Pale gets smarter. But so do you."}]},{npcId:"duskmar",tier:2,lines:[{id:"dusk_t2_1",speaker:"Duskmar",text:"I died once, you know. In the Pale. Wren brought me back with Echoes."},{id:"dusk_t2_2",speaker:"Duskmar",text:"Death isn't permanent here. That should worry you more than it comforts you."}]},{npcId:"mott",tier:0,lines:[{id:"mott_t0_1",speaker:"Mott",text:"Fragments! Echoes! Bits and pieces of a world that doesn't exist anymore!"},{id:"mott_t0_2",speaker:"Mott",text:"I trade in what the Pale leaves behind. Potions, mostly. Sometimes relics."},{id:"mott_t0_3",speaker:"Mott",text:"The shop in the Pale? That's me. Well, a memory of me. It's complicated."}]},{npcId:"mott",tier:1,lines:[{id:"mott_t1_1",speaker:"Mott",text:"Found something odd today. A card that doesn't match any pattern I've seen."},{id:"mott_t1_2",speaker:"Mott",text:"The relics aren't just powerful. They're pieces of the old world. Before the Pale."}]},{npcId:"mott",tier:2,lines:[{id:"mott_t2_1",speaker:"Mott",text:"I figured it out. The fragments? They're not resources. They're memories \u2014 compressed."},{id:"mott_t2_2",speaker:"Mott",text:"When you spend fragments, you're spending someone's past. Heavy, right?"}]},{npcId:"pale_visitor",tier:0,lines:[{id:"pv_t0_1",speaker:"???",text:"..."},{id:"pv_t0_2",speaker:"???",text:"You can see me. Interesting. Most Wardens take longer."}]},{npcId:"pale_visitor",tier:1,lines:[{id:"pv_t1_1",speaker:"The Visitor",text:"Do you know why the Keep exists, Warden?"},{id:"pv_t1_2",speaker:"The Visitor",text:"Not to fight the Pale. To contain it. There's a difference."}]},{npcId:"pale_visitor",tier:2,lines:[{id:"pv_t2_1",speaker:"The Visitor",text:"I am the Pale. Or I was. The part of it that wanted to stop."},{id:"pv_t2_2",speaker:"The Visitor",text:"Every run you complete, you weaken the whole. But you also feed it."}]},{npcId:"pale_visitor",tier:3,lines:[{id:"pv_t3_1",speaker:"The Visitor",text:"The Architect built me as a failsafe. I was supposed to shut it all down."},{id:"pv_t3_2",speaker:"The Visitor",text:"But I can't. Not alone. That's why I need a Warden."}]},{npcId:"pale_visitor",tier:4,lines:[{id:"pv_t4_1",speaker:"The Visitor",text:"There is a true ending, Warden. But it requires Ascension 15."},{id:"pv_t4_2",speaker:"The Visitor",text:"At the peak, the Pale reveals its core. And you will have a choice."},{id:"pv_t4_3",speaker:"The Visitor",text:"Destroy the Pale \u2014 and everything it remembers. Or become the new Architect."}]}];function Va(e,t){return Fa.filter(a=>a.npcId===e&&a.tier<=t).flatMap(a=>a.lines)}function Bt(e,t,r){let a=Va(e,t),o=new Set(r);return a.filter(n=>!o.has(n.id))}import{writeFileSync as nt,readFileSync as fr,mkdirSync as hr,existsSync as aa,renameSync as gr,unlinkSync as fn}from"fs";import{join as ra,dirname as yr}from"path";import{homedir as vr}from"os";function F(e){let t=e|0;return()=>{t=t+1831565813|0;let r=Math.imul(t^t>>>15,1|t);return r=r+Math.imul(r^r>>>7,61|r)^r,((r^r>>>14)>>>0)/4294967296}}function z(e){let t=0;for(let r=0;r<e.length;r++){let a=e.charCodeAt(r);t=(t<<5)-t+a|0}return t}function Nt(e,t){let r=[...e];for(let a=r.length-1;a>0;a--){let o=Math.floor(t()*(a+1));[r[a],r[o]]=[r[o],r[a]]}return r}var Oa=1;function Ve(e){return{instanceId:`card-${Oa++}`,defId:e,upgraded:!1}}function st(){return Ht.map(Ve)}function Ka(e,t){return Nt(e,t)}function Se(e,t,r,a){let o=[],n=[...e],l=[...t];for(let u=0;u<r;u++){if(n.length===0){if(l.length===0)break;n=Nt(l,a),l=[]}o.push(n.shift())}return{drawn:o,newDrawPile:n,newDiscardPile:l}}var Ua=[{templateId:"boss_suture",name:"The Suture",act:1,phases:[{hpThreshold:1,intentPattern:[{type:"attack",value:8},{type:"advance",value:1},{type:"attack",value:12}]},{hpThreshold:.5,intentPattern:[{type:"attack",value:15},{type:"summon",value:2},{type:"attack",value:10}]}],onPhaseChange:(e,t,r)=>{let a=e.columns[2];a.enemies.length<3&&a.enemies.push(it("needle",2))}},{templateId:"boss_archivist",name:"The Archivist",act:2,phases:[{hpThreshold:1,intentPattern:[{type:"debuff",value:2},{type:"attack",value:10},{type:"shield",value:15}]},{hpThreshold:.5,intentPattern:[{type:"attack",value:18},{type:"debuff",value:3},{type:"summon",value:3}]}]},{templateId:"boss_pale",name:"The Pale Itself",act:3,phases:[{hpThreshold:1,intentPattern:[{type:"attack",value:12},{type:"advance",value:1},{type:"summon",value:2}]},{hpThreshold:.6,intentPattern:[{type:"attack",value:20},{type:"debuff",value:3},{type:"attack",value:15}]},{hpThreshold:.3,intentPattern:[{type:"attack",value:25},{type:"summon",value:3},{type:"attack",value:20}]}],onPhaseChange:(e,t,r)=>{if(r>=2)for(let a of e.columns)for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="empowered");n?n.stacks+=1:o.statusEffects.push({type:"empowered",stacks:1,duration:99})}}}];function La(e){return Ua.find(t=>t.act===e)}function qa(e,t,r){let a=e.phases[0];for(let n of e.phases)t<=n.hpThreshold&&(a=n);let o=(r-1)%a.intentPattern.length;return a.intentPattern[o]}function Wt(e){switch(e){case 1:return[{templateId:"boss_suture",column:2},{templateId:"hollow",column:0},{templateId:"hollow",column:4}];case 2:return[{templateId:"boss_archivist",column:2},{templateId:"wraith",column:1},{templateId:"wraith",column:3}];case 3:return[{templateId:"boss_pale",column:2},{templateId:"echo",column:0},{templateId:"echo",column:4}];default:return[{templateId:"boss_suture",column:2}]}}var ja=1;function it(e,t){let r=ne(e);if(!r)throw new Error(`Unknown enemy template: ${e}`);return{instanceId:`enemy-${ja++}`,templateId:e,hp:r.hp,maxHp:r.hp,column:Math.max(0,Math.min(t,ke-1)),row:0,intent:null,statusEffects:[]}}function ct(e,t,r=1){let a=ne(e.templateId);if(!a)return{type:"advance",value:1};let o=La(a.act);if(o&&o.templateId===e.templateId){let l=e.hp/e.maxHp;return qa(o,l,r)}let n=t();switch(e.templateId){case"shielder":return n<.3?{type:"shield",value:5}:n<.6?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"flanker":{if(n<.4){let l=t()<.5?-1:1,u=Math.max(0,Math.min(ke-1,e.column+l));return e.column=u,{type:"attack",value:a.damage,targetColumn:u}}return n<.7?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column}}case"breaker":return n<.6?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"wraith":return n<.4?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column};case"echo":return n<.3?{type:"buff",value:1}:n<.6?{type:"attack",value:a.damage,targetColumn:e.column}:{type:"advance",value:a.speed};default:return n<.5?{type:"advance",value:a.speed}:{type:"attack",value:a.damage,targetColumn:e.column}}}function za(e,t,r){return r<0||r>=e.columns.length||e.columns[r].emplacement||!t.emplaceHp||!t.emplaceEffects?!1:(e.columns[r].emplacement={cardDefId:t.id,hp:t.emplaceHp,maxHp:t.emplaceHp,effects:t.emplaceEffects},e.events.push({type:"emplacement_placed",turn:e.turn,data:{cardId:t.id,column:r}}),!0)}function Ya(e){for(let t of e.columns)if(t.emplacement){for(let r of t.emplacement.effects)Xa(e,t.index,r);e.events.push({type:"emplacement_triggered",turn:e.turn,data:{column:t.index,cardId:t.emplacement.cardDefId}})}}function Xa(e,t,r){let a=e.columns[t];switch(r.type){case"damage":{if(r.target==="column")for(let o of a.enemies)o.hp-=r.value;else if(r.target==="adjacent")for(let o=Math.max(0,t-1);o<=Math.min(e.columns.length-1,t+1);o++)for(let n of e.columns[o].enemies)n.hp-=r.value;else for(let o of a.enemies)o.hp-=r.value;break}case"block":e.gateBlock+=r.value;break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+r.value);break;case"weak":for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="weak");n?n.stacks+=r.value:o.statusEffects.push({type:"weak",stacks:r.value,duration:2})}break;case"vulnerable":for(let o of a.enemies){let n=o.statusEffects.find(l=>l.type==="vulnerable");n?n.stacks+=r.value:o.statusEffects.push({type:"vulnerable",stacks:r.value,duration:2})}break}}function Ja(e){e.statusEffects=e.statusEffects.map(t=>({...t,duration:t.duration-1})).filter(t=>t.duration>0&&t.stacks>0)}function ge(e,t,r,a=2){let o=e.statusEffects.find(n=>n.type===t);o?(o.stacks+=r,o.duration=Math.max(o.duration,a)):e.statusEffects.push({type:t,stacks:r,duration:a})}function Ie(e,t){return e.statusEffects.find(a=>a.type===t)?.stacks??0}function Za(e){let t=1,r=Ie(e,"vulnerable");return r>0&&(t*=1+r*.25),t}function Qa(e){let t=1,r=Ie(e,"weak");return r>0&&(t*=Math.max(.25,1-r*.15)),Ie(e,"empowered")>0&&(t*=1+Ie(e,"empowered")*.25),t}function er(e){let t=Ie(e,"burn");if(t>0){e.hp-=t;let r=e.statusEffects.find(a=>a.type==="burn");return r&&(r.stacks=Math.max(0,r.stacks-1)),t}return 0}function $t(e,t,r=he,a=he,o=[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]){let n=F(t),l=Ka(e,n),u=Array.from({length:ke},(k,_)=>({index:_,enemies:[],emplacement:null}));for(let k of o){let _=it(k.templateId,k.column);u[_.column].enemies.push(_)}let{drawn:s,newDrawPile:p,newDiscardPile:g}=Se(l,[],et,n),f={columns:u,hand:s,drawPile:p,discardPile:g,exhaustPile:[],gateHp:r,gateMaxHp:a,gateBlock:0,resolve:We,maxResolve:We,turn:1,phase:"player",outcome:"undecided",events:[],seed:t};for(let k of u)for(let _ of k.enemies)_.intent=ct(_,n,1);return A(f,"turn_start",{turn:1}),f}function A(e,t,r){e.events.push({type:t,turn:e.turn,data:r})}function Ft(e,t,r,a=!1){if(e.phase!=="player")return e;let o=e.hand[t];if(!o)return e;let n=$(o.defId);if(!n)return e;if(a&&n.type==="emplace"&&n.emplaceCost!==void 0){if(n.emplaceCost>e.resolve)return e;e.resolve-=n.emplaceCost,e.hand.splice(t,1),za(e,n,r??0),e.exhaustPile.push(o)}else{if(n.cost>e.resolve)return e;e.resolve-=n.cost,e.hand.splice(t,1);for(let l of n.effects)tr(e,l,r??0);e.discardPile.push(o)}return A(e,"card_played",{cardId:o.defId,targetColumn:r,asEmplace:a}),rt(e),e}function tr(e,t,r){switch(t.type){case"damage":{if(t.target==="all")for(let a of e.columns)for(let o of a.enemies)tt(e,o,t.value);else if(t.target==="column"){let a=e.columns[r];if(a)for(let o of a.enemies)tt(e,o,t.value)}else{let a=e.columns[r];if(a&&a.enemies.length>0){let o=a.enemies.reduce((n,l)=>n.row>=l.row?n:l);tt(e,o,t.value)}}at(e);break}case"block":e.gateBlock+=t.value,A(e,"block_gained",{value:t.value});break;case"heal":e.gateHp=Math.min(e.gateMaxHp,e.gateHp+t.value);break;case"draw":{let{drawn:a,newDrawPile:o,newDiscardPile:n}=Se(e.drawPile,e.discardPile,t.value,F(e.seed+e.turn));e.hand.push(...a),e.drawPile=o,e.discardPile=n;break}case"resolve":e.resolve=Math.min(e.maxResolve+5,e.resolve+t.value);break;case"vulnerable":{let a=e.columns[r];if(a){for(let o of a.enemies)ge(o,"vulnerable",t.value,3);A(e,"status_applied",{type:"vulnerable",value:t.value,column:r})}break}case"weak":{let a=e.columns[r];if(a){for(let o of a.enemies)ge(o,"weak",t.value,3);A(e,"status_applied",{type:"weak",value:t.value,column:r})}break}case"burn":{if(t.target==="all")for(let a of e.columns)for(let o of a.enemies)ge(o,"burn",t.value,99);else{let a=e.columns[r];if(a)for(let o of a.enemies)ge(o,"burn",t.value,99)}break}case"self_damage":{e.gateHp-=t.value,A(e,"gate_hit",{self:!0,damage:t.value});break}}}function tt(e,t,r){let a=Za(t),o=Math.floor(r*a);t.hp-=o,A(e,"damage_dealt",{targetId:t.instanceId,damage:o})}function at(e){for(let t of e.columns)t.enemies=t.enemies.filter(r=>r.hp<=0?(A(e,"enemy_killed",{enemyId:r.instanceId,templateId:r.templateId}),!1):!0)}function Vt(e){return e.phase!=="player"||(A(e,"turn_end",{turn:e.turn}),e.phase="enemy",ar(e)),e}function ar(e){let t=F(e.seed+e.turn*31);for(let n of e.columns)for(let l of n.enemies){if(l.hp<=0)continue;let u=l.intent??{type:"advance",value:1};rr(e,l,u)}if(at(e),rt(e),e.outcome!=="undecided"||(e.turn++,e.phase="player",e.resolve=We,e.gateBlock=0,Ya(e),at(e),rt(e),e.outcome!=="undecided"))return;e.discardPile.push(...e.hand),e.hand=[];let{drawn:r,newDrawPile:a,newDiscardPile:o}=Se(e.drawPile,e.discardPile,et,t);e.hand=r,e.drawPile=a,e.discardPile=o;for(let n of e.columns)for(let l of n.enemies)l.intent=ct(l,t,e.turn);A(e,"turn_start",{turn:e.turn})}function rr(e,t,r){let a=ne(t.templateId),o=Qa(t);if(er(t),Ja(t),!(t.hp<=0))switch(r.type){case"advance":if(t.row=Math.min(Te-1,t.row+(a?.speed??1)),A(e,"enemy_advance",{enemyId:t.instanceId,row:t.row}),t.row>=Te-1){let n=e.columns[t.column];if(n.emplacement)n.emplacement.hp-=a?.damage??4,n.emplacement.hp<=0&&(A(e,"emplacement_destroyed",{column:t.column}),n.emplacement=null);else{let l=Math.floor((a?.damage??4)*o),u=Math.min(e.gateBlock,l);e.gateBlock-=u,e.gateHp-=l-u,A(e,"gate_hit",{enemyId:t.instanceId,damage:l-u,blocked:u})}}break;case"attack":{let n=Math.floor((a?.damage??4)*o),l=Math.min(e.gateBlock,n);e.gateBlock-=l,e.gateHp-=n-l,A(e,"gate_hit",{enemyId:t.instanceId,damage:n-l,blocked:l});break}case"buff":ge(t,"empowered",r.value,3);break;case"debuff":{e.gateBlock=Math.max(0,e.gateBlock-r.value*2);break}case"shield":{let n=e.columns[t.column];for(let l of n.enemies)ge(l,"fortified",r.value,2);break}case"summon":{let n=e.columns.findIndex(l=>l.enemies.length===0);if(n>=0){let l=it("wisp",n);e.columns[n].enemies.push(l),l.intent=ct(l,F(e.seed+e.turn*97))}break}}}function rt(e){if(e.gateHp<=0){e.gateHp=0,e.outcome="lose",e.phase="ended",A(e,"combat_end",{outcome:"lose"});return}e.columns.reduce((r,a)=>r+a.enemies.length,0)===0&&(e.outcome="win",e.phase="ended",A(e,"combat_end",{outcome:"win"}))}function lt(e,t=3,r=[]){let a=new Set(["strike","guard","bolster","brace","mend",...r]),n=xe.filter(p=>!a.has(p.id)).map(p=>({card:p,weight:p.rarity==="common"?6:p.rarity==="uncommon"?3:1})),l=n.reduce((p,g)=>p+g.weight,0),u=[],s=new Set;for(let p=0;p<t&&n.length>0;p++){let g=e()*l;for(let f of n)if(!s.has(f.card.id)&&(g-=f.weight,g<=0)){u.push(f.card),s.add(f.card.id);break}if(u.length<=p){let f=n.filter(k=>!s.has(k.card.id));if(f.length>0){let k=f[Math.floor(e()*f.length)];u.push(k.card),s.add(k.card.id)}}}return u}var nr=[{name:"Scout Party",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3}]},{name:"Swift Assault",enemies:[{templateId:"needle",column:0},{templateId:"needle",column:2},{templateId:"needle",column:4}]},{name:"Pale Vanguard",enemies:[{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"needle",column:2}]},{name:"Wisp Swarm",enemies:[{templateId:"wisp",column:0},{templateId:"wisp",column:1},{templateId:"wisp",column:2},{templateId:"wisp",column:3},{templateId:"wisp",column:4}]},{name:"Heavy Patrol",enemies:[{templateId:"shade",column:1},{templateId:"hollow",column:3}]},{name:"Armored Column",enemies:[{templateId:"husk",column:2},{templateId:"wisp",column:0},{templateId:"wisp",column:4}]}],or=[{name:"The Dark Tide",enemies:[{templateId:"shade",column:1},{templateId:"shade",column:3},{templateId:"needle",column:2}]},{name:"Hollow Legion",enemies:[{templateId:"hollow",column:0},{templateId:"hollow",column:1},{templateId:"hollow",column:3},{templateId:"hollow",column:4}]}],sr=[{name:"The Warband",enemies:[{templateId:"breaker",column:2},{templateId:"shielder",column:1},{templateId:"shielder",column:3}]},{name:"Flanking Ambush",enemies:[{templateId:"flanker",column:0},{templateId:"flanker",column:4},{templateId:"wraith",column:2}]}],ir=[{name:"Echo Vanguard",enemies:[{templateId:"echo",column:1},{templateId:"echo",column:3}]},{name:"The Final Test",enemies:[{templateId:"echo",column:2},{templateId:"breaker",column:0},{templateId:"flanker",column:4}]}],cr=[{name:"Wraith Drift",enemies:[{templateId:"wraith",column:1},{templateId:"wraith",column:3}]},{name:"Breach Team",enemies:[{templateId:"breaker",column:2},{templateId:"flanker",column:0},{templateId:"flanker",column:4}]},{name:"Shield Wall",enemies:[{templateId:"shielder",column:2},{templateId:"hollow",column:1},{templateId:"hollow",column:3}]}],lr=[{name:"Echoes of the End",enemies:[{templateId:"echo",column:1},{templateId:"echo",column:3}]},{name:"Final Surge",enemies:[{templateId:"echo",column:2},{templateId:"wraith",column:0},{templateId:"wraith",column:4},{templateId:"breaker",column:1}]}];function Ot(e,t,r=!1){let a;if(r)switch(e){case 2:a=sr;break;case 3:a=ir;break;default:a=or;break}else switch(e){case 2:a=cr;break;case 3:a=lr;break;default:a=nr;break}let o=Math.floor(t()*a.length);return{...a[o],isElite:r}}var Fe=12,Gt=4;function dr(e,t,r){if(e===0)return"combat";if(e===t-1)return"boss";let a=r();return e===Math.floor(t/2)?a<.5?"rest":"shop":a<.5?"combat":a<.65?"elite":a<.78?"event":a<.88?"rest":"shop"}function Kt(e,t){let r=F(t),a=[],o=[];for(let n=0;n<Fe;n++){let l=[],u=n===0||n===Fe-1?1:Math.floor(r()*2)+2,s=new Set;for(;s.size<u;)s.add(Math.floor(r()*Gt));for(let p=0;p<Gt;p++)if(s.has(p)){let g=dr(n,Fe,r),f={id:`node-${e}-${n}-${p}`,type:g,row:n,column:p,connections:[],visited:!1};l.push(f),a.push(f)}else l.push(null);o.push(l)}for(let n=0;n<Fe-1;n++){let l=o[n].filter(s=>s!==null),u=o[n+1].filter(s=>s!==null);if(u.length!==0){for(let s of l){let p=u.map(g=>({node:g,dist:Math.abs(g.column-s.column)})).sort((g,f)=>g.dist-f.dist);s.connections.push(p[0].node.id),p.length>1&&r()<.4&&s.connections.push(p[1].node.id)}for(let s of u)!l.some(g=>g.connections.includes(s.id))&&l.length>0&&l.map(f=>({node:f,dist:Math.abs(f.column-s.column)})).sort((f,k)=>f.dist-k.dist)[0].node.connections.push(s.id)}}return{act:e,nodes:a}}function Ce(e,t){return e.nodes.find(r=>r.id===t)}function Ut(e,t){if(!t)return e.nodes.filter(a=>a.row===0);let r=Ce(e,t);return r?r.connections.map(a=>Ce(e,a)).filter(a=>!!a):[]}function Lt(e,t=0){let r=z(e),a=Kt(1,r);return{id:`run-${Date.now()}-${r}`,seed:e,act:1,map:a,currentNodeId:null,deck:st(),gateHp:he,gateMaxHp:he,fragments:0,potions:[null,null,null],relics:[],ascensionLevel:t,combat:null}}function dt(e,t){return{...e,deck:[...e.deck,t]}}function mt(e,t){return{...e,deck:e.deck.filter(r=>r.instanceId!==t)}}function qt(e,t){let r=Ce(e.map,t);return r&&(r.visited=!0),{...e,currentNodeId:t}}function ut(e,t){return{...e,gateHp:Math.min(e.gateMaxHp,e.gateHp+t)}}function jt(e,t){return e.fragments<t?null:{...e,fragments:e.fragments-t}}function Oe(e,t){return{...e,fragments:e.fragments+t}}function zt(e,t){let r=e.potions.indexOf(null);if(r===-1)return null;let a=[...e.potions];return a[r]=t,{...e,potions:a}}function Yt(e,t){if(t<0||t>=e.potions.length)return null;let r=e.potions[t];if(!r)return null;let a=[...e.potions];return a[t]=null,{run:{...e,potions:a},potionId:r}}function Xt(e){let t=e.act+1,r=z(e.seed+`-act${t}`),a=Kt(t,r);return{...e,act:t,map:a,currentNodeId:null}}function Jt(e){let t=[],r=xe.filter(n=>n.rarity!=="common"||n.id==="slash"||n.id==="flare"),a=new Set;for(let n=0;n<5&&a.size<5;n++){let l=r[Math.floor(e()*r.length)];if(a.has(l.id)){n--;continue}a.add(l.id);let u=l.rarity==="rare"?75:l.rarity==="uncommon"?50:30;t.push({type:"card",cardDef:l,cost:u})}let o=_e[Math.floor(e()*_e.length)];return t.push({type:"potion",potionDef:o,cost:25}),t.push({type:"card_remove",cost:50}),t}var mr=[{id:"wandering_smith",name:"The Wandering Smith",description:"A cloaked figure sits by a forge that shouldn't exist this far into the Pale. They offer to work on your defenses.",choices:[{label:"Ask them to reinforce the gate. (Heal 15 HP)",effect:{type:"heal",value:15}},{label:"Ask for supplies. (Gain 15 fragments)",effect:{type:"fragments",value:15}},{label:"Leave them be.",effect:{type:"nothing"}}]},{id:"pale_fountain",name:"The Pale Fountain",description:"A spring of luminous water bubbles from cracked stone. Its glow is unsettling but beckons.",choices:[{label:"Drink deeply. (+10 max Gate HP)",effect:{type:"max_hp",value:10}},{label:"Fill a flask. (Gain a random card)",effect:{type:"card_reward"}},{label:"Leave. The Pale gives nothing freely.",effect:{type:"nothing"}}]},{id:"abandoned_cache",name:"Abandoned Cache",description:"You find a sealed chest among the rubble of a collapsed watchtower.",choices:[{label:"Pry it open. (Gain 25 fragments)",effect:{type:"fragments",value:25}},{label:"Smash it quickly. (Gain 10 fragments)",effect:{type:"fragments",value:10}},{label:"Leave it. Could be cursed.",effect:{type:"nothing"}}]},{id:"old_veteran",name:"The Old Veteran",description:`A scarred soldier leans against the wall. "I've seen the Pale take stronger keeps than yours," they say.`,choices:[{label:'"Teach me." (Remove a card from your deck)',effect:{type:"remove_card"}},{label:'"Give me your supplies." (Gain 20 fragments)',effect:{type:"fragments",value:20}},{label:'"Good luck." (Nothing happens)',effect:{type:"nothing"}}]},{id:"strange_merchant",name:"Strange Merchant",description:"A merchant with too many eyes offers wares from a floating pack.",choices:[{label:"Browse their cards. (Gain a card)",effect:{type:"card_reward"}},{label:"Sell your blood. (Lose 10 HP, gain 30 fragments)",effect:{type:"damage",value:10}},{label:"Walk away.",effect:{type:"nothing"}}]}],ur=[{id:"pale_scholar",name:"The Pale Scholar",description:'An ancient figure pores over a tome that seems to read itself. "The Pale remembers everything," they whisper.',choices:[{label:"Ask for knowledge. (Gain a card)",effect:{type:"card_reward"}},{label:"Ask for healing. (Heal 20 HP)",effect:{type:"heal",value:20}},{label:"Back away slowly.",effect:{type:"nothing"}}]},{id:"fractured_mirror",name:"The Fractured Mirror",description:"A mirror stands in the corridor, cracked but gleaming. Your reflection looks stronger than you feel.",choices:[{label:"Reach through. (+5 max Gate HP, lose 5 HP)",effect:{type:"max_hp",value:5}},{label:"Smash it. (Gain 20 fragments)",effect:{type:"fragments",value:20}},{label:"Walk past. Mirrors lie.",effect:{type:"nothing"}}]},{id:"deserters_cache",name:"Deserter's Cache",description:"Behind a collapsed wall, you find supplies left by someone who fled the Keep long ago.",choices:[{label:"Take everything. (Gain 30 fragments)",effect:{type:"fragments",value:30}},{label:"Take only what you need. (Heal 10 HP, gain 10 fragments)",effect:{type:"heal",value:10}},{label:"Leave it for the next warden.",effect:{type:"nothing"}}]}],pr=[{id:"the_last_wall",name:"The Last Wall",description:"The final barrier before the Pale's heart. Ancient wards still flicker across its surface.",choices:[{label:"Channel the wards. (+15 max Gate HP)",effect:{type:"max_hp",value:15}},{label:"Break through quickly. (Gain 20 fragments)",effect:{type:"fragments",value:20}},{label:"Let it stand. Its purpose is not yours.",effect:{type:"nothing"}}]},{id:"echo_of_a_warden",name:"Echo of a Warden",description:`A ghostly figure in warden's garb appears. "I held this keep once. Take what remains of my strength."`,choices:[{label:"Accept their gift. (Gain a card, lose 5 HP)",effect:{type:"card_reward"}},{label:"Ask them to strengthen the gate. (Heal 25 HP)",effect:{type:"heal",value:25}},{label:'"Rest now, Warden."',effect:{type:"nothing"}}]},{id:"pale_bargain",name:"The Pale Bargain",description:'The void itself speaks: "Give me something, and I will give you power."',choices:[{label:"Sacrifice a card. (Remove a card)",effect:{type:"remove_card"}},{label:"Sacrifice blood. (Lose 15 HP, gain 40 fragments)",effect:{type:"damage",value:15}},{label:'"I make no deals with the void."',effect:{type:"nothing"}}]}];function Zt(e,t){let r;switch(e){case 2:r=ur;break;case 3:r=pr;break;default:r=mr;break}return r[Math.floor(t()*r.length)]}var q=[{id:"forge",name:"The Forge",description:"Unlock card upgrades at rest sites. Higher levels improve upgrades.",maxLevel:3,upgradeCost:e=>e*30},{id:"archive",name:"The Archive",description:"Unlock rare cards in the reward pool. Higher levels add legendaries.",maxLevel:3,upgradeCost:e=>e*40},{id:"beacon_tower",name:"Beacon Tower",description:"+1 starting hand size per level.",maxLevel:2,upgradeCost:e=>e*50},{id:"foundry",name:"The Foundry",description:"Emplacements start with +2 HP per level.",maxLevel:3,upgradeCost:e=>e*25},{id:"sanctum_hall",name:"Sanctum Hall",description:"+5 max Gate HP per level.",maxLevel:3,upgradeCost:e=>e*35}];function Ee(e,t){return e.structures[t]??0}function Qt(e,t){let r=q.find(n=>n.id===t);if(!r)return null;let a=Ee(e,t);if(a>=r.maxLevel)return null;let o=r.upgradeCost(a+1);return e.echoes<o?null:{...e,echoes:e.echoes-o,structures:{...e.structures,[t]:a+1}}}function pt(){return[{id:"wren",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"sable",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"duskmar",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"mott",tier:0,echoesGiven:0,dialoguesSeen:[]},{id:"pale_visitor",tier:0,echoesGiven:0,dialoguesSeen:[]}]}function ft(e,t,r){return(e?10+t*5:3)+Math.floor(r*.5)}function ea(e,t){let r=t.npcs.find(o=>o.id===e);if(!r)return null;let a=Bt(e,r.tier,r.dialoguesSeen);return a.length===0?null:{speaker:a[0].speaker,text:a[0].text,dialogueId:a[0].id}}function ta(e,t,r){let a=e.npcs.findIndex(l=>l.id===t);if(a===-1)return e;let o=[...e.npcs],n={...o[a]};return n.dialoguesSeen=[...n.dialoguesSeen,r],o[a]=n,{...e,npcs:o}}var ot=2;function wr(){return ra(vr(),".config","codekeep")}function na(){return ra(wr(),"game.json")}function br(){return{structures:{},npcs:[],echoes:0,highestAscension:0,totalRuns:0,totalWins:0,unlockedCardIds:[],achievements:[],narrativeFlags:[]}}function oa(e){return{schemaVersion:ot,savedAtUnixMs:Date.now(),playerName:e,keep:br(),activeRun:null}}function Y(e){let t=na(),r=yr(t);aa(r)||hr(r,{recursive:!0});let a=t+".tmp",o=JSON.stringify({...e,savedAtUnixMs:Date.now()},null,2);nt(a,o,"utf-8"),gr(a,t)}function ye(){let e=na();if(!aa(e))return null;let t;try{t=fr(e,"utf-8")}catch{return null}let r;try{r=JSON.parse(t)}catch{let a=e+".bak";try{nt(a,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Corrupted save backed up to ${a}
|
|
3
|
+
`),null}if(r.schemaVersion!==ot){let a=e+`.v${r.schemaVersion}.bak`;try{nt(a,t,"utf-8")}catch{}return process.stderr.write(`[codekeep] Save version mismatch (got ${r.schemaVersion}, need ${ot}), backed up to ${a}
|
|
4
|
+
`),null}return r}import Ir from"react";import{Box as Cr,Text as S}from"ink";import{writeFileSync as kr,mkdirSync as Tr,existsSync as xr}from"fs";import{join as sa}from"path";import{homedir as _r}from"os";var ht=sa(_r(),".config","codekeep","crashes");function ia(e,t){if((e instanceof Error?e.message:String(e)).includes("Raw mode is not supported"))return"";xr(ht)||Tr(ht,{recursive:!0});let a={timestamp:new Date().toISOString(),error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch,screen:t?.screen,gameState:t?.gameState},o=`crash-${Date.now()}.json`,n=sa(ht,o);return kr(n,JSON.stringify(a,null,2),"utf-8"),n}function ca(e){let t=encodeURIComponent(`[Crash]: ${e.error.slice(0,80)}`),r=encodeURIComponent(`## Crash Report
|
|
11
5
|
|
|
12
6
|
**Error:** ${e.error}
|
|
13
7
|
|
|
@@ -22,18 +16,8 @@ ${e.stack??"N/A"}
|
|
|
22
16
|
- OS: ${e.platform} ${e.arch}
|
|
23
17
|
- Screen: ${e.screen??"unknown"}
|
|
24
18
|
- Time: ${e.timestamp}
|
|
25
|
-
`);return`https://github.com/tooyipjee/codekeep/issues/new?title=${t}&body=${o}&labels=bug,crash`}import{Fragment as hi,jsx as Se,jsxs as Rt}from"react/jsx-runtime";function hn({onBack:e,onResetGame:t,onReplayTutorial:o,asciiMode:s,onToggleAscii:r}){let[n,c]=mo(0),[d,g]=mo(!1),[l,T]=mo(!1),[R,u]=mo(""),[i,y]=mo(null),w=[{key:"ascii",label:`ASCII Mode: ${s?"ON":"OFF"}`,desc:"Use plain ASCII borders (for basic terminals)"},{key:"tutorial",label:"Replay Tutorial",desc:"Learn how to play again"},{key:"report",label:"Report Bug",desc:"Open a GitHub issue for bugs"},{key:"reset",label:"Reset Game",desc:"Delete save and start over"},{key:"back",label:"Back",desc:"Return to menu"}];return mi((h,x)=>{if(i){(x.escape||x.return||h==="q")&&(y(null),T(!1));return}if(l){if(x.escape){T(!1),u("");return}if(x.return&&R.length>0){let _={timestamp:new Date().toISOString(),error:R,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch,screen:"settings"},j=zo(_);y(j),u("");let K=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";gi(`${K} "${j}"`,()=>{});return}if(x.backspace||x.delete){u(_=>_.slice(0,-1));return}h&&h.length===1&&u(_=>_+h);return}if(d){if(h==="y"||h==="Y"){t();return}g(!1);return}if(h==="q"||x.escape){e();return}if((h==="j"||h==="s"||x.downArrow)&&c(_=>Math.min(_+1,w.length-1)),(h==="k"||h==="w"||x.upArrow)&&c(_=>Math.max(_-1,0)),x.return){let _=w[n];_.key==="ascii"?r():_.key==="tutorial"?o():_.key==="report"?T(!0):_.key==="reset"?g(!0):_.key==="back"&&e()}}),Rt(go,{flexDirection:"column",padding:1,children:[Se(he,{bold:!0,color:"yellow",children:"\u25C6 Settings"}),Se(he,{children:" "}),i?Rt(go,{flexDirection:"column",children:[Se(he,{bold:!0,color:"green",children:"Bug report ready!"}),Se(he,{children:" "}),Se(he,{children:"Opening in your browser..."}),Se(he,{children:"If it didn't open, copy this URL:"}),Se(he,{children:" "}),Se(he,{color:"cyan",wrap:"wrap",children:i}),Se(he,{children:" "}),Se(he,{dimColor:!0,children:"Press Enter or Esc to go back"})]}):l?Rt(go,{flexDirection:"column",children:[Se(he,{bold:!0,color:"cyan",children:"Report a Bug"}),Se(he,{children:"Describe the issue briefly:"}),Rt(he,{color:"cyan",children:[R,Se(he,{dimColor:!0,children:"_"})]}),Se(he,{children:" "}),Se(he,{dimColor:!0,children:"Enter to generate GitHub issue URL \xB7 Esc cancel"})]}):d?Rt(go,{flexDirection:"column",children:[Se(he,{bold:!0,color:"red",children:"Are you sure you want to reset?"}),Se(he,{children:"This will permanently delete your save file."}),Se(he,{children:"All structures, resources, and achievements will be lost."}),Se(he,{children:" "}),Rt(he,{bold:!0,children:["Press ",Se(he,{color:"red",children:"Y"})," to confirm, any other key to cancel"]})]}):Rt(hi,{children:[w.map((h,x)=>Rt(go,{children:[Rt(he,{color:x===n?"yellow":void 0,bold:x===n,children:[x===n?" \u25B8 ":" ",h.label]}),Rt(he,{dimColor:!0,children:[" ",h.desc]})]},h.key)),Se(he,{children:" "}),Se(he,{dimColor:!0,children:" \u2191\u2193 navigate \xB7 Enter select \xB7 Esc back"})]})]})}import xi from"react";import{Box as Ti,Text as Le}from"ink";import{Fragment as xn,jsx as tt,jsxs as Kt}from"react/jsx-runtime";var Xo=class extends xi.Component{constructor(t){super(t),this.state={error:null,crashFilePath:null,issueUrl:null}}static getDerivedStateFromError(t){return{error:t}}componentDidCatch(t){try{let o=gn(t),s={timestamp:new Date().toISOString(),error:t.message,stack:t.stack,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch},r=zo(s);this.setState({crashFilePath:o,issueUrl:r})}catch{}}render(){return this.state.error?Kt(Ti,{flexDirection:"column",padding:1,children:[tt(Le,{bold:!0,color:"red",children:"\u25C6 CodeKeep \u2014 Something went wrong"}),tt(Le,{children:" "}),tt(Le,{color:"red",children:this.state.error.message}),tt(Le,{children:" "}),tt(Le,{dimColor:!0,children:"Your save file is at:"}),Kt(Le,{children:[" ","~/.config/codekeep/game.json"]}),tt(Le,{children:" "}),this.state.crashFilePath&&Kt(xn,{children:[tt(Le,{dimColor:!0,children:"Crash report saved to:"}),Kt(Le,{children:[" ",this.state.crashFilePath]}),tt(Le,{children:" "})]}),this.state.issueUrl&&Kt(xn,{children:[tt(Le,{dimColor:!0,children:"Report this bug:"}),Kt(Le,{color:"cyan",children:[" ",this.state.issueUrl]}),tt(Le,{children:" "})]}),tt(Le,{dimColor:!0,children:"If the game won't start, try:"}),Kt(Le,{bold:!0,children:[" ","codekeep --tutorial"]}),tt(Le,{children:" "}),tt(Le,{dimColor:!0,children:"Press Ctrl+C to exit"})]}):this.props.children}};import{useState as yi}from"react";import{Box as Zo,Text as xt,useInput as wi}from"ink";import{jsx as Bt,jsxs as Ft}from"react/jsx-runtime";var Ri={copper:"white",iron:"gray",silver:"whiteBright",gold:"yellow",diamond:"cyan"},Si={copper:"\u25CB",iron:"\u25CF",silver:"\u25C6",gold:"\u2605",diamond:"\u25C8"};function Tn({pvpProfile:e,targets:t,isSearching:o,onSearch:s,onAttack:r,onWarCamp:n,onLeaderboard:c,onBack:d}){let[g,l]=yi(0),T=[{key:"search",label:"Find Opponent"},{key:"warcamp",label:"War Camp"},{key:"leaderboard",label:"Leaderboard"},...t.map((y,w)=>({key:`target-${w}`,label:`Attack ${y.displayName} (${y.trophies} trophies)`})),{key:"back",label:"Back"}];wi((y,w)=>{if(w.upArrow||y==="k")l(h=>Math.max(0,h-1));else if(w.downArrow||y==="j")l(h=>Math.min(T.length-1,h+1));else if(w.return){let h=T[g];if(h.key==="search")s();else if(h.key==="warcamp")n();else if(h.key==="leaderboard")c();else if(h.key==="back")d();else if(h.key.startsWith("target-")){let x=parseInt(h.key.split("-")[1]),_=t[x];_&&r(_)}}else(w.escape||y==="q")&&d()});let R=e?.league??"copper",u=Ri[R]??"white",i=Si[R]??"\u25CB";return Ft(Zo,{flexDirection:"column",padding:1,children:[Bt(xt,{bold:!0,color:"red",children:"\u2694 PVP ARENA"}),Bt(xt,{children:" "}),e&&Ft(Zo,{flexDirection:"column",children:[Ft(Zo,{flexDirection:"row",gap:2,children:[Ft(xt,{color:u,bold:!0,children:[i," ",R.toUpperCase()," League"]}),Ft(xt,{children:["Trophies: ",Bt(xt,{bold:!0,color:"yellow",children:e.trophies})]})]}),e.shieldExpiresAt&&e.shieldExpiresAt>Date.now()&&Ft(xt,{color:"cyan",children:["Shield active (",Math.ceil((e.shieldExpiresAt-Date.now())/36e5),"h remaining)"]}),Bt(xt,{children:" "})]}),o&&Bt(xt,{color:"yellow",children:"Searching for opponents..."}),T.map((y,w)=>Bt(Zo,{children:Ft(xt,{color:w===g?"yellow":void 0,bold:w===g,children:[w===g?" \u25B8 ":" ",y.label]})},y.key)),Bt(xt,{children:" "}),Bt(xt,{dimColor:!0,children:"\u2191\u2193 select Enter choose Esc back"})]})}import{useState as Jo,useEffect as bi}from"react";import{Box as ho,Text as ot,useInput as ki}from"ink";import{jsx as Et,jsxs as At}from"react/jsx-runtime";var Vr=[{type:"raider",name:"Raider",symbol:"R"},{type:"scout",name:"Scout",symbol:"S"},{type:"brute",name:"Brute",symbol:"B"}];function yn(e){let t=Math.ceil(e/1e3),o=Math.floor(t/60),s=t%60;return`${o}:${String(s).padStart(2,"0")}`}function wn({warCamp:e,resources:t,onTrain:o,onBack:s}){let[r,n]=Jo(0),[c,d]=Jo(!1),[g,l]=Jo(0),[T,R]=Jo(Date.now());return bi(()=>{let u=setInterval(()=>R(Date.now()),1e3);return()=>clearInterval(u)},[]),ki((u,i)=>{if(c){i.upArrow||u==="k"?l(y=>Math.max(0,y-1)):i.downArrow||u==="j"?l(y=>Math.min(Vr.length-1,y+1)):i.return?(o(r,Vr[g].type),d(!1)):i.escape&&d(!1);return}i.upArrow||u==="k"?n(y=>Math.max(0,y-1)):i.downArrow||u==="j"?n(y=>Math.min(e.maxSlots-1,y+1)):i.return?e.slots[r]?.raiderType||d(!0):(i.escape||u==="q")&&s()}),At(ho,{flexDirection:"column",padding:1,children:[Et(ot,{bold:!0,color:"red",children:"\u2694 WAR CAMP"}),Et(ot,{dimColor:!0,children:"Train raiders for PvP attacks"}),Et(ot,{children:" "}),At(ho,{flexDirection:"row",gap:2,children:[At(ot,{children:["Gold: ",t.gold,W.gold]}),At(ot,{children:["Wood: ",t.wood,W.wood]}),At(ot,{children:["Stone: ",t.stone,W.stone]})]}),Et(ot,{children:" "}),At(ot,{bold:!0,children:["Slots (",e.slots.filter(u=>u.raiderType).length,"/",e.maxSlots,")"]}),Array.from({length:e.maxSlots},(u,i)=>{let y=e.slots[i],w=r===i,h=y?.readyAtMs?y.readyAtMs<=T:!1,x=y?.readyAtMs?y.readyAtMs>T:!1,_=x&&y?.readyAtMs?y.readyAtMs-T:0,j;return y?.raiderType?x?j=`${y.raiderType} training... ${yn(_)}`:h?j=`${y.raiderType} READY!`:j=`${y.raiderType}`:j="[ Empty ]",Et(ho,{children:At(ot,{color:w?"yellow":void 0,bold:w,children:[w?" \u25B8 ":" ","Slot ",i+1,": ",j]})},i)}),c&&At(ho,{flexDirection:"column",marginTop:1,children:[Et(ot,{bold:!0,color:"cyan",children:"Select raider type:"}),Vr.map((u,i)=>{let y=Bo[u.type],w=hs[u.type],h=Qe[u.type],x=t.gold>=y.gold&&t.wood>=y.wood&&t.stone>=y.stone;return Et(ho,{children:At(ot,{color:g===i?"yellow":x?void 0:"red",bold:g===i,children:[g===i?" \u25B8 ":" ",u.name," \u2014 HP:",h.hp," DMG:",h.damage," SPD:",h.speed," \u2014 ",y.gold,"g ",y.wood,"w ",y.stone,"s \u2014 ",yn(w)]})},u.type)})]}),Et(ot,{children:" "}),Et(ot,{dimColor:!0,children:"\u2191\u2193 select Enter train Esc back"})]})}import{Box as Qo,Text as Xe,useInput as vi}from"ink";import{jsx as rt,jsxs as xo}from"react/jsx-runtime";var Ci={copper:"white",iron:"gray",silver:"whiteBright",gold:"yellow",diamond:"cyan"};function Rn({entries:e,currentPlayerId:t,isLoading:o,onBack:s}){return vi((r,n)=>{(n.escape||r==="q")&&s()}),xo(Qo,{flexDirection:"column",padding:1,children:[rt(Xe,{bold:!0,color:"yellow",children:"\u{1F3C6} LEADERBOARD"}),rt(Xe,{children:" "}),o&&rt(Xe,{color:"yellow",children:"Loading..."}),!o&&e.length===0&&rt(Xe,{dimColor:!0,children:"No players on the leaderboard yet."}),!o&&e.length>0&&xo(Qo,{flexDirection:"column",children:[xo(Qo,{children:[rt(Xe,{bold:!0,children:" # "}),rt(Xe,{bold:!0,children:"Name "}),rt(Xe,{bold:!0,children:"League "}),rt(Xe,{bold:!0,children:"Trophies"})]}),e.map(r=>{let n=r.id===t,c=n?"green":void 0,d=Ci[r.league]??"white";return xo(Qo,{children:[xo(Xe,{color:c,bold:n,children:[String(r.rank).padStart(3," ")," "]}),rt(Xe,{color:c,bold:n,children:(r.displayName||"Unknown").padEnd(20," ")}),rt(Xe,{color:d,children:r.league.padEnd(10," ")}),rt(Xe,{color:"yellow",bold:!0,children:r.trophies})]},r.id)})]}),rt(Xe,{children:" "}),rt(Xe,{dimColor:!0,children:"Esc/q back"})]})}import{useState as jr}from"react";import{Box as er,Text as $e,useInput as Ei}from"ink";import{Fragment as Ai,jsx as Ye,jsxs as Ht}from"react/jsx-runtime";function Sn({onLogin:e,onRegister:t,onPlayOffline:o,error:s,isLoading:r}){let[n,c]=jr("menu"),[d,g]=jr(""),[l,T]=jr(0),R=[{key:"register",label:"Register (new player)"},{key:"login",label:"Login (with API key)"},{key:"offline",label:"Play Offline"}];return Ei((u,i)=>{if(!r){if(n==="menu"){if(i.upArrow||u==="k")T(y=>Math.max(0,y-1));else if(i.downArrow||u==="j")T(y=>Math.min(R.length-1,y+1));else if(i.return){let y=R[l];y.key==="register"?c("register"):y.key==="login"?c("login"):y.key==="offline"&&o()}return}if(i.escape){c("menu"),g("");return}if(i.return&&d.length>0){n==="login"?e(d):n==="register"&&t(d),g("");return}if(i.backspace||i.delete){g(y=>y.slice(0,-1));return}u&&u.length===1&&g(y=>y+u)}}),Ht(er,{flexDirection:"column",padding:1,children:[Ye($e,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep Online"}),Ye($e,{children:" "}),s&&Ye($e,{color:"red",children:s}),r&&Ye($e,{color:"yellow",children:"Connecting..."}),n==="menu"&&!r&&Ht(Ai,{children:[R.map((u,i)=>Ye(er,{children:Ht($e,{color:i===l?"yellow":void 0,bold:i===l,children:[i===l?" \u25B8 ":" ",u.label]})},u.key)),Ye($e,{children:" "}),Ye($e,{dimColor:!0,children:"\u2191\u2193 navigate Enter select"})]}),n==="login"&&!r&&Ht(er,{flexDirection:"column",children:[Ye($e,{children:"Enter your API key:"}),Ht($e,{color:"cyan",children:[d,Ye($e,{dimColor:!0,children:"_"})]}),Ye($e,{children:" "}),Ye($e,{dimColor:!0,children:"Enter to submit Esc back"})]}),n==="register"&&!r&&Ht(er,{flexDirection:"column",children:[Ye($e,{children:"Choose a display name:"}),Ht($e,{color:"cyan",children:[d,Ye($e,{dimColor:!0,children:"_"})]}),Ye($e,{children:" "}),Ye($e,{dimColor:!0,children:"Enter to submit Esc back"})]})]})}import{useState as Yt}from"react";import{Box as it,Text as J,useInput as Mi}from"ink";import{jsx as ee,jsxs as Pe}from"react/jsx-runtime";var bn={N:"North",S:"South",E:"East",W:"West"},oo={raider:"R",scout:"S",brute:"B"},tr={raider:"red",scout:"cyan",brute:"magenta"},kn=8;function vn(e,t){switch(e){case"N":return{x:t,y:0};case"S":return{x:t,y:I-1};case"E":return{x:I-1,y:t};case"W":return{x:0,y:t}}}function Cn({targetName:e,targetTrophies:t,targetGrid:o,warCamp:s,resources:r,onLaunch:n,onBack:c}){let[d,g]=Yt("scout"),[l,T]=Yt([]),[R,u]=Yt(0),[i,y]=Yt([]),[w,h]=Yt("N"),[x,_]=Yt(Math.floor(I/2)),[j,K]=Yt(0),N=["raider","scout","brute"];Mi((k,b)=>{if(b.escape||k==="q"){if(d==="scout"){c();return}if(d==="army"){g("scout");return}if(d==="deploy"){g("army");return}if(d==="confirm"){g("deploy");return}return}if(d==="scout"){b.return&&g("army");return}if(d==="army"){b.upArrow||k==="k"?u(L=>Math.max(0,L-1)):b.downArrow||k==="j"?u(L=>Math.min(N.length-1,L+1)):b.return?l.length<kn&&T(L=>[...L,N[R]]):b.backspace||b.delete?T(L=>L.slice(0,-1)):(k==="n"||b.tab)&&l.length>0&&(y(l.map(()=>({edge:"N",offset:Math.floor(I/2)}))),K(0),g("deploy"));return}if(d==="deploy"){b.leftArrow||k==="a"?_(w==="E"||w==="W"?L=>Math.max(0,L-1):L=>Math.max(0,L-1)):b.rightArrow||k==="d"?_(L=>Math.min(I-1,L+1)):b.upArrow||k==="w"?_(L=>Math.max(0,L-1)):b.downArrow||k==="s"?_(L=>Math.min(I-1,L+1)):k==="n"?h("N"):k==="e"?h("E"):k==="z"?h("S"):k==="x"?h("W"):b.return?(y(L=>{let m=[...L];return m[j]={edge:w,offset:x},m}),j<l.length-1?K(L=>L+1):g("confirm")):b.tab&&K(L=>(L+1)%l.length);return}if(d==="confirm"){if(b.return||k==="y"){let L=l.map((m,P)=>({raiderType:m,edge:i[P]?.edge??"N",offset:i[P]?.offset??8,waveDelay:0}));n(L)}return}});let U=(()=>{let k=Array.from({length:I},()=>Array(I).fill("\xB7"));for(let m of o.structures)m.kind==="trap"||m.kind==="ward"?k[m.pos.y][m.pos.x]="?":k[m.pos.y][m.pos.x]=ye[m.kind];if(d==="deploy"||d==="confirm"){for(let m=0;m<i.length;m++){let P=i[m];if(!P)continue;let Y=vn(P.edge,P.offset);k[Y.y][Y.x]=oo[l[m]]}if(d==="deploy"){let m=vn(w,x);k[m.y][m.x]="\u25BC"}}let b=" "+Array.from({length:I},(m,P)=>P.toString(16).toUpperCase()).join(" "),L=k.map((m,P)=>{let Y=P.toString(16).toUpperCase(),se=m.join(" ");return`${Y} ${se}`});return[b,...L]})();return Pe(it,{flexDirection:"column",padding:1,children:[ee(J,{bold:!0,color:"red",children:"\u2694 RAID PLANNER"}),Pe(it,{gap:2,children:[Pe(J,{children:["Target: ",ee(J,{bold:!0,color:"yellow",children:e})]}),Pe(J,{children:["Trophies: ",ee(J,{bold:!0,children:t})]})]}),ee(J,{children:" "}),ee(it,{flexDirection:"column",children:U.map((k,b)=>b===0?ee(J,{dimColor:!0,children:k},b):ee(J,{children:k},b))}),ee(J,{children:" "}),ee(it,{gap:2,children:ee(J,{dimColor:!0,children:"# wall $ treasury ^ tower ! archer & vault ? hidden"})}),ee(J,{children:" "}),d==="scout"&&Pe(it,{flexDirection:"column",children:[ee(J,{bold:!0,color:"cyan",children:"SCOUT PHASE"}),ee(J,{children:"Study the enemy keep. Traps and wards are hidden (?)."}),Pe(J,{children:["Structures: ",o.structures.length," placed"]}),ee(J,{children:" "}),ee(J,{dimColor:!0,children:"Enter \u2192 pick your army Esc \u2192 back"})]}),d==="army"&&Pe(it,{flexDirection:"column",children:[Pe(J,{bold:!0,color:"cyan",children:["ARMY SELECTION (",l.length,"/",kn,")"]}),ee(J,{children:" "}),N.map((k,b)=>{let L=Qe[k],m=Bo[k];return ee(it,{children:Pe(J,{color:R===b?"yellow":void 0,bold:R===b,children:[R===b?" \u25B8 ":" ",ee(J,{color:tr[k],children:oo[k]})," ",k.charAt(0).toUpperCase()+k.slice(1)," ","HP:",L.hp," DMG:",L.damage," SPD:",L.speed," ","(",m.gold,"g ",m.wood,"w ",m.stone,"s)"]})},k)}),ee(J,{children:" "}),Pe(it,{gap:1,children:[ee(J,{children:"Army: "}),l.length===0?ee(J,{dimColor:!0,children:"(empty)"}):l.map((k,b)=>ee(J,{color:tr[k],bold:!0,children:oo[k]},b))]}),ee(J,{children:" "}),ee(J,{dimColor:!0,children:"Enter add Backspace remove Tab/N \u2192 deploy Esc back"})]}),d==="deploy"&&Pe(it,{flexDirection:"column",children:[Pe(J,{bold:!0,color:"cyan",children:["DEPLOY \u2014 Placing ",oo[l[j]]," (",l[j],") [",j+1,"/",l.length,"]"]}),Pe(J,{children:["Edge: ",ee(J,{bold:!0,color:"yellow",children:bn[w]})," Position: ",ee(J,{bold:!0,children:x.toString(16).toUpperCase()})]}),ee(J,{children:" "}),Pe(it,{gap:1,children:[ee(J,{children:"Spawns: "}),l.map((k,b)=>{let L=i[b],m=b<j||b===j&&!1;return Pe(J,{color:b===j?"yellow":m?tr[k]:"gray",children:[oo[k],L?`@${L.edge}${L.offset.toString(16)}`:"",b<l.length-1?" ":""]},b)})]}),ee(J,{children:" "}),ee(J,{dimColor:!0,children:"\u2190\u2192 / \u2191\u2193 position N/E/Z/X edge Enter place Tab next Esc back"})]}),d==="confirm"&&Pe(it,{flexDirection:"column",children:[ee(J,{bold:!0,color:"green",children:"READY TO ATTACK"}),ee(J,{children:" "}),ee(it,{flexDirection:"column",children:l.map((k,b)=>{let L=i[b];return Pe(J,{children:[ee(J,{color:tr[k],bold:!0,children:oo[k]})," ",k," \u2192 ",L?`${bn[L.edge]} edge, pos ${L.offset.toString(16).toUpperCase()}`:"unplaced"]},b)})}),ee(J,{children:" "}),Pe(J,{bold:!0,children:["Press ",ee(J,{color:"green",children:"Enter"})," to launch raid or ",ee(J,{color:"red",children:"Esc"})," to adjust"]})]})]})}import{useState as To}from"react";import{Box as ro,Text as pe,useInput as Ii}from"ink";function _i(){let e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}function En(){let e=_i(),t=It(e),o=Wo(t),s=[],r=Math.floor(I/2),n=Math.floor(I/2);s.push({id:"treasury-8-8",kind:"treasury",level:1,pos:{x:r,y:n},placedAtUnixMs:Date.now()});let c={id:`daily-${e}`,name:`Daily Challenge ${e}`,ownerPlayerId:"daily",grid:{width:I,height:I,structures:s},resources:{gold:60,wood:30,stone:20},createdAtUnixMs:Date.now(),updatedAtUnixMs:Date.now()};return{dateKey:e,seed:t,keep:c,wave:0,score:0,alive:!0,lastReplay:null,lastGrid:null}}function An(e){if(!e.alive)return e;let t=e.wave+1,o=Math.min(10,Math.floor(t/2)+1),s=3+o,r=`daily-${e.dateKey}-wave-${t}`,n=at(It(r)),c=Lt(s,o,n),d=ut({probeCount:s,keepGrid:e.keep.grid,seed:r,probeTypes:c}),g=d.events[d.events.length-1];if(g?.type!=="raid_end")return{...e,wave:t,lastReplay:d,lastGrid:e.keep.grid};let l=g.outcome==="defense_win",T=d.events.filter(h=>h.type==="raider_destroyed").length,R=l?s*o*10:T*o*3,u=d.events.filter(h=>h.type==="treasury_breach").reduce((h,x)=>({gold:h.gold+x.lootTaken.gold,wood:h.wood+x.lootTaken.wood,stone:h.stone+x.lootTaken.stone}),{gold:0,wood:0,stone:0}),i=l?{gold:10+o*3,wood:5+o*2,stone:5+o*2}:{gold:0,wood:0,stone:0},y={gold:Math.max(0,e.keep.resources.gold-u.gold),wood:Math.max(0,e.keep.resources.wood-u.wood),stone:Math.max(0,e.keep.resources.stone-u.stone)},w=nt(He(y,i));return{...e,wave:t,score:e.score+R,alive:l,keep:{...e.keep,resources:w},lastReplay:d,lastGrid:e.keep.grid}}function Mn(e){let t=`${e.dateKey}:${e.wave}:${e.score}`;return Math.abs(It(t)).toString(36).slice(0,8)}import{jsx as Be,jsxs as Ge}from"react/jsx-runtime";function _n({onBack:e,onSaveScore:t}){let[o,s]=To(()=>En()),[r,n]=To({x:8,y:8}),[c,d]=To(0),[g,l]=To("build"),[T,R]=To("Place structures, then press R to start a wave"),u=qe[c];Ii((w,h)=>{if(g==="result"){if(!o.alive){(h.return||w===" ")&&(t(o.dateKey,o.wave,o.score),e());return}(h.return||w===" ")&&(l("build"),R("Prepare for next wave \u2014 place/upgrade, then R to fight"));return}if(h.escape){t(o.dateKey,o.wave,o.score),e();return}if(h.upArrow||w==="k")n(x=>({x:x.x,y:Math.max(0,x.y-1)}));else if(h.downArrow||w==="j")n(x=>({x:x.x,y:Math.min(I-1,x.y+1)}));else if(h.leftArrow||w==="h")n(x=>({x:Math.max(0,x.x-1),y:x.y}));else if(h.rightArrow||w==="l")n(x=>({x:Math.min(I-1,x.x+1),y:x.y}));else if(w==="[")d(x=>(x-1+qe.length)%qe.length);else if(w==="]")d(x=>(x+1)%qe.length);else if(w>="1"&&w<="6")d(parseInt(w)-1);else if(w==="e"){let x=Oo(o.keep,r,u);x.ok&&x.keep?(s(_=>({..._,keep:x.keep})),R(`Placed ${Je[u]}`)):R(`!${x.reason}`)}else if(w==="u"){let x=Uo(o.keep,r);x.ok&&x.keep?(s(_=>({..._,keep:x.keep})),R("Upgraded!")):R(`!${x.reason}`)}else if(w==="x"){let x=Go(o.keep,r);x.ok&&x.keep?(s(_=>({..._,keep:x.keep})),R("Demolished")):R(`!${x.reason}`)}else if(w==="r"){let x=An(o);s(x),l("result"),x.alive?R(`Wave ${x.wave} cleared! +${x.score-o.score} pts`):R(`Breached on wave ${x.wave}! Final score: ${x.score}`)}});let i=o.keep.resources,y=o.keep.grid;return Ge(ro,{flexDirection:"column",paddingX:1,children:[Ge(ro,{flexDirection:"row",gap:1,children:[Be(pe,{bold:!0,color:"magenta",children:"\u2605 Daily Challenge"}),Be(pe,{dimColor:!0,children:"\u2502"}),Ge(pe,{children:["Wave: ",Be(pe,{bold:!0,children:o.wave})]}),Be(pe,{dimColor:!0,children:"\u2502"}),Ge(pe,{children:["Score: ",Be(pe,{bold:!0,color:"yellow",children:o.score})]}),Be(pe,{dimColor:!0,children:"\u2502"}),Ge(pe,{color:"yellow",children:[W.gold,i.gold]}),Ge(pe,{color:"green",children:[W.wood,i.wood]}),Ge(pe,{color:"white",children:[W.stone,i.stone]})]}),Be(pe,{color:T.startsWith("!")?"red":"yellow",children:T}),Ge(ro,{marginTop:1,children:[Be(ro,{flexDirection:"column",children:Array.from({length:I},(w,h)=>Be(pe,{children:Array.from({length:I},(x,_)=>{let j=r.x===_&&r.y===h,K=y.structures.find(U=>U.pos.x===_&&U.pos.y===h),N=K?ye[K.kind]:yt,$=K&&K.level>1?String(K.level):" ";if(j)return Ge(pe,{backgroundColor:"white",color:"black",bold:!0,children:[N,$]},_);if(K){let U=K.kind==="treasury"?"yellow":K.kind==="wall"?"white":K.kind==="trap"?"magenta":K.kind==="archerTower"?"redBright":K.kind==="ward"?"cyan":"green";return Ge(pe,{color:U,bold:K.level>=2,children:[N,$]},_)}return Ge(pe,{dimColor:!0,children:[N," "]},_)})},h))}),Ge(ro,{flexDirection:"column",marginLeft:2,width:24,children:[Ge(pe,{children:["Sel: ",Be(pe,{bold:!0,children:Je[u]})]}),Be(pe,{dimColor:!0,children:"e place u upgrade x demo"}),Be(pe,{dimColor:!0,children:"[ ] cycle 1-6 select"}),Be(pe,{dimColor:!0,children:"r start wave"}),Be(pe,{dimColor:!0,children:"Esc back to menu"}),g==="result"&&o.alive&&Be(pe,{color:"green",bold:!0,children:"Press Enter to continue"}),g==="result"&&!o.alive&&Ge(ro,{flexDirection:"column",marginTop:1,children:[Be(pe,{color:"red",bold:!0,children:"GAME OVER"}),Ge(pe,{children:["Waves: ",o.wave," \u2502 Score: ",o.score]}),Ge(pe,{dimColor:!0,children:["Proof: #",Mn(o)]}),Be(pe,{dimColor:!0,children:"Press Enter to exit"})]})]})]})]})}import{useState as Pi}from"react";import{Box as qr,Text as Vt,useInput as Di}from"ink";import{jsx as so,jsxs as or}from"react/jsx-runtime";function In({options:e,anomalyNames:t,onClaim:o,onDismiss:s}){let[r,n]=Pi(0);return Di((c,d)=>{d.leftArrow||c==="h"?n(g=>Math.max(0,g-1)):d.rightArrow||c==="l"?n(g=>Math.min(e.length-1,g+1)):d.return||c===" "?o(e[r].id):(d.escape||c==="q")&&s()}),or(qr,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:2,paddingY:1,children:[so(Vt,{bold:!0,color:"yellow",children:"\u2605 Victory! Choose your reward \u2605"}),t&&t.length>0&&or(Vt,{dimColor:!0,children:["Anomalies faced: ",t.join(", ")]}),so(Vt,{children:" "}),so(qr,{flexDirection:"row",gap:2,children:e.map((c,d)=>or(qr,{flexDirection:"column",borderStyle:d===r?"bold":"single",borderColor:d===r?"yellow":"gray",paddingX:2,paddingY:1,width:26,children:[or(Vt,{bold:!0,color:d===r?"yellow":"white",children:[c.icon," ",c.name]}),so(Vt,{dimColor:d!==r,children:c.description}),d===r&&so(Vt,{color:"green",children:"\u25B2 [Enter] Claim"})]},c.id))}),so(Vt,{dimColor:!0,children:"\u2190 \u2192 to browse | Enter to claim | Esc to skip"})]})}import{useState as Li}from"react";import{Box as rr,Text as de,useInput as $i}from"ink";import{Fragment as Bi,jsx as _e,jsxs as Ne}from"react/jsx-runtime";function Pn({gameSave:e,eligible:t,reason:o,onPrestige:s,onBack:r}){let[n,c]=Li(!1),d=e.prestige?.ascensionLevel??0,g=d+1,l=e.prestige,T=Xt.find(R=>R.ascensionLevel===g);return $i((R,u)=>{u.escape||R==="q"?n?c(!1):r():R==="y"&&n&&t?s():u.return&&t&&!n&&c(!0)}),Ne(rr,{flexDirection:"column",padding:2,children:[_e(de,{bold:!0,color:"magenta",children:"\u2605 PRESTIGE / ASCENSION \u2605"}),_e(de,{children:" "}),Ne(de,{bold:!0,children:["Current Ascension: ",Ne(de,{color:"yellow",children:["Level ",d]})]}),l&&Ne(Bi,{children:[Ne(de,{dimColor:!0,children:["Lifetime raids won: ",l.lifetimeRaidsWon+e.progression.totalRaidsWon]}),Ne(de,{dimColor:!0,children:["Lifetime best streak: ",Math.max(l.lifetimeBestStreak,e.progression.bestWinStreak)]})]}),_e(de,{children:" "}),_e(de,{bold:!0,children:"Permanent Unlocks:"}),Xt.map(R=>{let u=R.ascensionLevel<=d,i=R.ascensionLevel===g;return Ne(de,{color:u?"green":i?"yellow":"gray",children:[u?" \u2713 ":i?" \u2192 ":" \xB7 ",Ne(de,{bold:u||i,children:["Lv.",R.ascensionLevel," ",R.name]}),Ne(de,{dimColor:!u&&!i,children:[" \u2014 ",R.description]})]},R.id)}),_e(de,{children:" "}),!t&&Ne(de,{color:"red",children:["Cannot prestige: ",o]}),t&&!n&&Ne(rr,{flexDirection:"column",children:[Ne(de,{color:"yellow",bold:!0,children:["Ready to ascend to Level ",g,"!"]}),T&&Ne(de,{color:"green",children:["New unlock: ",T.name," \u2014 ",T.description]}),_e(de,{children:" "}),_e(de,{bold:!0,color:"red",children:"WARNING: This resets your keep, structures, and raid history."}),_e(de,{bold:!0,color:"red",children:"Achievements and prestige unlocks are permanent."}),_e(de,{children:" "}),_e(de,{dimColor:!0,children:"Press Enter to begin ascension, Esc to go back"})]}),t&&n&&Ne(rr,{flexDirection:"column",children:[_e(de,{bold:!0,color:"red",children:"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"}),_e(de,{bold:!0,color:"red",children:"\u2551 ARE YOU SURE? This cannot be undone \u2551"}),_e(de,{bold:!0,color:"red",children:"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"}),_e(de,{children:" "}),Ne(de,{children:["Press ",_e(de,{bold:!0,color:"yellow",children:"Y"})," to confirm ascension"]}),Ne(de,{children:["Press ",_e(de,{bold:!0,children:"Esc"})," to cancel"]})]}),!t&&!n&&_e(rr,{marginTop:1,children:Ne(de,{dimColor:!0,children:["Requirement: ",Zt," raid wins"," ","(",e.progression.totalRaidsWon,"/",Zt,")"]})}),_e(de,{children:" "}),_e(de,{dimColor:!0,children:"Press Esc to return to menu"})]})}import{useState as st,useEffect as no,useCallback as ce,useRef as St}from"react";import{useEffect as Ui,useRef as Ln,useCallback as Gi}from"react";import{existsSync as $n,statSync as Bn,openSync as Wi,readSync as Ki,closeSync as Fi,watch as Hi}from"fs";import{existsSync as uu,mkdirSync as pu,readFileSync as fu,writeFileSync as mu,unlinkSync as gu}from"fs";import{join as Dn}from"path";import{homedir as Ni}from"os";var Oi=Dn(Ni(),".config","codekeep"),jt=Dn(Oi,"events.jsonl");var Yi=2e3;function Nn(e){let t=Ln(0),o=Ln(e);o.current=e;let s=Gi(()=>{if(!$n(jt))return;let r;try{r=Bn(jt).size}catch{return}if(r<t.current&&(t.current=0),r<=t.current)return;let n,c;try{let g=Buffer.alloc(r-t.current);c=Wi(jt,"r"),Ki(c,g,0,g.length,t.current),n=g.toString("utf-8")}catch{return}finally{c!==void 0&&Fi(c)}t.current=r;let d=n.split(`
|
|
26
|
-
`).filter(Boolean);for(let g of d)try{let l=JSON.parse(g);o.current(l)}catch{}},[]);Ui(()=>{if($n(jt))try{t.current=Bn(jt).size}catch{t.current=0}let r=null;try{r=Hi(jt,()=>s())}catch{}let n=setInterval(s,Yi);return()=>{clearInterval(n),r?.close()}},[s])}function sr(e){let{gameSave:t,replay:o,raidType:s,seed:r,difficulty:n,probeCount:c,attackerId:d,defenderKeepId:g,defenderGrid:l}=e,T=o.events[o.events.length-1];if(T?.type!=="raid_end")return null;let R=s==="defend",u=R?T.outcome==="defense_win":T.outcome!=="defense_win",i=o.events.filter(P=>P.type==="treasury_breach").reduce((P,Y)=>({gold:P.gold+Y.lootTaken.gold,wood:P.wood+Y.lootTaken.wood,stone:P.stone+Y.lootTaken.stone}),{gold:0,wood:0,stone:0}),y=o.events.filter(P=>P.type==="raider_destroyed").length,w=o.events.filter(P=>P.type==="wall_damaged"&&P.destroyed).length,h=o.events.filter(P=>P.type==="arrow_hit"&&P.hpRemaining<=0).length,x=(R?t.keep.grid:l??t.keep.grid).structures.filter(P=>P.kind==="archerTower").length,_=c>0?y/c:0,j,K,N;R?(N=i,K=u?{gold:10+n*3,wood:5+n*2,stone:5+n*2}:{gold:Math.floor(_*n*2),wood:Math.floor(_*n),stone:Math.floor(_*n)},j=K):(N={gold:0,wood:0,stone:0},K=u?i:{gold:0,wood:0,stone:0},j={gold:0,wood:0,stone:0});let $=R?{gold:Math.max(0,t.keep.resources.gold-N.gold),wood:Math.max(0,t.keep.resources.wood-N.wood),stone:Math.max(0,t.keep.resources.stone-N.stone)}:t.keep.resources,U=nt(He($,K)),k=u?t.progression.currentWinStreak+1:0,b={id:r,seed:r,rulesVersion:1,attackerId:d,defenderKeepId:g,startedAtUnixMs:Date.now(),resolvedAtUnixMs:Date.now(),outcome:T.outcome,lootLost:N,lootGained:K,replay:o,...l?{defenderGrid:l}:{}},L={...t,keep:{...t.keep,resources:U},raidHistory:[...t.raidHistory.slice(-19),b],progression:{...t.progression,totalRaidsWon:t.progression.totalRaidsWon+(u?1:0),totalRaidsLost:t.progression.totalRaidsLost+(u?0:1),currentWinStreak:k,bestWinStreak:Math.max(t.progression.bestWinStreak,k),totalRaidersKilledByArcher:t.progression.totalRaidersKilledByArcher+h}},m={won:u,raidType:s,outcome:T.outcome,lootGained:K,lootLost:N,raidersKilled:y,raidersTotal:o.events.filter(P=>P.type==="raider_spawn").length,wallsDestroyed:w,archersActive:x,difficulty:n};return{updatedSave:L,raidRecord:b,summary:m,defenseBonus:j,lootLost:N,lootGained:K,won:u,archerKills:h}}var On=5e3,zr=6e4;function Vi(e,t){if(e.keep.grid.structures.length<3)return{save:e,results:[]};let o=Math.min(Math.floor(t/io),cs);if(o===0)return{save:e,results:[]};let s=[],r=e;for(let n=0;n<o;n++){let c=r.progression.totalRaidsWon+r.progression.totalRaidsLost,d=Dt(c),g=`bg-${r.lastPlayedAtUnixMs}-${n}`,l=3+d,T=at(r.lastPlayedAtUnixMs+n),R=Lt(l,d,T),u=ut({probeCount:l,keepGrid:r.keep.grid,seed:g,probeTypes:R}),i=u.events[u.events.length-1];if(i?.type!=="raid_end")continue;let y=i.outcome==="defense_win",w=u.events.filter(k=>k.type==="treasury_breach").reduce((k,b)=>({gold:k.gold+b.lootTaken.gold,wood:k.wood+b.lootTaken.wood,stone:k.stone+b.lootTaken.stone}),{gold:0,wood:0,stone:0}),h=u.events.filter(k=>k.type==="arrow_hit"&&k.hpRemaining<=0).length,x=u.events.filter(k=>k.type==="raider_destroyed").length,_=l>0?x/l:0,j=y?{gold:10+d*3,wood:5+d*2,stone:5+d*2}:{gold:Math.floor(_*d*2),wood:Math.floor(_*d),stone:Math.floor(_*d)},K={gold:Math.max(0,r.keep.resources.gold-w.gold),wood:Math.max(0,r.keep.resources.wood-w.wood),stone:Math.max(0,r.keep.resources.stone-w.stone)},N=nt(He(K,j)),$=y?r.progression.currentWinStreak+1:0,U={id:g,seed:g,rulesVersion:1,attackerId:"npc-bg",defenderKeepId:r.keep.id,startedAtUnixMs:r.lastPlayedAtUnixMs+n*io,resolvedAtUnixMs:r.lastPlayedAtUnixMs+(n+1)*io,outcome:i.outcome,lootLost:w,lootGained:j,replay:u};r={...r,keep:{...r.keep,resources:N},raidHistory:[...r.raidHistory.slice(-19),U],progression:{...r.progression,totalRaidsWon:r.progression.totalRaidsWon+(y?1:0),totalRaidsLost:r.progression.totalRaidsLost+(y?0:1),currentWinStreak:$,bestWinStreak:Math.max(r.progression.bestWinStreak,$),totalRaidersKilledByArcher:r.progression.totalRaidersKilledByArcher+h}},s.push({won:y,lootLost:w,defenseBonus:j,difficulty:d})}return{save:r,results:s}}function Un(e,t){let[o,s]=st(null),[r,n]=st({x:8,y:8}),[c,d]=st(0),[g,l]=st(""),[T,R]=st(null),[u,i]=st(null),[y,w]=st(null),h=St({time:Date.now(),raidsWon:0,raidsLost:0,structures:0,resources:{gold:0,wood:0,stone:0},achievements:0}),[x,_]=st(null),[j,K]=st(null),[N,$]=st([]),U=St(null),k=St(0),b=St(0),L=St(null),m=St(null),P=St(null),Y=St(null),se=St(null),[v,oe]=st(1),fe=St([]),[be,me]=st([]),[Te,Ve]=st(null),C=ce(()=>{let f=Date.now();fe.current.push(f);let S=f-1800*1e3;fe.current=fe.current.filter(V=>V>S);let A=fe.current.length;A>=6?oe(2):A>=3?oe(1.5):A>=1?oe(1.2):oe(1)},[]);no(()=>{let f=setInterval(()=>{let A=Date.now()-1800*1e3;fe.current=fe.current.filter(X=>X>A);let V=fe.current.length;V>=6?oe(2):V>=3?oe(1.5):V>=1?oe(1.2):oe(1)},6e4);return()=>clearInterval(f)},[]);let B=qe[c];Nn(f=>{C(),s(S=>{if(!S)return S;let A=_r(S.keep,f),V={...S,keep:A,lastPlayedAtUnixMs:Date.now()};return t||Pt(V),V})}),no(()=>{let f=Jt();f||(f=Ks(process.env.USER||process.env.USERNAME||"Keeper")),f=Xs(f),f.lastPlayedAtUnixMs||(f={...f,lastPlayedAtUnixMs:f.savedAtUnixMs||Date.now()});let S=Date.now()-f.lastPlayedAtUnixMs,A={gold:0,wood:0,stone:0},V=[];if(S>zr&&f.keep.grid.structures.length>0&&(A=Ir(f.keep.grid,S),(A.gold>0||A.wood>0||A.stone>0)&&(f={...f,keep:{...f.keep,resources:nt(He(f.keep.resources,A))}})),S>io){let Ae=Vi(f,S);f=Ae.save,V=Ae.results}let X=Or(f);if(X.length>0){f={...f,progression:{...f.progression,achievements:[...f.progression.achievements,...X]}};for(let Ae of X){let Ze=Nr(Ae);Ze&&(f={...f,keep:{...f.keep,resources:He(f.keep.resources,Ze)}})}}(A.gold>0||A.wood>0||A.stone>0||V.length>0||X.length>0)&&K({resources:A,raids:V,newAchievements:X}),f={...f,lastPlayedAtUnixMs:Date.now()},e&&(f={...f,tutorialCompleted:!1}),h.current={time:Date.now(),raidsWon:f.progression.totalRaidsWon,raidsLost:f.progression.totalRaidsLost,structures:f.keep.grid.structures.length,resources:{...f.keep.resources},achievements:f.progression.achievements.length},s(f),t||Pt(f)},[]),no(()=>(L.current=setInterval(()=>{s(f=>{if(!f||f.keep.grid.structures.length===0)return f;let S=Ir(f.keep.grid,zr);if(Ko(f,"passive_income_boost")&&(S={gold:Math.floor(S.gold*1.5),wood:Math.floor(S.wood*1.5),stone:Math.floor(S.stone*1.5)}),S.gold===0&&S.wood===0&&S.stone===0)return f;let A={...f,keep:{...f.keep,resources:nt(He(f.keep.resources,S))},lastPlayedAtUnixMs:Date.now()};return t||Pt(A),A})},zr),()=>{L.current&&clearInterval(L.current)}),[t]),no(()=>(m.current=setInterval(()=>{$(f=>{if(!o)return f;let S=at(Date.now()),A=Os(f,Date.now());return Ns(A,o.keep.grid,Date.now(),S)})},ps),()=>{m.current&&clearInterval(m.current)}),[o]),no(()=>{let f=Y.current;if(!f||!o)return;Y.current=null;let S=Dr(N,f,o.keep.grid);if(!S)return;$(S.updatedFragments);let A=S.yield,V={...o,keep:{...o.keep,resources:nt(He(o.keep.resources,A))}};E(V);let X=[];A.gold>0&&X.push(`+${A.gold}${W.gold}`),A.wood>0&&X.push(`+${A.wood}${W.wood}`),A.stone>0&&X.push(`+${A.stone}${W.stone}`);let ke=S.collected[0]?.type.replace("_"," ")??"fragment",Ae=S.collected.length>1?` (${S.collected.length}x)`:"";F(`${X.join(" ")} ${ke}${Ae}`)},[r]),no(()=>()=>{U.current&&clearTimeout(U.current)},[]);let E=ce(f=>{let S=Or(f),A={...f,lastPlayedAtUnixMs:Date.now()};if(S.length>0){A={...A,progression:{...A.progression,achievements:[...A.progression.achievements,...S]}};for(let V of S){let X=Nr(V);X&&(A={...A,keep:{...A.keep,resources:He(A.keep.resources,X)}})}}if(s(A),t||Pt(A),S.length>0){let V=S.map(X=>lo.find(ke=>ke.id===X)?.name||X);F(`\u{1F3C6} ${V.join(", ")}!`)}},[t]),F=ce(f=>{U.current&&clearTimeout(U.current),l(f),U.current=setTimeout(()=>{l(""),U.current=null},3e3)},[]),D=ce((f,S)=>{n(A=>{let V={x:Math.max(0,Math.min(I-1,A.x+f)),y:Math.max(0,Math.min(I-1,A.y+S))};return Y.current=V,V})},[]),q=ce(f=>{d(S=>{let A=S+f;return A<0?qe.length-1:A>=qe.length?0:A})},[]),H=ce(f=>{f>=0&&f<qe.length&&d(f)},[]),Z=ce(f=>{se.current&&clearTimeout(se.current.timer);let S=setTimeout(()=>{se.current=null},1e4);se.current={save:f,timer:S}},[]),Ee=ce(()=>{if(!o)return;let f=Ko(o,"structures_lv2")?2:void 0,S=Oo(o.keep,r,B,f?{startingLevel:f}:void 0);if(S.ok&&S.keep){Z(o);let A={...o,keep:S.keep,progression:{...o.progression,totalStructuresPlaced:o.progression.totalStructuresPlaced+1}};E(A),F(`Placed ${B} [z undo]`)}else F(`!${S.reason}`)},[o,r,B,E,F,Z]),Tt=ce(()=>{if(!o)return;let f=Uo(o.keep,r);f.ok&&f.keep?(Z(o),E({...o,keep:f.keep}),F("Upgraded! [z undo]")):F(`!${f.reason}`)},[o,r,E,F,Z]),ar=ce(()=>{if(!o)return;let f=Go(o.keep,r);f.ok&&f.keep?(Z(o),E({...o,keep:f.keep}),F("Demolished (50% refund) [z undo]")):F(`!${f.reason}`)},[o,r,E,F]),ge=ce((f,S)=>{let A=Js(f,S),V=o?.activeBuffs??[],X=Qs(A,V);return me(A),{anomalies:A,modifiers:X}},[o]),_t=ce(f=>{let S=f.activeBuffs??[];return S.length===0?f:{...f,activeBuffs:en(S)}},[]),yo=ce(()=>{if(!o)return;let f=o.progression.totalRaidsWon+o.progression.totalRaidsLost,S=Dt(f),A=`attack-${Date.now()}-${Math.random()}`,V=Fo(A,S),X=3+S,ke=at(Date.now()),{anomalies:Ae,modifiers:Ze}=ge(S,ke),lt=Ze.bruteChanceMult??1,Oe=Lt(X,S,ke,lt),dt=ut({probeCount:X,keepGrid:V.grid,seed:A,probeTypes:Oe,modifiers:Ze});R(dt),i(V.grid),w("attack");let Ke=sr({gameSave:o,replay:dt,raidType:"attack",seed:A,difficulty:S,probeCount:X,attackerId:o.player.id,defenderKeepId:V.id,defenderGrid:V.grid});if(Ke){_(Ke.summary);let Nt=_t(Ke.updatedSave);if(Ke.won){let vo=at(Date.now()+1);Ve(Yo(S,vo))}E(Nt)}},[o,E,ge,_t]),ir=ce(()=>{if(!o)return;let f=o.progression.totalRaidsWon+o.progression.totalRaidsLost,S=Dt(f),A=`defend-${Date.now()}-${Math.random()}`,V=3+S,X=at(Date.now()),{anomalies:ke,modifiers:Ae}=ge(S,X),Ze=Ae.bruteChanceMult??1,lt=Lt(V,S,X,Ze),Oe=ut({probeCount:V,keepGrid:o.keep.grid,seed:A,probeTypes:lt,modifiers:Ae});R(Oe),i(o.keep.grid),w("defend");let dt=sr({gameSave:o,replay:Oe,raidType:"defend",seed:A,difficulty:S,probeCount:V,attackerId:"npc",defenderKeepId:o.keep.id});if(dt){_(dt.summary);let Ke=_t(dt.updatedSave);if(dt.won){let Nt=at(Date.now()+1);Ve(Yo(S,Nt))}E(Ke)}},[o,E,ge,_t]),wo=ce(()=>{if(!o)return;let f=o.progression.totalRaidsWon+o.progression.totalRaidsLost,S=Dt(f),A=`quick-${Date.now()}-${Math.random()}`,V=3+S,X=at(Date.now()),{modifiers:ke}=ge(S,X),Ae=ke.bruteChanceMult??1,Ze=Lt(V,S,X,Ae),lt=ut({probeCount:V,keepGrid:o.keep.grid,seed:A,probeTypes:Ze,modifiers:ke}),Oe=sr({gameSave:o,replay:lt,raidType:"defend",seed:A,difficulty:S,probeCount:V,attackerId:"npc",defenderKeepId:o.keep.id});if(Oe){let dt=_t(Oe.updatedSave);if(E(dt),P.current={replay:lt,grid:o.keep.grid,summary:Oe.summary},Oe.won){let Ke=Oe.lootGained,Nt=at(Date.now()+1);Ve(Yo(S,Nt)),F(`Defense WIN! +${Ke.gold}${W.gold} +${Ke.wood}${W.wood} +${Ke.stone}${W.stone} [v] view`)}else{let Ke=Oe.lootLost.gold+Oe.lootLost.wood+Oe.lootLost.stone;F(`Defense BREACH! Lost ${Ke} res (+${Oe.lootGained.gold}${W.gold} salvage) [v] view`)}}},[o,E,F,ge,_t]),Ro=ce(()=>{let f=P.current;return f?(R(f.replay),i(f.grid),w("defend"),_(f.summary),P.current=null,!0):!1},[]),So=ce(()=>{if(!o)return;let f=Dr(N,r,o.keep.grid);if(!f)return;$(f.updatedFragments);let S=f.yield,A={...o,keep:{...o.keep,resources:nt(He(o.keep.resources,S))}};E(A);let V=[];S.gold>0&&V.push(`+${S.gold}${W.gold}`),S.wood>0&&V.push(`+${S.wood}${W.wood}`),S.stone>0&&V.push(`+${S.stone}${W.stone}`);let X=f.collected[0]?.type.replace("_"," ")??"fragment",ke=f.collected.length>1?` (${f.collected.length}x)`:"";F(`${V.join(" ")} ${X}${ke}`)},[o,N,r,E,F]),lr=ce(f=>{if(!o)return!1;let S=f.attackerId!==o.player.id;return R(f.replay),i(S?o.keep.grid:f.defenderGrid??o.keep.grid),w(S?"defend":"attack"),_(null),!0},[o]),dr=ce(()=>{R(null),i(null),w(null),_(null)},[]),We=ce(()=>{o&&E({...o,tutorialCompleted:!0})},[o,E]),cr=ce(()=>{if(!o)return;let f=Date.now(),S=f-k.current;if(S<On){let lt=Math.ceil((On-S)/1e3);F(`Cooldown: ${lt}s remaining`);return}k.current=f,b.current++,C();let A=Ds(),V=Ko(o,"extra_faucet")?3:0,X=tn({...A.grants},Math.max(0,b.current-V)),ke=_r(o.keep,{...A,grants:X});E({...o,keep:ke});let Ae=zt+V,Ze=b.current>Ae?" (diminished)":"";F(`+${X.gold}${W.gold} +${X.wood}${W.wood} +${X.stone}${W.stone} (${ds[A.type]??A.type})${Ze}`)},[o,E,F]),qt=ce((f,S)=>{f>=0&&f<I&&S>=0&&S<I&&n({x:f,y:S})},[]),bo=ce(f=>{if(!o||o.keep.grid.structures.length===0)return;let S=o.keep.grid.structures,A=`${r.x},${r.y}`,V=S.findIndex(Ae=>`${Ae.pos.x},${Ae.pos.y}`===A),X=V===-1?0:V+f;X<0&&(X=S.length-1),X>=S.length&&(X=0);let ke=S[X];n({x:ke.pos.x,y:ke.pos.y})},[o,r]),ur=ce(()=>{if(!se.current){F("!Nothing to undo");return}let f=se.current.save;clearTimeout(se.current.timer),se.current=null,E(f),F("Undone!")},[E,F]),pr=ce((f,S,A)=>{o&&E({...o,progression:{...o.progression,dailyChallenges:{...o.progression.dailyChallenges,[f]:{wavesCleared:S,score:A}}}})},[o,E]),fr=ce(()=>{K(null)},[]),mr=ce(f=>{t||Pt(f),s(f),b.current=0},[t]),gr=ce(f=>{if(!o||!Te)return;let S=Te.find(V=>V.id===f);if(!S)return;let A={...o};if(S.type==="resources"&&S.resources)A={...A,keep:{...A.keep,resources:nt(He(A.keep.resources,S.resources))}},F(`Claimed ${S.name}!`);else if(S.type==="buff"&&S.buff){let V=A.activeBuffs??[];A={...A,activeBuffs:[...V,{...S.buff}]},F(`${S.buff.name} active for ${S.buff.raidsRemaining} raids!`)}Ve(null),E(A)},[o,Te,E,F]),hr=ce(()=>{Ve(null)},[]),xr=o?.keep.grid.structures.find(f=>f.pos.x===r.x&&f.pos.y===r.y)??null,ko=o?Pr(o.keep.grid):[],Tr=new Set(ko.flatMap(f=>f.affectedStructureIds)),yr=o?(()=>{let f=o.progression.totalRaidsWon+o.progression.totalRaidsLost,S=o.keep.grid.structures.filter(V=>V.kind==="watchtower").length,A=Zs(o.keep.id,f,S);return S>=2?A.detailed:A.vague})():void 0;return{gameSave:o,cursor:r,selectedStructure:B,structureAtCursor:xr,message:g,fragments:N,raidReplay:T,raidGrid:u,raidType:y,raidSummary:x,offlineReport:j,moveCursor:D,cycleStructure:q,selectStructure:H,placeAtCursor:Ee,upgradeAtCursor:Tt,demolishAtCursor:ar,undoLastAction:ur,collectAtCursor:So,startAttackRaid:yo,startDefendRaid:ir,quickDefend:wo,watchLastRaid:Ro,watchRaidRecord:lr,clearRaid:dr,completeTutorial:We,grantSimResources:cr,jumpToCoord:qt,jumpToNextStructure:bo,clearOfflineReport:fr,saveDailyChallengeScore:pr,showMessage:F,siegeForecast:yr,flowMultiplier:v,activeSynergies:ko,synergyStructureIds:Tr,raidAnomalies:be,pendingRewards:Te,claimReward:gr,dismissRewards:hr,applyExternalSave:mr,getSessionStats:()=>{if(!o)return null;let f=h.current;return{startedAt:f.time,raidsWon:o.progression.totalRaidsWon-f.raidsWon,raidsLost:o.progression.totalRaidsLost-f.raidsLost,structuresBuilt:o.keep.grid.structures.length-f.structures,resourcesEarned:{gold:Math.max(0,o.keep.resources.gold-f.resources.gold),wood:Math.max(0,o.keep.resources.wood-f.resources.wood),stone:Math.max(0,o.keep.resources.stone-f.resources.stone)},achievementsUnlocked:o.progression.achievements.length-f.achievements}}}}function Gn(e){let t=e.keep.grid,o=e.progression,s=e.keep.resources,r="\u250C"+"\u2500".repeat(I+2)+"\u2510",n="\u2514"+"\u2500".repeat(I+2)+"\u2518",c=[];c.push(r);for(let i=0;i<I;i++){let y="\u2502 ";for(let w=0;w<I;w++){let h=t.structures.find(x=>x.pos.x===w&&x.pos.y===i);if(h){let x=ye[h.kind]??"?";y+=h.level===3?x.toUpperCase():x}else y+=yt}y+=" \u2502",c.push(y)}c.push(n);let d=e.player.displayName||"Anonymous",g=Math.max(1,Math.floor((Date.now()-e.keep.createdAtUnixMs)/864e5)),l=o.totalRaidsWon+o.totalRaidsLost,T=l>0?Math.round(o.totalRaidsWon/l*100):0;c.push(` ${d}'s Keep \u2014 Day ${g}`),c.push(` ${s.gold}g ${s.wood}w ${s.stone}s \u2502 ${t.structures.length} structures`),c.push(` ${o.totalRaidsWon}W-${o.totalRaidsLost}L (${T}%) \u2502 Best streak: ${o.bestWinStreak}`);let R=`${e.keep.id}:${l}:${t.structures.length}:${s.gold}`,u=Math.abs(It(R)).toString(36).slice(0,6);return c.push(` #${u}`),c.join(`
|
|
27
|
-
`)}import{exec as Zi}from"child_process";var nr=class{mode="online";baseUrl;auth=null;constructor(t){this.baseUrl=t.replace(/\/$/,"")}async request(t,o={}){let s={"Content-Type":"application/json",...o.headers??{}};this.auth&&(s.Authorization=`Bearer ${this.auth.token}`);let r=await fetch(`${this.baseUrl}${t}`,{...o,headers:s});if(!r.ok){let n=await r.json().catch(()=>({error:r.statusText}));throw new Error(n.error??`HTTP ${r.status}`)}return r.json()}isAuthenticated(){return this.auth!==null}async register(t){let o=await this.request("/auth/register",{method:"POST",body:JSON.stringify({displayName:t})});return this.auth={token:o.token,apiKey:o.apiKey,playerId:o.playerId},o}async login(t){let o=await this.request("/auth/login",{method:"POST",body:JSON.stringify({apiKey:t})});return this.auth={token:o.token,apiKey:t,playerId:o.playerId},o}async load(){if(!this.auth)return null;try{let t=await this.request("/me");return t.keep?{schemaVersion:1,savedAtUnixMs:Date.now(),player:{id:t.player.id,displayName:t.player.displayName,settings:{asciiMode:!1}},keep:{id:t.keep.id,name:t.keep.name,ownerPlayerId:t.player.id,grid:t.keep.grid,resources:t.keep.resources,createdAtUnixMs:t.player.createdAt,updatedAtUnixMs:Date.now()},raidHistory:[],tutorialCompleted:!0,lastPlayedAtUnixMs:Date.now(),progression:{totalBuildsToday:0,totalCommitsToday:0,lastDailyResetDay:Math.floor(Date.now()/864e5),totalRaidsWon:t.progression?.totalRaidsWon??0,totalRaidsLost:t.progression?.totalRaidsLost??0,totalStructuresPlaced:t.progression?.totalStructuresPlaced??0,currentWinStreak:t.progression?.currentWinStreak??0,bestWinStreak:t.progression?.bestWinStreak??0,achievements:t.progression?.achievements??[],totalRaidersKilledByArcher:0}}:null}catch{return null}}async save(t){this.auth&&await this.request("/keep/save",{method:"POST",body:JSON.stringify({grid:t.keep.grid,resources:t.keep.resources})})}async createNew(t){let o=await this.register(t),s=await this.load();return s||{schemaVersion:1,savedAtUnixMs:Date.now(),player:{id:o.playerId,displayName:t,settings:{asciiMode:!1}},keep:{id:`keep-${o.playerId}`,name:`${t}'s Keep`,ownerPlayerId:o.playerId,grid:{width:16,height:16,structures:[]},resources:{...kt},createdAtUnixMs:Date.now(),updatedAtUnixMs:Date.now()},raidHistory:[],tutorialCompleted:!1,lastPlayedAtUnixMs:Date.now(),progression:{totalBuildsToday:0,totalCommitsToday:0,lastDailyResetDay:Math.floor(Date.now()/864e5),totalRaidsWon:0,totalRaidsLost:0,totalStructuresPlaced:0,currentWinStreak:0,bestWinStreak:0,achievements:[],totalRaidersKilledByArcher:0}}}async deleteAll(){return!1}async findMatch(){return(await this.request("/matchmaking/find",{method:"POST"})).targets}async launchPvpRaid(t,o,s){return this.request("/raids/launch",{method:"POST",body:JSON.stringify({defenderPlayerId:t,probeTypes:o,spawnSpecs:s})})}async getIncomingRaids(t){return(await this.request(`/raids/incoming?since=${t}`)).raids}async getPvpProfile(){try{let t=await this.request("/me");return{trophies:t.player.trophies,league:t.player.league,shieldExpiresAt:t.player.shieldExpiresAt,seasonId:"S1",seasonPeakTrophies:t.player.trophies}}catch{return null}}async getLeaderboard(t=50){return(await this.request(`/leaderboard?limit=${t}`)).players}async getWarCamp(){try{return await this.request("/warcamp")}catch{return null}}async trainRaider(t,o){let s=await this.request("/warcamp/train",{method:"POST",body:JSON.stringify({slotId:t,raiderType:o})});return{slots:s.slots,maxSlots:s.maxSlots}}async getBounties(){try{return(await this.request("/bounties")).bounties}catch{return[]}}async claimBounty(t){return(await this.request(`/bounties/${t}/claim`,{method:"POST"})).reward}async registerForMatchmaking(){await this.request("/matchmaking/register",{method:"POST"})}async sync(){this.auth&&await this.registerForMatchmaking()}};import{jsx as te,jsxs as xe}from"react/jsx-runtime";var Kn=60,Fn=18;function Ji(){let{stdout:e}=Xi(),[t,o]=Ce({columns:e?.columns??process.stdout.columns??80,rows:e?.rows??process.stdout.rows??24});return ji(()=>{let s=e??process.stdout,r=()=>{o({columns:s.columns,rows:s.rows})};return s.on("resize",r),()=>{s.off("resize",r)}},[e]),t}function Qi({asciiMode:e,compact:t,forceTutorial:o,autoResume:s,serverUrl:r,dryRun:n}){let{exit:c}=qi(),{columns:d,rows:g}=Ji(),[l,T]=Ce(r?"auth":s?"keep":"menu"),[R,u]=Ce(e),[i,y]=Ce(!1),[w,h]=Ce(null),[x,_]=Ce(null),[j,K]=Ce(!1),[N,$]=Ce(""),[U,k]=Ce("keep"),b=Wn(1),L=Wn(r?new nr(r):null),[m,P]=Ce(!1),[Y,se]=Ce(null),[v,oe]=Ce([]),[fe,be]=Ce(!1),[me,Te]=Ce([]),[Ve,C]=Ce(!1),[B,E]=Ce({slots:[],maxSlots:3}),[F,D]=Ce(null),[q,H]=Ce(null),[Z,Ee]=Ce(!1),Tt=t||d<80||g<24,ar=d<Kn||g<Fn,{gameSave:ge,cursor:_t,selectedStructure:yo,message:ir,fragments:wo,raidReplay:Ro,raidGrid:So,raidType:lr,raidSummary:dr,offlineReport:We,structureAtCursor:cr,moveCursor:qt,cycleStructure:bo,selectStructure:ur,placeAtCursor:pr,upgradeAtCursor:fr,demolishAtCursor:mr,undoLastAction:gr,collectAtCursor:hr,startAttackRaid:xr,startDefendRaid:ko,quickDefend:Tr,watchLastRaid:yr,watchRaidRecord:f,clearRaid:S,completeTutorial:A,grantSimResources:V,jumpToCoord:X,jumpToNextStructure:ke,clearOfflineReport:Ae,saveDailyChallengeScore:Ze,showMessage:lt,siegeForecast:Oe,flowMultiplier:dt,activeSynergies:Ke,synergyStructureIds:Nt,raidAnomalies:vo,pendingRewards:wr,claimReward:Vn,dismissRewards:jn,applyExternalSave:qn,getSessionStats:Zr}=Un(o,n),[ct,zn]=Ce(null),Jr=ao(()=>{let M=Zr();M&&(M.raidsWon+M.raidsLost>0||M.structuresBuilt>0)?(zn(M),setTimeout(()=>c(),3e3)):c()},[c,Zr]),Xn=ao(()=>{Fs(),c()},[c]),Qr=ao(async(M,re)=>{let we=L.current;if(we){Ee(!0),H(null);try{M==="login"?await we.login(re):await we.register(re),P(!0),T("menu"),we.getPvpProfile?.().then(je=>se(je)),we.registerForMatchmaking?.()}catch(je){H(je instanceof Error?je.message:"Connection failed")}finally{Ee(!1)}}},[]),Zn=ao(async()=>{let M=L.current;if(M?.findMatch){be(!0);try{let re=await M.findMatch();oe(re)}catch{oe([])}finally{be(!1)}}},[]),Jn=ao(async(M,re,we)=>{let je=L.current;if(!(!je?.launchPvpRaid||!ge))try{let bt=await je.launchPvpRaid(M.playerId,re,we);f({replay:bt.replay,attackerId:ge.player.id,defenderKeepId:M.playerId,defenderGrid:M.grid})&&(k("pvp"),T("raid")),se(Rr=>Rr&&{...Rr,trophies:bt.newTrophies,league:bt.newLeague})}catch{}},[ge,f]),Qn=ao(async()=>{let M=L.current;if(M?.getLeaderboard){C(!0);try{let re=await M.getLeaderboard();Te(re)}catch{Te([])}finally{C(!1)}}},[]);if(zi((M,re)=>{if(ct){c();return}if(!(wr&&l==="keep")){if(i){y(!1);return}if(M==="?"){y(!0);return}if(j){if(re.escape){K(!1),$("");return}if(re.return){let we=N.split(/[, ]+/);if(we.length===2){let je=parseInt(we[0],16),bt=parseInt(we[1],16);!isNaN(je)&&!isNaN(bt)&&X(je,bt)}K(!1),$("");return}if(re.backspace||re.delete){$(we=>we.slice(0,-1));return}/^[0-9a-fA-F, ]$/.test(M)&&$(we=>we+M);return}if(We&&l==="keep"){(re.return||M===" ")&&Ae();return}if(l!=="menu"&&l!=="tutorial"){if(l==="raid"||l==="friendRaid"){(M==="q"||re.escape)&&(S(),h(null),_(null),T("keep"));return}if(!(l==="friendList"||l==="raidLog"||l==="settings"||l==="pvp"||l==="warcamp"||l==="leaderboard"||l==="auth"||l==="raidPlanner"||l==="dailyChallenge")){if(M==="q"){Jr();return}if(re.escape){T("menu");return}if(M==="h"||M==="a"||re.leftArrow)qt(-1,0);else if(M==="l"||M==="d"||re.rightArrow)qt(1,0);else if(M==="k"||M==="w"||re.upArrow)qt(0,-1);else if(M==="j"||M==="s"||re.downArrow)qt(0,1);else if(re.return||M==="e")pr();else if(M==="u")fr();else if(M==="x")mr();else if(M==="[")bo(-1);else if(M==="]")bo(1);else if(M==="f")V();else if(M==="c")hr();else if(M==="r")Tr();else if(M==="v")yr()&&T("raid");else if(M==="g")K(!0),$("");else if(M==="z")gr();else if(M==="p"||M==="P"){if(ge){let we=Gn(ge),je=process.platform==="darwin"?"pbcopy":process.platform==="win32"?"clip":"xclip -selection clipboard",bt=Zi(je,()=>{});bt.stdin?.write(we),bt.stdin?.end(),lt("Keep postcard copied to clipboard!")}}else re.tab?ke(1):M>="1"&&M<="6"&&ur(parseInt(M)-1)}}}}),ct){let M=Math.floor((Date.now()-ct.startedAt)/1e3),re=Math.floor(M/60),we=M%60;return xe(Mt,{flexDirection:"column",paddingX:2,paddingY:1,children:[te(ae,{bold:!0,color:"yellow",children:"\u25C6 Session Summary"}),xe(ae,{children:["Time played: ",xe(ae,{bold:!0,children:[re,"m ",we,"s"]})]}),xe(ae,{children:["Raids: ",xe(ae,{color:"green",bold:!0,children:[ct.raidsWon," won"]})," / ",xe(ae,{color:"red",bold:!0,children:[ct.raidsLost," lost"]})]}),xe(ae,{children:["Structures built: ",te(ae,{bold:!0,children:ct.structuresBuilt})]}),xe(ae,{children:["Resources earned: ",xe(ae,{color:"yellow",children:[W.gold,ct.resourcesEarned.gold]})," ",xe(ae,{color:"green",children:[W.wood,ct.resourcesEarned.wood]})," ",xe(ae,{color:"white",children:[W.stone,ct.resourcesEarned.stone]})]}),ct.achievementsUnlocked>0&&xe(ae,{color:"magenta",bold:!0,children:["Achievements unlocked: ",ct.achievementsUnlocked]}),te(ae,{dimColor:!0,children:"(press any key to exit)"})]})}if(!ge)return te(ae,{children:"Loading..."});if(ar)return xe(Mt,{flexDirection:"column",padding:1,children:[te(ae,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep"}),te(ae,{children:" "}),te(ae,{color:"red",children:"Terminal too small"}),xe(ae,{children:["Need ",xe(ae,{bold:!0,children:[Kn,"x",Fn]}),", got ",xe(ae,{bold:!0,children:[d,"x",g]})]}),te(ae,{children:" "}),te(ae,{dimColor:!0,children:"Resize your terminal or run with --compact"})]});if(i)return te(zs,{});if(l==="tutorial"||!ge.tutorialCompleted)return te(ln,{gameSave:ge,onComplete:()=>{A(),T("keep")}});if(l==="auth")return te(Sn,{error:q,isLoading:Z,onLogin:M=>Qr("login",M),onRegister:M=>Qr("register",M),onPlayOffline:()=>{P(!1),T("menu")}});if(l==="menu")return te(rn,{gameSave:ge,onlineMode:m,onKeep:()=>T("keep"),onAttack:()=>{xr(),T("raid")},onDefend:()=>{ko(),T("raid")},onPvp:()=>T("pvp"),onDailyChallenge:()=>T("dailyChallenge"),onPrestige:()=>T("prestige"),onFriendRaid:()=>T("friendList"),onRaidLog:()=>T("raidLog"),onSettings:()=>T("settings"),onQuit:Jr});if(l==="prestige"){let M=Us(ge);return te(Pn,{gameSave:ge,eligible:M.eligible,reason:M.reason,onPrestige:()=>{let re=Gs(ge);qn(re),lt("\u2605 Ascension complete! Your keep has been reborn."),T("menu")},onBack:()=>T("menu")})}return l==="raidLog"?te(fn,{gameSave:ge,onBack:()=>T("menu"),onWatchReplay:M=>{f(M)&&(k("raidLog"),T("raid"))}}):l==="settings"?te(hn,{onBack:()=>T("menu"),onResetGame:Xn,onReplayTutorial:()=>T("tutorial"),asciiMode:R,onToggleAscii:()=>u(M=>!M)}):l==="dailyChallenge"?te(_n,{onBack:()=>T("menu"),onSaveScore:Ze}):l==="pvp"?te(Tn,{pvpProfile:Y,targets:v,isSearching:fe,onSearch:Zn,onAttack:M=>{D(M),T("raidPlanner")},onWarCamp:()=>{L.current?.getWarCamp?.().then(re=>{re&&E(re)}),T("warcamp")},onLeaderboard:()=>{Qn(),T("leaderboard")},onBack:()=>T("menu")}):l==="raidPlanner"&&F?te(Cn,{targetName:F.displayName,targetTrophies:F.trophies,targetGrid:F.grid,warCamp:B,resources:ge.keep.resources,onLaunch:M=>{let re=M.map(we=>we.raiderType);Jn(F,re,M),D(null)},onBack:()=>{D(null),T("pvp")}}):l==="warcamp"?te(wn,{warCamp:B,resources:ge.keep.resources,onTrain:(M,re)=>{L.current?.trainRaider?.(M,re).then(je=>E(je))},onBack:()=>T("pvp")}):l==="leaderboard"?te(Rn,{entries:me,currentPlayerId:ge.player.id,isLoading:Ve,onBack:()=>T("pvp")}):l==="friendList"?te(un,{onSelectFriend:M=>{let re=`friend-raid-${Date.now()}`,we=ut({probeCount:4,keepGrid:M.grid,seed:re});h(we),_(M.grid),T("friendRaid")},onBack:()=>T("menu")}):l==="friendRaid"&&w&&x?xe(Mt,{flexDirection:"column",children:[te(ae,{bold:!0,color:"yellow",children:"[ LOCAL SIMULATION ]"}),te(Kr,{replay:w,keepGrid:x,raidType:"attack",initialSpeed:b.current,onSpeedChange:M=>{b.current=M},onDone:()=>{h(null),_(null),T("menu")}})]}):l==="raid"&&Ro&&So?te(Kr,{replay:Ro,keepGrid:So,raidType:lr||"defend",summary:dr||void 0,anomalies:vo,initialSpeed:b.current,onSpeedChange:M=>{b.current=M},onDone:()=>{S(),T(U),k("keep")}}):xe(Mt,{flexDirection:"column",children:[We&&xe(Mt,{flexDirection:"column",children:[(We.resources.gold>0||We.resources.wood>0||We.resources.stone>0)&&xe(ae,{color:"green",children:["Passive income: +",We.resources.gold,W.gold," +",We.resources.wood,W.wood," +",We.resources.stone,W.stone]}),We.raids.length>0&&xe(ae,{color:"yellow",children:["Background raids: ",We.raids.filter(M=>M.won).length,"W / ",We.raids.filter(M=>!M.won).length,"L"]}),We.newAchievements.length>0&&xe(ae,{color:"magenta",bold:!0,children:["New achievements: ",We.newAchievements.join(", ")]}),te(ae,{dimColor:!0,children:"(press any key to continue)"})]}),wr&&te(In,{options:wr,anomalyNames:vo.map(M=>`${M.icon} ${M.name}`),onClaim:Vn,onDismiss:jn}),te(qs,{resources:ge.keep.resources,selectedStructure:yo,message:j?`Jump to: ${N}_`:ir,compact:Tt,structureAtCursor:cr,fragmentCount:wo.length,dryRun:n,siegeForecast:Oe,flowMultiplier:dt,activeBuffs:ge.activeBuffs}),xe(Mt,{flexDirection:"row",children:[te(Vs,{grid:ge.keep.grid,cursor:_t,asciiMode:R,compact:Tt,fragments:wo,synergyStructureIds:Nt}),!Tt&&xe(Mt,{flexDirection:"column",marginLeft:2,width:28,children:[te(cn,{selected:yo}),Ke.length>0&&xe(Mt,{marginTop:1,flexDirection:"column",children:[te(ae,{bold:!0,color:"magenta",children:"Active Synergies:"}),[...new Set(Ke.map(M=>M.name))].map(M=>xe(ae,{color:"magenta",children:[" \u2605 ",M]},M))]}),te(Mt,{marginTop:1,children:te(ae,{dimColor:!0,children:`e place u upgrade x demo
|
|
28
|
-
z undo p postcard
|
|
29
|
-
[ ] cycle 1-6 select
|
|
30
|
-
r raid v replay f +res
|
|
31
|
-
g jump Tab next ~ auto
|
|
32
|
-
?help Esc menu q quit`})})]})]}),Tt&&te(ae,{dimColor:!0,children:"e/u/x build r raid v replay f +res ~ auto-collect q quit"})]})}function Hn(e){return te(Xo,{children:te(Qi,{...e})})}import{jsx as ol}from"react/jsx-runtime";var Xr="0.3.0";globalThis.__CODEKEEP_VERSION=Xr;var Yn=new el;Yn.name("codekeep").description("Async tower defense terminal game powered by your coding activity").version(Xr).option("--ascii","Force ASCII-only rendering (no Unicode box drawing)").option("--compact","Compact layout for narrow terminals").option("--tutorial","Replay the tutorial").option("--resume","Skip menu, jump straight to keep").option("--no-save","Dry-run mode: play without writing to disk").option("--stats","Print save file stats as JSON (headless, no TUI)").option("--online <url>","Connect to a CodeKeep server for multiplayer").option("--server <url>","Alias for --online").action(e=>{if(e.stats){let d=Jt();d||(process.stdout.write(JSON.stringify({error:"No save file found"})+`
|
|
33
|
-
`),process.exit(1));let g=d.progression,l={version:Xr,player:d.player.displayName,keepAge:Math.max(1,Math.floor((Date.now()-d.keep.createdAtUnixMs)/864e5)),structures:d.keep.grid.structures.length,resources:d.keep.resources,raids:{won:g.totalRaidsWon,lost:g.totalRaidsLost,winStreak:g.currentWinStreak,bestStreak:g.bestWinStreak},achievements:g.achievements?.length??0,tutorialCompleted:d.tutorialCompleted,lastPlayed:new Date(d.lastPlayedAtUnixMs).toISOString()};process.stdout.write(JSON.stringify(l,null,2)+`
|
|
34
|
-
`),process.exit(0)}(!process.stdin.isTTY||!process.stdout.isTTY)&&(process.stderr.write(`codekeep requires an interactive terminal.
|
|
19
|
+
`);return`https://github.com/tooyipjee/codekeep/issues/new?title=${t}&body=${r}&labels=bug,crash`}import{Fragment as la,jsx as V,jsxs as oe}from"react/jsx-runtime";var Ke=class extends Ir.Component{constructor(t){super(t),this.state={error:null,crashFilePath:null,issueUrl:null}}static getDerivedStateFromError(t){return{error:t}}componentDidCatch(t){try{let r=ia(t),a={timestamp:new Date().toISOString(),error:t.message,stack:t.stack,version:globalThis.__CODEKEEP_VERSION??"unknown",nodeVersion:process.version,platform:process.platform,arch:process.arch},o=ca(a);this.setState({crashFilePath:r,issueUrl:o})}catch{}}render(){return this.state.error?oe(Cr,{flexDirection:"column",padding:1,children:[V(S,{bold:!0,color:"red",children:"\u25C6 CodeKeep \u2014 Something went wrong"}),V(S,{children:" "}),V(S,{color:"red",children:this.state.error.message}),V(S,{children:" "}),V(S,{dimColor:!0,children:"Your save file is at:"}),oe(S,{children:[" ","~/.config/codekeep/game.json"]}),V(S,{children:" "}),this.state.crashFilePath&&oe(la,{children:[V(S,{dimColor:!0,children:"Crash report saved to:"}),oe(S,{children:[" ",this.state.crashFilePath]}),V(S,{children:" "})]}),this.state.issueUrl&&oe(la,{children:[V(S,{dimColor:!0,children:"Report this bug:"}),oe(S,{color:"cyan",children:[" ",this.state.issueUrl]}),V(S,{children:" "})]}),V(S,{dimColor:!0,children:"If the game won't start, try:"}),oe(S,{bold:!0,children:[" ","codekeep --tutorial"]}),V(S,{children:" "}),V(S,{dimColor:!0,children:"Press Ctrl+C to exit"})]}):this.props.children}};import{Box as gt,Text as O}from"ink";import{Box as Sr,Text as J}from"ink";import{jsx as Pe,jsxs as Ue}from"react/jsx-runtime";function Er(e){if(!e)return"";switch(e.type){case"advance":return`\u2193${e.value}`;case"attack":return`\u2694${e.value}`;case"buff":return`\u25B2${e.value}`;case"debuff":return`\u25BC${e.value}`;case"shield":return`\u25C8${e.value}`;case"summon":return`+${e.value}`;default:return""}}function Pr(e){let t=[];for(let r of e.statusEffects)switch(r.type){case"vulnerable":t.push(`V${r.stacks}`);break;case"weak":t.push(`W${r.stacks}`);break;case"burn":t.push(`B${r.stacks}`);break;case"empowered":t.push(`E${r.stacks}`);break;case"fortified":t.push(`F${r.stacks}`);break}return t.join("")}function Rr(e,t,r=6){let a=Math.max(0,Math.round(e/t*r));return"\u2588".repeat(a)+"\u2591".repeat(r-a)}function Dr(e){return`${ne(e.templateId)?.symbol??"?"}${e.hp}`}function da({columns:e,targetColumn:t,showTarget:r,gateHp:a,gateMaxHp:o,gateBlock:n}){let u=e.map((f,k)=>{let _=`Col ${k+1}`,N=f.emplacement?`[${f.emplacement.hp}hp]`:"",B=N?`${_}${N}`:_,M=r&&k===t,G=B.padStart(Math.floor((14+B.length)/2)).padEnd(14);return M?`[${G.slice(1,-1)}]`:` ${G.slice(1,-1)} `}).join("\u2502"),s=[];for(let f=0;f<Te;f++){let k=e.map(_=>{let N=_.enemies.filter(M=>M.row===f);return N.length===0?"\xB7".padStart(Math.floor(7.5)).padEnd(14):N.map(M=>{let G=Er(M.intent),U=Pr(M),h=Dr(M);return G&&(h+=` ${G}`),U&&(h+=` ${U}`),h}).join(" ").slice(0,14).padEnd(14)}).join("\u2502");s.push(k)}let p=Math.round(a/o*100),g=p>60?"green":p>30?"yellow":"red";return Ue(Sr,{flexDirection:"column",children:[Pe(J,{bold:!0,dimColor:!0,children:"\u2500".repeat(74)}),Pe(J,{bold:!0,children:u}),Pe(J,{dimColor:!0,children:"\u2500".repeat(74)}),s.map((f,k)=>Pe(J,{children:f},k)),Pe(J,{dimColor:!0,children:"\u2550".repeat(74)}),Ue(J,{children:[Ue(J,{bold:!0,color:g,children:["Gate ",Rr(a,o,12)," ",a,"/",o]}),n>0&&Ue(J,{color:"cyan",children:[" \u{1F6E1}",n]})]})]})}import{Box as Ar,Text as ve}from"ink";import{jsx as ma,jsxs as Re}from"react/jsx-runtime";function Mr(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function Hr(e){switch(e){case"armament":return"\u2694";case"fortification":return"\u{1F6E1}";case"edict":return"\u2726";case"wild":return"\u25C8";default:return"\xB7"}}function ua({hand:e,selectedIndex:t,resolve:r}){return e.length===0?ma(ve,{dimColor:!0,children:"No cards in hand."}):Re(Ar,{flexDirection:"column",children:[ma(ve,{bold:!0,children:"Hand:"}),e.map((a,o)=>{let n=$(a.defId);if(!n)return null;let l=o===t,u=n.cost<=r,s=l?"\u25B6 ":" ",p=`${o+1}`,g=`[${n.cost}]`;return Re(ve,{children:[Re(ve,{bold:l,color:l?"yellow":void 0,children:[s,p,"."," "]}),Re(ve,{color:u?Mr(n.rarity):"gray",bold:l,children:[Hr(n.category)," ",n.name," ",g]}),l&&Re(ve,{dimColor:!0,children:[" \u2014 ",n.description]})]},a.instanceId)})]})}import{jsx as K,jsxs as De}from"react/jsx-runtime";function pa({combat:e,selectedCard:t,targetColumn:r,needsTarget:a,message:o}){let n=e.columns.reduce((l,u)=>l+u.enemies.length,0);return De(gt,{flexDirection:"column",children:[De(gt,{justifyContent:"space-between",children:[K(O,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),De(O,{children:["Turn ",K(O,{bold:!0,children:e.turn})," ","Resolve ",De(O,{bold:!0,color:"cyan",children:[e.resolve,"/",e.maxResolve]})," ","Enemies ",K(O,{bold:!0,color:"red",children:n})," ","Draw ",K(O,{dimColor:!0,children:e.drawPile.length})," ","Discard ",K(O,{dimColor:!0,children:e.discardPile.length})]})]}),K(O,{children:" "}),K(da,{columns:e.columns,targetColumn:r,showTarget:a&&t>=0,gateHp:e.gateHp,gateMaxHp:e.gateMaxHp,gateBlock:e.gateBlock}),K(O,{children:" "}),K(ua,{hand:e.hand,selectedIndex:t,resolve:e.resolve}),K(O,{children:" "}),o&&K(O,{color:"yellow",children:o}),e.phase==="player"&&De(O,{dimColor:!0,children:["1-",e.hand.length," card \u2190\u2192 column Enter play e emplace p potion Space end turn d deck q quit"]}),e.outcome!=="undecided"&&K(gt,{marginTop:1,children:K(O,{bold:!0,color:e.outcome==="win"?"green":"red",children:e.outcome==="win"?"\u2605 VICTORY \u2014 The siege is broken!":"\u2717 DEFEAT \u2014 The Gate has fallen."})})]})}import{Box as fa,Text as Z}from"ink";import{jsx as we,jsxs as Le}from"react/jsx-runtime";function Br(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function ha({cards:e,selectedIndex:t}){return Le(fa,{flexDirection:"column",padding:1,children:[we(Z,{bold:!0,color:"yellow",children:"\u25C6 Choose a Card Reward"}),we(Z,{children:" "}),e.map((r,a)=>{let o=a===t;return Le(fa,{flexDirection:"column",marginBottom:1,children:[Le(Z,{bold:o,color:o?"yellow":Br(r.rarity),children:[o?"\u25B6 ":" ",a+1,". ",r.name," [",r.cost,"] (",r.rarity,")"]}),Le(Z,{dimColor:!0,children:[" ",r.description]})]},r.id)}),we(Z,{children:" "}),we(Z,{dimColor:!0,children:t>=0?` ${e.length+1}. Skip \u2014 take no card`:`\u25B6 ${e.length+1}. Skip \u2014 take no card`}),we(Z,{children:" "}),we(Z,{dimColor:!0,children:"\u2191\u2193 navigate Enter select s skip"})]})}import{Box as Gr,Text as Ae}from"ink";import{jsx as vt,jsxs as yt}from"react/jsx-runtime";function Nr(e){switch(e){case"uncommon":return"green";case"rare":return"blue";case"legendary":return"magenta";default:return"white"}}function ga({deck:e}){let t=new Map;for(let a of e)t.set(a.defId,(t.get(a.defId)??0)+1);let r=[...t.entries()].map(([a,o])=>({def:$(a),count:o})).filter(a=>a.def).sort((a,o)=>{let n={common:0,uncommon:1,rare:2,legendary:3},l=n[a.def.rarity]??0,u=n[o.def.rarity]??0;return l!==u?l-u:a.def.name.localeCompare(o.def.name)});return yt(Gr,{flexDirection:"column",padding:1,children:[yt(Ae,{bold:!0,color:"yellow",children:["\u25C6 Your Deck"," (",e.length," cards)"]}),vt(Ae,{children:" "}),r.map(({def:a,count:o})=>yt(Ae,{color:Nr(a.rarity),children:[o>1?`${o}x `:" ",a.name," [",a.cost,"] \u2014 ",a.description]},a.id)),vt(Ae,{children:" "}),vt(Ae,{dimColor:!0,children:"Press q or Esc to go back."})]})}import{Box as wt,Text as Q}from"ink";import{jsx as se,jsxs as qe}from"react/jsx-runtime";function Wr(e){switch(e){case"combat":return"\u2694";case"elite":return"\u2605";case"rest":return"\u25B3";case"shop":return"$";case"event":return"?";case"boss":return"\u25C6";default:return"\xB7"}}function $r(e,t,r,a){if(a)return"cyan";if(r)return"yellow";if(e.visited||!t)return"gray";switch(e.type){case"combat":return"white";case"elite":return"red";case"rest":return"green";case"shop":return"yellow";case"event":return"magenta";case"boss":return"red";default:return"white"}}function ya({map:e,currentNodeId:t,reachableIds:r,selectedNodeId:a}){let o=new Set(r),n=Math.max(...e.nodes.map(u=>u.row)),l=[];for(let u=0;u<=n;u++)l.push(e.nodes.filter(s=>s.row===u).sort((s,p)=>s.column-p.column));return qe(wt,{flexDirection:"column",padding:1,children:[qe(Q,{bold:!0,color:"yellow",children:["\u25C6 Act ",e.act," Map"]}),se(Q,{children:" "}),l.map((u,s)=>qe(wt,{children:[qe(Q,{dimColor:!0,children:[String(s).padStart(2," ")," "]}),se(wt,{gap:2,children:[0,1,2,3].map(p=>{let g=u.find(G=>G.column===p);if(!g)return se(Q,{children:" "},p);let f=g.id===t,k=o.has(g.id),_=g.id===a,N=$r(g,k,_,f),B=Wr(g.type),M=_?`[${B}]`:f?`(${B})`:` ${B} `;return se(Q,{color:N,bold:_||f,dimColor:g.visited&&!f,children:M},p)})})]},s)),se(Q,{children:" "}),se(Q,{dimColor:!0,children:"\u2694=fight \u2605=elite \u25B3=rest $=shop ?=event \u25C6=boss"}),se(Q,{dimColor:!0,children:"\u2191\u2193 select Enter proceed d=deck q=quit"})]})}import{Box as Fr,Text as Me}from"ink";import{jsx as kt,jsxs as bt}from"react/jsx-runtime";function va({items:e,selectedIndex:t,fragments:r}){return bt(Fr,{flexDirection:"column",padding:1,children:[bt(Me,{bold:!0,color:"yellow",children:["\u25C6 Shop \u2014 ",r," fragments"]}),kt(Me,{children:" "}),e.map((a,o)=>{let n=o===t,l=r>=a.cost,u="";return a.type==="card"&&a.cardDef?u=`${a.cardDef.name} (${a.cardDef.rarity}) \u2014 ${a.cardDef.description}`:a.type==="potion"&&a.potionDef?u=`${a.potionDef.name} \u2014 ${a.potionDef.description}`:a.type==="card_remove"&&(u="Remove a card from your deck"),bt(Me,{bold:n,color:n?"yellow":l?"white":"gray",children:[n?"\u25B6 ":" ",u," [",a.cost,"f]"]},o)}),kt(Me,{children:" "}),kt(Me,{dimColor:!0,children:"\u2191\u2193 navigate Enter buy q leave"})]})}import{Box as Vr,Text as ie}from"ink";import{jsx as He,jsxs as Tt}from"react/jsx-runtime";function wa({event:e,selectedChoice:t}){return Tt(Vr,{flexDirection:"column",padding:1,children:[Tt(ie,{bold:!0,color:"magenta",children:["\u25C6 ",e.name]}),He(ie,{children:" "}),He(ie,{children:e.description}),He(ie,{children:" "}),e.choices.map((r,a)=>Tt(ie,{bold:a===t,color:a===t?"yellow":"white",children:[a===t?"\u25B6 ":" ",a+1,". ",r.label]},a)),He(ie,{children:" "}),He(ie,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{Box as Or,Text as ce}from"ink";import{jsx as be,jsxs as ba}from"react/jsx-runtime";function ka({gateHp:e,gateMaxHp:t,selectedChoice:r,deckSize:a}){let o=Math.floor(t*.3),n=[`Rest \u2014 Heal ${o} Gate HP (${e} \u2192 ${Math.min(t,e+o)}/${t})`,`Thin \u2014 Remove a card from your deck (${a} cards)`,"Leave \u2014 Continue without resting"];return ba(Or,{flexDirection:"column",padding:1,children:[be(ce,{bold:!0,color:"green",children:"\u25C6 Rest Site"}),be(ce,{children:" "}),be(ce,{children:"The campfire crackles against the encroaching Pale."}),be(ce,{children:" "}),n.map((l,u)=>ba(ce,{bold:u===r,color:u===r?"yellow":"white",children:[u===r?"\u25B6 ":" ",l]},u)),be(ce,{children:" "}),be(ce,{dimColor:!0,children:"\u2191\u2193 navigate Enter choose"})]})}import{Box as Kr,Text as le}from"ink";import{jsx as Be,jsxs as xt}from"react/jsx-runtime";var Ur={wren:"Wren, the Steward",sable:"Sable, the Archivist",duskmar:"Duskmar, First Wall",mott:"Mott, the Salvager",pale_visitor:"The Pale Visitor"};function Ta({keep:e,selectedIndex:t}){let r=[...q.map(a=>{let o=Ee(e,a.id);return{type:"structure",id:a.id,label:`${a.name} (Lv.${o}/${a.maxLevel}) \u2014 ${a.description}`}}),...e.npcs.map(a=>({type:"npc",id:a.id,label:`${Ur[a.id]??a.id} (Tier ${a.tier}/5)`})),{type:"action",id:"new_run",label:"Begin New Run"},{type:"action",id:"ascension",label:`Ascension Level: ${e.highestAscension}`}];return xt(Kr,{flexDirection:"column",padding:1,children:[Be(le,{bold:!0,color:"yellow",children:"\u25C6 The Keep"}),xt(le,{children:["Echoes: ",Be(le,{bold:!0,color:"cyan",children:e.echoes})," | Runs: ",e.totalRuns," | Wins: ",e.totalWins]}),Be(le,{children:" "}),r.map((a,o)=>xt(le,{bold:o===t,color:o===t?"yellow":"white",children:[o===t?"\u25B6 ":" ",a.label]},a.id)),Be(le,{children:" "}),Be(le,{dimColor:!0,children:"\u2191\u2193 navigate Enter select q quit"})]})}import{useState as Ge,useCallback as de,useRef as Lr}from"react";function xa(){let[e,t]=Ge(null),[r,a]=Ge(-1),[o,n]=Ge(2),[l,u]=Ge(""),[s,p]=Ge(!1),g=Lr(null),f=de((h,y,b,W,H)=>{let te=h??st(),ae=y??Math.floor(Math.random()*2147483647),C=$t(te,ae,b,W,H);g.current=C,t({...C}),a(-1),n(2),p(!1),u("Your turn. Select a card (1-5) and a column (\u2190\u2192).")},[]),k=(()=>{if(r<0||!e)return!1;let h=e.hand[r];if(!h)return!1;let y=$(h.defId);return y?y.effects.some(b=>b.type==="damage"&&(b.target==="single"||b.target==="column")):!1})(),_=de(h=>{if(!g.current||g.current.phase!=="player"||h<0||h>=g.current.hand.length)return;let y=g.current.hand[h],b=$(y.defId);if(!b)return;if(b.cost>g.current.resolve){u(`Not enough Resolve for ${b.name} (costs ${b.cost}).`);return}a(h);let W=b.effects.some(H=>H.type==="damage"&&(H.target==="single"||H.target==="column"));u(W?`${b.name} selected. Choose column (\u2190\u2192), Enter to play.`:`${b.name} selected. Enter to play.`)},[]),N=de(h=>{h>=0&&h<5&&n(h)},[]),B=de(()=>{let h=g.current;if(!h||h.phase!=="player"||r<0)return;let y=h.hand[r];if(!y)return;let b=$(y.defId);b&&(Ft(h,r,o,s),g.current=h,t({...h}),a(-1),p(!1),h.outcome==="win"?u("Victory! The Pale recedes."):h.outcome==="lose"?u("The Gate has fallen..."):u(`${s?"Emplaced":"Played"} ${b.name}. ${h.resolve} Resolve left.`))},[r,o,s]),M=de(()=>{if(r<0||!g.current)return;let h=g.current.hand[r];if(!h)return;let y=$(h.defId);if(!y||y.type!=="emplace"){u("This card cannot be emplaced.");return}p(b=>!b),u(s?`${y.name}: cast mode`:`${y.name}: EMPLACE mode \u2014 place in a column`)},[r,s]),G=de(()=>{let h=g.current;!h||h.phase!=="player"||(Vt(h),g.current=h,t({...h}),a(-1),h.outcome==="win"?u("Victory! The Pale recedes."):h.outcome==="lose"?u("The Gate has fallen..."):u(`Turn ${h.turn}. Your move.`))},[]),U=de(h=>{let y=g.current;if(!(!y||!h)){for(let b of h.effects)switch(b.type){case"heal":y.gateHp=Math.min(y.gateMaxHp,y.gateHp+b.value);break;case"block":y.gateBlock+=b.value;break;case"damage":{if(b.target==="column"){let W=y.columns[2];for(let H of W.enemies)H.hp-=b.value}else if(b.target==="all")for(let W of y.columns)for(let H of W.enemies)H.hp-=b.value;break}case"draw":{let W=F(y.seed+y.turn*53),{drawn:H,newDrawPile:te,newDiscardPile:ae}=Se(y.drawPile,y.discardPile,b.value,W);y.hand.push(...H),y.drawPile=te,y.discardPile=ae;break}case"resolve":y.resolve=Math.min(y.maxResolve+5,y.resolve+b.value);break}g.current=y,t({...y}),u(`Used ${h.name}.`)}},[]);return{combat:e,selectedCard:r,targetColumn:o,message:l,emplaceMode:s,selectCard:_,selectTarget:N,confirmPlay:B,endTurn:G,toggleEmplace:M,startCombat:f,applyPotion:U,needsTarget:k}}import{Fragment as _t,jsx as w,jsxs as T}from"react/jsx-runtime";var _a=60,Ia=18;function Xr(){let{stdout:e}=Yr(),[t,r]=E({columns:e?.columns??process.stdout.columns??80,rows:e?.rows??process.stdout.rows??24});return qr(()=>{let a=e??process.stdout,o=()=>r({columns:a.columns,rows:a.rows});return a.on("resize",o),()=>{a.off("resize",o)}},[e]),t}function Jr({dryRun:e}){let{exit:t}=jr(),{columns:r,rows:a}=Xr(),[o,n]=E("menu"),[l,u]=E(0),[s,p]=E(null),[g,f]=E(0),[k,_]=E([]),[N,B]=E(0),[M,G]=E([]),[U,h]=E(0),[y,b]=E(null),[W,H]=E(0),[te,ae]=E(0),[C,me]=E(null),[ue,je]=E(0),[It,pe]=E(""),[Ct,Pa]=E(!1),[ze,fe]=E(0),Ye=xa(),{combat:X,selectedCard:St,targetColumn:Xe,message:Ra,emplaceMode:tn,selectCard:Et,selectTarget:Pt,confirmPlay:Da,endTurn:Aa,toggleEmplace:Ma,startCombat:Je,applyPotion:an,needsTarget:Ha}=Ye,Ba=r<_a||a<Ia,L=j(()=>{let i=ye();return i||(i=oa("Warden"),i.keep.npcs.length===0&&(i.keep.npcs=pt()),Y(i)),i.keep.npcs.length===0&&(i.keep.npcs=pt()),i},[]),I=j(i=>{if(e)return;let d=L();d.activeRun=i,C&&(d.keep=C),Y(d)},[e,C,L]),Rt=j(()=>{let i=`run-${Date.now()}`,d=L(),c=Lt(i,d.keep.highestAscension);p(c),f(0),d.activeRun=c,d.keep.totalRuns++,me(d.keep),Y(d),n("map")},[L]),Ga=j(()=>{let i=ye();i?.activeRun&&(p(i.activeRun),me(i.keep),f(0),n("map"))},[]),Dt=j(()=>{let i=L();me(i.keep),je(0),pe(""),n("keep")},[L]),At=j(()=>s?Ut(s.map,s.currentNodeId):[],[s]),Na=j(i=>{if(!s)return;let d=qt(s,i.id);if(i.type==="combat"||i.type==="elite"){let c=z(s.seed+i.id),m=F(c),x=Ot(s.act,m,i.type==="elite");Je(d.deck,c,d.gateHp,d.gateMaxHp,x.enemies),p(d),n("combat")}else if(i.type==="boss"){let c=z(s.seed+"-boss-"+s.act),m=Wt(s.act);Je(d.deck,c,d.gateHp,d.gateMaxHp,m),p(d),n("combat")}else if(i.type==="shop"){let c=F(z(s.seed+i.id));G(Jt(c)),h(0),p(d),n("shop")}else if(i.type==="event"){let c=F(z(s.seed+i.id));b(Zt(s.act,c)),H(0),p(d),n("event")}else i.type==="rest"&&(ae(0),p(d),n("rest"));I(d)},[s,Je,I]),Ze=j((i,d)=>{Pa(d),p(i);let c=L(),m=ft(d,i.act,i.ascensionLevel);c.keep.echoes+=m,d&&(c.keep.totalWins++,i.ascensionLevel>=c.keep.highestAscension&&(c.keep.highestAscension=i.ascensionLevel+1)),c.activeRun=null,me(c.keep),Y(c),n("result")},[L]),Wa=j(()=>{if(!X||!s)return;let i={...s,gateHp:X.gateHp};if(X.outcome==="lose"){Ze(i,!1);return}let d=s.currentNodeId?Ce(s.map,s.currentNodeId):null,c=d?.type==="elite"?$e.elite:d?.type==="boss"?$e.boss:$e.combat;if(i=Oe(i,c),d?.type==="boss"){s.act<3?(i=Xt(i),p(i),I(i),f(0),n("map")):Ze(i,!0);return}let m=F(z(s.seed+(s.currentNodeId??"")+"-reward"));_(lt(m)),B(0),p(i),I(i),n("reward")},[X,s,I,Ze]),Mt=j(i=>{if(!s)return;let d=s;i&&(d=dt(d,Ve(i.id))),p(d),I(d),f(0),n("map")},[s,I]),Qe=[{label:"The Keep",action:"keep"},{label:"New Run",action:"new"},...ye()?.activeRun?[{label:"Resume Run",action:"resume"}]:[],{label:"Quit",action:"quit"}];if(zr((i,d)=>{if(o==="menu"){if(d.upArrow||i==="k")u(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")u(c=>Math.min(Qe.length-1,c+1));else if(d.return){let c=Qe[l]?.action;c==="keep"?Dt():c==="new"?Rt():c==="resume"?Ga():t()}else i==="q"&&t();return}if(o==="deck"){(i==="q"||d.escape)&&n("map");return}if(o==="map"&&s){let c=At();d.upArrow||i==="k"?f(m=>Math.max(0,m-1)):d.downArrow||i==="j"?f(m=>Math.min(c.length-1,m+1)):d.return&&c[g]?Na(c[g]):i==="d"?n("deck"):i==="q"&&(I(s),n("menu"));return}if(o==="reward"){let c=k.length;d.upArrow||i==="k"?B(m=>Math.max(0,m-1)):d.downArrow||i==="j"?B(m=>Math.min(c,m+1)):i==="s"?Mt(null):d.return&&Mt(N<k.length?k[N]:null);return}if(o==="shop"&&s){if(d.upArrow||i==="k")h(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")h(c=>Math.min(M.length-1,c+1));else if(i==="q"||d.escape)f(0),n("map");else if(d.return){let c=M[U];if(c&&s.fragments>=c.cost){let m=jt(s,c.cost);if(!m)return;if(c.type==="card"&&c.cardDef)m=dt(m,Ve(c.cardDef.id)),p(m),I(m),G(x=>x.filter((P,re)=>re!==U)),h(0);else if(c.type==="potion"&&c.potionDef){let x=zt(m,c.potionDef.id);x&&(m=x),p(m),I(m),G(P=>P.filter((re,Ne)=>Ne!==U)),h(0)}else c.type==="card_remove"&&(p(m),I(m),fe(0),G(x=>x.filter((P,re)=>re!==U)),n("shop_remove"))}}return}if(o==="event"&&y&&s){if(d.upArrow||i==="k")H(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")H(c=>Math.min(y.choices.length-1,c+1));else if(d.return){let c=y.choices[W],m=s;switch(c.effect.type){case"heal":m=ut(m,c.effect.value);break;case"damage":{m={...m,gateHp:Math.max(1,m.gateHp-c.effect.value)};let x=c.label.match(/(\d+)\s*fragments/i);x&&(m=Oe(m,parseInt(x[1])));break}case"fragments":m=Oe(m,c.effect.value);break;case"max_hp":m={...m,gateMaxHp:m.gateMaxHp+c.effect.value,gateHp:m.gateHp+c.effect.value};break;case"card_reward":{let x=c.label.match(/lose\s+(\d+)\s*hp/i);x&&(m={...m,gateHp:Math.max(1,m.gateHp-parseInt(x[1]))});let P=F(z(s.seed+(y?.id??"")));_(lt(P)),B(0),p(m),I(m),n("reward");return}case"remove_card":{p(m),I(m),n("deck_remove");return}case"nothing":break}p(m),I(m),f(0),n("map")}return}if(o==="rest"&&s){if(d.upArrow||i==="k")ae(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")ae(c=>Math.min(2,c+1));else if(d.return){let c=s;te===0?(c=ut(c,Math.floor(c.gateMaxHp*.3)),p(c),I(c),f(0),n("map")):te===1&&c.deck.length>5?(p(c),fe(0),n("deck_remove")):(p(c),I(c),f(0),n("map"))}return}if(o==="deck_remove"&&s){if(d.upArrow||i==="k")fe(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")fe(c=>Math.min(s.deck.length-1,c+1));else if(d.escape||i==="q")f(0),n("map");else if(d.return&&s.deck.length>5){let c=s.deck[ze];if(c){let m=mt(s,c.instanceId);p(m),I(m)}f(0),n("map")}return}if(o==="shop_remove"&&s){if(d.upArrow||i==="k")fe(c=>Math.max(0,c-1));else if(d.downArrow||i==="j")fe(c=>Math.min(s.deck.length-1,c+1));else if(d.escape||i==="q")n("shop");else if(d.return&&s.deck.length>5){let c=s.deck[ze];if(c){let m=mt(s,c.instanceId);p(m),I(m)}n("shop")}return}if(o==="keep"&&C){let c=q.length+C.npcs.length+2;if(d.upArrow||i==="k")je(m=>Math.max(0,m-1));else if(d.downArrow||i==="j")je(m=>Math.min(c-1,m+1));else if(i==="q")n("menu");else if(d.return)if(ue<q.length){let m=q[ue],x=Qt(C,m.id);if(x){me(x);let P=L();P.keep=x,Y(P),pe(`Upgraded ${m.name}!`)}else Ee(C,m.id)>=m.maxLevel?pe(`${m.name} is already max level.`):pe(`Not enough Echoes to upgrade ${m.name}.`)}else if(ue<q.length+C.npcs.length){let m=ue-q.length,x=C.npcs[m],P=ea(x.id,C);if(P){let re=ta(C,x.id,P.dialogueId);me(re);let Ne=L();Ne.keep=re,Y(Ne),pe(`${P.speaker}: "${P.text}"`)}else pe("(No new dialogue)")}else ue===q.length+C.npcs.length&&Rt();return}if(o==="result"){d.return||i===" "?Dt():i==="q"&&n("menu");return}if(o==="combat"&&X){if(X.outcome!=="undecided"){(d.return||i===" ")&&Wa();return}if(i==="q"){n("menu");return}if(i==="d"&&s){n("deck");return}if(i==="e"){Ma();return}if(i==="p"&&s){let c=s.potions.findIndex(m=>m!==null);if(c>=0){let m=Yt(s,c);if(m&&Ye.combat){let x=_e.find(P=>P.id===m.potionId);p(m.run),I(m.run),Ye.applyPotion(x??null)}}return}if(i>="1"&&i<="9"){Et(parseInt(i)-1);return}if(d.leftArrow||i==="h"){Pt(Math.max(0,Xe-1));return}if(d.rightArrow||i==="l"){Pt(Math.min(4,Xe+1));return}if(d.return&&St>=0){Da();return}if(i===" "){Aa();return}if(d.escape){Et(-1);return}}}),Ba)return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),T(v,{color:"red",children:["Terminal too small (",r,"x",a,", need ",_a,"x",Ia,")"]})]});if(o==="menu")return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),w(v,{children:" "}),w(v,{children:"The fortress stands at the edge of the Pale."}),w(v,{children:"Beyond the walls, something stirs."}),w(v,{children:" "}),Qe.map((i,d)=>T(v,{bold:d===l,color:d===l?"yellow":void 0,children:[d===l?"\u25B6 ":" ",i.label]},i.action)),w(v,{children:" "}),w(v,{dimColor:!0,children:"\u2191\u2193 navigate Enter select q quit"})]});if(o==="deck"&&s)return w(ga,{deck:s.deck});if(o==="keep"&&C)return T(ee,{flexDirection:"column",children:[w(Ta,{keep:C,selectedIndex:ue}),It&&T(v,{color:"cyan",bold:!0,children:[" ",It]})]});if((o==="deck_remove"||o==="shop_remove")&&s)return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"red",children:"\u25C6 Remove a Card"}),T(v,{dimColor:!0,children:["Choose a card to remove from your deck (",s.deck.length," cards)."]}),w(v,{children:" "}),s.deck.map((i,d)=>{let c=$(i.defId),m=d===ze;return T(v,{bold:m,color:m?"yellow":"white",children:[m?"\u25B6 ":" ",c?.name??i.defId," (",c?.rarity,") \u2014 ",c?.description??""]},i.instanceId)}),w(v,{children:" "}),w(v,{dimColor:!0,children:"\u2191\u2193 navigate Enter remove Esc cancel"})]});if(o==="map"&&s){let i=At();return T(ee,{flexDirection:"column",children:[T(ee,{justifyContent:"space-between",paddingX:1,children:[T(v,{children:["Gate ",T(v,{bold:!0,color:s.gateHp>40?"green":s.gateHp>20?"yellow":"red",children:[s.gateHp,"/",s.gateMaxHp]})]}),T(v,{children:["Fragments ",w(v,{bold:!0,color:"yellow",children:s.fragments})]}),T(v,{children:["Deck ",w(v,{dimColor:!0,children:s.deck.length})]})]}),w(ya,{map:s.map,currentNodeId:s.currentNodeId,reachableIds:i.map(d=>d.id),selectedNodeId:i[g]?.id??null})]})}if(o==="reward")return w(ha,{cards:k,selectedIndex:N});if(o==="shop"&&s)return w(va,{items:M,selectedIndex:U,fragments:s.fragments});if(o==="event"&&y)return w(wa,{event:y,selectedChoice:W});if(o==="rest"&&s)return w(ka,{gateHp:s.gateHp,gateMaxHp:s.gateMaxHp,selectedChoice:te,deckSize:s.deck.length});if(o==="result"){let i=s?ft(Ct,s.act,s.ascensionLevel):0;return T(ee,{flexDirection:"column",padding:1,children:[w(v,{bold:!0,color:"yellow",children:"\u25C6 CodeKeep \u2014 The Pale"}),w(v,{children:" "}),Ct?T(_t,{children:[w(v,{bold:!0,color:"green",children:"\u2605 RUN COMPLETE"}),w(v,{children:"The Pale recedes. The Keep endures."})]}):T(_t,{children:[w(v,{bold:!0,color:"red",children:"\u2717 DEFEAT"}),w(v,{children:"The Gate has fallen. The Pale consumes all."})]}),s&&T(_t,{children:[T(v,{dimColor:!0,children:["Act ",s.act," | Deck: ",s.deck.length," cards | Fragments: ",s.fragments]}),T(v,{children:["Echoes earned: ",T(v,{bold:!0,color:"cyan",children:["+",i]})]})]}),w(v,{children:" "}),w(v,{dimColor:!0,children:"Enter \u2192 The Keep | q \u2192 menu"})]})}return o==="combat"&&X&&s?T(ee,{flexDirection:"column",paddingX:1,children:[T(v,{dimColor:!0,children:["Act ",s.act," | Gate ",s.gateHp,"/",s.gateMaxHp," | Fragments ",s.fragments," | d=deck"]}),w(pa,{combat:X,selectedCard:St,targetColumn:Xe,needsTarget:Ha,message:Ra})]}):w(v,{children:"Loading..."})}function Ca(e){return w(Ke,{children:w(Jr,{...e})})}import{jsx as en}from"react/jsx-runtime";var Sa="0.5.0";globalThis.__CODEKEEP_VERSION=Sa;var Ea=new Zr;Ea.name("codekeep").description("CodeKeep: The Pale \u2014 deck-building tactical roguelike in your terminal").version(Sa).option("--ascii","Force ASCII-only rendering (no Unicode box drawing)").option("--compact","Compact layout for narrow terminals").option("--tutorial","Replay the tutorial").option("--no-save","Dry-run mode: play without writing to disk").action(e=>{(!process.stdin.isTTY||!process.stdout.isTTY)&&(process.stderr.write(`codekeep requires an interactive terminal.
|
|
35
20
|
Run it directly in your terminal (not piped or in CI).
|
|
36
|
-
|
|
37
|
-
`),process.exit(1));let t=!!Jt(),o=e.online||e.server||process.env.CODEKEEP_SERVER,{waitUntilExit:s,unmount:r}=tl(ol(Hn,{asciiMode:e.ascii??!1,compact:e.compact??!1,forceTutorial:e.tutorial??!1,autoResume:(e.resume??!1)&&t,serverUrl:o,dryRun:e.save===!1}),{exitOnCtrlC:!1}),n=!1;function c(){if(!n){if(n=!0,e.save!==!1){process.stderr.write(`
|
|
21
|
+
`),process.exit(1));let{waitUntilExit:t,unmount:r}=Qr(en(Ca,{asciiMode:e.ascii??!1,compact:e.compact??!1,forceTutorial:e.tutorial??!1,dryRun:e.save===!1}),{exitOnCtrlC:!1}),a=!1;function o(){if(!a){if(a=!0,e.save!==!1){process.stderr.write(`
|
|
38
22
|
Saving...
|
|
39
|
-
`);try{let
|
|
23
|
+
`);try{let n=ye();n&&Y(n)}catch{}}r()}}process.on("SIGINT",o),process.on("SIGTERM",o),process.on("SIGHUP",o),t().then(()=>{process.exit(0)}).catch(()=>{process.exit(1)})});Ea.parse();export{Sa as CLI_VERSION};
|
package/package.json
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codekeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "CodeKeep: The Pale — deck-building tactical roguelike in your terminal",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/tooyipjee/codekeep.git",
|
|
10
10
|
"directory": "packages/cli"
|
|
11
11
|
},
|
|
12
|
-
"engines": {
|
|
13
|
-
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"terminal",
|
|
17
|
+
"game",
|
|
18
|
+
"roguelike",
|
|
19
|
+
"deck-building",
|
|
20
|
+
"ascii",
|
|
21
|
+
"cli",
|
|
22
|
+
"tui"
|
|
23
|
+
],
|
|
14
24
|
"bin": {
|
|
15
25
|
"codekeep": "./dist/index.js"
|
|
16
26
|
},
|
|
17
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
18
30
|
"scripts": {
|
|
19
31
|
"build": "tsup",
|
|
20
32
|
"dev": "tsx src/index.tsx",
|
|
@@ -31,6 +43,7 @@
|
|
|
31
43
|
"devDependencies": {
|
|
32
44
|
"@codekeep/server": "workspace:*",
|
|
33
45
|
"@codekeep/shared": "workspace:*",
|
|
46
|
+
"@types/node": "^25.5.0",
|
|
34
47
|
"@types/react": "^19",
|
|
35
48
|
"@vitest/coverage-v8": "3",
|
|
36
49
|
"ink-testing-library": "^4",
|