Code viewer for World: Competitive Hangman
// Hangman code ported from The Coding Artist
// https://codingartistweb.com/2022/05/hangman-game-with-javascript/
// Features added: User Chat using websockets, users guess the same word,
// if one user wins, the other users lose.
// Whoever guesses the word first is the winner.


document.write ( `

<h1>  </h1>

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hangman</title>
  </head>
  <body>
    <div class="container">
      <div id="categoryContainer"></div>
      <div id="charContainer" class="charContainer hide"></div>
      <div id="userInput"></div>
      <canvas id="canvas"></canvas>
      <div id="newGameContainer" class="newGame hide">
        <div id="resultMsg"></div>
        <button id="newGameButton">New Game</button>
      </div>
    </div>
  </body>
</html>

<div style="width:20vw; margin-top:20px; background-color:white;  border: 1px solid black; margin:20; padding: 20px;">
<h3> Send A Message! </h3>
<INPUT style="width:10vw;" id=me >
<button onclick="sendchat();"     class=ab-normbutton > Send </button> 
</div>

<div style="width:20vw; background-color:#ffffcc;  border: 1px solid black; margin:20; padding: 20px;">
<h3> Chat </h3>
<div id=txt style="max-height: 150px; overflow-y: scroll;"> </div>
</div>

<div style="width:20vw; background-color:#ffffcc;  border: 1px solid black; margin:20; padding: 20px;">
<h3> Users </h3>
<div id=userlist style="display: block;/* or inline-block */
  text-overflow: ellipsis;
  word-wrap: break-word;
  overflow: hidden;
  max-height: 3.6em;
  line-height: 1.8em;" id=userlist > </div>
</div>

<style>
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: "Verdana", sans-serif;
}
.container {
  font-size: 20px;
  width: 90vw;
  max-width: 34em;
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  padding: 3em;
  border-radius: 0.6em;
  box-shadow: 0 1.2em 2.4em rgba(111, 85, 0, 0.25);
}
#categoryContainer {
  text-align: center;
}
#categoryContainer div {
  width: 100%;
  display: flex;
  justify-content: space-between;
  margin: 1.2em 0 2.4em 0;
}
#categoryContainer button {
  padding: 0.6em 1.2em;
  border: 3px solid #000000;
  background-color: #ffffff;
  color: #000000;
  border-radius: 0.3em;
  text-transform: capitalize;
}
#categoryContainer button:disabled {
  border: 3px solid #f52027;
  color: #171616;
  background-color: #ff5257;
}
#categoryContainer button.active {
  background-color: #49f566;
  border: 3px solid #00ff2a;
  color: #000000;
}
.charContainer {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6em;
}
#charContainer button {
  height: 2.4em;
  width: 2.4em;
  border-radius: 0.3em;
  background-color: #ffffff;
}
.newGame {
  background-color: #ffffff;
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  border-radius: 0.6em;
}

h1 {
    text-align: center;
    padding-top: 200px
}

#userInput {
  display: flex;
  justify-content: center;
  font-size: 1.8em;
  margin: 0.6em 0 1.2em 0;
}
canvas {
  display: block;
  margin: auto;
  border: 1px solid #000000;
}
.hide {
  display: none;
}
#resultMsg h2 {
  font-size: 1.8em;
  text-align: center;
}
#resultMsg p {
  font-size: 1.25em;
  margin: 1em 0 2em 0;
}
#resultMsg span {
  font-weight: 600;
}
#newGameButton {
  font-size: 1.25em;
  padding: 0.5em 1em;
  background-color: #f4c531;
  border: 3px solid #000000;
  color: #000000;
  border-radius: 0.2em;
}
.winMsg {
  color: #39d78d;
}
.lossMsg {
  color: #fe5152;
}
</style>

` );

//Chat

AB.socketStart();

document.getElementById('me').onkeydown   = function(event) 	{ if (event.keyCode == 13)  sendchat(); };

function sendchat()
{
  var txtInput = $("#me").val();
  document.getElementById("me").value = ""
  if (txtInput === "")
  {
      document.getElementById("me").value = ""
  }
  else
  {
    $("#txt").append ('<p>' +  AB.myusername + ": " + txtInput + '\n' + '<p>');
    var elem = document.getElementById('txt');elem.scrollTop = elem.scrollHeight;
    var data = 
    {
        userid:     AB.myuserid,
        username:   AB.myusername,
        txt:        txtInput
    };
  
    AB.socketOut ( data ); 
  }
}

function userlink ( userid, username )
{
   if ( userid == "none" ) return ( "Anonymous" );
   else                    return ( "<a target='_blank' href='https://ancientbrain.com/user.php?userid=" + userid + "'>" + username + "</a>" );
}

AB.socketIn = function(data)
{
    var word = $(data).attr('word');
    var userid = $(data).attr('userid');
    var win = $(data).attr('win');
    var loss = $(data).attr('loss');

    console.log(data);
    if (typeof userid !== 'undefined' && userid !== false)
    {
        var userhtml = userlink ( data.userid, data.username );
        $("#txt").append ('<p>' +  userhtml + ": " + data.txt + '\n' + '<p>');
    }
    else if (typeof loss !== 'undefined' && loss !== false)
    {
        console.log("You win against: " + data.username);
        resultMsg.innerHTML = `<h2 class='win-msg'>You Win!!</h2><p>The word was <span>${word}</span></p>`;
        //block all buttons
        blocker();
    }
    else if (typeof win !== 'undefined' && win !== false)
    {
        console.log("You lost against: " + data.username);
        resultMsg.innerHTML = `<h2 class='lose-msg'>You Lose!!</h2><p>The word was <span>${word}</span></p>`;
        blocker();
    }
    else if (typeof word !== 'undefined' && word !== false)
    {
        var chosenWord = word; 
        let categoryButtons = document.querySelectorAll(".category");
  
        //If categoryValue matches the button innerText then highlight the button
        categoryButtons.forEach((button) => {
            if (button.innerText.toLowerCase() === data.categoryValue) {
            button.classList.add("active");
            }
            button.disabled = true;
        });

        //initially hide letters, clear previous word
        charContainer.classList.remove("hide");
        userInput.innerText = "";

        //replace every letter with span containing dash
        let displayHiddenWord = chosenWord.replace(/./g, '<span class="dashes">_</span>');

        //Display each element as span
        userInput.innerHTML = displayHiddenWord;
        
        getWord(chosenWord);
        data = null;
    }
    
    
};

AB.socketUserlist = function ( a ) 
{ 
    console.log ( "Got user list: " );
    console.log ( JSON.stringify ( a, null, ' ' ) );

    if ( a.length < 2 )  
    { 
        $("#userlist").html ( "<h3 style='color:red'> 0 users. </h3>"); 
        return; 
    }
    
    // else  
    var str = " <ol> ";
    for (var i = 0; i < a.length; i++) 
    {
        var userhtml = userlink ( a[i][0], a[i][1] );
        str = str + " <li> " + userhtml ;
    }
    $("#userlist").html ( str + " </ol> " );
};


//Initial References
const charContainer = document.getElementById("charContainer");
const categoryContainer = document.getElementById("categoryContainer");
const userInput = document.getElementById("userInput");
const newGameContainer = document.getElementById("newGameContainer");
const newGameButton = document.getElementById("newGameButton");
const canvas = document.getElementById("canvas");
const resultMsg = document.getElementById("resultMsg");

//Options values for buttons
let category = {
  food: [
    "Pizza",
    "Spaghetti",
    "Sandwich",
    "Cheeseburger",
    "Doughnut",
    "Steak",
    "Hamburger",
    "Noodles",
    "Biscuit",
    "Cheese",
    "Chips",
    "Soup",
    "Sausage",
  ],
  languages: [
      "Irish",
      "French",
      "Japanese",
      "Croatian",
      "Lithuanian",
      "Spanish",
      "German",
      "Portuguese",
      "Latvian",
      "Filipino",
      "Russian",
      "English",
      "Polish",
  ],
  sports: [
    "Football",
    "Basketball",
    "Baseball",
    "Archery",
    "Badminton",
    "Tennis",
    "Volleyball",
    "Swimming",
    "Handball",
  ],
  insects: [
      "Spider",
      "Beetle",
      "Mantis",
      "Stinkbug",
      "Ladybird",
      "Cockroach",
      "Mosquito",
      "Tarantula",
      "Wasp",
      "Butterfly",
      "Dragonfly"
      ]
};

//count
let winCount = 0;
let count = 0;

let chosenWord = "";
let categoryValue = null;

function getCategoryValue(value)
{
    categoryValue = value;
}

function getWord(word="")
{
    if (word === "")
    {
        chosenWord = "";
    }
    else
    {
        chosenWord = word;
    }
}


//Display Category buttons
const displayCategories = () => {
  categoryContainer.innerHTML = "<h3>Please Select A Category</h3>";
  let buttonCon = document.createElement("div");
  for (let value in category) {
    buttonCon.innerHTML += `<button class="category" onclick="generateWord('${value}')">${value}</button>`;
  }
  categoryContainer.appendChild(buttonCon);
};

//Block all the Buttons
const blocker = () => {
  let categoryButtons = document.querySelectorAll(".category");
  let charButtons = document.querySelectorAll(".char");
  //disable all options
  categoryButtons.forEach((button) => {
    button.disabled = true;
  });

  //disable all letters
  charButtons.forEach((button) => {
    button.disabled.true;
  });
  newGameContainer.classList.remove("hide");
};

//Word Generator
const generateWord = (categoryValue) => {
  let categoryButtons = document.querySelectorAll(".category");
  
  //If optionValur matches the button innerText then highlight the button
  categoryButtons.forEach((button) => {
    if (button.innerText.toLowerCase() === categoryValue) {
      button.classList.add("active");
    }
    button.disabled = true;
  });

  //initially hide letters, clear previous word
  charContainer.classList.remove("hide");
  userInput.innerText = "";

  let categoryArray = category[categoryValue];
  //choose random word
  if (chosenWord === "")
  {
      chosenWord = categoryArray[Math.floor(Math.random() * categoryArray.length)];
  }
  
  chosenWord = chosenWord.toUpperCase();

  //replace every letter with span containing dash
  let displayHiddenWord = chosenWord.replace(/./g, '<span class="dashes">_</span>');

  //Display each element as span
  userInput.innerHTML = displayHiddenWord;
  
  var data = 
  {
    word: chosenWord,
    categoryValue: categoryValue
  };
  
  AB.socketOut ( data );        // server gets this, and sends the data to all clients running this World
};

//Initial Function (Called when page loads/user presses new game)
const initializer = () => {
  winCount = 0;
  count = 0;

  //Initially erase all content and hide letteres and new game button
  userInput.innerHTML = "";
  categoryContainer.innerHTML = "";
  charContainer.classList.add("hide");
  newGameContainer.classList.add("hide");
  charContainer.innerHTML = "";

  //For creating letter buttons
  for (let i = 65; i < 91; i++) {
    let button = document.createElement("button");
    button.classList.add("char");
    
    //Number to ASCII[A-Z]
    button.innerText = String.fromCharCode(i);
    
    //character button click
    button.addEventListener("click", () => {
      let charArray = chosenWord.split("");
      let dashes = document.getElementsByClassName("dashes");
      //if array contains clciked value replace the matched dash with letter else dram on canvas
      if (charArray.includes(button.innerText)) {
        charArray.forEach((char, index) => {
          //if character in array is same as clicked button
          if (char === button.innerText) {
            //replace dash with letter
            dashes[index].innerText = char;
            //increment counter
            winCount += 1;
            //if winCount equals word lenfth
            if (winCount == charArray.length) {
                var data = 
                {
                    username:   AB.myusername,
                    word:       chosenWord,
                    win:       "won"
                };
                
  
                AB.socketOut ( data );        // server gets this, and sends the data to all clients running this World
              resultMsg.innerHTML = `<h2 class='win-msg'>You Win!!</h2><p>The word was <span>${chosenWord}</span></p>`;
              //block all buttons
              blocker();
            }
          }
        });
      } else {
        //lose count
        count += 1;
        //for drawing man
        drawMan(count);
        //Count==6 because head,body,left arm, right arm,left leg,right leg
        if (count == 6) {
            
            var data = 
            {
                username:   AB.myusername,
                word:       chosenWord,
                loss:       "lost"
            };
  
            AB.socketOut ( data );        // server gets this, and sends the data to all clients running this World
          resultMsg.innerHTML = `<h2 class='lose-msg'>You Lose!!</h2><p>The word was <span>${chosenWord}</span></p>`;
          blocker();
        }
      }
      //disable clicked button
      button.disabled = true;
    });
    charContainer.append(button);
    chosenWord = "";
  }

  displayCategories();
  //Call to canvasCreator (for clearing previous canvas and creating initial canvas)
  let { initialDrawing } = canvasCreator();
  //initialDrawing would draw the frame
  initialDrawing();
};

//Canvas
const canvasCreator = () => {
  let context = canvas.getContext("2d");
  context.beginPath();
  context.strokeStyle = "#000";
  context.lineWidth = 2;

  //For drawing lines
  const drawLine = (fromX, fromY, toX, toY) => {
    context.moveTo(fromX, fromY);
    context.lineTo(toX, toY);
    context.stroke();
  };

  const head = () => {
    context.beginPath();
    context.arc(70, 30, 10, 0, Math.PI * 2, true);
    context.stroke();
  };

  const body = () => {
    drawLine(70, 40, 70, 80);
  };

  const leftArm = () => {
    drawLine(70, 50, 50, 70);
  };

  const rightArm = () => {
    drawLine(70, 50, 90, 70);
  };

  const leftLeg = () => {
    drawLine(70, 80, 50, 110);
  };

  const rightLeg = () => {
    drawLine(70, 80, 90, 110);
  };

  //initial frame
  const initialDrawing = () => {
    //clear canvas
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    //bottom line
    drawLine(10, 130, 130, 130);
    //left line
    drawLine(10, 10, 10, 131);
    //top line
    drawLine(10, 10, 70, 10);
    //small top line
    drawLine(70, 10, 70, 20);
  };

  return { initialDrawing, head, body, leftArm, rightArm, leftLeg, rightLeg };
};

//draw the man
const drawMan = (count) => {
  let { head, body, leftArm, rightArm, leftLeg, rightLeg } = canvasCreator();
  switch (count) {
    case 1:
      head();
      break;
    case 2:
      body();
      break;
    case 3:
      leftArm();
      break;
    case 4:
      rightArm();
      break;
    case 5:
      leftLeg();
      break;
    case 6:
      rightLeg();
      break;
    default:
      break;
  }
};

//New Game
newGameButton.addEventListener("click", initializer);
window.onload = initializer;