kmaebashi.comのサーバ移転を行いました

私の個人Webサイトである「K.Maebashi's home page」(kmaebashi.com)を置いていたレンタルサーバ業者が事業を撤退するとのことで、新たにさくらインターネットVPSを借りてそちらに移転しました。

昨晩20:00頃にDNSの更新を行いました。TTLはデフォルトで3600秒だったので、DNSの浸透()は最長1時間で終わるはずで、今kmaebashi.comを見たら確実に新しい方のサーバが見えている、のではないかと思います。

http://kmaebashi.com

これが想定している新しいサーバを見たときの画面、

移転後サーバの画面

もし何らかの事情で古いサーバが見えてしまったとしたら、以下の画面になります。

古いサーバの画面

もし下の方の画面が見えてしまったら、掲示板なりここのコメント欄なりで教えてください(まあ、教えていただいたとして、私にできることはないように思いますが)。

このホームページは、最初に作ったのは1998年頃、私が使っていたプロバイダ(Nifty)のホームページスペースに置いたのが始まりです。その後、ドメインkmaebashi.comを取って移転して、そのレンタルサーバ業者が破産してサーバの再販元に引き取られたりとか、ところがその後もDNSはつぶれた会社に依存していて2017年頃に一度止まってしまったりとか、実はその後もそのDNSに依存していたらしいということが今回の移行で発覚したりとか色々ありましたが、なんやかんやでNiftyから数えれば25年にわたり運営してきたページです。今は見てくれる方も減ったとは思いますが、サーバがなくなるに任せて消えてしまう、というわけにはいきませんね。著書の正誤表なんかもここにありますし。

このページには、動的コンテンツとして、掲示板とアクセスカウンタがあります。掲示板は、元のものは「レンタルサーバでそれしか使えなかった」という理由でPHP + MySQLで書きましたが(2005年頃)、今回はVPSで言語も自由に選べるので、Java + Spring boot + PostgreSQLでゼロから作り直しました。データも移行済みです。この掲示板は、パスワードを設定しておけば自分の投稿を後から削除できるのですが、その削除用パスワードも「ほぼ」移行できたと思います。元の掲示板はパスワードにランダムなSALTを付けてMD5でハッシュしたものをDBに保持していて、そのままでいいかな、と思っていたら、ちょうど作り直している最中にMD5のパスワードが抜かれたというニュースがあったりして(この例ではSALTもついてなかったようですが)、MD5でハッシュ化したものに再度bcryptをかけるようにしました。「ほぼ」移行できたというのは、元のパスワードにシングルクォート等が入っていた場合、旧掲示板ではPHPのmagic_quotes機能で変換されてしまっていたからです。だからこんな変な機能を使ってはいけなかったんだ! (参考)

掲示板のURLは以下。拡張子が.phpになっていますが、これは過去投稿へのリンクを維持するためで、上述の通り中身はPHPではありません。

http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs

あとはアクセスカウンタ。これもJava + 生Servlet + PostgreSQLで書き直しました。私は「プログラムは依存が少ないほどえらい」と思っているのでさすがにこんなのにSpring bootとか使う気にはなれず。でもTomcatのコネクションプールは使った。

いまどき、「ホームページ」に「アクセスカウンタ」を、わざわざスクラッチで作り直してまで配置する奴もめずらしいかと思いますが、「元ページにあるものは基本全部引っ越す」方針としました。アクセスカウンタとか掲示板とかの作成記事はまた別途書こうと思います。

あまり更新もしていない過疎サイトですが、今後ともよろしくお願いいたします。

Rustで親への参照を持つ木構造

前回の記事、Rustで木構造の続きです。
前回の木は、親が子への参照を持つ1方向の木でしたが、今回は、子が親への参照も持つようにします。親が子への参照を持つだけであれば、各ノードを指す参照は必ずひとつなのでBoxが使えましたが、子が親への参照も持つとなると各ノードが複数の箇所から参照されることになり、かつ、循環参照になります。こういう場合は、親から子への参照を参照カウンタ式のスマートポインタ型であるRc(reference countingの略)とし、子から親への参照を弱参照(weak reference)で保持します。
図にするとこんな感じ。

題材としては、「The Rust Programming Language」の以下のページでやっていることそのものです。
doc.rust-jp.rs
だったら丸写しするだけじゃん、と思われるでしょうが、まあ慣れない言語で自分で組んでみるとそんなことでもはまるものです。
上の図にある木構造を作って、それを行きがけ順で走査するプログラムが以下。

use std::rc::Weak;
use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
    parent: RefCell<Weak<Node>>
}

impl Node {
    fn new(value: i32) -> Node {
        Node {
            value: value,
            children: RefCell::new(Vec::new()),
            parent: RefCell::new(Weak::new())
        }
    }
}

fn main() {
    let root = Rc::new(Node::new(0));

    let node1 = Rc::new(Node::new(1));
    *node1.parent.borrow_mut() = Rc::downgrade(&root);

    let node2 = Rc::new(Node::new(2));
    *node2.parent.borrow_mut() = Rc::downgrade(&node1);
    node1.children.borrow_mut().push(node2);

    let node3 = Rc::new(Node::new(3));
    *node3.parent.borrow_mut() = Rc::downgrade(&node1);
    node1.children.borrow_mut().push(node3);
    root.children.borrow_mut().push(node1);

    let node4 = Rc::new(Node::new(4));
    *node4.parent.borrow_mut() = Rc::downgrade(&root);

    let node5 = Rc::new(Node::new(5));
    *node5.parent.borrow_mut() = Rc::downgrade(&node4);
    node4.children.borrow_mut().push(node5);

    root.children.borrow_mut().push(node4);

    dump(&root);
}

fn dump(node: &Rc<Node>) {
    print!("node({}) ", node.value);
    match node.parent.borrow().upgrade() {
        Some(parent) => {
            println!("parent..{}", parent.value);
        },
        None => {
            println!("parent..None");
        }
    }
    for child in &*node.children.borrow() {
        dump(child);
    }
}

実行結果はこんな感じ。

node(0) parent..None
node(1) parent..0
node(2) parent..1
node(3) parent..1
node(4) parent..0
node(5) parent..4

単にRcとWeakで木構造を作るだけなら、こう書けばよいのでは、と思うかもしれませんが、

use std::rc::Weak;
use std::rc::Rc;

struct Node {
    value: i32,
    children: Vec<Rc<Node>>,
    parent: Weak<Node>
}

impl Node {
    fn new(value: i32) -> Node {
        Node {
            value: value,
            children: Vec::new(),
            parent: Weak::new()
        }
    }
}

fn main() {
    let root = Rc::new(Node::new(0));

    let node1 = Rc::new(Node::new(1));
    node1.parent = Rc::downgrade(&root);
    root.children.push(node1);
}

これだと、以下のエラーが出ます。Rustでは、「共有されているものには書き込めない」ので、Rcの指す先に書き込むことはできません。

error[E0594]: cannot assign to data in an `Rc`
  --> src\main.rs:24:5
   |
24 |     node1.parent = Rc::downgrade(&root);
   |     ^^^^^^^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Node>`

error[E0596]: cannot borrow data in an `Rc` as mutable
  --> src\main.rs:25:5
   |
25 |     root.children.push(node1);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Node>`

Rcの指す先のものに書き込みたければ、最初に挙げたプログラムのように、RefCellで包む必要があります。

で、ここまでは、上で挙げた「The Rust Programming Language」に書いてあることそのものなんですが。
私がはまったのは、dump()関数の中の以下のループのところです。

    for child in &*node.children.borrow() {
        dump(child);
    }

たとえば、main()の中で、rootの子としてnode1を足すところにはこう書いてあって、

    root.children.borrow_mut().push(node1);

RefCellからborrow_mut()で書き換え可能な参照を借用し、それに対してpush()をいきなり呼んでいる、ということは、root.children.borrow_mut()はVecと同じように扱えるのだな、と思って、dump()関数を最初はこう書きました。

fn dump(node: Rc<Node>) { // 引数の型も違う。後述。
    print!("node({}) ", node.value);
    match node.parent.borrow().upgrade() {
        Some(parent) => {
            println!("parent..{}", parent.value);
        },
        None => {
            println!("parent..None");
        }
    }
    for child in node.children.borrow() { // ←ここ
        dump(child);
    }
}

しかしこれはコンパイルエラー。

error[E0277]: `Ref<'_, Vec<Rc<Node>>>` is not an iterator
  --> src\main.rs:58:18
   |
58 |     for child in node.children.borrow() {
   |                  ^^^^^^^^^^^^^^^^^^^^^^ `Ref<'_, Vec<Rc<Node>>>` is not an iterator
   |
   = help: the trait `Iterator` is not implemented for `Ref<'_, Vec<Rc<Node>>>`
   = note: required because of the requirements on the impl of `IntoIterator` for `Ref<'_, Vec<Rc<Node>>>`

For more information about this error, try `rustc --explain E0277`.

node.children.borrow()の型はVec>>だと言っている。つまり、node.children.borrow_mut().push()の時はピリオドがあったから自動参照外しが行われたけれど、for文のinのところにただ置いただけでは自動参照外しはしてくれないということか。
そこで、手動で参照を外すため*を付けたら、

    for child in *node.children.borrow() {
        dump(child);
    }

これもコンパイルエラー。

error[E0507]: cannot move out of dereference of `Ref<'_, Vec<Rc<Node>>>`
  --> src\main.rs:58:18
   |
58 |     for child in *node.children.borrow() {
   |                  ^^^^^^^^^^^^^^^^^^^^^^^
   |                  |
   |                  value moved due to this implicit call to `.into_iter()`
   |                  move occurs because value has type `Vec<Rc<Node>>`, which does not implement the `Copy` trait
   |
note: this function takes ownership of the receiver `self`, which moves value
help: consider iterating over a slice of the `Vec<Rc<Node>>`'s content to avoid moving into the `for` loop
   |
58 |     for child in &*node.children.borrow() {
   |                  +

For more information about this error, try `rustc --explain E0507`.

まあこのエラーは見慣れてる。Vecはコピーできないから、こういう場合は&を付けて参照を見ればよいのだ。

fn dump(node: &Rc<Node>) {
    print!("node({}) ", node.value);
    match node.parent.borrow().upgrade() {
        Some(parent) => {
            println!("parent..{}", parent.value);
        },
        None => {
            println!("parent..None");
        }
    }
    for child in &*node.children.borrow() {
        dump(child);
    }
}

というわけでこうなって、実際動いたわけですが、なんかこの「&*」のあたり、「これでいいの?」と思ってしまう。

ていうかRustの参照外し演算子の「*」ってなんで前置なの? もちろんCの*も前置ですが、これはCの作者Dennis Ritchieが、1981年に後置の方がよかったと指摘され、「but by then it was too late to change. 」(しかし、その時には、変更するにはもう遅すぎた」と言っているような仕様なのに。自動参照外しがあるからいいというものではなく、だいたい自動とか暗黙とかそういったものは混乱を招くものなのに。まさに今回私がはまったように。

なにかこう、Rust初心者の私の知らない理由があるんでしょうか。

Rustで木構造

最近Rustの勉強を始めました。手始めに以前Javaで作った簡易プログラミング言語samplanでも実装してみようかと思ったのですが、コンパイラを作るとなれば解析木が作れなければ話にならず、では木を作ろうとするとRustといえば所有権だのなんだのと参照(ポインタ)の扱いが大変なのが知られているわけで、ひとまず小さなサンプルプログラムで木構造を作ってみました。Boxを使って、こんな感じの木構造を作ります。
木構造の図

// 木構造のノードを表す構造体。
// 複数の子を指す参照をVecで保持している。
struct Node {
    value: i32,
    children: Vec<Box<Node>>
}

impl Node {
    fn new(value: i32) -> Node {
        Node {
            value: value,
            children: Vec::new()
        }
    }
}

fn main() {
    let mut root = Node::new(0);
    let mut node1 = Node::new(1);
    let node2 = Node::new(2);
    node1.children.push(Box::new(node2));
    let node3 = Node::new(3);
    node1.children.push(Box::new(node3));
    root.children.push(Box::new(node1));
    let mut node4 = Node::new(4);
    let node5 = Node::new(5);
    node4.children.push(Box::new(node5));
    root.children.push(Box::new(node4));

    dump(root);
}

// 木構造を行きがけ順(先行順)でダンプする
fn dump(node: Node) {
    println!("node({})", node.value);

    for child in node.children {
        dump(*child);
    }
}

まあ、簡単です。ヒープに領域を確保するにはBoxを使う、というのはRust独特ですが、それを除けばCやJavaに慣れた人なら読めるコードだと思います。所有権云々で気を付けなければいけないのは、main()の中で木構造を作っているところで、たとえばこんなふうに、「Nodeを作ったらとりあえず親に登録しておこう」という方針で書くと、変数が所有権を失って以後使えなくなるので、順番に気を付ける必要がある、ということぐらいです。

fn main() {
    let mut root = Node::new(0);
    let mut node1 = Node::new(1);
    root.children.push(Box::new(node1)); // node1をrootの子として登録した時点で、変数node1は所有権を失う
    let node2 = Node::new(2);
    let node3 = Node::new(3);
    node1.children.push(Box::new(node2)); // だから、ここでnode1は使えない
    node1.children.push(Box::new(node3));
    let mut node4 = Node::new(4);
    root.children.push(Box::new(node4));
    let node5 = Node::new(5);
    node4.children.push(Box::new(node5));

    dump(root);
}

上記のソースだと、こんなエラーが出ます(抜粋)。

error[E0382]: borrow of moved value: `node1`
  --> src\main.rs:23:5
   |
19 |     let mut node1 = Node::new(1);
   |         --------- move occurs because `node1` has type `Node`, which does not implement the `Copy` trait
20 |     root.children.push(Box::new(node1));
   |                                 ----- value moved here
...
23 |     node1.children.push(Box::new(node2));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move

……まあ、その程度のことで、ここまでは簡単なんです。
しかし、プログラミング言語の解析木だと、こんな単純な木構造では済まず、「子から親への参照」が必要になる部分もあります。samplanのような言語(CやJavaも同じですが)では、波括弧で囲まれたブロックがスコープを示し、各ブロックの中では、その外側のブロックで宣言された変数とかが参照できます。そうなると、子のブロックから親のブロックへの参照が保持できないと嬉しくありません。
それをやってみたらえらく大変だったのですが、その話はまた次回に。乞うご期待!

「完全初心者のためのプログラミング入門」完結しました

2021年の6月頃に初公開した「完全初心者のためのプログラミング入門」に、以下の二つのページを加えました。これにて完結となります。

ここまでは「UFOゲーム」を作ってきましたが、最終回である今回は、こんな感じのシューティングゲームを作ります。

シューティングゲーム」の画面

下にあるキャノン砲で、敵を撃墜するゲームです。敵は、UFO、エイリアン、フライングボード(くるくる回りながら落ちてくる板です。何かに似ていますが気にしないように)、それからエイリアンが落とす爆弾の4種類です。実際のゲームは、こちらで遊べます。

ここまで作ってきたUFOゲームに比べると、相当複雑なプログラムに思えるのではないでしょうか。実際それなりにプログラムは長くなっていますが、メソッドオーバーライドとかポリモルフィズムとかのオブジェクト指向の機能により、それほどごちゃごちゃにならずにプログラムが組める、ということを示せたと思います。今後、敵の種類をもっと増やすのも簡単にできるようにしています。

一昔前は、「オブジェクト指向」について、難しいとかわからないとかそんなものは役に立たないとか、そんな意見が山ほど出ていましたが*1、実際にこのような「オブジェクト指向が明確に役に立つ例」を見てみれば、そういう疑問も解消するのではないかと思います*2

JavaScriptの「オブジェクト指向」はやわかり』の方は、class構文を使わない、昔ながらのJavaScriptオブジェクト指向を実現する方法について説明しています。いまどきそんなのなんの役に立つのか、と思うかもしれませんが、JavaScriptのclass構文が単なるシンタックスシュガーにすぎない以上、知らないで済ませることはやはりできないでしょう。

これを始めたときに書いたはてなブログがこれです。

kmaebashi.hatenablog.com

この記事の最後で、『最終的には、これくらいのシューティングゲームくらいまで持っていきたいと思っていますが、さて、どうなりますことやら。』と書いていますね。どうやらそこまで持っていくことはできました。いやあ長かった。

入門記事本体の「なぜこんなのを書いたのか」にも書きましたが、この「入門」は、1982年の「マイコンBASICマガジン」(略してベーマガ)の記事「UFOゲームを作ってみよう」へのオマージュです。しかし、当時の私には、UFOゲームは作れても、このシューティングゲームくらい複雑なプログラムは書けなかったと思います。今書けるのは、もちろんプロとして30年くらいやってるので当たり前ではあるのですが、ハードウェアの進歩(1982年当時なら、このゲームは機械語が必要だったでしょう)のほか、プログラミング言語や技法の進歩もあると思っています。今の若い人はここから始められて幸運だ、ぐらいのことは言っていいかもしれません。

多くの人が、プログラミングの楽しみに触れられますように。

 

*1:最近あまり聞かない気がするのは、たぶんみんながオブジェクト指向を理解したためではなく、話題としてトレンドではなくなったため、理解している人は普通に使っているが理解できない人が無理して理解する必要がなくなったからじゃないかなあ。

*2:その上で、そういう役に立つ範囲がどれぐらい広いのかはまた別問題

Vue.jsでテトリス風ゲームを作ったよ

タイトルの通り、Vue.jsでテトリス風のゲームを作りました。

http://kmaebashi.com/programmer/vuetetris/index.html

ゲーム画面はこんな感じ。

遊び方やゲーム本体のページへのリンク、ソースプログラム一式は上記のページに記載しています。

見た目、前回のCanvasを使ったJavaScriptテトリスReact.js版テトリスと何が違うんだ、と言われそうですが、中身は結構違うのです。いやほんと。

React.js版と同様、ゲーム画面はtableで組んでいます。React.jsに比べると、Vue.jsはアプリそのものの作り方がまるっきり変わってしまう、というわけではなくてよいですね。

しかしVue.js、「splice()とかpush()を使わず、配列の要素に直接代入すると、それを検知してくれないのでDOMが更新されない」ってのは落とし穴ですな。みんなはまっているようですが私もはまりました……

 

WordleとKotobade Asobou 言葉で遊ぼうの過去の解答ページを作りました

タイトルの通りですが、Wordleと「Kotobade Asobou 言葉で遊ぼう」の過去の解答ページを作りました。2022年6月からの分を掲載しています。

 

Wordle

http://kmaebashi.com/etc/wordle.html

 

Kotobade Asobou 言葉で遊ぼう

http://kmaebashi.com/etc/kotobade_asobou.html

 

時々更新するつもりですが、カンニング用のページにするつもりはないので、世界中のどこかでまだ誰かが解いているものは載せません。後から「あんな解答あったねえ」と振り返るページです(需要があるかどうかは知りませんが)。

 

緑のAACR 2022に参加してきました

先日、アルプスあずみのセンチュリーライド(緑のAACR)に参加してきました。

aacr.jp

これは私は以前にも参加していたのですが、コロナ禍で2年連続で中止となり、今回は3年ぶりの開催となりました。自粛ムードは世間でもすっかり緩んでいますが、新型コロナ感染者数で言えば今は2年前よりも去年よりも多いので、今年開催すること、それに参加することには異論もあると思います。しかし、このイベントは私にとっては大変楽しいイベントで、我慢ができなかった、というのが正直なところです。

前回参加した時の内容はこちら。

kmaebashi.hatenablog.com

イベント自体は5/22の日曜ですが、出走が05:30ですし受付は前日なので、土曜のうちに移動します。名古屋駅輪行袋に自転車を詰めてしなの7号で松本まで。

輪行は何度もやっているのに、毎回苦労します。

最後席を取ったので、席の後ろに置きます。しなの7号だとぎりぎりでした。

松本で輪行解除。

松本駅から受付場所の「梓水苑」というホテルまで移動します。途中のロイヤルホストで昼食。なんか3回連続でここに寄っていますが、せっかくここまで来ているのだから、うまそうな蕎麦屋でも探せばよかったですかね。次回はそうしよう。

受付場所の「梓水苑」はこのイベントに協賛しているホテルであり、160kmコースだとスタートとゴールもここなのでここに泊まれば一番楽なのですが、毎回予約でいっぱいいになってしまいます。AACRの参加申し込みをした後では間に合いません。

この日は天気も良くなかったので、受付がすんだらとっとと宿に移動して、宿に併設のお寿司屋さんで夕方から飲んで寝てしまいました。

お通し。

天ぷら盛り合わせ。山菜がたくさん。

カツオの刺身。

バイ貝の煮つけ。

茶碗蒸し。

翌朝に響くほどは酒飲んでませんよ!

 

で、翌朝です。

4時前には起きたのですが、あいにくの雨。出走までには止むといいなと思いながら朝食を食べて、宿を出ようとしたところでトラブルが。フロントのチェーンが外れてフレーム側に落ち込んで、しかもフレームとチェーンリングの間でがっちり挟まってしまい外れない。結局クランクをぐりぐり回してやっと外れたのですが、かなり焦りました。フレームに傷も増えてしまった……

で、荷物を預けて、いよいよスタート地点です。この頃には雨も上がっていました。

アルプスの山々も、よく見えない。

で、5人一組で10秒ごとにスタートします。

23kmほど走ると、第1エイドの穂高エイド。AACRでは、このように途中に何か所かエイドがあり、補給食を食べることができます。これが充実しているのがAACRの特長です。

写真の奥の方、木製のデッキとなっており、「すべるので注意してください!」と係りの方が盛んに声を出していました。それでも滑っていた人がいましたが、私の靴はロードバイク用のビンディングペダル(SPD-SL)ではなく、本来マウンテンバイク用のSPDだから大丈夫だろう、と余裕ぶっこいていたらやっぱり滑った。

ここの補給は、

米粉とポロネギのポタージュスープと、

やさいぱん。4種からふたつ選べるので、私は紫芋のとほうれん草のをいただきました。

みんなスタンド付いてないので、駐輪場はこうなります。私の愛車は右下の赤いTREK EMONDA SL5 2017年モデル。

ここから20kmばかり走ると、第2エイドの大町エイドです。あづみの公園の中にあり、公園内を結構走る必要があります。しかも結構な上り坂です。

その上り坂を登り切れば伝説のねぎ味噌おにぎり……だったのですが、今回は違っていました。

冷麦です。前回は冷麦(そうめん?)はこの次の木崎湖エイドだったのですが。

行者ニンニク入りの冷麦に、

水羊羹。

また20kmほど走って、今度は青木湖エイドです。この区間が、激坂というほどでもないゆるい上り坂がだらだらと続いて、精神的に一番きつい区間でした。エイドの直前には結構な坂があったし。

伝説のネギ味噌おにぎりは、このエイドで出てきました。

以前と違い、個包装のお弁当のようになっていますが、感染対策でしょうね。

ここからまたしばらく走って、折り返し点、白馬エイドです。

白馬エイドは場所が違っていて、レストランか何かの前庭でした。

ここの駐輪場にはバイクラックが用意してあったのですが、どうもわかってない人が設営したのか低すぎて、サドルを高く設定している人はかけられなくて困っていたようです。私はといえばサドルは十分に低いので楽勝でかけられましたよ畜生。

これがおそらく今回の目玉の石窯ピザ。これのために場所も変えたのでしょう。ちゃんと熱々でした。

甘酒。これの配布はボランティアなのか子どもがやっていて、「これ、お酒?」と聞かれて「ノンアルコール!」と即答していた。

白馬エイドで折り返したら、青木湖エイドに戻ります。往路は青木湖エイドから白馬エイドまでが基本下り基調で、また坂を上るのかー、と思ったのですが、復路は別経路で、もちろん上り坂はありましたけど一部に集中していてだらだらつづかないだけ楽でした。

ここの補給は、

冷奴と、

おやき。中身はあんこでした。

青木湖エイドから次のあずみのエイドまでの途中に、前回も前々回も寄ったヤマザキYショップがありました。ここはAACRの参加者には有名なところなのですが、そのせいか見ての通り混んでいて、おやきはさっき食べたしな、と今回はスキップ。

……寄っとくべきだったかなあ。

しばらく走ると最終エイド、安曇野エイドです。

ここの補給は前回前々回と同様、ごまおはぎとリンゴジュース。写真撮り忘れ……

ここから20kmばかり、地味な登りの道を走ると、ゴールです。

ゴールした人たちがまったりしています。

ピントが地面にあってしまっていますが、「完走証代わりのりんごパイ」と、なぜかレッドブルがもらえました。私なんかは前回も完走しているからいいけれど、「初めて160km完走した!」って人なんかは完走証が欲しかったんじゃないかなあ。

この後は、梓水苑のお風呂で汗を流して、また輪行で帰りました。

以下、風景です。正直今回、写真ポイントにおける参加者のマナーが悪すぎて、あれに混じるのも嫌だなあ、と思ったのであまり撮っていません。白馬エイドで撮ればいいやと思っていたのに今回場所が違ってたしな。

白馬エイドは毎回絶景なんですが、今回の場所だとちょっと木が邪魔ですね……

帰路。駅弁が全部売り切れという悲劇。

これは運営側が「これぐらいのペースで走るとちょうどいいですよ」と公開しているシートです。これよりはちょっと早めに走って、15:40頃にはゴールしたのですが、今回ヤマザキYショップに寄らなかったし、後半になるにつれて余裕の時間が減っていったのがなんとも。

コロナでなまったか。それとも単に加齢か。3年経ったしなあ。

今回も大変楽しかったので来年も(開催されれば)行くのは確定として、もうちょっとのんびり、寄り道なんかもしながら120kmで参加するか、今回と体力の衰え方を比べるために160kmで出るべきか、迷い中です。