12/08/2018, 13:46

Tạo game hangman

Tài liệu dịch: A Ruby Story Trong bài viết này chúng tôi sẽ đi tạo một game hangman, đây là một game đơn giản mà bất kỳ một người chơi nào cũng có thể tìm ra từ mà ứng dụng của chúng tôi đã đưa ra bởi việc lựa chọn các chữ cái. Chúng tôi sẽ sử dụng: Rails (phiên bản 4): bạn sẽ thấy những gì ...

Tài liệu dịch: A Ruby Story

Trong bài viết này chúng tôi sẽ đi tạo một game hangman, đây là một game đơn giản mà bất kỳ một người chơi nào cũng có thể tìm ra từ mà ứng dụng của chúng tôi đã đưa ra bởi việc lựa chọn các chữ cái.

Chúng tôi sẽ sử dụng:

Rails (phiên bản 4): bạn sẽ thấy những gì hoàn toàn khác nếu bạn cài sai phiên bản. Foundation: cho looks & feels Font Awesome: cho icons

Tổng Quan:

Ứng dụng sẽ bao gồm 2 trang: 1. Welcome page - bắt đầu một game mới - tiếp tục game khi người dùng sẵn sàng bắt đầu 2. Game page - gallows container - một bộ chứa các chữ cái - một bộ chứa các ký tự la tinh - một button để quay trở lại home page và bắt đầu một game mới

Chúng tôi không dùng authentication/authorisation và không dùng active record để lưu trữ dữ liệu. Tất cả người dùng và trạng thái game sẽ lưu giữ trong session.

Code cho bài viết có sẵn trên Github

Hands on:

Hãy bắt đầu bằng việc tạo một ứng dụng rails và sử dụng -O để loại bỏ Active Record: rails new hangman -O

Bây giờ trong ứng dụng chúng tôi sẽ xóa đi những thứ không sử dụng:

Turbolinks: remove turbolinks từ Gemfile

mở file app/assets/javascripts/application.js và remove đi dòng //=require turbolinks

mở app/views/layouts/application.html.erb và remove đi 'data-turbolinks-track' => true

CoffeeScript:
remove đi gem coffee-rails từ Gemfile

sau khi đã thay đổi, mở terminal và chạy bundle từ thư mục root của ứng dụng để đảm bảo chúng ta không làm hỏng bất cứ thứ gì bundle

bây giờ sẽ bật server để kiểm tra tiến trình mà chúng ta đã thiết lập từ trước. Từ thư mục root của ứng dụng thực thi rails s

Controllers:
tạo ra 2 controller là HomeController và GamesController

rails generate controller home index rails generate controller games new show update destroy

Routes: mở file config/routes.rb và remove tất cả nội dung ở bên trong, sau đó thêm vào các dòng sau:

root to: 'home#index' resource :game, only: [:new, :show, :update, :destroy]`

Model: chúng ta cần tạo ra một lớp Game để giứ trạng thái game (tạo ra một file game.rb nằm trong thư mục models).

trong lớp Game cần làm những việc sau: 1. Xác định số lần lớn nhất của người chơi bị trượt. 2. Giữ những từ đã đoán được 3. Giữ những chữ cái đã được lựa chọn bởi người chơi 4. khởi tạo game 5. Trả lời số chữ cái đoán trượt 6. Trả lời nếu người chơi đoán được từ hay không 7. Trả lời nếu game là kết thúc hay chưa 8. Lựa chọn chữ cái

Xác định số lần lớn nhất của người chơi bị trượt:

Người chơi không thể cứ tiếp tục lựa chọn chữ cái mãi bởi vì như thế sẽ không phải là một trò chơi mà một bài tập luyện click chuột. Chúng ta phải xác định một gioi hạn số lận đoán trượt. Do đó thêm dòng sau vào lớp Game MAX_FAILED_ATTEMPTS = 5

Giữ những từ đã được đoán: thêm một attribute accessor cho wordattr_accessor :word

Giữ những chữ cái mà người chơi đã lựa chọn: attr_accessor :selected_letters

Khởi tạo một game:

def initialize
  @word = 'Hangman'.upcase
  @selected_letters = []
end

Giờ tạm thời chúng ta lựa chọn từ Hangman nhưng sau chúng ta sẽ tạo ra một cơ chế cho phép lựa chọn từ ngẫu nhiên từ một nguồn cụ thể.

Trả lời số chữ cái đoán trượt:

def failed_attempts
  selected_letters.select { |letter|
    !word.include?(letter)
  }.size
end

Ở đây chúng ta lặp qua các chữ cái được lựa chọn và đếm xem có bao nhiêu trong số chúng đã được bao gồm trong từ của game.

Trả lời nếu người chơi đã đoán được từ

def guessed?
  (word.split(') - selected_letters).empty?
end

Ở đây chúng ta chuyển đổi word thành một mảng các ký tự của nó và sau đó chúng ta sẽ loại bỏ những chữ cái được lựa chọn từ mảng này. Nếu kết qủa là empty điều đó có nghĩa là người dùng đã lựa chọn tất cả các chữ cái của từ và người chơi đã đoán được từ.

Trả lời nếu trò trơi được kết thúc:

def finished?
  failed_attempts >= MAX_FAILED_ATTEMPTS || guessed?
end

Ở đây chúng ta sẽ trả lời rằng một game đã kết thúc khi đã đạt đén giới hạn số lần đoán trượt hoặc người chơi đã đoán từ thành công.

Lựa chọn một chữ cái:

def select!(letter)
  raise GameOverError if finished?
  selected_letters << letter unless selected_letters.include? letter
  word.include? letter
end

Ở đây chúng ta sẽ phát sinh ra lỗi trong trường hợp game kết thúc. Chúng ta sẽ xử lý lỗi này sau trong controller của chúng ta. Chúng ta sẽ add nhưng chữ cái được lựa chọn trong trạng thái của game. và sẽ đưa ra trả lời là đúng hay sai dựa trên việc có chứa hay không chữ cái này.

Chú ý: chúng ta định nghĩa lớp GameOverError ở một nơi nào khác ngoài code của chúng ta là không hợp lệ. bởi vậy hãy thêm dòng sau vào lớp Gameclass GameOverError < StandardError; end

Tiếp tục thêm code sau vào class game

include ActiveModel::Serializers::JSON

def attributes
  {word: nil, selected_letters: nil}
end

def attributes=(hash)
  hash.each do |key, value|
    send("#{key}=", value)
  end
end

Đã đến lúc chúng ta cấu hình ứng dụng để sử dụng Foundation và Font Awesome để phục vụ cho việc tạo các views

Foundation:

chúng ta sẽ sử dụng Foundation bởi vì grid linh động tuyệt vời của nó.

qua đó sẽ sử dụng thêm một số tính năng hấp dẫn khác (như là các button styles & utilities).

đầu tiên cài đặt gem foundation-rails

tiếp theo bundle

kế tiếp, thiết lập nó cho ứng dụng thực thi như sau

rails generate foundation:install

tiếp đến khi chạy câu lệnh trên terminal có hỏi chúng ta có muốn ghi đè message thì nhấn enter tiếp tục, lúc đó Foundation đã được cài đặt thành công và thiết lập cho ứng dụng.

Font Awesome:

chúng tôi sử dụng font-awesome bởi vì các icons tuyệt vời của nó và thực tế chúng có thay đổi được kích thước và màu sắc theo quy luật kích thước font và màu sắc của css.

đầu tiên chúng ta cần cài đặt gem font-awesome-rails:

`gem 'font-awesome-rails'

thêm dòng sau vào file app/stylesheets/application.css:

*= require font-awesome`

Bây giờ chúng ta có thể bắt đầu xây dựng views

Welcome page:

Giao diện của trang như hình dưới: welcome-draft.png

Thêm code sau tới file index.html.erb

<div class="row">
    <div class="small-12 columns text-center">
        <h1>Welcome to Hangman!</h1>
    </div>
</div>
<div class="row">
    <div class="medium-6 columns">
        <button class="button expand">New game</button>
    </div>

    <div class="medium-6 columns">
        <button class="button expand">Continue game</button>
    </div>
</div>

Chúng tôi không sử dụng bất kỳ database nào mà chúng tôi sử dụng session để lưu trữ vì đây là một ví dụ cơ bản để thực hành còn trong các ứng dụng thực thì không nên dùng theo cách này.

Do đó mở file app/controllers/application_controller.rb thêm vào code sau:

  class ApplicationController < ActionController::Base
      protect_from_forgery with: :exception

      helper_method :current_game

      def current_game
        @current_game ||= load_current_game
      end

      def set_current_game(game)
        @current_game = game
        session[:serialized_current_game] = game.present? ? game.to_json : nil
      end

      def update_current_game
        set_current_game @current_game
      end

      protected

      def load_current_game
        Game.new.from_json(session[:serialized_current_game]) if session[:serialized_current_game].present?
      end
  end

Chú ý:

current_game: load và giữ game hiện thời tới biến @current_game.

set_current_game: thiết lập cho biến @current_page và tên của game hiện thơi đã được serializer lưu trữ trong sesssion.

update_current_game: serialize game hiện thời tới session, chúng ta sẽ sử dụng nó phụ thuộc vào việc người dùng lựa chọn chữ cái.

Thay thề file index.html.erb với code sau:

<div class="row">
    <div class="small-12 columns text-center">
        <h1>Welcome to Hangman!</h1>
    </div>
</div>
<div class="row">
    <div class="<%= current_game.present? && !current_game.finished? ? 'medium-6' : '' %> columns">
        <%= link_to new_game_path, :class => 'button expand' do %>
            <i class="fa fa-play"> New game</i>
        <% end %>
    </div>

    <% if session[:serialized_current_game].present? %>
        <div class="medium-6 columns">
            <%= link_to game_path, :class => 'button expand' do %>
                <i class="fa fa-refresh"> Continue game</i>
            <% end %>
        </div>
    <% end %>
 </div>

Reload lại page của trình duyệt home-3.png

Ở đây khi nhấn button "New game" chúng ta chỉ cần tạo ra một game hangman mới, thiết lập session của người chơi giống như là game hiện thời và sau đó chuyển đến page show game.

Bởi vậy, xóa đi file phát sinh tự động app/views/games/new.html.erb và thay đổi action new trong controller games như sau:

def new
  set_current_game Game.new
  redirect_to game_path
end

Bây giờ, nhấn button "New game" sẽ chuyển sang page show game

Show page: Giao diện như sau: show-draft.png

Cho gallows, chúng ta sử dụng 6 cái ảnh (download chúng ở đây, sau đó thêm tới app/assets/images)

Bây giờ mở file app/views/games/show.html.erb và thay thế nội dung bên trong bằng code dưới đây:

    <div class="row">
    <div class="medium-4 columns">
        <div id="gallows" class="gallows gallows-state-<%= current_game.failed_attempts %>">

        </div>
    </div>

    <div class="medium-8 columns">
        <div class="row">
            <div class="small-12 columns">
                <ul class="word small-block-grid-<%= current_game.word.length %>">
                    <% current_game.word.split(').each do |letter| %>
                        <li>
                            <div class="word-letter">
                                <%= current_game.finished? || current_game.selected_letters.include?(letter) ? letter : '_' %>
                            </div>
                        </li>
                    <% end %>
                </ul>
            </div>
        </div>

        <% if current_game.finished? %>
            <div class="row game-status">
                <div class="small-12 columns text-center">
                    <% if current_game.guessed? %>
                        <span class="label success radius">You successfully guessed the word! :)</span>
                    <% else %>
                        <span class="label alert radius">No, no... You didn't find the word :(</span>
                    <% end %>
                </div>
            </div>
        <% end %>

        <%= form_for :game, :url => game_path, :method => :patch do |form| %>
            <div class="row">
                <div class="letters">
                    <% ('A'..'Z').each do |letter| %>
                        <%
                            if current_game.selected_letters.include? letter
                                button_class = current_game.word.include?(letter) ? 'success' : 'alert'
                            end
                        %>
                        <div class="medium-2 columns text-center">
                            <div class="letter">
                                <%= form.submit letter, :name => 'letter', :class => "button expand #{button_class}" %>
                            </div>
                        </div>
                    <% end %>
                </div>
            </div>
        <% end %>

        <div class="row">
            <div class="game-actions">
                <div class="<%= current_game.finished? ? '' : 'medium-6' %> columns">
                    <%= link_to root_path(:method => :delete), :class => 'button expand' do %>
                        <i class="fa fa-home"></i> Home
                    <% end %>
                </div>

                <% unless current_game.finished? %>
                    <div class="medium-6 columns">
                        <%= link_to game_path, :class => 'button expand alert', :method => :delete do %>
                            <i class="fa fa-fire"></i> Cancel game
                        <% end %>
                    </div>
                <% end %>
            </div>
        </div>
    </div>
</div>

Chỉnh sửa file app/controllers/games_controller.rb và thêm vào 2 phương thức như sau:

def update
  current_game.select! params[:letter]
  update_current_game
rescue Game::GameOverError
  flash[:alert] = 'This game is finished...'
ensure
  redirect_to game_path
end

def destroy
  set_current_game nil
  redirect_to root_path
end

Reload lại page giờ chúng ta có thể chơi game: hangman-reset.png

0