<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Multiplayer Game</title> <link rel="stylesheet" type="text/css" href="mflx.min.css"> <style type="text/css"> .text-with-shadow { text-shadow: -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000, 1px 1px 1px #000; color: white; } .div-shadow { box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; } .puzzleboard { display: grid; grid-template-columns: repeat(12, 1fr); /* 12 columns, equal width */ grid-template-rows: repeat(4, auto); /* 4 rows, height adjusts to content */ gap: 1px; /* Optional: spacing between grid items */ height: 205px; width: 500px; max-width: 100%; /* Optional: ensures grid fits within the container */ max-height: 100%; /* Optional: ensures the grid respects height limits */ } .grid-item { background-image: radial-gradient(circle,#21D375 ,green); /* For visualization */ border: 1px solid black; /* Optional: visual boundary for items */ text-align: center; padding: 1px; border-radius: 5px; } .grid-item-2 { background-color: white; /* For visualization */ border: 1px solid black; /* Optional: visual boundary for items */ text-align: center; padding: 1px; display: flex; justify-content: center; align-items: center; font-size:1.2rem; font-family:'sans'; font-weight:bold; border-radius: 5px; } .category { background-image: radial-gradient(circle,#1f57dd ,#0000ae); color: white; max-width: 500px; font-size:1.2rem; font-family:'sans'; font-weight:bold; border-radius: 5px; text-align: center; } .hidden { display: none; visibility: hidden; } /* Dialog styling */ dialog { border: none; border-radius: 8px; padding: 20px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); font-family: Arial, sans-serif; max-width: 300px; text-align: center; } dialog::backdrop { background-color: rgba(0, 0, 0, 0.5); } dialog h2 { margin-top: 0; font-size: 18px; } .nameInput { text-align: center; } </style> </head> <body> <!-- Dialog --> <dialog class="nameDialog"> <div class="flx(column) space-evenly"> <h2>Change Your Name?</h2> <label for="nameInput">Name:</label> <input type="text" class="nameInput is-round" style="padding: .25rem; "> <div class="mt-4 flx(wrap) space-evenly"> <button class="saveButton btn is-success is-round mr-1 div-shadow">save</button> <button class="closeDialogButton btn is-error is-round ml-1 div-shadow">close</button> </div> <div> </dialog> <button id="create-room">Create Room</button> <input id="join-code" placeholder="Enter room code"> <button id="join-room">Join Room</button> <div class="room-code"></div> <div class="flx(wrap) mb-2 players"></div> <div id="status"></div> <button id="start-game" style="display: none;">Start Game</button> <button id="spin-wheel" style="display: none;">Spin Wheel</button> <input id="guess-letter" placeholder="Guess a letter" style="display: none;"> <button id="guess-button" style="display: none;">Guess</button> <div class="puzzleboard mb-2"></div> <div class="category mb-5"></div> <div style="display:relative;"> <span style="position: absolute; top: 0; left:50%"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>arrow-down-bold</title><path d="M9,4H15V12H19.84L12,19.84L4.16,12H9V4Z" /></svg> </span> <canvas id="wheelCanvas" width="500" height="500"></canvas> </div> <script> //================general Purpose functions=========================================// function cryptoSecureShuffle(array) { const shuffledArray = [...array]; // Create a copy to avoid mutating the original array const n = shuffledArray.length; // Fisher-Yates shuffle for (let i = n - 1; i > 0; i--) { // Generate a cryptographically secure random index let randIndex; do { // Generate a random value in the range [0, 2^32) const randomBuffer = new Uint32Array(1); window.crypto.getRandomValues(randomBuffer); randIndex = randomBuffer[0] >>> 0; // Convert to a non-negative integer } while (randIndex >= Math.floor(2 ** 32 / (i + 1)) * (i + 1)); // Avoid bias // Compute the unbiased index randIndex %= (i + 1); // Swap the elements [shuffledArray[i], shuffledArray[randIndex]] = [shuffledArray[randIndex], shuffledArray[i]]; } return shuffledArray; } //====================making Name Tags/ assigning colors to names================// let nameColors = [ '#ff0000','#ff8700','#ffd300','#deff0a','#a1ff0a', '#0aff99','#0aefff','#147df5','#580aff','#be0aff' ] nameColors = cryptoSecureShuffle(nameColors); function assignColors(names, colors) { let result = []; let n = 0; // Keep track of name index let c = 0; // Keep track of color index if (names.length > colors.length) { // Assign colors cyclically while (n < names.length) { result.push({ name: names[n], color: colors[c] }); n++; c = (c + 1) % colors.length; // Cycle through the colors } } else { // Assign colors directly, keeping track of indices while (n < names.length) { result.push({ name: names[n], color: colors[c] }); n++; c++; } } return result; } //=========================================wheel==========================================// const wheel = [ 'lose a turn', 800, 350, 450, 700, 300, 600, 5000, 300, 600, 300, 500, 800, 550, 400, 300, 900, 500, 'spin again', 900, 'Bankrupt', 600, 400, 300 ]; const wheelSpinSpeedData = { '0':0.2523,//lose a turn '1':.2497,//800 '2':.2471,//350 '3':.2445,//450 '4':.2418,//450 '5':.2392,//300 '6':.2366,//600 '7':.2339,//5000 '8':.2313,//300 '9':.2287,//600 '10':.2261,//300 '11':.2235,//500 '12':.2208,//800 '13':.2182,//550 '14':.2156,//400 '15':.213,//300 '16':.2104,//900 '17':.2078,//500 '18':.2052,//spin again '19':.2026,//900 '20':.1999,//Bankrupt '21':.1973,//600 '22':.1947,//400 '23':.1921//300 }; const canvas = document.getElementById('wheelCanvas'); const ctx = canvas.getContext('2d'); const radius = canvas.width / 2; let rotation = 0 - (.283*6); //sets the zero index on top let spinning = false; let spinSpeed = 0; let img = new Image(); img.src = "arrow-down-bold.png"; img.onload = function() { ctx.drawImage(img, 200, -50, 100,100); } // Draw the wheel function drawWheel(drawResult = false) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.translate(radius, radius); ctx.rotate(rotation); const anglePerSegment = (2 * Math.PI) / wheel.length; wheel.forEach((value, i) => { const startAngle = i * anglePerSegment; const endAngle = startAngle + anglePerSegment; ctx.beginPath(); ctx.moveTo(0, 0); ctx.arc(0, 0, radius, startAngle, endAngle); ctx.closePath(); ctx.fillStyle = i % 2 === 0 ? '#ffdd99' : '#ffeecc'; ctx.fill(); ctx.stroke(); // Add text ctx.save(); ctx.translate( Math.cos(startAngle + anglePerSegment / 2) * radius * 0.7, Math.sin(startAngle + anglePerSegment / 2) * radius * 0.7 ); ctx.rotate(startAngle + anglePerSegment / 2 + Math.PI / 2 - 1.5); ctx.textAlign = 'center'; ctx.fillStyle = '#000'; ctx.font = '12px Arial'; ctx.fillText(String(i+","+value), 0, 0); ctx.restore(); }); ctx.restore(); ctx.drawImage(img, 200, -50, 100,100); } // Start spinning function startSpin(targetIndex) { console.log('targetIndex: ',targetIndex); let resultingSpeed = wheelSpinSpeedData[targetIndex]; console.log('resultingSpeed: ', resultingSpeed); if (spinning) return; rotation = 0 - (.283*6) spinning = true; const totalSpins = Math.floor(Math.random() * 3) + 3; // 3-6 full spins const anglePerSegment = (2 * Math.PI) / wheel.length; const targetAngle = targetIndex * anglePerSegment; console.log(anglePerSegment); let currentRotation = rotation; // Start from the current rotation spinSpeed = wheelSpinSpeedData[targetIndex]; // Initial speed function animate() { if ( spinSpeed < 0.001) { spinning = false; console.log(`Landed on: ${wheel[targetIndex]}`); return; } // console.log('currentRotation: ',currentRotation); // console.log('spinSpeed: ', spinSpeed); currentRotation += spinSpeed; spinSpeed *= 0.99; // Gradual slowdown rotation = currentRotation ; drawWheel(); requestAnimationFrame(animate); } animate(); } // Simulate WebSocket server response // function simulateServerResponse() { // const targetIndex = Math.floor(Math.random() * wheel.length); // Random result // startSpin(targetIndex); // } // Initial draw drawWheel(); // Trigger spin after 2 seconds //setTimeout(simulateServerResponse, 2000); //==================================end of Wheel=====================================// function drawPlayers(message) { console.log(message); let players = document.querySelector('.players'); //assignColors(names, colors) { let processedNametags = assignColors(message.clients,nameColors); const playersHtml = processedNametags.map(client => { if(client.name != window.you) { if (client.name === message.leaderName) { return `<span class='mr-2 btn is-round text-with-shadow div-shadow' style='background-color:${client.color};'>⭐ ${client.name} ⭐</span>`; // Highlight the leader with stars } else { return `<span class='mr-2 btn is-round text-with-shadow div-shadow' style='background-color:${client.color};'>${client.name}</span>`; } } else if (client.name === message.leaderName) { return `<span class='mr-2 btn is-round text-with-shadow div-shadow' style='background-color:${client.color};' onclick="changeName()">⭐ ${client.name} ⭐ (you)</span>`; // Highlight the leader with stars } else { return `<span class='mr-2 btn is-round text-with-shadow div-shadow' style='background-color:${client.color};' onclick="changeName()">${client.name} (you)</span>`; } }).join(""); // Combine all elements into a single string players.innerHTML = playersHtml; } function drawPuzzle(message, gridRows = 4, gridCols = 12) { //legend null = space, "" = white space to guess, "a" is a letter already solved for you. console.log(message); //wheel of fortune puzzles must fit within a 12x4 grid of letter spaces //first we draw the spaces let puzzle = message.puzzle.puzzle; let puzzleBoard = document.querySelector('.puzzleboard'); let resultingPuzzleBoard = []; //if the first entry of the puzzle is an array and not a string display the puzzle differently if (Array.isArray(message.puzzle.puzzle[0])) { console.log('first entry is an array in this puzzle...'); } else if (typeof message.puzzle.puzzle[0] == 'string') { console.log('first entry is a string in this puzzle...'); //if we have a single string we need to return it on the second line from the bottom resultingPuzzleBoard.push(Array(12).fill(null)); resultingPuzzleBoard.push(Array(12).fill(null)); const nextLine = Array(12).fill(null); const startIndex = Math.floor((12 - message.puzzle.puzzle.length) / 2); console.log(startIndex); for (let i = 0; i < message.puzzle.puzzle.length; i++) { nextLine[startIndex + i] = message.puzzle.puzzle[i]; } console.log(nextLine); resultingPuzzleBoard.push(nextLine); resultingPuzzleBoard.push(Array(12).fill(null)); console.log(resultingPuzzleBoard); //after we've drawn our data for our board,now its time to visualize it to the user for(let i of resultingPuzzleBoard) { for (let j in i) { const letter = i[j]; if (letter == null || letter == ' ') { let el = document.createElement('div'); el.classList.add('grid-item'); el.innerHTML = ' '; // el.style.height = '50px'; // el.style.width = '50px'; // el.style.border = 'solid' puzzleBoard.appendChild(el); } else if (letter != null && letter != ' ') { let el = document.createElement('div'); el.classList.add('grid-item-2'); el.innerText = letter; puzzleBoard.appendChild(el); } } } } else { console.error('error: ','message does not have correct datatype of string or array'); } //finally draw the category! document.querySelector('.category').classList.add('pt-2','pb-2'); document.querySelector('.category').innerHTML = `${message.puzzle.category}` } function changeName() { console.log('change name hit.'); const nameDialog = document.querySelector('.nameDialog'); const nameInput = document.querySelector('.nameInput'); const saveButton = document.querySelector('.saveButton'); const closeDialogButton = document.querySelector('.closeDialogButton'); nameDialog.showModal(); nameInput.value = window.you; closeDialogButton.addEventListener('click',() => {nameDialog.close()}); saveButton.addEventListener('click',() => { socket.send(JSON.stringify({ type: "change_name", deadName:window.you, newName:nameInput.value })); console.log('you changed your name.'); nameDialog.close(); }); } const socket = new WebSocket("ws://localhost:8080"); document.getElementById("create-room").onclick = function () { socket.send(JSON.stringify({ type: "create_room" })); this.style.display = 'none'; document.getElementById("join-room").style.display = 'none'; document.getElementById('join-code').style.display = 'none'; }; document.getElementById("join-room").onclick = () => { const roomCode = document.getElementById("join-code").value; socket.send(JSON.stringify({ type: "join_room", roomCode })); this.style.display = 'none'; document.getElementById("create-room").style.display = 'none'; document.getElementById('join-code').style.display = 'none'; }; document.getElementById("start-game").onclick = () => { socket.send(JSON.stringify({ type: "start_game" })); }; document.getElementById("spin-wheel").onclick = () => { socket.send(JSON.stringify({ type: "spin_wheel" })); }; document.getElementById("guess-button").onclick = () => { const letter = document.getElementById("guess-letter").value; socket.send(JSON.stringify({ type: "guess_letter", letter })); document.getElementById("guess-letter").value = ""; // Clear input }; socket.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === "room_created") { window.you = message.you; document.querySelector(".room-code").innerText =`Room Code: ${message.roomCode}`; document.getElementById("status").innerText = `Room created!`; drawPlayers(message); if (message.isLeader) { document.getElementById("start-game").style.display = "inline"; } } if (message.type === "changed_name_you") { window.you = message.you; console.log('server has changed your name!'); } if (message.type === "changed_name") { drawPlayers(message); console.log('server has changed your name!'); } if (message.type === "joined_room_you") { window.you = message.you; } if (message.type === "joined_room") { drawPlayers(message); const status = message.isLeader ? "You are the party leader!" : "You joined the room!"; // document.querySelector('.players').innerHtml += `<span>${}<span>` document.getElementById("status").innerText = `${status}`; document.querySelector(".room-code").innerText =`Room Code: ${message.roomCode}`; if (message.isLeader) { document.getElementById("start-game").style.display = "inline"; document.getElementById("spin-wheel").style.display = "inline"; document.getElementById("guess-letter").style.display = "inline"; document.getElementById("guess-button").style.display = "inline"; } } if (message.type === "game_started") { let result = drawPuzzle(message); console.log(result); document.getElementById("status").innerText = "The game has started!"; document.getElementById("start-game").style.display = "none"; document.getElementById("start-game").style.display = "inline"; document.getElementById("spin-wheel").style.display = "inline"; document.getElementById("guess-letter").style.display = "inline"; document.getElementById("guess-button").style.display = "inline"; } if (message.type === "spin_result") { console.log('spin result recieved'); document.getElementById("status").innerText = `Spin result: ${message.spinResult} points (${message.player})`; //find target index let targetIndexArr = wheel.entries(); let filterResultsArr = []; for(let x of targetIndexArr){ if (x[1] == message.spinResult) { filterResultsArr.push(x); } } console.log(filterResultsArr[0][0]); //if there is multiple entries where the spin exists pick a random index. if(filterResultsArr.length > 1) { startSpin(filterResultsArr[Math.floor(Math.random() * filterResultsArr.length)][0]); } else{ startSpin(filterResultsArr[0]); } } if (message.type === "guess_result") { const outcome = message.correct ? "correct" : "incorrect"; document.getElementById("status").innerText = `Guess '${message.letter}' was ${outcome} (${message.player})`; } if (message.type === "new_leader") { drawPlayers(message); document.getElementById("status").innerText = "You are now the party leader!"; } if (message.type === "error") { alert(message.message); } }; </script> </body> </html>