2020年8月24日月曜日

Render: Netlify/Vercel の新たな代替サービス

Render という無料の静的ホスティングサービス (有料で動的ホスティングサービスも可能) が登場していました。



静的ホスティングはどんどん無料化が進んでいます。 これまでも NetlifyVercel (ZEIT)、GitHub Pages という頼れる味方がいましたが、 あくまで個人利用において無料という条件が付いており、チーム開発は高額になりがちでした。 どちらかと言えばワンマンアーミーご用達といって良いでしょう。

チーム開発でも無料のものとしては、FireBaseSurge があります。 しかしこれらは利用可能な帯域幅が狭く、少し本気を出してしまうと、無料枠から突き抜けてしまうでしょう。 チーム開発でも使えて帯域幅も大きいサービスは、おそらく Neocities, Fast.io (→気付いたらなくなってた…) があります。 他にも最近では Azure Static Web Apps が出てきました。 ただ Neocities は 1GB のディスク制限や、複数サイト運営の難しさなどがあります。 Fast.io はサイトが 5個までの条件、 Azure Static Web Apps もアプリが 2個までの条件があります。

色々な無料サービスが登場してきているものの、チーム無料+複数サイト運営+大容量の帯域幅を許容しているサービスは、 私の知る限りではありませんでした。 そこをついに無料にしたのが Render、と私は理解しています。


RenderNetlify/Vercel/FireBase のように、Functions を自在に操れ、一部無料で利用できる訳ではありません。 しかしデータベースなどを利用しても、小規模ならかなり安く利用できるのが、もう1つの特徴のように思います。

この部分について語るなら、disk write というのはやっぱりサービス運営においてめちゃくちゃ重いものです。 少し前に Unubo という動的ホスティングが無料でできるサービスがあったのですが、さすがにきつ過ぎたのか終了してしまいました。 とはいえ静的ホスティングが無料提供できるくらいの価格帯に落ちてきたので、 残るは大容量メモリと大容量帯域と disk write あたりという時代になってきているとは思います。

Amazon S3 などの disk write サービスも、運用価格はどんどん下がっています。 いつかは、かなり安い値段で提供される日が来るとは思っています。 Render が安いかと言われるとほとんどの場合 VPS のほうが安いとは思うのですが、 「ライトな処理だけど、地理的に分散していて、高頻度に write するケース」では、面白いかも知れません (未確認)。

何にしても、新しい選択肢ができたのは良いことです。

2020年8月23日日曜日

2chのAAをOSに依存せず綺麗に表示するCSSの設定方法

2ch の AA のフォントについてなぜかアクセスがあるので、私のサイトを見て混乱される方がいても困るので、書き直しておきます。 これまで以下のような記事を書いていますが、どれもブラウザの実装がバグりまくっていたことによって起きた話です。 将来バグった時の処方箋として残してますが、現状はバグが治っており気にしないで良いです。

今では決定版とも言える書き方があります。小細工も必要ありません。
@font-face {
  font-family: 'Textar';
  font-style: normal;
  font-weight: normal;
  src: local('Textar'),
  url('https://marmooo.github.io/fonts/textar-light.woff2') format('woff2'),
  url('https://marmooo.github.io/fonts/textar-light.woff') format('woff'),
  url('https://marmooo.github.io/fonts/textar-light.ttf') format('ttf');
}

pre.aa, textarea.aa {
  font-size: 1em;
  line-height: 1.1;
  font-family: 'MS Pゴシック','MS PGothic','梅Pゴシック',Textar,sans-serif;
  white-space: pre;
  word-wrap: normal;
}
梅Pゴシックは私の趣味で、Linux な人に対応しています。 最低限度で良いなら、Windows でフォントが不要な人のための MS PGothic と、Linux/Mac/スマホで必要な人のための Web フォント、そして sans-serif を指定しておけば良いです。 今回は Web フォントの指定例として textar-light を使っています。

AA 表示用のフォントはたくさんありますが、今やフォントを手動でインストールする人はいないでしょう。 よってスマホ等でも AA を見るために Web フォントが必要になります。 AA Hub Fonts で 10 種類ほど配布されているので、ここから好みで選んでも良いでしょう。 Web フォントは結構重たいので、漢字グリフを削除して軽量化した aahub_light は結構おすすめではあります。 ただ拡大するとギザギザになるので用途によっては適しません。

そこで Textar フォントから漢字グリフを削除した textar-light というフォントを私も配布しています。 興味のある方はこれを利用してみると良いかと思います。

2020年8月20日木曜日

頻出熟語を自動生成してみた

夏休みの自由研究として熟語ゲームを作ってみようと思い、頻出熟語を生成するコードを書いてみました。 自然に考えれば最も効率的なのは、形態素の n-gram からの生成です。 誰得感はありますが、良さそうな熟語生成方法についてまとめました。

漢字の熟語の生成

まず作り方ですが、ノイズが多めでも語彙数を増やしたい場合は、漢字の形態素を複数個繋げて熟語を生成します。 ただしこのやり方だと、「今見た」の「今見」などを熟語にしてしまう上に、判別が難しいです。 三文字以上の熟語はともかくニ文字熟語ならたいして生成数も変わらないので、単一の形態素だけで生成するほうがお気楽に熟語を生成できます。 ただ完璧はないので、どちらにしても多少の足切りは必要です。

二文字熟語

まず本質的な注意点は、形態素と熟語は違います。 わかりやすい例が形態素の「期生」や「年生」です。 熟語では「三期生」などの使い方をしますが、「期生」単体では使うことはありません。 つまり形態素解析辞書から、熟語ではないパターンを手動で削除したり、逆に場合によっては生成しないといけません。 そしてこれが大変。

Google n-gram で複数の形態素からニ文字熟語を作る場合、体感値的には頻度 10000 で足切りしていればいい感じです。 頻度が下がってくると、苗字・会社名・地名、最終的には普段使わないような難しい熟語をどうするかといった問題が出てきます。 苗字は「田中」「山田」などの頻出パターンをリスト化してテンプレートマッチングで削除すれば一気にノイズが消えるので、考える必要はありません。 頻度が下がると意味のある熟語が出てきてしまいますが、最低でも上位2000件くらいまでは処理したほうが良さそう。 地名「東京」は何百万回も登場しており、もはや熟語と考えたほうが良いかと思います。

方針としては、会社名・苗字・氏名はすべて削除、地名は都道府県に留めて市区町村は排除、郡市区町村と令制国名は排除、海外は国名までとしました。 市区町村は頻度別に出てくるので残しておいても良かったかもなあ。 会社名は簡単な漢字を使っている有名会社を想定してみて、会社名が出ない程度に足切りすれば良さそうです。 会社名を覚える必要はそれほどないので、基本的にはすべて削除して良いと思います。 ホームセンター系の会社が、Webページ数も多いことがあって残りやすいので注意っぽい。 日本語ウェブコーパス 2010 をベースに話をすると、 閾値としては 15000 までは必修熟語という感じですが、10000 くらいでたまに難しいもの+ノイズが少し入ってきて、 5000 くらいになると人によってはまず使わないものが出てくる。 どこで切るかは利用用途によりそう。

三文字熟語 (成語)

三文字熟語の場合は単一の形態素では数が少な過ぎるので、複数の形態素で生成します。 ニ文字熟語は単一の形態素だけでも充分、つまり 1-gram で生成できます。 しかし複数の形態素の組み合わせとなると、前後の文字をよく見ないといけないので 5-gram くらい必要になります。 頑張れば結構ノイズは減るものの完璧にはノイズが取れないので、閾値 5000 くらいまで手動調整するのが良さそうです。 頻度が 5000 以下になると使えそうな熟語より、使えない文字の羅列のほうが多くなり手間が増えます。 手作業でやるにしてもこれくらいに留めておくのが良さそう。

頻度 5000 まで 4000件ほどありましたが、ある程度は機械的に削除できました。 「校舎東」とか「会社等」などの、東西南北や〜等〜的〜様などで終わる熟語はつまらないので削ります。 このあたりの処理は説明が面倒なのでコードを見て頂いたほうが早いかと思う。

ひらがな/カタカナ熟語の生成

次にひらがな/カタカナ熟語の生成について考えます。 ひらがな+カタカナ、ひらがな+漢字、カタカナ+漢字で n文字になるような熟語はあまりないです。 あっても漢字が形容詞的に使われることがほとんどと予想できるので、熟語というよりは「トマト」のような単語探しになるかと思います。

そして結論から言えば、ひらがなはなかなかうまくいきません。 「やはり」「そして」などの接続詞や形容詞が強過ぎるので、ノイズが大きいです。 反面「うどん」のようにひらがなが異常に強い熟語 (単語) もある。 熟語を探すためだけに生成して、手動削除を前提にすれば使えないこともありませんが、データを見ても使える気はしないなあ。

よってカタカナに留めるのが無難です。 熟語ではなく単語を探すと考えれば、[カナ以外][n文字のカナ][カナ以外] となる形態素を探せば良いので、簡単です。 カタカナは擬音語や名前が多い感じです。手動で削除するよりないかなあ。 頻度が下がってくると花名などが出てきて面倒なことになってくるので、これも 10000 で足切りが楽そうです。 インターネット関連用語が多い気がしますが、ひとまず良しとしました。


ここまでの内容をまとめたデータ&コードはこちら (GitHub)。

ngram-idioms

漢字は形態素版と n-gram 版の両方を用意してみましたが、形態素版のほうが良いかと思います。 二文字熟語は高頻度ならほぼ完璧な生成です。 三文字熟語はたまに「名以外」など怪しげなものがでてきますが、これ以上綺麗に削るのは難しそうで、残りは手動削除が良いかと思います。 コード自体は四文字以上の熟語も作れるようになってますが、ニ文字熟語の組み合わせで適当に作れてしまうので面白みはないため、ゲームに使うことはないかな。

この成果を活かして作ったのが以下のゲームです。 ゲームで鍛えていれば、面倒くさい意味調べを、たぶんあまりやらないで済むようになります。どぞ。

漢字迷路 / カタカナ迷路を作った

ニ文字熟語と三文字熟語がいい感じに自動生成できるとわかったので、漢字の迷路ゲームをいくつか作ってみました。 ついでにカタカナも。他にも色々作れそうですが、ひとまず簡単なものから素振りです。



なぜ作ったかというと、国語が苦手な子は多いだろうなあ、というありがちな理由です。 本を読んでいる子は自然に言葉を知っていけるでしょうが、読んでない子が増えているので割と重要な問題です。 本を遊んで読む子にとって国語は「遊び」だと思いますが、そうでない子は急激に労苦が増える傾向があります。 例を挙げると、今も昔も国語力の改善法の一つは「意味調べ」ですが、このご時世にもなって辞書を引かせるのは労苦です。

といって語彙量が極端に少ない子はどうやって遅れを取り戻し、身に付けるべきでしょうか。 数学年前の教科書を読んだり解かせたりするのも王道ですが、コストも掛かるし面白くはないはずで、もう少しひねりが欲しいところ。 本来は「知らない言葉を効率良く、自然に知る」手段があれば良いはずなのです。 「ゲームにしてしまえば良い」ということで、作ってしまいました。

という訳で、小1くらいの子から大人まで楽しめる漢字ゲームをいくつか作りました。 色々な漢字ゲームを試したり、考えてみましたが、迷路は最初から良いなと思っていました。 なぜかというと、せいぜい 10こくらいの連接状態を見れば解けるかわかるからです。 小さい子に難しいパズルはを出しても困ってしまうだけなので、10こくらい熟語を考えてわからなければヒントを見てもらう。 これで解けるのが良いです。

いくつかゲームを作っていますが、私はどのゲームもわからない言葉があったら、クリックするだけで意味を調べられるように作っています。 そのため、ゲームを遊んでれば自然に語彙が身に付きます。 注意点としては、可能な限りの排除はしているけど、別解ができてしまう時もあります。 完璧は難しいので今のところ仕様です。

初学者用としてコードも公開しています (カタカナ迷路漢字迷路)。 ロジックはわかりやすいものの生成が面倒なので、やや中級者向けかも知れない。

2020年8月16日日曜日

フリゲ紹介: 群青禍刻ノ魔人 (ブルーアワーインカネーション)

群青禍刻ノ魔人 (ブルーアワーインカネーション) は、敵や敵の攻撃をつかんで投げ返すアクションゲーム。 一周約20分弱…と書かれてますが、たぶん 2時間は掛かると思う。 なかなか歯ごたえのあるアクションゲームです。



主人公は女忍者の「暮六 常宵 (くれむつ とこよ)」で、敵やその攻撃をつかんで投げ返すことに特化しているのが特徴です。 まあキックなどもできはするんですが、なかなか使うことはない。 敵をつかめるだけでなく、ボスの攻撃も投げ返すことで戦っていきます (下)。 たいていのアクションゲームはショットが常識になってるので新鮮です。



ラスボスまでは結構サクサクとクリアできたのですが、ラスボスはかなり強い。 フリゲはいつもキーボードでプレイしているのですが、キーボードだと 3つ同時に押すアクションがあると難しい (壁蹴りとか) ので、ひさびさにジョイパッドが欲しくなりました。 色々な方向に投げられることを知ってないとキツイかな?

ラスボスは凄く強いので、かなり挑戦しました。短編ながらとても面白かった作品です。

フリゲ紹介: Vampire Night

Vampire Night は、ホラー風の探索型 RPG。 ホラー風であって怖がらせる要素はないかな? 戦闘重視の探索 RPG でクリアまで 10 時間でしたが、とても楽しめた作品でした。



魑魅魍魎を狩るのが生業の主人公メグ、拳銃使いの神父ガブリエル、堕天使レイブンと、風変わりなパーティでプレイしていきます。 悪魔退治の依頼、本ゲームの鍵となる「楔」の破壊依頼をこなすところ物語は始まります。 拳銃使いの神父ってなんかいいよね…トライガンとか HELLSING とか想像しちゃいます。 戦闘画面は全員の立ち絵が独特で、作者様のセンスが凄いなあと思いました (下)。 バットを持った堕天使も奇抜過ぎる。



探索はシンプルなシンボルエンカウント (左下) のため、敵はいくらでも避けられる仕組みになっていますが、あまり避けないほうが良い気がします。 というのも、戦闘中の行動でステータスが増えたり、戦闘回数でスキルを覚える仕組みなので、戦闘はガンガンこなしていかないと、終盤がキツくなる。 実際ほぼすべてのスキル / パッシブスキルは戦闘回数で覚えます (右下)。 戦闘中に増える系って全体的に難しいイメージあるけど、本作のはかなりバランス良くできてる気がします。



戦闘のバランスが良いのと、全体的にボス戦が白熱しやすい仕組みになっているのが、そう感じさせる理由なのかな。 難易度を変えられる仕組みもあり、終盤からはノービス (かんたんモード) にしてプレイしていましたが、それでもラスボスはギリギリでした。 ver 2.0 から魔魂集めなどの要素が追加されたようなのですが、おかげでかなりプレイしやすくなってると思う。 魔魂がないとラスボスは歯が立たなかったかも。

とても面白かった作品ですが、より楽しむためのポイントを書いておくなら、アイテムはガンガン使ったほうが良さそうです。 また注意点を挙げるなら、図鑑コンプしたい人は結構注意深くプレイしたほうが良いのと、 True End 前の最終ボス戦はやり直しが効かなくなるので注意、というところでしょうか。

2020年8月7日金曜日

Node.jsでよく使う書き方まとめ

最近 Node.js をメインにし始めてみてますが、あまり使い方がまとまってないので、自分用にまとめてみました。 これだけわかれば、普通の言語並に使いやすいんじゃないかな。 よく使うとこだけのまとめなので、困った時は公式のリファレンスを見ましょう。



sprintf

var util = require('util');
util.format('hello %s', 'world');
console.log('hello %s', 'world');

// npm install printf
var printf = require('printf');
var result = printf(format, args...);

// on Browser (上を後述の方法で野良ビルドしても良い)
// https://github.com/alexei/sprintf.js

printf (改行なし)

process.stdout.write("hello: ");

コマンドライン引数

// argv[0] = node, argv[1] = 'test.js', argv[2] = ...
let args;
if (process.argv.length < 3) {
  console.log('USAGE: test.js [file]');
  process.exit(1);
}
args = process.args.slice(2);
for (var i = 0; i < process.argv.length; i++){
  console.log(process.argv[i]);
}

sleep

Node.js で真っ先にハマるのは sleep でしょう。 Promise/async/await でも良いんだけど for 文でネスト地獄になり、また事故りやすいです。 色々な書き方があるけど、事故のないシンプルな時間 sleep が一番良いと思ってます。
function sleep(time) {
  const d1 = new Date();
  while (true) {
    const d2 = new Date();
    if (d2 - d1 > time) {
      return;
    }
  }
}

ファイル読み込み (一度に全部)

const fs = require("fs");
fs.readFile("input.txt", 'utf-8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

ファイル読み込み (複数を同期的に1行ずつ)

このコードで重要なのは、top-level で await しようとするとできない (Node.js v14.8.0 からは"モジュールなら" できる、そしてここがまた新たなトラップでもある…) ので、無名関数でラップしているということです。 私は top-level await が効かないのが嫌で、同期的に呼び出したい時は適当なライブラリを使ったりしていました。 ただ Deno に完全移行するに当たって、Node.js の遺産は Deno の書き方に近い、以下のコードで統一することにしました。 await で同期化したり、EOF チェックでバグを起こしにくくしているのがポイントです。 個人的バグ踏みやすさ No.1 は間違いなくこれ。
const fs = require("fs");
const readline = require("readline");

async function main() {
  const stream = fs.createReadStream(file);
  const reader = readline.createInterface({ input: stream });
  for await (const line of reader) {
    if (!line) continue;
    console.log(line);
  }
}
await main();

encoding

ファイル単位の同期処理、行単位の同期処理、そしてそこに encoding が入ってくると大変なことになる。 たぶんだけど行単位で shift-jis → utf8 に変換しながら同期的に処理はできない。 今のとこ対応してる標準ライブラリはない感じに見える。 たいていのケースは手動で事前変換して処理したほうが良さそう。 外部ライブラリなら、私は polygonplanet/encoding.js を使うことが多いです。

ファイル書き込み

バッファリングなど他の書き方はこちら
const fs = require('fs');
fs.writeFileSync("output.txt", "abc");
fs.writeFileSync("output.txt", "abc", (err) => {
  if (err) throw err;
  console.log('OK');
});

ファイルとディレクトリの判別

const fs = require('fs');
fs.stat('/dir/path', function (err, stats) {
  if (err) throw err;
  if (stats.isDirectory()) {
    console.log('This is a directory');
  }
});

glob

// npm install glob
const glob = require("glob")

// options is optional
glob("**/*.js", options, function (err, files) {
});

時間計測

console.time('hoge');
// 処理
console.timeEnd('hoge');

シェル実行 (非同期)

const exec = require('child_process').exec;
exec('ls -la', (err, stdout, stderr) => {
  if (err) { console.log(err); }
  console.log(stdout);
});

シェル実行 (同期)

const execSync = require('child_process').execSync;
var stdout = execSync('ls -la');  // Buffer なので通常は toString() が必要
console.log(`stdout: ${stdout}`);

野良ビルド

// npm install esbuild  // ビルドツール (webpack, etc.)
// npm install mnemonist  // 使いたいライブラリ
./node_modules/.bin/esbuild --bundle --minify build.js > bit-vector.js
build.js では使いたいライブラリのモジュールを require するだけ。
// npm install esbuild
(function() {
  var BitVector = require('mnemonist/bit-vector');  // 使いたいモジュール
})();

ファイルの扱い、async/await の書き方 (私も苦手なのでまとめた)、そしてビルドにさえ慣れてしまえば、あまりハマるところはなく使いやすい。 ただ Deno と比較するとだいぶ使いにくいさを感じるようにもなりました。

JavaScriptで画面内や要素内に文字列を最大化

小さなWebアプリやWebゲームを作っていると、画面内や要素内に文字を最大化する処理は、必ずと言っていいほど出てきます。 よく使う割には作り方が知られていないので、まとめておきます。 ライブラリもいくつかあるのですが、使い勝手が悪いので自作です。

方法としては、まず表示したい文字列を適当なフォントサイズで canvas でレンダリングしてみて、仮の width/height を取得します。 そしてその width/height が表示したい要素内で最大になるようフォントサイズを指定し直すだけです。

const tmpCanvas = document.createElement('canvas');

function resizeFontSize(node) {
  // https://stackoverflow.com/questions/118241/
  function getTextWidth(text, font) {
      // re-use canvas object for better performance
      // var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
      var context = tmpCanvas.getContext("2d");
      context.font = font;
      var metrics = context.measureText(text);
      return metrics.width;
  }
  function getTextRect(text, fontSize, font, lineHeight) {
    var lines = text.split('\n');
    var maxWidth = 0;
    var fontConfig = fontSize + 'px ' + font;
    for (var i=0; i<lines.length; i++) {
      var width = getTextWidth(lines[i], fontConfig);
      if (maxWidth < width) {
        maxWidth = width;
      }
    }
    return [maxWidth, fontSize * lines.length * lineHeight];
  }
  function getNodeRect() {
    var headerHeight = document.getElementById('header').clientHeight;
    var timerHeader = document.getElementById('timerHeader');
    var timerFooterHeight = document.getElementById('timerFooter').clientHeight;
    var height = document.documentElement.clientHeight - headerHeight - timerHeader.clientHeight - timerFooterHeight;
    return [timerHeader.clientWidth, height];
  }
  function getPaddingRect(style) {
    var width = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
    var height = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
    return [width, height];
  }
  var style = getComputedStyle(node);
  var font = style.fontFamily;
  var fontSize = parseFloat(style.fontSize);
  var lineHeight = parseFloat(style.lineHeight) / fontSize;
  var nodeRect = getNodeRect();
  var textRect = getTextRect(node.innerText, fontSize, font, lineHeight);
  var paddingRect = getPaddingRect(style);

  // https://stackoverflow.com/questions/46653569/
  // Safariで正確な算出ができないので誤差ぶんだけ縮小化 (10%)
  var rowFontSize = fontSize * (nodeRect[0] - paddingRect[0]) / textRect[0] * 0.90;
  var colFontSize = fontSize * (nodeRect[1] - paddingRect[1]) / textRect[1] * 0.90;
  if (colFontSize < rowFontSize) {
    node.style.fontSize = colFontSize + 'px';
  } else {
    node.style.fontSize = rowFontSize + 'px';
  }
}
おおまかな処理としては冒頭で述べたようにシンプルですが、実用上は margin/padding をよく考えないといけません。 今回は かんたんタイマー で使っている、表示要素の width/height 算出関数を例として挙げます。 ポイントとしては、要素の縦横幅は clientWidth / clientHeight で算出できます。 文字を表示する時にはさらに padding を差し引く必要がありますが、このサンプルでは getPaddingRect() で汎用的に処理しています。

汎用的に書くと getComputedStyle() を呼び出すことになるので、処理は遅くなります。 1回しか呼ばないので気にするほどではないかもですが、事前算出してベタ書きしても良いかと思います。 例えば Bootstrap の場合、padding は画面サイズに関係なく rem 固定です。 以下のように rem → px に変換した上で、事前に padding を算出することができます。
var remSize = parseInt(getComputedStyle(document.documentElement).fontSize);


最後に補足ですが、canvas を使わない方法もあります。 ただ少しコードが長くなります。 またどちらの手法を使っても、仮の width/height を算出する内部関数 getTextWidth() は、Safari の精度がよろしくありません。 要素内ギリギリに最大化して表示しようとすると誤差ではみ出てしまう問題があるので、算出されたフォントサイズの 90% を指定すると、今のところ良い塩梅です。

2020年8月1日土曜日

フリゲ紹介: SHATIKU QUEST 2

SHATIKU QUEST 2 は、悪名高き天下のブラック企業「幸福カンパニー」の強制労働施設から脱走した主人公が、完全な「退社」をするまでの物語。 ネタっぽいタイトルですが、本編のクリアまで6〜9時間くらい、そしてやりこみ要素もあり、作り込みも凄いです。



幸福カンパニーから脱走した主人公が、魔法学園の女生徒2人と出会い、脱走に成功するところから物語は始まります。 順風満帆の生活が待っていると思った主人公ですが、どこまで行っても幸福カンパニーから逃げ切れていないことがわかり、打倒カンパニーを目指します。 …現実の大企業なんかも完全な退社をしようとすると、意外と難しいんですよね。 退社したつもりでも企業のサービスを使わないと生きていけなかったりします。あるある。



シンプルな探索ビューとサクサクのサイドビュー戦闘が印象的でした (左下)。 探索は色々なところにガイドが付いていてわかりやすいです。 らんだむダンジョンのように宝箱や戦闘後ドロップでネタっぽい武器防具が手に入り、クスッときます。 序盤から最終盤まで使えるような武器防具が手に入ることもあり、結構大盤振る舞いです。 他にも、ぶきあつめ ~なんでも武器になるRPG~ っぽい要素もあったり、他の名作の良いところを組み合わせた一作になっています。

戦闘の比重が結構大きめの作品ですが、サクサクで、バランスがとても良く、楽しめました。 雑魚戦 (右下) はほぼワンパンですが、ボス戦や FOE 戦は中盤くらいから、結構歯ごたえがあります。 終盤はかなりギリギリの戦いになり、白熱しました。



最近は戦闘バランスが上手いし、戦闘が凄く面白い作品がたくさん出てきている (たとえば ロストヘブンツキノヒトイリスのゲーム) ので、SHATIKU QUEST 2 が面白かった理由を考察してみます。

まずは転職しやすい転職システムを用意したのが、特徴的だと思います。 転職システムでキャラごとの性能がガラッと変えられるので、ボスの特性に合わせて試行錯誤が楽しいです。 またキャラが最大8人とほどよく多く、戦闘に全員参加できるので、戦いの幅が広がって面白いです。 どの作品にも言えるのは、キャラごとのスキルがうまく分かれていて (左下)、状態異常をうまく使っていることです。 SHATIKU QUEST 2 では敵の耐性パズルが見えるので、方針を立てやすいのが良いですね (右下)。 昔の RPG は状態異常探しが大変だったけど、(攻略 HP なくても) 長く楽しめる作品にもなる。 方針を立ててもボスは第二形態くらいになってくるとまた一味違うので、白熱するという作りが面白かった。



小刻みにボス戦があるのも、面白さの要因ではないかと思います。 ライトなストーリーを楽しみつつ、白熱したボス戦を楽しむのに良い作品です。

フリゲ紹介: ツキノヒト

ツキノヒト は、壊滅した月面都市を舞台に少年少女が過酷な冒険と生温い青春をする探索 RPG。 様々な難易度でプレイできますが、どの難易度でも適度にスリリングでいて、ちょいムズくらいの、バランスの良い作品。 クリアまで6時間くらいでした。



盗掘屋のジーマとイアンが、コールドスリープされていた記憶喪失の少女カグヤを発見するところから物語は始まります (下)。 カグヤは彼らの仕事に同行し、記憶を呼び覚ますことが最初の目的になります。 ストーリーは王道的ですが、コールドスリープ前のツンツン性格と、コールドスリープ後のギャップが印象的でした。



数十年前に磁気嵐により破壊された廃都市群を探索することになりますが、どのマップも退廃した感じが出てて良いです (左下)。 ストーリーに沿って、ボロボロでギミックの効いたマップや、何十年経っても原型を残したマップが使い分けられていて、印象的でした。 探索は難易度カジュアルでも、なかなか歯ごたえがあります。 HP/MP に注意しながら、そして敵の遭遇確立 (右上にあるやつ) を見ながら、そしてお金と相談しながら、探索していきます。 MP が一番重要かな? ハック&スラッシュらしい難易度で、結構緊張感があります。

戦闘はシンプルなタイプのバトルですが、雑魚戦も油断は禁物です (右下)。 気を抜きすぎると、時々強いのに遭遇して全滅もあります。 ボス戦は難しめですが、拠点でしっかり準備をしていけば、カジュアルでは順調にクリアできました。 どのボスも状態異常が効きやすいので、色々な戦略が立てられて面白かった。 ボスになると状態異常が入らない作品も多いので、状態異常入ると面白さが増す感じがします。



状態異常技や回復技の有能イアン、脳筋/盾役のジーマ、魔法が使えるカグヤ、 どのキャラクターもそれぞれの特性を活かして戦えて、とても面白かった作品です。