diff --git a/.gitignore b/.gitignore index 6a502e9..83e5b73 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Ignore bundler config. /.bundle +.rvm # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal @@ -14,3 +15,5 @@ # Ignore all logfiles and tempfiles. /log/*.log /tmp +*.sw* +/**/*.sw* diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf01ad1..b41e4c0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,14 +6,14 @@ class ApplicationController < ActionController::Base # run these methods on page load before_filter :current_user - private - - # Finds the User with the ID stored in the session with the key - # :current_user_id This is a common way to handle user login in - # a Rails application; logging in sets the session value and - # logging out removes it. + helper_method :current_user + def current_user - session[:id] ||= request.remote_ip.hash + rand(1000) - @_current_user = session[:id] + @current_user ||= unless @current_user + session[:id] ||= request.remote_ip.hash + rand(1000) + end end + + private + end diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index fc00435..38cb550 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -1,74 +1,48 @@ class GamesController < ApplicationController - before_action :set_game, only: [:show, :edit, :update, :destroy] + before_action :set_game, only: [:add_player, :deal, :player_action, :show] def add_player - unless @_current_user.nil? - set_game - state = @game.load_state - if state[:players].size < 5 - unless state[:players].include?(@_current_user) - if state[:cards_in_play] - redirect_to @game, notice: 'Cant join once the game has already started' - return - end - state[:players].push @_current_user - while state[:names].include? params[:username] - # make sure username is unique by appending random numbers - params[:username] += rand(10).to_s - end - state[:names].push params[:username] - @game.save_state state - if @game.save - redirect_to @game, notice: 'Joined the game!' - else - redirect_to @game, notice: 'Failed to join the game' - end - else - redirect_to @game, notice: 'Youre already in the game' - end - else - redirect_to @game, notice: 'There are too many players in the game already' - end + if @game.state_data[:players].size > 5 + redirect_to @game, notice: 'There are too many players in the game already' + elsif @game.has_player?(current_user) + redirect_to @game, notice: 'Youre already in the game' + elsif !@game.waiting_for_players? + redirect_to @game, notice: 'Cant join once the game has already started' + else + session[:player_name] = @game.add_player(current_user, params[:username]) + redirect_to @game, notice: "Welcome to the game" end end - + def deal - set_game - state = @game.load_state - if state[:players].size < 3 or state[:players].size > 5 - redirect_to @game, notice: 'Must have between 3 and 5 players to start' - return - end - if state[:players].first == @_current_user - if state[:cards_in_play].nil? - @game.save_state @game.deal_cards(state) - @game.save - redirect_to @game, notice: 'Game has started!' - else - redirect_to @game, notice: 'Game has already started' - end - else - redirect_to @game, notice: 'Only the creator of the game can start it' - end + message = if @game.state_data[:players].size < 3 or @game.state_data[:players].size > 5 + 'Must have between 3 and 5 players to start' + elsif !@game.waiting_for_players? + 'Game has already started' + elsif @game.can_deal?(current_user) + 'Only the creator of the game can start it' + else + @game.deal_cards(@game.state_data) + @game.state_data[:current_status] = :in_play + @game.save! + 'Game has started!' + end + redirect_to @game, notice: message end def player_action - set_game - state = @game.load_state - - # player isnt allowed to do anything if it's not their turn - if state[:waiting_on] != @_current_user - redirect_to @game, notice: "Dude... It's not your turn" + if @game.state_data[:waiting_on] != current_user + redirect_to @game, notice: "Bro... It's not your turn" return end - + if params[:bid] # user is making a bid - if @game.done_bidding? state + if @game.done_bidding? redirect_to @game, notice: 'Bidding is over BRO' return else - if @game.player_action(@_current_user, params[:bid]) + if @game.player_action(current_user, params[:bid]) @game.save redirect_to @game, notice: 'Placed bid, YEAAA!' else @@ -78,7 +52,7 @@ def player_action else # user is playing a card card = Card.new(params[:suit], params[:value]) - if @game.player_action(@_current_user, card) + if @game.player_action(current_user, card) @game.save redirect_to @game, notice: "Played a card, niceee!" else @@ -96,49 +70,47 @@ def index # GET /games/1 # GET /games/1.json def show - state = @game.load_state - - @is_playing = state[:players].include?(@_current_user) - @game_started = !state[:bids].nil? - player_index = state[:players].index(@_current_user) || 0 + player_index = @game.state_data[:players].index(current_user) || 0 + @is_playing = @game.state_data[:player].has_key?(current_user) + @game_started = !@game.state_data[:bids].nil? # round number - @round = state[:total_rounds] - state[:rounds_played] + @round = @game.state_data[:total_rounds] - @game.state_data[:rounds_played] # names/scores around the board # add in different order so the user is always on the bottom @names = [] @round_scores = [] - @game.iterate_through_list_with_start_index(player_index, state[:names]) do |name, i| - tricks_taken = (state[:tricks_taken] and state[:tricks_taken][i]) ? state[:tricks_taken][i].size : 0 - bid = (state[:bids] and state[:bids][i]) ? state[:bids][i] : 'No bid yet' + @game.iterate_through_list_with_start_index(player_index, @game.state_data[:players]) do |player_id, i| + tricks_taken = (@game.state_data[:tricks_taken] and @game.state_data[:tricks_taken][i]) ? @game.state_data[:tricks_taken][i].size : 0 + bid = (@game.state_data[:bids] and @game.state_data[:bids][i]) ? @game.state_data[:bids][i] : 'No bid yet' score = 0 - if state[:score] and state[:score][i] - score = state[:score][i].inject :+ + if @game.state_data[:score] and @game.state_data[:score][i] + score = @game.state_data[:score][i].inject :+ end - @names.push name - @round_scores.push (name.nil?) ? nil : "Tricks taken: #{tricks_taken} | Bid: #{bid} | Score: #{score}" + @names.push @game.state_data[:names][i] + @round_scores.push (player_id.nil?) ? nil : "Tricks taken: #{tricks_taken} | Bid: #{bid} | Score: #{score}" end - + # cards that have been played - @played_cards = state[:cards_in_play] + @played_cards = @game.state_data[:cards_in_play] if @game_started # display cards in different order since the user is on the bottom @played_cards = [] - @game.iterate_through_list_with_start_index(player_index, state[:players]) do |player,i| - @played_cards.push state[:cards_in_play][i] + @game.iterate_through_list_with_start_index(player_index, @game.state_data[:players]) do |player,i| + @played_cards.push @game.state_data[:cards_in_play][i] end end # players hand @cards = [] - if @is_playing - @cards = state[:player_hands][player_index] || @cards + if @is_playing && @game_started + @cards = @game.state_data[:player_hands][player_index] || @cards # can't play any cards unless it's your turn playable_cards = [] - if state[:waiting_on] == @_current_user - playable_cards = @game.get_playable_cards(state[:first_suit_played], state[:player_hands][player_index]) + if @game.state_data[:waiting_on] == current_user + playable_cards = @game.get_playable_cards(@game.state_data[:first_suit_played], @game.state_data[:player_hands][player_index]) end @cards.each do |card| if playable_cards.include? card @@ -149,21 +121,22 @@ def show @cards.sort! { |a,b| a.suit_order b } # game status (ie. who we're waiting on) - if state[:waiting_on] - waiting_on_index = state[:players].index(state[:waiting_on]) - @waiting_on = waiting_on_index ? state[:names][waiting_on_index] : "Table to clear" - @waiting_on = 'YOU' if @_current_user == state[:waiting_on] - @done_bidding = @game.done_bidding? state + if @game.state_data[:waiting_on] + waiting_on_index = @game.state_data[:players].index(@game.state_data[:waiting_on]) + @waiting_on = @game.state_data[:names][waiting_on_index] || "Table to clear" + @waiting_on = 'YOU' if current_user == @game.state_data[:waiting_on] + + @done_bidding = @game.done_bidding? unless @done_bidding @waiting_on += " (BIDDING)" end else @waiting_on = 'Game to start' end - + # show ace of spades if game hasnt started - @trump = state[:trump_card] || Card.new('Spades', 12) - + @trump = @game.state_data[:trump_card] || Card.new('Spades', 12) + # make sure show.js.erb is executed in the views folder respond_to do |format| format.js @@ -176,59 +149,24 @@ def new @game = Game.new end - # GET /games/1/edit - def edit - end - - # POST /games - # POST /games.json def create - @game = Game.new(game_params) - @game.set_up + @game = Game.create!(game_params) + @game.state_data[:players] << current_user + @game.save! respond_to do |format| - if @game.save - format.html { redirect_to @game, notice: 'Game was successfully created.' } - format.json { render :show, status: :created, location: @game } - else - format.html { render :new } - format.json { render json: @game.errors, status: :unprocessable_entity } - end + format.html { redirect_to @game, notice: 'Game was successfully created.' } + format.json { render :show, status: :created, location: @game } end end - # PATCH/PUT /games/1 - # PATCH/PUT /games/1.json - def update - respond_to do |format| - if @game.update(game_params) - format.html { redirect_to @game, notice: 'Game was successfully updated.' } - format.json { render :show, status: :ok, location: @game } - else - format.html { render :edit } - format.json { render json: @game.errors, status: :unprocessable_entity } - end - end - end + private - # DELETE /games/1 - # DELETE /games/1.json - def destroy - @game.destroy - respond_to do |format| - format.html { redirect_to games_url, notice: 'Game was successfully destroyed.' } - format.json { head :no_content } - end + def set_game + @game = Game.find(params[:id]) end - private - # Use callbacks to share common setup or constraints between actions. - def set_game - @game = Game.find(params[:id]) - end - - # Never trust parameters from the scary internet, only allow the white list through. - def game_params - params.require(:game).permit(:name, :state) - end + def game_params + params.require(:game).permit(:name, :state) + end end diff --git a/app/models/game.rb b/app/models/game.rb index da726e1..d927cf4 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -1,22 +1,43 @@ class Game < ActiveRecord::Base require 'yaml' + after_initialize :state_data + before_save :serialize_state - def set_up - state = {} + def enough_players? + state_data[:player].size > 2 && state_data[:player].size <= 5 + end - state[:total_rounds] = 10 - state[:rounds_played] = 0 - state[:players] = [] - state[:player_hands] = [] - state[:score] = [] - state[:deck] = [] - state[:names] = [] + def has_player?(player_id) + state_data[:player].has_key?(player_id) + end - state[:players].shuffle! + def serialize_state + self.state = state_data.to_yaml + end - save_state state + def can_deal?(player_id) + state_data[:players].first == player_id && state_data[:player].has_key?(player_id) + end + + def needs_to_bid?(current_user) + raise "Not a player in this game" unless has_player? current_user + end + + def waiting_for_players? + state_data[:current_status] == :waiting_for_players && state_data[:player].size < 5 end - + + def add_player(player_id, player_name) + state_data[:players] << player_id + while state_data[:names].include? player_name + player_name += rand(10).to_s + end + state_data[:names] << player_name + state_data[:player][player_id] = player_name + save! + player_name + end + def deal_cards state # need to have at least 3 players to start the game raise 'Must have between 3 and 5 players to start' if state[:players].size < 3 or state[:players].size > 5 @@ -32,7 +53,7 @@ def deal_cards state # person to the 'left' of the dealer bids first state[:bids] = [] state[:waiting_on] = get_next_player state[:dealer], state[:players] - + # deal out number of cards equal to whatever round we are on # to each player # deal cards first to player on 'right' of dealer @@ -40,7 +61,7 @@ def deal_cards state iterate_through_list_with_start_index(state[:players].find_index(state[:waiting_on]), state[:players]) { |player, i| state[:player_hands][i] = state[:deck].slice!(0..(num_cards_per_player-1)) } - + # the next card in the deck is trump # whatever suit is trump is valued higher than non-trump suits # an Ace as trump means there is "no trump" @@ -56,25 +77,25 @@ def deal_cards state # player either bids or plays a card if it's their turn def player_action user_id, user_input=nil - state = load_state - + state = game_state + # return false if it's not the players turn return false if user_id != state[:waiting_on] current_player_index = state[:players].find_index user_id - + # check if we are in bidding or playing a card if !done_bidding? state # player is making a bid bid = user_input.to_i - + # dealer cannot bid the same amount as the number of cards dealt total_bids = state[:bids].select { |bid| !bid.nil? }.inject(:+) num_cards_per_player = state[:total_rounds] - state[:rounds_played] if state[:dealer] == user_id and total_bids + bid == num_cards_per_player return false end - + # record the bid state[:bids][current_player_index] = bid @@ -97,7 +118,7 @@ def player_action user_id, user_input=nil elsif not state[:player_hands][current_player_index].empty? # player is playing a card card = user_input - + # ensure that the card is in their inventory return false unless state[:player_hands][current_player_index].any? { |player_card| player_card == card } @@ -105,7 +126,7 @@ def player_action user_id, user_input=nil state[:first_suit_played] ||= card.suit playable_cards = get_playable_cards(state[:first_suit_played], state[:player_hands][current_player_index]) return false unless playable_cards.include? card - + # actually play the card state[:cards_in_play][current_player_index] = state[:player_hands][current_player_index].delete(card) @@ -119,11 +140,35 @@ def player_action user_id, user_input=nil state[:waiting_on] = get_next_player state[:waiting_on], state[:players] end end - + save_state state return true end + def state_data + current_time = self.updated_at || self.created_at + @state_read_at ||= current_time + if @state_data.nil? || (@state_read_at && current_time > @state_read_at) + if self.state.nil? + self.state = { + current_status: :waiting_for_players, + total_rounds: 10, + rounds_played: 0, + players: nil, + player_hands: nil, + score: nil, + deck: nil, + names: nil + }.to_yaml + end + @state_data = load_state + @state_data[:players] ||= Array.new + @state_data[:names] ||= Array.new + @state_data[:player] ||= Hash.new + end + @state_data + end + # clear the table of cards and calculate who won the trick/game def clear_table current_player_index, state # sleep a bit so the table isn't cleared immediately @@ -189,10 +234,10 @@ def clear_table current_player_index, state end def load_state - return YAML.load(self.state) + return self.state.nil? ? Hash.new : YAML.load(self.state) end - - def save_state state + + def save_state(state = state_data) self.state = state.to_yaml end @@ -207,14 +252,14 @@ def get_highest_card cards, first_suit_played, trump, start_index # only cards that are the same suit as the first card played matter same_suit_cards = cards.select { |c| c.suit == first_suit_played } card_set = trump_cards.empty? ? same_suit_cards : trump_cards - + # now simply get the highest card left return card_set.max #iterate_through_list_with_start_index(start_index, card_set) do |card| # return card if card.value == highest_value #end end - + def get_playable_cards first_suit_played, cards # player can play any card if they are the first to play a card # or if they only have a single card left @@ -248,7 +293,7 @@ def player_size_and_nil_check arr, state # return true or false if we're done bidding # done bidding if there are the same number of valids bids # as there are players in the game - def done_bidding? state + def done_bidding? state = game_state return player_size_and_nil_check(state[:bids], state) end @@ -256,81 +301,3 @@ def all_players_played_a_card? state return player_size_and_nil_check(state[:cards_in_play], state) end end - -class Card - include Comparable - - SUITS = %w{Spades Hearts Diamonds Clubs} - attr_accessor :suit, :value, :playable - - # create a single card - def initialize suit, value - raise 'Invalid card suit' unless SUITS.include? suit - raise 'Invalid card value' unless value.to_i >= 0 and value.to_i < 13 - @suit = suit - @value = value - end - - def value_name - card_names = %w[Two Three Four Five Six Seven Eight Nine Ten Jack Queen King Ace] - return card_names[@value] - end - - def abbreviated_name - %w[2 3 4 5 6 7 8 9 10 J Q K A][@value] - end - - def <=> other - return 1 if other.nil? - return 0 if @value.nil? and other.value.nil? - return 1 if other.value.nil? - return -1 if @value.nil? - @value.to_i <=> other.value.to_i - end - - def == other - return false if other.nil? - return false if (@value.nil? or other.value.nil?) and @value != other.value - return (@value.to_i == other.value.to_i and @suit == other.suit) - end - - def suit_order other - if @suit != other.suit - SUITS.index( @suit ) <=> SUITS.index( other.suit ) - else - @value <=> other.value - end - end - - # creates a standard deck of 52 cards, Ace high - # the '0' represents 2, and '12' is Ace - def self.get_deck - cards = [] - SUITS.each do |suit| - 1..13.times do |i| - cards.push Card.new(suit, i) - end - end - return cards - end -end - -class Player - attr_accessor :name, :inventory - - def initialize name - raise 'Invalid player name' if name.nil? or name.empty? - @name = name - @inventory = [] - end - - def place_bid - raise 'TODO: implement me!' # TODO: implement this - return 0 - end - - # play a card from the inventory - def play_card - raise 'TODO: implement me!' # TODO: implement this - end -end diff --git a/app/views/games/_form.html.erb b/app/views/games/_form.html.erb index 44beb73..4acc0df 100644 --- a/app/views/games/_form.html.erb +++ b/app/views/games/_form.html.erb @@ -15,10 +15,6 @@ <%= f.label :name %>
<%= f.text_field :name %> -
- <%= f.label :state %>
- <%= f.text_area :state %> -
<%= f.submit %>
diff --git a/app/views/games/index.html.erb b/app/views/games/index.html.erb index 10e1c0e..4264aa2 100644 --- a/app/views/games/index.html.erb +++ b/app/views/games/index.html.erb @@ -1,29 +1,40 @@ -

Listing games

+
+

Crush, The Card Game

- - - - - - - - +
+ <%= link_to 'Start a New Game', new_game_path %> +
+ +

+ <% if current_user %> + Your Current User: <%= current_user %> + <% end %> +

+ +

Current Games

- - <% @games.each do |game| %> +
NameState
+ - - - - - + + + + - <% end %> - -
<%= game.name %><%= game.state %><%= link_to('Show', game, data: { no_turbolink: true }) %><%= link_to 'Edit', edit_game_path(game) %><%= link_to 'Destroy', game, method: :delete, data: { confirm: 'Are you sure?' } %>Game NamePlayersRoundJoin
+ -
+ + <% @games.order(:updated_at).each do |game| %> + + <%= game.name %> + <%= game.state_data[:player].size %> + <%= "#{10 - game.state_data[:rounds_played]}" %> + <% label = game.waiting_for_players? ? "Join Game" : "Game Started Already" %> + <% label_class = game.waiting_for_players? ? "btn-success" : "btn-info" %> +
<%= link_to(label, game, data: { no_turbolink: true }) %>
+ + <% end %> + + -<%= link_to 'New Game', new_game_path %> -
-<%= @_current_user %> +
diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb index eda56e3..4b457f3 100644 --- a/app/views/games/show.html.erb +++ b/app/views/games/show.html.erb @@ -1,102 +1,102 @@ <%= stylesheet_link_tag 'style' %> - + - +
- +

+ <%= @game.name %>: <%= @game.has_player?(current_user) ? @game.state_data[:player][current_user] : "not joined yet" %> +

+ +Players: <%= @game.state_data[:players].join(", ") %> +
+Player: <%= @game.state_data[:player].keys.join(", ") %> +
+ +
<%= notice %> <%= @game.state_data[:current_status] %>
+<% if @game.waiting_for_players? && !@game.has_player?(current_user) %> + <%= form_tag(games_add_player_path) do %> + <%= hidden_field_tag(:id, params[:id]) %> +
+ <%= text_field_tag("username", "", placeholder: "Player Name", class: "form-control input-lg") %> + + <%= button_tag("Join game", type: "submit", class: "btn btn-lg btn-primary") %> + +
+ <% end %> +<%= @game.state_data[:names].join(", ") %> +<% end %> + + +
- - -
@@ -142,111 +162,96 @@ function handleCardDrop( event, ui ) { <%= render "board_info" %>

+ <% if @game.has_player?(current_user) %>
- - <% unless @is_playing or @game_started %> -
- <%= form_tag(games_add_player_path) do %> - <%= hidden_field_tag(:id, params[:id]) %> -
-
- <%= text_field_tag("username", "Username", class: "form-control input-lg") %> - - <%= button_tag("Join game", type: "submit", class: "btn btn-lg btn-primary") %> - -
- <% end %> -
-
- <% end %> - - <% if @is_playing and not @game_started %> -
- <%= button_to 'Deal', games_deal_path, method: :post, params: {:id => params[:id]}, class: "btn btn-lg btn-primary" %> -
-
- <% end %> - - <% if @is_playing and not @done_bidding %> -
- <%= form_tag(games_player_action_path) do %> - <%= hidden_field_tag(:id, params[:id]) %> -
- <%= number_field_tag("bid", 0, min: 0, max: 10, class: "form-control input-lg") %> - - <%= button_tag("Bid", type: "submit", class: "btn btn-lg btn-primary") %> - -
- <% end %> -
- <% end %> -
+ <% if @game.can_deal?(current_user) %> + <% if @game.enough_players? %> + <%= button_to 'Deal', games_deal_path, method: :post, params: {:id => params[:id]}, class: "btn btn-lg btn-primary" %> + <% else %> + Waiting on more players before you can deal. + <% end %> + <% elsif @game.needs_to_bid?(current_user) %> + <%= form_tag(games_player_action_path) do %> + <%= hidden_field_tag(:id, params[:id]) %> +
+ <%= number_field_tag("bid", 0, min: 0, max: 10, class: "form-control input-lg") %> + + <%= button_tag("Bid", type: "submit", class: "btn btn-lg btn-primary") %> + +
+ <% end %> + <% else %> + Waiting for more players or the creator to start the game. + <% end %>
-
+ <% end %>
-
- Your Cards -
- <%= render "card_pile" %> -
+
+
+
+ Your Cards +
+ <%= render "card_pile" %>
- +
+
- -
-

You did it!

- -
- + +
+

You did it!

+ +
+
<%= javascript_include_tag "cards.js" %> diff --git a/config/application.rb b/config/application.rb index dfc4a8b..e6029f6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,6 +12,8 @@ class Application < Rails::Application # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + config.autoload_paths += %W(#{config.root}/lib) + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' diff --git a/config/routes.rb b/config/routes.rb index d59edd3..878be0f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ Rails.application.routes.draw do resources :games + # TODO these should be member vars post 'games/add_player' post 'games/deal' post 'games/player_action' diff --git a/lib/card.rb b/lib/card.rb new file mode 100644 index 0000000..df7c328 --- /dev/null +++ b/lib/card.rb @@ -0,0 +1,57 @@ +class Card + include Comparable + + SUITS = %w{Spades Hearts Diamonds Clubs} + attr_accessor :suit, :value, :playable + + # create a single card + def initialize suit, value + raise 'Invalid card suit' unless SUITS.include? suit + raise 'Invalid card value' unless value.to_i >= 0 and value.to_i < 13 + @suit = suit + @value = value + end + + def value_name + card_names = %w[Two Three Four Five Six Seven Eight Nine Ten Jack Queen King Ace] + return card_names[@value] + end + + def abbreviated_name + %w[2 3 4 5 6 7 8 9 10 J Q K A][@value] + end + + def <=> other + return 1 if other.nil? + return 0 if @value.nil? and other.value.nil? + return 1 if other.value.nil? + return -1 if @value.nil? + @value.to_i <=> other.value.to_i + end + + def == other + return false if other.nil? + return false if (@value.nil? or other.value.nil?) and @value != other.value + return (@value.to_i == other.value.to_i and @suit == other.suit) + end + + def suit_order other + if @suit != other.suit + SUITS.index( @suit ) <=> SUITS.index( other.suit ) + else + @value <=> other.value + end + end + + # creates a standard deck of 52 cards, Ace high + # the '0' represents 2, and '12' is Ace + def self.get_deck + cards = [] + SUITS.each do |suit| + 1..13.times do |i| + cards.push Card.new(suit, i) + end + end + return cards + end +end diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..e69de29