UNPKG

mc_arkanoid

Version:

A pure HTML / custom element Arkanoid clone using micro components

590 lines (569 loc) 18.2 kB
// 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');