今年買ってよかったと感じたもの

振り返りがてら覚え書き。

 

食べ物編

朝食

ミューズリー(アララ デラックスミューズリー

牛乳を注ぐだけで食事になるので重宝していた。全く甘くないのがいいところだけど、少し飽きてきてバナナを添えたりしていた。

 

ニュー・クイックの生ソーセージ

駅ビルなどにあるニュー・クイックという肉屋が売っている。吉祥寺で買っている。

噛むと肉汁が染み出してきてとてもおいしいが、カロリーが高そう。

 

昼食

調理がつらいので加熱するだけでよいものを探していた。おいしさを求めると成城石井に行き着く。一食300円台と内食にしては高いのだろうが外食に比べれば安い。

 

蓬莱 肉まん

秋からこればっかり食べてた。

 

成城石井 アソートピザ

秋からこればっかり食べてた。

 

うす家 冷凍うどん

成城石井などに売っている。普通のうどんを買って茹でるよりおいしく感じる。

 

コンテンツ編

劇場版 少女☆歌劇 レヴュースタァライト

心を動かされました。大場なな……

 

今日はまだフツーになれない

漫画。今年一番刺さった。何度も読み返している。

 

モノ編

Amazon Fire TV stick

親が買ってきて居間のTVがFire TVになった。YouTubeで音楽を流すと捗る。

 

Bluetoothイヤホン

秋葉原のe-イヤホンみたいな名前の店で購入。それまで使っていた10年ものの古いイヤホンと比べると音質が段違いだった。

 

グンゼ パジャマ

睡眠の質が明らかに向上した。肌触りの大事さを実感している。

 

ニトリ 野菜水切り器

レタスの水切りが格段に楽になった。

 

あんまりよくなかったもの

Yogibo

気持ちよいのでずっと横になっていたら2度腰痛になった。適正体重で適正な時間使える人間のためのものであって、肥満の病人に与えてはいけない気がする。

 

総評

さっさと労働して好きなモノにもっと金を払いたいと毎年思う。

「まちカドまぞく vs. ウクライナ語警察」 を生み出した後輩 vs. 警察先になりうるきららを勝手に探してあげる先輩

Introduction

↓100RTされたやつ

f:id:Szkieletor:20211204213355p:image

f:id:Szkieletor:20211204213359p:image

 

!!

 

Methods

↓これは、ツイート分析ツールが彼の237,695ツイートを取得中の画像

f:id:Szkieletor:20211204223004j:image

 

↓こっちは、終わらないので諦めて手で思いつくまま検索をかけ始めたGIF

gyazo.com

 

↓努力の履歴

f:id:Szkieletor:20211117144404j:imagef:id:Szkieletor:20211117144440j:image
f:id:Szkieletor:20211117144414j:image
f:id:Szkieletor:20211117144410j:image

 

疲れた。

 

Results

いい感じの接点が見当たりませんでした

ミステリ、艦これ、"linux"、"arch linux"、ソビエト、化学、プログラミング、プログラム、ふぁぼん、Twitter、海軍、提督、軍、"天才 クール"、天才、東大、物理、物理学、地理、社会主義、共産、ラテン語ベラルーシコーカサス

 

ロシア

※ "from:syobon_hinata ロシア" で検索してみると、彼が結構な頻度で1日に5〜6回ロシアに言及しているのがわかるよ

↓これは画像

f:id:Szkieletor:20211204211906p:image

 

これはまんがタイムきらら公式のツイートで引っかかった2つ

 

エロゲ ギャルゲ

※ "from:syobon_hinata エロゲ" で検索してみると、彼が平均1日1回は(略)

これは画像

f:id:Szkieletor:20211204211943p:image

 

R18!

 

f:id:Szkieletor:20211204211554j:image
f:id:Szkieletor:20211204211552j:image

f:id:Szkieletor:20211204212021j:image

OK(?)

 

桃色シンドローム

アキバのエロゲーショップの地下に眠っていた少女は大戦中に作られた兵器人形だった!?

スミヤ

本作の主人公。本業は大学生。エロゲ好きの変態と自他共に認めている。

 

inote! -アイノテ!-

自然あふれる田舎に引っ越してきた、ギャルゲー大好き中学生ちえ。

 

スロウスタート

百地 たまて(ももち たまて)

(中略)

所謂オタクで、10年以上前からギャルゲーをプレイしている。

 

ライトノベル

ななかさんの印税生活入門

放置気味に育てられた中学生のななかは、両親を見返すため、 web小説で一発当てて夢の印税生活を目指すことに。 次々と飛び出す今どきのライトノベル・web小説に対する独特すぎる分析は、 思わず吹き出してしまうこと必至。

 

せっかくなので

f:id:Szkieletor:20211204210220j:image

f:id:Szkieletor:20211204210223j:image

f:id:Szkieletor:20211204210226j:image

 

せっかくなので2

↓同好会内できららに最も詳しいと思われる部長

twitter.com

 

f:id:Szkieletor:20211204210332j:image

f:id:Szkieletor:20211204210357j:image

 

なるほどね。

 

ちなみに

 

 

 

 

サークルと、コードゴルフと、コードゴルフ会(提供: @kuromunori)

サークルでコードゴルフ会を作ってみた。

8月から、数えたら8回開いたようだ。

 

きっかけ

サークルでは、以前から「コードゴルフ大会」という大きなイベントが年1程度で開催されていた。毎回20人くらいは参加している気がする。2〜3日がかりのお祭りのような感じだ。

私も開催に関わったが、このイベントは準備に結構な労力がかかる。作問をして、サーバーを立てて、チーム分けをして、ホストは開催日にある程度張り付いていないといけない。

もちろん大規模なイベントの方が参加者も楽しくはあるが、忙しいなどで開催できそうにないタイミングでも「コードゴルフしたいな」という持て余した部員の呟きをチャットやTwitterで数度見かけた。

しかしそれ以上何も起こらなかったので、「場を用意すれば集まるのだろうか?」と思い、作ってみることにした。

 

その他の要因

プレイヤー

コードゴルフは元々熱意のある人や普段から趣味にしている部員が数人いて、彼らの力でコードゴルフ大会が作られ、作問がなされ、維持されていたように思える。しかし、学年が上がるに従って社会人になったりで、彼らへの院進や卒論でサークルへの参加度合いが下がった。

現在のシステムは熱意と技量のある人間の存在に依存している。そういう人間がいなくなれば途絶えるのが自然な帰結かもしれないが、せっかく持ち込まれた文化であるからには継承したい。今までとは違う形、軽いモチベーションで続いていく形態として、こういう会はどうだろう、というのがあった。

 

形式

既存のコードゴルフ大会はコストが高いため、気軽に開催できない。ならば、逆に極限までローコストにしてみるのはどうかと考えた。

具体的には、

大会ごとにオリジナルの問題を作っていた。これはあるとうれしいが、労力がいる。ということで、AtCoderやAnarchy Golfの既存の問題を使うことにした。

ジャッジシステムも自前で用意していたが、AtCやAnagolを使う。

一般的な分科会(ゼミ)は学期中など決まった期間で開催され、持ち回りで担当を決めるなど、その期間に責任を持って参加できることが必要な場合がある。そのハードルを下げるために、「誰かのやる気がある時に開催され、開催時間中にひょっこり現れれば誰でも参加できる」という形にした。

また、一般的な分科会は週の担当になるとスライドを作るなど、資料の準備がいる。あと、当然出席する必要がある。これもハードルを下げるために、「開催者はお題の問題と言語を選ぶだけで、あとは時間中に各々勝手に書く会」とした。つまり、開催者が不参加でも成り立つ。

 

その他

うら(Twitter:@n4o847)氏がbotを作ってくれて開催リマインダーの自動化と提出を検出してくれるようになった。ありがとう。

私の場合、場を多く用意しておくのが目的であるため、参加者0人でもかまわないと思っていた。だが、他の人からするとどうなのか気になっている。

別に厳密な大会ではないので出題者が選んだ問題を解いてもいい、むしろ自分の練習の機会にしたらどうか、と思っていた(私は問題を選んだ回でも普通に書いたりしていた)。が、伝わっていなかったかもしれない。

問題を選ぶのもそんなに気合いを入れる必要はないと思いかなり適当にやっていたが、もしかすると他の人はそうではなかったかもしれない。

 

やってみて感じたこと

文化として継続したいのであれば、そう思っている数人で持ち回りでやると決めた方がよかったかもしれない。たとえば4人で運営すると、ひと月に1回問題と言語探すだけになる。

ハードルを下げるために「開催したい人が現れたらやる」という形式で完全に自由にしたが、自分から手を挙げる人は現れなかった。縛りには意義もあるのかもしれない。例えば、3ヶ月みたいな期を設けて、その間の主催は決める。3ヶ月ごとに更新として、そのタイミングで主催から降りることができる。…など。

常設したいということであれば、カレンダーが1ヶ月先くらいまで埋まっていると参加しやすいのかもしれない。

初期の夏休み中の会は3〜4人参加者がいたが、学期が始まると参加者は毎回1〜2人くらいになった。人が集まる方が参加のハードルは下がるし、競うことが面白いものでもある。コードゴルフは月1程度でいいかな、と思う人が多いのであれば、そもそも月1にするとか。学期中は頻度を落とすとか。

既に何らかの機会でコードゴルフをやっている人以外は参加がなかった。そもそもコードゴルフが馴染みがないからあまり告知してもということだろうか。初心者回とか用意する?

チャンネルで会話が交わされていて、新しく参加した人が入りやすく、かつ無言でも構わない、のような雰囲気が理想的だと思っているが、どうすればよいだろうか。

 

その他

ut-codegolfで参加してくれている方もありがとうございます。

こういう常設の超低コストの会(問題を選び、解くだけ)は他分野でもできるのでは。競プロで1問解く会とか、CTFの過去問を1題解く会とか。

以前から、学期途中からサークルに入部してくれる人がいるが、ゼミ系の分科会がメインだと入部してすぐ参加できるものがないのが残念なんじゃないか、と思っていた。常設の会があれば敷居が低く

f:id:Szkieletor:20211110103833j:image

 

 

 

 

 

 

 

 

 











 

 

 

【夏休み子ども?科学?電話?相談】DiscordのBotを作るには、特別なプログラミングが必要なの?

f:id:Szkieletor:20210809175211j:plain

話し手:鈴木・letor・太郎
東京仮想大学理学部パソコンカタカタ学科在学中。通常の学生は最大8年までしか在学できない東大で、休学により8年半という最長在学記録を打ち立てる(著者調べ)。病気により現在も記録を更新中。
Discord上でのBotの開発経験はなく、そもそも1年2か月コードを書いていないものの、質問に回答してあげる勇気を持つ聖人君子。
コミュニティではSzkieletorというハンドルネームを用いる。
ちなみに写真は別人。



f:id:Szkieletor:20210809181350j:plain

聞き手:Yさん
JavaScriptでプログラミングを始めて3日目。DiscordのBotを作ろうとして、サークルの質問チャンネルで相談した。
ちなみに写真は別人。



鈴木・letor・太郎(以下、Szki)
じゃあ、話します。ただ、私はDiscordのBotをちゃんと作ったことはないです。なので、間違っているところもあると思います。
いま、YさんはJavaScriptでどんなプログラムが書けますか?
コードを貼ってもらえると楽です。

Yさん(以下、Y) 
条件分岐までかなあという感じです。 スマホで入ってるのでコードは今ないですが、ひとまず10000以下の素数を列挙させることはできました。

Szki 
わかりました。では、できる範囲で解説してみます。

Y
ぜひ! お願いします。

Szki
まず、Yさんの質問から見てみましょう。

《Yさんの質問》

先日discordにbotを導入し、中身のコードはネットからコピペしたら動作させることはできました。

そこで、コードを自力で書いて、メッセージを送らせるとかリアクションをつけさせるといったことをしたいのですが、コードの具体的な書き方がわからないです。

そもそもそういったことをさせるためのDiscord botのための特別な文があるんでしょうか。それともプログラミングの初歩的な本に書いてある類の構文だけでbotを動かさねばならぬのでしょうか。

特別な文があると思ってぐぐったんですが見当たらなくて行き詰まりました。

うおん。

Szki 
なるほど。プログラミングを始めてみたけれども、実際にどうやってBotとして動くのか? という所にカベがありそうですね。

Szki
では、最初に状況を確認するために質問させてください。
その1。Yさんが使っている(プログラミング)言語は何ですか?
その2。「API」という言葉を知っていますか?

Y
言語はJavaScriptですが、私はプログラミング歴3日なので……:uon: :uon: :uon:
API分からず……以前ぐぐってこの言葉が出てきてなんだこれになりました。

Szki
わかりました。
すると、こちらの記事が参考になりそうですね。JavaScriptでDiscord Botを作ろうという入門記事です。

qiita.com

Szki
このサンプルコードを使って解説していきます。

const Discord = require('discord.js')
const client = new Discord.Client()
client.on('ready', () => {
  console.log(`${client.user.tag} でログインしています。`)
})

client.on('message', async msg => {
  if (msg.content === '!ping') {
    msg.channel.send('Pong!')
  }
})

client.login('トークン')

Y
はい、よろしくお願いします!

他のファイルのプログラムを使う

f:id:Szkieletor:20210809181233j:plain

const Discord = require('discord.js')

Szki
YさんはDiscordに「Botにメッセージをpostさせる」「チャンネルに投稿されたメッセージを取得する」みたいな色々なことをしたいわけですが、それは全部Yさんが書かなければいけないわけではありません。

Discordくんがやり方を用意してくれています。 「関数」というものに覚えさせておくと、それを簡単に実行することができます。 たとえば、msg.channel.send(送りたい内容の文字列)で、文字列を送れます。 こういう、「これがあればBotを開発できますセット」みたいなものをDiscordが用意してくれていて、それがdiscord.jsというJSファイルの中にあります。

const Discord = require('discord.js')

これは、プログラムの先頭で「俺は Discord という名前でdiscord.jsの中のものを使うぜ!」と宣言している文です。そうすると、次の行からdiscord.js内で用意されている便利な関数が使えるようになるんですね。

いま、厳密さは犠牲にして、とにかくわかりやすさ重視で解説しています。 Yさん、ここまで大丈夫ですか?

Y
少し大丈夫じゃないです…。
const Discord =Discord という名前の変数(定数?)を宣言して、それが require('discord.js') である、ってことで、だとしたら require って何やねんってなってました。

Szki
正直この部分はBotを書く上では全て共通なので、いまのYさんのレベル的に全てのキーワードを理解する必要はないと思います(私の意見です)
とりあえず一歩目として、「DiscordでBotを書くときは、discord.jsというDiscordくんが用意してくれたセットから関数を呼び出して使うのだなあ」さえわかればいいと思います。
ちなみに、これはわかっていましたか?

Y
今、まあそういうものなのかなってなりました。

Szki
正確には全て自分で書くこともできますが、それはとても大変だし、もうDiscordさんが用意してくれたものがあるので、みんなそれを使っています。

Y
なるほど。

Szki
DiscordやSlackなど、ユーザーが「何か拡張したい、アプリケーションやサーバーと通信して自分のプログラムに何かやらせたい」という欲求はよくユーザーに発生します。

でも、ユーザーが勝手になんでもできちゃうと、当然困るので(たとえば1秒間に1億回postするプログラムを実行されると、困る)

Discordくんなどが「こういう手順で通信してくれや!」みたいなのを用意してくれています

これがAPIです。

まあ、つまりはDiscordくんとしては「こっちでこうしたい場合これを使ってっていう関数は用意しとくから、それを使っていいよ」としておきます。 すごく雑な説明をしています まあAPIは「とりあえずDiscordでもSlackでも向こうが用意してくれる道/手順があって、それがAPIと呼ばれている」くらいの理解でいいと思います。 Yさんも含め、Botを動かしたい人はみんなAPIを利用してDiscordでBotを動かします。

const Discord = require('discord.js')
const client = new Discord.Client()

// やらせたいこと

client.login('トークン')

ここは、何やってるか分からないと思いますが、今のレベルなら「とりあえずAPIを使うために必要なおまじないの文字列」という認識でいいと思います。コピペしましょう。 このコメントの部分がやらせたいことを書くところで、いま貼った部分は通信のための手順や本質のための準備です。 @Yさん ここまで大丈夫ですか?

Y
見たら過去のコピペのおかげですでに出来てたのですが、最後がclient.login();になってました。これでもいいんでしょうか?

Szki
ふむ。 では先に、トークンというものについて説明しましょうか。

f:id:Szkieletor:20210809181754j:plain

ここはまあ後で追加で(上級者)さんとかに具体的にコードを見てもらうと良いと思います。 とりあえず、「自分のプログラムを動かしたい!」となったとき、SlackでもDiscordでも、「じゃあ何でもできる権限をあげるね☆」とはなりません。 「欲しい機能を選択して、そしたらその機能の使用許可をあげるね」という感じになります。 Tokenというのは、Botに一意に振られるマイナンバーのようなものです。

YさんがBotを作るぞ! となったら、まずこのマイナンバーの発行を申請します。 そのとき、Discordの場合は「じゃあこのBotの権限を設定してね」ということを要求されます。 すると、DiscordくんはTokenをデータベース的なものに登録するわけですね。

Szki
それで、Yさんがプログラムを書いたとき、「Discordさんと通信したいんですが、ぼくのトークンはこれです!」ということをプログラムに書いておきます。

つまり、Discordくんは相手が誰で何を許可したかわかるわけですね。

Szki

https://qiita.com/yuto0214w/items/1ecee25efca6b5b7445b

この記事もそうですが、Discord Bot作ろう系の入門記事は、必ず最初に「トークンを取得しようね」って書いてありますね。確認してみるといいと思います。

ちなみに、トークンが漏れると、他の人がYさんと偽って通信できるようになるので、破滅します。

この記事にもちゃんと書いてありますね。

client.login('トークンの文字列')

これは、トークンをプログラム中に示すひとつのやり方です ちょっとここは(上級者)さんに聞けば分かると思いますが、たぶんトークンの示し方が一通りではなく、ここで書かなくても良いということだと思います Yさんがコピペしたプログラムがどんなものかはわかりませんが、どこかでtokenを示しているはずです まあこれも、Yさんがコピペしたやつで動いてるなら書き換えなくてもいいです 本質(Yさんがやらせたいことに関わる)ではないので。

client.on('ready', () => {
  console.log(`${client.user.tag} でログインしています。`)
})

client.on('message', async msg => {
  if (msg.content === '!ping') {
    msg.channel.send('Pong!')
  }
})

サンプルの残りはこれです。日本語で書き直してみます。

Discordから通信の準備が整った(readyになった)ら
  ターミナルに誰でログインしているか表示する
(おわり)

Discordからメッセージを受け取る
  受け取ったメッセージが"!ping"だったら
    "Pong!"を返す
(おわり)

まず、

client.on('ready', () => {
  *readyになったときにやらせたいことを書く*
})

について説明します。

readyのときにコンソールに表示する意義

Szki
Yさんはまだ分からないと思いますが、Botに限らず、自分のプログラムが通信可能になったかどうかはとても知りたくなる情報です。なので書いているのだと思います。 別に把握しなくても大丈夫、というのであれば、消しても問題ありません。

ただ、こう書いておくと、プログラムがバグった時に、そこそこ重要な役目を果たします。 「おや、!pingと送っても何も帰ってこないぞ」というとき、

  • 手順やトークンをミスっていて、そもそもプログラムが通信可能ではない
  • 通信可能だが、!pingと受け取ったりPongを返したりするところにミスがある

の2通りのミスの可能性がありえます。起動時に何も表示が出なければどちらの可能性もあるわけですが、この例のように書いておけば、「ターミナルにメッセージが出たから、通信はできたんだな」「メッセージが出ないから、通信ができていないんだな」、と、エラーの原因がどちらなのかを識別できますね。

プログラムが動かないとき、当然ですがまず原因を特定する必要があります。 そのとき、原因を特定するために細かく場合分けしてターミナルに何か表示させる、という手順は一番オーソドックスです。

プログラムを書くのであればいつかバグに苦しみます。 苦しんだ時に思い出してみてください。

他に、単にreadyになった瞬間にという条件で何かやらせたいという時にも使います。

client.on()とは何か

次に移ります。

client.on() は、

const Discord = require('discord.js')
const client = new Discord.Client()

が関わってくるんですが、つまり「Discord.jsの中のClient()というグループ内のon()という関数を使う」ということを言っています。 変数の格納とか難しいことはいいので、とりあえずイコールで置換して考えてみてください。 client.on()は、client = Discord.Client()なので、Discord.Client().onです。 //JSマンからツッコミが入るかもしれん さらに、Discord = discord.js なので、 Discord.Client().on()は、discord.jsのClient.on()、ということになります。

変数に格納する意味

Y
この小さいプログラムでわざわざこうやって短縮する意味はあるんでしょうか。

Szki
このサイズで完成ということなら、特に恩恵はないと思います。ただ、これから色々な処理を追加したい、という場合には、この書き方が効いてきます。 Discord.Client()はよく使うものなんでしょうね。 「何回も繰り返し同じ文字列を書く羽目になっていて、無駄だなあ」ってなったとき、変数を使うと便利です。

ただ、最初は小さいプログラムから書き始めて、だんだん大きくしていくのが慣れている人でも基本だと思います

慣れている人がコードを書くときも、まずこの例のような小さいプログラムから始めて、だんだん大きくしていく、という手順はオーソドックスです。すると、「後々何度も使いそうだから、最初から変数に入れて短くしておこう」と、プログラムが小さいうちから変数に入れる、ということが起こります。

全くこういうことを意識しないと、プログラムが大きくなったときに、これはやっぱり変数に入れたいな、と思っても、あちこちに影響が出てしまいます。そうすると直す作業がけっこう大変だったりします。 だから、このコードも含めて最初から拡張性を意識して書く、ということがなされています。でも、Yさんは初心者なのでまだ気にしなくても良いのではないでしょうか。

Y
完全に理解しました。

f:id:Szkieletor:20210809182122j:plain

client.on()のやっていること

Szki
最後はこの部分ですね。

client.on('message', async msg => {
  (受け取ったメッセージに対してやらせたいことを書く)
})

Discordの用意したライブラリのClient()on()くんは、一つ目のパラメータで、Discordくんから何を受け取った時に発動するかを指定しているようです。これは私の推測なので、ちゃんと知りたければドキュメントを読んでください。

ステータス

Szki
readymessage以外にも、ステータスは色々なものが存在します。たとえば接続が終了したことを示すもの、finishみたいな単語とか、あってもおかしくないですね。

このステータスとif文を組み合わせれば、どういうタイミングでプログラムが発動するかをYさんが操作できます。

'message'を指定すると全てのメッセージに対して発動するので、実はメッセージが"!ping"じゃなくてもこの関数の発動はしています。ただ次のif文で条件に合致しないので何もせずに終わっている、となります。

変数名

Szki
2つ目のパラメータ(「引数」と呼ばれます)にメッセージが入るというルールになっているようですね。これを使ってまた条件分岐させることができます。

ここは msg という名前を使わなくても大丈夫です。これはただの慣習的に使われている変数名なので、

client.on('message', async aaaa => {
  if (aaaa.content === '!ping') {
    aaaa.channel.send('Pong!')
  }
})

みたいに、別の名前に変えても問題なく動くと思います。違ったらすみません。 けれど、 aaaa という変数名だと、後で自分が読んだ際や他の人が読む際に、この変数の中身は何なのか、ぱっとわかりませんよね? コードを追う必要が出てきますね。 でも、 msg としておけば、これはメッセージが入ってるんだな、とみんなわかるので、そう命名してあります。


msg の中身

Szki
とりあえず、on()の第二引数の msg から、

  • msg.contentでメッセージの中身が取り出せます。
  • msg.channel.send('送りたい文字列')でメッセージを受け取ったチャンネルに送り返すことができます。

ということです。

Discordくんは、メッセージが来たことを知らせるときに、メッセージに関する色々な情報をひとまとめにして送ってきます。

msg = {
  content = 'おはよう'
  channel = #Uxxxxxxxxx //(英数字のIDで送ってきます)
  channel.send() = ... //(送り返す関数へのリンク)
  //(実は他にもたくさんある)
}

メッセージ情報には、たとえば

  • どのユーザーが書き込んだか
  • チャンネルはどこか
  • 日時はいつか

みたいなものがありますね。ユーザー情報ならchannel.userのような名前でアクセスできるはずです。それでif文を使えば、「ユーザーが○○さんだったら」のように分岐させられます。



Szki
……さて、これで説明したいことは終わったのですが、何かわからないところはありますか?

Y
大丈夫です。ありがとうございました!
あ、ぼく実は大富豪なので、お礼に5億円振り込んでおきますね!

Szki
やったー!



2021/08/09 18:35 配信



























注意

この記事は、インタビュー風の文章を書こうかなという動機のみで作成されたものです。
この記事内の技術的内容については、一切の正確性を保証しません。
内容に間違いがある場合、Szkieletor (@gh_end_) | Twitter までご連絡ください。

クレジット

写真:フリー素材ぱくたそ(www.pakutaso.com)様
鈴木・letor・太郎:model by 大川竜弥 様
Yさん:model by 大川竜弥 様

ろくろ系男子のフリー素材
https://www.pakutaso.com/20120432100post-1358.html

アイキャッチに使えそうなインタビュー中の風景のフリー素材
https://www.pakutaso.com/20180545128post-16090.html

WEB業界のろくろ回しのフリー素材
https://www.pakutaso.com/20120310082web.html

デスクの上でろくろ回しのフリー素材
https://www.pakutaso.com/20120313082post-1310.html

鋭い質問に一瞬たじろぐインタビュイーのフリー素材
https://www.pakutaso.com/20180548128post-16092.html

簡単! 誰でもできるパスタの作り方

7/15(木) 美少女ゲームを全スキップしてスチルだけ回収してみたが、もうやらないだろう。

7/16(金) 腕を上げると痛い、しかし気にせず腕を上げる。これを4年ほど続けると、身体の警告はもはや意味をなさないらしい。

7/17(土) 寝ながらフィクションのキャラクタとイチャつきたくなってゲームを探した結果、あらゆる広告が際どさで釣ってくるタイプのアプリゲームになった。

7/18(日) 映画の公開終了に焦り、あわててチケットを買った。 クレープが食べたくなると大抵チョコバナナを頼む。

7/19(月) エクセルシオールに行ったが、一人席が空いてなかった。大テーブルしか空いてなかったらもう入ることはない。

7/20(火) 一般人のような行動を取ったことでアイデンティティ・クライシスに陥った。 ファッションとファッショは似ている。

7/21(水) しゃがんでいたときパンツがモロ見えだったのを指摘された。誰もお前を愛さない。

Brainfuckに初挑戦して完成させるまでの記録

TSG Advent Calendar 2019 19日目です。

adventar.org

Brainf*ck Advent Calendar 2019 19日目です。

adventar.org

両方とも、前日も私が書いてます。

Brainf*ckで九九表に載っているか判定するプログラム

はじめに

Szkieletorと申します。

書くに至った経緯やお題などは昨日の記事を読んでください。

この記事ではプログラムができるまでの試行錯誤、つまづいたポイントと助けになってページ・ツールなどを書いていきます。

挑戦中

書き始め

「Brainf*ckで九九判定のプログラムを書くぞ!」と意気込んでまずは8つの記号の説明を読みましたが、これでどうプログラムするのかはさっぱりでした。

ただ私の所属するTSGは、コードゴルフ大会を部内で開催するなどesolangについての知見があり、詳しい部員の方(bitmathさん、昨日の記事のリンクの五月祭Day3と駒場祭コードゴルフ大会の実況解説をしています)が「こんなのがあるよ!」と教えてくれました。それがこちらのBrainfuck Visualizerです。

このヴィジュアライザはデフォルトでHello, Worldのプログラムが置いてあり、その実行過程をセルの状態を眺めながら追えたので、非常に助かりました。Brainf*ck入門者にはとてもおすすめですね。

その後、九九判定ということでこのページでまず掛け算の実装を追い、自分のケースに置き換えて実装しました。このあたりで「あっループ回す値は必然的に0になるから後で使いたい場合はコピーしておかないといけないんだな~」など、初歩の作法を理解し始めました。

中盤

9回のループを2重に回して、その値は掛け算にも使うからコピーして……みたいなことをひとつひとつ実装していました。

あとは九九の積と入力の一致判定で詰まったりしていましたが、Esolang wiki - Brainfuck algorithmsが非常に役に立ちました。これもbitmathさんに教えてもらった。

x=x==y をちょうど必要としていたので使わせていただいたんですが、このロジックをちゃんと理解することでBrainfuckの作法がだいぶ分かった気がします。

ここらへんからソースコードがちょっとかわいいな……と思い始めました。 -[>+<]- みたいにループ節はたいてい進んだ分だけ戻るので対称性があってかわいいですよね。伝われ。

終盤

「だいたい実装したけどうまくいかないな……。ヴィジュアライザは速度が遅いし、逆再生できないから使いづらい……」となってつらくなったんですが、TSGとKMC(京大マイコンクラブ)のSlackがつながっていた関係で、primeさん作のデバッガと出会いました。実行時間を調整できることと逆再生の機能がめちゃくちゃありがたかったです。たぶんデバッガがあるという事実を知らなかったら挫折していたと思います。

あとは思っていたのと違う挙動をひとつずつ潰しながらテストして、直して……を繰り返して、繰り返して、ひたすら繰り返して、完成です。

デバッグはつらかったですが、正答を返して改行を取り去ったパッと見全くわからないコードを眺めて、これを書いたんだなあ、何のプログラムか全然わかんないや……と感慨に浸れるのはいいですね。それからprimeさんbitmathさんはじめ周囲(?)の支えがあってこその完成だなと、メダルを取ったアスリートみたいな感想を抱きました。

振り返って

つまずきポイントその他

  • やっぱり掛け算とかif文、==などの比較を自力で思いつくのは無理があるので、その実装を理解しながら書いていくとちょうどいいんじゃないかな、と。
  • 汎用言語に慣れきっていたので整数と文字列をいったりきたりしないといけない点にはじめにつまずきました。
  • 元になったPythonのコードではfor-else文を使ったりして二重ループから脱出していましたが、Brainfuckで「数が一致したらループを抜けて出力処理に入る」方法を思いつけませんでした。結局途中で一致しても81回ループは回る(九九全部計算する)方法に変更。
  • セルの値はたいてい最後にはゼロになるので、ときどきゼロにならないセルを見落としてデバッグで苦しんでいました。ループが止まらなくなったらたいていこれか、ポインタの戻し忘れ。
  • セルを結局15個くらい使ったのですが、途中で#2と#11の比較をするせいで >>>>>>>>>(処理)<<<<<<<<< みたいなことをしています。もっと上級者なら全体を見通して最適なセル配置ができるのかな。
  • ビジュアライザとデバッガの存在がめちゃくちゃありがたかったです。めちゃくちゃありがたかったです。

コードゴルフについて

さて、今回挑戦したお題ももともとは学祭のコードゴルフ大会のお題でして、完成したあとじゃあここからどう縮められるだろう? と考えてました。

思いつくところでは、

  • セルの再利用――今回はほとんどセルを再利用しなかったんですが、先ほども書いた通りたいていのセルは0になる or 0にするので、再利用できます。するとセル間の距離が近くなり、コードが縮みます。
  • >><<は連続していた場合打ち消せる――間違いのないようにいちいち基準になるセルに戻ってから移動……という手順を踏んでいたんですが、これも打ち消せば減らせますね。

書いてて思ったんですが、ゴルフがデバッグをつらくするのは当然ですね。やっぱりアルゴリズム的な改善のほうが大きく縮みそうな気がします。ほんとかな?

あと、もうネットの海に放流されているので手遅れなんですが、五月祭コードゴルフ大会の実況で「(Brainfuckを手で書く人々は)マゾヒストなんですか? w」と発言したことをおわびしておきます。

あとがき

今回紹介したページやツールの助けがあればかなり敷居が下がりますし、界隈の方々はとてもやさしいので、皆さんもぜひBrainfuckプログラミングの世界に足を踏み入れてみてはいかがでしょうか。

アドベントカレンダーの明日の記事は、

  • TSG: iLiss.さんによるCODEBLUE CTF 参加記です。
  • Brainfuck: 未定 です。

ここまで読んでいただきありがとうございました。

twitter.com

リンクまとめ

Brainf*ckで九九表に載っているか判定するプログラム

TSG Advent Calendar 2019 18日目です。

17日目:

adventar.org

Brainf*ck Advent Calendar 2019 18日目です。

17日目:

adventar.org

まえがき

Szkieletorと申します。

Brainf*ckという変わった言語でのプログラミングに初挑戦したので、記録を残しておきます。

いきなりですがちょっと脱線して、きっかけについて。

学祭とTSG LIVE!とコードゴルフ

私が所属するサークルTSGでは、"TSG LIVE!"という「ライブプログラミングショー」を学祭でやっており、その中の一つに「ライブコードゴルフ大会」というものがあります。

コードゴルフは簡単に言うと「コード長をできるだけ縮める」という遊びです。TSG LIVE!では、言語が書かれたマスを陣取りで取り合います。

特徴的なのは、RubyやC、Pythonといった汎用言語だけでなく、Brainf*ckを始めとしたesolang(難解プログラミング言語)のマスがいくつもあるという点です。

今回五月祭・駒場祭の両方でこのコードゴルフ大会に少しだけ関わって、なにかesoを書いてみたいなあと思い、一番有名なのはBrainf*ckだな! ということで書いてみることにしました。

ちなみにコードゴルフ大会は動画があがっています。

www.youtube.com

  • 五月祭Day2

www.youtube.com

  • 駒場祭(これもうらさんがプレイヤー)

www.youtube.com

Brainf*ckとは?

TSG LIVE! ライブコードゴルフ大会で定番になりつつある言語でもあります。

駒場祭の前に各esolangでTwitterで検索をかけたんですが、Brainf*ckはesoの中でもにぎわいがあっていいですね。

今回のお題

駒場祭2019 ライブコードゴルフ大会で出題されたものと同じ、九九判定のプログラムを書きました。

お題:2桁の整数が100個改行区切りで与えられるので,それぞれについて九九表に存在する数のとき1,そうでないとき0を出力せよ

上のリンクに詳しいルール、入出力例等があります。安心してゴルフしてください。

やったこと

Pythonでプログラムを書く

汎用言語で書いてそれをアルゴリズムそのままにBrainf*ckに移植すればいいんじゃないかな、と思い、まずPythonで書きました。

ちなみに私はPythonもほぼ書いたことがありません。for-elseなどググりながら書きました。

for i in range(100):
    number = int(input())
    for j in range(10):
        for k in range(10):
            if number == j*k:
                print "1",
                break
        else:
            continue
        break
    else:
        print "0",

単純に、九九を1から81まで計算し、入力と比較して合っていたら二重ループを抜けて"1"を出力、81までのどれともマッチしなかったら"0"を出力、という方針です。

ゴルフ的にはもっとよいアルゴリズムがあると思いますが、未知の言語に移植するということを念頭に置いて、簡単そうだという理由で愚直な方法を。

あとは移植だ! ということで、ところどころ方針を変えつつBrainfuckを書いていきます。

できたBrainf*ckプログラム

先に完成したコードを見せておきます。

>++++++++++[<++++++++++>-]<[>,>,>++++++++++++++++++++++++++++++++++++++++++++++++[->+>+<<]>[-<<<->>>]>[-<<<->>>]<<<>++++++++++<<[->>[-<+>]++++++++++<<]>>----------<<>>>+++++++++>+++++++++<[>[<[->>+>+<<<]>>[-<<+>>]<[->+>>+<<<]>>>[-<<<+>>>]<<[->>+<<]>[-<>>[->+>+<<]<<>>>>[-<<+>>]<<<<>>>[-<<<+>>>]<<<>]<>>[-]<<<<<<[->>>>>>>>>+>+<<<<<<<<<<]>>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>]<[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]]>>>>>>>[>+>+<<-]>[<+>-]+>[<->[-]]<[<<<[->>+<<]>>>-]<<<<<<<<<-]+++++++++<-]>[-]<>>>>>>>>>>++++++++++++++++++++++++++++++++++++++++++++++++[-<+>]<.[-],[-]<<<<<<<<<<<<<-]

……はい。訳が分からないと思うので、コメント付きのプログラムも載せますね。

ちなみに処理系は apt で入れたbfを使いました。

>
+++++ +++++                     initialize cell #1 to 10
[                               use loop to set 100 to cell #0
    < +++++ +++++                   add 10 to cell #0
    > - 
]
<
[
    > ,                         store the input (10th place) to cell #1
    > ,                         store the input (1st place) to cell #2
    > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ 
                                set 48
    [->+>+<<]                   clone to #4 and #5
    >[-<<<->>>]                 #1 sub #4
    >[-<<<->>>]                 #2 sub #5     
    <<<                         move #2
    > +++++ +++++               set 10 to cell #3
    <<[->>[-<+>]++++++++++<<]     
                                compute decimal and store it to cell #2
    >>----------<<
    >>> +++++ ++++              set 9 to cell #4
    > +++++ ++++                set 9 to cell #5
    
    <                           move #4
    [>
        [
            <                       move #4
            [->>+>+<<<]             #6:7 add #4
            >>                      move #6
            [-<<+>>]                #4 add #6
            <                       move #5
            [->+>>+<<<]             #6/#8 add #5
            >>>[-<<<+>>>]           #5 add #8
            <<                      move #6
            [->>+<<]                #8 add #6
            >[-<                    while #7 do {decr #7
                >>[->+>+<<]<<           #9/#10 add #8
                >>>>[-<<+>>]<<<<        #8 add #10
                >>>[-<<<+>>>]<<<        #6 add #9
                >]<                 end
                                    and prod stored to #6
            >>[-]<<                 delete #8
            <<<<[->>>>>>>>>+>+<<<<<<<<<<]   
                                    #11/#12 add #2
            >>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>] 
                                    #2 add #12
            <[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]]  
                                    #11=#11==#6
            >>>>>>>                 move #13
            [>+>+<<-]>[<+>-]+       if(#13)
            >[<->[-]]                   (#13=1 so do nothing)
            <[
                <<<[->>+<<]             (#13=0 so #13 add #11)
                >>>-
            ]      
        <<<<<<<<<-]             move #5 and decrement
    +++++ ++++                  set 9 in #5
    <-]                         decrement #4

>[-]<                           reinitialize #5
>>>>>>>>>                       move #13
> +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ 
                                set 48 to #14
[-<+>]                          #13 add #14
<.                              output #13
[-],[-]                         set 0 to #13 and read newline
<<<<<<<<<<<<<                   back to #0
-
]

#がついているものはセルの番号です。

手順の解説

単体で説明されても理解できないかと思いますが、デバッガ(これとか)で過程を一緒に目で追うとわかるかと。

1. 入力が100行のため100回ループを回す準備をする
>
+++++ +++++                     initialize cell #1 to 10
[                               use loop to set 100 to cell #0
    < +++++ +++++                   add 10 to cell #0
    > - 
]
<
2.100回のループに入る。入力(#1, #2)を文字列から整数に変換する

1バイトずつしか読み込んでくれず、さらに文字列になるため、変換する必要があります。変換後の整数、すなわち入力をxとします。

文字(1~9)から数字への変換は、48(ASCIIコードの0)を引くとできます。

[
    > ,                         store the input (10th place) to cell #1
    > ,                         store the input (1st place) to cell #2
    > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ 
                                set 48
    [->+>+<<]                   clone to #4 and #5
    >[-<<<->>>]                 #1 sub #4
    >[-<<<->>>]                 #2 sub #5     
    <<<                         move #2
    > +++++ +++++               set 10 to cell #3
    <<[->>[-<+>]++++++++++<<]     
                                compute decimal and store it to cell #2
    >>----------<<
3.九九を1個ずつ計算する

まず、9をふたつセット(#4, #5)。

    >>> +++++ ++++              set 9 to cell #4
    > +++++ ++++                set 9 to cell #5
4.2重ループに入り、二つの数字それぞれを、ループを回す用と掛け算用にコピーして増やす。

#6と#7を掛け算のために使うことにします。

    <                           move #4
    [>
        [
            <                       move #4
            [->>+>+<<<]             #6:7 add #4
            >>                      move #6
            [-<<+>>]                #4 add #6
            <                       move #5
            [->+>>+<<<]             #6/#8 add #5
            >>>[-<<<+>>>]           #5 add #8
5.掛け算をする

積をyとします。

#8,#9,#10を一時的に使います。

            <<                      move #6
            [->>+<<]                #8 add #6
            >[-<                    while #7 do {decr #7
                >>[->+>+<<]<<           #9/#10 add #8
                >>>>[-<<+>>]<<<<        #8 add #10
                >>>[-<<<+>>>]<<<        #6 add #9
                >]<                 end
                                    and prod stored to #6
            >>[-]<<                 delete #8
 
6.x=(x==y) xとyが等価かどうか比較し、その値を#11に入れる

#2の値(input)は保存する必要があるため、コピーして二つに増やしています。

            <<<<[->>>>>>>>>+>+<<<<<<<<<<]   
                                    #11/#12 add #2
            >>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>] 
                                    #2 add #12
            <[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]]  
                                    #11=#11==#6
 
7.#13を結果を入れるセルとし、これまで0でいま1になった場合のみ書き換える

Pythonで書いたようないわゆる break の方法が思いつかず、ループを81周しています。ただ書き換えるだけだと#13が1になったあとで0で書き換えられる事態が発生するので、条件分岐させています。

            >>>>>>>                 move #13
            [>+>+<<-]>[<+>-]+       if(#13)
            >[<->[-]]                   (#13=1 so do nothing)
            <[
                <<<[->>+<<]             (#13=0 so #13 add #11)
                >>>-
            ]      
8.ループ変数をデクリメント
        <<<<<<<<<-]             move #5 and decrement
    +++++ ++++                  set 9 in #5
    <-]                         decrement #4
9.#13にあわせて0か1を出力

文字列に変換する必要があるため、今度は48を足しています。48を作るのは手抜きしました。

>[-]<                           reinitialize #5
>>>>>>>>>                       move #13
> +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ 
                                set 48 to #14
[-<+>]                          #13 add #14
<.                              output #13
[-],[-]                         set 0 to #13 and read newline
<<<<<<<<<<<<<                   back to #0
-
]

できました。574byteです。

あとがき

実際はこれをスムーズに書けたわけではなく、あちこちつまづきながら苦しんで実装していました。記事が長くなってしまったので、それは「Brainfuckに初挑戦して完成させるまでの記録」として明日書こうと思います。

明日の記事:

twitter.com