A "multiplayer" HTML5 <canvas> to which people can connect and make changes over time.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

183 lines
4.9 KiB

// canvas-app.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
window.dtp = window.dtp || { };
const IMAGE_W = 32;
const IMAGE_H = 32;
const DTP_COMPONENT_NAME = 'canvas-app';
import '../less/style.less';
import { NiceAudio, NiceLog } from 'dtp-nice-game';
import CanvasSocket from './canvas-socket.js';
import Color from 'color';
import ColorConvert from 'color-convert';
export default class CanvasApp {
constructor ( ) {
this.audio = new NiceAudio();
this.log = new NiceLog(DTP_COMPONENT_NAME);
this.log.info('init', 'Canvas app online');
this.initCanvas();
this.initColorPalette();
document.addEventListener('mpcvs:socket-connected', this.onSocketConnected.bind(this));
this.socket = new CanvasSocket();
this.socket.connect({
withRetry: true,
withError: false,
connectToken: window.dtp.connectToken,
});
}
async onSocketConnected ( ) {
console.log('APP: socket connected');
const { socket } = this.socket;
socket.on('canvas-error', this.onCanvasError.bind(this));
socket.on('canvas-image-data', this.onCanvasImageData.bind(this));
socket.on('canvas-setpixel', this.onCanvasSetPixel.bind(this));
socket.emit('getimagedata');
}
async onCanvasError (message) {
console.log('onCanvasError', { message });
}
async onCanvasImageData (message) {
console.log('onCanvasImageData', { message });
this.imageData = new ImageData( // jshint ignore:line
new Uint8ClampedArray(message.data),
message.width,
message.height,
);
this.ctx.putImageData(this.imageData, 0, 0);
}
async onCanvasSetPixel (message) {
console.log('onCanvasSetPixel', message);
this.setPixel(message);
this.ctx.putImageData(this.imageData, 0, 0);
}
initCanvas ( ) {
this.canvas = document.querySelector('canvas');
this.canvas.onclick = this.onCanvasClick.bind(this);
this.ctx = this.canvas.getContext('2d');
}
initColorPalette ( ) {
const setDrawColor = this.setDrawColor.bind(this);
const palette = document.querySelector('.color-palette');
const colors = [ ];
for (let h = 0; h <= 360; h += 36) {
for (let v = 25; v <= 75; v += 25) {
for (let s = 25; s <= 100; s += 25) {
colors.push('#' + ColorConvert.hsl.hex(h, s, v));
}
}
}
// for (let r = 0; r <= 255; r+= 64) {
// for (let g = 0; g <= 255; g += 64) {
// for (let b = 0; b <= 255; b += 64) {
// const c = Color.hsl() Color({ r, g, b });
// colors.push(c.hex());
// }
// }
// }
colors.push("#ffffff"); // white
colors.push("#000000"); // black
colors.push("#ff0000"); // red
colors.push("#00ff00"); // green
colors.push("#0000ff"); // blue
this.colorButtons = [ ];
let idx = 0;
colors.forEach((color) => {
const button = document.createElement('button');
button.classList.add('color-select-btn');
button.setAttribute('data-idx', idx++);
button.setAttribute('data-color', color);
button.onclick = setDrawColor;
button.style.backgroundColor = color;
this.colorButtons.push(button);
palette.appendChild(button);
});
this.colorButtons[0].click();
}
setDrawColor (event) {
const target = event.target || event.currentTarget;
const color = target.getAttribute('data-color');
this.log.info('setDrawColor', 'setting draw color', { color });
this.currentColor = Color(color);
this.colorButtons.forEach((button) => {
button.classList.remove('active');
});
target.classList.add('active');
}
updatePixels (message) {
for (const pixel of message.pixels) {
if ((pixel.x < 0) ||
(pixel.x > (IMAGE_W - 1)) ||
(pixel.y < 0) ||
(pixel.y > (IMAGE_H - 1))) {
this.log.info('updatePixels', 'rejecting invalid pixel update', pixel);
return;
}
}
this.ctx.putImageData(this.imageData, 0, 0);
}
setPixel (pixel) {
let pixelIdx = ((pixel.y * IMAGE_W ) + pixel.x) * 4;
this.log.info('updatePixels', 'updating pixel', { pixelIdx, pixel });
this.imageData.data[pixelIdx++] = pixel.r;
this.imageData.data[pixelIdx++] = pixel.g;
this.imageData.data[pixelIdx++] = pixel.b;
this.imageData.data[pixelIdx++] = 255;
}
async onCanvasClick (event) {
const cellPixels = this.canvas.clientWidth / 32;
if (cellPixels === 0) {
return; // prevent a divide by zero
}
const x = Math.floor(event.offsetX / cellPixels);
const y = Math.floor(event.offsetY / cellPixels);
console.log(x, y, this.currentColor.hex());
const pixel = {
x, y,
r: this.currentColor.red(),
g: this.currentColor.green(),
b: this.currentColor.blue(),
};
this.socket.socket.emit('setpixel', pixel);
}
}
window.addEventListener('load', async ( ) => {
window.dtp.app = new CanvasApp();
});