キー割当変更などが可能になるWindows用アプリケーション。
キー割当を変更したり(caps lockキーにbackspaceを割り当てるなど)、ショートカットキーを新たに設定したり、キー押下時に自分で書いたプログラムを実行させたりすることができる。
公式サイト:AutoHotkey
公式ドキュメント:Quick Reference | AutoHotkey
Wiki:AutoHotkey Wiki
- AutoHotkeyの活用に関して書いた記事をまとめたマガジン
動じないために。
キー割当変更などが可能になるWindows用アプリケーション。
キー割当を変更したり(caps lockキーにbackspaceを割り当てるなど)、ショートカットキーを新たに設定したり、キー押下時に自分で書いたプログラムを実行させたりすることができる。
公式サイト:AutoHotkey
公式ドキュメント:Quick Reference | AutoHotkey
Wiki:AutoHotkey Wiki
Backlinks
他の「Application」カテゴリの語句
他の「Programming」カテゴリの語句
前回の続きです。
前回作った簡易ランチャーは、実行ファイルのアドレスをリストにして、それを数字で選ぶ形式にしていました。
私自身はそれでも特に不満を覚えていませんでしたが、しかしアドレスでの表示はわかりやすいとは言いがたい感じがします。表示するのはアプリケーション名にして、数字を選ぶとアプリケーション名に紐付けられたアドレスによって実行する、という形にした方がいいかもしれません。
まず改良前のコードをおさらいします。解説は前回の記事(【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%
}
候補を一覧して、その中から選んで文字列を取得する、というのは色々使い途がありそうです。
AutoHotkeyでは、アプリケーションの起動をキーに割り当てることができます。アプリケーション起動のコマンド(Run)がスクリプトに用意されているので、それをホットキーで実行するように書けば良いということです。
これは便利だと思い、最初は「Insert+Wで○○起動」みたいな形で個別にアプリケーションを割り当てていました。
しかし、調子に乗って数を増やすとどれに何を割り当てたのか覚えていられなくて、だんだん億劫になっていきました。
加えて、ホットキーとして登録できる数はそう多くないという問題もあります。キーの組み合わせとして可能なものはたくさんあっても、実際に同時に押しやすいキーの配置というのは限られているからです。他に登録したいコマンドも色々あります。
ということで、キーひとつにリストの形でまとめてしまい、その中から指定してアプリケーションを起動する格好にしようと考えました。
完成形を先に見せるとこんな感じのものです。
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点です。
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をコマンドで使う時によくカレントディレクトリにするアドレスをリストにしていたりします。
割り当てるキーの数が少なくて済むのと、何を登録してあるのかが一目瞭然であるところがメリットです。もちろん本当に頻繁に使うものは個別に割り当てた方が良いわけですが、そこまででもないものはひと手間増えても「見てわかる」のが楽ですね。
前回、カーソル位置の色情報を取得する方法について書きました。
後で活用するために、取得した色情報を記録しておきたいという需要が私の中にあるので、ローカルファイルにどんどん溜めていくことができるようにしたいと思います。
準備として、前回作った関数をちょっと書き換えます。処理に変更はありませんが、使い回したい部分を分離しています。
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でローカルファイルに書き込むということには色々使い途があり得ると思います。
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形式とどちらが欲しいかがその時によって違うので、その都度ダイアログでどちらで取得するのか選択できるようにしましたが、必要な形式が決まっているならそうする必要はないですね。引数で分岐してもいいですし、そもそも両方作る必要がない人もいるでしょう。
どのタイミングでどう指定して分岐させるのかということは数本前の記事でちょっと書きました。コーディングが不慣れな人は良かったらこちらをどうぞ。
AutoHotkeyのコードの書き方に慣れるために簡単なプログラムを作ってみよう第二弾。
前回はおみくじとじゃんけんを作ってみました。
今回はトランプのハイアンドローゲームを作ります。
ルールは単純で、山から取り出された1枚のカードに対して、次に取り出されるカードの数字がそれより高いか低いかを当てるものです。
一番高いのがA、一番低いのが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でハイアンドローゲームを発動
それではこれからハイアンドローゲーム本体を作ります。必要な処理の流れは以下の通り。
こう書くと多いですね。それぞれの工程で処理は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) . "%)"
}
他にも作り方は様々あり得ると思います。自分ひとりのためのプログラムなら、自分が動かしたいように動けばそれでオッケーと思っています。
Twitterのコミュニティ(知的好奇心向上委員会)でキーバインド(ホットキー、ショートカットキー)が話題になっていたので、そういえばショートカットキーを足すことはあっても「大本のキー配列を変えてしまう」ということは考えたことなかったな、と思い早速チャレンジすることにした。
Windowsを使っているのでAutoHotkeyというものを導入した。
公式ドキュメントとWikiは難解すぎて何言ってるのかわからないレベルだが、世の中には親切に知見を書いてくれている人がたくさんいるので、そういう人々の導きによって必要な設定は特に苦労なくできた。
参考にした記事・サイトやひとまずの設定はScrapboxにまとめている。
あれこれ設定して使ってみたところ、確かに便利である。
何よりありがたいのが、「うっかり押すと面倒くさいキー」を封じられることだ。例えばCapsLockとかCapsLockとかCapsLockとか。他にはNumLockとInsertも反応しないようにした。
注意が必要なこととして、CapsLockなどのLock系キーは他のキーとそもそもの挙動が違っているらしく、例えばCapsLockをCtrlに置き換える、ということはレジストリを弄らないとうまくいかないらしいということがある。慣れない領域の話なのでよく解っていないのだが、多分「同時押し」が必要な修飾キーとして使うのが無理ということなので、他の役割なら持たせられる。最初はCapsLockを単に封印していたが、途中でBackspaceを割り当てることにした。まあまあ便利である。
修飾キー(Shift、Ctrl、Altの類)を独自に増やせるというのも大きい。既存の修飾キーはWindowsや各種ソフトによってショートカットが割り当てられていて、自分で設定するとそれらと干渉する可能性が高い。しかし、他のキーを修飾キー化してしまえば、それがショートカットキーとして登録されている可能性はほぼゼロなので、何も気にせずに自由に設定できる。私はとりあえず無変換キーとAppキー(コンテキストメニューを出すキー)を修飾キー化してみた。変換キーも修飾キーの候補だったが、そちらは今のところCtrlを割り当てている。
更に、AutoHotkeyは任意のキーに他のキーの役割を持たせるというだけに留まらず、スクリプトで相当複雑なことができるらしい。言語としてはそんなに難しくはなさそうだが、自分が慣れているJavaScriptとは違う部分もあちこちにあるので、馴染むにはちょっと時間がかかりそうだ。
そもそも「こうできたらいいのに」という願望が特になかったので、実現すべきものを思いついていない。でもこれから何か思いついた時に、それを叶わぬ願いなどではなく「スクリプトを書けば実現できるもの」と思えるのは、気持ちの面で随分違ってくるように思う。
そんな感じで便利なのだが、これまでの長い歳月で両手にはデフォルトのキー配列に適応した動きが染み付いている。変更した配列に従えば小指や手首に負担がかからないとわかってはいても、反射的に従来どおりの無理な動きをしてしまう。自分の手がリマップされるのにはそれなりの時間が必要そうである…。
あと、やはり後から被せた形であるがゆえにAutoHotkeyがちゃんと効かない時が偶にある。ほとんどは大丈夫だが、メモリに余裕がなくなった瞬間に変になったりする気がするので、そういう状況ではちょっと注意が必要かもしれない。まあPCのスペックが高ければ問題ないのかも。
先程スクリプトの話をちらとしたが、文字列をそのまま貼り付けたいということから関数を使う必要が生じ、その延長で簡単なコードを書けるように色々試行錯誤してみた。そしてどうせならと、文章化してnoteにいくつか記事を投稿した。
noteへの投稿は実に一年半ぶりである。noteに投稿していたのはこのブログを始める前のことなので、文章を書いて人の目のあるところに投稿するということに対する考え方が今とは随分違っている。一言で言うと、昔はものすごく構えて書いていた。
当時の自分とはどういうものであったかというのは何度かこのブログ内に書いたかと思うが、フェードアウトした理由を簡単にまとめるなら「noteに何かを投稿しようとしている時の自分が嫌になった」ということになるだろう。
どういう頻度で書くべきか、どういう長さで書くべきか、どういう内容を書くべきか、どういう文体で書くべきか、そういったことをぐるぐる考えていた。考えて悩んだだけで何も実行できていないのだが、とにかく「こうではいけない」という自己認識で苦しんでいた。
そういう悩みはもう捨ててしまった――むしろ捨てて書いたものによって出会いを得た――ので、noteという場に対する考え方も変わってきた。まあどうせ「読まれる」ことは期待できないのだから、テキトーに書いていいだろう、という気分になっている。テキトーというのはぞんざいという意味ではなく、「自分が良いと思えば良いだろう」ということだ。どうするのが"適切"か、ということを考えるのはやめた。
noteを「ホーム」にしようとしていた時はひたすら辛かった。でももうホームは別にあるので、noteは自分にとって大した意味を持っていない。だから逆にサラッと書けるし、多分その方が読み手にとっても読みやすく有用な記事になるという気がしている。
AutoHotkeyのコードの書き方に慣れるために、簡単なプログラムを作ってみます。
私はJavaScriptは少し齧っていますが、プログラミングが得意なわけではないので、新しい言語だとよちよち歩きからのスタートです。どうせなので、他のよちよち歩きの人の参考になればと書いておくことにします。
まずはおみくじ。乱数とIf分岐を使います。
Fortune() {
FormatTime,TimeString,,yyyy年M月d日 ;変数TimeStringに指定したフォーマットで現在日時を代入する
Random, r,1,4 ;変数rに1から4までの範囲の乱数(整数)を代入する
If r = 1
Result = 大吉
Else If r = 2
Result = 吉
Else If r = 3
Result = 中吉
Else
Result = 凶
MsgBox, %TimeString%の運勢は`n%Result%です ;ダイアログに結果を表示する
}
vk1D & F::Fortune() ;無変換キー+F
前回(【AutoHotkey】あの手この手で日時を表示してみる)、日時を入力するにあたってIMEのオンオフがどうこうということを書きましたが、今回はダイアログの中に表示するので貼り付け用の関数は必要ありません。
Randomコマンドは、「Random, 変数,最小値,最大値」の形で変数に乱数を入れます。今回は変数をrにしましたが、rの部分は何でもいいです。
AutoHotkeyは文字列と変数名の区別の仕方がちょっと独特に思えます。JavaScriptとは違うので少し混乱しました。文字列と数字の型の区別もありません。
現時点で自分がわかったことをまとめますが、理解が不十分な可能性があります。この記事は個人の日記として読んでください。
;「=」で代入する
A = 文字列 ;「文字列」という文字列をAに代入
A = 123
A = %B% ; 変数Aに変数Bの値を代入(変数は%で挟む)
;「:=」で代入する
A := "文字列" ;「文字列」という文字列をAに代入(""で囲む)
A := 123 ;数字はそのまま
A := B ;変数Aに変数Bの値を代入(%で挟まない)
;Ifコマンド(括弧なし)
If A = 文字列 ;囲まない
If A = %B% ;%で挟む
;If文
If (A = "文字列") ;""で囲む
If (A = B) ;%で挟まない
;コマンド内
MsgBox, 変数Aの中身は%A%です ;変数を%で挟む
MsgBox, % "変数Aの中身は" . A . "です" ;最初に「% 」を書いておくと文字列の方を囲む形になる
なお、エスケープ文字は「\(バックスラッシュ)」ではなく「`(バッククォート)」です。
上記のおみくじは細かくIf分岐しましたが、これだとすごく垢抜けない感じがします。選択肢が増えていくと大変です。
なので、If分岐ではなく配列を使った形に書き換えてみます。
Fortune() {
FormatTime,TimeString,,yyyy年M月d日
array := ["大吉","吉","中吉","凶"]
Random, r,1,4
MsgBox, % TimeString . "の運勢は" . array[r] . "です"
}
vk1D & F::Fortune() ;無変換キー+F
いくらかシュッとした感じがしますね。
なおJavaScriptでは添字(array[i]のiの部分)が0始まりですが、AutoHotkeyでは1始まりのようです。なので、array[1]に「大吉」が入っているということになります。
次はじゃんけんを作ってみます。
Janken() {
InputBox, hand,じゃんけんゲーム,どの手を出しますか?(数字を半角で入力してください)`n1:グー`n2:チョキ`n3:パー
If ErrorLevel <> 0 ;キャンセルを選択した時はReturn
Return
If hand = ;空欄のままOKした場合もReturn
Return
Random, r,1,3 ;1から3までの乱数
array := ["グー","チョキ","パー"]
If (hand = r)
Result = あいこ
Else If (hand = r - 1 || (hand = 3 && r = 1)) ;「||」はOR、「&&」はAND
Result = 勝ち!
Else
Result = 負け…
MsgBox, % "結果は" . "`nあなた:" . array[hand] . "`n相手:" . array[r] . "`n`n" . Result
MsgBox, 4,,もう一度やりますか?
IfMsgBox, Yes
Janken()
}
vk1D & J::Janken() ;無変換キー+J
ちょっと複雑になったような感じがしますが、やっていることはほぼ既に登場したことの組み合わせです。
これでいつでもおみくじを引けるしじゃんけんで遊べます。…というのは大してありがたいことではありませんが、乱数と条件分岐とダイアログでの入力が使えればその組み合わせでかなり色々なことができるので、自由度は大きく上がったように思います。
AutoHotkeyを導入したよ、という話を前の記事でしました。
あるキーに別のキーの役割を持たせるというだけでも、キーボードの操作感は随分違います。
長年の「慣れ」があるので、便利なはずの新たなキー配置を使いこなせるようになるまでにそれなりの時間が必要になりそうですが、適応できればこれまで小指や手首にかかっていた妙な負担もなくなり、文章を書くことが肉体的に楽になるであろうと思います。多分馬鹿にならない違いです。
しかしAutoHotkeyにできることはキー配列の置き換えに留まりません。例えば、関数を使って複雑な機能を各キーに持たせることもできるのです。
あまりにも多くのことができてしまうので、プログラミングの知識の乏しい私には到底全貌を見渡すことなどできませんが、ごく簡単なものなら少しの理解で活用することができます。
シンプル且つ自分の中で極めて大きい需要のあるものが、任意の文字列をぱっと入力できるようにする、ということです。
これまでは単語登録で何とかしてきたわけですが、候補の選択をするのが少し煩わしい場合もあり、キーを二つ三つ打鍵するだけで良いのならそちらの方が楽です。
参考にしたのは以下の記事です。
InsertText(Content) {
cb_bk = %ClipboardAll%
Clipboard = %Content%
Send, ^v
Sleep, 200
Clipboard = %cb_bk%
}
こちらは大変に便利です。特にIMEのオンオフに左右される日本語話者にとっては、IMEの状況がどうであれ決まった文字列をそのまま入力できるのは非常にありがたいものです。アルファベットなら押すべきキーの打鍵を順番に再現すればいいだけでも、日本語ではそうはいきません。
更にこのコードは、クリップボードを利用して実装されていますが、元々入っていたデータを取っておいて最後に元に戻すので、発動前のクリップボードの中身を保持できるところが良いです。
コードの詳しい解説は上記の記事内にあるので、是非読んでみてください。
なお「Sleep」の値を私は100に設定していますが、もう少し短くてもいいのかもしれません。確実に動かすためには、気にならない範囲でなるべく大きく取った方がいいかも、とは思います。
この関数を利用して、例えば以下のように設定しています。
AutoTrim, off ;変数代入時の空白文字自動除去をoff
::h1::
InsertText("# ")
Return
これの意味は、「h」「1」を続けて打ってEnterかSpaceかTab、あるいはいくつかの記号のいずれか(具体的には-()[]{}':;"/,.?!)を打つと、入力した「h1」は消えて代わりに「# 」が記述されるということです(ホットストリングと言います)。
IMEのオンオフには関係なく発動するので、日本語入力中でも半角記号を楽に打つことができます。
なお、最初の「AutoTrim, off」は、関数に代入する文字列の両端の半角スペースを反映させるためのものです。これを最初の方に書いておかないと、例えば上記の例なら「#」だけしか入力されません。
Trimしてくれた方がありがたい場合もあると思いますが、今回に関してはそうされない方が嬉しいのでoffにします。
同じようにして、h2やh3も設定します。こうしておくとMarkdownの編集が楽になります。
::h2::
InsertText("## ")
Return
::h3::
InsertText("### ")
Return
さて、この関数をほんのちょっとだけ応用したいと思います。例えばこんな関数を作ってみます。
InsertBracket(Content) {
InsertText(Content) ;先述の関数を中に組み込み、同じ引数(Content)を渡す
Send,{Left} ;←キーを自動で打鍵する(キャレットが括弧内に入る)
}
;この関数をホットキーとして設定する
vk1D & ]::InsertBracket("【】") ;無変換キー+]で【】を入力し、キャレットを括弧内に入れる
これは以下のように書くのと同じことですが、複数の括弧について設定するなど同じ形を二回以上用いる場合は、ごく簡単なものでも関数にした方が便利かと思います。
vk1D & ]::
InsertText("【】")
Send,{Left}
Return
プログラミングに縁のなかった人は、こういう「ちょっと足す」「ちょっと変える」を試して色々な関数を作ってみてもいいと思います。後から「関数にするほどでもなかった」と思ったら解体すればいいだけです。
もう一段階、If分岐も使って日時の入力に応用したいと思いますが、それは次回にします。
なおWikiに詳しい解説がまとめられています(が、プログラミング的なものに慣れていないとちょっとハードルが高いように感じます…)。
自分が参加しているTwitterコミュニティで話題になっていたので、AutoHotkeyというものを導入してみました。Windowsで、キーボードの各キーの役割を他のものに変えたり、新たな機能を付与したりするツールです。
そういった設定変更というのは、当たり前の人にとっては超当たり前で当たり前でない人には全く当たり前でないという、両極端になりがちなタイプのものだと思います。この記事は当たり前でない人に向けて書いているものです。
具体的な導入方法については先人たちが詳しく書いてくれているので、たまたま私が参考にしたものを貼っておきます。検索すれば色々出てくるので自分にとってわかりやすい記事を探してみてください。
AutoHotkeyを入れてまずやったことは、うっかり押すと混乱が生じるキーの無効化と、よく使うのに端にあるキーの機能を別なキーに割り当てることです。
;.ahkファイルの中身
NumLock::Return ;NumLockを無効化
vkF0::Return ;CapsLockを無効化
Ins::Return ;Insertを無効化
vk1D::Return ;無変換キーを無効化(修飾キー化するため)
vkF2::Enter ;ひらがな/カタカナキーにEnterを割り当て
vk1D & vkF2::Tab ;無変換+ひらがな/カタカナでTab
vk1D & Space::+Tab ;無変換+スペースでShift+Tab
Ins & N::Run, Notepad.exe ;Insertはアプリケーションの起動やフォルダを開くのに使用
;(実際にはメモ帳ではなく他のアプリケーションを設定しています)
;変換キーにCtrlを割り当て
*vk1C:: Send,{Blind}{Ctrl DownTemp}
*vk1C Up:: Send,{Blind}{Ctrl Up}
何が何のキーを表しているのか、というのは以下で確認できます。
最後の変換キー→Ctrlは少し複雑ですが、以下を参考にしました。
単純なリマップ(キー配列の置き換え)はこのような感じです。他にも色々設定していますが、書き方に変わりはないので省略します。
EmacsやVimといったものを使っている人はそちらとリンクさせるのがオーソドックスなやり方かと思いますが、私はそれらを使う予定はないので、自分のイメージに合うような設定を探して試みています。
そして更に、関数を使って任意の文字列や日時を出力できるようにしています。その話をしたくて書き始めたのですが、今回は「何の話なのか」の準備までとして次の記事で書くことにします。