ARMERIA

通信とWeb技術とRubyとゲーム制作と

Rubyでポンの対戦を作っています

まだ全然できてないけど。

f:id:betrue12:20150527224317p:plain

とりあえずフィールドを2つ並べて、左は自分が動かして、右はAIで動かして、どちらかがゲームオーバーになったら終了、っていうのはできました。

あとはオジャマを作れば一応対戦はできる…かな?オジャマを作るのもけっこう面倒そうですが。

ゆくゆくはこれの入力と描画をJavaScriptなどに置き換えて、Web上にホスティングしてブラウザで遊べるようにするとか、オンライン対戦とか、できたらいいなーという野望を持っているのですが…いつになることやら。まだまだ勉強すべきことはたくさんありますね。

インタフェースのMACアドレス「だけ」を取ってくる方法はないものか

通信系のエントリを書くのは初めてかも?

Linuxの場合、インタフェースのMACアドレスとかIPアドレスとかは、ipコマンドで表示することができます。(実行環境はVirtualBox上のCentOS 6です)

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:3d:94:80 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
    inet6 fe80::a00:27ff:fe3d:9480/64 scope link
       valid_lft forever preferred_lft forever

ただ単にipコマンドを使うだけだと、上記のように色んな情報がずらずらーっと出てきます。

目視で確認するだけなら上の方法で構わないんですが、スクリプトで処理を自動化する時などは、1つのインターフェースの1つの情報だけを取ってきたい、ということがたまにあります。

ipコマンドのオプションとか、別のコマンドでできないかな?と思って色々調べましたが、どうやらなさそうなので、泥臭いですがシェルスクリプトで文字列操作して取り出す、という方法を考えました。

まずip addr show eth0などとすると、表示対象を1つのインタフェースに限定することができます。

$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:3d:94:80 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
    inet6 fe80::a00:27ff:fe3d:9480/64 scope link
       valid_lft forever preferred_lft forever

MACアドレスを取ってきたい場合、目当ての値はlink/etherの直後にあるので、まずlink/etherが含まれる行だけを取ってきます。

$ ip addr show eth0 | grep link/ether
    link/ether 08:00:27:3d:94:80 brd ff:ff:ff:ff:ff:ff

最後にsedMACアドレスだけを持ってきます。表示の形式に合わせて正規表現を組みます。

$ ip addr show eth0 | grep link/ether | sed -E "s@.*link/ether\s(\S+)(\s.*|$)@\1@g"
08:00:27:3d:94:80

sedの式は、ラベル(ここではlink/ether)とスペースに続く値を取り出します(\S+の部分が本命の取って来たい値)。/ipの出力内容によく含まれているので、区切り記号は登場しなさそうなものに変更。

同じようにIPv4アドレスは以下のような感じ。サフィックスは除去するようにしてみました。grep時に単にinetとするとinet6にもマッチしてしまうので注意。

$ ip addr show eth0 | grep "inet\s" | sed -E "s@.*inet\s(\S+)/[0-9]+(\s.*|$)@\1@g"
10.0.2.15

当然この方法はipコマンドの出力形式に依存するので、バージョンが変わった時の動作保証はできません…あとは表示内容次第で思わぬ正規表現のマッチが起こってしまうかも。何かスマートな方法はないものか。

GitLabでEncode Errorが出て困ったらとりあえず最新バージョンにしよう

社内でGitLabを立てて活用しているのですが、日本語の扱い関係で引っかかったのでメモ。

環境

  • CentOS 6.6
  • GitLab 7.0.0(rpmで入れたもの)

エラーの内容

Tag名に日本語を含んだものを登録すると、ブラウザからアクセスしたときに「Encode Error」が発生し、ファイルブラウザやコミットログなどが見れなくなる。クライアント側から当該タグを消すことができないし、ブラウザからタグを消すボタンにも辿りつけなくなる。

利用者に周知して、日本語は使わない、という運用にしてしまうのが普通だとは思いますが、日本語含みタグは作成できてしまうのに削除できない、という状況はあまり健全ではないですよね。

結論

GitLabのバージョンを7.9.4に上げたら解決しました。(記事作成時点での最新)

GitLab開発プロジェクトでのIssueでも挙がっていて、7.9にてfixされた、と書いてありますね。

https://gitlab.com/gitlab-org/gitlab-ce/issues/696

アップデート

以下の公式ドキュメントにある通り。元々rpmで入れてたので、アップデートもラクチンでした。

https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md

「Updating from GitLab 6.6 and higher to the latest version」に従って最新へ。バックアップは必ず取りましょう。

$ sudo gitlab-ctl stop
$ sudo gitlab-rake gitlab:backup:create
$ curl -O https://downloads-packages.s3.amazonaws.com/centos-6.6/gitlab-7.9.4_omnibus.1-1.el6.x86_64.rpm
$ sudo rpm -Uvh gitlab-7.9.4_omnibus.1-1.el6.x86_64.rpm
$ sudo gitlab-ctl reconfigure
  # ここで色々WARNが出たけど、気にしなくていいのかな?
$ sudo gitlab-ctl restart

マシンのパワー不足か、再起動して1分くらいはブラウザで見ても502が返ってくるけど、そのうち見れるようになりました。

あとは「画面のレイアウト変わっちゃったけどごめんね☆(・ω<)」みたいなメールを利用者に投げて作業終了です。お疲れ様でした。

旧バージョンを入れたのが昨年の7月ごろで、半年以上のアップデートを溜めてたので、だいぶインターフェースは変わったように見えましたね。日本語タグもきちんと表示&削除できるようになって、めでたしめでたし。

おまけ:カスタマイズがreconfigureで戻っちゃった時は

GitLab導入時に色々設定をいじったのですが、本来gitlab-ctl reconfigureで自動生成されるべき設定ファイルを、直接書き換えちゃってることがありました。

今回アップデートに伴って再度reconfigureをかけたので、その設定がリセットされて少し戸惑いました。

本来は/etc/gitlab/gitlab.rbに設定を書いて、gitlab-ctl reconfigureがそこを読んで各種設定ファイルを作るのが正しいです。下記サイトに大変お世話になりました。

続・GitHubクローンのGitLabを5分でインストールした - アルパカDiary

よくあるのがApacheやらJenkinsやらとのポート競合を避けるためのポート番号変更だと思います。これがリセットされると、gitlab-ctl startではOKと出てもブラウザからアクセスできなかったり502を吐き続けたりするので、注意しましょう。

リファクタリングと妥協を繰り返して君だけの.rubocop.ymlを作ろう

前回Rubocopで315個の警告を出した話を書きましたが、色々警告の意味を調べたり試行錯誤しながら、.rubocop.ymlを作って警告ゼロまで持っていきました。

超あまあま設定です。

Lint/NonLocalExitFromIterator:
  Enabled: false

eachの中でreturnとか書くのを許す設定。

def hoge
  (0...X).each do |x|
    return if ...
  end

  ...
end

のように、配列の要素を順に見て行って、要素の1つでもある条件を満たしていたら即メソッド終了、みたいなときに使いたい。

Lint/UnderscorePrefixedVariableName:
  Enabled: false

_で始まる変数名を許す設定。

(0...X).each do |x|
  (x...X).each do |_x|
    ...
  end
end

のように、同じ次元(?)の変数で多重ループを回すときの変数名として使いたい。

Metrics/AbcSize:
  Max: 35

Metrics/ClassLength:
  Max: 200

Metrics/CyclomaticComplexity:
  Max: 9

Metrics/MethodLength:
  Max: 25

Metrics/LineLength:
  Max: 100

Metrics/PerceivedComplexity:
  Max: 11

メトリクス系はわりと適当ですが、リファクタリングしていって「これ以上減らすのはしんどい」と思った時点での値を目安にしてます。

Style/BracesAroundHashParameters:
  Enabled: false

ハッシュをメソッドの引数にするときに{}を省略できるけど、書いてもいい設定。

Style/Documentation:
  Enabled: false

ドキュメント用のコメントは付けなくていい設定。

Style/GuardClause:
  Enabled: false

Style/Next:
  Enabled: false

ガード節にできるところでif endでもいい設定。ガード節にするかif endにするかは意味として自然なほうを採用したいので、行数だけ見てガード節にさせようとする設定はオフに。

Style/HashSyntax:
  EnforcedStyle: hash_rockets

ハッシュリテラル書くときの=>これ。前回も書きました。

Style/LineEndConcatenation:
  Enabled: false

文字列などを複数行に渡って書くときに、行末の+でつないでもよい設定。単に他言語と共通する記法にしたいだけ。

Style/NumericLiterals:
  Enabled: false

長い桁の数を10_000などと書かなくてもいい設定。慣れない。

Style/RedundantReturn:
  Enabled: false

returnがなくてもいい場所で書いてもいい設定。明示的に値を返してメソッドが終わる時は書くようにしたい。書いてないと、後ろ側に何か処理を加えたい時、そこにreturnがあるべきなのかどうか分からなくなりそう。

Style/SymbolProc:
  Enabled: false

array.map(&:somemethod)みたいな書き方ができる場所でしなくてもいい設定。慣れない。

.rubocop.ymlに書いた項目は以上。Ruby哲学は難しい…

Rubocopを走らせたら664行のコードで315個のoffenseが出た

昨日の時点でのRubyでポンのコードをかけてみたら、

1 file inspected, 315 offenses detected
Created .rubocop_todo.yml.
Run `rubocop --config .rubocop_todo.yml`, or
add inherit_from: .rubocop_todo.yml in a .rubocop.yml file.

ひどい。

警告数ランキング

吐き出された.rubocop_todo.ymlを見て、多いやつから順に見ていく。

1位:77個

# Offense count: 77
# Cop supports --auto-correct.
Style/TrailingWhitespace:
  Enabled: false

ぶっちぎりで大量に警告されてるのが、行末のスペース。要は何かというと、インデント中の空行のスペース。

行を足すときはあったほうが何かと便利だし、わざと残してる人もけっこういるみたいで、悩みどころ。エディタでの保存時に行末スペース全部取ってくれる機能があったので、それを使うことにした。

空行のインデントは活かす前提で、空でない行の末尾スペースを警告してくれる設定があればいいのにな、とは思います。

2位:33個

# Offense count: 33
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
Style/BlockDelimiters:
  Enabled: false

{}do endをちゃんと使い分けろって話。今のエディタだとeachの後にスペースを入れた時点で{|| }まで補完してくれるので、全部{}になってた。

ぶっちゃけどっちでも良いような気もしていたが、「if や while が end で終わるので、それとそろえたほうがよい」というのを見て確かにそうだと思ったので、行をまたぐ場合はdo endに直すことにする。ついでにエディタも乗り換えてやろうか。

3位:24個

# Offense count: 24
# Cop supports --auto-correct.
# Configuration parameters: MultiSpaceAllowedForOperators.
Style/SpaceAroundOperators:
  Enabled: false

演算子前後のスペース不足ね。はい、心当たりあります。

panels[x+1][y-1]
# panels[x + 1][y - 1] って書けって言われてる

こういう奴。個人的には前者のほうが見やすいんだけど…ちょっと保留。

4位:23個

# Offense count: 23
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
Style/SpaceInsideHashLiteralBraces:
  Enabled: false

ハッシュの{の後とか}の前にスペースが要るか、とかそういう話。両方試したけど特にどっちでも良いかなと思ったので、デフォルト設定に従ってみる。

少し特殊ケースですが、

input_hash = { :up          => Input.key_push?(K_UP),
               :down        => Input.key_push?(K_DOWN),
               :right       => Input.key_push?(K_RIGHT),
               :left        => Input.key_push?(K_LEFT),
               :exchange    => Input.key_push?(K_SPACE),
               :force_slide => Input.key_down?(K_Z)      }

みたいな時の閉じカッコってどこに置くのがスマートなんでしょね。何となくの感覚で、最長の行の末尾+スペース1個の位置に置いてるけど。

5位:22個

# Offense count: 22
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles, UseHashRocketsWithSymbolValues.
Style/HashSyntax:
  EnforcedStyle: hash_rockets

=>これ。すいません、これは使わせてください。好きなんです。

理由は単純で、リテラル記述と値の取り出しでキーの見た目が同じになるから、です。

hash = { :first => 1, :second => 2 }
puts hash[:first] # 見た目が同じ

hash = { first: 1, second: 2 }
puts hash[:first] # 見た目が変わる

まとめ

315個はさすがにビビりましたが、種類自体はそんなに多くないので…一通り意味を調べて、「これは譲れねえ!」ってやつだけ見逃してもらって、自動修正(auto_correct)してもらおうと思います。

本当に怖いのは、登場回数が少なくてかつauto_correctできないやつだったりしますよね(クラスが長いとか…)

パズルゲームのAIってどうやって作ればいいんだろう

今日、将棋の棋士とコンピュータが戦う電王戦(in ニコニコ生放送)を観てました。私は第3戦から見始めたのですが、すごく面白かったですね。多くの解説者やコメントが、無条件に棋士を応援する感じだったのは、ちょっと違うよなあと思ったのですが。

パネポンのAIを作りたい

さて、あの将棋ソフトのようなすごいものを作ろうとは全く思ってないのですが、パネルでポンのクローンとして作っている「Rubyでポン」にも、AIを付けたいとは思っていまして。

パネル交換や消滅といった基本動作、連鎖と同時消しの判定まではできてきたので、次にやるとしたら対戦かなと。対戦だとおじゃまを落とすとかの機能ももちろん必要なのですが、対戦相手のAIはとにかく何をすれば良い感じに実装できるのか全く予想がつかないので、最近は「パズルゲーム AI」みたいなので検索をかけてたりしたわけです。いまいち良い情報はなかったですが。

というわけで、どういう要素を作ればいいのかもやーっと考えているものを、整理も兼ねて書き出してみようと思います。評価関数式とか強化学習とか今の自分の知識じゃできっこないのでルールベースです。

ルール1:パネルを消す

横を入れ替えて行けば消せるパネルを探して消す。基本となる操作です。横1列に同色パネルが3つあるか、隣り合う横3列で1個以上ずつ同じ色のパネルがあれば、揃えにいく、とかですか。

パネルを一度落とさないと(高さの座標を崩さないと)消せないパターンについては、上記の単純な方法だと探せないので、難しいかも。

ルール2:同時消しを狙う

基本的には縦消しが主だと思いますが、隣り合う横4列以上に同じ色があった場合、同時消しを狙うようにします。

ルール3:連鎖を狙う

ちょっと難しくなってきました。組み連鎖のパターンを覚えてもらって、その形が横移動だけで作れそうかということを判定するとか(ぷよぷよだと、連鎖の形はかなり定跡化されてますよね)。ただそれだとパネポンっぽくないので、なんとかしてアクティブ連鎖をさせられないかなー。

ルール4:整地する

対戦ですぐ死なないようにするためには必要でしょう。特に実装は難しくなさそう?

消える処理の探索を簡単にするために、常にある程度は整地しておく、というアルゴもありかなーとは思います。

ルール5:おじゃまを優先して消す

これも対戦用。おじゃまを待ち構える連鎖(いわゆるカウンター)もできるといいですね。

本当は詰み防止の消せるパネル残しとかもやりたいけど、とりあえずそこまではいいかな…

さあ、作ろう

みたいなのを考えつつも、まだ全然コードに手をつけられていない自分。明日は書こうと思います。

PandocでMarkdownからPDF化を試してみた

最近、やれ社内向けの説明資料作りだ、やれ雑用だ、という感じで、仕事で開発っぽいことができていません…

社内資料ではExcelPowerPointを使わないと一部のオジさん達が嫌な顔をするので(何故かWordは使わない)、ファイル名に日付入れてメールで送るみたいな儀式をやっていますが、開発文書くらいはplaintextベースで作ってgitで管理・メンテしたい!ということで、過去の遺産をMarkdown化する作業をこそこそと少しずつ進めています。

んで、Markdownで書いたものをPDF化する際に、試してみたのがPandocです。

Pandocについて

Pandocは異なる形式のドキュメントやマークアップ言語を変換することができるツールです。HTML、Markdown、LaTeX、そしてWordのdocxファイルなども対応しています。

Pandoc - About pandoc

ここに書いてるグラフ、これを作る意味があるのかは分からないけどなんとなくすごさは伝わってくる。入力docxで出力Markdownとかもできるみたいだけどどうなるんだろう。

MarkdownからPDF化する、という点に限って言うと、利点は

  1. インストールが楽(Pandocだけ、ならば)
  2. コマンド一発で変換できる
  3. Markdownを章や節ごとに分割して、結合してPDF化、みたいなことができる

などなど。逆に欠点は、

  1. PDF化するには別途LaTeXをインストールする必要あり。
  2. 仕上がりにLaTeXの癖が出る。スタイルをカスタマイズするのにはLaTeXの知識が必要。
  3. ちょっと変換に時間がかかる。

とかかな。

手順

環境は会社でWindows 7でやって、自宅でWindows 8.1で試しました。

Pandocをインストール

Releases · jgm/pandoc · GitHub

最新のリリースノートの末尾から自分の環境に合ったものをダウンロードします。今回はWindowsなのでmsiを選択。立ち上げて特に問題なくインストール。

パスは自動で通ってくれるものの、コマンドプロンプトに反映されてくれない場合があるようです。GitHubのIssueを見ると、環境変数の設定画面を開けばいい、みたいなことも書いてありますが、自分は1回アンインストールしてインストールし直すとなぜか上手くいきました。

コマンドプロンプトを立ち上げて

>pandoc -v

でバージョン表記が出ればOKです。

LaTeXインストール

PDF化のために必要なLaTeXをインストールします。自分は学生時代にずっとLaTeX使ってた影響で、入社1年目の時はLaTeXで技術文書書いてたので、既に入ってました(きっと周りからは変人と思われていたでしょう…)

Microsoft Windows - TeX Wiki

新しく入れる場合はこちらを参考に。W32TeXTex Liveの2通りが紹介されてますが、TeX Liveのほうが多分かんたんです。LaTeXインストールだけでわりと時間かかりますががんばりましょう。

Markdownをごりごり書く

今回は、複数ファイルに章を分割して書く、というのをやってみます。結合してからPandocに食べさせても別にいいのですが、Pandocなら変換時に結合することができます。今回はファイル名を次の2つとします。

  • 01_hoge.md
  • 02_fuga.md

PDFを作ってみる

基本コマンドは

>pandoc 01_hoge.md 02_fuga.md -o test.pdf

こんな感じです。(ファイル名ベタ書きなのがちょっとダサいですが、bash等と違い、*.mdとかで展開されないので…何かいい方法ないかな。)

ただ、このままでは文章中の日本語が消えてしまうので、オプションを付けます。

>pandoc 01_hoge.md 02_fuga.md -o test.pdf -V documentclass=ltjarticle --latex-engine=lualatex

このコマンド一発でPDFが作れます!素晴らしい。

自分はこれに加えて

  • 整形済みテキストのフォントをConsolas
  • 見出しに番号を付ける
  • 文書の最初に目次を作る

というのを指定して、以下のオプションで使っています。

>pandoc 01_hoge.md 02_fuga.md -o test.pdf -V documentclass=ltjarticle -V monofont=Consolas --latex-engine=lualatex -N --toc

.batファイルとしてリポジトリに入れておけば、ファイル名もオプションも含めてメンテできますね!

気になる点

結局は一度LaTeXにしてからPDF化しているので、LaTeXの癖が良く出ます。一番気になるのが、整形済みテキストが自動改行されない点ですね(長いコマンドとかを書いてると困る)。あと、ハイフンとかアンダースコアとかの一部の記号は、たまに変な表記になったりします(フォントを上手く選べば解決することも多い)。

LaTeXのテンプレートはちゃんとカスタマイズできるようになってるみたいなので、そっちでどうにかなる部分は直していきたいですね。

参考にしたサイト

Pandoc ユーザーズガイド 日本語版 - Japanese Pandoc User's Association

HTML - 多様なフォーマットに対応!ドキュメント変換ツールPandocを知ろう - Qiita