2011-12-16
点数付けの導入でタスク管理を楽しくする[Emacs拡張]
まず趣旨!この記事はタスク管理を楽しく行なって作業能率をあげようっていう内容です。
あえてEmacsを知らない人も想定して書いていますが、この記事はEmacs Advent Calendar jp: 2011の16日目になります。15日目は@g177564さんのmultiple-value-blog1: Emacs Lispで式単位のコメントアウトでした。
はじめに
僕は先送り癖がひどく、テスト勉強やレポート、論文なども締切りギリギリまで行動できないタイプ。対策本を読んだ所、計画をしっかり立ててやることを明確にすると、初めの一歩を踏み出す先がわかって行動できると書いてあり、それはそうだと思う。だが、言うは易しというところで、自分の場合はあまり効果的には習慣付けられなかった。
しかし、このタスク管理をゲーム感覚で楽しめたらどうだろう?
以下のちょっとしたカスタマイズを導入した所、毎日計画を立てたり見直しをしたりするようになり、その結果として先送り癖がわりと軽減された気がする(あくまで主観)ので記事にまとめる。
本題
TODO管理を行うソフトは数多く存在する。
例えばテキストエディタであるEmacsのorg-modeにはTODO管理の機能が備わっており、タスク管理にも使うことができる(org-modeでTODO管理 - handlename::blog)。org-modeにおけるTODO管理は、タスクの終了度合いとして[X%完了]や[全XX個中X個完了]のような表示ができるのだが、他のソフトでもままあるように、各タスクの難しさや重要さがそれほど重視されていない。
その日はどれだけ頑張ったかを示す絶対的な指標、つまりスコアが欲しいと思った。合わせて、他の日と比べられたり、自分がどれぐらい頑張ってるかなどがわかるとタスクをこなすのが楽しくなるかも、というのが基本的なアイディア*1。
やったこと
よりよいTODO管理のために、Emacsのorg-modeを拡張することにした。
具体的には、各タスクに S,A,B,C,D,E の5段階の指標を設けてその日の合計スコアを出すようにした*2。
ふだんの見た目としてはこんな感じ*3。
ついでなので、その日のスコアが過去のデータ中何番目にあたるかという情報を出し、さらに直近のスコアに勝つにはあと何ポイント必要かという情報も表示できるようにした。
しかしこれだと一日が終わるまで他の日と正確に比較ができず、タスクへのモチベーションを高めるという意味では不十分だと感じる。
なので、とりあえず試験的に朝8:00から夜24:30までを一日の活動時間として、単純に今の時刻と達成具合からその日の達成度を予想することにした。
コード
記事の最後に載せておきます。
使い方
だいたいは図にあるような感じでツリーを構築していってもらえば使えます。スコア計算の対象となる書式は以下*4。重要度を示す文字はS,A,B,C,D,Eが対象。
+ [ ] A:書式はこんな感じで、これは重要度Aの場合
スコアリング自体は C-c C-c によるチェックボックスのトグル関数にくっつけているので、チェックボックスをトグルした際に自動的に計算を行う。もう少し細かく言うと、現在位置のレベルの最初から次のレベルまでをまとまった区間として計算する。計算時はネスト(入れ子)も特別な扱いはしない*5。
結果は、 "タスク完了分の得点/全タスクの得点" でミニバッファおよび親のアウトライン行の末尾*6に表示。
過去のスコアとの比較は、コマンド C-c C-; で行う。比較のためには、同じバッファ内で過去の得点が見えるようにしておく必要がある。
結果はミニバッファに表示する。結果の読み方は以下の通り。
score:[現在点/最大点] rank:[現在順位/全体数] ~次のランクまで必要なスコア || expected:[このペースで予想されるスコア(予想順位)] || day:[活動時間に対する現在時刻%] time:[経過時間/残り時間]
期待している効果
毎日のタスク達成度をスコアリングすることで、タスク管理を楽しくできて色々捗るかも。期待している効果は以下。
- 計画を立てることが楽しくなる(点数配分を考えることが新たな楽しみ要素)
- タスクを達成することが楽しくなる(タスクを達成すると点数が増えるから)
- ランキングによりタスク達成へ頑張る気持ちが生まれる(他の日の得点と比べられるから)
- スコアリングによってタスクの重要度が顕著になる(上の例だとタスクSはタスクEの32倍大事)
- 予想スコアや現在時刻と残り時間を表示することで時間を意識し、先延ばしが軽減される
そもそも計画を立ててうれしいことに関しては、
- 計画を立てることで自分の今の状態がはっきりする
- タスクの手順を明らかにすることで心理的負担を軽減させる
計画の立て方
うまい計画の立て方というのはやはり存在する。一応個人的に注意していることは以下。
- タスクは必ず一定の基準を持って細分化する(サクサク感を得るため)
- やりたくないタスクは間に簡単なタスクを挟む(はじめの一歩を簡単に)
- 計画にないものでもやったことは評価する(減点法でなく加点法を採用する)
まとめ
ゲーム化のデザインに関してはもっといいものがあると思うし、表現ももっと色々できそう。例えば日毎のスコアの推移のグラフを出してみるとか、あるいは一日のうちのタスクをどれぐらいのペースでやっているかとか、どのタスクにどれくらい時間がかかったかを可視化するとか。
Emacsを使ってなくてここまで読んでくださった方は、これを機に導入してみるのもいいんじゃないでしょうか。
コード
以下を.emacsに記述する。自分の使用スタイルでしかバグを見てないので、環境や使い方によっては動かないかもしれませんが、その場合はごめんなさい。
基本的にはバッファ内の規定の書式を正規表現で検索して計算しているので、ツリーを非表示にしていたり独立したバッファに分割して表示していたりするとうまく動きません。
(defun org-my-calculate-score-insert () (interactive) ;; 現在位置を保存、終了位置を探す(空行がなければバッファの終わりを代入) (let ((pos (point)) (score 0) (max 0) (end (string-match "^\*" (buffer-substring (line-beginning-position) (point-max))))) (if (not end) (setq end (point-max)) (setq end (+ (line-beginning-position) end))) ;; レベルの始めから、優先順位付きのチェックボックスを探し、scoreとmaxに格納 (outline-backward-same-level 0) (while (and (search-forward-regexp "^\\W*\+ *\[[ X-]\]\\W*[SABCDE]:" nil t) (< (point) end)) (backward-char 1) (let ((pt 0) (cchar (preceding-char))) (if (eq cchar (string-to-char "S")) (setq pt (expt 2 5)) (setq pt (expt 2 (- 5 (- cchar (- (string-to-char "A") 1)))))) (message (format "found: %s, %d" (string cchar) pt)) (when (string-match "\\[X\\]" (buffer-substring (line-beginning-position) (line-end-position))) (setq score (+ score pt))) (setq max (+ max pt)) )) ;; 何らかの点数があれば更新する (goto-char pos) (unless (< max 1) (outline-backward-same-level 0) (search-forward-regexp "^.*\]" nil t) ;; 文字を消したらposの位置が保証されない (let ((delp (point)) (pofs (- (line-end-position) (point))) (nofs 0)) (delete-region (point) (line-end-position)) (tab-to-tab-stop) (insert (format "+{%3d/%3d}" score max)) (message (format "score/max: %d/%d" score max)) (setq nofs (- (point) delp)) (goto-char (+ pos (- nofs pofs))))) (list score max))) ;; チェックボックスをトグルしたらスコアを更新する (defadvice org-toggle-checkbox (after org-toggle-checkbox-advice activate) (org-my-calculate-score-insert)) ;; ランキング計算時に時刻補間する際の時刻設定 (defvar org-my-get-ranking-day-split 4.0) (defvar org-my-get-ranking-day-start 8.0) (defvar org-my-get-ranking-day-end 24.5) ;; @myfunc ランキングを表示する (defun org-my-get-ranking () (interactive) (let ((pos (point)) (score 0) (max 0) (expect 0) (slist (list)) (rank 1) (mrank 1) (erank 1) (num 0)) (let ((scores (org-my-calculate-score-insert))) (setq score (car scores)) (setq max (car (cdr scores)))) (goto-char (point-min)) (while (and (search-forward-regexp "\\+{\\W*" nil t) (string-match "[0-9]+/\\W*[0-9]+}" (buffer-substring (point) (line-end-position)))) (setq slist (cons (string-to-number (buffer-substring (point) (search-forward-regexp "[0-9]+" nil t))) slist))) ;; 朝のorg-my-get-ranking-day-start時から夜のorg-my-get-ranking-day-end時までが活動時間とする (let ((next 99999) (ratio 0.0) (time 0.0) (active (- org-my-get-ranking-day-end org-my-get-ranking-day-start))) (setq time (+ (nth 2 (decode-time (current-time))) (/ (nth 1 (decode-time (current-time))) 60.0))) ;; 時間を整形:org-my-get-ranking-day-split時を一日の境目とする (when (< time org-my-get-ranking-day-split) (setq time (+ time 24.0))) (setq time (- time org-my-get-ranking-day-start)) (when (< time 0.0) (setq time 0.0)) (when (> time active) (setq time active)) (setq ratio (/ time active)) (if t (setq expect (/ score ratio)) (setq expect score)) (when (> expect max) (setq expect max)) ;; 何番目か調べる:score, max, expectを同時に調べる:直近のスコアを得る (while slist (let ((top (car slist))) (when (< score top) (setq rank (+ rank 1))) (when (< max top) (setq mrank (+ mrank 1))) (when (< expect top) (setq erank (+ erank 1))) (when (and (< score top) (> next top)) (setq next top))) (setq num (+ num 1)) (setq slist (cdr slist))) ;; score: [現在点数/最大点数] rank: {現在/数}, ;; expected score: 予想スコア(予想ランク), day: [経過時刻h/活動時間h](比率%) (message (format "score:[%d/%d] rank:[%d/%d] ~%d || expected:[%d(%d)] || day:[%.1f%s] time:[%.1fh/%.1fh]" score max rank num (- next score) expect erank (* 100.0 ratio) "%%" time (- active time))) (goto-char pos)))) ;; org-mode自体の設定 (add-hook 'org-mode-hook '(lambda () ;; - ここあたりはその他の設定 - ;; ランキング計算をショートカットキーに登録(キーバインドは適当に調節) (org-defkey org-mode-map (kbd "C-c C-;") 'org-my-get-ranking)))
記事の修正
2011/12/16 23:50 説明文の間違いや表現を修正, コード中の無意味なコメント行を削除し,忘れていたキーの設定を追加
*1:ゲーミフィケーションを意識
*2:得点配分は人それぞれだと思うけど、自分は S:32, A:16, B:8, C:4, D:2, E:1 の配分を採用した
*3:ちなみに自分はorg-modeのタスク管理機能(時間とか)はそこまでフル活用できてない。めんどくさいので箇条書き程度がちょうどいい
*4:ところで自分はわざわざこういう書式を打ち込むのが面倒なので、yasnippetという拡張で優先度のアルファベット一文字からチェックボックスまで展開させている
*5:最初の画像では親の書式にセミコロン(;)を用いてわざと得点計算に入れないようにしていた。得点は細分化したタスクの分で十分で、単に優先順位だけ表示したかったから
2011-04-10
第三回関西Emacs勉強会
出ました。
ポジションペーパー
自己紹介
情報系の院生をしています。
研究の興味があるのはコンピュータビジョンやユーザインタフェースなど。しかし逃げるようにEmacsのカスタマイズに没頭した時期がありました。最近はWindowsばっかりなのでUnixコマンドを忘れかけ。でもMeadowは使う。モノづくり系は大好きで、最近だと「不思議箱」というのを作った。ちなみに就活中。最近の趣味は語学学習。
カスタマイズ自体は、既存機能の拡張や調整をしたり、例えばvimのfsearchなど、ちょっとした機能を実装してみたりした程度。だいたいは.emacsの中に書いているので、一度ぐらいはモード作りに挑戦したい。
お気に入りのelisp拡張(デフォルト含む)
これまでにやってみたこと
- elscreenの拡張(番号詰めとかresumeとか)
- compileの連携(コンパイル後自動実行とか)
- その他いろんな調整
作りたいモノ
- 何となく検索
- 画面の絶対位置移動
- 見栄えに特化したorg-mode
今回の勉強会で知りたいこととか
- anything.elは若干食わず嫌いになっているかもしれないのでそのすばらしさ
- メジャー/マイナーモードを作るにあたってのおさらい
- 実装したい機能で必要な関数がわからないとき、普段どうやって調べているか
- 初参加なので、とりあえずお話ししたいです
関連するエントリ
2010-11-28
あれ, TechCrunch って英語の勉強にいいんじゃないの?
英語 |
経緯1
自分は結構情報を秘密にしたがる性格なので,そうした卑しい心を断ち切って*1サービス精神を少しでも身につけるためにも,ここ2,3日でふと気づいたお気に入りの英語学習を紹介.
経緯2
多読だ精読だというのを何度も目にしたが,個人的にはどちらも負荷が大きすぎて長続きしない,あるいはそこから生じる嫌気で実際にやる時間がとても少ないという事態に陥ってしまったように思う.いや,だって多読は優しい本だとあんまり種類がない上に面白くないし,難しい本だとストレスで長続きしなかったり,がんばって読んだとしても時間がかかってしまっては結局読む量が少なかったりする.精読もやっぱりそうなりがちでしんどい.
そんなこんなで,なんか自分には日本語訳付きで普通レベルの文を無理せずどんどん噛み砕いていくのが精神衛生上好ましいと思った次第.
ところで,有名な海外のブログは,日本版があったりする.詳しくは 日本版がある著名な海外ブログをまとめてみた - YAMDAS現更新履歴 や,英語の技術blog・ニュースサイトをまとめてみた! - 毛の生えたようなもの が参考になると思うが,たとえばTechCrunchなんかは日本語訳が結構忠実なので,同時に眺め読みするだけで勉強になる.
リスト
というわけで,個人的なおすすめ日本語版のリンクを以下にコメントと一緒に載せておこう.だいたいは記事の最後に原文へのリンクがある.ちなみにGizmodeやEngadget Japanese,lifehackerは訳が忠実じゃなかったり文が短かったりでこのスタイルには向かないと思った.
- TechCrunch IT関係.記事の数が豊富.翻訳の質もよく*2,程よい長さなので個人的には一番好き
- Wall Street Journal 金融関係.文章は固め.ここの記事も原文へのリンクがあったり特集コーナーがあったりする.
- Wired Vision 本当に見つけたばっかでよくわからないがトピックが多岐にわたる.全センテンスを訳しているわけではなさそう
あと,以下も結構いいかもしれない.
- ニュースで英会話 - トップ 音声まである.ニュース性を求めるには記事の数(と量)が少ない
- Joel on Software - 訳は忠実.更新は終わったらしいが結構面白い.ただし情報科学に興味ある人向け*3
- 毎日jp(毎日新聞) 原文リンクのある記事はGoogle先生にお願いできる*4.ただしパラグラフが対応してなかったりする上,記事によっては違うことが書いてあったりするっぽい
なお,さらにvallog: プログラマが好きそうな読み物100 からいくつかよさげなものをリストアップ.
- Fine Software Writings
- Technical Knockout
- Practical Scheme
- YAMAGATA Hiroo: The Official J-Page
- Martin Fowler’s Bliki in Japanese - ページ一覧
読み方
通常は文章を読むとき,subvocalizationといって無意識に頭の中で音声に変換していると思うが,最低限発音を練習した後ならこのsubvocalizationは積極的にすべきだと思う.というのは,単語認識の速度を上げる効果的な練習ができることや,音声で意味を検索(頭の中で)できたり記憶に留めやすかったりで読解の補助に繋がることなどが考えられるから.前回の記事で触れた速読云々でも,subvocalizationが十分でないうちからそれを避けるのは得策ではないと今は思います*5.
ついでに
上記以外の英語サイトで,どうしても日本語がないとダメという人は翻訳サイトを使うという手もある.ただ英日の変換は個人的にはまだまだ過ぎると思うので,それをやるぐらいだったら普通に読んだほうがいいとは思う.
ちなみに,日本語版のURLをクリップボードから読んで,パラグラフごとに対応文を挿入したHTMLを生成するプログラムを書いてみたけど,いつものごとくひどい実装で,クオリティも低いので公開はしない.
まぁでも2ペイン表示のアドオンとかウィンドウを左右に2つ配置とかで全然事足ります.chromeだとデフォルトでこんなテクも使えるらしいですね.
追記
日本版がある著名な海外ブログをまとめてみた - YAMDAS現更新履歴 を参照したのを忘れてたので,記事に追加.CNetとかZDNetという選択肢もあるのかもしれない.
さらに,vallog: プログラマが好きそうな読み物100 を参照していくつか追加.
2010-11-14
リーディング時のWPM向上を支援するスクリプト書いてみた
自分は英文を読むのがクソ遅いんですが,知らないと損する英語の速読方法(1) - 一法律学徒の英語と読書な日々に感化されてちょっとWord Per Minute(WPM),つまり分あたりに読む単語数を改善しようと思い立った.
で,それっぽい支援ソフトウェアを探したんだがあまり自分の好みの物は見つからなかったので,pythonで適当に書いてみた.ウィンドウのフォーカスあたりでちょいちょいしてるので多分windowsのみサポート.そこを消せば他でも動くはずだけど実用レベルではない.
いやね,なんかフラッシュしながら表示するのはあるんだけど,ハイライトが見つからなかったんです.フラッシュ版は,Free online speed reading software | Spreeder.com なんかはある程度注目されてる模様.
- 設定したWPMに応じて,Ctrl+Shift+→ キーを送って,選択中の領域を単語ごとに拡大する
だけ.
要するに,表示テキストを選択できてCtrl+Shift+→ が有効なアプリケーションならなんでもよくて*1,ウィンドウを登録した後に開始付近の単語をマウスで選択状態にしてからスタートすると指定したWPMで単語がどんどんハイライトされる.
一応ウィンドウのフォーカスをとっているので暴走はそこまでしないハズだけど,これを実行したことによって生じる不利益はなんの保証もできません.
いろいろと恥ずかしいコードだけど,まぁ自分の欲求は最低限満たしているし,時間が勿体無いから書き直すこともないだろうということで投げやりに記載*2.適当に改良してください.いや,本当はchromeのextensionでできたらよかった*3んだけど,今は新しいものに手を出す余裕が無い.まぁこのスクリプトはPDFファイルにも応用できるのでこれはこれでよしとする.
実行には,以下のインストールが必要.
あとは,下のスクリプトをコピペして"speedReader.py"とでも名前をつけて保存して,ダブルクリック.
#!usr/bin/python # coding: utf-8 # 使い方:まず使用の対象となる英文を表示しているウィンドウを登録 # マウスなどを使って開始したい位置の文字を選択状態(ハイライト)にし, # スタートすると指定したWPMで選択領域が拡大.原理的には Ctrl+Shift+->を送っているだけ # 止めたいときは,いったんフォーカスを他のものに移す:通常はspeedReaderをクリック # キー出力を乗っ取っているので,こちらからの入力がたまに被さる可能性がある # 自動スクロールは実用的でないので,キー入力などを使って手動でスクロール推奨. import sys import time import SendKeys import msvcrt import win32gui import win32con gGoalWPM = 250 gUnitTime = 0.0 gAutoScrollNum = 10 # auto scroll with this word count gThisWindow = win32gui.GetForegroundWindow() gTargetWindow = gThisWindow def setWPM(w): global gGoalWPM, gUnitTime if w < 10: w = 10 elif w > 1500: w = 1500 gGoalWPM = w gUnitTime = 1.0 / (float(gGoalWPM)/60.0) print "Goal WPM = %d" % gGoalWPM def selectWindow(): global gTargetWindow print "select target window" while True: gTargetWindow = win32gui.GetForegroundWindow() if gTargetWindow != gThisWindow: print "[", win32gui.GetWindowText(gTargetWindow), "]" return True if msvcrt.kbhit(): print "cancel" return False time.sleep(0.1) def printHelp(): print "s:start, o:stop, u/d:up/down speed" print "c:start in 3sec, 1-9:preset speed," print "w:register target window, r:reset, " print "h:help, p:print target window, q:quit" # main def main(): global gGoalWPM, gUnitTime, gTargetWindow, gCurrentWindow # variables mCurrentTime = time.clock() mLastTime = mCurrentTime mTotalTime = 0.1 # temp mShouldStop = True mDefocus = False mWordCount = 0 # variables # always on top, window size, position win32gui.SetWindowPos(gThisWindow,win32con.HWND_TOPMOST,500,200,350,200,0) if(len(sys.argv) > 1): gGoalWPM = sys.argv[1] setWPM(gGoalWPM) mTotalTime = gUnitTime printHelp() while True: firstLoop = True # wait untile goal WPM rate while firstLoop or (mCurrentTime - mLastTime) < gUnitTime: firstLoop = False time.sleep(0.001) mCurrentTime = time.clock() # keyboard interrupt if msvcrt.kbhit(): hitkey = msvcrt.getch() if hitkey == '\000': pass # Send key elif hitkey == 'q': print "exit" sys.exit() elif hitkey == 'u': setWPM(gGoalWPM + 10) elif hitkey == 'd': setWPM(gGoalWPM - 10) elif hitkey == '1': # preset 1 setWPM(150) elif hitkey == '2': # preset setWPM(200) elif hitkey == '3': # preset setWPM(250) elif hitkey == 'w': selectWindow() # win32gui.SetForegroundWindow() elif hitkey == 'h': printHelp() elif hitkey == 'p': print "[", win32gui.GetWindowText(gTargetWindow), "]" elif hitkey == 's': print "start" if gTargetWindow == gThisWindow: if selectWindow(): win32gui.SetForegroundWindow(gTargetWindow) mShouldStop = False else: win32gui.SetForegroundWindow(gTargetWindow) mShouldStop = False elif hitkey == 'o': print "stop" mShouldStop = True elif hitkey == 'c': if mShouldStop: print "wait 3 seconds..." time.sleep(3) win32gui.SetForegroundWindow(gTargetWindow) mShouldStop = False elif hitkey == 'r': print "reset" printHelp() setWPM(gGoalWPM) mLastTime = mCurrentTime mTotalTime = gUnitTime mShouldStop = True mWordCount = 0 if not mShouldStop: if not mDefocus: curWindow = win32gui.GetForegroundWindow() if curWindow != gTargetWindow: print "defocus" print "[", win32gui.GetWindowText(curWindow), "]" mDefocus = True else: SendKeys.SendKeys("""^+{RIGHT}""", 0.0) minutes = int(mTotalTime) / 60 seconds = int(mTotalTime) - minutes * 60 decisec = int((mTotalTime - float(minutes*60 + seconds)) * 100.0) print "%d/%d[wpm], " % (float(mWordCount+1)/mTotalTime*60.0, gGoalWPM), print "%3d[w], %02d:%02d:%02d" % (mWordCount, minutes, seconds, decisec) if gAutoScrollNum > 0 and mWordCount%gAutoScrollNum == 0: # I don't know why this doesn't work # win32gui.SendMessage(gTargetWindow, win32con.WM_VSCROLL, win32con.SB_LINEDOWN, 0) pass mTotalTime += gUnitTime mWordCount = mWordCount + 1 else: if gTargetWindow != gThisWindow and gTargetWindow == win32gui.GetForegroundWindow(): print "get focus" mDefocus = False mShouldStop = False mLastTime = mCurrentTime # main if __name__ == "__main__": main()
メモ
始めてwin32API使ったけど,なんかよくわからんことが多いのでメモ.
- ScrollWindowEx()で他のアプリケーションをスクロールさせるのは,スクロール領域がわからないので無理?
- 正当法であろう SendMessage() はなぜかうまく動かなかった
- SetForegroundWindow() は,自分自身を非アクティブからアクティブにできないらしい
追記
11/14 最大wpmが低かったのでコードを修正.




