JPHACKSに参加した話(プロダクト紹介)

12/09追記

JPHACKS 2020 Advent Calendar 2020が生えていたのでこの記事の公開日である4日に入れておきます。

JPHACKSとは

結構大きめのハッカソンイベントです。2020年は以下のように開催されました。

  • 10/31~11/6日の間に開発、7日に発表
  • 決勝に進む上位16チームが13日に発表され、27日までさらに開発を進め、28日に発表
  • フルリモートで開催

僕は現在elabという研究室に所属して、かつGiveryでバイトをしているのですが、elabの教授である江崎先生とGiveryはこのJPHACKSの主催です。参加しない理由が無いといういう理由で、研究室の同期を誘って出場しました。

この記事は、僕たちelab4b(elab for beginners)が作成した「SATORI」というプロダクトについてです。JPHACKSでの流れとかエピソードとかそういうところも書きたいところですが、文章量が異常になりそうなため、プロダクトについてちょこっと(ちょこっととは言っていない)紹介するだけに留めます。

SATORIについて

オンライン試験に特化したカンニング対策プラットフォームです。COVID-19の影響で多くの大学生がオンラインで試験を受けた経験があると思います。人によっては大学院院試がオンラインであったり、TOEFLを始めとした資格試験、コーディング試験などもオンライン試験として挙げられます。ただ、これらの試験には問題点があり、特に大学の試験についてはTwitterでも一時期話題になったことでしょう。

大学でのオンライン試験や院試の問題点

例えば、僕らの大学で行われたオンライン試験の一部では、以下のような環境での試験でした。

  • 全員バーチャル背景をOFFにした状態でZOOMでカメラON
  • カメラは手元/横顔/PC画面が映るような横からの視点でZOOMに参加する
  • マイクON/OFFは試験によって異なる
  • ZOOMは録画されます

受験者の立場では、このようなオンライン試験では以下のような問題点があると考えられます。

  • カンニングが対面での試験以上に行いやすい環境での試験であったこと
  • プライバシーの問題。部屋が他の人に見られるのが嫌だ。
    • マイクONの場合、咳払いがブロードキャストされるのがすごい嫌だ、という人もいました。
  • 大人数が同時にZOOMでカメラをONにすることで、ネットワーク負荷が非常に大きくなる
    • 僕の実家は回線が非常に弱く、1:1でも両者がカメラONにするとダメです。多分そこで試験を受けるのは厳しいでしょう。
    • 4月頃とかはZOOM使っているとブラウザが重くなるっていう現象起きませんでしたか?僕の環境では起こっていたのですが、これが試験中に行われると試験の続行が怪しいレベルでつらいです。

また、試験監督の立場だと、以下のような問題があります。

  • 対面の試験に比べて、カンニング行為の検出が難しい
  • 試験後に不正が無いか見直せるのはオンライン試験ならではの利点だが、見る量多すぎて無理

TOEFLの問題点

専用ソフトをインストールします。リモートアクセスを許可するソフトのため、試験官が自分のPCを操作します。これが不快だというチームメンバーがいました。僕も聞いていて正直もう少しマシな方法が無いものかと思いました。

また、この方法を運用するには多くの人数を試験官として割り当てる必要があります。

コーディング試験の問題点

現在ほとんどのコーディング試験がカンニング対策をしていないでしょう。もちろん出題される問題には一定の新規性があり、同じ問題を探して答えをコピペ、みたいなことはほぼ不可能です。では、以下のシナリオはどうでしょう?

  1. 受験者Aさんが別のサービスのチャット機能を利用し、十分に実力のあるBさんに「競プロ勉強中なのですが、この問題はどうやったら解けますか?」みたいなことを聞きます。
  2. Bさんは勉強熱心なAさんに感心し、その問題の解き方や実際にACするコードを返信します。
  3. Aさんはシメシメとそのコードをコピペして無事ACします。

このようなことは無いと言い切れますか?現在既にWebテストは替え玉で受験されることがある1ことを考えると、オンライン試験でも同様のことが起こるのは時間の問題だと思います(し、もう起こっているかもしれません)

SATORIの目標

では、上に挙げた問題点を全て克服できるようなツールは作成可能でしょうか?SATORIはそれらの多くを克服したプロダクトです。これからさらに良くなっていくかもしれません。

競合について

実は結構最近出ています。以下のようなものが割と検索でヒットするイメージです。

これらとの大きな違いは「処理をローカルで行って必要最低限のデータのみサーバーに送る」ため、「プライバシーが最大限守られる」「ネットワーク負荷がかからない」ことです。詳細を詳しく調べ切れてはいませんが、例えばCheckPointZはあらゆる操作を記録するみたいですので、恐らくキーロガーも導入することになるでしょう。また、SATORIにも通じるところがありますが、いずれの手法も何かしらの抜け道は存在すると思った方がよいです(開発者がそれを言うのか)。ここに挙げている手法すら対策されていない可能性もあります。

SATORIの構成要素

ざっくり現在の技術スタックは以下のようになっています。 satori (1).png

electronとして動作するクライアントが様々な処理を行い、アラートデータのみをAPIサーバーへ送信、DBに保存します。試験監督は管理画面にアクセスすることで統計情報を取得することが出来ます。

正直本体がクライアントで、Web側はチームの1名に任せてしまったので申し訳ない...と思いつつクライアントの方に手を伸ばしていました。そこまで強くWebサイドに触れていないということもあり、この記事ではクライアントサイドのみに焦点を当てていこうと思います。

SATORIの検出能力と技術

さて、そもそもオンライン試験でのカンニングとしてどのような行為が考えられるでしょうか?そしてそれは「カンニング行為として検出すべきか」についても同時に考えていきます。

スマホを含む別端末や本の参照

対面だと一瞬でバレるタイプのやつです。一応類似する手法として「他の人の回答を見る」も存在しますが、オンライン試験を考えた時にこのケースは通常存在しないでしょう。

これらの動作の特徴としては、「一定以上の時間、パソコンの画面から目を離す」という点があります。つまり視線検出や顔向き検出が役に立ちそうです。現時点ではOpenCVにあるFacemark APIをJSで利用し、顔の特徴点から顔向き推定を行っています。結構簡易的ですが、「一定時間以上横方向を向いていた場合、怪しいと考える」としています。

想定される問題点として、「複数の画面を利用している」「Webカメラの位置決め打ちでいいのか」があります。前者については大学院入試で「複数の画面の利用を禁止する」という運用がなされていたため、それを活用(複数画面があると警告が出るようになっています)しています。後者については、「1画面であることが担保される状態で、ノートPCも含めWebカメラの位置はほぼ同じだろう」というやや強めの仮定を置いてしまっているため、運用でカバーor改善が必要です。

なお、上に挙げている競合は全てこれは実装していると思われます。また、この検出は対面でのテストでも使えるという汎用性を持っています。

チャット機能の利用

コーディング試験で例に挙げていたものもここに入ります。僕らの考えた「取り出すべき行動の特徴」として、以下の2つが挙げられました。

  • チャット機能を利用するためには必ず別のウィンドウ(ブラウザならタブ)を利用してチャット画面を開く必要がある
  • 貰った情報を利用するために、特にコーディング試験ではコピー&ペーストをする可能性が高い

これらのことから、「アクティブウィンドウの変化の検知」「クリップボードの内容変化の検知」を行っています。electronに入れるということでnpmからclipboardyactive-winを引っ張ってきて、利用しています。本当はイベント検知出来たほうがいいんでしょうけど...

const activeWin = require('active-win');
// 現在アクティブなウィンドウのウィンドウタイトルを取得。この方法ならブラウザのタブ変化も検知可能です
const current_active_app_title = (async() => {
    active_app_title = (await activeWin()).title;
})();
const clipboardy = require('clipboardy');
// 現在のクリップボードの内容を取得
const current_clipboard = clipboardy.readSync();

これの結果、「あらゆるブラウザの検索」等も検知できてしまいますが、それをカンニングとみなすかどうかは試験によって異なってくると思います。例えばTOEFLならネット辞書使えるのでアウト、コーディング試験ならAPIとか言語仕様とか調べていいでしょ、みたいな。ここは運用に任せたいかなと思っている箇所です。

(説明能力を考えるとこのアラートログから実際の挙動をシミュレーション出来ると望ましいでしょう)

会話

カメラから見える受験者は普通に受験しているようにも見えます。ただし、受験者は画面外の誰かと会話をしていて2、受験者がヒントや答えを得ている、といったパターンです。

テスト前に受験者の音声データを録音し、その人と同一人物の音声か判定する、ということをしています。

ここがかなり闇で、「speaker dialization」という問題に分類されそうです。ケプストラムの類似度を取ってクラスタリングみたいなことをすることを考えると、RNNが一般的な手法として挙がります。元々「ローカルで実行する」という都合SVMを使って頑張っていたのですが、精度が出ないのでpyanote-audioを頼ってみたりしました。結構微妙な箇所です。

学習における特徴として、学習データを極限まで削っているということが考えられます。これは「テスト前にユーザーの声紋を録る時間が長いと体験がよくない」ということがあり、日本語においてケプストラムをクラスタリングすることを考えると各母音を平均化した曖昧母音を学習データにするとよいらしい(他のメンバー談)のですが、これを得るための最低情報は「あいうえおを各5秒間喋ってもらう」が考えられ、現時点ではこの手法を採用しています。

このモジュールはPythonで動くため、electronから別プロセスを立てて動かす必要があります。python-shellを利用して、Python Programを立てています。

const ps = require('python-shell');
const pyshell = new ps.PythonShell(script_path);
pyshell.on('message', (message) => {
    // pythonでのstdoutがmessageに渡される
});

なりすまし

そもそも別の人が受験しているパターン。さて、どのようなケースが考えられるでしょうか。

  • そもそも現地で受験者が入れ替わるパターン。対面試験なら学生証チェックで落とせるやつです。これはdlibを使って検出が出来ています。3これも競合は全て実装していると思われます。
  • 受験者がsshサーバーで受験を行い、外部から他の人がリモートアクセスすることで他の人が試験問題を解く場合。このように、監督側には正規の受験者が受験しているように見えますが、実際には他の人が問題を解いている場合を今後「実質的ななりすまし」と呼んでいきます。
    • sshならプロセス監視すればそれっぽい名前がヒットするでしょう。systeminformationを使うと分かります。
    • 一般的なリモートアクセスの場合、GUIでの操作になることが想定されます。実際に検証してみたところ、そのプロセスの通信量は1MB/s前後になることが多いです。この数値は試験中であることを考慮するとかなり大きい(ZOOMや動画視聴に相当するため)可能性が高いため、パケットキャプチャーを行ってリアルタイムにポート毎の通信量を測定します。閾値を超えた場合、該当ポートからプロセスを割り出す、みたいなことをしていますが、例えばChrome Remote Desktopの場合はUDPクライアントになっているのか現時点でポートからプロセスが割り出せないみたいです。

パケットキャプチャーについてはcapを利用してます。nodeでもパケットキャプチャー出来るんだ!と見つけた瞬間これでやってしまいました。結局Mac/Linux環境ではtcpdumpにroot権限が必要なため、electronとプロセスを分ける方が望ましいです。今回はsudo-promptを利用して、直接nodeを実行しています

const AsyncLock = require('async-lock');
const Cap = require('cap').Cap;
const decoders = require('cap').decoders;
const PROTOCOL = decoders.PROTOCOL;

const c = new Cap();
const filter = "";
const bufSize = 10 * 1024 * 1024;
const buffer = Buffer.alloc(65535);
const linkType = c.open(device.name, filter, bufSize, buffer);
c.setMinBytes && c.setMinBytes(0);

// UDP/TCP問わず、port毎にネットワークトラフィックを記録していきます
const lock = new AsyncLock({ timeout: 1000 });
let portTraffics = {};
c.on('packet', (nbytes, _) => {
    if (linkType === 'ETHERNET') {
        const eth = decoders.Ethernet(buffer);
        const internet = eth.info.type === PROTOCOL.ETHERNET.IPV4 ? decoders.IPV4(buffer, eth.offset) :
            eth.info.type === PROTOCOL.ETHERNET.IPV6 ? decoders.IPV6(buffer, eth.offset) :
                null;
        if (internet === null) {
            return;
        }
        const saddr = internet.info.srcaddr;
        const daddr = internet.info.dstaddr;
        if (saddr !== ipAddr && daddr !== ipAddr) {
            return;
        }
        const trans = internet.info.protocol === PROTOCOL.IP.TCP ? decoders.TCP(buffer, internet.offset) :
            internet.info.protocol === PROTOCOL.IP.UDP ? decoders.UDP(buffer, internet.offset) :
                null;
        if (trans === null) {
            return;
        }
        const sport = trans.info.srcport;
        const dport = trans.info.dstport;
        lock.acquire("portTraffics", () => {
            if (saddr === ipAddr) {
                portTraffics[sport] = portTraffics[sport] ? portTraffics[sport] + nbytes : nbytes;
            }
            else {
                portTraffics[dport] = portTraffics[dport] ? portTraffics[dport] + nbytes : nbytes;
            }
        });
    }
});

今後の課題: そもそも与えられる情報は本当なのか?

実用化を踏まえたうえで考えなければならない大きな問題として、「与えられる情報は本当か否か考える必要がある」というものがあります。

例えばWebカメラだと「監督から見ると普通に受験しているように見える。ただし、実際には前日に撮影した動画をカメラとして流し込んでいる」みたいなことがありえます。こうするだけで持ち込みや別端末を利用したカンニングが可能になるわけです。

また、このソフトがVMにインストールされた場合どうなるでしょうか。当然VMの外部へのアクセスは基本的には出来ません。そのため、ホスト側で自由にカンニングが出来てしまうため、自身がVMにインストールされていないかどうか調べる必要があります。

バイス情報やキーロガー、パケットキャプチャーについても同様で、OS側の処理に割り込むことで騙すことが出来そうです。例えばパケットキャプチャーについては、優先度がどうなるか不明ですが、BPFを用いてtcpdumpよりも前にパケットデータを取り出して、tcpdumpにデータを渡さない(DROPすれば出来る?)、みたいなことが出来れば理論上は検証を騙せる可能性があります。この辺あまり詳しくないですが。

ただ、共通点としてこれらのことには行った痕跡が存在します。例えば疑似カメラなら「カメラの詳細情報を調べると実在しないものである」であったり、VMなら「MACアドレス等ハードウェア情報がVM固有のものである」等。このような手法についても考察と対策を深め、同時に新しく想定される手法を考えていく必要があります。


  1. 過去に僕は実際に頼まれたことがあります。その時は「じゃあ全問不正解で答えてあげるよ」と拒否しました。

  2. これ書きながら「腹話術でも使ってるの?」と思いました。そもそも普通に会話してたら口動くので怪しいですよね…ただし、オンライン試験だからラバーダッグ・デバッグが出来るのも利点だとは思うのでまぁいいと今はこじつけます。

  3. 実は僕これ詳細なコードを見ていないので、どのようにして「人が異なっているか」については知らないのです…特徴点を取ってごにょごにょするみたいですが。

Zoomにおけるバーチャル配信の手法

COVID-19の影響でオンライン会議が増える中、自分の顔出しや自室が映るのを避けたいという人は多いかと思います。僕が所属している学校でも授業がオンラインになりましたが、顔出しNGの方はそれなりにいるようです。 僕の所属している学校では授業にZoomを使用しているのですが、現在その周りで問題が起こっているので、その概要と回避策を書いておきます(解決策ではないです)

続きを読む

この世界で最も愛しい生物とそれに関する技術について

この記事はTSG Advent Calendar 2019 - Adventar21日目の記事として書かれています。前日担当のiLissさん早く記事書けやオラァ。これTSGに関係あるの?毎年恒例。全く関係ないのもアレなので、未来に関係あるかもしれない話でもしましょう。

この世界で最も愛しい生物について

唐突ですが、僕はグソクムシ*1です。現実社会ではこのような等脚目は生きずらい*2ので、人間に化けて生活をしています*3。そのため、現在の科学技術において生物学的にホモサピエンスに分類しているはず。

ここまでどうでもいい茶番でしたが、そういうこともあり僕はグソクムシが好きです。どの程度好きかと言うと、近場であるすみだ水族館の年間パスポートを所持しており、2か月に一度程度の頻度でそこにいるダイオウグソクムシと会話したり*4、旅行先付近の水族館にわざわざ行ってグソクムシと会話してきたり。写真フォルダの30%位は彼らで占められているといっても過言ではないでしょう。僕のTwitterの画像欄を見ればそのごくごく一部が垣間見れるかも。

さて、ここまで何も考えずに書いていましたが、これ以上書き続けると逆に可読性を失ってしまったりしてしまうので、本題に入りましょう。

バーチャルグソクムシ「fiord」を取り巻く技術

そんな僕ですが、今年の春になって「VTuber」なるものを知りました。これを見た僕は思ったのです。「これはグソクムシをバーチャル空間に顕現させる義務がある」と。既存技術を探して、GWを使ってバーチャル空間上で動けるようにしました。そのため誕生日が2019/5/5になっています。Twitterに登録したら凍結するの目に見えているから登録しないけど。やっていることは以下のようなものです。

  1. 人間の顔をOpenCVで撮影、顔の位置を認識
  2. 1で取得した顔からdlibを用いて顔の特徴点(輪郭から左目の左端など、68の点を抜き出します)を取得
  3. 2で取得した顔の特徴点を雑に作った3Dモデルの点に合わせることで顔の角度を取得
  4. 顔の特徴点からEAR(eye aspect ratio)を用いて瞬きをしているか取得
  5. 3,4で取得したデータをUnityにUDPで投げつけて、Unityでそれを用いてモデルの顔を動かす
  6. 同時にNintendo SwitchのJoyconをPCにbluetooth接続しておくことで、ジャイロセンサーから傾きの変化を取得し、モデルの腕を動かす
  7. Unityではグリーンバックにしてモデルを表示し、OBS Studioでカラーキー入れれば配信でも使えるよ!デスクトップマスコット化するかは考え中。

ここまでサクッと書きました。TSGのみんなはプロなのでこれ以上説明は要らないでしょうし、多分これだけでも実装まで持っていけると思いますが、外の普通の方が見に来ることを考慮してちゃんと中身を書きます。

全体像

ところで、人間の動作とグソクムシの動作は明らかに異なるため、人間の動きをマッピングさせる必要があります*5。取り敢えず限定的に「顔と目、前足」のみの実現を目指します。Pythonで顔認識系統を全部担当。そこからUDPでUnityに「顔の角度、目の開き度合い」を送り付けています。別途UnityではNintendo SwitchのJoyConからその傾きの変化を取得して、その分腕を動かしています。

Python部分

opencv_contribにはfacemark apiというものがあり、それを用いるだけでも実は顔の特徴点を取得することができます。作成した当時はそんなことも知らなかったので、opencv+dlibを用いています。若干の高速化のためにopencvで顔の矩形検出、その範囲のみでdlibの顔の特徴点検出に回しています。UDPに送り付けるパートは…何でUDPにしたんだろう。他いい手段ないですか?また、誤差も出るので「EARだけ取る(カルマンフィルターにより瞬きが消されるので)」→「カルマンフィルターに全顔特徴点をかける」→「顔の傾きを取る」の流れで計算を行います。

Unity部分

Pythonから投げられたデータを受け取ります。顔の角度については(x,y,z)=(0,0,0)からのズレをオイラー角度のまま再現します。と同時に、SwitchのJoyconからも入力を受けます。こんなライブラリがあるので余裕です!

github.com

目の開き度合いについては、「閾値以下なら目を完全に閉じる」「閾値以上なら一定時間をかけて目を開いていく」というモーションを付けると自然な動きが出来ます。EARそのものは目の開き度合いに関与するとまずいので。ただ、これだといわゆる「薄目」が表現できないので、その辺は要考察。

出力背景が緑になっているので、例えばOBS Studioという配信ソフト(といいながら主にデスクトップ録画に僕は用いています)に送ってカラーキーつけるとVTuberになれます。

それで出来たのが

www.youtube.com

ところで、VTuberになる権限を得た僕ですが、「バーチャル空間で動かせるようになる」のが目標だったのでコンテンツもモチベーションもありません。よって微動だにしません。そこ、「グソクムシまんまじゃん」とか言うな!ダイオウグソクムシが動かないのは事実だろうけど、オオグソクムシ結構動くぞ!

試しに配信*6していますが、よく腕がすっとんでいくので、はよモーキャプしろ。ただ、使っているのが慣性系なので、おそらく誤差が出るでしょう。何らかの定期補正機能を導入したいお気持ち。

これから

  • VRChatに出したい: VRCモデルには専用のフォーマットがあって、多分それに合わせたモデルを作る必要があります。多分必要なのボーン構成だけなので、うまい具合にマッピングすれば通ります。
  • 脱Joycon: 現在僕が所属している学科にある通称「BDM(びっくりドッキリメカ」と呼ばれる授業で「モーションキャプチャー」を作成中です。というのも、Joycon結構誤差が出ます*7。ということで脱Joyconをしたい+前足以外も動かしたいということで頑張ってはんだ付けするぞ。
  • 他の個体にも: これ書きながら&人と通話しながら21日21:00~超のんびり雑に以下のモデルを作ってました。

f:id:Hyoga:20191222011123p:plain
TSGに大量発生する謎の生物「おじぎねこ」

あくまで人間が非人間を動かす際は専用の動きのマッピングが必要になりますが、おそらく可能でしょう。体のくねりと顔の動きがあると思うんだけど、どうするんだ…

教えて、おじぎねこ!

*1:誰がダイオウグソクムシだ。僕はオオグソクムシです。どうでもいいけどはてなさんなんでダイオウグソクムシグソクムシはてなキーワード(ブログ上のワードに線が引かれて自動的にリンクが貼られる機能)に入っていてオオグソクムシが入っていないのですか?

*2:人間もそうだという説はありますが

*3:ただし、その時間が長すぎて解除方法が分からなくなっているんですよね…

*4:巡礼と僕は言っています。年内にもう一度行く予定。

*5:はやくグソクムシになりたぁい…

*6:今のところYoutube配信は配信開始の押し忘れ等で何もできていない

*7:joyconlibにちゃんと誤差が出ることは書いてあります

yukicoder No.819 Enjapma game

ゲーム問題にしては面白い(といっても典型だと思う)問題だったので。

問題概要

H×Wのグリッドに駒がいくつか置かれている。相手スタートで以下の操作を交互に繰り返す。

  • 駒を1つ選んで取り除く。
  • 駒を1つ選んで、左か下のどちらか隣のマスに移動させる。ただし、移動先に他の駒があってはならない。

操作を行えなくなった方の負け、というルールで、お互いに最善手を取ったとき勝てるか判定せよ。

No.819 Enjapma game - yukicoder

解説

ゲーム分野にはいくつか解法があって、大体そのどれかに該当する。

  • DFSを行って最善手を全探索するタイプ(grundy数が使えない場合はこっち)
  • grundy数を用いるタイプ(上のよりシンプルな問題で用いることが出来る)
  • その他数学的な考察を行うことで規則的に問題を解くタイプ

今回は上2つをやろうとすると探索範囲が広すぎて計算が間に合わない。ということで自然と規則を見つけて解く形に。

3x3辺りまで適当に脳内シミュレーション・grundy数の設定を行っていると、「盤面を一松模様と捉えて、移動=白黒を1つひっくり返す」と捉えるとよさそうなことに気が付きます。主に駒が2個の時に勝利条件=白黒1つずつだったので。
これは、最終的に駒を1つ取り除いたほうがその次に相手が最後の駒を取り除いてくるので負けるため、お互い可能な限り移動し続けます。その結果必ず白黒1つずつの状態になって移動できなくなるので、それと同じ状態(白黒→白黒の遷移は出来ないけど、必ず白白→白黒と黒黒→白黒は存在するので)で負けることが分かります。

さて、市松模様の白黒を用い始めたところで、駒の個数ごとに勝てる条件を探してみましょう。探し方は「2個の基準が存在するので、3個以降はn-1個の時の勝利条件を相手が作れない」状態を探すことです。

  • 1個→相手が取り除くので必ず負けます
  • 2個→前述の通り白黒1つずつで勝ち
  • 3個→白白白と黒黒黒で勝てます(色を変えてきても取り除いてきても白黒の状況が作れます)
  • 4個→白白黒黒で勝てます(同様相手の行動に応じて必ず白白白か黒黒黒が作れます。それ以外は逆に白白白か黒黒黒を作られます)
  • 5個→白白白白黒か白黒黒黒黒が勝利条件です
  • 6個→白×6、白×3黒×3、黒×6が勝利条件です

という感じです。これを見直して規則を探すと、「白の個数-黒の個数が3の倍数で勝利」という規則が見えてくるのではないでしょうか。

ということで、これをそのまま実装すると通ります。O(HW)。

int main() {
    int h, w;  cin >> h >> w;
    vector<string> s(h);  cin >> s;
    int cnt = 0;
    rep(i, h)   rep(j, w) {
        if (s[i][j] == 'o') cnt += ((i + j) & 1 ? 1 : -1);
    }
    cout << (cnt % 3 == 0 ? "YES" : "NO") << endl;
    return 0;
}

感想

僕がゲーム分野得意なのって、絶対にこの解き方で解けるからなんだよね…あまりいいことではない気がする…

TSG LIVE2!の感想・反省等を適当に書いたポエム

この記事はTSG Advent Calendar 2018の13日目の記事として書かれたはずでした。書き始めたの23:45分なんですが。15分で書けるかボケ。

昨日はpizzacat83プロの『語彙力ゲーム「弓箭」であそぶ』でした。今日はkakinotaneプロの「linux sourcecode readingでやった環境構築」だそうです。

編集後記:この記事は異常に長いので読むことを推奨しないことを書いておきます。

続きを読む

ちょっとしたlsのtips

この記事はTSG Advent Calendar 2018の5日目の記事が当日18:00になっても出てこないことから「やべぇ」となって慌てて作られた記事です。昨日はmoratorium08さんのTSG live CTF writeup - cruel dd, rop4, rop1, simple pwn, RSA modoki, repeat64, leap it, TSG shooter - moragramming!でした。

さて、来週or再来週のコンテンツ制作に奔走している僕ですが(無駄に期待値を上げる人)、今学期のTSG分科会で触れた内容を少しここに書いてみたいなと思いました。

続きを読む