Dota on Rails
Bài đăng này đã không được cập nhật trong 3 năm
I. Giới thiệu về Dota 2 và hệ thống API
1. Dota 2
Dota 2 là một trò chơi hành động chiến thuật thời gian thực (ARTS) được Valve Corporation phát triển, dựa theo một mod game nổi tiếng, Defense of the Ancients, từ trò chơi Warcraft III: Reign of Chaos và bản mở rộng của nó The Frozen Throne. Valve phát hành Dota 2 qua hệ thống điều phối Steam của họ mà qua đó trò chơi được cập nhật song song với hệ thống phiên bản DotA.
The International là giải đấu lớn nhất của trò DotA 2, được tổ chức bởi Valve Corporation, nhà sang lập của trò chơi. Uy danh và giá trị của giải đấu thật không thể đo được: tiền thưởng cho cả giải đấu lên tới 16 triệu USD và vẫn tiếp tục tăng lên theo từng mùa. Team chiến thắng giải đấu có thể trở về nhà với 6 triệu USD, cùng với việc danh tiếng của họ được đi cùng trò chơi trong vòng 1 năm. (quaylen)
2. Steam API
Với nền tảng Engine mạnh mẽ do tự tay mình phát triển, cùng với gameplay tuyệt vời, Valve còn cung cấp API miễn phí cho các web developer.
Ngoài thông tin về account, với từng game API có thể lấy dữ liệu chi tiết cho từng trận đấu, dựa vào đó, người chơi có thế thống kê, phân tích tới từng game đấu. Điển hình như một số trang web: dotabuff, dotamax, ...
p=. _Hệ thống thống kê tuyệt vời_
Steam Web APIs
ISteamNews: Cung cấp methods lấy news feeds cho từng game Steam.
ISteamUserStats: Cung cấp methods lấy tất cả các chỉ số, info.
ISteamUser: Cung cấp thông tin về User Account.
Output Formats
Tất cả các API sẽ được request theo form:
http://api.steampowered.com/<interface name>/<method name>/v<version>/?key=<api key>&format=<format>.
Format có thể trả về dưới các dạng: json
, xml
, vdf
Steam OpenID Provider
Steam cung cấp OpenID, giúp ứng dụng của bạn có thể authenticate thông qua SteamID mà không cần phải tạo riêng User trên website của bạn.
II. Demo
Mục tiêu
Xây dựng ứng dụng thống kê các game đấu dota 2 thông qua dữ liệu lấy từ SteamAPI.
Yêu cầu
- Rails 4.
- Có account Steam và đặt chế độ public.
Các công việc thực hiện
- Login thông qua tài khoản Steam, save thông tin User vào DB.
- Lấy thông tin và show ra lịch sử các game đấu của User đang login hiện tại.
Good luck have fun! (honho)
1. Khởi tạo (len)
Tạo rails application mới:
rails new Doto
Tạo khung layout bằng Bootstrap
<nav class="nav navbar-inverse">
<div class="container" >
<div class="navbar-header">
<%= link_to "Doto", root_path, class: "navbar-brand" %>
</div>
</div>
</nav>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>">
<%= message %>
</div>
<% end %>
<%= yield %>
</div>
Create controller mới tên là MatchesController
rails g controller matches index show
Chỉnh root đến index
của controller vừa tạo
# config/routes.rb
root "matches#index"
2. Login thông qua tài khoản Steam
Steam web API có hỗ trợ OAuth protocol
, ta có thể thông qua nó để lấy về các thông tin như ID, nickname, avatar, ... của tài khoản đó trên Steam.
Để giản lược công việc ta sử dụng gem omniauth-steam
.
Nguồn: https://github.com/reu/omniauth-steam
Add gem:
gem "omniauth-steam"
Sau khi bundle install
ta tạo thêm file để config:
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :steam, ENV['STEAM_KEY']
end
ENV["STEAM_KEY"]
là key mà steam cung cấp, thông qua đó có thể kết nối tới steam API.
Truy cập trang để đăng ký (free): https://steamcommunity.com/dev/apikey
Nhưng thông tin ta sẽ lấy về:
- uid: user id của account.
- nickname: nickname của user.
- avatar_url: đường dẫn avatar.
- profile_url: url profile.
Tạo model User
và migration:
rails g model User uid:string nickname:string avatar_url:string profile_url:string
rake db:migrate
Thêm một vài routes.
# config/routes.rb
post '/auth/:provider/callback' => 'sessions#create'
delete '/logout' => 'sessions#destroy'
Gem chỉ hỗ trợ lấy về account detail, để xử lý login, ta tạo controller tên SessionsController
class SessionsController < ApplicationController
def create
begin
@user = User.omniauth_to_db request.env['omniauth.auth']
rescue
flash[:error] = "Can't authorize you..."
else
session[:user_id] = @user.id
flash[:success] = "Welcome, #{@user.nickname}!"
end
redirect_to root_path
end
end
Trong đó có hàm omniauth_to_db
là class method được ta định nghĩa trong Model
, với mục đích save user nếu nó chưa tồn tại trong DB.
# models/user.rb
def self.omniauth_to_db auth
info = auth['info']
user ||= find_by uid: auth['uid']
user.nickname = info['nickname']
user.avatar_url = info['image']
user.profile_url = info['urls']['Profile']
user.save!
user
end
Tuy nhiên, lúc này login ta sẽ gặp lỗi
Nguyên nhân là khi create sessions, ta gửi tới Steam thông qua phương thức POST
.
Vì vậy Rails sẽ tự động tìm tới CSRF token (không tồn tại). Để login hoạt động, cần add thêm callback để skip token.
skip_before_filter :verify_authenticity_token, only: :create
Thông tin user được lấy bằng API, trả về theo dạng hash
, được đặt trong request.env['omniauth.auth']
.
{
:provider => "steam",
:uid => "76561198010202071",
:info => {
:nickname => "Reu",
:name => "Rodrigo Navarro",
:location => "BR",
:image => "http://media.steampowered.com/steamcommunity/public/images/avatars/3c/3c91a935dca0c1e243f3a67a198b0abea9cf6d48_medium.jpg",
:urls => {
:Profile => "http://steamcommunity.com/id/rnavarro1/"
}
},
:credentials => {},
:extra => {
:raw_info => {
:steamid => "76561198010202071",
:communityvisibilitystate => 3,
:profilestate => 1,
:personaname => "Reu",
:lastlogoff => 1325637158,
:profileurl => "http://steamcommunity.com/id/rnavarro1/",
:avatar => "http://media.steampowered.com/steamcommunity/public/images/avatars/3c/3c91a935dca0c1e243f3a67a198b0abea9cf6d48.jpg",
:avatarmedium => "http://media.steampowered.com/steamcommunity/public/images/avatars/3c/3c91a935dca0c1e243f3a67a198b0abea9cf6d48_medium.jpg",
:avatarfull => "http://media.steampowered.com/steamcommunity/public/images/avatars/3c/3c91a935dca0c1e243f3a67a198b0abea9cf6d48_full.jpg",
:personastate => 1,
:realname => "Rodrigo Navarro",
:primaryclanid => "103582791432706194",
:timecreated => 1243031082,
:loccountrycode => "BR"
}
}
}
Xử lý Logout
Tạo method destroy
đơn giản, xóa đi session khi trước được thiết lập dựa trên user id lấy về từ steam.
# sessions_controller.rb
def destroy
if current_user
session.delete :user_id
flash[:success] = "GGWP"
end
redirect_to root_path
end
Xử lý login & logout bên phía backend đã xong, giờ đến lượt layout
<!-- layouts/application.html.erb -->
<nav class="nav navbar-inverse">
<div class="container" >
<div class="navbar-header">
<%= link_to "Doto", root_path, class: "navbar-brand" %>
</div>
<div id="navbar">
<% if current_user %>
<ul class="nav navbar-nav pull-right">
<li><%= image_tag current_user.avatar_url, alt: current_user.nickname %></li>
<li><%= link_to 'Log Out', logout_path, method: :delete %></li>
</ul>
<% else %>
<ul class="nav navbar-nav">
<li><%= link_to 'Log In', '/auth/steam' %></li>
</ul>
<% end %>
</div>
</div>
</nav>
Kết quả hoàn thành bước 1
Khi click vào link Log In
Sau khi ấn nút Sign In
lập tức được khởi tạo session và redirect về trang chủ
2. Lấy thông tin từ Dota 2 API (lịch sử trận đấu)
Tương tự như khi login, ta cũng sử dụng thêm gem dota
Nguồn: https://github.com/vinnicc/dota
Add gem
gem "dota", github: "vinnicc/dota", branch: "master"
Sau khi chạy `bundle install`, tạo thêm file để config:
# config/initializers/dota.rb
Dota.configure do |config|
config.api_key = ENV['STEAM_KEY']
end
ENV['STEAM_KEY']
cũng chính là Steam Api key giống bước 1.
Dota 2 API cung cấp cho ta ty tỷ thứ liên quan đến trận đấu, gem dota
đã giản lược chúng, cung cấp các method giúp ta dễ dàng lấy ra các thông tin. List các method bạn có thể nghiên cứu ở đây
https://github.com/vinnicc/dota#matches
Đối với app của chúng ta, dưới đây sẽ là các thông tin mà tôi sẽ get về:
- uid: id của trận đấu.
- winner: team chiến thắng (1 trong 2 phe - Radiant hoặc Dire).
- starts_at: thời gian trận đấu bắt đầu.
- mode: chế độ find (thường hoặc rank)
- match_type: chế độ chơi (Tournament, Co-op with Bots, ...)
- duration: thời lượng của trận đấu.
- user_id: id của user.
Tạo model để lưu trữ:
rails g model Match uid:string winner:string starts_at:datetime mode:string match_type:string duration:string user:references
rake db:migrate
Thêm quan hệ trong model `User`
# models/user.rb
has_many :matches
Tương tự như method `omniatuth_to_db` ta cũng tạo mới 1 method nhằm lưu trữ lịch sử trận đấu được lấy về thông qua API vào DB.
def load_matches count
matches_arr = Dota.api.matches(player_id: self.uid, limit: count)
if matches_arr.present?
matches_arr.each do |match|
unless self.matches.where(uid: match.id).present?
match_info = Dota.api.matches match.id
new_match = self.matches.create({
uid: match.id,
winner: match_info.winner.to_s,
starts_at: match_info.starts_at,
first_blood: match_info.first_blood,
mode: match_info.mode,
duration: parse_duration(match_info.duration),
match_type: match_info.type
})
end
end
end
end
Trong đó agrument count
truyền vào là số trận đấu lấy về (tính từ thời điểm gần nhất).
Ngoài ra các bạn có thể còn thấy có hàm parse_duration
, để chuyển đổi format thời lượng trận đấu - vốn được tính bằng tổng số giây => hh:mm:ss
# models/user.rb
private
def parse_duration(d)
hr = (d / 3600).floor
min = ((d - (hr * 3600)) / 60).floor
sec = (d - (hr * 3600) - (min * 60)).floor
hr = '0' + hr.to_s if hr.to_i < 10
min = '0' + min.to_s if min.to_i < 10
sec = '0' + sec.to_s if sec.to_i < 10
hr.to_s + ':' + min.to_s + ':' + sec.to_s
end
Gọi hàm load_matches
tại controller
# sessions_controller.rb
def create
begin
@user = User.omniauth_to_db request.env['omniauth.auth']
rescue
flash[:error] = "Can't authorize you..."
else
@user.load_matches 20
session[:user_id] = @user.id
flash[:success] = "Welcome, #{@user.nickname}!"
end
redirect_to root_path
end
Dưới model
đã xử lý lưu trữ xong, giờ trên controller
cần load nó ra:
# matches_controller.rb
def index
@matches = current_user.matches if current_user
end
Config lại routes
resources :matches, only: [:index, :show]
Hiển thị trên view:
<!-- matches/index.html.erb -->
<% if @matches.present? %>
<table class="table table-striped table-hover">
<% @matches.each do |match| %>
<tr>
<td>
<%= link_to match.starts_at, match_path(match) %>
</td>
</tr>
<% end %>
</table>
<% end %>
<!-- matches/show.html.erb -->
<h2><%= @match.winner %> won</h2>
<ul>
<li><strong>Mode:</strong> <%= @match.mode %></li>
<li><strong>Type:</strong> <%= @match.match_type %></li>
<li><strong>Duration:</strong> <%= @match.duration %></li>
<li><strong>First blood:</strong> <%= @match.first_blood %></li>
</ul>
Kết quả thực hiện bước 2
Trang index hiện ra list các trận đấu
Show ra kết quả trận đấu
Good game well play!
To be continue. Phần tiếp theo sẽ ra chi tiết của từng trận đấu (hero, skill build, GPM, XPM, ...)
Github:
https://github.com/NguyenTanDuc/doto
Heroku:
Coming soon
**Nguồn tham khảo**:
All rights reserved