# [*Version*]
#   0.8.1
# [*Author*]
#   Hajime Hoshi
# [*Copyright*]
#   Copyright (c) 2006-2007 Hajime Hoshi
# [*License*]
#   GNU Lesser General Public License

# == Star Engine について
#
# === 特徴
#
# * <b>2D ゲームを作成するための汎用ゲームライブラリです。</b>SNES (スーパーファミコン) 風のノスタルジックなゲームを作ることができます。
# * エンジンの大半を C#2.0 で記述しています。今のところ Windows 限定ですが、マルチプラットフォームも視野に入れています。
# * ゲーム作成において、煩雑な描画処理やループ周りを抽象化・カプセル化しました。ゲーム作成者はゲームのロジック部分の作成のみに尽力することができます。
# * 描画、音声関係には SDL を使用しています。
# * ほとんどの機能を Ruby スクリプトで利用可能です。
#
# === 機能
#
# * PNG、 GIF、 BMP などの形式に対応したテクスチャ機能。
# * 2D スプライト描画。拡大縮小回転描画や、加算合成、減算合成などにも対応。
# * 文字の描画。 GDI+ で扱えるフォントが全て使えます。
# * キーボードとジョイパッドが利用可能。複数人プレイにも対応可能。
# * WAV、OGG、MP3 などの形式に対応したサウンド機能。
# * 解像度は 320×240 固定。
# * 低解像度のままの、ウィンドウの 2 倍拡大表示機能。
# * GZIP ファイルのシームレスな読み出し。
#
# == スクリプト
#
# === 文字コード
#
# 文字コードは UTF-8/N です。
#
# == Hello, world!
#
# Texture を使って "Hello, world!" と表示し、それを右方向に移動させ、やがて消滅するサンプルです。
#
# === <tt>Scripts/main.rb</tt>:
#
#   include StarEngine
#
#   font = Font.new "Arial", 12
#
#   texture = Texture.new 100, 50
#   texture.render_text "Hello, world!", 0, 0, font, Color.new(255, 255, 255)
#
#   x = 0
#   Game.run do
#     x += 1
#     s.clear
#     s.render_texture texture, x, 0
#   end
module StarEngine
  
  # Star Engine 特有のエラーです。
  class StarEngineError < StandardError
  end
  
  # コンソールをアクティブ状態にします。
  def activate_console
  end
  
  # :stopdoc:
  
  # C# と連携をとるオブジェクトのための Mix-in です。
  #
  # クリエイター側では特に気にする必要はありません。
  module CSharpCachedObject
    
    private
    
    # C# で定義されるイニシャライザです。
    # 適宜オーバーライドされます。
    #
    # デフォルトでは、 __cs_set_finalizer__ メソッドを呼ぶだけです。
    def __cs_initialize__ # :nodoc:
      __cs_set_finalizer__
    end
    
    # ファイナライザをセットします。
    # __cs_initialize__ の中で呼ばれることを想定しています。
    def __cs_set_finalizer__ # :nodoc:
      ObjectSpace.define_finalizer self, CSharpCachedObject.finalizer
    end
    
    class << self
    
      # ファイナライザの Proc を返します。
      def finalizer # :nodoc:
        proc {|id| __cs_finalize__(id)}
      end
      
      private
      
      def __cs_finalize__(id) # :nodoc:
      end
      
    end
    
  end
  
  # :startdoc:
  
  # 色を表します。
  # immutable なので値型のように扱えます。
  #
  # 各成分は 0 から 255 までの値です。
  #
  # アルファ値は描画の際の不透明度を表します。
  # 255 で完全に不透明、 0 で完全に透明です。
  #
  class Color
    
    # 赤 (0〜255)。
    attr_reader :red
    
    # 緑 (0〜255)。
    attr_reader :green
    
    # 青 (0〜255)。
    attr_reader :blue
    
    # アルファ値 (0〜255)。
    attr_reader :alpha
    
    # Color オブジェクトを生成します。
    def initialize red, green, blue, alpha = 255
      @red   = [[red,   0].max, 255].min
      @green = [[green, 0].max, 255].min
      @blue  = [[blue,  0].max, 255].min
      @alpha = [[alpha, 0].max, 255].min
    end
    
    # 同じ Color オブジェクトかどうか比較します。
    def == other
      other.kind_of? Color and red == other.red and green == other.green and blue == other.blue and alpha == other.alpha
    end
    
    # オブジェクトが等しいかどうか比較します。
    # Hash で使われます。
    #
    # <tt>==</tt> と同じ定義です。
    def eql? other
      self == other
    end
    
    # ハッシュ値を返します。
    def hash
      red ^ green ^ blue ^ alpha
    end
  end
  
  # 色調を表します。 immutable なので値型のように扱えます。
  #
  # 描画命令などに指定することによって使用します。
  #
  # 赤、緑、青の成分は、 -255 から 255 までの値をとります。
  # 負の場合はその色の成分を弱めます。
  # 正の場合はその色の成分を強めます。
  # 0 の場合はその色の成分を変化させません。
  #
  # 彩度は 0 から 255 までの値をとります。
  # 値が小さくなるほど彩度が減少してモノクロに近づき、 0 で完全にモノクロになります。
  # 255 の場合は彩度を変化させません。
  #
  # 色調変化の順は「彩度変化」→「色味変化」となります。
  class Tone
    
    # 赤 (-255〜255)。
    attr_reader :red
    
    # 緑 (-255〜255)。
    attr_reader :green
    
    # 青 (-255〜255)。
    attr_reader :blue
    
    # 彩度 (0〜255)。
    attr_reader :saturation
    
    # Tone オブジェクトを生成します。
    def initialize red, green, blue, saturation = 255
      @red        = [[red,        -255].max, 255].min
      @green      = [[green,      -255].max, 255].min
      @blue       = [[blue,       -255].max, 255].min
      @saturation = [[saturation, 0   ].max, 255].min
    end
    
    # 何も変化させない色調。
    EMPTY = Tone.new 0, 0, 0
    
    # 同じ Tone オブジェクトかどうか比較します。
    def == other
      other.kind_of? Tone and red == other.red and green == other.green and blue == other.blue and saturation == other.saturation
    end
    
    # オブジェクトが等しいかどうか比較します。
    # Hash で使われます。
    #
    # <tt>==</tt> と同じ定義です。
    def eql? other
      self == other
    end
    
    # ハッシュ値を返します。
    def hash
      red ^ green ^ blue ^ saturation
    end

  end
  
  # 書体を表します。 immutable です。
  class Font
    
    # :stopdoc:
    include CSharpCachedObject
    # :startdoc:
    
    # ピクセル単位の大きさ。
    attr_reader :size
    
    # フォント名。
    attr_reader :name
    
    # Font オブジェクトを生成します。
    #
    # name_or_path には、インストール済みのフォント名もしくは
    # フォントファイルへのパスを指定します。
    #
    # styles にはフォントのスタイルを Hash で指定します。キーと値は以下の通りです。
    # [<tt>:bold</tt>]
    #   太字のときに真値を指定します。デフォルトは false です。
    # [<tt>:italic</tt>]
    #   斜体のときに真値を指定します。デフォルトは false です。
    def initialize name_or_path, size, styles = {}
      styles = {
        :bold => false,
        :italic => false,
      }.merge(styles)
      __cs_initialize__ name_or_path, size, styles
    end
    
    # スタイルが太字ならば true を返します。
    def bold?
      @bold
    end
    
    # 文字列 text を描画するのに必要十分なテクスチャの大きさを取得します。
    #
    # 大きさは [(幅), (高さ)] という配列で表されます。
    #
    # 描画時のサイズの厳密な定義は、「.NET の System.Drawing.StringFormat.GenericTypographic で描画したときのサイズを切り上げしたもの」としています。
    def get_size text
    end
    
    # スタイルが斜体ならば true を返します。
    def italic?
      @italic
    end
    
    class << self
      
      # name_or_path の名前もしくはファイルのフォントが存在するならば true、それ以外の場合は false を返します。
      def exist? name_or_path
      end
      
    end
    
  end
  
  # 画像そのものを表します。
  #
  # 画像を生成するメソッドは重いので、
  # 毎フレーム呼び出すようなことはしないでください。
  #
  # == その他
  #
  # dispose 状態や freeze 状態の時に画素を変化させる操作を行うと、例外が発生します。
  class Texture
    
    # :stopdoc:
    include CSharpCachedObject
    # :startdoc:
    
    def initialize # :nodoc:
      __cs_initialize__
    end
    
    # 画像の幅。
    attr_reader :width
    
    # 画像の高さ。
    attr_reader :height
    
    # 画像のサイズ ([(幅), (高さ)] の配列)。
    attr_reader :size
    
    def size # :nodoc:
      [width, height]
    end
    
    # テクスチャの複製を返します。
    #
    # clone と dup の違いは、 Object クラスのそれと同じです。
    def dup
      super
    end
    
    # テクスチャの複製を返します。
    #
    # clone と dup の違いは、 Object クラスのそれと同じです。
    def clone
      super
    end
    
    # このテクスチャを解放します。
    # 一度 dipose したテクスチャは、使用することが出来ません。
    #
    # このメソッドを 2 回以上呼ぶと、例外が発生します。
    def dispose
    end
    
    # このオブジェクトが解放済みの場合 true を、
    # それ以外の場合は false を返します。
    def disposed?
    end
    
    # (x, y) 座標のピクセルの色 (Color) を取得します。
    def get_pixel x, y
    end
    
    # (x, y) 座標のピクセルの色 (Color) を設定します。
    def set_pixel x, y, color
    end
    
    # 色相を指定した値の分だけ変化させます。
    #
    # 色相変化量は 0 から 359 までの値をとります。
    # 色相変化は周期的で、変化量 180 で補色 (真反対の色) になります。
    # 0 の場合色相を変化させません。
    #
    # この変換は不可逆変換です。また、変換には多少の誤差を含みます。
    def change_hue d_hue
    end
    
    # 画素をすべて消去します。
    def clear
    end
    
    # 単色で塗りつぶします。
    def fill color
    end
    
    # 単色で矩形を塗りつぶします。
    def fill_rect x, y, width, height, color
    end
    
    # 文字列 text を描画します。
    #
    # 改行文字、タブ文字はスペースに置き換えられます。
    def render_text text, x, y, font, color
    end
    
    # テクスチャを描画します。
    #
    # x、 y には、描画位置の左上点を指定します。
    #
    # options に指定できるキー、値は以下の通りです。
    #
    # [<tt>:src_x</tt>]
    #   描画元矩形の X 座標。デフォルトは 0 です。
    # [<tt>:src_y</tt>]
    #   描画元矩形の Y 座標。デフォルトは 0 です。
    # [<tt>:src_width</tt>]
    #   描画元矩形の幅。デフォルトはこのテクスチャの幅です。
    # [<tt>:src_height</tt>]
    #   描画元矩形の高さ。デフォルトはこのテクスチャの高さです。
    # [<tt>:scale_x</tt>]
    #   X 軸方向の拡大率。負の値を指定すると反転描画します。デフォルトは 1.0 です。
    # [<tt>:scale_y</tt>]
    #   Y 軸方向の拡大率。負の値を指定すると反転描画します。デフォルトは 1.0 です。
    # [<tt>:angle</tt>]
    #   回転角度。4096 度系です。デフォルトは 0 です。
    # [<tt>:center_x</tt>]
    #   幾何変換の中心の X 座標。原点はテクスチャの転送元矩形の左上点です。デフォルトは 0 です。
    # [<tt>:center_y</tt>]
    #   幾何変換の中心の Y 座標。原点はテクスチャの転送元矩形の左上点です。デフォルトは 0 です。
    # [<tt>:alpha</tt>]
    #   α値。0〜255の値を指定します。デフォルトは 255 です。
    # [<tt>:blend_type</tt>]
    #   ブレンドの種類。 :alpha で通常のα合成、 :add で加算合成、 :sub で減算合成です。デフォルトは :alpha です。
    # [<tt>:tone</tt>]
    #   色調 (Tone)。デフォルトは Tone::EMPTY です。
    #
    # これら以外のキーを指定すると、例外が発生します。
    #
    # 拡大や回転などの幾何変換処理は、次の順に行われます。
    #
    # 1. center_x、 center_y を中心に拡大縮小処理を行う。
    # 2. center_x、 center_y を中心に回転処理を行う。
    # 3. x、 y だけ平行移動する。
    #
    # 転送時に、転送先 (このテクスチャ) のピクセルごとのα値は、転送元と転送先のα値のうちの大きいほうが使われます。
    #
    # texture に self を指定することもできます。
    def render_texture texture, x, y, options = {}
      __cs_render_texture__(texture, x, y, options[:src_x], options[:src_y], options[:src_width], options[:src_height], options[:scale_x], options[:scale_y], options[:angle], options[:center_x], options[:center_y], options[:alpha], options[:blend_type], options[:tone])
    end
    
    class << self
      
      # :stopdoc:
      
      alias _orig_new new
      
      private :_orig_new
      
      # :startdoc:
      
      # 新しいテクスチャを生成します。
      def new width, height
      end
      
      # 画像をファイルから読み込み、テクスチャを生成します。
      #
      # 使用できる画像形式は PNG、 GIF、 BMP です。
      #
      # path は、拡張子を省略することが出来ます。
      # また、 GZIP で圧縮した画像を読み込むことができます。
      def load path
      end

    end
    
  end
  

  # ゲームそのものを表します。
  #
  # FPS のデフォルト値は 30 です。
  module Game
    
    class << self
    
      # FPS を取得します。
      #
      # ゲームはこの FPS に近づけるように努力します。実際の FPS を取得するには real_fps メソッドを使ってください。
      def fps
      end
      
      # FPS を設定します。
      def fps= val
      end
      
      # 実際の FPS を取得します。
      def real_fps
      end

      # ゲームの実行を開始します。
      #
      # 指定したブロックを毎フレーム実行します。
      def run &block
        raise StarEngineError, "already running" if running?
        (class << self; self end).class_eval do
          define_method :call_frame_block do
            block.call
          end
          private :call_frame_block
        end
        __cs_game_run__
      end
      
      private
      
      def __cs_game_run__ # :nodoc:
      end
      
      public
      
      # ゲームが実行中なら true、それ以外の場合は false を返します。
      def running?
      end

      # ゲームを終了させます。
      def terminate
      end

      # ゲームのタイトルを取得します。
      def title
      end
      
      # ゲームのタイトルを設定します。
      def title= val
      end

    end

  end
  
  # プレイヤーからの入力を管理します。
  module Input
    
    # 押されたキー、ボタンの配列を取得します。
    # 
    # device はデバイスの種類を表します。キーボードは <tt>:keyboard</tt>、ゲームパッドは <tt>:game_pad</tt>、マウスは <tt>:mouse</tt> です。
    # 
    # ボタン一個一個は以下のような値で表されます。
    # 
    # [キーボード]
    #   キーを表す Symbol。使用される値は以下の通りです。これらは .NET の System.Windows.Forms.Keys 列挙型の一部を小文字にしたものです。
    #
    # - <tt>:a</tt> 〜 <tt>:z</tt>
    # - <tt>:d0</tt> 〜 <tt>:d9</tt>
    # - <tt>:f1</tt> 〜 <tt>:f15</tt>
    # - <tt>:numpad0</tt> 〜 <tt>:numpad9</tt>
    # - <tt>:add</tt>
    # - <tt>:back</tt>
    # - <tt>:capslock</tt>
    # - <tt>:clear</tt>
    # - <tt>:decimal</tt>
    # - <tt>:delete</tt>
    # - <tt>:divide</tt>
    # - <tt>:down</tt>
    # - <tt>:end</tt>
    # - <tt>:enter</tt>
    # - <tt>:escape</tt>
    # - <tt>:help</tt>
    # - <tt>:home</tt>
    # - <tt>:insert</tt>
    # - <tt>:lcontrolkey</tt>
    # - <tt>:left</tt>
    # - <tt>:lmenu</tt>
    # - <tt>:lshiftkey</tt>
    # - <tt>:lwin</tt>
    # - <tt>:multiply</tt>
    # - <tt>:numlock</tt>
    # - <tt>:pagedown</tt>
    # - <tt>:pageup</tt>
    # - <tt>:rcontrolkey</tt>
    # - <tt>:right</tt>
    # - <tt>:rmenu</tt>
    # - <tt>:rshiftkey</tt>
    # - <tt>:rwin</tt>
    # - <tt>:scroll</tt>
    # - <tt>:space</tt>
    # - <tt>:subtract</tt>
    # - <tt>:tab</tt>
    # - <tt>:up</tt>
    # 
    # [ゲームパッド]
    #   ボタン番号を表す Integer、および方向キーを表す Symbol (<tt>:up</tt>、 <tt>:down</tt>、 <tt>:left</tt>、 <tt>:right</tt>)。
    # [マウス]
    #   ボタンを表す Symbol (<tt>:left</tt>、 <tt>:middle</tt>、 <tt>:right</tt>)。
    #
    # options は Hash を指定します。キーと値は以下の通りです。 :delay と :interval は、オートリピーティングに使います。
    # 
    # [<tt>:device_number</tt>]
    #   デバイス (主にゲームパッド) が複数ある場合の識別番号。デフォルトは 0 です。
    # [<tt>:duration</tt>]
    #   キーが押されたとみなされる持続時間。 -1 を指定した場合は無限です。デフォルトは -1 です。
    # [<tt>:delay</tt>]
    #   1 回目の判別が終了してから 2 回目にキーが押されたとみなされるまでの遅延時間。 -1 を指定した場合は無限です (2 回目の判別はありません)。デフォルトは -1 です。duration が 0 以上の値の場合に有効です。
    # [<tt>:interval</tt>]
    #   2 回目以降のキーの時間間隔。デフォルトは 0 です。
    def self.pressed_keys(device, options = {})
      options = {
        :device_number => 0,
        :duration => -1,
        :delay => -1,
        :interval => 0,
      }.merge(options)
      __cs_pressed_keys__ device, options
    end
    
    # マウスカーソルの座標 ([(X 座標), (Y 座標)] で表される配列) を取得します。
    def self.mouse_location
    end
    
    # :stopdoc:
    class << self
      private
      def __cs_pressed_keys__
      end
    end
    # :startdoc:
    
  end
  
  # 画面を表します。
  # ここでいう画面は、抽象的な画面です。
  # そのため、プレイヤーがどの大きさの画面でプレイしているかどうかに依存しません。
  module Screen
    
    class << self
      
      # サイズ ([(幅), (高さ)]の配列) を取得します。
      def size
        [width, height]
      end
      
      # 幅を取得します。
      def width
        @width
      end
      
      # 高さを取得します。
      def height
        @height
      end
      
      # オフスクリーン (Texture) を取得します。
      #
      # 毎フレームこのテクスチャに描画することによって、画面に表示します。
      def offscreen
        @offscreen
      end
      
      # このフレームにおけるスクリーンショットを、ファイル名 path に保存します。
      # 保存される画像は、呼び出した時点のフレームにおける最終的な出力です。そのため、実際に画像が生成されるタイミングは、フレームの終了以降です。
      #
      # 形式は PNG です。
      def save_screenshot path
      end
      
      private
      
      def renderers
        @renderers ||= []
      end
      
      def execute_renderers rendering_context
        renderers.each do |r|
          raise TypeError, "not a rendering command" unless r.respond_to? "render"
          r.render rendering_context
        end
      end
      
    end
    
  end
  
  # オーディオを表します。
  module Audio
    
    # path の BGM を再生します 。
    #
    # 対応形式は WAV、 OGG、 MP3、 MOD、 MIDI です。
    #
    # options にはオプションを表す Hashを指定します。キー、値は以下の通りです。
    # [<tt>:volume</tt>]
    #   音量。0〜255 の値です。
    #   デフォルトは 255 です。
    # [<tt>:loop</tt>]
    #   ループ再生するかどうかを示す真偽値。
    #   デフォルトは true です。
    # [<tt>:time</tt>]
    #   フェードインに要する時間。ミリ秒で指定します。
    #   デフォルトは 0 です。
    def self.play_bgm path, options = {}
      options = {
        :volume => nil,
        :loop => nil,
        :time => nil,
      }.merge(options)
      __cs_play_bgm__ path, options[:time], options[:volume], options[:loop]
    end
    
    # BGM を停止します。次回再生時は最初からの再生になります。
    #
    # options にはオプションを表す Hashを指定します。キー、値は以下の通りです。
    # [<tt>:time</tt>]
    #   フェードインに要する時間。ミリ秒で指定します。
    #   デフォルトは 0 です。
    def self.stop_bgm options = {}
      options = {
        :time => nil,
      }.merge(options)
      __cs_stop_bgm__ options[:time]
    end
    
    # BGM が再生中ならば true を返します。
    def self.playing_bgm?
    end
    
    # BGM の音量を取得します。
    # 音量は 0〜255 です。
    def self.bgm_volume
    end
    
    # BGM の音量を設定します。
    # 音量は 0〜255 です。
    def self.bgm_volume= val
    end
    
    # path の SE を再生します。
    #
    # 対応形式は WAV、 OGG、 MP3、 MOD、 MIDI です。
    #
    # options には再生時のオプションを表す Hash を指定します。
    # キー、値は以下の通りです。
    #
    # [<tt>:volume</tt>]
    #   音量。 0〜255 の値です。
    #   デフォルトは 255 です。
    # [<tt>:panning</tt>]
    #   定位 (パンニング)。 0〜255 の値です。
    #   0 で左端、 128 で真ん中、 255 で右端を表します。
    #   デフォルトは 128 です。
    # [<tt>:time</tt>]
    #   フェードインに要する時間。ミリ秒で指定します。
    #   デフォルトは 0 です。
    def self.play_se path, options = {}
      options = {
        :volume => nil,
        :panning => nil,
        :time => nil,
      }.merge(options)
      __cs_play_se__ path, options[:time], options[:volume], options[:panning]
    end
    
    # 全ての SE を停止します。
    #
    # options にはオプションを表す Hashを指定します。キー、値は以下の通りです。
    # [<tt>:time</tt>]
    #   フェードインに要する時間。ミリ秒で指定します。
    #   デフォルトは 0 です。
    def self.stop_all_ses options = {}
      options = {
        :time => nil,
      }.merge(options)
      __cs_stop_all_ses__ options[:time]
    end
    
    # :stopdoc:
    class << self
      
      private
      
      def __cs_play_bgm__ *args
      end
      
      def __cs_stop_bgm__ *args
      end
      
      def __cs_play_se__ *args
      end
      
      def __cs_stop_all_ses__ *args
      end
      
    end
    # :startdoc:
    
  end
  
end

# :enddoc:
