aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--1-sliding-puzzle/authors.markdown3
-rw-r--r--1-sliding-puzzle/entry.pretty.rb80
-rw-r--r--1-sliding-puzzle/entry.rb44
-rw-r--r--1-sliding-puzzle/remarks.markdown90
-rw-r--r--2-the-false-awakens/authors.markdown3
-rw-r--r--2-the-false-awakens/entry.clean.rb100
-rw-r--r--2-the-false-awakens/entry.rb57
-rw-r--r--2-the-false-awakens/remarks.markdown248
-rw-r--r--3-ruby-tags/Gemfile5
-rw-r--r--3-ruby-tags/Gemfile.lock14
-rw-r--r--3-ruby-tags/authors.markdown3
-rw-r--r--3-ruby-tags/entry.rb120
-rw-r--r--3-ruby-tags/index.html45
-rw-r--r--3-ruby-tags/remarks.markdown63
-rw-r--r--README.md7
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/)