# -*- coding: utf-8 -*-

require 'app'
require 'canvas'
require 'thread'

W = 640
H = 480

PAGES =
  [
   %Q(
HTML5でゲーム配信とか

はまじしん一ろう
),
   %Q(
HTML5

次のHTML
HTMLと言いつつ
JSインターフェースとか
DOM/DOM eventなども定義

R.I.P.: XHTML

最近の仕事(らしい)
),
   %Q(
HTML5

すごいタグ: <video>, <audio>...
細かいタグ: <section>, <article>...
すごいform: <input type="date">, ...
絵をかく: Canvas2D, ...
WebStorage: すごいクッキー
WebSocket: ソケット
Worker: スレッド
AppCache
),
   %Q(
今回

絵をかく: Canvas2D, ...
WebSocket: ソケット

このふたつ
),
   %Q(
Canvas2D

普通の2D API

<canvas id="c">

c = document.getElementById("c")
ctx = c.getContext("2d");
ctx.fillStyle = "rgb(255,255,255)"
ctx.fillRect(0, 0, W, H)
),
   %Q(
Canvas2D実装

IE以外は大丈夫ぽい
IE用のcanvas実装はある(VML)

案外速い
),
   %Q(
WebSocket

TCPみたいな双方向通信路
最初はHTTPでhandshake

Upgrade: WebSocket
Connection: Upgrade

R.I.P.: Commet, JSONP
),
   %Q(
WebSocketの通信路

番犬

@socket.write("\\\\\\\\x00"+data+"\\\\\\\\xff")

文字列長を送るのもあったような
),
   %Q(
WebSocketの実装

わすれた
WebKitは開発中
Firefoxはpatchとかあったっけ…

今回はFlashでの実装を使ってみた
),
   %Q(
この2つでゲーム配信
),
   %Q(
クライアント

サーバから送られてくる
JSをevalするだけ

イベントは全部サーバに送る
),
%Q(
ws.onmessage = function(e) {
  try {
    var canvasElem = $("canvas");
    var ctx = canvasElem.getContext("2d");
    eval(e.data);
  } catch (err) {
    output("exception: " + err);
  }
};
),
%Q(
サーバ

全ロジックの処理
→チートしにくい

画像表示するJSを送りつける
→VNCとかよりは速い
),
%Q(
デモ

このプレゼン自体

http://shinh.org/wsock/tc.html
),
[%Q(
ほげほげ
),
 proc{|ctx, turn|
   7.times{|i|
     r = ((i+3) * Math::PI / 180) * turn
     x = 150 * Math.cos(r) + W / 2
     y = 150 * Math.sin(r) + 250
     ctx.beginPath()
     ctx.arc(x, y, 15, 0, Math::PI * 2, 1)
     ctx.fill()
   }
 }
],
%Q(
課題 - ネットワーク遅い

毎度コードを送りつけるのでなく
関数を送っておいてデータを
後で送る仕組みなどが必要？

明らかにWebSocket自体にも
なんらかの圧縮が欲しい
),
%Q(
課題 - 認証

タブ閉じるだけでアウトなので
コネクションは結構切ってしまう

そのたびにログインは面倒

Cookieがどうなっているか
調べていないけどたぶん
Upgrade時に送られてきている?
),
%Q(
課題 - 画像データ

画像データは大きいが
アイコンちゃんを動かす
必要があるので必須

画像をクライアントから
見えるところに置いて
JSで読ませて読み終わったら
使い始める?
),

%Q(
課題 - live update

サーバを全く止めずに
アップデートできるとかっこいい

実際問題どうでもいいけど
),
  ]

class State
  INITIALIZING = 0
  WAIT_PRESENTER = 1
  PRESENTATION = 2

  attr_accessor :state, :down_x, :down_y, :x, :y

  def initialize
    @state = INITIALIZING
    set_xy(W / 2, H / 2)
    init_down
  end

  def init_down
    @down_x = @down_y = nil
  end

  def set_xy(x, y)
    @x = x
    @y = y
  end
end

class Comment
  attr_accessor :msg, :x, :y

  def initialize(msg, y)
    @msg = msg.gsub("'", '\\\\\\\\\'')
    @x = W
    @y = y
  end
end

class Shot
  attr_accessor :x, :y, :vx, :vy

  def initialize(x, y, vx, vy)
    @x = x
    @y = y
    @vx = vx
    @vy = vy
  end

  def move
    @x += vx
    @y += vy

    @x >= -32 && @x < W && @y >= -32 && @y < H
  end
end

class PresenApp < App
  def initialize
    @presenter = nil
    @ws = {}
    @num = 0
    #@num = PAGES.size-1
    @state = State::WAIT_PRESENTER
    @comments = []
    @mu = Mutex.new
    @turn = 0

    init_icon_chan
    init_gravity
    @shots = []

    @dead_icon_chan = 0
  end

  def init_icon_chan
    @icon_chan_x = rand(W-32)
    @icon_chan_y = rand(H-32)
    init_icon_chan_speed
  end

  def init_icon_chan_speed
    r = rand(Math::PI * 2)
    v = 3 + rand(5.0)
    @icon_chan_vx = v * Math.cos(r)
    @icon_chan_vy = v * Math.sin(r)
  end

  def init_gravity
    @gravity_x = rand(W)
    @gravity_y = rand(H)
    @gravity_p = 0
    @gravity_p = rand(1000.0)
  end

  def onTimer
    @turn += 1

    ctx = CanvasContext.new
    ctx.fillStyle = 'rgba(255,255,255,0.8)'
    ctx.globalCompositeOperation = 'source-atop';
    ctx.fillRect(0, 0, W, H)

    ctx.fillStyle = 'rgb(0,0,0)'
    ctx.globalCompositeOperation = 'source-over';
    ctx.font = '40px sans-serif'

    if @turn % 300 == 299
      init_gravity
    end

    if rand(10000) == 0
      init_icon_chan_speed
    end

    dx = @gravity_x - @icon_chan_x
    dx2 = dx * dx
    dy = @gravity_y - @icon_chan_y
    dy2 = dy * dy
    d = Math.sqrt(dx2 + dy2)
    if dx2 > 1
      @icon_chan_vx += @gravity_p / dx2 * dx / d
    end
    if dy2 > 1
      @icon_chan_vy += @gravity_p / dy2 * dy / d
    end

    @icon_chan_x += @icon_chan_vx
    @icon_chan_y += @icon_chan_vy
    if @icon_chan_x < 0
      @icon_chan_x = 0
      @icon_chan_vx = -@icon_chan_vx
    elsif @icon_chan_x >= W-32
      @icon_chan_x = W-33
      @icon_chan_vx = -@icon_chan_vx
    end
    if @icon_chan_y < 0
      @icon_chan_y = 0
      @icon_chan_vy = -@icon_chan_vy
    elsif @icon_chan_y >= H-32
      @icon_chan_y = H-33
      @icon_chan_vy = -@icon_chan_vy
    end
    ctx.add_code("ctx.drawImage(icon, #{@icon_chan_x}, #{@icon_chan_y});")

    @mu.synchronize do
      nshots = []
      @shots.each do |s|
        if s.move
          if (@icon_chan_x - s.x).abs < 30 && (@icon_chan_y - s.y).abs < 30
            @dead_icon_chan += 1
            init_icon_chan

            y = rand(H-100)+50
            @comments << Comment.new('%d名のあいこんちゃんが凶弾に倒れました' % @dead_icon_chan, y)
          else
            ctx.add_code("ctx.drawImage(icon, #{s.x}, #{s.y});")
            nshots << s
          end
        end
      end
      @shots = nshots
    end

    page, graphics = PAGES[@num]

    if graphics
      graphics[ctx, @turn]
    end

    page.strip!
    y = 50
    page.each_line do |line|
      ctx.add_code("ctx.fillText('#{line.chomp}', 10, #{y});")
      y += 45
    end

    ctx.fillStyle = 'rgb(0,0,0)'
    ctx.font = '30px sans-serif'
    @mu.synchronize do
      ncomments = []
      @comments.each do |c|
        c.x -= 5
        if c.x > -W
          ncomments << c
          ctx.add_code("ctx.fillText('#{c.msg}', #{c.x}, #{c.y});")
        end
      end
      @comments = ncomments
    end

    @ws.each do |ws, st|
      if st && st.down_x && st.down_y
        dx = st.x - st.down_x
        dy = st.y - st.down_y
        d = Math.sqrt(dx * dx + dy * dy)

        if d > 32
          ctx.beginPath()
          ctx.moveTo(st.down_x, st.down_y)
          ctx.lineTo(st.x, st.y)
          ctx.stroke()
        end
      end
    end

    @ws.each do |ws, st|
      message = ctx.code
      if st.state != @state
        st.state = @state

        ctrl = ''
        if @state == State::WAIT_PRESENTER
          ctrl = %Q{<input type="button" value="Start" onclick="sendMessage('start')">}
        elsif  @state == State::PRESENTATION
          ctrl = %Q{<form onsubmit="sendMessage('comment', $('c').value); $('c').value = ''; return false;">}
          #if @presenter == ws
            ctrl += %Q{<input type="button" value="←" onclick="sendMessage('left')"><input type="button" value="→" onclick="sendMessage('right')">}
          #end
          ctrl += %Q{<input id="c"><input type="submit" value="投稿">}
          ctrl += '</form>'
        end

        code = %Q<$('ctrl').innerHTML = '#{ctrl.gsub("'","\\\\\\\\'")}';>
        message += code
      end

      begin
        ws.send(message)
      rescue
        puts $!
        puts $!.backtrace
        @ws.delete(ws)
      end
    end
  end

  def onOpen(ws)
    @ws[ws] = State.new
  end

  def onClose(ws)
    @ws.delete(ws)
    if ws == @presenter
      @state = State::WAIT_PRESENTER
    end
  end

  def onMouseMove(ws, x, y)
    @ws[ws].set_xy(x, y)
  end

  def onMouseUp(ws, x, y)
    st = @ws[ws]
    if st && st.down_x && st.down_y
      dx = x - st.down_x
      dy = y - st.down_y
      d = Math.sqrt(dx * dx + dy * dy)

      if d > 32
        @mu.synchronize do
          @shots << Shot.new(st.down_x, st.down_y, 5 * dx / d, 5 * dy / d)
        end
      end

      st.init_down
    end
  end

  def onMouseDown(ws, x, y)
    st = @ws[ws]
    if st
      st.down_x = x
      st.down_y = y
    end
  end

  def onMessage(ws, args)
    p args
    cmd = args[0]
    case cmd
    when 'start'
      @state = State::PRESENTATION
      @presenter = ws
    when 'left'
      #if @presenter == ws && @num > 0
      if @num > 0
        @num -= 1
        @turn = 0
      end
    when 'right'
      #if @presenter == ws && @num < PAGES.size-1
      if @num < PAGES.size-1
        @num += 1
        @turn = 0
      end
    when 'comment'
      msg = args[1]
      y = rand(H-100)+50
      @mu.synchronize do
        @comments << Comment.new(msg, y)
      end
    end
  end
end

def newApp
  PresenApp.new
end

if __FILE__ == $0
  require 'cgi'
  puts %Q(<html><head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>HTML5でゲーム配信とか</title>
</head>
<body>
)

  PAGES.each do |p, c|
    puts CGI.escapeHTML(p.strip).gsub("\n", "<br>").gsub(" ", "&nbsp;")
    if c
      puts '<br><br>※<a href="http://shinh.skr.jp/tmp/mawaru.html">これ</a>が回ってました'
    end
    puts "<hr>"
  end
  puts '(Generated by wsock/server/presen.rb)'
end
