This example demonstrates how to create a shared worker that is accessed by multiple pages that send and receiver messages in JavaScript.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>XoaX.net's Workers Demo</title>
<style type="text/css">
iframe {
padding 0px;
width: 517px;
height: 1200px;
}
</style>
<script>
function AddAnotherPlayerPage() {
// We could open two views by using index.html
window.open('View.html');
}
</script>
</head>
<body>
<!-- We will have 2 iframes because of the size, but we could use any number.
To open another view, use the button to create a new page -->
<p><button type=button onclick="AddAnotherPlayerPage()">Add another player</button></p>
<p>Each additional player page opens in a new window. The number of players is restricted to 5.</p>
<p>Use the arrow keys to move the player that has focus (red outline) and the <b><</b> and <b>></b> keys to change the terrain.</p>
<iframe src="View.html"></iframe>
<iframe src="View.html"></iframe>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>XoaX.net's Workers Demo</title>
<style type="text/css">
#idMap {
width: 500px;
height: 500px;
background-color:gray;
position: absolute;
}
.cMapTile {
width: 100px;
height: 100px;
background-color:black;
position: absolute;
}
#idButtons {
position: absolute;
left: 175px;
width: 150px;
height: 150px;
background-color:lightgray;
}
.cButton {
position: absolute;
width: 50px;
height: 50px;
border-radius:4px;
font-size:xx-large;
font-weight:bold;
}
.cButton:hover {
background-color: #AAAAAA;
}
</style>
<script>
var gqSharedWorker = null
var qaaMapTiles = [];
var gqaManImageElements = [];
// Message Handlers
function SetName(e) {
// Make sure that this is the name.
if (e.data.substr(0, 5) != 'Name ') {
return;
}
let sName = e.data.substr(5);
if (parseInt(sName) == -1) {
window.close();
}
let qTitleElement = document.createElement("h1");
qTitleElement.innerHTML = "Player " + sName;
let qBodyElement = document.body;
qBodyElement.firstElementChild.after(qTitleElement);
}
function DisplayMap(e) {
// Make sure that this is the view data.
if (e.data.substr(0, 5) != 'View ') {
return;
}
let saTiles = e.data.substr(5).split(',');
let saImages = ["Grass.png", "Forest.png", "Bushes.png", "Water.png"];
const kiSizeX = 5;
const kiSizeY = 5;
for (let i = 0; i < kiSizeX; ++i) {
for (let j = 0; j < kiSizeY; ++j) {
// Clean the tile before we fill it. This will get rid of prior image elements.
RemoveAllChildren(qaaMapTiles[i][j]);
let iIntValue = parseInt(saTiles[kiSizeX*i + j], 10);
let sBackGroundImage = '';
// Values outside the map have a value of -1. They do not get an image
if (iIntValue >= 0) {
// These tiles will have player images on them
if (iIntValue > 3) { // We could check for the center and render it differently or use values and different images.
//sBackGroundImage += 'url("Man.png"), ';
let iImageIndex = (iIntValue >> 2) - 1;
qaaMapTiles[i][j].appendChild(gqaManImageElements[iImageIndex]);
}
sBackGroundImage += 'url('+saImages[iIntValue%4]+')';
qaaMapTiles[i][j].style.backgroundImage = sBackGroundImage;
} else {
qaaMapTiles[i][j].style.backgroundImage = "none";
}
}
}
}
function CreateLocalMap() {
for (let i = 0; i < 5; ++i) {
gqaManImageElements[i] = document.createElement("img");// new Image(100, 100);
gqaManImageElements[i].src = "Man.png";
gqaManImageElements[i].style.filter = "brightness(0%) invert(" +(20*(i + 1))+ "%) drop-shadow(5px 5px 2px black)";
}
// Create a 5x5 grid of divs for the local map
let qMapDiv = document.getElementById("idMap");
let saImages = ["Water.png", "Grass.png", "Bushes.png", "Forest.png"];
const kiSizeX = 5;
const kiSizeY = 5;
for (let i = 0; i < kiSizeX; ++i) {
qaaMapTiles[i] = [];
for (let j = 0; j < kiSizeY; ++j) {
let qNewTile = document.createElement("div");
qNewTile.classList += "cMapTile";
qNewTile.style.left = i*100+"px";
qNewTile.style.top = j*100+"px";
qaaMapTiles[i][j] = qNewTile;
qMapDiv.appendChild(qNewTile);
}
}
}
function UpdatePublicMessages(e) {
if (e.data.substr(0, 5) != 'Mess ') return;
let sPlayerNumber = e.data.substr(5).split(' ', 1)[0];
let sMessage = e.data.substr(5 + sPlayerNumber.length + 1);
// Display "[Player Number]" before the message to indicate the sender
let qPublicContainerElement = document.getElementById('idPublic');
let qMessageContainer = document.createElement('p');
let qButtonElement = document.createElement('button');
qButtonElement.textContent = '[' + sPlayerNumber + ']';
qButtonElement.onclick = function () { gqSharedWorker.port.postMessage('Chat ' + sPlayerNumber); };
//'Recall that Player ' + sPlayerNumber + ' said \"' + this.nextSibling.innerHTML+'\"'); };
qMessageContainer.appendChild(qButtonElement);
let qMessageElement = document.createElement('span');
qMessageElement.textContent = sMessage;
qMessageContainer.appendChild(qMessageElement);
qPublicContainerElement.appendChild(qMessageContainer);
}
function BeginPrivateChat(e) {
if (e.data.substr(0, 5) != 'Chat ') return;
let sPlayerNumber = e.data.substr(5).split(' ', 1)[0];
let qPort = e.ports[0];
// Create the elements for the chat
let qMainUL = document.getElementById('idPrivate');
let qNewLI = document.createElement('li');
let qHeader = document.createElement('h3');
qHeader.textContent = 'Private chat with ' + sPlayerNumber;
qNewLI.appendChild(qHeader);
let div = document.createElement('div');
let fnAddMessage = function(sPlayerNumber, message) {
let p = document.createElement('p');
let n = document.createElement('strong');
n.textContent = '[' + sPlayerNumber + '] ';
p.appendChild(n);
let t = document.createElement('span');
t.textContent = message;
p.appendChild(t);
div.appendChild(p);
};
qPort.onmessage = function (e) {
fnAddMessage(sPlayerNumber, e.data);
};
qNewLI.appendChild(div);
let form = document.createElement('form');
let p = document.createElement('p');
let input = document.createElement('input');
input.size = 50;
p.appendChild(input);
p.appendChild(document.createTextNode(' '));
let button = document.createElement('button');
button.textContent = 'Post';
p.appendChild(button);
form.onsubmit = function () {
qPort.postMessage(input.value);
fnAddMessage('me', input.value);
input.value = '';
return false;
};
form.appendChild(p);
qNewLI.appendChild(form);
qMainUL.appendChild(qNewLI);
}
function Initialization() {
// This is done at the window level so that we can prevent the default scrolling.
window.onkeydown=KeyDownFunction;
CreateLocalMap();
gqSharedWorker = new SharedWorker('MapWorker.js');
let bUseCapture = false;
gqSharedWorker.port.addEventListener('message', DisplayMap, bUseCapture);
gqSharedWorker.port.addEventListener('message', SetName, bUseCapture);
gqSharedWorker.port.addEventListener('message', UpdatePublicMessages, bUseCapture);
gqSharedWorker.port.addEventListener('message', BeginPrivateChat, bUseCapture);
// This is required for the event listener.
gqSharedWorker.port.start();
}
function Send(sMessage) {
gqSharedWorker.port.postMessage(sMessage);
}
function RemoveAllChildren(qElement) {
let qLastChild = qElement.lastElementChild;
while (qLastChild) {
qElement.removeChild(qLastChild);
qLastChild = qElement.lastElementChild;
}
}
function OnFocus() {
document.body.style.border = "1px red solid";
}
function OnBlur() {
document.body.style.border = "none";
}
function KeyDownFunction(e) {
var iKeyUp = 38;
var iKeyLeft = 37;
var iKeyRight = 39;
var iKeyDown = 40;
var iKeyLessThan = 188;
var ikeyGreaterthan = 190;
var iKeyCode = 0;
if (e) {
iKeyCode = e.which;
} else {
iKeyCode = window.event.keyCode;
}
switch (iKeyCode) {
case iKeyUp: {
Send('Move Up');
// Prevent the window scrolling
e.preventDefault();
break;
}
case iKeyLeft: {
Send('Move Left');
// Prevent the window scrolling
e.preventDefault();
break;
}
case iKeyRight: {
Send('Move Right');
// Prevent the window scrolling
e.preventDefault();
break;
}
case iKeyDown: {
Send('Move Down');
// Prevent the window scrolling
e.preventDefault();
break;
}
case iKeyLessThan: {
Send('Fill 1');
// Prevent the window scrolling
e.preventDefault();
break;
}
case ikeyGreaterthan: {
Send('Fill 3');
// Prevent the window scrolling
e.preventDefault();
break;
}
default: {
break;
}
}
}
</script>
</head>
<body onload="Initialization()" onfocus="OnFocus()" onblur="OnBlur()">
<div style="position:static;width:500px;height:658px;">
<div id="idMap"></div>
<div style="position: absolute;top: 508px;background-color:#DDDDDD; width:500px;height:150px;">
<div id="idButtons">
<button type=button class="cButton" style="left:50px;top:0px;" onclick="Send('Move Up')">↑</button>
<div>
<button type=button class="cButton" style="left:0px;top:50px;" onclick="Send('Move Left')">←</button>
<!-- Add 1 or -1 = 3 mod 4 -->
<button type=button class="cButton" style="height:25px;left:50px;top:50px;font-size:medium;" onclick="Send('Fill 1')">↺</button>
<button type=button class="cButton" style="height:25px;left:50px;top:75px;font-size:medium;" onclick="Send('Fill 3')">↻</button>
<button type=button class="cButton" style="left:100px;top:50px;" onclick="Send('Move Right')">→</button>
</div>
<button type=button class="cButton" style="left:50px;top:100px;" onclick="Send('Move Down')">↓</button>
</div>
</div>
</div>
<h2>Public Chat</h2>
<div id="idPublic"></div>
<form onsubmit="gqSharedWorker.port.postMessage('Mess ' + message.value); message.value = ''; return false;">
<p><input type="text" name="message" size="50"><button>Post</button></p>
</form>
<h2>Private Chat</h2>
<ul id="idPrivate"></ul>
</body>
</html>
// To debug, use the URL brave://inspect/#workers or chrome://inspect/#workers var giViewCount = 0; var gqMap = null; onconnect = function (qEvent) { // If the map was not created, perform the initializations that will created a new one. if (gqMap == null) { Initialization(); } // Create the new player with his position on the map and get his index. let iPlayerIndex = gqMap.AddPlayer(qEvent.ports[0]); qEvent.ports[0].postMessage('Name ' + iPlayerIndex); qEvent.ports[0].onmessage = GetMessage; //let iaPlayerPosition = gqMap.GetPlayerPosition(iPlayerIndex); gqMap.UpdateAllMaps(); } function GetMessage(qEvent) { // Find the player with the equivalent port let qPlayer = gqMap.FindPlayer(qEvent.target); let sAction = qEvent.data.substr(5); switch (qEvent.data.substr(0, 5)) { case 'Move ': switch (sAction) { case 'Up': qPlayer.Up(); break; case 'Down': qPlayer.Down(); break; case 'Left': qPlayer.Left(); break; case 'Right': qPlayer.Right(); break; } // Then call for an update of all of the maps gqMap.UpdateAllMaps(); break; case 'Fill ': switch (sAction) { case '1': qPlayer.Bury(); break; case '3': qPlayer.Dig(); break; } // Then call for an update of all of the maps gqMap.UpdateAllMaps(); break; case 'Mess ': let sText = qEvent.data.substr(5); let qaPlayers = gqMap.GetAllPlayers(); let iSender = qaPlayers.findIndex(qCheck => qCheck == qPlayer); for (let i = 0; i < qaPlayers.length; ++i) { let qPlayerPort = qaPlayers[i].GetPort(); qPlayerPort.postMessage('Mess ' + iSender + ' ' + sText); } break; case 'Chat ': let qFirstPlayer = qPlayer; let iFirstPlayerIndex = gqMap.GetAllPlayers().findIndex(qCheck => qCheck == qFirstPlayer); let iSecondPlayerIndex = parseInt(qEvent.data.substr(5).split(' ', 1)[0]); let qSecondPlayer = gqMap.GetAllPlayers()[iSecondPlayerIndex]; let qChannel = new MessageChannel(); qFirstPlayer.GetPort().postMessage('Chat ' + iSecondPlayerIndex, [qChannel.port1]); qSecondPlayer.GetPort().postMessage('Chat ' + iFirstPlayerIndex, [qChannel.port2]); break; } } function Initialization() { gqMap = new CMap(); } class CMap { static #siMapSize = [25, 25]; #mqaPlayers = []; #miaaMapSquares = null; constructor() { this.#miaaMapSquares = []; for (let i = 0; i < CMap.#siMapSize[0]; ++i) { this.#miaaMapSquares[i] = []; for (let j = 0; j < CMap.#siMapSize[1]; ++j) { this.#miaaMapSquares[i][j] = Math.floor(4*Math.random()); } } } // Return the index of the added player. AddPlayer(qPort) { // Limit the number of players to 5 if (this.#mqaPlayers.length < 5) { this.#mqaPlayers[this.#mqaPlayers.length] = new CPlayer(this, qPort); return this.#mqaPlayers.length - 1; } else { return -1 } } GetAllPlayers() { return this.#mqaPlayers; } FindPlayer(qPort) { for (let k = 0; k < this.#mqaPlayers.length; ++k) { if (this.#mqaPlayers[k].HasSamePort(qPort)) { return this.#mqaPlayers[k]; } } return null; // Nothing was found } UpdateAllMaps() { for (let i = 0; i < this.#mqaPlayers.length; ++i) { this.#mqaPlayers[i].UpdateConnectionMap(); } } GetPlayerPosition(iPlayerIndex) { return this.#mqaPlayers[iPlayerIndex].GetPosition(); } GetLocalMap(iaCenter, iaSize) { let iaLocalMap = []; let iaBoundsX = [iaCenter[0] - Math.floor(iaSize[0]/2), iaCenter[0] - Math.floor(iaSize[0]/2) + iaSize[0]]; let iaBoundsY = [iaCenter[1] - Math.floor(iaSize[1]/2), iaCenter[1] - Math.floor(iaSize[1]/2) + iaSize[1]]; for (let i = iaBoundsX[0]; i < iaBoundsX[1]; ++i) { for (let j = iaBoundsY[0]; j < iaBoundsY[1]; ++j) { // If the coordinates are out of bounds, return -1. if (i < 0 || j < 0 || i >= CMap.#siMapSize[0] || j >= CMap.#siMapSize[1]) { iaLocalMap.push(-1); } else { // Check for a player and add it to the map, if it exists. let iPlayer = 0; for (let k = 0; k < this.#mqaPlayers.length; ++k) { let iaPos = this.#mqaPlayers[k].GetPosition(); if (iaPos[0] == i && iaPos[1] == j) { iPlayer = 4*(k + 1); } } iaLocalMap.push(this.#miaaMapSquares[i][j] + iPlayer); } } } return iaLocalMap; } GetValue(iX, iY) { return this.#miaaMapSquares[iX][iY]; } SetValue(iX, iY, iValue) { this.#miaaMapSquares[iX][iY] = iValue; } IsOccupied(iX, iY, qPlayerPort) { for (let k = 0; k < this.#mqaPlayers.length; ++k) { // Only check the position of other players if (!this.#mqaPlayers[k].HasSamePort(qPlayerPort)) { let qOtherPlayer = this.#mqaPlayers[k]; let iaOtherPlayersPos = qOtherPlayer.GetPosition(); if (iX == iaOtherPlayersPos[0] && iY == iaOtherPlayersPos[1]) { return true; } } } return false; } static GetMapSize() { return CMap.#siMapSize; } } class CPlayer { static #siViewSize = [5, 5]; static GetViewSize() { return CPlayer.#siViewSize; } #miaPos = []; #mqMap = null; #mqPort = null; constructor(qMap, qPort) { let iX = 0; let iY = 0; let bIsOccupied = true; let iaMapSize = CMap.GetMapSize(); while (bIsOccupied) { iX = Math.floor(iaMapSize[0]*Math.random()); iY = Math.floor(iaMapSize[1]*Math.random()); bIsOccupied = qMap.IsOccupied(iX, iY, qPort); } this.#miaPos[0] = iX; this.#miaPos[1] = iY; this.#mqMap = qMap; this.#mqPort = qPort; } GetPort() { return this.#mqPort; } HasSamePort(qPort) { return (this.#mqPort == qPort); } GetPosition() { return [this.#miaPos[0], this.#miaPos[1]]; } SetPosition(iX, iY) { if (this.#mqMap.IsOccupied(iX, iY)) { return false; } else { this.#miaPos[0] = iX; this.#miaPos[1] = iY; return true; } } GetLocalMap() { return this.#mqMap.GetLocalMap(this.#miaPos, CPlayer.#siViewSize); } UpdateConnectionMap() { let sTerrain = ''; let iaLocalMap = this.GetLocalMap(); let iLocalIndex = 0; // Add the first entry without a comma sTerrain += iaLocalMap[iLocalIndex]; for (let i = 1; i < iaLocalMap.length; ++i) { ++iLocalIndex; sTerrain += ','+iaLocalMap[iLocalIndex]; } this.#mqPort.postMessage('View ' + sTerrain); } Up() { // Check whether there is anything in the way. let iNewY = this.#miaPos[1] - 1; if (iNewY >= 0) { if (!this.#mqMap.IsOccupied(this.#miaPos[0], iNewY)) { this.#miaPos[1] = iNewY; } } } Left() { // Check whether there is anything in the way. let iNewX = this.#miaPos[0] - 1; if (iNewX >= 0) { if (!this.#mqMap.IsOccupied(iNewX, this.#miaPos[1])) { this.#miaPos[0] = iNewX; } } } Right() { // Check whether there is anything in the way. let iNewX = this.#miaPos[0] + 1; if (iNewX < 25) { if (!this.#mqMap.IsOccupied(iNewX, this.#miaPos[1])) { this.#miaPos[0] = iNewX; } } } Down() { // Check whether there is anything in the way. let iNewY = this.#miaPos[1] + 1; if (iNewY < 25) { if (!this.#mqMap.IsOccupied(this.#miaPos[0], iNewY)) { this.#miaPos[1] = iNewY; } } } Bury() { let iCurr = this.#mqMap.GetValue(this.#miaPos[0], this.#miaPos[1]); let iTwoNewBits = (((iCurr & 3) + 1) % 4); iCurr = (iCurr & ~(iCurr & 3)) + iTwoNewBits; this.#mqMap.SetValue(this.#miaPos[0], this.#miaPos[1],iCurr); } Dig() { let iCurr = this.#mqMap.GetValue(this.#miaPos[0], this.#miaPos[1]); let iTwoNewBits = (((iCurr & 3) + 3) % 4); iCurr = (iCurr & ~(iCurr & 3)) + iTwoNewBits; this.#mqMap.SetValue(this.#miaPos[0], this.#miaPos[1],iCurr); } }
© 20072026 XoaX.net LLC. All rights reserved.