Ruby
Sinatra
websocket

Web開発コースでも対戦ゲームが作りたかった。

Web開発コースでも対戦ゲームが作りたかった。

こんにちはしげるです!
名古屋のWebコースのメンターしてます。
Web開発コースってサービスばっかりでゲーム作っていないイメージありませんか?
サーバーを使って他の人と対戦したい!って思いありませんか?
ゲームコース楽しそうとか思っているそこの君っ!この記事を呼んでゲームを作ってみよう!!

アイディア

  • 対戦型のオセロを作ろう!

利用技術

  • sinatra
  • web socket

web socketとは

HTML5から利用が可能で、他のクライアントと簡単に相互通信ができる規格。
WebSocketについて調べてみた。

はじめに参加しているユーザーをカウントしてみよう!

テンプレート

 $ git clone https://github.com/4geru/simple-websocket-count.git
 $ bundle # パッケージのインストール
 $ bundle exec rake db:migrate # データベースの構成
 $ bundle exec ruby app.rb # サーバーの起動

オセロ盤を作る

複数のブラウザページを開いて数が増えたり減ったりするのを確認できたらOK!
次に、オセロの板を作ってみよう!for文を使うことでtableを簡単に作ることができます!

html
<table>
  <% for i in 0..7 %>
  <tr>
    <% for j in 0..7%>
      <td onclick="s('<%= i %><%= j %>')"  id='<%= i %><%= j %>'>
        <% # 真ん中のおけないところ %>
        <% if (i == 4 and j == 4) or (i == 3 and j == 3)%>
          <div class='black'></div>
        <% end %>
        <% if (i == 3 and j == 4) or (i == 4 and j == 3)%>
          <div class='white'></div>
        <% end %>
      </td>
    <% end %>
  </tr>
  <% end %>
</table>
css
td{
  background: green;
  width: 100px; height: 100px;
}
table{
  background: black;
}
.black{
  background: black;
  margin: auto;
  width: 80px; height: 80px;
  border-radius: 50%;
}
.white{
  background: white;
  margin: auto;
  width: 80px; height: 80px;
  border-radius: 50%;
}

WebSocketを実装しよう

実際に、WebSocketを使って行きます!
WebSocketは常にサーバーとブラウザが接続されているイメージ!
app.rbの中のsettings.socketsで、接続しているブラウザを全て管理しています。
リクエストを送って来たsocketをwsとして、app.rbの中で使っています。

ws.send("send")で、元のsocketに対して "send" というメッセージを送ることができます!
データは、JavaScriptでは、JSON。RubyではHashで扱いたいけど、sendでは、文字しか送れないので、JavaScirpt・Rubyでそれぞれ変換する必要がります。
複数のデータを送りたい場合は、送る時に、typeを書くと管理しやすいです。

app.rb
get '/websocket/count' do
  if request.websocket? then
    request.websocket do |ws|
      ws.onopen do # 接続を開始した時
        settings.sockets << ws # socketsリストに追加
        c = Count.first # count の数を増やす
        c.count += 1
        c.save
        settings.sockets.each do |s| # 全体へメッセージを転送
          c = Count.first
          s.send({type: 'count', count: c.count}.to_json.to_s)
        end
      end
      ws.onmessage do |msg| # メッセージを受け取った時
        puts 'メッセージを受け取ったよ!'
        data = JSON.parse(msg)
        case data['type']
        when 'open', 'close' # 送られたデータが open or close データだったら
          settings.sockets.each do |s| # 全体へメッセージを転送
            c = Count.first
            s.send({type: 'count', count: c.count}.to_json.to_s)
          end
        when 'board' # 送られたデータが board データだったら
          turn  = data['turn'] == 'black' ? 'white' : 'black'
          puts data
          settings.sockets.each do |s| # メッセージを転送
            s.send({type: 'board', turn: turn, pos: data['pos']}.to_json.to_s)
          end
        end
      end
      ws.onclose do # メッセージを終了する時
        c = Count.first # count の数を減らす
        c.count -= - 1
        c.save
        settings.sockets.each do |s| # 全体へメッセージを転送
          c = Count.first
          s.send({type: 'count', count: c.count}.to_json.to_s)
        end
        settings.sockets.delete(ws) # socketsリストから削除
      end
    end
  end
end
javascript
var countBox = document.getElementById("count");
var turnBox = document.getElementById("turn");

window.onload = function(){
  var count = new WebSocket('ws://' + window.location.host + "/websocket/count");

  // 接続が始まった時
  count.onopen = function() { count.send('{"type":"open"}'); };
  // 接続が終わった時
  count.onclose = function() { count.send('{"type":"close"}'); };
  // メッセージを受け取った時
  count.onmessage = function(m) {
    data = JSON.parse(m.data)
    if(data.type == 'count'){ // サーバーからカウントを変更する命令が来た時
      countBox.innerHTML = data.count         
    } else if(data.type == 'board'){ // サーバーから石を置く命令が来た時
      turn = data.turn == 'black' ? 'white' : 'black'
      turnBox.innerHTML = data.turn
      document.getElementById(data.pos).innerHTML = '<div class=' + turn + '></div>'
      console.log('get board')
    }
  }

  s = function(msg){
    // すでに石が置かれていたら置かない
    if(!document.getElementById(msg).innerHTML.match('div'))
      count.send(JSON.stringify({type:'board', pos: msg, turn: turnBox.innerHTML}));
  }
};

ezgif-4-d808a842d8.gif
ここまでのリポジトリ
なんとか形になるものにはできた。
WebSocketを使ってみたなので、実際のオセロの実装については、また別のお話。

次は

明日は担当はまなみんです!gitの記事を書くみたいです。
gitをソースコード管理以外にもレポート管理にも使えるので、どんな内容か気になります。