Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
python知識ゼロからポケモンの名前でしりとりするslackbotを作ったノウハウのすべて
こんなのです
ソースはgithubで公開してますhttps://github.com/wagase/pokeshiri
よかったら「いいね」してください。
環境
OS:windows
言語:python
筆者について
趣味でプログラム書いてるにわか。
htmlとcssとjavascriptくらいはかける。
python知識ゼロ
Twitterフォローされると喜びます
https://twitter.com/wagase
よろしくお願いします
なぜpython?
話題だから
なぜslackbot?
開発経験ゼロだから気軽に作れるのがよかった
開発環境構築
知識ゼロだからまずpythonという言語の仕様を学ぶ
参考にしたサイト
https://www.pythonweb.jp/tutorial/
斜め読みしながらわからないところはググる
pythonをwindowsにインストール
https://www.python.org/
Python 3.6.5にしました
Download for windowsからpython-3.6.5.exeを取得して実行
Add Python to PATHチェックする
コンソールでHello pythonくらいはprintできた
Visual Studio Codeでpythonの設定をする
拡張機能「python」で検索して
・Python
・Python for VSCode
をインストール
pylintがないとかいわれるのでインストールpip install pylint
これでいい感じに開発できるようになった
slackbotの作り方をググる
参考にしたもの
Pythonを使ったSlackBotの作成方法
PythonのslackbotライブラリでSlackボットを作る
↑この記事をみながらpip3 install slackbotを実行
slackbotの登録
https://my.slack.com/services/new/bot
にいって書いてある項目を埋めるだけ
API トークンをコピーしておく
いざ実行
上記の参考サイトを丸パクリしてpython run.py
をしてみる
Appのところにあるbotがアクティブ表示になった!
リプライすると「何言ってんだこいつ」って返ってくる!!
実行できた!
あとはゴリゴリプログラミングする
githubで公開中
https://github.com/wagase/pokeshiri
わからないところはググる
ポケモンのデータはこちらからお借りしました
全ポケモンのJSONデータ
https://github.com/kotofurumiya/pokemon_data/
ありがとうございます
ログ出力で参考にしたもの
機能とか
ポケモンじゃないときは
ンで終わるポケモンはペナルティ
しりとりになってないとペナルティ

slackのシステム的に1対Nのしりとりを想定してるため多少まちがっても
ゲームオーバーにはならない
一度登場したポケモンを言うとペナルティ
困ったときはヒントで答えてない一覧をだせる
知らないポケモンがでたときに詳細表示機能で教えてもらえる
ランキング機能

登場回数順にランキングを表示。
ル攻めすると勝てるのでルチャブルは受け攻め強い
リセット機能

ただのリセット。ゲームリスタートの意味。
リセットするとペナルティと今まで言ったポケモンを忘れる。
リセットしてもランキングは消さない
ログ表示機能
ソース
ソースはgithubで公開してます
https://github.com/wagase/pokeshiri
<追記 date=20180705>
この記事に下記にコピペしているソースは
特定のポケモンやある条件で発生するバグがあります
github上では見つけ次第修正していますがこの記事でも同じように修正するのは記事の趣旨とは異なるため
あえて初期のままにすることにしました
(初学者はこういう書き方をしてしまうとか反面教師にもなるかと思います)
追記>
# -*- coding: utf-8 -*-fromslackbot.botimportrespond_to# @botname: で反応するデコーダfromslackbot.botimportlisten_to# チャネル内発言で反応するデコーダfromslackbot.botimportdefault_reply# 該当する応答がない場合に反応するデコーダfromlibsimportmy_functions# 自作関数の読み込みfromlibsimportlog# 何回呼ばれたかカウントしたいmaincount=0resetcount=0hintcount=0detailcount=0rankingcount=0nomalcount=0errorcount=0notpokecount=0@respond_to(r'.+')defmention_func(message):globalmaincountglobalresetcountglobalhintcountglobaldetailcountglobalrankingcountglobalnomalcountglobalerrorcountglobalnotpokecountmaincount=maincount+1req=message.body['text']log.logger.info("["+str(maincount)+"] :総実行回数【"+str(req)+"】:受け取ったメッセージ")ifreq=="リセット"orreq=="reset":resetcount=resetcount+1my_functions.reset()message.send("リセットしました")elifreq=="log"orreq=="ログ"orreq=="記録":message.send("["+str(maincount)+"] :総実行回数")message.send("["+str(resetcount)+"] :総リセット回数")message.send("["+str(hintcount)+"] :総ヒント回数")message.send("["+str(detailcount)+"] :総詳細表示回数")message.send("["+str(rankingcount)+"] :総ランキング表示回数")message.send("["+str(notpokecount)+"] :総ポケモンじゃなくね?回数")message.send("["+str(nomalcount)+"] :総しりとり成立回数")message.send("["+str(errorcount)+"] :総しりとり不成立回数")elifreq=="ランキング"orreq=="ranking":rankingcount=rankingcount+1message.send(my_functions.remarkRanking())elifreq[:4]=="ヒント|"orreq[:4]=="ヒント|"orreq[:4]=="hint":hintcount=hintcount+1log.logger.info("["+str(hintcount)+"] :ヒント回数")hint=my_functions.hint(req[4:5])message.send(str(hint))elifreq[:3]=="詳細|"orreq[:3]=="詳細|":detailcount=detailcount+1log.logger.info("["+str(detailcount)+"] :詳細表示回数")ifmy_functions.checkExistenceAllPoke(req[3:len(req)]):message.send(my_functions.getpokedetail(req[3:len(req)]))else:message.send("よくわかりませんでした"+req[3:len(req)])else:ifmy_functions.checkExistencePoke(req):my_functions.memoryRemark(req)IsShiritoriOK=True# すでに言ったことがあるかどうかifmy_functions.checkExistencereq(req):IsShiritoriOK=Falsemessage.send(my_functions.countreqstock(req))# しりとりになってるかどうかifnotmy_functions.checkTruelastword(req):IsShiritoriOK=Falsemessage.send(my_functions.forgivelastword(req))ifIsShiritoriOK:nomalcount=nomalcount+1log.logger.info("["+str(nomalcount)+"] :しりとり成立回数")else:errorcount=errorcount+1log.logger.info("["+str(errorcount)+"] :しりとり不成立回数")my_functions.reqstockappend(req)ret=my_functions.shiritori(req)log.logger.info("【"+str(ret)+"】:返答")else:notpokecount=notpokecount+1ret="ポケモンじゃなくね?"log.logger.info("["+str(notpokecount)+"] :ポケモンじゃなくね?回数")message.send(ret)# -*- coding: utf-8 -*-importjsonimportrandomimportcollectionsdefmid(text,s,e):returntext[s-1:s+e-1]defleft(text,e):returntext[:e]defright(text,s):returntext[-s:]# pokemon_data.jsonを読み取ってポケモンの名前だけにするdefgetpokenamelist():dic={}forkeyinPOKEDATA:ifnotkey["no"]indic.keys():dic[key["no"]]=key["name"]returndic# 辞書{'ア':['アーボ','アーボック'....],'イ':['イシツブテ','イワーク'....].....} の形にするが最後にンがつくものは除外defmakekanalistNotnn():kanalist={}foriinrange(1,len(KATAKANA)+1):kanas=[]j=1forkeyinPOKENAMELIST:ifleft(POKENAMELIST[key],1)==mid(KATAKANA,i,1)andright(POKENAMELIST[key],1)!="ン":kanas.append(POKENAMELIST[key])j=j+1kanalist[mid(KATAKANA,i,1)]=kanasreturnkanalist# 辞書{'ア':['アーボ','アーボック'....],'イ':['イシツブテ','イワーク'....].....} の形にするが「ン」で終わるやつを取得defmakekanalistGetnn():kanalist={}foriinrange(1,len(KATAKANA)+1):kanas=[]j=1forkeyinPOKENAMELIST:ifleft(POKENAMELIST[key],1)==mid(KATAKANA,i,1)andright(POKENAMELIST[key],1)=="ン":kanas.append(POKENAMELIST[key])j=j+1kanalist[mid(KATAKANA,i,1)]=kanasreturnkanalist# makekanalistNotnnのリストから指定した文字で始まるポケモンを適当に選ぶdefpokechoice(kana):val=""iflen(stock[kana])==0:iflen(nstock[kana])!=0:val=random.choice(nstock[kana])memoryRemark(val)delnstock(kana,val)val=val+"・・・もう【"+kana+"】から始まるポケモンは答えられないよ。負けました。リセットしてね"ifpenalty!=0:val=val+" ペナルティ合計は("+str(penalty)+"回)でした"else:val=random.choice(stock[kana])memorylastword(val)reqstockappend(val)delstock(kana,val)rest=len(stock[kana])memoryRemark(val)val=val+"・・・【"+kana+"】のこり【"+str(rest)+"】"+"次のことばは【"+getshiri(val)+"】です"returnval# そのポケモンがしりとりで存在するかどうかdefcheckExistencePoke(req):ifreqinPOKENAMELIST.values():returnTrueelse:returnFalse# そのポケモンがそもそも存在するかどうかdefcheckExistenceAllPoke(req):forkeyinPOKEDATA:ifreq==key["name"]:returnTruereturnFalse# しりとりメソッドdefshiritori(req):atama=left(req,1)shiri=getshiri(req)ifshiri=="ン":globalpenaltypenalty=penalty+1return"「ン」で終わるやつはだめだよ ペナルティ("+str(penalty)+"回)"else:ifreqinstock[atama]:delstock(atama,req)returnpokechoice(shiri)# 末尾の文字を調整するdefgetshiri(req):shiri=right(req,1)# ミミッキュ対策ifshiriin"ァィゥェォッャュョヮヵヶ":shiri=shiri.replace("ァ","ア")shiri=shiri.replace("ィ","イ")shiri=shiri.replace("ゥ","ウ")shiri=shiri.replace("ェ","エ")shiri=shiri.replace("ォ","オ")shiri=shiri.replace("ッ","ツ")shiri=shiri.replace("ャ","ヤ")shiri=shiri.replace("ュ","ユ")shiri=shiri.replace("ョ","ヨ")shiri=shiri.replace("ヮ","ワ")shiri=shiri.replace("ヵ","カ")shiri=shiri.replace("ヶ","ケ")# 長音対策ifshiri=="ー":shiri=mid(req,len(req)-1,1)returnshiri# 一度いったやつはストックから消すdefdelstock(kana,val):stock[kana].remove(val)# 一度いったやつはストックから消すdefdelnstock(kana,val):nstock[kana].remove(val)# 一度言われたやつを覚えるdefreqstockappend(req):reqstock.append(req)# 一度言われたことがあるかどうかしらべるdefcheckExistencereq(req):ifreqinreqstock:returnTrueelse:returnFalse# 何回言われてるか調べて返すdefcountreqstock(req):globalpenaltypenalty=penalty+1returnreq+"は【"+str(reqstock.count(req)+1)+"】回目だよ。できれば違うやつ言ってね ペナルティ("+str(penalty)+"回)"# リセットdefreset():globalstockglobalnstockgloballastWordglobalreqstockglobalpenaltypenalty=0lastWord=""stock=makekanalistNotnn()nstock=makekanalistGetnn()reqstock.clear()# ヒントdefhint(req):globalpenaltyifreqinstock:penalty=penalty+1returnstock[req]else:return"カタカナ一文字でお願いします"# 詳細機能defgetpokedetail(req):ret=""forkeyinPOKEDATA:ifkey["name"]==req:ret=ret+str(key)+"\n"returnret# 最後の文字を覚えるdefmemorylastword(req):globallastWordlastWord=getshiri(req)# しりとりになってるか調べるdefcheckTruelastword(req):iflastWord!=left(req,1)andnotlastWord=="":returnFalseelse:returnTrue# しりとりになってないメッセージdefforgivelastword(req):globalpenaltypenalty=penalty+1returnreq+"はしりとりになってないよ。できれば【"+lastWord+"】から始まるやつ言ってほしかったな ペナルティ("+str(penalty)+"回)"# 呼ばれたものを記憶defmemoryRemark(req):remarkstock.append(req)# ランキングカウントdefremarkRanking():ret=""i=0c=collections.Counter(remarkstock)foriteminc.most_common():i=i+1ifi>5:breakret=ret+str(item[0])+" "+str(item[1])+"回"+"\n"returnret# 定数群KATAKANA="アイウエオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモヤユヨラリルレロワヲンヴ"POKEDATA=json.load(open("data/pokemon_data.json","r",encoding="utf-8"))POKENAMELIST=getpokenamelist()# 変数群stock=makekanalistNotnn()nstock=makekanalistGetnn()remarkstock=[]reqstock=[]lastWord=""penalty=0importlogginglogger=logging.getLogger(__name__)_detail_formatting='[%(asctime)s] %(module)s.%(funcName)s %(levelname)s -> %(message)s'logging.basicConfig(level=logging.DEBUG,format=_detail_formatting,# 出力のformatも変えられるfilename="./pokeshiri.log",# logファイルのありか)その他
ニドラン♀とかニドラン♂が入力できない問題
<追記 date=20180705>
slackの仕様なのか♀が「:女性のマーク:」っていう絵文字になるんですけど(困惑)
だれか解決方法知ってたら教えてください(小声)
追記>
<追記 date=20190819>
↑これslack公式に問い合わせたところ♀が勝手に:女性のマーク:になるのは仕様で
設定等で勝手に変わらないようには現状できないとのことでした。
要望は出しておきました。
追記>
おまけ
Amazon linux (EC2 t2.micro)での実行方法 無料
pythonとpipとslackbotをインストール
sudo git clone https://github.com/yyuu/pyenv.git /usr/bin/.pyenvcd /usr/bin/.pyenvpyenv install 3.6.5pyenv global 3.6.5sudo apt-get install python3-pipsudo yum install -y python36u-pippip3 install slackbot上記はpythonにわかが2018/07頃に実行したhistoryです。
正確な情報は自分で調べることをおすすめします。
pokeshiriのモジュールを適当なところにおいて
nohup python -u run.py >out.logでサーバーで実行し続けてくれます。
止めたいときは
ps -C pythonkill 番号でいけます
Herokuで実行する方法 無料
Herokuのアカウントを作る
https://signup.heroku.com/login
HerokuCLIをインストールしてパスを通す
https://devcenter.heroku.com/articles/getting-started-with-python#set-up
パスはC:\Program Files\heroku\bin
ここでいいはず
herokuコマンドでherokuのgitに上げる
heroku loginheroku create {名前}heroku git:clone -a {名前}cd {名前}作業フォルダ(名前つけたやつ)にpokeshiriのモジュールをおいて
git add .git commit -am "init"git push heroku masterこれでpythonが動く環境ができて勝手にデプロイしてくれる
なお外部依存のモジュールは
requirements.txtに書く必要があるのでrootにコミットしておく
slackbot==0.5.3実行するには
heroku run nohup python -u run.py >out.log止めるには
heroku psheroku kill {実行名}でいけました
あとがき
python知識ゼロからポケモンの名前でしりとりするslackbotを作ったノウハウのすべてでした
pythonは本当に学習コストが少ないと思いました
JSONの読み込みとかリストの並び替えとか辞書の扱い方とかググればすぐにサンプルコードがでてきます。
またググればでてくるポケモンデータJSONのすごさにちょっと感動
ポケモンデータJSON作者様にこのうえない謝辞をおくります。
以上
ありがとうございました。
よかったら「いいね」してください。
Twitterフォローされると喜びます
https://twitter.com/wagase
よろしくお願いします
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme






