diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-04-16 03:28:54 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-04-16 03:28:54 +0900 |
| commit | 52c3d0499e3fbf4930034ab452af3f562f5672ed (patch) | |
| tree | d3c165f9362b9c6768b4c1af0fd4e6f1b5951015 | |
| parent | 0d39bfde42901478e8ca5e22b146263b94658a5f (diff) | |
| download | trick-2025-52c3d0499e3fbf4930034ab452af3f562f5672ed.tar.gz trick-2025-52c3d0499e3fbf4930034ab452af3f562f5672ed.tar.zst trick-2025-52c3d0499e3fbf4930034ab452af3f562f5672ed.zip | |
| -rw-r--r-- | 1-sliding-puzzle/authors.markdown | 3 | ||||
| -rw-r--r-- | 1-sliding-puzzle/entry.pretty.rb | 80 | ||||
| -rw-r--r-- | 1-sliding-puzzle/entry.rb | 44 | ||||
| -rw-r--r-- | 1-sliding-puzzle/remarks.markdown | 90 | ||||
| -rw-r--r-- | 2-the-false-awakens/authors.markdown | 3 | ||||
| -rw-r--r-- | 2-the-false-awakens/entry.clean.rb | 100 | ||||
| -rw-r--r-- | 2-the-false-awakens/entry.rb | 57 | ||||
| -rw-r--r-- | 2-the-false-awakens/remarks.markdown | 248 | ||||
| -rw-r--r-- | 3-ruby-tags/Gemfile | 5 | ||||
| -rw-r--r-- | 3-ruby-tags/Gemfile.lock | 14 | ||||
| -rw-r--r-- | 3-ruby-tags/authors.markdown | 3 | ||||
| -rw-r--r-- | 3-ruby-tags/entry.rb | 120 | ||||
| -rw-r--r-- | 3-ruby-tags/index.html | 45 | ||||
| -rw-r--r-- | 3-ruby-tags/remarks.markdown | 63 | ||||
| -rw-r--r-- | README.md | 7 |
15 files changed, 882 insertions, 0 deletions
diff --git a/1-sliding-puzzle/authors.markdown b/1-sliding-puzzle/authors.markdown new file mode 100644 index 0000000..f308e56 --- /dev/null +++ b/1-sliding-puzzle/authors.markdown @@ -0,0 +1,3 @@ +* Kensuke Imamura (@nsfisis) + * nsfisis@gmail.com + * cctld: jp diff --git a/1-sliding-puzzle/entry.pretty.rb b/1-sliding-puzzle/entry.pretty.rb new file mode 100644 index 0000000..80e493e --- /dev/null +++ b/1-sliding-puzzle/entry.pretty.rb @@ -0,0 +1,80 @@ +$b = 'bcdefghia'; +eval($s=' +t = { + h: [1, 0], + j: [0, -1], + k: [0, 1], + l: [-1, 0], + i: [0, 0] +}; +p = -> a, b { + i = $b.index(?a); + x = i % 3 + a; + y = i / 3 + b; + (x < 0 || 2 < x || y < 0 || 2 < y) || + ($b[j = y * 3 + x], $b[i] = $b[i], $b[j]) +}; +i = (o = :"#{$*[0]}") == :i; +while (o != :q); + if (q = t[o]); + p[*q]; + else; + (o = o.to_s.to_i) > 0 && ( + srand(o); + a = b = n = 45; + (p[*(a, b = (t.values - [[-a, -b], [0, 0]]).sample)] && n -= 1) while (n > 0); + ); + end; + print("$b=\47#$b\47;eval($s=\47"); + n = "1111141424414143341142414424344111143434".scan(/.{,5}/); + m = "000001100101111".scan(/.{,3}/); + s = 0; + b = -> _ = 1 {_.times{print($s[(s += 1) - 1])}}; + w = -> _ = 13 {print "\40" * _}; + b[22]; + puts; + 3.times {|y| + b[45]; + puts; + b[2]; + w[]; + b[]; + w[]; + b[]; + w[]; + b[2]; + puts; + 10.times {|l| + b[2]; + 3.times {|x| + w[2]; + $b[y * 3 + x] == ?a ? ( + (l == 3 && !print("\40\40\124\122\111\103\113\40\40")) || + (l == 5 && !print("\105\120\111\123\117\104\105\40\111")) || + w[9] + ) : + m[n[$b[y * 3 + x].ord - 98][l / 2].to_i].chars{(_1 == ?1 ? b : w)[3]}; + w[2]; + b[x / 2 + 1] + }; + puts + }; + b[2]; + w[]; + b[]; + w[]; + b[]; + w[]; + b[2]; + puts; + b[45]; + puts + }; + b[9]; + puts("\47.gsub(/[[:space:][:upper:]]/,%%%));"); + i || + break; + print("\12enter\40[hjkl]\40or\40[q]uit\40>\40"); + o = :"#{$stdin.gets.chomp}"; + puts; +end'.gsub(/[[:space:][:upper:]]/,%%%)); diff --git a/1-sliding-puzzle/entry.rb b/1-sliding-puzzle/entry.rb new file mode 100644 index 0000000..2f3f31e --- /dev/null +++ b/1-sliding-puzzle/entry.rb @@ -0,0 +1,44 @@ +$b='bcdefghia';eval($s='t={h:[1,0],j:[0,-1],k: +[0,1],l:[-1,0],i:[0,0]};p=->a,b{i=$b.index(?a +); x = i% +3+ a;y = i/3+b;(x< 0 ||2<x||y< 0| +|2 <y) | |($b[j=y* 3 +x],$b[i] =$ +b[ i], $ b[j ] )}; i= +(o =:" # {$* [ 0]} ") +== :i; w hile(o!=: q );if(q=t[ o] +); p[* q ];else;(o = o.to_s.to _i +)> 0&& ( sra n d(o ); +a= b=n = 45; ( p[* (a +,b =(t . values-[[ - a,-b],[0, 0] +]) .sa m ple)]&&n- = 1)while(n >0 +); ) ; en +d;print("$b=\47#$b\47;eval($s=\47");n="111114 +1424414143341142414424344111143434".scan(/.{, +5} / ) ;m +=" 000 001 1 00101111" . scan(/.{, 3} +/) ;s= 0;b = ->_=1{_.t i mes{print ($ +s[ (s+ =1) - 1]) } };w =- +>_ =13 {pr i nt" \ 40" *_ +}; b[22];put s ;3.times{ | y|b[45];p ut +s; b[2];w[]; b [];w[];b[ ] ;w[];b[2] ;p +ut s;1 0 .ti m es{ |l| b[ +2] ;3. t ime s {|x |w[ 2] +;$ b[y * 3+x]==?a? ( (l==3&&!p ri +nt ("\ 4 0\40\124\ 1 22\111\10 3\ +11 3 \ 40 +\40"))||(l==5&&!print("\105\120\111\123\117\1 +04\105\40\111"))||w[9]):m[n[$b[y*3+x].ord-98] +[l / 2 ]. +to _i].chars { (_1==?1?b : w) +[3 ]};w[2];b [ x/2+1]};p u ts +}; b[2 ] ;w[ ];b [ ]; +w[ ];b [ ];w []; b TRICK [2 +]; put s ;b[45];pu t s} +;b [9] ; puts("\47 . EPISODE I gs +ub (/[ [ :sp ace : ][ +:u ppe r :]] /,% % %) +); "); i ||break;p r in +t( "\1 2 enter\40[ h jk +l] \ 4 0o +r\40[q]uit\40>\40");o=:"#{$stdin.gets.chomp}" +;puts;end'.gsub(/[[:space:][:upper:]]/,%%%)); diff --git a/1-sliding-puzzle/remarks.markdown b/1-sliding-puzzle/remarks.markdown new file mode 100644 index 0000000..c5bb8ae --- /dev/null +++ b/1-sliding-puzzle/remarks.markdown @@ -0,0 +1,90 @@ +# 3x3 Sliding Puzzle Quine + + + +## Remarks + +This is a quine-like program. + +When run without arguments, it outputs `entry.rb` itself: + +``` +$ ruby entry.rb +$ ruby entry.rb | ruby +$ ruby entry.rb | ruby | ruby +``` + +It has been tested with the following environment: + +``` +$ ruby --version +ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux] +``` + + + +## Description + +This program is not only a quine but also a 3x3 sliding puzzle. + + +### How to Play + +You can move a tile adjacent to the empty slot using `h` (left), `j` (down), `k` (up), `l` (right). +For example: + +``` +$ ruby entry.rb l +``` + +This command moves `8` to right. +Of course, the output source code is still a quine. These two commands' output are identical: + +``` +$ ruby entry.rb l +$ ruby entry.rb l | ruby +``` + + +### How to Shuffle + +You can shuffle the board by passing a seed as an argument: + +``` +$ ruby entry.rb 2025 +``` + +The output puzzle can be solved using the following move sequence: + +``` +$ ruby entry.rb 2025 | \ + ruby - k | ruby - l | ruby - k | ruby - j | ruby - h | \ + ruby - j | ruby - l | ruby - k | ruby - h | ruby - j | \ + ruby - h | ruby - l | ruby - k | ruby - k | ruby - l | \ + ruby - j | ruby - h | ruby - h | ruby - k | ruby - j | \ + ruby - l | ruby - l | ruby - k | ruby - h | ruby - h > solved.rb +$ diff entry.rb solved.rb +``` + + +### Interactive mode + +In addition, it supports interactive mode. Simply run: + +``` +$ ruby entry.rb i +``` + +It will display the current board and ask for your next move. +You can exit by typing `q` in interactive mode. + + + +## Internals + +* `entry.pretty.rb` is a human-readable version. +* `$b` represents the board's current state. +* `$s` is the program's own source code for quine generation. It contains no "padding" characters, such as unnecessary parentheses, semicolons, or comments. +* Font data is encoded in the variables `n` and `m`. +* The string "TRICK EPISODE I" shown in the empty slot is removed using `gsub(/[[:upper:]]/)`. This works because there are no uppercase letters in `$s` except for that. +* The size of the `entry.rb` is 2025 byte. diff --git a/2-the-false-awakens/authors.markdown b/2-the-false-awakens/authors.markdown new file mode 100644 index 0000000..f308e56 --- /dev/null +++ b/2-the-false-awakens/authors.markdown @@ -0,0 +1,3 @@ +* Kensuke Imamura (@nsfisis) + * nsfisis@gmail.com + * cctld: jp diff --git a/2-the-false-awakens/entry.clean.rb b/2-the-false-awakens/entry.clean.rb new file mode 100644 index 0000000..9a3ce0b --- /dev/null +++ b/2-the-false-awakens/entry.clean.rb @@ -0,0 +1,100 @@ +# A long time ago in a galaxy far, +# far away.... + + + + + + + +eval(%w{ + +def'false(p,s=[],v={}); + c=0; + while(c<p.size); + if(!(i=p[c]));false; + elsif"$"==i;s.push(s.last); + elsif"%"==i;s.pop; + elsif(28.chr)==i;s.push(s.pop,s.pop); + elsif"@"==i;s[-3],s[-2],s[-1]=s[-2],s[-1],s[-3]; + elsif"+"==i;s.push(s.pop+s.pop); + elsif"-"==i;s.push(-s.pop+s.pop); + elsif"*"==i;s.push(s.pop*s.pop); + elsif"/"==i;s.push(s.pop.then{s.pop/_1}); + elsif"_"==i;s.push(-s.pop); + elsif"&"==i;s.push(s.pop&s.pop); + elsif"|"==i;s.push(s.pop|s.pop); + elsif"~"==i;s.push(~s.pop); + elsif">"==i;s.push((s.pop<s.pop)?~0:0); + elsif"="==i;s.push((s.pop==s.pop)?~0:0); + elsif"!"==i;self.false(s.pop,s,v); + elsif"?"==i;s.pop.then{s.pop!=0&&self.false(_1,s,v)}; + elsif":"==i;v[s.pop]=s.pop; + elsif";"==i;s.push(v[s.pop]); + elsif","==i;print(s.pop.chr); + elsif"."==i;print(s.pop); + elsif/[0-9]/=~i;s.push(i.to_i); + elsif/[a-z]/=~i;s.push(i); + elsif"["==i; + q=c+=(b=1); + while(b!=0); + b+={"["=>1,"]"=>-1}[p[c]]||0; + c+=1; + end; + s.push(p[q...(c-=1)]); + elsif"#"==i; + a,b=s.pop,s.pop; + loop{; + self.false(b,s,v); + s.pop!=0&&break; + self.false(a,s,v); + }; + end; + c+=1; + end; +end; + + +class'String; + def&(other); + (self[1..]+other[1..]) + .chars + .map{(_1.upcase==_1)??1:?0} + .join + .to_i(2) + .chr; + end; + + def|(other); + (self||"")+(other||""); + end; + + def'method_missing(_); + self; + end; +end; + + +def'Object.const_missing(n); + const_set(n,n.to_s); +end; + +}.join.gsub("'"," ")) + + + + + EPISODE_VII + + THE_FALSE_AWAKENS + + + + +self.false(( + May.the.false.be.with.you; + TrUE&FaLsE|TrUe&FaLse|TrUe&FAlSe|TrUE&FalSE|TrUe&FAlSe|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUE&FAlsE|TrUe&FAlSE|TrUe&FALse| + TrUE&FaLSE|TrUe&FAlSE|TrUe&FALse|TrUE&FalSe|TrUe&FALsE|TrUe&FALse|TrUE&FAlse|TrUe&FALsE|TrUe&FALse|TrUe&FALse|TrUE&FaLse|TrUE&FAlse| + TrUe&FAlSe|TrUe&FALse|TrUE&FaLsE|TrUE&FalSe|TrUE&False|TrUE&FalSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUE&FalSe|TrUE&FaLsE| + TrUe&FAlSe|TrUe&FALse|false +)) diff --git a/2-the-false-awakens/entry.rb b/2-the-false-awakens/entry.rb new file mode 100644 index 0000000..199d42a --- /dev/null +++ b/2-the-false-awakens/entry.rb @@ -0,0 +1,57 @@ +# A long time ago in a galaxy far, +# far away.... + + + + + + + +eval(%w{def'false(p,s=[],v={});c=0;while(c<p.size);if( !(i=p[c]));false;e lsif"$"==i;s. push (s.l +ast);elsif"%"==i;s.pop;elsif(28.chr)==i;s.push(s.pop,s.p op);elsif"@"==i;s[ -3],s[-2],s[-1]=s [-2] ,s[- + 1],s [-3] ;els if"+ "==i ;s.p ush( s.po + p+s. pop) ;el sif" -"== i;s. push (-s. + pop+ s.po p); elsi f"*" ==i; s.pu sh(s + .pop *s.p op); elsi f"/" ==i; s.pu + sh(s .pop.then{s.pop/_1});e lsif "_"= =i;s.push + (-s. pop);elsif"&"==i;s.pu sh(s .pop &s.pop);el + sif" |"== i;s. push (s.p op|s .pop) + ;els if"~ "==i ;s.p ush( ~s.p op); elsi + f">" ==i; s.pu sh(( s.po p<s. pop) ?~0: + 0);e lsif "="= =i;s .pus h((s .pop ==s. + pop) ?~0: 0);elsif" !"==i;self.false(s .pop,s,v);elsif"? "==i ;s.pop.then{s.pop!=0& + &sel f.fa lse(_1, s,v)};elsif":"==i; v[s.pop]=s.po p;el sif";"==i;s.push(v[ + + +s.pop]);elsif","==i;print(s.pop.chr) ;elsif"."==i;p rint(s.pop);elsi f/[0-9]/=~i;s.push(i.to_i);elsif/[a-z]/=~i;s +.push(i);elsif"["==i;q=c+=(b=1);while(b !=0);b+={"["=>1,"] "=>-1}[p[c]]||0;c+= 1;end;s.push(p[q...(c-=1)]);elsif"#"==i;a,b=s.p + op,s .pop ;loo p{;s elf. + fals e(b ,s, v);s .pop + !=0& &br eak ;sel f.fa + lse( a,s ,v) ;};e nd;c + +=1;e nd; end ;clas s'Str + ing;d ef& (ot her); (self + [1.. ]+o the r[1. .]). + char s.m ap{ (_1. upca + se== _1) ??1 :?0} .joi + n.to _i(2 ).ch r;en d;de + f|(other);(self||"" )+(other||"");end; def'method_missing(_);self;end;end;def'Obj + ect.const_missin g(n);const_set (n,n.to_s);end}.join.gsub("'"," ")) + + + + + EPISODE_VII + + THE_FALSE_AWAKENS + + + + +self.false(( + May.the.false.be.with.you; + TrUE&FaLsE|TrUe&FaLse|TrUe&FAlSe|TrUE&FalSE|TrUe&FAlSe|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUE&FAlsE|TrUe&FAlSE|TrUe&FALse| + TrUE&FaLSE|TrUe&FAlSE|TrUe&FALse|TrUE&FalSe|TrUe&FALsE|TrUe&FALse|TrUE&FAlse|TrUe&FALsE|TrUe&FALse|TrUe&FALse|TrUE&FaLse|TrUE&FAlse| + TrUe&FAlSe|TrUe&FALse|TrUE&FaLsE|TrUE&FalSe|TrUE&False|TrUE&FalSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUE&FalSe|TrUE&FaLsE| + TrUe&FAlSe|TrUe&FALse|false +)) diff --git a/2-the-false-awakens/remarks.markdown b/2-the-false-awakens/remarks.markdown new file mode 100644 index 0000000..7f072a9 --- /dev/null +++ b/2-the-false-awakens/remarks.markdown @@ -0,0 +1,248 @@ +# Episode VII: The False Awakens + +> A long time ago in a galaxy far, +> far away.... + + + + +## Remarks + +Just run it with: + +``` +$ ruby entry.rb +``` + +It will output "TRICK 2025". + +It has been tested under the following environment: + +``` +$ ruby --version +ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux] +``` + + + +## Description + +The program consists of two parts: + +* an interpreter of a subset of [FALSE esolang](https://esolangs.org/wiki/FALSE) and +* an obfuscated FALSE source code. + +### FALSE interpreter + +The interpreter part is embeeded into an ASCII art, "TRICK 2025" shaped like the Star Wars logo. +For more readable version, see `entry.clean.rb`. + +### FALSE source code + +The FALSE source code is encoded to the following expression: + +``` + TrUE&FaLsE|TrUe&FaLse|TrUe&FAlSe|TrUE&FalSE|TrUe&FAlSe|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUE&FAlsE|TrUe&FAlSE|TrUe&FALse| + TrUE&FaLsE|TrUe&FAlSE|TrUe&FALse|TrUE&FalSe|TrUe&FALsE|TrUe&FALse|TrUE&FAlse|TrUe&FALsE|TrUe&FALse|TrUe&FALse|TrUE&FaLse|TrUE&FAlse| + TrUe&FAlSe|TrUe&FALse|TrUE&FaLsE|TrUE&FalSe|TrUE&False|TrUE&FalSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUE&FalSe|TrUE&FaLsE| + TrUe&FAlSe|TrUe&FALse|false +``` + +Decoded version: + +``` +5$*3*$$$$9+,7+,2-,8-,,48*,5202....25*, +``` + +FALSE is a simple stack-based esolang. For instance, + +* `5` pushes 5: `[5]` +* `$` duplicates the top: `[5 5]` +* `*` pushes the product: `[25]` +* `3` pushes 3: `[25 3]` +* `*` pushes the product: `[75]` +* `$$$$` duplicates the top 4 times: `[75 75 75 75 75]` +* `9` pushes 9: `[75 75 75 75 75 9]` +* `+` pushes the sum: `[75 75 75 75 84]` +* `,` writes the character: `[75 75 75 75]` (output `T`) +* `7` pushes 9: `[75 75 75 75 7]` +* `+` pushes the sum: `[75 75 75 82]` +* `,` writes the character: `[75 75 75]` (output `R`) +* ... + + +The source code is full of Star Wars references and homages. + + +``` +# A long time ago in a galaxy far, +# far away.... +``` + +This is the iconic Star Wars opening phrase. + +``` +# (inside the eval'ed code) + + while(c<p.size); + if(!(i=p[c]));false; + elsif"$"==i;s.push(s.last); + elsif"%"==i;s.pop; + ... +``` + +All comparison operations used in the program are written in the so-called "Yoda notation" (placing constants on the left hand side), referencing the Jedi Master Yoda. + + +``` + EPISODE_VII + + THE_FALSE_AWAKENS +``` + +This parodies the Star Wars Episode VII subtitle, "The Force Awakens." +"Force" refers to a supernatural power in Star Wars, but for our program, "false" has awakened instead because we used many `false`s. + + +``` + May.the.false.be.with.you; +``` + +This is a twist on one of the most famous Star Wars lines, "May the Force be with you." + + + + +## Internals + +### FALSE interpreter + +The interepreter is implemented as a method named `false`. In Ruby, you can use keywords as method names like this. It cannot be invoked directly, but you can call such a method via explicit receiver:`self.false()` + + +### Encoding the FALSE program + +The FALSE program is obfuscated like this: + +``` + TrUE&FaLsE|TrUe&FaLse|TrUe&FAlSe|TrUE&FalSE|TrUe&FAlSe|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUe&FaLse|TrUE&FAlsE|TrUe&FAlSE|TrUe&FALse| + TrUE&FaLsE|TrUe&FAlSE|TrUe&FALse|TrUE&FalSe|TrUe&FALsE|TrUe&FALse|TrUE&FAlse|TrUe&FALsE|TrUe&FALse|TrUe&FALse|TrUE&FaLse|TrUE&FAlse| + TrUe&FAlSe|TrUe&FALse|TrUE&FaLsE|TrUE&FalSe|TrUE&False|TrUE&FalSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUe&FALSe|TrUE&FalSe|TrUE&FaLsE| + TrUe&FAlSe|TrUe&FALse|false +``` + +Each segment separated by `|` represents one instruction. + +* `TrUE&FaLsE` => `5` +* `TrUe&FaLse` => `$` +* `TrUe&FAlSe` => `*` +* ... + +This is achieved by defining `Object.const_missing`, `String#&`, and `String#|`. + +First, our `const_missing `dynamically defines constants like `TrUE`: + +``` +# (inside the eval'ed code) + +def Object.const_missing(n); + const_set(n,n.to_s); +end; +``` + +With that, constant `TrUE` is resolved to string `"TrUE"`. + +Second, such strings are concatenated with `&` with special decoding: + +``` +# (inside the eval'ed code) + +class'String; + def&(other); + (self[1..]+other[1..]) + .chars + .map{(_1.upcase==_1)??1:?0} + .join + .to_i(2) + .chr; + end; +end; +``` + +For `TrUE & FaLsE`, + +``` + (self[1..]+other[1..]) # => "rUEaLsE" + .chars # => ["r","U","E","a","L","s","E"] + .map{(_1.upcase==_1)??1:?0} # => ["0","1","1","0","1","0","1"] + .join # => "0110101" + .to_i(2) # => 53 + .chr; # => "5" +``` + +Finally, each FALSE instruction is concatenated by `|` operator: + +``` +# (inside the eval'ed code) + +class'String; + def|(other); + (self||"")+(other||""); + end; +end; +``` + + +# Misc. + +``` + EPISODE_VII + + THE_FALSE_AWAKENS +``` + +This code is equivalent to this: + +``` + "EPISODE_VII" + + "THE_FALSE_AWAKENS" +``` + +It does nothing. + +``` + May.the.false.be.with.you; +``` + +`May` is resolved to string `"May"` and the rest method chain is dispatched to `String#method_missing` defined here: + +``` +# (inside the eval'ed code) + +class'String; + def'method_missing(_); + self; + end; +end; +``` + +It does nothing too. + + + + + +## Limitations + +Our FALSE interpreter does not support the following instructions: + +* `'c`: literal character "c" +* `ø`: pick +* `^`: input +* `"string"`: output "string" +* `ß`: flush the inout/output buffer. +* `{...}`: comments +* `\``: compile + +Also, only one-digit numeric literals are supported, so `123` is interpreted as pushing `1`, `2` and `3` separately. diff --git a/3-ruby-tags/Gemfile b/3-ruby-tags/Gemfile new file mode 100644 index 0000000..72dcf70 --- /dev/null +++ b/3-ruby-tags/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "webrick" diff --git a/3-ruby-tags/Gemfile.lock b/3-ruby-tags/Gemfile.lock new file mode 100644 index 0000000..b0d2268 --- /dev/null +++ b/3-ruby-tags/Gemfile.lock @@ -0,0 +1,14 @@ +GEM + remote: https://rubygems.org/ + specs: + webrick (1.9.1) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + webrick + +BUNDLED WITH + 2.6.2 diff --git a/3-ruby-tags/authors.markdown b/3-ruby-tags/authors.markdown new file mode 100644 index 0000000..f308e56 --- /dev/null +++ b/3-ruby-tags/authors.markdown @@ -0,0 +1,3 @@ +* Kensuke Imamura (@nsfisis) + * nsfisis@gmail.com + * cctld: jp diff --git a/3-ruby-tags/entry.rb b/3-ruby-tags/entry.rb new file mode 100644 index 0000000..4ad7b7f --- /dev/null +++ b/3-ruby-tags/entry.rb @@ -0,0 +1,120 @@ +eval $s=<<'TRICK' +require "cgi" +require "js" +require "prism" + +@s = String.new +def p(s) = @s << s + +def tag(t, s, c) = "<#{t} class=#{c}>#{s}</#{t}>" +def span(s, c) = tag("span", s, c) + +def ruby(t) + s = CGI.escape_html(t.value) + if r = rt(t) + tag("ruby", s + tag("rp", "(", "") + tag("rt", r, "") + tag("rp", ")", ""), t.type) + else + span(s, t.type) + end +end + +def kana(s) + s + &.scan(/.{2}/) + &.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)} + &.*("") +end + +def rt(t) + r = { + :"&&" => "1136", + :"=" => "04199275", + :"||" => "623147", + :$s => "41750825", + :* => "111775", + :+ => "557325", + :- => "62044225", + :<< => "02588341", + :@s => "0235400825", + LESS: "2371064274", + USTAR: "", + c: "2392", + cgi: "239224920204", + chr: "336792", + def: "3953", + document: "411369658340", + downcase: "320683179225", + each: "043533", + else: "087525", + encoding: "0883199239038316", + end: "088341", + end_column: "088341117364", + end_line: "088341730483", + end_with?: "088341060326", + escape_html: "082517925508043338039208640875", + getelementbyid: "183540087665834048040204390392", + global: "1677924875", + if: "0453", + innerhtml: "0483429208043338039208640875", + js: "2407920825", + kana: "1142", + l: "0875", + lex: "76351525", + location: "771792237183", + map: "623555", + new: "436992", + nil: "4375", + p: "5292", + prism: "55742664", + r: "029275", + require: "7415790402", + rp: "0292755292", + rt: "029275380392", + ruby: "7551", + s: "0825", + scan: "25136783", + span: "254983", + star: "111775", + start_column: "25319240117364", + start_line: "25319240730483", + string: "2540748316", + t: "380392", + tag: "3116", + to_i: "4005920204", + type: "310455", + utf_8: "70923803920853080440", + value: "48746992", + x: "08351525", + y: "7904", + } + kana( + r[:"#{t.type}"] || + r[s = :"#{t.value.downcase}"] || + s.end_with?(":") && r[:"#{s[..-2]}"] || + nil + ) +end + +y = 1 +x = 0 +Prism.lex($s).value[..-2].each {|t, *| + l = t.location + r = l.start_line + if y < r + p "\n" * (r - y) + x = 0 + end + c = l.start_column + if x < c + p " " * (c - x) + end + p ruby(t) + y = l.end_line + x = l.end_column +} + +JS.global[:document].getElementById("src")[:innerHTML] = + span("eval $s=<<'TRICK'\n", "COMMENT") + + @s + + span("TRICK\n", "COMMENT") +TRICK diff --git a/3-ruby-tags/index.html b/3-ruby-tags/index.html new file mode 100644 index 0000000..63a6a6d --- /dev/null +++ b/3-ruby-tags/index.html @@ -0,0 +1,45 @@ +<html> + <script src="https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@2.7.1/dist/browser.script.iife.js"></script> + <script type="text/ruby" src="entry.rb"></script> + <title>TRICK 2025 (Episode I)</title> + <body> + <style> + pre { + background-color: #eee; + color: #333; + padding: 1em; + border-radius: 4px; + overflow: auto; + } + + .COMMENT { + color: #777; + font-style: italic; + } + + .CONSTANT, .GLOBAL_VARIABLE, .INSTANCE_VARIABLE, .IDENTIFIER { + color: #088; + } + + .KEYWORD_BREAK, .KEYWORD_CASE, .KEYWORD_DEF, .KEYWORD_ELSE, .KEYWORD_END, .KEYWORD_IF, .KEYWORD_IF_MODIFIER, .KEYWORD_WHEN { + color: #04c; + font-weight: bold; + } + + .INTEGER, .KEYWORD_NIL { + color: #990; + } + + .STRING_BEGIN, .STRING_END, .HEREDOC_START, .HEREDOC_END, .REGEXP_BEGIN, .REGEXP_END, .SYMBOL_BEGIN, .LABEL, .STRING_CONTENT { + color: #d50; + } + + ruby * { + user-select: none; + } + </style> + <main> + <pre style="font-size: 16px;"><code id="src"></code></pre> + </main> + </body> +</html> diff --git a/3-ruby-tags/remarks.markdown b/3-ruby-tags/remarks.markdown new file mode 100644 index 0000000..2621a32 --- /dev/null +++ b/3-ruby-tags/remarks.markdown @@ -0,0 +1,63 @@ +# Ruby Tags + + + +## Remarks + +This program utilizes `ruby.wasm`. To run it, first launch an HTTP server: + +``` +$ bundle install +$ bundle exec ruby -run -e httpd +``` + +Then access <http://127.0.0.1:8080/>. + +NOTE: gems are used only for the server. + +It has been tested under the following environment: + +``` +$ ruby --version +ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux] +``` + +`ruby.wasm` version: + +``` +<script src="https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@2.7.1/dist/browser.script.iife.js"></script> +``` + + + +## Description + +This is a quine-like program that renders its own source code as HTML elements in browser. +In addition, it highlights the source code and adds "ruby" annotations for each token. + +Example for `<ruby>` tag: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby + +> ``` +> <ruby> 明日 <rp>(</rp><rt>Ashita</rt><rp>)</rp> </ruby> +> ``` + +The pronounciation table is compressed and hard-coded to `entry.rb`. + + + +## Internals + +### Syntax Highlighting + +Syntax highlighting is powered by Prism, a recently introduced parser/lexer ecosystem. The program calls `Prism.lex()` and iterates over the resulting token stream to apply highlighting. + +### Ruby Annotations + +Initially, I tried adding ruby annotations to *every* token. However, the pronounciation data for all tokens was too large to fit within the size limit. Although I implemented a simple compression algorithm (see `kana()` function), I finally had to reduce the table size. + +### Compression Algorithm for the Pronounciation Table + +The compression algorithm is fairly simple: each Katakana letter is encoded as a two-digit integer, offset by U+30A0. +There are certainly more sophisticated approaches. + +Because the `index.html` has no strict size limit according to the regulation, the table data could be embedded there instead. However, that approach was not very elegant. diff --git a/README.md b/README.md new file mode 100644 index 0000000..669021c --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# My entries of TRICK 2025 + +I submitted the following entries to TRICK 2025: + +* [3x3 Sliding Puzzle Quine](./1-sliding-puzzle/) +* [Episode VII: The False Awakens](./2-the-false-awakens/) +* [Ruby Tags](./3-ruby-tags/) |
