Upgrade to Pro — share decks privately, control downloads, hide ads and more …

mixi tech note #03

mixi tech note #03

#技術書典8 に出典されたミクシィグループエンジニア有志による技術書です。

<< 目次 >>
1章:BigQuery ML で釣果データを分析
2章:アナログカードゲームの作り方
3章:カードゲームの対戦管理・分析アプリケーションを作った話
4章:Unity で作る競技型イベントの回答システム
5章:Haskell での Web API クライアントの作り方

<< TECH NOTE 一覧 >>
mixi tech note #01
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-01

mixi tech note #02
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-02

mixi tech note #03
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-03

mixi tech note #04
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-04

mixi tech note #05
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-05

mixi tech note #06
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-06

mixi tech note #07
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-07

MIXI TECH NOTE #08
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-08

XFLAG Tech Note Vol.01
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-01

XFLAG Tech Note vol.02
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-02

MIXI ENGINEERS

February 29, 2020
Tweet

More Decks by MIXI ENGINEERS

Other Decks in Technology

Transcript

  1. ·͕͖͑ ຊॻʮmixi tech note #03ʯ͸ɺϛΫγΟάϧʔϓʹॴଐ͢Δ༗ࢤୡʹΑͬͯࣥචɾ੍࡞͞Εͨ ٕज़ॻͰ͢ɻ࣮ࡍͷݱ৔Ͱ࢖ΘΕٕͨज़΍ߟ͑ํɺ·ͨɺݸਓతʹڵຯɾؔ৺ͷ͋Δ෼໺͔Βɺࢥ͍ ࢥ͍ʹࣥච͍ͨ͠·ͨ͠ɻͦͷͨΊɺ֤ষͦΕͧΕͰ׬͍݁ͯ͠Δ಺༰ʹͳ͍ͬͯ·͢ͷͰɺ޷͖ͳ ষ͔Β޷͖ͳॱ൪Ͱָ͓͠Έ͍ͩ͘͞ɻ ·ͨɺຊॻ͸ɺϛΫγΟάϧʔϓʹ͋Δٕज़త஌ݟ΍ΞΠσΞΛੵۃతʹڞ༗ɾެ։͍ͯ͘͜͠ͱ ͰɺੈͷதʹΑΓྑ͍αʔϏε͕ҲΕग़͢͜ͱΛئͬͯץߦ͞Ε͍ͯ·͢ɻܝࡌ͞Ε͍ͯΔ৘ใ͸ɺ

    ࣥචऀࣗ਎ͷ؀ڥͰݕূࣥ͠ච͞Εͨ΋ͷͰ͢ͷͰɺ͝ࢀߟʹ͞ΕΔࡍ͸ɺࣗ͝਎ͷ੹೚Ͱ൑அ͠ ͝׆༻͍ͩ͘͞ɻͳ͓ɺจষදݱʹ͖ͭ·ͯ͠΋ɺࣥචऀࣗ਎ͷݴ༿Ͱ఻͑ͨ͘ɺϑϥϯΫͳදݱͱ ͳ͓ͬͯΓ·͢͜ͱ͝ཧղ͍͚ͨͩΕ͹ͱࢥ͍·͢ɻ σΟϕϩούʔϦϨʔγϣϯζνʔϜҰಉ ˗ຊॻʹؔ͢Δ͓໰͍߹Θͤઌ ɹ https://twitter.com/mixi_engineers ˗ϛΫγΟάϧʔϓʹ͍ͭͯ ɹ https://mixi.co.jp/ ˞ʠϛΫγΟʡ ɺ ʠmixiʡ ɺ ʠmixi ϩΰʡ ͸ɺגࣜձࣾϛΫγΟͷ঎ඪ·ͨ͸ొ࿥঎ඪͰ͢ɻ·ͨɺ֤ ࣾͷձ໊ࣾɺαʔϏεٴͼ੡඼ͷ໊শ͸ɺͦΕͧΕͷॴ༗͢Δ঎ඪ·ͨ͸ొ࿥঎ඪͰ͢ɻ iii
  2. ໨࣍ ·͕͖͑ iii ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1

    1.1 ͸͡Ίʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 BigQuery ML ͱ͸ʁ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ . . . . . . . . . . . . . . . . . . 2 1.4 ऩूͨ͠௼Ռσʔλͷಛ௃ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.5 ௼ՌσʔλΛ BigQuery ML Ͱ෼ੳ . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.6 ͓ΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 9 2.1 ͸͡Ίʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2 ΞφϩάΧʔυήʔϜͱ͸Կ͔ʁ . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 اը͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4 ϓϩτλΠϓΛ࡞Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5 ςετϓϨΠͱվྑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.6 ΧʔυͷσβΠϯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.7 ࢼ࡞ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.8 ྔ࢈ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.10 εΫϦϓτશจ͸ gist ʹͯʂ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.11 ͋ͱ͕͖ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.12 ࢀߟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 21 3.1 spread ʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2 spread ͷٕज़త֓ཁ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.3 my spread ͷσϓϩΠखॱ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 37 v
  3. ໨࣍ 4.1 Πϕϯτʹ͍ͭͯ . . . . . . .

    . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.2 ४උ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.3 ։ൃ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.4 ൃల . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 51 5.1 Լ४උ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.2 ࣮૷: ଟ૬తͳΫϥΠΞϯτ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5.3 ࣮૷: API ͷઃܭΛܕͰ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 5.4 ςετΛهड़͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.5 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 ஶऀ঺հ 69 vi
  4. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ Google ͕ެ։͍ͯ͠ΔػցֶशͷϓϩμΫτΛ࢖ͬͯɺझຯͷ௼ΓͱػցֶशΛ͔͚߹Θͤͨݸ ਓϓϩδΣΫτΛ঺հ͠·͢ɻࠓճ͸ Web

    ্ͰεΫϨΠϓͨ͠௼Ռ*1σʔλ 32511 ݅ʢڕछ 75 छ ྨʣΛ BigQuery ʹอଘ͠ɺBigQuery ML Λ࢖ͬͨ෼ੳΛͯ͠Έ·ͨ͠ɻ 1.1 ͸͡Ίʹ ࢲ͸খ͍͞ࠒ͔Β௼Γ͕޷͖ͰɺΤϯδχΞͱ͍͏͜ͱ΋͋Γීஈ͔Βʮ௼ΓʷςΫϊϩδʔʯͱ ͍ͬͨςʔϚͰԿ͔Ͱ͖ͳ͍͔ߟ͍͑ͯ·͢ɻͨͱ͑͹௼ΓΞϓϦΛ։ൃͨ͠Γɺ৽͍ٕ͠ज़Λֶ Ϳࡍʹ௼ΓΛ୊ࡐʹ͍ͯ͠·͢ɻͦͷҰྫ͕ٕज़ॻయ 7 Ͱ഑෍ͨ͠ɺmixi tech note #2 ͷʮୈ 2 ষɹ௼Γʷػցֶशʹ௅ઓͨ͠࿩ʯͰ͢ɻͪ͜ΒͷهࣄͰ͸ɺػցֶशॳ৺ऀͰ΋ը૾ೝࣝͷϞσ ϧΛ࡞੒Ͱ͖Δ Google Cloud AutoML Vision Λ࢖ͬͨڕͷը૾ೝࣝΛߦ͍·ͨ͠ɻ·ͨʮػցֶ शͷϨΠϠʔͱֶͿϋʔυϧʯʹ͍ͭͯ΋঺հ͠·ͨ͠ɻͪ͜Β͸ແྉͰμ΢ϯϩʔυͰ͖ΔͷͰɺ ڵຯ͕͋Δ͔ͨ͸͝ཡʹͳ͍ͬͯͩ͘͞*2ɻࠓճ΋ Google ͕ެ։͍ͯ͠ΔϓϩμΫτͷ 1 ͭͰ͋Δ BigQuery ML Λ࢖ͬͨऔ૊Λ঺հ͠·͢ɻࠓճͷهࣄ͸ڵຯͷ͋Δ୊ࡐͰ BigQuery ML Λ৮Δ͜ ͱʹΑΓ BigQuery ML ΍ػցֶशʹڵຯΛ࣋ͬͯ΋Β͑ΔΑ͏ͳղઆΛ͍ͯ͠·͢ɻׂѪ͍ͯ͠Δ ػೳ΍ղઆ͕͋ΔͨΊɺຊ֨తʹ BigQuery ML Λ࢝Ί͍ͨํ͸ॳΊʹެࣜͷνϡʔτϦΞϧΛ΍ͬ ͯΈΔͷΛΦεεϝ͠·͢*3ɻ 1.2 BigQuery ML ͱ͸ʁ BigQuery ML ͸ BigQuery ʹอଘ͍ͯ͠ΔσʔλΛ࢖ͬͯػցֶश͕Ͱ͖ΔϓϩμΫτͰɺ Google Cloud Next 2018 Ͱൃද͞Ε·ͨ͠ɻBigQuery*4͸ΠϯϝϞϦɺ BI Τϯδϯͱ AI Platform Λ౥ࡌͨ͠ߴεέʔϥϏϦςΟͰίετޮ཰ʹ༏ΕͨαʔόʔϨεΫϥ΢υσʔλ΢ΣΞϋ΢εͱ͠ ͯެ։͞Ε͍ͯ·͢ɻ *1 ௼Ռɿ ڕ௼Γͷ੒Ռɻ௼Εͨڕͷྔɻ *2 mixi tech note #02ɿ https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-02 *3 BigQuery ML νϡʔτϦΞϧɿ https://cloud.google.com/bigquery-ml/docs/tutorials *4 BigQueryɿ https://cloud.google.com/bigquery 1
  5. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ

    BigQuery ML ͷಛ௃ BigQuery ML ͷಛ௃͸ඪ४ͷ SQL ΫΤϦΛ࢖༻ͯ͠ɺػցֶशϞσϧͷ࡞੒ɾධՁɾ༧ଌͰ͖ Δͷ͕ಛ௃Ͱ͢ɻBigQuery ʹσʔλ͕อଘͯ͋͠Ε͹ɺ͙͢ʹ࣮ߦͰ͖·͢ɻBigQuery ͷσʔλ ΛΤΫεϙʔτ͠ػցֶशͷ؀ڥΛ֎෦Ͱ࡞੒͠ͳ͘ͱ΋ɺطଘͷ SQL πʔϧ΍εΩϧͰ࣮ߦͰ͖ ΔͷͰػցֶशʹৄ͘͠ͳ͍ํͰ΋ؾܰʹѻ͏͜ͱ͕Ͱ͖Δͱظ଴Ͱ͖·͢ɻ // ϩδεςΟοΫճؼͷϞσϧΛ࡞੒͢Δྫ CREATE OR REPLACE MODEL ‘fishing_report.model‘ OPTIONS(model_type=’logistic_reg’) AS SELECT prefecture, IF (COUNT(*) < 20, 0, 1) AS label FROM ‘fishing_report.latest‘ GROUP BY prefecture 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ BigQuery ML Ͱ͸ઢܗճؼɺϩδεςΟοΫճؼɺk ฏۉ๏ͱ͍ͬͨػցֶशϞσϧ͕αϙʔτ ͞Ε͍ͯ·͢*5ɻ͜ΕΒʹՃֶ͑ͯशࡁΈͷ TensorFlow ͷϞσϧ͔Β BigQuery ML ϞσϧΛ࡞੒ ͯ͠ɺBigQuery ML Ͱ༧ଌ͢Δ͜ͱ΋Ͱ͖·͢ɻBigQuery ML ͕αϙʔτ͍ͯ͠ΔػցֶशϞσ ϧͷಛ௃Λ؆୯ʹه͠·ͨ͠ɻ ઢܗճؼ ઢܗճؼ͸ɺ͋Δઆ໌ม਺ͷมԽʹԠͯ͡໨తม਺΋࿈ଓతʹมԽ͢Δؔ܎ੑΛϞσϧԽ͢Δख๏ Ͱ͢ɻͨͱ͑͹ɺച্σʔλΛ༻͍ͯϞσϧΛ࡞੒͠ಛఆͷ೔ͷ঎඼ΞΠςϜͷച্Λ༧ଌ͍ͨ࣌͠ ʹ࢖༻͠·͢ɻϥϕϧ͸࣮਺஋ʢਖ਼ෛͷແݶେ΍ NaN ʹ͢Δ͜ͱ͸Ͱ͖·ͤΜʣͰͳ͚Ε͹ͳΓ· ͤΜɻ ϩδεςΟοΫճؼ ϩδεςΟοΫճؼ͸͋Δࣄ৅͕ى͜Δ֬཰Λى͜Δɾى͜Βͳ͍ͱ͍ͬͨΑ͏ͳɺ໨తม਺Λ 2 ߲ʢ0 ͔ 1 ͔ʣͰ෼ྨ͢Δͱ͖ʹ࢖༻͢Δख๏Ͱ͢ɻͨͱ͑͹ɺ͓٬༷͕ಛఆͷ৆඼Λߪೖ͢Δ͔ɺ ͠ͳ͍͔Λ༧ଌ͍ͨ࣌͠ʹ࢖༻͠·͢ɻ·ͨɺBigQuery ML Ͱ͸ 2 ߲͚ͩͰ͸ͳ͘ɺ3 ߲Ҏ্ͷଟ *5 2020 ೥ 1 ݄ݱࡏ 2
  6. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.4 ऩूͨ͠௼Ռσʔλͷಛ௃ ߲ϩδεςΟοΫճؼ΋αϙʔτ͍ͯ͠·͢ɻ k

    ฏۉ๏ k ฏۉ๏ʢk-means ๏ʣ͸ΫϥελϦϯάΛ͢ΔͨΊͷख๏Ͱ͢ɻΫϥελϦϯάͱ͸༩͑ΒΕͨ σʔλશମ͔ΒɺࣅͨΑ͏ͳ܏޲Λ࣋ͭσʔλͲ͏͠ΛΫϥελͱͯ͠·ͱΊΔख๏ͷ͜ͱΛݴ͍· ͢ɻͨͱ͑͹ɺސ٬ͷߪങߦಈͷ෼ੳΛ͍ͨ͠ͱ͖ʹ࢖༻͠·͢ɻk ฏۉ๏͸ڭࢣͳֶ͠श*6*7ʹ͋ ͨΔͨΊτϨʔχϯά΍ධՁ༻ʹϥϕϧͷࢦఆ΍σʔλͷ෼ׂΛߦ͏ඞཁ͸͋Γ·ͤΜɻ 1.4 ऩूͨ͠௼Ռσʔλͷಛ௃ ࠓճͷ࣮ݧΛ͢Δʹ͋ͨͬͯ Web ʹެ։͞Ε͍ͯΔ௼Ռσʔλɺ߹ܭ 32511 ݅ʢڕछ 75 छྨʣͷ σʔλΛऩूͰ͖·ͨ͠ɻ·ͨ͜ΕΒͷ௼Ռσʔλ͸େ෺ͱݺ͹ΕΔҰఆҎ্ͷେ͖͞ʹୡͨ͠௼Ռ σʔλͰ͋Γɺւͷ཮௼Γ*8ͷσʔλͷΈऩू͍ͯ͠·͢ɻΑͬͯඞવతʹւͷ཮௼ΓͰ௼ΕΔେ͖ ͍ڕͷσʔλͱ͍͏͜ͱʹͳΓ·͢ɻ // σʔλεΩʔϚ - name: ڕछ - size: ڕͷશ௕ʢcmʣ - date: ೥݄೔ 2006೥12݄͔Β2019೥11݄ͷσʔλ͕͋Δ - prefecture: ౎ಓ෎ݝʢւͷແ͍ݝ͸আ͘ͷͰ39౎ಓ෎ݝͱͳͬͨʣ ֤ڕछͷαϯϓϧ਺ͱαΠζͷಛ௃ αϯϓϧ਺͕ଟ͍ڕछ্Ґ 10 ͱαΠζͷ࠷େ஋࠷খ஋͸Լهͷ௨ΓͰ͢ɻ60cm Ͱେ͖͍ͱ͞Ε Δ΋ͷ΍ɺ30cm Ͱେ͖͍ͱ͞ΕͨΓͱڕͷαΠζ͸ڕछʹΑͬͯ͞·͟·Ͱ͢ɻ·ͨڕʹৄ͍͠ํ ͳΒ͓ؾ෇͖͔΋͠Ε·ͤΜ͕ɺγϩΪεͷ࠷খ஋͕ 30cm ͩͬͨΓϚμΠͷ࠷খ஋͕ 50cm ͩͬͨ Γͱɺ࠷খ஋ʹͯ͠͸͔ͳΓେ͖ΊͰ͢ɻࠓճͷσʔλ͸େ෺ͱݺ͹ΕΔσʔλͷΈऩू͍ͯ͠Δͨ Ίɺ࠷খͷ஋΋Ұൠతʹ͸େ͖͍ͱݟͳ͞ΕΔ஋ͱͳ͍ͬͯΔͷ͕ಛ௃Ͱ͢ɻ SELECT name, MAX(size) AS max, MIN(size) AS min, *6 ڭࢣ͋Γֶशɿਖ਼ղͰ͋Δ໨తม਺ʢϥϕϧʣΛඞཁͱ͢Δֶशํ๏ *7 ڭࢣͳֶ͠शɿਖ਼ղͰ͋Δ໨తม਺ʢϥϕϧʣΛඞཁͱ͠ͳֶ͍शํ๏ *8 ཮௼Γ: ԭͳͲਫ্ʹग़ͣւ؛΍઒؛ͳͲ཮্͔ΒڕΛ௼Δ͜ͱɻࠓճͷσʔλ͸ւ௼ΓͷΈͰ͢ 3
  7. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.5 ௼ՌσʔλΛ BigQuery ML

    Ͱ෼ੳ FROM fishing_report.latest WHERE name IN( SELECT name FROM fishing_report.latest GROUP BY name ORDER BY count(name) DESCɹLIMIT 10 ) GROUP BY name ڕछ αϯϓϧ਺ ࠷େ ࠷খ ϚμΠ 3472 95.5 50.0 ϫχΤι 3471 76.0 45.7 Ϛΰν 3101 77.8 50.0 εζΩ 2914 102.9 65.0 ΞΠφϝ 1852 62.2 45.0 ΫϩμΠ 1760 64.5 45.0 ΧϫϋΪ 1737 41.5 30.0 ώϥϝ 1291 103.9 50.0 γϩΪε 1124 37.0 30.0 ϚίΨϨΠ 915 58.0 45.0 1.5 ௼ՌσʔλΛ BigQuery ML Ͱ෼ੳ BigQuery ML Λ࢖ͬͯ௼ՌσʔλΛ෼ੳ͠·͢ɻҎԼͷ୊ࡐʹ͋Θͤͯ BigQuery ML Ͱ࢖༻Ͱ ͖Δख๏ΛҰ௨Γࢼ͠·͢ɻ k-means ͰڕΛΫϥελϦϯάͯ͠ΈΔ σʔλͷಛ௃Ͱઆ໌ͨ͠ͱ͓ΓɺڕछʹΑͬͯେখݺ͹ΕΔαΠζͷج४͸ɺ͞·͟·Ͱ͢ɻ k-means Λ࢖ͬͯڕछΛখɾதɾେͱ෼ྨͯ͠Έ͍ͨͱࢥ͍·͢ɻֶशͰ࢖༻͢ΔσʔλͰ͕͢ɺα ϯϓϧ਺্Ґ 10 ͷத͔ΒখɾதɾେͷϞσϧΛ࢖͍·͢ɻখ͍͞ॱ͔ΒɺγϩΪεɺϚίΨϨΠɺϚ μΠΛબͼ·ͨ͠ɻ ֶश ֶशͰ࢖༻͢Δ SQL ͸ҎԼʹͳΓ·͢ɻ͜ͷΑ͏ʹ BigQuery ML ͷ SQL ͷ஌ࣝͰػցֶ शͷϞσϧ͕࡞੒Ͱ͖Δͷ͕ಛ௃Ͱ͢ɻߏจ͸গͳ͍Ͱ͕͢গ͠ղઆ͠·͢ɻCREATE OR 4
  8. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.5 ௼ՌσʔλΛ BigQuery ML

    Ͱ෼ੳ REPLACE MODEL ͸ػցֶशϞσϧΛ࡞੒͢Δ࣌ͷએݴͰ͢ɻmodel_type Ͱճؼ෼ੳɺ k-means ͱ͍ͬͨػցֶशͷख๏Λࢦఆ͠·͢ɻnum_clusters ͸෼ྨ͍ͨ͠਺Λࢦఆ͠·͢ɻࠓ ճ͸খɾதɾେͱ̏෼ྨʹ͍ͨ͠ͷͰ 3 Λࢦఆ͠·͢ɻk-means ͸ϥϕϧΛࢦఆ͠ͳ͍ڭࢣͳֶ͠श ʹ͋ͨΔͨΊϥϕϧͷࢦఆ͸͠·ͤΜɻ·ͨ BigQuery ML Ͱ͸ iteration ͱݺ͹ΕΔֶशͷ൓෮ճ ਺্͕ݶճ਺ 20 ͱܾ·͓ͬͯΓɺ্ݶճ਺ʹୡ͠ͳͯ͘ BigQuery ML ֶ͕शͷ൓෮Λे෼ͱ൑அ ͨ͠৔߹͸ࣗಈతʹఀࢭ͠·͢ɻ CREATE OR REPLACE MODEL ‘fishing_report.kmeans‘ OPTIONS( model_type = ’kmeans’, num_clusters = 3 ) AS SELECT size AS size, MIN(size) AS min, MAX(size) AS max, AVG(size) AS avg, PERCENTILE_CONT(size, 0.5) OVER() AS median FROM fishing_report.latest WHERE name = ’γϩΪε’ OR name = ’ϚίΨϨΠ’ OR name = ’ϚμΠ’ GROUP BY size ༧ଌ ࡞੒ͨ͠ϞσϧΛ࢖͍ڕछΛ෼ྨ͠·͢ɻ༧ଌʹ͸ ML.PREDICT ͱݺ͹ΕΔએݴΛ͠·͢ɻ ֶशͰ࢖༻ͨ͠γϩΪεɺϚίΨϨΠɺϚμΠ͕ͪΌΜͱ෼ྨ͞Ε͍ͯΔ͔֬ೝ͠·͢ɻ SELECT CENTROID_ID, name FROM ML.PREDICT( MODEL ‘fishing_report.kmeans‘, ( SELECT name, size AS size, MIN(size) AS min, MAX(size) AS max, AVG(size) AS avg, PERCENTILE_CONT(size, 0.5) OVER() AS median FROM 5
  9. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.5 ௼ՌσʔλΛ BigQuery ML

    Ͱ෼ੳ fishing_report.latest WHERE name = ’γϩΪε’ ɹ OR name = ’ϚίΨϨΠ’ OR name = ’ϚμΠ’ GROUP BY size, name ) ) GROUP BY CENTROID_ID, name ORDER BY CENTROID_ID ASC ݁Ռ͸ҎԼͷ௨ΓʹͳΓ·ͨ͠ɻ͓͓ΑͦখɾதɾେͱΫϥελϦϯάͰ͖·ͨ͠ɻখ͕ 3ɺத͕ 2ɺେ͕ 1 ͱಡΈऔΕ·͢ɻϚμΠͷΈதͱݟͳ͞Ε͍ͯͨ΋ͷ͕͋Γ·ͨ͠ɻ͜Ε͸ಉ͡ϚμΠͰ ΋େͱ͞ΕΔ΋ͷͱதͱ͞ΕΔ΋ͷ͕͋ΔͱಡΈऔΕ·͢ɻ ෼ྨ ڕछ 1 ϚμΠ 2 ϚμΠ 2 ϚίΨϨΠ 3 γϩΪε ࣍͸ֶशʹ࢖༻͠ͳ͔ͬͨڕΛ༧ଌͨ݁͠ՌͰ͢ɻචऀͷ௼Γܦݧ্ɺ͓ͦΒ͘খɾதɾେʹͳΔ ͩΖ͏ͳͱࢥ͑ΔڕΛϐοΫΞοϓ͠·ͨ͠ɻػցֶशͷ༧૝ͱൺֱ͕ඞཁͳͷͰචऀͷ༧૝΋௥Ճ ͠·ͨ͠ɻචऀ͕Ͱ͖ΔͳΒػցֶश͠ͳͯ͘΋ྑ͍ͷͰ͸ʁ ͱײ͡Δํ΋͍Βͬ͠ΌΔ͔΋͠Ε ·ͤΜ͕ɺ͜Ε͕ 1000 छɺ10000 छͱͳΔ೉͘͠ͳΓ·͢ɻ ෼ྨ ڕछ චऀͷ༧૝ 1 εζΩ 1 1 ώϥϝ 1 2 ΞΠφϝ 2 2 ώϥϝ 1 2 ΫϩΨγϥΨϨΠ 2 2 ΢αΪΞΠφϝ 2 2 ψϚΨϨΠ 2 2 εζΩ 1 3 Ωϡ΢ηϯ 3 3 Ϛϋθ 3 ͓͓Αͦචऀͷ༧૝͕౰ͨΓ·ͨ͠ɻεζΩ΍ώϥϝ͸ϚμΠͱಉ͡Α͏ʹதͱݟ׳Ε͞ΕΔ΋ͷ ΋͋Γ·ͨ͠ɻ 6
  10. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.5 ௼ՌσʔλΛ BigQuery ML

    Ͱ෼ੳ ઢܗճؼͰڕͷαΠζΛ༧ଌͯ͠ΈΔ ઢܗճؼΛ࢖ͬͯڕͷαΠζΛ༧ଌͰ͖Δ͔ࢼ͠·͢ɻਫ਼౓޲্ΛਤΓ͍ͨͨΊɺࠓճ͸ٶ৓ݝͰ ௼ΕΔεζΩʹର৅ΛߜΓ·ͨ͠ɻ ֶश WHERE Ͱ౎ಓ෎ݝɺαΠζɺ݄ɺ༵೔ΛߜΓֶश͠·͢ɻֶशʹ࢖༻͢Δσʔλ͸೔෇ͷΈͰ ͢ɻσʔλ͕͋ΔҰఆظؒʹִͨΓ͕͋ΔͨΊֶशͰ࢖༻͢Δظ͕ؒ௕Ίͱͳ͍ͬͯ·͢ɻ CREATE OR REPLACE MODEL ‘fishing_report.linear_reg‘ OPTIONS (model_type = ’linear_reg’) AS SELECT size AS label, EXTRACT(MONTH FROM date) as month, EXTRACT(DAYOFWEEK FROM date) as dayofweek, FROM ‘fishing_report.latest‘ WHERE date BETWEEN CAST(’2006-01-01’ AS date) AND CAST(’2017-12-31’ AS date) AND prefecture = ’ٶ৓ݝ’ AND name = ’εζΩ’ ༧ଌ WHERE Ͱ೔෇Λࢦఆ͠ɺֶशʹ͸࢖͍ͬͯͳ͍ผͷσʔλͰ༧ଌ͠·͢ɻ༧ଌ݁Ռͱ࣮ࡍͷ σʔλͱൺֱͰ͖ΔΑ͏ʹςʔϒϧΛ݁߹͠·ͨ͠ɻ WITH miyagi_suzuki AS ( SELECT name, date, size AS label, EXTRACT(MONTH FROM date) as month, EXTRACT(DAYOFWEEK FROM date) as dayofweek FROM ‘fishing_report.latest‘ WHERE prefecture = ’ٶ৓ݝ’ AND name = ’εζΩ’ AND date BETWEEN CAST(’2018-01-01’ AS date) AND CAST(’2019-12-31’ AS date) ) SELECT name, ROUND(predict.size, 2) AS size, actual.date as date, actual.size AS ac_size, 7
  11. ୈ 1 ষ BigQuery ML Ͱ௼ՌσʔλΛ෼ੳ 1.6 ͓ΘΓʹ ABS(ROUND(predict.size -

    actual.size, 2)) AS diff, FROM ( SELECT predicted_label as size, date FROM ml.predict(MODEL ‘fishing_report.linear_reg‘, TABLE miyagi_suzuki) ) AS predict JOIN ( SELECT * FROM fishing_report.latest WHERE prefecture = ’ٶ৓ݝ’ AND name = ’εζΩ’ AND date BETWEEN CAST(’2010-01-01’ AS date) AND CAST(’2019-12-31’ AS date) ) AS actual ON predict.date = actual.date ORDER BY date ্Ґ 10 ݅ͷݕࡧ݁Ռ ڕछ ༧ଌͷαΠζ ೔෇ ࣮ࡍͷαΠζ ༧ଌͱ࣮ࡍͷࠩ෼ εζΩ 71.56 2018-06-17 71.0 0.56 εζΩ 72.98 2018-07-01 82.0 9.02 εζΩ 72.98 2018-07-08 70.0 2.98 εζΩ 72.56 2019-06-04 70.0 2.56 εζΩ 74.41 2019-08-25 78.0 3.59 εζΩ 76.42 2019-08-29 83.0 6.58 ૝૾͍ͯͨ͠ΑΓ΋αΠζͷζϨ͕͋Γ·ͤΜͰͨ͠ɻࠓճ͸σʔλྔ͕গͳ͔ͬͨͨΊ೔෇ͷ༧ ଌͰ͸͏·͍͔͘ͳ͔ͬͨΊɺ݄ͱ༵೔Ͱ༧ଌͯ͠Έ·ͨ͠ɻΑΓଟ͘ͷ೔෇ͷσʔλΛूΊΔ͜ͱ ͸΋ͪΖΜେࣄͰ͕͢ɺ௼Γ͸ि຤΍ॕ೔ͷલޙʹ͢Δ͜ͱ͕ଟ͍ͷͰ༵೔ͰߜΔͷ͸߹ཧతͩͬͨ ͔΋͠Ε·ͤΜɻֶशલʹσʔλΛ֬ೝͨ͠Γɺσʔλ੔ܗΛ͢Δେ੾͕͞Α͘෼͔Γ·ͨ͠ɻ·ͨ ఱؾɺைࣚɺਫԹɺ৔ॴɺ௼Γਓɺ࣌ؒͷλΠϛϯάͱ͍ͬͨ൑அࡐྉͷछྨΛ૿΍ͤ͹ɺ͞Βʹྑ ͍݁Ռ͕ͰΔ͔΋͠Ε·ͤΜɻ͜ͷΑ͏ͳେࡶ೺ͳ༧ଌͱͳΓ·͕ͨ͠ɺͲΜͳσʔλΛετοΫ͠ ͓͖ͯσʔλ੔ܗ͕ඞཁ͔ɺώϯτ͕ݟ͑ͨͷͰྑ͍ษڧͱͳΓ·ͨ͠ɻ 1.6 ͓ΘΓʹ ࠓճ͸ػցֶशʹ࢖༻͢Δαϯϓϧσʔλ͕গͳ͔ͬͨΓɺఱؾɺைࣚɺਫԹͱ͍ͬͨ௼Ռʹؔ ࿈ͦ͠͏ͳσʔλ΋ͳ͔ͬͨͨΊେࡶ೺ͳ༧ଌ͔͠Ͱ͖·ͤΜͰ͕ͨ͠ɺࣗ෼ͷ޷͖ͳ௼Γͱ͍ͬ ͨςʔϚͰීஈઢܗճؼ΍ k-means ͱ͍ͬͨػցֶशͷٕज़ʹ৮ΕΔ͜ͱ͕Ͱ͖وॏͳମݧͱͳΓ ·ͨ͠ɻBigQuery ML ͸ػցֶश͕෼͔Βͳ͍ਓͰ΋ؾܰʹѻ͑ΔΑ͏޻෉͞Ε͍ͯΔͷͱɺࠓճ ͷΑ͏ʹࣗ෼ͰσʔλΛूΊͳͯ͘΋ BigQuery ʹ͸େྔͷαϯϓϧσʔλ͕ެ։͞Ε͍ͯ·͢*9ɻ BigQuery ML ΛػցֶशΛֶͿڭࡐπʔϧͱͯ͠બ୒͢Δͷ΋͓΋͠Ζ͍͔΋͠Ε·ͤΜɻࠓճͷ هࣄͰ BigQuery ML ʹগ͠Ͱ΋ڵຯΛ͍͚࣋ͬͯͨͩͨΒ޾͍Ͱ͢ɻ *9 BigQueryɿ https://cloud.google.com/bigquery/public-data 8
  12. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.1 ͸͡Ίʹ ΞφϩάήʔϜͷ࡞ΓํΛ঺հ͠·͢ɻࢲ͸ 2018 ೥͔ΒถޫҰ੒͞Μ͕݄ 1

    ճͷϖʔεͰ։࠵͢ Δʮදݱಓ৔ʯͷߨٛʹࢀՃ͠·ͨ͠ɻ ͦͷߨٛͰ͸ɺ ʮจֶήʔϜશूʯͱͯ͠ɺจֶΛϞνʔϑʹΞφϩάΧʔυήʔϜΛ࡞Δ͜ͱͰ ͨ͠ɻࢲ͸ɺՆ໨ᕸੴʮ͜͜ΖʯΛબΜͰήʔϜΛ࡞Γ·ͨ͠ɻݖརͷ໰୊Λճආ͢ΔͨΊʹɺஶ࡞ ݖ͕੾Εͨ΋ͷΛ༻͍·ͨ͠ɻ ΧʔυήʔϜͷ࡞Γํʹಛʹܾ·Γ͸͋Γ·ͤΜ͕ɺ͜ͷͱ͖ʹಘͨܦݧΛ·ͱΊ·͢ɻҰͭͷࣄ ྫͱͯ͠ࢀߟʹͳΕ͹޾͍Ͱ͢ɻ ର৅ಡऀ • ΞφϩάΧʔυήʔϜΛ࡞ͬͯΈ͍ͨਓɻ • ΠϥϨͷ୯७࡞ۀΛࣗಈԽ͍ͨ͠ਓɻ 2.2 ΞφϩάΧʔυήʔϜͱ͸Կ͔ʁ ΞφϩάΧʔυήʔϜ͸ɺࢴ΍νοϓͷΑ͏ͳ෺ʹɺҙຯΛ༩͑ͯɺผͷԿ͔ʹݟཱͯ·͢ɻͦͷ ͨΊɺ෺ͱͯ͠ͷଆ໘ͱɺ֓೦ͷଆ໘͕͋Γ·͢ɻ ʮ֓೦ʯͱͯ͠ͷΞφϩάΧʔυήʔϜ ήʔϜ͸͍͋·͍Ͱ͢ɻ͍Ζ͍Ζͳ΋ͷ͕ήʔϜͱͯ͠ଘࡏ͍ͯ͠·͢ɻࠓճͷήʔϜ͸ɺڱ͍ҙ ຯͰ͢ɻ੒ޭͱࣦഊ͕͋ͬͯɺ܁Γฦ͠༡΂Δఔ౓ͷҙຯͰ͢ɻ֓೦͸ɺݴ༿ͱҙຯͷෳ߹෺Ͱ͢ɻ ήʔϜΛઃܭ͢Δͱ͖͸ɺ֓೦Λฒ΂ͨΓɺͭͳ͛ͨΓɺ૊Έ߹ΘͤͨΓ͠·͢ɻৄࡉ͸ޙड़͠·͢ɻ 9
  13. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.3 اը͢Δ ʮ෺ʯͱͯ͠ͷΞφϩάΧʔυήʔϜ લड़ͷ௨ΓɺήʔϜ͸֓೦Λߏ੒ͯ͠࡞ΔͷͰ͕͢ɺ࠷ऴతʹ෺ʹ͠·͢ɻΑ͋͘Δߏ੒͸ɺശɺ આ໌ॻɺΧʔυɺνοϓɺίϚͰ͢ɻΧʔυɺνοϓɺίϚΛͻͬ͘ΔΊͯίϯϙʔωϯτͱݺͼ· ͢ɻήʔϜΛઃܭ͠ςετϓϨΠͨ݁͠ՌΛɺ͜ΕΒͷ෺ʹམͱ͠ࠐΈ·͢ɻ

    ήʔϜͱ͸ ΞφϩάʹݶΒͳ͍ͷͰ͕͢ɺήʔϜͱ͸γεςϜͱϞνʔϑ͔Βੜ·ΕΔΠϯλϥΫγϣϯͰ ͢ɻϓϨΠϠʔ͕ΠϯλϥΫγϣϯΛ௨ͯ͡ಘΒΕΔମݧ͕๛͔ͳ΄ͲɺΑ͍ήʔϜͰ͢ɻ ΠϯλϥΫγϣϯ ΠϯλϥΫγϣϯ͸ɺΠϯλʔʴΞΫγϣϯͰ͢ɻ೔ຊޠ༁Ͱ͸ɺ૬ޓ࡞༻Ͱ͢ɻήʔϜͰ͸ɺ1 ͭͷΞΫγϣϯͰऴΘΓ·ͤΜɻΞΫγϣϯͷ͋ͱʹɺผͷΞΫγϣϯ͕Կ౓͔ى͖ͯɺऩଋ͠· ͢ɻͦͷ࿈ଓੑ͸෺ޠͰ͢ͷͰɺήʔϜ͸෺ޠൃੜ૷ஔͰ͢ɻ༡Ϳͨͼʹ͓΋͠Ζ͍෺ޠ͕ੜΈग़͞ ΕΔΑ͏ʹ஌ܙΛߜΓ·͢ɻ γεςϜ γεςϜ͸ɺΠϯλϥΫγϣϯΛੜΈग़ͨ͢Ίͷ͘͠ΈͰ͢ɻ͘͠Έʹ͸ɺήʔϜΛߏ੒͢Δཁૉ ͱɺϓϨΠϠʔͷखॱɺ݁Ռͷྑ͠ѱؚ͕͠·Ε·͢ɻ࠷ऴతʹ͸ɺओʹઆ໌ॻʹϧʔϧͱͯ͠දݱ ͞Ε·͢ɻ Ϟνʔϑ ϞνʔϑʹΑΓ෺ޠ͕๛͔ʹͳΓ·͢ɻಈ෺ɺ༯ոɺո्ɺւ଑ɺϐϥϛουɺਆɺѱຐɺຐ๏... ͳ ͲͳͲɺ࣮ࡏ͢Δ΋ͷ΍ɺ૝૾ͷ࢈෺ͳͲΛɺήʔϜͷϞνʔϑʹͳΓ·͢ɻϞνʔϑΛ͏·͘ήʔ ϜʹऔΓࠐΉ͜ͱͰɺϧʔϧʹೲಘײ͕ੜ·ΕͨΓɺͦͷੈքʹೖΓ͜Ήָ͠͞΋ੜ·Ε·͢ɻ΋ͪ ΖΜɺϞνʔϑ͕ͳ͍ɺ਺ࣈ΍ه߸ͱγεςϜ͚ͩͰ࡞ΒΕΔήʔϜ΋͋Γ·͢ɻͦͷΑ͏ͳήʔϜ ͸ɺ ʮΞϒετϥΫτήʔϜʯͱݺ͹Ε͍ͯ·͢ɻ 2.3 اը͢Δ ੿࡞ʮ͜͜Ζ -੨य़׉౻ฤ-ʯͷࣄྫΛॱʹॻ͖·͢ɻ ϞνʔϑΛબͿ จֶήʔϜશूͰ͸ɺ·ͣ޷͖ͳจֶΛϞνʔϑʹબͼ·͢ɻબͿࡍʹ͸ɺ·ͣ͸੨ۭจݿ͔Β୳ ͠·͢ɻ੨ۭจݿʹ͋Δখઆ͸͢΂ͯஶ࡞ݖ͕੾Ε͍ͯΔͷͰɺϞνʔϑʹબΜͰ΋ݖརؔ܎ͷ΍΍ ͜͠͞Λආ͚ΒΕ·͢ɻࢲ͸ɺ޷͖ͳ࡞඼ͱ͍͏ͷ͕ࢥ͍͔ͭͳ͔ͬͨͷͰɺߴߍ͔ͩͬͨதֶͩͬ 10
  14. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.4 ϓϩτλΠϓΛ࡞Δ ͔ͨͷڭՊॻʹ͋ͬͨՆ໨ᕸੴͷʮ͜͜ΖʯΛબͼ·ͨ͠ɻ੨ۭจݿͷϥϯΩϯάͷ্Ґʹ͍ͨࣄ ͱɺڭՊॻʹࡌ͍ͬͯͨͷͰ஌໊౓͕ߴ͍ͩΖ͏ͱ͍͏ࢉஈͰͨ͠ɻ࡞඼ࣗମ΋աܹ͕γʔϯ͕͋ͬ ͨͱهԱ͍ͯͨ͠ͷͰɺ ɺܹࢗతͳήʔϜʹͳΓͦ͏ͩͱࢥ͍·ͨ͠ɻ͋ΒͨΊͯʮ͜͜ΖʯΛಡΈ

    ·ͨ͠ɻ ൃ૝͢Δ બΜͩจֶ࡞඼ΛಡΜͰɺࢥ͍෇͘୯ޠΛॻ͖ग़͠·͢ɻࢥ͍ग़ͯ͠ɺಡΈฦͯ͠ɺؾʹͳͬͨ୯ ޠ΍ɺ࿈૝͞ΕΔ୯ޠΛͲΜͲΜॻ͖ग़͠·͢ɻͦ͏΍ͬͯॻ͖ग़ͨ͠ࢴ͸ɺ࡞ۀத͸ৗʹࢀর͠· ͢ɻήʔϜͷ౔୆ʹͳΓ·͢ɻ ϧʔϧΛ࡞Δ όΧʹͳͬͯϧʔϧΛߟ͑·͢ɻਂ͘ߟ͑ͣࢥ͍͖ͭͰॻ͖·͢ɻ͍ΖΜͳόΧʹͳΓ·͢ɻ࠷ޙ ͔͍֮͑ͯ͠ͳ͍όΧɺ੝Γ্͕ͬͨͱ͜Ζ͔͍֮͑ͯ͠ͳ͍όΧɺົʹࡉ͔͍ͱ͜ΖΛ͍֮͑ͯΔ όΧɺͳͲ͍ΖΜͳόΧʹͳͬͯϧʔϧΛߟ͑·͢ɻࢲ͸ɺ࣍ͷΑ͏ͳϧʔϧΛߟ͑·ͨ͠ɻ •ʮ͓৖͞Μͱ݁ࠗͨ͠Βউͪɻ ʯ •ʮࣗࡴͨ͠Βෛ͚ɺࣗࡴʹ௥͍ࠐΜͩΒউͪʯ •ʮಓΛۃΊͨΒউͪʯ •ʮۏ͕͏·͘ͳͬͨΒউͪʯͳͲͰ͢ɻ ͜ΕΒͷϧʔϧΛ΋ͱʹϓϩτλΠϓΛ࡞੒͠·͢ɻ 2.4 ϓϩτλΠϓΛ࡞Δ ඦԁγϣοϓʹച͍ͬͯΔ୯ޠாΛ࢖͍·͢ɻ୯ޠாͷΧʔυʹ࠷௿ݶඞཁͳ৘ใΛͰ͖Δ͚ͩࡶ ʹهೖ͠·͢ɻͦΕΛɺҰਓͰԿ໾ʹ΋ͳͬͯɺ͋Δఔ౓༡΂ΔΑ͏ʹϧʔϧͷৄࡉΛ࡞Γ·͢ɻ༡ ͼͳ͕ΒɺΧʔυΛ௥Ճͨ͠ΓɺഁΓࣺͯͨΓΛؾָʹߦ͍·͢ɻ ݁ࠗͨ͠Βউͪ (͓৖͞Μ͸খѱຐฤ) ؾ·͙Εͳ͓৖͞ΜΛɺ͋ͷख͜ͷखͰؾΛҾ͘ήʔϜͰ͢ɻ͓৖͞Μ͸ɺಓΛ௥ٻ͢Δ࢟ʹऒ͔ ΕͨΓɺࣗ෼ʹ࿀ͯ͘͠ΕΔଶ౓ʹتΜͩΓ͠·͢ɻͦͷ͋ͨΓΛநग़͠ɺ͓৖͞Μ͸৚݅ʹҰக͠ ͨͱ͜ΖʹҠಈ͠·͢ɻҠಈͯ͠΋Β͑ͨΒ 1 ఺Ͱ͢ɻͦͷ৚݅͸ɺ ʮ͓৖͞Μͷؾ࣋ͪʯΧʔυ͕ ࢁࡳʹͳ͓ͬͯΓɺͦͷҰ൪্ͷΧʔυͰ͢ɻ ʮಓ࠷େ਺ʹҠಈʯ ʮ࿀࠷খ਺ʹҠಈʯͳͲ͕͋Γ·͢ɻ ϓϨΠϠ͸ɺ ʮ͓৖͞Μͷؾ࣋ͪʯΛ༧૝ͯ͠ɺ ʮಓ 1ʯ ʮಓ 2ʯ ʮಓ 3ʯ ʮ࿀ 1ʯ ʮ࿀ 2ʯ ʮ࿀ 3ʯΛग़͠· ͢ɻ ʮ͓৖͞Μʯ͕ʮؾ࣋ͪʯʹैͬͯɺҠಈ͠·͢ɻ͓৖͞Μ͕͖ͨϓϨΠϠ͸ϙΠϯτΛಘ·͢ɻ 11
  15. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.5 ςετϓϨΠͱվྑ ࣗࡴͨ͠Βෛ͚ (ࣗࡴճආฤ) ʮࣦ࿀ʯ ʮཪ੾Γʯ

    ʮ่ޚʯ ʮ෕ͷපؾʯ.... ͦΕͧΕͷΧʔυʹ͸ɺࢮʹ͍ͨϙΠϯτͱɺੜ͖͍ͨ ϙΠϯτ͕͋Γ·͢ɻϓϨΠϠ͕ͨͪͤʔͷͰͩͯ͠ɺετϨεΧʔυΛͳΔ΂͘औΒͳ͍Α͏ʹ͢ ΔήʔϜͰ͢ɻετϨεΧʔυ͕ 3 ຕͨ·Δͱɺࣗࡴ͠·͢ɻ 2.5 ςετϓϨΠͱվྑ ʮࣗࡴͨ͠Βෛ͚ʯ͸ҰਓϓϨΠΛ͍ͯͯ͠ɺؾ෼͕௜ΜͩͷͰϘπʹ͠·ͨ͠ɻ ʮ݁ࠗͨ͠Βউͪʯͷํ͸ɺ͏Ε͠͸͔ͣ͠ͳײָ͕͍͡͠ͷͰɺ༑ਓͱډञ԰ͰςετϓϨΠΛ ߦ͍·ͨ͠ɻ࣮ࡍʹ༡ΜͰΈΔͱɺͲ͏ʹ΋ϓϨΠ͕൥ࡶͰ͢ɻࢲ͸໘౗͕͘͞Γ԰Ͱ͢ɻ͓৖͞Μ ͷΧʔυΛҠಈͤͨ͞Γɺ఺਺Λܭࢉͨ͠Γͱ͍͏ͷ͕Ͳ͏ʹ΋ɺ΍Γʹ͘͘ɺ͓΋͠Ζ͞ͱϓϨΠ ίετ͕߹͍·ͤΜͰͨ͠ɻ ଞʹ΋ɺ ʮ͓৖͞Μͷؾ࣋ͪʯΛ༧૝͠Α͏ʹ΋ɺطग़ͷΧʔυ͔͠৘ใ͕ͳ͘ɺॳճ͸ͨΜͳΔ ӡʹͳͬͯ͠·͍ͬͯ·͢ɻ͋·Γʹ΋ӡήʔ͗͢·ͨ͠ɻ΋ͬͱɺӡҎ֎ͷཁૉͰ͋ΔϓϨΠϠͷ ൑அ΍࡞ઓΛήʔϜʹཉ͘͠ͳΓ·ͨ͠ɻ൥ࡶ͞ΛݮΒͭͭ͠ɺӡཁૉΛബΊ͍ͨɺͦ͏͍͏มߋΛ ໛ࡧ͠·ͨ͠ɻ খઆʮ͜͜Ζʯ͸ɺઌੜ͕ʮ࿀͸ࡑѱʯͱݴ͍ͳ͕Βɺ͓৖͞Μͱ݁ࠗͯ͠͠·͏׉౻͕ڵຯਂ͍ ࡞඼Ͱ͢ɻήʔϜΛϞνʔϑͷओ୊ʹۙ෇͚ͭͭઌͷ໰୊Λղܾ͢ΔͨΊʹɺ ʮ͓৖͞ΜʯͷཁૉΛ ݮΒ͠·ͨ͠ɻ ʮ͓৖͞Μͷؾ࣋ͪʯ͸ʮ͜͜Ζʯʹมߋ͠·ͨ͠ɻؾ·͙ΕɺӡͷཁૉΛݮΒͯ͠ ૬खͷʮ͜͜ΖʯΛಡΉήʔϜʹํ޲స׵͠·ͨ͠ɻ खॱΛɺϓϨΠϠͲ͏͠ͷۦ͚Ҿ͖ΛੜΈग़͢Α͏ʹมߋ͠·ͨ͠ɻ ʮ͜͜ΖʯΧʔυ͸਌͚͕ͩ ݟΔΑ͏ʹ͠·͢ɻͦ͏͢Δͱɺ·ͣ਌͕༗རʹͳΓ·͢ɻ͔͠͠ɺ਌͔ΒΧʔυΛग़͢ͷͰʮ͜͜ Ζʯ͕ಡ·Εͯ͠·͍ͦ͏ʹͳΓ·͢ɻ਌͕ग़͢Χʔυͱද৘΍ଶ౓Λ૯߹తʹ؍࡯͢Δ͜ͱͰɺϓ ϨΠϠʔͲ͏͠ͷಡΈ߹͍͕ੜ·Εͦ͏ͩͱԾઆΛཱͯ·ͨ͠ɻ ࠶౓ςετϓϨΠΛͨ͠ͱ͜Ζɺ͔ͳΓૂ͍௨ΓͷΑ͍ײ͡ͷήʔϜʹͳΓ·ͨ͠ɻγϯϓϧͰ͢ ͕ϧʔϧ͸͜ΕͰ֬ఆͱͯ͠ɺ ʮ͜͜ΖʯΧʔυͷऔࣺબ୒Λߦ͍·ͨ͠ɻ ήʔϜϚʔέοτ 2018 ळʹग़ͨ͢Ίʹ͸కΊ੾Γ΋ۙ͘ɺ3 ਓઐ༻ͷήʔϜͱ͠·ͨ͠ɻ4 ਓͰ ΋༡΂ͦ͏ͳϧʔϧͰ͕͢ɺ3 ਓͰ༡Ϳͷ͕ϕετͰ͋Δ͜ͱͱɺήʔϜͷߏ଄্Χʔυຕ਺ͷௐ੔ ͕ඞཁͳͨΊɺ࣌ؒతʹ΍Γ͖Εͳ͍ͱ൑அ͠·ͨ͠ɻ 2.6 ΧʔυͷσβΠϯ खॻ͖ͷ୯ޠாͰ༡ͼͭͭ΋ɺϦϦʔε༻ͷΧʔυͷσβΠϯ΋ਐΊ·͢ɻΧʔυͷσβΠϯʹ Αͬͯ༡ͼ΍͢͞΋มΘΔͷͰɺࢼ࡞Λ܁Γฦ͠·͢ɻΧʔυ͸ 30 ຕͱ਺͕ଟ͍ͷͰɺগ͠ϨΠΞ ΢τΛมߋ͢Δ͚ͩͰ΋ɺ30 ճಉ͜͡ͱΛ܁Γฦ͢͜ͱʹͳΓ·͢ɻͦ͜ͰɺΠϥετϨʔλͷε ΫϦϓτΛ࡞੒͠·ͨ͠ɻεΫϦϓτʹ͍ͭͯ͸ޙड़͠·͢ɻ 12
  16. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.7 ࢼ࡞ 2.7 ࢼ࡞ ྔ࢈ͷલʹɺσβΠϯΛ֬ೝ͢Δ্Ͱ΋࣮ࡍʹҹ࡮Λߦ͍·͢ɻ౰ॳ͸ɺϓϦϯλ͍࣋ͬͯͳ͍ͷ ͰɺίϯϏχͰΧϥʔҹ࡮Λߦ͍·ͨ͠ɻҹ࡮ͨ͠ࢴΛΧολʔͰ੾Γ཭͠ɺͷΓͰްࢴʹషΓ·͠

    ͨɻ͔ͳΓखؒͷ͔͔Δ࡞ۀͰ͢ɻͦΜͳख͕͔͔ؒΒͣɺֹۚతʹ΋ίϯϏχͷΧϥʔҹ࡮ఔ౓ͷ ํ๏͕͋Γ·ͨ͠ɻϥΠΦϯ໊ࢗͷʮҟछ໘෇͚ʯͰ͢ɻ ϥΠΦϯ໊ࢗͷʮҟछ໘෇͚ʯ ໊ࢗҹ࡮ͷձࣾ͸ଟʑ͋Γ·͕͢ɺ஌ΔݶΓʮҟछ໘෇͚ʯαʔϏε͕͋Δͷ͸͚ͩ͜͜Ͱ͢ɻ௨ ৗɺ໊ࢗҹ࡮͸ಉ͡σβΠϯΛ 100 ຕͰ 1 ηοτͰ͕͢ɺ ʮҟछ໘෇͚ʯ͸ 20 छྨ·ͰͷσβΠϯΛ 1 ηοτͷ஫จͰ࡞Ε·͢ɻ͞Βʹɺখϩοτͷ 20 ຕηοτͷ৔߹ 460 ԁͰ͢ɻ20 ຕҎ಺ͷήʔϜ Ͱ͋Ε͹ʮҟछ໘෇͚ʯΛར༻͢Δͱɺ460 ԁͰࢼ࡞͕࡞Ε·͢ɻ 2.8 ྔ࢈ ΧʔυͷσβΠϯ͕Ͱ͖ͨΒҹ࡮ձࣾʹೖߘ͠·͢ɻҹ࡮ձࣾ͝ͱʹઐ༻ͷΠϥετϨʔλͷςϯ ϓϨʔτ͕͋Γ·͢ɻͦΕʹ౰ͯ͸Ίͯೖߘ͢Δ͜ͱʹͳΓ·͢ɻ ॳΊͯͷΧʔυҹ࡮ ࠷ॳʹʮ͜͜Ζ-੨य़׉౻ฤ-ʯΛྔ࢈ͨ͠ͱ͖͸ɺᤈҹಊͱ͍͏ҹ࡮ձࣾͰҹ࡮͠·ͨ͠ɻᤈҹಊ ͸ɺΧʔυήʔϜͷҹ࡮Ͱ͸࿝ฮͱݴΘΕ͍ͯ·͢ɻ ௨ৗͷίʔεͰɺ100 ݸ࡞Γ·ͨ͠ɻ͜ͷίʔεͰ͸ɺ100 ݸ͕࠷খϩοτͳͷͰҰ൪ׂߴͰ͢ɻ ҹ࡮ؔ܎͸νϥγͳͲͰ΋ͦ͏Ͱ͕͢ɺখϩοτ͸ׂߴͰ͢ɻͨ͘͞Μ࡞Δ΄ͲʹɺΧʔυ 1 ຕ͋ͨ Γͷ஋ஈɺശ 1 ݸͷ஋ஈ͕҆͘ͳΓ·͢ɻ ࣮͸ɺᤈҹಊʹ͸௨ৗίʔεͷ΄͔ʹɺ ʮখϩοτԠԉҹ࡮ύοΫʯ͕͋Γ·͢ɻ͜Ε͸ɺԽহശɺ Χʔυͷηοτ͕ѹ౗తʹ҆͘࡞Ε·͢ɻ ͔͠͠ɺ౰࣌͸ॳΊͯͷҹ࡮ͱ͍͏͜ͱͰڵฃ͍ͯͨ͠ͷͰɺ௨ৗͷίʔεͰൃ஫͠·ͨ͠ɻ͔͠ ΋Φϓγϣϯ΋ͨ͘͞Μ෇͚·ͨ͠ɻ߹ܭɺ໿ 8 ສԁ͔͔Γ·ͨ͠ɻͦͷ࣌ͷɺΦϓγϣϯΛ঺հ͠ ·͢ɻ஋ஈ͸౰࣌ͷݟੵ΋ΓͰ͢ͷͰɺݱࡏͰ͸ҧ͏͔΋͠Ε·ͤΜɻ γϡϦϯΫϥοϓ γϡϦϯΫϥοϓͱ͸ɺബ͍ϑΟϧϜͰแΉ͜ͱͰ͢ɻ͙ͬͱ঎඼ͬΆ͘ͳΓ·͢ɻശ 1 ݸ͋ͨΓ 50 ԁͰ͢ɻ 13
  17. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ശ٧Ί ശ٧Ί͸ɺͦͷ໊ͷ௨ΓശʹೖΕͯ΋Β͍·͢ɻശ 1 ݸ͋ͨΓ

    50 ԁͰ͢ɻγϡϦϯΫϥοϓΦϓ γϣϯΛ෇͚Δ৔߹͸౰વඞਢͰ͢ɻ આ໌ॻͷંՃ޻ આ໌ॻΛંΓۂ͛Ճ޻΋ΦϓγϣϯͰ͢ɻ1 ຕ 30 ԁͰ͢ɻശ٧ΊΦϓγϣϯʹ͸ඞਢʹͳΓ·͢ɻ ൓ল఺ ͕͋͜ΕͷγϡϦϯΫϥοϓͰ͕ͨ͠ɺ݁ՌతʹࣦഊͰͨ͠ɻޙ೔ɺ͜͜ΖҰཡΧʔυΛ௥Ճͨ͠ Γɺઆ໌ॻΛϦχϡʔΞϧͨ͠Γ͠·ͨ͠ɻ͔͠͠ɺγϡϦϯΫϥοϓΛണ͕͞ͳ͍ͱɺശʹೖΕΔ ͜ͱ͕Ͱ͖·ͤΜɻ݁ہɺྠΰϜͰͦΕΒΛശʹ͘͘Γ͚ͭ·ͨ͠ɻ 2 ճ໨ͷΧʔυҹ࡮ ߨ࠲ͷडߨੜͨͪͰɺശΛڞ௨Խͯ͠ྔ࢈͠·ͨ͠ɻ1000 ݸ΄Ͳɺྔ࢈ͨ͠ͷͰׂ҆Ͱͨ͠ɻͨ ͩɺڞ௨ͷശͰ͢ͷͰɺλΠτϧ΍આ໌ͳͲ͸ҹ࡮Ͱ͖·ͤΜɻͦͷͨΊɺผ్γʔϧΛ࡞ͬͯషΔ ͜ͱʹ͠·ͨ͠ɻจֶήʔϜશूͷશ࡞඼͕ಉ͡ശʹͳͬͨͷͰɺ1 ϒʔεʹ 14 ࡞඼ฒͼ·ͨ͠ɻ ͔ͳΓͷഭྗͰͨ͠ɻ όʔδϣϯ 1 ͷࡏݿ΋͋Γ·͕͢ɺ͜ͷྲྀΕʹ৐Γ͔ͨͬͨͷͰɺ20 ݸ෼ͷΧʔυ͚ͩҹ࡮͢Δ ͜ͱʹ͠·ͨ͠ɻ·ͨɺόʔδϣϯ 2 ͸ɺֆΛೖΕͨΓΧʔυͷݟͨ໨Λมߋ͠·ͨ͠ɻগྔ͚ͩ΄ ͔ͬͨ͠ͷͰɺઌड़ͷʮҟछ໘෇͚ʯΛར༻͠·ͨ͠ɻ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ٕज़ॻͱͯ͠͸ɺ͕͜͜ϝΠϯͱͳΓ·͢ɻΠϥετϨʔλͷεΫϦϓτͰɺεϓϨουγʔτͷ Χʔυ৘ใͱɺςϯϓϨʔτσβΠϯ͔Βɺ30 ຕͷΧʔυσʔλΛ࡞੒͠·͢ɻ Adobe Extendscript Toolkit CC Πϯετʔϧ Adobe Extendscript Toolkit ͕ͳ͘ͱ΋ɺεΫϦϓτͷ࣮ߦ͸ՄೳͰ͢ɻ ʮϑΝΠϧ/εΫϦϓτ/ ͦͷଞͷεΫϦϓτʯͰ࣮ߦͰ͖·͢ɻͱ͸͍͑ɺ͜ͷΞϓϦ͔Β࣮ߦ͢Δํָ͕Ͱ͢ɻτϥϒϧ࣌ ʹΤϥʔϝοηʔδ͕ݟ͑ΔͷͰݪҼͷ௥ٻ΋Ͱ͖·͢ɻ 14
  18. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ਤ 2.1: Adobe Extendscript

    Toolkit CC Χʔυͷจࣈ΍਺஋σʔλͷ࡞੒ εϓϨουγʔτΛ࢖༻ Google υΩϡϝϯτͷεϓϨουγʔτͰΧʔυσʔλΛ࡞੒͠·͢ɻΧʔυͷछྨ΍਺ࣈ΍ϑ ϨʔόʔςΩετΛγʔτʹॻ͖·͢ɻ ࢲ͕࡞੒ͨ͠ʮ͜͜Ζ-੨य़׉౻ฤ-ʯ͸ԼهεΫγϣͷΑ͏ͳσʔλͰ͢ɻ 15
  19. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ਤ 2.2: εϓϨουγʔτ εϓϨουγʔτͷྻΛॱʹઆ໌͠·͢ɻ

    ˙ຕ਺ ͦͷߦͷΧʔυͷຕ਺Ͱ͢ɻಉ͡Χʔυ͕ෳ਺͋Δͱ͖ʹ͸ 2 Ҏ্Λೖྗ͠·͢ɻ ˙Χʔυ (ۭന) εΫϦʔϯγϣοτͰ͸ۭനͰ͕͢ɺΧʔυͷछྨͰ͢ɻ ˙छྨ ΧʔυͷछྨͰ͢ɻΠϥετ΍จࣈ͕੾ΓସΘΓ·͢ɻ ˙υοτ ಉ͡Χʔυͷຕ਺Ͱ͢ɻ1 ྻ໨ͱ෼͚ͨͷ͸ɺ͜͜ΖΧʔυͷʮ௨ৗϧʔϧʯ͸ػೳ͕ಉ ͡ͰϑϨΠόʔςΩετ͚͕ͩҧ͏΋ͷ͕͋ΔͨΊͰ͢ɻ ˙Ϩϕϧ ॻੜΧʔυͷڧ͞Λදݱͨ͠จࣈͰ͢ɻ ˙ϑϨΠόʔ খઆʮ͜͜Ζʯ͔Βൈਮͨ͠ɺϨϕϧʹԠͨ͡ڧ͞Λײ͡ΒΕΔจষͰ͢ɻϧʔϧʹ ͸Өڹ͠·ͤΜɻ ˙λΠτϧ ʮ͜͜ΖʯΧʔυͷϧʔϧͷλΠτϧͰ͢ɻ ˙ϧʔϧ ʮ͜͜ΖʯΧʔυ͸ɺͦͷλʔϯͷউར৚͕݅ॻ͔Ε͍ͯ·͢ɻ͜ͷ৚݅ͰΧʔυΛ෼഑ ͠·͢ɻ 16
  20. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ txt ϑΝΠϧʹίϐʔ&ϖʔετ εϓϨουγʔτ͔ΒςΩετϑΝΠϧʹίϐʔ&ϖʔετ͠·͢ɻ͜ͷςΩετ͸ɺྻ͕λϒͰ ۠੾ΒΕ͍ͯ·͢ɻεΫϦϓτͰ͸ɺ͜ΕΛར༻ͯ͠ղੳ͠·͢ɻͦͷͨΊɺεϓϨουγʔτ಺ͷ

    λϒ͸ېࢭͰ͢ɻ ςΩετϑΝΠϧͷҰ෦Λൈਮ͠·ͨ͠ɻຊདྷ͸ɺ30 ຕ෼ͷσʔλ͕͋Γ·͢ɻ Ϧετ 2.1: kokoro-v2.txt() ຕ਺ छྨ υοτ Ϩϕϧ ϑϨΠόʔ 1 ॻੜ ಓ 1 Ұ फڭͱ͔఩ֶͱ͔͍͏Ή͔͍ͣ͠໰୊ɻ 1 ॻੜ ಓ 1 ೋ ࢲ͸େ఍Ή͔͍ͣ͠ॻ෺Λصͷલʹ։͚ͯɺͦΕΛݟ٧ Ί͍ͯ·ͨ͠ɻ ຕ਺ λΠτϧ υοτ ৭ ϧʔϧ ϑϨΠόʔ 1 ͜͜Ζ ௨ৗϧʕϧ 2 ௨ৗ ࿀ͷ࠷େ͕࿀ͷউऀɻ࿀ΛऔΔɻ<br>ಓͷ࠷େ͕ ಓͷউऀɻಓΛऔΔɻ ࢲΛݺͼʹདྷΔͷ͸ɺେ఍͓৖͞ΜͰͨ͠ɻ 1 ͜͜Ζ ௨ৗϧʕϧ 2 ௨ৗ ࿀ͷ࠷େ͕࿀ͷউऀɻ࿀ΛऔΔɻ<br>ಓͷ࠷େ͕ ಓͷউऀɻಓΛऔΔɻ Ұिؒ͹͔Γͯ͠ࢲ͸·̠ͨͱ͓৖͞Μ͕͍ͬ͠ΐʹ࿩͍ͯ͠ΔࣨΛ௨Γൈ͚· ͨ͠ɻ ςϯϓϨʔτͷ࡞੒ ΧʔυͷςϯϓϨʔτΛ࡞੒͠·͢ɻςϯϓϨʔτʹ͸ɺશछྨͷΧʔυͷཁૉΛ͢΂ͯೖΕ· ͢ɻϨΠϠ໊΍ΞΠςϜ໊͸ɺεΫϦϓτ͔ΒΞΫηε͢Δͱ͖ʹ࢖༻͢ΔͷͰɺ൒֯ͷΞϧϑΝ ϕοτͰॻ͖·͢ɻςΩετʹ͸ɺԾͷจࣈΛೖΕ·͢ɻֆΛ੾Γସ͑Δ৔߹͸ɺ͢΂ͯͷֆΛೖΕ ·͢ɻ ॻੜΧʔυ ਤ 2.3: ॻੜΧʔυςϯϓϨʔτ 17
  21. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ॻੜΧʔυͷʮಓҰʯΛྫʹɺ੾Γସ͑ΔΞΠςϜΛઆ໌͠·͢ɻ • name Χʔυ໊ʮಓҰʯΛೖΕ·͢ɻ

    • flavor ϑϨʔόʔςΩετʮफڭͱ͔఩ֶͱ͔͍͏೉͍͠໰୊ɻ ʯΛೖΕ·͢ɻ • koi-icon ࿀ͷ࣌ͷΠϥετ • koi-back ࿀ͷ࣌ͷ࿮ • michi-icon ಓͷ࣌ͷΠϥετ • michi-back ಓͷ࣌ͷ࿮ (koi-icon, koi-back) ͱ (michi-icon, michi-back) ͸ͲͪΒ͔ෆཁʹͳΔͷͰɺ࡟আ͞Ε·͢ɻ εΫϦϓτͷ࣮ߦ 1. Adobe Extendscript Toolkit CC Λىಈ͠·͢ɻ 2. εΫϦϓτΛೖྗ͠·͢ɻ 3. ࠨ্ͷখ૭ʹ͋Δ࣮ߦ؀ڥΛ Adobe Illustrator CC 2018 ʹมߋ͠·͢ɻ ਤ 2.4: ࣮ߦ؀ڥͷબ୒ 1. F5 ͔ɺ⾣ϘλϯΛԡ͠·͢ɻ ى఺͸ activeDocument activeDocument ͔Β֤छςΩετ΍ΠϝʔδͳͲͷΞΠςϜΛͨͲΕ·͢ɻΧʔυ͝ͱʹϨΠϠ Λ෼͚ΔͷͰɺಉ໊͡લͷΞΠςϜ͕͋Γ·͢ɻͦͷͨΊɺϨΠϠͱɺΞΠςϜͷ྆ํΛࢦఆͯ͠Ξ ΠςϜΛ୳͠·͢ɻͦͷίʔυ͸ԼهͷΑ͏ʹͳΓ·͢ɻ Ϧετ 2.2: main() // ΞΠςϜΛ໊લͱϨΠϠͰ୳͢ function findItemWithLayer(itemName, layerName) { var items = activeDocument.pageItems for (var n = 0; n < items.length; ++n) { 18
  22. ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.10 εΫϦϓτશจ͸ gist ʹͯʂ item =

    items[n] if (item.name == itemName && item.layer.name == layerName) { return item } } } 2.10 εΫϦϓτશจ͸ gist ʹͯʂ ࣮ߦ࣌ʹ͸ɺ࣮ߦ؀ڥʹ߹ΘͤͯԼهͷઃఆΛมߋ͍ͯͩ͘͠͞ɻίʔυʹશྗͰίϝϯτΛॻ͖ ·ͨ͠ɻ • https://gist.github.com/toymany/5385dff5f44146c48590000035af199d 2.11 ͋ͱ͕͖ ࢲ͸ɺΞφϩάήʔϜΛ 1 ࡞࡞Γɺݱࡏ 2 ࡞໨Λ࡞੒தͰ͢ɻ·ͩ·ͩɺۦ͚ग़͠Ͱ͕͢ɺ͜͜· Ͱʹ΋͍ΖΜͳ͜ͱΛֶͼ·ͨ͠ɻଟ͘ͷਓͷखΛआΓͳ͕Βɺ஌ݟΛ͍͖ͨͩͳ͕ΒɺήʔϜϚʔ έοτʹग़ల͢Δͱ͜Ζ·ͰΛ੎͍Ͱۦ͚ൈ͚·ͨ͠ɻ࡞ۀͷޮ཰ԽͷͨΊɺΠϥετϨʔλͷࣗಈ ԽʹνϟϯϨϯδ͠·ͨ͠ɻΞφϩάΧʔυήʔϜΛ࡞Δ͜ͱ͸ɺσδλϧήʔϜΛ࡞Δͷͱ͸ҧͬ ͨཁૉ͕ͨ͘͞Μ͋Γͱͯ΋ษڧʹͳΓ·͢ɻ͜ͷهࣄ͕ʮ࡞Γ͍͚ͨͲɺͲ͏͠Α͏ʯͱ͍͏ਓͷ ޙԡ͠ʹͳΕ͹޾͍Ͱ͢ɻ 2.12 ࢀߟ • Illustrator ࣗಈԽجຊฤ (Adobe JavaScript γϦʔζ (NextPublishing)) https://amzn. to/2GXdb0j • ถޫҰ੒ͷදݱಓ৔ https://cul.7cn.co.jp/programs/program_695450.html • ᤈҹಊ https://www.mnd.co.jp/ 19
  23. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦ έʔγϣϯΛ࡞ͬͨ࿩ ͜Μʹͪ͸ʂ ଠా Ұߦ (@samayotta) Ͱ͢ɻ

    ࠓճ͸ mixi tech note ͷεϖʔεΛ͓आΓͯ͠ɺࢲ͕࡞੒ͨ͠ΞϓϦέʔγϣϯ spread ͷ঺հ Λ͍͚ͤͯͨͩ͞ΔػձΛಘ·ͨ͠*1ɻ؆୯ʹ঺հ͢Δͱɺ͜ͷΞϓϦέʔγϣϯ͸ରਓήʔϜʢಛ ʹ TCG/DCG ΧʔυήʔϜʣͷରઓه࿥ΛνʔϜͰ؅ཧڞ༗͠ɺ݁ՌΛ෼ੳ͢ΔͨΊͷ OSS Ͱ͢ɻ ͜ͷهࣄͰ͸ຊΞϓϦέʔγϣϯΛ಺༰ͱٕज़ͷ໘͔Β঺հ͠ɺ͞ΒʹηϧϑϗεςΟϯά͢Δखॱ Λ঺հ͢Δ΋ͷͰ͢ɻ ͜ͷίϥϜΛಡΜͰಘΒΕΔ಺༰͸ҎԼͷ௨ΓͰ͢ɻ • spread ʹ͍ͭͯͷ஌ࣝ • Nuxt.js + Firebase αʔόϨεΞϓϦ։ൃͷݟऔΓਤ • ࣗ෼͚ͩͷ spread αΠτͷ࡞Γํ 3.1 spread ʹ͍ͭͯ ΠϯτϩμΫγϣϯ ͍·ɺΧʔυήʔϜͷੈքʹ͸େ͖ͳมԽ͕๚Ε͍ͯ·͢ɻe-sports ͷྲྀߦΛഎܠʹɺඇৗʹଟ͘ ͷਓ͕ΧʔυήʔϜΛϓϨΠ͍ͯ͠·͢ɻ̍̌ສυϧ୯Ґͷߴֹͳใु͕༻ҙ͞ΕͨήʔϜ΋গͳ͘ ͋Γ·ͤΜɻϓϨΠϠʔͨͪ͸ΈͳɺΠϯλʔωοτͰௐ΂ͨ৘ใΛ΋ͱʹɺΑΓউ͍ͬͯΔͱ͞Ε ͨྲྀߦͷσοΩΛબ୒͢Δ܏޲Λڧ͍ͯ͘͠·͢ɻͦͯ͠ྲྀߦ͸·ͨͨؒ͘ʹҠΓมΘΓ·͢ɻͦͷ ͳ͔Λউͪൈ͘ʹ͸ɺΦϦδφϧσοΩΛߏஙͯ͠஁্͍͑͛ͯ͘ͷͱಉ͘͡Β͍ɺ ྲྀಈతͳϝλ ήʔϜΛ၆ᛌͯ͠෼ੳɾ೺ѲͰ͖Δྗɺ͢ͳΘͪʢ͋Ε͹αΠυϘʔσΟϯά΋ؚΊͯʣྲྀߦʹର͠ ͯ࠷΋༏ΕͨσοΩΛબ΂Δྗ͕ɺτʔφϝϯτγʔϯͰ͸ٻΊΒΕΔΑ͏ʹͳ͖͍ͬͯͯ·͢ɻ ͻͱͭͷղܾࡦͱͯ͠ߟ͑ΒΕΔͷ͸ɺ͍ΘΏΔʮνʔϜ੍ʯͰ͢ɻݶΒΕͨෳ਺ਓ਺Ͱ໢ཏతʹ *1 https://github.com/IKKO-Ohta/spread 21
  24. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.1 spread ʹ͍ͭͯ ήʔϜΛϓϨΠ͠ɺϝλήʔϜΛ͠ΒΈ௵͠ʹղੳ͠Α͏ͱ͢Δಈ͖ͱݴ͍׵͑ΒΕ·͢ɻͨͱ͑ ͹ɺMagic: The

    Gathering ͷτοϓϓϩɾϓϨΠϠʔͰ͋Δࠤ౻ϨΠબख͸ɺࣗ਎ͷରઓίϛϡχ ςΟͰ Google εϓϨουγʔτΛར༻͠ରઓ੒੷ΛूΊ͍ͯΔ͜ͱͰ஌ΒΕ͍ͯ·͢*2ɻࢯ͕ར༻ ͍ͯ͠Δγʔτͷܗࣜ͸Ұൠʹެ։͞Ε͓ͯΓɺ୭Ͱ΋ར༻ՄೳͰ͢ɻ ࢲ͸͜ͷΞΠσΞʹೲಘ͠ɺͬͦࣗ͘͞෼Ͱ΋औΓೖΕΑ͏ͱ͠·͕ͨ͠ɺ͙͢ʹ࠳ંͯ͠͠·͍ ·ͨ͠ɻ՝୊ͱͯ͠ײͨ͡ͷ͸࣍ͷΑ͏ͳ͜ͱͰ͢ɻ • ͨͱ͑͹ΧʔυγϣοϓͰରઓऴྃͨ͠ͱ͖ɺεϚʔτϑΥϯ͔ΒؾܰʹฤूͰ͖ͳ͍ • ΧʔυήʔϜશൠʹ࢖༻Ͱ͖ΔΑ͏ͳҰൠੑΛ࣋ͬͨ͘͠Έʹͳ͍ͬͯͳ͍ • εϓϨουγʔτΑΓ΋༏ΕͨϢʔβʔମݧΛఏڙ͍ͨ͠ ͜͏͍͏ཧ༝Ͱ࡞੒ͨ͠ͷ͕ spread Ͱ͢ɻεϚʔτϑΥϯ͔Βؾܰʹରઓ݁ՌΛ౤ߘͰ͖ɺΧʔ υήʔϜશൠʹ࢖༻Ͱ͖ΔΑ͏ͳҰൠੑΛඋ͍͑ͯ·͢ɻ΋ͪΖΜɺGoogle εϓϨουγʔτͰ࣮ ݱͰ͖͍ͯͨӾཡ؅ཧʢݟͤΔਓΛ੍ݶ͢Δʣػೳ΍ɺूܭ͢ΔػೳΛඋ͍͑ͯ·͢ɻ *2 https://twitter.com/r_0310/status/1102829047472521216 22
  25. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.1 spread ʹ͍ͭͯ ਤ 3.2: εϚʔτϑΥϯදࣔ

    ͜Ε͕ରઓه࿥ͷ౤ߘը໘ɺ݁Ռ͕֨ೲ͞ΕͨϖʔδͰ͢ɻ • ରઓΧʔυʹهೖ͠ɺͲΜͲΜετοΫ͍ͯ͘͠ํࣜͰ͢ɻ • σοΩΛ͍ͭ͘Ͱ΋ొ࿥Ͱ͖·͢ɻ • Gmail ΞΧ΢ϯτΛ௨ͯ͡ট଴ͨ͠ϢʔβʔͱγʔτΛڞ༗Ͱ͖·͢ɻ 24
  26. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.1 spread ʹ͍ͭͯ ਤ 3.4: εϚʔτϑΥϯදࣔ

    ͜Ε͕෼ੳͷͨΊͷϖʔδͰ͢ɻ • ه࿥ͨ͠ରઓΧʔυ͔ΒσοΩ ύϑΥʔϚϯεΛଌఆ͠·͢ɻ • σοΩɹύϑΥʔϚϯε͸ɺͦͷσοΩ͕ͲΕ͘Β͍উ͍ͬͯΔ͔Λ৭ͷೱ୶Ͱࣔ͠·͢ɻ಺ ෦తʹ͸Ծઆ:উ཰̑̌ˋΛͦΕͧΕغ٫཰ 20%, 10%, 5% Ͱ྆ଆݕఆ͠ɺͦͷ݁ՌʹΑͬͯ 26
  27. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.2 spread ͷٕज़త֓ཁ ৭Λग़͠෼͚͍ͯ·͢ʢͨͱ͑͹غ٫཰ 5% ͷʴଆͰغ٫͞ΕΕ͹࠷΋ೱ͍྘ʣ

    ɻ • ͞ΒʹσοΩ͝ͱͷৄࡉͳύϑΥʔϚϯεΛूܭ͠·͢ɻઌޙউ཰ͷࠩ͸ͲΕ͘Β͍͔ɺBO3 ήʔϜͰ͸ϝΠϯϘʔυɾαΠυϘʔσΟϯάޙผͷউ཰Λूܭ͠·͢ɻ[ࣥච࣌఺Ͱ͸࣮ ૷த] • σοΩύϑΥʔϚϯεʹ͸ϑΟϧλΛ͔͚ΒΕ·͢ɻ[ࣥච࣌఺Ͱ͸࣮૷த] ϗεςΟϯάʹ͍ͭͯ spread ͸αʔό༻ͷ΋ͷ΋ؚΊͯ͢΂ͯͷιʔείʔυ͕ MIT ϥΠηϯεͷ΋ͱͰެ։͞Εͯ ͍·͢ɻ·ͨࢲࣗ਎͕ϗεςΟϯάͨ͠αΠτ*3ʹΞΫηεͯ͠׬શແྉͰར༻Ͱ͖·͢ɻ͔͠͠΋ ͪΖΜɺࣗ෼ͰϗεςΟϯά͢Δ͜ͱ΋Ͱ͖·͢ɻͨͱ͑͹ಠࣗΧελϚΠζΛࢪͨ͠ spread Λ࢖ ༻͍ͨ͠৔߹ɺϗετओʹରઓ৘ใ౳Λ༬͚ͨ͘ͳ͍৔߹ͳͲʹ͸ɺࣗ෼ΦϦδφϧͷ spread Λϗ εςΟϯά͢Δඞཁ͕͋ΔͰ͠ΐ͏ɻ͜ͷهࣄΛಡΉͱɺspread Λߏ੒͍ͯ͠Δٕज़ʹՃ͑ͯϗε ςΟϯάͷํ๏ʹ͍ͭͯҰ௨Γͷ஌ࣝΛಘΒΕ·͢ɻ࠷ޙ·ͰಡΈऴ͑Δͱࣗ෼͚ͩͷ spread αΠ τΛ͍࣋ͯΔΑ͏ʹͳ͍ͬͯΔ͸ͣͰ͢ɻ ϥΠηϯε MIT ϥΠηϯεͰ͋Διϑτ΢ΣΞ͸׬શʹແྉͰ࢖༻Ͱ͖ɺڐ୚ͳ͠ʹվ଄Ͱ͖·͢ɻ༗ঈͰ ൢചͯ͠΋͔·͍·ͤΜɻͨͩ͠ɺஶ࡞ݖදࣔ͸໨ཱͭΑ͏ʹ໌ه͠ͳ͚Ε͹ͳΓ·ͤΜɻ·ͨιϑ τ΢ΣΞͷར༻ʹ͋ͨͬͯɺ࡞ऀͰ͋Δࢲ (@samayotta) ͸Ұ੾ͷ੹೚Λෛ͍·ͤΜɻ 3.2 spread ͷٕज़త֓ཁ ࠷΋୺తʹ spread ʹ͍ͭͯઆ໌͢Δͱɺ όοΫΤϯυʹ Firestore Λ࠾༻ͨ͠αʔόϨε Nuxt.js γϯάϧϖʔδΞϓϦέʔγϣϯͰ͢ɻΞϓϦέʔγϣϯΛϗεςΟϯά͢Δʹ͋ͨͬͯඞͣ͠΋ϑ ϩϯτΤϯυͷ஌ࣝ͸ඞཁ͋Γ·ͤΜͷͰɺࢀߟఔ౓Ͱे෼Ͱ͢ɻٯʹར༻ऀͷ৘ใΛकΔͨΊόο ΫΤϯυͷηΩϡϦςΟʹ͍ͭͯ͸஫ҙͯ͠ಡΉΑ͏ʹ͍ͯͩ͘͠͞ɻ ϑϩϯτΤϯυ: Nuxt.js Nuxt.js ͸ɺϑϩϯτΤϯυϑϨʔϜϫʔΫͷҰͭͰɺVue.js ʹΑΔ Web ΞϓϦ੍࡞Λચ࿅͞Ε ͨΞʔΩςΫνϟͰߦ͑ΔΑ͏ʹ͢Δ΋ͷͰ͢ɻReact ʹΑΔ Next.js ʹ͍ͭͰਓؾͷ͋ΔϑϨʔϜ ϫʔΫͰɺ2020 ೥ 1 ݄ݱࡏɺGitHub ͷελʔ਺͸ 24.9K ͱͳ͍ͬͯ·͢ɻ spread ͸ Nuxt.js ͷػೳʹશ໘తʹґଘͯ͠ߏங͞Ε͍ͯ·͢ɻ͍Ζ͍Ζͳہ໘Ͱ໘౗ͳ࡞ۀΛ γϣʔτΧοτͰ͖Δͷ͕͋Γ͕ͨ͘ɺ։ൃʹ͔͔Δ࣌ؒΛେ෯ʹ୹ॖͰ͖͍ͯ·͢ɻ·ͨɺ༏Ε ͨΞʔΩςΫνϟͷ͓͔͛ͰϦϙδτϦͷঢ়ଶΛΫϦʔϯʹอͪଓ͚Δ͜ͱ͕Ͱ͖Δ͜ͱɺNuxt.js *3 https://spread-samayotta.com/ 27
  28. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.2 spread ͷٕज़త֓ཁ ʢͱ͍͏͔ϑϩϯτΤϯυ ϑϧελοΫϑϨʔϜϫʔΫશൠʣʹ஌ݟͷ͋ΔΤϯδχΞͰ͋Ε͹શମ Λ͙͢ʹ೺ѲͰ͖Δ͜ͱͳͲͷϝϦοτ΋ॏཁͰ͢ɻ

    Nuxt.js ʹɺ͞Βʹ࣍ͷ̎఺ΛՃ͑ͯ։ൃޮ཰Λ޲্͍ͤͯ͞·͢ɻ 1. TypeScript ʹΑΔܕ҆શԽɻ ϦʔμʔϏϦςΟͷߴ͞ɺVSCode ʹΑΔڧྗͳิ׬ػೳɺ ϓϩάϥϛϯάதͷࢥߟ͕ܕͱ͍͏ந৅తͳ࣍ݩͰΑΓ੔ཧ͞ΕΔ͜ͱͳͲͷϝϦοτ͔ Βɺ΋͸΍ TypeScript ͳ͠ͷ࣮૷͸͋Γ͑ͳ͍ͱߟ͍͑ͯ·͢ɻ͞Βʹɺspread Ͱ͸֤छ decorator ϥΠϒϥϦ (vue-property-decorator, vuex-property-decorator) Λ༻͍ͯܕ҆શͷ ౓߹͍ΛߴΊ͍ͯ·͢ɻ 2. Vuex ͷΫϦʔϯͳར༻ɻ͍ΘΏΔΫϦʔϯΞʔΩςΫνϟΛΏΔ͘࠾༻͍ͯ͠·͢ɻVuex ʹ͸ϏδωεϩδοΫʹؔ࿈͢Δ৘ใΛอ࣋͢ΔΑ͏ʹ͠ɺFirestore ͱͷ௨৴෦෼ʢσʔλ ϕʔεͷߋ৽ɺAPI ௨৴ʣ΋͢΂ͯ͜Εʹू໿͢ΔΑ͏ʹ͍ͯ͠·͢ɻprops όέπϦϨʔΛ ݮΒ͢ɺίϯϙʔωϯτ୯ҐͰͷϢχοτςετΛΑΓָʹ͢Δɺ·ͨ௨৴෦෼ͷվมΛ΍Γ ΍͘͢͢ΔͳͲͷԸܙ͕ड͚ΒΕ·͢ɻ ࢽ໘ͷؔ܎্ϑϩϯτΤϯυͷ࣮ࡍͷίʔυ͸঺հ͠·ͤΜ͕ɺ্هͷ಺༰Λ೺Ѳͨ͠͏͑Ͱίʔ υΛಡΜͰ͍ͨͩ͘ͱΑΓશମ͕ݟ௨͠΍͍͢ͱߟ͍͑ͯ·͢ɻ ˙ίϥϜ: SSR ͷෆ࠾༻ ͳ͓ɺNuxt.js ͸ αʔόαΠυ ϨϯμϦϯάΛ Vue.js Ͱ༰қʹ࣮૷Ͱ͖Δ఺Ͱ΋஫໨͞Εͯ ͍·͢ɻαʔόαΠυ ϨϯμϦϯάͱ͸ɺͬ͘͟Γͱͨ͠આ໌Ͱ͸ɺࣄલʹαʔόͰ HTML ͱ ࢖༻͢ΔσʔλΛ·ͱΊͯ࡞੒ͯ͠ૹ৴ɺͦΕΛ௥͍͔͚ΔΑ͏ʹϒϥ΢β಺Ͱ JavaScript ͷ Ұ෦ΛಡΈࠐΈಈతͳৼΔ෣͍Λߦ͑ΔΑ͏ʹ͢ΔϨϯμϦϯάํ๏Ͱ͢ɻϢʔβʔ͸Ұ଍ઌʹ HTML ͷจষΛಡΊΔͷͰ Time To ContentʢԿΒ͔ͷҙຯͷ͋ΔίϯςϯπʹΞΫηεͰ ͖Δ·Ͱͷ࣌ؒʣ͕୹ॖͰ͖·͢ɻͨͩ͠ spread Ͱ͸ɺTime To Content ͷվળ͕ཁ্݅ΫϦ ςΟΧϧͰͳ͘ɺඞવతʹϨϯμϦϯάΛߦ͏ͨΊͷ Node.js αʔόΛཁٻ͠ӡӦ͢Δख͕ؒ૿ ͑ΔͷͰɺ͜ͷػೳΛ࠾༻͠·ͤΜͰͨ͠ɻ όοΫΤϯυ: Firestore spread ͸ʮαʔόϨεʯͳΞϓϦέʔγϣϯͰ͕͢ɺຊ౰ʹαʔό্ͷॲཧΛԿʹ΋ߦ͍ͬͯͳ͍ ͷ͔ͱ͍͑͹ͦ͏Ͱ͸͋Γ·ͤΜɻ࣮ࡍɺ౤ߘ͞Εͨσʔλͷอଘͱऔಘɺೝূɺট଴ϝʔϧͷૹ৴ ͱ͍ͬͨૢ࡞͸αʔόͰߦ͏ͷ͕ࣗવͰ͢ɻͦ͜ͰɺGoogle Firebase Λར༻ͯ͠࠷௿ݶඞཁͳػೳ Λ·͔ͳ͍ͬͯ·͢ɻ • ݱࡏ͸ Firebase ͷແྉϓϥϯͰӡ༻͍ͯ͠·͢ɻ΋͜͠Ε͕͍ͬͺ͍ʹͳΔΑ͏ͳΒ༗ྉϓ ϥϯ (Blaze) ʹ੾Γସ͑ΔͰ͠ΐ͏ɻੈքதͷΧʔυήʔϜɾϓϨΠϠʔ͕࢖ͬͨͱԾఆͯ͠ 28
  29. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.2 spread ͷٕज़త֓ཁ ΋ Firebase Ͱ࿫͑ͳ͍Α͏ͳ௒େن໛ϓϩμΫτʹ͸ͳΓΑ͏͕ͳ͍ͱ൑அ͍ͯ͠·͢ɻ

    • σϓϩΠ·Ͱ Firebase Hosting ʹґଘ͍ͯ͠·͢ɻ͜ͷํ๏Ͱ͸αʔόίʔυ΍ηΩϡϦ ςΟϧʔϧΛ΋ҰׅͰσϓϩΠͰ͖·͢ɻαʔόίʔυΛؚΉ͜ͷϓϩάϥϜͰ͸ɺҰׅͷσ ϓϩΠ͸͔ܽͤͳ͍ཁ݅ͩͱߟ͍͑ͯ·͢ɻ σʔλߏ଄ɿ Firebase Cloud Firestore ͜ͷΞϓϦͷ firestore σʔλߏ଄͸࣍ͷΑ͏ʹͳ͍ͬͯ·͢ɻ spread---{sheetId}---games---{gameId} ͔ͤͬ͘ TypeScript Λར༻͍ͯ͠ΔͷͰɺγʔτ͓ΑͼήʔϜʹ͍ͭͯ͸ΠϯλϑΣʔεఆٛΛ ܝࡌ͓͖ͯ͠·͢ɻ Ϧετ 3.1: γʔτͷߏ଄ interface SheetInfo { id: string members: string[] sheetName: string gameTitle: GameTitle decks: string[] bestOf: BestOf } enum BestOf { Bo3 = ’BO3’, Bo1 = ’BO1’ } Ϧετ 3.2: ήʔϜͷߏ଄ interface GameInfo { id: string win: Result black: Bw myDeck: string | null oppDeck: string | null timestamp: string user: string describe: string wins?: Result[] blacks?: Bw[] 29
  30. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.2 spread ͷٕज़త֓ཁ } enum Result

    { win = ’উར’, lose = ’ഊ๺’ } enum Bw { black = ’ઌख’, white = ’ޙख’ } ೝূ: Firebase Authentification ೝূʹ͸ Firebase Authentification Λ༻͍·͢ɻAuthentification ͸ଟ༷ͳϩάΠϯखஈ ʢGmailɺ FacebookɺTwitter etc.) Λαϙʔτ͠ɺϩʔΧϥΠζ΍ݟͨ໨ͳͲͷΧελϚΠζ΋؆୯ͳαʔϏ εͰ͢ɻಋೖʹඞཁͳखॱ͸ɺFirebase Λઃఆ͍ͯ͠Ε͹ઐ༻ͷϥΠϒϥϦΛద੾ʹઃஔ͢Δ͚ͩ Ͱ͢ɻ Firebase ηΩϡϦςΟ ϧʔϧ Firebase Authentification Λར༻͢ΔͱϩάΠϯػೳΛ؆୯ʹ࣮૷Ͱ͖·͢ɻ͔͠͠ɺͦΕͱۓ ີʹ݁ͼ෇͍͍ͯΔ Firebase ηΩϡϦςΟ ϧʔϧΛͲͷΑ͏ʹ؅ཧ͢Δ͔͸গ͚ͩ͠ෳࡶͰ͢ɻར ༻ऀͷ৘ใΛकΔͨΊɺηϧϑϗεςΟϯάΛߦ͏ਓ͸͜ͷ߲໨ΛඞͣಡΈɺࣗ਎ͷϗεςΟϯά͠ ͨ spread ʹద༻͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻ ηΩϡϦςΟͱ Cloud Firestore ΁ͷΞΫηε Firebase Ͱ͸Ϣʔβʔ͕௚઀σʔλϕʔεʹΞΫηεͰ͖·͢ɻͦͷϢʔβʔͷΞΫηεݖݶΛ ੍໿͢Δͷ͕ Firebase ηΩϡϦςΟ ϧʔϧͰ͢ɻ ͜ͷ࣮૷ʹ͸࣍ͷར఺͕͋Γ·͢ 1. ΫϥΠΞϯτଆ͕ηΩϡϦςΟͷ੹຿͔Βղ์͞Ε·͢ɻ΋͠ΫϥΠΞϯτʹόάΛ࡞ΓࠐΜ ͩ··σϓϩΠͯ͠͠·ͬͯ΋ɺσʔλΛ҆શͳ··ʹ͓͚ͯ͠·͢ɻ 2. σʔλϕʔεࣗ਎͕ηΩϡϦςΟΛ୲͍ͬͯΔͷͰɺڮ౉͠Λ͢ΔαʔόΛඞཁͱ͠·ͤΜɻ σʔλϕʔε΁ͷΞΫηε͸ɺ͞·͟·ͳϧʔϧʹΑ੍ͬͯ໿͞Ε·͕͢ɺ࠷΋యܕతͳ੍໿ϧʔ ϧ͸Ϣʔβʔ͕ೝূ͞Ε͍ͯΔ͔Ͳ͏͔*4Ͱ͢ɻ *4 https://firebase.google.com/docs/rules/basics?hl=ja#all_authenticated_users 30
  31. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩ 3.2 spread ͷٕज़త֓ཁ Ϧετ 3.3: యܕతͳϧʔϧ

    service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth.uid != null; } } } ಡΈॻ͖Λ੍໿͢Δ৚݅ͱͯ͠ɺrequest.auth.uid ͢ͳΘͪೝূ৘ใΛར༻͍ͯ͠Δͷ͕Θ͔ ΔͰ͠ΐ͏ɻಉ͡Α͏ʹɺCloudStore ʹؚ·ΕΔ৘ใ΋੍໿ͱͯ͠ར༻Ͱ͖·͢ɻ spread Ͱ͸ҎԼͷઃఆΛਪ঑͍ͯ͠·͢ɻ Ϧετ 3.4: ਪ঑ϧʔϧ rules_version = ’2’; service cloud.firestore { match /databases/{database}/documents { match /sheet/{sheetName} { allow read; allow write: if request.auth.token.email != null } match /sheet/{sheetName}/games/{game} { allow read, write: if request.auth.token.email in get(/databases/$(database)/documents/sheet/$(sheetName)).data.members; } } } • γʔτ ͸ͲΜͳϢʔβʔ΋ಡΈग़͢͜ͱ͕Ͱ͖·͢ɻҰํͰɺ৽͘͠γʔτΛ࡞੒͢ΔͨΊ ʹ͸ϩάΠϯΛඞཁͱ͠·͢ɻ • ήʔϜ ͸ಛʹӅ͞ͳ͚Ε͹ͳΒͳ͍৘ใͰ͢ɻಡΈॻ͖ͷͲͪΒʹ΋ɺͦͷγʔτͷϝϯόʔ Ͱ͋Δ͜ͱΛཁٻ͠·͢ɻ FaaS: Firebase Cloud Functions Cloud Functions(ҎԼ Functions) ͸όοΫΤϯυίʔυΛ HTTPS ϦΫΤετʹԠ࣮ͯ͡ߦ͢Δ αʔϏεͰ͢ɻ͜ΕΛ༻͍ΔͱɺREST API ෩ͷ࢓ࣄΛαʔόͳ͠ʹ࣮ߦͰ͖·͢ɻ ͨͱ͑͹࣍ͷΑ͏ʹίʔυΛσϓϩΠ͠·͢ɿ 31
  32. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩3.3 my spread ͷσϓϩΠखॱ Ϧετ 3.5: ping

    functions exports.ping = functions.https.onCall((data, context) => { return {text:’pong’} }); ΫϥΠΞϯτଆ͔Β͸͜ͷΑ͏ʹ HTTPS ϦΫΤετΛߦ͍·͢ (ઐ༻ͷ API ΫϥΠΞϯτ͕෇ ଐ͍ͯ͠·͢): Ϧετ 3.6: ping-pong const ping = functions.functions().httpsCallable(’ping’) try { const res = await ping() console.log(res.data.text) // => pong } catch (error) { throw new Error(’#ping ERROR’) } ࣮ࡍʹ͸σʔλϕʔεͷߋ৽΍ Google Analytics ͷ݁ՌͳͲɺHTTPS ϦΫΤετҎ֎ͷτϦΨ Λ༻͍ͯؔ਺Λ࣮ߦ͠ɺ஋Λฦ٫ͤ͞Δ͜ͱ͕Ͱ͖·͢ɻFunctions ͔Β͸σʔλϕʔε΍ೝূͳͲ Ұ௨Γͷ৘ใΛࢀরͰ͖ΔͨΊɺαʔόϨεΞϓϦʹ͓͍ͯαʔόͷ୅໾Λ͜ΕͰ·͔ͳ͑·͢ɻ spread Ͱ͸ট଴ϝʔϧͷૹ৴ʹͷΈ͜ΕΛ༻͍͍ͯ·͕͢ɺͨͱ͑͹৽͘͠ήʔϜ͕ϓογϡ͞ Εͨͱ͖ʹ slack ʹ௨஌͢Δػೳ΍ɺσʔλʹର͢ΔόονॲཧΛఆظతʹ࣮ߦ͢ΔػೳͳͲΛ௥Ճ Ͱ͖Δͱߟ͑ΒΕ·͢ɻ 3.3 my spread ͷσϓϩΠखॱ σϓϩΠ͸࣍ͷखॱͰߦ͍·͢ɻ 1. Node.js ͓Αͼ yarn Λऔಘ͠·͢ɻ 2. ϑϩϯτΤϯυͷίʔυΛར༻Մೳʹ͠·͢ɻ 3. Firebase ʹొ࿥͠·͢ɻ͞Βʹ Firebase CLI Λ༻͍ͯɺspread ʹ Firebase Λ௥Ճ͠·͢ɻ 4. ͍͔ͭ͘ͷઃఆΛ֬ೝͨ͠ΒσϓϩΠ͠·͢ɻ 1. Node.js ͓Αͼ Yarn Λऔಘ Node.js ͷαϙʔτόʔδϣϯ͸ɺ2020 ೥ 1 ݄ 24 ೔ݱࡏͷ҆ఆ൛ʢLTSʣͰ͋Δ 12.14.1 Ͱ͢ɻ nvm ͳͲͷ Node.js όʔδϣϯ؅ཧγεςϜΛ͓޷ΈͰ࢖༻͍ͨͩ͘ͱศར͔ͱࢥ͍·͢ɻ 32
  33. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩3.3 my spread ͷσϓϩΠखॱ $ brew install

    yarn --ignore-dependencies • --igonore-dependencies Φϓγϣϯͳͩ͠ͱɺbrew ͕ Node.js 12.14.1 Ҏ֎ͷόʔδϣ ϯΛ߹ΘͤͯάϩʔόϧΠϯετʔϧ͢Δ͜ͱ͕͋Γ·͢ɻ஫ҙ͍ͯͩ͘͠͞ɻ 2. ϑϩϯτΤϯυͷίʔυΛར༻Մೳʹ͢Δ ্هͷ४උ͕Ͱ͖ͨΒɺͬͦ͘͞ spread ΛΫϩʔϯ͠·͠ΐ͏ɻ $ git clone https://github.com/IKKO-Ohta/spread ·ͣ͸ґଘϥΠϒϥϦΛΠϯετʔϧ͠·͢ɻ $ cd spread $ yarn install ͜ͷ࣌఺Ͱςετͷ࣮ߦ͸ՄೳʹͳΔ͸ͣͰ͢ɻ $ yarn test 3. Firebase ʹొ࿥ ϒϥ΢βଆૢ࡞ ࠓ౓͸ϒϥ΢βΛ։͖ɺ͋ͳͨͷ Gmail ΞΧ΢ϯτʹ Firebase Λ௥Ճ͠·͢ɻ 1. Google ͷެࣜαΠτ*5ʹΞΫηε͠ɺ ʮίϯιʔϧʹҠಈʯΛΫϦοΫɻ ʮϓϩδΣΫτΛ࡞ ੒ʯͰ৽͘͠ Firebase ϓϩδΣΫτΛ࡞੒͠·͢ɻ໊લ͸ͳΜͰ΋ྑ͍Ͱ͕͢ɺ͜͜Ͱ͸Ծ ʹ my-spread ͱ͠·͠ΐ͏ɻར༻ن໿ʹಉҙͨ͠ΒϓϩδΣΫτ͕࡞੒͞Ε·͢ɻ 2. GCP ϦιʔεϩέʔγϣϯΛઃఆ͠·͢ɻࠨϖΠϯͷࣃंϚʔΫ͔ΒηοςΟϯάը໘ʹҠ ಈ͠ɺ্ͷΧʔυ͔Βࣗ෼ͷ޷͖ͳϦʔδϣϯΛઃఆ͠·͠ΐ͏ɻ *5 https://firebase.google.com/ 33
  34. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩3.3 my spread ͷσϓϩΠखॱ 3. ΞϓϦΛ௥Ճ͠·͢ɻίϯιʔϧτοϓը໘ʹ໭Γɺதԝ͔ΒʮWebʯΛબͼ·͢ɻΞϓϦͷ χοΫωʔϜ΋ԿͰ΋ྑ͍Ͱ͢ɻmy-spread

    ͱԾʹ͓͖ͯ͠·͢ɻhosting ͸ͷͪʹઃఆ͠· ͕͢ࠓ͸௨Γա͗Δ͜ͱͱ͠·͢ɻ ʮΞϓϦΛ௥ՃʯΛΫϦοΫ͢Δͱɺ͜ͷΞϓϦΛར༻͢ ΔͨΊͷΫϨσϯγϟϧ৘ใ͕දࣔ͞Ε·͢ɻগ͠ΤσΟλʹ໭ΓɺϓϩδΣΫτϧʔτʹ c redentials σΟϨΫτϦΛ࡞੒͠·͢ɻcredentials/firebasekey.ts ͱ͍͏ϑΝΠϧ໊ Ͱɺ࣍ͷΑ͏ʹॻ͖·͢ɻ΋ͪΖΜ export default ͷத਎ͷ෦෼͸͖͞΄Ͳϒϥ΢βʹද ࣔ͞ΕͨΫϨσϯγϟϧ৘ใͰ͢ɻvar firebaseConfig ʹ୅ೖ͞Ε͍ͯΔΦϒδΣΫτΛ export default ͢ΔΑ͏ʹ͠·͢ɻ ਤ 3.5: Firebase ͷίϯιʔϧը໘ɻࠨ্ʹηοςΟϯά΁ͷϦϯΫ͕ɺதԝʹΞϓϦ௥ՃϘλϯ͕ ͋Δ Ϧετ 3.7: credentials/firebasekey.ts export default { apiKey: *** , authDomain: ***, databaseURL: ***, projectId: ***, storageBucket: ***, messagingSenderId: ***, appId: ***, measurementId: *** } 34
  35. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩3.3 my spread ͷσϓϩΠखॱ ίϯιʔϧଆૢ࡞ ࠶ͼίϯιʔϧʹ໭ΓɺFibase CLI

    Λμ΢ϯϩʔυ͠·͢ɻ $ yarn global add firebase-tools Firebase Λ spread ʹઃఆ͠·͢ɻݸਓ৘ใͷऩू΄͔ɺFirebase ͷઃఆʹ͍͍͔ͭͯͭ͘ͷ࣭໰ ͕ߦΕ·͢ɻҎԼͷΑ͏ʹճ౴͍ͯͩ͘͠͞ɻ $ firebase login $ firebase init ... services => firestore, hosting, functions ... Project Setup => Use an existing project => my-spread‘ // ϓϩδΣΫτ໊ ... Firestore Setup => // EnterͰσϑΥϧτ໊ firestore.rulesΛ༻͍Δ =>ɹfirestore.rules Λ্ॻ͖͠ͳ͍ ... Functions Setup => TypeScript => tslintΛ࢖༻͠ͳ͍ // ͜ͷϦϙδτϦ͸eslintʹΑͬͯ؅ཧ͞Ε͍ͯΔͨΊ => ͍Ζ͍ΖͳϑΝΠϧͷ্ॻ͖ʹ͍࣭ͭͯ໰͞ΕΔ͕ɺશ෦্ॻ͖͠ͳ͍ => Y // ґଘؔ܎ͷΠϯετʔϧ ... Hosting Setup => dist // public directory͸σϑΥϧτʹͤͣɺnuxtͷgenerateઌʹ߹ΘͤΔ => Yes // γϯάϧϖʔδΞϓϦέʔγϣϯͱͯ͠ઃఆ͢Δ͔ʁ Firebase initialization complete! ͱදࣔ͞ΕͨΒ׬ྃͰ͢ɻ ͳ͓ɺ͜ͷաఔͰ࣍ͷΤϥʔ͕ग़Δ͔΋͠Ε·ͤΜɻ • Error: Error fetching Firestore indexes 35
  36. ୈ 3 ষ ΧʔυήʔϜͷରઓ؅ཧɾ෼ੳΞϓϦέʔγϣϯΛ࡞ͬͨ࿩3.3 my spread ͷσϓϩΠखॱ Firestore ͸ωΠςΟϒ ϞʔυΛ૝ఆ͍ͯ͠·͢ɻDataStore

    Ϟʔυʹઃఆ͞Ε͍ͯΔ৔߹͸ɺ GCP ܦ༝ͰωΠςΟϒϞʔυʹ੾Γସ͑Δඞཁ͕͋Γ·͢ɻFirestore ͷϖʔδ͔Β GCP ʹΞΫη εͯ͠มߋ͠·͠ΐ͏ɻ 4. ॳظઃఆϑΝΠϧͷ֬ೝ ηΩϡϦςΟϧʔϧͷઃఆ͓Αͼ Functions ͷઃఆΛߦ͍·͢ɻREADME_about_firestore_set tings.md Λݟͳ͕Βɺੜ੒͞ΕͨϑΝΠϧ firestore.rules ͓Αͼ functions/src/index.ts Λ֬ೝ͠·͢ɻ • firestore.rules ͷઃఆ͸ηΩϡϦςΟ্ॏཁͰ͢ɻඞͣ֬ೝ͍ͯͩ͘͠͞ɻ • functions/src/index.ts ͸ট଴ϝʔϧΛૹ৴͢ΔͨΊͷॲཧͰ͢ɻট଴ϝʔϧͷૹ৴ݩ Gmail ΞΧ΢ϯτΛઃఆ͠·͢ɻԼͷΑ͏ͳίϚϯυͰઃఆͰ͖·͢ɻͨͩ͠ɺ͜ͷ Gmail ΞυϨε͸׬શʹઐ༻ͷ৽͍͠΋ͷΛ࡞੒͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻηΩϡϦςΟϦεΫΛ૿ େͤ͞ΔͨΊͰ͢ɻొ࿥ͨ͠ΞΧ΢ϯτͰ͸ܯࠂϝʔϧΛड͚औΔͷͰɺ಺༰Λཧղͨ͠͏͑ ͰڐՄ͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻ firebase functions:config:set gmail.email="[email protected]" gmail.password="your-password" ͍Α͍ΑσϓϩΠͰ͢ɻ $ yarn generate $ firebase deploy ਖ਼ৗʹऴྃͨ͠ΒσϓϩΠऴྃͰ͢ɻϗεςΟϯάઌͷ URL ͕දࣔ͞Ε·͢ɻ ࠓޙ͸ development ؀ڥͰ࣮ߦ͢Δ͜ͱ΋Ͱ͖·͢ɻ $ yarn dev ͓ർΕ͞·Ͱͨ͠ʂ ίϛοτͯ͘͠ΕΔํ͕૿͑Δͱ͏Ε͘͠ࢥ͍·͢ɻΑΖ͓͘͠ئ͍͠·͢ɻ 36
  37. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γε ςϜ ॳΊʹ ຊষ͸ɺฐࣾͰ 2019/12/07 ʹֶੜ޲͚ʹ։࠵ͨ͠ڝٕϓϩάϥϛϯάίϯςετʹ͓͍ͯɺͦͷ

    ΠϕϯτਐߦͷͨΊʹઃܭͨ͠ճ౴γεςϜͱӡ༻ํ๏ʹؔͯ͠ɺͦͷιϑτ΢ΣΞΛઃܭ։ൃͨ͠ ୲౰ࣗ਎͕ͦͷγεςϜ֓ཁͱܦҢͱ͓΋͠ΖΈΛࢲݟͰղઆ͢Δ΋ͷͰ͋Δɻͩͨઌʹ͓఻͑ͯ͠ ͓͖͍ͨɻຊষ͸ιʔείʔυΛ͋·Γܝࡌ͍ͯ͠ͳ͍ɻͱ͍͏ͷ΋ɺԿΛͲͷΑ͏ʹ͢Δ͔ͷ૊Έ ߹Θ͕ͤओʹͳ͔ͬͨΒͰ͋ΔɻͦͷͿΜɺιϑτ΢ΣΞ΍γεςϜΛܭը͢Δͱ͖ʹͲΜͳࢹ఺Ͱ ԿΛߟ͑Δ͔ͷώϯτͱͳΔ͜ͱΛئ͍ຊষΛ·ͱΊΔɻ 4.1 Πϕϯτʹ͍ͭͯ Unity Engine Challenge by mixi GROUP ɹͱ͸ ਤ 4.1: ϛΫγϧܝࡌهࣄΑΓൈਮ 37
  38. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.2 ४උ ϛΫγΟάϧʔϓͰ͸ɺ ʮgit challengeʯ

    ʮTDD challengeʯ ʮBug Shooting ChallengeʯͳͲɺΤϯ δχΞࣾһ͕࣮຿ʹ͍ۙ؀ڥΛ༻ҙ͠ɺֶੜ޲͚ͷٕज़ΠϕϯτΛఆظతʹ։࠵͍ͯ͠Δɻ2019 ೥ 12 ݄ʹ৽ͨͳδϟϯϧͷ Challenge γϦʔζͱͯ͠ɺ ʮUnity Engine Challenge by mixi GROUPʯ ʢhttps://mixil.mixi.co.jp/report/6788ʣΛ࣮ࢪͨ͠ɻ Twitter ϋογϡλάɿ#mixi_unity *1 ࢀՃऀ͸౰೔஌Β͞ΕΔ໊̎ͰνʔϜΛ૊Έࢦఆͷ GitHub ϦϙδτϦ͔Β՝୊Λड͚औΔɻ՝୊ ʹ߹ΘͤͨϓϩάϥϜΛࣗ਎͕ Unity Ͱ࣮૷͠ɺ͔ͭεϚʔτϑΥϯʹϏϧυͯࣗ͠ΒΞϓϦʹ࣮ ૷ͨ͠ػೳΛ࢖͍ϦΞϧੈքͰղ౴Λग़͢ͱ͍͏΋ͷɻϦΞϧੈքʹ͸՝୊ͷͨΊͷಡΈऔΓը૾΍ εϙοτ͕ઃ͚ΒΕ͓ͯΓɺଞνʔϜͷಈ͖Λԣ໨ʹࣗ෼ͨͪ΋ෛ͚ͯ͸ͳΒͳ͍ͱࣗવͱڝ૪ҙཉ ͕ૡཱ͖ͯΒΕΔɻ೉͍͠՝୊ɺಘҙෆಘҙͷ෼໺ɺݶΒΕͨ࣌ؒͱ͍͏͜ͱ΋͋Γɺ̎ਓͷνʔϜ ϫʔΫΛ͍͔ʹ׆༻͢Δ͔΋େ͖ͳݤͱ͍͑Α͏ɻͦ͏ͯ͠൴Β͸ձ৔Λา͖ճͬͯ͸ҰتҰ༕͠ɺ ੠Λ্͛ͯ͸௅ઓΛଓ͚͍ͯ͘ɺͱ͍͏೤͍࠵͠Ͱ͋Δɻ ࠓճͷ՝୊ʹ͸̏ͭͷ෼໺͕͋Γɺ ʮARʯ ʮShaderʯ ʮMulti ϓϨΠʯ͔ΒͦΕͧΕ͍͔ͭ͘ͷ՝୊ ͕༻ҙ͞Ε͍ͯΔɻͦΕ͸Ͳͷॱ൪ʹ࢝Ίͯ΋Α͘ɺνʔϜͰ෼୲ͯ͠΋ɺҰॹʹਐΊͯ΋ࣗ༝ɻ՝ ୊ʹΑͬͯ͸༻ҙ͞Ε̙ͨ̏ϞσϧͷΞηοτΛར༻ͨ͠Γɺඞཁͳϛυϧ΢ΣΞΛར༻ͨ͠Γɺ਺ ஋ܭࢉΛ͝Γ͝Γ΍ͬͨΓͱɺUnity ͱϓϩάϥϛϯάΛ࣮ફతʹߦ͏૯߹తཧղ͕ٻΊΒΕΔͱ͍ ͏΋ͷͩɻ ҎԼͷ·ͱΊ͸͜Ε͔ΒຊষͰޙड़͢Δݕ౼ΛਐΊͨ݁Ռͱͯ͠ࢸͬͨ΋ͷͰ͋Δɻ 1. ֤νʔϜ͝ͱʹϒϥ΢β͔Β͜ͷνϟϨϯδճ౴γεςϜΛ։͍ͯࢀՃ͢ΔɻࢀՃνʔϜͱճ ౴ঢ়گ͸ձ৔ϝΠϯϞχλʔʹҰཡ͞Ε͍ͯΔɻ 2. GitHub ͔Β՝୊Λड͚औΓɺඞཁͳ΋ͷΛ࣮૷͢ΔɻͦΕͧΕணख͢ΔͨΊͷϓϩδΣΫτ ͕഑෍͞Ε͓ͯΓ clone ͯ࢝͠ΊΔɻ 3. ձ৔಺ʹઃஔ͞Εͨग़୊ͷͨΊͷಡΈऔΓը૾΍࣮ફεϙοτ͕͋Γɺͦ͜Ͱࣗ਎Ͱ࡞ͬͨΞ ϓϦΛར༻ͯ͠ղ౴จࣈΛಋ͖ग़͢ɻ 4. νϟϨϯδճ౴γεςϜͷϑΥʔϜ͔Βղ౴จࣈΛೖྗ͠੒൱ΛಘΔɻ νʔϜ͸՝୊Ҏ֎ʹ͓͍ͯ͸γϯϓϧͳঢ়گ͔ͩͬͨͱࢥ͏ɻ͜Εʹ͍͍͔ͭͯʹͯ͠ߦ͖ண͍ͨ ͔ͷ෣୆ཪͱɺܝ୊ͷࣔ͢௨Γճ౴γεςϜιϑτ΢ΣΞʹ͍ͭͯΛղઆͯ͠ߦ͖͍ͨɻ 4.2 ४උ step1. ඞཁͳ΋ͷ͸ νϟϨϯδΠϕϯτΛ࣮ࢪ͢Δʹ͋ͨΓɺզʑ͸ࢀՃνʔϜ͕ղ౴Λݟ͚ͭग़ͨ͠ͱ͖ʹ౴͑ ߹Θͤ͢Δํ๏ͱɺෳ਺໰͋Δ՝୊ͷਐ௙ঢ়گΛه࿥͢Δํ๏ɺͦΕʹฐࣾͰաڈ 12 ճʹ΋౉ͬ ͯ։࠵͞Εଓ͚͍ͯΔఆ൪Πϕϯτ git challengeʢhttps://github.com/mixi-git-challenge/ publicationsʣ ͷ޷ྫΛ౿·͑ͯձ৔ͷڊେεΫϦʔϯʹࢀՃνʔϜͷਐ௙ঢ়گΛදࣔ͢Δํ๏ *1 Twitterɿ https://twitter.com/search?q=mixi_unity&src=typed_query 38
  39. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.2 ४උ Λݕ౼ͨ͠ɻ͍ΘΏΔϦΞϧ୤ग़ήʔϜͰ͋Ε͹౴Ҋ༻ࢴΛࢀՃऀʹ౉͓͍ͯͯ͠ऴྃ࣌ʹఏग़ͯ͠ ΋Β͑͹Α͍༁ͳͷ͕ͩɺ͔ͤͬ͘ςοΫاۀΛ΍͍ͬͯΔΘ͚Ͱ͋ΓిࢠԽ͍ͨ͠ɻϑΥʔίϛϡ χέʔγϣϯͰΤϯλʔςΠϯϝϯτΛఏڙ͍ͯ͠Δͷ͕ฐࣾͰ͋ΓɺΠϕϯτਐߦ΍ίϛϡχέʔ

    γϣϯͱͯ͠౰વ੝Γ্͕Γʹߩݙͨ͘͠ͳͬͯ͘ΔϫέͰ͋Δʢࢲͷؾ࣋ͪʣ ɻͨͩ͠ɺࠓޙܧଓ ͯ͠਺ճͣͭ։࠵͢Δͱͯ͠΋͍Ζ͍Ζͳख഑ʹࠔΒͳ͍ํ๏͕ྑ͍ͷͰαʔόϨεͰ͋Ε͹ͦͷํ ͕ྑ͘ɺ͜ͷ఺΋ΞΠσΞΛ࿅ͬͯΈΔ͜ͱʹ͢Δɻ·ͨɺਖ਼ղͯ͠ਐ௙͕͋Δঢ়گҎ֎ʹ΋ɺճ౴ ऀͷೖྗཤྺͷ෼ੳ΍ίϯςετશମͷࢀՃਐ௙৘ใͷอશ΍෮چʹ͍ͭͯ΋୲อ͍ͨ͠ɻɾɾɾͳ ʹ΍Βن໛ײ͕େ͖͘ͳΓͦ͏ͳ೏͍͕͖ͯͨ͠ɻ Step2. ࣮ݱՄೳੑͷൺֱ ͜ΕΒΛ౿·͑ͯࢥ͍෇࣮͘ݱํ๏Λฒ΂ͯൺֱݕ౼ͯ͠ΈΔ͜ͱͱ͢Δɻ 参加者持参PCで ❏ 回答文字入力はどこで行う? 会場の専用PCで 貸与するタブレットの専用アプリで 回答ソフトウェア配布は避けたい あるいは回答Webページが必要 回答入力場所に足を運ぶのが大変すぎるかもし れない 端末の確保と回答アプリが必要 参加者持参PCで ❏ 課題の成否判定はどこで? 成否判定サーバーで 成否判定PCで 解答自体、もしくは判定ソフトを配るのは避けた い Webサーバー設置とWeb API(成否やりとり)、ド メインが必要 クライアント間通信手段とAPIが必要 MySQLなどのデータベースから ❏ 永続性のある進捗情報はどこから参照する? 成否判定サーバー上のローカルストレージから 成否判定PCのローカルストレージから メモリやmemcachedなどのキャッシュシステムか ら データベースサーバーの設置が必要 データの永続性がない Webサーバー設置が必要 クライアント間通手段が必要 ਤ 4.2: ൺֱݕ౼ ͜ͷΑ͏ʹ͋Ε΍͜Ε΍ͱύλʔϯΛߟ͑ͯద͍࣮ͯͯ͠ݱ͠΍͍͢ํ๏ΛΈ͍ͯ͘ɻલهͨ͠Α ͏ʹαʔόϨε͕ྑ͍ͷͰɺΫϥΠΞϯτؒ௨৴ΛԠ༻ͯ͠੒൱൑ఆ໾୺຤ʹΞΫηε͢Δ࢖͍ํ͸ Ͳ͏͔ɻͦͯ͠ճ౴ೖྗ୺຤͸ɺAndroid λϒϨοτΞϓϦ΋͘͠͸੩త Web αΠτͰಈ͔͢ϒϥ 39
  40. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.2 ४උ ΢βΞϓϦʢUnity Ͱ build

    ͢ΔͳΒ WebGL ൛ͱ͍͏͜ͱʣͰ͋Ε͹ྑͦ͞͏ɺͱݟཱͯΔɻ Step3. ߏ੒͸ Unity ͱ Photon Ͱ΍Δ චऀ͸ຊΠϕϯτͰ՝୊ͷ࡞੒΋ಉ࣌ʹ୲౰͍ͯ͠ΔɻͦΕ͕ʮMulti ϓϨΠʯύʔτͰ͋Γɺ Unity ͱ Photon (Exit Games ˜ ) Λ࢖ͬͯ՝୊Λղ͘ͱ͍͏΋ͷͰ͋ΔɻPhoton ͷબఆཧ༝ʹ ͸ɺචऀ͕ͦΕҎલͷۀ຿ܦݧͱͯ͠Կ౓͔ར༻ͯ͠αʔϏεΠϯ͍ͯͨ͠ܦҢ͕͋Γ࢖͍উख͕෼ ͔͍ͬͯΔɺͱ͍͏͜ͱ͔Β࠾୒ͨ͠ɻͰ͸ճ౴γεςϜͰ͸Ͳ͏͢Δ͔ͱ͍͏ͱ͜ͱͰɺظ೔తͳ ΋ͷ΋͋Δ͜ͱ͔Βɺ͜ͷͲͪΒ΋͋Δఔ౓ಉ͡ Multi ௨৴؀ڥͰ։ൃ࣮ͯ͠ݱ͢Δ͜ͱ͕ࢲʹͱͬ ͯ࠷దͩͬͨ͜ͱ͔Βɺճ౴γεςϜʹ΋ Unity ʷ Photon ͷ։ൃΛ࠾୒͢Δɻ Step4. Platform Platform ͸্ͷൺֱݕ౼ͷܦҢΛ౿·͑ͯɺࢀՃऀͷ؀ڥ͸ఏڙͱιϑτ΢ΣΞͷ؅ཧ͕؆୯ͳ ʮWebGL ൛ʢUNITY_WEBGLʣ ʯͰɺ੒൱൑ఆ໾ଆͰ͸࣮ߦϩάΛ؆୯ʹऔͬͯ PC Ͱਫ਼ࠪ΍෼ੳ͕Ͱ ͖ΔΑ͏ʹͨ͘͠ʮσεΫτοϓΞϓϦέʔγϣϯ൛ʢUNITY_STANDALONEʣ ʯͰͱ 2 ͭͷ Platform Λ઀ଓ͢Δ͜ͱͱͨ͠ɻ 40
  41. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ 4.3 ։ൃ ͜͏ͯ͠ճ౴γεςϜϓϩδΣΫτͷશମͷྲྀΕ͕Ͱ͖͕͋ͬͨ

    6. 成否判定 受信と返信 8. スコアボード表示 4. 課題情報取得 +リスト配布 5. 課題リスト 受信と表示 7. 回答入力 送信と成否受信 9. リザルト等演出 3. 参加者版 ifdefでBuildスイッチ 10. 進捗情報 保存と復帰 1. Unity x Photon Multiクライアントソフト ベース開発 2. 主催者版 ifdefでBuildスイッチ ਤ 4.3: ओͳ։ൃ޻ఔ ਤ 4.3 ʹ͍ͭͯɺ • ओ࠵ऀͱճ౴ऀͰͦΕͧΕιϑτ΢ΣΞͷ༷͕ࣜ΄ͱΜͲҧ͏Θ͚͕ͩɺࠜ͸ಉ͡ͳ͜ͱͱ ࡞ۀޮ཰ͷ౎߹͔Β 2ɾ3 ͷΑ͏ʹಉҰϓϩδΣΫτ಺Ͱ؀ڥґଘίϯύΠϧͰ෼͚ͯਐΊͯ ͍Δɻ • 4 ͷ՝୊৘ใ͸ਖ਼ղҰཡͷऔಘͱ഑෍͕ͩɺਐߦͱͯ͠ઌʹܾΊଧͪͰ࣮૷Λऴ͑ͨޙɺ࠷ऴ తʹ֎෦഑৴ରԠʹ֦ு͢Δɻ • 6ɾ̓ͷσʔλ௨৴ʹ͍ͭͯ͸΋͏গ͠ৄ͘͠Λ࣍ʹਤղ͍ͯ͠ΔͷͰࢀߟʹͯ͠΄͍͠ɻ • 8 ͷείΞϘʔυ͸ࢀՃऀʷ՝୊ਐ௙ঢ়گͷϦετը໘͕ͩɺڧ͍ͯղઆ͢Ε͹ࢀՃऀͱ՝୊ ͕ࢀՃঢ়گ΍՝୊ϦετσʔλʹԠͯ͡ՄมͰಈ͘Α͏ʹ޻෉͢Δ͜ͱ͕ඞཁͩɻ 41
  42. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ 主 催 者

    参 加 者 1.デスクトップ アプリ版 2.WebGL版 5.参加 (JoinRoom) 3.ルーム開設 (CreateRoom) 4.room情報 保存 6.メインモニ ターに反映 11.リザルト 9.event受信 (OnEvent) 成否判定送信 10.進捗情報 保存 12.メインモ ニターに反 映 7.回答 フォーム 8.event 送信 (RaiseEvent) ਤ 4.4: ௨৴ͱ૬ޓͷಈ͖ ਤ 4.4 ʹ͍ͭͯɺ • 1 ͸ɺओ࠵ऀଆͷ PC Ͱ੒൱൑ఆαʔό໾ɺ͔ͭձ৔ͷϝΠϯϞχλʔʹग़͢શνʔϜͷਐ௙ දࣔΛදࣔ͢Δ΋ͷͱͳΔσεΫτοϓΞϓϦέʔγϣϯɻ • 2 ͸ɺ֤ࢀՃऀ͕࣋ࢀ͢Δݸਓॴ༗ PC Λ࢖ͬͯࣗ੮ͰͦΕͧΕ͕࢖͑Δճ౴ೖྗϑΥʔϜͷ WebGLɻ • 3,5,8,9 ͸ɺPhoton Ͱߦ͏ωοτϫʔΫͷରԠͰ͋Γɺjoinroom Ͱ઀ଓ͢Δͱ 6 ͰࢀՃνʔ Ϝ͕૿͑Δɻ • 7 Ͱ͸ɺࢀՃऀ͕ͷࣗ༝ςΩετೖྗϑΥʔϜʢUIʣ͔Βճ౴จࣈྻΛೖྗ͠ɺ11 ͷϦβϧτ ςϩοϓʢUIʣͰ੒൱Λ஌Γɺਖ਼ղ͍ͯ͠Ε͹ 12 ͷϝΠϯϞχλʔ΁൓ө͞ΕΔɻ 42
  43. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ 参加者A 主催者   回答送信

      成否送信   全問正解リザルト送信 ReceiverGroup.Others 成否判定 対象者ID 自身ID == 対象者ID? (リザルト) (スルー) ReceiverGroup.MasterClient 回答文字列 自身ID 全問正解? 自身ID == 対象者ID? (コンプリート リザルト) (スルー) ① ② ③ (スルー) ReceiverGroup.Others 全問正解 対象者ID ਤ 4.5: ճ౴ͱ੒൱ͷૹड৴ϑϩʔ ਤ 4.5 ʹ͍ͭͯɺ • ᶃͰࢀՃऀ͕ೖྗϑΥʔϜ͔ΒจࣈΛʮओ࠵ऀʯʹૹ෇͢Δɻͦͷͱ͖ͷૹ෇৘ใ͸࿮ઢʹه ࡌ͍ͯ͠Δ΋ͷͰ͋Δɻ • ্Λड৴ͨ͠ͱ͖ᶄͰ੒൱൑ఆͱૹ৴ݩ΁ͷૹΓฦ͠Λߦ͏ɻૹ෇৘ใ͸࿮ઢͷͱ͓ΓͰ͋Δ ͕ɺOthers ଐੑͰૹ෇͍ͯ͠Δ͜ͱΛཹҙ͍ͨͩ͘ͱΑ͍ͩΖ͏ɻ • ্Ͱਖ਼ղͩͬͨ৔߹ʹ͸શ໰ਖ਼ղ൑ఆΛᶅͰߦ͏ɻશ໰ਖ਼ղ࣌ʹಛผͳϦβϧτԋग़΍ਐ௙Ϧ ετʹ׬ྃͨ͠දهΛߦ͏౎߹ɺ͜͜Ͱ෼͚ͯॲཧ͢Δɻ ͜͜·Ͱ͘Δͱޙ͸ͻͨ͢Β Unity Ͱ࣮૷ͱςετͱτϥΠΞϯυΤϥʔͰ͋Δɻ ࣮૷ͷ঺հ ͔͜͜Β͸࣮૷಺༰ʹ͍ͭͯগ͠৮Ε͍ͨͱࢥ͏ɻओʹ Photon ࣮૷෦෼ʹ͍ͭͯղઆ͓ͯ͠ ͜͏ɻ ˞ҎԼ͔Βͷ࣮૷ྫͰ͸ using ͸লུ͍ͯ͠Δ 43
  44. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ Ϧετ 4.1: Photon

    ؅ཧجఈ class 1: public class IAppliPunBehaviour : MonoBehaviourPunCallbacks, IOnEventCallback 2: { 3: // ओͳ࣮૷Ϋϥεɻ΄͔ʹ΋ MonoBehaviourPunCallbacks ͷதΛΈΕ͹ 4: // PhotonϥΠϒϥϦ͔ΒಘΒΕΔCallbackؔ਺͕͋ΔͷͰ୳ͬͯ΄͍͠ɻ 5: public virtual void OnEvent(EventData photonEvent){} 6: public override void OnRoomListUpdate(List<RoomInfo> _roomList){} 7: public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged){} 8: public override void OnPlayerPropertiesUpdate(Player target, 9: Hashtable changedProps){} 10: public override void OnPlayerEnteredRoom(Player newPlayer){} 11: public override void OnPlayerLeftRoom(Player otherPlayer){} 12: public override void OnCreateRoomFailed(short returnCode, string message){} 13: public override void OnJoinRoomFailed(short returnCode, string message){} 14: public override void OnJoinRandomFailed(short returnCode, string message){} 15: public override void OnConnectedToMaster(){} 16: public override void OnJoinedLobby(){} 17: public override void OnLeftLobby(){} 18: public override void OnJoinedRoom(){} 19: public override void OnLeftRoom(){} 20: public override void OnDisconnected(DisconnectCause cause){} 21: } Ϧετ 4.2: ૹड৴؅ཧ࣮૷ class 1: public class MasterReciver : IAppliPunBehaviour 2: { 3: // PhotonͷWebίϯιʔϧ͔Βೖख͢ΔAppId 4: // ຊདྷScriptableObjectͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 5: public static string AppId; 6: // ࢦఆ͢Δ৔߹͸઀ଓ࠶։࣌ʹલճͱಉҰUserͱݟͳͤΔ 7: // ຊདྷηʔϒσʔλͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 8: public static string UserId; 9: // ೖྗϑΥʔϜͰઃఆ͢Δݺশ 10: // ຊདྷೖྗϑΥʔϜ΍ηʔϒσʔλͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 11: public static string NickName; 12: // Photon΁ͷ઀ଓ։࢝ 13: public static void ConnectUsingSettings() 14: { 15: PhotonNetwork.AuthValues = new AuthenticationValues(); 16: PhotonNetwork.AuthValues.UserId = UserId; 17: PhotonNetwork.NetworkingClient.NickName = NickName; 18: PhotonNetwork.PhotonServerSettings.AppSettings.AppIdRealtime = AppId; 19: PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = 20: MasterReciver.AppVersion; 21: PhotonNetwork.ConnectUsingSettings(); 22: } 23: // ͦͷ΄͔֤छPhotonͷΠϕϯτൃಈʹ࿈ಈͯ͠৭ʑ࣮૷͢Δ͕ࠓճ͸লུ 24: // ྫ͑͹ɺOnConnectedToMaster, JoinedLobbyͰը໘ભҠ 25: // OnRoomListUpdate, OnPlayerPropertiesUpdate ·ΘΓͰUIΛߋ৽͢Δॲཧ 44
  45. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ 26: // OnJoinRoomFailed,

    OnDisconnected, ͱ͖ʹ࠶઀ଓμΠΞϩά 27: } ·ͣϦετ 4.1 ͷιʔείʔυʹ͍ͭͯɻ ຊιϑτ΢ΣΞશମͱ Photon ͱͷڮ౉͠ͱͯ͠ Photon ؅ཧΫϥεΛ̍ͭਾ͑ΔɻPhoton Πϕ ϯτ Callback ͕ݺ͹ΕΔΑ͏ʹ Photon.Pun.MonoBehaviourPunCallbacks class Λ೿ੜ͠ɺRai seEvent ͷ௨஌͕ಘΒΕΔΑ͏ʹ Photon.Realtime.IOnEventCallback interface Λ࢖༻͢Δɻ جఈΫϥεͱ࣮૷Λ෼͚Δඞཁੑ͸έʔεόΠέʔε͕ͩࠓճ͸Ϧετ 4.2 ͷΑ͏ʹϚωδϟʔ࣮ ૷ͷͨΊʹ override ͍ͯ͠Δɻಉ͡ Photon ΠϕϯτΛड͚औͬͯߦ͏ΞΫγϣϯҧ͍͕͋Δͱ͖ ʹద͍ͯ͠ΔͩΖ͏ɻ ͦͯ͠ Photon αʔό΁ͷ઀ଓؔ਺Λ ConnectUsingSettings Ͱઃ͚Δɻޙड़ͷ઀ଓ Button ͔ Β࣮ߦͤ͞Ε͹Α͍ɻதͰ Photon ͷ Setting Λॻ͖׵͍͑ͯΔɻ ͦΕҎ֎ͰͷରԠͱͯ͠͸Լʹྫࣔ͢ΔΑ͏ͳը໘ UI ͱΠϕϯτͷඥ͚ͮΛ͢Ε͹Α͍ͩΖ͏ɻ 1. Photon αʔό΁ͷʮ઀ଓ։࢝ ButtonʯΠϕϯτͰ্ͷ઀ଓ։࢝ؔ਺ΛݺͿ 2. Room Ϧετը໘ભҠͱ OnRoomListUpdate ͰϦετ൓өΛ࡞Δ 3. Room ର৅ࢦఆ࣌ʹ Room ໊Ͱ JoinRoom ͢Δ 4. ओ࠵ऀ൛Ͱ͸ೖྗϑΥʔϜ΢Οϯυ΢Λݺͼग़ͤ͹ CreateRoomɺJoinOrCreateRoom Λݺ ΜͰ Room Λ࡞ΕΔΑ͏ʹ͢Δ 5. ओ࠵ऀɾࢀՃऀͦΕͧΕ OnJoinedRoom ͨ͠Βຊฤͷը໘ʹભҠ͢ΔΑ͏ʹ͢Δ 6. ओ࠵ऀ൛͸ࢀՃऀҰཡͱਐ௙දࣔΛߦ͏ͷͰɺ՝୊ҰཡΛࢀՃऀʹૹΔ 7. OnRoomPropertiesUpdateɺOnPlayerPropertiesUpdateɺOnPlayerLeftRoom ͷؔ࿈Ͱ ৘ใ͕ߋ৽͞ΕͨΒ UI ൓ө͢Δ 8. ࢀՃऀ൛͸ɺ՝୊ҰཡΛड͚औͬͨΒ೚ҙ՝୊ʹରͯ͠ճ౴ೖྗ͕Ͱ͖ΔΑ͏ʹೖྗϑΥʔϜ ݺͼग़͠ϘλϯΛ՝୊Ϧετͱͯ͠දࣔ͢Δ ೖྗϑΥʔϜ΢Οϯυ΢͸ɺϑΥʔϜɾૹ৴ɾΩϟϯηϧ͕ஔ͍ͯ͋Ε͹Α͍ɻ Web αʔόɾσʔλϕʔεαʔό͕ͳ͍͜ͱΛ౿·͑ͨྫ֎ॲཧ ྫ֎ॲཧ͸͖ͭ΋ͷͰ͋Δ͕ɺࠓճಛʹେࣄͳ෦෼͸ճઢःஅʹਚ͖Δɻॠஅ΍ Wi-Fi ճઢःஅ ͔Β෮ؼͨ͠৔߹ʹࢀՃঢ়گΛશһཱͯ௚ͤΔ͜ͱ͕ॏཁͰ͋Δɻ͞Βʹճઢͷॠஅͷͨͼʹຊ୊Ͱ ͋ΔࢀՃऀͷ՝୊औΓ૊ΈΛ્֐͠ͳ͍Α͏ʹ͠ͳ͚Ε͹ͳΒͳ͍ɻ 45
  46. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.3 ։ൃ 1. 主催者 回線断絶時

    ❏ 回線切断に備える例外 自動再接続、復帰後にRoomが残っていれ joinして MasterClientを任意に取り戻せる機能をつける 2. 参加者 回線断絶時 自動再接続 3. 全員 回線断絶時 主催者 Room情報とUser進捗情報をストレージ保存、参 加者 UserId値を固定する 5. 主催者ソフトウェア 再起動 3 対策 とおり情報 ストレージ保存な で再起動後でも すべて復旧させる 6. 参加者ソフトウェア 再起動 3 対策 とおりUserId値を固定しておき主催者端末側で マッチさせ再開させる 7. 主催者誤操作によるルーム解除時 主催者がRoomを閉じる操作をしても同一RoomIdxUserId なら再開となることとする 4. 参加者 接続失敗タイムアウト時 ダイアログ再接続 ❏ 回線接続タイムアウトに備える例外 ❏ 再起動対策 ❏ 誤操作対策 ਤ 4.6: ճઢʹؔ͢Δྫ֎ύλʔϯ ϩʔΧϧηʔϒ࣮૷ྫ MasterReciever Ϋϥεͷ OnRoomPropertiesUpdat ࣌ʹԼه onRoomPropertiesUpdate Λݺ ͼग़ͯ͠ਵ࣌อଘ͢ΔΑ͏ʹ࢓ࠐΉɻ Ϧετ 4.3: Room ৘ใอଘ struct 1: [Serializable] 2: public struct RoomPropertySavedata 3: { 4: public List<Pair> datas; 5: [Serializable] 6: public struct Pair 7: { 8: public string k; 9: public string v; 10: } 11: } 46
  47. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.4 ൃల Ϧετ 4.4: UnityChallenge

    Ϋϥε 1: public class UnityChallenge 2: { 3: public void onRoomPropertiesUpdate(Hashtable propertiesThatChanged) 4: { 5: // ओ࠵ऀ͔ࢀՃऀ͔Λ൑ఆ͢ΔԿΒ͔ 6: if (MasterReciver.IsRoomMasterClient) 7: { 8: // game master 9: // ߋ৽͞ΕͨroompropertyΛอଘ༻structʹऔΓ·ͱΊΔ 10: roompropertysavedata = new RoomPropertySavedata() 11: { 12: datas = PunUtility.room.CustomProperties 13: .Where(c => c.Value.GetType() == typeof(string)) 14: .Select(c => new RoomPropertySavedata.Pair() 15: ɹɹɹɹɹɹɹɹɹɹɹɹɹɹ { k = (string)(c.Key), v = (string)(c.Value) }) 16: .ToList() 17: }; 18: // ԿΒ͔ͷࣗલͷηʔϒϢʔςΟϦςΟ (ToJsonͰςΩετอଘ) 19: SavedataUtility.Save(roompropertysavepath, roompropertysavedata); 20: // ओ࠵ऀͷ՝୊Ϧετߋ৽ 21: // example~ 22: } 23: else 24: { 25: // student 26: // ࢀՃऀͷ՝୊Ϧετߋ৽ 27: // example~ 28: } 29: } 30: } 4.4 ൃల Πϕϯτͱͯ͠ͷԋग़Λߟ͑Δ جຊػೳ͕ݟ͑ͨͱ͜ΖͰ࣮ࡍʹ։࠵͞Εͨ࣌ͷ༷ࢠΛΠϝʔδͯ͠ΈΔ͜ͱ͸ͱͯ΋େ੾Ͱ͋ Δɻ͔ͦ͜Βݟ͖͑ͯͨޮՌతͳΠϕϯτԋग़ͱͯ͠ʮα΢ϯυʯͱʮཷΊʯͱ͍͏ཁૉͰ͋Δɻࠓ ճͰ΋কདྷతͰ΋͜ͷ͋ͨΓͱςϩοϓԋग़΍ӡӦʹΑΔ࣮گ͕खް͘Ͱ͖͍͚ͯΕ͹͔ͬ͠Γͨ͠ Πϕϯτײ͕ग़ͯ͘ΔͩΖ͏ɻࠓճಋೖͨͦ͠ͷ̎ͭͷཁૉʹ͍ͭͯͦͷظ଴͢Δͱ͜ΖΛղઆͯ͠ ͓͜͏ɻ·ͣʮα΢ϯυʯͱʮཷΊʯ͸̎ͭ͸ີ઀ͳؔ܎ʹ͋ͬͯɺͦ΋ͦ΋ޮՌతͳα΢ϯυ͕ͳ ͚Ε͹ཷΊ͕੒ཱ͠ͳ͍ͱ͍͏΋ͷͰ͋Δɻࠓճಛผʹ੍࡞ͯ͠ఏڙͯ͘͠Εͨα΢ϯυνʔϜʹ͸ ײँۃ·Γͳ͍ɻ ࡞੒ͨ͠α΢ϯυ͸ҎԼͷ௨Γɻ 47
  48. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.4 ൃల ❏ サウンドリスト no.

    name 画面 用途  image 1 send_message 送信画面 回答送信 (静かに) 2 result_ok テロップ  正解 (静かに) 3 result_ng テロップ  不正解 (静かに) 4 completed_all テロップ  全問正解 (静かに) ਤ 4.7: α΢ϯυϦετ ͜ΕҎ্ͳ͍͘Β͍࠷খݶͳ༁͕ͩɺͦΕͰඞཁे෼Ͱɺͳ͔ͳ͔޷ධͩͬͨɻ#01 ͷ໰͍߹Θͤ ԻͰ͔ͬ͠Γͦͯ͠ҧ࿨ײ͕ͳ͍ΪϦΪϦͷ௕͞ͷʮཷΊʯΛೖΕ͔ͯΒ#02/#03 ͷ੒൱Իͱը໘ς ϩοϓ͕࿈ಈͯ͠͸͖ͬΓͱ੒൱Λࣔ͢༁Ͱ͋Δɻ͜ͷ΄ΜͷҰॠͷ࣌ؒʹਓ͸͔ͳΓଟ͘ͷ͜ͱΛ ߟ͑ΔΒ͘͠ɺۓுͱߴ༲ͦͯ͠ୡ੒ײΛɺճ౴Λೖྗͨ͠ຊਓ͚ͩͰͳ͘ɺձ৔શମʹ΋ɺຯ߹Θ ͤͯ͘ΕΔͱ͍͏͔͚͠Ͱ͋Δɻ ӡ༻ͷޮ཰ԽΛߟ͑Δ ޮ཰తͳӡ༻ͱ͸ɺՔಇ΍σʔλߋ৽͕؆ܿͰߴ౓ͳεΩϧΛඞཁͱͤͣɺࣦഊ͠ʹ͍͘ɺͱ͍ͬ ͨ͜ͱ͕ڍ͛ΒΕΔͩΖ͏͔ɻ௚ۙͷঢ়گ͔Βɺස౓͕ߴ͍ͱΈΒΕΔཁ݅ͱͯ͠͸ʮ՝୊ͷ౴͑ͷ ߋ৽ʯͱ͍͏͜ͱͰ͋ͬͨɻ΋ͪΖΜσʔλϕʔεͳͲͳ͍ɻ·ͨ͞Βʹෳࡶʹ͢Δཁૉͱͯ͠ʮࢀ Ճऀຖʹਖ਼ղ͕ҟͳΔΑ͏ʹ͢Δʯͱ͍͏΋ͷؚ͕·Ε͍ͯΔɻͳΜͱ͍͏͜ͱ͔ɺͭ·Γʮ౴͑Ұ ཡΛࢀՃνʔϜ෼͚ͩଟॏʹ࣋ͭʯʹ౳͍͠Կ͔Λ࣮૷ͨ͠͏͑ͰɺͦΕΛ؆୯ʹ؅ཧ͢Δ͜ͱΛߟ ͑Δඞཁ͕͋Δɻͨͩɺ՝୊ղܾͱ͸ৗʹ݁࿦͔Βฉ͚͹͍ͨͬͯγϯϓϧͳ΋ͷɺ͕ϕετͳͷͰ ͸ͳ͍͔ͱࢥ͏ɻࠓճͱͬͨखஈ͸σʔλςʔϒϧΛ tsv*2ςΩετϑΝΠϧͰ cdn ʹஔ͍ͨͱ͍͏ ΋ͷͰ͋ΔɻӡӦऀͱࢀՃऀͷΈڐՄͷΞΫηε੍ݶ͕͚ͭΒΕΔ͜ͷΑ͏ͳ։࠵؀ڥ΋͍͋·ͬ ͯͱ͍͏͜ͱ΋൱Ίͳ͍͕ඞཁे෼Ͱ͓௼Γ͕͘Δɻͦͷཧ༝ʹɺ՝୊ͱਖ਼ղҰཡΛ Spreadsheet ʢExcel දʣͰ؅ཧ͍ͯͨͨ͠ΊɺදΛίϐʔ&ϖʔετ͢Ε͹ tsv ܗࣜςΩετʹͳΔͷͰ cdn ʹ্ ͛௚ͤ͹࠷ܰྔσʔλγʔτߋ৽ʹͳΔ͜ͱ͔Βɺσʔλϕʔε͍ΒͣͷΈͳΒͣग़ྗϚΫϩ͍Βͣ ͱݴ͏͜ͱ΋͋ͬͨɻͳ͓ಉ͡Α͏ͳ୅ସાஔʹ csv ʹग़͢ͱ͔ɺExcel Λ௚઀ಡΈࠐ·ͤΔͱ͔ɺ json ʹ͢Δͱ͔ɺग़ྗ͔Β cdn ʹ্͛ͯ͘ΕΔҰ࿈ͷԿ͔Λ૊ΉɺͳͲ͞ΒʹखΛֻ͚Δબ୒ࢶ͸ ͋ΔͩΖ͏ɻ·ͨઌʹड़΂ͨΑ͏ʹࢀՃऀผͷਖ਼ղରԠ΋͋ΓɺSpreadsheet ΍σʔλϕʔεͷΑ͏ ͳ΋ͷͰෳ਺ओΩʔͰ؅ཧͰ͖Δߏ଄ͷ΋ͷͰਖ਼ղҰཡΛ؅ཧ͢Δͷ͕࠷దͩɻ *2 ςΩετσʔλʹ͓͍ͯ tab Ͱ۠੾ΒΕͨܗࣜͷ΋ͷɻಉ༷ͷ΋ͷʹΧϯϚͰ۠੾Δ΋ͷΛ csv ͱݺͿɻ 48
  49. ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճ౴γεςϜ 4.4 ൃల (共通正 解文字) Team

    A B C D E F G H I J problem key index 0 TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID ar-1 1 0 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 ar-2 1 1 解答 shader-1 3 0 解答 shader-2 3 1 解答 shader-3 3 2 解答 shader-4 3 3 解答 shader-5 3 4 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 shader-6 3 5 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 multi-1 2 0 解答 multi-2 2 1 解答 multi-3 2 2 解答 multi-4 2 3 解答 multi-5 2 4 解答 ❏ 正解文字テーブル ਤ 4.8: ਖ਼ղσʔλςʔϒϧΠϝʔδ දʹ͍ͭͯɺproblem ྻ͕՝୊ɺkey ྻ͕՝୊ΧςΰϦɺindex ͕՝୊φϯόʔɻ ʮڞ௨ਖ਼ղจࣈʯ͔Β Team AʙJ ·Ͱ͕νʔϜผΛߟྀͨ͠ਖ਼ղςʔϒϧͱͳ͍ͬͯΔɻνʔϜ 0 ʹղ౴͕ೖ͍ͬͯΔߦͷ՝୊͸શνʔϜڞ௨ɺνʔϜผղ౴͕ೖ͍ͬͯΕ͹ݸผղ౴ʹɺͱผΕΔͱ ͢Δɻ ͜ΕΒ͕ߟྀ͞Εͯਖ਼ղσʔλ؅ཧ͕͍ͩͿָʹͳΓ෼཭΋͞Εͨɻͦͷߕ൹͋ͬͯσʔλςʔϒ ϧͷ؅ཧऀ͸͏·͘෼୲Ͱ͖͔ͨͬͯʁɺߋ৽؅ཧ୲౰ऀ͸ࢲ͕ͩԿ͔໰୊ͩͬͨͩΖ͏͔ɻ ऴΘΓʹ ୈ 1 ճ։࠵͕େ͖ͳτϥϒϧͳ࣮͘ݱͰ͖ͨͷ΋ӡӦϝϯόʔʹΑΔ౓ॏͳΔݕূձͱ KPTʢৼ ΓฦΓʹ͍ͭͯͷΑ͋͘ΔϑϨʔϜϫʔΫʣΛ͖ͪΜͱશһͰग़͠߹͑ɺ՝୊࡞੒΍γεςϜʹର͠ ͯ࠷ળΛਚ͔ͤͨ͘Βʹ΄͔ͳΒͳ͍ɻӡӦϝϯόʔશһͷଟେͳΔߩݙͱ׆༂ʹײँ͍ͨ͠ɻͦ͠ ͯͦ͏ԕ͘ͳ͍͏ͪʹ͞ΒʹύϫʔΞοϓͯ͠ୈ 2 ճ͕։࠵͞ΕΔ͜ͱɺͦͯ͠ git challenge ͷΑ ͏ʹ໊෺Πϕϯτʹ੒௕͢Δ͜ͱΛୈ 1 ճϝϯόʔͱͯ͠৺͔Βئ͍ͬͯΔɻ 49
  50. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ ࡞Γํ ຊߘͰ͸

    GitHub API ΍ Slack API ͷΑ͏ͳެ։͞Ε͍ͯΔ Web API ͷΫϥΠΞϯτΛ Haskell ༻ʹɺ؆୯ʹ࡞Δʢࣗ෼ྲྀͷʣํ๏Λ঺հ͠·͢ɻຊߘͰ͸Լهͷ͜ͱΛهड़͍ͯ͠·͢ʢ֤ύο έʔδʹ͍ͭͯ͸ޙड़͠·͢ʣ ɻ • req ύοέʔδΛ࢖ͬͨ API ΫϥΠΞϯτͷ࡞Γํ • extensible ύοέʔδʹΑΔ API ΫϥΠΞϯτ࡞੒ͷิॿ • servant ύοέʔδΛ࢖ͬͨ API ΫϥΠΞϯτͷςετͷॻ͖ํ ·ͨɺ͢΂ͯͷιʔείʔυ͸ GitHub ͷ matsubara0507/haskell-slack-conversations*1 ϦϙδτϦʹ͋Γ·͢ɻ 5.1 Լ४උ ࣮૷Λ࢝ΊΔલʹɺ·ͣ͸ར༻͢Δ Haskell ύοέʔδͷಛ௃ͱɺࠓճ୊ࡐʹ͢Δ API ʹ͍ͭͯ ঺հ͠·͢ɻ ར༻ύοέʔδ ຊߘͰ͸ίΞͳύοέʔδͱͯ͠ҎԼͷ 4 ͭΛར༻͠·͢ɻׅހͷத͸ར༻ͨ͠όʔδϣϯͰ͢ɻ • aeson (1.4.6.0) : JSON σίʔμɾΤϯίʔμ • req (3.0.0) : HTTP ΫϥΠΞϯτ • extensible (0.7.0) : ֦ுՄೳϨίʔυͷಋೖ • servent (0.17) : Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫ *1 https://github.com/matsubara0507/haskell-slack-conversations 51
  51. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ

    ·ͨɺຊߘͰ͸ GHC*2 8.6.5 Ͱಈ࡞֬ೝΛ͍ͯ͠·͢ɻ aeson : JSON σίʔμɾΤϯίʔμ aeson*3 ͸σϑΝΫτελϯμʔυͳ JSON ͷσίʔμɾΤϯίʔμύοέʔδͰ͢ɻaeson Ͱ͸ จࣈྻͰ౉͞Εͨ JSON ΦϒδΣΫτΛதؒߏ଄ Value ܕʹม׵͠ɺ͜ΕΛ೚ҙͷܕ΁ม׵Ͱ͖· ͢ɻValue ܕ͔Βͷม׵Λఆٛ͢Δʹ͸ FromJSON ܕΫϥεͷΠϯελϯεΛఆٛ͢Δඞཁ͕͋Γ ·͢ɻ FromJSON ͷΠϯελϯεྫ import Data.Aeson (FromJSON(..), withObject, (.:)) data ToDo = ToDo { todoID :: Int, title :: Text, done :: Bool } instance FromJSON ToDo where parseJSON = withObject "ToDo" $ \v -> ToDo <$> v .: "id" <*> v .: "title" <*> v .: "done" -- Generics ͱ͍͏ػೳΛ࢖ͬͯ΄ͱΜͲ࣮૷Λল͘͜ͱ΋ՄೳͰ͢ ·ͨɺٯʹ೚ҙͷܕ͔Β JSON ʹม׵͢Δʹ͸ ToJSON ܕΫϥεͷΠϯελϯεΛఆٛ͠·͢ɻ ToJSON ͷΠϯελϯεྫ import Data.Aeson (ToJSON(..), object, (.=)) instance ToJSON ToDo where toJSON todo = object [ "id" .= todoID todo , "title" .= title todo , "done" .= done todo ] req : HTTP ΫϥΠΞϯτ API ΫϥΠΞϯτΛ࡞Δͷʹ͔ܽͤͳ͍ͷ͕ HTTP ΫϥΠΞϯτύοέʔδͰ͢ɻࠓճ͸ HTTP ΫϥΠΞϯτύοέʔδͱͯ͠ req*4 ͱ͍͏΋ͷΛ࢖͍·͢ɻHaskell ʹ͸͍͔ͭ͘ͷ HTTP Ϋϥ ΠΞϯτύοέʔδ͕͋Γ·͢ʢ༗໊ͳ΋ͷʹߜͬͯ΋ෳ਺͋Γ·͢ʣ ɻͦͷதͰ΋ req ͸ൺֱతޙ *2 GHC ͸ 2020 ೥ݱࡏɺ Haskell ͷσϑΝΫτελϯμʔυͳॲཧܥͰ͢ *3 https://hackage.haskell.org/package/aeson-1.4.6.0 *4 https://hackage.haskell.org/package/req-3.0.0 52
  52. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ

    ൃͷϥΠϒϥϦͰ͢ɻͦͷͨΊ req ͸ɺreq ࡞ऀࣗ਎͕΄͔ͷϥΠϒϥϦʹରͯ͠ײ͡Δෆຬ఺Λվ ળ͢Δ໨తͰ࣮૷͞Ε͍ͯ·͢ʢෆຬ఺ʹ͍ͭͯ͸ req ͷ README ʹهड़͞Ε͍ͯ·͢ʣ ɻ req ύοέʔδͷར༻ྫ import Data.Aeson (Value) import Data.ByteString (ByteString) import Network.HTTP.Req displayUserInfo :: ByteString -> IO () displayUserInfo token = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse (oAuth2Bearer token) liftIO $ print (responseBody response :: Value) where url = https "slack.com" /: "api" /: "users.info" extensible : ֦ுՄೳϨίʔυͷಋೖ extensible*5 ύοέʔδ͸֦ுՄೳϨίʔυͷΑ͏ͳɺߴ֊ΧΠϯυͳσʔλܕΛ؆୯ʹѻ͑Δ ύοέʔδͰ͢ɻߴ֊ΧΠϯυʹ͍ͭͯ͸͜͜Ͱ͸ׂѪ͠·͕͢ɺͨͱ͑͹લड़ͨ͠ ToDo ܕΛ extensible Λ࢖ͬͯ࣍ͷΑ͏ʹهड़Ͱ͖·͢ɻ extensible ه๏ͷ ToDo ܕ import Data.Extensible type ToDo = Record ’[ "id" >: Int , "title" >: Text , "done" >: Bool ] ϑΟʔϧυʹΞΫηε͢Δ৔߹͸ person^.#name ͷΑ͏ʹͰ͖·͢*6ɻ͜Ε͚ͩͷ৔߹ɺΘ͟ Θ͟ extensible Λར༻͢Δར఺͸͋Γ·ͤΜɻextensible ه๏ʹΑΔϨίʔυܕͷେ͖ͳར఺ͷҰ ͭ͸ɺϨίʔυϑΟʔϧυΛ഑ྻ΍Ϧετߏ଄ͷΑ͏ʹڞ௨ͷॲཧΛద༻ͨ͠͏͑Ͱ৞ΈࠐΊΔ఺ Ͱ͠ΐ͏ɻͨͱ͑͹ɺϑΟʔϧυ໊ʢจࣈྻʣͷϦετΛߏங͢Δؔ਺΍ɺ֤ϑΟʔϧυͷ FromJ SON ܕΫϥεͷΠϯελϯεΛ΋ͱʹͯ͠ JSON ΦϒδΣΫτʢValue ܕʣΛߏங͢Δؔ਺ͳͲ ͕Ͱ͖·͢ɻޙऀ͸·͞ʹͦΕࣗମ͕ FromJSON ܕΫϥεͷΠϯελϯεͷؔ਺Ͱ͋Γɺ͢ͳΘͪ extensible ه๏ͷ͢΂ͯͷϨίʔυ͸ FromJSON ܕΫϥεͷΠϯελϯεͱͳ͍ͬͯ·͢ʢToJSON ΋ಉ༷Ͱ͢ʣ ɻ͜ͷΑ͏ʹ extensible ه๏ͷϨίʔυ͸Ϩίʔυʹରͯ͠ڞ௨ͷॲཧΛ࣮૷͍ͨ͠৔ *5 https://hackage.haskell.org/package/extensible-0.7 *6 ^ . ͸ lens ܥ ύ ο έ ʔ δ ͷ ؔ ਺ Ͱ ͢ https://hackage.haskell.org/package/lens-4.18.1/docs/ Control-Lens-Getter.html#v:-94-. 53
  53. ୈ5ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ ߹ʹ༗ޮͰ͢ɻ servent

    : Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫ servant*7 ͸͍ΘΏΔ Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫͷҰͭͰɺܕͱͯ͠ Web ΞϓϦέʔ γϣϯͷ API Λهड़Ͱ͖·͢ɻ ToDo ΞϓϦΛΠϝʔδͨ͠ API ܕͷྫ import Servant.API type API = "todos" :> Get ’[JSON] [Todo] :<|> "todos" :> ReqBody ’[JSON] Todo :> Post ’[JSON] Todo :<|> "todos" :> Capture "id" Int :> ReqBody ’[JSON] Todo :> Put ’[JSON] () :<|> "todos" :> Capture "id" Int :> Delete ’[JSON] () ΋ͪΖΜ͜Ε͸ API ͷϧʔςΟϯάͷఆ͚ٛͩͰɺ࣮૷͸ผͰ༩͑·͢ɻ͔͠͠ɺJSON ͱͯ͠ ԿΛฦ͔͢ͳͲ͕ܕͱͯ͠ఆٛ͞Ε͍ͯΔͷͰɺͦͷ௨Γʹ࣮૷͠ͳ͍ͱܕݕࠪͰΤϥʔͱͳΓ· ͢ɻຊߘͰ࡞੒͍ͨ͠ओ୊͸ʮެ։͞Ε͍ͯΔ APIʯͷΫϥΠΞϯτ͚ͩͰ͢ͷͰɺservant ͸ςε τͷͱ͖ʹϞοΫαʔόΛ࡞Δ໨తͰ࢖͍·͢ɻ ୊ࡐ: Slack Conversations API ຊߘͷ୊ࡐͱͯ͠ Slack ͷ Conversations API*8 Λ༻͍Δ͜ͱʹ͠·͢ɻSlack API ͸΋ͱ΋ͱ public νϟϯωϧ͔ private νϟϯωϧ͔ DM ͔Ͱ API Λ׬શʹ෼͚͍ͯ·͕ͨ͠ɺ͜ΕΒΛ͢ ΂ͯ౷Ұͨ͠ͷ͕͜ͷ API Ͱ͢ɻͨͩ͠ɺϨεϙϯε JSON ͷߏ଄͸όϥόϥͰ͢ʢ೚ҙͷܕ ʹσίʔυ͢Δܥٽ͔ͤͰ͢Ͷʣ ɻطଘͷ Haskell ͷ Slack API ΫϥΠΞϯτ*9͸ͲΕ΋ɺݱࡏ͜ ͷ API ʹରԠ͍ͯ͠ͳ͍Α͏ͩͬͨͷͰ͍ͭͰʹࣗ࡞͢Δ͜ͱʹ͠·ͨ͠ʢ2020 ೥ 1 ݄ʣ ɻͳ͓ɺ Conversations API ͕Ͱ͖ͨͨΊݩͷνϟϯωϧͷछྨຖͷ API ͸ 2020 ೥ 11 ݄ 25 ೔͔Β࢖͑ͳ ͘ͳΔΑ͏ͳͷͰ஫ҙ͠·͠ΐ͏ɻ $ curl https://slack.com/api/conversations.info?channel=...\&token=... | jq { "ok": true, "channel": { "id": "C4LFB6DE0", "name": "general", "is_channel": true, *7 https://hackage.haskell.org/package/servant-0.17 *8 https://api.slack.com/docs/conversations-api *9 https://github.com/mpickering/slack-api ΍ https://github.com/jpvillaisaza/slack-web 54
  54. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.2 ࣮૷:

    ଟ૬తͳΫϥΠΞϯτ "is_group": false, "is_im": false, ... } } 5.2 ࣮૷: ଟ૬తͳΫϥΠΞϯτ ·ͣ͸τʔΫϯͳͲΛอ࣋ͨ͠ SlackApiClient ܕΛఆٛ͠·͢ɻ SlackApiClient ܕఆٛ module Web.Slack.Conversations.Client ( SlackApiClient -- ஋ίϯετϥΫλ͸ΤΫεϙʔτ͠ͳ͍ , newClient ) import Data.ByteString (ByteString) type Token = ByteString newtype SlackApiClient = SlackApiClient Token newClient :: Token -> SlackApiClient newClient = SlackApiClient SlackApiClient ܕͷ஋ίϯετϥΫλΛΤΫεϙʔτͤͣʹɺઐ༻ͷߏஙؔ਺ newClient Λఆ ͍ٛͯ͠·͢ɻ͜͏͢Δ͜ͱͰɺ֎෦Ϟδϡʔϧ͔Βͷ Token ͷࢀরํ๏͕ͳ͍ͨΊ Token ܕͷར ༻Λ੍ݶͰ͖·͢*10ɻ ͯ͞ɺSlackApiClient ܕ͔Β API ͷ base URL ΍τʔΫϯΛຒΊࠐΜͩϔομͳͲΛੜ੒͢ Δ෦෼Λఆٛ͠·͠ΐ͏ɻ͜ͷ࣌ʹɺ௥ʑϞοΫαʔόͰ΋ར༻Ͱ͖ΔΑ͏ʹܕΫϥεͱͯ͠ఆٛ͠ ͓͖ͯ·͢ɻ Client ܕΫϥε module Web.Slack.Conversations.Client ( Client (..) , SlackApiClient -- ஋ίϯετϥΫλ͸ΤΫεϙʔτ͠ͳ͍ , newClient ) import Network.HTTP.Req (Option, Scheme (..), Url, (/:)) import qualified Network.HTTP.Req as Req *10 ͜ͷΑ͏ʹܕ͚ͩΛެ։͠σʔλίϯετϥΫλΛӅณͨ͠Α͏ͳܕΛ Opaque Data Type ͱݺͿͦ͏Ͱ͢ 55
  55. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ class Client a where type ClientScheme a :: Scheme baseUrl :: a -> Url (ClientScheme a) mkHeader :: a -> Option (ClientScheme a) instance Client SlackApiClient where type ClientScheme SlackApiClient = ’Https baseUrl = const (Req.https "slack.com" /: "api") mkHeader (SlackApiClient token) = Req.oAuth2Bearer token class ҎԼʹ͋Δ type ͸ Type Family ͱݺ͹ΕΔػೳͰɺܕΫϥεͷΠϯελϯεએݴ͝ͱʹ ಺෦Ͱར༻͢ΔܕΛࢦఆͰ͖·͢ɻScheme ͱॻ͍ͯ͋Δ෦෼͸ΧΠϯυʢܕͷܕʣͷࢦఆͰ͢ɻreq Ͱ͸ HTTP ͔ HTTPS ͔ΛܕͰදݱ͓ͯ͠ΓɺͦΕΒͷܕͷΧΠϯυ͕ Scheme Ͱ͢ɻεΩʔϚ ΛܕͰදݱ͍ͯ͠ΔͷͰɺHTTPS ͷܕͰ HTTP ௨৴Λ͢Δ http ؔ਺Λར༻͠Α͏ͱ͢Δͱܕݕ ࠪͰΤϥʔͱͳΓ·͢ɻΘ͟Θ͟ܕΫϥεͰεΩʔϚΛࣗ༝ʹઃఆͰ͖ΔΑ͏ʹ͍ͯ͠Δͷ͸ɺϞο Ϋαʔόͷ࣌ʹ͸ HTTP Λઃఆ͢ΔͨΊͰ͢ɻ 5.3 ࣮૷: API ͷઃܭΛܕͰ͢Δ ͯ͞ɺ͔͜͜Β͕͍Α͍Αຊ୊Ͱ͢ɻຊߘͰ͸ conversations.info API *11Λ࡞ͬͯΈ·͢ʢ΄ ͔ͷ࣮૷͸ׂѪ͠·͢ʣ ɻ ϨεϙϯεͷܕΛ࡞Δ ࠷ॳʹϨεϙϯεͷܕ Conversation Λఆٛ͠·͢ɻυΩϡϝϯτʹΑΔͱ conversations.in fo ͸࣍ͷΑ͏ͳ JSON ͕ฦͬͯ͘ΔΑ͏Ͱ͢ɻ conversations.info ͷϨεϙϯε JSON ͷྫ { "ok": true, "channel": { "id": "C012AB3CD", "name": "general", "is_channel": true, "is_group": false, "is_im": false, "created": 1449252889, "creator": "W012A3BCD", "is_archived": false, "is_general": true, "unlinked": 0, *11 https://api.slack.com/methods/conversations.info 56
  56. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ "name_normalized": "general", "is_read_only": false, "is_shared": false, "parent_conversation": null, "is_ext_shared": false, "is_org_shared": false, "pending_shared": [], "is_pending_ext_shared": false, "is_member": true, "is_private": false, "is_mpim": false, "last_read": "1502126650.228446", "topic": { "value": "For public discussion of generalities", "creator": "W012A3BCD", "last_set": 1449709364 }, "purpose": { "value": "This part of the workspace is for fun. Make fun here.", "creator": "W012A3BCD", "last_set": 1449709364 }, "previous_names": [ "specifics", "abstractions", "etc" ], "locale": "en-US" } } Conversation ܕͱͯ͠දݱ͍ͨ͠ͷ͸ "channel" Ωʔͷ JSON ΦϒδΣΫτͷ෦෼Ͱ͢ɻҰ ݟ͜ΕΛ͜ͷ··ܕͱͯ͠ॻ͚͹ྑͦ͞͏Ͱ͕͢ɺ1 ର 1 ͷμΠϨΫτϝοηʔδͷ৔߹ͷ JSON ͸ ࣍ͷΑ͏ͳ΋ͷ͕ฦͬͯ͘ΔΑ͏Ͱ͢ɻ conversations.info ͷϨεϙϯε JSON ͷμΠϨΫτϝοηʔδͷͱ͖ͷྫ { "ok": true, "channel": { "id": "C012AB3CD", "created": 1507235627, "is_im": true, "is_org_shared": false, "user": "U27FFLNF4", "last_read": "1513718191.000038", "latest": { "type": "message", "user": "U5R3PALPN", "text": "Psssst!", 57
  57. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ "ts": "1513718191.000038" }, "unread_count": 0, "unread_count_display": 0, "is_open": true, "locale": "en-US", "priority": 0.043016851216706 } } શવҧ͍·͢ͶɻࠔΓ·ͨ͠ɻ͠ΐ͏͕ͳ͍ͷͰɺID Ҏ֎͸ΦϓγϣφϧͳϑΟʔϧυͱ͍͏͜ ͱʹͯ͠ఆٛ͠·͢ɻ Conversation ܕఆٛ module Web.Slack.Conversations.Type where type ChannelID = Text type UserID = Text type Conversation = Record ’[ "id" >: ChannelID , "name" >: Maybe Text , "is_channel" >: Maybe Bool , "is_group" >: Maybe Bool , "is_im" >: Maybe Bool , "created" >: Maybe Int64 , "creator" >: Maybe UserID ... -- ׂѪ ] Ϩεϙϯε JSON ΛΑ͘ݟΔͱ "ok":true ͱ͍͏ͷ͕෇͍͍ͯ·͢Ͷɻ࣍͸͜ͷ෦෼Λܕఆٛ ͠·͢ɻ ΤϥʔܕͰϥοϓ͢Δ "ok":true ͱ͍͏͜ͱ͸ɺԿ͔Ͱࣦഊͨ͠৔߹͸ "ok":false ʹͳΔͰ͋Ζ͏͜ͱ͸༰қʹ૝૾ Ͱ͖·͢ɻͦͷ৔߹͸ͳΜͱ࣍ͷΑ͏ͳ JSON ͕ฦ͖ͬͯ·͢ɻ Τϥʔͷͱ͖ͷϨεϙϯε JSON ͷྫ { "ok": false, "error": "channel_not_found" } 58
  58. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ ͜Ε·ͨ JSON ͷߏ଄͕୅ΘΓ·͢ɻ͔͠΋࢒೦ͳ͜ͱʹϦβϧτίʔυ͸ 200 Ͱฦ͖ͬͯ·͢ɻ req ͸ 400 ൪୆΍ 500 ൪୆ͷϦβϧτίʔυͰ͋Ε͹ඇಉظྫ֎Λ౤͛ͯ͘Ε·͕͢ɺ200 ͷ৔߹͸ ී௨ʹ JSON ͷσίʔυΛࢼΈ·͢ɻͰ͢ͷͰɺ"ok" ͷ஋ΛΈͯσίʔυ͢ΔܕΛ෼ذ͢Δඞཁ͕ ͋Γ·͢ͶɻͦͷͨΊͷܕ Ok Λ࣍ͷΑ͏ʹఆٛ͠·͢ɻ Ok ܕͷఆٛ import qualified Data.Aeson as J import qualified Data.HashMap.Strict as HM data Ok a = Ok a | Err Text deriving (Show, Eq) instance FromJSON a => FromJSON (Ok a) where parseJSON = J.withObject "Result e a" $ \obj -> case HM.lookup "ok" obj of Just (J.Bool True) -> Ok <$> parseJSON (J.Object obj) Just (J.Bool False) -> Err <$> obj .: "error" _ -> fail "key ‘ok:bool‘ is not found." instance ToJSON a => ToJSON (Ok a) where toJSON (Ok a) = case toJSON a of J.Object obj -> J.Object $ HM.insert "ok" (J.Bool True) obj value -> J.Object $ HM.fromList [("ok", J.Bool True), ("value", value)] toJSON (Err e) = J.Object $ HM.fromList [("ok", J.Bool False), ("error", J.String e)] σίʔυͱΤϯίʔυ͕ରশʹͳ͍ͬͯͳ͍ͷ͸গ͠΋΍ͬͱ͠·͕͢க͠ํ͋Γ·ͤΜɻ͜ΕͰ "ok":true ͷ৔߹͸ Ok (Record ’[ "channel" >: Conversation]) ͷܕ͕ฦͬͯ͘Δͱ͍͏ ͷΛఆٛͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ϦΫΤετύϥϝʔλͷܕΛ࡞Δ ϨεϙϯεͷܕΛఆٛͰ͖ͨͷͰ࣍͸ϦΫΤετͷܕΛߟ͑·͠ΐ͏ɻυΩϡϝϯτΛಡΉͱ con versations.info API ʹ͸࣍ͷΑ͏ͳϦΫΤετύϥϝʔλ͕͋Δ͜ͱ͕Θ͔Γ·͢ɻ • channel : νϟϯωϧ ID (ඞਢ) • include_locale : Ϩεϙϯεʹ locale ΛؚΉϑϥάʢσϑΥϧτ falseʣ • include_num_members : Ϩεϙϯεʹ num_members ΛؚΉϑϥάʢσϑΥϧτ falseʣ ϦΫΤετύϥϝʔλʹ͸جຊతʹඞਢͷ΋ͷͱͦ͏Ͱͳ͍΋ͷ͕͋Γ·͢ɻࠓճߟ͑Δͷ͸ඞਢ Ͱͳ͍ํͰ͢ɻ͜ͷΑ͏ͳΦϓγϣφϧͳ΋ͷΛදݱ͢Δͷʹ Haskell Ͱ͸ Maybe a ܕΛ࢖͍·͢ɻ conversations.info ͷϦΫΤετύϥϝʔλܕ module Web.Slack.Conversations.API where import Data.Extensible 59
  59. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ type InfoParams = Record ’[ "include_locale" >: Maybe Bool , "include_num_members" >: Maybe Bool ] -- શͯσϑΥϧτ஋ͷ৔߹͸͜͏ॻ͖·͢ -- #include_locale @= Nothing <: #include_num_members @= Nothing <: nil ͔͠͠ɺͲ͏ߟ͑ͯ΋͜ΕΛ؅ཧ͢Δͷ͸໘౗Ͱ͢Ͷɻ • ύϥϝʔλ਺͕૿͑ͨͱ͖ʹ Maybe Λॻ͖·͘Δඞཁ͕͋Δ • σϑΥϧτ஋Ͱྑ͍ͱ͖ʹύϥϝʔλΛ౉͢ͷ͕໘౗͍͘͞ ͜Μͳͱ͖ʹศརͳͷ͕ extensible ͷ Nullable*12 Ͱ͢ɻ͢΂ͯͷϑΟʔϧυ͕ Maybe a ܕͷ ϨίʔυΛఆٛͯ͘͠Ε·͢ɻ Nullable Λ༻͍ͨ conversations.info ͷϦΫΤετύϥϝʔλܕ import Data.Functor.Identity (Identity (..)) -- type Record = xs :& Field Identity Ͱ͢ type OptionalParams xs = xs :& Nullable (Field Identity) type InfoParams = OptionalParams ’[ "include_locale" >: Bool , "include_num_members" >: Bool ] Nullable ʹ͸࣍ͷΑ͏ͳศརͳؔ਺͕͋Γɺ͜ΕΛ༻͍Δ͜ͱͰ؆୯ʹ෦෼తͳϦΫΤετύϥ ϝʔλΛ౉͢͜ͱ͕Ͱ͖·͢ɻ Nullable ͷศརͳؔ਺ -- શͯ Nothing ͳϨίʔυͱಉٛ vacancy :: xs :& Nullable h -- wrench (#include_locale @= True) ͰҰ෦͚ͩ Just ͷϨίʔυΛ࡞ΕΔ wrench :: xs ˧ ys => (xs :& h) -> ys :& Nullable h ϦΫΤετύϥϝʔλͷܕ͕Ͱ͖ͨͷͰɺ࣍͸࣮ࡍʹ͜ͷܕͷ஋ΛϦΫΤετύϥϝʔλͱͯ͠ req ʹ౉͢ํ๏Λߟ͑·͠ΐ͏ɻreq ͰϦΫΤετύϥϝʔλΛ౉͢৔߹ʹ͸࣍ͷΑ͏ʹॻ͖·͢ɻ *12 https://hackage.haskell.org/package/extensible-0.7/docs/Data-Extensible-Nullable.html 60
  60. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ req ʹϦΫΤετύϥϝʔλΛ౉͢ྫ main :: IO () main = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse $ "id" =: ("ABCDEFG" :: Text) <> "limit" =: (100 :: Int) <> "flag" =: True ... (=:) ԋࢉࢠͰύϥϝʔλΩʔͱ஋Λ݁ͼ෇͚ͯɺ(<>) ԋࢉࢠͰύϥϝʔλͲ͏͠Λ݁ͼ෇͚· ͢ɻϨίʔυͷϑΟʔϧυΛݺͼग़ͯ͠ຖճ͜ΕΒͷԋࢉࢠͰߏஙͯ͠΋ྑ͍Ͱ͕͢ɺউखʹ req ΁ ౉͢ܗࣜʹม׵ͯ͘͠ΕΔؔ਺͕͋Δͱ͏Ε͍͠ͰΑͶʁ ͏Ε͍͠ঢ়گͷྫ import Web.Internal.HttpApiData (ToHttpApiData (..)) hoge :: InfoParams -> IO () hoge params = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse (buildRequestParams params) ... -- (=:) :: (ToHttpApiData a, QueryParam param) => Text -> a -> param -- (<>) :: Monoid param => param -> param -> param buildRequestParams :: (QueryParam param, Monoid param) => OptionalParams xs -> param buildRequestParams = undefined buildRequestParams Λ࡞Δͷ͸؆୯Ͱ͢ɻલड़ͨ͠ͱ͓Γ extensible ͷϨίʔυ͸ϑΟʔϧυ ʹڞ௨ͷॲཧΛద༻ͨ͠͏͑Ͱ৞ΈࠐΊΔͷͰ֤ϑΟʔϧυΛ (=:) ͯ͠ʢڞ௨ͷॲཧʣ͔Β (<>) Ͱ৞ΈࠐΊ͹Α͍ͷͰ͢ɻ buildRequestParams ͷ࣮૷ -- Θ͔Γ΍͢͞ͷͨΊʹ৑௕ʹهड़͓ͯ͠Γ·͢ buildRequestParams’ :: ( Forall (KeyTargetAre KnownSymbol ToHttpApiData) xs -- தͰ (=:) Λ࢖͏ͨΊͷ৚݅ , QueryParam param, Monoid param ) => OptionalParams xs -> param buildRequestParams’ params = hfoldMapWithIndexWith @ (KeyTargetAre KnownSymbol ToHttpApiData) -- TypeApplication ͱ͍͏ͷΛ࢖ͬͯ·͢ (\key value -> case unwrap value of Nothing -> mempty -- mempty ͸ Monoid ͷ୯Ґݩʢa <> mempty = aʣ Just v -> stringKeyOf key =: v ) params 61
  61. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ ͔ͳΓஸೡʹ͍ॻ͍ͨͷͰجຊతʹงғؾͰಡΊΔͱࢥ͍·͢ɻಠಛͳͷ͸ TypeApplication ͱ͍͏ GHC ݴޠ֦ு*13ʹΑͬͯɺKeyTargetAre... ͱ͍͏ͷܕΫϥεΛద༻͍ͯ͠Δ఺Ͱ͢ɻ ͜͏͢Δ͜ͱͰɺhfoldMapWithIndexWith ʹద༻͢Δؔ਺Ͱ KeyTargetAre ಺ͷܕΫϥεʢࠓճ ͸ KnownSymbol ͱ ToHttpApiDataʣʹґଘͨؔ͠਺Λݺͼग़͢͜ͱ͕Ͱ͖·͢ɻ΋ͪΖΜɺͦͷ ͨΊʹ͸ buildRequestParams ʹద༻͢ΔϨίʔυͷ͢΂ͯͷϑΟʔϧυ͕ KeyTargetAre ಺ͷ ܕΫϥεͷΠϯελϯε͡Όͳ͍ͱ͍͚·ͤΜɻ͜Ε͸ܕݕࠪ࣌ʹΤϥʔͱͯ͠ݕग़͞Ε·͢ɻstr ingKeyOf ؔ਺͸ϑΟʔϧυ໊ʢ͜͜Ͱ͸ keyʣ͔ΒจࣈྻΛऔΓग़ؔ͢਺Ͱɺ͜ΕΛ࢖͏ͷʹ Kno wnSymbol ܕΫϥεͷΠϯελϯεͰ͋Δඞཁ͕͋Γ·͢ɻToHttpApiData ܕΫϥε͸ (=:) Λ࢖ ͏ͨΊͰ͢ɻ ͯ͞ɺ͜͜Ͱ໰୊͕Ұͭ͋Γ·͢ɻConversation API ͷϦΫΤετύϥϝʔλͷதʹ͸ types=p ublic_channel,private_channel ͷΑ͏ͳϦετΛཁٻ͢Δͷ͕͋ΔͷͰ͕͢ɺ(=:) ͸Ϧετ ʹରԠ͍ͯ͠·ͤΜɻແཧʹϦετͷΠϯελϯεʹରԠͤ͞ΔͱӨڹ͕େ͖͍ͷͰରԠ൛ͷܕΫϥ εΛಠࣗʹఆٛ͢Δ͜ͱʹ͠·͠ΐ͏ɻ ഑ྻʹରԠͨ͠ buildRequestParams ͷ࣮૷ import Data.Maybe (mapMaybe) import Data.Text (Text, intercalate) class ToHttpApiData’ a where toQueryParam’ :: a -> Maybe Text -- ϦετҎ֎͸ ToHttpApiData ͷ࣮૷Ͱ͢ʢtoQueryParam ͸ ToHttpApiData ͷؔ਺ʣ instance ToHttpApiData’ Int where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ Bool where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ Text where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ a => ToHttpApiData’ [a] where toQueryParam’ [] = Nothing toQueryParam’ xs = Just $ intercalate "," $ mapMaybe toQueryParam’ xs buildRequestParams :: ( Forall (KeyTargetAre KnownSymbol ToHttpApiData’) xs , QueryParam param, Monoid param ) => OptionalParams xs -> param buildRequestParams params = hfoldMapWithIndexWith @ (KeyTargetAre KnownSymbol ToHttpApiData’) (\key value -> case toQueryParam’ =<< unwrap value of Nothing -> mempty Just v -> stringKeyOf key =: v ) params *13 https://downloads.haskell.org/~ghc/8.6.5/docs/html/users_guide/glasgow_exts.html# extension-TypeApplications 62
  62. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮૷:

    API ͷઃܭΛܕͰ͢Δ ͜ΕͰ؆୯ʹϦΫΤετύϥϝʔλΛѻ͑ΔΑ͏ʹͳΓ·ͨ͠ɻ͋ͱ͸ɺϦΫΤετͦͷ΋ͷΛ͠ ͯ͘ΕΔؔ਺Λ࣮૷͢Δ͚ͩͰ͢ɻ ϦΫΤετͷؔ਺Λ࣮૷͢Δ ࣮૷͸͜Μͳײ͡Ͱ͢ɻ ϦΫΤετͷؔ਺ type SlackApiResponse r = JsonResponse (Slack.Ok r) info :: (MonadHttp m, Client c) => c -> ChannelID -> InfoParams -> m (SlackApiResponse (Record ’[ "channel" >: Slack.Conversation ])) info client cid = buildGetApi "info" client . ("channel" =: cid <>) . buildRequestParams ͨͬͨ͜Ε͚ͩͰ͢ɻଞͱॏෳ͢ΔͰ͋Ζ͏࣮૷͸͢΂ͯ buildGetApi ʹ͠·ͨ͠ɻ buildGetApi ؔ਺ͷ࣮૷ buildGetApi :: (MonadHttp m, Client c, FromJSON r) => Text -- ^ request path -> c -- ^ client -> Option (ClientScheme c) -- ^ request params -> m (SlackApiResponse r) buildGetApi path c params = req GET (buildUrl c path) NoReqBody jsonResponse (mkHeader c <> params) buildUrl :: Client c => c -> Text -> Url (ClientScheme c) buildUrl c path = baseUrl c /: "conversations." <> path buildGetApi ͸ GET ϦΫΤετݶఆͰ͢ɻConversations API ʹ͸ GET ͱ POST ͕͋Δͷ ͰɺPOST ൛΋࡞Δඞཁ͕͋Γ·͕͢ɺ΄ͱΜͲಉ͡ͳͷͰׂѪ͠·͢ɻ࣮ࡍʹ GHCi Ͱࢼ͢લʹɺ ࢼ͢ͱ͖ʹศརͳؔ਺Λ༻ҙ͓͖ͯ͠·͠ΐ͏ɻ ϦΫΤετΛ࣮ߦ͢Δศརͳؔ਺ module Web.Slack.Conversations where run :: (MonadIO m, FromJSON r) => Req (SlackApiResponse r) -> m (Either Text r) run request = runReq defaultHttpConfig $ do 63
  63. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ

    result <- request pure $ case responseBody result of Ok r -> Right r Err e -> Left e ͜ΕΛ࢖͏͜ͱͰ run (info ...) Ͱ؆୯ʹ࣮ߦͰ͖·͢ɻ gchi> import Lens.Micro gchi> import Data.Extensible ghci> import Web.Slack.Conversations as Conversations ghci> client = newClient "XXX" -- Slack API Token ghci> Right ch <- run $ Conversations.info client "YYY" vacancy ghci> ch ^. #channel ^. #name Just "general" 5.4 ςετΛهड़͢Δ ͯ͞ɺ͔͜͜Β͸͓·͚Ͱ͢ɻࠓճΑ͏ʹެ։͞Εͨ API ͷΫϥΠΞϯτΛςετ͢Δͱ͖ʹͲ ͏͠·͔͢ʁ Ұͭͷํ๏ͱͯ͠ɺ؆қతͳϞοΫαʔόΛ༻ҙͯ͠ςετ͢Δͱ͍͏ͷ͕͋Δͱࢥ ͍·͢ɻHaskell ͷ৔߹ɺ๯಄Ͱड़΂ͨ servent Λ࢖͏͜ͱͰ؆୯ʹϞοΫαʔόΛ༻ҙͰ͖·͢ɻ ·ͣ͸ϞοΫαʔόΛ༻ҙ͢Δͱ͜Ζ͔Β࢝Ί·͠ΐ͏ɻ ϞοΫαʔόΛ༻ҙ͢Δ લड़ͨ͠ͱ͓Γɺservent Ͱ͸ܕʹΑͬͯ API ͷϧʔςΟϯάΛهड़͠·͢ɻ ϞοΫαʔόͷ API ܕ module Web.Slack.Conversations.Test.MockServer where import qualified Web.Slack.Conversations as Slack type ConversationResult = Record ’[ "channel" >: Slack.Conversation ] type API = "conversations.archive" :> Post ’[JSON] (Slack.Ok (Record ’[ "ok" >: Bool ])) :<|> "conversations.close" :> Post ’[JSON] (Slack.Ok Slack.CloseResule) :<|> "conversations.create" :> Post ’[JSON] (Slack.Ok ConversationResult) :<|> "conversations.history" :> Get ’[JSON] (Slack.Ok Slack.Messages) :<|> "conversations.info" :> Get ’[JSON] (Slack.Ok ConversationResult) ... -- ׂѪ ͯ͞ɺ࣍͸αʔόଆͷ࣮૷Ͱ͢ɻ͠ΐͤΜ͸؆қతͳϞοΫαʔόͰ͢ͷͰɺͲΜͳϦΫΤετύ 64
  64. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ

    ϥϝʔλ͕དྷͯ΋υΩϡϝϯτͷαϯϓϧʹ͋Δ JSON Λฦ͢Α͏ʹ͓͖ͯ͠·͠ΐ͏ɻ ϞοΫαʔόͷ࣮૷ server :: Server API server = returnJsonFile "test/fixture/ok.json" :<|> returnJsonFile "test/fixture/ok.json" :<|> returnJsonFile "test/fixture/conversation.json" :<|> returnJsonFile "test/fixture/messages.json" :<|> returnJsonFile "test/fixture/conversation.json" ... -- ׂѪ where returnJsonFile path = do file <- liftIO (J.eitherDecodeFileStrict path) case file of Right json -> pure json Left err -> throwError $ err500 { errBody = fromString err } returnJsonFile ͸Ҿ਺ͷύεͷ JSON ϑΝΠϧΛಡΈࠐΜͰɺࢦఆ͞Εͨܕʹσίʔυͯ͠ฦ ͍ͯ͠Δؔ਺Ͱ͢ɻϑΝΠϧ͕ͳ͍ɺ͋Δ͍͸ࢦఆͨ͠ܕʹͪΌΜͱσίʔυͰ͖ͳ͍৔߹͸ 500 ͷϦβϧτίʔυΛฦ͍ͯ͠·͢ɻ͜͏͢Δ͜ͱͰɺαϯϓϧίʔυͱఆٛͨ͠ܕͷରԠ͕ͪΌΜͱ ͱΕ͍ͯΔ͔΋͔֬ΊΔ͜ͱ͕Ͱ͖·͢Ͷɻ͋ͱ͸ɺςετͱҰॹʹϞοΫαʔόΛόοΫΤϯυͰ ىಈ͢Δؔ਺Λ༻ҙ͓͖ͯ͠·͢ɻ ϞοΫαʔόΛόοΫΤϯυͰىಈ͢Δؔ਺ import Network.Wai.Handler.Warp (run) mockServer :: IO () mockServer = run 8000 (serve (Proxy @ API) server) runMockServer :: IO () -> IO () runMockServer action = do _ <- forkIO mockServer action αʔόଆΛ༻ҙͰ͖ͨͷͰɺ࢒Γ͸ϞοΫαʔό༻ͷ Client ܕΫϥεͷΠϯελϯεΛ༻ҙ͢Ε ͹४උ͸׬ྃͰ͢ɻ ϞοΫαʔόͷ Client ܕΫϥεͷΠϯελϯε module Web.Slack.Conversations.Test.Client where data TestClient = TestClient instance Client TestClient where type ClientScheme TestClient = ’Http 65
  65. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ

    baseUrl _ = http "localhost" mkHeader _ = mconcat [ port 8000 ] hspec ʹΑΔςετͷهड़ ςετͷهड़ʹ͸ Haskell ൛ RSpec Ͱ͋Δ hspec*14ͱɺΑΓϦονʹςετΛग़ྗ͢Δ tasty*15 ͱ͍͏ύοέʔδΛར༻͠·͢ɻͻͳܕ͸͜Μͳײ͡Ͱ͢ɻ ςετίʔυͷͻͳܕ import qualified Spec.Web.Slack.Conversations.API import Test.Tasty (TestTree, testGroup, defaultMain) import Web.Slack.Conversations.Test.Client (TestClient (..)) import Web.Slack.Conversations.Test.MockServer (runMockServer) main :: IO () main = runMockServer $ defaultMain =<< spec spec :: IO TestTree spec = testGroup "Web.Slack.Conversations" <$> sequence [ Spec.Web.Slack.Conversations.API.specWith TestClient ] ͜͜ͰςετΛָʹهड़Ͱ͖ΔΑ͏ʹิॿؔ਺Λ࣮૷͓͖ͯ͠·͠ΐ͏ɻલઅͰఆٛͨ͠ info ؔ ਺ͱɺϞοΫαʔόͰར༻ͨ͠ JSON ϑΝΠϧͷύεΛ౉͚ͩ͢ͰൺֱͰ͖ΔΑ͏ͳؔ਺Ͱ͢ɻ ςετͷهड़Λָʹ͢Δؔ਺ module Web.Slack.Conversations.Test.Helper where import qualified Data.Aeson as J import Test.Tasty.Hspec (Expectation, expectationFailure, shouldReturn) shouldResponse :: (J.FromJSON r, Show r, Eq r) => Req (JsonResponse r) -> r -> Expectation shouldResponse request expect = do resp <- runReq defaultHttpConfig request responseBody resp ‘shouldReturn‘ expect shouldResponseAs :: (J.FromJSON r, Show r, Eq r) => Req (JsonResponse r) -> FilePath -> Expectation shouldResponseAs request path = do file <- J.eitherDecodeFileStrict path *14 https://hackage.haskell.org/package/hspec *15 https://hackage.haskell.org/package/tasty 66
  66. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.5 ऴΘΓʹ

    case file of Right json -> request ‘shouldResponse‘ json Left err -> expectationFailure err ͜ͷؔ਺Λར༻͢Δ͜ͱͰɺςετ͸؆୯ʹهड़Ͱ͖·͢ɻ API ΫϥΠΞϯτؔ਺ͷςετ module Spec.Web.Slack.Conversations.API where import Test.Tasty import Test.Tasty.Hspec import Web.Slack.Conversations.API as Conversations import Web.Slack.Conversations.Client (Client) import Web.Slack.Conversations.Test.Helper (shouldResponseAs) specWith :: Client c => c -> IO TestTree specWith client = testSpec "Web.Slack.Conversations" $ do describe "info" $ it "should return conversation" $ Conversations.info client "C1234567890" vacancy ‘shouldResponseAs‘ "test/fixture/conversation.json" ࣮૷Ͱ͖ͨ API ͷछྨ͕૿͑ͨΒɺಉ͡Α͏ʹ describe ҎԼΛॻ͚ͩ͘Ͱ͢Ͷɻ $ stack test slack-conversations> test (suite: spec) Web.Slack.Conversations Web.Slack.Conversations info should return conversation: OK (0.05s) All 1 tests passed (0.05s) slack-conversations> Test suite spec passed 5.5 ऴΘΓʹ req ͱ extensible Λར༻ͨ͠ΫϥΠΞϯτ࡞੒͸ 2017 ೥͝ΖʹΞϧόΠτͳͲͰ΍͍ͬͯ·͠ ͨɻࠓճ͸ٱ͠ͿΓʹಉ༷ͷํ๏Ͱ࣮૷ΛࢼΈ͍ͯ·͢ɻ͜ͷύλʔϯ͸ҙ֎ͱ࠶ར༻Ͱ͖Δؾ͕͢ ΔͷͰɺύοέʔδͱͯ͠·ͱΊͯΈͯ΋͓΋͠Ζ͍͔΋Ͱ͢Ͷɻ·ͨɺਖ਼௚ Conversations API ͚ͩΛ੾Γग़ͨ͠ΫϥΠΞϯτ΋ෆศͳͷͰؾ͕޲͍ͨΒ΋ͬͱଟ͘ͷ Slack API ʹରԠͨ͠΋ͷ 67
  67. ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.5 ऴΘΓʹ

    Λࣗ࡞͢Δ͔΋͠Ε·ͤΜɻ΋͠ɺ2020 ೥ 11 ݄·ͰʹରԠͨ͠ΫϥΠΞϯτ͕ݱΕͳ͔ͬͨΒͥͻ ๻ͷΛ࢖͍ͬͯͩ͘͞ʢextensible Λ࢖͏ඞཁ͕ग़͖ͯͯ͠·͍·͕͢ʣ ɻ 68
  68. ஶऀ঺հ ௡ా ګฏ (ୈ 1 ষ୲౰, GitHub,Medium,Qiita: flatfisher, Twitter: @canoefishing)

    ϥΠϒಈըϓϥοτϑΥʔϜࣄۀ෦ͷαʔόʔαΠυΤϯδχΞɺGCP ͱ Go ݴޠΛ࢖༻͠ ͍ͯΔɻGDG ͱݺ͹ΕΔٕज़ίϛϡχςΟͷΦʔΨφΠβʔΛ͓ͯ͠ΓɺίϛϡχςΟք۾ Ͱ͸ϑΟογϡͱݺ͹Ε͍ͯΔɻ௼Γ͕झຯͳͷͰʮ௼ΓʷςΫϊϩδʔʯͱ͍ͬͨςʔϚʹ औΓ૊Ήͷ͕޷͖ɻ ɹ ϠϚϠλέγ (ୈ 2 ষ୲౰, GitHub: toymany, Twitter: @toyman_jp) ۙ೥͸εϚϗ༻ͷήʔϜ։ൃϓϩδΣΫτʹࢀՃ͍ͯ͠·͢ɻझຯͰΞφϩάΧʔυήʔϜΛ ࡞ͬͨΓɺήʔϜΞϓϦΛ࡞ͬͨΓ͍ͯ͠·͢ɻ ɹ ଠా Ұߦ (ୈ 3 ষ୲౰, GitHub:IKKO-Ohta, Qiita,Twitter: @samayotta) 19 ৽ଔɻϑϩϯτΤϯυͱػցֶशͷೋ଍ͷΘΒ͡Λཤ͍͍ͯΔɻspread ͷ։ൃʹ͔͚ͨ࣌ ؒ͸ʰϖϧιφ̑ RʱҎ্ɺ ʰϑΝΠΞʔΤϜϒϨϜ ෩Ֆઇ݄ʱະຬɻ ɹ ాಹล ً (ୈ 4 ষ୲౰) ։ൃຊ෦ CTO ࣨॴଐɻΤϯδχΞͱͯ͠εϚϗήʔϜ։ൃͱӡӦɺͦΕ͔Βऩӹੑ΍ήʔϜ ੑͱ UX ໘ͷΫϦΤΠςΟϒʹܞΘ͖ͬͯͨɻࠓ͸ϓϩάϥϛϯάڭҭʹ͍ͭͯͷֶशιϑ τͱΧϦΩϡϥϜΛࣗࣾ಺Ͱ୯ಠͰ࡞Γɺதɾߴߍੜʹ޲͚ͯϓϩάϥϛϯάߨ࠲ͷߨࢣ΋͠ ͍ͯΔɻ ɹ দݪ ৴஧ (ୈ 5 ষ୲౰, GitHub: matsubara0507) ॴଐ͸ϞϯεταʔόνʔϜͰ Ruby Λॻ͍ͯΔɻϓϩάϥϛϯά͕޷͖Ͱɺීஈ͸ਪ͠ݴ ޠͷ Haskell Ͱ༡ΜͩΓɺ৽͍͠ϓϩάϥϛϯάݴޠΛษڧͨ͠Γ͍ͯ͠ΔɻHaskell-jp ΍ Elm-jp Ͱͪΐͪ͜ΐ͜׆ಈ΋͍ͯ͠Δɻ ɹ 69
  69. ෇࿥ ஶऀ঺հ ౔԰ խ (σβΠφʔ, Twitter: @miyabt_, note: miyabt) ࠓճ΋͓੠͕͚͍͖ͨͩσβΠϯΛ୲౰͍͖ͤͯͨͩ͞·ͨ͠ɺ͋Γ͕ͱ͏͍͟͝·͢ʂ

    ٕ ज़ॻ΋ 5 ࡭໨ͱ͍͏͜ͱͰɺօ͞Μͷࣥචྗ͕ૉఢͰ͢Ͷʂ σβΠϯ΋ָ͓͠Έ͍͚ͨͩͨ Βخ͍͠Ͱ͢ʂ ɹ ਿా ֆඒ (ϓϩδΣΫτਪਐɾ੍࡞ਐߦ౳ͷॸ຿, GitHub: esugita, Twitter: @semiemi7) ݩΤϯδχΞͰɺϛΫγΟͰ͸ɺPerl Λ৮ͬͨΓɺΞϓϦΛ৮ͬͨΓɺ৽نࣄۀͷ PM Λ͠ ͨΓͯ͠ɺࠓ͸ɺDevRel νʔϜͰ֤छΠϕϯτΛاըɾӡӦͨ͠Γɺٕज़తͳ஌ݟͷΞ΢τ ϓοτ׆ಈΛαϙʔτͨ͠Γ͍ͯ͠·͢ɻ ɹ تଟ ޭ࣍ (੍࡞Ξγετ, GitHub: kojikita, Twitter: @kojikita) ݩΤϯδχΞͰҎલ͸ web ϑϩϯτଆͷαʔϏε։ൃ΍ΞυςΫͳͲΛ୲౰͍ͯ͠·ͨ͠ɻ ࠓ͸ DevRel νʔϜͰษڧձ΍ΠϕϯτͷӡӦͳͲΛ͍ͯ͠·͢ɻ࠷ۙ͸ษڧձͷੜ์ૹʹྗ Λ͍Ε͍ͯ·͢ɻ 70
  70. mixi tech note #03 2020 ೥ 2 ݄ 29 ೔ɹॳ൛ୈ

    1 ࡮ɹൃߦ ஶɹऀ גࣜձࣾϛΫγΟ ༗ࢤ ൃߦॴ גࣜձࣾϛΫγΟ ҹ࡮ॴ ೔ޫاը ɹ ˜ mixi, Inc.