対戦型Quine Snake Battleをruby 2.7に対応させてCIを追加した
この記事はOkinawa.rb Advent Calendar 2019の1日目の記事です。 明日書く人を募集しています。
2.7対応
最新の2.7に対応しました
Before
こちらが対応前の2.7向けのQuineです。今はなき .:
を使っているのがわかりますね。
eval($c=%w(require'socket';$c="eval($c=%w(#{$c}).join)";[[436,6],[515,10],[593,5 ],[602,4],[672,4],[679,1],[681,1],[683,4],[752,4],[760,2],[763,5],[831,3],[840,1 ],[842,7],[911,7],[919,1],[921,8],[991,7],[999,1],[1005,4],[1072,7],[1085,4],[11 53,15],[1235,11]].each{$c[_1,0]=0x20.chr*_2};;;;;;;;;;;;;;;;;;;H,W,T,A=24,80,Thr ead,Array;using(Module.new{refine(A){define_method(:y){first};define_method(:x){ last}}});m,ts,fs=nil,[],[];print("\x 1b[2J");dm,bp,rt={"\e[A"=>->y,x{[(y-1) %H,x]},"\e[B"=>->y,x{[(y+1)%H,x]}," \e[C"=>->y,x{[y,(x+1)%W]},"\e[D"=>- >y,x{[y,(x-1)%W]}},->{loop{;;;;;" ____ ";x=rand(W);y=rand(H);unless(m[y][ x].start_with?("\x1b"));return(" _/. . L ";[y,x]);end}},T.start{loop{nm=A. new(H){A.new(W){''}};i=0;(;;;;;" L___ / ";nm).each_with_index{|r,y|r.eac h_with_index{|c,x|nm[y][x]=$c[( %>~~L/ / ">;i)]||'#';i+=1}};_=ts.select{ |t|t.respond_to?(:n)}.each{|t|" | / ";t.n;t.b.each{|y,x|;;nm[y][x]= "\x1b[48;5;%dm%s\x1b[0m"%[(;;;" | |__/| ";t.p),nm[y][x]]}};m=nm;_.combi nation(2){_2.b.include?(_1.b[(;" L____/ ";0)])&&_1.e;_1.b.include?(_2.b .first)&&_2.e};fs.size<5&&fs<<bp. ();fs.each{m[_1][_2]="\x1b[48;5; 1m%s\x1b[0m"%m[_1][_2]};$stdout.pri nt(m.map.with_index{|r,y|"\x1b[#{y +1};1H\x1b[K"+r.join}.join);sleep(0.1)}};p=1;gs=TCPServer.open(1234);begin;loop{ ts<<T.start(gs.accept){|s|q,d,l,b=(p=p.next),"",true,[bp.()];f=T.current.:define _singleton_method;f.(:p){q};f.(:d){d};f.(:e){l=false};f.(:b){b};f.(:n){nb=b.dup; h=nb.first;e=nb.pop;nh=dm[d]&.(*h)||h;nb.unshift(nh);fs.delete(nh)&&nb.push(e);b =nb;};s.print([255,253,34,255,250,34,1,0,255,240,255,251,1].pack('c*')+"\x1b[2J" +"\x1b[?12l");loop{begin;d=s.read_nonblock(3);rescue(IO::EAGAINWaitReadable);res cue;break;end;s.print(m.map.with_index{|r,y|"\x1b[#{y+1};1H\x1b[K"+r.join}.join) ;l||break}rescue(1);s.close;ts.delete(T.current)}};rescue(Interrupt);end).join)#
After
こちらが対応後の2.7向けのQuineです。 .:
を method
メソッドで置き換えました。
eval($c=%w(require'socket';$c="eval($c=%w(#{$c}).join);";[[436,6],[515,10],[593, 5],[602,4],[672,4],[679,1],[681,1],[683,4],[752,4],[760,2],[763,5],[831,3],[840, 1],[842,7],[911,7],[919,1],[921,8],[991,7],[999,1],[1005,4],[1072,7],[1085,4],[1 153,15],[1235,11]].each{$c[_1,0]=0x20.chr*_2};;;;;;;;;;H,W,T,A=24,80,Thread,Arra y;using(Module.new{refine(A){define_method(:y){first};define_method(:x){last}}}) ;m,ts,fs=nil,[],[];print("\x1b[2J"); dm,bp,rt={"\e[A"=>->y,x{[(y-1)%H,x]}," \e[B"=>->y,x{[(y+1)%H,x]},"\e[C"=>- >y,x{[y,(x+1)%W]},"\e[D"=>->y,x{[y, (x-1)%W]}},->{loop{x,=rand(W);;;" ____ ";y=rand(H);unless(m[y][x].start_w ith?("\x1b"));return[y,x]end}};" _/. . L ";T.start{loop{nm=A.new(H){A.new( W){''}};i=0;nm.each_with_index{" L___ / ";y=_2;_1.each_with_index{|c,x|n m[y][x]=$c[i]||'#';i+=1}};;;;;; %>~~L/ / ">;_=ts.select{|t|t.respond_to? (:n)}.each{|t|t.n;t.b.each{|y=" | / ",x|;;;nm[y][x]="\x1b[48;5;%dm% s\x1b[0m"%[t.p,nm[y][x]]}};;;;" | |__/| ";m=nm;_.combination(2){_2.b.in clude?(_1.b[0])&&_1.e;b=_1.b;;;" L____/ ";b.include?(_2.b.first)&&_2.e} ;fs.size<5&&fs<<bp.();fs.each{m[_ 1][_2]="\x1b[48;5;1m%s\x1b[0m"%m [_1][_2]};$stdout.print(m.map.with_ index{|r,y|"\x1b[#{y+1};1H\x1b[K"+ r.join}.join);sleep(0.1)}};p=1;gs=TCPServer.open(1234);begin;loop{ts<<T.start(gs .accept){|s|q,d,l,b=(p=p.next),"",true,[bp.()];f=T.current.method(:define_single ton_method);f.(:p){q};f.(:d){d};f.(:e){l=false};f.(:b){b};f.(:n){nb=b.dup;h=nb.f irst;e=nb.pop;nh=dm[d]&.(*h)||h;nb.unshift(nh);fs.delete(nh)&&nb.push(e);b=nb;}; s.print([255,253,34,255,250,34,1,0,255,240,255,251,1].pack('c*')+"\x1b[2J"+"\x1b [?12l");loop{begin;d=s.read_nonblock(3);rescue(IO::EAGAINWaitReadable);rescue;br eak;end;s.print(m.map.with_index{|r,y|"\x1b[#{y+1};1H\x1b[K"+r.join}.join);l||br eak}rescue(1);s.close;ts.delete(T.current)}};rescue(Interrupt);end).join);######
対戦型QuineのCI
対戦型Quineを書いている皆さんはCIでお困りではないでしょうか?
対戦型なので実際対戦してみないと動くかどうかはわかりません。 しかし対戦するスクリプトを書いてテストするのは手間です。 でもテストを書いてCIしないと安定してQuineを届けられないですよね...困った。
今回は、対戦型Quineに簡易的に文法チェックをするだけのCIを追加してみました。
スクリプトが文法的に正しいかどうかは ruby -c
でテストできます。
ただしスクリプトのほとんどの部分が eval
に渡す文字列の中で書かれているのでそれだけではあまり意味がありません。
eval
に渡しているスクリプトの文法のテストは eval
をstubして RubyVM::AbstractSyntaxTree.parse(src)
してやるとパース可能かどうかがわかります。
GitHub Actionsでの設定は以下のような感じです、べんりですね! ご自由にお使い下さい。
name: Syntax OK on: - push jobs: syntax_ok: name: Syntax OK runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - ruby-head - ruby-2.7 steps: - uses: actions/checkout@master - name: Set up RVM run: curl -sSL https://get.rvm.io | bash -s -- master - name: Set up Ruby run: | source $HOME/.rvm/scripts/rvm rvm install ${{ matrix.ruby }} --binary rvm --default use ${{ matrix.ruby }} - name: Test Syntax OK run: | source $HOME/.rvm/scripts/rvm ruby -c snake.rb - name: Test Syntax OK (eval) run: | source $HOME/.rvm/scripts/rvm ruby -e 'def eval(src); RubyVM::AbstractSyntaxTree.parse(src) ;end; load "snake.rb"'
まとめ
QuineをCIする手法を2つ紹介しました。
ruby -c
で文法チェックeval
をstubしてevalするコードをRubyVM::AbstractSyntaxTree.parse
で文法チェック
いかがでしたか? お手持ちのQuineでぜひ試してみて下さい✨💎✨
余談: GitHub Actionsハマりどころ
matrixでテストを組むときに fail-fast: false
しておかないと一見うまく行っているように見えるjobが他のjobが落ちたタイミングで ##[error]The operation was canceled.
と出て止まってしまいどうしたらいいのか分からなくて小一時間ぐらい(実際は2時間以上)つかってしまった。
jobs: syntax_ok: strategy: matrix: ruby: - ruby-head - ruby-2.7 fail-fast: false