emacs on WSLでSuper/Hyperキーを使う方法
はじめに
この前の東京Emacs勉強会で雑談してたときに、Superキー、Hyperキーの話になり、Macは修飾キーが多くてうらやましかった。 キーバインド覚えやすいところうまっていて、追加で悩む状況でもあるし。
ということで自分もSuper/Hyperデビューすべく、WSLでのやりかたを調べてみた。
環境
- wsl
- wsl上で動くEmacs
- X410 (Xサーバ)
なので、emacs for windowsとか、mingwのemacsではないです。その場合は、また他の手段を取る必要があるはず 1。
なお、wslで使うXサーバといえば、xmingかvcxsrvが有名ですが、emacsとの相性がよくない(※)ので別の有償のXサーバを使ってる。 X410は、ストアアプリとして購入できるが、しょっちゅう80%Offセールしているので割引タイミングを狙いましょう。
(※) emacsとの相性がわるい問題については以下のスライドを
目指すゴール
無変換キーをSuperキーとして、変換キーをHyperキーとして使えるようにする。
一般的には、WindowsキーがSuperキーにあたると思うし、なんなら設定なしでもそう動いてしかるべきなのだけど、そもそもOS側にトラップされてアプリケーションまでうまく届かなかったのと、Windows10になってWin+○のショートカットキーが増えてそれを無効にするのも不便だったので、上記の設定をおこなうことにした。
前置きは以上。
Superキー/Hyperキーを設定する
手段その1 AutoHotKeyを使う
多分これが一番早いと思います。 サンプルも多いはず。
自分は、なんとなくWindowsのユーザランドでキーいじるやつが嫌だったので採用しなかった。 (CapsとCtrlの入れかえで、結局レジストリいじらないとうまくいかなかったりというのに以前ぶつかったりしたので)
手段その2 xmodmapを使う
xmodmapはlinuxというか、Xの世界でキーマップ変更するためのもの。調べてみると最近は別のがあるっぽいけど、xmodmapでうまく動いたので気にしない。
そうそうキーコードなんて変わらないと思うので、同じ設定で同じように動くはず。
手順1. キーコードを特定する
xev
というコマンドを使うと、Xのアプリが起動して、そこでもろもろのイベントを標準出力に出して確認することができる。
これを使うと、変換と無変換のキーコードは以下のとおり、変換が129、無変換が131であることがわかる。
KeyRelease event, serial 33, synthetic NO, window 0x800001, root 0xf4, subw 0x0, time 426541921, (67,97), root:(179,232), state 0x0, keycode 131 (keysym 0xff22, Muhenkan), same_screen YES, XLookupString gives 0 bytes: XFilterEvent returns: False KeyPress event, serial 33, synthetic NO, window 0x800001, root 0xf4, subw 0x0, time 426596187, (843,330), root:(955,465), state 0x0, keycode 129 (keysym 0xff23, Henkan_Mode), same_screen YES, XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False
手順2. xmodmapの動作確認する
キーコードがわかったら、xmodmap
コマンドで一時的にキーシムを書きかえて動作確認してみる。
# xmodmap -e "keycode 131 = Super_L Super_L" # xmodmap -e "keycode 129 = Hyper_L Hyper_L"
実行後、再度xevを使って期待通り変わっていることを確認する。
KeyPress event, serial 33, synthetic NO, window 0x800001, root 0xf4, subw 0x0, time 426819890, (125,123), root:(263,284), state 0x0, keycode 131 (keysym 0xffeb, Super_L), same_screen YES, XKeysymToKeycode returns keycode: 115 XLookupString gives 0 bytes: XmbLookupString gives 0 bytes: XFilterEvent returns: False
先程はキーコード131が無変換だったのが、Superに変わっていることがわかる。
手順3. xmodmapの設定を作成する
調べると、xmodmap -pke
を実行して、必要なところだけ書きかえましょう。というのが出てくるのだが、実際のところ必要な設定だけ書けばよかったので、いきなり .Xmodmap
ファイルを作成する。
WSLで手持ちのXサーバ使う分には別にファイル名は何でもいいと思うのだが、ここは慣例に従っておく。(startxコマンドでx起動するときは、雛形で.Xmodmapを詠み込むのでファイル名重要)
自分の設定は、こんなかんじ。
https://github.com/grugrut/dotfiles/blob/master/.Xmodmap
clear mod3 clear mod4 !<muhenkan> keycode 129 = Hyper_L Hyper_L Hyper_L Hyper_L !<henkan> keycode 131 = Super_L Super_L Super_L Super_L add mod3 = Hyper_L add mod4 = Super_L Super_R
デフォルトの状態だと、SuperキーとHyperキーが同じ修飾キーとしてあつかわれていて、Hyperキー単体でうまくうけとれないので使われていないmod3にHyperキーを割当ておいた。
手順4. 自動で適用されるようにする
xmodmap ~/.Xmodmap
とコマンド実行すればよいのだけど、注意点が一つ。
xmodmapはXサーバに対して設定をおこなうコマンドなので、Xサーバが起動していない状態ではうまく動かない。
LinuxやBSD使ってるときにもxmodmap使ってたけど、当時は常にxorg-serverが起動してたので今回はじめてそのこと知った。
ついでなので、OSログイン時にXサーバを起動すべく適当なbatを作成した。 https://github.com/grugrut/dotfiles/blob/master/_windows/startx.bat
start /b x410.exe ubuntu.exe run "DISPLAY=127.0.0.1:0.0 xmodmap ~/.Xmodmap"
x410.exeが使っているXサーバ。バックグラウンド実行させたいので、/b
オプションをつけてる。
そして、ubuntu.exe run
をすることでwslでコマンド実行できる。
これを Windowsの shell:startup
に配置することでスタートアップ時に自動実行することができる。
ただし、直接おくよりもショートカットを配置することをおすすめする。直接配置すると、実行時にコマンドプロンプトが一瞬表示されてうっとうしいが、ショートカットであれば最小化して実行することができるので気にならないからだ。
まとめ
xmodmapを使うことで、他のアプリには影響なくwslのX使うアプリだけにいろいろ手をいれられることが確認できた。他にもその手の機能で便利なのありそう。
-
たぶん
w32-lwindow-modifier
とかが使えるはず↩
cephを1ノードで構築する
最近作ったkubernetesのクラスタのPersistent Volumeを何にするか考えていて、NFSでもいいのだけど、せっかくだからdynamic provisioningを体験するべく、cephを導入してみることにした。
とはいえ、冗長構成にすると、VMだらけになってうっとうしいので、1ノード(All-in-one)で作ってみた。
参考にしたページ
- 公式ドキュメント (当然よね
- ceph-ansibleでall-in-oneで導入した記事
- debianでceph-deployで導入した記事
環境
いつものHyper-V上のfedora30のVM。 なんか1ノード構成でもディスク複数用意するっぽいのでそのようにした。ドキュメントみても複数ノード前提で書かれてて、1ノードの場合にディスクが本当に複数必要なのかは調べてない。
導入手順
はじめ、qiitaでRed Hatの中の人の手順でそのままやろうとしたのだけど、 ansible-playbook
コマンドがうまく動かなかったので、そうそうにあきらめ。たぶん設定もれなのだろうけど。
で、ceph-deployを使うことにした。
dnfで入らなかった(pythonまわりの依存関係がおかしかった)ので、pipでインストール
# pip install ceph-deploy
あとはドキュメントどおりなのだけど
# mkdir my-cluster # cd my-cluseter # ceph-deploy new storage01.mshome.net
これで、 ceph.confが作成されるので、ちょっと編集。以下の行を追加した。
[osd] osd_pool_default_size = 1 osd_crush_chooseleaf_type = 0
あとは、ceph-deploy installのときに、fedoraの場合、うまく存在するrepositoryをみにいってくれなかったので、直接指定
# ceph-deploy install --repo-url https://download.ceph.com/rpm-nautilus/el7/noarch/ceph-release-1-1.el7.noarch.rpm storage01 # ceph-deploy mon create-initial # ceph-deploy admin storage01 # ceph-deploy mgr create storage01 # ceph-deploy osd create --data /dev/sdb storage01 # ceph-deploy osd create --data /dev/sdc storage01 # ceph-deploy osd create --data /dev/sdd storage01
確認コマンド実行してみると、ちゃんとできたっぽい。
# ceph health HEALTH_OK # ceph -s cluster: id: c067a00a-46f7-474f-90f3-5f244b5d695f health: HEALTH_OK services: mon: 1 daemons, quorum storage01 (age 7m) mgr: storage01(active, since 3m) osd: 3 osds: 3 up (since 11s), 3 in (since 11s) data: pools: 0 pools, 0 pgs objects: 0 objects, 0 B usage: 3.0 GiB used, 375 GiB / 378 GiB avail pgs:
以上
kubernetesクラスタはDHCPは利用せずstatic ipで作成すべし
件名以上のことはなく、まあ普通の感性ならそうするよね、ということしかない
で作ったクラスタだが、残念ながらOS再起動時にぶっこわれてしまった。
手抜きしてDHCPでやっていたら、etcdやらapiserverやらが、元のIPアドレスに探しにいってしまい、必要コンポーネントがまるで起動しない。
ログをみてみると、
```
Jul 3 18:30:56 master01 kubelet[1437]: E0703 18:30:56.729641 1437 controller.go:115] failed to ensure node lease exists, will retry in 7s, error: Get https://lb01.mshome.net:6443/apis/coordination.k8s.io/v1beta1/namespaces/kube-node-lease/leases/master01.mshome.net?timeout=10s: dial tcp: lookup lb01.mshome.net on 192.168.217.225:53: read udp 192.168.217.233:46188->192.168.217.225:53: i/o timeout
```
みたいな感じで古いIPアドレス(192.168.217.233)を使おうとしているのがわかる。
/etc/kubernetes/manifests/etcd.yaml とかを見ると確かにIPアドレスがベタ書きされてるので、修正してみたけども状況あまりかわらず。
ホスト名解決で失敗しているので、coreDNSがあやしいのだけど。
いずれにしても、ipかわるたびにもろもろ修正するの手間でしかないので、static ipで作り直すことにする。
クラスタを構成するノードの情報がどこに、どのように格納されているかは気になるので、正しく動いたらおさえておきたいね。
そもそもDHCPでもいっか、て判断したのが、ドキュメントに
```
It is not recommended to use an IP address directly in a cloud environment.
```
とあったので、クラウドだとたしかにIP制御できないし、ホスト名で解決できる環境ならいいんだなって判断してしまったのだけど、これLoadBalancerの条件だったので、誤読ですね。
やっぱminikubeとかマネージドに頼らない自前クラスタ組んでみると、いろいろ予期せぬところではまって興味深いです
高可用性k8sクラスタをkubeadmで構築してみる
はじめに
最近コンテナに入学したので、おうちクラスタを作ってみることにした。PCのメモリ64Gまで増強したので仮想マシンばんばん建てる体力もあるし。
といいつつ、シングルマスタのクラスタは、昔はやったラズパイクラスタ作るときに体験済で、せっかくなのでマルチマスタクラスタを組んでみることにした。 苦労はしたけど、苦労したポイントが
- ドキュメントを右往左往させられる
- タイポしてた
が大半なので、ほぼドキュメントへのリンク集。
構成
同一WindowsマシンのHyper-V上の仮想マシンとして構築した
用途 | ホスト名 | OS | CPU | メモリ *1 |
---|---|---|---|---|
マスターノード | master01.mshome.net | Fedora 30 | 2コア | 2GiB |
master02.mshome.net | Fedora 30 | 2コア | 2GiB | |
master03.mshome.net | Fedora 30 | 2コア | 2GiB | |
コンピュートノード | node01.mshome.net | Fedora 30 | 4コア | 4GiB |
node02.mshome.net | Fedora 30 | 4コア | 4GiB | |
node03.mshome.net | Fedora 30 | 4コア | 4GiB | |
ロードバランサ | lb01.mshome.net | Fedora 30 | 2コア | 2GiB |
どこの設定か調べてないけど、mshome.netにするとホストのWindows機からも、追加設定なしに名前解決できて便利だったので、FQDNはそのように設定した。
インストール
仮想マシンの作成
Hyper-Vは仮想マシンの複製が、(簡単には)できないので、めんどくさい。
docker-ceのインストール
基本的にはドキュメントの通り。fedoraではyumのかわりにdnfが採用されて、基本的にyumコマンドが使えるのだけど、yum-utils
は使えないのでレポジトリ追加は、 yum-config-manager
のかわりに、
$ sudo dnf config-manager --add-repo url
とする必要がある。
Yum-utils package has been deprecated, use dnf instead. See 'man yum2dnf' for more information.
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo YumRepo Error: All mirror URLs are not using ftp, http[s] or file. Eg. http://dummy
みたいな出力なので、docker-ceのrepoファイルがおかしいのかと思ってたら、そうではなかった。
master/computeの構築
ドキュメントのとおり。後述の問題のとおり、weaveを使う場合は、構築自体は完了するが、その後問題がおきるので要注意。
Creating Highly Available clusters with kubeadm - Kubernetes
ロードバランサの設定
apiをmasterに分散するために、ロードバランサが必要になる。今回はhaproxyを使った。 /etc/haproxy/haproxy.cfgに適当に設定書いた。
frontend k8s bind *:6443 mode tcp default_backend k8s backend k8s balance roundrobin mode tcp option tcp-check server master01 master01.mshome.net:6443 check server master02 master02.mshome.net:6443 check server master03 master03.mshome.net:6443 check
問題点
corednsが動かない
kubectl get podで見てみる、corednsだけが動いてない(きづかずkubernetes-dashboardもdeployしてから気付いた)
$ kubectl get all -n kube-system NAME READY STATUS RESTARTS AGE pod/coredns-5c98db65d4-phqkw 0/1 ContainerCreating 0 71m pod/coredns-5c98db65d4-tznld 0/1 ContainerCreating 0 71m pod/etcd-master01.mshome.net 1/1 Running 0 70m pod/etcd-master02.mshome.net 1/1 Running 0 32m pod/etcd-master03.mshome.net 1/1 Running 0 33m pod/kube-apiserver-master01.mshome.net 1/1 Running 0 70m pod/kube-apiserver-master02.mshome.net 1/1 Running 0 32m pod/kube-apiserver-master03.mshome.net 1/1 Running 0 33m pod/kube-controller-manager-master01.mshome.net 1/1 Running 1 70m pod/kube-controller-manager-master02.mshome.net 1/1 Running 0 32m pod/kube-controller-manager-master03.mshome.net 1/1 Running 0 33m pod/kube-proxy-kdfbj 1/1 Running 1 54m pod/kube-proxy-ndcdt 1/1 Running 1 55m pod/kube-proxy-qvpkr 1/1 Running 1 54m pod/kube-proxy-spwnw 1/1 Running 0 33m pod/kube-proxy-vhjtg 1/1 Running 0 32m pod/kube-proxy-vw4w5 1/1 Running 0 71m pod/kube-scheduler-master01.mshome.net 1/1 Running 1 70m pod/kube-scheduler-master02.mshome.net 1/1 Running 0 32m pod/kube-scheduler-master03.mshome.net 1/1 Running 0 33m pod/kubernetes-dashboard-7d75c474bb-9rp8z 0/1 ContainerCreating 0 28m pod/weave-net-5kf5b 2/2 Running 2 54m pod/weave-net-5rbgf 2/2 Running 2 55m pod/weave-net-69v4x 2/2 Running 2 54m pod/weave-net-bc4sb 2/2 Running 0 32m pod/weave-net-jql64 2/2 Running 0 59m pod/weave-net-k6d2h 2/2 Running 0 33m
より詳しく見てみるとこんな感じ
$ kubectl describe po coredns-5c98db65d4-phqkw -n kube-system Name: coredns-5c98db65d4-phqkw Namespace: kube-system Priority: 2000000000 Priority Class Name: system-cluster-critical Node: master01.mshome.net/192.168.112.71 Start Time: Sun, 23 Jun 2019 09:07:10 +0900 Labels: k8s-app=kube-dns pod-template-hash=5c98db65d4 Annotations: <none> Status: Pending IP: Controlled By: ReplicaSet/coredns-5c98db65d4 Containers: coredns: Container ID: Image: k8s.gcr.io/coredns:1.3.1 Image ID: Ports: 53/UDP, 53/TCP, 9153/TCP Host Ports: 0/UDP, 0/TCP, 0/TCP Args: -conf /etc/coredns/Corefile State: Waiting Reason: ContainerCreating Ready: False Restart Count: 0 Limits: memory: 170Mi Requests: cpu: 100m memory: 70Mi Liveness: http-get http://:8080/health delay=60s timeout=5s period=10s #success=1 #failure=5 Readiness: http-get http://:8080/health delay=0s timeout=1s period=10s #success=1 #failure=3 Environment: <none> Mounts: /etc/coredns from config-volume (ro) /var/run/secrets/kubernetes.io/serviceaccount from coredns-token-kwrq4 (ro) Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Volumes: config-volume: Type: ConfigMap (a volume populated by a ConfigMap) Name: coredns Optional: false coredns-token-kwrq4: Type: Secret (a volume populated by a Secret) SecretName: coredns-token-kwrq4 Optional: false QoS Class: Burstable Node-Selectors: beta.kubernetes.io/os=linux Tolerations: CriticalAddonsOnly node-role.kubernetes.io/master:NoSchedule node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 60m (x11 over 74m) default-scheduler 0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate. Normal Scheduled 60m default-scheduler Successfully assigned kube-system/coredns-5c98db65d4-phqkw to master01.mshome.net Warning FailedCreatePodSandBox 60m kubelet, master01.mshome.net Failed create pod sandbox: rpc error: code = Unknown desc = [failed to set up sandbox container "cfeba2e58c4cbca0af13ce92e804804077f336ca4f5f11bfb8aca05f1822164a" network for pod "coredns-5c98db65d4-phqkw": NetworkPlugin cni failed to set up pod "coredns-5c98db65d4-phqkw_kube-system" network: failed to find plugin "loopback" in path [/opt/cni/bin], failed to clean up sandbox container "cfeba2e58c4cbca0af13ce92e804804077f336ca4f5f11bfb8aca05f1822164a" network for pod "coredns-5c98db65d4-phqkw": NetworkPlugin cni failed to teardown pod "coredns-5c98db65d4-phqkw_kube-system" network: failed to find plugin "portmap" in path [/opt/cni/bin]] Normal SandboxChanged 5m29s (x257 over 60m) kubelet, master01.mshome.net Pod sandbox changed, it will be killed and re-created. Normal SandboxChanged 15s (x12 over 2m20s) kubelet, master01.mshome.net Pod sandbox changed, it will be killed and re-created.
failed to find plugin "portmap" in path [/opt/cni/bin]
とあるので、そうなんでしょう、と調べてみると、今回使った(ドキュメントどおりにやってくと導入される)CNIは、weave netなんだけど、これはportmapが不要なので導入しても一緒には入らないので、必要なら自分で入れろ、ということのようだ。
https://www.weave.works/docs/net/latest/kubernetes/kube-addon/
バイナリダウンロードしてきて配置して無事起動した(正確には、loopbackも必要だった)
ダッシュボード
デフォルトではWebUIは存在せず、cliだけなので、ダッシュボードを入れた
https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/
NodePortかなんか使えば外から見れると思うけど、一旦,kubectl proxyして、sshポートフォワーディングで確認。
とりあえず今日のところはここまで
*1:動的メモリの設定してるので参考程度。これ書きながら確認したらマスタに3GiB割り当てられてた。
:ensure tを:straight tにおきかえるところから、straight.elを使ってみよう。MELPAでもgithubでもどこのパッケージも取得できるよ
自分は use-package
で、emacsのパッケージ管理および設定管理をしている。
use-packageは、遅延ロードとかを意識しなくてもよしなにやってくれるし、なにより :ensure
キーワードを使うことで、MELPAからダウンロードも自動でやってくれるので、パッケージの取得と設定を一元管理することができるのがよい。
ただ、そんなuse-packageだけど、不満点がひとつあり、 package.el
をラッピングしてるだけなので、当然のことながら package.el の対象のサイトである、elpa, melpaなどからしかパッケージを取得することができない。
なので、githubで管理されていて、melpaのレシピが作成されていないものとか、melpaにあるんだけどバグがあってフォーク版がgithubにあるとか、そういったものを管理するのがめんどくさい。 自分でダウンロードして、load-pathに配置して、という作業が必要になってしまうのだ。
かといって、そのためにcask使ってみるのもなあ。。。と思っていた。quelpaとかも使ってみたのだけどイマイチぱっとしなかったり。
そんななか、straight.elの存在を知って使ってみたのですが、これがけっこういいかんじ
何がいいって、use-packageのキーワードに追加してくれるので、何も考えなければ、これまで :ensure t
と書いてたところを :straight t
と一括置換するだけで、とりあえず乗り換えが完了するのです。
(use-package dash :straight t)
こんな感じで、あまり移行を意識しなくてすむのがよい。
melpaで管理されているもののフォーク版を使いたいものの例としては、key-chordがあります。
これも、 :straight
キーワードをちょっと変えて取得先を指定するだけなので、非常に簡単に書けます。
(use-package key-chord :straight (:host github :repo "zk-phi/key-chord" :branch "master"))
正直、ここまで意識せずに移行できると思わなかったので、なぜ早く乗り換えなかったのか悔むばかり。以前からstraight聞いてたけど、readme.mdがちょっと読みにくくて、どこがキモかよくわからなかったのよね。。。まあ、だからこそこのエントリを書くにいたったのですが。
use-packageで最近知った機能3選
最近、.emacsの整理をしていて、use-packageがデフォルトでdiminishを付属させないことを知りました。
で、ついでにREADME.orgをあらためて読みかえしてたら、これまで知らなかった面白い機能がいくつかあることを知ったので紹介。
(package.elを使うと、emacs上でアップデートができてしまうので、破壊的な変更に気付かずに、いつのまにか挙動変わってる...ってことがしばしばあるのが厄介ですね)
ちなみに、use-packageの基本的な使いかたは、kai2nenobuさんの、この記事がお勧め。
imenuに対応させる
個人的に今回かなりのヒットだったのがこれ。
(setq use-package-enable-imenu-support t) (require 'use-package)
とすると、emacslisp-modeの場合に、use-packageの各パッケージ設定がimenuに載ってきてくれる。
ポイントは、requireの前に書くこと。
これまで、init.elから目的の設定を探すのに、isearchしてみたり、helm-swoopしてみたりと苦戦していたのだけど、一発でジャンプできるようになった。
use-packageの状況を確認する
(use-package-compute-statistics t)
しておくと、M-x use-package-reportで、各パッケージの読み込み状況(:configまで進んだとか、:initまでとか)がわかる。
状況が変化した日時と時間がわかるのだけど、多分累計じゃないと思う。org-modeのloadがこんな短いわけがないので。
key-chordを設定する
key-chordは、emacsに同時押しを実現するパッケージです。
use-packageのマクロにこの設定を組み込むことができるらしい。
(use-package use-package-chords :ensure :config (key-chord-mode 1)) (use-package view :ensure :defer t :chords (("jk" . view-mode)))
こんな感じで、:chordsキーワードが使えるようになる。
ただ残念なのが、使えるのがオリジナルのkey-chordなので、暴発問題が残っている。
こちらの改良版の方が便利だと思うので使いどころかも(うまく上書きできればよさげだけど)
ちなみに、このマクロ拡張のための機能が提供されているようで、純正のもの以外にも、hydraを使えるようにするuse-package-hydraなんてのもあるみたい。
おわりに
README.orgを読むと、他にもいろいろ書いてある(:afterキーワードでandとかorとか使えるってことも知らなかった。使いどころは?だけども)ので、当然のことながら、原典をまずは読むべし。
Emacsで簡単にゲームが作れるgamegrid.el
この記事は、Emacsアドベントカレンダー 18日目の記事です。
ちょっとEmacsで、わけあって *1 ドット絵的なものを書きたく、いろいろ調べてるうちにgamegrid.elに出会いました。
gamegrid.elは、Emacs本体に同梱されている、まさにgridなgameを作るためのパッケージです。
gamegrid.elで調べると、いろいろ出てきますが、emacsに内蔵されているpongとかsnake、tetrisなんかに使われています。
そして、使いかたがよくわからず、ぐぐっても使ってみた的記事があっても使い方がよくわからなかった(みつからなかったけど、infoに該当セクションあったりするのかな?)ので、実際使われているものをソースコードリーディングしながら使ってみました。
ソースコードリーディングする上では、snakeゲームのソースコードがわかりやすかった。tetrisはちょっと複雑。
github.com
内容と解説
理解しきれてないところもあるので、間違いあればマサカリください。
どうやって描画するか
(defconst sample1-player 0) (defconst sample1-block 1) (defconst sample1-blank 7) (defconst sample1-border 8) (defconst sample1-space 9)
gamegridの名のとおり、gridベースなので、x, y座標に対して、そのセルが何であるかを指定することになる。その指定のための定数定義。
(defvar sample1-player-options '(((glyph colorize) (t ?\040)) ((color-x color-x) (mono-x grid-x) (color-tty color-tty)) (((glyph color-x) [1 0 0]) (color-tty "red"))))
(defun sample1-display-options () "." (let ((options (make-vector 256 nil))) (dotimes (c 256) (aset options c (cond ((= c sample1-blank) sample1-blank-options) ((= c sample1-player) sample1-player-options) ((= c sample1-block) sample1-block-options) ((= c sample1-border) sample1-border-options) ((= c sample1-space) sample1-space-options) (t '(nil nil nil))))) options))
先の定数あたりも使って、どのセルをどう描画するかを決めるところ。Xの場合は、 [1 0 0]のようにvectorで、r g bを0から1の範囲で設定する。
ターミナルの場合は、"red"のように、指定できる。ターミナルの場合になにが使えるのかは調べてない。
また、Xを使う場合は、画像をxpm形式で指定できるはず。未指定の場合は、デフォルトのブロック画像が使われる。
この辺を駆使するため、以下の初期化処理でいろいろ設定する。
初期化処理と毎ターンの描画
基本的にはメジャーモード書いて作る感じ。それ以外のgamegrid特有の内容だと以下な感じ
(defun sample1-start-game () "." (interactive) (sample1-reset-game) ;;(use-local-map sample1-mode-map) (gamegrid-start-timer sample1-tick 'sample1-update-game))
gamegrid-start-timerで何ミリ秒間隔でどの関数をコールするかを設定できる。このタイマー間隔がFPS維持するような仕組になってるかどうかは未調査。
sample1-reset-gameの中では、 (gamegrid-kill-timer)でタイマーを念の為リセット + 以下のような感じで、gamegrid-init-bufferにより、gamegridで描画する範囲と初期値を指定している。
(defun sample1-init-buffer () "." (gamegrid-init-buffer sample1-width (+ 2 sample1-height) sample1-space) (let ((buffer-read-only nil)) (dotimes (y sample1-height) (dotimes (x sample1-width) (gamegrid-set-cell x y sample1-space)))))
ついでに、gamegrid-set-cellを使うことで、指定したセルを別の内容に塗り替えることができる。
なので、updateの内容も、変更点を計算して、キャラが移動する場合は、前の時刻のセルを消して、新しい座標のセルを描画する、とかの処理をすることになる。
ゲームの終了
(defun sample1-stop-game () "." (interactive) (gamegrid-kill-timer) (gamegrid-add-score "sample1-scores" sample1-total-cell))
gamegrid-add-scoreを使うことで、ハイスコアランキング(ただしローカル)を簡単に作れて便利
その他
グリフをブロック以外に自分が描画したものも使えるらしいので、試してみたい。
まとめ
なんか旨い使いかたないかなーと思うのですが、更新が激しいものを作るには適してない(結構チューニングが必要)ようなので、ピクロス的なゲームとか作るのには向いてそうです。
*1:挫折したけど途中まで自分も秋口ぐらいからNESエミュレータ作ろとせっせと書いてた。なので、gongoさんのアドベントカレンダーがとても楽しみです