Spaces:
Sleeping
Sleeping
Commit
·
e144fd4
1
Parent(s):
df4bec6
fix(vs-people): enable resign functionality to emit socket event
Browse filesThe resign function was showing confirmation but not actually emitting
the resign event. Now properly calls socketApi.resign() for multiplayer.
Co-Authored-By: Claude <noreply@anthropic.com>
trigo-web/app/dist/assets/{index--_hEjmsI.js → index-BucvvkZ4.js}
RENAMED
|
@@ -6481,7 +6481,7 @@ ${s}`,a=n.createShaderModule({code:o,label:t.name});le("verbose",()=>`[WebGPU] $
|
|
| 6481 |
* =============================================================================
|
| 6482 |
*/class ModelInferencer{constructor(e,n={}){this.session=null,this.PAD_TOKEN=0,this.START_TOKEN=1,this.END_TOKEN=2,this.VALUE_TOKEN=3,this.TensorClass=e,this.config={vocabSize:n.vocabSize||128,seqLen:256,...n}}setSession(e){this.session=e,console.log("[ModelInferencer] ✓ Session set successfully"),this.printModelInfo()}async testBasicInference(){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");console.log("[ModelInferencer] Running basic inference test...");const e=1,n=this.config.seqLen,i=this.createRandomInput(e,n),r=new this.TensorClass("int64",i,[e,n]),s=performance.now(),o=await this.session.run({input_ids:r}),a=performance.now()-s,l=o.logits;this.validateOutput(l,e,n);const c=this.getPredictions(l.data,e*n),u=String.fromCharCode(...c.slice(0,100));console.log("[ModelInferencer] Inference completed:"),console.log(` Input shape: [${r.dims.join(", ")}]`),console.log(` Output shape: [${l.dims.join(", ")}]`),console.log(` Output dtype: ${l.type}`),console.log(` Inference time: ${a.toFixed(2)}ms`),console.log(` Sample predictions: [${c.slice(0,10).join(", ")}]`);const d=Array.from(l.data);return console.log(` Logits range: [${Math.min(...d).toFixed(3)}, ${Math.max(...d).toFixed(3)}]`),{tokens:c,text:u,logits:l.data,inferenceTime:a}}async generateText(e,n=10){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");console.log(`[ModelInferencer] Generating ${n} tokens from prompt: "${e}"`);const i=Array.from(e).map(l=>l.charCodeAt(0));console.log(` Prompt tokens (${i.length}): [${i.join(", ")}]`);const r=[...i],s=[];for(let l=0;l<n;l++){const c=this.padSequence(r,this.config.seqLen),u=new BigInt64Array(c.map(w=>BigInt(w))),d=new this.TensorClass("int64",u,[1,this.config.seqLen]),f=performance.now(),m=await this.session.run({input_ids:d});s.push(performance.now()-f);const b=m.logits.data,v=(r.length-1)*this.config.vocabSize;let _=0,x=b[v];for(let w=1;w<this.config.vocabSize;w++)b[v+w]>x&&(x=b[v+w],_=w);if(r.push(_),_===this.END_TOKEN){console.log(" Generated END token, stopping...");break}}const o=String.fromCharCode(...r),a=s.reduce((l,c)=>l+c,0)/s.length;return console.log("[ModelInferencer] Generation complete:"),console.log(` Generated text: "${o}"`),console.log(` Token sequence (${r.length}): [${r.join(", ")}]`),console.log(` Avg inference time: ${a.toFixed(2)}ms`),console.log(` Tokens/sec: ${(1e3/a).toFixed(2)}`),{tokens:r,text:o,logits:new Float32Array,inferenceTime:a}}getModelInfo(){return this.session?{inputs:[...this.session.inputNames],outputs:[...this.session.outputNames]}:null}getConfig(){return this.config}async runInference(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const n=this.config.seqLen,i=[this.START_TOKEN,...e],r=new BigInt64Array(n);for(let a=0;a<n;a++)r[a]=a<i.length?BigInt(i[a]):BigInt(this.PAD_TOKEN);const s=new this.TensorClass("int64",r,[1,n]);return(await this.session.run({input_ids:s})).logits.data}async runEvaluationInference(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const{prefixIds:n,evaluatedIds:i,evaluatedMask:r}=e,s=1,o=n.length,a=i.length,l=new BigInt64Array(s*o);for(let v=0;v<o;v++)l[v]=BigInt(n[v]);const c=new BigInt64Array(s*a);for(let v=0;v<a;v++)c[v]=BigInt(i[v]);const u=new Float32Array(a*a);for(let v=0;v<a*a;v++)u[v]=r[v];const d=new this.TensorClass("int64",l,[s,o]),f=new this.TensorClass("int64",c,[s,a]),m=new this.TensorClass("float32",u,[1,a,a]);return{logits:(await this.session.run({prefix_ids:d,evaluated_ids:f,evaluated_mask:m})).logits.data,numEvaluated:a}}async runValuePrediction(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const n=e.length,i=new BigInt64Array(n);for(let l=0;l<n;l++)i[l]=BigInt(e[l]);const r=new this.TensorClass("int64",i,[1,n]),o=(await this.session.run({input_ids:r})).values;if(!o)throw new Error("Evaluation model did not return 'values' output. Check model export.");return o.data[0]}softmax(e,n){const i=this.config.vocabSize,r=n*i,s=new Float32Array(i);let o=-1/0;for(let l=0;l<i;l++)o=Math.max(o,e[r+l]);let a=0;for(let l=0;l<i;l++)s[l]=Math.exp(e[r+l]-o),a+=s[l];for(let l=0;l<i;l++)s[l]/=a;return s}isReady(){return this.session!==null}destroy(){this.session=null,console.log("[ModelInferencer] Session destroyed")}printModelInfo(){this.session&&(console.log("[ModelInferencer] Model Information:"),console.log(" Inputs:"),this.session.inputNames.forEach((e,n)=>{console.log(` [${n}] ${e}`)}),console.log(" Outputs:"),this.session.outputNames.forEach((e,n)=>{console.log(` [${n}] ${e}`)}))}createRandomInput(e,n){const i=e*n,r=new BigInt64Array(i);for(let s=0;s<i;s++)r[s]=BigInt(Math.floor(Math.random()*this.config.vocabSize));return r}padSequence(e,n){const i=[...e];for(;i.length<n;)i.push(this.PAD_TOKEN);return i.slice(0,n)}validateOutput(e,n,i){const r=[n,i,this.config.vocabSize];if(e.dims.length!==3)throw new Error(`Expected 3D output, got ${e.dims.length}D`);if(e.dims[0]!==r[0]||e.dims[1]!==r[1]||e.dims[2]!==r[2])throw new Error(`Shape mismatch! Expected [${r.join(", ")}], got [${e.dims.join(", ")}]`);if(e.type!=="float32")throw new Error(`Expected float32 output, got ${e.type}`)}getPredictions(e,n){const i=[];for(let r=0;r<n;r++){let s=0,o=e[r*this.config.vocabSize];for(let a=1;a<this.config.vocabSize;a++){const l=e[r*this.config.vocabSize+a];l>o&&(o=l,s=a)}i.push(s)}return i}}we.wasm.numThreads=1;we.wasm.simd=!0;class OnnxInferencer extends ModelInferencer{constructor(e){const{modelPath:n,executionProviders:i=["wasm"],sessionOptions:r,...s}=e;super(qe,s),this.modelPath=n,this.sessionOptions={executionProviders:i,graphOptimizationLevel:"all",...r}}async initialize(){console.log("[OnnxInferencer] Initializing..."),console.log("[OnnxInferencer] Model path:",this.modelPath);try{const e=await Kp.create(this.modelPath,this.sessionOptions);this.setSession(e)}catch(e){throw console.error("[OnnxInferencer] Failed to create session:",e),e}}}function useTrigoAgent(){const t=ref(!1),e=ref(!1),n=ref(null),i=ref(0);let r=null,s=null;const o=async()=>{if(t.value){console.log("[useTrigoAgent] Already initialized");return}n.value=null;try{console.log("[useTrigoAgent] Initializing tree agent with evaluation mode..."),s=new OnnxInferencer({modelPath:"/onnx/20251230-trigo-value-llama-l6-h64-it2_251221-value0.01-pretrain/LlamaCausalLM_ep0036_tree.onnx",vocabSize:128,seqLen:256}),await s.initialize(),r=new TrigoTreeAgent(s),t.value=!0,console.log("[useTrigoAgent] ✓ Tree agent ready")}catch(u){const d=u instanceof Error?u.message:"Failed to initialize AI agent";throw n.value=d,console.error("[useTrigoAgent] Initialization failed:",u),u}},a=async u=>{if(!r)throw new Error("AI agent not initialized. Call initialize() first.");if(!t.value)throw new Error("AI agent is not ready yet.");if(e.value)return console.warn("[useTrigoAgent] Already generating a move, ignoring request"),null;n.value=null,e.value=!0;try{console.log("[useTrigoAgent] Generating move with tree agent...");const d=performance.now(),f=await r.selectBestMove(u),m=performance.now()-d;return i.value=m,console.log(`[useTrigoAgent] ✓ Move generated in ${m.toFixed(2)}ms`),f?f.isPass?(console.log("[useTrigoAgent] AI chose to pass"),f):f.x!==void 0&&f.y!==void 0&&f.z!==void 0?f:(console.warn("[useTrigoAgent] Move has undefined coordinates:",f),null):(console.log("[useTrigoAgent] No valid move available"),null)}catch(d){const f=d instanceof Error?d.message:"Failed to generate move";throw n.value=f,console.error("[useTrigoAgent] Move generation failed:",d),d}finally{e.value=!1}},l=()=>r!==null&&s!==null,c=()=>{(r||s)&&(console.log("[useTrigoAgent] Cleaning up..."),r=null,s=null,t.value=!1,e.value=!1,n.value=null,i.value=0)};return onUnmounted(()=>{c()}),{isReady:t,isThinking:e,error:n,lastMoveTime:i,initialize:o,generateMove:a,checkIsReady:l,cleanup:c}}function useRoomHash(){function t(){return new URLSearchParams(window.location.search).get("room")||null}function e(r){const s=new URL(window.location.href);s.searchParams.set("room",r),window.history.replaceState(null,"",s.toString())}function n(){const r=new URL(window.location.href);r.searchParams.delete("room"),window.history.replaceState(null,"",r.toString())}function i(r){return/^[A-Z0-9]{8}$/.test(r)}return{getRoomIdFromHash:t,updateHash:e,clearHash:n,isValidRoomId:i}}const _sfc_main$c=defineComponent({__name:"InlineNicknameEditor",props:{nickname:{type:String,required:!0},editable:{type:Boolean,required:!0},playerColor:{type:[String,null],required:!0}},emits:["update"],setup(t,{expose:e,emit:n}){e();const i=t,r=n,s=ref(!1),o=ref(i.nickname),a=ref(""),l=ref(null),c=computed(()=>i.nickname||"Guest"),u=computed(()=>a.value!==""),d=computed(()=>"3-20 characters, letters/numbers/spaces only");function f(_){const x=_.trim();return x.length<3?{valid:!1,error:"Must be 3+ chars"}:x.length>20?{valid:!1,error:"Max 20 chars"}:/^[a-zA-Z0-9 ]+$/.test(x)?x!==_?{valid:!1,error:"No leading/trailing spaces"}:{valid:!0,error:""}:{valid:!1,error:"Letters, numbers, spaces only"}}function m(){i.editable&&(s.value=!0,o.value=i.nickname,a.value="",nextTick$1(()=>{var _,x;(_=l.value)==null||_.focus(),(x=l.value)==null||x.select()}))}function b(){if(!s.value)return;const _=o.value.trim();if(_===i.nickname){y();return}const x=f(_);if(!x.valid){a.value=x.error;return}s.value=!1,a.value="",r("update",_)}function y(){s.value=!1,o.value=i.nickname,a.value=""}watch(()=>i.nickname,_=>{s.value||(o.value=_)});const v={props:i,emit:r,editing:s,editValue:o,errorMessage:a,inputRef:l,displayNickname:c,hasError:u,inputTooltip:d,validateNickname:f,startEdit:m,confirmEdit:b,cancelEdit:y};return Object.defineProperty(v,"__isScriptSetup",{enumerable:!1,value:!0}),v}}),_hoisted_1$c=["title"],_hoisted_2$c={class:"nickname-text"},_hoisted_3$c={key:0,class:"edit-icon"},_hoisted_4$c={key:1,class:"nickname-edit"},_hoisted_5$c=["title"],_hoisted_6$c=["title"],_hoisted_7$c={key:2,class:"edit-info"},_hoisted_8$c={class:"char-count"};function _sfc_render$c(t,e,n,i,r,s){return openBlock(),createElementBlock("div",{class:normalizeClass(["inline-nickname-editor",{editable:n.editable,editing:i.editing,[n.playerColor||"neutral"]:!0}])},[i.editing?(openBlock(),createElementBlock("div",_hoisted_4$c,[createBaseVNode("span",{class:normalizeClass(["stone-icon",n.playerColor])},null,2),withDirectives(createBaseVNode("input",{ref:"inputRef","onUpdate:modelValue":e[0]||(e[0]=o=>i.editValue=o),type:"text",class:normalizeClass(["nickname-input",{error:i.hasError}]),maxlength:20,title:i.inputTooltip,onKeydown:[withKeys(i.confirmEdit,["enter"]),withKeys(i.cancelEdit,["esc"])],onBlur:i.confirmEdit},null,42,_hoisted_5$c),[[vModelText,i.editValue]]),i.hasError?(openBlock(),createElementBlock("span",{key:0,class:"error-icon",title:i.errorMessage},"⚠️",8,_hoisted_6$c)):createCommentVNode("",!0)])):(openBlock(),createElementBlock("div",{key:0,class:"nickname-display",title:n.editable?"Click to edit nickname":"",onClick:i.startEdit},[createBaseVNode("span",{class:normalizeClass(["stone-icon",n.playerColor])},null,2),createBaseVNode("span",_hoisted_2$c,toDisplayString(i.displayNickname),1),n.editable?(openBlock(),createElementBlock("span",_hoisted_3$c,"✏️")):createCommentVNode("",!0)],8,_hoisted_1$c)),i.editing?(openBlock(),createElementBlock("div",_hoisted_7$c,[e[1]||(e[1]=createBaseVNode("span",{class:"help-text"},"Enter to save • Esc to cancel",-1)),createBaseVNode("span",_hoisted_8$c,toDisplayString(i.editValue.length)+"/20",1)])):createCommentVNode("",!0)],2)}const InlineNicknameEditor=_export_sfc(_sfc_main$c,[["render",_sfc_render$c],["__scopeId","data-v-c3b5c618"],["__file","/home/camus/work/trigo/trigo-web/app/src/components/InlineNicknameEditor.vue"]]),_sfc_main$b=defineComponent({__name:"RoomSelector",props:{currentRoom:{type:[String,null],required:!0},rooms:{type:Array,required:!0},loading:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1}},emits:["create","select"],setup(t,{expose:e,emit:n}){e();const i=t,r=n,s=ref(!1),o=ref(null),a=computed(()=>[...i.rooms].sort((y,v)=>y.id===i.currentRoom?-1:v.id===i.currentRoom?1:y.status==="waiting"&&v.status!=="waiting"?-1:y.status!=="waiting"&&v.status==="waiting"?1:y.playerCount!==v.playerCount?y.playerCount-v.playerCount:new Date(v.createdAt).getTime()-new Date(y.createdAt).getTime())),l=()=>{i.disabled||(s.value=!s.value)},c=()=>{s.value=!1,r("create")},u=y=>{y.isFull&&y.id!==i.currentRoom||(s.value=!1,r("select",y.id))},d=y=>y.playerCount===0?"empty":y.playerCount===1?"partial":"full",f=y=>y.isFull?"Full":y.status==="waiting"?"Waiting":y.status==="playing"?"Playing":y.status,m=y=>{o.value&&!o.value.contains(y.target)&&(s.value=!1)};onMounted(()=>{document.addEventListener("click",m)}),onUnmounted(()=>{document.removeEventListener("click",m)});const b={props:i,emit:r,isOpen:s,selectorRef:o,sortedRooms:a,toggleDropdown:l,handleCreate:c,handleSelect:u,playerCountClass:d,formatStatus:f,handleClickOutside:m};return Object.defineProperty(b,"__isScriptSetup",{enumerable:!1,value:!0}),b}}),_hoisted_1$b={class:"room-selector",ref:"selectorRef"},_hoisted_2$b={class:"selected-room"},_hoisted_3$b={key:0,class:"room-display"},_hoisted_4$b={key:1,class:"placeholder"},_hoisted_5$b={class:"dropdown-arrow"},_hoisted_6$b={key:0,class:"dropdown-menu"},_hoisted_7$b={key:0,class:"dropdown-divider"},_hoisted_8$b={class:"room-list"},_hoisted_9$b={key:0,class:"loading-state"},_hoisted_10$b={key:1,class:"empty-state"},_hoisted_11$8=["onClick"],_hoisted_12$8={class:"room-id"};function _sfc_render$b(t,e,n,i,r,s){return openBlock(),createElementBlock("div",_hoisted_1$b,[createBaseVNode("div",{class:normalizeClass(["dropdown-container",{disabled:n.disabled}]),onClick:i.toggleDropdown},[createBaseVNode("div",_hoisted_2$b,[n.currentRoom?(openBlock(),createElementBlock("span",_hoisted_3$b,toDisplayString(n.currentRoom),1)):(openBlock(),createElementBlock("span",_hoisted_4$b,"Select room..."))]),createBaseVNode("span",_hoisted_5$b,toDisplayString(i.isOpen?"▲":"▼"),1)],2),i.isOpen?(openBlock(),createElementBlock("div",_hoisted_6$b,[createBaseVNode("div",{class:"dropdown-item create-room",onClick:i.handleCreate},[...e[0]||(e[0]=[createBaseVNode("span",{class:"item-icon"},"➕",-1),createBaseVNode("span",{class:"item-text"},"Create New Room",-1)])]),n.rooms.length>0?(openBlock(),createElementBlock("div",_hoisted_7$b)):createCommentVNode("",!0),createBaseVNode("div",_hoisted_8$b,[n.loading?(openBlock(),createElementBlock("div",_hoisted_9$b," Loading rooms... ")):n.rooms.length===0?(openBlock(),createElementBlock("div",_hoisted_10$b," No active rooms ")):(openBlock(!0),createElementBlock(Fragment,{key:2},renderList(i.sortedRooms,o=>(openBlock(),createElementBlock("div",{key:o.id,class:normalizeClass(["dropdown-item room-item",{"is-current":o.id===n.currentRoom,"is-full":o.isFull}]),onClick:a=>i.handleSelect(o)},[createBaseVNode("span",_hoisted_12$8,toDisplayString(o.id),1),createBaseVNode("span",{class:normalizeClass(["player-count",i.playerCountClass(o)])},toDisplayString(o.playerCount)+"/2 ",3),createBaseVNode("span",{class:normalizeClass(["room-status-badge",o.status])},toDisplayString(i.formatStatus(o)),3)],10,_hoisted_11$8))),128))])])):createCommentVNode("",!0)],512)}const RoomSelector=_export_sfc(_sfc_main$b,[["render",_sfc_render$b],["__scopeId","data-v-99ecebab"],["__file","/home/camus/work/trigo/trigo-web/app/src/components/RoomSelector.vue"]]),_sfc_main$a=defineComponent({__name:"TrigoView",setup(t,{expose:e}){e();const n=useRoute(),i=computed(()=>n.meta.mode||"single"),r=useGameStore(),{game:s,currentPlayer:o,moveHistory:a,currentMoveIndex:l,capturedStones:c,gameStatus:u,boardShape:d,moveCount:f,isGameActive:m,passCount:b}=storeToRefs(r),y=usePlayerStore(),v=useSocket(),{getRoomIdFromHash:_,updateHash:x,clearHash:w,isValidRoomId:M}=useRoomHash(),C=ref(!1),$=ref([]),R=ref(!1),D=G=>{const Ce=G.split(/[^\d]+/).filter(Boolean).map(Number);return{x:Ce[0]||5,y:Ce[1]||5,z:Ce[2]||5}},A=G=>`${G.x}*${G.y}*${G.z}`,k=G=>{const Ce=d.value;return encodeAb0yz([G.x,G.y,G.z],[Ce.x,Ce.y,Ce.z])},Y=useTrigoAgent(),{isReady:ne,isThinking:q,error:fe,lastMoveTime:me}=Y,ie=()=>{const G=storage.getString(StorageKey.AI_PLAYER_COLOR);return G==="black"||G==="white"?G:"white"},Q=ref(ie()),H=computed(()=>Q.value==="white"?"black":"white"),de=()=>{const G=storage.getString("vs-people-color-preference");return G==="black"||G==="white"?G:"black"},se=ref(de());watch(se,G=>{storage.setString("vs-people-color-preference",G)});const V=ref(null),ce=ref(0),Ie=ref(0),Xe=ref(!0),zt=ref(A(d.value)),Tn=ref(!1),X=ref(!1),Fe=ref({visible:!1,groupSize:0,liberties:0}),_t=ref(""),Qt=ref("idle"),Yt=ref("");let ge=null;const U=ref(null),F=ref(null);let Z=null,$e=null;const Be=computed(()=>m.value),Ge=computed(()=>{const G=[];for(let Ce=0;Ce<a.value.length;Ce+=2){const Nt=a.value[Ce],bt=a.value[Ce+1]||null;G.push({black:Nt,white:bt,blackIndex:Ce+1,whiteIndex:bt?Ce+2:null})}return G}),gt=computed(()=>{const G=D(zt.value),Ce=d.value;return G.x!==Ce.x||G.y!==Ce.y||G.z!==Ce.z}),mt=computed(()=>{var G;return((G=r.game)==null?void 0:G.toTGN({application:"Trigo Demo v1.0",date:new Date().toISOString().split("T")[0].replace(/-/g,".")}))||""}),It=computed(()=>Qt.value==="valid"),At=computed(()=>Qt.value==="idle"?"idle":Qt.value==="valid"?"valid":"invalid"),z=computed(()=>Qt.value==="idle"?"Ready to validate":Qt.value==="valid"?"✓ Valid TGN":`✗ ${Yt.value}`),O=()=>{ge&&clearTimeout(ge),Qt.value="idle",ge=setTimeout(()=>{const G=validateTGN(_t.value);G.valid?(Qt.value="valid",Yt.value=""):(Qt.value="invalid",Yt.value=G.error||"Invalid TGN format")},300)},pe=()=>{if(It.value)try{const G=TrigoGameFrontend.fromTGN(_t.value);r.game=G,r.saveToSessionStorage(),Z&&(Z.setBoardShape(G.getShape()),Zt()),X.value=!1}catch(G){console.error("Failed to apply TGN:",G),Qt.value="invalid",Yt.value=G instanceof Error?G.message:"Failed to apply TGN"}},st=async()=>{if(!(!ne.value||q.value||!Be.value))try{console.log("[TrigoView] Generating AI move...");const G=await Y.generateMove(r.game);if(G)if(G.isPass)console.log("[TrigoView] AI chose to pass"),r.pass().success?console.log(`[TrigoView] AI pass applied successfully (${me.value.toFixed(0)}ms)`):console.error("[TrigoView] Failed to apply AI pass");else{console.log(`[TrigoView] AI suggests move at (${G.x}, ${G.y}, ${G.z})`);const Ce=r.makeMove(G.x,G.y,G.z);if(Ce.success&&Z){const Nt=r.opponentPlayer;Z.addStone(G.x,G.y,G.z,Nt),Ce.capturedPositions&&Ce.capturedPositions.length>0&&(Ce.capturedPositions.forEach(bt=>{Z.removeStone(bt.x,bt.y,bt.z)}),console.log(`[TrigoView] AI captured ${Ce.capturedPositions.length} stone(s)`)),console.log(`[TrigoView] AI move applied successfully (${me.value.toFixed(0)}ms)`)}}else console.log("[TrigoView] AI returned no move")}catch(G){console.error("[TrigoView] Failed to generate AI move:",G)}},dt=()=>{if(!Z)return;const G=r.getAllStones();for(const Ce of G)Z.addStone(Ce.x,Ce.y,Ce.z,Ce.color);console.log(`[TrigoView] Synced viewport with ${G.length} stones`)},Et=async(G,Ce,Nt)=>{if(!Be.value)return;if(i.value==="vs-people"){if(!Nn.value){console.log("[TrigoView] Not your turn, ignoring click");return}console.log(`[TrigoView] Emitting makeMove: (${G}, ${Ce}, ${Nt})`),v.makeMove(G,Ce,Nt);return}const bt=r.makeMove(G,Ce,Nt);if(bt.success&&Z){const xn=r.opponentPlayer;Z.addStone(G,Ce,Nt,xn),bt.capturedPositions&&bt.capturedPositions.length>0&&(bt.capturedPositions.forEach(Pn=>{Z.removeStone(Pn.x,Pn.y,Pn.z)}),console.log(`Captured ${bt.capturedPositions.length} stone(s)`)),Z.hideDomainCubes(),Tn.value=!1,i.value==="vs-ai"&&ne.value&&o.value===Q.value&&setTimeout(()=>{st()},100)}},_n=(G,Ce,Nt)=>{G!==null&&Ce!==null&&Nt!==null?V.value=`(${G}, ${Ce}, ${Nt})`:V.value=null},Vt=(G,Ce,Nt)=>{const bt={x:G,y:Ce,z:Nt},xn=o.value==="black"?StoneType.BLACK:StoneType.WHITE;return validateMove(bt,xn,r.board,d.value,r.lastCapturedPositions).valid},ze=(G,Ce)=>{G>0?Fe.value={visible:!0,groupSize:G,liberties:Ce}:Fe.value={visible:!1,groupSize:0,liberties:0}},oe=()=>{Q.value=Q.value==="white"?"black":"white",storage.setString(StorageKey.AI_PLAYER_COLOR,Q.value),console.log(`[TrigoView] AI color swapped to: ${Q.value}`),Be.value&&ne.value&&o.value===Q.value&&setTimeout(()=>{st()},100)},yt=()=>{const G=D(zt.value);r.initializeGame(G),r.startGame(),Z&&(Z.setBoardShape(G),Z.clearBoard(),Z.setGameActive(!0),Z.hideDomainCubes()),ce.value=0,Ie.value=0,Tn.value=!1,console.log(`Starting new game with board shape ${G.x}×${G.y}×${G.z}`),i.value==="vs-ai"&&Q.value==="black"&&ne.value&&setTimeout(()=>{st()},100)},wn=()=>{if(!confirm("Reset the game? This will clear all moves."))return;const G=D(zt.value),Ce=y.playerColor!==se.value;v.socket.emit("resetGame",{boardShape:G,swapColors:Ce},Nt=>{Nt.success?console.log("Multiplayer game reset successfully"):(console.error("Reset failed:",Nt.error),alert(`Failed to reset game: ${Nt.error||"Unknown error"}`))})},bn=()=>{if(i.value==="vs-people"){if(!Nn.value){console.log("[TrigoView] Not your turn, cannot pass");return}console.log("[TrigoView] Emitting pass"),v.pass();return}const G=o.value;r.pass()&&console.log(`${G} passed (Pass count: ${r.passCount})`)},vn=()=>{confirm(`Are you sure ${o.value} wants to resign?
|
| 6483 |
|
| 6484 |
-
This will end the game immediately.`)},An=()=>{const G=r.gameResult;if(!G)return;let Ce="";if(G.reason==="resignation")Ce=`${G.winner==="black"?"Black":"White"} wins by resignation!
|
| 6485 |
|
| 6486 |
Game continues for analysis.`;else if(G.reason==="double-pass"){const Nt=r.computeTerritory(),bt=Nt.black+c.value.black,xn=Nt.white+c.value.white;ce.value=bt,Ie.value=xn,bt>xn?Ce=`Black wins by ${bt-xn} points!
|
| 6487 |
|
|
|
|
| 6481 |
* =============================================================================
|
| 6482 |
*/class ModelInferencer{constructor(e,n={}){this.session=null,this.PAD_TOKEN=0,this.START_TOKEN=1,this.END_TOKEN=2,this.VALUE_TOKEN=3,this.TensorClass=e,this.config={vocabSize:n.vocabSize||128,seqLen:256,...n}}setSession(e){this.session=e,console.log("[ModelInferencer] ✓ Session set successfully"),this.printModelInfo()}async testBasicInference(){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");console.log("[ModelInferencer] Running basic inference test...");const e=1,n=this.config.seqLen,i=this.createRandomInput(e,n),r=new this.TensorClass("int64",i,[e,n]),s=performance.now(),o=await this.session.run({input_ids:r}),a=performance.now()-s,l=o.logits;this.validateOutput(l,e,n);const c=this.getPredictions(l.data,e*n),u=String.fromCharCode(...c.slice(0,100));console.log("[ModelInferencer] Inference completed:"),console.log(` Input shape: [${r.dims.join(", ")}]`),console.log(` Output shape: [${l.dims.join(", ")}]`),console.log(` Output dtype: ${l.type}`),console.log(` Inference time: ${a.toFixed(2)}ms`),console.log(` Sample predictions: [${c.slice(0,10).join(", ")}]`);const d=Array.from(l.data);return console.log(` Logits range: [${Math.min(...d).toFixed(3)}, ${Math.max(...d).toFixed(3)}]`),{tokens:c,text:u,logits:l.data,inferenceTime:a}}async generateText(e,n=10){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");console.log(`[ModelInferencer] Generating ${n} tokens from prompt: "${e}"`);const i=Array.from(e).map(l=>l.charCodeAt(0));console.log(` Prompt tokens (${i.length}): [${i.join(", ")}]`);const r=[...i],s=[];for(let l=0;l<n;l++){const c=this.padSequence(r,this.config.seqLen),u=new BigInt64Array(c.map(w=>BigInt(w))),d=new this.TensorClass("int64",u,[1,this.config.seqLen]),f=performance.now(),m=await this.session.run({input_ids:d});s.push(performance.now()-f);const b=m.logits.data,v=(r.length-1)*this.config.vocabSize;let _=0,x=b[v];for(let w=1;w<this.config.vocabSize;w++)b[v+w]>x&&(x=b[v+w],_=w);if(r.push(_),_===this.END_TOKEN){console.log(" Generated END token, stopping...");break}}const o=String.fromCharCode(...r),a=s.reduce((l,c)=>l+c,0)/s.length;return console.log("[ModelInferencer] Generation complete:"),console.log(` Generated text: "${o}"`),console.log(` Token sequence (${r.length}): [${r.join(", ")}]`),console.log(` Avg inference time: ${a.toFixed(2)}ms`),console.log(` Tokens/sec: ${(1e3/a).toFixed(2)}`),{tokens:r,text:o,logits:new Float32Array,inferenceTime:a}}getModelInfo(){return this.session?{inputs:[...this.session.inputNames],outputs:[...this.session.outputNames]}:null}getConfig(){return this.config}async runInference(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const n=this.config.seqLen,i=[this.START_TOKEN,...e],r=new BigInt64Array(n);for(let a=0;a<n;a++)r[a]=a<i.length?BigInt(i[a]):BigInt(this.PAD_TOKEN);const s=new this.TensorClass("int64",r,[1,n]);return(await this.session.run({input_ids:s})).logits.data}async runEvaluationInference(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const{prefixIds:n,evaluatedIds:i,evaluatedMask:r}=e,s=1,o=n.length,a=i.length,l=new BigInt64Array(s*o);for(let v=0;v<o;v++)l[v]=BigInt(n[v]);const c=new BigInt64Array(s*a);for(let v=0;v<a;v++)c[v]=BigInt(i[v]);const u=new Float32Array(a*a);for(let v=0;v<a*a;v++)u[v]=r[v];const d=new this.TensorClass("int64",l,[s,o]),f=new this.TensorClass("int64",c,[s,a]),m=new this.TensorClass("float32",u,[1,a,a]);return{logits:(await this.session.run({prefix_ids:d,evaluated_ids:f,evaluated_mask:m})).logits.data,numEvaluated:a}}async runValuePrediction(e){if(!this.session)throw new Error("Inferencer not initialized. Call setSession() first.");const n=e.length,i=new BigInt64Array(n);for(let l=0;l<n;l++)i[l]=BigInt(e[l]);const r=new this.TensorClass("int64",i,[1,n]),o=(await this.session.run({input_ids:r})).values;if(!o)throw new Error("Evaluation model did not return 'values' output. Check model export.");return o.data[0]}softmax(e,n){const i=this.config.vocabSize,r=n*i,s=new Float32Array(i);let o=-1/0;for(let l=0;l<i;l++)o=Math.max(o,e[r+l]);let a=0;for(let l=0;l<i;l++)s[l]=Math.exp(e[r+l]-o),a+=s[l];for(let l=0;l<i;l++)s[l]/=a;return s}isReady(){return this.session!==null}destroy(){this.session=null,console.log("[ModelInferencer] Session destroyed")}printModelInfo(){this.session&&(console.log("[ModelInferencer] Model Information:"),console.log(" Inputs:"),this.session.inputNames.forEach((e,n)=>{console.log(` [${n}] ${e}`)}),console.log(" Outputs:"),this.session.outputNames.forEach((e,n)=>{console.log(` [${n}] ${e}`)}))}createRandomInput(e,n){const i=e*n,r=new BigInt64Array(i);for(let s=0;s<i;s++)r[s]=BigInt(Math.floor(Math.random()*this.config.vocabSize));return r}padSequence(e,n){const i=[...e];for(;i.length<n;)i.push(this.PAD_TOKEN);return i.slice(0,n)}validateOutput(e,n,i){const r=[n,i,this.config.vocabSize];if(e.dims.length!==3)throw new Error(`Expected 3D output, got ${e.dims.length}D`);if(e.dims[0]!==r[0]||e.dims[1]!==r[1]||e.dims[2]!==r[2])throw new Error(`Shape mismatch! Expected [${r.join(", ")}], got [${e.dims.join(", ")}]`);if(e.type!=="float32")throw new Error(`Expected float32 output, got ${e.type}`)}getPredictions(e,n){const i=[];for(let r=0;r<n;r++){let s=0,o=e[r*this.config.vocabSize];for(let a=1;a<this.config.vocabSize;a++){const l=e[r*this.config.vocabSize+a];l>o&&(o=l,s=a)}i.push(s)}return i}}we.wasm.numThreads=1;we.wasm.simd=!0;class OnnxInferencer extends ModelInferencer{constructor(e){const{modelPath:n,executionProviders:i=["wasm"],sessionOptions:r,...s}=e;super(qe,s),this.modelPath=n,this.sessionOptions={executionProviders:i,graphOptimizationLevel:"all",...r}}async initialize(){console.log("[OnnxInferencer] Initializing..."),console.log("[OnnxInferencer] Model path:",this.modelPath);try{const e=await Kp.create(this.modelPath,this.sessionOptions);this.setSession(e)}catch(e){throw console.error("[OnnxInferencer] Failed to create session:",e),e}}}function useTrigoAgent(){const t=ref(!1),e=ref(!1),n=ref(null),i=ref(0);let r=null,s=null;const o=async()=>{if(t.value){console.log("[useTrigoAgent] Already initialized");return}n.value=null;try{console.log("[useTrigoAgent] Initializing tree agent with evaluation mode..."),s=new OnnxInferencer({modelPath:"/onnx/20251230-trigo-value-llama-l6-h64-it2_251221-value0.01-pretrain/LlamaCausalLM_ep0036_tree.onnx",vocabSize:128,seqLen:256}),await s.initialize(),r=new TrigoTreeAgent(s),t.value=!0,console.log("[useTrigoAgent] ✓ Tree agent ready")}catch(u){const d=u instanceof Error?u.message:"Failed to initialize AI agent";throw n.value=d,console.error("[useTrigoAgent] Initialization failed:",u),u}},a=async u=>{if(!r)throw new Error("AI agent not initialized. Call initialize() first.");if(!t.value)throw new Error("AI agent is not ready yet.");if(e.value)return console.warn("[useTrigoAgent] Already generating a move, ignoring request"),null;n.value=null,e.value=!0;try{console.log("[useTrigoAgent] Generating move with tree agent...");const d=performance.now(),f=await r.selectBestMove(u),m=performance.now()-d;return i.value=m,console.log(`[useTrigoAgent] ✓ Move generated in ${m.toFixed(2)}ms`),f?f.isPass?(console.log("[useTrigoAgent] AI chose to pass"),f):f.x!==void 0&&f.y!==void 0&&f.z!==void 0?f:(console.warn("[useTrigoAgent] Move has undefined coordinates:",f),null):(console.log("[useTrigoAgent] No valid move available"),null)}catch(d){const f=d instanceof Error?d.message:"Failed to generate move";throw n.value=f,console.error("[useTrigoAgent] Move generation failed:",d),d}finally{e.value=!1}},l=()=>r!==null&&s!==null,c=()=>{(r||s)&&(console.log("[useTrigoAgent] Cleaning up..."),r=null,s=null,t.value=!1,e.value=!1,n.value=null,i.value=0)};return onUnmounted(()=>{c()}),{isReady:t,isThinking:e,error:n,lastMoveTime:i,initialize:o,generateMove:a,checkIsReady:l,cleanup:c}}function useRoomHash(){function t(){return new URLSearchParams(window.location.search).get("room")||null}function e(r){const s=new URL(window.location.href);s.searchParams.set("room",r),window.history.replaceState(null,"",s.toString())}function n(){const r=new URL(window.location.href);r.searchParams.delete("room"),window.history.replaceState(null,"",r.toString())}function i(r){return/^[A-Z0-9]{8}$/.test(r)}return{getRoomIdFromHash:t,updateHash:e,clearHash:n,isValidRoomId:i}}const _sfc_main$c=defineComponent({__name:"InlineNicknameEditor",props:{nickname:{type:String,required:!0},editable:{type:Boolean,required:!0},playerColor:{type:[String,null],required:!0}},emits:["update"],setup(t,{expose:e,emit:n}){e();const i=t,r=n,s=ref(!1),o=ref(i.nickname),a=ref(""),l=ref(null),c=computed(()=>i.nickname||"Guest"),u=computed(()=>a.value!==""),d=computed(()=>"3-20 characters, letters/numbers/spaces only");function f(_){const x=_.trim();return x.length<3?{valid:!1,error:"Must be 3+ chars"}:x.length>20?{valid:!1,error:"Max 20 chars"}:/^[a-zA-Z0-9 ]+$/.test(x)?x!==_?{valid:!1,error:"No leading/trailing spaces"}:{valid:!0,error:""}:{valid:!1,error:"Letters, numbers, spaces only"}}function m(){i.editable&&(s.value=!0,o.value=i.nickname,a.value="",nextTick$1(()=>{var _,x;(_=l.value)==null||_.focus(),(x=l.value)==null||x.select()}))}function b(){if(!s.value)return;const _=o.value.trim();if(_===i.nickname){y();return}const x=f(_);if(!x.valid){a.value=x.error;return}s.value=!1,a.value="",r("update",_)}function y(){s.value=!1,o.value=i.nickname,a.value=""}watch(()=>i.nickname,_=>{s.value||(o.value=_)});const v={props:i,emit:r,editing:s,editValue:o,errorMessage:a,inputRef:l,displayNickname:c,hasError:u,inputTooltip:d,validateNickname:f,startEdit:m,confirmEdit:b,cancelEdit:y};return Object.defineProperty(v,"__isScriptSetup",{enumerable:!1,value:!0}),v}}),_hoisted_1$c=["title"],_hoisted_2$c={class:"nickname-text"},_hoisted_3$c={key:0,class:"edit-icon"},_hoisted_4$c={key:1,class:"nickname-edit"},_hoisted_5$c=["title"],_hoisted_6$c=["title"],_hoisted_7$c={key:2,class:"edit-info"},_hoisted_8$c={class:"char-count"};function _sfc_render$c(t,e,n,i,r,s){return openBlock(),createElementBlock("div",{class:normalizeClass(["inline-nickname-editor",{editable:n.editable,editing:i.editing,[n.playerColor||"neutral"]:!0}])},[i.editing?(openBlock(),createElementBlock("div",_hoisted_4$c,[createBaseVNode("span",{class:normalizeClass(["stone-icon",n.playerColor])},null,2),withDirectives(createBaseVNode("input",{ref:"inputRef","onUpdate:modelValue":e[0]||(e[0]=o=>i.editValue=o),type:"text",class:normalizeClass(["nickname-input",{error:i.hasError}]),maxlength:20,title:i.inputTooltip,onKeydown:[withKeys(i.confirmEdit,["enter"]),withKeys(i.cancelEdit,["esc"])],onBlur:i.confirmEdit},null,42,_hoisted_5$c),[[vModelText,i.editValue]]),i.hasError?(openBlock(),createElementBlock("span",{key:0,class:"error-icon",title:i.errorMessage},"⚠️",8,_hoisted_6$c)):createCommentVNode("",!0)])):(openBlock(),createElementBlock("div",{key:0,class:"nickname-display",title:n.editable?"Click to edit nickname":"",onClick:i.startEdit},[createBaseVNode("span",{class:normalizeClass(["stone-icon",n.playerColor])},null,2),createBaseVNode("span",_hoisted_2$c,toDisplayString(i.displayNickname),1),n.editable?(openBlock(),createElementBlock("span",_hoisted_3$c,"✏️")):createCommentVNode("",!0)],8,_hoisted_1$c)),i.editing?(openBlock(),createElementBlock("div",_hoisted_7$c,[e[1]||(e[1]=createBaseVNode("span",{class:"help-text"},"Enter to save • Esc to cancel",-1)),createBaseVNode("span",_hoisted_8$c,toDisplayString(i.editValue.length)+"/20",1)])):createCommentVNode("",!0)],2)}const InlineNicknameEditor=_export_sfc(_sfc_main$c,[["render",_sfc_render$c],["__scopeId","data-v-c3b5c618"],["__file","/home/camus/work/trigo/trigo-web/app/src/components/InlineNicknameEditor.vue"]]),_sfc_main$b=defineComponent({__name:"RoomSelector",props:{currentRoom:{type:[String,null],required:!0},rooms:{type:Array,required:!0},loading:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1}},emits:["create","select"],setup(t,{expose:e,emit:n}){e();const i=t,r=n,s=ref(!1),o=ref(null),a=computed(()=>[...i.rooms].sort((y,v)=>y.id===i.currentRoom?-1:v.id===i.currentRoom?1:y.status==="waiting"&&v.status!=="waiting"?-1:y.status!=="waiting"&&v.status==="waiting"?1:y.playerCount!==v.playerCount?y.playerCount-v.playerCount:new Date(v.createdAt).getTime()-new Date(y.createdAt).getTime())),l=()=>{i.disabled||(s.value=!s.value)},c=()=>{s.value=!1,r("create")},u=y=>{y.isFull&&y.id!==i.currentRoom||(s.value=!1,r("select",y.id))},d=y=>y.playerCount===0?"empty":y.playerCount===1?"partial":"full",f=y=>y.isFull?"Full":y.status==="waiting"?"Waiting":y.status==="playing"?"Playing":y.status,m=y=>{o.value&&!o.value.contains(y.target)&&(s.value=!1)};onMounted(()=>{document.addEventListener("click",m)}),onUnmounted(()=>{document.removeEventListener("click",m)});const b={props:i,emit:r,isOpen:s,selectorRef:o,sortedRooms:a,toggleDropdown:l,handleCreate:c,handleSelect:u,playerCountClass:d,formatStatus:f,handleClickOutside:m};return Object.defineProperty(b,"__isScriptSetup",{enumerable:!1,value:!0}),b}}),_hoisted_1$b={class:"room-selector",ref:"selectorRef"},_hoisted_2$b={class:"selected-room"},_hoisted_3$b={key:0,class:"room-display"},_hoisted_4$b={key:1,class:"placeholder"},_hoisted_5$b={class:"dropdown-arrow"},_hoisted_6$b={key:0,class:"dropdown-menu"},_hoisted_7$b={key:0,class:"dropdown-divider"},_hoisted_8$b={class:"room-list"},_hoisted_9$b={key:0,class:"loading-state"},_hoisted_10$b={key:1,class:"empty-state"},_hoisted_11$8=["onClick"],_hoisted_12$8={class:"room-id"};function _sfc_render$b(t,e,n,i,r,s){return openBlock(),createElementBlock("div",_hoisted_1$b,[createBaseVNode("div",{class:normalizeClass(["dropdown-container",{disabled:n.disabled}]),onClick:i.toggleDropdown},[createBaseVNode("div",_hoisted_2$b,[n.currentRoom?(openBlock(),createElementBlock("span",_hoisted_3$b,toDisplayString(n.currentRoom),1)):(openBlock(),createElementBlock("span",_hoisted_4$b,"Select room..."))]),createBaseVNode("span",_hoisted_5$b,toDisplayString(i.isOpen?"▲":"▼"),1)],2),i.isOpen?(openBlock(),createElementBlock("div",_hoisted_6$b,[createBaseVNode("div",{class:"dropdown-item create-room",onClick:i.handleCreate},[...e[0]||(e[0]=[createBaseVNode("span",{class:"item-icon"},"➕",-1),createBaseVNode("span",{class:"item-text"},"Create New Room",-1)])]),n.rooms.length>0?(openBlock(),createElementBlock("div",_hoisted_7$b)):createCommentVNode("",!0),createBaseVNode("div",_hoisted_8$b,[n.loading?(openBlock(),createElementBlock("div",_hoisted_9$b," Loading rooms... ")):n.rooms.length===0?(openBlock(),createElementBlock("div",_hoisted_10$b," No active rooms ")):(openBlock(!0),createElementBlock(Fragment,{key:2},renderList(i.sortedRooms,o=>(openBlock(),createElementBlock("div",{key:o.id,class:normalizeClass(["dropdown-item room-item",{"is-current":o.id===n.currentRoom,"is-full":o.isFull}]),onClick:a=>i.handleSelect(o)},[createBaseVNode("span",_hoisted_12$8,toDisplayString(o.id),1),createBaseVNode("span",{class:normalizeClass(["player-count",i.playerCountClass(o)])},toDisplayString(o.playerCount)+"/2 ",3),createBaseVNode("span",{class:normalizeClass(["room-status-badge",o.status])},toDisplayString(i.formatStatus(o)),3)],10,_hoisted_11$8))),128))])])):createCommentVNode("",!0)],512)}const RoomSelector=_export_sfc(_sfc_main$b,[["render",_sfc_render$b],["__scopeId","data-v-99ecebab"],["__file","/home/camus/work/trigo/trigo-web/app/src/components/RoomSelector.vue"]]),_sfc_main$a=defineComponent({__name:"TrigoView",setup(t,{expose:e}){e();const n=useRoute(),i=computed(()=>n.meta.mode||"single"),r=useGameStore(),{game:s,currentPlayer:o,moveHistory:a,currentMoveIndex:l,capturedStones:c,gameStatus:u,boardShape:d,moveCount:f,isGameActive:m,passCount:b}=storeToRefs(r),y=usePlayerStore(),v=useSocket(),{getRoomIdFromHash:_,updateHash:x,clearHash:w,isValidRoomId:M}=useRoomHash(),C=ref(!1),$=ref([]),R=ref(!1),D=G=>{const Ce=G.split(/[^\d]+/).filter(Boolean).map(Number);return{x:Ce[0]||5,y:Ce[1]||5,z:Ce[2]||5}},A=G=>`${G.x}*${G.y}*${G.z}`,k=G=>{const Ce=d.value;return encodeAb0yz([G.x,G.y,G.z],[Ce.x,Ce.y,Ce.z])},Y=useTrigoAgent(),{isReady:ne,isThinking:q,error:fe,lastMoveTime:me}=Y,ie=()=>{const G=storage.getString(StorageKey.AI_PLAYER_COLOR);return G==="black"||G==="white"?G:"white"},Q=ref(ie()),H=computed(()=>Q.value==="white"?"black":"white"),de=()=>{const G=storage.getString("vs-people-color-preference");return G==="black"||G==="white"?G:"black"},se=ref(de());watch(se,G=>{storage.setString("vs-people-color-preference",G)});const V=ref(null),ce=ref(0),Ie=ref(0),Xe=ref(!0),zt=ref(A(d.value)),Tn=ref(!1),X=ref(!1),Fe=ref({visible:!1,groupSize:0,liberties:0}),_t=ref(""),Qt=ref("idle"),Yt=ref("");let ge=null;const U=ref(null),F=ref(null);let Z=null,$e=null;const Be=computed(()=>m.value),Ge=computed(()=>{const G=[];for(let Ce=0;Ce<a.value.length;Ce+=2){const Nt=a.value[Ce],bt=a.value[Ce+1]||null;G.push({black:Nt,white:bt,blackIndex:Ce+1,whiteIndex:bt?Ce+2:null})}return G}),gt=computed(()=>{const G=D(zt.value),Ce=d.value;return G.x!==Ce.x||G.y!==Ce.y||G.z!==Ce.z}),mt=computed(()=>{var G;return((G=r.game)==null?void 0:G.toTGN({application:"Trigo Demo v1.0",date:new Date().toISOString().split("T")[0].replace(/-/g,".")}))||""}),It=computed(()=>Qt.value==="valid"),At=computed(()=>Qt.value==="idle"?"idle":Qt.value==="valid"?"valid":"invalid"),z=computed(()=>Qt.value==="idle"?"Ready to validate":Qt.value==="valid"?"✓ Valid TGN":`✗ ${Yt.value}`),O=()=>{ge&&clearTimeout(ge),Qt.value="idle",ge=setTimeout(()=>{const G=validateTGN(_t.value);G.valid?(Qt.value="valid",Yt.value=""):(Qt.value="invalid",Yt.value=G.error||"Invalid TGN format")},300)},pe=()=>{if(It.value)try{const G=TrigoGameFrontend.fromTGN(_t.value);r.game=G,r.saveToSessionStorage(),Z&&(Z.setBoardShape(G.getShape()),Zt()),X.value=!1}catch(G){console.error("Failed to apply TGN:",G),Qt.value="invalid",Yt.value=G instanceof Error?G.message:"Failed to apply TGN"}},st=async()=>{if(!(!ne.value||q.value||!Be.value))try{console.log("[TrigoView] Generating AI move...");const G=await Y.generateMove(r.game);if(G)if(G.isPass)console.log("[TrigoView] AI chose to pass"),r.pass().success?console.log(`[TrigoView] AI pass applied successfully (${me.value.toFixed(0)}ms)`):console.error("[TrigoView] Failed to apply AI pass");else{console.log(`[TrigoView] AI suggests move at (${G.x}, ${G.y}, ${G.z})`);const Ce=r.makeMove(G.x,G.y,G.z);if(Ce.success&&Z){const Nt=r.opponentPlayer;Z.addStone(G.x,G.y,G.z,Nt),Ce.capturedPositions&&Ce.capturedPositions.length>0&&(Ce.capturedPositions.forEach(bt=>{Z.removeStone(bt.x,bt.y,bt.z)}),console.log(`[TrigoView] AI captured ${Ce.capturedPositions.length} stone(s)`)),console.log(`[TrigoView] AI move applied successfully (${me.value.toFixed(0)}ms)`)}}else console.log("[TrigoView] AI returned no move")}catch(G){console.error("[TrigoView] Failed to generate AI move:",G)}},dt=()=>{if(!Z)return;const G=r.getAllStones();for(const Ce of G)Z.addStone(Ce.x,Ce.y,Ce.z,Ce.color);console.log(`[TrigoView] Synced viewport with ${G.length} stones`)},Et=async(G,Ce,Nt)=>{if(!Be.value)return;if(i.value==="vs-people"){if(!Nn.value){console.log("[TrigoView] Not your turn, ignoring click");return}console.log(`[TrigoView] Emitting makeMove: (${G}, ${Ce}, ${Nt})`),v.makeMove(G,Ce,Nt);return}const bt=r.makeMove(G,Ce,Nt);if(bt.success&&Z){const xn=r.opponentPlayer;Z.addStone(G,Ce,Nt,xn),bt.capturedPositions&&bt.capturedPositions.length>0&&(bt.capturedPositions.forEach(Pn=>{Z.removeStone(Pn.x,Pn.y,Pn.z)}),console.log(`Captured ${bt.capturedPositions.length} stone(s)`)),Z.hideDomainCubes(),Tn.value=!1,i.value==="vs-ai"&&ne.value&&o.value===Q.value&&setTimeout(()=>{st()},100)}},_n=(G,Ce,Nt)=>{G!==null&&Ce!==null&&Nt!==null?V.value=`(${G}, ${Ce}, ${Nt})`:V.value=null},Vt=(G,Ce,Nt)=>{const bt={x:G,y:Ce,z:Nt},xn=o.value==="black"?StoneType.BLACK:StoneType.WHITE;return validateMove(bt,xn,r.board,d.value,r.lastCapturedPositions).valid},ze=(G,Ce)=>{G>0?Fe.value={visible:!0,groupSize:G,liberties:Ce}:Fe.value={visible:!1,groupSize:0,liberties:0}},oe=()=>{Q.value=Q.value==="white"?"black":"white",storage.setString(StorageKey.AI_PLAYER_COLOR,Q.value),console.log(`[TrigoView] AI color swapped to: ${Q.value}`),Be.value&&ne.value&&o.value===Q.value&&setTimeout(()=>{st()},100)},yt=()=>{const G=D(zt.value);r.initializeGame(G),r.startGame(),Z&&(Z.setBoardShape(G),Z.clearBoard(),Z.setGameActive(!0),Z.hideDomainCubes()),ce.value=0,Ie.value=0,Tn.value=!1,console.log(`Starting new game with board shape ${G.x}×${G.y}×${G.z}`),i.value==="vs-ai"&&Q.value==="black"&&ne.value&&setTimeout(()=>{st()},100)},wn=()=>{if(!confirm("Reset the game? This will clear all moves."))return;const G=D(zt.value),Ce=y.playerColor!==se.value;v.socket.emit("resetGame",{boardShape:G,swapColors:Ce},Nt=>{Nt.success?console.log("Multiplayer game reset successfully"):(console.error("Reset failed:",Nt.error),alert(`Failed to reset game: ${Nt.error||"Unknown error"}`))})},bn=()=>{if(i.value==="vs-people"){if(!Nn.value){console.log("[TrigoView] Not your turn, cannot pass");return}console.log("[TrigoView] Emitting pass"),v.pass();return}const G=o.value;r.pass()&&console.log(`${G} passed (Pass count: ${r.passCount})`)},vn=()=>{confirm(`Are you sure ${o.value} wants to resign?
|
| 6483 |
|
| 6484 |
+
This will end the game immediately.`)&&(i.value==="vs-people"?v.resign():r.resign()&&An())},An=()=>{const G=r.gameResult;if(!G)return;let Ce="";if(G.reason==="resignation")Ce=`${G.winner==="black"?"Black":"White"} wins by resignation!
|
| 6485 |
|
| 6486 |
Game continues for analysis.`;else if(G.reason==="double-pass"){const Nt=r.computeTerritory(),bt=Nt.black+c.value.black,xn=Nt.white+c.value.white;ce.value=bt,Ie.value=xn,bt>xn?Ce=`Black wins by ${bt-xn} points!
|
| 6487 |
|
trigo-web/app/dist/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
<title>Trigo - 3D Go Game</title>
|
| 8 |
-
<script type="module" crossorigin src="/assets/index
|
| 9 |
<link rel="stylesheet" crossorigin href="/assets/index-Siwlapuk.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
|
|
|
| 5 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
<title>Trigo - 3D Go Game</title>
|
| 8 |
+
<script type="module" crossorigin src="/assets/index-BucvvkZ4.js"></script>
|
| 9 |
<link rel="stylesheet" crossorigin href="/assets/index-Siwlapuk.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
trigo-web/app/src/views/TrigoView.vue
CHANGED
|
@@ -843,11 +843,16 @@
|
|
| 843 |
|
| 844 |
if (!confirmed) return;
|
| 845 |
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
};
|
| 852 |
|
| 853 |
const showGameResult = () => {
|
|
|
|
| 843 |
|
| 844 |
if (!confirmed) return;
|
| 845 |
|
| 846 |
+
if (gameMode.value === "vs-people") {
|
| 847 |
+
// Multiplayer: emit resign to server
|
| 848 |
+
socketApi.resign();
|
| 849 |
+
} else {
|
| 850 |
+
// Single player: local resign
|
| 851 |
+
const success = gameStore.resign();
|
| 852 |
+
if (success) {
|
| 853 |
+
showGameResult();
|
| 854 |
+
}
|
| 855 |
+
}
|
| 856 |
};
|
| 857 |
|
| 858 |
const showGameResult = () => {
|