20211127のJavaScriptに関する記事は13件です。

【React】【Vue】 など流行りのフレームワークを使うもののためのJS基礎

流行りのフレームワークを使っていると忘れがち(意識しなくてよい)JSの基礎を学ぶ直してみた。 関数とメソッドの違い 結論:オブジェクトに入っているのかどうか 関数→複数の箇所から共通で呼び出せるプログラムの塊 メソッド→オブジェクトがプロパティとして持っている関数 例 ・関数 const sample = () => { console.log("hello") } sample() ・メソッド const person = { name:"菅田将暉", age:28, sample:()=>{ console.log("結婚おめでとう!") } } person.sample() クラス・インスタンス ・クラス 設計図をイメージ クラス名は基本的に大文字から始める 例 class Car{ } ・インスタンス  オブジェクトを生成するための設計図(class)が用意できたので、  その設計図から実際にオブジェクトを生成する為には、「new クラス名()」とする。  クラスから生成したオブジェクトはインスタンスと呼ぶ。 例 class Car{ } // Carクラスのインスタンスを定数carに代入 const car = new Car(); ・コンストラクタ  クラスにはコンストラクタと呼ばれる機能が用意されている。  コンストラクタはインスタンスを生成するときに実行したい処理や設定を追加するための機能。  クラスの中括弧 { } 内に 「constructor() { }」と記述する。 例 class Car{ // クラスの中に追加 constructor() { } } コンストラクタの処理  コンストラクタの中に処理を記述することで、ここに書いた処理は  インスタンスが生成された直後に実行される。  そして、大切なのは、インスタンスごとに毎回実行されるという事。 例 class Car{ constructor() { console.log("新車が発売されます!"); } } const car1 = new Car(); const car2 = new Car(); 出力結果 // インスタンスごとに実行される 新車が発売されます! 新車が発売されます! プロパティと値を追加  コンストラクタの中で、生成したインスタンスに関する情報を追加するには、  コンストラクタの中で「this.プロパティ = 値」とする事で、  生成されたインスタンスにプロパティと値を追加する事が出来る。 例 class Car{ constructor() { this.プロパティ名 = 値; } }  コンストラクタの中で追加した値は、  「インスタンス.プロパティ」とする事でクラスの外で使用出来る。 例 class Car{ constructor() { this.name = "トラック"; } } const car = new Car(); // 「名前: 〇〇」となるように出力 console.log(`名前: ${car.name}`); 出力結果 名前: トラック 引数ごとに値を変える  コンストラクタでは、関数と同じように、引数を受け取ることが可能。  「constructor」の後の括弧「( )」内に引数名を記述することで、  その引数をコンストラクタの処理内で使用出来る。  コンストラクタに引数として値を渡すには、  「new クラス名( )」の括弧「( )」内に値を追加する。 例 class Car{ constructor(name, color) { this.name = name; this.color = color; } } // 引数に「"トラック"」と「黒」を渡す const car = new Car("トラック", "黒"); // 「名前: 〇〇」となるように出力 console.log(`名前: ${car.name}`); // 「色: 〇〇」となるように出力 console.log(`色: ${car.color}`); 出力結果 名前: トラック 色: 黒 メソッド  メソッドとはそのインスタンスの「動作」のようなもの。  「名前」や「年齢」などの情報はプロパティで追加したのに対して、  メソッドは「挨拶をする」「値を計算する」などの処理のまとまりを表す。 メソッドの定義  メソッドはクラスの中で定義します。  「メソッド名() { }」とすることでメソッドは定義出来る。  メソッドは関数と似たようなもので、中括弧「{ }」の中にそのメソッドで行いたい処理を記述する。 例 class Car{ メソッド名() { // 行いたい処理 } } メソッドの呼び出し方  「インスタンス.メソッド名()」とする事でそのメソッドを呼び出し、  処理を実行することが出来る。 例 class Car{ constructor(name, color) { this.name = name; this.color = color; } // actionメソッドを追加 action() { console.log("走る"); } } const car = new Car("トラック", "黒"); // carに対してactionメソッドを呼び出す car.action(); 出力結果 走る メソッド内で値を使う  メソッド内でインスタンスの値を使用するには、「this」という特殊な値を用いて、  「this.プロパティ名」とします。 例 class Car{ constructor(name, color) { this.name = name; this.color = color; } action() { console.log(`${this.name}は、走る`); } } const car = new Car("トラック", "黒"); // carに対してactionメソッドを呼び出す car.action(); 出力結果 トラックは、走る メソッド内でのメソッド呼び出し  メソッド内で他のメソッドを呼び出すことも可能。  メソッド内で「this.メソッド名()」とすることで、同じクラスの他のメソッドを使うことが出来る。 例 class Car{ constructor(name, color) { this.name = name; this.color = color; } // actionメソッド action() { console.log(`${this.name}は、走る`); } // infoメソッド info() { // actionメソッドを呼び出す this.action(); console.log(`この車は${this.name}です`); console.log(`${this.color}色です`); } } const car = new Car("トラック", "黒"); car.action(); 出力結果 トラックは、走る この車はトラックです 黒色です 実際の使用方法 実際クラスを作ってインスタンス化して使うことはあまりしないと思います。 元から用意されて物を使うのが一般的かなと思います。 //インスタンス化(初期化) let today = new Date() //引数を渡してインスタンス化 let newYear = new Date(2021,1,1) //インスタンスに元から入っているメソッドを使う today.getDate() newYear.getFullYear()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

InDesign JavaScript XML 変数の値を変数名に

変数の値を変数名にするスクリプトは、これで良いのかな・・・? /* このスクリプトを利用して起こった不具合の責任は取れません。 ご了承下さい。 更新 2021/11/27 */ // アプリ指定 #target "indesign"; // スクリプト名 var scriptName = "変数の値を変数名に"; // 変更した数 var changeNumber = 0; // 失敗した数 var failNumber = 0; //スクリプトの動作指定(一つのアンドゥ履歴にする、及び、アンドゥ名) app.doScript(function(){ // ダイアログ var dialogueFlg = confirm("activeDocumentに存在するカスタムテキスト変数の値を変数名にします。","", scriptName); // Noの場合 if (dialogueFlg == false) { // 終了 exit(); } // テキスト変数の数だけ繰り返す for(var i = 0; i < app.activeDocument.textVariables.count(); i++){ // カスタムテキスト変数の場合 if(app.activeDocument.textVariables.item(i).variableType == VariableTypes.CUSTOM_TEXT_TYPE){ // 名前と内容が違う場合 if(app.activeDocument.textVariables.item(i).name != app.activeDocument.textVariables.item(i).variableOptions.contents){ // エラー対策 try{ // 内容を名前にする app.activeDocument.textVariables.item(i).name = app.activeDocument.textVariables.item(i).variableOptions.contents; // 変更数を増やす changeNumber++; // エラーの場合 }catch(e){ // 失敗数を増やす failNumber++; } } } } // 結果 alert("変更数 " + changeNumber + "\r\r" + "変更出来なかった数 " + failNumber, scriptName); //スクリプトの動作指定の続き }, ScriptLanguage.JAVASCRIPT, [scriptName], UndoModes.ENTIRE_SCRIPT, scriptName);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】モジュール② ES Moduleと即時関数

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 モジュールについての理解を深める 本題 1. ES Moduleと即時関数 二つの動きがとても似ているのでコードで確認していく 例1 前提 即時関数を定義している状態 main.js // moduleAという変数に対して、即時関数が定義されている const moduleA = (function () { console.log('IIFE called'); let privateVal = 1; let publicVal = 10; function publicFn() { console.log('publicFn called: ' + privateVal++); } function privateFn() { } // 戻り値を定義しているので、以下のオブジェクトがmoduleAに返る return { publicFn, publicVal } })(); 上記のmoduleAをpublicFnを通して読んでみる main.js const moduleA = (function () { console.log('IIFE called'); let privateVal = 1; let publicVal = 10; function publicFn() { // privateValが1ずつカウントアップする仕組みがある console.log('publicFn called: ' + privateVal++); } function privateFn() { } return { publicFn, publicVal } })(); // 下記のようにするとpublicFnを実行できる // privateFn, privateValは外部から呼べない moduleA.publicFn(); // 1ずつ増えていく moduleA.publicFn(); moduleA.publicFn(); // 下記のようにすると10という値が取れてくる console.log(moduleA.publicVal++) // こちらも++すると11,12とカウントアップする console.log(moduleA.publicVal++) console.log(moduleA.publicVal++) 例2 moduleAとpublicFnをmoduleBに格納する main.js const moduleA = (function () { console.log('IIFE called'); let privateVal = 1; let publicVal = 10; function publicFn() { console.log('publicFn called: ' + privateVal++); } function privateFn() { } return { publicFn, publicVal } })(); // 以下のように即時関数を定義して格納する // 一般的には仮引数、実引数に値を入れて使用する const moduleB = (function(moduleA) { // 内部でmoduleAを使用 moduleA.publicFn(); moduleA.publicFn(); moduleA.publicFn(); console.log(moduleA.publicVal++) console.log(moduleA.publicVal++) console.log(moduleA.publicVal++) })(moduleA); // 省略して書くこともできる // moduleAはオブジェクトなので、直接publicFn, Valを分割代入可能 const moduleB2 = (function( {publicFn, publicVal }) { // moduleAを使用していないのでmoduleAを削除 publicFn(); publicFn(); publicFn(); console.log(publicVal++) console.log(publicVal++) console.log(publicVal++) })(moduleA); // さらに名前をつけて書くことも可能 const moduleB3 = (function( {publicFn: Fn, publicVal: Val }) { // このようにすると上記のmoduleB2と同じ結果が出力される Fn(); Fn(); Fn(); console.log(Val++) console.log(Val++) console.log(Val++) })(moduleA); 例3 即時関数で書いていたものをES moduleに書き直す moduleA.js // moduleAの値をそのままコピー console.log('ES Module called'); let privateVal = 1; // publicとあるものは外部に露出したいので(ファイルの外で使いたい)exportをつける // 渡す時にエラーが発生しないようにプリミティブ型でなくオブジェクトとして定義する export let publicVal = { prop : 10 }; export function publicFn() { console.log('publicFn called: ' + privateVal++); } function privateFn() { } moduleB.js // // moduleBも全体をコピーして持ってくる // const moduleB = (function(moduleA) { // 以下の書き方に変更 import { publicFn as Fn, publicVal as Val} from "./moduleA.js"; Fn(); Fn(); Fn(); // moduleAに合わせて変更 console.log(Val.prop++) console.log(Val.prop++) console.log(Val.prop++) 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム Let'sプログラミング JavaScript入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイクラプログラミング(統合版)いろいろやってみた[Scripting API/Functions/WebSocket/MakeCode]

概要 この記事は KLab Engineer Advent Calendar 2021の10日目の記事です。 こんにちはhamasan05です。 昨年に引き続きマイクラ(統合版)の記事です。 今年はマイクラでなんでもいいからゲームを一つ作ってみるということで作ってみました。 マイクラ(統合版)には複数のプログラミング可能な環境が用意されており 同じゲームを複数のやり方で実装することにチャレンジしてみました。 どんなゲームを作ったか マイクラのウィザーという裏ボスを出現させないようにするゲームです。 ウィザーには出現条件があり、その出現条件を満たさないように立ち回り 出現させてしまった人が負け、責任をもってウィザーを倒すという内容になります。 動画 実際に遊ぶとこのような感じになります。 ※ゲームの元ネタです。 機能の紹介 必要な機能は下記の二つだけにしました 1. ステージのリセット 2. 必要なアイテムの付与 ステージのリセット 一定の範囲に特定のブロックを配置する 出現条件をランダムにするために、特定のブロックの配置をランダムにする 必要なアイテムの付与 装備や消耗品の付与 ゲームの進行に必要な配置可能なブロックの付与 1. Scripting APIと呼ばれているものを使った実装 公式を見てもわからなかったのでいろいろネットをさまよって たどり着いたのが下記でした https://wiki.bedrock.dev/scripting/scripting-intro.html Windows10で動くスクリプトということで試してみました。 server.js const systemServer = server.registerSystem(0, 0) systemServer.initialize = function () { // (中略) // ボタンを押したときのイベント登録 this.listenForEvent('minecraft:block_interacted_with', (e) => this.onInteracted(e)); }; // ボタンを押したときの挙動 systemServer.onInteracted = function (e) { x = e.data.block_position.x, y = e.data.block_position.y, z = e.data.block_position.z // アイテムを付与するボタン if (x === 0 && y === 5 && z === 0) { this.initUser(); } // ステージをリセットするボタン if (x === 0 && y === 5 && z === 1) { this.initGame(); } }; // 必要なアイテムの付与 const playerInitCommands = [ // ゲームの進行に必要な配置可能なブロックの付与 '/give @p skull 128 1 {"minecraft:can_place_on":{"blocks":["soul_sand"]}}', // 装備や消耗品の付与 "/give @p netherite_sword 1", "/give @p bow 1", "/give @p enchanted_golden_apple 64", "/give @p netherite_helmet 1", "/give @p netherite_chestplate 1", "/give @p netherite_leggings 1", "/give @p netherite_boots 1", "/give @p arrow 64", ] systemServer.initUser = function () { playerInitCommands.forEach(function(x) { systemServer.executeCommand(x, (e) => {}); }); }; // ステージのリセット const gameInitCommands = [ "/fill 20 6 0 30 6 10 soul_sand", "/fill 20 7 0 30 7 10 air", "/fill 20 5 0 30 5 10 air", ] systemServer.initGame = function () { // 出現条件をランダムにするために、特定のブロックの配置をランダムにする var x = Math.floor(Math.random() * 11) + 20; var z = Math.floor(Math.random() * 11); command = `/fill ${x} 5 ${z} ${x} 5 ${z} soul_sand`; gameInitCommands.forEach(function(x) { systemServer.executeCommand(x, (e) => {}); }); systemServer.executeCommand(command, (e) => {}); }; Github 普段JavaScriptを書かない私ですが、これならいろいろ実現できそうだという感触が得られました。 一番のデメリットはWindows10でしか動かないというところと感じました。 ※サーバ側でスクリプトを動かせば、クライアントはモバイル端末でもよい模様です。 2. Functionsで実装 Functionsはゲーム内で使えるコマンドをひとまとめにしてまとめて実行する機能です。 これを使っても実装が可能ということが分かったので試してみました ステージのリセット fill 4 5 13 -5 7 22 air fill 4 6 13 -5 6 22 soul_sand scoreboard objectives add x dummy scoreboard players random @e[name=random] x 1 100 execute @e[name=random,scores={x=1}] ~ ~ ~ fill -5 5 13 -5 5 13 soul_sand execute @e[name=random,scores={x=2}] ~ ~ ~ fill -5 5 14 -5 5 14 soul_sand execute @e[name=random,scores={x=3}] ~ ~ ~ fill -5 5 15 -5 5 15 soul_sand execute @e[name=random,scores={x=4}] ~ ~ ~ fill -5 5 16 -5 5 16 soul_sand execute @e[name=random,scores={x=5}] ~ ~ ~ fill -5 5 17 -5 5 17 soul_sand execute @e[name=random,scores={x=6}] ~ ~ ~ fill -5 5 18 -5 5 18 soul_sand execute @e[name=random,scores={x=7}] ~ ~ ~ fill -5 5 19 -5 5 19 soul_sand execute @e[name=random,scores={x=8}] ~ ~ ~ fill -5 5 20 -5 5 20 soul_sand execute @e[name=random,scores={x=9}] ~ ~ ~ fill -5 5 21 -5 5 21 soul_sand execute @e[name=random,scores={x=10}] ~ ~ ~ fill -5 5 22 -5 5 22 soul_sand execute @e[name=random,scores={x=11}] ~ ~ ~ fill -4 5 13 -4 5 13 soul_sand execute @e[name=random,scores={x=12}] ~ ~ ~ fill -4 5 14 -4 5 14 soul_sand execute @e[name=random,scores={x=13}] ~ ~ ~ fill -4 5 15 -4 5 15 soul_sand execute @e[name=random,scores={x=14}] ~ ~ ~ fill -4 5 16 -4 5 16 soul_sand execute @e[name=random,scores={x=15}] ~ ~ ~ fill -4 5 17 -4 5 17 soul_sand execute @e[name=random,scores={x=16}] ~ ~ ~ fill -4 5 18 -4 5 18 soul_sand execute @e[name=random,scores={x=17}] ~ ~ ~ fill -4 5 19 -4 5 19 soul_sand execute @e[name=random,scores={x=18}] ~ ~ ~ fill -4 5 20 -4 5 20 soul_sand execute @e[name=random,scores={x=19}] ~ ~ ~ fill -4 5 21 -4 5 21 soul_sand execute @e[name=random,scores={x=20}] ~ ~ ~ fill -4 5 22 -4 5 22 soul_sand execute @e[name=random,scores={x=21}] ~ ~ ~ fill -3 5 13 -3 5 13 soul_sand execute @e[name=random,scores={x=22}] ~ ~ ~ fill -3 5 14 -3 5 14 soul_sand execute @e[name=random,scores={x=23}] ~ ~ ~ fill -3 5 15 -3 5 15 soul_sand execute @e[name=random,scores={x=24}] ~ ~ ~ fill -3 5 16 -3 5 16 soul_sand execute @e[name=random,scores={x=25}] ~ ~ ~ fill -3 5 17 -3 5 17 soul_sand execute @e[name=random,scores={x=26}] ~ ~ ~ fill -3 5 18 -3 5 18 soul_sand execute @e[name=random,scores={x=27}] ~ ~ ~ fill -3 5 19 -3 5 19 soul_sand execute @e[name=random,scores={x=28}] ~ ~ ~ fill -3 5 20 -3 5 20 soul_sand execute @e[name=random,scores={x=29}] ~ ~ ~ fill -3 5 21 -3 5 21 soul_sand execute @e[name=random,scores={x=30}] ~ ~ ~ fill -3 5 22 -3 5 22 soul_sand execute @e[name=random,scores={x=31}] ~ ~ ~ fill -1 5 13 -1 5 13 soul_sand execute @e[name=random,scores={x=32}] ~ ~ ~ fill -1 5 14 -1 5 14 soul_sand execute @e[name=random,scores={x=33}] ~ ~ ~ fill -1 5 15 -1 5 15 soul_sand execute @e[name=random,scores={x=34}] ~ ~ ~ fill -1 5 16 -1 5 16 soul_sand execute @e[name=random,scores={x=35}] ~ ~ ~ fill -1 5 17 -1 5 17 soul_sand execute @e[name=random,scores={x=36}] ~ ~ ~ fill -1 5 18 -1 5 18 soul_sand execute @e[name=random,scores={x=37}] ~ ~ ~ fill -1 5 19 -1 5 19 soul_sand execute @e[name=random,scores={x=38}] ~ ~ ~ fill -1 5 20 -1 5 20 soul_sand execute @e[name=random,scores={x=39}] ~ ~ ~ fill -1 5 21 -1 5 21 soul_sand execute @e[name=random,scores={x=40}] ~ ~ ~ fill -1 5 22 -1 5 22 soul_sand execute @e[name=random,scores={x=41}] ~ ~ ~ fill 0 5 13 0 5 13 soul_sand execute @e[name=random,scores={x=42}] ~ ~ ~ fill 0 5 14 0 5 14 soul_sand execute @e[name=random,scores={x=43}] ~ ~ ~ fill 0 5 15 0 5 15 soul_sand execute @e[name=random,scores={x=44}] ~ ~ ~ fill 0 5 16 0 5 16 soul_sand execute @e[name=random,scores={x=45}] ~ ~ ~ fill 0 5 17 0 5 17 soul_sand execute @e[name=random,scores={x=46}] ~ ~ ~ fill 0 5 18 0 5 18 soul_sand execute @e[name=random,scores={x=47}] ~ ~ ~ fill 0 5 19 0 5 19 soul_sand execute @e[name=random,scores={x=48}] ~ ~ ~ fill 0 5 20 0 5 20 soul_sand execute @e[name=random,scores={x=49}] ~ ~ ~ fill 0 5 21 0 5 21 soul_sand execute @e[name=random,scores={x=40}] ~ ~ ~ fill 0 5 22 0 5 22 soul_sand execute @e[name=random,scores={x=51}] ~ ~ ~ fill 1 5 13 1 5 13 soul_sand execute @e[name=random,scores={x=52}] ~ ~ ~ fill 1 5 14 1 5 14 soul_sand execute @e[name=random,scores={x=53}] ~ ~ ~ fill 1 5 15 1 5 15 soul_sand execute @e[name=random,scores={x=54}] ~ ~ ~ fill 1 5 16 1 5 16 soul_sand execute @e[name=random,scores={x=55}] ~ ~ ~ fill 1 5 17 1 5 17 soul_sand execute @e[name=random,scores={x=56}] ~ ~ ~ fill 1 5 18 1 5 18 soul_sand execute @e[name=random,scores={x=57}] ~ ~ ~ fill 1 5 19 1 5 19 soul_sand execute @e[name=random,scores={x=58}] ~ ~ ~ fill 1 5 20 1 5 20 soul_sand execute @e[name=random,scores={x=59}] ~ ~ ~ fill 1 5 21 1 5 21 soul_sand execute @e[name=random,scores={x=60}] ~ ~ ~ fill 1 5 22 1 5 22 soul_sand execute @e[name=random,scores={x=61}] ~ ~ ~ fill 2 5 13 2 5 13 soul_sand execute @e[name=random,scores={x=62}] ~ ~ ~ fill 2 5 14 2 5 14 soul_sand execute @e[name=random,scores={x=63}] ~ ~ ~ fill 2 5 15 2 5 15 soul_sand execute @e[name=random,scores={x=64}] ~ ~ ~ fill 2 5 16 2 5 16 soul_sand execute @e[name=random,scores={x=65}] ~ ~ ~ fill 2 5 17 2 5 17 soul_sand execute @e[name=random,scores={x=66}] ~ ~ ~ fill 2 5 18 2 5 18 soul_sand execute @e[name=random,scores={x=67}] ~ ~ ~ fill 2 5 19 2 5 19 soul_sand execute @e[name=random,scores={x=68}] ~ ~ ~ fill 2 5 20 2 5 20 soul_sand execute @e[name=random,scores={x=69}] ~ ~ ~ fill 2 5 21 2 5 21 soul_sand execute @e[name=random,scores={x=70}] ~ ~ ~ fill 2 5 22 2 5 22 soul_sand execute @e[name=random,scores={x=71}] ~ ~ ~ fill 3 5 13 3 5 13 soul_sand execute @e[name=random,scores={x=72}] ~ ~ ~ fill 3 5 14 3 5 14 soul_sand execute @e[name=random,scores={x=73}] ~ ~ ~ fill 3 5 15 3 5 15 soul_sand execute @e[name=random,scores={x=74}] ~ ~ ~ fill 3 5 16 3 5 16 soul_sand execute @e[name=random,scores={x=75}] ~ ~ ~ fill 3 5 17 3 5 17 soul_sand execute @e[name=random,scores={x=76}] ~ ~ ~ fill 3 5 18 3 5 18 soul_sand execute @e[name=random,scores={x=77}] ~ ~ ~ fill 3 5 19 3 5 19 soul_sand execute @e[name=random,scores={x=78}] ~ ~ ~ fill 3 5 20 3 5 20 soul_sand execute @e[name=random,scores={x=79}] ~ ~ ~ fill 3 5 21 3 5 21 soul_sand execute @e[name=random,scores={x=80}] ~ ~ ~ fill 3 5 22 3 5 22 soul_sand execute @e[name=random,scores={x=81}] ~ ~ ~ fill 4 5 13 4 5 13 soul_sand execute @e[name=random,scores={x=82}] ~ ~ ~ fill 4 5 14 4 5 14 soul_sand execute @e[name=random,scores={x=83}] ~ ~ ~ fill 4 5 15 4 5 15 soul_sand execute @e[name=random,scores={x=84}] ~ ~ ~ fill 4 5 16 4 5 16 soul_sand execute @e[name=random,scores={x=85}] ~ ~ ~ fill 4 5 17 4 5 17 soul_sand execute @e[name=random,scores={x=86}] ~ ~ ~ fill 4 5 18 4 5 18 soul_sand execute @e[name=random,scores={x=87}] ~ ~ ~ fill 4 5 19 4 5 19 soul_sand execute @e[name=random,scores={x=88}] ~ ~ ~ fill 4 5 20 4 5 20 soul_sand execute @e[name=random,scores={x=89}] ~ ~ ~ fill 4 5 21 4 5 21 soul_sand execute @e[name=random,scores={x=90}] ~ ~ ~ fill 4 5 22 4 5 22 soul_sand execute @e[name=random,scores={x=91}] ~ ~ ~ fill -2 5 13 -2 5 13 soul_sand execute @e[name=random,scores={x=92}] ~ ~ ~ fill -2 5 14 -2 5 14 soul_sand execute @e[name=random,scores={x=93}] ~ ~ ~ fill -2 5 15 -2 5 15 soul_sand execute @e[name=random,scores={x=94}] ~ ~ ~ fill -2 5 16 -2 5 16 soul_sand execute @e[name=random,scores={x=95}] ~ ~ ~ fill -2 5 17 -2 5 17 soul_sand execute @e[name=random,scores={x=96}] ~ ~ ~ fill -2 5 18 -2 5 18 soul_sand execute @e[name=random,scores={x=97}] ~ ~ ~ fill -2 5 19 -2 5 19 soul_sand execute @e[name=random,scores={x=98}] ~ ~ ~ fill -2 5 20 -2 5 20 soul_sand execute @e[name=random,scores={x=99}] ~ ~ ~ fill -2 5 21 -2 5 21 soul_sand execute @e[name=random,scores={x=100}] ~ ~ ~ fill -2 5 22 -2 5 22 soul_sand Github 「出現条件をランダムにするために、特定のブロックの配置をランダムにする」 を実現するために記述がかなり増えてしまいました。 変数の扱いに制限があって気楽にプログラミングするという感じではなかったです。 必要なアイテムの付与 give @a[c=1] skull 128 1 {"minecraft:can_place_on":{"blocks":["soul_sand"]}} give @a[c=1] netherite_sword 1 give @a[c=1] bow 1 give @a[c=1] enchanted_golden_apple 64 give @a[c=1] netherite_helmet 1 give @a[c=1] netherite_chestplate 1 give @a[c=1] netherite_leggings 1 give @a[c=1] netherite_boots 1 give @a[c=1] arrow 64 Github こちらはほぼほぼJavaScriptと変わらない記述量でした。 ターゲットをどうするかということろが少しほかの方法で実装したときと異なる感じとなりました。 3. WebSocket Serverで実装(言語はPython) 普段Pythonを使って業務しているので何とかPythonで自由に書けないかといろいろ調べてみました。 WebSocketサーバを作ってクライアントからつなぐことによってプログラミングできることが分かったので WebSocketサーバをPythonで書いてみることにしました。 下記の記事を参考にしてみました http://www.s-anand.net/blog/programming-minecraft-with-websockets/ main.py import asyncio import json import random from uuid import uuid4 import websockets EVENTS = [ "PlayerMessage", ] async def mineproxy(websocket, path): print("Connected") msg = { "header": { "version": 1, "requestId": "", "messageType": "commandRequest", "messagePurpose": "subscribe", }, "body": {"eventName": "PlayerMessage"}, } await websocket.send(json.dumps(msg)) async def send(cmd): await websocket.send( json.dumps( { "header": { "version": 1, "requestId": str(uuid4()), "messagePurpose": "commandRequest", "messageType": "commandRequest", }, "body": { "version": 1, "commandLine": cmd, "origin": {"type": "player"}, }, } ) ) async def stage_reset(): # 一定の範囲に特定のブロックを配置する await send("/fill -5 5 13 4 7 22 air") await send("/fill -5 6 13 4 6 22 soul_sand") # 出現条件をランダムにするために、特定のブロックの配置をランダムにする x = random.randint(1, 10) z = random.randint(1, 10) await send(f"/setblock {-5 + x} 5 {13 + z} soul_sand") async def user_init(name: str): cmds = [ # ゲームの進行に必要な配置可能なブロックの付与 '/give {} skull 128 1 {"minecraft:can_place_on":{"blocks":["soul_sand"]}}', # 装備や消耗品の付与 "/give {} netherite_sword 1", "/give {} bow 1", "/give {} enchanted_golden_apple 64", "/give {} netherite_helmet 1", "/give {} netherite_chestplate 1", "/give {} netherite_leggings 1", "/give {} netherite_boots 1", "/give {} arrow 64", ] for cmd in cmds: await send(cmd.format(name)) try: async for msg in websocket: print(msg) msg = json.loads(msg) if msg["body"].get("eventName", "") == "PlayerMessage": text = msg["body"]["properties"]["Message"] if text == "stage-reset": await stage_reset() elif text == "user-init": player_name = msg["body"]["properties"].get("Sender") await user_init(player_name) except websockets.ConnectionClosedError: print("Disconnected") start_server = websockets.serve(mineproxy, port=19131) print("/connect localhost:19131") asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() 実装してみたところ直接的にゲーム内のボタンに割り当てることが難しかったので コマンドとして実装する形に落ち着きました。 WebSocketを制御する部分を作り込む必要があるのでコード量はその分増えましたが かなり自由度が高いと感じました。 4. MakeCodeで実装(Python) 最後に試したのがMakeCodeでした。 こちらは教育目的と思って後回しにしていたのですが、 教育目的だけあってハードルの低さが際立ちました。 バックエンド仕組みとしては3. WebSocketを使っています。 (むしろこちらのためにある機能のようです) プログラミングの方法としてブロックプログラミング、JavaScript、Pythonを選択可能です。 私はもちろんPythonを選択しました。 main.py def stage_reset(): # 一定の範囲に特定のブロックを配置する blocks.fill(AIR, world(-5, 5, 13), world(4, 7, 22)) blocks.fill(SOUL_SAND, world(-5, 6, 13), world(4, 6, 22)) # 出現条件をランダムにするために、特定のブロックの配置をランダムにする x = randint(1, 10) -5 z = randint(1, 10) + 13 blocks.place(SOUL_SAND, world(x, 5, z)) def user_init(): # ゲームの進行に必要な配置可能なブロックの付与 player.execute('give @s skull 128 1 {"minecraft:can_place_on":{"blocks":["soul_sand"]}}') # 装備や消耗品の付与 player.execute("give @s netherite_sword 1") player.execute("give @s bow 1") player.execute("give @s netherite_helmet 1") player.execute("give @s netherite_chestplate 1") player.execute("give @s netherite_leggings 1") player.execute("give @s netherite_boots 1") player.execute("give @s arrow 64") player.on_chat("stage-reset", stage_reset) player.on_chat("user-init", user_init) Github 実装してみて感じたのはWebSocketの制御部分がすべて隠蔽されていて マイクラを操作するための関数+標準的なPythonの関数が準備されているため やりたいことに対して直感的に実装できるということでした。 注意点としてMakeCodeを使ったプログラムを動かすためには Minecraft Windows10版またはMinecraft Education Edition(iPadOS/macOS/ChromeOS/Windows10)が必要ということです。 Minecraft Windows10版ではCode Connection for Minecraftが必要になります。 これがWebSocketサーバとして動作するためほかのOSでは動かないということになります。 あくまで「プログラミングを学ぶ」用途なので「ゲーム作りをする」のには向かないといえます。 最後に 全部で4種類の方法でゲームを実装してみることを試してみましたが、 まだまだ簡単にできることしかできなかったので、来年は調べたことをもとに もう少し凝ったものが作れるといいなと考えています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase Realtime Databaseのクエリの書き方(JavaScript version9対応)

はじめに Firebase Realtime Databaseを扱うためのSDK(JavaScript用 version9)に関する資料があまり見当たらなかったため、本記事では参考資料の公式動画を基に、一般的なSQLのクエリに対するSDKでのクエリの書き方をまとめました。 SQLの場合 まずは一般的なSQLのクエリを見ていきます。 Users テーブル UID Name Email Age Location 1 satoh satoh@example.com 20 osaka 9 tanaka tanaka@example.com 52 tokyo UID=1のユーザーを取得 SELECT * FROM Users WHERE UID = 1; Email列から"tanaka@example.com"を含むユーザーを取得 SELECT * FROM Users WHERE Email = "tanaka@example.com"; 選択するユーザーを10人に制限 SELECT * FROM Users LIMIT 10; "s"から始まるユーザーを取得 SELECT * FROM Users WHERE Name LIKE "s%"; 50歳未満のユーザーを取得 SELECT * FROM Users WHERE Age < 50; 50歳超過のユーザーを取得 SELECT * FROM Users WHERE Age > 50; 20歳から100歳までのユーザーを取得 SELECT * FROM Users WHERE Age >= 20 && Age <= 100; 東京都在住の52歳のユーザーを取得 SELECT * FROM Users WHERE Location = "tokyo" && Age = 52; Firebase SDK(JavaScript version9)の場合 Firebase Realtime Databaseで上記のUsersテーブルを表現するには次のようになります。 { "Users": { "1": { "Name": "satoh", "Email": "satoh@example.com", "Age": 20, "Location": "osaka", "Location_Age": "osaka_20" }, "9": { "Name": "tanaka", "Email": "tanaka@example.com", "Age": 52, "Location": "tokyo", "Location_Age": "tokyo_52" } } } まず準備として、Firebase SDKでデータベースを初期化します。 const firebaseConfig: FirebaseOptions = { //初期化設定 }; // Initialize Firebase const app = initializeApp(firebaseConfig); const database = getDatabase(app) 次にデータベースのrootとUsersの参照を宣言します。 // Root Reference const rootRef = ref(database) // Users Root Reference const usersRef = child(rootRef, "Users") 各参照先は次のようになります。 これで準備が完了したので、先程の1~8のクエリを書き換えていきます。 UID=1のユーザーを取得 // SELECT * FROM Users WHERE UID = 1; // Firebase SDK Ver.8 // const oneRef = rootRef.child("child").child("1") // Firebase SDK Ver.9 const oneRef = child(rootRef, "Users/1"); // または // const oneRef = child(usersRef, "1"); Ver.8はメソッドチェーンが主流でしたが、Ver.9からは引数で指定する形に変わっていることに注意してください。 oneRefを定義後、下記のコードからデータを取得できます。 // Firebase SDK Ver.8での書き方 // oneRef.on("value", (snapshot) => console.log(snapshot.val()); // Firebase SDK Ver.9での書き方 onValue(oneRef, (snapshot) => console.log(snapshot.val())); Email列から"tanaka@example.com"を含むユーザーを取得 // SELECT * FROM Users WHERE Email = "tanaka@example.com"; const twoRef = query(usersRef, orderByChild("Email"), equalTo("tanaka@example.com")); UID以外のネストされた各ユーザーのkeyを検索する場合、query関数とorder関数やフィルタリング関数を用います。 query関数の第1引数にUsersの参照先、第2引数以降はorder関数やフィルタリング関数を入れます。(第2引数以降は入れた順番に処理が実行されます。) 注意しないといけないのは「1つのqueryに対して指定できるorder関数は1つまで」という制約があります。 order関数を2つ以上入れると、データ取得時にエラーが返ってきます。 上記のコードは、「各ユーザーのEmailキーに"tanaka@example.com"が含まれるユーザーを取得」するためのクエリとなります。 選択するユーザーを10人に制限 //SELECT * FROM Users LIMIT 10; const threeRef = query(usersRef, orderByKey(), limitToFirest(10)); // orderByKey()は省略可能のため、下記でも可能 const threeRef = query(usersRef, limitToFirst(10)); フィルタリング関数のlimitToFirestを用います。 引数に入れた数値分のデータを取得します。 "s"から始まるユーザーを取得 // SELECT * FROM Users WHERE Name LIKE "s%"; const fourRef = query(usersRef, orderByChild("Name"), startAt("s"), endAt("s\uf8ff")); フィルタリング関数のstartAtとendAtを用います。 order関数と違い、フィルタリング関数は複数入れることができます。 endAt("s\uf8ff"))でワイルドカードを再現しています。 50歳未満のユーザーを取得 // SELECT * FROM Users WHERE Age < 50; const fiveRef = query(usersRef, orderByChild("Age"), endBefore(50)); 〇〇未満はendBefore 、〇〇以下はendAtを用います。 50歳超過のユーザーを取得 // SELECT * FROM Users WHERE Age > 50; const sixRef = query(usersRef, orderByChild("Age"), startAt(51)); 〇〇超過はstartAfter、〇〇以上はstartAtを用います。 20歳から100歳までのユーザーを取得 // SELECT * FROM Users WHERE Age <= 20 && Age >= 100; const sevenRef = query(usersRef, orderByChild("Age"), startAt(20), endAt(100)); 東京都在住の52歳のユーザーを取得 // SELECT * FROM Users WHERE Location = "tokyo" && Age = 52; // 注意: このクエリでデータを取得しようとするとエラーが返ります const eightRef = query(usersRef, orderByChild("Location"), equalTo("tokyo"), orderByChild("Age"), equalTo(52)); ただし、このクエリでデータを取得しようとするとエラーが返ります。 これは2.で述べた「1つのqueryに対して指定できるorder関数は1つまで」という制約があるためです。 この制約を守りつつ検索するには、データ構造を検索できる形に最適化する必要があります。 つまり、order関数が1つしか使えないのなら、検索対象を1つにまとめてデータベースに追加すれば良いのです。 { "Users": { "1": { "Name": "satoh", "Email": "satoh@example.com", "Age": 20, "Location": "osaka", "Location_Age": "osaka_20" }, "9": { "Name": "tanaka", "Email": "tanaka@example.com", "Age": 52, "Location": "tokyo", "Location_Age": "tokyo_52" } } } "Location_Age"を追加しました。 この状態で、クエリを修正します。 // SELECT * FROM Users WHERE Location = "tokyo" && Age = 52; const eightRef = query(usersRef, orderByChild("Location_Age"), equalTo("tokyo_52")) これで、UID=9のデータを取得できます。 まとめ WHERE句やLIMIT句はFirebase SDKの関数ベースで再現できます。 Firebase SDK Ver.9はVer.8から破壊的変更がされており、Ver.8メソッドチェーンが主流だったのに対して、Ver.9からは引数で指定する形に変わっているため注意が必要です。 1つのクエリに対して指定できるorder関数は1つまでとなっているため、検索できるようにデータ構造を最適化または設計段階で検索方法を検討した上で構築する必要があります。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue + ros 基本の通信

はじめに Vueとは何かというとJavaScriptのフレームワークでHTML,JavaScript,CSSのプログラムを1つのコンポーネントとして扱いそれを組み合わせWebアプリを作成することができます。 そのため1つ1つの機能のプログラムを小さく保つことができWebアプリ開発が楽になります。ロボットのWebアプリ開発でも規模が大きい場合は使うと良いかも知れません。 ここでは,Vueの最初のサンプルにrosと通信する部分を追加する方法について記載させていただきます。 また、Vueのインストール方法や立ち上げ方法などは以下など他の人の記事を参考にしてください。 環境 Ubuntu 20.04 ROS noetic Vue 3.0 準備 vueプロジェクトを作成したら,まずROSとの通信に使用するroslibをインストールします npm install roslib あとはsrc/App.vueを以下に記載しているサンプルに変更し, roslaunch rosbridge_server rosbridge_websocket.launchとともに実行することでROSとの通信を色々試すことができます。 サンプル 1.ベースプログラム rosbridgeとのコネクションをするだけのコードです.今後記載するサンプルのベースプログラムとなります. src/App.vue <template> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </template> <script> import ROSLIB from "roslib" // 追加 import HelloWorld from './components/HelloWorld.vue' // *** 追加 ここから *** // const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' }); // *** 追加 ここまで *** // export default { name: 'App', components: { HelloWorld }, // *** 追加 ここから *** // mounted() { this.init(); }, methods: { init: function () { ros.on("connection", function() { console.log("connected to websocket server."); }); ros.on("error", function(error) { console.log("error connecting to websocket server: ", error); }); ros.on("close", function() { console.log("connection to websocket server closed."); }); }, // *** 追加 ここまで *** // } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> 2.Publisher ブラウザからPublishするサンプルプログラムです。 src/App.vue <template> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> <p><button v-on:click="publish()">publish</button></p> <!-- 追加 --> </template> <script> import ROSLIB from "roslib" import HelloWorld from './components/HelloWorld.vue' const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' }); // *** 追加 ここから *** // var cmdVel = new ROSLIB.Topic({ ros : ros, name : '/cmd_vel', messageType : 'geometry_msgs/Twist' }); var twist = new ROSLIB.Message({ linear : { x : 0.1, y : 0.2, z : 0.3 }, angular : { x : -0.1, y : -0.2, z : -0.3 } }); // *** 追加 ここまで *** // export default { name: 'App', components: { HelloWorld }, mounted() { this.init(); }, methods: { init: function () { ros.on("connection", function() { console.log("connected to websocket server."); }); ros.on("error", function(error) { console.log("error connecting to websocket server: ", error); }); ros.on("close", function() { console.log("connection to websocket server closed."); }); }, // *** 追加 ここから *** // publish: function () { cmdVel.publish(twist); }, // *** 追加 ここまで *** // } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> 実行の結果以下のようになり、ブラウザのボタンを押すと,/cmd_velがpublishされます 3.Subscriber 次はSubscriberのサンプルプログラムです。 src/App.vue <template> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> <p>sub msg: {{ msg }} </p> <!-- 追加 --> </template> <script> import ROSLIB from "roslib" import HelloWorld from './components/HelloWorld.vue' const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' }); // *** 追加 ここから *** // var sub = new ROSLIB.Topic({ ros: ros, name: "/listener", messageType: "std_msgs/String" }); // *** 追加 ここまで *** // export default { name: 'App', components: { HelloWorld }, // *** 追加 ここから *** // data: function () { return { msg: "", }; }, // *** 追加 ここまで *** // mounted() { this.init(); }, methods: { init: function () { ros.on("connection", function() { console.log("connected to websocket server."); }); ros.on("error", function(error) { console.log("error connecting to websocket server: ", error); }); ros.on("close", function() { console.log("connection to websocket server closed."); }); // *** 追加 ここから *** // var self = this; sub.subscribe(function(message) { //console.log("callback",message); self.msg = message; }); // *** 追加 ここまで *** // }, } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> 実行の結果以下のようになり、/listenerにデータを送るとブラウザに表示されます 4.Service Client Service Clientのサンプルです。 src/Add.vue <template> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> <p><button v-on:click="call()">service call</button></p> <!-- 追加 --> </template> <script> import ROSLIB from "roslib" import HelloWorld from './components/HelloWorld.vue' const ros = new ROSLIB.Ros({ url: 'ws://localhost:9090' }); // *** 追加 ここから *** // var addTwoIntsClient = new ROSLIB.Service({ ros : ros, name : '/add_two_ints', serviceType : 'rospy_tutorials/AddTwoInts' }); var request = new ROSLIB.ServiceRequest({ a : 1, b : 2 }); // *** 追加 ここまで *** // export default { name: 'App', components: { HelloWorld }, mounted() { this.init(); }, methods: { init: function () { ros.on("connection", function() { console.log("connected to websocket server."); }); ros.on("error", function(error) { console.log("error connecting to websocket server: ", error); }); ros.on("close", function() { console.log("connection to websocket server closed."); }); }, // *** 追加 ここから *** // call: function () { addTwoIntsClient.callService(request, function(result) { console.log('Result for service call on ' + addTwoIntsClient.name + ': ' + result.sum); }); }, // *** 追加 ここまで *** // } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> rospy_tutorialsのadd_two_serverを実行するとサービスのコールを確認できます。標準では入っていないと思うので別途インストールしてください。 4.最後に vueでrosトピックのpublish,subscribeとサービスのコール方法を記載しました。 興味のある方はお試しください。 5.参考 スマホアプリでROSのUIを作る場合はFlutterがおすすめらしい(ロボットにスマホ乗せる場合とか良さそう) roslib JavaScriptでの使用方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webの勉強はじめてみた その4

今日は1コマだけ。 今日やったこと JavaScriptのデータ型、計算、変数 気付いたこと 1. 指数表現 2. 変数 1. 指数表現 Xe + Y 初めて知った表現。 X × 10 の Y 乗 ( X×10**Y ) コンソールなんかで、値が大きすぎるとこういう表示になるらしい。 そういえばエクセルでも同じだったような気がする。 1.5e+20 ⇒ 1.5 × 10**20 Xにあたる数字は桁数を増やさずに小数で表現するのが一般的? 15e+10 ⇒ 1.5e11 2. 変数 var let の違い const は名前的にも定数では? と思うものの、var と let の違いはなんとなく。 letで宣言したほうがチームでの開発とかでは無難なのかも。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動きのあるホームページ アイディア

動きのあるおしゃれなWebページを作るときのアイディアの参考 自分用備忘録です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータ特有の演算誤差

この記事の執筆者 プログラミング学習を始めて、5ヶ月ほど。Vue.js Typescript Railsを学習中の大学生. きっかけ Typescriptの参考書を学習中に下記のようなコードを書いたら予期しない結果になったため調べてみることにした。参考書には誤差が出る理由等は詳しく書かれていなかった。 let price = 12500 let tax = 1.1 let withTax = price * tax console.log("金額" + price ) console.log(`税込価格は、${withTax}`) 結果 金額12500 税込価格は、13750.000000000002 上記のコードは定価である12,500円に対して税率である0.1を加えたものである。 期待していた結果としては下記のようなもの 12,500 * 1.1 = 13,750 コンピューター特有の演算誤差 このような予期しない演算結果が出るのは、どうやら調べてみるとJavascriptだけではないようだ。 コンピュータの内部では2進数で計算されているため! らしいのだが2進数について完全に忘れていたため(おそらく高校で少しだけやった...)まずは2進数について調べてみた。 2進数 そもそも2進数とは 0と1で表された値 10進数 2進数 0 0 1 1 3 11 4 100 5 101 6 110 7 111 0.1 0000 0000 . 00001 1001 1001 1001 1001... どうやら 0.1は2進数で表すと0000 0000 . 0001 1001 1001 1001 1001 1001 .....以下無限に続くよう(循環小数) このように小数の一部では2進数で表すと循環小数になってしまう。よってそれを扱った演算では計算が意図しないものになることがわかった。 解決方法 let price = 12500 let tax = 1.1 * 10 let withTax = (price * tax) / 10 console.log("金額" + price ) console.log(`税込価格は、${withTax}`) 結果 金額12500 税込価格は、13750 上記のコードでは小数を10倍して整数に直す(2進数では整数の形だと循環小数にならないため) そして、計算した結果を10で割ることによって結果を出している。 他のやり方もあるそうなのでできなかった方は以下を参考にしてください。  まとめ 万能に見えていたコンピュータでも1と0しか把握できないことで意図しない計算結果を生む可能性があることがわかった。複雑な計算になればなるほどこのような箇所は見落としがちなので十分に注意したい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsby×HeadlessCMS(Contentful)でポートフォリオサイトを作った話

ポートフォリオサイト、今まであったものを作り直し、GatsbyとHeadlessCMS(Contentful)で作ってみました。 できたのがこちら!? https://www.qulo.ooo/ 作り直しの経緯とこれらの組み合わせについてまとめてみることにします✍️ ?ポートフォリオサイトの経緯 1代目 FC2ホームページ 2代目 html+CSS(この頃はプログラミングのプの字もわからず…海外のデザインサイトを検証→ソースコードみて必要そうなパーツをつなぎ合わせてヨシとしたキメラ) ?どうして2代目から作り直そうと思ったか? 更新性が以下の理由から悪かった… 表示用画像をディスプレイサイズの最適化用に複数枚用意する必要があった 記事を増やすときはhtmlを編集、記事ファイルがその度に増えていく CSS記述だったため関数が使えず全体的にちょっとカラー変えたいみたいなときに該当するカラーコードをすべて書き換えなければいけない html CSS以外の言語でかいてみたいなとおもった そのために更新頻度が無&モダンな技術を試してみたいこともあり作り直すことにしました。 ?Gatsbyのよいところ そもそもGatsbyとは? Facebook社(最近Metaへの社名変更が話題になってましたね〜)が開発しているReact製の静的サイトジェネレーター。JavaScriptライブラリ。 スターター(テンプレート)がいっぱいある Netlifyというホスティングサービスを使い、それをGitHubと連携させることで、masterブランチに更新内容をマージするだけでサイトを更新することができるので更新性が◎ マークダウン式で記事をかけるので、QiitaやMediumといった別サイトへそのまま持っていくことができる ?Headless CMS(Contentful)のよいところ Gatsbyだけでもマークダウン式で記事をかくことはできるが、記事ファイルをリポジトリ内で管理しないといけない… =html CSSで記述していた2代目ポートフォリオサイトのときに問題となっていた、記事が増える度にファイル数が増えていく問題の解決になっていない! ?Headless CMSの導入により記事管理を外部サービスにお任せすることができるように! (サイトを多言語化したいといった場合、ファイル数が単純に倍になる問題も解決◎) なぜContentful? Gatsbyとの組み合わせで使用されているユーザーが多くコミュニティが活発なため 将来的に対応したい多言語化対応にはContentfulがつよそうだったため マークダウン式でかけるので、MediumやQiitaなど外部サービスにそのままもっていくこともできる ?使ってみた感想 Gatsby × Contentful めちゃ使いやすい! けど自分用にもドキュメントを残しておくべし なぜドキュメントが必要? HeadlessCMS使用している箇所はソースコードからも読み解きにくい。 例えば更新性があるところだけ使っている人もいれば、About画面など更新性の低いページもすべてHeadlessCMSで管理している人もいた。 「あれっ、ここの文言修正したいけどソースコードにない!」ということが起こりうるので、HeadlessCMSの使用範囲は記録しておく必要があるなぁと思った React(JavaScriptライブラリ)により、html CSSだと手が届かなかった表現も可能になるGatsby。 スターターセットを活用することでお手軽にはじめることができるので、 個人サイト興味あるけどなにからはじめたらいいかわからない…という方にもすごくおすすめです! ?他に検討したこと フレームワークについて FlutterWebなども検討したがSEOの観点からやめておいた ✅スライド資料公開中 Speaker Deckにスライド資料投稿しております! よかったら合わせてご覧ください?‍♀️
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

いまさらJavascriptの非同期処理について

PHP8.1で非同期処理が書ける様になったので、JavaScriptの非同期処理について、スタック、キュー、イベントループなどの言葉を使わないで説明します。 // 素数を割り算で求める計算だけをする実行に時間のかかる関数 function sleep(){ for(let i= 2; i<100000; i++){ for(let j = i - 1; j > 1; j--){ if(i % j === 0){ break; } } } console.log('おしまい'); } // 0.1秒後, 0.2秒後に表示されるのを期待している setTimeout(()=>{console.log('setTimeout!200')}, 200); setTimeout(()=>{console.log('setTimeout!100')}, 100); sleep(); 上記コード実行するとマシンのパワーにもよりますが5秒くらいたってから setTimeout!100 setTimeout!200 と表示されます。0.1秒後と0.2秒後に表示したかったのに・・・。 登場人物は「ブラウザ」、「JSを実行する場所」、「順番待ち列」 setTimeOutはブラウザの役割です。これがポイントです。 JSの関数の実行順序は以下の通り。 JavaScriptのコードは書かれている順に順次「JSを実行する場所」で実行されていく。 順次実行するコードにsetTimeOutがあったら、setTimeOutの中の関数を実行しないで「ブラウザ」に渡す。 ブラウザは指定された時間になったら「順番待ち列」に受け取った関数を入れる。 「JSを実行する場所」は引き続きコードを実行していっている。実行するコードがなくなったら、やっと関数は「順番待ち列」から「JSを実行する場所」へ入ることができる。 「JSを実行する場所」で関数が実行される。 上の方で書いたプログラムだと、ブラウザはsetTimeoutで渡された関数を100ms, 200ms後に「順番待ち列」に入れたんだけど、その関数たちはsleep();が終わるまでを「JSを実行する場所」に入れなかったという事です。 setTimeOutはブラウザの役割と書きましたが、例えば、ボタンをクリックしたときに関数を「順番待ち列」に入れるのもブラウザの役割です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lineでタスク管理

gasを使用して、lineでタスク管理するツールを作ってみました! 仕様 Lineでタスク名、完了期限をメッセージ送信するとタスクが登録されます。 タスクの確認、完了タスクの削除、リマインドが可能です。 動作させる為に必要な準備 1.Messaging Apiの作成 2.マスタシートの準備 3.ソースの準備 4.スクリプトプロパティの登録 5.以降で動作確認、プログラムの簡単な説明 Messaging Apiの作成 やる事としては、 1. Lineコンソールへログイン 2. プロバイダの新規作成 3. チャネル(Messaging API)の作成 です。 下記サイトを参考にLine関係の準備をしてください! 設定について Messaging Apiの作成が終わると下記のような画面になると思います。 まず、Messaging Api設定タブを開きます。 下の方に行くと、応答メッセージというのがあるので「無効」にしてしまいましょう。 編集リンク押下で編集出来ます! 編集ボタン押下でこんな画面が出るので、下記のように変更してください。 「あいさつメッセージ」はどっちでも良いです! 変更したらこのページは閉じてOKです。 次は、アクセストークンを発行しましょう! 発行ボタンを押せば完了です。このトークンを使用してAPIアクセス出来ます! ついでに上の方に戻るとQRコードあるので、友達追加しておいてください。 マスタシートの準備 マスタはスプレッドシートを使用します。 下記のように、「タスク」シートと「メッセージ」シートを作って下さい。 「タスク」シートは実際にタスクが登録されるマスタです。 「メッセージ」シートは、受信メッセージによって処理を決定するためのマスタです。 例えば、Lineで「今のタスク」と入力するとshorikbnとして"3"が処理に渡ります。 今回の実装では下記区分値とさせていただいております。 1:タスクの登録 2:タスクの削除 3:登録されているタスクの照会 メッセージを追加することで、色々なメッセージに対応出来るようになります。 ソース 今回実装したコードになります。 上記で作成したスプレッドシートからスクリプトエディタを開き、コピペして下さい。 main.gs main.gs //postリクエストを受取ったときに発火する関数 function doPost(e) { // 応答用Tokenを取得。 const replyToken = JSON.parse(e.postData.contents).events[0].replyToken; // メッセージを取得 const userMessage = JSON.parse(e.postData.contents).events[0].message.text; try { //メッセージを改行ごとに分割 const allMsg = userMessage.split(NEW_LINE); // スプレッドシートの取得 const spreadSheet = SpreadsheetApp.getActiveSpreadsheet(); // 処理区分の取得 const shoriKbn = shoriKbnGet(spreadSheet.getSheetByName(SHEET_MESSAGE), allMsg[0]); // データを書き込むスプレッドシートを定義 const taskSheet = spreadSheet.getSheetByName(SHEET_TASK); // 返答メッセージ let message = ""; switch (shoriKbn) { case SHORI_KBN_INS: allMsg.shift(); message = dataAdd(taskSheet, allMsg); break; case SHORI_KBN_DEL: allMsg.shift(); message = deleteRow(taskSheet, allMsg); break; case SHORI_KBN_GET: message = returnData(taskSheet); break; default: message = "メッセージマスタに登録されていない文字が送信されたので処理が出来ませんでした。"; break; } // lineで返答する lineReply(message, replyToken); } catch (e) { let message = "エラーが発生しました。" + e; lineReply(message, replyToken); } } parts.gs parts.gs /** * 処理区分取得処理 * @param msgSheet メッセージシート * @param inputMsg 入力メッセージ * @return 処理区分 */ function shoriKbnGet(msgSheet, inputMsg) { const msgObjects = dataGet(msgSheet); //メッセージマスタと入力メッセージを比較し、処理区分を決定する。 for (const msg of msgObjects) { if (inputMsg.indexOf(msg.message) != -1) { return msg.shorikbn; } } return ""; } /** * バリデーションチェック処理 * @param taskSheet シート * @param lastColumn 最終列 * @param allMsg インプットメッセージ * @return エラーメッセージ */ function validationCheck(taskSheet, lastColumn, allMsg) { //メッセージの形式チェック if (allMsg.length !== lastColumn) { return `以下の形式で送信して下さい。${NEW_LINE}${NEW_LINE}登録 登録して 等${NEW_LINE}タスク名${NEW_LINE}完了期限(yyyyMMdd)`; } // タスク名チェック const taskName = allMsg[0]; const dataObjects = dataGet(taskSheet); for (const dataObj of dataObjects) { if (dataObj.taskname === taskName) { return "すでに登録されているタスクです。"; } } // 完了期限チェック const timeLimit = allMsg[1]; if (!timeLimit.match("^[0-9]{8}$")) { return "期限はyyyyMMdd形式で指定してください。" } const y = timeLimit.substr(0, 4); const m = timeLimit.substr(4, 2); const d = timeLimit.substr(6, 2); const date = new Date(y, m - 1, d); if (m != date.getMonth() + 1) { return "無効な日付です。" } return ""; } /** * データ登録処理 * @param taskSheet シート * @param allMsg インプットメッセージ * @return 正常終了メッセージ or エラーメッセージ */ function dataAdd(taskSheet, allMsg) { // 最終列の取得 const lastColumn = taskSheet.getLastColumn(); //受信メッセージが正しい形式か確認 const errorMsg = validationCheck(taskSheet, lastColumn, allMsg); if (errorMsg) { return errorMsg; } // タスクを書き込む行 const newRow = taskSheet.getLastRow() + 1; // タスクを書き込む allMsg.forEach((msg, i) => taskSheet.getRange(newRow, i + 1).setValue(msg)); // 完了期限順でソートしておく taskSheet.getRange(START_ROW, START_COLUMN, newRow, lastColumn).sort(COLUMN_TIMELIMIT); return "データを登録しました。"; } /** * データ返却処理 * @param taskSheet シート * @return reTaskArray リプライ用タスクリスト */ function returnData(taskSheet) { // データがあるか判定 if (taskSheet.getLastRow() == 1) { return "タスクがありません。"; } // タスクを全て取得し、返却用メッセージに編集する。 let reTaskArray = `現在登録されているタスクです。${NEW_LINE}`; const dataObjects = dataGet(taskSheet); dataObjects.forEach(dataObject => { // タスクごとに改行を入れる。 reTaskArray += NEW_LINE; Object.keys(dataObject).forEach(key => reTaskArray += `${key}:${dataObject[key]}${NEW_LINE}`); }); return reTaskArray; } /** * データ削除処理 * @param taskSheet シート * @param allMsg インプットメッセージ * @return 正常終了メッセージ or エラーメッセージ */ function deleteRow(taskSheet, allMsg) { // 最終行の取得 const lastRow = taskSheet.getLastRow(); // 削除対象キー const key = allMsg[0]; // 1行目から順にタスク名を比較し、一致していたら削除する。 for (let i = START_ROW; i <= lastRow; i++) { const taskName = taskSheet.getRange(i, 1).getValue(); if (key === taskName) { taskSheet.deleteRow(i); return "完了タスクを削除しました。"; } } return "入力されたタスクがありません。" } /** * トリガー設定処理 */ function setTrigger() { const date = new Date(); // 9時にトリガーを設定する date.setHours(9); date.setMinutes(0); date.setSeconds(0); ScriptApp.newTrigger('remind').timeBased().at(date).create(); } /** * リマインド処理 */ function remind() { // データを書き込むスプレッドシートを定義 const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_TASK); // スプレッドシートからタスクを取得 const dataObjects = dataGet(sheet); // 現在日時の取得 const keyDate = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyyMMdd"); let message = ""; // 現在日時とtimelimitが一致するデータをリマインドする。 dataObjects.forEach(dataObject => { if (dataObject.timelimit == keyDate) { message += NEW_LINE; Object.keys(dataObject).forEach(key => message += `${key}:${dataObject[key]}${NEW_LINE}`); } }); // リマインドするタスクが無い場合は処理しない。 if (!message) { return; } push(`今日期限のタスクです。${NEW_LINE}${message}`); } /** * データ取得処理 * マスタデータをオブジェクト化して返却する * * @param sheet シート * @return dataObjects データオブジェクト */ function dataGet(sheet) { const dataArray = sheet.getRange(HEADER_ROW, START_COLUMN, sheet.getLastRow(), sheet.getLastColumn()).getValues(); const header = dataArray.shift(); const dataObjects = []; dataArray.forEach(dataRow => { const dataObj = {}; dataRow.forEach((data, i) => { dataObj[header[i]] = data; }); dataObjects.push(dataObj); }); return dataObjects; } /** * メッセージ送信処理 * @param message メッセージ */ function push(message) { //プロパティを取得 const scriptProperties = PropertiesService.getScriptProperties(); const LINE_TOKEN = scriptProperties.getProperty("LINE_TOKEN"); const USER_ID = scriptProperties.getProperty("USER_ID"); const LINE_PUSH_URL = scriptProperties.getProperty("LINE_PUSH_URL"); const headers = { "Content-Type": "application/json; charset=UTF-8", "Authorization": `Bearer ${LINE_TOKEN}` }; const postData = { "to": USER_ID, "messages": [ { 'type': 'text', 'text': message, } ] }; const options = { "method": "post", "headers": headers, "payload": JSON.stringify(postData) }; // lineで応答する UrlFetchApp.fetch(LINE_PUSH_URL, options); } /** * メッセージ応答処理 * @param message メッセージ * @param replyToken リプライトークン */ function lineReply(message, replyToken) { //プロパティを取得 const scriptProperties = PropertiesService.getScriptProperties(); const LINE_TOKEN = scriptProperties.getProperty("LINE_TOKEN"); const LINE_REPLY_URL = scriptProperties.getProperty("LINE_REPLY_URL"); const headers = { "Content-Type": 'application/json; charset=UTF-8', "Authorization": `Bearer ${LINE_TOKEN}` }; const postData = { "replyToken": replyToken, "messages": [ { "type": "text", "text": message } ] }; const options = { "method": "post", "headers": headers, "payload": JSON.stringify(postData) }; //lineで返答する UrlFetchApp.fetch(LINE_REPLY_URL, options); } variable.gs variable.gs /**改行コード */ const NEW_LINE = "\n"; /**シート名_タスク */ const SHEET_TASK = "タスク"; /**シート名_メッセージ */ const SHEET_MESSAGE = "メッセージ"; /**処理区分_登録 */ const SHORI_KBN_INS = "1"; /**処理区分_削除 */ const SHORI_KBN_DEL = "2"; /**処理区分_照会 */ const SHORI_KBN_GET = "3"; /**ヘッダー行番号 */ const HEADER_ROW = 1; /**データのスタート行番号 */ const START_ROW = 2; /**データのスタート列番号 */ const START_COLUMN = 1; /**カラム_完了期限 */ const COLUMN_TIMELIMIT = 2; スクリプトプロパティの登録 コードの中で、スクリプトプロパティにアクセスしている箇所がいくつかあります。 例えば、下記のようにトークンを使用する際などです。 parts.gs // ~ 略 ~ /** * メッセージ送信処理 * @param message メッセージ */ function push(message) { //プロパティを取得 const scriptProperties = PropertiesService.getScriptProperties(); const LINE_TOKEN = scriptProperties.getProperty("LINE_TOKEN"); const USER_ID = scriptProperties.getProperty("USER_ID"); const LINE_PUSH_URL = scriptProperties.getProperty("LINE_PUSH_URL"); // ~ 略 ~ これらは事前に設定する必要があります。 下記コードを実行して下さい。 setPropertie.gs function setPropertie() { //スクリプトプロパティを取得。 const scriptProperties = PropertiesService.getScriptProperties(); scriptProperties.setProperties({ 'LINE_TOKEN': 'Messaging Apiの作成にて発行したアクセストークン', 'LINE_REPLY_URL': 'https://api.line.me/v2/bot/message/reply', 'LINE_PUSH_URL': 'https://api.line.me/v2/bot/message/push', 'USER_ID': 'この後の説明参照' }); } USER_IDについては、Messaging Apiで確認可能です。 チャネル基本設定タブへ行き、 下の方に行くと、あなたのユーザーIDというのがあります。これをUSER_IDとして登録して下さい。 動作確認 まずはタスクを登録してみます。 登録時のメッセージの送信形式は 処理名(登録、登録してなど) タスク名 完了期限 です。 入力して送信すると、 タスクシートに登録されます。 一応日付や形式のチェックとかはしています。 例えば存在しない日付を入力すると、 エラーになります。 メッセージマスタに登録されている単語であれば、処理をしてくれます。 例えば、「追加して」と言ってみます。 登録されました! 登録は完了期限の昇順でソートされます。 次は登録されているタスクの確認をしてみます。 「今のタスクは?」や「照会して」などと聞くと、教えてくれます。 完了したタスクは削除してしまいましょう。 「削除して」や「完了」と入れて2行目にタスク名を入力すると、削除されます。 スプレッドシートからも削除されています! 完了期限当日になってもタスクが残っている場合は、9:00にリマインドしてくれます。 プログラム説明 簡単に処理の流れを解説します。 処理区分の判定 まず、main.gsのこの部分で処理を判定します。 main.gs // ~ 略 ~ // 処理区分の取得 const shoriKbn = shoriKbnGet(spreadSheet.getSheetByName(SHEET_MESSAGE), allMsg[0]); // データを書き込むスプレッドシートを定義 const taskSheet = spreadSheet.getSheetByName(SHEET_TASK); // 返答メッセージ let message = ""; switch (shoriKbn) { case SHORI_KBN_INS: allMsg.shift(); message = dataAdd(taskSheet, allMsg); break; case SHORI_KBN_DEL: allMsg.shift(); message = deleteRow(taskSheet, allMsg); break; case SHORI_KBN_GET: message = returnData(taskSheet); break; default: message = "メッセージマスタに登録されていない文字が送信されたので処理が出来ませんでした。"; break; } // ~ 略 ~ 処理区分の取得でshoriKbnGetという関数を呼び出します。 shoriKbnGetはparts.gsに定義しています。 parts.gs /** * 処理区分取得処理 * @param msgSheet メッセージシート * @param inputMsg 入力メッセージ * @return 処理区分 */ function shoriKbnGet(msgSheet, inputMsg) { const msgObjects = dataGet(msgSheet); //メッセージマスタと入力メッセージを比較し、処理区分を決定する。 for (const msg of msgObjects) { if (inputMsg.indexOf(msg.message) != -1) { return msg.shorikbn; } } return ""; } // ~ 略 ~ shoriKbnGet関数で送信されたメッセージをキーにメッセージマスタを取得し、それに紐づく処理区分を返却しています。 タスクの登録 処理区分が"1"(登録)の場合、タスクの登録処理を行います。 先ほどのmain.gsのswitch文でdataAdd関数を呼び出します。 parts.gs // ~ 略 ~ /** * データ登録処理 * @param taskSheet シート * @param allMsg インプットメッセージ * @return 正常終了メッセージ or エラーメッセージ */ function dataAdd(taskSheet, allMsg) { // 最終列の取得 const lastColumn = taskSheet.getLastColumn(); //受信メッセージが正しい形式か確認 const errorMsg = validationCheck(taskSheet, lastColumn, allMsg); if (errorMsg) { return errorMsg; } // タスクを書き込む行 const newRow = taskSheet.getLastRow() + 1; // タスクを書き込む allMsg.forEach((msg, i) => taskSheet.getRange(newRow, i + 1).setValue(msg)); // 完了期限順でソートしておく taskSheet.getRange(START_ROW, START_COLUMN, newRow, lastColumn).sort(COLUMN_TIMELIMIT); return "データを登録しました。"; } // ~ 略 ~ ここでは、下記の処理を実施しています。 入力メッセージのバリデーションチェック 現在登録されているデータの最終行を取得し、次の行に新しいタスクを書き込む 完了期限順にソートして、登録完了のメッセージを返却 タスクの削除 処理区分が"2"(削除)の場合、deleteRow関数を呼び出し、タスクの削除を行います。 parts.gs // ~ 略 ~ /** * データ削除処理 * @param taskSheet シート * @param allMsg インプットメッセージ * @return 正常終了メッセージ or エラーメッセージ */ function deleteRow(taskSheet, allMsg) { // 最終行の取得 const lastRow = taskSheet.getLastRow(); // 削除対象キー const key = allMsg[0]; // 1行目から順にタスク名を比較し、一致していたら削除する。 for (let i = START_ROW; i <= lastRow; i++) { const taskName = taskSheet.getRange(i, 1).getValue(); if (key === taskName) { taskSheet.deleteRow(i); return "完了タスクを削除しました。"; } } return "入力されたタスクがありません。" } // ~ 略 ~ ここでは、下記の処理を実施しています。 データの最終行までループ 入力メッセージをキーとし、一致するものがあれば行を削除し、完了メッセージを返却する。 タスクがない場合は、その旨をメッセージとして返却する。 タスクの照会 処理区分が"3"(照会)の場合、現在登録されているタスクを取得し、メッセージとして返却します。 parts.gs // ~ 略 ~ /** * データ返却処理 * @param taskSheet シート * @return reTaskArray リプライ用タスクリスト */ function returnData(taskSheet) { // データがあるか判定 if (taskSheet.getLastRow() == 1) { return "タスクがありません。"; } // タスクを全て取得し、返却用メッセージに編集する。 let reTaskArray = `現在登録されているタスクです。${NEW_LINE}`; const dataObjects = dataGet(taskSheet); dataObjects.forEach(dataObject => { // タスクごとに改行を入れる。 reTaskArray += NEW_LINE; Object.keys(dataObject).forEach(key => reTaskArray += `${key}:${dataObject[key]}${NEW_LINE}`); }); return reTaskArray; } // ~ 略 ~ ここでは、下記の処理を実施しています。 データが存在しない(ヘッダーのみ)の場合は、その旨をメッセージとして返却する。 dataGet関数を呼び出し、登録されているタスクをオブジェクトで取得。 キー:バリューの形式でメッセージとしてタスクを返却する。 2では下記の関数を呼び出しています。 parts.gs // ~ 略 ~ /** * データ取得処理 * マスタデータをオブジェクト化して返却する * * @param sheet シート * @return dataObjects データオブジェクト */ function dataGet(sheet) { const dataArray = sheet.getRange(HEADER_ROW, START_COLUMN, sheet.getLastRow(), sheet.getLastColumn()).getValues(); const header = dataArray.shift(); const dataObjects = []; dataArray.forEach(dataRow => { const dataObj = {}; dataRow.forEach((data, i) => { dataObj[header[i]] = data; }); dataObjects.push(dataObj); }); return dataObjects; } // ~ 略 ~ この関数はメッセージマスタの参照など、呼び出している箇所がいくつかあります。 データはgetRangeで二次元配列として取得することもできるのですが、参照の際に数字で指定することになり微妙だったのでこんな実装となっています。 リマインド処理 毎日9:00にremind関数を呼び出します。 parts.gs // ~ 略 ~ /** * リマインド処理 */ function remind() { // データを読み込むスプレッドシートを定義 const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_TASK); // スプレッドシートからタスクを取得 const dataObjects = dataGet(sheet); // 現在日時の取得 const keyDate = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyyMMdd"); let message = ""; // 現在日時とtimelimitが一致するデータをリマインドする。 dataObjects.forEach(dataObject => { if (dataObject.timelimit == keyDate) { message += NEW_LINE; Object.keys(dataObject).forEach(key => message += `${key}:${dataObject[key]}${NEW_LINE}`); } }); // リマインドするタスクが無い場合は処理しない。 if (!message) { return; } push(`今日期限のタスクです。${NEW_LINE}${message}`); } // ~ 略 ~ ここでは、下記の処理を実施しています。 登録されているタスクを取得する 現在日時と完了期限を比較し、一致しているタスクをリマインド対象とし返却する。 一致するタスクがない場合は、メッセージを返却せず処理を終了する。 remind関数を毎日9:00に実行する為にトリガーとして登録する必要があります。 そこで、gasのトリガー機能を使用するのですがピッタリ9:00に設定することが出来ません、、 こんな感じで、起動時刻に幅があります。 ピッタリの時間にトリガー設定したい場合はコードでやる必要があるようです。 ということで、9:00にトリガーを設定する関数を実装し、その関数を9:00より前に実行するようにしましょう! スクリプトエディタより、トリガーを選択します。 トリガーの追加をポチ 下記のように設定します。 実行する関数setTriggerはこんな感じの実装になっています。 parts.gs // ~ 略 ~ /** * トリガー設定処理 */ function setTrigger() { const date = new Date(); // 9時にトリガーを設定する date.setHours(9); date.setMinutes(0); date.setSeconds(0); ScriptApp.newTrigger('remind').timeBased().at(date).create(); } // ~ 略 ~ 今回は9:00と決め打ちですが、スプレッドシートに設定ファイルシートみたいなの作ってそこから取得とかしても良いかもしれません。 まとめ 今回、初めてAPI連携を用いた開発だったのですが、他にも色々応用して作れそうだなと思いgasの手軽さに感動しました! スプレッドシートの操作をコードで書いた時の可読性についても改めて考えたいと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【p5.js】beginShapeとvertex関数

p5.jsについての備忘録です。 rect関数などとは別に存在する描画方法、beginShapeとvertex関数についてまとめ。 beginShape beginShape(kind?: BEGIN_KIND): p5; beginShapeを使用することでより複雑な形を表現できる。 引数kindには形となるものを渡す必要があり、それは以下の通り POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, QUAD_STRIP 指定しない場合は不規則な多角形となる。 この関数を実行した後はvertex関数を呼び出す必要があり、 endShapeで描画を止める beginShape内ではellipseやrectは使用できない。 vertex beginShape と endShapeの中でのみ使われる関数。 図形の頂点座標をvertex関数で指定する。 vertex(x: number, y: number): p5; 例えばvertexを下記のように4つ指定した場合、正方形が出来上がる (左側の罫線はendShape(CLOSE)とすることで描画できる) vertex(30, 20); vertex(85, 20); vertex(85, 75); vertex(30, 75); もしbeginShapeの引数にTRIANGLE_FANを渡していた場合、 頂点の結びは三角形を作ろうとするためマスのような図形になる 頂点を一つ増やし、一部を動的にすれば次のようになる vertex(30, frameCount); 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む