6

I am developing a card game in HTML that shows real-time perspectives and card movements to all players.

All players are connected via socket.io.

BACKGROUND:

If there are 4 players, table is square and table HTML body is rotated based on player position on their screen.

The structure is like this:

<gameTable style=”position: relative;”> 

<card style=”position: absolute; top: 1%, left: 1%;”></card> 

</gameTable > 

Now in order to move cards, player picks a card with mouse and it

1: Saves that location

2: Based on movement, it converts movement of card (in px), into relatively equal percentage and then moves the card on table that much.

This is so that whether someone is using big screen or small screen, the card movement will be same for all people by using % as top, left coordinates.

-- SQUARE TABLE:

When player is in 0 degree table position, the mouse and card movements are directly linked.

Card top: --mouse y, card left: --mouse x

When player is in 180 degree table position, the mouse movements are reversed:

Card top: ++mouse y, card left: ++mouse x

For 90 degree:

Card top: ++mouse x, card left: --mouse y

Similar translation with small change of coordinates for 270 degree rotated table.

This works perfectly as with this translation, it perfectly translates mouse over all directions in code.

THE PROBLEM:

For a 6 player game, I want to use Hexagonal table. Now in this table, the players table rotate to:

0, 60, 120, 180, 240 and 300 degrees.

0 and 180 degree are fine but I am unable to figure out a proper formula to translate mouse coordinates to card coordinates based on rotation.

-- CURRENT SOLUTION (Not good)

Currently, I did some brute force programming and did it like this: (60 degree table)

If (mouse moving up / down) {

card top: --mouse y/1.7, card left: --mouse y

}

If (mouse moving left / right) {

card top: ++mouse x, card left: --mouse x/1.7

}

Similar approaches for other positions on hexa table with small changes.

This is brute force approach and it blocks me from transitioning from up/down movement to left/right state as I have to leave the mouse and then start again.

In short, it is not working and I would like to know if it is possible to have a formula that can correctly translate mouse coordinates of mouse to cards based on rotation.

Please view following images for visual representation of what I mean

Square Table

Hexa Table

dale landry
  • 7,831
  • 2
  • 16
  • 28
Hayder Ameen
  • 133
  • 5

1 Answers1

2

tl;dr See this JSFiddle

To move from four players to six players, you have to understand the math that underlies the four player case and then apply it to the six player case.

Four Player Case

You can think of each player's location as a given number of degrees (or radians) around a circle with an origin at the center of the table. In the four player case, the 1st player (index 0) will be at degrees 0, the 2nd player will be at 90 degrees, etc.

To determine how one player's movement affects the movement of a card on another person's screen that is not at the same angle, you need to break down the movement of the card in terms of its basis. In this case, the basis is composed of two orthogonal vectors that define the current player's movement space. The first vector is the vector generated using the angle. The second vector can be found by taking the current angle and adding 90 degrees (Pi/2 radians). For the four player case, the 1st player will have a primary basis vector of 0 degrees, which is (1,0). The 1st players secondary basis vector will be 0+90=90 degrees, which is (0,1).

To translate the mouse movement (delta screen x and y) into movement using an arbitrary basis, you take the dot product of delta screen x and y and the x and y of each vector in the basis. In the four player case, when the 1st player moves a card, the dot product of the primary and secondary basis result in the x delta and the y delta, respectively.

After you calculate this for the current player, you can to move the cards on other player's screens in terms of their basis. To do this, take the movement that has been converted into movement along the two bases and apply it to each other player's basis. In the four player case, the 2nd player's primary basis is 90 degrees = (1,0) and the secondary basis is 90+90 degrees = (-1,0). Thus to mirror the movement at the 2nd player's position, the original x movement is converted to y movement and the original y movement is converted to -x movement.

Six (or arbitrary number) of Players

To change this to a game with an arbitrary number of players and with a potential offset from 0 degrees for the 1st player, you need to follow the same math. First, calculate the bases for each player. Second, use dot products to calculate the delta of the card being movement in terms of its basis. Third, apply this movement to the bases of the other players.

The example below lets you change the number of players and the starting offset angle. The cards are divs and there is a canvas superimposed over the divs to show the primary axes (red) and the secondary axes (blue) for each basis.

<div>
<div class="table-div">
</div>
<div>
  This example shows how the current players card (blue) can be shown moving on other players' (red "ghost" cards).
  Below you
  can change the number of players and the angle offset of the starting player.
</div>
<div>
  <p>Players</p>
  <input type="text" onchange="playerChange()" id="playerText" value="6">
</div>
<div>
  <p>Angle (Degrees)</p>
  <input type="text" onchange="angleChange()" id="angleText" value="45">
</div>
<canvas id="canv"></canvas>
body {
  margin: 0;
}

canvas {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 9;
}

.table-div {
  background-color: lightgreen;
  width: 400px;
  height: 400px;
}

.card {
  position: absolute;
  width: 50px;
  height: 70px;
}

.current {
  background-color: blue;
}

.ghost {
  background-color: red;
}
//A simple class to hold an x,y pair
class Vector2 {
  constructor(x, y) { [this.x, this.y] = [x, y] }
}

//Set up our parameters
let playerCount = 6; //Can be changed by the user
let offsetAngle = 45; //Can be changed by the user
let width = 400;
let height = width;
let radius = width / 2;
let cardWidth = 50;
let cardHeight = 50 * 1.4;//~70
let cards;

//Track the mouse
let lastMouse = null;
let currentMouse = null;
let mouseDown = false;

//The angle in radians between each player on the table
let angleDelta;

//Grab the table div
let tableElement = document.querySelector(".table-div");
let canvas = document.querySelector("canvas")
let ctx = canvas.getContext("2d");
canvas.style.width = width + "px";
canvas.style.height = height + "px"
canvas.width = width;
canvas.height = height;

function update() {
  ctx.clearRect(0, 0, width, height);

  for (let i = 0; i < playerCount; i++) {
    //Draw the first axis
    ctx.strokeStyle = "red";
    ctx.beginPath();
    ctx.moveTo(radius, radius);
    ctx.lineTo(radius + Math.cos(angleDelta * i + offsetAngle) * radius, radius + Math.sin(angleDelta * i + offsetAngle) * radius)
    ctx.stroke();

    //Draw the second axis
    let secondAxisAngle = angleDelta * i + Math.PI / 2;
    let startX = radius + Math.cos(angleDelta * i + offsetAngle) * radius / 2;
    let startY = radius + Math.sin(angleDelta * i + offsetAngle) * radius / 2;
    let endX = Math.cos(secondAxisAngle + offsetAngle) * radius / 4;
    let endY = Math.sin(secondAxisAngle + offsetAngle) * radius / 4;

    ctx.strokeStyle = "green";
    ctx.beginPath();
    ctx.moveTo(startX, startY)
    ctx.lineTo(startX + endX, startY + endY)
    ctx.stroke();
  }
}

document.body.addEventListener("mousemove", e => {
  //Keep track of the last mouse position so we can calculate a delta
  lastMouse = currentMouse;
  currentMouse = new Vector2(e.clientX, e.clientY);
  if (lastMouse && mouseDown) {
    let mouseDelta = new Vector2(currentMouse.x - lastMouse.x, currentMouse.y - lastMouse.y);
    if (mouseDown) {
      //Determine the movement in the current player's basis
      let primaryAxisCurrent = new Vector2(Math.cos(angleDelta * 0 + offsetAngle), Math.sin(angleDelta * 0 + offsetAngle))
      let secondAxisCurrent = new Vector2(Math.cos(angleDelta * 0 + Math.PI / 2 + offsetAngle), Math.sin(angleDelta * 0 + Math.PI / 2 + offsetAngle))
      //Determine the movement in terms of the primary axes
      let primary = (primaryAxisCurrent.x * mouseDelta.x + primaryAxisCurrent.y * mouseDelta.y)// * mouseDelta.x;
      let second = (secondAxisCurrent.x * mouseDelta.x + secondAxisCurrent.y * mouseDelta.y)// * mouseDelta.y;

      //Update all the cards using the primary and secondary motion
      for (let i = 0; i < playerCount; i++) {
        //Get the axes for this card
        let primaryAxis = new Vector2(Math.cos(angleDelta * i + offsetAngle), Math.sin(angleDelta * i + offsetAngle))
        let secondAxis = new Vector2(Math.cos(angleDelta * i + Math.PI / 2 + offsetAngle), Math.sin(angleDelta * i + Math.PI / 2 + offsetAngle))
        //Translate the motion into this card's axes
        let primaryAxisMovement = new Vector2(primaryAxis.x * primary, primaryAxis.y * primary)
        let secondAxisMovement = new Vector2(secondAxis.x * second, secondAxis.y * second)

        //Get the current location (strip the 'px') and add the change in position.
        let div = cards[i];
        let location = new Vector2(parseFloat(div.style.left) + primaryAxisMovement.x + secondAxisMovement.x, parseFloat(div.style.top) + primaryAxisMovement.y + secondAxisMovement.y)
        //Reappend 'px'
        div.style.left = location.x + "px"
        div.style.top = location.y + "px"
      }
    }
  }
})

document.body.addEventListener("mousedown", () => mouseDown = true)
document.body.addEventListener("mouseup", () => mouseDown = false)

function initDivs() {
  //Create all the cards
  cards = [];
  tableElement.innerHTML = "";
  for (let i = 0; i < playerCount; i++) {
    let div = document.createElement("div");
    tableElement.appendChild(div);
    div.classList.add("card")
    if (i == 0) div.classList.add("current")
    else div.classList.add("ghost")

    let radians = angleDelta * i;
    div.style.left = (radius - cardWidth / 2 + Math.cos(radians + offsetAngle) * (radius - cardWidth / 2)) + "px";
    div.style.top = (radius - cardHeight / 2 + Math.sin(radians + offsetAngle) * (radius - cardHeight / 2)) + "px"
    cards.push(div);
  }
}
function playerChange() {
  playerCount = +document.querySelector("#playerText").value;
  angleDelta = Math.PI * 2 / playerCount;
  initDivs();
  angleChange();
}
function angleChange() {
  //Convert from degrees to radians
  offsetAngle = parseFloat(document.querySelector("#angleText").value) * Math.PI / 180;
  initDivs();
  update();
}

playerChange();
bricksphd
  • 147
  • 9
  • I cannot thank you enough!! You took out time and gave me the most ingenious solution! I was able to use it in my game and everything works. However, I had to make one modification that I did not understand. (I'm not very good in Maths). What I am doing is, calculating card coordinates on each player's screen and then transmitting to others. The only thing that confused me is offset angle. I had to use it differently in 2 areas: .. next comment. – Hayder Ameen Apr 24 '21 at 04:21
  • The offsetAngle calculated used at: `offsetAngle = (parseFloat(document.querySelector("#angleText").value) * Math.PI) / 180;` is as: `offsetAngles = { // key: tableRotation, value: value used inside the code above 0: 0, 60: 0, 120: 180, 180: 0, 240: 180, 300: 0, };` – Hayder Ameen Apr 24 '21 at 04:22
  • Then for calculating movement, the offset angle used in following line of code is: `angles = { // key: tableRotation, value: value used inside the code 0: 180, 60: 60, 120: 120, 180: 180, 240: 240, 300: 300, }; offsetAngle = (angles[tableRotation] * Math.PI) / 180;` Using 1 values did not seem to work. I am sure that I must be missing some math here. – Hayder Ameen Apr 24 '21 at 04:22
  • Hi @bricksphd, could I ask you to take a look at this question? Thanks in advance! :) https://stackoverflow.com/questions/67377180/coordinates-calculation-how-to-translate-an-object-on-a-rotated-html-body-respe – Hayder Ameen May 04 '21 at 12:46