Goでlotteryという抽選ライブラリを作った
単なるmath/rand
のラッパーですが。
もしかしたら他にも似たようなやつあるかもしれませんが、見つからなかったので作りました。
指定した確率
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万回実行した結果は以下になります。
2015/07/26 15:51 追記
@k_yokomi こんな感じにも使えていいですね!https://t.co/mlA7qfHv5z #golang
— taku ʕ ◔ϖ◔ʔ ==Go (@tenntenn) 2015, 7月 26
@tenntenn さんが「袋の中に球が3種類入っていてそれを順次取り出して行く」ような使い方を見つけてくれたので、紹介させていただきます。
ありがとうございます!
GoConでGoで作った拡張しやすいSlack botについてLTしてきた
拡張しやすいはず・・・
最初はkyokomi/gomaの話にしようかなと思ったけど、LT駆動開発キメたろうと思って、slack bot作ることにした。
LTであまり時間無く、ざくっと概要とデモ見せる感じで終わったので、exampleとか詳細を書こうと思います。
実際に実行するbotのサンプル
tokenの取得
slack botのtokenは以下で取得してください。
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>
これで実行終わり。起動時のログはこんな感じ。
Slackで発言してみる。(botが居る部屋で)
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
を返すCheckMessage
でtrue
を返した時にDoAction
が実行されるDoAction
でcontext
に突っ込んでいたSendMessage
を使ったSlackへ返信する
たったこれだけ。簡単なのでぜひ使ってみてください! そしてプラグイン作ったら報告してくれると嬉しいです!! (特定の環境に依存してないプラグインじゃなければ、PullRequestでもOK)
GWにGo言語で作ったMeetAppというサービスの開発記録
GWに2〜3日くらい本気だして、MeetAppというサービスをリリースしました。
フロントエンド&企画をやっていただいた@tejitakさんのブログに大体の概要が書いてありますので、こちらを併せてご覧いただければと。
GWハッカソンでMeetAppという趣味アプリ開発者のためのサービス作りました - TEJI TECH BLOG
自分の方は、Goの構成や使っているライブラリや開発中のTipsなどをまとめようかと思います。
Goの構成
使ったライブラリ
- guregu/kami : Webフレームワーク(先輩)
- unrolled/render : jsonやhtmlのレンダリング
- kyokomi/goroku : 今回作った(heroku用)
- gotsunami/go-cloudinary : CloudinaryのAPI
- gopkg.in/airbrake/gobrake.v1 : airbrakeのAPI
- gopkg.in/mgo.v2 : mongoDB
- gopkg.in/redis.v2 : Redis
- boj/redistore : redisでsession管理
- huandu/facebook : facebookのGraph API
- ChimeraCoder/anaconda : TwitterのAPI
- microcosm-cc/bluemonday : Markdown変換1(1と2セットで使う)
- russross/blackfriday : Markdown変換2(1と2セットで使う)
今回の開発過程で作ったライブラリ
kyokomi/goroku
herokuの以下アドオンをx/net/contextベースで利用できるライブラリ
- mongolab : 永続データ
- Redis To Go : ログインセッションとか
- Cloudinary : 画像アップロードと画像のリサイズ
- airbrake : エラー通知
作ったといっても、他のライブラリをラップして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の実行を行っています。
たぶん公式ドキュメント見ればわかると思いますが、 サクッとやりたい人向けにCircleCI上でMySQLとPostgreSQLを利用する方法をご紹介しようかと思います。
CircleCIでCREATE DATABASE等のsqlを実行
この辺みたら大体わかると思いますが、普通にsqlファイルをmysqlとpsqlコマンドで実行してます。
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.yaml
のdatabase:
に以下を追加します。
# 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
フェーズの後になりますので、database
のpost
もしくは、test
フェーズでDBを使ったtest等を実行しましょう。
kyokomi/gomaの実行時のログですがこんな感じになります。
おわり
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書いてたり。
iOSとAndroidとPythonとGoとRuby書いたりインフラやったりする人がいたり(マジすげぇ。。。)
上記のようなメンバーと一緒に仕事できるのと、業務でGo書いているとこんなGoのツールあるといいなーとかライブラリあるといいなーという閃きをそのまま形にできる環境ということもあって、すごく充実してます。
以下、仕事中に閃いてつくってるやつ。
- github/kyokomi/goma: domaぽいDatabaseアクセスフレームワーク
- 生クエリとORMの中間がほしい...的な
- github/kyokomi/renkin: GoDocベースのAPIドキュメント生成ツール
ここまでガッツリ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
なんかチャットっぽいやつのようだ。
色々触って動きを追って見る
サーバーのログを見るとわかるけど、2秒毎にGET:/comments.json
が呼ばれている。
それっぽいコードがある。
// 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の良さがわかってきた気がする。
英語が苦手な方は、こちらを読むといいと思います!!!とてもわかりやすいです!
GoogleComputeEngineのVMインスタンスにerrbitを構築
はじめに
errbitを業務で使っているので、自分用のSandboxがほしくて作ろうかなと思ってやってみたら、 思った以上に苦戦したので備忘録がてら残します。
※herokuでの導入例はよくあるのですが、ローカルとかAWSとかでの記事があまり見当たらず...
苦戦したのは、たぶん自分がRails初心者なのが理由かと思いますが一応
環境について
※ちなみに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でエラー投げまくってる。