mc_arkanoid
Version:
A pure HTML / custom element Arkanoid clone using micro components
590 lines (569 loc) • 18.2 kB
JavaScript
// UNUSED EXPORTS: Arkanoid
;// ./node_modules/@twobirds/microcomponents/dist/MC.js
const isBrowser = new Function('try {return this===window;}catch(e){ return false;}');
function isDOM(element) {
return element && element instanceof HTMLElement;
}
function add(target, key, element = {}) {
let node = target;
if (!node._mc) {
node._mc = new DC(target);
}
const _mc = node._mc;
if (key.length && !_mc[key]) {
_mc[key] = element;
if (target instanceof HTMLElement && !target.getAttribute('data-mc')) {
target.setAttribute('data-mc', '');
}
}
return node;
}
function remove(target, key) {
if (!key)
return;
const _mc = target?._mc || {};
if (_mc && _mc[key]) {
delete _mc[key];
}
if (!Object.keys(_mc).length &&
target instanceof HTMLElement &&
target.hasAttribute('data-mc')) {
target.removeAttribute('data-mc');
}
return;
}
function init(element) {
setTimeout(() => {
if (element.constructor === DC)
return;
element.trigger('Init');
}, 0);
}
function getMcEventName(eventName) {
return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
}
function off(domNode, eventName, handler) {
domNode.removeEventListener(getMcEventName(eventName), handler);
}
function on(domNode, eventName, handler, once = false) {
domNode.addEventListener(getMcEventName(eventName), handler, { capture: false, once });
return handler;
}
function one(domNode, eventName, handler) {
on(domNode, eventName, handler, true);
return handler;
}
class BubbleController {
stopped = false;
constructor() {
}
stop() {
this.stopped = true;
}
}
function trigger(domNode, event, data = {}, bubble = 'l', controller) {
const eventName = /^_mc/.test(event) ? event : getMcEventName(event), bubbleController = !!controller ? controller : new BubbleController(), ev = new CustomEvent(eventName, { detail: { data, control: bubbleController }, bubbles: false });
if (bubbleController.stopped)
return;
if (/l/.test(bubble)) {
domNode.dispatchEvent(ev);
ev.stopImmediatePropagation();
if (bubbleController.stopped)
return;
}
if (/[ud]/.test(bubble)) {
if (bubble.indexOf('l') === -1) {
bubble += 'l';
}
setTimeout(() => {
if (bubble.indexOf('u') > -1) {
let parentMc = domNode.parentElement?.closest('[data-mc]');
if (!!parentMc) {
parentMc?._mc.trigger(eventName, data, bubble, bubbleController);
}
}
if (bubble.indexOf('d') > -1) {
let childNodes = domNode._mc.children();
if (!!childNodes) {
childNodes.forEach((_mc) => _mc.trigger(eventName, data, bubble));
}
}
}, 0);
}
}
function autoAttachListeners(element) {
if (!isBrowser() || element.constructor === DC)
return;
const target = element.target;
if (target instanceof HTMLElement) {
let that = element;
Object.getOwnPropertyNames(that.constructor.prototype)
.filter((key) => /^on[A-Z]|one[A-Z]/.test(key) &&
typeof that.constructor.prototype[key] === 'function')
.forEach((key) => {
let once = /^one/.test(key), withoutOnOne = key.replace(/^on[e]{0,1}/, ''), nativeEventName = withoutOnOne.toLowerCase();
if (('on' + nativeEventName) in target) {
let listener = (ev) => {
that[key](ev);
};
target.addEventListener(nativeEventName, listener);
}
else {
let listener = (ev) => {
that[key](ev);
};
on(target, withoutOnOne, listener, once);
}
});
}
}
function getMcChildNodes(domNode) {
const id = Math.random().toString().replace('.', '');
const selector = '[data-mc]:not([temp_id="' + id + '"] [data-mc] [data-mc])';
domNode.setAttribute('temp_id', id);
const mcArray = [...domNode.querySelectorAll(selector)]
.filter((e) => !!e._mc)
.map(e => e._mc);
domNode.removeAttribute('temp_id');
return mcArray.length ? mcArray : null;
}
class McBase {
_target;
constructor(target) {
let element = this;
this._target = new WeakRef(target || {});
init(element);
autoAttachListeners(element);
}
get target() {
return this._target.deref();
}
}
class MC extends McBase {
constructor(target) {
super(target);
}
}
class DC extends McBase {
constructor(target) {
super(target);
}
static add(target, key, element = {}) {
return add(target, key, element);
}
;
static remove(target, key) {
remove(target, key);
}
off(eventName, handler) {
off(this.target, eventName, handler);
}
on(eventName, handler) {
return on(this.target, eventName, handler);
}
one(eventName, handler) {
return one(this.target, eventName, handler);
}
trigger(eventName, data = {}, bubble = 'l', controller) {
trigger(this.target, eventName, data, bubble, controller);
}
ancestors() {
let currentNode = this?.target, result = [];
if (!isDOM(currentNode))
return [];
while (currentNode = (currentNode.closest('[data-mc]'))) {
if (currentNode?._mc) {
result.push(currentNode?._mc);
}
}
return result.length ? result : null;
}
closest() {
let target = this.target;
if (!isDOM(target))
return null;
return target.closest('[data-mc]')?._mc || null;
}
children() {
let target = this?.target;
if (!isDOM(target))
return null;
return getMcChildNodes(target);
}
descendants() {
const target = this?.target;
if (!isDOM(target))
return [];
const mcArray = [...target.querySelectorAll('[data-mc]')]
.filter((e) => !!e._mc)
.map(e => e._mc);
return mcArray.length ? mcArray : null;
}
}
const loadingDCs = new Set();
function attachDC(element) {
return () => {
const elements = Array
.from(document.querySelectorAll(`[data-mc*="${element}"]`))
.filter(element => element instanceof HTMLElement);
if (elements.length === 0)
return;
const klass = customElements.get(element);
elements.forEach((node) => {
DC.add(node, klass.name, new klass(node));
const new_mc = node
.getAttribute('data-mc')
.split(' ')
.filter((name) => name !== element && name !== '')
.join(' ')
.trim();
node.setAttribute('data-mc', new_mc);
});
};
}
;
function makeLoadScript(element) {
if (loadingDCs.has(element))
return;
loadingDCs.add(element);
const fileName = './' + element.split('-').join('/') + '.js';
const se = document.createElement('script');
se.setAttribute('src', fileName);
se.setAttribute('blocking', 'render');
se.async = true;
se.setAttribute('type', 'module');
se.setAttribute('data-name', element);
se.setAttribute('loading', '');
const loadedPromise = customElements.whenDefined(element);
loadedPromise.then(attachDC(element));
se.addEventListener('load', (ev) => {
loadingDCs.delete(element);
document.head.querySelector(`script[data-name="${element}"]`)?.remove();
});
se.addEventListener('error', (ev) => {
console.error('could not load DC code from', fileName);
});
document.head.append(se);
}
function loadUndefinedMCs() {
[...document.querySelectorAll(':not(:defined)')]
.filter((element) => !!element
&& !customElements.get(element.tagName.toLowerCase())
&& !loadingDCs.has(element.tagName.toLowerCase()))
.forEach((el) => {
makeLoadScript(el.tagName.toLowerCase());
});
[...document.querySelectorAll('[data-mc]:not([data-mc=""]')]
.filter((element) => !!element)
.forEach((el) => {
const _mc = el.getAttribute('data-mc')?.split(' ').filter(name => name !== '');
_mc?.forEach(element => {
const isDefined = customElements.get(element);
if (!loadingDCs.has(element) && !isDefined) {
makeLoadScript(element);
}
else if (isDefined) {
attachDC(element)();
}
});
});
}
let autoloadEnabled = false;
let observer = null;
function autoload(enable = true) {
if (!enable) {
observer?.disconnect();
observer = null;
return autoloadEnabled;
}
autoloadEnabled = enable;
observer = new MutationObserver(loadUndefinedMCs);
setTimeout(() => {
observer.observe(document.body, { childList: true, subtree: true });
loadUndefinedMCs();
}, 5);
window.autoload = window.autoload || autoload;
return autoloadEnabled;
}
autoload.state = loadingDCs;
;// ./node_modules/@twobirds/microcomponents/dist/helpers.js
function isNativeObject(value) {
return value && value['constructor'] && value.constructor === Object;
}
function sortObject(o) {
let result = {}, keys = [...Object.keys(o)].sort();
keys.forEach((key) => {
if (isNativeObject(o[key])) {
result[key] = sortObject(o[key]);
}
else {
result[key] = o[key];
}
});
return result;
}
function flattenObject(o, doSort = true) {
let result = {};
[...Object.getOwnPropertyNames(o)].forEach((propName) => {
if (o[propName] &&
o[propName]['constructor'] &&
o[propName].constructor === Object) {
const temp = flattenObject(o[propName], false);
for (const j in temp) {
result[propName + '.' + j] = temp[j];
}
}
else {
result[propName] = o[propName];
}
});
return doSort ? sortObject(result) : result;
}
function deepEqual(o1, o2) {
return (JSON.stringify(flattenObject(o1)) === JSON.stringify(flattenObject(o2)));
}
function nameSpace(ns, obj = {}) {
let nsa = ns.constructor === String ? ns.split('.') : ns;
if (!obj || !nsa[0] || !obj[nsa[0]]) {
console.warn('ns', nsa[0], 'not in', obj);
return;
}
if (obj[nsa[0]].constructor === Object && nsa.length > 1) {
return nameSpace(nsa.splice(1), obj[nsa[0]]);
}
return obj[nsa[0]];
}
function parse(what, parseThis) {
var args = Array.from(arguments);
if (args.length > 2) {
while (args.length > 1) {
args[0] = parse(args[0], args[1]);
args.splice(1, 1);
}
return args[0];
}
if (typeof what === 'string') {
what.match(/\{[^\{\}]*\}/g)?.forEach(function (pPropname) {
var propname = pPropname.substring(1, pPropname.length - 1), value = nameSpace(propname, parseThis);
if (typeof value !== 'undefined') {
what = what.replace(pPropname, value);
}
});
}
else if (isNativeObject(what)) {
switch (what.constructor) {
case Object:
Object.keys(what).forEach(function (pKey) {
if (what.hasOwnProperty(pKey)) {
what[pKey] = parse(what[pKey], parseThis);
}
});
break;
case Array:
what.forEach(function (pValue, pKey, original) {
original[pKey] = parse(what[pKey], parseThis);
});
break;
}
}
return what;
}
function kebabToPascal(str) {
return str
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
}
function debounce(func, milliseconds) {
let timeout;
return () => {
clearTimeout(timeout);
timeout = setTimeout(function () {
func(...arguments);
}, milliseconds);
};
}
function htmlToElements(html) {
let template = document.createElement('template');
html = html.replace(/<([A-Za-z0-9\-]*)([^>\/]*)(\/>)/gi, '<$1$2></$1>');
template.innerHTML = html;
template.content.normalize();
return Array.from(template.content.childNodes);
}
function copyGettersSetters(source, target) {
const descriptors = Object.getOwnPropertyDescriptors(source);
for (const [key, descriptor] of Object.entries(descriptors)) {
if ('get' in descriptor || 'set' in descriptor) {
Object.defineProperty(target, key, descriptor);
}
}
}
;// ./node_modules/@twobirds/microcomponents/dist/elements.js
class CeBaseClass extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.dispatchEvent(new CustomEvent('_mcConnected'));
}
connectedMoveCallback() {
this.dispatchEvent(new CustomEvent('_mcConnectedMove'));
}
disconnectedCallback() {
this.dispatchEvent(new CustomEvent('_mcDisconnected'));
}
adoptedCallback() {
this.dispatchEvent(new CustomEvent('_mcAdopted'));
}
attributeChangedCallback(name, oldValue, newValue) {
this.dispatchEvent(new CustomEvent('_mcAttributeChanged', { detail: { name, oldValue, newValue } }));
}
formResetCallback() {
this.dispatchEvent(new CustomEvent('_mcFormReset'));
}
}
function createNamedClass(name, ClassBody) {
return Object.defineProperty(ClassBody, 'name', { value: name });
}
function isClass(func) {
return typeof func === 'function'
&& /^class\s/.test(Function.prototype.toString.call(func));
}
function defineCE(tagName, DefinitionClass, observedAttributes = []) {
if (customElements.get(tagName)) {
console.warn(`IGNORED: Custom element "${tagName}" is already defined!`);
return;
}
if (!tagName.includes('-')) {
throw new Error('createCustomElement: tagName must contain at least one hyphen ("-").');
}
let name = kebabToPascal(tagName);
if (!isClass(DefinitionClass.prototype.constructor)) {
customElements.define(tagName, DefinitionClass);
return;
}
if (!(DefinitionClass.prototype instanceof McBase)) {
customElements.define(tagName, DefinitionClass);
return;
}
customElements.define(tagName, createNamedClass(name, class extends CeBaseClass {
instance;
constructor(data) {
super();
const instance = isClass(DefinitionClass)
? new DefinitionClass(this, data)
: DefinitionClass;
DC.add(this, name, instance);
}
static get observedAttributes() {
return observedAttributes;
}
}));
}
function getCE(tagName) {
return customElements.get(tagName);
}
;// ./build/arkanoid/levels.js
const levels = [
{
hitpoints: [
'99999999999999999999',
' 5 9',
' 6 4 8 ',
' 7 3 7 ',
' 8 2 6 ',
'9 1 5 ',
' 1 4 ',
' 1 3 ',
' 2 ',
' 1 ',
],
boni: [
' ',
' S ',
' ',
' ',
' B ',
' M ',
' ',
' ',
' ',
' ',
]
}
];
;// ./build/arkanoid/block.js
const X_SIZE = 30;
const Y_SIZE = 40;
const colors = [
'rgb( 64 64 64 )',
'rgb( 255 255 0 / 20%)',
'rgb( 0 255 255 / 20%)',
'rgb( 255 0 255 / 20%)',
'rgb( 255 0 0 / 50%)',
'rgb( 0 255 0 / 50%)',
'rgb( 0 0 255 / 50%)',
'rgb( 128 128 0 / 80%)',
'rgb( 0 128 128 / 80%)',
'rgb( 200 200 200 )',
];
class Block {
x = 0;
y = 0;
px = 0;
py = 0;
hitpoints = 0;
bonus = ' ';
constructor(x, y, hitpoints, bonus) {
this.x = y;
this.y = x;
this.px = x * 30;
this.py = y * 40;
this.hitpoints = parseInt(hitpoints);
this.bonus = bonus;
}
draw(ctx) {
ctx.fillStyle = colors[this.hitpoints];
ctx.strokeStyle = 'rgb( 0 0 0 )';
ctx.fillRect(this.py, this.px, Y_SIZE, X_SIZE);
ctx.strokeRect(this.py, this.px, Y_SIZE, X_SIZE);
}
}
;// ./build/arkanoid.js
class McArkanoid extends DC {
ctx = null;
constructor(target) {
super(target);
}
onConnected() {
const target = this.target;
target.innerHTML = '<canvas></canvas>';
const canvas = target.querySelector("canvas");
canvas.setAttribute('width', '800px');
canvas.setAttribute('height', '600px');
this.ctx = canvas.getContext("2d");
console.log(this.ctx);
this.drawLevel(0);
}
drawLevel(index = 0) {
const ctx = this.ctx;
let hitpoints = levels[index].hitpoints;
hitpoints.forEach((s, index) => {
hitpoints[index] = s.split('');
});
let boni = levels[index].boni;
boni.forEach((s, index) => {
boni[index] = s.split('');
});
for (let row = 0; row < 10; row++) {
for (let col = 0; col < 20; col++) {
if (hitpoints[row][col] !== ' ') {
const block = new Block(row, col, hitpoints[row][col], boni[row][col]);
block.draw(this.ctx);
}
}
}
}
}
defineCE('mc-arkanoid', McArkanoid);
const Arkanoid = getCE('mc-arkanoid');