1 line
13 KiB
HTML
1 line
13 KiB
HTML
<!doctypehtml><html lang="en"><meta charset="UTF-8"><meta name="viewport"content="width=device-width,initial-scale=1"><title>HEX-A-ROW! 5-in-line madness! 👾 | cffy.in</title><script src="https://cdn.tailwindcss.com"></script><style>.font-impact{font-family:Impact,Haettenschweiler,'Arial Narrow Bold',sans-serif}.font-comic{font-family:"Comic Sans MS","Comic Sans",cursive}body::-webkit-scrollbar{display:none}body{-ms-overflow-style:none;scrollbar-width:none}.hex{fill:rgba(255,255,255,.7);stroke:#0e7490;stroke-width:3;transition:fill .2s ease-out,transform .1s ease-out;cursor:pointer}.hex:hover{fill:#a5f3fc;transform:scale(1.08)}.hex.occupied{cursor:default;pointer-events:none}.player1-stone{fill:#84cc16;stroke:#365314;stroke-width:2}.player2-stone{fill:#ec4899;stroke:#831843;stroke-width:2}.hex.winning-hex{stroke:#d97706;stroke-width:5;animation:pulse-win 1s infinite}@keyframes pulse-win{0%,100%{stroke-width:5}50%{stroke-width:10}}</style><body class="antialiased font-comic text-gray-800"><nav class="bg-white/70 backdrop-blur-sm border-b-2 border-amber-800 p-3 sticky top-0 z-20"><div class="max-w-6xl mx-auto flex justify-between items-center font-impact text-amber-900"><a href="index.html"class="text-2xl font-black hover:text-amber-700 transition-colors">cffyin</a><div class="flex gap-6 text-lg items-center"><a href="index.html"class="hover:underline">Home</a> <a href="german.html"class="hover:underline">German Trainer</a> <a href="game.html"class="hover:underline">Hex-a-Row</a> <a href="snapmark-app/index.html"class="hover:underline">SnapMark</a></div></div></nav><div class="min-h-screen bg-amber-50 relative overflow-hidden"><div style="background-image:radial-gradient(circle,#000 1px,transparent 1px);background-size:20px 20px"class="absolute inset-0 opacity-10"></div><div class="absolute top-10 left-[5%] text-6xl opacity-20 animate-bounce">🎲</div><div class="absolute bottom-20 right-[10%] text-8xl opacity-20 animate-pulse">👾</div><div class="absolute top-1/3 right-[5%] text-7xl opacity-20 animate-bounce"style="animation-duration:5s">🎮</div><div class="absolute bottom-1/4 left-[15%] text-6xl opacity-20 animate-pulse">🤯</div><div class="relative z-10 p-4 md:p-8"><header class="text-center mb-8"><h1 class="font-impact text-7xl md:text-8xl font-black text-cyan-900 transform -rotate-1 inline-block"style="text-shadow:6px 6px 0 rgba(251,191,36,.5)">👾 HEX-A-ROW! 👾</h1><p class="text-xl mt-2 text-amber-800">The classic 5-in-line game, but twisty! 🌀</header><div class="flex flex-col lg:flex-row justify-center items-center lg:items-start gap-8"><div id="settings-panel"class="bg-lime-400 border-4 border-lime-600 shadow-lg p-6 w-full max-w-sm transform rotate-1 transition-all duration-300 hover:rotate-0 hover:shadow-2xl"><h2 class="font-impact text-4xl text-lime-900 mb-4"style="text-shadow:2px 2px 0 rgba(255,255,255,.5)">🎮 GAME SETTINGS</h2><div class="space-y-4 text-lg"><div><label for="mode"class="block font-bold mb-2 text-lime-900">Select Mode:</label> <select id="mode"class="w-full p-3 border-4 border-lime-600 bg-lime-50 rounded-none focus:ring-2 focus:ring-pink-400 focus:border-pink-400"><option value="pvc">Player vs. Computer 🤖<option value="pvp">Player vs. Player 🧑🤝🧑</select></div><div id="difficulty-wrapper"><label for="difficulty"class="block font-bold mb-2 text-lime-900">Computer Skillz:</label> <select id="difficulty"class="w-full p-3 border-4 border-lime-600 bg-lime-50 rounded-none focus:ring-2 focus:ring-pink-400 focus:border-pink-400"><option value="easy">Easy Peasy 🍋<option value="medium"selected="selected">Getting Tricky 🤔<option value="hard">Nightmare Mode 🔥</select></div><div class="border-b-4 border-dashed border-lime-800 my-6"></div><div id="game-status"class="bg-amber-100 p-4 border-4 border-amber-800 text-center"><p class="text-xl font-bold text-amber-900">Waiting to start...</div><button id="restart-btn"class="w-full mt-4 py-3 px-4 bg-pink-400 text-pink-900 border-4 border-pink-600 font-impact text-2xl font-black hover:bg-pink-300 transition-colors">RESTART GAME</button></div></div><div id="board-container"class="bg-cyan-400 border-4 border-cyan-600 shadow-lg p-4 transform -rotate-1 transition-all duration-300 hover:rotate-0"><div id="svg-container"class="relative"></div></div></div></div><div id="game-over-modal"class="hidden fixed inset-0 bg-black bg-opacity-60 z-50 items-center justify-center"><div class="bg-amber-100 border-8 border-amber-900 p-10 shadow-2xl transform -rotate-2 max-w-lg text-center"><h2 id="modal-title"class="font-impact text-6xl text-amber-900 mb-6"style="text-shadow:4px 4px 0 rgba(251,191,36,.8)">GAME OVER!</h2><p id="modal-message"class="text-2xl mb-8">Player 1 Wins! 🎉🎉🎉</p><button id="modal-close-btn"class="py-3 px-8 bg-lime-400 text-lime-900 border-4 border-lime-600 font-impact text-2xl font-black hover:bg-lime-300 transition-colors">PLAY AGAIN?</button></div></div></div><script>let BOARD_SIZE=6,MAX_COORD=BOARD_SIZE-1,HEX_SIZE=25,WIN_LENGTH=5,TOTAL_CELLS=3*BOARD_SIZE*(BOARD_SIZE-1)+1,board=new Map,currentPlayer=1,gameOver=!1,gameMode="pvc",difficulty="medium",aiIsThinking=!1,svgContainer=document.getElementById("svg-container"),gameStatusEl=document.getElementById("game-status"),modeSelect=document.getElementById("mode"),difficultySelect=document.getElementById("difficulty"),difficultyWrapper=document.getElementById("difficulty-wrapper"),restartBtn=document.getElementById("restart-btn"),gameOverModal=document.getElementById("game-over-modal"),modalTitle=document.getElementById("modal-title"),modalMessage=document.getElementById("modal-message"),modalCloseBtn=document.getElementById("modal-close-btn");function axialToPixel(e,a){return{x:HEX_SIZE*(Math.sqrt(3)*e+Math.sqrt(3)/2*a),y:HEX_SIZE*(1.5*a)}}function getHexCorners(a){var t=[];for(let e=0;e<6;e++){var r=60*e-30,r=Math.PI/180*r;t.push({x:a.x+HEX_SIZE*Math.cos(r),y:a.y+HEX_SIZE*Math.sin(r)})}return t}function isInBounds(e,a){return Math.max(Math.abs(e),Math.abs(-e-a),Math.abs(a))<=MAX_COORD}function createSVGElement(e,a){var t,r=document.createElementNS("http://www.w3.org/2000/svg",e);for(t in a)r.setAttribute(t,a[t]);return r}let boardOffsetX=0,boardOffsetY=0;function generateBoardCoordinates(){var t=[];for(let a=-MAX_COORD;a<=MAX_COORD;a++){var r=Math.max(-MAX_COORD,-a-MAX_COORD),n=Math.min(MAX_COORD,-a+MAX_COORD);for(let e=r;e<=n;e++)t.push({q:a,r:e})}return t}function drawBoard(){svgContainer.innerHTML="";var e=generateBoardCoordinates();let t=1/0,r=-1/0,n=1/0,i=-1/0;e.forEach(({q:e,r:a})=>{e=axialToPixel(e,a);t=Math.min(t,e.x),r=Math.max(r,e.x),n=Math.min(n,e.y),i=Math.max(i,e.y)});var a=r-t+HEX_SIZE*Math.sqrt(3),o=i-n+2*HEX_SIZE,a=(boardOffsetX=-t+HEX_SIZE*Math.sqrt(3)/2,boardOffsetY=-n+HEX_SIZE,createSVGElement("svg",{width:a,height:o,viewBox:`0 0 ${a} `+o}));let l=createSVGElement("g",{});a.appendChild(l);o=createSVGElement("g",{});a.appendChild(o),e.forEach(({q:e,r:a})=>{var t=axialToPixel(e,a);t.x+=boardOffsetX,t.y+=boardOffsetY;let r=createSVGElement("polygon",{points:getHexCorners(t).map(e=>e.x+","+e.y).join(" "),class:"hex","data-q":e,"data-r":a});r.addEventListener("click",()=>handleHexClick(e,a,r)),l.appendChild(r)}),svgContainer.appendChild(a)}function drawStone(e,a,t){var r=svgContainer.querySelector("svg");r&&((e=axialToPixel(e,a)).x+=boardOffsetX,e.y+=boardOffsetY,a=createSVGElement("circle",{cx:e.x,cy:e.y,r:.7*HEX_SIZE,class:1===t?"player1-stone":"player2-stone"}),r.children[1].appendChild(a))}function initializeGame(){board.clear(),currentPlayer=1,gameOver=!1,aiIsThinking=!1,updateSettings(),drawBoard(),updateStatus(),hideModal()}function handleHexClick(e,a,t){var r;gameOver||aiIsThinking||"pvc"===gameMode&&2===currentPlayer||(r=e+","+a,board.has(r))||(makeMove(e,a,currentPlayer,t),gameOver)||"pvc"!==gameMode||2!==currentPlayer||triggerAIMove()}function triggerAIMove(){aiIsThinking=!0,updateStatus("🤖 AI is thinking..."),setTimeout(aiMove,500)}function makeMove(e,a,t,r=null){var n=e+","+a,n=(board.set(n,t),(r=r||svgContainer.querySelector(`[data-q="${e}"][data-r="${a}"]`))&&r.classList.add("occupied"),drawStone(e,a,t),checkWin(e,a,t));n?endGame(t,n):board.size===TOTAL_CELLS?endGame(0):(currentPlayer=3-currentPlayer,aiIsThinking||updateStatus())}function checkWin(r,n,i){var e;for(e of[[{q:1,r:0},{q:-1,r:0}],[{q:0,r:1},{q:0,r:-1}],[{q:1,r:-1},{q:-1,r:1}]]){var o,l=[{q:r,r:n}];let t=1;for(o of e){let e=r+o.q,a=n+o.r;for(;isInBounds(e,a);){var d=e+","+a;if(board.get(d)!==i)break;l.push({q:e,r:a}),t++,e+=o.q,a+=o.r}}if(t>=WIN_LENGTH)return l}return null}function endGame(e,a=[]){gameOver=!0,aiIsThinking=!1,0===e?(updateStatus("It's a DRAW! 🤝"),showModal("IT'S A DRAW!","The board is full. Good game! 🤝")):(updateStatus(`🎉 ${e=getPlayerName(e)} WINS! 🎉`),highlightWinningLine(a),showModal("VICTORY! 🏆",e+" connected 5! Amazing! 🎉🎉🎉"))}function highlightWinningLine(e){for(var{q:a,r:t}of e){a=svgContainer.querySelector(`[data-q="${a}"][data-r="${t}"]`);a&&a.classList.add("winning-hex")}}function updateSettings(){gameMode=modeSelect.value,difficulty=difficultySelect.value,difficultyWrapper.style.display="pvc"===gameMode?"block":"none"}function getPlayerName(e){return 1===e?"Player 1 (🟢)":2===e?"pvc"===gameMode?"Computer (🤖)":"Player 2 (💖)":""}function updateStatus(e=null){e?gameStatusEl.innerHTML=`<p class="text-xl font-bold text-amber-900">${e}</p>`:gameOver||(e=getPlayerName(currentPlayer),gameStatusEl.innerHTML=`<p class="text-xl font-bold text-amber-900">It's ${e}'s turn!</p>`)}function showModal(e,a){modalTitle.textContent=e,modalMessage.textContent=a,gameOverModal.classList.remove("hidden"),gameOverModal.classList.add("flex")}function hideModal(){gameOverModal.classList.add("hidden"),gameOverModal.classList.remove("flex")}function getAvailableMoves(){var e,a,t=[];for({q:e,r:a}of generateBoardCoordinates())board.has(e+","+a)||t.push({q:e,r:a});return t}function aiEasy(){var e=getAvailableMoves();return 0===e.length?null:e[Math.floor(Math.random()*e.length)]}function aiMedium(e=2){var a,t,r,n=3-e,i=getAvailableMoves();if(0===i.length)return null;for(a of i)if(checkWinSimulation(a.q,a.r,e))return a;for(t of i)if(checkWinSimulation(t.q,t.r,n))return t;let o=i[0],l=-1/0;for(r of i){var d=evaluateMoveHeuristic(r.q,r.r,e);(d>l||d===l&&.5<Math.random())&&(l=d,o=r)}return o}function aiHard(){var e=minimax(2,2,-1/0,1/0);return e.move||aiMedium(2)}function minimax(a,e,t,r){var n=evaluateBoardForMinimax(2);if(0===a||9e5<Math.abs(n))return{score:n,move:null};n=getAvailableMoves();if(0===n.length)return{score:0,move:null};n.sort((e,a)=>Math.max(Math.abs(e.q),Math.abs(-e.q-e.r),Math.abs(e.r))-Math.max(Math.abs(a.q),Math.abs(-a.q-a.r),Math.abs(a.r)));let i=null;if(2===e){let e=-1/0;for(var o of n){board.set(o.q+","+o.r,2);var l=minimax(a-1,1,t,r).score;if(board.delete(o.q+","+o.r),l>e&&(e=l,i=o),r<=(t=Math.max(t,l)))break}return{score:e,move:i}}{let e=1/0;for(var d of n){board.set(d.q+","+d.r,1);var s=minimax(a-1,2,t,r).score;if(board.delete(d.q+","+d.r),s<e&&(e=s,i=d),(r=Math.min(r,s))<=t)break}return{score:e,move:i}}}function checkWinSimulation(e,a,t){var r=e+","+a;if(board.has(r))return!1;board.set(r,t);e=checkWin(e,a,t);return board.delete(r),null!==e}function evaluateMoveHeuristic(e,a,t){var r=0,n=3-t,i=Math.max(Math.abs(e),Math.abs(-e-a),Math.abs(a));return(r+=5*(MAX_COORD-i))+10*analyzeThreats(e,a,t)+8*analyzeThreats(e,a,n)}function analyzeThreats(i,o,l){let e=0;var a,t=i+","+o;if(board.has(t))return 0;board.set(t,l);for(a of[[{q:1,r:0},{q:-1,r:0}],[{q:0,r:1},{q:0,r:-1}],[{q:1,r:-1},{q:-1,r:1}]]){let r=1,n=0;for(var d of a){let e=i+d.q,a=o+d.r,t=0;for(;isInBounds(e,a);){var s=e+","+a;if(board.get(s)!==l){board.has(s)||n++;break}t++,e+=d.q,a+=d.r}r+=t}5<=r?e+=1e4:4===r?2===n?e+=500:1===n&&(e+=100):3===r?2===n?e+=50:1===n&&(e+=10):2===r&&2===n&&(e+=5)}return board.delete(t),e}function evaluateBoardForMinimax(e){var a=3-e;let t=0;var r,n={FIVE:1e6,OPEN_FOUR:5e4,FOUR:5e3,OPEN_THREE:2e3,THREE:500,OPEN_TWO:100,TWO:20},i=countPatterns(e),o=countPatterns(a);if(0<o.FIVE)return-n.FIVE;if(0<i.FIVE)return n.FIVE;for(r in n)"FIVE"!==r&&(t=(t+=(i[r]||0)*n[r])-(o[r]||0)*n[r]*1.1);return t}function countPatterns(o){var e,a,l={},d=new Set,t=[{q:1,r:0},{q:0,r:1},{q:1,r:-1}];for([e,a]of board.entries())if(a===o){var[s,u]=e.split(",").map(Number);for(let i=0;i<3;i++){var c=t[i],m=s+`,${u},`+i;if(!d.has(m)){d.add(m);let e=1,a=s+c.q,t=u+c.r;for(;isInBounds(a,t)&&board.get(a+","+t)===o;)d.add(`${a},${t},`+i),e++,a+=c.q,t+=c.r;let r=0;isInBounds(a,t)&&!board.has(a+","+t)&&r++;var m=s-c.q,f=u-c.r;isInBounds(m,f)&&!board.has(m+","+f)&&r++;let n=null;5<=e?n="FIVE":4===e?n=2===r?"OPEN_FOUR":1===r?"FOUR":null:3===e?n=2===r?"OPEN_THREE":1===r?"THREE":null:2===e&&(n=2===r?"OPEN_TWO":1===r?"TWO":null),n&&(l[n]=(l[n]||0)+1)}}}return l}function aiMove(){if(!gameOver){let e;switch(difficulty){case"easy":e=aiEasy();break;case"medium":e=aiMedium();break;case"hard":e=aiHard();break;default:e=aiEasy()}e?makeMove(e.q,e.r,2):console.warn("AI failed to find a move."),aiIsThinking=!1,gameOver||updateStatus()}}modeSelect.addEventListener("change",()=>{0<board.size&&!confirm("Changing the mode will restart the game. Continue?")?modeSelect.value=gameMode:initializeGame()}),difficultySelect.addEventListener("change",()=>{0<board.size&&!confirm("Changing the difficulty will restart the game. Continue?")?difficultySelect.value=difficulty:initializeGame()}),restartBtn.addEventListener("click",initializeGame),modalCloseBtn.addEventListener("click",initializeGame),difficulty=difficultySelect.value,gameMode=modeSelect.value,initializeGame()</script> |