不思議な感じ
日記(ここは読まなくていいです)
今日はこんな事がありました。
Jestで環境変数を変えながらユニットテストしたいときには(明示的に再読み込みしない限り、各モジュールはコード内で変更された環境変数の内容とともにキャッシュされて、ロードされた時以降のコード内での環境変数に対する変更を読み込まないので) jest.resetModules() or jest.isolateModules() あたりを使うわけですが、
昨日までbabel-jest 環境で普通に jest.mock でmockを入れてたりprototypeを書き換えたりする際には resetModules を使うと(mockを一番最初に挿入するように jest が処理の順番を入れ替えてくれる関係上)うまく行かないので isolateModules() の方を使うようにしていたんですけど、
それを回避するための jest.domock() っていうメソッドがあることにさっき気づいて、おおって思いました。
思ったこと
自分が困ったまあまあマイナーそうな問題に既に他の人が遭遇していて、
しかも既にそれの解決の仕組みが用意されていることに気がついた時っていつもちょっと感動します。
もうちょっと詳しく言語化すると、自分の課題へのアプローチの仕方や前提に対する認識がその会ったこともない人たちと一致していたことに気がついて、つながってる感じがして楽しい、とかなのかななんて。
まとめ
謎のポエムでした。 お読みいただきありがとうございました。
GitHubでコミット履歴のアカウントを変える方法
GitHubでCommitした人のアカウント名とアイコンを表示する部分あるじゃないですか。
同じ端末でアカウントを2つ以上使ってるときに、ここの変え方がわからなくて困ったのでメモ。
コミット履歴のアカウントの変え方
赤枠部分で表示されるアカウントは、コミットした際にローカルのgitで使用しているuse.emailをGitHubが見て、そのメールアドレスで登録されているGitHubアカウントを見つけてくることで紐づけてるようです。
そのため、ローカルのgitのメールアドレスを赤枠部分で表示したいアカウントのものに設定してやれば解決します。
$ git config user.email yourNewAddress@example.com
めでたしめでたし。
過去のコミットのメールアドレスの変え方
すでにCommitしちゃった分の表示も変えたい人向け。
CommiterとAuthorの両方を変更する必要があるようです。
Windowsの人はGit Bashあたりから実行してください。
$ newName=yourName $ newEmail=yourNewAddress@example.com $ git filter-branch -f --env-filter "GIT_AUTHOR_NAME='$newName'; GIT_AUTHOR_EMAIL='$newEmail'; GIT_COMMITTER_NAME='$newName'; GIT_COMMITTER_EMAIL='$newEmail';" HEAD
すでにPushしてある分については、上のコマンドでローカルを変更した後にForce Pushを実行する必要があります。
特にほかの人と共同で作業している場合には、Force Pushで何が起こるかを把握したうえで実行しましょう。
bot記事の後編について
結論から申し上げますと、途中で執筆を諦めてしまいました。
大変申し訳ありません。
執筆が止まった理由
非同期処理が思った以上に(初学者にとっては)ハイコンテキストで、簡潔に説明することは難しいと私が感じたからです。
Javascriptで手軽にhttp通信を行おうといった場合によく使われるのがaxiosやビルトインのfetch関数だったりするわけですが、これらを利用する際に避けて通れないのが非同期処理です。
反省
前編で安易に「Javascriptは実行環境の用意は簡単(何ならブラウザで実行できる)で汎用性も高い!」って考えて紹介しましたが、レスポンスを取ってきてこねこねできるようになってもらうには結構ハードルが高かったです。(非同期という概念の説明や、コールバック関数かawait(いずれにしろ関数を定義する方法を理解してもらう必要がある)を使えるようになる必要があります。)
私の目的は読者の皆さんに汎用的な問題解決能力を身に着けていただくことであるため、とりあえずこれを写経しておいてね、で終わりたくなかったのがその根本原因でもあります。
こういう完璧主義は捨てて、ある程度割り切った発信を行うことも必要なのかな、と思う今日この頃です。
今後の展望
また時間ができた時に、今回説明を諦めた辺りも含めて記事を書きたいです。
読者の皆様が成長し、自分の手でほしいものを作れるようになるように引き続き精進してまいります。
今後ともよろしくお願い致します。
くら寿司キャンセル待ちBotを作れるようになろう!(後編 - 実装編) - 初心者向け
この記事は永久に書きかけです。 途中まででも読みたいという方のみご覧ください。
前編はこちら
後編でやること
前編では、Webサイトの挙動を調べることで、「予約可否を調べる」とは具体的に何を意味するのか、を言語化することに成功しました。
今回は、それを20行程度のプログラムとして、コンピュータが解る言語に書き下していきましょう!
1. 調べたい時間帯向けのURLの内容を取得する 2. 取得した内容に、「~」が含まれているかを確認する 3-a. 含まれていた場合、何らかの形で通知する 3-b. 含まれていなかった場合、数秒待ってまた1を繰り返す
JavaScriptの文法を知る
プログラムは、言語です。
言語を使うには、文法や覚えるべき概念がたくさんあります。
とはいっても、とりあえず「変数という概念とその定義の仕方」と、「関数を定義・実行する方法」と「繰り返しという概念が存在すること」さえ覚えていれば、あとはGoogle検索で何とかなります。
あなたが思っているほど怖くないです。Google検索もあるし、困ったらこの記事のコメント欄や、Twitterにスクリーンショットでも貼って「たすけて!」と言えば、みんな優しく教えてくれることでしょう。
世界はあなたが扉を開くのを待っています。
やっていきましょう!
実行環境を起動する
コマンドプロンプトを開き、「node」と入力してEnterキーを押してみましょう。
nodeのインストールがうまく行っていれば、このように入力してください的な感じになります。
変数の定義
変数とは、「計算した結果を名前をつけて保存しておくことで、あとで使えるようにする」という機能です。
JavaScriptでは、const
や `let というキーワードを使って新しい変数の名前とその内容(=値)を定義することができます。
const name = 'ぴよ' let age = 100
上記のように、行の最初に let
あるいは const
と書き、その次に変数名を記し、その後にイコールでつないで値(=内容)を記します。
ちょうど創世記第一章3節あたりで神が世界を創造している時のように、自分の世界に変数を定義することができます。
const
と let
の違いは、その変数の値を上書きすることが可能か否かという点です。
const
は後から値を上書きして代入できず、letは後から値を上書きできます。
let myLover = 'Taro' myLover = 'Jiro' // 上書きの例(いま覚える必要はないですが、これは「コメント」という機能です。「//」以降は人間様向けのコンテンツとして、プログラム中に好きなように注釈を書けます。) // コメントは無視されるので、コピペして実行する際にはコメントを含めなくても構いません。
変数を定義する際は基本的に const
を使い、どうしても必要なときだけ let
を使いましょう。(let
を使うと、書いたコードを後日読み返すときにいつ再代入されるかワクワクしながら読み返せますが、疲れます。)
なお、太古の遺産として var
というキーワードでも変数を定義できますが、面倒に巻き込まれがちなので近寄らないほうが無難です。
変数の名前には、「記号と絵文字以外のUnicode文字」と、「数字」を用いることができます。アンダーバー(_)とドル記号($)も、記号ですが用いることができます。ただし、数字で始めることはできません。
いま定義した変数の内容を確認したいときは、console.logという機能を使うと良いでしょう。
定義した変数を利用する
一度変数を定義したら、その後のコード内でその変数名を記述すると、そこには変数の内容が書いてあるものとして解釈されます。
console.log(name) // nameの部分には、’ぴよ’があるものとして解釈されます。
console.log(変数の名前)を実行することで、指定した変数の中身を見ることができます。
関数
関数とは、定められた処理のまとまりに名前をつけて定義したものです。
これにより、同じような処理を複数回実行する際に記述量を少なくしたり、
大きな処理を分割することでコードを読みやすくしたりすることができます。
関数は、入力を受け取り、受け取った入力に対して定められた処理をした結果を出力します。
入力のことを、引数と呼びます。出力のことを戻り値と呼びます。
関数を自分で定義して実行することもできますし、世界の誰かが作ってくれた関数を実行することもできます。
関数を定義する
関数は、「【functionキーワード】 【関数名】 【(引数(0~複数))】【{処理}】」の形式で定義できます。
例えば、以下のような感じです。
function add (arg1、arg2) { return arg1 + arg2 }
また、多くの場合、以下のような省略記法でも関数を定義できます。(細かい点で違ったりするのですが、それはまた今度。)
const addTwo = (arg1, arg2) => { return arg1 + arg2 }
関数を実行する
関数は、関数名に「()」をつけるて記述することで実行できます。
括弧の中に引数をカンマ区切りで記述できます。
// add関数に2と3を引数として渡した実行結果を、変数resultに代入する const result = add(2, 3) console.log(result) // 5 と表示されるはず
繰り返し
今回のbotでは、数秒ごとに処理を繰り返し実行する必要があります。
JavaScriptでは、「for」や「while」というキーワードを利用することで実現します。
詳しい使い方は、最初は繰り返し処理を実行したくなったときに都度Google検索すれば良いと思います。
いま概要を読んでおきたい方は、ループと反復処理 - JavaScript | MDNをお読みください。
プログラムを書く環境を用意する
これまでは、プログラムはnodeのコンソールで実行してきました。
nodeコンソールは手軽に利用できるので、1-2行の処理を試しに実行してみるのには最適なのですが、
複数行に渡るプログラムを書いていくには不便です。
そこで、あなたがプログラムを書くのをサポートしてくれる道具を用意して、そこでまとめて書いたものを実行できる環境を用意しましょう。
VSCodeをインストールする
VSCode(Visual Studio Code)は、Microsoftが作って世界中の開発者が改良している、すごく高機能な無料のテキストエディタです。
基本的な機能はWindows標準の「メモ帳」と同じく「テキストを編集する」のみですが、プラグインとして様々な機能が提供されています。
プログラムを書いているときに間違っていたら教えてくれたりするのでとても便利です。
こちらからインストールしましょう。 code.visualstudio.com
インストールの仕方がわからなくても大丈夫です。私達にはGoogle検索があります。
「VSCode インストール方法」で検索しましょう!
今回はこのVSCodeを使って、前編でインストールしたNodeJSで動くJavaScriptを書いていきます。
VSCodeを起動する
無事インストールできたら、起動しましょう。
起動すると、このような画面が表示されます。
画面最上部の左端にある「ファイル」から「新規ファイル」を選択することで、プログラムを書くための画面を表示することができます。
くら寿司キャンセル待ちBotを作れるようになろう! - 初心者向け(前編)
後半の途中で非同期処理の説明とかが必要になり、面倒だったので途中で執筆を中断しました。 それでも良いという方のみ読み進めてください。
最近巷では、くら寿司の無限ループが流行っています。
流行るだけならいいですが、そのせいでくら寿司はどこも予約が満席になってしまっています。
日々世間のおこぼれを集めながら命をつないできた我々にとっては、迷惑な話です。
しかし、ここで引き下がってはプロ乞食の名が廃るというもの。
キャンセル待ちをして、空きが出た瞬間に予約できるBotを作ってしまいましょう!
この記事の目的
この記事は、私が今日Botをどのように作ったかを思考の隅々まで細かに説明することで、
読者の皆様が明日から同じような問題に直面した際に、
自力でプログラムを書いて解決できるようになっていただくことを目的としています。
対象の読者
プログラムなんて書いたことがないし、私には難しいかも...
そんなあなたも、今日から自分の欲しい物を作れるようになる思考の方法と、Google検索との付き合い方を学ぶことができるでしょう。授業や研修でちょっとはプログラムを書いたことあるけど、自信ない...
この記事は、あなたのためのものです。
ちょっとした作業を自動化できるようになって、毎日の生活を豊かにしましょう。プログラミング チョットワカル な人
この記事は読まないほうが良いでしょう。
より生産的な人生の送り方をあなたは知っているはずです。
所要時間
今回の記事で作るBotであれば、慣れれば調べながらでも15分程度でつくれるようになります。
目次
作り始めるまで
予約をしようとしたら、満席なことに気づく
朝起きる。Discordで人間を集め終わって、さあ予約するか、と予約サイトを開く。
夜23時以降しか空いていないではないか!
ああ、こんなことなら、一昨日予約しておけばよかった。
しかし、後悔しても仕方ない。
せっかく人間を集めたのだ。なんとしても19時台か20時台の予約しなければ!
キャンセル待ちBotを作ろうと決心する
今後10分間くらい分のやる気を捻出します。
怠惰でなかなか返事をよこさない人間を集めようとする際に必要なそれに比べれば、大したことはありません。
空き状況を表示するページを眺め、触ってみる
やる気が捻出できたら、まずはこれから対戦することになるページを観察します。
観察の目的は、私達の予約したい時間が「予約可能な状態」と「予約不可能な状態」のどちらであるかを見分けるにはどこを見て判断すればよいかを知り、それをbotに教えるためです。
EPARKのサイトからくら寿司池袋東口店を予約する場合、予約手続きを進めていくと、
この画面で時刻を選択した際に、
- 予約可能な時間を選択した場合
と、
- 予約不可な時間を選択した場合
とで、選択肢に差があることがわかります。
また、「時」を選択した際に、何かを読み込んでいるようなマークと待ち時間があるので、
「時」を選択するたびに、サーバと何らかの通信を行ってデータを取得しているのかもしれない、という仮設を立てます。
通信内容を調べる
先程立てた仮設を検証するために、ブラウザの通信内容を覗いてみましょう!
ChromeやFirefox、Edgeなど現代のPC用ブラウザでは、「F12」ボタンを押すことで、
「開発者ツール」という、サーバとの通信内容など、ブラウザの裏側を詳細に見ることができる機能を開くことができます。
今回は、Firefoxの画面で紹介します。(開発者ツールのUIが日本語でわかりやすいので、Firefoxがおすすめです。Chrome向けの英語表記も括弧内に併記しておきます。)
「F12」を押すと、こんな感じで開発者ツールが出現します。(Chromeだと画面右側に表示されるかもしれません。)
いろいろなタブがあり、それぞれ非常に便利なのですが、
今回は通信内容を調べたいので、「ネットワーク(Network)」タブを開きます。
すると、こんな感じで表示されるかと思います。
開発者ツールの「ネットワーク」タブを開いたままの状態で情報の送受信やページの遷移が発生すると、
通信した内容がここに記録されます。
今回は「時」を選択する際に通信を行っているかどうか、またその内容を見たいと思っていたので、
この状態で「時」を選択してみましょう。
23時を選択した場合(予約可能な時間帯を選択した場合)
まずは、予約可能な時間帯である「23時」を選択してみましょう。
すると、「ネットワーク」タブ内に行が追加されました。
どうやら「時」を選択すると、通信が発生しているようです。
増えた行を右クリックして、「URLをコピー」を選択すると、
通信先のURLを確認することができます。
通信先のURLは、
https://epark.jp/receive_department/wait/reserve_minute_select_ajax/110202/depart/11768?receive%5Breceipt_date%5D%5Bdate%5D=1603897200&receive%5Breceipt_date%5D%5Bhour%5D=23
のようです。
内容も覗いてみましょう。
「ネットワーク」タブ内の、いま増えた行をクリックします。
すると、開発者ツールの右半分にその行の詳細が開きます。
「応答(Response)」タブでサーバから送られてきた情報を確認できるので、開いてみましょう。
Firefoxでは、このように応答の内容のプレビューが表示されます。
一番下に「応答ペイロード」と書いてある箇所があり、そこをクリックすると実際のデータを見ることができます。(Chromeでは、「Preview」と「Response」で確認できます。)
なんということでしょう!*1
このサイトでは、HTMLのパーツを送受信しているようです。
これでひとまず「くら寿司池袋東口店の今日の23時」についての予約可否の問い合わせ先URLと、
予約可能なときのサーバからの応答内容を手に入れることができました。
19時を選択した場合(予約不可な時間帯を選択した場合)
今度は、予約不可能な場合を試してみましょう。
「時」の選択肢から、「19時」を選択してみます。
すると、先ほどと同じように開発者ツールの「ネットワーク」タブに行が追加されました。
早速その行をクリックし、内容を確認します。
「応答」タブの「応答ペイロード」を見ると、先程と同じように応答の内容はHTMLであることがわかります。
また、今回のリクエスト先であるURLは、次のようになっていました。
https://epark.jp/receive_department/wait/reserve_minute_select_ajax/110202/depart/11768?receive%5Breceipt_date%5D%5Bdate%5D=1603897200&receive%5Breceipt_date%5D%5Bhour%5D=19
これで、19時を選択した際の(=予約可能でない場合の)通信内容と通信先のURLも手に入れる事ができました。
2つの通信を比較する
URLを比較する
先程の23時を選択した際のURLと、19時を選択した際のURLを比較してみましょう。
並べてみると、最後の「19」と「23」だけ異なっていることがわかります。
このことから、同じ日の別の時刻を調べたいときは、URLの末尾だけを書き換えれば良さそうです。
通信の応答内容を比較する
今度は、通信内容を確認してみましょう。
<select name="receive[receipt_date][minute]" id="receive_receipt_date_minute"> <option value="" selected="selected">▼分を選択</option> <option value="20" >20~30</option> <option value="30" >30~40</option> </select>
<select name="receive[receipt_date][minute]" id="receive_receipt_date_minute"> <option value="" selected="selected">▼分を選択</option> </select>
上が23時(予約可能)、下が19時(予約不可)の応答の内容です。
同じ部分が多いですが、よく見ると予約可能なときには「20~30」のような選択肢が存在しているという違いがあることが読み取れます。
これはつまり、応答内容に「~」が1つ以上含まれているときには、その時間帯は予約可能であると判定することができるということです。
Botの内容を定める
このことから、キャンセル待ちbotは次のよな動作で実現できそうだとわかります。
1. 調べたい時間帯向けのURLの内容を取得する 2. 取得した内容に、「~」が含まれているかを確認する 3-a. 含まれていた場合、何らかの形で通知する 3-b. 含まれていなかった場合、数秒待ってまた1を繰り返す
3-aの通知は、Discordのサーバに飛ばして、気づいた誰かが予約できるようにするのが良さそうです。
それでは、これを実装していきましょう!
Botを実装する
どの言語で作るかを決める
ぶっちゃけどの言語を使っても大差ないので、好きな言語を使うのが良いでしょう。
今回初めてor久しぶりにプログラムを書く場合は、
NodeJS(JavaScript)かPythonあたりが今後他の用途にも知識を活用しやすくて良いかと思います。
今回は、NodeJSを使う場合を解説します。
NodeJSとは
本来ブラウザ上で動くJavaScriptを、PCやサーバのコマンドラインから実行できる環境のことです。
詳しくは、公式ドキュメントに色々(本当に色々!)書いてあるので、興味がある方は読んでみると良いかもしれません。(英語ですが)
NodeJSの実行環境をインストールする
(既にNodeJSをインストール済みの方は、先に進んでください。)
①インストール方法を検索する
「NodeJS インストール」でGoogle検索します。
するとこんな感じに検索結果が表示されます。
ここで一番上のQiitaのリンクをクリックしがちですが、まずここに落とし穴があります。
Qiitaの記事は、情報が古かったり、(この記事と同じように)余計な情報が多く、かえって分かりづらいことも多かったりします。お気をつけください。
しかし糞記事を踏む経験を積むことも、インターネットリテラシーを上げて情報の氾濫する時代を生き残るためには必要です。
ここは敢えて、一番上の記事をクリックして内容に従っても良いでしょう。
もしあなたが良質な情報にしか触れたくないとお考えであれば、公式っぽい検索結果を選ぶのが良いでしょう。
今回のスクリーンショットだと、3番目がそれになります。
ただ、この公式っぽいのを探すのにも少しコツが必要です。
こういうソフトウェアの公式ページは、大体.orgドメインを使っているので、それが1つの目印になります。
Google検索結果を状況に応じて適切に取捨選択する能力は、プログラムを書くときに限らず、豊かな人生を送る上で非常に重要です。
プログラミングより先に小学校の授業で取り上げるべき課題だと私は考えています。
②インストールが完了した
さて、あなたは持ち前のインターネットリテラシーを活用し、NodeJSのインストール方法を調べ、
調べた結果に従って、あるいは経験に基づく直観でそれをインストールすることに成功しました。
③うまくインストールできたか確認する
この節の内容は、「node インストール 確認」とGoogle検索することで、自力で探すことが可能です。 もしくは、先程あなたが見つけたインストール手順の最後に含まれているかもしれません。
それでは、うまくNodeJSの環境をセットアップ(=NodeJSをインストール)できたかを確認しましょう。
Windowsを利用している人は、「コマンドプロンプト」を開いてください。
開き方がわからない人、忘れてしまった人は、Googleで調べましょう。
「コマンドプロンプト 開き方」で検索するのです。
開いたら、こんな感じの画面が表示されます。
環境が正しくセットアップできていれば、nodeコマンドが実行できるようになっているはずです。 「node -v」とコマンドプロンプトに入力し、Enterキーで実行してみましょう。
画像のように、バージョン情報が表示されればインストールは成功しており、NodeJSが利用可能になっています。
前編の終わり
長くなってしまったので、ここまでを前編とさせていただきます。
後編では、先程まとめた内容を基にプログラムを書いていきます。
1. 調べたい時間帯向けのURLの内容を取得する 2. 取得した内容に、「~」が含まれているかを確認する 3-a. 含まれていた場合、何らかの形で通知する 3-b. 含まれていなかった場合、数秒待ってまた1を繰り返す
TTC (Top Trading Cycle) をNetworkXで実装する
学生の頃、マーケットデザインの授業で習った「TTC(Top Trading Cycle)アルゴリズム」を使ってプレゼント交換会などで使えるWebサイトを作成した際の、アルゴリズム部分のNetworkXを利用した実装サンプルを紹介します。
TTCアルゴリズムとは
2012年にノーベル経済学賞を受賞したロイド・シャープレーらが考案した、物々交換のためのアルゴリズムです。
近年経済学で注目を浴びているマーケットデザインの分野で利用されています。
アルゴリズムの詳細
財を1つずつ持っている各参加者が、ほかの参加者と財を1回交換するゲームを考えます。
- 現時点で残っている参加者の持つ財のうち、最も欲しいものを持つ参加者を、参加者それぞれが指す。(自分を指しても良い)
- 閉路(輪を作れる箇所)が必ず1つ以上できるので、その閉路に沿って財を交換する。
- 閉路に含まれる参加者をゲームから除外する。
- 残っている参加者で、①に戻って繰り返す。
以上がTTCアルゴリズムです。
ここで得られる交換の結果は、個人合理性や効率性を満たす「強コア」な配分となります。
アルゴリズムをもっと詳しく知りたい方は、ここなどをご覧ください。
実装例
import networkx as nx import random import matplotlib.pyplot as plt def ttc_algorithm(preference_dict: dict): result_cycles_list = [] while preference_dict != {}: # create directed graph g = nx.DiGraph() # add nodes nodes_list = [] for key in preference_dict: nodes_list.append(key) nodes_list.sort() for elem in nodes_list: g.add_node(elem) for key in preference_dict: g.add_edge(key, preference_dict[key][0]) # search for cycles cycles = nx.simple_cycles(g) cycles_list = list(cycles) # Graph drawing part starts here color_map_dict_pre = {key: "deepskyblue" for key in preference_dict.keys()} for cycle in cycles_list: for edge_name in cycle: color_map_dict_pre[edge_name] = "yellow" color_map = list(color_map_dict_pre.values()) nx.draw_networkx(g, node_color=color_map) plt.show() # Graph drawing part ends here # add cycles to result result_cycles_list.append(cycles_list) # start removing edges from preference dict for cycle in cycles_list: for edge_name in cycle: preference_dict.pop(edge_name, None) for key in preference_dict: if edge_name in preference_dict[key]: preference_dict[key].remove(edge_name) # end removing edges from preference dict return result_cycles_list if __name__ == "__main__": num_gifts = random.randint(1, 100) preferences = {} for i in range(num_gifts): preferences[i] = random.sample(list(range(num_gifts)), num_gifts) print(preferences) print(ttc_algorithm(preference_dict=preferences))
実装の説明
表題の通り、今回のプログラムはNetworkXを使って実装されています。
TTCアルゴリズムを実装するにあたって、アルゴリズムの②と③の部分(閉路の探索)が課題となります。
そこで、NetworkXを使うことにしました。
NetworkXは、グラフを扱うためのPythonのライブラリで、非常に便利なメソッドが多く用意されています。
TTCアルゴリズムの閉路探索の部分は、上記のコード28行目のわずか1行で実現できました。
cycles = nx.simple_cycles(g)
32行目から41行目は、matplotlibで実行の経過を可視化するためのコードになっています。
実行すると、こんな感じのグラフが出力されます。
このようにTTCアルゴリズムは、有向グラフの問題を解くライブラリを用いれば簡単に実装できます。
もっと広まってほしい。
あとがき
「クリスマス会のプレゼント交換でがっかりすることがなくなる!TTC最高!!!」って言ってノリノリでWebアプリケーションを作って、
いざ実戦投入しようと思ってサークル員にプレゼンしたら、
「プレゼント交換は何もらえるかわからないワクワク感が楽しいんだよ」って言われてお蔵入りになりました。
悲しい。。