きょこみのーと

技術に関係ないほうのブログ

Goでlotteryという抽選ライブラリを作った

単なるmath/randのラッパーですが。

もしかしたら他にも似たようなやつあるかもしれませんが、見つからなかったので作りました。

github.com

指定した確率

20%の確率で〜みたいな処理をしたいときです。

lot := lottery.New(rand.New(rand.NewSource(time.Now().UnixNano())))

if lot.Lot(20) {
    // 20%の時の処理
}

抽選リストから1件抽選

以下のような複数の抽選対象から1件だけ抽選するときのinterfaceもあります。

  • A: 10%
  • B: 20%
  • C: 30%
  • D: 40%

Prob() intというインターフェースを実装すればOKです。

type DropItem struct {
    ItemName string
    DropProb int
}

func (d DropItem) Prob() int {
    return d.DropProb
}

var _ lottery.Interface = (*DropItem)(nil)

抽選はこんな感じにやる。

lot := lottery.New(rand.New(rand.NewSource(time.Now().UnixNano())))

// 抽選対象リスト
dropItems := []lottery.Interface{
    DropItem{ItemName: "エリクサ", DropProb: 10},      // 10%
    DropItem{ItemName: "エーテル", DropProb: 20},      // 20%
    DropItem{ItemName: "ポーション", DropProb: 30},    // 30%
    DropItem{ItemName: "ハズレ", DropProb: 40},       // 40%
}

// 抽選
lotIdx := lot.Lots(dropItems...)
if lotIdx == -1 {
    // errorです(抽選リストの指定ミスかと)
}

// 抽選結果のItem
dropItem := dropItems[lotIdx].(DropItem)

以下のテストコードも参考にしていただければと。

lottery/lottery_test.go at 2c0264f14a47bf1be830ae12d2c84a90ce9f1ffa · kyokomi/lottery · GitHub

200万回実行した結果は以下になります。

f:id:kyokomi:20150726150526p:plain

2015/07/26 15:51 追記

@tenntenn さんが「袋の中に球が3種類入っていてそれを順次取り出して行く」ような使い方を見つけてくれたので、紹介させていただきます。

ありがとうございます!

GoConでGoで作った拡張しやすいSlack botについてLTしてきた

拡張しやすいはず・・・

最初はkyokomi/gomaの話にしようかなと思ったけど、LT駆動開発キメたろうと思って、slack bot作ることにした。

LTであまり時間無く、ざくっと概要とデモ見せる感じで終わったので、exampleとか詳細を書こうと思います。

実際に実行するbotのサンプル

tokenの取得

slack botのtokenは以下で取得してください。

f:id:kyokomi:20150621211458p:plain

f:id:kyokomi:20150621211555p:plain

exampleを実行する

<token>は、一つ前の節で取得したもの。環境変数SLACK_BOT_TOKENでも可。

$ go get github.com/kyokomi/slackbot
$ cd $GOPATH/src/github.com/kyokomi/slackbot/example
$ go run main.go -token <token>

これで実行終わり。起動時のログはこんな感じ。

f:id:kyokomi:20150621212112p:plain

Slackで発言してみる。(botが居る部屋で)

f:id:kyokomi:20150621212144p:plain

echoプラグインが動いていることがわかる。

pluginの仕組み

_ importするだけでpluginを追加できているのは、この辺をみればわかる。

init()でplugins.AddPluginを読んでいる箇所

slackbot/echo.go at v1.1 · kyokomi/slackbot · GitHub

func init() {
    plugins.AddPlugin(pluginKey("naruhodoMessage"), NaruhodoMessage{})
}

SlackのMessage受信時に呼び出される処理している箇所

pluginsに突っ込まれたものを順番に呼び出している。

slackbot/plugin.go at v1.1 · kyokomi/slackbot · GitHub

func ExecPlugins(ctx context.Context, message string) {
    for _, p := range plugins {
        ok, m := p.CheckMessage(ctx, message)
        if !ok {
            continue
        }

        next := p.DoAction(ctx, m)
        if !next {
            break
        }
    }
}

実は、とても原始的な実装。

割とウケた「なるほどですぞ」プラグインのソースは以下だけ。(ウケてよかった・・・)

package naruhodo

import (
    "math/rand"
    "strings"
    "time"

    "github.com/kyokomi/slackbot/plugins"
    "golang.org/x/net/context"
)

type pluginKey string

var naruhodoList = []string{
    "なるほどなるほどですぞ!",
    "なるほど!",
    "なるほど?",
    "なーるほど!",
    "それはなるほどですね",
    "なるほど!!",
    "なるほど!!!",
}

var rd = rand.New(rand.NewSource(time.Now().UnixNano()))

func init() {
    plugins.AddPlugin(pluginKey("naruhodoMessage"), NaruhodoMessage{})
}

type NaruhodoMessage struct {
}

func (r NaruhodoMessage) CheckMessage(ctx context.Context, message string) (bool, string) {
    return strings.Index(message, "なるほど") != -1, message
}

func (r NaruhodoMessage) DoAction(ctx context.Context, message string) bool {
    idx := int(rd.Int() % len(naruhodoList))
    plugins.SendMessage(ctx, naruhodoList[idx])
    return false // next ng
}

var _ plugins.BotMessagePlugin = (*NaruhodoMessage)(nil)

ほとんどgenerateしたコードのまま。

  • CheckMessageでなるほどという文字がmessageに含まれていたらtrueを返す
  • CheckMessagetrueを返した時にDoActionが実行される
  • DoActioncontextに突っ込んでいたSendMessageを使ったSlackへ返信する

たったこれだけ。簡単なのでぜひ使ってみてください! そしてプラグイン作ったら報告してくれると嬉しいです!! (特定の環境に依存してないプラグインじゃなければ、PullRequestでもOK)

GWにGo言語で作ったMeetAppというサービスの開発記録

GWに2〜3日くらい本気だして、MeetAppというサービスをリリースしました。

f:id:kyokomi:20150518233235p:plain

フロントエンド&企画をやっていただいた@tejitakさんのブログに大体の概要が書いてありますので、こちらを併せてご覧いただければと。

GWハッカソンでMeetAppという趣味アプリ開発者のためのサービス作りました - TEJI TECH BLOG

自分の方は、Goの構成や使っているライブラリや開発中のTipsなどをまとめようかと思います。

Goの構成

使ったライブラリ

今回の開発過程で作ったライブラリ

kyokomi/goroku

herokuの以下アドオンをx/net/contextベースで利用できるライブラリ

作ったといっても、他のライブラリをラップしてherokuの環境変数を使うようにしているだけですが。

開発時のTipsとか

心がけたこと

  • 汎用性 vs 開発速度(2対8くらいの割合)
  • 読みやすいコード vs 捨てやすい/改修しやすいコード(4対6くらいの割合)
  • パフォーマンス考慮できるならやるけど、少しでも複雑になるなら// TODO:残してサクッとスルーする
  • おれおれフレームワークとか作らない
  • テスト書いたほうが開発が早くなるor画面上で動作確認が難しいテストのみ書くようにした

良かった点

  • mongolab便利だった(管理画面でデータいじったりするの楽)
  • Cloudinary便利だった(画像リサイズとかURLベースでやってくれる)
  • CircleCIで自動herokuデプロイを組んでから開発速度が加速した(最初からやってもよさそう)
  • 開発後半で、リファクタしたりよく使うコードとかを別パッケージにしたりする余裕があった
  • 開発はほぼ2人だったのでコミュニケーションロスもほとんど無く、スピード感良い感じでスムーズに開発できた

おわり

引き続き改善していくつもりですが、x/net/contextベースで開発したため結構使いまわせるようになっているので次はもっと早く開発できそうですので新しく別サービス作るのもアリかなと思ってます。

一緒に作るのも面白かったので、またやりたい。

CircleCI上でMySQLとPostgreSQLを扱ったtestを行う

先日Gunosy.go#12でLTしたkyokomi/gomaですが、 CircleCI上でDBを利用してgo generateとtestの実行を行っています。

github.com

たぶん公式ドキュメント見ればわかると思いますが、 サクッとやりたい人向けにCircleCI上でMySQLPostgreSQLを利用する方法をご紹介しようかと思います。

CircleCIでCREATE DATABASE等のsqlを実行

この辺みたら大体わかると思いますが、普通にsqlファイルをmysqlpsqlコマンドで実行してます。

https://github.com/kyokomi/goma/blob/master/circle.yml#L7

  • $HOME/$CIRCLE_PROJECT_REPONAMEは、CircleCIでgit checkoutされるリポジトリのPATH
  • 今回は例として、sample_dbという名前のDatabaseを作成する
  • data.sqlには、CREATE TABLE文やINSERT文が入っているイメージ

MySQL

/* ddl/mysql/setup.sql */

CREATE DATABASE sample_db;

circle.yamldatabase:に以下を追加します。

# circle.yaml

database:
  post:
    - mysql -u root < $HOME/$CIRCLE_PROJECT_REPONAME/ddl/mysql/setup.sql
    - mysql sample_db -u root < $HOME/$CIRCLE_PROJECT_REPONAME/ddl/mysql/data.sql

PostgreSQL

念のためSUPERUSERとLOGINのROLEを設定してますが不要かもです。

/* ddl/postgres/setup.sql */

CREATE DATABASE sample_db;
CREATE ROLE postgres SUPERUSER;
ALTER ROLE postgres WITH LOGIN;

mysqlと書き方が違うので注意。

# circle.yaml

database:
  post:
    - psql -U postgres -f $HOME/$CIRCLE_PROJECT_REPONAME/ddl/postgres/setup.sql
    - psql -d sample_db -U postgres -f $HOME/$CIRCLE_PROJECT_REPONAME/ddl/postgres/data.sql

CircleCI実行の順番

databaseフェーズは、dependenciesフェーズの後になりますので、databasepostもしくは、testフェーズでDBを使ったtest等を実行しましょう。

kyokomi/gomaの実行時のログですがこんな感じになります。

f:id:kyokomi:20150418193629p:plain

f:id:kyokomi:20150418193636p:plain

おわり

CircleCI困ったらsshして入れるので色々試せるのが便利ですね。(どっかでもいいましたが)

※go generateして生成したファイルもgo testしてるのがポイント。

脱ソシャゲして、Gunosyに転職しました

某ソシャゲ会社を退職して、2015年1月中旬にGunosyに転職しました。

といっても、もう約2ヶ月くらい経過していますね。(今更感ある)

Gunosyに入ったキッカケでもある、Gunosy.go#11を先日開催して一段落ついたのでご報告?も兼ねてブログに書くことにしました。

前職

2年半くらいスマートフォン向けのソーシャルゲームの開発してました。ちゃんとしたエンジニアが一杯いる中で、日々成長してこれたのは前職のおかげだと思ってます。

最初のPJがiOS向けのWebViewのゲームだったので、JavaでサーバーサイドとBackbone.jsとかのフロントエンドをやりました。 1年くらい運用して、Android向けも出すことになって、サーバーサイドまたやるのも味気ないので、Android側やらせてもらって、ついでにCocos2d-xでクロスプラットフォーム対応していったりなど色々自由にやらせてもらいました。

Cocos2d-xは、個人開発でも色々やってこのブログもなんかCocos2d-xブログっぽくなりました。

良かった点は、色々とあって中々辞め時が難しかったです。

  • Qiita:Teamがかなり活発になってて、エンジニアの雰囲気がよくなってた
  • 設計書と呼べるものはないが、ER図は必ず正とする文化ができていた
  • 裁量労働制、福利厚生など充実してた( ^ω^)

退職した細かい理由は色々あったけど重要な点を以下に抜粋。

  • 数年先を考えた時、このままゲーム開発技術を伸ばしていくことへの疑問
  • 今は技術力よりプロジェクトを回す能力を高めるべきでは?思ってしまうような状況(マネージャ不足)
  • 一緒に研鑽したり、尊敬できるようなエンジニアが周りにいなくなった
  • Go書きたい

タイミングとか色々ちょうどいい感じだったのもあったのですが、社内政治的な話になりそうなので黙っておきます。

Gunosyを選んだ理由

  • 冒頭にも書きましたが、Gunosy.goに参加して興味を持った
  • Goをプロダクトで採用していて、今後もGoにする勢いの強さを感じた
  • 技術的なチャレンジを推奨する文化(仮に爆死したとしても)

Gunosy入って感じたこと

とにかく開発者各個人のスキルレベルが高いです。デザイナもiOS書いたり、React.Js書いてたり。 Android開発担当がGoでAPI書いてたり。

iOSAndroidPythonとGoとRuby書いたりインフラやったりする人がいたり(マジすげぇ。。。)

上記のようなメンバーと一緒に仕事できるのと、業務でGo書いているとこんなGoのツールあるといいなーとかライブラリあるといいなーという閃きをそのまま形にできる環境ということもあって、すごく充実してます。

以下、仕事中に閃いてつくってるやつ。

ここまでガッツリGoをプロダクトで採用している会社は、国内では今のところGunosyだけなのかな?と思います。

800万DL!Gunosyと共に成長したいGoエンジニア募集! by Gunosy Wantedly

あと、今後もGunosy.goの運用やらせてもらうことになったので、Goの布教活動とGoの実例収集を行っていければと思いますので宜しくお願いします。

個人活動

Goのライブラリ開発とかはもちろんやっていきますが、 ゲーム開発は引き続き作っていきたいと思いますので、Cocos2d-xのバックエンドにGoで書いてほしいとか、デザインとグラフィックやるからCocos2d-xやってくれとかあればお気軽にお声がけしていただければと!!!

とりあえず、年内に前作ってたローグライクゲームのライト版を作って出したい・・・(今度こそGWに)

一緒に企画からやる人募集中。。。

React.jsのチュートリアルをGoで動かしてみた

React.js入門しました。とりあえず動かしてみたレベル。

(とりあえず触る&ソース読んでよくわからない点とかメモってから、概要とか読む派なので。。。)

チュートリアルのexampleで用意されている言語はRuby, Node.js, Pythonしかありませんが、あえてGoでやります。


Goで起動する

チュートリアルソースコードgit cloneで取得します。

$ git clone https://github.com/reactjs/react-tutorial

プロジェクト直下にmain.goを作成します。(goji使ってます)

$ go get github.com/zenazn/goji

ソースコード

React.jsのチュートリアルをGoで書いてみたhttps://github.com/reac ...

あとは起動するだけ。

$ go run main.go

スクリーンショット 2015-02-01 15.54.26.png

なんかチャットっぽいやつのようだ。

色々触って動きを追って見る

サーバーのログを見るとわかるけど、2秒毎にGET:/comments.jsonが呼ばれている。

スクリーンショット 2015-02-01 15.57.02.png

それっぽいコードがある。

// public/scripts/example.js(126行目)
<CommentBox url="comments.json" pollInterval={2000} />,

このCommentBoxのComponentの実装は以下のようにajaxでGETしてsetStateでCommentBoxのrenderを行っている。

// public/scripts/example.js(29行目)
var CommentBox = React.createClass({
  // 2秒毎に呼ばれるやつ
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  // CommentBox子要素のCommentFormのPostボタンを押した時に呼ばれる
  handleCommentSubmit: function(comment) {
    var comments = this.state.data;
    comments.push(comment);
    this.setState({data: comments}, function() {
      // `setState` accepts a callback. To avoid (improbable) race condition,
      // `we'll send the ajax request right after we optimistically set the new
      // `state.
      $.ajax({
        url: this.props.url,
        dataType: 'json',
        type: 'POST',
        data: comment,
        success: function(data) {
          this.setState({data: data});
        }.bind(this),
        error: function(xhr, status, err) {
          console.error(this.props.url, status, err.toString());
        }.bind(this)
      });
    });
  },
  // 初期呼び出し
  getInitialState: function() {
    return {data: []};
  },
  // TODO: これが2秒毎にajax呼ぶやつのトリガーっぽい?
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  // 表示更新(setStateで呼ばれる)
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

CommentBoxは子要素にCommentListとCommentFormを持っていて、setStatusで渡されたdataでrenderを呼び出している。

また、CommentFormのPostボタンを押したは、親要素からpropsでもらったonCoomentSubmitのfunctionを呼び出して、ajaxでPOST:/comments.jsonを呼び出している。

呼び出し後のrenderの流れは2秒毎に呼んでいる方と同じ。

おわり

とりあえず動かしてみて、その後に概要を読んでReact.jsの良さがわかってきた気がする。

英語が苦手な方は、こちらを読むといいと思います!!!とてもわかりやすいです!

一人React.js Advent Calendar 2014 - Qiita

GoogleComputeEngineのVMインスタンスにerrbitを構築

はじめに

errbitを業務で使っているので、自分用のSandboxがほしくて作ろうかなと思ってやってみたら、 思った以上に苦戦したので備忘録がてら残します。

※herokuでの導入例はよくあるのですが、ローカルとかAWSとかでの記事があまり見当たらず...

苦戦したのは、たぶん自分がRails初心者なのが理由かと思いますが一応

環境について

  • GoogleComputeEngineのVMインスタンスn1-highcpu-2(vCPU 2 個、メモリ 1.8 GB)
  • OSはubuntu 14.04

※ちなみにf1-micro(vCPU 1 個、メモリ 0.6 GB)はメモリ不足でbundle installコケますのでご注意ください。

rubyとかその他必要な物をinstall

色々更新とinstall

$ sudo apt-get update
$ sudo apt-get install -y mongodb
$ sudo apt-get install -y libxml2 libxml2-dev libxslt-dev libcurl4-openssl-dev libzip-dev libssl-dev
$ sudo apt-get install -y libreadline6 libreadline6-dev
$ sudo apt-get install -y g++
$ sudo apt-get install -y make
$ sudo apt-get install -y git

rubyのバージョン管理はrbenvにした。

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source .bash_profile

2.1.0だとlibreadlineのバージョンが上がって型の定義が変更な不具合で詰んだので、2.1.2にしました。

$ rbenv install -v 2.1.2

install ながい。。。

$ rbenv rehash
$ rbenv global 2.1.2

bundlerとかinstall

$ gem install bundler
$ gem install therubyracer

errbit起動

ちなみにinstallしたerrbitのリビジョンはこちら 。更新が活発なのでご注意ください↓

errbit/errbit at d3d7ad54f7b136a804d5cb7c9a118ae35b7e5c7a · GitHub

errbitをgit cloneして手順通り起動。

$ git clone https://github.com/errbit/errbit
$ cd errbit
$ bundle install
$ rake errbit:bootstrap
$ sudo script/rails server -p 80

supervisorで常駐させる

無事起動したけど、常駐してほしいので、雑にsupervisorを導入。

$ sudo apt-get install supervisor
$ sudo vim /etc/supervisor/conf.d/supervisord.conf

supervisord.conf

[program:errbit]
command=sudo /home/kyokomi/.rbenv/shims/ruby /home/kyokomi/errbit/script/rails server -p 80
user=kyokomi
autorestart=true
stdout_logfile=/var/log/supervisor/jobs/errbit.log
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=5
stdout_capture_maxbytes=1MB
redirect_stderr=true

supervisor起動

sudo service supervisor start

おわり

Goでエラー投げまくってる。

f:id:kyokomi:20150128222115p:plain

参考