20191101のJavaScriptに関する記事は20件です。

年末まで毎日webサイトを作り続ける大学生 〜14日目 DOMのノード関係 + mousemoveを学ぶ〜

はじめに

初めまして。
年末まで毎日webサイトを作っている者です。
今日もMDNを見て勉強しましたが、実装は違うものを作りました。
DOMを操作して要素ノードをつけたりテキストノードをつけたりです。
というか、ノードについていまいち理解できていなかったので復習しました。
はやりたいことあったけど実力が及ばず・・・今日は残念な仕上がりになりました。
扱う技術レベルは低いですが、同じように悩んでる初心者の方を勇気付けられれば幸いです。
今日は14日目。(2019/11/1)
よろしくお願いします。

サイトURL

前提

覚えておくといいノードは4つです。全部ではないですが、大体これだけ知っていればwebサイト制作には困らないと思います。

  1. ドキュメントノード (htmlより上の文書の大元になるノード、よって1つしかない)
  2. 要素ノード (pとかdivとか)
  3. テキストノード (pで挟まれている文字とか)
  4. 属性ノード (idとかclassとか、タグの中に書かれているやつ)

やったこと

  1. 灰色の部分にJavaScriptで要素やテキストを加える
  2. マウスの位置を表示する 3.##

本当はマウスを動かすといい感じの丸がついてくるようにしたかったんです・・・。
でもうんともすんとも言わず、結果位置だけの表示に・・・

test.gif

1. 灰色の部分にJavaScriptで要素やテキストを加える

では1から行きます。

JSで要素、テキストを加える方法↓

 <div id="mouseMove"></div>
            let circle = document.getElementById('mouseMove');
            let div = document.createElement('div');
            div.setAttribute('id', 'test');
            let text = '';
            let textNode = document.createTextNode(text);
            div.appendChild(textNode);
            let plusDiv = circle.appendChild(div);

まずcreateElementで要素を作ります。setAttributeで属性を付け加えられます。
createTextNodeは要素ノードの中にあるテキストノードを作る役割です。作ってどこに加えるかはappendChildを使います。

一見、テキストノードを作らなくてもinnerHTMLでいいと思うかもしれませんが、
innnerHTMLは要素ノード内のテキストを丸ごと書き換えてしまうので、+@で文字を加えたい場合はテキストノードを使う必要があります。

要素内にテキストを入れたい場合
- 追加 → createTextNode
- 書き換え → innnerHTML

2. マウスの位置を表示する

基本は大切ということで一応書きます↓

mouseMove.addEventListener('mousemove', function (e) {
                let coordinate = ' (X座標:' + e.pageX + ' Y座標:' + e.pageY + ')';
                eNode.innerHTML = coordinate;
            })

イベントリスナーでmousemoveを指定しています。mouseMoveというIDがつけられたdivの中でマウスが動かされるとマウスの座標を表示します。

3. 5秒ごとに通知する

setIntervalという関数を使いました。この関数は一定間隔で繰り返し処理をしてくれます。

       let plusTime = 5;
            function timeCount() {
                alert('遊び始めてから' + plusTime + '秒経ちました');
                plusTime += 5;
            }
            setInterval(timeCount, 5000);

setInterval(timeCount, 5000);
↑これで呼び出しています

第一引数が呼び出す処理で、第二引数が処理間隔(ms)です。5000なので5秒間隔ですね。

感想

mousemoveでいい感じの丸が動くやつを作りたかったな〜。
多分cssに原因があったはずだから、javascriptだけじゃなくcssもやらないと。
明日も頑張ります!

最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。

参考

  1. MouseEvent - Web API | MDN (https://developer.mozilla.org/ja/docs/Web/API/MouseEvent)

参考になりました。ありがとうございます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript Vue.js + Firebase 教材まとめ

はじめに

javascriptとVue.jsを勉強する上で有力な教材をまとめました。
GeekSalonの教材以外でも、とてもためになるqiitaの記事やサイトがありますのでどんどん活用しましょう!
ゼミの課題をやり終えてもっと勉強したい人や、基礎的なところから勉強しなおしたい人は、
是非自分の求める教材を探してみてください!
また、後半にはfirebaseの教材のリンクもまとめているので興味のある方は読んでみてください。

javascriptを学ぶ

  • プロゲート : 基礎, 復習
    プロゲートとは、初心者向けのプログラミング学習サービスです。
    ブラウザ上でコードを書いて結果の確認までできるため自分のPC内で環境構築などの準備をしなくてもいい点が魅力です。
    エキスパートコースの教材でもjavascriptの基礎を学ぶことはできますが、プロゲートではとてもわかりやすく丁寧に教えてくれる教材が揃っています。なので、ゼミや教材を読んで少し難しく感じた人にオススメです。復習したい人も是非プロゲートを使ってみてください!

Vue.jsを学ぶ

  • 公式ドキュメント : 基礎
    Vue.jsの公式ドキュメントは日本語翻訳もされていて内容も充実しているので、
    ゼミでも扱うくらいとてもわかりやすいです。 はじめは少し難しく感じてしまう単語があるかもしれませんが、 公式と言うこともあってとても信憑性もあり、Vue.jsを理解するには十分の教材です。
  • Vue-cli : 基礎
    Vue.jsのインストールから、一通りの機能について載っています。
    Vue-cliを簡単に説明すると、Vue.jsの開発を手早く進めるためのシステムです。
    具体的なVue-cliの使い方としてコマンドプロンプト上で以下のコマンドが打てるようになります!とても便利です!
    • vue --version: Vue.jsのバージョンを確認できます。
    • vue create appName: 新しいプロダクトのひな型を作れます。(rails new appみたいに)
    • vue serve: プロダクトのサンプルをローカル環境で確かめることができます。

Firebaseとは

Firebaseには、アプリ開発を助けてくれる機能が備わっています。
主要な機能を下にいくつかまとめてみました。

  • 認証(Firebase Authentication)
    ユーザーのアカウント登録やログインを簡単に実装することができる機能です。 またSNS認証では、Google、Facebook、Twitter、GitHubのアカウントが利用できます。
    Firebase Authentication公式ドキュメント
  • リアルタイムデータベース(Firebase Realtime Database)
    Firebaseが提供するデータベースです。データベースでは、データを保存したり管理したり簡単に編集して使うことができる機能です。
    Firebase Realtime Database公式ドキュメント
  • ホスティング(Firebase Hosting)
    作ったプロダクトをデプロイすることができます。 ちなみにデプロイとは、簡単に言うとプロダクトを公開(リリース)するという意味です。
    Firebase Hosting公式ドキュメント

その他にもアプリケーションを作る上で便利なサービスがあるので、
是非興味があれば調べてみてください!

Vue.js + Firebaseを学ぶ

  • Firebaseのセットアップとホスティング : 基礎
    このサイトでは、Firebaseの大まかな使い方とホスティングの機能について学べます。
    Firebaseのホスティング機能を使い、アプリケーションをデプロイ(公開)する行程を知ることで、Firebaseに慣れることと、ある程度Firebaseとはどうゆうものなの理解することができます。
  • Firebase Authentication ユーザー認証 : 基礎
    FirebaseのAuthentication機能を使って簡単にユーザーアカウントを作ったりログインしたりすることができます。 この記事は、とても丁寧に説明してくれてます。
  • Vue.js + Firebaseでchatを作る : 応用編
    この記事では、Vue.jsとfirebaseを使ってチャットアプリを作るまでを紹介しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 143日目

100日チャレンジの143日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

143日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YYTypeScript#7「他の言語からTSに来てみての感想は?」「チームにTSをどう導入していったか?」「フロント開発環境、Docker使ってる?」「TS開発時の鉄板構成は何?」「Reactのクラスコンポーネントとファンクショナルコンポーネントどう違う?」「フロントエンドで採用すべき、おすすめアーキテクチャ」

これは2019年11月1日に開催したTypeScriptイベントYYTypeScript#7のイベントレポートです。

YYTypeScriptは一言で「TypeScripterの部室」です。発表者の話を聞く「一方向的な勉強会」とは真逆で、TypeScriptについて、雑に・ゆるく・ワイワイ話しながらTypeScripter同士の交流を深める「双方向的な座談会」の形式になります。集まった人たちで「今日話たいこと」「聞きたいこと」をいくつか挙げていき、それをテーマに雑談していきます。

今回の配信動画

過去回の配信動画YouTubeプレイリスト「YYTypeScript」
前回YYTypeScript#6「JSのプロジェクトにTypeScriptを入れるときの知見」「aspida.jsはAPIクライアントを自動生成するツール」「WebStormとPrettierの相性悪い?」「TypeScript 3.7では何が変わる」「関数型のテストの書き方」「TypeScriptのエディタはVS CodeかIntelliJか」 - Qiita

雑談

他の言語からTSに来てみての感想は?

TS使う前の言語は何使ってた? TSを使ってみてどういう感想を持ったか?

・・・

  • (かきうち) 一瞬JS → Javaちょっと → PHP → TS
  • (くまもん) Ruby → PHP7 → PHP5.3 ~ PHP5.6 → Go → JS → TS
  • (すいん) JS → VBS → PHP4~5 → Go → Scala → PHP7 → TS
  • (れおりん) Basic → C → Perl → PHP5 → JS → Java → Scala → PHP7 → TS
    • TS型指定が柔軟にできていい
    • 型推論すごい
  • (じま) JS → アセンブラ → JS → Java → C# → めっちゃJS → Java → TS → Java → PHP → TS
    • TSでたてでいろいろサポートしてなくて、ひたすら苦しんだ思い出がある
    • 最近はまた書いてみて、そういうのが少なくなって快適になった
  • (じゅり) JS → Java → PHP5 → Java → JS → TS
    • Java型つけられるので、JavaからPHPに行ったときにいやな気持になった
      • なんでもはいるのが
    • JSもなんでも入るけど、DOM操作が楽しかったので良かった。
    • TSでてきてええやん。型指定できるし。IDE補完してくれるし。
  • (ふじた) JS → PHP → これからTS
  • (やまなか) C → C++ → Java → めっちゃC# → Swift → Kotlin → TS → JS → TS → Rust
    • キーワードがC#と似ているのでとっつきやすさが良かった
    • 違うなと思うところ
      • 型システムがカジュアルにできる
      • C#は最初にごりごり型を決めてから書く感じ
    • JavaScriptに引っ張られるところがあることに気がついた
    • まわりにJavaScript嫌いって人が多くて、TSをオススメしにくくなった
    • DDDがJSだけだと辛かった
      • ドメインを型で表現するというのを気に入ってるので

チームにTSをどう導入していったか聞きたい

仕事でTSを使いたいなと思ったので、仕事でTSをどう導入していったか聞きたい。

  • 自分はしれっと入れました。
    • しれっとTSで作ってました。
      • プロダクトではなくてツール的なものを
    • その後任に人はTSやらないとですよね
      • そうですね
  • わがまま言ってTSを入れさせてもらった
    • 少人数で意見が通りやすく、上司もDartをやったことがあるので寛容的な環境だった。
    • 「JSは型が無いのに等しい言語だし、そもそもTSやらない理由がなくない」と言ったら賛成してもらえた。
  • 長い目で見るのであれば、TSでしっかり型を付けて書いたほうがいいと思います。
    • バグりにくく、リファクタリングしやすい
  • TSの学習コストは掛かるけど、そのデメリットを上回る開発効率アップをアピールするしかないかな。
    • 最終的にJSになるので、新言語とはちょっと違いますしね。
  • 社内で使っているのは自分ひとりだけど、社内Wiki(esa)にちょこちょこTipsを投稿している。
    • そうすると、数人は反応してもらえて、興味を持ってもらえる。
  • まずは自分がある程度できるようにならないと布教もしづらいと思う。
  • 入れた後って、周りの人にも勉強してもらわないとだめじゃないですか?
    • 周りの人が書いたコードが全部 any になっちゃってたりする場合とかどうしてます?
    • 最初は全部 any になってもいいんじゃないですか?
      • おいおい慣れてきたら型をしっかりしていく

フロント開発環境、Docker使ってる?

  • フロントエンドはDockerいらないかも
    • 本番環境はブラウザなので
  • Docker for Macはファイルシステムが遅いため、ソースコード更新をトリガーにしたジョブが遅くなりがち
  • フロントの開発をしているが、Dockerは使ってない。
  • package.jsonにnodeのバージョンを指定できるのでそれで縛っておくこともできる
  • IOのボトルネックが厳しいので、フロントだけはホストというパターンもあるそうです

TS開発時の鉄板構成は何?

先日、TS何それ、という初学者向けに勉強会を開きました。そのとき以下を開発環境として紹介したのですが
もう古かったり?質問者はいまだにgulp, nodemonを使っているのですがもう天然記念物ですか?

  1. とりあえず脳死で ts-node (https://www.npmjs.com/package/ts-node)
    • 鉄板
  2. ちょっと調べて ts-node-dev (https://www.npmjs.com/package/ts-node-dev)
    • ソースコードに変更があったらリロードしてくれる
  3. 俺はgulp, nodemon (https://gulpjs.com/) (https://nodemon.io/)
    • さくっと使うなら良い。使い込む場合難が出る場合あり

・・・

  • denoですか?
    • denoはまだいろいろと問題が多いので、それを乗り越える気合があるならいいかと……
    • TSがそのまま動く。
  • webpack
    • gulpとかを駆逐して、webpack一度になった感が
    • だがふくざつ
  • parcel
    • webpackの複雑さに対する反動的なzero configuration
    • メジャーではないが、たまに見かける印象
    • fuzebox はお亡くなりになったのだろうか
  • tsc
    • 最初はこれで。シンプル。
    • npm install -g typescript
    • tscたたいて、nodeで起動する
    • 最初の1日目はこれでいい、2日目からts-nodeで。
    • ts-node知ってから一度もtsc叩いてないかもw

Reactのclass component vs functional component

React Nativeでclass componentで書いてたら、functional componentに書き直されたので、その違いを聞きたい。

・・・

  • なぜ書き換えたか?書き換えた本人に聞いてみる
    • hooksを使ってみたかったから。
    • ステートレスなコンポーネントを作るときに、HOCなどのデザパタで解決していたが、functitonal componentはひとつの書き方で全部できるから
    • ステートフルな書き方から、ステートレスな書き方に移行していったほうがいいと思ったから。
  • クラスコンポーネントなくなれってこと?

    • クラスコンポーネントの書き方は今後も残るし、サポートされるけど、functional componentのほうが推奨という感じがある。
  • functional だと、「このコンポーネントでは状態を管理しませんよ」「データもらって表示する以上のことはしませんよ」という表明としても機能しそう

    • 逆にclass 使うなら「ここでは状態持ちます」「ここで状態変化を持って、子コンポーネントへ伝播させます」ということになりそうで、状態無い場面でclassを使うと、読む人に不要なプレッシャーを与えそう(const で書ける変数をletで宣言してしまってる的な)

Reactってtsの型補完効くんですか??Vue、あんまり効かなくてつらいです

  • Reactってtsの型補完効くんですか??Vue、あんまり効かなくてつらいです
    • Vueよりは効きやすい感ある
    • Vueもtemplateで補完を効かせる策が無いわけではない
  • Vueじゃなくて、Vuexとテンプレートが型補完が聞かないのでは?
    • 解決方法はいろいろある。だけど導入コストは高め

React NativeとExpo、どっちを使ったらいいか?

使ったことある人がいたら聞きたい。

・・・

  • ケースバイケースな感じがあるが、Expoの場合はAppストア、Google Playへの公開まで面倒みてくれる。
  • ネイティブモジュールはExpoでは作れないので、ネイティブ要件が出てきたら、EjectしてReact Nativeにできる
  • Expoは、Appストアに公開しなくても、アプリを配布できる。
    • Over-the-Airとは違う

フルサーバレスで実装している人がいたら、どういう感じで作っているか聞きたい (れおりん)

いませんでした。

フロントのパフォーマンスチューニング

フレームワークを使うより、素のJSで書いたほうが速いのか?

・・・

パッケージと、そのパッケージの型定義のバージョニングを合わせたい

私的なプロジェクトではパッケージも、@typesも最新版を使うようにしていたためか気がつかなかったのですが、社で使っているNode.jsが古く、最新の@types/nodeを入れるとそのバージョンではまだ追加されていないメソッドが補完で出ているっぽいです。あるパッケージと、@typesのバージョンは合わせるためにはどうすれば?わかりやすいのはあるパッケージとその@typesが同じsemantic versioningをしてくれることですが、そんな保証ないですよね?

"A": "x.y.z",
"@types/A": "x.y.z"
  • むずかしい。。。
  • 公式のやつ使えば大丈夫かもしれないが、第3者が作ったやつだと難しい。
  • 古いパッケージを上げるしかない
  • peerDependencyが書いてあればよかったんですが……

マイクロサービスで困った経験談を聞かせて

マイクロサービスやったことあるひと?

・・・

  • マイクロサービスって
  • マイクロサービスの対義語はモノリシックサービスですかね?
    • 単純に、Monolithic だけですね。

フロントのプロジェクトでおすすめのアーキテクチャーがあれば教えてほしい。

  • 絶賛模索中です。
  • Flux + Containerのプレゼンテーションパターンを悩みながらやってる
  • 一個のアーキテクチャが正解というのではなくて、
    • ステートを管理するならFluxもあるし、最近はクリーンアーキテクチャも使われてるし、いろいろ組み合わせたりしていると思う。
  • フロントだとデザイナーさんとの協業もあるので、デザイナーがいるいないでも、アーキテクチャが変わってくる
  • 正解があるというわけではなく、状況に適したアーキテクチャを選ぶのがいいと思う。
  • マイクロフロントエンドとかもありますしね。
  • 課題を先に考えて、それに対するソリューションとしてアーキテクチャを選ぶのがまっとう。

参加してよかったこと(参加者の感想)

  • そんな質問されても・・・みたいな質問でも皆さんで考えてもらえるところ
  • 些細な質問に対しても、たくさんの返事をいただいたので良かったです。
  • リモートでも参加可能なのがよかったです
  • 経験豊富な方々の経験やツールの情報が効けたのがよかった
  • TypeScriptだけでなく、アーキテクチャーやエンジニアにとって重要な情報が知られて事です。とにかく満足度は高いです。
  • アーキテクチャの話はおもしろかった、というか共感できるポイントがあって嬉しかった
  • 言語/技術に関係なくエンジニアにとって有用な知見が得られれたのがよかった(ドキュメンテーションの話とかアピールの話とか)

YYTypeScriptは毎週やってます

YYTypeScriptについてワイワイ話したい方は、YYTypeScriptのイベント情報をチェックしてみて下さい。

以上、YYTypeScriptのレポートでした。次回もワイワイやっていきたいと思います! では、また来週!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue + webpack の非nuxt環境でコンポーネントをまるっとrequireする

タイトルのまんまです。 require.context を利用して、或るディレクトリ配下にあるコンポーネントファイル(.vue)をまるっとrequireしたい。

エントリーファイル(main.js)に次のように記述します。

function requireAll (context) {
  const components = {}
  context.keys().forEach((_path) => {
    const name = _path.split('/')
      .filter((s) => !/^\./.test(s))
      .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
      .join('')
      .replace(/\.vue$/, '')
    components[name] = context(_path).default
  })
  return components
}

const components = requireAll(require.context('./pages', true, /\.vue$/))

components にはパス名がPascalCaseされた名前をキーにしたコンポーネントが格納されるので、あとは環境にあわせて煮るなり焼くなり好きにしましょう。

new Vue({
  el: '#app',
  components
})

プロジェクトの都合でnuxtを導入できなかったりする時などに出番があるやもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

6行HTMLブラックジャック

上海が短くならなかったので気晴らしに。

絵文字版


See the Pen
jOOYNQd
by nagtkk (@nagtkk)
on CodePen.


テキスト版


See the Pen
Blackjack6
by nagtkk (@nagtkk)
on CodePen.


再ゲームはリロード(右下の Rerun) で。

なんか最近 CodePen 重いなあ。

github.io のほう
https://nagtkk.github.io/shortcoding/bj6-text.html
https://nagtkk.github.io/shortcoding/bj6-emoji.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?上海のクリア可能パターン生成

?上海?

「上海」とは、積み上げられた麻雀牌の山から、

  • 左右少なくとも一方に隣接する牌が無く、
  • 上に別な牌が積み重なっていない、
  • 同じ柄の牌の組

を取り除いて行き、すべて取り除いたらクリアというパズルゲームです。

基本的には 144枚(数牌+字牌+花牌+季節牌)の牌を使いますが、難易度や盤面設定により様々なバリエーションがあります。牌の積み方も色々。

上海の指南書や攻略サイトなどでは、

  • 左右少なくとも一方に隣接する牌が無く、上に別な牌が積み重なっていない牌

のことを、自由牌と呼ぶようです。

この定義を使って言い換えれば、「上海」は、

  • 積み上げられた麻雀牌の山から、同じ柄の自由牌の組を取り除いて行き、全て取り切ったらクリアというパズルゲーム

ということになります。

説明.png

サンプル: とりあえず遊んでみよう

See the Pen Shanghai by nagtkk (@nagtkk) on CodePen.

大きい画面で
難易度低めの積み方

  • マウスクリックで選択
  • 全部消せたら clear
  • これ以上取ることができない状態(詰み)になったら game over と表示されます。
  • 各種ボタン
    • Undo: 一手戻る
    • Reset: 最初に戻る
    • New: 新規問題を生成
  • 生成される問題は、解答が一つ以上存在します。

本題

前回、四川省をやったのでその流れで上海、なんですけど、

上海は真面目に作ろうとすると結構難しい。

コード自体は割とシンプルに収まるのですが、盤面の生成周りが色々と大変。
少なくとも初心者向きとは言い難い。

と言うわけで、前回と異なり実装例の紹介まで行きません。
基礎部分の作り方と盤面生成の話をメインにしようかと思います。

サンプルの中身が見たい方は CodePen からどうぞ。
描画部分は相変わらず手抜き気味ですが。

あとは四川省でやったショートコーディングも無しです。
上海は色々と理由があって向かないと思っているので。

基礎部分: 盤面状態の表現と自由牌判定

さて、上海を作るには、まず麻雀牌の山をデータとして表現する必要があります。
上海で用いられる麻雀牌の山は、山と言うくらいですから3次元です。
また、単に並んでいるだけではなく牌を縦横「半分ずらして」配置することができます。

やり方は一通りではありませんが、今回は「縦横二倍の密度の盤面」を高さ毎に用意し、
4マスセットで一つの牌を表現することにします。

盤面.png

こうしておけば、「半分ずらし」も問題なく表現できます。
「隣の牌」は、データ上「2マス」離れていることになります。

この形式のデータで自由牌か否かを判定するには、判定対象の座標を (x,y,z) とすると、以下の領域に他の牌があるかを判定すれば良いことになります。

判定.png

柄の判定は、柄 ID でも付けて比較すればよいので略。

これで実行に必要なものは一通りそろいました。

選択された二つの牌が、

  • 同じ柄である
  • どちらも自由牌である

ならば、取り除くだけです。

いやあ楽ちん楽ちん、

とならないところが上海の怖いところ。

乱数による盤面生成の問題点

前回の「四川省」も、今回の「上海」も、単に乱数のみで盤面を生成すると、最初から詰んでる(クリア不能)パターンが発生することがあります。

四川省の時は、実装例では「内部的に一度手当たり次第に解いてみて、解けなかったら再生成」としていました。
ショートコーディング版ではそれすらさぼって、「詰んでたらプレイヤーがリロードしてね」と雑な対処をしていました。
「四川省」は、そもそも初期状態で詰んでいる可能性が低いので、こんなんでも割と遊べてしまいます。

が、「上海」ではそうもいきません。乱数頼りで盤面生成すると、最初から詰んでいるケースが多いのです。

どのくらい詰みやすいかは、牌の積み方で変わってきますが、今回使った積み方 TURTLE (サン電子版では「龍」)では、50個生成して全部クリア不能 みたいなことも起こります。

何らかの対処をしないと「普通に遊べる」状態になりません。

一つのやり方は、盤面をランダムに生成しコンピュータに解かせるプログラムを書いてしまう方法です。
実際に解けたもののみ採用すれば、最初から詰んでいる状態は回避できます。
時間がかかるので、実行時にやるのではなく、事前に作りためて実行時には単にロードするだけ。
ついでに難易度のラベリングなんかもしておけば、ゲームの幅が広がります。
(自動で難易度判定するのは、それはそれで別方向で難しい話ですが)

あるいは、製作者が解いてみて解けたやつを記録するとか、手作業で盤面作るとかいう手もありかもしれません。(大変そう)

今回は別なアプローチで、「必ず一つ以上解が存在する」ように上海の盤面を自動生成する方法を解説しようかと思います。

無地上海を利用した盤面生成

無地上海、という名前は今適当に決めました。
要は「柄のない」(あるいは「柄がすべて同じな」)上海のことです。
柄が無いので、自由牌ならどれでも組にできます。

生成したい普通の上海と同じ牌の積み方で無地上海を作り、自由牌からランダムに組を作って取り除いて行きます。
このとき、取った牌の組を元の位置も含めて覚えておきます。
そして、解いた後に実際に組(ペア)になった牌に後付けで柄を決め、元の形になるように積みなおします。

生成.png

こうして出来上がった上海は、無地上海と同じ手順で解くことができます。
つまり、解が一つ以上存在する、を満たせたわけですね。

上海に限らず、パズルゲームでは「制約緩めて解いてしまって後付けで都合のいい条件を追加する」やり方がいろんな場面で役に立ったりします。自作のパズルゲーム作るときとか。
閑話休題。

これで詰んでない上海が作れた、

と言いたいところですが、これでも上手くいかないのが上海の怖いところ。(二回目)

無地上海にも解いている最中に「詰み」が発生する可能性があるので、完全ランダムではなく、詰みを回避するように解く必要があります。

無地上海の詰む・詰まない

柄のない上海が詰むのは「組が作れない」つまり「自由牌が一つしかない」ためです。

例えば以下のようなケース。

無地詰み.png

最終的に自由牌が 1 になって詰みます。
これらはあくまで一例で、他にも色々と自由牌 1 に至るパターンがあります。

「自由牌が 1 になったその時に詰んでいる」のではない点にご注意ください。
その前段階から、回避手段が存在しないので既に詰んでいます。
つまり、詰み回避のためには、「最終的に自由牌が 1 になるルート」に突入しないことが必要です。

逆に、詰み回避できているパターンも見てみましょう。

無地詰み回避.png

上は、「取り方によっては詰むけれど、順番を間違えなければ詰まない」ケース。
下は、「どうあがいても詰まない」ケースです。

これらは詰むパターンと一体何が違うのか、というのを厳密にやりだすとひたすら長くなるので、十分条件で抑え込んでしまいましょう。

$$ {\rm 最も高い自由牌の高さ} - {\rm 二番目に高い自由牌の高さ} \lt {\rm 自由牌の個数}$$

の時、まだ詰んでいません。そうでない場合、詰んでいる可能性があります。
とりあえず詰み回避条件とでも呼びましょうか。

よくわからんと言う人は、上の図と見比べてみるとわかりやすいかも。
要は、「自由牌 1 」の状態は「高低差を埋めるために必要な自由牌が足りない」時に発生するので、高低差が開きすぎちゃうと取り方工夫しても挽回できなくなるかもよ、ということですね。

初期状態で詰み回避条件に合致していれば、この条件を維持するように取ることで、無地上海は詰まずに解ききることが可能です。

この方法は、十分条件で縛っているので、本来詰まないパターンも一部回避してしまっている(理論的に生成可能なパターンをすべて網羅はできない)ことになります。
実用上十分だろうと思いますが、一手間違えただけで即詰むようなスリルのある上海がやりたい、という場合には、この方法は適していません。

また「高さがある牌の下面には他の牌がある」という前提での話ですので、変則的な上海には適用できませんのでご注意を。

非対応.png

コーナーケース

今回の TURTLE では起こらない話ですが、牌の積み方によっては、実際には解けるけど初期状態で詰み回避条件に合致していない、と言うケースもあります。
その場合は、条件を満たすまで「最も高い自由牌を含む組」を取っていきます。
進めていくと、高低差が維持あるいは減少し、解が存在するならばいずれ条件を満たすようになります。

もし詰み回避条件を満たさないまま詰んでしまった場合。
その牌の積み方には、そもそも解が存在しません。
普通の上海としてもクリアパターンが存在しないことになります。

ちなみに、組の両方とも常に高いものから取っていくという方法でも、解が存在する場合には無地上海を解ききることができます。
ただし最終的に出来上がる上海は、とりあえず上から取っていけばクリアできるイージーモードになります。
ミニゲームとかはそれでもいいのかも。

詰み回避条件の維持

今回サンプルで使った方法は、基本的にはランダムに自由牌の組を選びますが、

$$ {\rm 最も高い自由牌の高さ} - {\rm 二番目に高い自由牌の高さ} + 2 \ge {\rm 自由牌の個数}$$

の時は、取り除く牌を、

  • 一つ目は最も高いもの
  • 二つ目はランダム

にする、というものです。

詰みルートに突入する可能性があるときに、片方を最も高いものから取ることで高低差が開かない(=詰み回避条件を維持する)ようにしています。
コーナーケースの場合にも、最も高い自由牌を含んだ組が選ばれます。

詰む危険性がない時には単にランダムに取っていくので、常に簡単になってしまうという心配はあまりありません。

さて、これで詰まずに無地上海が解けるようになったので、前述の方法で解が一つ以上存在する上海のパターンが作れます。

おつかれさま!

おまけ: 麻雀牌

スタイルシートでごり押し。
若干おかしいけど、まあ、黙ってりゃバレないバレない。

See the Pen Mahjong Tile by nagtkk (@nagtkk) on CodePen.

余談: なぜショートコーディングに向かないのか

一つには、ここまで散々述べてきたように、詰みパターンの回避を入れないとそもそも遊べる状態になりにくいからです。
ショートコーディング的には、その手のものを省いてでも短さを追求する方が「ぽい」と思うのですが、遊べないものを作ってもなあ、と色々と悩みどころ。

で、二つ目に「牌の積み方」が大半を占めてしまうから、というのがあります。

今回のサンプルでは、積み方データは牌の有無を表す 0 or 1の 配列で 32 * 17 * 5 = 2720 要素あります。でかい。

牌は144個しかないので、座標で持つようにすれば 144 要素にできますが、それでもまだでかい。

牌の有無を一行まとめて 32 ビットの整数として扱えば、17 * 5 = 85 要素。一要素当たりのコードが長くなるので効果はいまいち。

で、ここから短くしようと考えると、ランレングスかハフマンか、いや事前に要素間の差分を取ったほうが……それもうショートコーディングっていうかデータ圧縮の話だよね?

面白さが無いとは思いませんが、どんどん別物になってくるので、個人的にはショートコーディングには向いてないなあという結論に至りました。
上海自体は面白いんですけどね。

まとめ

  • 上海のサンプルと基本的な作り方を解説しました。コード解説はサボった。
  • 適当に盤面作るとまともに遊べないぞ!気をつけろ!
  • 事前に盤面作ってもいいけど、楽したいよね。
  • 無地上海使うと盤面生成できるぞ!
  • 無地上海にも詰みがあるぞ!
  • 詰みルート回避しよう!
  • 回避できた!
  • でもショートコーディングに向かない。悲しい。
  • シャンハーイ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲームをつくる:キー入力とスライダーインターフェイスを実装

この記事は、

ヘックス戦略SLFが好きな筆者が、楽しい戦略SLGを自分で作る記録です。

この記事のまとめ

  • Sliderインターフェースの実装をした
  • キー入力インターフェイスの実装をした

こちらから動くものを確認できます。

Sliderインターフェースを実装した

カメラのズーム機能をSliderインターフェースで実装します。
Sliderインターフェースは、操作された際にメソッドをコールバックで呼び出してくれるので、
まずそのメソッドをCameraにアタッチしたcc.Componentに作成します。

  // **********************************************************
  // 開発用カメラズーム
  // **********************************************************
  debugCameraZoom(slider) {
    cc.log(slider.progress);
    CAM_M.SetCameraZoom(slider.progress * 4 + 0.55);
  }
}

コールバックにはcc.Sliderのインスタンスが渡されます。
インスタンスのprogressにスライダー状態に応じて0−1の値が入るので、それを元にいい感じでカメラのズームを調整します。

スライダーコンポーネントの設定。

NodeTreeにスライダーコンポーネントを設定して、Slide Eventを追加します。
cc.Nodeの欄に、先程メソッドを作成したコンポーネントが設置されているノードをドラッグアンドドロップ。
右の欄から該当コンポーネントを選択して、更に右の欄で先程作成したコンポーネントを選択して完成です。

キー入力インターフェイスを実装した

キー入力の初期化は以下のような形で行います。イベントが発生すると、登録したコールバックが呼ばれます。

    // キー入力を検出
    cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, KEY.onKeyDown);
    cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, KEY.onKeyUp);

コールバックメソッドはこんな感じ。

  // *******************
  // キーを押したとき
  // *******************
  static onKeyDown(event) {
    KEY.setKeyFlag(event.keyCode, true);
  }

  // *******************
  // キーを離したとき
  // *******************
  static onKeyUp(event) {
    KEY.setKeyFlag(event.keyCode, false);
  }

 // *******************
  // キーのフラグをセットする
  // *******************
  static setKeyFlag(keyCode, bool) {
    switch (keyCode) {
      case cc.macro.KEY.up: // 上
      case cc.macro.KEY.w:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.up = bool;
        break;

      case cc.macro.KEY.down: // 下
      case cc.macro.KEY.s:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.down = bool; break;

      case cc.macro.KEY.left: // 左
      case cc.macro.KEY.a:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.left = bool;
        break;

      case cc.macro.KEY.right: // 右
      case cc.macro.KEY.d:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.right = bool;
        break;
    }
  }

  // *******************
  // 方向フラグをリセット
  // *******************
  static resetDirectFlag() {
    KEY.up = false;
    KEY.down = false;
    KEY.left = false;
    KEY.right = false;
  }

KeyFlagをセットしておいて、色々なキー入力に関して処理を行います。
複数の方向入力を受け付けないような処理にしています。
カーソル入力の他に、wasdでも動くようにしました。

後は、コンポーネントの中のupdateで、毎フレーム必要な処理を書きます。

static update(dt) {
    if (KEY.left) {
      CAM_M.MoveCamera(-MOVE_SPEED * dt, 0);
    }
    if (KEY.right) {
      CAM_M.MoveCamera(MOVE_SPEED * dt, 0);
    }
    if (KEY.down) {
      CAM_M.MoveCamera(0, MOVE_SPEED * dt);
    }
    if (KEY.up) {
      CAM_M.MoveCamera(0, -MOVE_SPEED * dt);
    }

  }

次はヘックスに地形と地形パラメータをセットしようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲームをつくる:Cocosでキー入力とスライダーインターフェイスを実装

この記事は、

ヘックス戦略SLFが好きな筆者が、楽しい戦略SLGを自分で作る記録です。

この記事のまとめ

  • Sliderインターフェースの実装をした
  • キー入力インターフェイスの実装をした

こちらから動くものを確認できます。
ヘックスのタッチをどのように検出しているかはこちら

Sliderインターフェースを実装した

カメラのズーム機能をSliderインターフェースで実装します。
Sliderインターフェースは、操作された際にメソッドをコールバックで呼び出してくれるので、
まずそのメソッドをCameraにアタッチしたcc.Componentに作成します。

  // **********************************************************
  // 開発用カメラズーム
  // **********************************************************
  debugCameraZoom(slider) {
    cc.log(slider.progress);
    CAM_M.SetCameraZoom(slider.progress * 4 + 0.55);
  }
}

コールバックにはcc.Sliderのインスタンスが渡されます。
インスタンスのprogressにスライダー状態に応じて0−1の値が入るので、それを元にいい感じでカメラのズームを調整します。

スライダーコンポーネントの設定。

NodeTreeにスライダーコンポーネントを設定して、Slide Eventを追加します。
cc.Nodeの欄に、先程メソッドを作成したコンポーネントが設置されているノードをドラッグアンドドロップ。
右の欄から該当コンポーネントを選択して、更に右の欄で先程作成したコンポーネントを選択して完成です。

キー入力インターフェイスを実装した

キー入力の初期化は以下のような形で行います。イベントが発生すると、登録したコールバックが呼ばれます。

    // キー入力を検出
    cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, KEY.onKeyDown);
    cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, KEY.onKeyUp);

コールバックメソッドはこんな感じ。

  // *******************
  // キーを押したとき
  // *******************
  static onKeyDown(event) {
    KEY.setKeyFlag(event.keyCode, true);
  }

  // *******************
  // キーを離したとき
  // *******************
  static onKeyUp(event) {
    KEY.setKeyFlag(event.keyCode, false);
  }

 // *******************
  // キーのフラグをセットする
  // *******************
  static setKeyFlag(keyCode, bool) {
    switch (keyCode) {
      case cc.macro.KEY.up: // 上
      case cc.macro.KEY.w:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.up = bool;
        break;

      case cc.macro.KEY.down: // 下
      case cc.macro.KEY.s:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.down = bool; break;

      case cc.macro.KEY.left: // 左
      case cc.macro.KEY.a:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.left = bool;
        break;

      case cc.macro.KEY.right: // 右
      case cc.macro.KEY.d:
        if (bool === true) {
          KEY.resetDirectFlag();
        }
        KEY.right = bool;
        break;
    }
  }

  // *******************
  // 方向フラグをリセット
  // *******************
  static resetDirectFlag() {
    KEY.up = false;
    KEY.down = false;
    KEY.left = false;
    KEY.right = false;
  }

KeyFlagをセットしておいて、色々なキー入力に関して処理を行います。
複数の方向入力を受け付けないような処理にしています。
カーソル入力の他に、wasdでも動くようにしました。

後は、コンポーネントの中のupdateで、毎フレーム必要な処理を書きます。

static update(dt) {
    if (KEY.left) {
      CAM_M.MoveCamera(-MOVE_SPEED * dt, 0);
    }
    if (KEY.right) {
      CAM_M.MoveCamera(MOVE_SPEED * dt, 0);
    }
    if (KEY.down) {
      CAM_M.MoveCamera(0, MOVE_SPEED * dt);
    }
    if (KEY.up) {
      CAM_M.MoveCamera(0, -MOVE_SPEED * dt);
    }

  }

次はヘックスに地形と地形パラメータをセットしようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペで使うFirebase【Cloud functions編】

概要

Cloud functionsを使う際にコピペで動かしたいのでそのまとめです。

公式ドキュメント
Cloud Functions for Firebase
HTTP トリガー
Storage トリガー
Firestore トリガー
Authentication トリガー

トリガー

HTTP

リクエスト

<functions url>/httpEvent

exports.httpEvent = functions.https.onRequest((req, res) => {
    res.status(200).send(/* params */)
});

GET, POST

<functions url>/httpEvent

exports.httpEvent = functions.https.onRequest((req, res) => {
    switch (req.method) {
        case 'GET':
            /* ... */
            break
        case 'POST':
            /* ... */
            break
        default:
            res.status(400).send("error")
            break
    }
});

クエリーパラメータ取得

<functions url>/httpEvent?param=1234

exports.httpEvent = functions.https.onRequest((req, res) => {
    const param = req.query.param;
    res.status(400).send(param)
});

Storage

正常に作成された場合

exports.registeredFile = functions.storage.object().onFinalize((object) => {
    const fileBucket = object.bucket; // バケットのフルパス
    const filePath = object.name; // ファイルまでのパス
    const contentType = object.contentType; // ファイルのタイプ
    const metageneration = object.metageneration; // メタデータのバージョン?(メタデータが変更されるたびに増えていく)
})

削除された場合

exports.deleteFile = functions.storage.object().onDelete((object) => {
    const fileBucket = object.bucket; // 削除したバケットのフルパス
    const filePath = object.name; // 削除したファイルまでのパス
    const contentType = object.contentType; // 削除したファイルのタイプ
    const metageneration = object.metageneration; // 削除したメタデータのバージョン
})

Firestore

新しいドキュメントが作成されたとき

exports.create = functions.firestore.document('<Collection名>/{ID}').onCreate((snap, context) => {
    const createdValue = snap.data(); // 作成したデータ
})

ドキュメントが更新されたとき

exports.update = functions.firestore.document('<Collection名>/{ID}').onUpdate((snap, context) => {
    const newValue = snap.after.data(); // 更新後のデータ
    const previousValue = snap.before.data(); // 更新前のデータ
})

ドキュメントが削除されたとき

exports.delete = functions.firestore.document('<Collection名>/{ID}').onDelete((snap, context) => {
    const deletedValue = snap.data(); // 削除したデータ
})

Authentication

ユーザー作成時

exports.createUser = functions.auth.user().onCreate((user) => {
    const email = user.email; // email
    const displayName = user.displayName; // user name
});

ユーザー削除時

exports.deleteUser = functions.auth.user().onDelete((user) => {
    /* ... */
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】JavaScript while文/for文/配列/length【備忘録9】

10/29~31で勉強したこと

while文

- 1~100までなど、くり返しの処理を行う際に使う。
- 条件式には、「この値まで繰り返し処理を行う」という式を入力する。
【例】let number=1;
while(number <= 100){
console.log(number);
number += 1;
}

for文

- while文をさらに簡単に書く方法があり、forを使った式を使う。
【例】for(let number=1;number <= 100;number +=1){
console.log(number);
}

【おまけ】number +=1 を簡略化すると number++ という入力でも処理が可能。

for/ifなどを組み合わせた文章

- 例えば、繰り返しの中で3の倍数だけ処理を変えたい場合は下記のように書く。
【例】for(let number=1;number <= 100;number ++){
if(number % 3 === 0){
console.log("3の倍数");
}else{
console.log(number);
}
}

配列

- 同じような種類の値がいくつもあるときは一覧でまとめて管理すると便利。
【例】
定義⇒const fruits=["apple","banana","orange"];
一つだけ処理⇒console.log(fruits[1]);
上書きで代入⇒fruits[0]="grape";

【配列したものを繰り返し処理したい】
const fruits=["apple","banana","orange"];
for(let i = 0 ; i < 3 ; i ++){
console.log(fruits[i]);
}

length

- 配列がたくさんある場合もあり、値がいくつあるのか数えるのが面倒な時など使用できる。
- 配列を定義したあと、console.log(fruits.length);と書くことで値がいくるあるのか数値化される。
- for文も書き換えができる。
【例】配列部分で書いたfor文を書き換えると↓
for(let i = 0 ; i < fruits.length ; i ++);

気を付けること

  • while文章でnumber++;など、追加していく式が抜けると【無限ループ】してしまい、負担がかかってしまう。
  • for分の変数定義や条件の間の記号は:ではなく;なので気を付ける。
  • 配列の定義の際、文字列入力で""を忘れやすい!
  • 配列の一番左は[1]ではなく[0]から始まる!
  • lengthを書く際に、.(ドット)を忘れたり、:で書いてしまうことがあったので気を付ける!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

js-DAY4-ブラウザで入力されたデータをjson形式でDL

Start making apps everyday as a javascript training.
Everything made by scratch.
Limit to make it this app is 1hour.
Not responsive design, show on the desktop screen.

About this app

「データベース使わずにJavascriptだけでjsonファイルをいじいじしてDLできたらな」
こう思ったことはありませんか?

このスクリプトはそれを実現します。
javascriptだけでブラウザ上のフォームに入力された値を配列でjsonファイルとしてDLできます。

DAY3までに”ローカルストレージにデータを入力して保持”をできるようになりました。
そこで、「もしこのデータを何らかの形でローカルに保存できれば」と考えていたところ、jsonファイルでのDLが可能になりました。
これで、仮にローカルストレージがリセットされても、jsonファイル形式でデータを保管して置けますね。

使いどころとしては、、、
データのバックアップが主な用途かなと思います。

jsonファイルとは、JavaScript Object Notationの略で、javascript形式のファイルのことです。中身は配列だったり、連想が配列だったり。データを管理するときに便利です。

Screenshot

スクリーンショット 2019-11-01 15.36.09.png

Code

HTML

index.html
<!doctype html>
<html lang="ja">
<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <!-- original css file -->
  <link rel="stylesheet" type="text/css" href="assets/style.css">
  <title>EverChanginProject</title>
</head>
<body id="body">
  <div class="container-fluid text-center pt-5">
    <!-- contents start -->


    <p>名前 <input type="text" id="name"></p>
    <p>性別     <input type="text" id="url"></p>
    <button id="download">ダウンロード</button>



    <!-- contents end -->
  </div>
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  <!-- original javascript file -->
  <script type="text/javascript" src="assets/js/main.js"></script>
</body>
</html>

Javascript

main.js
window.onload = () => {
  var download = document.getElementById('download');
  var name = document.getElementById('name');
  var url = document.getElementById('url');
  var getData = () =>{
    return {
      name: name.value,
      url: url.value
    };
  };

  function func() {
    var data = getData();
    var blob = new Blob([JSON.stringify(data, null, '  ')],
      {type: 'application\/json'});
    var url = URL.createObjectURL(blob);
    var link = document.createElement('a');
    link.href = url;
    link.download = 'sample.json';
    link.click();
    URL.revokeObjectURL(url);
  };

  download.addEventListener('click', func, false);
};

CSS

style.css
body {
    background-color: #f1f1f1;
}

Point

今回のポイントとなるスクリプト。

function func() {
    var data = getData();
    // json形式でダウンロードするファイルの作成
    var blob = new Blob([JSON.stringify(data, null, '  ')],
      {type: 'application\/json'});
    // ダウンロードリンクの作成
    var url = URL.createObjectURL(blob);
    var link = document.createElement('a');
    link.href = url;
    link.download = 'sample.json';
    link.click();
    URL.revokeObjectURL(url);
  };

Comment

今回はブラウザからローカルへアクションするというスクリプトでした。
通常、javascriptはHTMLに変更を加えることが目的とされているので、意外な用途ではないでしょうか。
json形式でDLしたので、それを再びUPして表示させるということもできそうですね。
これにデータベースを加えると、データのバックアップを1クリックでできそうですね。
javascriptだけで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

js-DAY3-簡易アプリで出現するフォームを操作

Start making apps everyday as a javascript training.
Everything made by scratch.
Limit to make it this app is 1hour.
Not responsive design, show on the desktop screen.

About this app

「ページの移動なくいくつか質問して、その結果次第でデザインを変更したい」
こう思ったことはありませんか?

このスクリプトはそれを解決します。
クイズをいくつか出題し、その結果次第でどんどん枝分かれしていきます。
そして、最後に出現するフォームを変化させます。

ちなみに、今回使用するフォームは実際に送信できちゃいます。
さらに、そのフォームはGoogleフォームを自由にカスタマイズしたものとなっています。
HTML,css,javascriptだけのファイルですが、Googleフォームを連携させることでデータベースなしでフォーム機能を実装できるんです。

Googleフォームは実際のGoogleフォームのformのactionとそのほかのinput要素やtextarea要素のnameそれぞれの値をコピーしてくるだけです。(Googleの開発者ツールでコピーしてくれば良いですね。)

Screenshot

スクリーンショット 2019-11-01 14.47.18.png

Code

HTML

index.html
<!doctype html>
<html lang="ja">
<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <!-- original css file -->
  <link rel="stylesheet" type="text/css" href="assets/style.css">
  <title>EverChanginProject</title>
</head>
<body id="body">
  <div class="container-fluid text-center pt-5">
    <!-- contents start -->


    <!-- question box start -->
    <div id="q1" class="text-center p-3">
      <p class="mb-5">天使と悪魔汝いずれを好むか。</p>
      <div class="row">
        <div class="col-6" onclick="func1(this)"><p>天使</p></div>
        <div class="col-6" onclick="func1(this)"><p>悪魔</p></div>
      </div>
    </div>

    <div id="q2" class="text-center p-3 d-none">
      <p class="mb-5">正義と娯楽汝いずれを好むか。</p>
      <div class="row">
        <div class="col-6" onclick="func2(this)"><p>正義</p></div>
        <div class="col-6" onclick="func2(this)"><p>娯楽</p></div>
      </div>
    </div>

    <div id="q3" class="text-center p-3 d-none">
      <p class="mb-5">権力と知性汝いずれを好むか。</p>
      <div class="row">
        <div class="col-6" onclick="func3(this)"><p>権力</p></div>
        <div class="col-6" onclick="func3(this)"><p>知性</p></div>
      </div>
    </div>
    <!-- question box end -->


    <!-- form box start -->
    <div id="gri" class="d-none">
      <h3 class="mb-5">グリフィンドール!!!!</h3>
      <form action="https://docs.google.com/forms/d/e/1FAIpQLSeonxvUDgauhZEvh4iNSr-BnpvMoTzd8D1T5cfC5Y6BAviFpg/formResponse">
        <div class="mb-4">
          <label for="email">お名前</label>
          <input id="email" type="text" name="entry.1712724130" placeholder="ハリー・ポッター">
        </div>
        <div class="mb-4">
          <label for="msg">入学理由</label>
          <input id="email" type="text" name="entry.1975168500" placeholder="アイツを殺したくて">
        </div>
        <button class="active" type="submit" name="button" value="送信">SENT</button>
      </form>
    </div>


    <div id="hah" class="d-none">
      <h3 class="mb-5">ハッフルパフ!!!!</h3>
      <form action="https://docs.google.com/forms/d/e/1FAIpQLScjh_L0Yv9yVOma0PASvsYWPw6OrbCF6Dh23s_U_7T9qJXcpg/formResponse">
        <div class="mb-4">
          <label for="email">お名前</label>
          <input id="email" type="text" name="entry.1712724130" placeholder="ハリー・ポッター">
        </div>
        <div class="mb-4">
          <label for="msg">入学理由</label>
          <input id="email" type="text" name="entry.1975168500" placeholder="アイツを殺したくて">
        </div>
        <button class="active" type="submit" name="button" value="送信">SENT</button>
      </form>
    </div>


    <div id="rei" class="d-none">
      <h3 class="mb-5">レイブンクロー!!!!</h3>
      <form action="https://docs.google.com/forms/d/e/1FAIpQLSeV0dReY--TLtlP5CCeBrDi7sIazU1aUSxL5eVMjpZn-_WkFg/formResponse">
        <div class="mb-4">
          <label for="email">お名前</label>
          <input id="email" type="text" name="entry.1712724130" placeholder="ハリー・ポッター">
        </div>
        <div class="mb-4">
          <label for="msg">入学理由</label>
          <input id="email" type="text" name="entry.1975168500" placeholder="アイツを殺したくて">
        </div>
        <button class="active" type="submit" name="button" value="送信">SENT</button>
      </form>
    </div>



    <div id="sul" class="d-none">
      <h3 class="mb-5">スリザリン!!!!</h3>
      <form action="https://docs.google.com/forms/d/e/1FAIpQLScUBqhnNfkI874rJgpajeDcZ2x3QN-O7leXOuLKCvuEGEnfcg/formResponse">
        <div class="mb-4">
          <label for="email">お名前</label>
          <input id="email" type="text" name="entry.1712724130" placeholder="ハリー・ポッター">
        </div>
        <div class="mb-4">
          <label for="msg">入学理由</label>
          <input id="email" type="text" name="entry.1975168500" placeholder="アイツを殺したくて">
        </div>
        <button class="active" type="submit" name="button" value="送信">SENT</button>
      </form>
    </div>
    <!-- form box end -->




    <!-- contents end -->
  </div>
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  <!-- original javascript file -->
  <script type="text/javascript" src="assets/js/main.js"></script>
</body>
</html>

Javascript

main.js
var q1 = document.getElementById('q1');
var q2 = document.getElementById('q2');
var q3 = document.getElementById('q3');
var q4 = document.getElementById('q4');

var gri = document.getElementById('gri');
var hah = document.getElementById('hah');
var rei = document.getElementById('rei');
var sul = document.getElementById('sul');

var body = document.getElementById('body');

function func1( item ) {
    q1.classList.add('d-none');
    if ( item.textContent == "天使" ) {
        q2.classList.add('d-block');
    } else {
        q3.classList.add('d-block');
    }
}

function func2( item ) {
    q2.classList.remove('d-block');
    if ( item.textContent == "正義" ) {
        body.classList.add('bk-gri');
        gri.classList.add('d-block');
    } else {
        body.classList.add('bk-hah');
        hah.classList.add('d-block');
    }
}

function func3( item ) {
    q3.classList.remove('d-block');
    if ( item.textContent == "権力" ) {
        body.classList.add('bk-sul');
        sul.classList.add('d-block');
    } else {
        body.classList.add('bk-rei');
        rei.classList.add('d-block');
    }
}

CSS

style.css
body {
    background-color: #f1f1f1;
}
.active {
    background-color: #ff00a3;
    color: #ffffff;
}
.bk-gri {
    background-color: #ff006a;
}
.bk-hah {
    background-color: #ff9900;
}
.bk-rei {
    background-color: #037b6a;
}
.bk-sul {
    background-color: #015814;
}

Point

今回のポイントとなるスクリプト。

function func1( item ) {
    // 質問項目を消す
    q1.classList.add('d-none');
    // if文で回答によって表示する要素を操作
    if ( item.textContent == "天使" ) {
        q2.classList.add('d-block');
    } else {
        q3.classList.add('d-block');
    }
}

Comment

今回は新しいスクリプトは使用しませんでした。
ただ、DOMに対するクラスの追加と削除は頻繁に使うので、覚えておく必要がありますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

人気のSMS APIをまとめてみた

一般にSMSに短縮されたショートメッセージングサービスは、携帯電話間でテキストメッセージを送信するための一般的な方法です。 電子メールや電話などの他の形式の通信と比較して、SMSの送信には、費用対効果、慎重さ、時間消費の削減、高いオープンレートなど、いくつかの利点があります。

SMS API(アプリケーション プログラミング インターフェース)プラットフォームを使用すると、テキストメッセージを送信し、この一般的な通信ソリューションの利点を活用できます。 たとえば、一部のAPIを使用すると、大量のショートメッセージ(通常はメッセージごとに約160文字)を送信し、世界中の潜在的な顧客に費用対効果の高い方法で製品を販売できます。
0.Top10SMSAPI.png

次の4つの主要な基準に基づいて、いくつかのSMS APIを検討しました。

API機能:テキストメッセージの送信にAPIをアピールするさまざまな機能を評価しました。
地理的範囲:各APIの地理的可用性を評価しました。
価格設定:ショートメッセージの送信にAPIを使用するコストを確認しました。
使いやすさ:各APIをアプリケーションに統合する容易さを評価しました。

最終的に、下記人気のSMS APIトップ10のリストを作成しました。

人気のSMS APIトップ10
調査結果をまとめた表です。

API API の機能 地理的範囲 価格 使いやすさ
TeleSign Score API 不正な電話番号を識別するために0〜1,000のスコアを提供します グローバル 毎月1ドルから500ドルまでの有料プラン
TeleSign SMS Verify API 配信通知、リマインダー、ワンタイムパスワード、双方向プライベートSMS、二段階認証 グローバル 毎月1ドルから250ドルまでの有料プラン
Twilio SMS API ショートコードの送信、スマートコンテンツ処理、テキストメッセージ配信インテリジェンス グローバル 毎月10ドルから250ドルまでの有料プラン
Twilio Lookup API 電話番号に関する情報を取得し、地域に対応した電話番号形式を指摘し、発信者名を認識します グローバル 利用毎に0.01ドルから0.005ドルの従量課金プラン
Twilio Verify Phone Number API SMSまたは音声でユーザーに送信される確認コードを要求します グローバル 毎月10ドルから200ドルまでの有料プラン
SMSly – SMS to INDIA API OTP SMS配信サービス、セキュリティで保護されたSMSルート、優先SMSゲートウェイルート、ライブ配信レポートを取得します インドのみ 無料プラン、毎月26ドルから190ドルまでの有料プラン
MessageBird SMS Gateway API テキストメッセージの送受信、メッセージ配信ステータスの確認、無効な電話番号の検索 グローバル 無料
Octopush SMS API 配信レポート、双方向SMS、2要素認証、プッシュSMS、スケジュールされたSMS グローバル 無料
FreeSMS8 API 無制限のテキストメッセージ、グループSMS、分類されたメッセージ、スケジュールされたメッセージを送信する インドのみ 無料

1.TeleSign Score API

1.TeleSign.png

TeleSign Score APIは、電話番号のレピュテーションスコアリングを提供します。これにより、不正行為に使用された電話番号を認識できます。

API機能TeleSign Score APIは、電話番号インテリジェンス、トラフィックパターン、機械学習、および不正な電話番号の検出を支援する国際データコンソーシアムに依存しています。 APIに電話番号を送信すると、評価され、不正リスクのレベルを示すスコアが返されます。 スコアの範囲は0〜1,000で、電話番号をブロック、フラグ、または検証するかどうかを判断するのに役立ちます。

地理的範囲:世界中で利用可能です。

価格:APIの使用は、1か月あたり最大30リクエストまで無料です。 より集中的に使用するには、月額25ドルから月額500ドルまでの有料プランを利用する必要があります。

使いやすさ:APIをアプリケーションに簡単に実装するのに役立つ広範なドキュメントがあります。

2.TeleSign SMS Verify API

2.TeleSignSMS Verify.png

TeleSign SMS Verify APIを使用すると、ユーザーの信頼性を検証し、Webおよびモバイルアプリケーションのセキュリティを強化できます。

API機能TeleSign SMS Verify APIを使用すると、開発者はメッセージ配信通知、リマインダー、ユーザー検証用の1回限りの時間ベースのパスコード、および双方向のプライベート通信機能を統合できます。 APIを使用して、2要素認証を追加し、アプリケーションのセキュリティを強化できます。さらに、このAPIを使用すると、1回限りのパスコードをFacebook Messengerユーザーに送信し、長いSMSメッセージを自動的に分割し、電話番号を整理して配信を増やすことができます。

地理的範囲:TeleSignは、85以上の言語で200以上の国と地域にサービスを提供しています。

価格:アカウントを作成して、TeleSign SMS Verify APIのテストを無料で開始できますが、1か月あたり1,000クレジットに制限されます。より多くのクレジットが必要な場合は、月額10ドルから月額250ドルまでの有料プランを選択できます。

使いやすさ:TeleSignは、メッセージングAPIをアプリケーションに完全に実装できるように、わかりやすいドキュメント、広範な開発者ツール、24時間365日のサポートを提供します。

3.Twilio SMS API

3.Twilio SMS.png
Twilioは、開発者がWebおよびモバイルアプリケーションに通信機能を統合できるようにする広く使用されているプラ​​ットフォームです。 Twilio SMS APIを使用すると、強力なメッセージング機能をアプリケーションに組み込むことができます。

API機能Twilio SMS APIには、SMSの送受信にショートコードを使用する、複数の国の電話番号、MMSリッチメディアサポートなどのスマートコンテンツ処理、メッセージレコード削除、メッセージのキューイングなどのテキストメッセージ配信インテリジェンスなど、いくつかの機能が付属しています。

地理的範囲:Twilio SMS APIを使用すると、世界中のキャリアネットワークとメッセージを送受信できます。

価格:月額10ドルで、APIの使用に対して100,000クレジットを取得できます。より高い与信限度が必要な場合は、月額25ドルから月額250ドルまでのその他の有料プランに登録できます。

使いやすさ:ヘルパーライブラリ、最も一般的なプログラミング言語の包括的なドキュメント、およびアプリケーションにAPIを簡単に埋め込むことができる汎用デバッグツールがあります。

4. Twilio Lookup API

4Twilio Lookup.png

Twilio Lookup APIを使用すると、電話番号に関する追加情報を取得できるため、コミュニケーションの効率を高めることができます。

API機能Twilio Lookup API を使用すると、電話番号に関する情報をプログラムで検索し、テキストメッセージの未配信の可能性を低くしたり、ローカルフレンドリーな電話番号形式を指摘したり、発信者名を認識したりできます。現在、APIは、発信者名、携帯電話会社情報、地域固有の電話番号のフォーマットと検証の3つの主要なデータタイプをサポートしています。

地理的範囲:このAPIを使用すると、世界中のキャリアから電話番号を検索できます。

価格:APIは従量制で課金されます。キャリアを含むリクエストはリクエストごとに0.005ドルかかりますが、発信者名を含むリクエストはリクエストごとに0.01ドルかかります。

使いやすさ:Twilioは、開発者がLookup APIを最大限に活用できるように、幅広い役立つリソースを提供しています。すぐに使用を開始できるように、包括的なドキュメントと詳細なSDKを入手できます。

5. Twilio Verify Phone Number API

5.Twilio Verify Phone.png

Twilio電話番号確認APIを使用すると、電話番号の所有権を検証し、不正なアカウントを減らし、アプリケーションのセキュリティを強化できます。

API機能Twilio Verify Phone Number APIを使用すると、ユーザーのデバイスに送信される確認コードを要求し、受信したコードが本物であることを確認できます。 APIは、SMSまたは音声を介してユーザーに送信されるコードをサポートします。

地理的範囲:APIを使用すると、世界中のキャリアネットワークに確認コードを送信できます。

価格:1か月あたり10ドルで、1か月あたり最大200件の確認を実行できます。 より多くの検証を実行する場合は、月額20ドルから月額200ドルまでのその他の有料プランを購読できます。

使いやすさ:Twilio Verify APIは使いやすいです。 詳細なドキュメントの提供とは別に、すぐに使い始めるのに役立つチュートリアルとSDKがあります。

6. SMSly – SMS to INDIA API

6.SMSly.png
SMSly – SMS to INDIA APIは、テキストメッセージを安価で簡単にユーザーに送信できるシンプルなAPIです。

API機能SMSly – SMS to INDIA APIは、ユーザー認証に使用できるワンタイムパスワード(OTP)SMS配信サービスを提供します。 さらに、APIはセキュリティで保護されたSMSルート、優先SMSゲートウェイルート、およびライブ配信レポートを提供します。

地理的範囲:インドでのみ利用可能です。

価格設定:APIを使用すると、毎月30の無料のSMS / OTPを入手できます。 より多くのメッセージを送信するには、月額$ 25.99から月額$ 189.99の範囲の有料プランを選択できます。

使いやすさ:SMSLyは、堅牢なメッセージング機能を使用してアプリケーションを簡単に過給するのに役立つ、わかりやすいドキュメントとその他の役立つ開発者リソースを提供します。

7. MessageBird SMS Gateway API

7.MessageBirdSMS.png
MessageBirdは、SMS、音声、チャット通信を簡素化するための信頼できるAPIを提供するアムステルダムに本拠を置く通信会社です。

API機能MessageBird SMS Gateway APIを使用すると、開発者はテキストメッセージの送受信をアプリケーションに埋め込むことができます。 すべてのメッセージには一意のIDが付いており、ユーザーはメッセージの配信ステータスを確認できます。 さらに、APIを使用して無効な電話番号を検索し、メッセージを送信しないようにすることもできます。

地理的範囲:MessageBirdには900を超えるグローバルキャリアネットワークがあり、世界中の任意の国または言語でテキストメッセージを送受信できます。

価格:APIは無料で提供されます。 ただし、プレミアムSMSメッセージの送受信は有料です。

使いやすさ:メッセージングAPIは使いやすいです。 詳細なドキュメントの提供とは別に、すぐに使い始めるのに役立つチュートリアルとSDKがあります。

8. Octopush SMS API

8.OctopushSMS.png
Octopushは、ビジネスシステムまたはアプリケーションでテキストメッセージを送信するためのフランス語ベースのオンラインサービスです。

API機能Octopush SMS APIを使用すると、Octopushのインフラストラクチャを活用してテキストメッセージを送信できます。 APIを使用すると、配信レポートの管理、双方向SMSの組み込み、2要素認証の埋め込み、プッシュSMSの送信、スケジュールされたSMSの送信、クレジット残高の確認ができます。 さらに、Octopush APIは、Gmail、Magento、Dropbox、Zendeskなどの300以上のアプリケーションとの統合をサポートしています。

地理的範囲:APIを使用して、世界中の200か国以上に大量のテキストメッセージを送信できます。

価格:APIは無料で提供されます。

使いやすさ:Octopushは、ほとんどの一般的なプログラミング言語で簡単なドキュメントを提供します。 そのため、登録ユーザーは、HTTP、HTTP、またはSMTPプロトコルを使用してSMS APIを簡単に統合できます。

9. FreeSMS8 API

9.FreeSMS.png
FreeSMS8 APIを使用すると、インドの携帯電話番号に無制限のSMSを送信できます。

API機能FreeSMS8 APIを使用すると、無制限の数のテキストメッセージを簡単かつ迅速に送信できます。 グループSMS、分類されたメッセージ、およびスケジュールされたメッセージを送信できます。

地理的範囲:インドでのみ利用可能です。

価格:APIは無料で提供されます。

使いやすさ:統合に関する問題のトラブルシューティングに役立つ、簡単なドキュメントやその他の開発者向けの有益なリソースがあります。

これはRakuten RapidAPIのトップ10ベストSMS APIリストです。 アプリケーションに実装して世界中にテキストメッセージを送信できるAPIを見つけていただければ幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ARでサイコロを投げるまで ~Blenderを使って3Dモデルの作成~JavaScriptでサイコロを動かす~[PlayCanvas / 8th Wall]

はじめに

kotowaza_hato_mamedeppou

前回の記事の続きになります。「PlayCanvas 入門- モデルの作成~ゲームに入れ込むまで

8th Wallとは

8th Wall というARのライブラリ / プラットフォームをご存知でしょうか?これは、WebARを簡単に行う事ができるようになるもので、世界初のモバイルWeb用のARプラットフォームです。
表面の検出や照明の検出が出来る マーカレス・任意の画像をマーカーとして 使用すること可能です。

8th Wallで作れるコンテンツ

サンプル集はGitHubに存在していますが、マーカを使った面白い表現や、オフィス木を生やしたりと面白い表現がいくつかあります。

https://github.com/8thwall/web

8thwalldemo.PNG

8th Wall - Productsをみると各種3D系のフレームワークに対応をしており、

  • A-Frame
  • three.js
  • babylon.js
  • Amazon Sumerian
  • PlayCanvas

に対応しております。

その中でも今回はPlayCanvasと8th Wallを使ってARのコンテンツを出すための手順を紹介します。

サイコロのモデルを作る

作るもの

Blenderを使用してサイコロのfbxのモデルを作る

Blender 2.8を使用しています。Blenderのダウンロードは 公式サイト からできます。
ダウンロードできない方はdice.fbx← ここにサイコロのモデルがあります(自由に利用してください)。

Blenderを開く

1

マテリアルを選択

2
1. UV Editorを選択
2. マテリアルを選択

画像テクスチャを追加

3
1. ベースカラーのを選択
2. 画像テクスチャを選択

3-1
3. 新規を選択

UV展開の図をエクスポートする

4

4-1

  • Aまたはマウスカーソルを使って全選択

5

  • UV Editing → UV
  • UV配置をエクスポート

UV配置にサイコロの絵柄を入れる

6

7

  • ペイントなどのソフトで展開図を開く、今回はFigmaを使用します。

  • 絵柄を入れていきます

8

  • 1~6の目を入れます pngなどでエクスポートする場合には元の展開図の灰色の背景と線は消しておきます。

Blenderに戻りテクスチャをドラッグ・アンド・ドロップ

D_D

作成したUV展開された絵柄をBlenderのUV Editingの画面にドラッグ・アンド・ドロップします。

3Dビューのシェーディングをレンダープレビューかルック開発モードに変更します

9

マテリアルの画面でテクスチャをエクスポートをしたサイコロの絵柄に変更します

10

サイコロの絵柄が表示されます

11

FBXでエクスポート

12

  • 作成したモデルをオブジェクトモードの状態で全選択(Aキー)をしてエクスポートをします。

pathmode

PlayCanvas上に、エクスポートを時のオプションを選択は選択したオブジェクトにチェックを入れるのと、パスモ...をコピーにし、パスモ...右のアイコンをアクティブにします。

PlayCanvasのプロジェクトを作成する

13

https://playcanvas.com/project/631719/overview/ar-world-tracking-starter-kit
8th WallというライブラリのテンプレートとなるプロジェクトがありますのでそちらをForkします。

14

ForkをしたらEditorから開発画面に入ります。

15

WorkdTrackingSceneを選択します

PlayCanvasのプロジェクトが開かれるので8th WallのAPIキーを入れ込む

16

2

  • PlayCanvasのSettings → External Scriptsの設定から????となっている部分に8th WallのダッシュボードからAPIを取得したものを記入

  • 8th Wall

apikey

作成したFBXのモデルをドラッグ・アンド・ドロップ

18

アップロードしたfbxのモデルがjson, テクスチャ,マテリアルとして展開されます。

19

.jsonをゲーム画面にアップロードします。

サイコロに物理演算を加える

222

サイコロに Rigid Body と Collision を追加します

1. RIGID BODY を追加する

追加したサイコロのモデルを選択します。そのモデルに対して「ADD COMPONENTS → RIGID BODY」を追加します。その後 RIGID BODY のタイプをDynamicにします。

RIGID BODY のタイプには 3 種類あります

種類 説明
Dynamic 重力影響を受ける
Kinematic プログラムで制御される(重力の影響を受けない)
Static 動かないけど衝突がする

3 つのタイプがあります。

2. COLLISION を追加する

ADD COMPONENTS →   COLLISION を選択  →   Mesh を選択  →   ASSETS 欄からモデル名.jsonを追加します。

透明な床を追加する

21

床に対しても同様にRIGID BODYとCOLLISIONを追加します。
- RIGID BODYCOLLISION

床を透明にする

22

床を透明にするためにMODELOFFにします。

スクリプトを書き換える

  • 物理演算を加える
  • サイコロを指定

taprecent.jsにこの2つの要素追加します。

エンティティを選択

23

  • XRControllerエンティティを選択します
  • SCRIPTからEditを選択 24
/*jshint esversion: 6, asi: true, laxbreak: true*/

// taprecenter.js: Defines a playcanvas script that re-centers the AR scene when the screen is
// tapped.

const taprecenter = pc.createScript("taprecenter");
taprecenter.attributes.add("dice", { type: "entity" });

// Fire a 'recenter' event to move the camera back to its starting location in the scene.
taprecenter.prototype.initialize = function() {
  this.app.touch.on(pc.EVENT_TOUCHSTART, event => {
    if (event.touches.length !== 1) {
      return;
    }
    this.dice.rigidbody.teleport(0, 5, 0);
    this.dice.rigidbody.applyTorque(
      Math.random() * 10,
      Math.random() * 10,
      Math.random() * 10
    );
    this.app.fire("xr:recenter");
  });
};

こちらに書き換えます。

PlayCanvas Editorに戻る

26

PlayCanvas Editorの方へ戻り、SCRIPT → taprecenterの EDITの横にある ♻のようなマークのPaserボタンをクリックします。

追加したスクリプトについて

taprecenter.attributes.add("dice", { type: "entity" });

diceというエンティティというタイプの属性を追加しています

スクリプト属性 | Learn PlayCanvas

タップされたときの挙動について

this.app.touch.on(イベント名)でタッチされたときのイベントを登録しています。他にもキーボード(this.app.keyboard.on)、マウス(this.app.mouse.on)などでも同様なことができます。

this.app.touch.on(pc.EVENT_TOUCHSTART, event => {
    関数....
});
y軸の座標を5に移動
this.dice.rigidbody.teleport(0, 5, 0);

トルクをランダムに追加しています

this.dice.rigidbody.applyTorque(
    Math.random() * 10,
    Math.random() * 10,
    Math.random() * 10
);

ForceとImpulse | Learn PlayCanvas

サイコロの大きさを変更して起動

28

この状態で起動をするとQRコードが開かれますのでそのQRコードを読み込むことでARで遊ぶことができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

js-DAY2-ページを更新しても再訪問しても同じ言語設定

Start making apps everyday as a javascript training.
Everything made by scratch.
Limit to make it this app is 1hour.
Not responsive design, show on the desktop screen.

About this app

「webサイトを多言語対応する時、一度言語を選択してもらったら、次回の選択を省けるようにしたい」
そう思ったことはありませんか?

そんな悩みを解決するのがローカルストレージです。
ローカルストレージはブラウザに搭載されているミニデータベースのようなものです。
Amazonで一度カートに入れた商品がずっとカートの中に入っていますよね。
ページを更新しても、iPhoneを閉じても。
Amazonのサイトに行けばずっとカートの中にある。
この技術を多言語対応のwebサイトとして応用しています。

このスクリプトは、一度選択した言語を、それからもずっと保存していくことで、何度も翻訳言語を選択する必要をなくしてくれます。

それと、おまけでテーマカラーも同じように保存して置けるようにしておきました。

ローカルストレージとは、ブラウザの一時的に利用できるデータベースのようなものです。
配列や連想配列で値の保存が可能で、5MBほどの容量があります。
一度保存してしまえば、半永久的に値が保存されます。

Screenshot

スクリーンショット 2019-11-01 13.03.13.png

Code

HTML

index.html
<!doctype html>
<html lang="ja">
<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <!-- original css file -->
  <link rel="stylesheet" type="text/css" href="assets/style.css">
  <title>EverChanginProject</title>
</head>
<body id="body">
  <div class="container-fluid">
    <!-- contents start -->


    <div class="mt-5 mb-5">
      <p>あなたが選択した言語は<span id="slclan"></span></p>
      <p>あなたが選択したテーマは<span id="slccol"></span></p>
    </div>

    <div id="wrp-language" class="">
      <h3>Chose your Language.</h3>
      <div id="language" class="box">
        <p onclick="clickLa(this)">Japanese</p>
        <p onclick="clickLa(this)">English</p>
        <p onclick="clickLa(this)">Chinese</p>
      </div>
    </div>

    <div id="wrp-color" class="">
      <h3>Chose theme color.</h3>
      <div id="color" class="box">
        <p onclick="clickCo(this)">red</p>
        <p onclick="clickCo(this)">blue</p>
        <p onclick="clickCo(this)">yellow</p>
      </div>
    </div>



    <!-- contents end -->
  </div>
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  <!-- original javascript file -->
  <script type="text/javascript" src="assets/js/main.js"></script>
</body>
</html>

Javascript

main.js
// ローカルストレージに連想配列で”言語”と”テーマカラー”を設定し、ページを更新しても値を保存して置けるようにしました。

// ローカルストレージをリセット
// localStorage.clear();

// DOMの取得
var wrpLan = document.getElementById('wrp-language');
var wrpCol = document.getElementById('wrp-color');
var slclan = document.getElementById('slclan');
var slccol = document.getElementById('slccol');
var outlan = JSON.parse(localStorage.getItem("languages"));
var outcol = JSON.parse(localStorage.getItem("colors"));
var body = document.getElementById('body');

// bodyのカラーを設定
body.classList.add(`bk-${outcol}`);

slclan.textContent = outlan;
slccol.textContent = outcol;



// ローカルストレージへ連想配列で2つのオブジェクトを保存する
// 言語のテキストをローカル保存
var items = {}
function clickLa(txt) {
    items.lan = txt.textContent;
    console.log("保存する連想配列は " + items.lan);
    localStorage.setItem("languages", JSON.stringify(items.lan));
    var output2 = JSON.parse(localStorage.getItem("languages"));
    console.log(output2);
    slclan.textContent = items.lan;
    wrpLan.classList.add("d-none");
}


// テーマカラーをローカル保存
function clickCo(txt) {
    items.col = txt.textContent;
    console.log("保存する連想配列は " + items.col);
    localStorage.setItem("colors", JSON.stringify(items.col));
    slccol.textContent = items.col;
    body.classList.add(`bk-${items.col}`);
    wrpCol.classList.add("d-none");
}

CSS

style.css
body {
    background-color: #f1f1f1;
}
p,article {
    margin: 0 !important;
    padding: 0 !important;
}
.br-m {
    border-radius: 10px;
}



/*background*/
.bk-white {
    background-color: #ffffff;
}
.bk-lightGray {
    background-color: #eaeaea;
}



/*shadow*/
.shadow-black {
    box-shadow: 5px 5px 15px #00000052;
}



/*color*/
.color-accent {
    color: #ff00a3;
}



.active {
    background-color: #ff00a3;
    color: #ffffff;
}
.nonactive {
    background-color: #ffffff;
    color: black;
}

.box p {
    background-color: #ffffff;
    margin-bottom: 20px !important;
    padding: 10px !important;
    color: gray;
    box-shadow: 5px 5p 15px #0000008a;
}
.bk-blue {
    background-color: blue !important;
}
.bk-red {
    background-color: red !important;
}
.bk-yellow {
    background-color: #c7c72b !important;
}

Point

今回のポイントとなるスクリプト。

式展開
body.classList.add(`bk-${items.col}`);

ローカルストレージ保存
localStorage.setItem("languages", JSON.stringify(items.lan));

ローカルストレージ出力
var outlan = JSON.parse(localStorage.getItem("languages"));

ローカルストレージリセット
localStorage.clear();

連想配列
var items = {}

クラスの追加
body.classList.add(`bk-${outcol}`);

クリックイベント
function clickLa(txt) {}

Comment

ローカルストレージは配列、連想配列のどちらかで値を保存できます。
個人的には後々のことを考えると連想配列の方がいいかなと思います。
ただ、連想配列は配列と異なり、pushで追加できないので、注意です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoJSをNuxt.jsで使う

GoJSとは

GoJSは、JavaScriptとTypeScriptで、インタラクティブにダイアグラムやグラフを書くためのライブラリです。
有料のライブラリですが、美しいダイアグラムやグラフを書くツールを作成できます。
https://gojs.net/latest/index.html

前提

まずNuxt.jsプロジェクトを作成してください。
作成したプロジェクトのディレクトリに移動してください。

1. Go.jsのインストール

インストールは、npmコマンドまたは、yarnコマンドでインストールできます。
いつも通りですね。

npm install --save gojs

npm ではなく、yarnを利用している場合は、次になります。

yarn add gojs

2. 動作確認

pages/index.vueを次のものに置き換えてください。
(どのvueファイルでもいいです。動作確認できるものであれば)

pages/index.vue
<template>
  <div class="wrapper">
    <div
      id="myDiagramDiv"
      style="width:100%;height:600px;"
    />
  </div>
</template>

<script>
import * as go from 'gojs'
const $ = go.GraphObject.make
export default {
  components: {},
  props: {},
  data () {
    return {
    }
  },
  mounted () {
    const myDiagram = $(go.Diagram, 'myDiagramDiv',
      {
        'undoManager.isEnabled': true 
      })
    const myModel = $(go.Model)
    myModel.nodeDataArray = [
      { key: 'Alpha' },
      { key: 'Beta' },
      { key: 'Gamma' }
    ]
    myDiagram.model = myModel
  },
  methods: {}
}
</script>

下記にアクセス。
http://localhost:3000/

3. Vue.jsっぽくしていく

このままだとVue.jsとして扱いにくいですね。そこで少しだけVue.jsらしくすることで、扱いやすくしましょう。

pages/index.vue
<template>
  <div class="wrapper">
    <div
      id="myDiagramDiv"
      style="width:100%;height:600px;"
    />
  </div>
</template>

<script>
import * as go from 'gojs'
const $ = go.GraphObject.make
export default {
  components: {},
  props: {},
  data () {
    return {
      nodes:[   //変更
         { key: 'Alpha' },
         { key: 'Beta' },
         { key: 'Gamma' }
      ]
  },
  mounted () {
    const myDiagram = $(go.Diagram, 'myDiagramDiv',
      {
        'undoManager.isEnabled': true 
      })
    const myModel = $(go.Model)
    myModel.nodeDataArray = this.nodes //変更
    myDiagram.model = myModel
  },
  methods: {}
}
</script>

このサンプルだと、あまり変えるところがないですが、
propsやVuexを使うことで、データを渡すだけで、データに応じた初期データを持つダイアグラムを作るコンポーネントにできます。

おわりに

ReactやAngularのプロジェクトは、始まっているのですが、Vueのプロジェクトは始まっていません。Vueプロジェクトを始めるチャンス?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

保存版 JavaScript <=> PHP の文字列操作対応リスト

フロントエンドとバックエンドにおいては分業が当たり前のこのご時世ですが、僕はちょうどお互い半々で仕事を頂けているので備忘録も兼ねて対応表を作成しました。

普段自分が担当している分野の反対側を扱わなくてはならなくなったときや、一人でフロント・バック両方開発するとき、もしくは暇つぶしにどうぞ。

※目次・各項目に書いてあるコードは JavaScript, PHP の順です。

追加・結合

結合 'hoge'+'fuga', 'hoge'.'fuga'

sample.js
'hoge'+'fuga';
//'hogefuga'
sample.php
'hoge'.'fuga';
//'hogefuga'

最も基本的かつよく使う文字列を繋げる処理ですが、この時点で JavaScript と PHP の差を感じます。

普段 JavaScript を使用している開発者には違和感を感じる「.」での連結ですが、これは PHP が Perl に影響を受けて開発されたことによるものと思われます。
PHP 公式マニュアル: PHP の歴史

PHP でも数値の加算には「+」を使うのですが、個人的には数値の加算なのか文字列の連結なのかを明示的に指定できるので便利に感じています。そもそも変数の型は安全に把握しておくべきですが……。

なお、JavaScript には concat() も用意されていますが、MDN でも説明されている通り、パフォーマンス的によろしくないので普通に「+」を使用した方が良いでしょう。

追加 'hoge'+='fuga', 'hoge'.='fuga'

sample.js
let str = 'hoge';
str += 'fuga';
//'hogefuga'
sample.php
$str = 'hoge';
$str .= 'fuga';
//'hogefuga'

どちらも結合時に使用した演算子に「=」を付けることで追加を行うことができます。

ところで、文字列を追加するときには上記の方法以外にも、

sample.js
let str = 'hoge';
str = str+'fuga';
sample.php
$str = 'hoge';
$str = $str.'fuga';

という書き方がありますが、結果は同じでも JavaScript 側のみ、下の方が処理速度が速い傾向にあるようです。

指定した文字数まで特定の文字で埋める(末尾へ追加) 'hoge'.padEnd(10, 'e'), str_pad('hoge', 10, 'e')

sample.js
'hoge'.padEnd(10, 'e');
//'hogeeeeeee'
sample.php
str_pad('hoge', 10, 'e');
//'hogeeeeeee'

どちらも計10文字になるまで 'hoge' の末尾に 'e' を追加を追加し、結果 'hogeeeeeee' を得るコードです。

この後も同じようなコードが沢山登場しますが、PHP 使いからすると、PHP では関数の引数として文字列を仕込むのに対し、JavaScript では文字列にメソッドが生えているのに違和感を感じるところですね。

JavaScript の文字列は大抵「String グローバルオブジェクト」というものを継承しているので、PHP のインスタンスのようにメソッドが生えているのです。

MDN: String

どちらも指定した文字数より元の文字数の方が長い場合、元の文字列がそのまま返されます。文字数までカットされるわけではないので注意しましょう。

ちなみに padEnd() は IE で動かないので使用する場合は Polyfill をお忘れなく。
MDN: String.prototype.padEnd()

指定した文字数まで特定の文字で埋める(先頭へ追加) 'hoge'.padStart(10, 'h'), str_pad('hoge', 10, 'h', STR_PAD_RIGHT)

sample.js
'hoge'.padStart(10, 'e');
//'eeeeeehoge'
sample.php
str_pad('hoge', 10, 'e', STR_PAD_LEFT);
//'eeeeeehoge'

JavaScript は別のメソッドを使用することになるのに対し、PHP では str_pad() 関数の第四引数に STR_PAD_LEFT を指定することで先頭への追加を行えます。

先の例では省略されていた str_pad() 関数の第四引数ですが、実はデフォルト値として STR_PAD_RIGHT 定数が定義されているので文字列が末尾に追加されていたのです。

なお、STR_PAD_LEFT などの定数は PHP が自動的に設定する定数で、特に開発者が定義することなく安心して使えます。
PHP マニュアル: 定義済み定数(文字列)

例によって padStart() は IE で動かないので使用する場合は Polyfill をお忘れなく。

同じ文字を指定回数結合した文字列を生成 'hoge'.repeat(10), str_repeat('hoge', 10)

sample.js
'hoge'.repeat(10);
//'hogehogehogehogehogehogehogehogehogehoge'
sample.php
str_repeat('hoge', 10);
//'hogehogehogehogehogehogehogehogehogehoge'

同じ文字列を指定回数結合した文字列を生成する関数です。どちらも指定回数に 0 を指定すると空文字を返します。

例によって repeat() は IE で動かないので使用する場合は Polyfill をお忘れなく。MDN によると Android でも動かないとのことです。
MDN: String.prototype.repeat()

判定・情報取得

文字数取得 'hoge'.length, mb_strlen('hoge')

sample.js
'hoge'.length;
//4
sample.php
mb_strlen('hoge');
//4

JavaScript 側はクラス風に言えばメソッドではなくプロパティなので () は不要です。

PHP で文字列操作をしていると頻繁に登場する「mb_○○○」系の関数ですが、「mb_」が付いていない関数とはマルチバイト文字を考慮するかしないかの違いがあります。

たとえば mb_strlen('ほげ') は「2」を返すのに対し、 strlen('ほげ') は「6」を返します。これは  strlen('ほげ') が返すのが、正確には文字数ではなくバイト数なのが原因です。

英語圏では絵文字でも使わない限り特に意識せずにコーディングしても影響なさそうですが、文字数を主眼に操作を行う場合は意識して「mb_○○○」系の関数を使用するようにしましょう。

特定の文字が現れる位置を取得する 'hoge'.indexOf('og'), strpos('hoge', 'og')

sample.js
'hoge'.indexOf('og');
//1
sample.php
strpos('hoge', 'og');
//1

どちらも先頭を「0」として数え始めるので「'hoge'」の中にある「'og'」の位置は「1」となります。

両者の決定的な差は検索結果が無かった場合の返り値で、indexOf() の場合は -1strpos() の場合は false です。
これについては次項で詳しく説明します。

特定の文字が含まれているか判定する 'hoge'.indexOf('og') !== -1, strpos('hoge', 'og') !== false

直感的には、「文字列中に特定の文字列が存在するか」を判定する際には正規表現を使う方法が考え付くと思います。
しかし、JavaScript でも PHP でもこれを判定する最速の方法は前項で説明した関数の返り値を使用する方法です(もちろん、静的な文字列ではなく、正規表現における . 等を表現する場合などは正規表現を使用することになります)。

そのためそれぞれのメソッド・関数が「一致無し」の場合に返す値を利用するのですが indexOf() の場合は -1strpos() の場合は false になるので、それを === で比較します。

注意すべきは PHP 側で、strpos()0 を返す場合があり、これは緩い比較では false と同義になってしまう falsy な値であるためしっかり === false で判定しないと意図しない挙動となってしまいます。

正規表現にマッチするか判定する 'hoge'.match(/^h.+e$/), preg_match('\A^h.+e\z/', 'hoge')

sample.js
'hoge'.match(/^h.+e$/);
//['hoge', index: 0, input: 'hoge']
sample.php
preg_match('\A^h.+e\z/', 'hoge');
//1

プログラミングをするなら避けて通れない正規表現ですが、JavaScript ・ PHP の両者にももちろん正規表現を扱うメソッド・関数が用意されています。

ただし、PHP 側では文字列を正規表現として扱うのに対し、JavaScript では RegExp オブジェクトを使用します。
上記サンプル中、PHP 側の正規表現は文字列なのに対し、JavaScript 側はクオーテーションで囲われていない正規表現を使用している点に注目してください。
これは JavaScript においては / で括った文字は RegExp リテラルとして扱われることを表しています。

PHP 側では文字列を表すクォーテーションの中で、さらに正規表現を / で括っていますが、これは「preg_○○○」系の関数では必須となるデリミタとなります。
JavaScript では / 固定ですが、PHP では / に限らず @ や {} なども使用できます。 URL 等を扱う場合はデリミタに / を使用すると正規表現中の / をエスケープする手間が発生するため、正規表現中に出現しない文字をデリミタに使用したほうが良いでしょう。
PHP マニュアル: PCRE はじめに

マッチすることを判定するだけなら両者とも if() の中に入れれば用をなしますが、返り値は全く異なります。

.match() はマッチした場合に様々な情報が入った配列を返します。

グローバル (g) フラグの有無によって内容が変わる Array を返します。マッチが見つからなかった場合は null を返します。
g フラグがある場合、キャプチャグループを除いた、正規表現にマッチしたすべての結果を返します。
g フラグがない場合、最初のマッチとそれに対するキャプチャグループのみを返します。この場合、返される要素には下記の「追加されるプロパティ」が存在します。
MDN: String.prototype.match()

マッチしなかった場合は null を返すため、if() での判定が行えるわけですね。

対して preg_match() ではマッチした場合は 1、マッチしなかった場合は 0 を返します(エラー時は false)。
特定の文字列を抜き出すためにマッチ系のメソッド・関数を使用する場合、.match() では返り値の配列が使用できますが、上記のとおり preg_match() の返り値はその用途に使用できません。

この場合は第三引数に変数を仕込むと参照渡しでマッチ内容の配列が挿入されます。

matches を指定した場合、検索結果が代入されます。\$matches[0] にはパターン全体にマッチしたテキストが代入され、 $matches[1] には 1 番目のキャプチャ用サブパターンにマッチした 文字列が代入され、といったようになります。

PHP マニュアル: preg_match

ところで PHP 側で登場している \A\z ですが、何故 ^$ を使用していないかというとセキュリティ的に問題があるからです。
正規表現をセキュリティに関わる処理に使用しているかどうかは実装次第ですが、PHP で正規表現を扱う場合は基本的に ^$ を使用しないクセを付けておいた方が無難かと思われます。
徳丸浩の日記: 正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう

置換・削除・整形

両端の空白を削除する ' hoge '.trim(), trim(' hoge ')

sample.js
'        hoge        '.trim();
//'hoge'
sample.php
trim('        hoge        ');
//'hoge'

メソッドなのか関数なのかという違い以外は同じノリで使える両者ですが、実は PHP 側は追加の引数が指定可能です。

sample.php
trim('ふがふがふがhogeふがふがふが', 'ふが');
//'hoge'

デフォルトでは空白文字を除去する trim() 関数ですが、第二引数を指定した場合はその文字を両端から削除します。便利ですね。

先頭か末尾の空白を削除する ' hoge'.trimStart() or 'hoge '.trimEnd(), ltrim(' hoge') or rtrim('hoge ')

sample.js
'        hoge'.trimStart();
//'hoge'

'hoge        '.trimEnd();
//'hoge'
sample.php
ltrim('        hoge');
//'hoge'

rtrim('hoge        ');
//'hoge'

名前が違いますが、どちらも同じものが用意されています。

例によって PHP 側では第二引数に任意の文字列を指定可能です。

大文字化・小文字化 'HOGE'.toLowerCase() or 'hoge'.toUpperCase(), strtolower('HOGE') or strtoupper('hoge')

sample.js
'HOGE'.toLowerCase();
//'hoge'

'hoge'.toUpperCase();
//'HOGE'
sample.php
strtolower('HOGE');
//'hoge'

strtoupper('hoge');
//'HOGE'

どちらも指定したアルファベットを大文字化、または小文字化します。

日本の制作現場ではレアケースですが、JavaScript 側でトルコ語を扱う場合は注意が必要です。
JavaScriptの文字列を全部小文字/大文字化する

なお、PHP 側には文字列全体ではなく先頭文字だけを変換対象とする lcfirst()ucfirst() が存在します。
さらに日本語圏では中々ユースケースがなさそうですが、文章中の単語の先頭を大文字化する ucwords() なんて関数も存在します。

特定の文字を置換する 'hoge'.replace('g', 'p'), str_replace('g', 'p', 'hoge')

sample.js
'hoge'.replace('g', 'p');
//'hope'
sample.php
str_replace('g', 'p', 'hoge');
//'hope'

文字列置換と言えば正規表現ですが、静的な文字列を検索して置換する場合、JavaScript では .replace() で共通なのに対し PHP では str_replace() を使用します。
後述する preg_replace() を使用するより遥かに処理速度が速いので積極的にこちらを使用するようにしましょう。
なお、str_replace() の第一引数には配列が使用できるので、静的な文字列であれば複数でも str_replace() が使用できます。

sample.php
str_replace(array('h', 'g'), 'p', 'hoge');
//'pope'

正規表現で置換する 'hoooooooooooge'.replace(/o+/g, 'o'), preg_replace('/o+/', 'o', 'hoooooooooooge')

sample.js
'hoooooooooooge'.replace(/o+/g, 'o');
//'hoge'
sample.php
preg_replace('/o+/', 'o', 'hoooooooooooge');
//'hoge'

.replace() はそのまま正規表現を仕込めますが、PHP 側では str_replace() ではなく preg_replace() を使用することになります。

.replace() の第二引数には置換する文字列を指定しますが、実は文字列ではなく関数を指定して、更に複雑な置換を行うこともできます。

MDN: String.prototype.replace() 引数としての関数の指定

同じようなことを PHP 側で実現するためには preg_replace() ではなく preg_replace_callback() が用意されているのでそちらを使用しましょう。

PHP マニュアル: preg_replace_callback

指定した範囲の文字だけ切り抜く 'hogehugapiyo'.slice(0, 4), mb_substr('hogehugapiyo', 0, 4)

sample.js
'hogehugapiyo'.slice(0, 4);
//'hoge'
sample.php
mb_substr('hogehugapiyo', 0, 4);
//'hoge'

意外と使う範囲指定の抜き出し処理は両者似たノリで扱うことができます。
どちらも開始位置を指定している引数(上記例では0)を負の数にすると末尾から処理をスタートさせる挙動となります。

変換

文字列中で変数展開する `${hoge}`, "$hoge"

sample.js
const hoge = 'ほげ'
`${hoge}`;
//'ほげ'
sample.php
$hoge = 'ほげ';
"$hoge";
//'ほげ'

JavaScript は ES2015 以降限定ですが、バッククォートで括られた範囲の ${} 内は変数が展開されます。

対して PHP ではダブルクオーテーションで括られた文字列内では自動で変数展開が行われます。
従って、パフォーマンスの観点から PHP で文字列を扱う場合は変数展開の行われないシングルクオーテーションを使用することをお勧めします。

数値を三桁ごとに「,」で区切った文字列にする new Intl.NumberFormat().format(3500), number_format(3500)

sample.js
new Intl.NumberFormat().format(3500)
//'3,500'
sample.php
number_format(3500);
//'3,500'

存在を知らなかった頃は専用関数を作成していたものですが、多用するからなのか、両者ともに専用のメソッド・関数が存在します。

デフォルトは両者三桁のカンマ区切りですが、フランスでの表記等、独特の区切り方にも対応しているので実装の際はマニュアルを参照してみてください。

MDN: Intl.NumberFormat.prototype.format
PHP マニュアル: number_format

JSON エンコード JSON.stringify({hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'}), json_encode(array('hoge ' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ'))

sample.js
JSON.stringify({hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'});
//'{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}'
sample.php
json_encode(array('hoge ' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ'));
//{"hoge":"\u307b\u3052","fuga":"\u3075\u304c","piyo":"\u3074\u3088"}

API を中心に据えた開発が主流となりつつある現代において、ますます重要になってきている JSON ですが、もちろん両言語でサポートされています。
JavaScript と PHP では連想配列の振る舞いが全く別ですが、JSON に変換する分には同じノリで扱えるので困ることはないでしょう。
もちろんストレートな配列を仕込んでも OK です。

なお、JSON.stringify() は第二・第三引数を指定して出力内容を人間用に成型して見やすくすることができます。デバックの際に活用しましょう。
GUNMA GIS GEEK: JSON.stringifyの第2引数を使って出力結果(JSON)を整形する

JSON デコード JSON.parse('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}'), json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}', true)

sample.js
JSON.parse('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}');
//{hoge: 'ほげ', fuga: 'ふが', piyo: 'ぴよ'}
sample.php
json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}', true);
//array ( 'hoge' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ', )

json_decode('{"hoge":"ほげ","fuga":"ふが","piyo":"ぴよ"}');
//stdClass::__set_state(array( 'hoge' => 'ほげ', 'fuga' => 'ふが', 'piyo' => 'ぴよ', ))

エンコードがあれば当然デコードがありますが、json_decode() は第二引数に true を指定すると連想配列、省略した場合は PHP のデフォルトクラスである stdClass のインスタンスとなります。
個人的には後から操作しやすい連想配列にして使用することが多いですが、メソッドなどを定義する場合は第二引数を省いておいたほうが良いでしょう。

配列との組み合わせ

文字列を特定の文字で区切って配列にする 'hoge,fuga,piyo'.split(','), explode(',', 'hoge,fuga,piyo')

sample.js
hoge,fuga,piyo'.split(',');
//['hoge','fuga','piyo']
sample.php
explode(',', 'hoge,fuga,piyo');
//array('hoge','fuga','piyo')

URL を / で分割したりと意外と出番の多い機能ですね。

実は explode() は引数を逆にしても正常に動作するという特徴があります。これは次項で紹介する implode() でも一緒なのですが、これには歴史的な理由があります。

配列を特定の文字列で区切った文字列にする ['hoge', 'fuga', 'piyo'].join(','), implode(',', array('hoge', 'fuga', 'piyo'))

sample.js
['hoge', 'fuga', 'piyo'].join(',');
//'hoge,fuga,piyo'
sample.php
implode(',', array('hoge', 'fuga', 'piyo'));
//'hoge,fuga,piyo'

こちらも良く使う機能ですが、両者ともに区切り文字に '' を指定すると単に文字列を結合する関数として使用できます。

便利なので多用しがちですが、実行速度が単純な結合よりかなり遅くなってしまうので、静的な結合であれば極力演算子による結合を使いましょう。

前項でも紹介した通り、implode() は引数を逆にしても正常に動作しますが、PHPer には有名な言い回しである「歴史的な理由」が PHP のマニュアルに登場するのがこの implode() です。

PHP マニュアル: implode

詳しくは以下のスライドが非常に秀逸かつ感動的です。

お前は PHP の歴史的な理由の数を覚えているのか

感想

意外と書き味が共通してて面白かったです。
フロントエンドであってもバックエンドであっても基本的な文字列操作は重要かつ、ベストプラクティスが似通っているのでしょう。

もちろん、url の扱いやバイナリファイルの文字列化、暗号関連等片方にしかない機能も存在するのですが、急な分野外の作業でも基本的なことをする分にはあまり困らないのではないでしょうか。

記事を書いている身としては、両者ともに公式ドキュメントが充実して読みやすいのがなによりの助けでした。著者、翻訳者に感謝。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「ねそプロ」ショッピングサイトを作ろう

ネットワークを利用した双方向性のあるコンテンツのプログラミング教材

ネットワークを利用したプログラムの例として、「ショッピングサイト」の教材を作成しました。
ブロック部品をドラッグして置くだけの簡単プログラミングです。Google Blockly を使ってJavaScriptのコードを作成して実行します。データは、Ajaxで非同期通信をサーバと行い、データベースに記録しています。

http://iwate-manabi-net.sakura.ne.jp

1 ショッピングサイトを作ろう
2 ショッピングサイトで買い物しよう
3 売り上げランキングを見よう

以上3つのWebページでできています。

「ショッピングサイトを作ろう」の使い方

(1) 「画面に表示 スタート」のブロックを置きます。
(2) 「商店名」のブロックを置き、商店の名前を入力します。
(3) ショッピングカートを置きます。
(4) 商品を並べるワクを置きます。
(5) その中に商品を入れます。

ここで「実行」ボタンを押すと、ショップサイトの画面が作られます。
商品の写真、商品名、価格のカードが表示されます。
ブロックを入れ替えると、商品の表示順も変わります。
商品カードの「▲、▼」ボタンを押しても個数が変わりません。
↑これは、プログラムをまだしていないからです。

(6) 「▲ボタンが押された」を置きます。
(7) その中に、「個数を増やす(+1)」を置きます。
  このままでは、無限に注文できるので、
(8) 「もし、個数<5なら」を置き、
   この中に「個数を増やす(+1)」を置くようにします。
 →これで、最大注文数5に制限されます。

(9) 「▼ボタンが押された」を置きます。
(10) その中に、「個数を減らす(-1)」を置きます。
   このままでは、注文数がマイナスにできるので、
(11) 「もし、個数>0なら」を置き
   この中に、「個数を減らす(-1)」を置きます。
  → これで、注文数がマイナスになることを防ぎます。

(12) 「カートボタンが押された」を置き、決済処理します。
(13) 「選択した全商品を表示」で商品名一覧と合計を表示します。
(14) 「データをサーバに送る」で買い上げ処理をします。
(15) 「もどるボタン」で、途中でやめることができるようにします。

ショッピングサイトに必要な最低の機能を,ブロック一つ一つに分けて
組み合わせてプログラミング学習できるようにしました。
さらに、セキュリティを学べる機能の追加を考えております。

「ショッピングサイトで買い物しよう」の使い方

(1) 「ショッピングサイトを作ろう」の「接続先アドレス」にとなりの人と、同じ値を入力します。
(2)  「実行」ボタンを押してください。
  → 接続先アドレスにショッピングサイトが出店されます。
(3) 「ショッピングサイトで買い物しよう」のページを表示します。
 ※ タブで表示を切り替えてください。
(4) 「ショッピングサイトで買い物しよう」の「接続先アドレス」にも同じ値を入力します。
(5) 「ショッピングサイトで買い物しよう」の「最新情報」ボタンを押します。
 → 自分と、となりの人のショッピングサイトが表示されます。
 → 商店名をクリックすると、右側に作成したショッピングサイトが表示されます。
(6) タブで「ショッピングサイトを作ろう」を表示されて、商品を追加してください。
 → 「ショッピングサイトを作ろう」の「実行」ボタンを押して、出店します。
 → 「ショッピングサイトで買い物しよう」→「最新情報」ボタン
  → 商店の商品が追加されたことを確認してください。
※ 同一コンピュータから、同一アドレスに出店できるのは1サイトのみです。
※ 商店名を変えても上書きされます。
※ プログラムを組んだとおりに作動します。不具合のあるサイトもそのまま表示されます。
 ↑評価に使ってください。(ただし、表示は1日のみです)
(7) クラス全体で、「接続先アドレス」を同じものにします。
  ※ 「接続先アドレス」は先生が指定してください。
(8) 「ショッピングサイトを作ろう」の「実行」ボタンを押して、出店します。
(9) 「ショッピングサイトで買い物しよう」で出店を確認します。
※ 「ショッピングサイトで買い物しよう」は、最新更新の順で表示します。
(10) クラス全体で、ショッピングモールを作って、売り上げを競いましょう。

「売り上げランキングを見よう」の使い方

(1) 「売り上げランキングを見よう」のページを表示します。
 →「売り上げランキングを見よう」のページを選択します。
※ タブで表示を切り替えてください。
(2) 「売り上げランキングを見よう」の「接続先アドレス」にも同じ値を入力します。
(3) クラス全体で売れている商品ランキングを表示します。
(4) 売り上げを上げるために、追加したい機能を考えさせます。
(5) 買い物額のランキングも表示されます。
  → 使ったお金を支払うための「労働時間」について考えさせます。
  → お金を大切に使う心を育てます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Papaparseで、Excelで0落ちしないCSVファイルを作る方法

下記のようにCSVをExcelで読み込むと
電話番号の先頭の0が消えてしまいます。

元データ.csv
名前,電話番号
佐々木,08022222222
湯浅,08022222221
excelで読み込んだデータ.csv
名前,電話番号
佐々木,8022222222
湯浅,8022222221

解決策

区切り文字を,=にすれば、Excelでも先頭の0を表示することができるので
Papaparseで区切り文字を変更する方法を紹介します。

名前,=電話番号
佐々木,=08022222222
湯浅,=08022222221

ただし、ジェネリックではないExcelのクソ仕様ですので
どうしても、ExcelでCSVを開きたい場合だけ使ってください。?

実装方法

unparseのオプションに{ delimiter: ",=" }を渡すだけです。

  const data = Papa.unparse(json, { delimiter: ",=" })

その他のオプション

その他のオプションは、公式のドキュメントからご確認ください。

公式ドキュメント

注意点

papaparseのversion5以前は
delimiterに2文字以上の文字列を使えないのでご注意ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む