【HTML】Quine時計


## 前書き

* 毎フレーム画面上に時計として機能するQuineが表示される
* PCのChromeで動けばいいとする
* あんま動作確認してないのでバグるかも…

## 成果物

https://mogamoga1024.github.io/html_clock_quine/

コピペはコンソールから

## コード

```html
<script>s=String.fromCharCode(32);n=String.fromCharCode(10);z=`/*#zounotamag
ohaoisiizouzounotamagohaoisiizouzounotamagohaoisiizouzounotamagohaoisiizouzo
unotamagohaoisiizouzounotamagohaoisiizouzoun   otamagohaoisiizouzounotamagoh
aoisi      iz      ou      zouno   tamagohao   isiiz      o        uzounotam
agoh   ao  is  ii  zo  uz   oun    otamagoha  oisiiz  ou  zounot   amagohaoi
siizouzou      no   tamag   oh     aoisiizo   uzoun   ot   amago  haoisiizou
zounotama  g   oh   aoisi   i   z  ouzounot  amagoh   ao   isii   zouzounota
magohaoi   s   ii   zouz   o   un  otamago   haoisi   iz   ouzo  unotamagoha
oisiizo   uz   ou   not   am         agoha   oisiiz   ou   zou   notamagohao
isiizo   uzo   un   ot   amagohao  isiizou  zounota   ma   go   haoisiizouzo
u#*/d  =new@D  at  e(   );canvas=  new@Of   fscreenC  an  vas   (76,13+24+16
);co        n      te       xt=ca  nvas.g  etContext      ("   2d");context.
font="13px@sans-serif";context.textBasel   ine="top";context.fillText(d.getF
ullYear()+"@/@"+String(d.getMonth()+1).p   adStart(2,"0"),4,0);context.font=
"24px@sans-serif";context.fillText(String(d.getDate()).padStart(2,"0"),4,13)
;contex         t.fo             nt="16px@sans-serif";context.fillText(["Sun
","Mon           ","             Tue","Wed","Thu","Fri","Sat"][d.getDay()],4
+36,13     +     7);context.fo   nt="16px@sans-serif";hhmm=String(d.getHours
()).p     adS     tart(2,"0")    +":"+String(d.getMinutes()).padStart(2,"0")
;cont    ext.f    illText(hh     mm,4,13+24);w=context.measureText(hhmm).wid
th;co    ntext    .font="13p    x@sans-serif";context.fillText(String(d.getS
econd    s()).    padStart(     2,"0"),4+w       +2,13+24+3);imageData=conte
xt.ge   tImageD   ata(0,0,c    anvas.widt   h,    canvas.height);template=""
;row=   "";rowC   harCount     =0;prevRo   wChar  Count=0;for(i=0;i<imageDat
a.dat   a.lengt   h;i+=4){    alpha=imag   eData.da   ta[   i       +3];if(a
lpha=   ==0){ro   w+="a";    rowCharCoun   t++;}els   e{r   o   w+   ="@";}i
f((i/    4+1)%    imageDa    ta.width===0     ){if(   row   C  har   Count!=
=prev    RowCh    arCoun    t||rowCharCount!     ==   ima   g  eDa   ta.widt
h){te    mplat    e+=row    +n;}row="";prevRowC   h   arC   o  unt   =rowCha
rCoun     t;r     owCha    rCount=0;}}margin="a   "   .re   p  eat   (imageD
ata.wi     d     th)+n;    template=marg  in+te   m   pla   t  e+m   argin;d
oc=doc           ument    .documentEleme    nt    ;   do    c  .st   yle.fon
tFamily         ="mono    space";doc.styl       e.wh        i  teS   pace="n
owrap";text=document.querySelector("script").outerHTML.replace(/@/g,"").repl
aceAll      (n,       "").replace(/   #[zou     ntamghis]*#/,"#"+"#");diffLe
ngth=   te   mp  la    te.replace(    /@/g   ,   "").replaceAll(n,"").length
-text  .le   ngth;mi   zumasi=""      ;fo   r(i   =0;i<diffLength;i++){mizum
asi+   ="zo  unotama   goh   aoisii   zou   "[i   %21]     ;}t     ext=text.
repl   ace(   "#"+"#   ","   #"+miz   uma   si+"  #")   ;   r   e   sult1=""
;res   ult2   ="";i=   0;for(j=0;j<   tem   plat  e.length  ;  j++   ){if(te
mpla   te[j   ]===n   ){result1+="<   br>    ";   result2+  =  n;c   ontinue
;}if   (tem   plat   e[j]==="@"){re   sult1       +="<spa   n   @s   tyle='b
ackg   roun  d-co   lor:yellow;'>&"   +"nbsp;</   span>"   ;re       sult2+=
"@";c  ont   inu   e;}char=text[i++   ];result2   +=cha   r;if(cha  r==="<")
{char   ="   &"   +"lt;";}   else@i   f(char==   =">"   ){char="&   "+"gt;";
}resul      t1          +=   cha        r;     }text=        t     ext.repla
ce(/#[zountamghis]*#/,"#"+"#");doc.innerHTML=result1+text;console.log(result
2);`.replace(/[\s@]/g,m=>m==="@"?s:"");setInterval(()=>eval(z),16);</script>
```

## 読みやすくしたコード

```html
<script>
s = String.fromCharCode(32);
n = String.fromCharCode(10);
z = `
/*#zounotamagohaoisiizou#*/

d = new@Date();

canvas = new@OffscreenCanvas(76, 13 + 24 + 16);
context = canvas.getContext("2d");

context.font = "13px@sans-serif";
context.textBaseline = "top";
context.fillText(d.getFullYear() + "@/@" + String(d.getMonth() + 1).padStart(2, "0"), 4, 0);

context.font = "24px@sans-serif";
context.fillText(String(d.getDate()).padStart(2, "0"), 4, 13);

context.font = "16px@sans-serif";
context.fillText(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()], 4 + 36, 13 + 7);

context.font = "16px@sans-serif";
hhmm = String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
context.fillText(hhmm, 4, 13 + 24);

w = context.measureText(hhmm).width;
context.font = "13px@sans-serif";
context.fillText(String(d.getSeconds()).padStart(2, "0"), 4 + w + 2, 13 + 24 + 3);

imageData = context.getImageData(0, 0, canvas.width, canvas.height);

template = "";
row = "";
rowCharCount = 0;
prevRowCharCount = 0;
for (i = 0; i < imageData.data.length; i += 4) {
    alpha = imageData.data[i + 3];
    if (alpha === 0) {
        row += "a";
        rowCharCount++;
    }
    else {
        row += "@";
    }
    if ((i / 4 + 1) % imageData.width === 0) {
        if (rowCharCount !== prevRowCharCount || rowCharCount !== imageData.width) {
            template += row + n;
        }
        row = "";
        prevRowCharCount = rowCharCount;
        rowCharCount = 0;
    }
}
margin = "a".repeat(imageData.width) + n;
template = margin + template + margin;

doc = document.documentElement;
doc.style.fontFamily = "monospace";
doc.style.whiteSpace = "nowrap";

text = document.querySelector("script").outerHTML
.replace(/@/g, "")
.replaceAll(n, "")
.replace(/#[zountamghis]*#/, "#" + "#");

diffLength = template.replace(/@/g, "").replaceAll(n, "").length - text.length;
mizumasi = "";
for (i = 0; i < diffLength; i++) {
    mizumasi += "zounotamagohaoisiizou"[i % 21];
}
text = text.replace("#" + "#", "#" + mizumasi + "#");

result1 = "";
result2 = "";
i = 0;
for (j = 0; j < template.length; j++) {
    if (template[j] === n) {
        result1 += "<br>";
        result2 += n;
        continue;
    }
    if (template[j] === "@") {
        result1 += "<span@style='background-color:yellow;'>&" + "nbsp;</span>";
        result2 += "@";
        continue;
    }

    char = text[i++];
    result2 += char;
    if (char === "<") {
        char = "&" + "lt;";
    }
    else@if (char === ">") {
        char = "&" + "gt;";
    }
    result1 += char;
}
text = text.replace(/#[zountamghis]*#/, "#" + "#");
doc.innerHTML = result1 + text;
console.log(result2);
`.replace(/[\s@]/g, m => m === "@" ? s : "");
setInterval(() => eval(z), 16);
</script>
```

## 仕組み

雑に説明すると、Canvasに時間書いて、それを基にQuineを作っているだけ。

## 終わりに

QuineアニメーションでCanvasを使うのはチートに近いね。

Canvasに描画したやつを白黒でマッピングするだけだから簡単に実装できる。

CanvasのおかげでHTML/JSのトリッキーなQuineは他の言語と比べてかなり作りやすいかも。

特にHTMLはCSSで色付けが容易なのもいい。

ちょっとずるいかもなんて思ったり。