El Mylar

映画・音楽作品の感想とか。

映画「Stasis ジャンプ」を観た

2017年。Netflix で見かけて、タイムトラベルモノだ〜と思って観てみたらかなりのガッカリ映画だった…。

映画の最初のカット。ビルの電気が煌々と輝く夜の都市に、突如核爆弾が投下されるカットから始まる。どうやら核戦争らしいけど、そんな戦争やってるんだとしたら、こんなにビルでガッツリ働いてる人達なんかいないのでは…。

とりあえずそういうワケで舞台は2067年。マッドマックス的な荒野を歩く男女は、何やらシェルターに辿り着く。入口でダサい合言葉を言い合う「反乱軍」の方たち。彼らはタイムマシンで過去に戻り、核戦争を未然に食い止めようとする派閥のようだ。

二人にタイムマシンの装置が取り付けられていく。この男女は恋仲みたいで、お互いの身を案じながら、過去にタイムスリップしていった。


一方、戦争が起こる前の2017年。なんだか魚みたいな顔をしたキンパツ少女が、友達にもらったドラッグを飲んで気を失ってしまう。後で分かるが、どうやらこれは「死んだ」描写らしいのだが、気絶したのと区別がイマイチつかない。

一度倒れた金髪は再び目を覚ますが、周囲にいた友達たちの姿がない。そして自宅に変えると、自分にそっくりな少女がベッドに寝ていた。あわててインターホンを鳴らし、母に助けを求めるが、母は自分の姿が見えていないらしい。この金髪はどうやら霊体になってしまったようだ。

翌朝、自分にそっくりな金髪少女は、家の食料をあさりまくる。そしてノートパソコンを開いて、「反乱軍」たちに「潜入成功」とチャットを送る。さらに、外出先の公園で見知らぬ男子大学生とハグをする。その男子大学生は「僕の体は新入生歓迎会でハメを外して急性アル中で死んだらしい」と語る。

どうも、金髪少女がドラッグで命を落とした瞬間、2067年からやってきた女がその少女の体に乗り移り、「金髪は死ななかった」という世界軸を作り出したようだ。同様に男の方も、どこぞの大学生が急性アル中で死んだところに乗り移ったようである。なんだか「マトリックス」みたいな設定である。

自分が死んでしまったことに気付いた霊体少女は、急に「千の風になって」の詩を読む。

若くして亡くなった2017年の男女に乗り移った、2067年から来た未来人の男女。二人は同じく過去にタイムスリップして活動している、他の「反乱軍」たちが集まるアジトに向かった。彼らが何の活動をして戦争を食い止めようとしているのかはよく分からないが、とりあえず反乱軍たちが集まっているのである。


一方2067年。「反乱軍」ときたら「帝国軍」がいるワケだが (違)、戦争で勝利を収めた派閥の連中は、反乱軍が過去にタイムトラベルして歴史を改変しようとしているのを快く思っていない。そこで「ハンター」と呼ばれる女を同じく過去に送り込み、反乱軍の活動を阻止することにした。

2017年に送り込まれたハンターはハル・ベリーもどきみたいな女で、ラジカセなどのパーツを組み合わせた自作のエフェクターみたいなモノを作る。そして帰宅途中の反乱軍のババアに近付くと、そのエフェクターみたいなヤツを投げつける。すると反乱軍のババアの魂がそのエフェクターに吸い込まれてしまった。ポケモンかな?

そしてそのエフェクターを道端に放置すると、帝国軍に電話をかけ、「2067年でこの装置を調べてちょうだい」と連絡する。2067年の帝国軍が、その道端に放置されていた装置を回収(50年もそのまま残ってたの?!)、中身を調べる。すると魂を吸い取られた反乱軍のババアの記憶が再生でき、2067年のアジトの所在がバレてしまった。

送り込まれた帝国軍によって、たちまち破壊される2067年のアジト。これにより、2017年に派遣されている反乱軍のエージェントたちは、帰る術を失ってしまった。


一方、霊体となった金髪少女は、なんとかして自分の体に戻りたいので、ハル・ベリーもどきのハンターとコンタクトを取る。ハンターがかけるサングラスは「ターミネーター2」みたいな感じで色んなセンサーが付いていて、霊体が見えるようになっていた。そして霊体少女は念力でスマホを操作し、「ココに自分の身体が居る」と、2017年のアジトの場所をチクる。

ハンターはアジトに潜入し、男女二人以外を皆殺しにする。男の方も銃で肩を撃たれてしまい、急いで金髪少女の自宅に避難する。未来人が2017年の人々と接触すると余計な歴史改変が起きるとも限らないから、彼らは怪我をしていても病院には行かないのだ (行けば?)

少女の自宅で止血しようとしていると、少女の母親が登場。まずいところを見られた二人は、仕方なく母親を縛り上げる。そこに金髪の霊体が戻ると、気配を察知した女は、「あなた、霊体として生きてるのね?!あなたがハンターを送り込んだせいで核戦争が起きたのよ!!」と、核戦争の原因を霊体少女になすりつける。平謝りする霊体少女。

そこにハンターが現れた。劣化版「パージ」とでもいおうか、ショボい戦いが家の中で繰り広げられる。なかなか止血をしてもらえず瀕死の男は、女に「君一人で、他の街にある、反乱軍の別のアジトに行くんだ」と言い残して倒れる。

金髪少女とハンターが一騎打ちで戦っていると、霊体少女が横から現れ、「ハンターさん、あんた悪いヤツなのね!」と言う。そしてハンターが持っていた、魂を吸い取るエフェクターを作動させ、ハンター自身を吸い取らせる。しかし近くにいた霊体少女と、金髪少女 (女エージェント) も巻き込まれてしまう。

さっきまで顔色が真っ青だったはずの男が起き上がり、エフェクターに駆け寄る。目を覚ました霊体少女は、元の身体に戻れていることに気付いた。ということは、女エージェントの魂はエフェクターに吸い取られてしまったのか…。落胆する男。その横で目を覚ましかけたハンター。慌ててハンターを締め上げようとするも、冒頭のダサい合言葉を口にするハル・ベリーもどき。そう、エフェクターに吸い取られたのはハンターで、ハル・ベリーもどきの身体には女エージェントが乗り移っていた。ご都合主義!

翌朝、男子大学生とハル・ベリーもどきのエージェント二人は、反乱軍の別のアジトへと旅を始める。よく分からないことだらけだったが、これまでの行いを反省した金髪少女は、二人に感謝し、別れを告げる。そして家の中に戻った少女が言う。「お母さーん、今日の朝ごはんは何ー?」

おしまい。


…何ですかコレ?\(^o^)/

  • タイムトラベルできるのは魂だけ
  • 魂が乗り移るのはその時代で命を落とした人の身体

という設定は面白かったのだが、タイムトラベルや核戦争にまつわる戦いにはフォーカスしておらず、2017年の世界で霊体となった金髪少女の更生の物語であった。

そのどちらもが中途半端に描かれ、登場人物たちの動機や行動理由がちっともハッキリしないまま終わる。話の意味は分かるが、映画として表現・映像化しきれていないのだ。

パソコンや監視カメラの画面のハメ込み合成感がスゴイし、「エージェントが見ていた記憶」を再生して見ているはずなのに、「さっきのカット」をそのまま利用してるから、記憶の中の映像なのに「自分自身の姿が第三者目線で映る」というお粗末っぷり。やはり、意味は分かるが表現が下手である。

劇中、唐突に「千の風になって」を英詩で歌うシーンがあって、「『千の風になって』って秋川雅史原曲じゃなくて海外の曲なの?」と思ったら、どうも海外の古い詩だったらしい。アレが原曲じゃない、というトリビアを覚えられたので、この映画の価値は多少ありそうか。

というワケで、全てが中途半端な、バカなティーン向けの映画でした。

TIME/タイム (吹替版)

TIME/タイム (吹替版)

  • 発売日: 2013/11/26
  • メディア: Prime Video

↑コレとどっこいどっこい。

EC ナビ・PeX の「まいにちニュース」に気持ちを自動で回答するブックマークレットを作った

「EC ナビ」と「PeX」というポイントサイトを、2003年頃から使っている。その中に「まいにちニュース」というコンテンツがある。これは、ニュース記事の末尾に「いいね」ボタンや「Bad」ボタンなどがあり、記事を読んだ気持ちを答えることでポイントがもらえる仕組みだ。

今回はこのボタンを自動的に押下するブックマークレットを作ってみた。

完成形

完成形は以下。以下のブックマークレットを EC ナビや PeX の「まいにちニュース」の記事ページで実行すれば、「いいね」ボタンがあるところまでスクロールして「いいね」ボタンをクリックしてくれる。

javascript:(e=>['angry','sad','cool','like'].map(x=>'#submit-'+x).concat(['bad','sad','glad','good'].map(x=>['.btn_'+x,'.btn_feeling_'+x]).flat()).some(x=>(e=document.querySelector(x))&&(e.scrollIntoView(),e.click(),!0)))();

以下、開発経緯

最初に作ったコード

まずは押下したいボタンの要素を特定する。EC ナビの PC 版は .btn_feeling_XXX、EC ナビのスマホ版は .btn_XXX、PeX は #submit-XXX という名前のボタンを押下すれば良いことが分かった。ただし、EC ナビは「bad」「sad」「glad」「good」の4種類であるのに対して、PeX は「angry」「sad」「cool」「like」と、XXX 部分に入れる「感情」の文言が若干異なっていた。

[
  // EC ナビ PC        // スマホ    // PeX
  '.btn_feeling_bad' , '.btn_bad' , '#submit-angry',
  '.btn_feeling_sad' , '.btn_sad' , '#submit-sad'  ,
  '.btn_feeling_glad', '.btn_glad', '#submit-cool' ,
  '.btn_feeling_good', '.btn_good', '#submit-like'
]

最初に作ったのは以下のようなコード。配列を順に走査し、要素が見つかれば scrollIntoView() でその要素のところまでスクロールし、クリックするという動きにした。break を使って、一度クリックできたらループから抜けるようにした。

javascript:((s, i, e) => {
  for(i = s.length; i--; ) {
    if(e = document.querySelector(s[i])) {
      e.scrollIntoView();
      e.click();
      break;
    }
  }
})(
  [
    '.btn_feeling_bad' , '.btn_bad' , '#submit-angry',
    '.btn_feeling_sad' , '.btn_sad' , '#submit-sad'  ,
    '.btn_feeling_glad', '.btn_glad', '#submit-cool' ,
    '.btn_feeling_good', '.btn_good', '#submit-like'
  ]
);

変数 ie はローカル変数として使うため、ド頭に宣言だけしている。if 文の中で代入しながら存在チェックを行っているところがコード短縮化のミソ。1行にまとめた際は、for 文のブレース {} が除去できる。なお、for ループは文字数短縮のために末尾から順にループするイディオムを利用しているので、配列末尾の方に優先的にクリックさせたい要素を並べておくと良いだろう。

コレでも動作するのだが、なんとなく冗長な気がして、もう少し文字数を減らせないか試してみた。

対象要素の配列を短縮化

まず、.btn_feeling_$submit- といった文言が重複しているので、ココを自動生成できないか考えてみた。

const pexElems = ['angry', 'sad', 'cool', 'like'].map((name) => {
  return '#submit-' + name;
});

const ecNaviElems = ['bad', 'sad', 'glad', 'good'].map((name) => {
  return ['.btn_' + name, '.btn_feeling_' + name];
});

const useFlat        = pexElems.concat(ecNaviElems.flat());
const useConcatApply = Array.prototype.concat.apply(pexElems, ecNaviElems);

このように、共通する文言を map() で付与して配列を生成してみた。EC ナビの方は .btn_XXX (スマホ版) と .btn_feeling_XXX(PC 版) とを同時に生成してみたかったのだが、上の変数ecNaviElems` は二重の配列として生成されている。

二重の配列を展開・フラット化するには、Array.prototype.flat() という関数が策定されている。コレを使って ecNaviElems を平たくし、pexElems と結合すれば良い。

別の方法で、Array.prototype.concat.apply(baseArray, nestedArray) といったイディオムもある。Array.prototype.flat() が動かないブラウザではコチラが使えるだろう。

というワケで、配列の宣言部分は次のようなコードに短縮化できた。

['angry', 'sad', 'cool', 'like']
  .map(x => '#submit-' + x)
  .concat(
    ['bad', 'sad', 'glad', 'good']
      .map(x => ['.btn_' + x, '.btn_feeling_' +x ])
      .flat()
  );

外側の map().concat() しているのが PeX 向けの配列で、内側で map().flat() しているのが EC ナビ向けの配列だ。

forbreak のイディオムを Array.prototype.some() に変更

次に、forbreak で処理していた部分を短くできないか見てみた。

配列を順に操作している時にループを抜ける方法には、Array.prototype.some() も存在することに気付いた。要素が見つかって、クリックができたら return true してやれば、以降の要素は走査されないワケだ。

allElems.some((name) => {
  if(elem = document.querySelector(name)) {
    elem.scrollIntoView();
    elem.click();
    return true;
  }
});

if 文に合致しなかった場合は何も返していないので undefined (Falsy な値) が返ったことになり、ループ処理が続く。

コレをこのまま1行にしても、イマイチ短くならない。しかし、&& 演算子を使った書き方にかえてやると、短くできそうだ。ちなみにこの && 演算子のことは「論理積」「論理 AND」演算子と呼ぶ。バイナリ論理演算子の一種だ。

allElems.some( name => (elem = document.querySelector(name)) && (elem.scrollIntoView(), elem.click(), true) );
  • (elem = document.querySelector(name)) で代入と存在チェックを兼ねる
    • 要素が存在しない場合はこの時点で false になるので、&& 演算子以降は処理されない
  • 要素が存在すると (elem.scrollIntoView(), elem.click(), true) 部分が実行され、最後の true が返るので、some() のループがココで中断される
    • 最後の true を忘れると、(elem.scrollIntoView(), elem.click()) だけでは undefined (Falsy) となり、some() のループが全要素に対して行われてしまう

完成形のおさらい

というワケで、全体を結合するとこのようなコードになる。

javascript:(e => 
  ['angry', 'sad', 'cool', 'like']
    .map(x => '#submit-' + x)
    .concat(
      ['bad', 'sad', 'glad', 'good']
        .map(x => ['.btn_' + x, '.btn_feeling_' + x])
        .flat()
    )
    .some(x =>
      (e = document.querySelector(x)) && (e.scrollIntoView(), e.click(), !0)
    )
)();
  • 全体の即時関数は (() => {})(); と書くより、適当な仮引数を1つ与えて (e => {})(); とする方が () より1文字減らせる。今回は e = document.querySelector() で DOM 要素を代入する変数のために仮引数を1つ用意できた
  • やっていることは allElems.some() の1処理だけなので、即時関数のブレース {} も除去して (e => allElems.some())() という構成にした。全体の即時関数の戻り地はどうでもいいのでこのように省いて良い
  • document.querySelector() の引数に渡す CSS クラス名や ID 名を、.map().concat() .map().flat() を駆使して構築する
    • もしココで要素の順番をシャッフルしたければ、.some() の手前で .sort(n=>Math.random()-.5) みたいなコードを入れれば、雑なシャッフルができる
  • some() の中は && 演算子を使って短縮化している

コレのスペースを除去して1行にまとめたのが冒頭のコード。

javascript:(e=>['angry','sad','cool','like'].map(x=>'#submit-'+x).concat(['bad','sad','glad','good'].map(x=>['.btn_'+x,'.btn_feeling_'+x]).flat()).some(x=>(e=document.querySelector(x))&&(e.scrollIntoView(),e.click(),!0)))();

その他

ブックマークレットを作るための短縮化には、拙作の @neos21/bookmarkletify という npm パッケージが有効かと思われる。コチラも合わせてドウゾ。

www.npmjs.com

以前作った、ポイントサイトやアンケートサイトで使えるブックマークレットは、別ブログ Corredor の方でいくつか紹介している。コチラもよかったらドウゾ。

neos21.hatenablog.com

neos21.hatenablog.com

neos21.hatenablog.com

neos21.hatenablog.com

neos21.hatenablog.com

neos21.hatenablog.com

以上。