render current player in secondary color
This commit is contained in:
parent
fbaad86555
commit
c4c4eb6358
13 changed files with 255 additions and 72 deletions
|
@ -1,7 +1,3 @@
|
|||
TANK_DOMAIN=vinzenz-lpt2
|
||||
VITE_TANK_API=http://$TANK_DOMAIN
|
||||
VITE_TANK_WS=ws://$TANK_DOMAIN
|
||||
|
||||
VITE_TANK_SCREEN_URL=$VITE_TANK_WS/screen
|
||||
VITE_TANK_CONTROLS_URL=$VITE_TANK_WS/controls
|
||||
VITE_TANK_PLAYER_URL=$VITE_TANK_API/player
|
||||
|
|
|
@ -41,7 +41,7 @@ export default function App() {
|
|||
{nameId.name !== '' &&
|
||||
<Button onClick={() => setNameId(getNewNameId)} text='logout'/>}
|
||||
</Row>
|
||||
<ClientScreen logout={logout} theme={theme}/>
|
||||
<ClientScreen logout={logout} theme={theme} playerId={nameId.id}/>
|
||||
{nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>}
|
||||
<Row className='GadgetRows'>
|
||||
{isLoggedIn && <Controls playerId={nameId.id} logout={logout}/>}
|
||||
|
|
|
@ -2,9 +2,13 @@ import useWebSocket from 'react-use-websocket';
|
|||
import {useEffect, useRef} from 'react';
|
||||
import './ClientScreen.css';
|
||||
import {hslToString, Theme} from "./theme.ts";
|
||||
import {Guid} from "./Guid.ts";
|
||||
|
||||
const pixelsPerRow = 352;
|
||||
const pixelsPerCol = 160;
|
||||
const observerMessageSize = pixelsPerCol * pixelsPerRow / 8;
|
||||
|
||||
const isPlayerMask = 1;
|
||||
|
||||
function getIndexes(bitIndex: number) {
|
||||
return {
|
||||
|
@ -19,42 +23,75 @@ function normalizeColor(context: CanvasRenderingContext2D, color: string) {
|
|||
return context.getImageData(0, 0, 1, 1).data;
|
||||
}
|
||||
|
||||
function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement, theme: Theme) {
|
||||
const drawContext = canvas.getContext('2d');
|
||||
if (!drawContext)
|
||||
throw new Error('could not get draw context');
|
||||
function drawPixelsToCanvas({context, width, height, pixels, additional, foreground, background, playerColor}: {
|
||||
context: CanvasRenderingContext2D,
|
||||
width: number,
|
||||
height: number,
|
||||
pixels: Uint8ClampedArray,
|
||||
additional: Uint8ClampedArray | null,
|
||||
background: Uint8ClampedArray,
|
||||
foreground: Uint8ClampedArray,
|
||||
playerColor: Uint8ClampedArray
|
||||
}) {
|
||||
let additionalDataIndex = 0;
|
||||
let additionalDataByte: number | null = null;
|
||||
const nextPixelColor = (isOn: boolean) => {
|
||||
if (!isOn)
|
||||
return background;
|
||||
if (!additional)
|
||||
return foreground;
|
||||
|
||||
const colorPrimary = normalizeColor(drawContext, hslToString(theme.primary));
|
||||
const colorBackground = normalizeColor(drawContext, hslToString(theme.background));
|
||||
let info;
|
||||
if (additionalDataByte === null) {
|
||||
additionalDataByte = additional[additionalDataIndex];
|
||||
additionalDataIndex++;
|
||||
info = additionalDataByte;
|
||||
} else {
|
||||
info = additionalDataByte >> 4;
|
||||
additionalDataByte = null;
|
||||
}
|
||||
|
||||
const imageData = drawContext.getImageData(0, 0, canvas.width, canvas.height, {colorSpace: 'srgb'});
|
||||
if ((info & isPlayerMask) != 0) {
|
||||
return playerColor;
|
||||
}
|
||||
|
||||
return foreground;
|
||||
}
|
||||
|
||||
const imageData = context.getImageData(0, 0, width, height, {colorSpace: 'srgb'});
|
||||
const data = imageData.data;
|
||||
|
||||
for (let y = 0; y < canvas.height; y++) {
|
||||
const rowStartPixelIndex = y * pixelsPerRow;
|
||||
for (let x = 0; x < canvas.width; x++) {
|
||||
const pixelIndex = rowStartPixelIndex + x;
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const pixelIndex = y * pixelsPerRow + x;
|
||||
const {byteIndex, bitInByteIndex} = getIndexes(pixelIndex);
|
||||
const mask = (1 << bitInByteIndex);
|
||||
const isOn = (pixels[byteIndex] & mask) !== 0;
|
||||
const color = isOn ? colorPrimary : colorBackground;
|
||||
const isOn = (pixels[byteIndex] & (1 << bitInByteIndex)) !== 0;
|
||||
const color = nextPixelColor(isOn);
|
||||
|
||||
for (let colorChannel of [0, 1, 2, 3])
|
||||
data[pixelIndex * 4 + colorChannel] = color[colorChannel];
|
||||
}
|
||||
}
|
||||
|
||||
drawContext.putImageData(imageData, 0, 0);
|
||||
context.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
export default function ClientScreen({logout, theme}: { logout: () => void, theme: Theme }) {
|
||||
export default function ClientScreen({logout, theme, playerId}: {
|
||||
logout: () => void,
|
||||
theme: Theme,
|
||||
playerId?: Guid
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const url = new URL('/screen', import.meta.env.VITE_TANK_WS);
|
||||
if (playerId)
|
||||
url.searchParams.set('player', playerId);
|
||||
|
||||
const {
|
||||
lastMessage,
|
||||
sendMessage,
|
||||
getWebSocket
|
||||
} = useWebSocket(import.meta.env.VITE_TANK_SCREEN_URL, {
|
||||
} = useWebSocket(url.toString(), {
|
||||
onError: logout,
|
||||
shouldReconnect: () => true,
|
||||
});
|
||||
|
@ -69,7 +106,34 @@ export default function ClientScreen({logout, theme}: { logout: () => void, them
|
|||
if (canvasRef.current === null)
|
||||
throw new Error('canvas null');
|
||||
|
||||
drawPixelsToCanvas(new Uint8Array(lastMessage.data), canvasRef.current, theme);
|
||||
const canvas = canvasRef.current;
|
||||
const drawContext = canvas.getContext('2d');
|
||||
if (!drawContext)
|
||||
throw new Error('could not get draw context');
|
||||
|
||||
const colorBackground = normalizeColor(drawContext, hslToString(theme.background));
|
||||
const colorPrimary = normalizeColor(drawContext, hslToString(theme.primary));
|
||||
const colorSecondary = normalizeColor(drawContext, hslToString(theme.secondary));
|
||||
|
||||
let pixels = new Uint8ClampedArray(lastMessage.data);
|
||||
let additionalData: Uint8ClampedArray | null = null;
|
||||
if (pixels.length > observerMessageSize) {
|
||||
additionalData = pixels.slice(observerMessageSize);
|
||||
pixels = pixels.slice(0, observerMessageSize);
|
||||
}
|
||||
|
||||
console.log('', {pixelLength: pixels.length, additionalLength: additionalData?.length});
|
||||
|
||||
drawPixelsToCanvas({
|
||||
context: drawContext,
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
pixels,
|
||||
additional: additionalData,
|
||||
background: colorBackground,
|
||||
foreground: colorPrimary,
|
||||
playerColor: colorSecondary
|
||||
});
|
||||
sendMessage('');
|
||||
}, [lastMessage, canvasRef.current, theme]);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ export default function Controls({playerId, logout}: {
|
|||
playerId: string,
|
||||
logout: () => void
|
||||
}) {
|
||||
const url = new URL(import.meta.env.VITE_TANK_CONTROLS_URL);
|
||||
const url = new URL('controls', import.meta.env.VITE_TANK_WS);
|
||||
url.searchParams.set('playerId', playerId);
|
||||
const {
|
||||
sendMessage,
|
||||
|
|
|
@ -38,7 +38,7 @@ export async function fetchTyped<T>({url, method}: { url: URL; method: string; }
|
|||
}
|
||||
|
||||
export function postPlayer({name, id}: NameId) {
|
||||
const url = new URL(import.meta.env.VITE_TANK_PLAYER_URL);
|
||||
const url = new URL('/player', import.meta.env.VITE_TANK_API);
|
||||
url.searchParams.set('name', name);
|
||||
url.searchParams.set('id', id);
|
||||
|
||||
|
@ -46,7 +46,7 @@ export function postPlayer({name, id}: NameId) {
|
|||
}
|
||||
|
||||
export function getPlayer(id: Guid) {
|
||||
const url = new URL(import.meta.env.VITE_TANK_PLAYER_URL);
|
||||
const url = new URL('/player', import.meta.env.VITE_TANK_API);
|
||||
url.searchParams.set('id', id);
|
||||
|
||||
return fetchTyped<Player>({url, method: 'GET'});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue