2011年11月9日水曜日

Clojureでゲームプログラミングその1 実験編

0 コメント
移転先にも同じ記事ですがのせました。

はじめに

LispでGAMEつくろうかなと。 最初CommonLispでつくろうかなと思ったけども、現時点でCommonLispとClojure(+Java)を比較したときに 後者のほうが詳しいかなということで、まずはClojureでつくってみて、落ち着いたらCommonLispでもつくってみようかなと。

とりあえず、下記の流れで、画像を表示して動かすところまでチャレンジしてみる。

  • ウィンドウ表示
  • 画像表示
  • メインループの実現
  • 画像移動

もちろん前回インストールしたSLIMEとLeiningenを使って作業しますよ。 ちなみにClojureで「もの」を作るのは初めてなので、ホント手探りです。 しかもClojureでGameプログラミングの情報ってほとんどないんだよな。さて。

ウィンドウ表示

ClojureでのGUIはさっぱりわからんけども、とりあえずJavaのサンプルとかを頼りにウィンドウを表示してみる。

(import (javax.swing JFrame))
(def frame (JFrame. "Clojure SampleGame")) 
(doto frame
(.setSize 640 480)
  (.setVisible true))

でた。簡単すぎる!結構感動します。

次は画像を表示してみる。

画像表示

まずは、画像ファイルの読み込みだ。下記のコードをclojureで動かしてみる。

java.awt.image.BufferedImage bimage;
image = javax.imageio.ImageIO.read(new java.io.File("hoge.png"));

下記の画像を表示してみる。これは昔作ったゲームで使用したものでアニメーションパターンもはいってる。

画像ファイルはプロジェクト直下においてある(REPLを起動したディレクトリ)。 外部ファイルはプロジェクトルートからの相対パスでOKのようである。

(import (java.awt.image BufferedImage))
(import (javax.imageio ImageIO))
(import (java.io File))
(def image (ImageIO/read (File. "gai.png"))) 

とりあえずうまく読み込めたっぽいので、ウィンドウに表示してみる。

(import (java.awt Graphics))
(def graphics (.. frame (getGraphics)))
(doto graphics
  (.drawImage image 0 0 frame))

でた!

線もかけたよ。

(.. graphics (drawLine 0 0 640 480))

ところでこれまでの画像表示はタイトルバーにめり込んでしまっている。 これは描画命令の座標原点が、ウィンドウそのもの左上を原点としているためである。 これを回避するには、Graphics#translateを呼び出す。

位置調整

ずれの原因であるタイトルバーや枠お情報は、java.awt.Insetsとういクラスに格納されている。 これはJframe#getInsetsメソッドで取得でいるので、束縛しておく。

(def insets (.. frame getInsets)) 

確認。それっぽい値が入っている。

user> insets
#<Insets java.awt.Insets[top=24,left=1,bottom=5,right=1]>

では、このinsetsを使って描画用原点をずらしてみる。

(.. frame (setVisible true))
(.. graphics (translate (.. insets left) (.. insets top)))
(.. graphics (clearRect 0 0 640 480))
(.. graphics (drawImage image 0 0 frame))

これで左上原点が、ずれていい具合に表示された。

insentsを利用したついでに説明。 実はウィンドウサイズで640x480を指定しているけれども、 タイトルバーや枠のサイズがあるため、描画領域は640x480よりもちょっと小さい。 なので、例えば640x480ちょうどのサイズの画像を表示しようとしても少し切れてしまったりする。 ということで、insentsを利用して描画領域が純粋に640x480似なるように調整する。

(doto frame
    (.setSize (+ 640 (.. insets left) (.. insets right)) (+ 480 (.. insets top) (.. insets bottom)))
    (.setVisible true))

以上で描画領域の細かい調整が完了した。

画像の一部を表示したい

ところで、現状だとアニメパターンがすべて表示されていてみっともないので 一部だけを表示したい。 この要件を満たすには、Graphics#DrawImageで下記のように引数を指定すれば良い。

(.drawImage
    image   ;; 描画画像
    0 0     ;; 転送先の左上座標
    32 32   ;; 転送先の右下座標
    0 0     ;; 画像元の左上座標
    32 32   ;; 画像元の右下座標
    frame)  ;; 描画対象
(import java.awt.Color)
(def clear-color (Color. 0 0 127))

(let [g (.. frame (getGraphics))]
  (doto g
    (.translate (.. insets left) (.. insets top))
    (.setColor clear-color)
    (.fillRect 0 0 640 480)
    (.drawImage image
                0 0 32 32
                0 0 32 32
                frame)
    (.dispose)))

なんども描画してると、前の画像が残って確認しづらいので 描画前にクリアカラーで塗りつぶしている。 ついでにグラフィックオブジェクトもその都度破棄するようにした。

つぎはゲームのかなめ、メインループを実現してみる。

メインループの実現

メインループの実現方法にはにはいろいろあけれども とりあえず動かすことが目的なので、 実装が簡単そうなjava.util.TimerTaskを使用してみる。

Clojureで継承が必要なJavaクラスを使うには、proxyを使う。 以下のようにTimerTaskを継承したクラスをつくる。

(import (java.util Timer TimerTask))
(import (java.util TimerTask))
(def mainloop
  (proxy [TimerTask] []
    (run []
      (println "呼びだされた"))
    )) 

テストしてみる。

user> (.. mainloop run) 
呼びだされた
nil

うまくいっているようである。

続いてTimeクラスにmainloopを渡してみる。これがはまった。

user> (.. (Timer.) schedule mainloop 0 500)

Malformed member expression
  [Thrown class java.lang.IllegalArgumentException]

Restarts:
 0: [QUIT] Quit to the SLIME top level

Backtrace:
  0: clojure.lang.Compiler$HostExpr$Parser.parse(Compiler.java:825)
  1: clojure.lang.Compiler.analyzeSeq(Compiler.java:5369)

「Malformed member expression」の原因がわからなくてかなりはまった。 ぐぐった結果、javaのlong型を引数として渡すときは、long関数を呼び出す必要があることがわかった。

user> (.. (Timer.) schedule mainloop (long 0) (long 500))
No matching field found: schedule for class java.util.Timer
  [Thrown class java.lang.IllegalArgumentException]

Restarts:
 0: [QUIT] Quit to the SLIME top level

Backtrace:
  0: clojure.lang.Reflector.getInstanceField(Reflector.java:245)
  1: clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:267)

とここでまたエラー。でまたぐぐった結果、メソッドの呼び出し方が間違っていたorz。 scheduleをカッコでくくらないと駄目らしい。

user> (.. (Timer.) (schedule mainloop (long 0) (long 500)))
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
呼びだされた
nil

ということでやっとTimerクラスにTimerタスクで作ったメインループを渡すことができた。ふぅ。 次は画像を動かしてみる。

画像の移動

いよいよ画像に魂を与える。その為には状態を管理しなければならない。座標ですね。 Clojureはデフォルトでは値を更新できない。これを変更するためには特別な定義が必要。 スレッドを使う予定は今のところ無いので、扱いの簡単そうなatomを使用してみる。

;; プレイヤー定義
(def player (atom {:pos [0 0]}))

こんな感じで参照できる。

user> (@player :pos)
[0 0]
user> 

x座標は配列の0番目

user> (nth (@player :pos) 0) 
0

y座標は配列の1番目

user> (nth (@player :pos) 1) 
0

playerの座標を更新してみる。

user> player
#<Atom@2a134eca: {:pos [0 0]}>

;; 変更
user> (swap! player assoc :pos [0 1])
{:pos [0 1]}

;; たしかに更新された
user> player
#<Atom@2a134eca: {:pos [0 1]}>

x座標を更新させる

user> (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1])
{:pos [1 1]}
user> (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1])
{:pos [2 1]}
user> (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1])
{:pos [3 1]}
user> (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1])
{:pos [4 1]}

ではここまでの移動処理を組み込む。

(def mainloop
  (proxy [TimerTask] []
    (run []
      (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1]) ;; 座標更新
      (if (< 640 (nth (@player :pos) 0))
             (swap! player assoc :pos [0 0]))

      (let [g (.. frame (getGraphics))
            player-x (nth (@player :pos) 0)
            player-y (nth (@player :pos) 1)]
        
        (doto g
          (.translate (.. insets left) (.. insets top))
          (.setColor clear-color)
          (.fillRect 0 0 640 480)
          (.drawImage image
                      player-x player-y
                      (+ player-x 32) (+ player-y 32)
                      0 0 32 32
                      frame)
          (.dispose))))))

定義したメインループを20ms間隔で呼び出す。

(.. (Timer.) (schedule mainloop (long 0) (long 20)))

画面がちらつきというか、画像が点滅していて話にならない。次はこのチラツキを抑えるために java.awt.image.BufferStrategyを使ってみる。

ちらつき防止対策

下記のようにJFrame#setIgnoreRepaint, JFrame#createBufferStrategyを呼び出しバッファの準備をする。 この時注意すべきは、JFrame#createBufferStrategyはJFrame#setVisibleのあとに呼び出さなければならないこと。

(doto frame
    (.setSize (+ 640 (.. insets left) (.. insets right)) (+ 480 (.. insets top) (.. insets bottom)))
    (.setVisible true)
    (.setIgnoreRepaint true) ;; ウィンドウの再描画を無効に(BufferStrategyを使うので)
    (.createBufferStrategy 2) ;;  setVisibleメソッドのあとで呼ばないと実行時エラーになる
    )
;; バッファ作成
(def buffer (.. frame (getBufferStrategy)))

bufferを使って以下のように呼び出し。TimerTaskクラスオブジェクトは都度生成できるように関数化しておいた。

(defn create-mainloop
  []
  (proxy [TimerTask] []
    (run []
      (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1]) ;; 座標更新
      (if (< 640 (nth (@player :pos) 0))
             (swap! player assoc :pos [0 0]))

      (if (not (.. buffer (contentsLost)))
        (let [g (.. buffer (getDrawGraphics))
              player-x (nth (@player :pos) 0)
              player-y (nth (@player :pos) 1)]
          (doto g
            (.translate (.. insets left) (.. insets top))
            (.setColor clear-color)
            (.fillRect 0 0 640 480)
            (.translate (.. insets left) (.. insets top))
            (.drawImage image
                        player-x player-y
                        (+ player-x 32) (+ player-y 32)
                        0 0 32 32
                        frame)
            (.dispose))
          (.. buffer (show))
          )))))

最終的なコード。

(ns hello-cube.core)

(import (javax.swing JFrame))
(import (java.util Timer TimerTask))
(import (java.awt Graphics Color))
(import (java.awt.image BufferedImage))
(import (javax.imageio ImageIO))
(import (java.io File))

(def clear-color (Color. 0 0 127))
(def frame (JFrame. "Clojure Sample Game")) 
(def image (ImageIO/read (File. "gai.png"))) ;; 画像読み込み
(def player (atom {:pos [0 0]}))

;; 枠を考慮してサイズ指定
(doto frame
    (.setVisible true)
    (.setIgnoreRepaint true) ;; ウィンドウの再描画を無効に(BufferStrategyを使うので)
    (.createBufferStrategy 2) ;;  setVisibleメソッドのあとで呼ばないと実行時エラーになる
    )

;; バッファ作成
(def buffer (.. frame (getBufferStrategy)))
(def insets (.. frame getInsets))  ;; ウィンドウを表示してから出ないと値が入らない。

(doto frame
    (.setSize (+ 640 (.. insets left) (.. insets right)) (+ 480 (.. insets top) (.. insets bottom))))


(defn create-mainloop
  []
  (proxy [TimerTask] []
    (run []
      (swap! player assoc :pos [(+ 1 (nth (@player :pos) 0)) 1]) ;; 座標更新
      (if (< 640 (nth (@player :pos) 0))
             (swap! player assoc :pos [0 0]))

      (if (not (.. buffer (contentsLost)))
        (let [g (.. buffer (getDrawGraphics))
              player-x (nth (@player :pos) 0)
              player-y (nth (@player :pos) 1)]
          (doto g
            (.translate (.. insets left) (.. insets top))
            (.setColor clear-color)
            (.fillRect 0 0 640 480)
            (.translate (.. insets left) (.. insets top))
            (.drawImage image
                        player-x player-y
                        (+ player-x 32) (+ player-y 32)
                        0 0 32 32
                        frame)
            (.dispose))
          (.. buffer (show))
          )))))

(def timer (Timer.))
(.. timer (schedule (create-mainloop) (long 0) (long 20)))

所感

ほとんどJavaのメソッドしか使ってないけどもSLIMEの良さは体感できた。 コードが即時反映されて画像が動くのは楽しい。 理想はSLIME上からGameObjectをリアルタイムに操作することだけど、これを実現するにはatomでは無理かも。 あとはスレッドとSLIMEの関係がよくわかってない。TimeTaskを使いにくく感じたのでメインループは別の方法で実現したい。

次回はもうちょっとコードのリファクタリングを施しつつ、 アニメーションとキーボード操作をできるようにしてみる。

参考書籍

Date: 2011-11-12 10:11:07 JST

2011年6月23日木曜日

Clojureのインストール

0 コメント

はじめに

この記事は、自分がClojureで開発環境を準備するために調べたことをまとめています。

  • 対象読者
    • Clojureに興味あるけど始め方がわからんという人。
    • 素のREPLでClojureしてるけど使いにくいと感じてる人。
    • 普段Emacs使ってるけど、SLIME?なにそれ?って人。
  • 推定環境
    • Java6
    • emacs23
    • lein
    • slime

OSは、Ubuntu10.04とwindows7, windowsxpで動作確認しています。

Clojureのインストールは、ビルドツールのLeiningenを使えばとっても簡単です。 Leiningenとは、Clojure用のビルドツールで、JavaでいうMavenみたいなものです。 プロジェクトのフォルダのひな形や、コンパイル、ライブラリの管理をしてくれます。

普通に考えると、Clojure本体をインストールして、ライブラリのクラスパスを通して、 起動コマンドを設定してなど、いろいろと手間をかけてやっと動くようになるもんですが、 Leiningenは、leinというシェルスクリプトをパスの通った場所におくだけで、インストールが完了しちゃいます。 あとは、指定のコマンドを叩くだけで、leinスクリプト自体がLeiningen本体はもちろんClojure本体もダウンロードしてくれます (Java本体だけは予め入れておく必要がありますが)。

ちなみに読み方は「ライニンゲン」だそうです。自分はいつも「れい人間」と読んでますw。

Leiningenのインストール

まずはJavaの最新版をインストールしちゃいましょう (現時点ではJavaSE7はいろいろ不安定なのでJava6をお勧めします)。

続いて下記サイトからLeiningenのアーカイブを取ってきます。

直リンは以下。

解凍したらできたフォルダをPATHに通します。 自分の場合は%HOME%\binというディレクトリにleinフォルダの中身(windwosの場合は、lein.batとwget.exe、linuxの場合は、lein)をコピーしました。

$ cd %HOME%
$ mkdir bin

Windwosの環境変数だとこんな感じ。

JAVA_HOME=C:\Program Files\Java\jdk1.6.0_24
PATH=%PATH%;%HOME%\bin;%JAVA_HOME%\bin;

linuxの場合は実行権を付与しておきます。

$ chmod +x lein 

パスが通ってるのを確認したら以下でインストール開始。

$ lein self-install

これで最新版のClojureも含めてLeiningenがインストールされます。

$ lein
Leiningen is a build tool for Clojure.
 :
 :

replを起動してインストールされたことを確認します。

$ lein repl

C-cで終了です。

簡単な使い方。

プロジェクト作成

$ cd ~/dev
$ lein new myproject
$ cd myproject

依存関係の解決

$ lein deps

プロジェクトのパッケージング

$ lein jar

Leiningenの次は

Leiningenがあれば、replも起動できるし、あとはテキストエディタさえあれば開発ができます。 極端な話、メモ帳とDOS窓、Linuxだったら端末とviさえあれば開発できるということです。

とはいえ、今時メモ帳で開発なんてありえません。メモ帳や(素の)viではカッコの対応がすぐ崩れてしまいます。 vimや高性能エディタを使えばそれなりに快適な環境が構築できるかもしれませんが、ほとんどLispで書かれているEmacsにはかないません。1 この際カッコでできたEmacsを使っちゃいましょうw。

てことで、Emacs用のIDEであるSLIMEという統合開発環境をインストールします。

SLIMEとは

Emacsには有名な(というかLispのデファクトスタンダードである)SLIMEという開発環境があります。2

SLIME is the Superior Lisp Interaction Mode for Emacs. 

直訳すると「SLIMEはEmacs向けの優れたlisp対話モードです」ですね。

SLIMEの特徴は以下に詳しいですが、

簡単にまとめると

  • デバッガやシンボルの補完
  • 関数の引数表示やドキュメントへの簡単アクセス
  • 処理系ごとに違うREPLの差を吸収したUIの提供

などがあります。

ほかにClojureでSLIMEをつかうメリットとして、スクリプト起動で待たされることがないということがあります。 ClojureはJVMベース故に起動がとても重いので、他のスクリプト言語のようにサクッと起動してちゃちゃっと処理するのが苦手ですが、 SLIMEで開発すればそのようなことはなく、即実行ができるので快適に使えると思います。Linuxなどでいう端末みたいな役割を果たすということですかね。 このように便利なSLIMEですから、多くのLisperはカッコが好きというよりもSLIMEの操作性の虜になってるのではないかっなって思います。

私は最初、LISPの処理系であればなんでもSLIMEが使えるものと勝手に勘違いしていたのですが、 SLIMEはもともとCommonLisp用に開発されたもので、当初はCommonLisp以外の環境はサポートされていなかったようです。 例えばGaucheは、もともとSLIMEに対応していなくて、2009年に最初のバージョンができたようです。意外ですね。

ClojureでSLIMEを利用するには以下のインストールが必要です。3

  • SLIME(SLIME本体、SLIME-REPL)
  • SWANKサーバー(swank-clojure)
  • Clojure-mode

SWANKサーバーとは、SLIMEとLisp処理系を橋渡しするサーバーです。 このSWANKサーバのおかげでClojureでもSLIMEを利用できるわけです。

これらのモジュールは、それぞれ手動でインストールしても良いのですが、 SLIMEとClojure-modeに関しては、最近Emacs界隈で有名なMarmaladeを利用すると簡単です。 MarmaladeはEmacsように開発されたパッケージシステムで簡単にEmacs用ソフトをインストールできます。 次はMarmaladeのインストールを説明します。

Marmaladeのインストール

ほとんど下記にある説明のとおりですが

Marmaladeとは次期Emacs24でサポートされているパッケージ管理サイトで Emacs24からは標準で利用できるらしいです。

インストール方法も上記サイトからのパクリですが

下記を実行して

(install-elisp "http://repo.or.cz/w/emacs.git/blob_plain/1a0a666f941c99882093d7bd08ced15033bc3f0c:/lisp/emacs-lisp/package.el")

下記を.emacsに設定すればインストール完了です。

(require 'package)

;;リポジトリにMarmaladeを追加
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))

;;インストールするディレクトリを指定
(setq package-user-dir (concat user-emacs-directory "vendor/elpa"))

;;インストールしたパッケージにロードパスを通してロードする
(package-initialize)

これでパッケージを簡単にインストールできるようになりました。

SLIMEのインストール

Marmaladeからインストールします。

M-x package-list-package

下記をiで選択し、xで実行してください。

  • slime
  • slime-repl
  • clojure-mode

SLIME自身は、SLIME本体とSLIME-REPLという2部構成になっているっぽいです。 また、Clojure-modeはSLIMEと直接関係ありませんが、ついでにインストールしておきます。

設定は以下になります。

(require 'slime)
(setq slime-net-coding-system 'utf-8-unix
      slime-protocol-version 'ignore)
(slime-setup '(slime-repl))

(require 'clojure-mode)
(add-hook 'slime-repl-mode-hook (lambda () (clojure-mode-font-lock-setup)))

自分の場合、下記のようにslime-fancy, slime-bannerを指定すると動きませんでした。

(slime-setup '(slime-repl slime-fancy slime-banner))

続いてSWANKサーバーをインストールします。

swank-clojureのインストール

Clojure用のswankサーバーは、Leiningenのプラグインとしてインストールします。

$ lein plugin install swank-clojure 1.3.3

下記でswankサーバーを起動できます。

$ cd ~/dev/project
$ lein swank

この方法では、Projectディレクトリに移動してから起動しますが、 こうすることにより、必要なクラスパスが設定された状態でSWANKサーバーが起動されます。

下記は、プロジェクトを指定しない起動方法です。

$ ~/.lein/bin/swank-clojure

この場合、クラスパスの指定がないので、clojure.coreの関数ぐらいしか使えません。

SLIMEの使い方

ここまでで、Leiningen, SLIME, SWANK-Clojure, Clojure-modeをインストールしてきました。 最後にこれらの使い方をまとめます。

REPLサーバ起動

依存するクラスライブラリが設定された状態で起動します。

$ cd myproject
$ lein swank

接続

SWNAKサーバーを起動した状態で、

M-x slime-connect
localhost:4005

とします。

操作方法

C-c C-cカーソル位置のコードのコンパイル
C-c C-zREPLバッファの表示
C-c C-kファイル全体のコンパイル
M-.定義にジャンプ
M-,元の定義に戻る
M-r正規表現で履歴検索

参考URL

SLIMEの操作方法については、下記が非常に参考になりました。

このサイトでも解説されていますが、ACモードやPopwinも入れたほうが格段に操作方法がよくなります。

windowsで日本語を扱う場合

Windowsからlein swankで起動したswankサーバと連携する場合 SLIME-REPL上で日本語を評価すると、サーバーが停止してしまいます。 これを回避するには、lein.batの先頭で下記を追記しておきます。

set JAVA_OPTS=-Dswank.encoding=utf-8-unix

参考書籍

注釈

Footnotes:

1 こんなこというとVimmerから非難されてしまいますがw。

2 SLIMEは、最近ではvimやeclipseからも利用できるようです。普及すればEmacsにこだわる必要はないかもですね。ライトユーザにはEclipseはよさそうです。

3 ちなみに普段emacsを使ってない人は、ClojureBoxをインストールするのが一番手っ取り早いです。 これは、Emacs+SLIME+CLojure、開発環境一式をすべてまるごとインストールしてくれる夢の様なプロダクトです。 Emacsはそれ自体の設定が大変煩雑なので、面倒が嫌いな人はこれをインストールすればOKかと思います。

Date: 2011-11-12 19:20:44

2011年6月1日水曜日

CentOSのインストール

0 コメント

はじめに

自宅サーバもしくは検証用環境としてCUIのLinuサーバーを立てたい。

  • 自宅サーバ
  • Windows環境での懸賞やウェブプログラミング用
ここでは、最もよく利用されてると思われるCentOSのオレオレインストール方法をメモしておく。

VMWareのインストール

WindowsでLinux環境を構築する場合、候補としてVMWareとCygwinがある。 経験的にCygwinでのウェブプログラミングはトラブルが多い。 なのでプログラミング目的であれば素直にVMWareを選択するのが良い。 ただし、どちらがいいというわけでなく、併用するのが一番良いと思う。

  • Web系プログラミングをするときはVMWare
  • その他DOSを補完する意味合いでのLinuxコマンドを利用するときにCygwin

VMWare

  • メリット
    • linuxそのものなので、環境周りの不具合は発生しづらい。
    • システムをスナップショットで保存できる。だから環境をまるごと移動するのが楽ちん。
    • 直接Linuxをインストールした場合ドライバがないためにうまく動かない場合があるが、VMWareならWindowsのドライバが動いてれば多分動く。
    • Windoowsでしか対応していないソフトがどうしてもあるため、直接インストールするとそれらを利用することが面倒になる。
    • Windowsのキーバインド(caps-ctrlの入れ替え)などがそのままLinux上で使える。
  • デメリット
    • マルチモニタとかが使えない。
    • 3Dデスクトップが使えない。
    • 動作が重い
    • パフォーマンスが落ちる。
    • IPアドレス周りで不具合が出るかも
    • ファイルの共有が面倒
    • メモリを食う

Cygwin

  • 長所
    • 早い
    • ファイルの共有が簡単
    • Windows用のファイルにコマンドが適用できる
  • 短所
    • linuxの連携で度々不具合が起きる(パスの指定など)

準備

  • VMWarePlayerをインストールしておく
  • DVDのisoイメージをダウンロード
  • VMWareイメージをメモリ1GBで作成
  • isoイメージをセットして起動する。

VMWareのネットワークアダプタについて

下記でそれぞれのIPアドレスを確認できる。 このIPアドレスはポートフォワードなどをするときに必要になる。

  • VMNet1 - ホストオンリー接続用
  • VMNet8 - NAT接続用

windowsのhosts設定

あらかじめゲストOS側でIPアドレスを確認しておく。 ゲストOSのIPを控えておき、Windowsからアクセスに使用する。

$ /sbin/ifconfig

バーチャルホスト用にローカルドメインをhostsファイルに記述する。

C:/WINDOWS/system32/drivers/etc/hosts
127.0.0.1       localhost.localdomain
192.168.10.x    vmware.localdomain

hotsファイル編集後、下記を実行

dos> nbtstat -R

puttyのインストール

  • ごった煮版をインストールする。
  • winscpもインストールしておく

まずやっておくこと

  • とりあえずシステムを最新にする。

ビープ音を消す。

  • [システム]-[管理]-[ハードウェア]-[サウンド]-[効果音の設定]-[サウンドタブ]
  • [警告音と硬化オウンを鳴らす]をチェックする
  • 残りのチェックを外す。

iptablesの設定

  • [システム]-[管理]-[セキュリティレベルとファイヤウェールの設定]
  • HTTP,HTTPS,SSHにチェックを入れる。

ユーザ周りの設定

rootで作業する。

// ユーザの作成
# useradd miki
# passwd miki

// root権限を許可するグループを作成
# groupadd sysadmins
# usermod -G sysadmins miki ;; グループ追加
# usermod -g sysadmins miki ;; プライマリグループを指定

// sudoを使えるようにする。
// visudoでsudersファイルの末尾にグループを追加する。%を忘れないように。
# visudo
%sysadmins ALL=(ALL) ALL

sshd周り

CENTOSはsshdは初めからあるので、rootの権限周りだけ設定する。

$ sudo vi /etc/ssh/sshd_config

## rootでのログインを禁止
PermitRootLogin no

## パスワード認証の有無
## 外部から接続しないのであれば(vmware)、yesにしても問題なし。
## yesの場合、鍵は必要なし。
PasswordAuthentication yes

$ sudo /sbin/service sshd restart

## ssh経由でログインしたユーザがsuでrootユーザになるのを禁止する。
## wheelグループに所属しているユーザだけがsuコマンドを使ってrootになれるようにする。
$ sudo vi /etc/login.defs
SU_WHEEL_ONLY yes # 追加

$ sudo vi /etc/pam.d/su
auth required /lib/security/pam_wheel.so group=wheel   # 追加

時刻同期の設定

  • これは会社ではやめておいたほうがよいかも
  • ntp:時刻サーバ。これに接続することで時刻を合わす。
  • ntpdate:ntpのクライアント。ntpに接続するらしい。
    $ sudo yum install ntp
    $ sudo vi /etc/cron.hourly/ntpdate
    $ sudo chmod 755 /etc/cron.hourly/ntpdate
    
    # -B は徐々に時刻を合わせるという意味
    #!/bin/sh
    /usr/sbin/ntpdate -B ntp.nict.jp > /dev/null
    
    # 最初の一発目だけ実行
    $ sudo /usr/sbin/ntpdate ntp.nict.jp
    
  • 参考

dagリポジトリの追加

$ sudo vi /etc/yum.repos.d/CentOS5-Base.repo

# enable=1にすると常用する。常用は不安なので0にしておく
[dag]
name=Dag RPM Repository for Redhat EL5
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=1
enabled=0
gpgkey=http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt

開発用ツール

$ sudo yum -y install wget
$ sudo yum install subversion
$ sudo yum install w3m
$ sudo yum --enablerepo=dag install git
$ sudo yum --enablerepo=dag install tmux

# コンパイルに必要
$ sudo yum groupinstall "Development Tools"

# GUIの開発するなら以下もインストールする。
$ sudo yum groupinstall "GNOME Software Development"

emacs

  • 以下のページを開き、「Obtaining/Downloading GNU Emacs」という見出しを探す。
  • ダウンロード用のページのリンクがあるのでそこをクリックし、最新版のtarボールのリンクをコピー
    $ cd ~/src
    $ wget http://ftp.gnu.org/pub/gnu/emacs/emacs-23.3.tar.gz
    $ tar xvfz emacs-23.3.tar.gz
    $ cd emacs-23.3
    $ ./configure --without-x
    

apacheのインストール

$ sudo /sbin/chkconfig httpd on
$ sudo /etc/init.d/httpd start

# 必要に応じて。
#$ sudo yum install httpd mod_perl


# ログの確認
$ sudo tail -f /var/log/httpd/access_log

# 確認用URL
http://vmware.localdomain/

# 設定ファイル
/etc/httpd/conf/httpd.conf

# html
/var/www/html

# pid
/var/run

Basic認証

llow = 許可する
deny = 拒否する

allowoverride FileInfo AuthConfig # FileInfo, AuthConfigオプションを許可する
authtype basic      # basic認証を使用する
authName "hoge"     # 認証領域。ダイアログに表示される
order allow,deny    # デフォルトとして許可する
allow from all      # すべてを許可
deny from 128.0.0.1 # 指定アドレスを拒否
<limit get post>
 require valid-user  # POST, GET には認証が必要
</limit>
satisfy any         # IPアドレス、認証のいずれかを通ったユーザを許可する
authuserfile /usr/hoge/.htaccess # パスワードファイルのありか

jdkのインストール

# 環境変数の追加
$ sudo vi /etc/profile.d/java.sh

[java.sh]
export JAVA_HOME=/usr/local/jdk1.6.0_24/
export PATH=$JAVA_HOME/bin:$PATH

# 有効化
$ sudo chmod +x /etc/profile.d/java.sh
$ . /etc/profile.d/java.sh

# alternativesに追加する。
$ sudo /usr/sbin/alternatives --install /usr/bin/java java /opt/jdk1.6.0_24/bin/java 3

# 確認
$ sudo /usr/sbin/alternatives --config java
$ java --version

PHPインストール

# # 以下は必要なかったかも。
# sudo yum -y install gcc*
$ sudo yum -y install flex libxml2-devel
$ sudo yum -y install zlib-devel libpng-devel
$ sudo yum -y install libjpeg-devel
$
sudo yum install -y git

#
# 以下で必要なパッケージを確認できる
#
# yum list '*php*'|less

# 今回は以下をインストール
$ sudo yum install -y php php-mbstring php-mysql php-gd
php-xml php-mcrypt
$ sudo yum install -y php-pear

webdav

CentOSにははじめから入っている。

$ sudo mkdir /var/www/share
$ chown -R apache:apache /var/www/share
$ sudo cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
$ sudo vi /etc/httpd/conf/httpd.conf
$ sudo htpasswd -cm /var/www/.htpasswd miki

$ sudo vi /etc/httpd/conf/httpd.conf
<IfModule mod_dav.c>
    DAVMinTimeout 600
    Alias /share/ "/var/www/share/"
    <Directory "/var/www/share">
        DAV On
        Options Indexes MultiViews
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from all
        AuthType Basic
        AuthName "Authorization Realm"
        AuthUserFile /var/www/.htpasswd
        Require valid-user
    </Directory>
</IfModule>

# 再起動
$ sudo /etc/rc.d/init.d/httpd reload

windowsXPからのアクセスする方法

  • エクスプローラを起動し、ネットワークを開く。
  • ネットワークプレイスの追加で下記を追加する。
  • 以後、このURLにドラッグアンドドロップで追加できる。

なぜポート番号も追加するのか

  • 普通にネットワークプライスから追加すると重い。
  • これは、windowsクライアントがhttp以外のリクエストを飛ばし、リトライとタイムアウトが発生することが原因とのこと。
  • これを回避するには、上述したようにネットワークプレイスを追加時にポートも一緒に指定して荒れれば良い。

Subversion

webdav経由のsvnリポジトリを作成する。

# まず、リポジトリの作成
$ sudo svnadmin create --fs-type=fsfs /home/svnroot
$ sudo htpasswd -cm /home/svnroot/.htpasswd miki
$ sudo chown -R apache:apache /home/svnroot

# webdav用のsvnモジュールをインストールする。
$ sudo yum -y install mod_dav_svn

# WebDavの設定ファイルを編集
$ sudo vi /etc/httpd/conf.d/subversion.conf

<Location /svn>
   DAV svn
   SVNPath /home/svnroot

   # Limit write permission to list of valid users.
   <LimitExcept GET PROPFIND OPTIONS REPORT>
      # Require SSL connection for password protection.
      # SSLRequireSSL

      AuthType Basic
      AuthName "svn"
      AuthUserFile /home/svnroot/.htpasswd
      Require valid-user
   </LimitExcept>
</Location>


# httpd を再起動
sudo /sbin/service httpd restart

リポジトリへインポートする。

$ mkdir work
$ cd work
$ mkdir trunk
$ mkdir branches
$ mkdir tags

$ svn import http://192.168.10.2/svn -m 'Initila imoprt.'

脚注のテスト

脚注1てすとしてみる

注釈

Footnotes:

1 脚注されるかな。

2011年4月23日土曜日

「プログラミングclojure」を読んでみた。その1--apply関数編--

0 コメント

「プログラミングclojure」を読んでいます。とりあえず流し読みが終わって今2巡目。

その中でsnake.cljといういスネークゲームのプログラムがあるんだけどその中で定義されているadd-pointsの中身が理解できない。


その関数の中身。

(defn add-points [& pts]
(vec (apply map + pts)))

これは座標を更新する関数。最初この関数の中身がわからなかった。

add-pointsは第一引数に現在の座標、第二引数に移動量を表すベクタを取る。

動作は以下。

user> (add-points [10 10] [1 -1])
[11 9]

よくわからないのはこの1行。apply関数。

(vec (apply map + pts)))

map はコレクションの各要素に第一引数の関数を適用して

それぞれの返り値をひとつのシーケンスにして返す。

user> (map + [10 10] [1 1])
(11 11)

返り値をベクタにする必要があるからvecを使うのはわかる。

user> (vec (map + [10 10] [1 1]))
[11 11]

理解を妨げているのはapply関数。

なんで必要なの?これじゃダメ?

(defn wrong-add-points [& pts]
(vec (map + pts)))

しかし実際に試すど動作しない。

user> (wrong-add-points [10 10] [1 -1])
java.lang.ClassCastException
[Thrown class java.lang.RuntimeException]

apply関数を調べてみる。

user> (doc apply)
-------------------------
clojure.core/apply
([f args* argseq])
Applies fn f to the argument list formed by prepending args to argseq.
nil

APIマニュアルを読んだけどよくわからんww。

でも動かしてみたらなんとなく動作理解。

user> (apply str '(1 2 3))
"123"
user> (apply str \A '(\B \C))
"ABC"

要はstr以降の引数のカッコをとって適用する感じだね。





で、add-pointsの中での使い方をもう一度見てみる。

(defn add-points [& pts]
(vec (apply map + pts)))

ptsは可変引数だから関数内部ではリストになっている。

ここで、可変長引数の動作確認のために関数を定義しみた。

user> (defn hoge-points [& pts] (println pts))
#'user/hoge-points
user> (hoge-points [1 1] [1 1])
([1 1] [1 1])
nil

やはりptsがリストになるのね。

add-pointsの引数ptsも同様にリストで受け取るわけだ。

だからこのptsをmapにそのままわたしてもうまくいかない。

先に作ったapplyを使わないadd-pointsだとmapにリストが渡されてしまう。

user> (wrong-add-points [10 10] [1 1])

この時のmapは次のように実引数が渡されることになる。

(map + ([10 10] [1 1])

試したら同じエラーが出た。

user> (map + '([10 10] [1 1]))
java.lang.ClassCastException
[Thrown class java.lang.RuntimeException]

期待する動作としてはカッコを外して渡したいと。

user> (map + [10 10] [1 1])
(11 11)

ここでやっとapplyを使う理由がわかった。

applyを使ってmapを使えばptsのカッコが外れてmapが適用できる。

user> (apply map + '([10 10] [1 1]))
(11 11)

返り値はベクタでほしいのでvecを噛まして完成と。

user> (vec (apply map + '([10 10] [1 1])))
[11 11]

ちなみにmapの説明を忘れていた。

map

以下mapのドキュメント。

user> (doc map)
-------------------------
clojure.core/map
([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
Returns a lazy sequence consisting of the result of applying f to the
set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is
exhausted.  Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments.
nil

う、思ったよりも長いヘルプ。

(map f coll)

とあった場合、collはシーケンスでcollのそれぞれの要素fを適用して

最後にシーケンスにして返す。

user> (map #(format "<p>%s</p>" %) [ "the" "quick" "brwon" "fox" ])
("<p>the</p>" "<p>quick</p>" "<p>brwon</p>" "<p>fox</p>")

複数のコレクションを取る場合、fはそのコレクションと同じ引数を取る関数でなければならない。

適用される要素の数は、一番数の少ないコレクションの数に合わされる。

user> (map #(format "<%s>%s</%s>" %1 %2 %1) [ "h1" "h2" "h3" ] [ "the" "quick" "brwon" "fox" ])
("<h1>the</h1>" "<h2>quick</h2>" "<h3>brwon</h3>")

で、add-pointsの場合、ptsが複数のコレクション(ベクタ)をとる。

user> (map + [10 9] [1 -1])
(11 8)

この時

(list (+ 10 1) (+ 9 -1))

のように動作する。

所感

自前の関数で受け取った引数をまるごとmapに渡すことってなんとなく結構ありそうな気がするから、add-pointsのようなパターンって結構あるのかなと思う。
add-pointsは中身なんてたった1行の関数なのに理解まで結構時間が掛かった。
解ってしまえばなんということのない動作だけども、ここにたどり着くまでにいろいろ寄り道が必要になる。
新しいことを学ぶときは最初はどうしても疑問が多いから時間がかるよね。
自分の知識が増えれば、わからないピースが埋まった状態で考えることができるから素早くタイプ出来るんだろうけど。
Lisperへの道は長い。





2011年2月23日水曜日

for文とかで使うセミコロンの位置

0 コメント


最近は、スクリプトが熱いですね。LLの基本はshにあると実感する今日この頃。そんな中、同僚がseqというコマンドを教えてくれた。





$ seq 10
1
2
3
4
5
6
7
8
9
10


数字を出力するだけのコマンド。最初なんのために使うんだと思ったけどもすぐにfor文で使うんだなと予測できた。


こんな感じ?



for i in `seq 10`
do
echo $i
done


別の書き方。doの位置が行末にあるバージョン。



for i in `seq 10`; do
echo $i
done


個人的にはこっちの方がすっきり見えて好きなんだけど、セミコロンの位置がわかりづらい。こんなのすぐ忘れるよって思った。



for i in `seq 10` do
echo $i
done


これじゃダメなの?って思うよね。でも、今回for文の構文を見直してみたら、3つの文から構成されているとことに気づいて合点がいった。



1行目:for $var in $wordlist
2行目:do $command
3行目:done


3つの文(命令?)から構成されているから、続けて書く場合にはそれぞれにセミコロンが必要になる。


だから一行で書くと次のようになる。



$ for i in `seq 10`; do echo $i; done


このようにfor文は三つ文(コマンド?)で成り立っているのである。これでセミコロンの位置について記憶が曖昧になることはないだろう^^。


応用してwhile文を一行で書くとこうなる。



$ while :; do echo hoge;done


:はなにもしない命令。


ちなみに俺はdoは行末にあるのが好き。doを改行するスタイルは冗長に見えてしまう。



for i in `seq 10`
do
echo $i
done



for (int i = 0; i < 9; i++)
{
System.out.println(i);
}


この書き方はjavaやc言語で書いてるぶんにはすっきりして良いのだが、シェルスクリプトで再現すると、中括弧を無理やりdoとdoneで置き換えた感じで嫌。


やっぱりjavaでいく行末カッコの以下の書き方の方が好きだな。



for (int i = 0; i < 9; i++) {
System.out.println(i);
}



for i in `seq 10`; do
echo $i
done


ということでif文も次のようなスタイルです。



if [ -f hoge.txt ]; then
echo 'exist!'
fi





2011年2月5日土曜日

antプロジェクトmaven化してみた。

0 コメント


仕事で使ってる社内のjavaライブラリがあるんだけどこれが昔ながらのantのみのモジュールでつくられていた。まぁわざわざmaven化するほど困ってるわけではないんだけども、このライブラリはいくつかのプロジェクトで利用されていて、バージョン管理とかをきちんとしたいのとmavenの練習になるかなってことでこのプロジェクトをmaven化してみた。







依存ライブラリの解決

まずは依存*1しているライブラリの洗い出し。


最初、eclipseのmavenプラグインを使ってEclipseから検索してpom.xmlを書き換えてたんだけど、途中でなぜかバグって依存ライブラリの記述が全部消えたorz。なんというかEclipseのプラグインって完成度がまちまちだからこういうことがよくあるんだよな。これでますますEclipseを離れたくなった。Eclipseも嫌いじゃないけど、やっぱ俺はemacsラブ。marabal-mode最高...と言いたい。


ということで素直に下記サイトで地味に検索して依存関係を潰していった。


http://mvnrepository.com/


このサイトの検索ボックスにライブラリの一部の文字列を入力すると検索結果がでるのでそこからめぼしのものを選んでdetailリンクをクリックすると、pom.xml用のタグが表示される。mavenのリポジトリは他のツールでも利用してるらしくmaven用ツールのほか、いろいろな依存関係の記述例が表示されるのが気になった。


これら依存関係のタグをpom.xmlにコピペしてその都度



$ mvn compile


していきながらコンパイルが成功するまで地味な作業の繰り返していった。


java-mail


その途中でjava-mailの依存関係を解決できなかった。このjarはmvnrepository.comの検索では見つかるものの、いざコンパイルをかけるとダウンロードでこけてしまう。ここをみるとライセンス的に怪しいという。


http://d.hatena.ne.jp/shinsuke_sugaya/20060503/1146608133


もともとmvnrepository.comにあったものが、移動したのだろうか?


その後、ググッて見つけた以下を参考にして新しくリポジトリを追加することで解決した。


http://d.hatena.ne.jp/Aileron/20091207/1260175613



<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.2</version>
</dependency>

<repositories>
<repository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven Repository</name>
<url>http://download.java.net/maven/2/</url>
</repository>
</repositories>


joglのリポジトリ


ところでこのリポジトリのURLはjava.netである。たしかjoglがjava.netで開発されてたので気になって検索してみたらあった。


http://download.java.net/maven/2/net/java/jogl/jogl/


joglもjava.netのリポジトリには登録されてるのね。rcというのが気になるけどこれを気にjoglのmavenを使ったビルド環境をもう一度見直してみよう。


社内リポジトリ


とまぁそんな地味な作業を重ね、ビルドが成功するようになった。完成したjarを社内リポジトリにインストールしたい。



## jarを生成
$ mvn package


まず、mavenの社内リポジトリをおく場所。これはsvnリポジトリをおいてる場所と同じにした。このsvnにはssh経由でアクセスしてunixユーザの権限で管理しているのでmvnリポジトリも同様にしたい。







リポジトリID hoge.com
リポジトリURL ssh 192.168.1.1:/usr/local/maven2repo

そこでまずリポジトリ用のディレクトリを作成した。



$ ssh -A 192.168.1.1
$ cd /usr/local/
$ sudo mkdir maven2repo
$ sudo chown -R root:dev maven2repo
$ sudo chmod -R 774 maven2repo
$ sudo chmod g+s maven2repo


devグループに属す人なら誰でもアクセスできるようにしたつもり。


で、今回maven化したプロジェクトのpom.xmlに以下のように登録先の情報を記述する。



<distributionManagement>
<repository>
<id>hoge</id>
<name>Hoge Repository</name>
<url>scpexe://192.168.1.1/usr/local/maven2repo</url>
</repository>
</distributionManagement>


ビルド作業しているホストの以下のディレクトリにsettings.xmlを準備する。




  • ~/.m2/settings.xml



<?xml version="1.0" encoding="UTF-8"?>
<settings>
<servers>
<server>
<id>hoge.com</id>
<username>${username}</username>
<filePermissions>664</filePermissions>
<directoryPermissions>775</directoryPermissions>
<configuration>
<sshExecutable>ssh</sshExecutable>
<scpExecutable>scp</scpExecutable>
</configuration>
</server>
</servers>
</settings>


デプロイコマンドは以下



$ cd ~/dev/goocore
$ mvn deply


以上でデプロイ完了


今回ハマったのは、ssh -A のようにエージェントフォワードしてる環境でのscpによるデプロイ方法。ググッてすぐに出てくるのはみな、usernameとprivatekeyのパスを設定する例ばかりで、今回のように鍵はエージェントフォワードしてる場合の設定例がなかなか見つからない。そんななか下記の参考を発見できたおかげで無事解決できた。感謝です!てかマニュアルちゃんと読めって話だよね。


http://yasushi.stbbs.net/c/articles/2006/03/23/%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9


所感


今回の作業のなかでivyというビルドツールの存在を知ったのだが、これがなかなか良さげ。このツールはantのタスクで、mavenのリポジトリを流用してjarの依存関係を解決してくれる。対するmavenは確かに便利なんだけども、依存関係の解決以外にたくさんの機能が備わっててものすごく大袈裟に見える。そもそもこれらの機能はほとんどど使わないし、中身はブラックボックスだからビルド方法を変えようと思っても気軽に拡張できない。その点ivyはantのいちタスクであるため、見通しもいいし簡単にビルドルーチンを改造できる。仕事で使ってるプロジェクトはどれも小規模なものばかりなので、mavenではオーバースペックかもしれない。そう考えると、ほんとに大規模なプロジェクト以外はどれもantですまして、必要に応じてivyを使ったりするのがべストかもしれない。mavenには豊富なプラグインが用意されていてリリースツールや継続的インテグレーションツールなどとの連携が充実してるように見える。しかし、これについてはantにもいえそうなことで、大抵antタスクがあるような気がする。逆にmavenからの利用だと導入コストが高そう。軽量そうなivyと重厚なmaven。どちらかというとivyのほうが魅力的に思える。とはいってもmalabar-mode使うにはpom.xmlが必要だから結局mavenを使いそうだけども。


ivyとかビルドのことについて、以下のサイトとその先の紹介サイトが大変参考になりました。


http://d.hatena.ne.jp/wyukawa/20101122/1290440174




*1:どうでもいいけど「依存」って「いそん」て読むんだよね。「既存」も「きそん」。アナウンサーとかの発音きくとわかるよ。この業界「既存」を使うことが非常に多いんだけど、いまだかつて「きそん」と発音する人を見たことががない。ただし「保存」は「ほそん」ではなくて「ほぞん」だよ。