絞り込み

最新の投稿

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

UITextViewでキーボードがカーソルを隠さないようにする明快な方法

内容霽月です。Swiftちょっと分かる人向けの記事です。 Appleのメモ帳アプリなどでは、改行しまくってキーボードに迫ったとしても、キーボードの下にカーソルが隠れることなく、自動的にスクロールされることで、カーソルが常に見える位置に存在するようになっていますよね。 その機能を再現する方法についてネットで少し調べてみましたが、あまり良い解決策は見つかりませんでした。ということで、この記事では、単純で分かりやすくその機能を実現する方法について紹介します。 環境macOS Ventura 13.2.1シミュレータ: iPhone 14 Pro(iOS16.2)Xcode14.2Swift5.7.2 UITextViewを配置まずはUITextViewを画面に配置します。import UIKit class ViewController: UIViewController {     private let textView = UITextView()          override func viewDidLoad() {         super.viewDidLoad()                  view.addSubview(textView)         textView.frame = view.bounds         textView.text = String(repeating: "これは UITextView を使ったテストです。\n", count: 100)     } }実行すると適当な文章が100行表示されます。 キーボードの高さを取得次に、キーボードが開かれた時に、キーボードの高さを取得するようにします。 保存先の変数を作って、    private var keyboardHeight: Double = 0.0以下をviewDidLoadの中に書いて、キーボードが表示された時にメソッドが呼び出されるように登録し、        NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillShow(_:)),             name: UIResponder.keyboardWillShowNotification,             object: nil)キーボードの高さを取得する処理を書きます。    @objc func keyboardWillShow(_ notification: Notification) {         guard let userInfo = notification.userInfo else { return }         guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }         keyboardHeight = keyboardFrame.height     }実行してエラーがないことを確認して、次の段階に進みます。 textViewDidChange関数textViewに変更が加えられた時に呼び出される関数を作ります。 textViewのdelegateをselfにしておいて、        textView.delegate = selftextViewDidChange関数の中に書いていきます。extension ViewController: UITextViewDelegate {     func textViewDidChange(_ textView: UITextView) {              } } 目的の機能を作成必要な情報は「現在のスクロール具合」「カーソルがある矩形領域」「キーボードが開いてても見える矩形領域」です。この3つの情報を取得します。        var contentOffset = textView.contentOffset         let cursorPosition = textView.selectedTextRange?.start ?? textView.beginningOfDocument         var cursorRect = textView.caretRect(for: cursorPosition)         let visibleRect = CGRect(x: 0, y: 0, width: textView.bounds.width, height: textView.bounds.height - keyboardHeight)         cursorRect.origin.y -= contentOffset.y順に「contentOffset」「cursorRect」「visibleRect」という名前で取得しました。 カーソルの下端がvisibleRectの下端より下に行かないようにします。        if cursorRect.maxY > visibleRect.maxY {             contentOffset.y += cursorRect.maxY - visibleRect.maxY         }最後にcontentOffsetの変更を保存して終わりです。        textView.setContentOffset(contentOffset, animated: true) 実行して確認問題なく実行できました。カーソルが見えない場所に行ってしまう現象は解決され、とても便利なUITextViewの完成です! と、思いきや? textVIewのコンテンツの下端に余白がないために、下の方を編集しようとすると物凄くやりにくいことになってしまいました。 キーボードが開いている時は下端にキーボードの高さ分の余白を開けるようにしてみましょう。 keyboardWillShowに以下を追加します。        textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)         textViewDidChange(textView)キーボードが閉じた時の通知も登録して、        NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillHide(_:)),             name: UIResponder.keyboardWillHideNotification,             object: nil)キーボードが閉じた時に余白を元に戻す処理を書きます。    @objc func keyboardWillHide(_ notification: Notification) {         textView.textContainerInset = UIEdgeInsets.zero     }これで完璧です。 最終的なコードの全文import UIKit class ViewController: UIViewController {     private let textView = UITextView()          private var keyboardHeight: Double = 0.0          override func viewDidLoad() {         super.viewDidLoad()                  view.addSubview(textView)         textView.frame = view.bounds         textView.delegate = self         textView.text = String(repeating: "これは UITextView を使ったテストです。\n", count: 100)                  NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillShow(_:)),             name: UIResponder.keyboardWillShowNotification,             object: nil)         NotificationCenter.default.addObserver(             self,             selector: #selector(keyboardWillHide(_:)),             name: UIResponder.keyboardWillHideNotification,             object: nil)     }          @objc func keyboardWillShow(_ notification: Notification) {         guard let userInfo = notification.userInfo else { return }         guard let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }         keyboardHeight = keyboardFrame.height                  textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight, right: 0)         textViewDidChange(textView)     }          @objc func keyboardWillHide(_ notification: Notification) {         textView.textContainerInset = UIEdgeInsets.zero     } } extension ViewController: UITextViewDelegate {     func textViewDidChange(_ textView: UITextView) {         var contentOffset = textView.contentOffset         let cursorPosition = textView.selectedTextRange?.start ?? textView.beginningOfDocument         var cursorRect = textView.caretRect(for: cursorPosition)         let visibleRect = CGRect(x: 0, y: 0, width: textView.bounds.width, height: textView.bounds.height - keyboardHeight)         cursorRect.origin.y -= contentOffset.y         if cursorRect.maxY > visibleRect.maxY {             contentOffset.y += cursorRect.maxY - visibleRect.maxY         }         textView.setContentOffset(contentOffset, animated: true)     } }

> 内容を見る

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

自作AIをAndroidにのっける

動機近年、人工知能を利用したサービスが増えている。 例えばChat GPT。言語処理をしてくれるAIだが非常に精度がいい。APIも公開されており、気軽にサービスに組み込むことができる。他にも様々なAIが公開されており、人工知能を用いたサービスというのは珍しく無くなってきた。 しかし、提供されたAPIを叩いただけでは人工知能を使った感が薄く感じる。 自分でAIを用意してサービスに組み込んでみたいと思う人は少なくないはず。 自分でサーバーを用意してAPIを公開してもいいが、サーバーの維持費、通信費用などがかかってしまう。端末上で動かすことができればこの問題も解決できる。 そう思い探してみたところ、「Tensorflow Lite」に行き着いた。そこで、実際にこのライブラリを用いてAndroidアプリを作ってみたい。 Tensorflow Liteとは機械学習を行うためのライブラリとして「Tensorflow」というものがある。比較的自由に機械学習を行えるため世界中で愛されているが、モバイル端末上で実行するには少し重たすぎる。 そこでTensorflow Liteである。このライブラリを使うことでモバイル端末上(iOS、Android、Raspberry pi等)で利用できるくらいに軽量化される。 同じようにモバイル端末上で人工知能を利用するためのライブラリとして「ML Kit」というものも存在するが、これだと自分で作成したモデルを利用できない。その点ではこちらの方が(手間はかかるが)自由度が高い。 使い方下の方で小難しく書いているが使い方はものすごく簡単である。 1. tfliteモデルを用意するtfliteとはTensorflow Lite専用の型。 自分で作ってもいいし、ネットから拾ってきてもいい。ここでみんな配布してるのですぐ手に入る。 2. Androidにぶち込むimport機能を使って指定の場所にtfliteモデルを入れる。 3. 使う入れればAndroid側が勝手にモデル用のクラスを作ってくれる。だからその関数を叩けばいい 例えば以下のコード。 mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。 このmnistModelというのが用意されたモデル用クラス(のインスタンス)。ここに生えているprocess関数に入力値を渡してやれば出力が得られる。 これだけで完成。 ものすごく簡単にできることがわかると思う。 アプリ作成実際にAndroidアプリを作ってみる。 作ったアプリは以下の通り。 数字を手書きしてボタンを押せば人工知能で識別してくれるアプリ。 返送速度もいい感じ(これはエミュレーターだが、実機でやればもう少し早くなる)。 こんな感じのアプリを作成していく 1.モデルの作成まずは人工知能のモデルづくりから始める。 Tensorflow Liteでは.tfliteという独自の拡張子のモデルを用意する必要がある。これには以下の方法が存在する。 1. Tensorflow Hubから有志が作成したモデルを取得する 2. TFLite Model Makerを使って事前学習済みのモデルを学習させて取得する 3. Tensorflowでモデルを作成してそれをtflite形式に変換する 今回は3の方式で行う。 ここからはPythonで記述する。 まずはTensorflowでモデルを作成する。  # モデル構造の設定  model = tf.keras.models.Sequential([   tf.keras.layers.Conv2D(48, kernel_size = (4, 4), activation="relu", input_shape=(28,28,1)),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Conv2D(96, kernel_size = (4, 4), activation="relu"),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Flatten(),   tf.keras.layers.Dense(200),   tf.keras.layers.BatchNormalization(),   tf.keras.layers.Dense(10),  ])    loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)  model.compile(optimizer='adam',         loss=loss_fn,         metrics=['accuracy'])  model.fit(train_input, train_label, epochs=5) このモデルにMnistのデータセットを入れて学習させたところ、識別率98.84%になった。 これを一度SavedModel形式に変換する。  model.save("mnist_model") そこからconverterを用いてtflite形式に変換する  converter = tf.lite.TFLiteConverter.from_saved_model("mnist_model")  tflite_model = converter.convert()  with open("mnist_model.tflite", 'wb') as o_:    o_.write(tflite_model) tfliteに変換することで、モバイル端末でも問題なく動作するように軽量化が行われる。 ただし、軽量化する中で精度が低下することがある。 そこで、以下のコードで精度を計測する。 # tflite形式のモデルのインスタンス化 interpreter = tf.lite.Interpreter("MnistModel.tflite") interpreter.allocate_tensors() input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() # 識別結果が正しい個数 correct_num = 0 for i in range(len(test_input)): # テストデータを入力の型に合わせる input = test_input[i].reshape(input_details[0]['shape']).astype(np.float32) interpreter.set_tensor(input_details[0]['index'], input) interpreter.invoke() preds_tf_lite = interpreter.get_tensor(output_details[0]['index']) # 正答率を計算 correct_num += np.argmax(preds_tf_lite) == test_label[i] print(f"accuracy: {correct_num / len(test_input)}") 識別率は98.84%で、低下は見受けられなかった。 こうしてモデルを用意できた。 詳細なコードは以下に示す。 https://colab.research.google.com/drive/18J0DxrKAMGnMvu0C2AYZlqxREkglFfNS?usp=sharing 2. アプリ部分の作成次にアプリの部分を作成する。 様々な記事を見てみると、識別部分まで全てActivityに記述しているものが散見された。 お試しで使う分にはこれで十分ではあるが、今回は普通にアプリに組み込んでみて問題なく使えるかをみてみたい。 そこで以下のアーキテクチャに沿って開発をした。 TFLiteModelは技術的要素が多く、バックエンドよりの処理であるためRepository層に押し込んでいる。 また、Bitmap型はAndroid独特の型である。今回はビジネスロジックが特定の技術に依存するのもどうかと思い一度Imageに変換している。ただし、その場合TensorBufferやTensorImageへの変換に苦労することになる。 TFLiteではBitmap -> TensorImageへの変換関数も用意してくれているため、Bitmapに依存するように書いても十分問題ないと思われる。 後は(TFLiteModel以外の部分を)この仕様書に合わせて頑張って実装する。 3. モデルとアプリの接続まずライブラリの依存関係を記述する。 version catalogに以下を記述。  # Tensorflow Lite  tensorflow-lite = { group = "org.tensorflow", name = "tensorflow-lite", version.ref = "tensorflow-lite" }  tensorflow-lite-support = { group = "org.tensorflow", name = "tensorflow-lite-support", version.ref = "tensorflow-lite-support" }  tensorflow-lite-metadata = { group = "org.tensorflow", name = "tensorflow-lite-metadata", version.ref = "tensorflow-lite-support" } tensorflow-lite-gpuを導入することでGPUを使用することもできるようになるが、今回は省いている。 次にModelをAndroidのライブラリに入れていく。 以下の画像のように選択。 クリックすると以下のようなモーダルが出てくるため記述。 するとmlファイルが出現し、ここに格納される。 こちらのファイルを見ると、使い方が書かれている。 こちらに従ってコードを記述していく。 まずはModelのインスタンスの作成。 Dagger Hiltを使ってDIできるようにしておく。  @Module  @InstallIn(SingletonComponent::class)  class ModelProvider {    @Provides    fun providesMnistModel(      @ApplicationContext context: Context,    ): MnistModel = MnistModel.newInstance(context)  } その後、Repositoryに以下を記述する。    override suspend fun classify(         target: Image,     ): ApiResult<ClassifyResult, DomainException> = withContext(dispatcher) {         mnistModel             .process(target.toTensorBuffer())             .outputFeature0AsTensorBuffer             .floatArray             .let { buffer ->                 ClassifyResult(                     zero = buffer[0],                     one = buffer[1],                     two = buffer[2],                     three = buffer[3],                     four = buffer[4],                     five = buffer[5],                     six = buffer[6],                     seven = buffer[7],                     eight = buffer[8],                     nine = buffer[9],                 )             }.let { ApiResult.Success(it) }     }          private fun Image.toTensorBuffer() = TensorBuffer         .createFixedSize(intArrayOf(1, 28, 28), DataType.FLOAT32)         .also { buffer -> buffer.loadArray(this.pixels.toFloatArray()) } ぱっと見色々書いているように見えるが、識別自体はprocess関数を叩くだけでできており、非常に手軽である。 mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。 ただデータの変換だけめんどくさい。実際残りのコードは全てデータの変換である。 Modelにmetadataを付与することでここら辺の記述はもう少し簡単にできるが、今回はこのまま行った。 以上で完成。結構簡単にできる。 完成したコードはこちら https://github.com/Wansuko-cmd/mnist-application 感想モデル作るのはそこまで難しくない。最悪既存やつ使えばいいアプリとの接続は面倒。入力されたものを頑張ってBufferに変換していく必要がある。(これはTensorImageを使えば解決できそう)モデルがでかい。今回のモデルサイズは37.5MB。一応Firebaseと連携させることで初期インストールしなくてもよくなるが、それでもでかい自分の作ったモデルをアプリに載せるのは結構楽しい気になった人は是非Androidを使って組んでみてほしい。公式チュートリアルに従ってやれば一日でできるようになる。

> 内容を見る

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

懐かしの高校物理をPythonで解こう ~ただし空気抵抗は考えるものとする~

はじめにこんにちは、ごまです! つい最近、Python で微分方程式を解いたり、グラフを描画することを可能にするライブラリがあることを知りました。 自分の知る限りでは、微分方程式を解くためには SymPy というライブラリが有効なようなのですが、正直なところあまり使ったことがありません。 そのため、SymPyの使い方を習得するためにも、今回はこのライブラリと Python を用いて、高校物理の問題を、それも (高校物理では殆どの場合考慮されなかった) 空気抵抗の考慮をしつつ解いてみようと思います。環境Windows 11Python 3.9.7SymPy 1.11.1SymPyについて軽く説明と実演本格的に演習に入る前に、SymPyについての軽い説明と、いくつかの実演を行います。 今回使用するライブラリであるSymPyは、簡単に言うと数式の処理や計算を行えるPythonのライブラリです。 これを用いることで、Pythonを用いての微分積分や因数分解などの様々な数式の処理が可能になります。 前置きはここまでとして、実際にSymPyを使ってみます。本稿ではこれ以降、SymPyをsymとしてimportします。import sympy as sym 因数分解まずは因数分解をやってみましょう。 SymPyで$x$や $y$ などの変数を扱う際は、次のようにsympy.symbols()を用いて定義を行います。x,y = sym.symbols('x,y') もちろん1文字ずつ定義を行うことも可能です。 因数分解はsympy.factor()の引数として、因数分解したい数式を渡すことによって行えます。 今回は $2x^{4}-9x^{3}-x^{2}-9x+2$という数式を因数分解してみます。 具体的には次のように記述します。sym.factor(2*x**4 -9*x**3 -x**2 -9*x +2) 少し記法が分かりづらいですが、この場合の*(アスタリスク)一つは乗算演算子、二つはべき乗演算子です。 例えば $a \times b$ はa*b、 $a^{b}$ はa**bと表現されます。 因数分解の結果は次のようになりました。 どうやら無事に因数分解できたようです。 これも記法が分かりづらいですが、SymPy はsympy.latex() を用いることで、数式を LaTeX の記法で出力してくれます。 そのため次のような関数 output() を定義することで、以降の結果を LaTeX の記法で出力することとします。def output(ans):     print("\n",sym.latex(ans),"\n") 実際に先程の計算を引数にして output() を実行すると次のような出力が得られました。 したがって結果は $ \left(x^{2} - 5 x + 1\right) \left(2 x^{2} + x + 2\right) $ となり、やはり問題なく因数分解できていることがわかります。 微分積分微分積分についても扱っておきます。 まず微分ですが、sympy.diff()を用いることで導関数を求めることができます。 例えば、 $x^{3}$ の $x$ に関しての1階微分は次のように書くことができ、結果として $3x^{2}$ が出力されます。output(sym.diff(x**3,x)) $n$ 回微分をしたければ、 $n$ の数だけ変数を渡すか、変数のあとに $n$ を渡すことによって行えます。 例えば、 $x^{3}$ の $x$ に関しての2階微分は次のように書くことができ、結果としてどちらも $6x$ が出力されます。output(sym.diff(x**3,x,x)) output(sym.diff(x**3,x,2)) 次に積分について説明します。不定積分を行う際はsym.integrate()関数に式と変数を与えればよいです。 例えば、 $6x$ を $x$ について不定積分しようとする場合は次のように書くことができ、結果として $3x^{2}$ が出力されます。output(sym.integrate(6*x)) 積分定数が含まれていませんが、これは微分方程式を解く場合には問題ありません。 定積分については、定積分の下限と上限を与えることで計算が行なえます。 次の計算では結果として48を得ます。output(sym.integrate(6*x,(x,3,5))) 微分方程式今回の記事で最も重要な、微分方程式の計算について説明します。 まず次のように、変数 $x$ に加えて、未定義の関数$f$を宣言します。 関数の宣言はsympy.Function()で行います。x = sym.symbols('x') f = sym.Function('f') そして、関数 $f(x)$ についての微分方程式を次のように宣言します。 今回解く微分方程式は $$\frac{d^{2}}{dx^{2}}f(x)-\frac{d}{dx}f(x)-2f(x)=0$$ です。 eq = sym.Eq(f(x).diff(x, x) - f(x).diff(x) - 2*f(x), 0) 方程式の定義はsympy.Eq()で行います。第一引数は方程式の左辺、第二引数は方程式の右辺です。 右辺は今回は0に設定していますが、勿論他の引数を渡すこともできます( $e^x$ とか)。 そして、微分方程式はsympy.dsolve()関数に先程宣言した方程式eqを引数として渡すことにより解くことができます。output(sym.dsolve(eq, f(x))) その結果、 $$f{\left(x \right)} = C{1} e^{- x} + C{2} e^{2 x} $$ が出力されました。 今回は不定積分のときと異なり、適切に任意定数 $C{1},C{2}$ が用いられています。 本稿ではこれを用いて、運動方程式の解を求めていきます。 今回扱う問題今回は河合出版の「物理のエッセンス 四訂版 力学・波動」の中から、簡単な力学の問題を抜粋して解いていきます。 比較のため、それぞれの問題に対して「空気抵抗ナシ」「空気抵抗アリ」の両方のパターンの解答の流れを示します。 演習放物運動問題質量$m$の質点を床から初速$v_{0}$で角度$\theta$の方向に投げた場合,最高点に達するまでの時間$t_{1}$と最高点の高さ$h$を求めよ。水平到達距離$x$を求めよ。解答(空気抵抗ナシ)1. 水平方向に $x$ 軸、鉛直方向に $y$ 軸を取ることを考えると、鉛直方向の運動方程式は $$m\frac{d^{2}y}{dt^{2}}=-mg$$ となります。 両辺を $m$ で割り $g$ を移項すると $$\frac{d^{2}y}{dt^{2}}+g=0$$ となるため、これをSymPyで記述するとeq1_0 = sym.Eq(y(t).diff(t, 2)+g) これを微分方程式としてsympy.dsolve()を用いて解く際、eq1_2 = sym.dsolve(eq1_0, y(t)) と記述することができ、この解は $$   y{\left(t \right)} = C_{1} + C_{2} t - \frac{g t^{2}}{2} $$ となリます。 実は、SymPyでは微分方程式を解く際に初期条件を課すことができ、ics={}の形で記述できます。 今回、 $y(0)=0$ 、 $\frac{dy}{dt}(0)=v_{0}\sin\theta$ ですから、先程の式はeq1_2 = sym.dsolve(eq1_0, y(t), ics={y(0): 0, y(t).diff(t, 1).subs(t, 0): v0*sym.sin(theta)}) と書き直すことができます。subs()関数は代入を行っていると考えていただければ大丈夫です。 $y(t)$ の1階微分に $t=0$ を代入したとき、その値が $v_{0}\sin\theta$ であるということを表しています( $v_{0}$ と $\theta$ は新たにsympy.symbolsで定義しました)。 そうして改めて微分方程式を解くと、解として $$   y{\left(t \right)} = - \frac{g t^{2}}{2} + t v_{0} \sin{\left(\theta \right)} $$ が得られます。 したがって、これの両辺を $t$ について微分した式は $$ \frac{dy}{dt}=-gt+v_{0}\sin\theta $$ ですから、 $t=t_{1}$ のとき、すなわち最高点における $y$ 方向の速さが0であることから、この式の左辺を0とおいた式を$t$ について次のように解きます。t_1 = sym.solve(eq1_1, t) これによって、解が次のように出力されます。 したがって、 $$   t_{1}= \left[ \frac{v_{0} \sin{\left(\theta \right)}}{g}\right] $$ ということがわかります。 最高点 $h$ を求めるには、この $t_1$ を(4)式に代入すればいいわけですから、h = eq1_2.subs(t, ((v0*sym.sin(theta)/g))) このように記述してやることで、値として $$   y{\left(\frac{v_{0} \sin{\left(\theta \right)}}{g} \right)} = \frac{v_{0}^{2} \sin^{2}{\left(\theta \right)}}{2 g} $$ が得られます。これが$h$ですから、書き直すと $$ h = \frac{v_{0}^{2} \sin^{2}{\left(\theta \right)}}{2 g} $$ となり、少々不格好ですが無事に小問1が解けたことになります。 2.投げ出されてから落下するまでの時間を $t_{2}$ とすると$t=t_{2}$のとき$y=0$ですから、 t_2 = sym.solve(eq1_2.subs(y(t),0), t) と記述してやることで、解として $$ \left[ 0, \ \frac{2 v_{0} \sin{\left(\theta \right)}}{g}\right] $$ が得られます。$t_{2}\neq0$ですから、 $$ t_{2}=\frac{2 v_{0} \sin{\left(\theta \right)}}{g}(=2t_{1}) $$ であることは明らかです。 ここで、質点に対しては水平方向の力は何も加わっていませんから、水平方向の運動方程式は次のようになります。 $$   m\frac{d^{2}x}{dt^{2}}=0 $$ したがって両辺を $m$ で割ったものをこのように記述します。eq2_0=sym.Eq(x(t).diff(t, 2)) これを、微分方程式として、初期条件 $x(0)=0$ 、 $\frac{d}{dt}x(t)=v_{0}\cos\theta$ を課して解くには次のように記述すれば良く、eq2_2 = sym.dsolve(eq2_0, x(t), ics={x(0): 0, x(t).diff(t, 1).subs(t, 0): v0*sym.cos(theta)}) 解として $$ x{\left(t \right)} = t v_{0} \cos{\left(\theta \right)} $$ が得られます。 これに $t=t_{2}$ を代入して解くためにはx_l= eq2_2.subs(t, ((2*v0*sym.sin(theta)/g))) と記述でき、解として $$   x{\left(\frac{2 v_{0} \sin{\left(\theta \right)}}{g} \right)} = \frac{2 v_{0}^{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}}{g} $$ すなわち水平到達距離 $x$ として $$   x= \frac{2 v_{0}^{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}}{g} $$ が得られます。解答(空気抵抗アリ)1. $x$ 軸および $y$ 軸を空気抵抗ナシの場合と同様に取ることを考えます。 ここで、空気抵抗の比例定数を $\kappa$ (カッパ)とすると、 $y$ 軸方向の運動方程式は次のようになります。 $$   m\frac{d^{2}y}{dt^{2}}=-mg-\kappa\frac{dy}{dt} $$ 両辺を $m$ で割り移項すると $$   \frac{d^{2}y}{dt^{2}}+\frac{\kappa}{m}\frac{dy}{dt}+g=0 $$ となります。これを記述するとeq1_0 = sym.Eq(y(t).diff(t, 2)+(kappa/m)*y(t).diff(t, 1)+g,0) と書けますから、これを次のようにsympy.dsolve()を用いて、初期条件 $x(0)=0$ 、 $\frac{d}{dt}x(t)=v_{0}\cos\theta$ を課して解きます。eq1_2 = sym.dsolve(eq1_0, y(t), ics={y(0): 0, y(t).diff(t, 1).subs(t, 0): v0*sym.sin(theta)}) すると次のような解が得られます。 $$   y{\left(t \right)} = - \frac{g m t}{\kappa} + \frac{\left(- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}\right) e^{- \frac{\kappa t}{m}}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2}} $$ 空気抵抗ナシの場合に比べて非常に式が複雑になっていますが、解法は特に変わりません。 式自体が長くなったので省略しましたが、式の両辺を微分すると $$ \frac{d}{dt}y(t) = -\frac{g m}{\kappa} - \frac{\left(- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}\right) e^{- \frac{\kappa t}{m}}}{\kappa m} $$ となります。 あとは空気抵抗ナシの場合と同様に、 $t=t_{1} $のとき $dy/dt=0$ となると考えて、直前の式を用いて定義したeq1_1を $t$ について解くと、 sympy.solve()を用いて次のように書くことができ、t_1 = sym.solve(eq1_1, t) 解として $$   t_{1}= \left[ \frac{m \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa}\right] $$ が出力されます。最高点$h$も空気抵抗アリの場合と同様にして、 $t_1$ を $y(t)$ の式に代入すれば良く、h = eq1_2.subs(t, m*sym.log(1 + kappa*v0*sym.sin(theta)/(g*m))/kappa) と記述すれば、計算結果として $$ y{\left(\frac{m \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa} \right)} = - \frac{g m^{2} \log{\left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2}} \\+ \frac{- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2} \cdot \left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m}\right)} $$ が得られますから、 $$ h= - \frac{g m^{2} \log{\left(1 + \frac{\kappa v{0} \sin{\left(\theta \right)}}{g m} \right)}}{\kappa^{2}} + \frac{g m^{2} + \kappa m v{0} \sin{\left(\theta \right)}}{\kappa^{2}} + \frac{- g m^{2} - \kappa m v_{0} \sin{\left(\theta \right)}}{\kappa^{2} \cdot \left(1 + \frac{\kappa v_{0} \sin{\left(\theta \right)}}{g m}\right)} $$ とわかります。 2.こちらの問題も空気抵抗アリの場合と同様に解く、といいたいところですが、数式が複雑化してしまったせいか、 投げ上げてから落下するまでの時間を求める過程でプログラムが停止してしまい計算を完了することができませんでした。 そのため、残念ですがこのあたりは今後の課題とします。 余談sympy.plot()を用いれば、物体が実際にどのような運動をするのか、というグラフをプロットすることができます。 次の図は私が適当に質点の質量やら投げる角度やら空気抵抗定数やらを指定してプロットした結果です。青が真空中での $y-t$ グラフ、オレンジが流体中での $y-t$ グラフです。 (ここで流体と表現したのは、空気抵抗定数あたりの計算を厳密にしなかったせいで現実ではありえない空気抵抗が発生している可能性があるからです。下手したらタイトル詐欺かも...) 終わりに至らない点も多くありましたが、SymPyを用いた計算の方法を習得することができ、個人的には有意義な取り組みだったと思います。 もしかしたらまた同様の記事を書くかもしれません。ここまでご覧いただきありがとうございました。 参考文献maskot1977 . "微分や微分方程式をPythonで理解する" . "Qiita" . 2019-01-09 .https://qiita.com/maskot1977/items/b4395da5f33f70cd4a09, (参照 2023-02-20)大窪 貴洋. "Sympyによる代数計算" . "コンピューター処理 ドキュメント" . 不明. https://amorphous.tf.chiba-.jp/lecture.files/chemcomputer/15Sympy%E3%81%AB%E3%82%88%E3%82%8B%E4%BB%A3%E6%95%B0%E8%A8%88%E7%AE%97/15.html,(参照 2023-02-27).SymPy Development Team. "SymPy". 2021. https://www.sympy.org/en/index.html, (参照 2023-02-28).浜島清利, 物理のエッセンス四訂版 力学・波動, 河合出版, 2013

> 内容を見る

Polygon

アーモンドチョコレートを作ろう

今年もバレンタインというくそかすうんちeventがやってきました。今年も1個ももらえないままかと思っている男子に朗報です。そんなあなたの気持ちを和らげるために私からアーモンドチョコレートの作り方をお教えしましょう。調理方法まず、チョコレートを用意しましょう。このとき、ホワイトチョコレートだとインスタバエルが狙える気がするのでいいです。 用意ができたら、チョコレートをバレンタインというくそイベントを生み出した聖ウァレティヌス(テルニのバレンタイン)の存在抹消することを念じながら、よのリア充に毒手を食らわせたいという気持ちを抑えながら、包丁で細かくします。その後、耐熱容器にチョコレートを入れて、湯銭して溶かします。その間にアーモンドを砕きたかったのですが、なんとアーモンドがありません(驚愕) 代わりのものが、サルミアッキしかなかったのでこれを入れます(不本意)。まあ、憎しみみたいなものですし多分あいます。 サルミアッキを細かくしたものを一緒に湯銭して、憎しみを断ち切ります。   と、やりたいところでしたが実はこれ…溶けないんですね どうにかして、世の非リア充の憎しみとリア充のさわやかな白の心が和解してほしいのですが…まあ無理だな  ちなみに、無理やり溶かそうとレンジでやってみたらゴミ(知り合いからは鹿野糞)ができました。味はアバ茶よりはおいしかったです;; 溶けないことが判明したところでお次に、適当なようきに入れて冷蔵庫にぶち込みます。早く食べたいせっかちなホモの皆さんは冷凍庫にぶちこむか液体窒素にでも入れといてください(どうなるかはしりません)。 1時間くらい冷やしたら完成!! 適当な画像にそれらしい文字を入れたら、彼女からチョコをもらったみたいで興奮するぞ…(*´д`)アハァ… レビュー ちょこっとまずかったです

> 内容を見る

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

KotlinのFlow用テストライブラリ「turbine」を使ってみる

動機とあるインターンでテストコードを書いていた時の事。「turbine」というライブラリを使って書き上げてプルリクを作成した。 するとレビュアーから「このライブラリなにこれすごいな」というコメントを頂いた。 結構気に入っているライブラリなのだが知られてなくて悲しかったため記事に書くことにした。 そもそもFlowとはFlowとはKotlinにて非同期で値を返してくれるすごいやつで、近年のAndroid開発において必須の技術である。 例えば以下のコード。 suspend fun countStream(num: Int): Flow<Int> = flow { repeat(num) { count -> delay(100) emit(count) // 0 1 2 3 4 ... 99 } } fun main() = runBlocking { countStream(100).collect { println(it) } } このコードを実行すると、0 ~ 99までの数値が0.1秒毎に出力される。 当然非同期処理なので、main関数にコードを追加すれば同時に実行されるようになる。 詳しくはこの記事が参考になると思う。 Flowのテストの難しさここで、以下のtestTarget関数のテストを書いてみる。 suspend fun testTarget(): Flow<String> = flow { emit("First") delay(1000L) emit("Second") delay(1000L) emit("Third") } fun main() = runBlocking { testTarget().collect { println(it) } } 一秒ごとに文字列を返す関数で、出力結果は以下のようになる。 First Second Third Process finished with exit code 0 テストで確認したい項目は以下の通りFirst、Second、Thridの順番に出力されるこれ以上の値は出力されない これをもとにテストを書くと以下のようなコードが考えられる。 @Test fun testTargetのテスト() = runTest { testTarget().collectIndexed { index, value -> if (index == 0) assertEquals("First", value) if (index == 1) assertEquals("Second", value) if (index == 2) assertEquals("Third", value) if (index >= 3) throw IndexOutOfBoundsException() } } このように記述することで上記の内容をテストできる。 これでもすっきりと記述できているがいくつか気になる点がある。 1.いちいちindexの値を見るコードを書かないといけないこれにより、ただコードを書く行が増えるだけではなく、変更に弱くなっている。例えばFirstとSecondの間にHogeが出力されることになったら大幅に変更する必要がある。 2.throwする内容が統一されない可能性がある現在IndexOutOfBountdException()が投げられている。しかし、多くのテストコードを書くうちにここが不明瞭になる可能性がある。複数人で開発しているときはなおさらである。 以上の問題点が考えられる。 これ以外にも、Flowの関数が複雑になる(例えばMutableStateFlowを用いる等)ごとにどんどん書きづらくなる。 テストが読みづらいコードであるとメンテナンス性が著しく低下するため、もう少し書きやすくしたいところである。 turbineを利用 ここで「turbine」の出番である。turbineはFlow用テストライブラリで、非常にシンプルに記述することができる。 先ほどまでのコードはturbineを使うことで以下のように書くことができる。 @Test fun testTargetのテスト() = runTest { testTarget().test { assertEquals("First", awaitItem()) assertEquals("Second", awaitItem()) assertEquals("Third", awaitItem()) awaitComplete() } } test {}、awaitItem()、awaitComplete()がturbineライブラリで提供される関数である。 これで上記の問題を解決できている。また可読性も向上している。 turbineの使い方build.gradle(.kts)に以下を追記する。 repositories { mavenCentral() } dependencies { testImplementation 'app.cash.turbine:turbine:0.12.1' } これだけで導入可能である。 メソッド紹介(一部)test()turbineを適用するFlowを指定するための関数。 timeout等も指定可能である。targetFlow.test { // turbineのコードをここに記述 } awaitItem()値が流れてくるのを待ち、流れてきたらその値を返す。 複数回呼び出した場合、流れてきた値順に値がかえされることになる。targetFlow.test { val first = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す val second = awaitItem() // 次の値が流れてくるまでブロッキング。 } awaitComplete()Flowが閉じたことを確認する関数。 新しく値が流れてきた場合TurbineAssertionErrorを投げる。targetFlow.test { val value = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す awaitComplete() // 新しい値が流れてきたらエラーが投げられる。 } awaitError()Flowでエラーが投げられたことを確認し、返り値として返す。 値が流れてきたりFlowが終了した場合、TurbineAssertionErrorを投げる。targetFlow.test { val value = awaitItem() // 値が流れてくるまでブロッキング。流れてきたらその値を返す val throwable = awaitError() // 新しい値が流れてきたらエラーが投げられる。投げられたエラーを返り値として返す } 基本的にはこれらの関数で成り立つ。 詳しくは公式のGithubや資料を参照するとよさげ。 https://github.com/cashapp/turbine https://cashapp.github.io/turbine/docs/0.x/turbine/app.cash.turbine/index.html 終わりにFlowに限らずテストは書くのが億劫になりがちだが、こういったライブラリを有効活用することでかっこよく書くことが可能である。 ぜひ自身のプロダクトでも記述してもらいたい。 ついでにAndroid技術者が全然足りていないのでぜひ勉強してほしい。結構簡単な部類のはずなので・・・

> 内容を見る

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

TwitterにDeflate圧縮されたバイナリをアップロードして利用する 🤔

まずはこれらのツイートを見てほしい。echo 'class ${public static void main(String[]a)throws Exception{var b=new int[810000];https://t.co/hd3ce1Xpzc(new https://t.co/vgGGZeOChL.File("/media/0")).getRGB(0,0,900,900,b,0,900);for(int i:b)if((i&255)!=0)System.out.write(i);}}'>$.java java $.*>_.java java _.* #シェル芸 pic.twitter.com/aCxDjD9ZS1— 社畜ちゃん (@Shachiku_nyan) February 23, 2023 次は◉のターンめう~  12345678 A         B         C         D   〇◉    E   ◉〇    F         G         H https://t.co/TZGGgxwtH7— シェル芸bot (@minyoruminyon) February 23, 2023 5F6F6E4F5G6G7G8H3D5H8G3E2E6D6H7H7D4G7E3F2G7F8F8E4H4C5C3C5B2C1C6B7A8C8D7C2D6C7B3G3H2H8B4B5A8A6A1H3A4A1B1A3B2B1E2F1G1F1D2A #シェル芸 https://t.co/mYeyPWMN9Y— 社畜ちゃん (@Shachiku_nyan) February 23, 2023 〇の勝ちめう!  12345678 A◉〇〇〇〇〇〇〇 B◉〇〇〇〇〇〇〇 C◉〇〇〇〇〇〇〇 D〇〇〇〇〇〇〇〇 E〇〇〇〇〇〇〇〇 F〇〇〇〇〇〇〇〇 G〇〇〇〇〇〇〇〇 H〇〇〇〇〇〇〇〇 https://t.co/1Ux236CqEU— シェル芸bot (@minyoruminyon) February 23, 2023 シェル芸bot(@minyoruminyon)とは、それがフォローしているユーザが #シェル芸 等のハッシュタグをつけてシェルコマンドをツイートしたとき、引用RTでその出力をつぶやくbot [注1] 。 つまり、上記のツイートはシェル芸botにjavaコマンドを実行させてオセロをしているということになる。しかしオセロのソースコードは含まれていないように見えるが……? /media/0シェル芸botには、ツイートに添付された画像を /media ディレクトリ下にあるものとして実行する機能がある。1枚ならその画像は /media/0 。 つまり最初のツイートは、画像を読み込んで java.awt.image.BufferedImage.getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) によって埋め込まれたバイナリを取り出しているということになる。 記事タイトルが物々しかったが、「Deflate圧縮されたバイナリ」とは、ただ単に「バイナリを埋め込んだPNG画像」のこと。 ちなみにDeflateはおなじみZIPやgzipにも使われる可逆圧縮アルゴリズム。 ちなみに元ツイートを書いた人によると、埋め込まれているのはJavaのソースらしい [2]。手元で実行してみたらわかる。 扱える画像今どうなっているかは知らないが、どうも数年前時点でのTwitterの画像の仕様はこうらしい [3](読み間違いがあれば申し訳ない)。JPEGは原則、quality85の4:2:0クロマサブサンプリングされたJPEGとして再エンコードされる。ただし次の条件をすべて満たすならば再エンコードしない。画像サイズが4096x4096以下である。5MB以下である。画像の回転が必要であるような情報を持っていない。1ピクセルあたり1バイト未満である。WebPもquality85のJPEGに変換される。PNGは色深度や画像サイズによって異なる。PNG-8は保持される。PNG-24,PNG-32のうち、実際の色数が256色以下であるならばPNG-8に変換される。上記を満たさないPNG-24,PNG-32のうち、画像サイズが900x900以下ならば保持される。上記を満たさないPNG画像は、JPEGに変換される。扱える最大の画像サイズは4096x4096である。 つまり、900x900のPNG-32を使えば約3MBもの情報を使うことができる。4枚使えば4倍(要検証)。 ……4096x4096のPNG-8でも16MBの情報を使えるのでは?などと思ったが多分JPEGに変換されるんだろう。知らんけど。 あとはプロトコルを交換しておけばメモ帳アプリのスクショをツイートするよりも大量の情報を一気に投稿できる(誰にわかるねん)。 おまけ失敗しとるやないかい! Lorem ipsumが出る予定だったのに/media/0: PNG image data, 15 x 10, 8-bit colormap, non-interlaced https://t.co/V6BDW0Wr5q— シェル芸bot (@minyoruminyon) February 28, 2023 ツイート1: 本当なら 8-bit/color RGB になってもらう予定だったが、256色以下だったので colormap に変換されてしまった例!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a%bcdefghijklllllllll https://t.co/MTbx2B4GR7— シェル芸bot (@minyoruminyon) February 28, 2023 ツイート2: WebPに変換される(多分?)とかいう知らない仕様によってASCIIコード表から順番に持ってきたみたいな謎文字列が出ている例 参考文献[1] 社畜ちゃん(@Shachiku_nyan)「echo 'class ${public static void …」 https://twitter.com/Shachiku_nyan/status/1628601778345500674 (2023年2月27日アクセス) [2] 社畜ちゃん(@Shachiku_nyan)「@AAAR_Salmon javaのソースそのまま押し込めてますw」 https://twitter.com/Shachiku_nyan/status/1628699291454963712 (2023年2月28日アクセス) [3] NolanOBrien「Upcoming changes to PNG image support」 https://twittercommunity.com/t/upcoming-changes-to-png-image-support/118695 (2023年2月28日アクセス) [注1] ふるつき「今の所判明しているシェル芸botの仕様」 https://furutsuki.hatenablog.com/entry/2018/07/13/221806 (2023年2月28日アクセス)

> 内容を見る

Unity Polygon
Unity

MyCrane制作記2

MyCrane制作記2 こんにちは.しげです. 前回から1年くらいあいてしまいましたが,続きを書きましたのでよければ見ていってください. *MyCraneは,C#とUnityで制作しているクレーンゲームです.自分でクレーンゲームの設定を作りたい!遊びたい!を形にするために,このプロジェクトは始動しました. プログラム構成まず,MyCraneのスクリプト(プログラム)構成はどのようになっているのでしょうか. 大きく分けると筐体全体をコントロールするメインプログラムと,筐体の各部をコントロールするサブプログラムで構成されています. 例としてMyCrane Type3を見てみると メインプログラムType3Manager.cs サブプログラムType3ArmController.csArmControllerSupport.csCraneBox.csGetPoint.csRopeManager.csRopePoint.csCreditSystem.csProbabilitySystem.cs(CreditSystem.csから分離)BGMPlayer.csSEPlayer.csMachineHost.csとなっています. メインプログラムはサブプログラムの呼び出しを行って,所定の動作を実行します.基本的にはメインプログラム→サブプログラムの方向でメッセージを送りますが,例外的にサブプログラムからメインプログラムにメッセージを送ることもあります. メインプログラムメインプログラムは,筐体の動作の流れをコントロールします.具体的には,待機状態横へ移動する縦に移動するアームが開く下降するアームが閉じる上昇するホームポジションに戻るアームが開くアームが閉じるの繰り返しが実現できるようなプログラムになっています. 簡単に言えば,メインプログラムはサブプログラムにメッセージを送ることで,筐体の動作を実現し,実際の動作についてはサブプログラム側に記述されているということになります.もし,アームユニットを右に動かしたい場合は,メインプログラムからcraneBox.Right()と関数を呼び出すことになります(正確にはCraneBoxクラスのインスタンスをメインプログラムが持っていなければならないのですが,今回は割愛します). 以下はメインプログラムからの呼び出し例です.craneStatusは筐体の現在の状態(横移動中,待機中など)を管理している変数です(今思えば列挙型の方が良かったかもしれない).if (craneStatus == 2) craneBox.Right(); サブプログラムサブプログラムには実際の動作が記述されています.内部的な処理はサブプログラムが行います.ほとんどの筐体で同じ処理を使うことになります.例としてCraneBox.csを見てみると次のような記述があります(一部簡略化してあります). public void Right() { if (!rightRefusedFlag) // 右移動制限に到達していない場合 { // x座標をmoveSpeed分だけ増加させる(moveSpeedは常に正の値) transform.localPosition += new Vector3(moveSpeed, 0, 0); } }このRight()関数には,座標を上書きすることでx座標を増やすという処理が書いてあります.この処理はゲーム内において,右に移動する動作として還元されます.ただし,筐体を回転させると座標系が変わってしまうため,筐体の向きに依存しないローカル座標の値を変更するプログラムになっています. メインプログラム→サブプログラム呼び出し→実際の処理という流れの例です.メインプログラムでは実際の処理内容を気にする必要はなく,ただプログラムの呼び出しをすれば良いという形です. 開発の流れまずはサブプログラムを作成することになります.メインプログラムはサブプログラムを呼び出すことで所定の処理を行うので,呼び出し先の関数や処理を用意しなければ書くことができません.MyCraneでは,BGMPlayer・SEPlayer・CreditSystemを初めに制作しました.これらの処理は物理演算が関係しないので,比較的取り組みやすいです. CreditSystemでは,クレジットサービスの実装に苦労しました.500円連続で入れると1プレイのおまけが付くという機能ですが,100円1プレイ,500円6プレイに設定していると,1000円で12プレイできるが,1100円入れてしまうと11プレイになってしまったり,加速度的にサービス回数が増えていったり… 柔軟に設定できるという仕様にかなり悩まされた覚えがあります. 次に開発に着手したのはCraneBoxです.物理演算が絡んでいそうな部分ですが,MyCraneでは座標書き換えにより移動を実現しているので,物理演算は関係していません. そして,ArmController・ArmControllerSupportの開発に着手しました.ここではUnityのMotorを使用して,アームの駆動制御やパワー制御を行いました. 最難関だったのは,RopePointとRopeManagerです.紐を模擬するためのプログラムですが,部分的に物理演算が混じっているプログラムのため,巻取りに失敗する事故が多発して,バグもまだ残っています.まだ理想には程遠いですね. 最後はGetPointです.これは,メインプログラムをサブプログラムから呼び出す(逆方向),数少ないサブプログラムです.景品がセンサーを通過すると獲得したことを通知する必要があるのですが,獲得演出はメインプログラムに組まれているため,その関数をGetPointが呼び出すことになります. 3Dモードが実装されてからMachineHostの開発を行いました.MachineHostは3Dモードにおいて,操作対象の筐体を1台に絞るために使用するプログラムです.MachineHostを実装する前は,全筐体が連動して動作してカオスでした.オートプレイ対応のためのAutoContollerも後に開発しました. 実は課題解決型プログラミングMyCraneは筐体の種類をType1,2,3と振ってはいますが,開発の順序は3,2,1,4から後ろに向かって,という順序になっています.MyCraneでは,当初から課題解決型プログラミングを意識しており,課題解決を行いながら規模を拡大していく開発になっています. 筐体開発は次の順序で行いました.Type3:基本動作作成(サブプログラム制作)・確率機能の作成Type2:基本技術成熟・レバー操作の実現・タイマープログラムの制作Type1:2人プレイ筐体の制作Type4:レバー操作の技術成熟・回転機能付き筐体・UnityのVideoPlayerの理解Type5:UnityのAnimatorの理解Type7:レバー操作の技術成熟Type6:同上Type8:複雑な動作フローの設計,プッシャー機構の制作Type9:4人プレイ筐体の制作Type10:ゲームプレイヤーからのリクエストType11:ロープ吊り下げ式筐体の制作Type12:回転機能付き筐体・UnityのAnimatorの理解EnarcYm1:実筐体のバグを再現MYPUSH1:プッシャー筐体制作Type13:コルーチン採用新マネージャープログラム作成MYPUSH2:プッシャー筐体(2人プレイ)制作EnarcYm2:8人プレイ筐体の制作・複雑デザイン筐体の制作 機能を一気に開発するのはかなり大変ですし,動く状態に持ってくるまでモチベーションを維持するのも難しいと思います.まずは基本機能から小さく作って,機能を付け足していくのが良さそうですね.また,機能を小さめの状態で開発して,後から拡張するというのも考え方の一つです.僕も初めはBGMPlayerとSEPlayerだけ作って楽しんでいました(). 終わりに次回は,筆者が3回生のときに履修したオブジェクト指向言語の講義の知識を使って,MyCraneのプログラム改良に取り組む話を書く予定です.ここまで読んでいただき,ありがとうございました. 今振り返ってみると,コルーチンを使わずに遅延処理を実現しようとしているなど,苦肉の策が伺えます.

> 内容を見る

NoImage Polygon

おすすめの簡単なボードゲーム!! 初心者でもできる!

ルールが比較的かんたんで面白いボードゲーム ボードゲームは多くの種類があり、ルールも複雑なものから簡単なものまでたくさんあります。今回紹介するボードゲームは初心者でも楽しめるものです。出会ったばかりの友人や、先輩や後輩と一緒に遊んで、仲を深めましょう。 1.あいうえバトルシンプルな言葉あてゲーム人数:2~6人時間:15分~ルール:まず、お題を決めます。例えば、好きな食べ物、行ってみたい国名、などなど。次に、お題に沿った1~7文字の単語を考えて、すべてひらがなで一文字ずつ”ちょっかんくん”にかきます。そして、順番に”あいうえボード”の上にチップをおいていき、自分の単語にある文字にチップが置かれたら、”ちょっかんくん”を裏返して、その文字を公開します。文字を書いた”ちょっかんくん”がすべて公開されたら、その人は負けになってしまいます。最後まで非公開の”ちょっかんくん”が残っている人の勝ちです。 おすすめポイント:お題を工夫して、話題を引き出すことで、お互いのことをよく知れて、仲良くなれます。 また、シンプルなゲームですが、相手の単語を当てるのに結構頭を使うので、おもしろいです。 2.ito 1~100までのカードを小さい順に並べる協力ゲーム 人数:2~10人 時間:10分~ ルール:お題を決めます。例えば、柔らかいもの、やよく行く場所、などです。 1から100までの数字が書かれたカードがあります。 ひとり何枚かずランダムで引き、お題と自分の数字にあった単語を発表します。 その時、いかなる数字も言ってはいけません。 そして、みんなで話し合って、数字が小さそうな順に並べることができたら 成功です。 おすすめポイント:お互いの価値観をすり合わせていくゲームなので、お互いのことをよく知れて仲良くなれます。 また、協力ゲームなので勝ち負けが発生しないので、初めて会った人とも楽しく遊べます。 3.Marralesh 絨毯を敷いていく陣取りゲーム 人数:2~4人 時間:20分 ルール:アッサムと呼ばれる人形の進む向きを決め、サイコロを振って出目だけ進み、 アッサムの周囲のどこかに絨毯を置きます。 これを順番に絨毯がなくなるまで続けます。 絨毯は重ねて敷くことができます。 また、もし自分のターンでほかの人の絨毯にアッサムが止まってしまった場合、 その絨毯の人につながっている絨毯の枚数分ディルハムというお金をはらいます。 最後に一番上に敷いて見えている絨毯のマス目と、持っているお金の一番多い人の勝利です。 おすすめポイント:自分の絨毯をつなげるために、相手の絨毯を踏むリスクをとれるかのチキンレース。 サイコロの出目に一喜一憂してとても盛り上がります。 また、自分の絨毯をそこに敷くか考えるのも頭を使って楽しいです。

> 内容を見る

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

スタートメニューからWSLを終了させ、RAM使用率を表示

作るものwsl --shutdown をGUIで叩く。GUIの方がめんどいわという人はこの記事の対象ではない。 例えばDocker DesktopやIDEを終了しても、WSLがかなりメモリを食っていたとする。 ポチッとな! パソコンルームでブラクラを作っていた頃を彷彿とさせるプロンプトが出る。 はいを押すと立ち上がってるディストロが全部落ちる。ついでにRAM使用率も表示する。数字丸めるの忘れた。 そもそも作業中はコマンドを普通に叩くだろうから、なにか作業中にポチッとなすることは想定していない。これからゲームする時とかに使え。 VSCode設定変更PowerShell拡張機能を入れる。   "terminal.integrated.env.windows": {     "PSExecutionPolicyPreference": "RemoteSigned"   }settings.json に上記を追記し、自作スクリプトを実行可能にする。 Add-Type -Assembly System.Windows.Forms $result = [System.Windows.Forms.MessageBox]::Show("WSL全部終了する?","確認","YesNo","Question","Button2") If($result -eq "Yes"){     wsl.exe --shutdown     # https://shellgeek.com/powershell-get-memory-usage/     $CompObject = Get-WmiObject -Class WIN32_OperatingSystem     $Memory = ((($CompObject.TotalVisibleMemorySize - $CompObject.FreePhysicalMemory)*100)/ $CompObject.TotalVisibleMemorySize)     [void][System.Windows.Forms.MessageBox]::Show("現在のRAM使用率: " + $Memory + "%", "WSLを終了しました") } exit 0 VSCodeで上記のスクリプトを書き、適当な場所に保存する。 ただ、MessageBoxはShift-JISしか日本語を受け付けないっぽいので、エンコードを変えて保存し直す必要があると思う。 UTF-8で保存して日本語を出せる方法を誰か教えてくれ~ ショートカット作成 ショートカットを作成。分かりやすい名前を付ける。 ショートカットを無理やり追加本当は上記ファイルをスタートメニューにぶちこみたいんだが、右クリックメニューにないしD&Dもできない。 仕方ないので `%AppData%\Microsoft\Windows\Start Menu\Programs にショートカットを放り込む。 放り込んだショートカットのプロパティを開く。 powershell -ExecutionPolicy RemoteSigned -File "ps1ファイルのパス"ただ単にps1ファイルへのショートカットになっているので、先頭にpowershellを追記し、あとポリシーを変える。 あとは検索すれば出てくるはずなので、スタートメニューにピン留めする。 参考RAM取得する部分https://shellgeek.com/powershell-get-memory-usage/ メッセージボックスhttps://acoustic-groove2.hatenablog.com/entry/2018/01/29/233637 実行ポリシーhttps://zenn.dev/nekocodex/articles/eb3403961ad9b966ff6e

> 内容を見る