絞り込み

最新の投稿

プログラミング Polygon
プログラミング

Minecraftでプレイヤーの参加をツイートするbotの作り方

こんにちはさしぐめです。初投稿の今回はマインクラフトとTypeScriptを使ったbotについて書こうと思います。 Twitterアプリを申請する Developer PortalでTwitterアプリの申請をしましょう。この時、botとして使いたいアカウントで申請するのがベストです。 間違えてメインアカで申請しちゃった場合は、OAuth1.0aを使ってサブアカのトークンを発行しましょう。(手順は割愛) また、アプリの権限はRead & Write にします(DM権限は使いません)。 APIを用意する Webhookの受け皿にはCloud Functions for Firebase (実質GCP)を使います。 Herokuでもいいんですが、個人的に ログが見やすいサーバーレスかつ応答が早いFirestore等と連携できる外出先で確認できるアプリがある という理由で選びました。 https://firebase.google.com/pricing/ なお、従量制プランを選択する必要があり、処理時間と回数が膨大になると請求される可能性もありますのでご注意ください。 ステップ1: 認証情報を用意 Firebaseプロジェクトを用意して、開発環境を用意します。ここの手順は日々変わるので割愛します。 また、Cloud Functions for Firebaseでは.env ではなくCLIで一つづつ環境変数を設定していきます。 後述するWebhookプラグインはリクエストのヘッダーをカスタマイズできません。仕方なくbodyでパスフレーズを送って認証します。 まずはオレオレパスフレーズを作ります。OAuthでもなんでもないただのパスワードなので、個人用途以外で使わないでください。$ cd functions/ # 32桁のオレオレパスフレーズを生成 $ openssl rand -base64 32 # configに登録 $ firebase functions:config:set minecraft.auth="(オレオレパスフレーズ)" もっといい生成方法があったら教えてください>< アプリの申請が通ると以下のキーが手に入ります。これらを全部コンフィグに登録します。コンシューマキーコンシューマシークレットユーザのトークンユーザのシークレット $ firebase functions:config:set twitter.key="(コンシューマキー)" $ firebase functions:config:set twitter.secret="(コンシューマシークレット)" $ firebase functions:config:set minecraft.auth=(ユーザのトークン)" $ firebase functions:config:set minecraft.auth="(ユーザのシークレット)" # エミュレータ用ファイルを生成 $ firebase functions:config:get > .runtimeconfig.json {   "twitter": {     "user_secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",     "user_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",     "key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",     "secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"   }, "minecraft": {     "auth": "(オレオレパスフレーズ)"   } }.runtimeconfig.json がこんな感じになります。これがローカルでテストする時の環境変数代わりになります。 functions/models/AdminConfig.d.tsexport interface AdminConfig {   twitter: {     key: string;     secret: string;     user_token: string;     user_secret: string;   };   minecraft: {     auth: string;   }; }TypeScriptの場合、こんな感じで型を作っておくと自動補完が楽です。 ステップ3: APIのエンドポイントを用意 さあいよいよAPIを作ります。 $ npm i twitter-api-clienttwitter-api-client というライブラリを使います。 FirebaseなのでFirebaseが用意しているfunctions.https.onRequest を使いますが、別にexpressでもいいです。 functions/src/twitter/post.tsimport * as functions from 'firebase-functions'; import { AdminConfig } from '../models/adminConfig'; const config = functions.config() as AdminConfig; import { TwitterClient } from 'twitter-api-client'; const client = new TwitterClient({ apiKey: config.twitter.key, apiSecret: config.twitter.secret, accessToken: config.twitter.user_token, accessTokenSecret: config.twitter.user_secret, }); const post = functions.region('asia-northeast1').https.onRequest(async (request, response) => { if (request.method !== 'POST') { response.status(405).json({ message: `POSTメソッドを使ってください`, }); return; } const body: { // マイクラサーバーのplugins/SimpleWebhooks/config.ymlで内容は定義してある tweet?: string; // コンフィグで定義 auth?: string; // テスト用 test?: boolean; } = request.body;   functions.logger.debug(`Received body: ${JSON.stringify(body)}`); if (Object.keys(body).length == 0) { response.status(400).json({ message: `bodyはJSONオブジェクトにしてください`, }); return; } if (body.auth !== config.minecraft.auth) { response.status(401).json({ message: `認証できませんでした`, }); return; } let tweet = body.tweet; if (!tweet) { response.status(422).json({ message: `ツイート本文を指定してください`, }); return; } // testがtrueならツイートはしない if (body.test) { response.status(200).json({ message: `テスト成功`,       ...body,     }); return; } // 以下、Twitter APIと通信する try { await client.tweets .statusesUpdate({ status: tweet, }) .then(async (apiResponse) => { // https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/post-statuses-update         // レスポンスにcreated_atがあればツイート成功 if (apiResponse.created_at) { response.status(200).json({ message: `ツイート成功`, ...apiResponse }); functions.logger.info(`Successfully tweeted: ${tweet}`); } else { response.status(500).json( apiResponse ?? { message: `Twitter APIからの応答がありませんでした`, } ); } }) .catch((e) => { response.status(500).json({ message: `Twitter API呼び出し中のエラー: ${JSON.stringify(e)}`, }); }); return; } catch (e) { response.status(500).json({ message: `その他のエラー: ${JSON.stringify(e)}`, }); } return; }); export default post; ステップ3: フォルダにまとめる expressならこんなことしなくて良いですが、Firebaseの書き方だとこうやってエンドポイントを整理します。 functions/src/index.tsconst twitter = require('./twitter'); exports.twitter = twitter; functions/src/twitter/index.tsexport { default as post } from './post'; こうすることで、エンドポイントが /twitter-post になります。 ステップ4: テストとデプロイ $ npm run serve Firebaseのエミュレータが立ち上がり、 http://localhost:5001/(プロジェクト名)/asia-northeast1/twitter-post でテスト可能になります。 curl --location --request POST 'http://localhost:5001/(プロジェクト名)/asia-northeast1/twitter-post' \ --header 'Content-Type: application/json' \ --data-raw '{     "tweet": "テストツイート",     "auth": "(オレオレパスフレーズ)",     "test": true }' こんな感じのリクエストを送ればテストできます。 $ npm run deploy デプロイすると https://asia-northeast1-(プロジェクト名).cloudfunctions.net/twitter-post にAPIが公開されます。 マイクラサーバー側の設定 あとはマイクラサーバーからWebhookを送るだけです。 Webhookプラグインを入れる https://github.com/Akenland/SimpleWebhooks SimpleWebhooks プラグインを使います。PaperMCの1.16.5で動作を確認しました。 プラグインの設定 $ vi plugins/SimpleWebhooks/config.yml config.yml webhooks:   join:     twitter-join:       url: https://asia-northeast1-(プロジェクト名).cloudfunctions.net/twitter-post       json:         tweet: "{PLAYER_DISPLAYNAME}が{SERVER_MOTD}に参加しました!"         auth: "(オレオレパスフレーズ)" こんな感じで、URLと送信するJSONを設定してください。YAMLマジで苦手。 豆知識: Cyberduckなら、サーバー上のファイルをローカルのように編集できます。宗教的こだわりがない場合は例えばVSCodeで開いて編集しましょう。 動作確認 YAMLを保存したら、マイクラサーバー側でwebhookをリロードします。 > webhooks reload サーバー再起動の必要はなく、このコマンドを実行するだけです。 プレイヤーが参加すると、Firebaseの管理画面にログが出るはずです。 .sasigumeが自由にサバイバル!に参加しました!#mc_asobinon 参加方法はこちら→ https://t.co/N1cU2977TQ— アソビノン - 誰でも編集できる攻略サイト (@asobinon) July 23, 2021 うちのサーバーではこんな感じでツイートしています。 --- 以上、マイクラサーバーと連携するbotの作り方でした。

> 内容を見る

プログラミング Polygon
プログラミング

Ktorを知る ~Pipeline編~

こんにちは、オキリョウと申します! 前回、Ktorの特徴について解説させていただきました。(https://oucrc.net/articles/ia_sv-bbmz) そこで今回はKtorで重要な概念であるPipelineについて解説していきたいと思います。 PipelineとはPipelineとは、Ktorにおいて処理を順序良く処理していくためのものです。 ライフサイクルを定義するためのもの、みたいな感じに考えていただけてたらと思います。 前回、Ktorには以下の特徴があるといいました。拡張性が高く、柔軟非同期処理に強いこれらはPipelineを導入することで実現しています。 Ktor内では、継承可能なクラスとして定義されており、そこらじゅうで使われています。Ktor内のクラスの継承元をたどっていくと一枚嚙んでいた・・・ということも珍しくありません。 それではより詳しく見ていきましょう。 Pipelineを図で表すと以下のようになります。 この画像の場合、オレンジ色の向きへと処理が流れていきます。 つまりフェーズ(Phase)1->フェーズ2->フェーズ3->フェーズ4の順番に処理が流れていくということです。 各フェーズ同士は依存していません。しかし、共有する必要のある値ももちろん存在します。 それらはPipelineが所有しているPipelineContextに保存しておき、そこから値を取り出す仕組みになっています。 また、フェーズは好きなところに付け足すことも可能です。 このように、左から右へと、それぞれ独立したフェーズを処理していく流れがPipelineです。 Pipelineの使用例では、Pipelineがどのように使われているのか見てみましょう。 例えば、サーバーサイドアプリケーションの中でも必須である、「リクエストを受けとってリスポンスを返す」という部分はPipelineで実装されています。(ApplicationCallPipelineという名前のクラスです) このオレンジ色の矢印がApplicationCallPipelineです。 こんな感じに差し込まれています。 リクエストを受けとって、そこから各フェーズを実行してリスポンスを作成、返すという感じです。 ではより具体的に見ていきましょう。 このような感じになっています。 左から順番にSetup -> Monitoring -> Features -> Call -> Fallbackの順番に処理が流れています。これら青い部分がフェーズですね。 これらのフェーズの役割は以下の通りです Setup: callやattributesを用意する Monitoring: callを追跡するための部分で、ログやコードの評価を行う、エラーハンドリングなどに使われる Feature: 大体のFeatureが入るところ Call: callを完全に作りきるために利用するところ Fallback: ハンドリングされていないcallを何らかの形で処理する部分 補足call:ApplicationCallの事。リスポンスであったりattributesであったりを管理している attributes:DIコンテナ(多分、詳しく知りたい人は公式をチェック) Feature:Ktorの追加機能の事。後述 それぞれのフェーズを順に通していく中で徐々にリスポンスを作成しています。 この流れがPipelineです。 ちなみにRouting等もPipelineを使用しています。 ではPipelineについてある程度説明したところで拡張性が高く、柔軟非同期処理に強いを実現している方法について説明していきたいと思います。 拡張性が高く柔軟Pipelineはそれぞれのフェーズを定められた順に実行していくことは先ほどまで書いた通りです。 また、これらのフェーズは互いに依存しておらず、好きなところに差し込むことができるということも説明したと思います。 必要なフェーズを差し込んだり、要らないフェーズを抜くことができるということは、必要な処理だけを記述することができるということです。 それに加えて、実はフェーズの間に処理を入れることも可能です。(intercept) 先ほどちらっと登場したFeatureというのは、このような処理を練りこんだライブラリといえます。 そのようにすることで、本来複雑になるはずのライブラリの作成、導入が非常にシンプルにできるわけです。 せっかくですので、リスポンスのヘッダーに現在時刻を付ける、というFeatureを定義、導入したコードを掲載します。 まずは現在時刻を付けるFeatureの定義からpackage com.example import io.ktor.application.* import io.ktor.response.* import io.ktor.util.* import io.ktor.util.pipeline.* import java.time.LocalDateTime class SendTimeHeader(configuration: Configuration) { private val name = configuration.headerName //設定 //この場合、ヘッダーのkeyの部分を設定できるようにする class Configuration{ var headerName: String = "Send-Time" } //差し込まれる処理 private fun intercept(context: PipelineContext<Unit, ApplicationCall>){ //設定された値と、現在時刻をheaderにセット context.call.response.header(name, LocalDateTime.now()) } //アプリケーション実行時に行われる処理 companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, SendTimeHeader>{ //Attributeを設定 override val key: AttributeKey<SendTimeHeader> = AttributeKey("SendTimeHeader") //install時の処理 override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): SendTimeHeader { val configuration = Configuration().apply(configure) //インスタンスの作成 val feature = SendTimeHeader(configuration) //ApplicationCallPipelineのCallというフェーズに以下の処理を入れる pipeline.intercept(ApplicationCallPipeline.Call){ //先ほど定義した、interceptの処理 feature.intercept(this) } return feature } } } こんな感じです。 非常に見づらくなってしまい申し訳ないですが、雰囲気は理解できたかと思います。 実装の流れはそのうち別の記事で説明すると思います。 ちなみに今回の場合は、ApplicationCallPipeline↓ のCallのところにこちらの処理が入ります。 それでは利用するときのコードも掲載します package com.example import io.ktor.application.* import io.ktor.response.* import io.ktor.routing.* import io.ktor.util.* import io.ktor.util.pipeline.* import kotlinx.coroutines.launch fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args) fun Application.main(){ //先ほどのFeatureの導入 install(SendTimeHeader){ headerName = "Time" } routing { //ホームルートにアクセスしたときに「Hello World」を返す get("/"){ call.respondText("Hello World") } } } これだけですね。 installのところでFeatureを指定(必要ならば設定も行う)するだけで、すべてのルートにて、現在時刻が追加で刻まれたヘッダーが返却されます。 一見複雑に見えるかもしれませんが、普段は利用するだけ、つまり下側だけですので非常にシンプルです。 上側の方も、やっていることはそこまで複雑ではなく、他のフレームワークで同じことをすることを考えたらはるかに簡単だと思います。 他にも認証機能やログを取る機能もこんな感じで簡単に導入できます。 このように、簡単にいろいろなライブラリを導入したり、作成したりすることができます。 ですので、自分が好きなライブラリで固めることも可能ですし、かなり規模の大きいアプリケーションを作成することも全然可能です。 これもPipelineだからできることです。 非同期処理に強い前回、KtorではCoroutineというものを使い倒しており、そのために非同期処理に強いという話を書きました。 Pipelineもこの流れを強く受けています。というのも、PipelineContextはCoroutine Scopeを継承しており、Coroutine Scopeとして扱うことができるためです。 例えばrouting { //ホームルートにアクセスしたときに「Hello World」を返す get("/"){ launch{ TODO("Coroutineを起動") } } } という感じで、さらっとCoroutine builderを呼び出すことができます。 get()関数は引数としてPipelineContextを取るからですね。 このように、わざわざCoroutine Scopeを定義する必要もないため、気軽に非同期処理を書くことができます。 また、非同期処理が強い理由としてProceed関数およびProceedWith関数の存在もあります。 ややこしい説明になりますが、この関数を実行したインターセプター(フェーズの間に入れる処理群の事)の処理は後回しにされます。 なんの役に立つのかと思うかもしれませんが、意外と使えます。 例えばかなり重たい処理があったとします。その時はPipelineと違うCoroutineを立ち上げて、その後Proceed関数を実行します。 本来だとCoroutineを立ち上げた場合は、そのCoroutineの処理が終わるまで待機しないといけないため、Coroutineの処理が長ければ長いほどレスポンスが遅くなります。 しかし、後回しにすることで、かなり重たい処理とほかの処理を並列に実行することができます。 重たい処理の完了を待つことなく別の処理を進めることができるため、早く処理を終わらすことができるというわけです。 このようにPipelineを用いることで、非同期処理を簡単に、しかし効果的に書き上げることができるのです。 他にもPipelineではCoroutineを生かして処理の効率化を図っていますが、ここでは省略しています。 まとめいかがだったでしょうか?ちょっと説明が難しくなってしまい、申し訳ございません。 ただ、このフレームワークはかなり面白いので、実際に組んでみる価値は大いにあります。 また、公式がより分かりやすく説明してくれています(英語ですが) ぜひそちらの方にも目を通してみてください! あと、ここまでの説明は公式の解説と、コードを読んでみた結果から書いています。 何かしら間違っていたら申し訳ございません。

> 内容を見る

プログラミング Polygon
プログラミング

音 割 れ ひ じ き (前編)

※想像以上にサムネが怖かったので苦情があればすぐに変えます.なんなら自主的に変えます.なので苦情があれば遠慮せずに言ってください.文句は一切言いません. 突然ですが,皆さんは音を割りたいと思ったことはありますか? ありますね?(圧力) しかしながら,最近,音割れブームは衰退してきており,音割れ動画の投稿数は日に日に減少の一途をたどっています. そこで,音割れ動画愛好家である私が音割れ動画の復興を図るため,この場を借りて,音割れ動画の作り方を伝授しようと思います. まず名乗れよメガネ とお思いになった人もいるかもしれないので,改めて自己紹介をしておきます. どうも,新2回生のひじきです. 最近,換気扇を回しても風呂が完全に乾かなくなってきて非常に困っております. よろしくお願いします. というわけで前置きはさておき,早速音を割っていこうと思います. と思ったのですが,まずは形からということでビジュアル面の方から作っていきたいと思います.(?????????) そして今回は,音割れブームの火付け役であり,私の知る全音割れ動画の中でも最も好きな動画である「音割れポッター」をオマージュしながら音を割っていきます. というわけでまずはこちらの画像をご覧ください. 何度見ても笑ってしまう.(諸般の事情によりモザイクをかけております) そしてここから ①魔法の杖 ②ポッターの外見の作成 ➂光る眼鏡 ④ポーズ の4つを主に寄せて再現しようと思います. 1.杖の再現 そういうことでまずは魔法の杖を再現しよう,というわけで近くのダイソーに向かい,こちらの4点を購入してきました. 1つ目は光を出すための懐中電灯と電池.令和では魔法は電池を用いて使用することができます.驚きですね. 2つ目は光の色を白からピンク色へと変えるための赤い平巻テープ.ちなみに体感3mぐらいしか使用してないので,残りの197m分は産廃と化しました. 3つ目は平巻テープを懐中電灯に張り付けるセロハンテープ.これに至っては家に元々セロハンテープがあったことに後から気づいたので購入した時点で産廃でした. ということで早速魔法の杖を作っていきます. まず,懐中電灯に単3電池をセット. 次に,平巻テープを適当な長さに切り取り,セロハンテープを使って懐中電灯に固定します. 完成\(^o^)/ +ドライバーが見つからなかったり,平巻テープが貧弱すぎてすぐに破れてしまったりなど,ここまで作るのすら普通にダルかったので想像よりも遥かに早く終わってホッとしました. というわけで,点灯させてみるとしましょう. 点灯!!!!!!!!!! あ れ ? 全然ピンクじゃないんだが….どうしてくれんのこれ どうやら平巻テープ1枚だとピンク要素が全然足りなかったようです. というわけで追加しました. 意外にも余裕で1時間かかった上にこの時点で午前5時なので著しく疲れました.そして眠い. こんだけ頑張ったんだから,きっと俺も魔法が使えるようになってるはずだ…. 点灯!!!!!!!!!!!!!!!!!!! 微妙 というかそもそも若干ピンク色にはなっているのですが,懐中電灯の性質上,如何せん光が発散してしまい, 某ポッターの杖の様に杖自体が光ってるようにはどうしても見えませんでした. 正直,この超絶しょうもない作業を1時間続けていたことによる疲労もあり,この時点でもう計画倒れムードでした. 終わりや…. しかし完全に戦意喪失したその時,偶然にも彼の目に飛び込んできたもの,それはだいぶん前から放置されていたトイレットペーパーの芯であったのだ. _人人人人_ > 合体 <  ̄Y^Y^Y^Y^ ̄ なんと今まで分散されていた懐中電灯の光がトイレットペーパーの芯を使用することにより,一ヵ所に集約され, より杖っぽく見えるようになりました. ぶっちゃけ,色々ツッコみどころの多い感じではありますが,これ以上改善しようと思うと気が狂いそうなので いったん杖はこれで完成ということにしておきましょう. この時点で朝6時なのでさすがに寝ます.おやすみ. 2.外見の作成 次は,音割れポッターの外見を作っていこうと思います. ちなみに,杖(?)を作ってから面倒臭すぎて約1週間経ってます.正直,しんどいのでザックリと進めていきます. というわけでまずは服装からですが,画像を見た感じだと服の詳細はよくわからなかったのですが, なんか黒っぽかったんで,唯一持っている真っ黒の服である,黒のヒートテックを着ることにしました. はっきり言ってクソ暑いです.もう初夏だというのにどうしてこんなものを着ないといけないのか. というわけで服装はOK 次は髪型です. 画像のポッターは,どこからか風(?)のようなものを受けており,髪が右の方へと猛烈にたなびいております. しかも思った以上にボッサボサなのでそこも再現しなければなりません.つっかえ. そこで,その双方を達成するために一旦風呂に入ってからドライヤーを使ってゴリ押しで髪の毛を右方向に いかせることにしました. しかし,ここで問題が発生します. このひじきという男,髪の毛がボサボサなくせに,美容師に「ツーブロックすら出来ない」と断言されてしまうほど 髪質が固いのである. そのため,いつまで経っても髪の毛が右にいかないというバグが発生しました.修正は不可能です. というわけで妥協して中程度ぐらいに収めました.写真撮ってからの結論から言うと,髪型正直どうでもよくなってるので今回はこれでいきます.これが大人です. 最後に光る眼鏡なのですが,マンガみたいに光に反射して眼鏡が光るとかは一切ないので(ひじき調べ), こうします(急にモザイクを外していくアホ)(右に行かせたはずの髪の毛が戻っているアホ) 適当にサイズ測って紙切って貼ってみたのですが,これはこれで完成度が高いように感じます. もうこれで完成です.(やけくそ) 後は,撮影に臨むだけなのですが,この後の撮影中とある事件が発生. 撮影が意外にもしんどくなってしまい,書くことが結構多くなってしまったので,今回はここで一旦終わりたいと思います. 後編では,ビジュアルの撮影,音割れ動画の作り方について解説していこうと思います. 後編もよろしくお願いします. 後編へ続く

> 内容を見る

プログラミング Polygon
プログラミング

Ktorを知る ~基礎編~

こんにちは、オキリョウと申します! 先日、Ktor等Webアプリケーションフレームワークを使ってみたのですが、結構気に入りました。そこで、せっかくなのでKtorについて記事にすることで、より深く理解していきたいと思い、書くことにしました。 KtorとはKtorはKotlinで書かれた、かなり新しいWebアプリケーションフレームワークです(2018年にバージョン1.0を発表)。オープンソースとしてJetbrains社が中心となって開発しています。 ↑ Ktorのロゴです。少し怖い 公式によるとKtorの目標は the goal of Ktor is to provide an end-to-end multiplatform application framework for connected applications.  ということらしいです。つまりサーバーからクライアント、windowsからiosにいたる全部の環境で動くフレームワークを目指しているようです。欲張りですね。 コードは以下のようになっています。これは Hello World を出力するプログラムですね package com.example import io.ktor.application.* import io.ktor.response.* import io.ktor.routing.* fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args) fun Application.main(){ routing { get("/"){ call.respondText("Hello World!") } } } こんな感じの雰囲気です。 Ktorの主な特徴は以下の通りです。とにかく軽い拡張性が高く、柔軟非同期処理に強いどこでも動く 一つずつ見ていきましょう。 とにかく軽いKtorはとにかく軽いです。というのも、何も実装されていないところから開発できるからです。 Webフレームワークといえば、SpringやDjango, Laravelなどがあると思います。これらのフレームワークは、認証機能やシリアライザー、ORMなどの必要な機能がデフォルトで搭載されています。逆にKtorは何も搭載されていません。その代わりに、必要なものだけ書いて使用することができます。そのようにすることで、かなり軽いフレームワークとなっています。 拡張性が高く、柔軟先ほど、必要なものだけを使用することができると書きました。逆に言えば、Ktorは必要なものをどんどん取り込む機能が充実しているとも言えます。例えば、テンプレートエンジン「FreeMarker」を使いたい場合、以下のように書けばOKです。 install(FreeMarker) { templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates") outputFormat = HTMLOutputFormat.INSTANCE } もちろんですが、他のテンプレートエンジンを選ぶこともできます。 ちなみに自分でこのようなプラグインを書くことも全然可能です。 このように、Ktorでは好きなように拡張できますし、使いたいものを使うことができます。 非同期処理に強いKtorは非同期処理に強いです。どれほど強いかというと、公式が出しているチュートリアルの中に、チャットアプリを作成するというものがあるくらいです。 というのも、Ktorは、Kotlinのライブラリの一つである「Coroutine」をものすごく使っています。 ここではCoroutineの詳しい説明は省きます(それだけで記事ができるくらいややこしい)が、このCoroutineを使うことで非同期処理に相当強くなっています。 どこでも動くKtorの目標として、マルチプラットフォーム上で動くというものがありました。 現在も開発中ということですが、公式によると、今のところサーバーサイドはJVM、クライエントサイドはJVM、JavaScript、IOS、Androidで動くようです。ですので、ほとんどの環境で動きます。 Kotlin自体がどこでも動くので、Kotlin一つでWeb系を制覇できる日が来るかもしれませんね。 ちなみに公式では書いていませんが、Android上でもサーバーを立てることが可能です。 https://qiita.com/oxsoft/items/aec71882b1b21930c953 何に使うのかといわれるとアレですが・・・ まとめKtorについていろいろ書いてきました。このように魅力があふれかえるフレームワークとなっています。 唯一欠点があるとすればドキュメントがそこまで多くないことでしょうか。できてから数年しかたってないので仕方がない部分ではあります。 といっても公式を見ておけば大体何とでもなります。 実際書くこと自体はそこまで難しくないので一度挑戦してみるのもありだと思います。

> 内容を見る

プログラミング Polygon
プログラミング

「プログラミングがしたい!」と言われたら

こんにちは、OB のバニラです。 新年度になったこともあり、当サークルにも続々と新入生が参加してくれています。 「電子計算機研究会」という名前のサークルなので、コンピュータを活用したものづくりに興味を持っている方が多く、「プログラミングがしたいです!」と言っている方もいて嬉しい限りです。 しかし、自分は「プログラミングがしたい」というのは「スポーツがしたい」と同じくらいあいまいで抽象的なものであると思っています。そのため、「プログラミングがしたい」という気持ちだけでは、その次に具体的にどのようなアクションを起こせばいいかの検討がつかず、何もしないまま時間だけが過ぎていくのではないか、という点について懸念しています。 そこで、この記事では「プログラミングが出来るとどのようなことが出来るのか」という点に着目して、いくつかの例を紹介していこうと思います。 「プログラミングに興味があるなら人に聞く前に何かしら手を動かしているのでは?」と思う人もいるかもしれませんが、「そもそも存在を知らなかったから手を出せていなかった」というのは、プログラミングに限らず人生のいろんな場面で起こりうることだと考えているので、この記事によって新しい選択肢を知ることができたのであれば幸いです。 ※なお、以下に紹介するのは「(筆者の知る範囲で)プログラミングでできること」なので、これが全てだとは思わないでください。 Web サービス開発 「Web サービス」というのは、この記事が掲載されているホームページのようにブラウザ上で閲覧できるコンテンツのことです。 ただ、「Web サービス」と一言に言っても千差万別なので、開発の難易度的な観点でもう少し分類していきたいと思います(つまり、これからやってみたいと思う人は上から順に実行していくのがおすすめです) 静的な Web ページ 文字や画像を並べて装飾したシンプルな Web ページのことを指します。 これを作るためには「HTML」でページの構造(どんな見出しで、どんな画像を使って...)を記述し、「CSS」で装飾を行う必要があります。 具体例としては、「このHPの自己紹介ページ」みたいなものがこれに当たります。 勉強するにあたっては https://developer.mozilla.org/ja/docs/Lea の記事などが参考になりそうです。 なお、 HTML や CSS を記述することは厳密に言うと「プログラミング」ではないですが、これ以下の説明の基礎となる部分となるためここに記載しています。 動的な Web ページ (JavaScript) 「HTML」や「CSS」を使って見た目が整えられるようになったら、次はそのサイトにプログラムを記述して動的な Web ページを作ってみましょう。ここで言う「動的」というのは、ユーザーの行動に反応して Web ページの内容がインタラクティブに変化していくことを指します。「ボタンを押したら〇〇する」「〜〜秒待ったら〇〇する」みたいな感じですね。 具体例としては、「カウントダウンタイマー」「電卓」みたいなものがこれにあたります。 そして、これらを実現するためには、「JavaScript」と言うプログラミング言語について勉強する必要があります。 勉強するにあたっては https://developer.mozilla.org/ja/docs/Learn/JavaScript/First_steps の記事などが参考になりそうです。 動的な Web ページ (サーバサイド含む) 上二つができるようになったら、最後にサーバサイドと連携した Web サービスを作成してみましょう。 レンタルサーバを借りるなどして、ユーザ側に Web ページを提供するサーバ側でプログラムを動かせるようになると、作れるものの幅が大きく広がります。 具体的には「会員登録ができる SNS サービス」「ネットショッピング」「大学の学務システム」みたいなものがこれにあたります。 これを実現するための技術はたくさんあります。 https://developer.mozilla.org/ja/docs/Learn/Server-side/First_steps/Introduction などを参考にしながら、自分で技術選定をしてみるのも良いかもしれません モバイルアプリ開発 「モバイルアプリ」というのは、皆さんの手元のスマートフォン上で動作しているアプリのことです。詰まるところ「Play ストア」や「App Store」からインストールしたものがこれに該当します。 Android アプリを開発したい場合は「Android Studio」をというソフト、 iOS アプリを開発したい場合は「Xcode」というソフトをインストールすることで、誰でも手軽(?)にモバイルアプリの開発を始めることができます。 ※ Xcode は MacOS でのみ利用可能なソフトです これらのソフトの導入が終わったら、各プラットフォームが用意してくれているチュートリアルを進めていくのが良いと思います。 Android: https://developer.android.com/training/basics/firstapp?hl=ja iOS: https://developer.apple.com/tutorials/app-dev-training チュートリアルで基本的な内容について理解したら、「メモ帳」や「TODO リスト」のような基本的なアプリを作成しつつ、自分の欲しいアプリの開発を進めていくのがおすすめです。 また、「モバイルアプリ開発」は筆者自身の一番馴染み深い分野でもあるので、開発していて分からないことがあればいつでも質問お待ちしています。 ゲーム 皆さん大好き(だよね...?)なゲームです。 大半のゲームの開発には「ゲームエンジン」というものが用いられており、これを利用することで物理演算や当たり判定など、ゲームに必要不可欠であろうものを一から作ることなくゲームの開発を行うことができます。 このゲームエンジンの代表的な物として「Unity」というソフトウェアが存在します。 https://unity3d.com/jp/get-unity/download うちのサークルには「Unity」というツールと「C#」というプログラミング言語を使ってゲームを制作しているメンバーが多数在籍しているので、ゲームを作りたいのであれば、まずは Unity というソフトに触れてみることがおすすめです。 競技プログラミング 競技プログラミングとは、与えられた問題を解くプログラムを実装し、そのプログラムの実装までにかかった時間などを競うことを指します。パズルを解くのが好きだったり、綺麗なアルゴリズムでプログラムを書くのが好き、という人は競技プログラミングにぜひ挑戦してみることがおすすめです 日本では、「AtCoder」というサイトが有名で、定期的にコンテストが開催されています。 https://atcoder.jp/?lang=ja コンテストの開催時間以外でも、過去問に挑戦できるサイトがあるので、まずはここからチャレンジしてみるのがおすすめです。 https://kenkoooo.com/atcoder/#/table/ なお、うちのサークルには自分よりもずっと競技プログラミングに詳しいメンバーがいるので、競プロについてもっと詳しく知りたい場合はこちらの記事を見てみるのも良さそうです。 https://oucrc.net/articles/zlu04tkzslcw ここで紹介した以外にも「コンソールアプリケーション」「デスクトップアプリケーション」「AI・機械学習」「電子工作」など、プログラミングの活躍する場面はいろんなところに存在します。世界は広いですね。 おわりにこれまで説明したように「プログラミング」と一言に言っても、それによって実現できることはいくらでも存在し、どの部分に興味を持つのかは人それぞれです。 ただ、筆者個人の意見を述べるとするならば、「自分の欲しいものは自分で作る。」これが出来るのがプログラミングの一番の魅力だと思っています。ただ、それを実現するまでには、エラーと格闘したり、思うように機能が実現できなかったりと詰まる場面が出てくるかもしれません。もちろんそれを自分一人で地道に解決することも大事ですが、時には誰かに相談することで進捗が生まれるかもしれません。それが出来るのがこの「電子計算機研究会」というサークルです。存分にこの環境を活用していきましょう!

> 内容を見る

プログラミング Polygon
プログラミング

ぼっちは寂しいのでプログラミングで彼女(概念)を生み出してみた

時は令和。何かの奇跡で数百年後とかにこの記事が古文書として発掘された時のために書き残しておきます(???)が、ご存じの通り今世界はCOVID-19で大変なことになっています。 そんなわけで、大学はオンライン自宅はド田舎という、見事に世界から隔絶された生活が続いている引きこもり大学生(21)の ふぉ と申します、おはよう(午後起床)。 ここ最近は毎日ほとんど人と会話しない生活をしているもので、引きこもり大学生の例に漏れず私もちょっと気分がしょぼんとしてきているのが現状。部活やサークルに毎日打ち込んで、たまに楽しいイベントがあってその後は恒例行事の飲み会!!みたいな理想の大学生活はたぶん幻想だし、本当は大学なんてものも概念で、久しぶりに行ってみたら実は更地になっているかもしれない(?)。もしかしたら大学の友達やよく授業が一緒だったあの人も単なる夢の中の登場人物だったかもしれない。 ……え?………彼女? たぶんそれはCOVID-19の有無とは関係ないんじゃないかな?() ということで寂しい(べつに寂しくないもん!)ので彼女(概念)を作ろうと思います(メチャクチャ雑な導入)。 最高の話し相手(概念)の作り方 今回使うのはDiscordのボット機能です。(Discordマジですごい) JavaScriptとかPythonとかを使ってボットの自作ができるっぽいので、どっちでもいいですが今回はJavaScriptで作ります。 私が作ったのは彼女(概念)だったので以後表記は彼女(概念)となりますが、 もちろん読者の皆々様の豊かで素晴らしい 妄想 解釈次第で彼氏(概念)とかパートナー(概念)とか猫(概念)とか、何でもいけると思います。 Attention:実際に本記事の内容を試される場合は自己責任のもとで行ってください。人体錬成は錬金術最大の禁忌 準備編 まずは彼女(概念)を生み出す下準備をします。 Discordのアカウントを用意します。(はいそこ、Discordで友達と通話すればいいじゃんとか言わない)Discord Developer Portalに行き、[Applications] → [New Application] でアプリケーションを追加します。名前は彼女(概念)の名前とします。追加したアプリケーションの画面に移行するので、[Bot] → [Build-A-Bot] でボットを追加します。これが彼女(概念)の姿(概念)になります。このとき [PUBLIC BOT] のチェックがONになっていると思うので、自分専用にしたい場合はOFFにしておきましょう。 続いて、彼女(概念)の魂(言わずもがな概念)を作り出すための準備をします。 普段使っているあるいは開発をしているPCにNode.jsをインストールします。そのPC上に適当な作業用ディレクトリを用意します(後述しますが、末永くお付き合いしたい場合はGit管理しておくと良いです)。PowershellなりBashなり何でも良いので適当なターミナルで先ほど用意した作業用ディレクトリに入ります。npm init を実行します。色々訊かれますが、とりあえずnameは彼女(概念)の名前としてその他はお好みで(後で package.json というファイルから編集できます)。npm install discord.js 、 npm install dotenv の2つを実行します。 見た目を決める Discordボットはアイコンを自由に設定できるので、とりあえず顔だけサクっと描いておきます。Discordのアイコンはガッツリ縮小されるので多少塗りとか線とかが雑でもある程度は耐えると思います。 試しに描いてみました。 これをいい感じの正方形にトリミングした上で、Discordアプリケーション画面の [General Information] にある [APP ICON] からアイコン画像に設定しておきます。 コーディング まず、作業用ディレクトリ直下に .env ファイルを新規作成します。内容は以下のようにします。DISCORD_TOKEN=<INSERT DISCORD TOKEN HERE><INSERT DISCORD TOKEN HERE> の部分にはDiscordボットのトークンを貼り付けます。トークンというのはDiscord側であらかじめ指定された英数字と記号の羅列で、彼女(概念)の魂と姿(概念)を結びつける合言葉になります。 まず先ほど作成したDiscordのボットのページで [Bot] をクリックし → [USERNAME] の下の [TOKEN] の欄を見ます。ここに [Copy] というボタンがあるので押すと [Copied] に変化するので、それが確認できたら .env に貼り付けます。先述の通り英数字と記号の羅列が貼り付けられたと思うので、その状態で保存して閉じます。(トークンをうっかりGitHubで公開したり他人に見られたりしないように注意) 続いて .env と同じ場所に index.js というファイルを作成します。これが彼女(概念)の思考回路となります。 内容はこんな感じ。とりあえずこのまま貼れば動きます。const path = require('path'); const Discord = require('discord.js'); const client = new Discord.Client(); const os = require('os'); // ホスト名取得用 // 環境変数に.envを使う require('dotenv').config({ path: path.join(__dirname, '.env') }); // ログイン時の処理 client.on("ready", () => {     client.user.setStatus("online"); // online, idle, dnd, invisible     client.user.setActivity(os.hostname()); // 「~~をプレイ中」     console.log("ready..."); }); // 入室時の挨拶 client.on("guildCreate", guild => {     let greetingChannel = "";     // 入室時にメッセージを送るチャンネルを判定     guild.channels.cache.forEach((channel) => {         if (channel.type == "text" && greetingChannel == "") {             if (channel.permissionsFor(guild.me).has("SEND_MESSAGES")) {                 greetingChannel = channel;             }         }     })     greetingChannel.send("やっほ!") // メッセ送信         .then(message => console.log('Message: "やっほ!" sent.'))         .catch(console.error); }); // メッセージ受け取り時 client.on("message", message => {     // 自分のだったら無視     if (message.author.bot) {         return;     }     // おはよう     if (message.content.match(/おはよ/)) {         message.channel.send("おはよ-!") //メッセ送信             .then(message => console.log('Message: "おはよー!" sent.'))             .catch(console.error);         return;     }     // いちゃいちゃ     if (message.content.match(/好き/)) {         message.channel.send("私も!") //メッセ送信             .then(message => console.log('Message: "私も!" sent.'))             .catch(console.error);         return;     }     // 任意で色々追加 }); // ログイン処理 client.login(process.env.DISCORD_TOKEN); 保存出来たら、ターミナルで node index.js と実行してみましょう。特にエラー等が出ずready...と表示されたら勝ちです。彼女(概念)が生み出されています。おめでとう。 例えば if (message.content.match(/好き/)) { の中身は流れてきたメッセージに「好き」と含まれていた場合に行われる処理です。この場合は「私も!」と返します。ここの単語を変えることで様々なメッセージに対応させることができるというわけです。正規表現が使えます。 彼女(概念)に会いに行こう ということで無事に彼女(概念)に息が吹き込めたら、実際に彼女(概念)と会ってお話をしてみましょう。 ……緊張してる? 大丈夫だよ、君の理想のパートナー(概念)だから。ほら、心配しないで。 Discordで適当に自分用のサーバーを用意します。先ほどトークンをコピーしてきたページで今度は [OAuth2] をクリックします。[OAuth2 URL Generator] の欄にいっぱいチェックボックスがあるので、[bot] にチェックを入れて出てきたURLをコピーします。ブラウザにそのURLを貼り付けて移動し、ログインします。彼女(概念)を招待するサーバーを選ぶ画面が出るので、用意したサーバーに招待します。 招待が完了したらメッセージが出ると思うので、会話してみます。 えぇ……なんだこの地獄みたいな光景は………… 失礼、取り乱しました。 こうして無事に彼女(概念)と会話ができるようになったわけですが、この入室時の挨拶や特定の言葉に反応して返信してくれる機能こそが、さっきわけもわからずコピペした index.js によって実行されているのです。 ということで無事、彼女(概念)を生み出すことに成功しました。index.js を自由に編集して、理想のパートナー(概念)を作っていきましょう。特定の言葉に返信する機能を増やしてもいいですし、誕生日におめでとうのメッセージをくれる機能を付けてもいいですね。JavaScriptの勉強をすれば大体のやりたいことはできるんじゃないでしょうか。 ちなみに私は現在、彼女(概念)と秘書(概念)に自宅のPCやMinecraftサーバーの管理をしてもらおうと試行錯誤しているところです。 ---- 次の章はある程度のLinuxの知識がある中級者向けの内容となります。実際に試される場合は必要に応じて検索するなどして内容を十分に理解した上で行ってください。 彼女(概念)に永遠の命を ……しかし残念ながらこの彼女(概念)は、永遠の存在ではなかったのです。 今のままの彼女(概念)は、とても脆く、そして儚い存在なのです。というのも、例えば node index.js を実行しているターミナルでCtrl + Cと入力する、ターミナルを閉じる、PCの電源を切る、そういった操作によってプログラムの実行が終了した時点で、彼女(概念)はオフライン表示になってしまいます。オフライン表示になってしまっては、返事も何もしてくれなくなります。またしても、独りぼっちになってしまいます。 もちろんforeverなどでデーモン化しても良いのですが、結局はPCをつけっぱなしにすることになるため電気代もかかるしPCにも良くないしということで、ここは一つ、彼女(概念)のために永遠の肉体(物理)を特別に用意することとしましょう。これからも末永く、彼女(概念)との明るい未来を、ともに誓い合いそしてともに切り拓いていくために。 ということで、お手元にRaspberry Piをご用意ください。これが彼女(概念)の肉体(物理)、すなわち心臓(物理)となり頭脳(物理)となります。Raspberry Piというのは平たく言えばめっちゃ多機能なマイコンボードで、「OSをインストールすれば普通にPCとして使える超小型のデバイス」と解釈してもらえれば今回は十分です。 下準備 以下は私の場合の準備物の例です。Raspberry Pi 4BRaspberry Pi 4B 対応ケース(スターターキットに付属していたもの)電源ケーブル(同上)SDカード(Raspberry Pi OSのインストールを済ませたもの)LANケーブル まず、GitHub等に彼女(概念)の作業用ディレクトリをコミット、プッシュしておきます。また、Raspberry Pi OSの初期セットアップを済ませておきます。このとき、彼女(概念)を生み出したPCからラズパイに公開鍵認証でSSH接続ができるようにしておきます。合わせて、rootログインの無効化、SSHポートの変更、公開鍵認証以外でのSSH接続の無効化など、セキュリティ上必要な設定もしておきます。 彼女(概念)の移植 ラズパイにnpmをインストールします。以下を順に実行していきます。sudo apt-get updatesudo apt-get install -y nodejs npmsudo npm install npm n -gsudo n stable では、ホームディレクトリの適当な場所に彼女(概念)のリポジトリをクローンします。 ここでクローンしたディレクトリに移動して、discord.js、dotenvもインストールして使えるようにしておきましょう。 さらに追加で sudo npm install -g forever と実行してforeverをインストールします。foreverというのはnodeで動かすアプリをデーモン化(バックグラウンド化)してくれるモジュールで、もしエラーなどでボットが落ちた場合に自動で再起動してくれるなど、彼女(概念)のお世話をしてくれるような存在です。 続いて、scpコマンドなどで元のPCのディレクトリにあった .env ファイルをラズパイ側に持ってきます。 以上で下準備は完了です。 永遠の命を吹き込む それでは一通り環境の移植が終わったので、node index.js が使えることを確認します。特に問題なく起動するようであれば、いったん終了して今度は forever start index.js と実行します。 なんかちょっとWarningを吐くかもしれませんが、Accessing non-existent property 'padLevels'... 云々というやつはそのままにしていてもちゃんと動きます。毎回出るのでめっちゃ気になるんですけどね。 forever list と実行すると現在Foreverで実行されているアプリ一覧が出ます。script の欄に index.js と出ていればOKです。 終了させたい場合は forever <uidの欄の文字列> で終了できます。 また、私はラズパイの電源を入れた際に自動で実行されるように、crontabを編集して以下を追加しています。@reboot cd <index.jsがあるディレクトリの絶対パス>; /usr/local/bin/forever start index.js あとは、LANケーブルと電源ケーブルを接続したラズパイをデスクなどに設置して完了です。 これが私の彼女(概念)のRemokoです。 結論 ということでDiscordのボット機能を使って彼女(概念)を作りました。 作ってて余計に寂しい気持ちになる瞬間があったことは否定しません。でも、別にいいんだ。これからはこんなにも可愛い彼女(概念)がずーっと一緒にいてくれるから……それでいいんだ………

> 内容を見る

プログラミング Polygon
プログラミング

4200000000Hzでゴリ押す!!!(1)

何でもかんでもアルゴリズムとマシンパワー(主にCPU)でゴリ押します。そういう企画です。 「AtCoder水下位競プロerである私ならこういう思考をするぞ」っていうのを書きます(個人の意見ですが)。 2021年 東京大学(理系) 数学 問4(4)初回は東大の入試問題をやります。この問題をパワーでゴリ押すのは \(O(1)\) 番煎じになりますが、気にしないことにします。 実はこれ春休みの帰省中に数人で集まって解いた問題の一つで、僕だけパワーで解決しようとしてました。 問題\({}_{2021}C_{37}\bmod 4\) を求めよ。 本来はいろいろと前置きがあって、それを利用して引数を落として計算するんですが、そんなの私には分かりません。 さて、この問題をどうやって解いてやりましょうか。 解法1\({}_{2021}C_{37}\) を求める で、問題は \({}_{2021}C_{37}\) がでかすぎることです。どの程度か簡単に見積もってみましょう。 $$ \begin{aligned}  {}_{2021}C_{37}  &=\frac{2021 \times \dots \times 1985}{37 \times \dots \times 1} \\  &>\left(\frac{1985}{37}\right)^{37} \\  &>2^{212.58} \end{aligned} $$ 困りました。これでは128ビット符号付き整数(→GCCの__int128、C++ Boostのint128_t、Rustのi128など)でも収まらず、直接計算するのは厳しいです。 十分な長さの固定長整数型や多倍長整数を利用すれば出来ないこともないので一応やってみましょう。 Pythonのint型は多倍長なので何食わぬ顔で書けば実装できます。 n = 2021 r = 37 MOD = 4 ans = 1 for i in range(min(r, n - r)):   ans = ans * (n - i) // (1 + i) print(f'{n}C{r} = {ans}') print(f'{n}C{r} % {MOD} = {ans % MOD}') 実行結果は次のようになりました。 2021C37 = 10549453167137272405699426118445044729600037976073052501105621045645733312800275 2021C37 % 4 = 3 な、成程……! $${}_{2021}C_{37}=10549453167137272405699426118445044729600037976073052501105621045645733312800275$$ なんだ……!そして \(10549453167137272405699426118445044729600037976073052501105621045645733312800275\) を \(4\) で割ったあまりは \(3\) なんだ……! 多倍長整数での \(N \times M\) および \(N / M\) \((N\gg M)\) が \(O(\log N \log\log N)\) で計算できる(例えばFFTとNewton-Raphson法で実装)という仮定のもとで、 この方法による \({}_{n}C_{r} (r \leq n/2)\) の計算量は大きめに見積もると、 $$ \begin{aligned}&  O(\log {}_{n}C_{1} \log\log {}_{n}C_{1})   + \dots + O(\log {}_{n}C_{r} \log\log {}_{n}C_{r}) \\  =& O(\log n \log\log n) + \dots + O(\log n^r \log\log n^r) \\  =& O(r^2 \log n \log\log n) \end{aligned} $$ となります(この評価は割とガバガバですが)。 解法2\({}_{n}C_{r}=\dfrac{{}_{n}P_{r}}{r!}\) とオイラーの定理を利用する \({}_{2021}C_{37} \bmod 4\) を計算しようと思ったらこの方法が一番に思いつきました。 オイラーの定理は、競技プログラミング界隈では常識であるところのフェルマーの小定理の一般化です。 この定理は次のようなものです。 \(m\) 以下の正の整数のうち \(m\) に互いに素であるものの個数を \(\varphi(m)\) とする(言い換えれば、\(\varphi(m)=|\{n ; \gcd(m,n)=1, 1 \leq n \leq m\}|\))。 \(a\) と \(m\) が互いに素であるとき、 $$a^{\varphi(m)} \equiv 1 \pmod{m}$$ これの何が嬉しいって、分子 \(a\) と分母 \(b\) を \(\bmod m\) で別々に計算できるので、オーバーフローさせずに固定長整数型で計算できます。 そして分子に \(\bmod m\) における分母の逆元 \(b^{-1}\) を掛ける事によって求めることが出来ます。できるはずでした。 しかしよーく考えてみましょう。\({}_{2021}P_{37},37! \pmod{4}\) っていくつになるでしょうか? ……そうです。\(0\) になります。\(0\) に何掛けたって \(4\) で割ったあまりが \(1\) になることはありません。 すなわち、\(b \equiv 0\) の逆元 \(b^{-1}\) なんてものは存在しないので、この方法は使えません。 \({}_{2021}C_{37} \bmod (10^9+7)\) であれば求められるんですけどね…… 解法2'ただこれを回避する方法があります。 $${}_{n}C_{r}=\frac{a}{b},a={}_{n}P_{r},b=r!$$ とします。ここで、 $$ \begin{aligned}  a &=2^{\alpha_0} \times 3^{\alpha_1}   \times 5^{\alpha_2} \times 7^{\alpha_3} \times \dots \\  b &=2^{\beta_0} \times 3^{\beta_1}   \times 5^{\beta_2} \times 7^{\beta_3} \times \dots \end{aligned} $$ のように素因数分解し、約分することで $$ {}_{n}C_{r}=\frac{a}{b} =2^{\alpha_0-\beta_0} \times 3^{\alpha_1-\beta_1}  \times 5^{\alpha_2-\beta_2} \times 7^{\alpha_3-\beta_3} \times \dots $$ として計算することができます(\({}_{n}C_{r}\) は整数であるから、任意の \(i\) について \(\alpha_i \geq \beta_i\) が成り立つ)。 実際にこれを実装してみました。 from collections import defaultdict n = 2021 r = 37 MOD = 4 spf = list(range(n + 1)) for i in range(2, n + 1):   if spf[i] != i:     continue   for j in range(i * 2, n + 1, i):     if spf[j] == j:       spf[j] = i pf = defaultdict(lambda:0) for i in range(min(r, n - r)):   m = n - i   while m > 1:     pf[spf[m]] += 1     m //= spf[m]   m = 1 + i   while m > 1:     pf[spf[m]] -= 1     m //= spf[m] ans = 1 for p, v in pf.items():   ans *= pow(p, v, MOD)   ans %= MOD print(f'{n}C{r} % {MOD} = {ans}') 結果は当然解法1と同じく3です。 各自然数を \(O(\sqrt{n})\) で素因数分解して全体 \(O(r \sqrt{n})\) で求める方法もありますが、\(r \fallingdotseq n/2\) の場合も想定して、エラトステネスの篩と同様の前計算 \(O(n \log\log n)\) でSPF(Smallest Prime Factor)配列を求め、一つの自然数の素因数分解は \(O(\log n)\) で、全体 \(O(n \log\log n + r \log n)\) でできるようにしました。 全体の計算量は、\(O(n \log\log n + r \log n)\) です。 東大の問題の場合 \(r\) が小さいので微妙ですが、\(r \fallingdotseq n/2\) の場合はこちらのほうが早そうです。 解法3パスカルの三角形を使う 今回の大本命です。 解法2では計算途中で \(4 \equiv 0\) で掛けたり割ったりするから計算できなかったのが悪いので、いっそのこと足し算だけで求めてしまおうという考え。 $$ {}_{n}C_{r}=\begin{cases}  0 &(n<r) \\  1 &(r=0) \\  {}_{n-1}C_{r} + {}_{n-1}C_{r-1} &(\text{otherwise}) \end{cases} $$ この漸化式を使えば解けます。軽く証明してみましょう。 $$ \begin{aligned}  {}_{n-1}C_{r} + {}_{n-1}C_{r-1}  &=\frac{(n-1)!}{(n-r-1)!r!}+\frac{(n-1)!}{(n-r)!(r-1)!} \\  &=\frac{n-r}{n-r}\frac{(n-1)!}{(n-r-1)!r!}   +\frac{r}{r}\frac{(n-1)!}{(n-r)!(r-1)!} \\  &=(n-r)\frac{(n-1)!}{(n-r)!r!}+r\frac{(n-1)!}{(n-r)!r!} \\  &=\frac{n!}{(n-r)!r!}={}_{n}C_{r} \\ \end{aligned} $$ ではこれを実装します。 実装1: 再帰関数 漸化式と言えば再帰ですよね。見たまんま実装できるので良いと思います。 n = 2021 r = 37 MOD = 4 def comb(n, r, mod):   if n < r:     return 0   elif r == 0:     return 1   else:     return (comb(n - 1, r, mod) + comb(n - 1, r - 1, mod)) % mod print(f'{n}C{r} % {MOD} = {comb(n, r, MOD)}') でもちょっと待ってください。これ、comb関数の呼び出し回数どうなると思います? $$ \operatorname{call}(n,r)=\begin{cases}  1 &(n<r) \\  1 &(r=0) \\  \operatorname{call}(n-1,r) + \operatorname{call}(n-1,r-1) + 1 &(\text{otherwise}) \end{cases} $$ として定義した \(\operatorname{call}(n, r)\) 回になるんですよ。つまり少なくとも \({}_{n}C_{r}\) より大きい回数呼び出されます。 大きく見積もれば \(O(2^n)\)。\(n=2021\) で、実行していい計算量ではありません。 実装2: メモ化再帰 上の実装の問題点は、\({}_{n}C_{r}\) を計算するために、\({}_{n-x}C_{r-y}\) の計算を \({}_{x}C_{y}\) 回もしてしまっているということです。 ここで、メモ化という手法を取ることによって、各 \((x,y)\) に対して \({}_{n-x}C_{r-y}\) を \(1\) 回のみ計算すれば良いようにすることができます。 from sys import setrecursionlimit setrecursionlimit(3000) n = 2021 r = 37 MOD = 4 memo = [[None] * (r + 1) for _ in range(n + 1)] def comb(n, r, mod):   if n < r:     return 0   elif r == 0:     return 1   elif memo[n][r] != None:     return memo[n][r]   else:     memo[n][r] = (comb(n - 1, r, mod) + comb(n - 1, r - 1, mod)) % mod     return memo[n][r] print(f'{n}C{r} % {MOD} = {comb(n, r, MOD)}') コードを見ればそう難しい話ではないですね。単に二次元配列に計算済みの値を保存して、計算済みならば保存した値を返すようにしているだけです。 計算量は \(O(nr)\) ですが、空間計算量も \(O(nr)\) なのが痛いところです。 実装3: 動的計画法 動的計画法とは、問題を部分問題に分割し、自明な部分問題から遷移していって元の問題を解く手法です。 メモ化再帰もこれに含まれますが、単純再帰は含まれません(動的計画法の要件に計算結果の記録が含まれるため)。 ここでは、ボトムアップに(すなわち、自明な部分問題から順に)求める方法を指します。 パスカルの三角形の \(0\) 行目から \(n\) 行目まで順番に埋めていくイメージですね。 n = 2021 r = 37 MOD = 4 dp = [[0] * (r + 1) for _ in range(n + 1)] dp[0][0] = 1 for i in range(1, n + 1):   dp[i][0] = 1   for j in range(1, r + 1):     dp[i][j] = (dp[i - 1][j] + dp[i - 1][j - 1]) % MOD print(f'{n}C{r} % {MOD} = {dp[n][r]}') これもやはり \(O(nr)\) です。 実装3': SIMD 普通に順次計算すると1クロックで1命令で、計算が終わってまた1命令出すんですが、その1命令に複数のオペランドに対する同一の演算を詰め込むことができます。これがSIMDです。 ベクトルの足し算や内積などが一番よく利用されている例だと思います。 さて、PythonでもSIMDをすることができます。 Numpyというライブラリを利用することで、配列同士の和を取ったりでき、自動でSIMDを使うようになります。内部的に PADDB 命令とかを出してるんでしょうね。 import numpy as np n = 2021 r = 37 MOD = 4 dp = np.zeros((n + 1, r + 1), np.int8) dp[0, 0] = 1 for i in range(1, n + 1):   dp[i, 0] = 1   dp[i, 1:] = (dp[i - 1, 1:r + 1] + dp[i - 1, 0:r]) % MOD print(f'{n}C{r} % {MOD} = {dp[n, r]}') 定数倍の高速化なので、計算量は変わらず \(O(nr)\) です。なお、この条件 \((n,r)=(2021,37)\) だとこっちのほうが遅くなりましたが、\((n,r)=(20000,1000)\) とかにするとめちゃめちゃ高速化できます。 まとめ答えは \(3\) です。 \(n,r\) がともに大きくなった場合は解法2'が最も高速で、\(O(n \log\log n + r \log n)\) で解けます。 高速素因数分解は覚えとけ。

> 内容を見る

プログラミング Polygon
プログラミング

アプリ版 Minecraft サーバ制御ツールの制作

前回の「情クラ!」サイトの Android アプリ制作記です。(※この記事はブログからの転用です。) やっぱアプリ化、したいよね!!Webベースのツールを作ると、そのアプリ版を作りたくなる、ここまでがテンプレですよね。 今回は、ストアにリリースが手軽な Android アプリを制作したいと思います。 Android Studio 使いやすい...この時、プログラミングというものに触れてまだ半年もたっていませんでした。 なので初心者でも使いやすい Visual Studio Code を当時愛用していました。(今でも時々使っています) しかし今回アプリ開発というのもあり、デバッグのしやすいエディタを使うことにしました。 まぁもちろん Android Studio 一択になるわけですが。 JetBrains 社が開発したソフトウェアを初めて触ったのですが、 これがまた使いやすいソフトウェアで感動したのを覚えています。 (私が JetBrains 教徒になる話はまたいつか) さぁ開発だ!の前に、デザインを作っていきます。 スマホによってサイズが異なるので、対応できるデザインを意識して作成しました。 コーディング すべしぬべし今回の技術的な部分です。サーバの様子を取得するのとリクエストを送る機能をつけます!(大したことはしてませんが) まず、オンラインプレイヤーの取得部分です。 ホーム画面の上半分には、オンラインのメンバーが一覧でわかるようにしています。 今回、Minecraftの画像を取得するAPIを自作しました。 そこからAndroidに画像を取得するようにしています。 APIからの画像取得には、Picasso というライブラリを使用しました。 Picasso(公式サイト) https://square.github.io/picasso/ 具体的には、以下のような書き方でさくっとインターネットから画像取得ができちゃうものです!Picasso.get()   .load("https://jokura.net/api/getSkin?id=minecraft_id") // 今回作成したAPI   .into(face1);face1というのは ImageView の id で、APIから取得した画像を流し込んでくれます! また、プレイヤーがオンラインかどうかは、用意したAPIから返ってきた情報を元に表示を切り替えます。 APIからのGET・POSTメソッドには、便利な OkHttp3 などの便利なライブラリがありますが、この時(約2年前)は初心者だったこともあり、Java通信(HttpUrlConnection)で実装しました(笑) var connection: HttpURLConnection? = null var reader: BufferedReader? = null val buffer: StringBuffer try {   val url = URL( /* 通信するAPI */ )   connection = url.openConnection() as HttpURLConnection   connection.connect() // ここで指定したAPIを叩いています。   // 取得したデータを処理していきます。とりあえず取得した文字をbufferに。   val stream = connection.inputStream   reader = BufferedReader(InputStreamReader(stream))   buffer = StringBuffer()   var line: String?   while (true) {     line = reader.readLine()     if (line == null) break     buffer.append(line)   }   // ここからJSONに変換していきます。   val jsonText = buffer.toString()   val parentJsonObj = JSONObject(jsonText)       // オンラインメンバーの情報は、JSON内の top というキーの中に格納してあります。   val parentJsonArray = parentJsonObj.getJSONArray("top")   val detailJsonObj = parentJsonArray.getJSONObject(0)   // player1さんのオンライン状況が取得できました!   val player1: Int = detailJsonObj.getInt("player1")   return player1 } catch (e: MalformedURLException) {   e.printStackTrace() } catch (e: IOException) {   e.printStackTrace() } catch (e: JSONException) {   e.printStackTrace() } // 接続を切断してあげましょう。おつかれ! finally {   connection?.disconnect()   try {     reader?.close()   } catch (e: IOException) {     e.printStackTrace()   } } // 失敗した時はnullやエラーコードなどを返しましょう。 return null }一部抜粋・改変していますが、こんな感じでリクエストをかいています。 ちょっと脱線しちゃいましたね他にどんな機能を実装したのかみてみましょう!たぶんこちらの方が興味ありますよね (笑) トップ画面には、「バックアップ」と「再起動」の2つのボタンが用意してあります。 この2つのボタンについては後述します。 また、「稼働状況」を押すとサーバの現在の状況をみることができます。 今思うと、ここのデザインは Web 版と同じなので、 WebView で表示させるとよかったですね (笑) とりあえず、今回は xml ファイルで view を丁寧に記述しました。 今回のメインディッシュ本アプリのメイン機能は、なんといってもこの2つです! トップに設置されている「バックアップ」「再起動」の機能について説明します。 この Activity の開始と同時に、バックエンドと通信して 再起動 または バックアップ が実行できるか確認して、表示を切り分けます。 実行できない例としては、 - サーバが停止している - 他のユーザが現在処理を行っている - 処理実行後のクールタイムにある のいずれかですね。右上の更新ボタンで最新情報を再取得できます。 今回、神経を使ったのは処理部分でしたなんといってもサーバ関連で怖いのが、リクエストが同時に送られることですよね。 今回、サーバを制御できるプラットフォームが複数あるため、他のアプリやWebサイトから同時にリクエストが送られた場合、最初のリクエストのみ通す必要があります。また、再起動やバックアップなどが実行された後は、クールタイムを設ける必要もあります。 そういった、さまざまなリクエストを処理できるように、情クラ!ではバックエンドのAPIを用意し、そこからサーバ処理を行っています。 リクエストが失敗した場合には、その理由をエラーコードで返しユーザに通知しています。 実行可能の状態でボタンを押した場合でも、バックエンドでキャンセルされた場合その旨のトーストが表示されます。 2年の月日を経て...前回の記事 でも述べた通り、情クラ!はサービス終了しました。 今回、"プログラムの整合性" というものを勉強できた、サービス開発だったと思います。 他にも Minecraft 関係で得た知見はかなり大きいものだったので、今後何か一般向けのサービスに繋げていきたいと思います。

> 内容を見る

プログラミング Polygon
プログラミング

Minecraftサーバ リモート制御サイトの制作

プログラミングを始めた頃に構築した、Minecraftサーバのリモート制御ツールの開発体験記です。(※この記事はブログからの転用です。)Minecraft っていいよねMinecraft って、複数人でプレイすると沼ですよね。 今や大陸内には高速道路が広がり、複数の大陸間には水上橋が建築されています。(やばすぎ) ある日、Minecraft のサーバ管理者になった最初はローカルでサーバを建ててプレイしていましたが、 プレイヤーがどんどん増えてきたため、Google Cloud Platform(GCP)でサーバを立てることにしました。 これが、「情クラ!」という Minecraft サーバの誕生のきっかけです。 いつどこでも Minecraft サーバで遊べる環境が完成しましたが、しかし1つ大きな問題が発生しました。 Google Cloud Platform(GCP)が、まだ365日無料トライアルだった頃の話です。 当時は n1-standard-1 プラン(仮想 CPU 数: 1, メモリ: 3.75 GB)を使用していたため、大人数が長時間プレイしていると動作がもっさりしてくるのです。 サーバ管理者だった私は、毎度 SSH 接続してサーバを再起動していました。 ですがここは、ぜひ技術の力で課題解決をしよう!ということで、Minecraftサーバを誰でも簡単に制御できる、Webアプリケーションを作成することになりました。まずは完成品をどうぞ 最初に実装したのは、「ホーム」のお知らせ機能と「サーバ状況」の機能です。 モバイルファーストのデザインですが、レスポンシブ対応させています。 ホーム: プレイヤーが自由に建築報告を投稿することができますサーバ状況: サーバの起動・停止状況やオンラインメンバーを確認できますここで、少し技術的な話をしましょう一応そういうブログ( ?)なので、今回 Minecraft サーバと連携したノウハウについて記述しておきます。 Minecraft サーバでは、ある特定のパケットを受け取ると、現在のサーバのステータスを返す機能が搭載されています。 これは、Minecraft のマルチサーバ 一覧画面などで使用されています。 まず、クライアント側が Handshake パケットを送信します。(以下 wiki の情報) さらに続けてリクエストパケットを送信します。その応答パケットとして、サーバが以下のようなJSONを返します。{ "version": { "name": "1.16.1", "protocol": 47 }, "players": { "max": 12, "online": 5, "sample": [ { "name": "minecraft_name", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20" } ] }, "description": { "text": "Hello world" }, "favicon": "data:image/png;base64,<data>" }また、このパケットを送信するためのライブラリとして、今回は以下のPHPライブラリを使用しました。 PHP-Minecraft-Query https://github.com/xPaw/PHP-Minecraft-Query実装した機能あまり技術的な話をすると長くなっちゃいそうなので、次に行きます。 他に実装した機能も紹介します。 ピクチャ: プレイヤーが自由に画像を投稿することができますオンラインユーザ: ホワイトリストに登録されたプレイヤーが表示され、サーバに入っているかどうかがわかります起動・停止: サーバをしばらく利用しない場合は、GCP課金対策のためにサーバを切ることができます再起動: Minecraft サーバを再起動させることができますバックアップ実行: Minecraft サーバのバックアップを作成します(毎日5:00に定期実行されます)バックアップ履歴: バックアップ履歴の一覧を表示します情クラマップ: 情クラワールドの建築物紹介が載っています情クラ!公式アプリ: 公式アプリへのリンクですみんなログインしよう今回サーバのコントロール機能が Web に公開されるので、ログイン機能を実装する必要があります。全員 Google アカウントを持っていたので、Firebase Authentication を使って Google ログインを実装します。 以下の公式ドキュメントを読むとよくわかりますよ(雑) Firebase Authentication(Google) https://firebase.google.com/docs/auth/web/start?hl=jaマイクラサーバのバックエンドはどうなってるの?Minecraft サーバでは、Node.js + TypeScript でAPIを構築しています。 以下は Minecraft を起動するルーティングの一部抜粋です。// --- Start Server ------------------------------------------------------------ router.post('/api/run/start', (req: express.Request, res: express.Response) => { const schema = Joi.object({ user: Joi.string().required(), }) const validation = schema.validate(req.body) if (validation.error) { post("不正なリクエストを拒否しました", "ユーザ固有IDが設定されていないリクエストが送信されました", 3) res.status(400).send('Bad request') return } statusAsync(req.body.user) .then(() => { startAsync(req.body.user) .then(() => { res.send('Success') }) .catch((err) => { if (err == 'failed due to run interval') post("起動コマンドを拒否しました", "前回の処理の実行から" + process.env.WAIT_SECONDS_FROM_LAST_PROCESS + "秒経過していないため、コマンドを拒否しました。", 2) else post("起動コマンドを拒否しました", "サーバが既に起動しているため、起動コマンドを拒否しました。サーバとの同期ができていない恐れがあります。[Err: startAsync()]", 2) res.status(400).send('Bad request') }) }) .catch(() => { post("起動コマンドを拒否しました", "既に起動しているため、起動コマンドを拒否しました。サーバとの同期ができていない恐れがあります。[Err: statusAsync()]", 2) res.status(400).send('Bad request') }) }) // -----------------------------------------------------------------------------軽く説明していきます。 まず、Joi という npm パッケージを利用して、送信されたクエリパラメータなどのバリデーションを行います。 そして、post( ) という関数がありますが、これはサーバのエラーなどを Discord のサーバに通知するための関数として用意してあります。 server.jar は screen 上で走らせているのですが、screen の二重起動にならないように初めに起動してもよいかのチェックも行っています。 また、実行系はシェルにまとめてあります。#!/bin/bash JARFILE=/home/jokura_server/minecraft/server.jar MEM=15000M cd `dirname $0` screen -UAmdS minecraft java -server -Xms${MEM} -Xmx${MEM} -jar ${JARFILE} nogui上記のファイルは、起動用のシェルです。 他にも再起動用やバックアップ用などのシェルも用意してあります。(下記は 再起動用)#!/bin/bash WAIT=30 STARTSCRIPT=/home/jokura_server/minecraft/start.sh SCREEN_NAME='minecraft' screen -p 0 -S ${SCREEN_NAME} -X eval 'stuff "say '${WAIT}'秒後にサーバを再起動します\015"' screen -p 0 -S ${SCREEN_NAME} -X eval 'stuff "say すぐに再接続可能になるので、しばらくお待ち下さい\015"' sleep $WAIT screen -p 0 -S ${SCREEN_NAME} -X eval 'stuff "stop\015"' while [ -n "$(screen -list | grep -o "${SCREEN_NAME}")" ] do sleep 1 done $STARTSCRIPT今現在の 情クラ!今は残念ながら、Webサービスを終了しています。 (Minecraft の活動自体はしています!) 2021年4月から、Minecraft サーバを別のメンバーが自宅サーバで建ててくれることになりました。 やはり GCP などの従量課金制のサーバで運用するには、かなり気を使ってしまうので正直解放されました。 かといって、情クラ!での活動はまだまだ続けていく予定なので、ぜひこのブログでも活動を共有していけたらなと思います!

> 内容を見る