Help us understand the problem. What is going on with this article?

闇シェル芸「さあ、闇のゲーム(シェルゲイム)の始まりだぜ!」

この記事では闇シェル芸と闇のゲームについて扱いません。シェル芸で作るゲームの話をします。

シェル芸でゲームを作りたいと思ったことはありませんか?私はありません。
しかし気の迷いからシェル芸でインタラクティブに入力を処理し動作する簡単なゲームを作ってみたら、できてしまいました。

作成する過程や学べたことを書いていきたいと思います。

完成物

shell_geime_pv.gif

説明

PONG の劣化版のようなゲームです。
動いてるボールをラケットで跳ね返しましょう。
ボールは壁にあたって跳ね返ってきます。
どれだけ続くかを競い合いましょう!

操作の仕方

「J」キーで下に移動、「K」キーで上に移動します。vimな感じです。
また、CTRL+Cで終了しましょう。

コード

awkがgawkでないと動かないです。
「0.1」の部分を変更することで難易度調整できます。

$ (f(){ read -s -n1 -t0.1 a;xxd -ps -l1 <<<"$a";f; };f)|awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|xargs -L1 -I_ bash -c "echo -e '\e[J\e[H';seq -w 1 10|tr -d '[0-9]'|sed '_s/^/I/'"|awk 'NR%11{printf("%1s%30sI\n",$0,"");fflush()}!(NR%11)' | awk -F "" -vOFS="" 'x<2{x=2}NR%11==0{print "score:"i" ";i+=1;y+=b;if(y<3||y>9){b*=-1}}NR%11==y{x=x+a;$x="@"}$0~/I@|@I/{a*=-1}{print;fflush()}$1~/@/{b=0;i-=1}' a=1 b=1 x=5 y=5

awkがgawkじゃない人向け

$ sudo apt-get install gawk
$ alias awk=gawk

どうやって作ったかの説明

シェル芸は1行で書くことが定義とされていますが、以下の文章では説明のために改行を入れることをご了承ください。

文中にでてくるawkはgawkです。
ubuntu 18.04 のawkはおそらく mawk なのでお気をつけてください。

ゲームの更新・入力処理

まず一定時間で更新ゲームを更新する処理と入力を受け付ける処理をしなければなりません。
この2つの機能はなんとreadコマンドのみで達成することができます。

$ (f(){ read -s -n1 -t0.2 a;xxd -ps -l1 <<<"$a";f; };f)

read コマンドにおいて、
-n オプションは受け取る文字数。
-t オプションはタイムアウト時間
になっているためこれで一定間隔更新しつつで文字が入力された場合はそれをキャッチできます。
連続入力(キーの長押し)されると更新間隔が早くなる欠点もあります。

image_0.gif

キー入力を処理する

あとは特定のキーが押されたときをパラメータに変換します。
簡単ですね。ここではキーボードの「↑」「↓」キーに設定してみます。

$ (f(){ read -s -n1 -t0.2 a;xxd -ps -l1 <<<"$a";f; };f)|awk '/5b/{a*=1}/41/{a-=1}{print a}'

しかしこれはうまくいきませんでした。実行してみればわかりますが、「↓」キーには反応しますが「↑」キーには反応しないです。
理由はわかりませんでしたが、「↑」キーを押した直後に行が更新されるとターミナルから「↓」キーと同じ5bが入力されてしまうようです。私のターミナルのせいかもしれません。

面倒なのでキーを「J」キーと「K」キーに変更します。これはうまくいきました。
1~9の間で値を上下させることができます。

$ (f(){ read -s -n1 -t0.2 a;xxd -ps -l1 <<<"$a";f; };f)|awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5}'

image_1.gif

パイプに処理を流す

ここまではサクサクいったのですが、awkからの処理をパイプに流そうとすると問題がおきます。
下記のコマンドを実行すればわかりますがパイプに流すと表示されなくなってしまいます。

$ (f(){ read -s -n1 -t0.2 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5}'|\
  cat

awkは出力がパイプだと認識すると、出力をバッファしてしまうためのようです。
普段は陰ながら私を支えてくれるバッファが、今回ばかりは苦しめてきます。
しかし、awkにはこれを解消するfflush()という関数があります。

$ (f(){ read -s -n1 -t0.2 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|\
  cat

fflush()のおかげで表示ができるようになります。

画面にラケットを描画する

あとは良い感じに画面に描画するだけですね。
ANSIエスケープシーケンスを利用して画面に描画します。

$ (echo -e 'e\[s';f(){ read -s -n1 -t0.4 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|\
  xargs -L1 -I_ bash -c "echo _;seq -w 1 12|sed 's/./ /g'|sed '_s/ /|/';echo -e '\e[u'"

image_2.gif

ついに画面にラケットを表示できました。

画面に壁を描画する

パイプでつなぐだけですね。

$ (echo -e 'e\[s';f(){ read -s -n1 -t0.4 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+6;fflush()}'|\
  xargs -L1 -I_ bash -c "echo _;seq -w 1 10|sed 's/.*/ /'|sed '_s/ /|/';echo -e '\e[u'"|\
  sed -r ':a s/$/ /;/.{20}/!ba;2,10s/$/|/'

image_3.gif

ボールの描画とゲーム処理の簡単な実装

ボールを描画します。
パラメータを保持したり、壁への跳ね返りなども追加します。

$ (echo -e 'e\[s';f(){ read -s -n1 -t0.1 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|\
  xargs -L1 -I_ bash -c "echo -e '\e[u'_;seq -w 1 10|tr -d '[0-9]'|sed '_s/^/I/';echo -e '\e[u'"|\
  awk '{printf("%1s%20sI\n",$0,"");fflush()}'|\
  awk -F "" -vOFS="" 'b<2{b=2}NR%12==5{b=b+a;$b="@"}$0~/I@|@I/{a*=-1}{print $0,a,b}' a=1 b=5

また、正規表現を描くときに「|」(パイプ)だと面倒なので「I」(大文字のI)に変更します。

image_4.gif

上下に跳ね返るように

$ (echo -e 'e\[s';f(){ read -s -n1 -t0.1 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|\
  xargs -L1 -I_ bash -c "echo -e '\e[u'_;seq -w 1 10|tr -d '[0-9]'|sed '_s/^/I/';echo -e '\e[u'"|\
  awk '{printf("%1s%20sI\n",$0,"");fflush()}'|\
  awk -F "" -vOFS="" 'x<2{x=2}x>2&&NR%12==0{y+=b;if(y<2||y>10){b*=-1}}NR%12==y{x=x+a;$x="@"}$0~/I@|@I/{a*=-1}{print $0,a":"x" "b":"y;fflush()}$1~/@/{b=0}' a=1 b=1 x=5 y=5

image_5.gif

ほぼほぼ完成ですね。
ここはデバッグコードが見られるので面白いかもしれません。

Scoreの追加と調整

スコアを追加し、デバッグコードを削除します。
またフィールドの幅や更新間隔などを変更し遊べるようにします。

$ (f(){ read -s -n1 -t0.1 a;xxd -ps -l1 <<<"$a";f; };f)|\
  awk '/6a/{a+=1}/6b/{a-=1}{gsub(5,4,a);print a+5;fflush()}'|\
  xargs -L1 -I_ bash -c "echo -e '\e[J\e[H';seq -w 1 10|tr -d '[0-9]'|sed '_s/^/I/'"|\
  awk 'NR%11{printf("%1s%30sI\n",$0,"");fflush()}!(NR%11)' |\
  awk -F "" -vOFS="" 'x<2{x=2}NR%11==0{print "score:"i" ";i+=1;y+=b;if(y<3||y>9){b*=-1}}NR%11==y{x=x+a;$x="@"}$0~/I@|@I/{a*=-1}{print;fflush()}$1~/@/{b=0;i-=1}' a=1 b=1 x=5 y=5

完成ですね。

shell_geime_pv.gif

勉強になったもの

gawkのfflush()

知らなかったので学びがありました。
個人的に使う機会は少なく、man読んでいて驚きました。
https://linuxjm.osdn.jp/html/GNU_gawk/man1/gawk.1.html

また、この関数の実装の違いなのかgawkでは動くがmawkでは動かないことに気づかず少し焦りました。

ANSIエスケープシーケンス (CSI (Control Sequence Introducer (Indicator))コード)

ANSIエスケープシーケンスは、面白いし素晴らしい仕様ですね。

正確にはその中の「CSI (Control Sequence Introducer (Indicator))コード」と呼ばれるものらしいですが、1
これについての仕様や規格などを網羅しているサイトや記事を知っている方はぜひ教えていただければ幸いです。
ワンライナーで無ければ、簡単にゲームとかおもしろいコマンドとか作れそうですね。

まとめ

完成してみるとほぼawkで書いてますね。。。
私はシェル芸初心者のため簡単なゲームの作成にとどまりましたが、
この記事がシェル芸やターミナルで面白いゲームを作る取っ掛かりになれば幸いです。

おまけ

世界で最もクリーンで洗練されたOSSプロジェクトの一つであるsuper_unkoにある
unko.pyramidコマンドは知っていますか?2

## クリスマス🎄もシェル芸で彩れます!
$ unko.pyramid 8|tr 💩 🎄|sed '1s/🎄/👑/';yes 💩|head -2|xargs printf '% 11s\n'
       👑       
      🎄🎄      
     🎄  🎄     
    🎄🎄🎄🎄    
   🎄      🎄   
  🎄🎄    🎄🎄  
 🎄  🎄  🎄  🎄 
🎄🎄🎄🎄🎄🎄🎄🎄
       💩
       💩
$ banner Merry Christmas!
#     #
##   ##  ######  #####   #####    #   #
# # # #  #       #    #  #    #    # #
#  #  #  #####   #    #  #    #     #
#     #  #       #####   #####      #
#     #  #       #   #   #   #      #
#     #  ######  #    #  #    #     #

 #####                                                                    ###
#     #  #    #  #####      #     ####    #####  #    #    ##     ####    ###
#        #    #  #    #     #    #          #    ##  ##   #  #   #        ###
#        ######  #    #     #     ####      #    # ## #  #    #   ####     #
#        #    #  #####      #         #     #    #    #  ######       #
#     #  #    #  #   #      #    #    #     #    #    #  #    #  #    #   ###
 #####   #    #  #    #     #     ####      #    #    #  #    #   ####    ###

  1. https://www.mm2d.net/main/prog/c/console-02.html 

  2. super_unkoのリンクカードは、https://ghlinkcard.com/ で作成しており公式のものでは無いです。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away