2012年4月24日火曜日

HTML5でソリティアを作ってみた(第1回)


HTML5勉強の一区切りとしてカードゲームのソリティアを作ってみました。
まず、下の画面写真を見て頂くと解るように、iPhone3GSでも動くことを目的としています。
このため動作確認は iPhone3GS(iOS5.1)、IS03(Android2.2)、FireFox(11.0)、IE(9.0.8112.16421) で行っています。
その他の環境での動作は保障できませんので、あしからず。
※どうもiPod Touch(4世代)にiOS4 の環境だと動きが鈍い(ドラッグへの追従が遅い)ようです。
テストコードは以下のページにあります。

[Solitare Single](カードを1組52枚使っています)
[Solitare Double](カードを2組104枚使っています)

また全ソースを見たいという方もここから落としてください。
後ほど説明するリストクラスは [List.js] にあります。
Doubleの方は104枚カードがあるためか、どうも動作が重いような感じ。
ま、この辺りは今後の課題ですね。
それから、私自身HTML5やJavaScriptは超ど素人なため、とんでもないコードになっている可能性もあります。
ですので、万が一にも(間違えて)このコードを参考に何かを作って大問題が発生したとしても当方は一切責任を取れませんので、その点はご理解下さい。

それでは、何回かに分けて簡単に説明していこうと思います。
【1.実行画面】
 
上部にはコントロール用のメニューを配置してます。
「New」で新しいゲーム開始。
「End」でゲームを終了。
「Time 00:00」には経過時間を表示します。
このメニュー部分は普通にHTMLで表現しています。

メニューの下がゲームステージになり、この部分は1つの canvas となっています。

まず、左上の4つの枠が、カード種別毎に1から順に積み上げていく「組み札」エリアです。
その右側で3枚開いているのは更にその右側にある「山札」を開いたカードです。
(「山札」のカードは3枚一度に開くというルールにしています。)
これらの下に7列表示されているのが「場札」で、ここは赤、黒交互に数字の大きい方から小さい方へ順にカードを重ねていく事ができます。
組み札、山札、場札は私が便宜上勝手に命名した名前ですが、今後もこの呼び方で説明しますので覚えておいてください。
【2.HTML】
HTML部分については余り難しい所はありません。
<!DOCTYPE html>
<html lang="ja">
  <head>
    
    
    solitare s
    
    
    
    
    
    
    
    
  </head>
  <body>
    
    
</body> </html>

<head>の<meta>部分は基本的に Aptana Studio3 が自動的に作成してくれた物をつかっています。
追加したのは、
<meta name="viewport" content="width=320; user-scalable=no" />
の部分で、ここでコンテンツの幅を 320px に制限して、ユーザーがスケーリングを変更できないように指定しています。
つまり、たったこの1行でiPhone3GSに最適な幅で、ピンチインやピンチアウトで画面の拡大率が変わらないようになります。素晴らしい!

<style>部分ですが、ここで注意して頂きたいのは、全エレメントに対して
position : absolute;
で絶対座標にしてる事です。
マウスクリックやタップ位置を処理するのに都合が良かったのが理由です。
ただ、このためにメニューやキャンバスにも座標をセットしなければならなくなっているのでご注意を。
また、メニューは hover した際に文字色を白くし、右端の経過時間は緑色の文字に設定しています。

HTML の方はいたってシンプルです。
id="menu_bar"の部分がメニュー部分で、"New" で NewGame()を呼び出してカードを初期化し新規ゲームをスタートします。
また、"End" は EndGame() を呼び出し、全てのカードを削除してゲームを終了します。
NewGame(), EndGame()については後ほど説明を行う予定です。

そして、divエレメントの id="game_stage" 内がゲームエリアになります。
この中には "stage_canvas" と、 "off_canvas" という2つの canvas エレメントがあり、"stage_canvas" は実際に表示されるゲームステージ用です。
これに対し "off_canvas" はドラッグ中のもたつきを抑える目的で使用するオフスクリーン用なので、display: none; を指定して常時表示させません。
"off_canvas"の具体的な説明も後ほどドラッグ処理の中で行います。

さて、キャンバスを使う場合に忘れてはいけないのが、width と height を指定する事です。 もし指定しないとブラウザが持っているデフォルト値になってしまいますので必ず指定するようにしましょう。
【3.リストクラスを作成する】
次はデータ構造について説明しましょう。
まず大前提として各カードはリスト構造で管理する事とします。
リスト構造とは、1つのアイテムが前後のアイテムへのリンクを持った構造の事で、線を手繰るようにして次々とアイテムを辿って行くことができます。
この構造の便利な所は、ある所から切り離して、別のリストに繋げるといった事が、前後のアイテムへのリンクを書き換えるだけで行える事にあります。
これが配列だったら、切り離す所から後ろのデータを全て別の配列にコピーするという処理が必要になり大変です・・・よね?
まぁリスト構造で実装したのには単に私がリスト構造が好きだからという理由もあります・・・が、でもでも便利なんですよ、本当に。

さぁ!リストですが、汎用的に使えるよう List.js ファイルに別途作成しています。

コードは以下の通り。
// リストのアイテムクラス
var ListItem = function(data,prev,next){
  this.prev = prev;  // 1つ前のListItemの参照。
  this.next = next;  // 1つ後ろのListItemの参照。
  this.data = data;  // 実際のデータへの参照。
};
// リストクラス本体
var List = function(){
  this.top    = null;   // 先頭のアイテム(の参照)
  this.last   = null;   // 最後のアイテム(の参照)
  this.length = 0;      // アイテム数
};
// List class method
List.prototype = {
  // アイテムの追加メソッド
  add : function(data) {  // 最後尾に追加
    var item = new ListItem(data,this.last,null);
    if( this.last != null ){
      this.last.next = item;
      this.last = item;
    }
    else{
      this.top = item;
      this.last = item;
    }
    this.length++;
    return item;
  },
  // アイテムの挿入メソッド
  // basepos の前に挿入 basepos がnullなら最後に挿入
  insert : function(basepos, data){
    if( basepos != null ){
      var previtem = basepos.prev;
      var item = new ListItem(data,previtem,basepos);
      basepos.prev = item;
      if( previtem == null ){
        this.top = item;
      }
      else{
        previtem.next = item;
      }
      this.length++;
      return item;
    }
    else{
      return this.add(data);
    }
  },
  // アイテムの削除メソッド
  remove : function(pos){
    if( pos != null && this.isitem( pos ) ){
      // 指定されたアイテムがリストに存在するかをチェックする
      if( this.top == pos ){
        this.top = pos.next;
      }
      if( this.last == pos ){
        this.last = pos.prev;
      }
      if( pos.next != null ){
        pos.next.prev = pos.prev;
      }
      if( pos.prev != null ){
        pos.prev.next = pos.next;
      }
      pos.next = null;
      pos.prev = null;
      delete pos;
      this.length--;
      return true;
    }
    return false;
  },
  // すべてを削除
  remove_all : function(){
    var item = this.top;
    while( item != null ){
      var next = item.next;
      delete item;
      item = next;
    }
    this.top = null;
    this.last = null;
    this.length = 0;
  },
  isitem : function(pos){
    if(pos==null)  return false;
    var bFound = false;
    var itempos = this.get_top();
    while( itempos != null ){
      if( itempos == pos ){
        bFound = true;
        break;
      }
      itempos = this.get_next(itempos);
    }
    return bFound;
  },
  get_data : function(pos){
    if(pos!=null)  return pos.data;
    else           return null;
  },
  get_prev : function(pos){
    if(pos!=null)  return pos.prev;
    else           return null;
  },
  get_next : function(pos){
    if(pos!=null)  return pos.next;
    else           return null;
  },
  get_top : function(){
    return this.top;
  },
  get_last : function(){
    return this.last;
  },
  get_length : function(){
    return this.length;
  },
};

ListItem はリスト内で使うアイテムクラスで、前後のリストクラスオブジェクトの参照とデータオブジェクトの参照を持ちます。

List がリストクラス本体です。
クラスと書きましたが厳密にはクラスでは無いんですよね、JavaScriptの場合は。 prototype へメソッドを追加していく事でクラスのような動きをさせているだけだそうです。
この辺は詳しく説明できないので、興味のある方は調べてみてください。
それでは、各メソッドについての説明です。
【データアクセス用メソッド】
メソッド名説明
get_top()先頭のリストアイテムの参照を返します。
get_last()最後のリストアイテムの参照を返します。
get_next(pos)posの1つ後ろのリストアイテムの参照を返します。 posが最後の場合は null を返します。 posにはget_top()等で得られたリストアイテムを指定します。
get_prev(pos)posの1つ前のリストアイテムの参照を返します。 posが先頭の場合は null を返します。 posにはget_top()等で得られたリストアイテムを指定します。
get_data(pos)posで指定したリストアイテムからデータの参照を取得します。
get_length()リストが持っているアイテムの数を返します。
【データの追加用メソッド】
メソッド名説明
add(data)最後尾にdataで指定したデータを追加します。
insert(basepos, data)baseposで指定したリストアイテムの前にdataを挿入します。 basepos が null なら最後尾に追加します。
【データの削除用メソッド】
メソッド名説明
remove(pos)posで指定したアイテムを削除します。
remove_all()リスト内の全アイテムを削除し、リストを初期状態に戻します。

実はget_next(),get_prev()では pos を進めてデータを返すようにしたかったのですが、JavaScriptでは C++ のようなポインタのポインタが使えない(ようなのです)。
このため仕方なく次(前)のアイテムを返していて、データは get_data() で取得するようにしています。
連想配列で返す手もあったのですが、それもちょっと違うかなと思ったのでこういう仕様としました。
この他にも通常リストクラスに用意されているメソッドは多々ありますが、今回は必要な機能のみとなってまして。
その辺りもおいおい実装できればと思っています。

ちょっと長くなってきたので第1回はここまでとさせて頂いて、続きは第2回で。
下手な説明に長々とお付き合い頂いて本当にありがとうございました。

2012年2月9日木曜日

[Windows]フォントをインストールせずに使うには

AddFontResourceEx() API を使えば、フォントをインストールせずに呼び出したアプリ内で利用できます。

int AddFontResourceEx(
   LPCTSTR lpszFilename,   // フォントリソースファイル名
   DWORD fl, // フラグ
   DESIGNVECTOR *pdv    // Multiple Master フォントを指定する
                                     // 軸値の組へのポインタ
);

パラメータ

lpszFilename
    有効なフォントファイル名が入る、NULL で終わる文字列へのポインタを指定します。
    ファイル名は次のいずれかを指定します。
ファイル名拡張子ファイルタイプ
.fonフォントリソースファイルを指定します。
.fntRaw ビットマップフォントファイルを指定します。
.ttfRaw TrueType ファイルを指定します。
.ttcTrue Type フォントコレクションを指定します。
.fotTrueType リソースファイルを指定します。
.otfPostScript OpenType フォントを指定します。
.mmmMultiple Master Type1 フォントリソースファイルを指定します。.pfm および .pfb ファイルと共に使ってください。
.pfbType 1 フォントビットファイルを指定します。.pfm ファイルと共に使います。
.pfmType 1 フォントメトリックスファイルを指定します。.pfb ファイルと共に使います。
   情報が複数のリソースファイルに分かれているフォントを追加するには、abcxxxxx.pfm | abcxxxxx.pfb のようにファイル名を | で区切り、lpszFileName パラメータを使って指します。

fl
    システムに追加されるフォントの特性を指定します。次の値が定義されています。
意味
FR_PRIVATEAddFontResourceEx 関数を呼び出したプロセスだけがこのフォントを使うことができます。そのフォントの名前とパブリックフォントが一致する場合は、プライベートフォントが選ばれます。プロセスが終了すると、システムは、そのプロセスが AddFontResourceEx 関数を使ってインストールしたすべてのフォントを削除します。
FR_NOT_ENUMAddFontResourceEx 関数を呼び出したプロセスも含め、いずれのプロセスもこのフォントを列挙できません。

pdv
    DESIGNVECTOR 構造体へのポインタを指定します。そのフォントが Multiple Master OpenType フォントでない場合は NULL になります。

戻り値

関数が成功すると、追加されたフォント数が返ります。
関数が失敗すると、0 が返ります。


[使ってみて]
※lpszFilenameにはパスを指定できる。(例)"font\MS 明朝"
このAPIをアプリの初期化時に呼んでおけば、アプリ起動中は指定したフォントを利用できる。
FR_NOT_ENUMを指定しなければフォントコンボボックスにも列挙されるのでユーザーに選択させる事も可能。

2012年1月11日水曜日

今年の抱負

遅ればせながら、今年の抱負を書いておこうと思います。

一つ、英語力を付ける
一つ、酒はほどほどに
一つ、物欲に負けない

以上。

2012年1月9日月曜日

『究極のドグマ』を読み終えて

機本伸司氏 著作の『究極のドグマ 穂瑞沙羅華の課外活動』をやっと読み終えたので感想を。

『神様のパズル』『神様の軌跡』に続くシリーズ3作目で、またまた綿さんこと綿貫基一が超天才にして超美少女の穂瑞沙羅華に翻弄されるというお話し。
正直、DNAだの量子コンピューターだのと言われても、私にはちんぷんかんぷん。
でも綿さんレベルまで噛み砕いて説明してくれて何となく解かったような気にさせてくれるのは、流石。
あくまでも気がするだけなんだけどね(^^;;

さて、今回は一匹の猫を探すんだけど猫探しは本当に大変ですよ。
実は昔々私も家の中で飼ってた猫が家出した事があり、多大なご迷惑をかけて探し出したという経験を持ってます。
見つかった時はすっかり野良状態で思いっきり噛まれたんだよねぇ。(ちょっと遠い目で思い出に浸っております)

しかし本に登場する猫は当然普通じゃない。その上探し出すのに、量子コンピューターを使い、人海戦術を使いと、こちらはど派手に捜索してます。まぁ我らが綿さんはいつも通り沙羅華に顎で使われ、相棒の犬と車で寝食を共にするという実にみじめ~な捜索も行うわけですが(^_^;)

ちょっと落ち込んでて生きる意味って何だろうと思った時に読むといいかなぁ。
ちょうど私がそんな時期だったからだけども、あくまでも軽く落ち込んでる時にね。