ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2026年7月8日水曜日

Turbo Streamsで簡単チャットアプリ

我々が開発している異文化間交流教育支援ツールDialogbookは学生・生徒と教員の間でコメントをやりとりするチャット機能を備えている.ところが,これまでの実装では旧来の「行って来い」型のプロトコルで実装していたため,新しいメッセージが投稿されても他者がリロードしないと画面には現れないというオールドファッションドなものとなっていた.

しかし,イマドキのアプリである以上は,既存のSNSやチャットツールのように,リアルタイムでやりとりしたい.コメントを投稿したら,すぐに相手の画面にも出るようにするのは,もはや当たり前の機能だろう.

そこで,そのようなリアルタイムチャット機能をDialogbookにも実装することにした.DialogbookはRailsアプリとして作られている.イマドキだと,Turbo Streamsというものを用いれば簡単に実装できるらしい.というわけで,まずはそのTurbo Streamsを理解するために,ちょっとしたチャットアプリを作ってみることにした.

準備

まずはRailsアプリを用意する.次の手順でOKである.

  • mkdir chat
  • cd chat
  • bundle init
  • Gemfileを編集(# rails ... のコメントアウトを外す)
  • bundle install
  • bundle exec rails new . -f

続いて,モデルとコントローラを作成する.あくまで習作なので,テキストの本体だけを持つ単純なモデルを作る.

  • bin/rails g model message body:text
  • bin/rails g controller messages index create
  • bin/rails db:migrate

ルーティングも単純なものでよい.config/routes.rb を次のように修正する.

Rails.application.routes.draw do

  root "messages#index"

  resources :messages, only: [ :index, :create ]

end

ここまでできたら,動作を確認しよう.bin/rails server して,ブラウザからlocalhost:3000にアクセス.問題なければなんらかの画面が出てくるはずである.

コントローラとビューの作成

コントローラのコードは次のようなものである.
app/controllers/ messages_controller.rb を次のようなコードで作成する.

class MessagesController < ApplicationController

  def index

    @messages = Message.all.order(created_at: :asc)

  end


  def create

    p = message_params

    Message.create!(p)

    redirect_to root_path

  end


  private

  def message_params

    params.require(:message).permit(:body)

  end

end

ビューはとりあえず箇条書きで表示するだけにしよう.
app/views/messages/index.html.erb のコードは次のとおりとする.

<h1>Messages</h1>

<ul>

  <% @messages.each do |m| %>

    <li><%= m.body %></li>

  <% end %>

</ul>

テストデータを喰わせて,動作確認しよう.db/seeds.rb に次のコードを追加し,bin/rails db:seed でデータを投入する.

Message.create!(body: "The 1st message.")

Message.create!(body: "The 2nd message.")

Message.create!(body: "The 3rd message.")

再び localhost:3000 にアクセス(あるいは,先ほどのページをリロード)して,メッセージが羅列されることを確認しよう.

フォームの実装

次はメッセージを投稿できるようにする.ビューにフォームを追加する.
app/views/messages/index.html.erb を以下のようにしよう.

<h1>Messages</h1>

<ul>

  <% @messages.each do |m| %>

    <li><%= m.body %></li>

  <% end %>

</ul>


<h2>Add a new message</h2>

<%= form_with model: Message.new do |f| %>

  <%= f.text_field :body %>

  <%= f.submit "send" %>

<% end %>

投稿後の処理はすでに記述済みなので,コントローラを更新する必要はない.ただし,エラー処理など全くしていないことには注意してほしい.あくまで習作である.

テストは,二つのブラウザからテストする.同じブラウザのタブやウィンドウを2個開き,それぞれからアクセスすればよい.実際にフォームにコメントを入れて送信してみれば,メッセージが追加されることを確認できるだろう.ただし,相手側に投稿したメッセージを表示するには,リロードが必要であることも確認されたい.

Turbo Streamsの追加

ビューに下記を追記する.ハイライトしている部分が追記部分である.Turbo Streamsのmessageというチャネルを使うよという指示(1行目)と,書き換え対象のDOM要素を明示的に指定するためのidの追加(3行目)である.

<%= turbo_stream_from :message %>

<h1>Messages</h1>

<ul id="messages">

  <% @messages.each do |m| %>

    <li><%= m.body %></li>

  <% end %>

</ul>


<h2>Add a new message</h2>

<%= form_with model: Message.new do |f| %>

  <%= f.text_field :body %>

  <%= f.submit "send" %>

<% end %>

モデルにも細工する.モデルは作っただけで空だったが,モデルのコード(app/models/message.rb)を次のようにする.

class Message < ApplicationRecord

  after_create_commit -> {

    broadcast_append_to(

      :message,

      target: :messages,

      partial: "messages/message",

      locals: { message: self }

    )

  }

end

モデルのレコードが作成され,データベースにコミットされたらbroadcast_append_toメソッドを実施しなさいという処理である.この引数は,messageというチャネルを介してブロードキャストし,書き換え対象はmessages,書き換える部分的なHTMLはmessages/messageにパーシャルとして用意してあるものを使い,そこでのローカル変数messageとして自分自身のデータを使えというものだ.

したがってパーシャルも用意しなければならない.
app/views/messages/_message.html.erbを用意する.内容は次のようなもの,1行だけである.

<li><%= message.body %></li>

これで準備が整った.二つのブラウザからテストし,メッセージを追加してみよう.追加されたメッセージがリアルタイムに反映されていることを確認できたら,めでたし,めでたし,である.

以前,紹介したActionCableをベタ書きするものよりはるかに簡単な実装手順となっていることがわかるだろう.

サーバにデプロイする際の留意点

今回の実装は,Apache + Passengerでサーバに実装している際に,うまく動かないという問題が顕在化した.解決策としては別ポートでAction Cable専用のRailsアプリのインスタンスをPuma経由で動作させ,Apacheのバーチャルホスト設定でWebSocket関連のパケットをProxy / Reverse Proxyでパススルーさせるというもの.その詳細は本稿では割愛するが,実際に運用する際には,十分に留意されたい.

2025年7月11日金曜日

ChatGPTがいつも正しいとは限らない

久しぶりにRailsでアプリを作っている.しばらく遠ざかっていたら,いつの間にかRailsのバージョンも8に上がっていてびっくりである.RailsでWebアプリを作ることをゴールにしているオンライン授業があるが,そろそろ動画コンテンツを作り直さないとマズいなあ.

それはそれとして,いろいろと改良されていて戸惑いも多い.やはり,常日頃から最新の状況をキャッチアップしていないとあかんなあ,というところだろうか.

今回,ブラウザのタブに表示されるアイコン,いわゆるfaviconを設定しようとして少し右往左往してしまった.ChatGPTにでも聞けばよかったのかもしれないが,いつものようにググって新しめの記述を探すと,適切なディレクトリにfaviconをおいてfavicon_link_tagをapplication.html.erbのヘッダ部分に設定すればよい,とある.

しかし,何度やってもfaviconがうまく表示されない.faviconを置く場所が悪いのか,タグのオプション指定をしなければならないのか,いろいろと試行錯誤してみても,一向に表示されないのでへこたれる.どうやっても赤丸のアイコンしか表示されない……赤丸?

赤い丸がなぜ表示されるんだろう?faviconの設定がないページは,Chromeだと地球のマークが表示されるはず.はてさて?ふと思い立ってpublicフォルダを見てみると,そのなかにicon.pngとicon.svgというファイルがあり,それらは赤い丸の画像イメージであった.こいつらか?

それらを入れ替えてみたのが次の図である.なんということはない,これだけでfaviconが表示された.試してはいないが,iPadやiPhoneでWebサイトをアイコン化したときにもこれらのアイコンが使われるようである.

今回のこの顛末,終わってみればなんとも拍子抜けな展開であった.それで,ついでにChatGPTに聞いてみたわけだが,彼もまた古い情報をしたり顔で(?)教えてくれたのであった(次図).皆さんAIがなんでも正しい情報を教えてくれると思ったら大間違いですぞ?

2024年10月20日日曜日

ゲーム・プログラミングの妙

プログラミングは楽しい.少なくとも,自分のために実施するプログラミングはいくら時間があっても足りないくらい.というわけで,今回はゲーム・プログラミングである.

題材はThe Chain Triangle Chess Gameというゲームである. Triggle Gameという名前でも流通しているらしい.Amazonで販売されていたゲーム盤のページから商品画像を引用する.

みてのとおり,ルールはとてもシンプルである.6角形の盤面に,37本の柱が立っている.プレイヤーは,交代でその柱を使って区切りを指定する.区切りの指定方法は,輪ゴムを4本の柱に引っ掛けて直線の区切りを設定するというものである.三角形の小さな区画の3辺が全て区切られたら,区切った人の領土になる(それを示すコマを置ける).

YouTubeのショート動画で楽しそうに遊んでいるのをみて,プログラミングの題材に選んだという次第.

環境の選択

最近であればPythonで書くべき?ゲームならUnityだろうか.まあ,慣れているからということと将来的に分散オンラインゲーム化することを見込んで,シンプルにHTMLとJavaScriptを用いて書くことにした.

GUIはHTMLのCanvasを利用する,Canvas要素に直に描いていく方法である.最も単純なパターンである.余計なライブラリに依存しないぶん,安定してメンテナンスできそうという目論見もある.

プログラミング

というわけで,1日くらいかけて実装した.ソースコードはGitHubに置いておいたので,興味がある人は参照してみてほしい.アプリは下記に置いてある.URLのパラメータを指定して2人〜4人の対戦に対応できるようにしてある.

プログラムを大まかに解説すると,GUI部品としてはWidgetクラスを継承したPole, Wall, Patchというクラスがそれぞれ,柱,壁,三角形の領域を表す.さらに,Containerクラスを継承したPoles,Walls,Patchesというクラスが,柱,壁,三角領域をまとめて管理するという構成になっている.

さらに,それぞれのインスタンスの関係を配列の配列で定義して,適切な参照関係を事前に用意してからゲームを始めている点も重要な点であろう.

遊び方

遊び方は,説明するまでもないのではなかろうか.まずは,どこでもよいから柱をクリックしてみよう.クリックした柱は黄色くなり,そこから直線上に3つ離れた場所にある柱が水色で表示される(次図).

水色で表示された柱以外は選択できない.なお,黄色の柱を再度クリックすると,選択を取り消せる.水色のどれかをクリックすると,最初に選択した黄色の柱と次にクリックした柱の間に区画が作られる.

3個の辺に区画ができた三角形は,その回を担当したプレイヤーの持ち物となる.色で示されるので分かりやすいだろう.全ての区画が誰かのものと定まった時点でゲームは終了,ページの最下部に得点状況が表示される(次図).

今後の課題

一通り実装できたので,今日のところはいったん完成としよう.しかし,まだいろいろと考えるべきところはある.細かな話をするとすでに壁ができているところに同じ壁を作るのを許容するかどうか.いまの実装では,その操作は許されているが,三角領域の獲得には何の寄与もしない.いわば「パス」を許していることになるが,ゲームとしてそれはよいのかどうか.検討が必要であろう.

オンライン対戦化させるのも興味深い.旧来の中央集権的サーバを用意して管理するもよし,WebRTCのような分散型の通信方法を利用するもよし,いろいろと工夫の余地はあろう.卒研生の題材にするってのも面白いかもしれないな.

2022年11月10日木曜日

hogeだのfugaだの

昨日,プログラミング基礎の授業中にほげほげ言っていたら学生たちがクスクス笑うので「なに,ほげだのふがだの珍しいか?」とメタ構文変数について解説した.皆さんもメタ構文変数という名前こそ馴染みがないかもしれないけれど,hoge, fuga, piyo, あるいは foo, bar, baz など,ITの皆さんであれば日常的にお使いなのではなかろうか.

次の図は,2011年に出版した拙書[1]からの引用である.これらの単語は,固苦しい名前がついており,メタ構文変数と呼ばれる.

まあ,こういうことにも名前がついているんだとか,foo, bar, baz だけでなくてひたすらいろいろあるんだとか,そういうトリビアを知っているのも悪くないだろう.

ところで,話は変わるがプログラミングの初学者に次の事実を突きつけるとみんなびっくりする.「コンピュータは正確な計算ができない」ってことは多くの人に知っておいてほしい事実なんだよなあ.

参考文献

  1. 飯尾淳 (2011) C言語によるスーパーLinuxプログラミング Cライブラリの活用と実装・開発テクニック, ソフトバンク クリエイティブ

2022年10月21日金曜日

Jupyter Notebookをリモートから使う方法(2022年改訂版)

 Jupyter Notebook(以下,Jupyter NB)は,本体がバックエンドで動いており,Webブラウザからアクセスして使う.ならばその本体に他のコンピュータからアクセスして使えるはずだ.とはいえ基本的にはローカル内で完結して利用することが前提となっているので,設定をいじる必要がある……ということで,そのやり方のメモ.

サーバ側の設定(事前準備:anyenvのインストール)

対象とするサーバは,さくらインターネットでお借りしたVPSのUbuntuサーバである.ポートは9999番を使うので,ダッシュボードの「パケットフィルタ設定」で9999番は開けておくこと.

リモートログインした端末で,次の手順を行う(「$」の部分はプロンプトなので入力しない).

$ uname -a

Linux ids 5.15.0-52-generic #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

まず,anyenvの設定をする.

$ git clone https://github.com/anyenv/anyenv ~/.anyenv

(略)
$ 
echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bashrc

$ echo 'eval "$(anyenv init -)"' >> ~/.bashrc

$ exec $SHELL -l

(略)

$ anyenv install --init

これで,anyenvがインストールされる.次の手順で,anyenvのバージョンを確認しておこう.

$ anyenv --version

anyenv 1.1.5-1-g5c58783


サーバ側の設定(事前準備:pyenvとpythonのインストール)

次に,pyenvをインストールして,最新版のpythonをインストールしよう.まずは,pyenvをインストールする.以下では,コマンドの出力は必要がある場合以外は省略する.さらに,[y/n]などでyes or noを尋ねられたときは,基本的にyを入力すればよい.

$ anyenv install pyenv

$ exec $SHELL -l

これで,pyenvがインストールされる.同様に,動作確認を兼ねてバージョンを確認しておこう.

$ pyenv --version

pyenv 2.3.5-2-g03a5d653

さて,次はpythonのインストールである.2022年10月20日の時点で安定版の最新版は3.10.8なので,それをインストールする.が,いろいろとライブラリなどを事前にインストールしておかないとpythonのインストールがうまくいかないので,以下のとおり,必要なライブラリをインストールしておこう.

$ sudo apt install build-essential

$ sudo apt install libbz2-dev libncurses-dev libffi-dev \

                   libssl-dev sqlite3 libsqlite3-dev liblzma-dev

これだけインストールしておけばOKのはず.そのうえで,pythonをインストールする.

$ pyenv install 3.10.8

$ pyenv local 3.10.8

とりあえずこの作業ディレクトリ以下(ローカル)で使えるようにしておこう.

サーバ側の設定(jupyterの設定と起動)

さて,ここまでお膳立てが整ったら,あとはjupyterの設定をするだけである.まず,インストールから行う.

$ pip install jupyter

簡単にインストールできるだろう.

$ jupyter-notebook --generate-config

これで,設定ファイルが作成される.

ホームディレクトリに.jupyter/jupyter_notebook_config.pyが作成されるはず.

続いて,ipythonを立ち上げる.次の手順でパスワードを設定する(In [*]: はipythonのプロンプトなのでコピペしないこと).Enter password: と Verify password: では好きなパスワードを入力する.エコーバックされないのでパスワードを間違えないように入力すること.また,ここで表示される'sha1:……'は,入力したパスワードに対するハッシュ値なのでこの値を正確にどこかにコピーしておく(あるいは,この端末を開きっぱなしにしておくこと).ここで入力するパスワードは,リモートから接続する際に利用するので忘れないこと.

$ ipython

Python 3.10.8 (main, Oct 19 2022, 17:28:48) [GCC 11.2.0]

Type 'copyright', 'credits' or 'license' for more information

IPython 8.5.0 -- An enhanced Interactive Python. Type '?' for help.


In [1]: from notebook.auth import passwd


In [2]: passwd()

Enter password: 

Verify password: 

Out[2]: 'sha1:******************** ... ***'


In [3]: exit

.jupyter/jupyter_notebook_config.pyファイルの末尾に以下を追記する.

c.IPKernelApp.pylab = 'inline'

c.NotebookApp.ip = '0.0.0.0'

c.NotebookApp.open_browser = False

c.NotebookApp.port = 9999

c.NotebookApp.password = u'sha1:******************** ... ***'

ここで,最後の行は先ほどのハッシュ値をコピペする.以上で準備は終わり.

リモートからの接続

サーバの端末に戻り,Jupyter NBを起動する.

$ jupyter-notebook

手元のマシンからWebブラウザで接続する.ポート番号を9999に変更したので,http://サーバ名:9999/としてアクセスすること.

パスワード画面が現れるので,先ほど設定したパスワードを入れてログインする.

フォルダを辿っていくと,作業用ディレクトリにアクセスできる

2022年9月25日日曜日

シンプルなチャットアプリ

WebSocket を使ってたいへんシンプルなチャットアプリを作りました.サーバのコードはこんなにシンプル(エラー処理と切断処理は省略しています).

#!/usr/bin/env python

import asyncio
import websockets

clients = {}
async def echo(soc):
  async for msg in soc:
    if not (soc in clients):
      clients[soc] = msg
      print(f'{msg} is registered')
      for s in clients:
        await s.send(f’{clients[soc]} が参加しました')
    else:
      print(f'RECV: {msg}')
      for s in clients:
        await s.send(f'{clients[soc]}: {msg}')

async def main():
  async with websockets.serve(echo, port=8765, ping_timeout=None):
    await asyncio.Future()

asyncio.run(main())

クライアントのコードもこんなに簡単!まずは,HTMLのコード.

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>A Simple BBS</title>
  <meta name="description" content="WebSockets TEST">
  <meta name="author" content="Jun IIO">
  <script src="jquery-3.6.1.min.js"></script>
  <script src="scripts.js"></script>
</head>
<body>
  <h1>シンプル・チャット・システム</h1>
  <div>
    名前:
    <input id="regname" type="text">
    <button id="register">登録</button>
  </div>
  <div>
    <textarea id="msgarea" cols=80 rows=10
              readonly disabled="disabled"></textarea>
  </div>
  <div>
    <input id="chatmsg" type="text" size=80 disabled="disabled">
    <button id="send" disabled="disabled">送信</button>
  </div>
</body>
</html>
JavaScriptのコードも,とってもシンプル.
$(function(){
  let socket = new WebSocket("ws://localhost:8765");

  $("#register").on("click", function(event) {
    name = $("#regname").val();
    if (name != "") {
      socket.send(name);
      $("#register").prop("disabled", true);
      $("#regname").prop("disabled", true);
      $("#msgarea").prop("disabled", false);
      $("#chatmsg").prop("disabled", false);
      $("#send").prop("disabled", false);
    };
  });

  socket.onmessage = function(event) {
    text = $("#msgarea").val();
    $("#msgarea").val(text + event.data + "\n");
  };

  $("#send").on("click", function(event) {
    msg = $("#chatmsg").val();
    if (msg != "") {
      socket.send(msg);
      $("#chatmsg").val("");
    };
  });
});

2022年7月7日木曜日

プログラミング本の難しさ

演習の準備をしていたら,教科書として使っている本に掲載されているプログラムのバグを発見してしまった.それがこちら.プログラムのとおり実行すると,こんなグラフが表示される.

おやおや?これ何さ.期待されている図は次のようなもの.インフォグラフィクスの事例である.

これ,扱うデータの問題で,書籍の説明が若干不十分だったことによる.筆者は「たまたまうまく動作するデータ」で検証してしまったのであろう.上の「なんだかよくわからない図」を,下の「まともな図」に直すにはどうすればよいか.実はコードの一行,一箇所だけ,手を加えればよいのだが,それも演習の課題にしてみよう.

環境を揃える難しさ

プログラミング指南本を執筆する際に,このような「環境を整える難しさ」を避けられない.とくに,バージョンが変わると仕様がコロコロ変わるような環境は要注意である.

私が過去執筆した某書籍,順を追ってアプリケーションを構築していくという建て付けになっており,当時の院生だったNさんも検証してくれて「これでエラーもなく完璧だ」と自身を持ってリリースした.ところが,すぐにプラットフォームのバージョンがアップデートされてしまい,また,ライブラリのバージョンもいろいろと変わって,書籍の説明どおりにやってもうまくいかないという状況になってしまった.

2022年6月24日金曜日

オンライン・プログラミング演習のその後

これまで何度か,オンラインでプログラミング演習を実施する方法について紹介してきた(末尾のリンクを参照されたい).学生の作業でトラブルが生じていないかどうか,教室では机間巡回して確認できるが,オンラインではそれができない.オンラインで学生の作業状況チェックを実現する方法として,OBSを使う方法や既存のサービスを使うもの,あるいは,Google Colaboratory(以下,Colab)のノートを共有して一覧表示させるために開発したMultiViewなど,いくつかのパターンを紹介した.

「オンライン・プログラミング演習がいよいよスタート」で紹介したように,Colabのノートを共有してMultiViewで閲覧しつつ,それぞれのプログラミングを指導するという方法でオンライン演習を10回ほど実施した.履修者でアンケートに応えてくれたのが11名と,サンプルサイズは小さいものの,一定の傾向は見られたので報告したい.

参加学生の評価

まず,Colabを用いたプログラミング演習について.「とても取り組みにくい」から「とても取り組みやすい」まで,5段階で評価してもらった.結果は次のグラフに示すとおりで,おおむね,取り組みやすいという感想であった.

続いて,MultiViewで皆の作業状況を共有することについて.こちらも「全く好ましくない」から「とても好ましい」までの5段階評価である.ひとり「全く好ましくない」に印をつけた学生がいたが,それ以外は好ましく感じてもらえているようである(次図).


そして,教室でやったほうがよいか,オンライン演習のほうがよいか.これは,なかなか難しい結果となった.ほぼ半数が「どちらでもよい」と答えており,2名が教室,4名がオンラインを希望という結果になった.キャンパスの立地条件と全学ゼミという性質から,どうしてもオンライン参加とせざるを得ないとの制約もあるため,やむなくオンライン参加という面もある.しかし,オンラインで気軽に参加できる良さと,教室で丁寧に学びたいというせめぎ合いもあるようだ.

ともあれ,手探りで始めたオンライン・プログラミング演習ではあれども,なんとか無事に離陸できたと安堵しているところである.

おまけ

本件は,2022年8月25日に開催される私情協の「ICT利用による教育改善研究発表会」で報告します.より詳しく知りたいかたは,そちらもどうぞ.

(参考)これまでの経緯

オンライン・プログラミング演習がいよいよスタート

オンライン演習までの(厳しい)道のり