- 投稿日:2019-04-25T23:07:02+09:00
人気のWEBサイトの誕生日をまとめてみた。
- 投稿日:2019-04-25T23:07:02+09:00
平成に生まれた人気のWEBサイトの誕生日をまとめてみた。
世界的に人気のWEBサイトやアプリーケーションがいつできたのか知っていますか?
個人的にものすごく気になったのでVue.jsの勉強がてらまとめてみました。
アレクサランキングTOP50を元にまとめました。
もちろん日本しか利用しない人気のサービスはたくさんありますが、そこらへんも含めるとキリがないので世界的に有名でトラフィックが多いサイトを元に作成しました。このサイトがないのはおかしいっていうのがあったら教えてください。
平成に誕生したWEBサイトの誕生日まとめ
DATE NAME 1994-07-05 Amazon.com 1995-03-01 Yahoo 1995-08-24 Msn 1995-09-03 eBay 1996 Alexa 1996 Ask.com 1996-01-31 Yahoo!Japan 1997 XVIDEOS 1997-02-07 楽天 1997-08-29 NETFLIX 1998 IMDb 1998-09 Amazon.co.jp 1998-09-04 1998-11-12 Tencent 1998-12 PayPal 1999-03 Alibaba 1999-03-08 Salesforce.com 1999-06 NAVER 1999-07-20 FC2 1999-08-23 Blogger 2000-01-01 Baidu 2000-05 Stack Overflow 2001-01-15 Wikipedia 2001-11-12 LIVEJASMIN 2002-12-18 2003-05-27 WordPress 2004-02 flickr 2004-02-04 2005 2005-02-14 YouTube 2006-03-21 2006-08 YouPorn 2006-12-12 ニコニコ 2007 DropBox 2007 xHamster 2007-02-19 tumblr 2007-05-25 Pornhub 2008 Bitly 2008-04 GitHub 2009 2009-02-23 imgur 2009-06-01 Bing 2010-03 2010-10-06 2011-06 Twitch 2011-06-23 LINE 2011-09 SnapChat おれとXVIDEOSの年齢が同じだということがわかってよかったです。
- 投稿日:2019-04-25T19:03:12+09:00
Vue+electron製ツールでSlackに�メッセージを送る
Vue + electron製のツールでSlackにメッセージを送る
事の発端
新しくアルバイトとして入社した企業の配属チームの勤怠ルール
出社、休憩開始、休憩終わり、退社時に特定のチャンネルにそれぞれに対応する絵文字を送信する。
割とめんどくさい・・・ ちなみにこれ以外にも勤怠システムとタイムカードの3重門。
やったこと
タイトルの通り、SlackのAPIをVue + electron のツールでたたき、ワンクリックで出社退社などの絵文字を送信できるようにっした。
コード
vue initでelectron-vueのミニマムテンプレートから作っています。App.vue<template> <div id="app"> <div class="cont"> <div class="button" style="border-color: rgb(129,188,189); color: rgb(129,188,189);" @click="send(1)"> <h2>出社</h2> </div> <div class="button" style="border-color: rgb(235,115,62); color: rgb(235,115,62);" @click="send(2)"> <h2>休憩</h2> </div> <div class="button" style="border-color: rgb(235,115,62); color: rgb(235,115,62); " @click="send(3)"> <h2>再開</h2> </div> <div class="button" style="border-color: rgb(58,61,145); color: rgb(58,61,145);" @click="send(4)"> <h2>退社</h2> </div> </div> </div> </template> <script> import { SlackOAuthClient } from 'messaging-api-slack'; export default { name: "kintary", methods: { send(type) { let mes = '' switch(type) { case 1: mes = ':sagyo-kaishi-作業開始_green:' break; case 2: mes = ':sagyo-ohiru-kyukei-お昼休憩_orange:' break; case 3: mes = ':sagyo-saikai-作業再開_orange:' break; case 4: mes = ':sagyo-shuryo-作業終了_navy:' break; } const client = SlackOAuthClient.connect( 'xoxp-slackから取得したアクセストークン(管理者じゃなくても自分のものは取得できた)' ); client.postMessage('frontteam_kintai', mes, { as_user: true }); } } }; </script> <style> #app { height: 100vh; width: 100vw; display: flex; justify-content: center; align-items: center; } .cont { width: 480px; height: 120px; display: flex; justify-content: center; align-items: center; } .button { height: 100px; width: 100px; border-radius: 50px; border: 3px solid; display: flex; justify-content: center; align-items: center; margin: 10px; } .button:hover { opacity: 0.6; } .button:active { transform: scale(0.95); } </style>悩んだところ
SlackのAPIを叩く際、Axios.postで叩いたところCORS関連のエラーが出た。
↓
超便利なnpmモジュールがあったのでおとなしくそれを利用、importして設定して.postMessageだけで利用できた。※as_userオプションをtrueにすることで、tokenを取得したUserとして投稿ができる。
できたもの
今後
ちゃんとビルドしたい。
- 投稿日:2019-04-25T12:00:02+09:00
k8sでPWAをびるどしてはいしんする。
Angularやvue.jsなどの静的ファイルをホストするときの流れだよ。
js等のビルドされたファイルをnginxのDocumentRootにコピーして、k8sにapplyするだけなので大した話ではないよ。
ALB経由で配信するよ。1. package.json
今回はvuejsで作ったもの
{ "name": "dapps", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "test:unit": "vue-cli-service test:unit" }, "dependencies": { "axios": "^0.18.0", "vue": "^2.6.6", "vue-class-component": "^6.0.0", "vue-property-decorator": "^7.0.0", "vue-router": "^3.0.1" }, "devDependencies": { "@types/jest": "^23.1.4", "@vue/cli-plugin-babel": "^3.4.0", "@vue/cli-plugin-typescript": "^3.4.0", "@vue/cli-plugin-unit-jest": "^3.4.0", "@vue/cli-service": "^3.4.0", "@vue/test-utils": "^1.0.0-beta.20", "babel-core": "7.0.0-bridge.0", "ts-jest": "^23.0.0", "typescript": "^3.0.0", "vue-template-compiler": "^2.5.21" }, "postcss": { "plugins": { "autoprefixer": {} } }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "jest": { "moduleFileExtensions": [ "js", "jsx", "json", "vue", "ts", "tsx" ], "transform": { "^.+\\.vue$": "vue-jest", ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", "^.+\\.tsx?$": "ts-jest" }, "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1" }, "snapshotSerializers": [ "jest-serializer-vue" ], "testMatch": [ "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" ], "testURL": "http://localhost/", "globals": { "ts-jest": { "babelConfig": true } } } }2. Dockerfile
ビルドするコンテナとhttpdを分けるとイメージ節約になる。
URLのルーティングにhistory apiを利用している場合には、別途 try_files の設定をしないといけない。
Docker+nginxでtry_filesを追加したい
# Stage 0, "build-stage", based on Node.js, to build and compile the frontend FROM node:11.3.0 as build-stage WORKDIR /app COPY package*.json /app/ RUN npm install --global npm@6.8.0 npx@10.2.0 && npm install COPY ./ /app/ RUN npm run build # Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx FROM nginx:1.15 COPY --from=build-stage /app/dist /usr/share/nginx/htmlできたDockerfileはECRにpushしておいた。
3. k8s.yaml
kubectl applay -f k8s.yamでデプロイする。
シンプルだね!アプリとそれのServiceのPod
kind: Deployment apiVersion: extensions/v1beta1 metadata: name: gascenter-web-frontend-dapps spec: replicas: 2 selector: matchLabels: app: gascenter-web-frontend-dapps template: metadata: labels: app: gascenter-web-frontend-dapps spec: containers: - args: image: 026695289470.dkr.ecr.ap-northeast-1.amazonaws.com/gascenter-web-frontend-dapps:latest imagePullPolicy: Always name: gascenter-web-frontend-dapps ports: - containerPort: 80 protocol: TCP imagePullSecrets: - name: awsecs --- kind: Service apiVersion: v1 metadata: name: gascenter-web-frontend-dapps-svc spec: # externalTrafficPolicy: Local type: NodePort ports: - name: "http-port" protocol: TCP port: 80 targetPort: 80 selector: app: gascenter-web-frontend-dappsここからはIngress(ALB)の設定
今回はEKSを使ったので、ALBの設定もk8sから行った。こいつを食わせるとALBをよしなにいじってくれます。alb.ingress.kubernetes.ioのアノテーションについてはこちら
https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/ingress/annotation/apiVersion: extensions/v1beta1 kind: Ingress metadata: name: gascenter-web-frontend-app annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing # alb.ingress.kubernetes.io/subnets: subnet-000000,subnet-111111,subnet-22222 alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]' alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:026695289470:certificate/65a878de-ed52-4415-a69e-c0fcf9aeca7a alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' spec: rules: - host: xxxx.takamattekita.cc http: paths: - path: /* backend: serviceName: ssl-redirect servicePort: use-annotation - path: /* backend: serviceName: gascenter-web-frontend-dapps-svc servicePort: 80
- 投稿日:2019-04-25T11:07:49+09:00
pͪoͣnͬpͣoͥnͭpͣa͡inͥを支える技術
pͪoͣnͬpͣoͥnͭpͣa͡inͥの作り方
ぽんぽんぺいんを簡単に作るサイトを作ったのでよかったら使ってみてください。
https://ykhirao.github.io/qiita/ponponpain/dist/
ponponpain(haraita-i)とは
画像でいうとこんなやつのこと。
今回参考にさせていただきましたが、以下のサイトによくまとまっていると思います。
まあ要するに、不思議な上付き文字を組み合わせて、文字に副題(ルビ)をふろうって感じの遊びのことで、昔流行ったみたいです。
今回はクリックだけで上付き文字を加えられるサイトを作ったのでぜひみんなに遊んでほしいなと思っています。
投稿について
UbuntuのChromeだと綺麗に表示されないのですが、Twitterだといい感じになるみたいです。
傑作ができたら、
#pͪoͣnͬpͣoͥnͭpͣa͡inͥを支える技術とかハッシュタグつけてツイートしてくれるか、Qiitaにコメントいただけると全部見ませう!!!私が作ったのは ponponpain(moudameda)です。電車の中で、この文字を見ていただけると幸いです。
使い方
そんなに難しくないので、こちらの画像を見ていただけるとなんとなくわかるかと思います。もしくは既出のGIF動画を見てからお使いください。
おわり
たくさん、遊んでいただけると幸いです!!!
以下は、ちょっとだけ、真面目な話するよ、読む人は優しい心を持ってみてね!!
真面目な話
この上付きの文字は
ダイアクリティカルマークというみたいで、日本語のパの̊とかそういうもんだと考えればなんとなく文字コードが別にあっても不思議ではないのではないでしょうか。// Chromeのコンソールで以下のコードを入力してください ` ${String.fromCharCode(867)} ` // 見やすい様に半角スペースいれている // ͣ がでてきます。ダイアクリティカルマークは番号でいうと
768 ~ 879HEXでいうと0x300 ~ 0x36Fが該当するみたいです。逆向きの変換は
'A'.codePointAt(0);みたいなStringのAPIを使えば、Aの文字コード。
pͪoͣnͬpͣoͥnͭpͣa͡inͥの文字コードはというと"pͪoͣnͬpͣoͥnͭpͣa͡inͥ".codePointAt(0) // 112 普通のpが一文字目 "pͪoͣnͬpͣoͥnͭpͣa͡inͥ".codePointAt(1) // 874 上付きのhが二文字目にきます // 気になる人はChromeのコンソールに右の値を入れましょう ` ${String.fromCharCode(874)} ` // ͪ がでてきますてな感じになります。
javascriptでいい感じに扱いやすくしたのは ぎとはぶ に乗せているので良かったら使ってください。↓以下のようなコードになります。
const codes = [ { "id": 768, "hex": "0x300", "val": "̀" }, { "id": 769, "hex": "0x301", "val": "́" }, { "id": 770, "hex": "0x302", "val": "̂" }, // ryaku ]またフロントエンドの実装はVue.jsでサクッとやったので、参考にする奇特な方がいたらこちらをどうぞ!!
https://github.com/ykhirao/qiita/blob/master/ponponpain/src/components/DraggableText.vue このあたり。右から左に文字列を読むアラビア語などのために
U+202Eという制御コードがあったり、UnicodeがーーとかSJISがーーーとか、正直難しい世界ですよね。なんで今回文字コードを追ってみようと思ったかというと、弊社でよく文字コードを見る機会があって、具体的には日本郵便とかに出す送り状を印刷している部分がWindows上のC#で書かれたアプリケーションでやっていて、WinのMS系のフォントにない文字コードのものは文字化けして大変なことになるんです。
なのでそのあたりの制御とかいろいろやっていて文字コードマスター(?)とかが社内にいて、あ、もしよかったら文字コード好きな人いたら Qiita Jobs からオープンロジという会社にチャット送っていただいて面接とか来てくれると嬉しいなーーと思います。。宣伝すみません。(ここまで早口)
文字コードの深淵を覗く時、文字コードの深淵もまたあなたを覗いているとニーチェが言ったとか(言ってない)ありますけど、今回いろいろ調べたので少しだけ文字コードと仲良くなれた気がします。
以上。ありがとうございました。
.
- 投稿日:2019-04-25T10:19:03+09:00
HTML の input type="number" には e を入力できる
HTML で
input要素のtype属性にnumberを指定したとき、数値以外で入力できる値として
-+.以外に、実は
eも入力できます。これは(おそらく)べき乗の表現にも対応しようとした処置です(例:
1e+2は100を表す)。JavaScript でも
1000000000000000000000以降は1e+21といった形で表現されたりしますしね。ただ、困ったことにこれだと場合によってはサーバーサイドでの値のバリデーション等でコケる可能性があるので、フロントエンドで
eを入力できないようにしたほうが親切かもしれません。自分は普段 Vue を使っているので、以下のように書くことで
eを入力できないようにできたので便利だな〜と思ったりました。<input type="number" @keydown.69.prevent>以上です。
参考
- 投稿日:2019-04-25T00:16:56+09:00
Vue + Vuex + TypeScript でテトリスを作った話
TL;DR
ここで遊べます.
リポジトリはこちら
操作方法
- カーソルキー (左下右) または A, S, D : 移動
- 上キーまたは W : ハードドロップ
- J : 左回転
- K : 右回転
- R または U : ホールド
一応おまけでスマホでも操作できるようにしてありますが PC 推奨です.
ARE も現代的な T-Spin も無いストイックなやつを目指して作りました. (しかしホールド機能は付けたかったので付けました)きっかけ
普段フロントエンドの開発をすることは少ないのですが, 遅ればせながら最近になってぼちぼち情報に触れるようになり, 自分も勉強してなんか書いてみようかなという気持ちになったのが昨年末あたり.
ちょうどその頃なぜかたまたま Classic Tetris World Championship (CTWC) の動画1をよく見る日々が続いており, テトリスでも作って自分で遊ぶか〜と思ったのがきっかけです.
とりあえず React の前に Vue から入ろうと思い, Vue と TypeScript の公式ドキュメントを一通り読んでから取り掛かり, 寝る前の隙間時間にちまちま作っていきました.
せっかくなので自分用の備忘録として残しておこうと思って書いたのがこの記事というわけですが, これもちまちま書いていたらこんな時間が経ってしまった.
Vue + TypeScript
Vue の公式ガイドを上から順に読んでいくと,
基本的にはコンポーネントと呼ばれるものを定義し,
- 親から子コンポーネントへは
v-bindを使ってpropsとしてデータを受け渡す- 子は親の状態を直接変化させない
- 子から親に何かを伝えたいときはカスタムイベントを発火して親が
v-onの形で受け取るpropsは子の中では immutable なものとして扱い,propsを使って宣言的に記述された算出プロパティが親からのpropsの変更に対してリアクティブに変化する
- それがテンプレートにも反映される
結局のところ単一ファイルコンポーネントを基本の形としてコードを書いていく
ということがわかります.
続いて TypeScript の公式ドキュメントを読んでいくわけですが, そこで素朴な疑問がひとつ浮かびます. つまり TypeScript では
const answer: number = 42のように変数を定義するのですが, Vue コンポーネントのdataやcomputedなどのオプションはオブジェクトリテラルの形で定義されます. すると TypeScript のvariableName: typeという表記とオブジェクトリテラルのkey: valueという表記が衝突してしまうのではないか?という疑問です.この疑問は TypeScript の Quick Start から辿れる TypeScript-Vue-Starter の中で解決されます.
README を読んでいくと, 一番最後のほうに「デコレータを使ってコンポーネントを定義する (Using decorators to define a component)」という章があります. それによれば, vue-property-decorator という便利なパッケージをインストールするとHelloDecorator.vueimport { Vue, Component, Prop } from "vue-property-decorator"; @Component export default class HelloDecorator extends Vue { @Prop() name!: string; @Prop() initialEnthusiasm!: number; enthusiasm = this.initialEnthusiasm; increment() { this.enthusiasm++; } decrement() { if (this.enthusiasm > 1) { this.enthusiasm--; } } get exclamationMarks(): string { return Array(this.enthusiasm + 1).join('!'); } }のように,
@Prop()というデコレータをつけることでその変数がpropsとして扱われるようになったり,getアクセサをつけることでそのメソッドが算出プロパティとして扱われるようになったりするという話です.
( 上記 README 内での書き方が若干まぎらわしいのですが, vue-property-decorator は vue-class-component を依存先として持つので, インストールするのは vue-property-decorator だけで十分です )デコレータの詳細については公式ドキュメントやほかの方の記事に解説を譲るとして, 利用者視点で言えば vue-property-decorator によって型を付けながらコンポーネントを定義することができるようになるというわけです.
vue-property-decorator を使ったさらなる書き方についてはこちらの記事も大変参考になりました:
tsconfig.json
この時点で tsconfig.json は基本的に以下のような内容になっていると思います:
tsconfig.json{ "compilerOptions": { "outDir": "./built/", "sourceMap": true, "strict": true, "noImplicitReturns": true, "experimentalDecorators": true, "module": "es2015", "moduleResolution": "node", "target": "es5" }, "include": [ "./src/**/*" ] }この記事を書いている 2019 年 4 月時点では
"experimentalDecorators": trueを明記する必要があります.webpack.config.js
今回は私は勉強のため vue-cli を使わず手動で (?) 必要なパッケージをインストールしたり webpack.config.js を書いたりすることにしました.
基本的には上記の TypeScript-Vue-Starter 内の指示に従えば良いと思いますが, いくつか注意点があったので記録しておきます.
VueLoaderPlugin
webpack で Vue の単一ファイルコンポーネントを扱うために vue-loader が必要ですが, vue-loader v15 からは
pluginsにVueLoaderPluginを指定することが必須であるとドキュメントに記載があります:webpack.config.jsconst VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { module: { rules: [ // ... other rules { test: /\.vue$/, loader: 'vue-loader' } ] }, plugins: [ // make sure to include the plugin! new VueLoaderPlugin() ] }ランタイム限定ビルドの利用
Vue 公式ドキュメントのランタイム + コンパイラとランタイム限定の違いという項を読むと, vue-loader を使用していれば, (単一ファイルコンポーネントのテンプレートはプリコンパイルされるので) 「完全ビルドに比べおよそ 30% 軽量」なランタイム限定ビルドを利用することができる, と書かれています.
つまり基本的に単一ファイルコンポーネントの形でコードを書き, エントリポイントとなる
index.tsでrender関数を使ってルート要素に描画していれば,vue.esm.jsではなくvue.runtime.esm.jsを利用することができます:src/idnex.tsimport Vue from 'vue'; import App from './components/App.vue'; new Vue({ el: '#app', render: h => h(App) })webpack.config.jsmodule.exports = { // ... resolve: { alias: { 'vue$': 'vue/dist/vue.runtime.esm.js' } } }ESLint + Prettier
私が知る限り, フロントエンド開発においてはリンターに ESLint を, コードフォーマッターに Prettier を用いるという構成が現在のスタンダードのようです. 私も長いものに巻かれてエディタに甘えようと思い, この構成を取ることにしました.
TypeScript の場合 TSLint というリンターもあるようですが, TSLint チームは今後 ESLint に寄っていくというような記事が今年の 1 月に出たり:
していたので ESLint を使おうと判断しました. また折しも今年の 2 月後半には 2019 年中に TSLint が deprecated となる旨がアナウンスされました:
さて, ESLint と Prettier を組み合わせるために Prettier の公式ドキュメントを見ていきましょう. するとまず ESLint から Prettier を実行してくれるプラグインであるところの eslint-plugin-prettier が必要と書かれています. リポジトリの README を見ると
.eslintrc.jsonに.eslintrc.json{ "plugins": ["prettier"], "rules": { "prettier/prettier": "error" } }と書けば良いようです.
さらに ESLint 側のコードフォーマットに関わるルールを無効にしてくれる eslint-config-prettier なるものもあります. こちらの README にもいろいろと書いてありますが, eslint-plugin-prettier 側の README とも合わせて読むと, 結局これら 2 つをインストールした上で
.eslintrc.jsonの"extends"配列の最後に"plugin:prettier/recommended"を指定すれば良いことがわかります.その上でさらにいくつか追加のプラグインも紹介されており, それらにも eslint-config-prettier が対応していることが示されています. 今回の構成だと私は
を使ったほうが良さそうです.
@typescript-eslint/eslint-plugin の README を読むと, @typescript-eslint/parser をインストールすることが求められています.
ただし eslint-plugin-vue のユーザーガイドには, eslint-plugin-vue はパーサとして vue-eslint-parser を用いるので, カスタムパーサを使いたいときは
parserオプションではなくparserOptions.parserオプションで指定せよとの記述があります.長々と見てきましたがこれで必要なものが揃いそうなので, すべてインストールして
npm install --save-dev eslint prettier eslint-plugin-prettier eslint-config-prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vueすべての設定を融合させましょう:
.eslintrc.json{ "extends": [ "eslint:recommended", "plugin:vue/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "prettier/@typescript-eslint", "prettier/vue" ], "plugins": [ "@typescript-eslint", "prettier", "vue" ], "parser": "vue-eslint-parser", "parserOptions": { "parser": "@typescript-eslint/parser", "project": "./tsconfig.json", "extraFileExtensions": [".vue"] }, "rules": { "prettier/prettier": "error" } }VSCode の拡張機能
上記の eslint-plugin-vue のユーザーガイドには Editor integrations という項があり, VSCode で ESLint 拡張機能を
.vueファイルに対しても適用するための設定が書かれています.
また Vetur プラグインを使っている場合は"vetur.validation.template": falseを設定せよとあります.私はそれに加えてファイル保存時に ESLint の auto fix をかけるよう設定したので,
.vscode/settings.jsonは以下のようになりました:.vscode/settings.json{ "eslint.validate": [ "javascript", "javascriptreact", { "language": "vue", "autoFix": true }, { "language": "typescript", "autoFix": true } ], "eslint.autoFixOnSave": true, "vetur.validation.template": false }ファイル保存時に自動整形がかかる体験は最高で, コードフォーマッティングに関する些事を人間が気にしなくてよくなり最高.
デフォルトのprintWidth: 80だけは少し窮屈すぎたのでそこだけ広げましたが, その上で prettier が改行すると判断したのならそれは正しいのです.(とはいえテトリミノの回転を配列で定義して, 見やすいように手で整形したのに prettier が押しつぶそうとしてくる箇所だけは disabled にしましたが...)
実装フェーズ
ようやく開発環境が整ったので, テトリスの実装に入ることができます.
「『小さく、自己完結的で、(多くの場合)再利用可能なコンポーネント』を組み合わせることで、大規模アプリケーションを構築する」2のが Vue のスタイルなので, 必要なコンポーネントについて考えると
メインとなる盤面 (ミノが落ちたり回転したり消えたりするところ)
NEXT ミノをプレビューできる部分
ホールド中のミノを表示する部分
現在のレベルと得点を表示する部分
ぐらいがあれば良さそうです.
するとおおまかな構成は以下のようになるでしょう:
dist/index.html<!doctype html> <html> <head> </head> <body> <div id="app"></div> <script src="./build.js"></script> </body> </html>src/index.tsimport Vue from "vue" import TetrisComponent from "components/Tetris.vue" new Vue({ el: "#app", render: h => h(TetrisComponent) })src/components/Tetris.vue<template> <div> <div id="tetris-component"> <hold class="inline-block" /> <play-field class="inline-block" /> <div class="inline-block"> <next-preview /> <level-score /> </div> </div> </div> </template> <script lang="ts"> import { Vue, Component } from "vue-property-decorator" import Hold from "components/Hold.vue" import PlayField from "components/PlayField.vue" import NextPreview from "components/NextPreview.vue" import LevelScore from "components/LevelScore.vue" @Component({ components: { Hold, PlayField, NextPreview, LevelScore } }) export default class TetrisComponent extends Vue {} </script> <style> #tetris-component { text-align: center; } .inline-block { display: inline-block; vertical-align: top; } </style>テトリス実装の基本概念
テトリスのロジック自体は素朴な実装にしました.
盤面
盤面として幅 (10 + 2) * 高さ (20 + 2) の 2 次元配列を 2 つ用意し, 1 つは色情報を, もう 1 つはブロックが埋まっているか否かの情報を格納するのに使います.
幅と高さを +2 しているのは外壁, いわゆる番兵というやつです. 色と埋まりの配列を分けずにもっと統一的に扱っても良いかもしれませんが, そこは好みの問題だと思います.
ミノ
テトリスの主役であるところのミノについては, ミノの型を定義して
export default interface Tetrominoし, すべてのミノの名前と色と回転パターンを記述したTetrominos: Tetromino[]を定義しておきます:src/Tetrominos.tsimport Tetromino from "typings/Tetromino" export const Tetrominos: Tetromino[] = [ { name: "I", color: "red", blocks: [ [ [1, 1, 1, 1] ], [ [1], [1], [1], [1] ] ] }, { name: "O", color: "gold", blocks: [ [ [1, 1], [1, 1] ] ] }, . . . ]この
Tetrominosのインデックスでミノの種類を表現できるというわけです.操作
ミノを移動させたり回転させたりといった操作を行うときは単純に, その操作後のミノが既に埋まっているマスとぶつからないかどうかを検査し, ぶつからなければ操作可能, ぶつかるなら操作不可能とすれば良さそうです.
ただし
moveDown()だけは特別で, それ以上ミノを下に動かせないということはミノが接地したということ, すなわち現在のターンが終了したということを示します. なので揃ったラインを消すことを始めとした様々な終了処理を行う必要が出てくるでしょう.ラインの消去
テトリスというゲームの命 (?) であるところのライン消去機能を作るには, Array.prototype.splice() がいい感じに使えるでしょう.
ミノを接地した結果すべての列が埋まったラインが出来たならば, 上から順番にそのラインのindexについてsplice(index, 1)すれば良いのです. そして上記の 2 次元配列の先頭には空っぽのunitLineを都度splice(1, 0, unitLine)して詰めてあげれば良いでしょう (index が 1 なのは一番上の 0 行目が壁だからです).ホールド
ホールドとはつまり現在の
tetrominoIndexとholdTetrominoIndexとを交換するという操作です. ただし初回だけはホールドされているミノが無いので, NEXT のミノを盤面に呼ぶ必要があるでしょう. なのでholdTetrominoIndexの初期値は -1 とか (インデックスとしてありえない値) にしておいて, それで初回の判定をします.またホールドは 1 ターンにつき 1 回しかできないので,
isHoldTetrominoUsedのような変数を用意して, ホールドしてからミノが接地するまではisHoldTetrominoUsed = trueとしてロックしておく等の処理が必要です.余談:
Math.floor(Math.random() * 7)を捨てる出現するミノをランダムにするために
Math.floor(Math.random() * 7)の値を使おうと思う方がいるかもしれませんが, やめたほうが良いです.代わりに 「7 種類のミノが 1 つずつ入った集合の中から, 空っぽになるまで 1 つずつランダムに取り出す」ことを繰り返すようにしてください.
実際にやってみるとわかるのですが, ナイーブな
Math.floor(Math.random() * 7)ではとても遊べるゲームになりません. 動作確認用くらいにとどめておくべきです. これは重要なポイントなのでぜひ覚えておいてください.今回私は Fisher–Yates アルゴリズムというシャッフルアルゴリズムを使いました:
Algorhythms.ts// Fisher–Yates shuffle algorithm export const shuffle = function(array: number[]): number[] { for (let i: number = array.length - 1; i > 0; i--) { const j: number = Math.floor(Math.random() * (i + 1)) ;[array[i], array[j]] = [array[j], array[i]] } return array }TetrominoIndices.tsimport { Tetrominos } from "Tetrominos" import { shuffle } from "Algorhythms" // ... nextTetrominoIndicesSet: number[] = shuffle(Array.from(Array(Tetrominos.length).keys())) // ...Vuex
さてこのまま
propsや$emitやmethodsを駆使してコードを書いていっても良いのですが (実際私も最初はそうしていました), だんだんと状態変数が増えてきて管理が辛くなっていきます.
そこで途中から Vuex による状態管理に移行することにしました (今からやり直すなら最初から Vuex を使うと思います).Vuex や Flux 自体についてはほかに良い記事がたくさんあると思うのであえて私がここで拙い解説を書くことはしませんが, Vuex の公式ドキュメントを読んでいけば
状態 (
state) はStoreと呼ばれるグローバルシングルトンの中に保持する状態を変更する唯一の方法は
mutationをコミットすることである
mutationは同期的な処理だけを行う非同期処理を行ったり複数の
mutationをコミットしたりするためにはactionを定義し, そのactionをdispatchすることで実行する状態は
getterで取り出すことが基本のスタイルであると読めます.
さらに Vuex で TypeScript を型安全に使うために vuex-module-decorators を利用しました.
これについては以下の記事が大変参考になりました:コンポーネントの中で
this.$storeと書かなくてよくなったり mutation 名や action 名を文字列で一字一句間違えずに書かずに済むようになったりというのはこの記事のとおりです.コーディングスタイル
Vuex の公式ドキュメントには「ミューテーション・タイプに定数を使用する」というオプションが提示されており, 上記の記事でもそのスタイルだったので, 今回は私もそれに則ることにしました.
また vuex-module-decorators を使うと VuexModule を TypeScript の class として書くことになり, 各プロパティやメソッドにアクセス指定子をつけることができます.
私は Vuex Store が外部 (Store を利用する Vue のコンポーネント) に公開すべきなのはgetterとactionだけであるとドキュメントからエスパーにより読み取った (?) ので,stateとmutationはprivateにし,getterとactionをpublicとしました.こうなるとテトリスを実装する上での主要なロジックはほとんど Vuex の action となり, Vue のコンポーネントからは
propsが消え去りました. コンポーネントに残ったのは少しの内部変数とmountedフック, それにactionを制御する薄いメソッド, リアクティブな描画のために Store の getter をそのまま返す getter ぐらいです.反省点
描画
何も考えずに Canvas 上でゲーム画面を描画するものとしてコードを書き始めてしまったのですが, そうすると例えば
import { Tetrominos } from "Tetrominos" // class PlayField extends Vue とか VuexModule とか { // ... // 適当な値です tetrominoIndex: number = 0 currentX: number = 5 currentY: number = 1 rotation: number = 0 unitWidth: number = 10 unitHeight: number = 10 drawTetromino(): void { const canvas: HTMLCanvasElement = document.getElementById("canvas") as HTMLCanvasElement const context = canvas.getContext("2d") context.strokeStyle = "lightgray" context.fillStyle = Tetrominos[this.tetrominoIndex].color for (const [dy, row] of Tetrominos[this.tetrominoIndex].blocks[this.rotation].entries()) { for (const [dx, blockElement] of row.entries()) { if (blockElement != 0) { context.fillRect( (this.currentX + dx) * this.unitWidth, (this.currentY + dy) * this.unitHeight, this.unitWidth, this.unitHeight ) context.strokeRect( (this.currentX + dx) * this.unitWidth, (this.currentY + dy) * this.unitHeight, this.unitWidth, this.unitHeight ) } } } } // ... // }的なコードを書くことになり,
fillRect()がいかにも手続き的です.
だいぶ後になってから気がつきましたが, せっかく属性に値を bind できるのだから SVG で描画したほうが良かったかもしれません. こんど何か図形を描画して動かすものを作るときは SVG の利用を検討しようと思います.そういう感じです
このテトリスを作っている間に Nintendo Switch で Tetris 993 が配信開始されました. この Tetris 99 は Nintendo Switch Online に加入していれば無料で遊べるし, 現代的な要素ももりもり詰まっています.
私はなるべくクラシックなテトリスに近いものを作って遊ぼうというモチベーションがあったから良かったものの, もし現代的テトリスを目指して作っていたら, べつに Tetris 99 とかいう良くできたゲームがあるしわざわざ俺がちまちま作んなくてもいいじゃん...という気持ちになって途中で心が折れていたかもしれません. そういう意味では運良く開発できたなあと思っています.そういうわけですので皆さんも Tetris 99 をやりましょう.





