うつろぐ

文章

「Slackbotの独自性って何」ってなった人が作ったクソSlackbot

総コン Advent Calendar 2017 - Adventar の12日目の記事です。

大学の研究室のゼミで「PythonでSlackbotを作ろう」みたいな流れになったのでそこで作ったSlackbotについて記事を書きます。

 

 

概念的な話

実装以前の「何を作るか?」みたいな話です。

Slackbotの独自性って何

今思えばこんなにこれについて悩む必要なかったというか、Slackbotの開発を始めるまで「Slackbotが動く」ということ自体が楽しい、みたいなことが想定できていなかったんだけど、当時「Slackbotに出来て他のbot(LINE@、Twitterbotなど)に出来ないことってなんだ???」ということについてはだいぶ悩んでました。

特にSlackは閉じたツールなので、「みんなに使ってもらう」という点についてはとても弱いのです。なのでその閉じた中で何が出来たら嬉しいかをずっと考えてました。

 

公開とか、機能を試してもらうとか

「面白対話botを作ってみんなに使ってもらう」みたいなことだとやっぱりLINE@一強な感じもします。対話の実装はSlackbot並みに簡単っぽいし、公開もTwitterとかにリンクやらを投げるだけだし。一方「一定間隔で何かをするbot」だとTwitterbotでしょうね。

Slackはやはり閉じたツールなので一般公開には向きません。当時はこのことばっかり気になってたのですが、よく考えてみたらSlackにも「使ってもらう」観点でのメリットがありました。

 

Slackbotはチームの一員として発言することになるので、「チームに仲間が一人加わった感」が出ます。これは当時全く意識出来てなかった点です。研究室のSlackに元々いるSlackbotが動いている様子を見ている感じだと、面白い奇抜な機能よりも、AIっぽさを優先するとこの「チームに仲間が一人加わった感」が強くなるのだと思います。

 

Slackの印象

で当時は「一般公開に向かない」ということしか考えてなかったのでここで詰まりそうになったのですが、Slackの特徴として「PCアプリにもスマホアプリにも通知が飛ぶ」というのを意識し始めていていました。これはLINEにも当てはまるのですが、僕の中のSlackに対する印象として

「LINEと比較してPCとスマホのどちらでもよく起動する」

というのがありました。LINEぜんぜん開きませんね。起動するのにエネルギーが要ります。というか僕のLINEに来るインフォメーションが今の所成人式の同窓会関連と心理学(大学の講義)のグループワーク関連しかないのが問題なのですが。


あと、制作物の方向性の決め手になった出来事が一つあって、なんと先日僕はゼミの日程が変わったことを完全に忘れていて結果的にゼミをブッチしてしまった。これはたぶんTrelloの通知が標準でオフになっているのが悪い。間違いない。これがきっかけで

「Slackにある重要事項を通知させるのに別のアプリが必要なのは馬鹿らしい」

という感じになってきました。

結果何になったか

上のようなことを経た結果、

「メモしたチーム内の重要事項を通知してくれる君」

になりました。:tada: 
 

実装

実装(実装)

よくあるサービス未満の何かをつくった

http://iida.nkmr.io/*Proj/Hatena_melTest/index.php

取り敢えず「メモしたチーム内の重要事項を通知してくれる君」の「メモ」の部分をPHPで作りました。これはSlackに紐づいてるものではなく動作サンプルです。CSS全く書いてないのはゆるして

諸々の操作は適当にやることにして、重要な部分だけ。

f:id:Distorted_Unchi:20171212011455p:plain

このようなメモを作ると、通知時刻の2017/12/12 01:15になると
 
f:id:Distorted_Unchi:20171212011820p:plain

このようなリプライが届く、という想定です。ユーザー名をhereにしてあげると全員に通知出来るし、ユーザー名で特定ユーザーに通知出来る。

 

これに追随したSlackbot

紐づいたSlackチームのチャンネルにリプライ通知を送信する、というようなSlackbotをPythonで作成しました。

機能として、

・通知時刻になったらリプライ通知送信(1分ごと)
コマンドライン的なメモの作成(create:ユーザーID,タイトル,説明,通知時刻)
・指定ユーザーページ表示(userpage:user)
・ホーム表示(home)
 
があります。後半二つは若干おまけっぽい。

だいたいのソースコードはこちら。サンプルなので一部アドレスやパスワードは変えてあります。

# -*- coding: utf-8 -*-

from slackbot.bot import Bot
from slackbot.bot import respond_to
from slackbot.bot import listen_to
from slacker import Slacker
import slackbot_settings

from datetime import datetime
import threading

import json
import requests
import re

#------------------------------------------

slack=Slacker(slackbot_settings.API_TOKEN)

#------------------------------------------

def timer_minute():
    resJson=requests.get('http://iida.nkmr.io/*Proj/Hatena_melTest/dumperAPI.php?username=xxxxxx&password=xxxxxx').json()
    for i in range(len(resJson)):
        if format(resJson[i]['due']) == datetime.now().strftime('%Y/%m/%d %H:%M'):
            slack.chat.post_message(
                "melbot_test",
                '@'+format(resJson[i]['author'])+': 「'+format(resJson[i]['title'])+'」の通知時刻になりました: http://iida.nkmr.io/*Proj/Hatena_melTest/edit_form.php?id='+format(resJson[i]['id']),
                as_user=True,
                link_names=True
            )

    timerM=threading.Timer(60.0,timer_minute)
    timerM.start()


#------------------------------------------

@respond_to(r'(create:.*,.*,.*,.*)')
def request_func(message,something):
    pattern=r'create:(.*),(.*),(.*),(.*)'
    searchOB=re.search(pattern,something)
    if searchOB:
        print(searchOB.group(1))
        print(searchOB.group(2))
        print(searchOB.group(3))
        print(searchOB.group(4))
    requests.post(
        'http://iida.nkmr.io/*Proj/Hatena_melTest/submitterAPI.php',
        {
            'author':searchOB.group(1),
            'title':searchOB.group(2),
            'body':searchOB.group(3),
            'due':searchOB.group(4),
            'mode':'new',
            'username':'xxxxxx',
            'password':'xxxxxx'
        }
    )
    message.reply('requestしたよ')

@respond_to(r'(userpage:.*)')
def userpage_func(message,something):
    pattern=r'userpage:(.*)'
    searchOB=re.search(pattern,something)
    if searchOB:
        print(searchOB.group(1))
        message.reply('http://iida.nkmr.io/*Proj/Hatena_melTest/userpage.php?author='+searchOB.group(1))

@respond_to(r'home')
def home_func(message):
    message.reply('http://iida.nkmr.io/*Proj/Hatena_melTest')

#------------------------------------------

def main():
    timerM=threading.Timer(60.0,timer_minute)
    timerM.start()
    bot = Bot()
    bot.run()

if __name__ == "__main__":
   main()

 

最初のブロックはライブラリのインポート。

次のブロックではSlackerの設定をしています。

次のブロックでは、「通知時刻になったらリプライ通知送信」の部分を実装しています。threadingを用いて1分ごとに全てのメモの時刻(due)を参照して、分までが現在時刻と一致したらSlackerのchat.post_messageでリプライを送っています(chat.post_ephemeralが何故か動かなかったので代用)。
chat.post_ephemeralをchat.post_messageで代用する際、link_namesをTrueにしておかないと、「@user」などの部分がハイライトされず、平文になってしまい通知も届かないので注意。こうなる。↓
f:id:Distorted_Unchi:20171212014155p:plain

その次のブロックでは、リプライに対する応答を書いています。@respond_toの引数には正規表現が使えます。また、@respond_toの引数の1部分を()で括るとその値が関数の第二引数somethingに格納されます。
somethingは一つしか取れないようで、例えばrequest_funcでやっているように複数に分けて取りたいときは一度全体を()で括ってsomethingに格納したのち、reライブラリのsearchで分けて格納し直す運びになります。

最後のブロックはテンプレですね

ドメインAPIもつくった

 気付いた方もいるかもしれませんが、今回データの参照や追加の際にpythonからmysqlを叩くのではなく、PHPで自ドメインAPI(もどき?)を作成してrequestsで叩いています。というのも、研究室のサーバーにmysql関連のpythonライブラリが入っておらず、教授に依頼してインストールしていただいても良かったのですが、「APIとかそういうの全然作ったことないな」と思ったので何となくやりました。

データ追加の方はinsert実行するだけなのでロクなこと書いてないので省略して、データをjsonで吐き出す方の解説を簡単にしたいと思います。

<?php
function raw_json_encode($input) {

  return preg_replace_callback(
    '/\\\\u([0-9a-zA-Z]{4})/',
    function ($matches) {
      return mb_convert_encoding(pack('H*',$matches[1]),'UTF-8','UTF-16');
    },
    json_encode($input)
  );

}

header('Access-Control-Allow-Origin:http://iida.nkmr.io');
header("Content-Type: application/json; charset=utf-8");

if($_GET['username']==='xxxxxx' && $_GET['password']==='xxxxxx'){
  $sql="
  select * from table;
  ";

  $mysqli=new mysqli("localhost", "username", "password");
  $mysqli->set_charset("utf8");
  $mysqli->select_db("hoge_db");
  $results=$mysqli->query($sql);

  $data=array();

  while($line=$results->fetch_array(MYSQLI_ASSOC)){
    $data[]=array(
      id=>$line['id'],
      author=>$line['author'],
      title=>$line['title'],
      body=>$line['body'],
      due=>$line['due']
    );
  }

  echo raw_json_encode($data);
  $mysqli->close();
}else{
  header("Location: error.php");
  exit;
}
?>

研究室のサーバーのphpのバージョンがゲロ古いのでPDOではなくmysqliを使っています。

ポイントは2つで、まず1つはheader("Content-Type: application/json; charset=utf-8");です。これで出力結果のフォーマットをjsonに指定しています。これは
PHPを用いて3行でAPIを作る!! | Life Tips
を参考にさせて頂きました

もう1つはjson_encodeで日本語が文字コードに変換されてしまうので、
json_encodeで全角文字が文字化けする - PHPプロ!Q&A掲示板
のraw_json_encodeという関数を利用させていただきました。これによるとPHP5.4以降ならjson_encodeにオプションが適用できるらしいですが、研究室のサーバーのphpのバーzyありがとうございました。

こんな感じに出力される
f:id:Distorted_Unchi:20171212021620p:plain

おしまい。

参考文献
PythonでSlackbotを作る(3) – ビットログ
初心者に捧げる対話システムの作り方 - Qiita
PHPを用いて3行でAPIを作る!! | Life Tips
json_encodeで全角文字が文字化けする - PHPプロ!Q&A掲示板

とにかく自分が作った曲で音ゲーがしたい人の話

総コン Advent Calendar 2017 - Adventar の5日目の記事です。

 

今回は僕の音楽班での主な活動内容についての記事になります。僕とDTM音ゲーについてです。

 

 

今までのことについて

若干P班っぽい内容も入ります。

B1の初めに作った「めっちゃ守る」

僕がB1(大学1年生)の頃の初めのほうに「EP演習」という科目があって、HSPで好きなものを作って発表会をする、といういきなりエキサイティングなイベントがあったのですが、当時からまあまあ太鼓の達人とかやっていて音ゲーが好きだった僕は「ここでいきなり音ゲーを作りたい!」となって、オリジナルの音ゲーを作りました。詳細は

音ゲー×タワーディフェンス「めっちゃ守る(体験版)」について - うつろぐ

に丸投げします。

この時は曲は無料音源を使用する形になっていて、当時から総コン音楽班に所属していたため(曲はまだ1曲も作っていなかった)、やはり「全部自分で作ってやりたい!」という気持ちになりました。

 

音ゲーに向けた楽曲というモチベーション

音楽班での主な活動として、僕は「何らかの音ゲーに収録されている楽曲の作成」を主にやってきました。

soundcloud.com

きいて

より具体的に言うと、「長さが2分前後で、何らかの世界観を持っている曲」を意識して作ってきました。

 

長さについて、これは僕個人の感覚なのかもしれないですが、曲の展開を詰め込む上で音ゲーの2~3分がちょうど間延びしにくい長さだなあと思います。音ゲーの楽曲だと大体

イントロ(サビに類したメロディであることも多い)→Aメロ→(Bメロ)→ブレイク→盛り上がり→サビ→ラストやアウトロ

という流れが多く、やっぱり丁度良く展開が詰まっているなあと思います。

 

世界観については、音ゲーの楽曲にはだいたい

・意味深なタイトル

・ジャケット画像

が付いていて、これが楽曲自体が聞き手に想起させるものを強めたり変えたりします。僕は作曲をしているときに「楽曲単体で深い世界観を出すの難しすぎる~~~」と思ったりするので、このことを逆手に取ってタイトルとジャケットから先に作って、そのタイトルとジャケットが想起させる世界観を深める楽曲を作ったりもしています。

因みにこの「LZH」という曲はジャケットから先に作って、そこからタイトル→曲と後から作りました

soundcloud.com

ジャケットはProcessing(正確に言うとOpenProcessing)で作成しました。

www.openprocessing.org

 

Dynamix公募

自分が作った曲で音ゲーをする方法は実は3つもあります。

・自作曲を自作音ゲーで遊ぶ

・自作曲の創作譜面をする

・自作曲を音ゲーの公募に出して受かる

この中の3つ目を実践した所、落ちました。なんか公募のレベルが前回より上がってるとかだったので当然と言えば当然。

この曲。

soundcloud.com

Dynamixというゲームの公募に出したのですが、実は僕はこのゲームをあまりプレイしたことがなく、あまり「この音ゲーに合う曲」みたいなのが考えられていなかったのがまずいかな~と思います。

取り敢えず当時意識したこととしては

・僕が音ゲーコアを出しても受からないので変な曲を作る

・リズムに特徴を出す

辺りでした。

 

音ゲーの創作譜面

 既存のAC音ゲーには大体有志によるシミュレータや譜面創作ツールが出てたりします。

太鼓の達人:太鼓さん次郎

ZMY's Place 太鼓さん次郎 使い方① ダウンロード

SOUND VOLTEX:K-Shoot MANIA

K-Shoot MANIA (ケーシューマニア)

CROSS×BEATS:

・X Beats Player

XBP Portal Site – クロスビーツシリーズの譜面シミュレータ「X Beats Player」のポータルサイト

・PxB Editor

クロスビーツ風ゲームシミュレーター PxB Editor作った - 游星通信

などなど。

因みにCROSS×BEATSの創作譜面に関しては個人的にはPxB Editorの方が機能が充実していて好きです。どうでもいいけど「PxB Editor」なのか「PxB Map Editor」なのかが謎。

音ゲーをしていると、譜面がきっかけで曲が好きになったりすることもあるのですが、譜面を作っていて曲が好きになったりすることもあります。また、好きな曲に綺麗な譜面が付けれられるととても気持ちいいです。

 

遂に自分の曲で創作譜面した話

先ほど紹介したLZHで、PxB Editorを用いて創作譜面しました。

www.youtube.com

作曲のときにDAWで自分が置いた音が分かっていて、どの音を叩かせたいかが分かっている状態で譜面を作っていたので、全てを支配している状態になりました。なんか神にでもなった気分。

 

音ゲー自作したい

最初に紹介した自作音ゲーのプロトタイプ「めっちゃ守る」は、実は音ゲータワーディフェンスを掛け合わせたようなゲームなのですが、音ゲーの部分は既存の音ゲーと丸被りしているので、新しいルールの音ゲー音ゲーはルールが同じであればすべて同じなので)を作りたいと思うわけですが、どうもPCで操作する音ゲーだと限界がありそう。そこで、「スマホ音ゲーを作りたい!」となるのですが、これは当時も今も若干ハードルと学習コストの高いことなので、なかなか手が出せていません...

そもそも、スマホでゲームを作ろうとするとAPIやバージョンの互換性とかを色々と考慮しないといけないっぽくて、それを知った時に「この世で最もめんどくさいな...」と思ってしまった。だからもういっそゲーム機の開発がしたくなって、「Nintendo Switch開発を進めた方が良いのでは!?!?!?」とか言い出したんだけど、どう考えても敷居高い。

 

簡易プロトタイプ

取り敢えずスマホで軽く動いてくれ~という思いで、OpenProcessingで、僕の脳内で絶賛稼働中の音ゲー「Lotustep Tone」のプロトタイプを作ってみました。これ詳しく説明してると日が暮れるので取り敢えずフィーリングでプレイしてくれ。

www.openprocessing.org

(マルチタップに対応してないのでうんこ)

 

今後のことについて

目標。

譜面から曲を作る

創作譜面をしていると時々「こういう配置したい!」と思うことがあるので、もう譜面から曲作ってみたら面白いんじゃないかなと思ったのでやってみたい。

 

ちゃんと音ゲー作る

ちゃんとスマホ開発する。

 

BOFに参加する

これが最も重要。BMS自体プレイしたことなくて毎年曲と映像しか見てないけど、「自分の曲」を「音ゲーの形式」で「色々な人に見てもらえる」機会なので、絶対にBOFに曲を出したい。

 

いじょう。

SQliteでカラム定義変更(カラム名変更、属性変更など)を行う最短ルート暫定版

大学の講義でSQLite使い始めたんですけど、けっこうたのしいですね。今まで研究室のサーバーにあるMySQLを使ってたんですが、あれみたいにくそでかい管理機構があってそこで色々するんじゃなくて、DBがファイル単位でさらっと管理できるのがたのしいです。ウケます。利便性というよりは感覚的な問題。

SQLiteはまあそんな感じで敷居が低くて、更に色々な言語や環境から扱えるのでステキです。なんかProcessingからも操作できるらしい。ほえ~。

ただ、他のDBでは標準で実装されてる機能がSQLiteには平気でなかったりします。今回のカラム定義変更がその一つです。今回はこれをなんとかしてやります。

 

大まかな流れ

適当な名前で新しいカラム定義を持ったtableを作る(1コマンド)

→元のtableのデータを全部同じ並びで新tableにぶち込む(1コマンド)

→元のtableを消す(1コマンド)

→新tableの名前を元のtableの名前にする(1コマンド)

 

やってみよう

今回この画像のようなtableを使います。

f:id:Distorted_Unchi:20171129234443p:plain

このtableについて、

・idをPRIMARY KEY&AUTOINCREMENTにしたい

・nameをusernameにしたい

という想定でやっていきます

 

適当な名前で新しいカラム定義を持ったtableを作る

先程の要件を満たしたtableを作って、tempとか脳が死んだような名前をつけます。

f:id:Distorted_Unchi:20171129235008j:plain

カラム数が多いtableに対して変更が1つとかだと本当にしんどいです。逆に、複数の変更をまとめて一遍にできるのでそのようなときには楽な方法になります。

 

元のtableのデータを全部同じ並びで新tableにぶち込む

ここが一番知名度の低い操作。insertの引数(?)にselect節をとります。

f:id:Distorted_Unchi:20171129235442j:plain

この構文は

insert into [table](ca1,ca2,ca3) select cb1,cb2,cb3 from [table];

のようにすることもできます。ここでcaとcbの数は同じでなければなりません。

 

元のtableを消す

なんで画像で説明してんだってくらい簡単な操作。

f:id:Distorted_Unchi:20171130000123j:plain

間違えてtempや他のtableをdropしないように注意...

あと消す前にtempにちゃんとデータが移ってるか1回確認したほうがいいかも。

 

新tableの名前を元のtableの名前にする

カラムのrenameは1コマンドでできない癖にtableのrenameは1コマンドでできるのでやります。

f:id:Distorted_Unchi:20171130000407j:plain

これでカラム定義の変更が完了しました。

 

結果

f:id:Distorted_Unchi:20171130001344j:plain

できた。いえ~~~。

 

まとめ

なんかグダグダでしたが、取り敢えず

1.新しいカラム定義を持ったtableをcreateする

2.insert into [table] select * from [table]; などでデータを全部同じ並びでぶっこむ

3.元のtableをdropする

4.alter table [table] rename to 名前で元のtableと同じ名前にする

こんな感じです。しつこいけど2から3に行く前に中身が移ってるか確認したほうがいいかも。以上。

画像が見にくいのは反省ですね...近いうちに何とかします

Processingできれいな歯車を描こう

あんまり難しいこと言わなくても書ける記事のネタができました~。某大学某学科のB1の皆さんはとくに今Processingでいろんな図形描きたいぞ~ってなってることもあるかと思うので良さげでは?と思って勢いで書いています

初級編 適当な歯車をかく

こんなのが描けます
f:id:Distorted_Unchi:20170701004400j:plain
手順としては、まず
f:id:Distorted_Unchi:20170701004537j:plain
このように二つ円を描き中心から2本線を延ばします
そうすると4つ交点ができる
そしてこれを一周分やるとこうなる
f:id:Distorted_Unchi:20170701005000j:plain
以下のように交点を順番に繋いでいけばなんとなく歯車っぽい図形が描けます かんたん
f:id:Distorted_Unchi:20170701005549j:plain
以下「こんなのが描けます」の歯車を描くコードです

float px, py;

void setup() {
  size(400, 400);
  px=width/2.0;
  py=height/2.0;
}

void draw() {
  background(-1);

  stroke(0);
  strokeWeight(1);
  noFill();
  beginShape();
  float div=12;
  for (int i=0; i<div; i++) {
    vertex(qPx(px, 150, 360*(i/div)-360/(div*4)), qPy(py, 150, 360*(i/div)-360/(div*4)));
    vertex(qPx(px, 180, 360*(i/div)-360/(div*4)), qPy(py, 180, 360*(i/div)-360/(div*4)));
    vertex(qPx(px, 180, 360*(i/div)+360/(div*4)), qPy(py, 180, 360*(i/div)+360/(div*4)));
    vertex(qPx(px, 150, 360*(i/div)+360/(div*4)), qPy(py, 150, 360*(i/div)+360/(div*4)));
  }
  endShape(CLOSE);
}

//---------------------------------------
//ある座標から極座標移動(長さ、角度を決めて移動)したときのx,y座標を計算する関数

float qPx(float _x, float _r, float _deg) { //x
  return _x+_r*cos(radians(_deg));
}

float qPy(float _y, float _r, float _deg) { //y
  return _y+_r*sin(radians(_deg));
}

どの辺が適当なのこれ

Googleで「歯車」と検索していちばん最初に出てきた画像がとりあえず
f:id:Distorted_Unchi:20170701011704j:plain
これなんですけど、さっき描いた歯車と見比べて気づいたことはないですか とくに歯のぶぶん

そう

f:id:Distorted_Unchi:20170701011447j:plain
これです これなのです
そもそも上>下だと歯車同士が噛み合わなくないですか 絶対噛み合わない うんち

(ここからは頻繁に「上」とか「上の円」とかそういう表現を使っていきたいと思います)

ちょっと上級編 なんとなく噛み合いそうな(上<下の)歯車を描く

解決法は色々ありそうなんですが僕が思いついたのはこれです
f:id:Distorted_Unchi:20170701012028j:plain
もう一つでかい円を描いて、その円の中心から延ばした線との交点と歯車の下の円の2点とで線をかいて、それと歯車の上の円との交点を上の歯の点にしようってやつです
でこれも一周する
f:id:Distorted_Unchi:20170701012632j:plain
これコードにすればいけるね!って感じなんですが、なんかそう簡単ではなかったっぽいです
さっきの場合は交点はぜんぶ円の中心からの距離が分かれば求められたけど、今回は本当に「円と線分の交点」を求めなくてはならないのです
これ、けっこうしんどいです なので僕は求めない方法を取っていきます
(勿論これが求められる方はあとはvertexすれば描けますぜ) (あとこれ以外にも楽な描き方はある筈なのでほんの一例としてって感じです)

ここからはPGraphicsが大活躍します ゆえにこれはProcessingでしかできない
PGraphicsってなにー?みたいなことはここでは省略させてください かなりの数の資料がある筈なのでこれは僕が説明するより調べたほうがわかりやすいからです

まずさっきのやつの外側の円と歯車の上の円以外をnoStrokeで白黒でPGraphicsに描画します
こんなかんじです PGraphicsのwidth及びheightは歯車の上の円の直径にしています
f:id:Distorted_Unchi:20170701014400p:plain
結論から言ってしまうとこれを上の円でちょん切るだけです これを実現するのがPGraphicsなんだぜということです
mask関数などを駆使してさっきのPGraphicsと同じサイズの円外枠を作ります こんなの
f:id:Distorted_Unchi:20170701014909p:plain
そしてこれを上からかぶせると
f:id:Distorted_Unchi:20170701015200p:plain
歯車の形のマスクができました あとはこれを無地なり画像なりにmaskして使って噛み合いそうな歯車の完成!
さっきと違って上の歯を直線じゃなく円形でちょん切ってますがそこはもう気にしない感じで…

たのしい
f:id:Distorted_Unchi:20170701020148j:plain

以下は上の「ね」の歯車を描くコードです
HagurumaTes.pde(本体)

PImage img;

void setup() {
  size(500, 500);
  img=loadImage("ね.jpg");
}

void draw() {
  background(-1);
  drawHag(width/2.0, height/2.0, 400);
}

fn_drawHag.pde(歯車を描く関数)

void drawHag(float px, float py, float size) {
  pushStyle();
  PGraphics hagBase, hagMask, cirFrameBase, cirFrameMask;
  float r1, r2, r3;
  r1=size/1.2; //下の円の直径
  r2=size; //上の円の直径
  r3=2.2*size/1.2; //外側のでかい円の直径

  cirFrameBase=createGraphics((int)r2, (int)r2, JAVA2D); //円外枠のベース
  cirFrameBase.beginDraw();
  cirFrameBase.noStroke();
  cirFrameBase.background(0);
  cirFrameBase.endDraw();

  cirFrameMask=createGraphics((int)r2, (int)r2, JAVA2D); //円外枠の切り抜き型
  cirFrameMask.beginDraw();
  cirFrameMask.noStroke();
  cirFrameMask.background(-1);
  cirFrameMask.fill(0);
  cirFrameMask.ellipse(r2/2.0, r2/2.0, r2, r2);
  cirFrameMask.endDraw();

  cirFrameBase.mask(cirFrameMask); //ここでcirFrameBaseが円外枠になる

  hagMask=createGraphics((int)r2, (int)r2, JAVA2D); //放射状のアレを描く
  hagMask.beginDraw();
  hagMask.background(0);
  hagMask.fill(-1);
  hagMask.noStroke();
  hagMask.ellipse(r2/2.0, r2/2.0, r1, r1);
  float div=12.0;
  for (int i=0; i<div; i++) {
    hagMask.beginShape();
    hagMask.vertex(qPx(r2/2.0, r3/2.0, 360*(i/div)), qPy(r2/2.0, r3/2.0, 360*(i/div)));
    hagMask.vertex(qPx(r2/2.0, r1/2.0, 360*(i/div)-360/(div*4)), qPy(r2/2.0, r1/2.0, 360*(i/div)-360/(div*4)));
    hagMask.vertex(qPx(r2/2.0, r1/2.0, 360*(i/div)+360/(div*4)), qPy(r2/2.0, r1/2.0, 360*(i/div)+360/(div*4)));
    hagMask.endShape(CLOSE);
  }
  hagMask.image(cirFrameBase, 0, 0); //円外枠を上から重ねる
  hagMask.endDraw(); //歯車のマスクが完成

  img.resize((int)r2, (int)r2);
  hagBase=createGraphics((int)r2, (int)r2, JAVA2D); //歯車のベース(maskする対象)
  hagBase.beginDraw();
  hagBase.image(img, 0, 0); //ね.jpgを描画
  hagBase.endDraw();
  hagBase.mask(hagMask); //歯車の形に切り抜けた!

  imageMode(CENTER);
  image(hagBase, px, py); //指定座標に描画

  popStyle();
}

//-----------------------------------

float qPx(float _x, float _r, float _deg) {
  return _x+_r*cos(radians(_deg));
}

float qPy(float _y, float _r, float _deg) {
  return _y+_r*sin(radians(_deg));
}

おわりに

PGraphicsは最高 最高だよ ありがとう

Google検索した際のデータを、自分のページのサーバーのMySQLに投げるChrome拡張をつくる

動機

ゼミで「サーバーサイドをやっていこう」って感じになって、phpやってMySQLやって、みたいな感じになった。そのときに「何かデータベースを作ってこい」という課題が出たので変なデータベースを作ってやろう、となって、Google検索をした際のキーワード・日時をデータベースにしたら何となく面白そうかな、と思ったのでめちゃくちゃ苦戦しながらやってみました
何せサーバーサイド自体初めてなので酷くグダりました

試行錯誤の大まかな流れ

まあGoogle検索をかけると動作するようなChrome拡張だろうとなる
Chrome拡張の内部には流石にphpは書けないので別phpページで送信しようとなる
Chrome拡張からphpページへhttpリクエストを飛ばそうとしてみる
phpページがhttpなのでhttpsGoogle検索ページからhttpリクエストを送るとだめっぽい
→GETを含めたリダイレクト(Google検索→phpページ→Google検索)で何とかならないかな?となる
Google検索のページの仕様を利用して何とかなる

GETの値でMySQLにデータを投げてGoogle検索ページにリダイレクトするphpページ

まずはここですね。 そこまで説明は要らないと思います。
日時はphpのdateにあるものをつかう
リダイレクトはjsで書く方がすきなのでそうしています

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>SUBMIT</title>
  <meta charset="utf-8"></meta>
</head>

<body>
  <?php
  $mysqli=new mysqli("localhost","username","password");
  $mysqli->set_charset("utf8");
  $mysqli->select_db("user_db");

  $query=$_GET["q"];
  $year=date("Y");
  $month=date("n");
  $day=date("j");
  $hour=date("G");
  $minute=date("i");
  if($query!=""){ //キーワードが空の場合はデータベースに挿入しない
    $sql="INSERT into GoogleQuery_table (query,year,month,day,hour,minute) values ('$query',$year,$month,$day,$hour,$minute)";
  }
  $mysqli->query($sql);

  $close_flag=$mysqli->close();
  if($close_flag){
    if($query!=""){
      //phpのリダイレクトはめんどいのでjsで書いてしまう
      echo "<script>window.location.href='https://www.google.co.jp/search?first=false&q=";
      echo $que;
      echo "';</script>";
    }
  }
   ?>
</body>
</html>

リダイレクト先の「first=false」はリダイレクトの無限ループを防ぐためのフラグのようなものです。(勝手に追加した)
これについては後述します

Chrome拡張

まずmanifestファイルから。これは何も難しいことやってません
example.comはさっきのphpページが置いてあるドメイン名にあたります

{
  "manifest_version": 2,
  "name": "検索ログくん",
  "description": "検索ログをおれのMySQLに送る。自分用。",
  "version": "1.0.0",

  "content_scripts": [{
    "matches": ["https://www.google.co.jp/search*"],
    "js": ["myscript.js"]
  }],

  "permissions": [
    "http://www.google.com/",
    "https://www.google.com/",
    "http://example.com/"
  ],

  "icons": {
    "16": "./がぞう.png",
    "48": "./がぞう.png",
    "128": "./がぞう.png"
  }
}

次にjs。今回の肝です
思いついた瞬間に「あああああ!」となって書いたのでかなり汚いです
submit.phpはさっきのphpページです

var url=window.location.href;
var query;

//qを抜き出してそれを用いてリダイレクト
if((url.indexOf("https://www.google.co.jp/search?q")!=-1) && (url.indexOf("#q=")==-1)){
  //新規タブ初めての検索のとき(first=falseなし、#qなし)
  query=url.substring(url.indexOf("q")+2,url.indexOf("&"));
  window.location.href="http://example.com/submit.php?q="+query;
}else if ((url.indexOf("https://www.google.co.jp/search?first=false")!=-1) && (url.indexOf("#q=")!=-1)) {
  //二回目以降の検索のとき(first=falseあり、#qあり)
  query=url.substr(url.indexOf("#q")+3);
  window.location.href="http://example.com/submit.php?q="+query;
}

何やら奇っ怪な条件のもとでリダイレクトしています。
これを説明するより、Google検索ページの仕様を説明したほうがわかりやすいのでそうします

Chromeで新しいタブからキーワードtestでGoogle検索をするとこんな感じのURLになります。
https://www.google.co.jp/search?q=test&oq=test&aqs=chrome.よくわかんねえ数字と記号の羅列&sourceid=chrome&ie=UTF-8
ここからこのようによくわからん箇所を省いても同じページが出ます
https://www.google.co.jp/search?q=test
更にここからこのように架空のGET変数を追加しても同じページが出ます
https://www.google.co.jp/search?first=false&q=test
で、上の三つのURLのとき新しくtest2で検索すると、URLの末尾に「#q=test2」というのが付加されます
こうなるとq=testがあってもtest2の検索結果を示すようです

…以上の仕様を上手いこと利用してGET込みのURLをフラグとして用い、リダイレクトの無限ループから抜け出すコードが
上のコード(+さっきのphp)になります 説明がへたくそすぎてもう読んでくれとしか言えない

おわりに

今回httpリクエスト辺りを試しているところで「セキュリティ関連むずすぎる」になりました むずすぎる ご唱和ください むずすぎる
やはりローカルでものを作っていたときより遥かに足止めを食らう確率が上がりましたね
ローカルでのプログラミング経験がない人はプログラムの書き方でも足止めを食らうことになるので、稀にいるサーバーサイドから始める人ってめちゃくちゃコーディング嫌いになりそうだなとおもいました

おわり

【雑記】Audacityのステレオミキサー録音がイヤホンを挿してるときだけうまくいかない謎の現象の解決

もうちょっと雑にブログをやっていきたいよね、ということで雑記を書くことに。いや雑はよくないんだけど。気軽にの方が正しい?

 

「作業用BGMが電波に依存するの駄目すぎるな~」と思ってAudacityでステレオミキサー録音して何曲か私的利用の元に音源を取ろう、ってなって前のPCと同じノリでステレオミキサー録音したら、何も録音されず無音だった。「無」が録音された ウケる ウケない

それで解決策を適当に調べていると、まず「そもそもステレオミキサーが無効になってるのでは」になった 早速スタートメニューの検索窓に「サウンド」と入力し(以下たぶんこれを「"サウンド"でサーチ」とか言う)サウンド設定を確認しました(ちなみに今回はWindows 10の場合です)

f:id:Distorted_Unchi:20170205004838j:plain

マジなのかお前

無効になってました

「ステレオミキサー」の上で右クリック→有効で有効にする。

f:id:Distorted_Unchi:20170205005100j:plain

f:id:Distorted_Unchi:20170205005147j:plain

(ここではマイクを無効にしてるけど規定のデバイスにしなければ無効にしなくても何でもいい)

これで録音いけるかな~と思ってもう一度やってみたけど、また無を録音した。そこからは色々試行錯誤した結果、イヤホンを外して録音するとうまくいく謎の現象に遭遇。ここまでにRealtekの周りの設定の存在とかを認識していたので、「これはもしや...?」と思ってある設定をいじったら無事解決しました

そのある設定ですが、結論から言うと

f:id:Distorted_Unchi:20170205010107j:plain

 これ

えーっとこれはどこにあるというと、まず"realtek"とかでサーチして「Realtek HD オーディオマネージャ」を開く

f:id:Distorted_Unchi:20170205010310j:plain

赤で囲んである「デバイス詳細設定」

f:id:Distorted_Unchi:20170205010107j:plain

 (使い回し)

赤丸のところをチェック 自分の場合これで解決しました たぶん「録音デバイス」はこっちじゃなくてもよかったけど、「再生デバイス」が重要なんだと思う

 

 

ちょっと解説・推測

このデバイス詳細設定の「再生デバイス」で上にチェックした場合、サウンド出力を一つのオーディオストリーム(スピーカー)で行います 下は「Realtek HD Audio 2nd output」というオーディオストリームを別に用意してそれを外部デバイス(イヤホンとか)用に割り当てます

僕の場合初期状態では下になっていた。 「じゃあスピーカーとイヤホン両方から音が鳴るんとちゃうんかい」という感じですが、

f:id:Distorted_Unchi:20170205012312j:plain

 こうなってて、イヤホンを挿すとイヤホン用のストリームしか出力してない 自分がいじった感じだと片方しか出力できないっぽい

 

(ここから推測)そうすると、初期状態では「再生デバイス」の設定が下、かつイヤホンを挿したときスピーカーが使われない状態だったわけですが、ここで録音することを考えると、録音の入力としてどちらのオーディオストリームを取るのか選ぶ必要が出てきます(オーディオストリームが二つあるので)

それをピンポイントに指定する設定は見当たらなかったので、どちらかを勝手に選んでると思われるけど、ここで使われていない「スピーカー」を常に選んでいるために無を録音しているんではなかろうか、という結論に至りました

 

この推測の元に「再生デバイス」の設定を上にしてオーディオストリームを「スピーカー」単一にして、それをステレオミキサーで録音させることによって解決しました たぶん正しい たぶんこう

 

なんかもうほんとに自分の文脈で発生した問題への解決メモなので、自分の文脈の文章になってしまってぐちゃぐちゃですが、同様の問題を抱えている方の助けになればなと思います といっても殆ど自分用になってしまった

P演習Ⅱのゲームのランチャとか

うつです。午前5時です いえい 

ア パソコン買えました ドスパラさんのAltair F-13というやつです 「タッチパネル対応とか指紋認証とかいらんから標準的な安いやつくれ」つって色々探してたらたどり着きました 安いしかっこいいしよいです シンプルイズベストなあなたに是非 i5-7200U、SSD256GB/RAM8GBで本体税抜きで9万切るんですこれ まあOfficeとか色々入れてたら税込み13万とかになった

f:id:Distorted_Unchi:20170122042736j:plain

f:id:Distorted_Unchi:20170122042746j:plain

こいつの難点としてはけっこうキーボードが意味不明な配置をしてるところですかね あとは充電中の状態を表示するようなランプがどこにも見当たらない 電源ランプは電源ボタンと一緒になってます(電源ボタンが光る)

f:id:Distorted_Unchi:20170122044321j:plain

キーボード氏です 思ったことを列挙すると

・スペースキーがめっちゃでかい

・左のシフト小さすぎ

・というか左端のキーが全体的に小さい

・¥、ろの位置が謎

こんな感じです 他は快適でした

 

ふつうにAltair F-13のレビューをした

本題に入りますか

1/16にプログラミング演習Ⅱの発表会があって、そこでわたしたちは「WaveHopper」という声を使ったゲームを作り、その中である種のランチャーを導入したのですが、そこで起こったうんちなこととかも含めてメモしておきたいぜ、というのが今回のメインの話です ブログとしてのコンテンツ力がゴミ以下

ゲーム氏はこんな感じです

f:id:Distorted_Unchi:20170122060013g:plain

マイクからの音声入力をフーリエ変換して、Hz帯ごとのdB値を地面のY座標に対応させて地面が動くようになっていて、それと左右キーでの横移動(自力での上下移動はない)で敵をかわすゲームです 実装はProcessing フーリエ変換とか難しそうなこと言いましたが実際はMinimのFFTというクラスがけっこうやってくれます 物理演算はFisica

そんでこのゲームには対戦モードもあって、今は片方が敵(3種類いる)を送ってもう片方が自機を操作する感じになってます 「今は」というのは、当初は双方が敵を送り合いつつ時期を操作する白熱バトルにする予定だったのですが、締め切りがやばかったのでより実装が簡単そうな分業スタイルになりました、というだけです うんち

 

ここからが大事なとこで、「敵を相手に送る」という操作は、最初はこんなふうに「送りたい敵のゲージをクリックしてから送りたい位置をクリックする」というものでした

f:id:Distorted_Unchi:20170122055735g:plain

前述の通り当初は双方が自機を操作しながら敵を送り合うバトルにする予定だったので、この方式だと(とくに上のほうに敵を配置するときに)マウスの移動量、とそれに伴う操作の手間が大きすぎてまともに自機を操作できないのでは、という感じになりました 実際つらかったです 声とキーとマウスの三つの操作がある中でこの手間は大きかったです そうして敵配置ランチャーができました

f:id:Distorted_Unchi:20170122055847g:plain

(何故か自分のところに送ってるのは気にしないでください)

マウスの左ボタンを押すとランチャーが固定されて、そこから送りたい敵のゲージがある方向に少し移動して離すと敵を送ることができます。敵を送るときに円形ゲージの外に出て離すとキャンセルされます このランチャーを採用して、攻守を分業したものがとりあえず発表したときのUIだったんですが、これもいくつか難があって、まずマウスパッド勢にこの小さい操作は難しいかもなぁと思います 発表まではマウス勢の自分は全然気になってなかったのですが、今になってだいぶ難しさがわかってきました ただこのへんは円の大きさを微調整すればよさそうかな~という感じはある

 

もう一つの難点としては、本当に小さいことかもしれないんですが、この発表時の仕様だと「敵を送ったときのフィードバックが『送った敵のゲージが0になる』以外に何もない」、かつ「敵を送った直後にランチャーがマウスの座標に瞬間移動する」ので、「敵を送る直前にランチャーの指定座標がずれて、ずれた位置に送られた感覚」に襲われるのではないか、という懸念があります さっきのランチャーのGIFをよく見てるとわかるかも まあゲームとしてはワイワイやる感じでシリアスなものではないし、どちらかというと慎重にならなくてもいい側である攻撃側だし、ずれたと感じるときのずれの程度も小さいのでこのゲームの本質からすると大した問題ではないのですが、ほかのシリアスなゲームにこのずれがあったらたぶん終わりだろうし、何より「自分の操作が報われていない」感じがして個人的に本当にムカつきます

 

これを解消するのに

f:id:Distorted_Unchi:20170122073514g:plain

こうとかかな、と思ったけどどうなんだろう。フィードバックは良さげだけど、すぐマウスを次の位置に動かしたい人にとっては一時停止するのは違うかもしれない。わからん。解散。

...というのが今回書きたかったことです。ブログにする必要あったんだろうか...

 

あ、今回はプログラムはこっちで公開とかはしません。うんちなので