Noratetsu Lab

動じないために。

2022年9月

2022/09/30

ツール製作日誌:トピック管理ツール

 4ヶ月ほど前に、GTD的な「プロジェクト」の名称はどうあるべきかという話題から(それには答えが出ていない)、そもそもタスクといわゆるプロジェクトはどういう関係にあり、どう管理したら良さそうかということを考えた。


 その後、自分なりにそれを体現したツールを作ってみた。例によって他の人が使えるように公開しているわけではないのだが、ツールの話を通してコンセプトを言葉にして書いておけたらと思う。

 さてツールの外観はこんな感じ。(例として書いてある項目は完全に適当に作ったものなので深く考えないでいただきたく。)

画像

 各エリアの意味は以下の通り。なお全てのToDoが「コンテクストごとのToDoリスト」のいずれかに配置される。そこに無ければ無いですね、ということである。
画像

 レイアウトは前に紹介した自作のタスク管理ツールと似ているがツール製作日誌:タスク&スケジュール把握ツール、プロジェクトの考え方などは大きく違っている。
 今回のツールは「Topic Manager」と名付けた通り、「トピック」をメインの単位としており、その下にまた「トピック」あるいは「ToDo」が属しているのが基本の形になっている。すなわちトピックとToDoの二種類のデータによって構成されているのだが、一方でトピックとToDoはプログラム上は同じ形のデータとして扱われており、ボタンひとつで切り替えることができる。今はこっちとして解釈する、という選択によって分けられているだけである。
 トピックには「企画」「日程」「目標」の三種類があり、それぞれ次のような意味合いを持たせている。

  • 企画:実行して成し遂げる必要のある、いわゆるプロジェクト
  • 日程:イベント・会合等の日にちをメモするもの(「締切日」は含まない)
  • 目標:こうしたい、こうなりたいという類の個人的かつ気分的な思い

 日程トピックに締切日は含まないというのは、締切を扱えないということではなく、個々のToDoに設定することができるので日程として用意する必要がないという意味である。またToDoには締切とは別に目標日を設定することもできる。
 以上を踏まえて、画面上の文字色や背景色による意味付けを説明すると以下の通り。なお右上のスケジュール欄の項目はマウスオーバーで日にちを確認できるようにしてある。

画像

 具体的にやるべきことを生み出す大本になっているのがトピックであり、ほとんどのToDoはいずれかのトピックに属し、また必要に応じてトピックの下にトピックが入れ子に作られる。トピックの粒度には決まりはなく、自分が思った大きさで自由に作る。形式的な整理のためにトピックを利用しても問題はない。
 親とするトピックはいつでも変更できるので、例えば「あ、こうしたいと私は思っているな」と思ったら目標トピックをとりあえず一番上の層(=親トピック無し)にパッと作ってしまう。
 他方、親トピックが存在しないToDoを作ることもできる。プロジェクトも目標も特にない、例で言えば「Yシャツのボタンつけ」のようなToDoだ。それは左のトピックリストの中には表示されず、右のToDo欄の中では背景色が灰色であることによって視覚的に区別される。やる必要はあることだが、何かを構築するピースにはなっていないToDoと言えるだろう。

 ところでトピックやToDoというのは具体的にどういうデータを持つものとして扱われているのか。それを伝えるために編集画面を載せることにする。(スクリーンショット内で編集画面の外が暗くなっているのは、そういうオシャレなプログラムとかではなく単に後から加工しただけ。)
 まずトピック。上から順に、トピックの名前、目的の記入欄、親トピック/ToDo指定(複数可)、種類等の選択欄、下位トピック/ToDoの一覧と作成、メモ欄(アウトライナー)となっている。

画像

 次にToDo。目的欄の下のあたりがトピックとは異なっている。各種の日付指定とコンテクストの選択ができる。
画像

 なんだかごちゃごちゃと欄があるが、別に必ず全部を使わなければならないわけではないし、実際半分以上はタイトルと親トピック、コンテクストの指定のみで済ませており、目的やメモを細かく書いてはいない(ToDoの例のようなスカスカの場合が多々ある)。必要な時に書けるためにあるのであって、書かなければならない欄ではない。書きたい時には書く欄があるというのが重要である。
 ただしあまりに自明な場合を除いて、何のためにやるのかを1行でいいから言葉にして書いておいた方が、迷走を回避できるし動機づけの強化にもなるのは確かである。欄があれば書きたくなるので、メモ欄とは区別してわざわざ目的欄を設置し、なるべく書くようにと自分を仕向けている。
 目的欄の下の日にち指定やチェックボックス、ラジオボタンの類は、項目数は一見すると多いが、要するに一覧画面でどこに表示されたら嬉しいかということなので、見た目ほど複雑ではないし億劫でもない。一覧画面の各分類の右上の+ボタンをクリックすればその欄が選択されている状態(コンテクストならコンテクスト欄にその項目が記入された状態)でToDoを作れるようにしてあるので、手間は感じていない。

 トピックとToDoの設定切り替えも自由にできるようになっている(前に作ったタスク管理ツールではそうできなかった)。一度ToDoとして色々設定したけどこれToDoじゃなくてトピックとして考えるべきだったわ、となったら右上のラジオボタンで切り替えればいいし、逆も同様にできる。切り替えても締切日等のデータは消えてしまわないようにしてあるので、元に戻すのにも不自由しない。
 左上、タイトル横の四角はチェックボックスで、チェックしてデータを保存するとアーカイブ送りになる。データは残るが、画面から表示は消えて見えなくなる。左下の削除ボタンも同様で、画面から消えるが一応データは残る。データベースになっているJSONの中に「アクティブ」「アーカイブ」「トラッシュ」の三つの箱がある状態である。

 ついでに自慢(?)すると、現時点で存在しているアクティブなトピックやToDoはうまいこと見出しをつけて加工し、mdファイルとしてLogseq用のフォルダ内に出力するようにしてあるので、(mdファイルを編集してもこのツールに反映されることはないが)他ツールとの間で横断的に情報を利用できるようになっている。
 アーカイブしたデータはTopic Manager内では表示されなくなるが、これも別途mdファイルに出力してあるので、これまでやり終えたToDoを確認するのは簡単だ。Topic Managerでは常に今とこれからに意識を向けられるようにし、なんとなくログを眺めるみたいなことにはならないようになっている。(なおconsole.logでデータベースの内容はコンソールに出力しているので、デベロッパーツール上でデータの確認自体はすぐできる。)

 このツールの一番の肝は、「目標トピック」という分類を作ったことにあるかもしれない。このツールには「やる必要のあること」だけでなく、「○○したいなあ」「~~な人間になりたいなあ」といった願望も書き込むことができる。背景色で視覚的に区別されているので、実際に遂行しなければならないものとの混在が気になることもない。(並び順も各階層で一番下になるようになっている。)
 トピックの下位にはToDoがあってもなくてもいい。つまり、トピックは「ToDoが発生しそうなトピック」でなくてよい。日程トピックについても、「とりあえず把握しておきたいけど実際には行かないかもしれない企画展」のスケジュールを書いておいてもいいのである。
 自分の行動を決定するために必要な内容がトピックとして並ぶのが大事なことで、「リストを空にしなきゃ」とか思う必要はない。むしろ、自分の願望を書いておく場でもあることから、空っぽになったら困るのである。トピックリストはタスクリストではないのでたくさん書いてあっていいのだ。
 感覚に従ってトピックを作っていくと、しばしば階層は複雑化する。そのせいでToDoが把握できなくなったら困るので、右側の欄でToDoはコンテクストのみに従ってフラットに並ぶようにしてある。こうすることで、自分の中の気持ちや問題意識に沿ってトピックが構造化されることがToDo管理の邪魔になってしまうことのないようにしている。今のところうまく働いている。

 あとは緊急・重要・要連絡・連絡待ちのToDoリストと、コンテクストごとのリストを「面」で一覧できるようにしているのも自分の中では重要なポイントだ。
 前に作ったタスク管理ツールについての記事ツール製作日誌:タスク&スケジュール把握ツール内でも、自分に必要なのは「細かくなくていいから全体がわかる形」かつ「一枚の面に整然と配置された状態」だということを書いたが、それを達成しているかどうかが私にとってはタスク管理ツールの価値の有無に直結している。そして世の中のタスク管理ツールはいまいちそうなっていなくて、カンバンツールもちょっと違う。列にはなっているが面にはなっていないからだろう。

 私自身はこれを自作したことで色々と楽になっているが、しかしこのツールはプロジェクトの全体像や自分の内面を俯瞰することを大きな目的としているので、仕事に追われに追われている人を救うための一助になるとは言い難い。「捌く」「こなす」ことを助けられるようには恐らくなっていないのである。
 そもそも全体像なんか関係なく目の前に発生するものを次々片付けないといけないような仕事では、トピックを立てるということに意味がないかもしれない。しかし逆に、そういう仕事の助けになるタスク管理ツールなら既に巷に溢れているような気がする。
 そして既存のタスク管理ツールがいずれも役に立たないほど多忙ならば、おそらくそれは多忙の度合いが激しすぎるので「ツールに入力する」ということがもうネックになってしまうだろうし、そうなると如何なるツールも力不足になるのではないかと思う。強いて言えば、音声入力で項目をスイスイ作って操作できるタスク管理ツールが存在したら少しは助かるだろうか。つまりは秘書であろう。

 多忙過ぎる人を救う手立ては私にはわからないのだが、とりあえず、タスク管理ツールが抱えている「タスクそのもの以外の情報を扱いにくい」という欠点を自分なりに解消し、自分の中にある情報を実現可能性・実現必要性に関係なく書き出せるようになり、そしてそれが「遂行しなければならないこと(いわゆるタスク)」の邪魔にならないように視覚的・位置的に区別されていることで、ごちゃごちゃあるのにごちゃごちゃしていないという形を作ることはできた。
 作って使い始めてから一ヶ月以上経っているが、今のところは「ここがネックなんだよなあ」といったことは感じていない。唯一スマホから編集できないのが玉に瑕だが(内容の確認だけなら上述したようにmdファイルを通じて可能)、スマホアプリを作れれば解決するよな~ということを考え始めているので、将来的には解消させられる可能性はある。が、そもそもそんなに欲していないので別にスマホからの編集機能は要らないかもしれない。

 ただ気をつけるべきは、全てのToDoがきちんとログとして残ることを期待してしまうことだろう。
 Topic Managerに書かずに片付けてしまったことを後から書くのかどうか。アーカイブのチェックを入れるのが遅くなった時に、厳密にいつやったことなのか確かにしようとするのかどうか。
 そういうものを必ず正しく書くと決めてしまうと、恐らく「後で書かなきゃ」が溜まり始めてしんどくなって続かなくなる。「Topic Managerのログは別に完璧なものではないが、おおよそこんな感じであったということがわかれば十分だ」というスタンスを守ることが大事だろうと考えている。
 

2022/09/29

【AutoHotkey】数字選択式の簡易ランチャーを作る2

前回の続きです。


前回作った簡易ランチャーは、実行ファイルのアドレスをリストにして、それを数字で選ぶ形式にしていました。
私自身はそれでも特に不満を覚えていませんでしたが、しかしアドレスでの表示はわかりやすいとは言いがたい感じがします。表示するのはアプリケーション名にして、数字を選ぶとアプリケーション名に紐付けられたアドレスによって実行する、という形にした方がいいかもしれません。

画像
任意につけたタイトル・説明を選択する形にする

まず改良前のコードをおさらいします。解説は前回の記事【AutoHotkey】数字選択式の簡易ランチャーを作るをご参照ください。

Ins::Return	;Insertを無効化  
Ins & A::	;Insertキー+Aで実行  
  Description := "アプリケーションを起動します。`n"  
  Array := ["C:\Program Files\Git\git-cmd.exe"  
    ,"C:\Program Files\Inkscape\bin\inkscape.exe"  
    ,"C:\Program Files\nexusfont\nexusfont.exe"  
    ,"C:\Program Files\Dropbox\Client\Dropbox.exe"  
    ,"Notepad.exe"]  
  Address := SelectText(Array,Description)  
  Run, %Address%  
  Return  
  
SelectText(Array,Description){  
  text := ""  
  count := 0  
  For index, element in Array  
  {  
      text := text . A_Index . ":" . element . "`n"  
      count += 1	;この部分は「count := A_Index」でも良いです  
  }  
  height := count * 18 + 170  
  Loop {  
    InputBox, num,,%Description%番号を選択してください。`n`n%text%,,500,%height%  
    If ErrorLevel <> 0  
      Return  
    If (num>0&&num<=count)  
      Break  
  }  
  Return Array[num]  
}  

表示名とアドレスの二つのデータを持たせるには、配列Arrayを二次元配列にすれば良いでしょう。(連想配列でも良いですが、今回はデータが単純なものなので見た目も処理も簡単な二次元配列にします。)

;変数Arrayを二次元配列にする  
Array := 〈〈"Git CMD","C:\Program Files\Git\git-cmd.exe"]  
  ,["Inkscape","C:\Program Files\Inkscape\bin\inkscape.exe"]  
  ,["NexusFont","C:\Program Files\nexusfont\nexusfont.exe"]  
  ,["Dropboxの同期を開始","C:\Program Files\Dropbox\Client\Dropbox.exe"]  
  ,["メモ帳","Notepad.exe"〉〉  

こうすると、配列の中身が配列ということになります。例えばArray[1]には配列["Git CMD","C:\Program Files\Git\git-cmd.exe"]が入っており、Array[1][2]の中身は「C:\Program Files\Git\git-cmd.exe」になります。

さて、次は関数SelectTextに改良が必要です。SelectTextはSelectTextで他の用途に使うので、今回の用途のためのものとして新たに関数SelectTitleを作ることにします。テキストそのものではなくタイトルを選択するという意味合いです。
ひとまずホットキー部分についてArrayと関数の変更を反映しましょう。

Ins::Return	;Insertを無効化  
Ins & A::	;Insertキー+Aで実行  
  Description := "アプリケーションを起動します。`n"  
  Array := 〈〈"Git CMD","C:\Program Files\Git\git-cmd.exe"]  
    ,["Inkscape","C:\Program Files\Inkscape\bin\inkscape.exe"]  
    ,["NexusFont","C:\Program Files\nexusfont\nexusfont.exe"]  
    ,["Dropboxの同期を開始","C:\Program Files\Dropbox\Client\Dropbox.exe"]  
    ,["メモ帳","Notepad.exe"〉〉  
  Address := SelectTitle(Array,Description)	;呼び出す関数をSelectTextからSelectTitleに変更  
  Run, %Address%  
  Return  

関数SelectTitleを作るために必要なことはごく簡単で、SelectTextをコピーして関数名を変えたら、ダイアログボックスに表示する値がArray[n][1]、アプリケーションの起動のためにRunコマンドに渡す値がArray[n][2]になるようにちょっと書き足せばよいだけです。

SelectTitle(Array,Description){  
  text := ""  
  count := 0  
  For index, element in Array  
  {  
      text := text . A_Index . ":" . element[1] . "`n"	;elementに[1]をつける  
      count += 1  
  }  
  height := count * 18 + 170  
  Loop {  
    InputBox, num,,%Description%番号を選択してください。`n`n%text%,,500,%height%  
    If ErrorLevel <> 0  
      Return  
    If (num>0&&num<=count)  
      Break  
  }  
  Return Array[num][2]	;Array[num]に[2]をつける  
}  

変わっているのは上から6行目と下から2行目の2箇所だけです。簡単ですね。

自分で説明を書き添えるなどして好きなように名付けることで、例えば「名前だけ見ても何のためのアプリケーションだったか思い出せない」という事態も回避できるでしょう。(私はフリーウェアを色々インストールしていてそういうことが割とよく起こります。)

おまけですが、前回「日時のフォーマットも複数パターン登録しておいて、前に書いた関数【AutoHotkey】あの手この手で日時を表示してみると合わせて呼び出せるようにしていたり」と書きました。具体的には以下のような形です。(今回作ったSelectTitleではなく前回のSelectTextを使ったものです。)

Ins & D::  
  text := "日時情報を挿入します。`n"  
  Array := ["yyyy/MM/dd HH:mm"  
    ,"yyyy/MM/dd"  
    ,"HH:mm"  
    ,"yyyy-MM-dd"  
    ,"yyyy_MM_dd"  
    ,"yyyy年M月d日"  
    ,"yyyy年M月d日H時m分"  
    ,"yyyy/MM/dd HH:mm:ss"]  
  var := SelectText(Array,text)  
  InsertDate(var)  
  Return  
  
InsertDate(format) {  
  FormatTime,TimeString,,%format%  
  InsertText(TimeString)  
}  
InsertText(Content) {  
  cb_bk = %ClipboardAll%  
  Clipboard = %Content%  
  Send, ^v  
  Sleep, 200  
  Clipboard = %cb_bk%  
}  

候補を一覧して、その中から選んで文字列を取得する、というのは色々使い途がありそうです。
 

2022/09/28

【AutoHotkey】数字選択式の簡易ランチャーを作る

AutoHotkeyでは、アプリケーションの起動をキーに割り当てることができます。アプリケーション起動のコマンド(Run)がスクリプトに用意されているので、それをホットキーで実行するように書けば良いということです。


これは便利だと思い、最初は「Insert+Wで○○起動」みたいな形で個別にアプリケーションを割り当てていました。
しかし、調子に乗って数を増やすとどれに何を割り当てたのか覚えていられなくて、だんだん億劫になっていきました。
加えて、ホットキーとして登録できる数はそう多くないという問題もあります。キーの組み合わせとして可能なものはたくさんあっても、実際に同時に押しやすいキーの配置というのは限られているからです。他に登録したいコマンドも色々あります。

ということで、キーひとつにリストの形でまとめてしまい、その中から指定してアプリケーションを起動する格好にしようと考えました。
完成形を先に見せるとこんな感じのものです。

画像
番号を打ち込み「OK」でその番号のアプリケーションを起動
このためには、ダイアログにテキストを複数表示して選択するための関数を作る必要があります。
しかしその前に、その関数(SelectTextとします)をどう使うことになるのかの全体像を先に書いておくことにします。

Ins::Return	;Insertを無効化  
Ins & A::	;Insertキー+Aで実行  
  Description := "アプリケーションを起動します。`n"	;説明文を用意して変数Descriptionに入れる  
  Array := ["C:\Program Files\Git\git-cmd.exe"		;アプリケーションのアドレスを配列Arrayに入れる  
    ,"C:\Program Files\Inkscape\bin\inkscape.exe"  
    ,"C:\Program Files\nexusfont\nexusfont.exe"  
    ,"C:\Program Files\Dropbox\Client\Dropbox.exe"  
    ,"Notepad.exe"]  
  Address := SelectText(Array,Description)	;関数SelectTextにArrayとDescriptionを渡し、返ってきた値を変数Addressに入れる  
  Run, %Address%	;変数Addressに入っているアドレスからアプリケーションを起動する  
  Return  

さて、関数SelectTextの中身に取り掛かります。必要なことは以下の4点です。

  • 渡した配列を元にダイアログに表示するテキストを作る
  • テキストの長さに応じてダイアログボックスのサイズを変える
  • 番号選択のダイアログボックス(InputBox)を用意し、入力が有効な値になるまでループさせる
  • 選択したテキストを返す
SelectText(Array,Description){	;テキストを入れた配列と説明文のテキストを渡す  
  text := ""  
  count := 0	;何行作ったかの記録用変数  
  For index, element in Array 	;配列の各要素について処理をする  
  {  
      text := text . A_Index . ":" . element . "`n"  
      count += 1	;この部分は「count := A_Index」でも良いです  
  }  
  height := count * 18 + 170	;ダイアログボックスのサイズを変更するためheightを計算  
  Loop {  
    InputBox, num,,%Description%番号を選択してください。`n`n%text%,,500,%height%  
    If ErrorLevel <> 0	;キャンセルしたら処理中止  
      Return  
    If (num>0&&num<=count)	;選択肢に存在する番号を入力した場合のみループから抜けて次に進む  
      Break  
  }  
  Return Array[num]	;番号で選択したテキストを返す  
}  

途中の変数countについては、処理の度に1を足すやり方でも、毎回A_Index(=現在繰り返しの何回目か)を代入して最新のindex番号が反映されるやり方でも結果は同じです。
あるいはループ内で取得せずに最初に「count := Array.MaxIndex()」の形でMaxIndex()メソッドによって取得してもいいだろうと思います(が、私自身このメソッドの理解の正確さに自信がないので素朴なやり方を記載しています)

私はアプリケーションの他に、よく開くファイル、よく開くフォルダ、AutoHotkeyの練習のために作ったミニゲーム類(ハイアンドロー(【AutoHotkey】ハイアンドローゲームを作ってみるなど)をそれぞれキーに割り当てて選択できるようにしています。
あとは日時のフォーマットも複数パターン登録しておいて、前に書いた関数【AutoHotkey】あの手この手で日時を表示してみると合わせて呼び出せるようにしていたり、Gitをコマンドで使う時によくカレントディレクトリにするアドレスをリストにしていたりします。
割り当てるキーの数が少なくて済むのと、何を登録してあるのかが一目瞭然であるところがメリットです。もちろん本当に頻繁に使うものは個別に割り当てた方が良いわけですが、そこまででもないものはひと手間増えても「見てわかる」のが楽ですね。

2022/09/23

ローカルのディレクトリの構造を大整理した

 ここ一週間くらいで、ローカルファイルのディレクトリ構成を大きく変更してファイルを整理した。ついこの間まで(唐突に)各所に記事を連投していたのに十日ほど前にあっさり途絶えたのはこの作業をやっていたからなのであった。


 ここで言うローカルファイルというのは、PCやスマホを使う中で作成したりダウンロードしたりで発生するファイル群のことを指している。自分が使っている端末の中にあり、かつコンピュータが読むものではなく、自分自身が開いて利用するタイプのファイルである。(普通ローカルファイルと言ったらプログラムが読むためのファイルも当然含むけれども、今言いたいのは人間用のファイルの話ということです。)

 フォルダとして括るべきプロジェクトがあればそれに関連ファイルをまとめれば良い、とシンプルに思いはするものの、普段PCやスマホを使っていて作成したり集めたりしたファイルはそううまく然るべきフォルダにまとめられない。困っているという話をそれほど聞かないけれど、みんな当たり前にうまくやっているのだろうか。自分は知らない常識がどこかにあったりするのだろうか? あるいは、Evernoteが綺麗に解決してくれたということか。
 もしかすると自分の混乱は私個人の独特な事情が災いしているということもあるのかもしれない。個人的な用途でOfficeファイルを作るということが結構あってOffice日誌:思想を自分の手に取り戻そうに以前書いた)、ローカルファイルがとにかくごちゃごちゃしやすい状態にあるのである。あとはデジタルノート系のフリーウェアやスマホアプリを試すことが多々あって、それぞれで生成したファイルも様々ある。更に諸々の趣味で作ったり集めたりする画像やファイルもなんだかんだ多岐に亘り、いつも悩みを抱えている。なるべくローカルファイルとして置いておきたいという願望がそれに拍車をかけている(例えばGyazoという便利なサービスを知っていつつも)
 そしてDropbox、Googleドライブ、OneDriveといった各クラウドのフォルダにもそれぞれファイルが置いてあり、使い分けとしてはおおよそ整理がついているものの、ディレクトリ構成の規格が整っていないせいで雑然としている感じが否めずにいた。

 長年迷走に迷走を重ねており、とはいえさすがにそう頻繁に変えてはよろしくないと思って前回の整理からは年単位で経過していたが、常に「なんか違うんだよなあ」と思って過ごしていた。
 今回の整理前は、昔Evernoteの使い方で見たような「連番+名称(だいたい英単語)」で順番をはっきりさせて十個くらいのフォルダを並べていた。「こういう感じのファイル群が上の方」みたいなニュアンスとしてはまずまず納得していたが、そもそも連番というのが私は気に食わないらしい。数字だけでなくアルファベットでも日本語でも同じことだが、順番をコントロールするための事務的な文字列が「名前」の欄にあるのが嫌なのである。Evernoteも自分が勝手にやり始めたこの手法の気に入らなさでフェードアウトへの一歩を踏み出したのに、また同じことをやっている。
 とりあえずこの時点では、設定っぽいファイルは上の方で、バックアップ類がそれに続き、日頃使っているファイルは対象ごとに分けて真ん中あたりで、集めたファイル類は下の方にあってほしいな~みたいなイメージを抱いていた。私にとっては位置のイメージが重要なようである。

 ところで近頃はHTMLやJavaScriptをいじっていることが多いので、htmlファイルを開いてぼけっとしていたことがあった。メインの内容はbodyタグ内に記述され、その上のheadタグにメタ情報の類が入っている。bodyタグ内にはheader、main、footerタグがある。navやarticle、section、asideについて調べたばかりでもあり、HTMLというのはうまく整理されているなあとかぼんやり考えていた。
 そこでハッと閃いた。設定っぽいファイルはHeadで、「使う」タイプのファイルはBodyと捉えればなんかスッキリする気がする。じゃあバックアップとかバッファー的位置づけのフォルダはFootということにしてみるか。「集める」タイプのファイル、HTMLで言えばインポートするcssファイルやjsファイル、表示する画像といったものの位置づけにあたる類のファイルは、また別にまとめた方が良さそうだ。
 「集める」ファイル群をまとめる言葉はHTMLのイメージからはうまく見つけられなかったが、とりあえずHead、Body、Footに合わせて四文字が良いかなと思い、安直にItemとすることにした。(英単語としてはちょっとずれているかもしれないが、ゲームに出てくる「アイテム」のニュアンスで採用した。)
 そしてそういう分類用のフォルダであることを示すために全部大文字にしてフォルダ名とした。

  • HEAD(設定に使うファイルやbatファイル、ユーザーガイドのファイルなど)
  • BODY(使うファイル全般)
  • FOOT(バックアップやバッファー)
  • ITEM(収集したファイル群や自分が撮った写真など)

 こんな感じ。これをローカルのユーザーフォルダと各クラウドに基本の形式として設置した。
 この時点で「使う」タイプのファイル以外のものにうまい名前がついてだいぶスッキリした。特にこれまでバックアップ類がなんとも微妙に漂っていたのが、もしもの時に支えてくれる縁の下的イメージに落ち着いたのは大きい。名前順に並べればBODY→FOOT→HEAD→ITEMの順になって上下の位置のイメージは崩れるが、名前にそのイメージが含まれているし、BODYが一番上に来るのはむしろありがたい。名前でイメージがわかるなら更新順にしても困らない。
 ちなみに、PCやスマホ内で自分自身が扱う種類のファイルを入れたフォルダの名前にも長らく納得がいっていなかったのだが、「」にすることで決着した。半角のアンダーバー1文字である。どう名前を付けても変にレトロニム的になって気持ちが悪く、何の意味も示さない且つ名前順で先頭に来る名前が良いと思って「これでいいじゃん」となった。何か支障が生じやしないかと心配しないわけではないが、もし何かあったらその時は改めることにする。(つまり、PCでは「C:\Users\ユーザー名\\_」、スマートフォンではルート直下の「」の中にBODYその他が入っている状態になる。)

 さて、使うファイルはBODYフォルダということにしたが、この中身は「明らかに他と混ざらない括り」によってそれぞれフォルダを作っている。要するにプロジェクト単位と言ってもいいかもしれないが、例えばGitのリポジトリごとのフォルダとか、小説を書くなら小説とか、絵を描くなら絵とか、そんな感じの括りである。
 ちなみについ数日前に閃いたばかりのフォルダ名に「Noting」がある。「ノーティング」は倉下忠憲氏が著書『すべてはノートからはじまる』で提示されていた概念で、そこから拝借した。例えばLogseqとかObsidianのルートディレクトリや、ノートとして作成したOfficeファイル類を、どういう括りでどこに置いたらいいのか心情として解決していなかったのだが、「要はノートである」ということでまとめてしまえばいいんじゃないかと気がついたのである。ひとつのフォルダにまとめられただけでなく、そこに相応しい名前を与えられたことに大きな意味がある。

 BODY内の各フォルダは、更に「COLLECT」「MAKING」「PROCESS」「RECORD」の四要素で区分することを基本とした。こちらは「これで統一する」というほど徹底しているわけではなく、経験的にこんな感じになっていることが多いなというところから必要に応じて作っている(特に創造的なものについて)
 それぞれ一応以下の意味合いである。名前は六~七文字のアルファベットにしようと思ってこうしているが、特にこだわりはない。

  • COLLECT(資料として必要なため集めたもの)
  • MAKING(作業しているファイル)
  • PROCESS(表に出すにあたり加工するための場)
  • RECORD(作業記録ややり取りの控えなど)

 こちらは先述の大きな四分類と比べると必然性が薄く、まだ括りや名称を変える可能性がある。そんなに「良いこと思いついた」感があるわけではないが、記録としてついでに書いておくかと思って書いた。

 今までの整理の試みと比べると自分の中で納得感があるが、果たしてどうなるか。

(なお、この整理をするにあたり「DiskInfo3」で解析結果を保存したほか、そのスクリーンショットをログとしてScrapboxに貼っておいたりした。こういう時はやはりScrapboxが便利である。)
 

2022/09/23

【AutoHotkey】取得した色情報をファイルに記録する

前回、カーソル位置の色情報を取得する方法について書きました。

後で活用するために、取得した色情報を記録しておきたいという需要が私の中にあるので、ローカルファイルにどんどん溜めていくことができるようにしたいと思います。


準備として、前回作った関数をちょっと書き換えます。処理に変更はありませんが、使い回したい部分を分離しています。

GetColor(RGB){	;取得するのはRGB値かカラーコードかを引数RGBにtrue/falseで渡して指定する  
  MouseGetPos, MouseX, MouseY  
  PixelGetColor, color, %MouseX%, %MouseY%, RGB  
  If(RGB){  
    R := "0x" . SubStr(color,3,2)  
    R += 0  
    SetFormat, integer, d  
    G := "0x" . SubStr(color,5,2)  
    G += 0  
    SetFormat, integer, d  
    B := "0x" . SubStr(color,7,2)  
    B += 0  
    SetFormat, integer, d  
    string := "rgb(" . R . "," . G . "," . B . ")"  
  }Else{  
    string := "#" . SubStr(color,3)  
    StringLower, string, string  
  }  
  Clipboard = %string%  
  Return  
}  
  
PickColor(){  
  MsgBox, 4, , RGB形式(10進数)にしますか?  
  IfMsgBox, Yes  
    RGB := true  
  Else IfMsgBox, No  
    RGB := false  
  GetColor(RGB)  
  MsgBox, %Clipboard% を取得しました。  
}  

前回はPickColorという名前の関数ひとつでしたが、色を取得する部分はGetColorという関数に分け、PickColorは取得形式の選択をしてからGetColorに選択を渡して色を取得する形になります。前回のPickColor関数を削除してこの二つに書き換えれば、前回同様PickColor()をキーに割り当てることで前回と同じ内容のプログラムが走ります。
今回はPickColor部分は使いません。記録用の別の関数を作り、その中でGetColor関数を実行することになります。

RecordColor(){	;新しく関数を作る  
  ;この中にGetColor(RGB)の実行を含む処理を書く  
}  

AutoHotkeyにはローカルのテキストファイルに追記するコマンドFileAppendがあります。「FileAppend, テキスト, テキストファイルのパス」の形でファイルに追記します。ファイルが存在しなければ新たに作成します。
(ちなみにテキストファイルの中身を取得するのはFileReadコマンドです。)
今回は、カーソル位置の色情報を取得して「カラーコード」「RGB値」「何の色か」「取得日時」をtsv(タブ区切りでデータを格納するテキストファイル)に記録したいと思います。RGB値にカンマを使用するため、csvではなくtsvにしています。
なお過程でクリップボードを利用することになるので、既にクリップボードに入っているデータを関数使用後も保持するように最初と最後に処理を加えます。

RecordColor(){  
  cb_bk = %ClipboardAll%	;発動時にクリップボードに入っているデータを取っておく  
  Sleep, 200		;念の為一瞬待つ  
    
  GetColor(false)	;カラーコード取得  
  colorH := Clipboard	;クリップボードにGetColorの結果が入っているので別の変数colorHを作って移す  
  GetColor(true)	;RGB値取得  
  colorI := Clipboard	;同様に変数colorIにGetColorの結果を移す  
  FormatTime,TimeString,,yyyy/MM/dd HH:mm	;好みのフォーマットを指定して現在日時を取得し、変数TimeStringに入れる  
  InputBox, string,,%colorH% %colorI%`n何の色ですか?	;色情報の説明を自分で入力して変数stringに入れる  
  If ErrorLevel <> 0	;InputBoxダイアログでキャンセルすると一連の処理を中止する  
    Return  
    
  ;`t(タブ)を挟んだデータを作り、指定したtsvファイルに追記する  
  FileAppend, % colorH . "`t" . colorI . "`t" . string . "`t" . TimeString . "`n", C:\PickedColor.tsv  
  ;(必要なら)書き込んだ内容をMsgBoxで表示して確認する  
  MsgBox, % colorH . "," . colorI . "," . string . "," . TimeString . " を記録しました。`nC:\PickedColor.tsv"  
    
  Clipboard = %cb_bk%	;元々クリップボードに入っていたデータを元に戻す  
}  

お試しになる場合は「C:\PickedColor.tsv」の部分を都合が良い場所に書き換えてください。なおファイルが存在しなければ自動で作成するので、予めファイルを作っておく必要はありません。
これで、RecordColor()を実行する度にtsvファイルに取得した色情報が蓄積していきます。

画像
実行すると入力ダイアログが表示される
画像
指定したファイルにタブ区切りで記録される

ちなみに私は、このtsvをJavaScriptで取得してHTML上に色情報の一覧を背景色付きで表示する、といったことをしています。ここではそのやり方に触れることはしませんが、そんな感じでAutoHotkeyでローカルファイルに書き込むということには色々使い途があり得ると思います。

2022/09/22

ゼロからのプログラミングについての所感

 プログラミング経験がないところからJavaScriptの勉強をスタートしておよそ八ヶ月になります。
 まだわからないことはたくさんありますが、難題だったコールバックや非同期処理をある程度使えるようになったり、Node.jsを活用できるようになったり、他の言語(AutoHotkeyとか)を多少書けるようになったり、コマンドプロンプトでGitを利用できるようになったり、プログラミングやコマンド周りの壁をいくつか乗り越えたところで、ここまでの所感をまとめておきたいと思います。(経緯についてご質問のメッセージをいただいたこともあり)


 これまでに他の記事に書いたことと重複する部分もありますが、あまりわかりやすく書いてこなかったと思うので、書き直すことにも意味があるだろうと思って改めて書くことにします。

 プログラミングをほぼゼロから学ぶにあたり、障害になっていると感じたことが三つほどあります。

  • 日本語じゃない
  • 人間用の言語じゃない
  • プログラミング的発想を普段そんなにしていない

この三点です。
 まず一つ目、「日本語ではない」ということ。英語が得意な方にはあまり共感されないかもしれませんが、プログラムを書くために用いる言葉が母語ではないために、「言葉に慣れていない」ということを「プログラミングが難しい」と混同する可能性が結構あると思います。出てくる単語出てくる単語、いちいちイメージが浮かばなくて「調べないと動きが想像すらできない」ということになるわけです。それはプログラミングの難しさとは全然関係ないことですが、プログラミングに挑戦しようとして直面することなので、プログラミングの壁として立ちはだかるように感じてしまうところがあります。
 二つ目の「人間用の言語でない」というのは、特に所謂文系人間にはつらいところかもしれません。自然言語を基準にして考えると、プログラミング用の表現というのは全然直感的ではありません。形式はもちろんのこと、括弧の種類やピリオド、カンマ、コロンなど、日常的に目にしていても用法について考える必要があまりない記号類にも、それぞれ固有の意味があり、それらを正しく使わなくてはなりません。更には、「等しい」「または」「かつ」などを示す際にも馴染みのない記法をあれこれ覚える必要があります。そのうち慣れますし慣れれば自然と使えるようにはなりますが、最初の最初は外国語をイチから勉強するがごとき苦労があるように思います。
 これら二点については、私の場合はプログラミングの勉強を始める前に数カ月間CSSの習得に取り組んでいたことで、少しハードルが下がったように思います。CSSというのは指令を出して何かを動かすものではないので、プログラミング的発想の足掛かりになったわけではありませんが、コンピュータ的な記述方法で書いてコンピュータに解ってもらう、ということの訓練になりました。後から振り返ってそうだったのだと思ったことですが、人工言語に馴染む一歩として大きな役割を果たしていたと思います。

 三つ目、「プログラミング的発想を普段していない」というのは、プログラミング教育を受けていないわれわれ世代には少々高い壁になっているかもしれません。「~~と書けば……という結果が得られる」ということをいくら覚えても、それをどう組み合わせて何をする必要があるのかがわからなければ(思いつかなければ)、ツールとして使えるものにはなっていかないのです。
 この点について「こうすれば良いですよ」と語れることは私にはないのですが、私が心がけたことは、再現したいものについて「実際に何がどうなることになるのだろう」ということを熟考することです。人間の感覚で考えると自分にとっての「意味」に引きずられがちですが、「情報」がどう「動いていく」必要があるのか、どうすればコンピュータが解釈し得るのか、それを「こうすれば情報はきちんと然るべきところに辿り着くぞ」という確信を得るまで考える。
 プログラミングをする人は当たり前にやっているであろうことですが、プログラミング未経験者としては少し負荷が高いことに思えます。でも、確信を得た時の「これでいけるはずだ!」という発見の喜びは他ではなかなか得難い知的感動だと思います。

 私自身が具体的に勉強をどうしていったか、ということもまとめておこうと思います。完全に独学でやった場合のことであり、これが良いということではありません。

  • 初心者向けの解説サイトで基礎を覚える
  • その時点でできそうなことを色々やる(おみくじとかじゃんけんとか、ごく単純なメモツールとか)
  • やりたいことを実現できるらしいメソッドを検索して試す
  • ツールとして使えるものを色々作ってみる
  • その後で本を読む
  • メソッドを全部解説しているサイトを「読む」

 勉強を始める前に解説本をちらっと見たのですが、手を動かしていない状態でいきなり読んでもわけがわからないという感じがしたので、初動で本を活用するのは諦めました。(読んでイメージできる人は本の方がサイトより体系的で俯瞰しやすいので役に立つと思います。)
 ということで、Web上に情報を提供してくれていることに感謝しながら、Web上で見てWeb上のオンラインエディタで書いて試す、ということを繰り返して勉強しました。解説サイトとエディタが一体になっている学習サービスも色々あるかと思いますが、私は記事の連載の形で書かれたものを見ながらTypeScript: プレイグラウンド - TypeScriptとJavascriptを探求するためのオンラインエディタで試しました。(なおTypeScriptの記法に沿っていないJavaScriptのコードはエラーが表示されることがしばしばありますが、実行すれば動きます。私は最初の最初TypeScriptから始めたのでこちらをよく利用しました。)
 六番目の「メソッドを全部解説しているサイトを『読む』」というのは、メソッドを調べる時に辿り着くサイト(特にJavaScript | MDN)を、検索して辿り着いた部分だけでなく、書かれているメソッド全部について一通り読んでいくということです。私もまだ途中ですが、だいぶ解るようになってから読むと「なんとそんな便利なメソッドが!」と感動できます。

 勉強する中で、自分の中でポイントになったことが三点あります。

  • 慣れないものは慣れるまで繰り返す
  • よくわからないものはとりあえずスルーする
  • 仕組み上難しいことは最初から諦める

 声を大にして何度でも強調したいのが、「慣れるまで繰り返してから次に進むべし」ということです。初心者向け解説サイトはとてもわかりやすく書いてあるので、読むだけで「なるほど」と思うことができます。しかし、じゃあそのページを閉じて今見たものを再現できるかというと、これがなかなかそうはいきません。そうできないと実際にプログラムは書いていけないわけですが、「なるほど」感の割に全然そうできないのです。「alert("Hello World!");」に「この程度のこと理解できないはずがない」と思っても、最初の最初はそれだけのことが見ないでは再現できなかったりするわけです。
 特に私が繰り返したのは「条件分岐」と「forループ」です。JavaScriptでツールを作る時、素人がやりたい範囲でツールらしさを構築する上で必要なことというのはほとんど「条件分岐」と「forループ」でまかなえるように思います。一方、素人目には一見して仕組みが分かりづらいものでもあります。If分岐は難しくないですが、do...whileやswitch文はちょっと考えないと捉えにくいように思いますし、forループに至ってはforに続く括弧の中に初期化式・条件式・加算式とあって一読で意味を理解するのは困難だと思います。とりあえず定型文として覚えて使えるようにすることが大事です。

 次に「よくわからないものはスルーする」ということが私の中では重要でした。今しがた書いたforループの中身も、もちろん最初に理解してしまえればそれが一番良いのですが、とりあえずは「ループ処理ができる」ということさえ獲得できればそれ以上のことはよくわからなくていいとしました。
 他にはアロー関数やコールバックがスルー対象でした。今はもうある程度わかるようになりましたが、最初はなんのこっちゃでしたし、まあ使わなくてもツールは作れるわけです。使えるようになってみると確かに劇的に軽やかにはなるのですが、それで「できること」が劇的に増えたのかというと、それはそうでもありません。
 他にも、私がやりたいことをスパッとやってくれるメソッドが言語の中に既に存在しているのに、その存在に気づいていなかったり、説明を読んでも使い方が理解できなかったりして、わざわざ自分で関数を作っていたということがかなりたくさんあります。多分プログラミングの巧者にコードを見られたら馬鹿だなあと笑われるような記述が山程あったと思いますが、でもプログラムは動くのです。jsファイルの中身さえ見られなければそれっぽいツールができているわけです。
 私の目的は綺麗なコードを書くことではなく、自分がほしいツールを自分で作って動かすことです。コードがどれだけダサくて汚くても、動けば私の勝ちなのです。そしてコーディングがわかってきたら、後から書き直して整えていけばいいことだと思います。いずれにせよ金をもらってやっている職業プログラマーじゃないのですから、そんなに構える必要はないと思っています。

 最後の「仕組み上難しいことは最初から諦める」というのは、なぜ自分でツールを作るのかということに関わることでもあります。プロが作ったちゃんとしたツールを使えるなら、そっちの方が断然賢明な選択です。プログラミングは魔法のようですが、多少齧った程度では全てを解決してくれはしません。
 難しいことは難しいがゆえに今ここで例示して説明することもできないのですが、例えば一つだけ挙げると、クラウド連携は早々に諦めました。Web技術(HTML、CSS、JavaScript)によってWebブラウザをツール化する時、セキュリティの都合上ローカルファイルにアクセスできないので代わりにDropboxやGoogleドライブと連携するという手があるのですが、初心者には些かハードルが高いと感じました(Dropbox連携はちょっとやりましたが、ログイン状態を維持する処理で挫折しました)
 ローカルファイルを扱えない=データをブラウザの中の保存領域に置くしかないというのはファイルの取り回しとして大きな制限なので、クラウド連携についてはこうできたらいいなと早いうちから夢想することになると思いますが、日頃種々のデジタルツールで当たり前にやれていることである一方自力で実現するのは容易ではありません。(なお、Electronというフレームワークを習得すればローカルファイルを扱えるようになるので、そこまでいければクラウドにファイルを置くということが自動的に可能になります。)

 プログラミング言語を習得するにあたっての所感はこんなところですが、そもそもどうしてプログラミングをやりたいのかということはよく考える必要があるかもしれません。
 私の動機は以前書いたので細かくは繰り返しませんがJavaScript日誌:一歩進んだら十回足踏みせよ、具体的にやりたいことがあったのと、「プログラミングができない」という劣等感を解消したかったというのが大きい理由です。記事を今読み返して「そうだったか」と思いましたが、ツールづくりをするということはそんなに考えていませんでしたね。まずどこまでできるものなのか全く想像がついていなかったので、何も思い描けていなかったのです。
 具体的に作りたいものが先んじてあればモチベーションとして強力なのは確かです。しかし、漠然と「プログラミングができない自分」に不満があるというだけでもチャレンジする意味は大いにあると思います。ただ大事なのは、いつも楽しくやることだと思っています。
 私はこの八ヶ月間ずっと楽しくJavaScriptのコードを書いていました。説明を読んでもちんぷんかんぷんなことはたくさんあって、「説明がわからない自分」に多少もやもやすることはありましたが、でもまあ、その記述がわからなくても、できるようになったことがたくさんある喜びと比べればそんなのは些末なことです。理解が及ばないがゆえにへんてこな自作関数山盛りでごちゃごちゃやっているダサさ極まるコードでも、自分でアウトライナーを作れたことの方が私にとっては重要です。
 ちゃんとやろうと身構えて取り組んでしまったせいで変にコンプレックスを増加させてしまったら元も子もありませんから、気楽なゲーム感覚でやるのが良いと私は思います。(過去にそうして失敗したという経験談です。)

 なお、いつも自分の挑戦について「自作ツールを作っている」というふうに表現してきましたが、「ツール」という言葉に含まれ得る範囲の中ではごくごく一部のことしかやっていないので、今度から「自作デジタルノート」と書くことにします。メモアプリやアウトライナー、付箋ツール、単語帳などのような、文字情報を自分が理解・操作・処理しやすいように加工したりピックアップしたりする種のツールということです。
 「なぜ自分でツールを作るのか」という問いを先程ちらっと書きましたが、私にとっては「自分が求める形で情報を表示してくれるデジタルノートが世の中に存在しないから」ということがその理由です。デバイスを横断する連携の可否よりも、自分が見たい形で見たいものを表示してくれること、自分が書きたい形で書ける欄を生成してくれること、その二つが遥かに重要なのです。
 デジタルノートツールを作るというのは、ノートの内容を格納したデータベースと、その内容をHTML要素に然るべき形で配置したりHTML要素からデータベースに内容を保存したりするスクリプトを用意することで成り立っています。逆に言うと「それだけのこと」なので、見た目の自由自在感の割に、必要な技術はそんなに多くはありません。プログラムの動きを確認しやすいこともあり、初心者でも気軽に挑戦できることだと私個人としては思っています。
 

2022/09/12

読みたくなるのは「きちんと加工された自慢話」

こちらの記事へのレスポンスです。


本題に入る前に、いくつか興味深い点があったので自分に照らしてみた感想を書いていこうと思います。

私は「この空間で情報を利用するならば、自分もまた何かしらの情報を投下しなければならない」という気持ちを持っていました。言い換えれば、インターネットを利用するというのは受信と発信がセットになった行為なのだ、という理解があったわけです。
私にはあまりこの感覚はありませんでした。「言いたい」という気持ちがある人間なので結果的に何かしらの発信もしていましたが、「受信したからには」的な認識は持っておらず、考えてみると少し不思議…というか、自覚していない歪みがあるような気がしてきました。
もしかすると、テレビによって、情報というのは「撒き散らされているもの」だという感覚が染み付いてしまっていたのかもしれません。誰かが作ってくれているということを理解はしつつも、だから自分もという当事者意識には繋がっていませんでした。もちろん、インターネットの黎明期を知らないということも大きな要因かとは思います。

このことを逆に見れば、私がブログを書くときには、「読みやすく、面白く、役に立つ」文章を意識していないことになります。だからこそ、ブログは楽しく書けるのでしょう。Twitterも然りです。「本」を書くときに一定のつらさ(あるいは負荷)がかかるのは、そうした指針を持っているからだとも言えます。
私は以前noteの書き手になろうとして(敢えて「noteの書き手」とします)、大変に辛い気持ちになって結局ほぼ何もしないまま挫折したのですが、まさに「読みやすく、面白く、役に立つ」文章というような指針が「縛り」となって書くことを辛いものにしてしまったように思います。
倉下さんと違って、題材探しの時点からその条件に合うかどうかを前提にしようとしていたのが苦しみの根本原因でしょう。自分のフィールドが先に決まっていてこそ、指針が「道標」として働いてくれるのだと思いました。

本題に入りますが、倉下さんはブログを書く動機について「率直にその精神性について書いてしまえば、たぶん「自慢」なのだと思います。」と書いていらっしゃいました。その上で、

純然に利他性を持って人を楽しませようとしているのではありません。まず自分の言いたいこと・自慢したいことがあり、それを──ある種の「加工」を経て──楽しめるように表現すること。このねじくれた動機が、私のブログにはありそうです。
とあります。更にタイトルには「こんがらがった動機」とあり、最後は「ちょっとやそっとでは動かせそうもないので、なんとかそれと付きあっていくしかなさそうです。」と仰っているので、なんとなく「なるべくならそうでない方がいいのになあ」というような気配を纏っている感じがしました(倉下さんがそうお思いになっているのだろうと感じたというよりは、言葉の印象がそういうベクトルだと感じた、という意味です)
このお話には実のところ意表を突かれた気分になったのですが、私の中では「こんがらがった」り「ねじくれた」りするどころか、なんというか、人と人が話をする上でのベーシックな動機のように思えていました。
そしてうまく加工してやろうということをも含めて、「どうだ!」とか「私はここにいるぞ」とか、そういう気持ちが支えているのではないかと。つまり一貫して、他ならぬ自分の存在、自分の体験、自分の心がけ(楽しませようという気持ちも含め)を他者に配り届けてしまおうという思いによって文章が書かれるように思いました。報酬を得ないのならば、それ以外の動機で書く方がむしろこんがらがっている気さえします。
この要素の中で、存在のアピールが強い自覚があれば「結局自慢なのだ」と感じ、体験のアピールが強い自覚があれば「自分語りに過ぎない」と感じ、心がけのアピールが強い自覚があれば「自分をコンテンツ化しているのさ」と感じる、みたいなことはあるのかもしれませんが(私は自分語り型です)、仮にこういう傾向の違いがあったとして、多分どの場合もそんなに違わないことをやっているのだろうなと思っています。

tasuさんも倉下さんも「誰かの役に立つものを分かりやすく伝えたい」とはあまりお思いになっておられないとのことですが、私も基本的にそう思っていません。(でも役に立ちそうなものを発見してしまったら分かりやすく伝えようとは思います。お二人もそうなのではと、勝手ながら思っております。)
翻って読み手の身になって考えた時、「読む」という体験として楽しいものというのは、「役に立つもの」より誰かの「どうだ!」だと感じています。「情報を得る」という作業の中では「役に立つもの」を欲することにはなりますが、別にそれは楽しいものというわけではないような気がします。TwitterやInstagramなどが面白いのも、やはり「どうだ!」に感心したり驚き呆れたりできるからだと感じています。
こう考えると、「書きたいもの」と「読みたいもの」は、楽しさを求める上では一致しているように思えます。読んで楽しいものにするためには「加工」の技術が求められるところに「書く」ということの難しさがあると思いますが、自分の何かを読み手に掴ませてやろうという動機自体は、利己的なようでありながら実はエンターテインメントの基本のような気もしています。多分、とても大事な動機なのだと思います。
 

2022/09/11

のらてつの茶の間とは

 「のらてつの茶の間」というミニブログを作った(半年前に)。位置づけとしてはTwitterとScrapboxの間のものである。
 Twitterでは時々茶の間茶の間と言っているのだが、それが何なのかということについてきちんと書いたことはなかったので、今更ながら記事にしておこうと思った。読んでもらいたいというより、言語化したものを置いておきたいという意味で書いた記事になっている。

※2023/02/08追記
(リニューアルして極限までシンプルな形に変更しました。現在は記事内の説明とは全く異なったものになっています。)


 見た目はこんな感じ。

画像

画像

 スマートフォンの画面だとちょっと狭くて苦しいが、サイドメニューがないと自分が使いづらいため強引に2カラムにしている。

 形としてはGitHubのpages機能を利用して個人サイトを公開したという格好だ。中身のデータをJSONファイルで公開リポジトリにプッシュ(≒アップロード)し、それを訪問者がHTMLを開いた時点で読み込んで表示している。
 各ノートはタグで整理され、本文内のリンクで他のノートやキーワードとネットワークを形成する。タグとリンクの挙動はそれぞれ結構こだわっている。

 冒頭でも書いたように一応これを「ミニブログ」と位置づけているが、発信用であると同時に、自分が自分のノートを管理しやすいようにと作ったシステムでもある。

 Scrapboxがあるのにわざわざ別のものを自作した理由のひとつとして、ついさっき書いたように「タグ」と「リンク」の機能がそれぞれ欲しいということがある。タグは「こういう領域の話」、リンクは「これについての話」というふうに使い分けている。私の中では、どうもこの二つに混ざってもらっちゃ困るようなのである。
 スクリーンショット内にあるように、タグの方には「生きる」とか「前に進む」とか、ベクトルを表してはいるが何の話なのかは示していないワードを設定している。これらのワードは話の対象を表してはいないので、基本的に本文中には登場しない。
 自分が自分の書いたものを探す時、キーワードで検索することもあるが、「こういう気持ちでこういう方向性の話を書いたんだよなあ」という形で覚えていることが多々あり、それはキーワードでは検索しづらい。なので「雰囲気」とか「ベクトル」で括っておくものが必要なのだ。
 それはリンクと一緒では駄目なのだろうか、と考えると、「一緒にするのは無理ではないが、一緒にすることにメリットはない」というのが今のところの結論である。自分の頭の中で「こういう領域」と「これについて」は混ざっていないので、わざわざごちゃごちゃにする必要がない。それぞれ同じワードが使われる可能性はあるが、それらは明らかに違う意味合いで使っているのである。

 一方リンクは、本文中に含まれる類のキーワードである。Scrapboxはハッシュタグを兼ねているが、茶の間では本文にあるワードやフレーズをダブルブラケットで括る形のみをリンクとして使っている。
 リンク欄には、本文内にリンクがあるキーワードと、開いているノートに対するバックリンクのほか、Scrapboxを参考に「同じワードをリンクしているノート」もリンク欄に子項目として表示している。
 更に、「このノートのタイトルについて、リンクでない形で言及しているノート」と「他のノートではリンクにしているがこのノート内ではリンクでない形で言及しているワード」を表示するようにしている。それぞれ「非明示的バックリンク」「非明示的フォワードリンク」と呼んでいる(一般的にどう呼ぶのかよくわからなかった)
 非明示的バックリンクは、ScrapboxにはなくてRoam ResearchとLogseqにはある機能だ。茶の間では、例えば「JavaScript」というワードを開いてみると、明示的にリンクを貼ってJavaScriptの話を積極的にしているノートと、話の流れでJavaScriptというワードも出したという程度のノートとが、色で区別された状態で両方列挙されている。つまり検索結果が関連度別に並んでいるということなのだが、これが結構便利である。
 非明示的フォワードリンクの方は、はっきり機能として搭載しているツールはあまり見たことがない。でもずっと欲していた機能で、Scrapboxにはそれがないから「全部見直してリンク貼り直すか…?」とかいう血迷ったことが頭をよぎったりする。もちろんScrapboxも「とりあえずキーワードは全部リンク」ってやっていけば良いだけの話ではあるのだが、個人的には「他のページではこの本文中にあるこのキーワードをリンクにしていますよ」というのが表示されていると非常にありがたい。ので、作った。
 ちなみに、どこかでリンクにしているキーワードの一覧を「リンク済みキーワード一覧」としてサイドメニュー上部のピン欄から確認できるようにしている。便利というほど実用性を実感しているわけではないが、全体を把握できる安心感があるので気に入っている。

 ツールとしての茶の間の存在意義はこんなところだが、もうひとつ、敢えてこういう形でミニブログを作った理由がある。急な方向転換で恐縮だが、先に進む前に次の記事をお読みいただければと思う。文量としては短めでするっと読めるものなので、構えなくて大丈夫です。はいカチッとクリック。あるいはペタッとタップ。

 この「個人の総合的な表現」という言い回しに甚く納得したのだが、人間の表現には話題や意味でスパスパ切り分けるわけにはいかない領域というものがあると思う。というか、そうやって分けてしまうとそこには現れることができなくなる種類の何かがある。その人が有機的に生きている存在であることを感じさせる何か、と言えるかもしれない。
 一行目にある、

このブログには「Diary」というカテゴリがあって、もちろんそれは「日記」を意味しているのだが、実際の「日記」が書かれることはまずない。
というのが、多分「ブログあるある」なことだが考えてみると深い話でもある。
 記事内では「単なる『日記』をブログに上げることは、昨今なぜか抵抗がある。」と続くので、ブログ内の「日記」の実態が日記として自然な形から離れているという意味合いが含まれていると思うが、本当の意味で日記らしくはないものが「日記」というカテゴリに収められるということの意味を考えると興味深い。
 おそらくは、カテゴライズできないもの、もっと言うとカテゴライズ「したくない」ものが、「日記」というラベルをつけて語られるのではないだろうか。この時、日記とは「私の日記」である。つまり、「私」という文脈に沿って読んでもらいたい、他の具体的な何かのキーワードにまつわる文脈によって恣意的に読まないでほしい、そんな願いがうっすら込められているように思う。「日記」というラベルを付けられた記事は、その筆者から分離し得ないものであり、「情報」として利用されることを筆者に全く望まれていないものなのだと思う。

 しかし筆者が望まなくとも、他人が部分を分離して語ることが容易な形態ならそうされてしまうリスクがある(現に今私がTak.さんの記事についてそうしているように)
 ということは、とりあえず「分離して語ることが容易な形態」をやめればよいのではないか。そう思って、記事ごとにURLが発行されることがない形式のものを作ってみた。
 もちろん、そうするとブログと比べて失われるものはたくさんある。何人が読みに来たかわからないし、そもそも読みにくいので読まれにくいものになる。感想をもらえることもないだろう。言及しにくい形にしているのだから、当然言及されないのである。広告収入が入ることもない(そもそもブログでもそれは得ていないけれども)。ただただ「書きたいから書いた」だけがそこに蓄積する。
 まあでも、それでいいのです。そうでは困るものは他の場所に書けばよく、ここにはこの面倒臭さに付き合ってくれる人だけ(数にして3人もいなさそうですが)立ち寄ってくれればいい。つまり私を「情報」ではなく「生き物」として見ようと思ってくれる人向けの場所。ゆえに情報として役に立つようなことは何も書いていないし、啓発的なことも書いていないし、早い話が自分以外の存在にとって「良いこと」は何もない。でも生きている自分にとってのリアルを表現している。
 そういう意味でこれに「茶の間」と名付けている。誰でも読める形にしているけれど、外に向かっているわけではない。「開いている」が、「出る」ことはしていない。影響力を持たないのだから、誰かに存在を咎められる謂れもない。
 昔は「それがブログ」だったかもしれないけれど、ソーシャルな種々のサービスによってそうではなくなってしまった。読まれにくくなった割にリスクは高い。数字で言えば可能性はほぼゼロに等しいとしても、いつ何の咎で日本中から責められるかわからないのは恐ろしい。日本中まで炎上しなくたって、どこの誰かもわからない人間に突然謎の憤怒をぶつけられたらたまったものではない。
 そんな近年の殺伐とした時流の中で少しでも自由と安心を守るための試みとして、今更素人がHTMLやJavaScriptをちまちま書いて作ったのがこれなのである。

 なのである、と格好つけて言ってみたものの、正直そんなに活用できているわけでもない。というのも、長らく更新手段の整備に迷走していたからだ。
 最初はmdファイルを編集の場にして、茶の間の1ノートを1ファイルとしてデータを作り、Pythonを使ってmdファイル群からJSONファイルを生成していた(その時点ではNode.jsがわかっていなかったのでPythonを使っていた)。自分の中では「バラバラのmdファイルからミニブログのデータを作れる!」ということが画期的だったのだが、ちょっと他の作業との都合上、茶の間用のmdファイルの更新が捗らなくなっていった。
 次は自作のカード式アウトライナーで編集できるようにした。これも仕組みとしては自分の中で超すごいことだったのだが、mdファイルでの管理以上に更新の手は鈍った。うまくいくと信じて疑っていなかったので、この失敗は割とショックである。
 しばらく停滞が続き、やっと最近になって、茶の間自体にエディットモードを作って茶の間のビューの中で編集できるようにした。このためには、半年も前、つまりコーディングの技術が非常に未熟な頃に作ったスクリプトをほぼ全部書き直す必要があり(何がなんだかわからなくなっていたので)、大変な手間がかかった。しかしその甲斐あって、更新がものすごく楽になった。

 そんな感じで劇的に編集しやすくなり、ようやく軌道に乗りつつある、それに伴ってか、Noratetsu Lab(このブログ)noteの更新頻度も急に増している。
 ひょっとすると、茶の間自体が人に読まれることはどうでもよく、それ以外のものをどんどか生み出せるようにするための助走として私にとって重要なものなのかもしれない。
 

2022/09/11

【AutoHotkey】カーソル位置の色情報を取得する

AutoHotkeyでカーソル位置のカラーコードを取得できると知り、なんて便利なのかと感動しました。これまでChrome拡張や画像編集ソフトの機能で取得していましたが、アプリケーションに依らないホットキーでできてしまうなら作業がずっと楽になります。


色情報の取得に使うのはPixelGetColorコマンドとMouseGetPosコマンドです。

MouseGetPos, MouseX, MouseY	;マウスカーソルの位置情報を取得して変数MouseX,MouseYに入れる  
PixelGetColor, color, %MouseX%, %MouseY%, RGB	;指定した位置の色情報を変数colorに入れる  
;例えばnoteのロゴの緑を取得してみると変数colorには「0x41C9B4」が入ります  

取得した情報をクリップボードに入れれば良いのですが、このままの形だとちょっと使いにくく感じます。なので文字列操作のコマンドをいくつか使って形を変えたいと思います。
まず16進数のカラーコード6桁にしてみます。16進数であることを示している「0x」を取り除くだけなので、これはとても簡単です。

;先頭に#をつけない時  
string := SubStr(color,3)  
  
;先頭に#をつける時  
string := "#" . SubStr(color,3)  

SubStr()メソッドは、SubStr(変数,開始位置,文字数)で、指定した変数の指定した位置から指定したサイズの文字列を取り出します。文字数指定を省略すると、開始位置以降全部を取得します。
今度はRGB値に変換してみます。R値、G値、B値それぞれを取り出してSetFormatコマンドで10進数に変換し、「rgb(r,g,b)」の形の文字列を作ります。

r := "0x" . SubStr(color,3,2)	;R部分の2桁を取り出し、16進数の数値であることを示す「0x」を頭につける  
r += 0				;SetFormatを発動させるために、値の変わらない数値演算をする  
SetFormat, integer, d		;変数rの中身を10進数に変換する  
  
g := "0x" . SubStr(color,5,2)  
g += 0  
SetFormat, integer, d  
  
b := "0x" . SubStr(color,7,2)  
b += 0  
SetFormat, integer, d  
  
string := "rgb(" . r . "," . g . "," . b . ")"	;変数stringに文字列を作って入れる  

SetFormatコマンドは直前の数値演算結果のフォーマットを変更してくれるものですが、「数値演算」が行われていないといけないので、0を足すという特に意味のない計算をしています。
なお「integer」は「整数値」、「d」は「10進数」を意味しています。10進数を16進数にすることもできますし、小数値のフォーマットもできます。

さてカラーコードとRGB形式の両方を取得できるようになったところで、関数にまとめたいと思います。

PickColor(){  
  MouseGetPos, MouseX, MouseY  
  PixelGetColor, color, %MouseX%, %MouseY%, RGB  
  
  MsgBox, 4, , RGB形式(10進数)にしますか?  
  IfMsgBox, Yes  
    RGB := true  
  Else IfMsgBox, No  
    RGB := false  
  
  If(RGB){  
    r := "0x" . SubStr(color,3,2)  
    r += 0  
    SetFormat, integer, d  
    g := "0x" . SubStr(color,5,2)  
    g += 0  
    SetFormat, integer, d  
    b := "0x" . SubStr(color,7,2)  
    b += 0  
    SetFormat, integer, d  
    string := "rgb(" . r . "," . g . "," . b . ")"  
  }Else{  
    string := "#" . SubStr(color,3)	;「#」をつける時はこっち  
    ;string := SubStr(color,3)		;「#」が要らない時はこちらに変更  
  }  
  
  Clipboard = %string%  
  MsgBox, %string% を取得しました。  
  Return  
}  
  
vk1D & C::PickColor()	;無変換キー+Cで発動(その時点のカーソル位置の色を取得)  

私の場合はカラーコード6桁とRGB形式とどちらが欲しいかがその時によって違うので、その都度ダイアログでどちらで取得するのか選択できるようにしましたが、必要な形式が決まっているならそうする必要はないですね。引数で分岐してもいいですし、そもそも両方作る必要がない人もいるでしょう。
どのタイミングでどう指定して分岐させるのかということは数本前の記事でちょっと書きました。コーディングが不慣れな人は良かったらこちらをどうぞ。

2022/09/10

【AutoHotkey】ハイアンドローゲームを作ってみる

AutoHotkeyのコードの書き方に慣れるために簡単なプログラムを作ってみよう第二弾。


前回はおみくじとじゃんけんを作ってみました。

今回はトランプのハイアンドローゲームを作ります。

ルールは単純で、山から取り出された1枚のカードに対して、次に取り出されるカードの数字がそれより高いか低いかを当てるものです。
一番高いのがA、一番低いのが2です。引き分け(同じ値)は勝ちにも負けにもなりません。
出来上がりはこうなります。ダイアログに従って入力したり答えたりしてゲームが進みます。

画像

1か2で選択肢を入力
画像

結果が表示されます
画像

継続するか否かを選択
画像

終えた時点の記録が表示されます

さて、まずトランプのデータを作ります。各カードのデータとして必要なものは、柄(クラブ、ダイヤ、ハート、スペード)とランク(2~A)、ランクの順番(1~13)です。(ハイアンドローはAが強く2が弱いので2~Aの順番で作ります。)

;配列で柄とランクのデータを用意する  
suits := ["クラブ","ダイヤ","ハート","スペード"]  
rank := ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]  
cards := []    ;各カードのデータを入れる配列を用意する  
  
Loop {  
  ;全ての柄について繰り返し処理  
  suit := suits[A_Index]  ;A_Indexは現在のループが何回目かという数字が入った変数  
    
  Loop {  
    ;各柄の中で全てのランクについて繰り返し処理  
    cards.Insert(Object("suit",suit,"rank",rank[A_Index],"num",A_Index))  ;各カードのデータを配列に追加する  
    If (A_Index >= 13)  
      Break    ;13枚処理した時点で繰り返し終了  
  }  
  
  If (A_Index >= 4)  
    Break    ;柄4種類処理した時点で繰り返し終了  
}  

処理を終えた時点で、配列cardsには、suitキーに柄、rankキーにランク、numキーに順番(=カードの強さ)がプロパティとして格納されたオブジェクトが入っています。例えば、52枚中の14番目に処理されている「ダイヤの2」なら、次のように値が入っているということになります。

cards[14].suit = ダイヤ  
cards[14].rank = 2  
cards[14].num = 1  

このトランプを生成する一連を含めて関数を作ります。他のトランプゲームも作るかもしれないので、引数でゲーム名を指定してゲーム用の別の関数を呼び出せるようにします。(英語で「トランプをする」は「play cards」ですが、日本人的にひと目でわかるように関数名は「playTrump」とします。)

playTrump(game) {  
  suits := ["クラブ","ダイヤ","ハート","スペード"]  
  rank := ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]  
  cards := []  
  Loop {  
    suit := suits[A_Index]  
    Loop {  
      cards.Insert(Object("suit",suit,"rank",rank[A_Index],"num",A_Index))  
      If (A_Index >= 13)  
        Break  
    }  
    If (A_Index >= 4)  
      Break  
  }  
  
  If(game="HighAndLow")  
    HighAndLow(cards)  ;引数にトランプのデータを渡し、ハイアンドローゲームを呼び出す  
}  
  
vk1D & H::playTrump("HighAndLow")  ;無変換キー+Hでハイアンドローゲームを発動  

それではこれからハイアンドローゲーム本体を作ります。必要な処理の流れは以下の通り。

  • 乱数を使い最初の1枚をランダムに選び、そのカードのデータは山から除く
  • ダイアログで予想が「ハイ」か「ロー」かを入力させる(有効な値になるまで入力を求める)
  • 再び乱数を使いランダムに1枚選んで山から除き、先に選んだカードの強さと比較する
  • 「ハイ」「ロー」の予想と合っていればwin回数を加算、引き分けならdraw回数を加算する
  • 結果に応じたテキストをダイアログで表示する
  • まだ続けるかをダイアログで質問し、続けるなら2に戻り、続けないなら全体の結果を表示

こう書くと多いですね。それぞれの工程で処理は5行前後になります。

HighAndLow(cards){  
  title := "ハイアンドロー"	;ゲーム名を変数titleに入れておく(ダイアログのタイトルに設定する)  
  
  ;1.乱数を使い最初の1枚をランダムに選び、そのカードのデータは山から除く  
  deck := cards.Clone()	;playTrump内で作ったトランプのデータを複製して使う(複製しなくてもいいと思うけど念の為)  
  Random, r,1,52	;52枚のうち何枚目を選ぶかを乱数で決める  
  dealt := deck[r]	;配列deckのr番目のデータを変数dealtに入れる  
  deck.Remove(r)	;配列deckから今引いた1枚のデータを取り除く  
  
  win := 0	;何回勝ったかを記録する変数winを用意しておく  
  draw := 0	;何回引き分けだったかを記録する変数drawを用意しておく  
  
  Loop {  
    count := A_Index	;プレイ回数を変数countに入れておく  
  
    Loop {  
      ;2.ダイアログで予想が「ハイ」か「ロー」かを入力させる(有効な値になるまで入力を求める)  
      InputBox, s,%title%,% "場のカード:" . dealt.suit . "の" . dealt.rank . "`n`n数字を入力してください。`n1:ハイ`n2:ロー"  
      If ErrorLevel <> 0  
        Return		;キャンセルの時はゲーム自体を終了する  
      If (s = 1 || s = 2)  
        Break		;有効な値が入力された時は質問の繰り返しから出る  
    }  
  
    ;3.再び乱数を使いランダムに1枚選んで山から除き、先に選んだカードの強さと比較する  
    rest := 52 - A_Index	;残りの枚数を計算して変数restに入れる  
    Random, r,1,rest		;残りの枚数の中から1枚選ぶ  
    next := deck[r]		;今選んだデータを変数nextに入れる  
    deck.Remove(r)		;今引いたカードを山(配列deck)から取り除く  
    diff := dealt.num - next.num	;先に選んだカードと今選んだカードの差を計算して変数diffに入れる  
  
    ;4.「ハイ」「ロー」の予想と合っていればwin回数を加算、引き分けならdraw回数を加算する  
    If ((s == 1 && diff < 0)||(s == 2 && diff > 0)) {	;予想が当たっている時  
      text := "Win!"  
      win := win + 1	;win回数を加算する  
    } Else If (diff == 0) {	;引き分けの時  
      text := "Draw"  
      draw := draw + 1	;draw回数を加算する  
    } Else {			;予想が外れている時  
      text := "Miss..."  
    }  
  
    ;5.結果に応じたテキストをダイアログで表示する  
    MsgBox,  0,%title%,% "場のカード:" . dealt.suit . "の" . dealt.rank . "`n引いたカード:" . next.suit . "の" . next.rank . "`n`n" . text  
    dealt := next	;基準となるカードを新たに引いた方に変更する  
    If (A_Index >= 51)  
      Break	;山札がなくなるまでプレイしたら繰り返しから出る  
  
    ;6.まだ続けるかをダイアログで質問し、続けるなら2に戻り、続けないなら全体の結果を表示  
    MsgBox, 4,%title%,% "現在" . A_Index . "回`n`nもう一度やりますか?`n(残り" . rest - 1 . "回)"  
    IfMsgBox, No  
      Break	;続けないなら繰り返しから出る(続ける場合はLoopの頭に戻って繰り返し)  
  }  
  ;全体の結果を表示  
  MsgBox,  0,%title%,% count . "回の挑戦で" . win . "回勝ちました。(引き分け:" . draw . "回)`n(勝率:" . Round(win / (count - draw) * 100) . "%)"  
}  

もっとスマートになる余地があるかもしれませんが、現時点の私の力量ではこんな感じです。ひとまず動けばいいのです。
説明部分を省き、playTrump関数と合わせて整理すると次のようになります。以下の部分をahkファイルにコピペして無変換キー+Hでハイアンドローゲームをプレイすることができると思います。

vk1D & H::playTrump("HighAndLow")  ;無変換キー+H  
  
playTrump(game) {  
  suits := ["クラブ","ダイヤ","ハート","スペード"]  
  rank := ["2","3","4","5","6","7","8","9","10","J","Q","K","A"]  
  cards := []  
  Loop {  
    suit := suits[A_Index]  
    Loop {  
      cards.Insert(Object("suit",suit,"rank",rank[A_Index],"num",A_Index))  
      If (A_Index >= 13)  
        Break  
    }  
    If (A_Index >= 4)  
      Break  
  }  
  If(game="HighAndLow")  
    HighAndLow(cards)  
}  
  
HighAndLow(cards){  
  title := "ハイアンドロー"  
  deck := cards.Clone()  
  Random, r,1,52  
  dealt := deck[r]  
  deck.Remove(r)  
  win := 0  
  draw := 0  
  Loop {  
    count := A_Index  
    Loop {  
      InputBox, s,%title%,% "場のカード:" . dealt.suit . "の" . dealt.rank . "`n`n数字を入力してください。`n1:ハイ`n2:ロー"  
      If ErrorLevel <> 0  
        Return  
      If (s = 1 || s = 2)  
        Break  
    }  
    rest := 52 - A_Index  
    Random, r,1,rest  
    next := deck[r]  
    deck.Remove(r)  
    diff := dealt.num - next.num  
    If ((s == 1 && diff < 0)||(s == 2 && diff > 0)) {  
      text := "Win!"  
      win := win + 1  
    } Else If (diff == 0) {  
      text := "Draw"  
      draw := draw + 1  
    } Else {  
      text := "Miss..."  
    }  
    MsgBox, 0,%title%, % "場のカード:" . dealt.suit . "の" . dealt.rank . "`n引いたカード:" . next.suit . "の" . next.rank . "`n`n" . text  
    dealt := next  
    If (A_Index >= 51)  
      Break  
    MsgBox, 4,%title%,% "現在" . A_Index . "回`n`nもう一度やりますか?`n(残り" . rest - 1 . "回)"  
    IfMsgBox, No  
      Break  
  }  
  MsgBox, 0,%title%,% count . "回の挑戦で" . win . "回勝ちました。(引き分け:" . draw . "回)`n(勝率:" . Round(win / (count - draw) * 100) . "%)"  
}  

他にも作り方は様々あり得ると思います。自分ひとりのためのプログラムなら、自分が動かしたいように動けばそれでオッケーと思っています。
 

管理人

アイコン画像

のらてつ Noratetsu

キーワード

このブログを検索

検索

ブログ アーカイブ

2025
2024
2023
2022
2021