アグリコラ ルール備忘録(3) アクションスペースの使用可否

アグリコラのアクションスペースの使用可否について議論する。今回は、空いているアクションスペースについてのみ取り扱い、占有状態を無視するカードについては取り扱わない。

アクションスペースの使用に関する基本ルール

説明書のWork Phaseの節でワーカープレイスメントの基本的なルールと共に、アクションスペースから得たアクションとアクションスペースの使用可否の関係についても書かれている。

You cannot use an action space without taking at least one of the available actions. AGRICOLA

得られたアクションのうち一つもtakeすることなくアクションスペースを使うことはできない、と書いてある。ここでは、このtakeを「消費」と訳すことにする。

Details on the Work Phaseの項にさらに詳しい記述があり、

We distinguish between permanent action spaces and accumulation spaces. Permanent action spaces provide the exact same actions every single round. Accumulation spaces accumulate goods, offering varying amounts of goods each round.

permanentアクションスペース(以下、恒久アクションスペース)と累積スペースは区別されるという話。そして、

Some permanent action spaces provide multiple actions that can be taken in any order (“and/or”). Other ones require you take the actions in order (“and afterward”). With the latter, you must take the first action; only then can you also take the second action. AGRICOLA

恒久アクションスペースには複数のアクションを提供するものがあり、任意順で消費してよい and/or タイプと、必須アクションの後に別のアクションが得られる and afterward タイプがあることが説明されている。例えば、「農場拡充」アクションスペースは「部屋の建設」アクションと「厩の建設」アクションの and/or であり、「子供がほしい」アクションスペースは必須の「家族を増やす」アクションの後に「小さい進歩」が得られる and afterward である。また、(二人プレイ以上での)「集会所」アクションスペースは、スタートプレイヤートークンの取得が常に有効なアクションとなるため、小進歩を出せなくても使用できる。

一方、BGAの挙動からの推察だが、累積スペースにはアクションが無く、そこに置いてある品物を得るのはアクションスペース使用時の共通の効果として処理されるようだ。得られるアクションが無ければ、「得られたアクションのうち一つも消費することなくアクションスペースを使うことはできない」という制約は無視される。言い換えれば、これは「アクションが得られる場合、その内の一つ以上を消費しなければならない」と解釈される。よって、品物のない累積スペースも使用できる。

アクションスペース使用の before でカード効果を誘発する場合

さて、「得られたアクションのうち一つも消費することなくアクションスペースを使うことはできない」という制約は、即ち結果的に必須アクションを消費すれば良いということを意味する。人物を配置する時点でアクションが実行できなくても、アクションスペース使用の before でカード効果を誘発し、結果的に必須アクションを消費できれば、そこに人物を配置できる。例えば、「森の労役者」前提で「農場拡充」を使用したり、「家畜警護」前提で「柵」を使用したりすることはよくあるだろうし、「開拓者」で資材を貰いながらの改築や、「教師机」で「左官」や「屋根付け」、「修理屋」等を出しながらの改築も同じ理屈で可能だ。

但し、他のプレイヤーの判断が介入する場合はこの限りではない。「居酒屋の店主」を出しているプレイヤーから小麦を貰うことをあてにして「穀物活用」を使うことはできない。もしこれを許可していたら、「居酒屋の店主」を出しているプレイヤーが小麦を渡さないことで「穀物活用」を使おうとしたプレイヤーを反則負けにできただろう。

また、BGAではカード効果をあてにしたアクションスペースの使用可否判断がしばしばバグっているので気を付けよう。過去に(今も?)「陶磁器」「鍋パン作り」「レンガ窯」を出しているのにレンガを持っていないと「穀物活用」を使用できない、「鍋パン作り」「樹木畑」を出しているのに「穀物活用」を使用できない、等の不具合があった。

アクションを置き換え

アグリコラには、特定のアクションを他のアクションに置き換えるカードが存在する。「畑の取引人」や「新入生」等である。(画像はBGAより。)

これについてはAgricola Revised Edition Unofficial CompendiumのCommon Points of Confusion for Original Edition Playersの章に記述がある。

It is now possible to use an action space and use no action given by the space, if and only if a card allows you to substitute one (or more) of those actions with another action 《or effect》 Agricola Revised Edition Unofficial Compendium

つまり、アクションの置き換えはそのアクションを消費したものとして扱う。「畑の取引人」で野菜を貰うためだけに「大きい進歩」を使用したり、「新入生」で職業を出すためだけに「穀物活用」を使用したりできる。

「若い農夫」や「配管工」等のカードとの違いに注意しよう。これらのカードだけでは、進歩を出さずに(or建設せずに)「大きい進歩」アクションスペースを使うことはできない。アクションスペースから得られるアクションを消費していないからである。

アグリコラ ルール備忘録(2) 繁殖

アグリコラで定義が不明瞭な繁殖とカード効果の関係について議論する。「塩づくり」、「かまど」+「かご運び」、「純血種の育成者」+「ボアスピア」等が関係する。

繁殖の定義

繁殖について、説明書では以下のように説明されている。

After you fed your family, your animals breed. If you have at least 2 animals of the same type, you get exactly one animal of that type from the general supply, but only if you can accommodate that new animal. AGRICOLA

番いが居て、収容可能であれば新たな家畜を得られることが書いてある。

注意点としては、収容可能でなければそもそも新たな家畜は得られないという所だろうか。BGAのUIでは、番いさえ居れば家畜が降ってきて収容して見せよと言われるが、これはルールを忠実に表現したものではない。恐らく、「新たな家畜を収容可能かどうか」を正確に実装するのが困難で、不具合によってできるはずの繁殖をできないままゲームが進行してしまうのを防ぐためにfail safe的にこのような実装になっているのだと思われる。

繁殖に関わる家畜の変換については、続く文言で

You are explicitly forbidden to turn the parent animals or the newborn into food during the breeding phase. AGRICOLA

繁殖フェイズでは、parent animals と the newborn を食料に変えることは明示的に禁止されている、と書いてある。

parent animals と newborn animals についてはAppendixに定義がある。

Newborn animals, parent animals: Newborn animals are animal offspring. Parent animals are the two animals that provided the offspring. Appendix

大したことは書いていない。newborn animalsは通常の家畜と排他的に区別されるのではなく、繁殖処理中の家畜のただの便宜的な呼称と捉えるべきだろう。

繁殖の疑問

さて、ここまで読むと2つの疑問が湧いてくる。

  1. 繁殖した家畜を、収穫内に変換/交換できるか?
  2. 繁殖フェイズ以外で繁殖した家畜を即座に変換/交換できるか?

繁殖した家畜の変換/交換

繁殖した家畜を収穫の内に調理したりカードの効果に使用できるかどうかは、「塩づくり」や「かご運び」等のカードの性能に大きく影響する。説明書の記述だけでは不明瞭なので、非公式説明書的な存在であるAgricola Revised Edition Unofficial Compendiumを読んでいく。

すると、Specific Terminology and Definitionsの章、Harvest phaseの項目に

The breeding phase is the only time in the game where you may not eat or exchange animals. However, eating or exchanging animals is possible after breeding and before the next round or scoring begins. Agricola Revised Edition Unofficial Compendium

繁殖の後かつ次のラウンド(or得点計算時)の前に家畜を食べたり交換したりできる、とある。

一方、かまどと調理場の説明には

After the breeding phase of the last harvest, the game ends immediately; you can not convert the animals received in the last harvest to food at all. Agricola Revised Edition Unofficial Compendium

最後の収穫の後は即座にゲームが終了し、最後の収穫で受け取った家畜を食料にすることは全くできない、と書いてある。矛盾している。

前回の4つのタイミングに照らして考えれば、繁殖フェイズにも対応する beforeimmediately afterafterがあり、after系は繁殖フェイズ終了後かつ収穫の間と考えられそうだ。このタイミングで家畜を変換/交換できるとするのが一貫性があるように思う。恐らく、かまどと調理場の説明が古くなってしまっているのではないかと思うが、この文書、別にgitでバージョン管理されてたりはしなさそうなので、今回はそこまで追ってない。

さて、BGAではどうなっているのかというと、繁殖した動物の再配置を行うと、家畜の変換/交換ができるようになる。そのためには、設定の「手番の確認」を「オフ」以外に「自動的に再配置する」を「確認なしで有効」以外に設定する必要がある。おすすめは、それぞれ「オン」「確定ボタンにより有効」だ。これなら、自動配置を有効にしつつ、常に確定ボタンが出る。操作が必要なタイミングでタイマーに焦らされることもない。誤ったタイミングで家畜を消費すると、繁殖の前に消費した扱いになり繁殖が行われないので注意しよう。

繁殖フェイズ

↑ここはまだ繁殖フェイズ。ここで家畜を消費すると繁殖フェイズの前に消費した扱いになる。

繁殖フェイズの後

↑このタイミングなら繁殖で得た家畜を消費できる。

繁殖フェイズ以外の繁殖

アグリコラには繁殖フェイズ以外に繁殖を行うカードが存在する。「純血種の育成者」「猪飼い」「穀物飼料」等。しかし、繁殖フェイズ以外での繁殖に関する記述は説明書にもUnofficial Compediumにも見付からなかった。説明書の記述では、家畜の変換禁止は繁殖フェイズに適用されるため、素直に読めば、これらのカードの効果で繁殖した家畜を即座に交換することは許されそうである。殆どの場合、これと繁殖の after で家畜を交換することに差異はないが、「ボアスピア」のような家畜を得た瞬間に交換に出してしまうカードの解釈には影響する。繁殖の定義をより明確にすべきと考える。

BGAでは、豚を丁度2頭飼っていて繁殖可能なとき、「純血種の育成者」の効果で得た豚を「ボアスピア」で使用すると飼っている豚が1頭に減るというバグがあった*1。BGAは繁殖フェイズの家畜の消費禁止の実装が怪しくて、繁殖前後に限らずカードの効果に家畜を使うとしばしば他の家畜が消滅するので気を付けたい。

*1:多分まだ直っていない

アグリコラ ルール備忘録(1) 4つのタイミングとカードのプレイの処理順

説明書に書かれていないアグリコラのいろいろな処理の順序についてのメモ。

Agricola (Revised Edition) Unofficial CompendiumやBGGから集めた情報、BGAの挙動からの推測を記憶から再構成して書いているため、個別の根拠は割愛する。カードのテキストはBGAから引用。

4つのタイミング

アグリコラのあらゆる処理には、それと関連する4つのタイミングがある。

  1. before
  2. 処理本体
  3. immediately after
  4. after

何かの度に起動するカードには主に "each time immediately after ..." や "each time after ..." 等と書かれたカード、或いは単に "each time ..." と書かれたカードがある。immediately afterafter は区別される異なるタイミングだが、恐らく質的な違いは無い。immediately after は「直後」「すぐ後」等の翻訳揺れがあるので注意。単に "each time ..." と書かれているのは、原則として before を指す(但し、アクションの結果を参照するテキストはこの限りではない?薪切り、セールスマン等)。これらのタイミングに関する記述はしばしば翻訳ミスに遭うので、正確を期すなら原文を参照すべし。

例えば、「木こり」は

Each time you use a wood accumulation space, you get 1 additional <WOOD>.

なので before、「キノコ探し」は

Immediately after each time you use a wood accumulation space, you can exchange 1 <WOOD> for 2 <FOOD>. If you do, place the <WOOD> on the accumulation space.

なので immediately after、「大工の手斧」は

Each time after you use a wood accumulation space, if you then have at least 7 <WOOD> in your supply, you can build exactly 1 stable for 1 <WOOD>.

なので after である。よって、この3枚を出している状態で木材の累積スペースを使用すると、

  1. 「木こり」を誘発
  2. アクションスペースの処理
  3. 「キノコ探し」を誘発
  4. 「大工の手斧」を誘発

という順に処理が進む。

同時処理

アグリコラでは、プレイヤー内で同時に起こる処理はそのプレイヤーの任意の順番で解決することができる。例えば、「キノコ探し」と「木彫」はどちらも immediately after なので、どちらから先に解決しても良い。*1

また、同タイミングに出したカードは即時誘発できる。例えば、「装備品揃え」は immediately after に誘発されるので、これで同じく immediately after に誘発される「木彫」をプレイすれば、即時誘発できる。但し、執筆時点のBGAは「装備品揃え」の誘発タイミングが after なっている不具合があり、「木彫」は誘発できないので注意。

余談だが、BGAは「工作台」も誘発タイミングが immediately after ではなく after になっている不具合がある。

(2026-07-21 追記) BGAの「装備品揃え」「工作台」の発動タイミングが誤っているバグは修正された。

カードのプレイ

アグリコラでは、カードをプレイする時の処理の順序は

  1. カードを場に置く
  2. カードの効果の解決

であり、永続効果はカードを場に置いた時点から有効になる。そのため、効果の解決中にそのカード自身を職業や進歩の数として数えられるし、そのカード自身を誘発できる。*2

例えば、職業0の状態で「受注生産職人」を出すと、前提に職業1の付いた進歩をプレイできる。進歩をプレイする時点で、既に「受注生産職人」が場に置かれているためである。

処理の入れ子

効果解決中に他の効果を誘発した場合、処理は入れ子になる。例えば、「授業」アクションスペースに入って職業を出す場合、

  1. 「授業」アクションスペース使用の before
  2. 「授業」アクションスペースの処理:
    1. 「職業を出す」の before
    2. 「職業を出す」
    3. 「職業を出す」の immediately after
    4. 「職業を出す」の after
  3. 「授業」アクションスペース使用の immediately after
  4. 「授業」アクションスペース使用の after

となる。

具体例

「教師机」+「若い農夫」

「教師机」は「大きな進歩」アクションスペースの before であり、「若い農夫」の小麦を貰う効果も同 before である。よって、「教師机」で「若い農夫」をプレイすると即時小麦が貰える。

「受注生産職人」+「書棚」

「受注生産職人」で「書棚」をプレイすると、

  1. 「職業を出す」の before
  2. 「職業を出す」:
    1. 「受注生産職人」を置く
    2. 「受注生産職人」の効果解決:
      1. 「進歩の建設」の before
      2. 「書棚」を置く
      3. 「進歩の建設」の immediately after
      4. 「進歩の建設」の after
  3. 「職業を出す」の immediately after
  4. 「職業を出す」の after (A)

の順に処理を行う。(A)の時点で「書棚」は既に場に置かれており、これを誘発して野菜を得る。

「工芸指南」+「セールスマン」

「工芸指南」を出している状態で工業を建設し、「セールスマン」をプレイすることを考える。「工芸指南」は進歩建設の after で誘発する。「セールスマン」の誘発タイミングは明示的な記述は無いが、現在のBGAでは after で誘発する。「セールスマン」をプレイするのは進歩建設の after の処理中であり、これは「セールスマン」の誘発タイミングなので「セールスマン」は即時誘発できる。

「馬具屋」も進歩建設の after なので同様に誘発できる。「転売人」は immediately after なのでタイミングは過ぎ去っており、誘発できない。「小作農」は "immediately after paying its cost."(コスト支払いの immediately after) なので多分誘発できない。

「受益者」+「教育手当」

3枚目の職業として「受益者」をプレイし、その効果で「教育手当」と適当な職業カードをこの順でプレイすると、

  1. 「職業を出す」の before
  2. 「職業を出す」:
    1. 「受益者」を場に置く
    2. 「受益者」の効果解決その1:
      1. 「進歩の建設」の before
      2. 「教育手当」をプレイ
      3. 「進歩の建設」の immediately after
      4. 「進歩の建設」の after
    3. 「受益者」の効果解決その2:
      1. 「職業を出す」の before
      2. 適当な職業カードをプレイ
      3. 「職業を出す」の immediately after (A)
      4. 「職業を出す」の after
  3. 「職業を出す」の immediately after (B)
  4. 「職業を出す」の after

となり、(A)と(B)でそれぞれ「教育手当」を誘発する。「教育手当」の何番目の職業というのは、効果解決時の職業の枚数ではなく、それを誘発した職業カードが何番目の職業であるかを参照する。よって、葦と石が貰える。BGAは以前バグっていて石が2つ貰えたが、修正された。

「受益者」+「厩大工」

3枚目の職業として「受益者」をプレイし、その効果で「厩大工」をプレイすると、

  1. 「職業を出す」の before
  2. 「職業を出す」:
    1. 「受益者」を場に置く
    2. 「受益者」の効果解決:
      1. 「職業を出す」の before
      2. 「厩大工」をプレイ
      3. 「職業を出す」の immediately after
      4. 「職業を出す」の after (A)
  3. 「職業を出す」の immediately after
  4. 「職業を出す」の after (B)

となり、「厩大工」の "2、3、5、7番目の職業" の条件により(B)で「厩大工」を誘発し、厩を建設できる。…と思うのだが、BGAでは誘発されない。バグ?

(2026-07-21 追記) BGAにバグ報告したところ、修正され、「厩大工」を誘発できるようになった。

「天秤」

「天秤」を出している状態で、いろいろなカードの効果で他のカードをプレイする状況を考える。

「唐変木」が「天秤」の前(「カードを出す」の immediately after )に誘発されるので、デバッグにおすすめ。

「天秤」+「受注生産職人」

職業1進歩1の状態で、「受注生産職人」から適当な小進歩(置くやつ)を建設する。

  1. 「職業を出す」の before
  2. 「職業を出す」:
    1. 「受注生産職人」を置く
    2. 「受注生産職人」の効果解決:
      1. 「進歩の建設」の before
      2. 適当な小進歩を建設
      3. 「進歩の建設」の immediately after
      4. 「進歩の建設」の after (A)
  3. 「職業を出す」の immediately after
  4. 「職業を出す」の after (B)

(A)と(B)の両方で職業2進歩2となり「天秤」の条件を満たすので、それぞれ「天秤」を誘発し合計4飯が貰える。

「カゴ編み」「窪地のみはり」「製粉業者」「現場監督」も同様である。

「天秤」+「教師机」

職業1進歩1の状態で、「教師机」を使って適当な職業をプレイし適当な小進歩を建設する。

  1. 「大きな進歩」アクションスペースの before
    1. 「教師机」を誘発
      1. 「職業を出す」の before
      2. 適当な職業を出す
      3. 「職業を出す」の immediately after
      4. 「職業を出す」の after (A)
  2. 「大きな進歩」
    1. 「進歩の建設」の before
    2. 適当な小進歩を建設
    3. 「進歩の建設」の immediately after
    4. 「進歩の建設」の after (B)
  3. 「大きな進歩」アクションスペースの immediately after
  4. 「大きな進歩」アクションスペースの after

(A)では職業2進歩1なので、「天秤」の条件を満たさない。(B)では職業2進歩2なので条件を満たし、「天秤」を誘発して2飯が貰える。

「天秤」+「受益者」

職業2進歩3の状態で、「受益者」から適当な職業をプレイし適当な進歩を建設する。

  1. 「職業を出す」の before
  2. 「職業を出す」:
    1. 「受益者」を場に置く
    2. 「受益者」の効果解決その1:
      1. 「職業を出す」の before
      2. 適当な職業をプレイ
      3. 「職業を出す」の immediately after
      4. 「職業を出す」の after (A)
    3. 「受益者」の効果解決その2:
      1. 「進歩の建設」の before
      2. 適当な小進歩を建設
      3. 「進歩の建設」の immediately after
      4. 「進歩の建設」の after (B)
  3. 「職業を出す」の immediately after
  4. 「職業を出す」の after (C)

(A)は職業3進歩3、(C)は職業4進歩4で「天秤」を誘発する。(B)は職業3進歩4で条件を満たさない。よって合計4飯が貰える。

*1:BGAでは、木彫の解決は自動で行われる

*2:昔は、出したカードはその直後には誘発できないという特殊ルールがあったようだが、一貫性がないとして修正され、「労働証明書」や「レンガの刻印打ち」等が出してすぐ誘発できるようになった。

あなごる/Peano Arithmetic

先日出題された http://golf.shinh.org/p.rb?Peano+arithmetic が良問だったので、ブログのリハビリがてらに参戦記を書いてみる。

問題はリンク先参照。

Ruby

初めに、あなごるでRubyと言われているのはRuby1.8.7である。Ruby2.2もRuby2という名前で用意されている。ゴルフ場はRuby1.8時代から運営されていたが、Ruby1.9以降がゴルフ的には別ゲーということで、このようになった。

早速、本題。中置演算子式が与えられるので、まずevalでできないか考える。Sを優先度の高い前置succ演算子として読めればよいので、gsub /S/, "-~" すればよい。-~ は+1、~- は-1する優先度の高い単項演算子として、この手の演算子を持つ言語のゴルフではよく使われる。PythonJavaScriptも同様の解法を使った。

#!ruby -n
puts"S"*eval(gsub /S/,"-~")+"0"

(41B)

行単位の処理には -n-p オプションがしばしば使われる。これらは、コード全体を while gets ... end で囲う効果がある。また、gets には結果を $_ に代入する効果がある。

Kernel#gsub$_ = $_.gsub(...) と同じ。このような暗黙に $_ を対象とするメソッドは、最近のRubyでは -n-p オプションを使用した時のみ定義されるようになった。Ruby2では削除されたものもあるので、要注意。

さて、gsub の代わりに split して join してもよい。せっかくなので、auto split mode(-a オプション)を使う。これで各ループの最初に $F = $_.split が実行される。split の引数のデフォルト値の $;-F オプションで指定できる。

#!ruby -naFS
puts"S"*eval($F*"-~")+"0"

(38B)

sed

足し算は余分な文字を消すだけなので、掛け算をどうするか考える。x * yのとき、xの一文字ずつに繰り返しマッチしてyの全体を継ぎ足していけば掛け算になる。ただし、継ぎ足した分ではない元々のyは余分なので、消さなくてはならない。また、sed正規表現には先読みが無いので、グローバルマッチではなくループで書く必要がある。t コマンドを使えば、指定したラベルに s コマンドでのマッチがあったかどうかで条件ジャンプできる。最後に、余分な文字をまとめて消す。

:
s/S\(0 \* \)\(S*\)/\1\2 \2/
t
s/[0*] S*\|\W//g

(48B)

昔のGNU sedでは、このように空のラベル名が許されていたが、バグとして最新版では修正されてしまった。あなごる環境を手元に用意する場合は、GNU sed 4.2を入れよう。

sedのサブマッチは高価なので、一つのサブマッチでいく方向で試行錯誤した結果、

:
s/S\(0 \* S*\)/\1+ \1/
t
s/* S*//
s/. //g

(43B)

:
s/S0 \*\( S*\)/0 *\1+\1/
t
s/* S*\|. //g

(42B)

等に辿り着き、力尽きた。

対して、tailsさんの解は

/*/y/S/s/
:
s/s\(0 . s*\)\|. s*/\1\U\1/
t

(41B)

掛け算の場合に事前に小文字に置き換えておき、小文字に対して掛け算を実行し、結果の側だけを \U で大文字化する。さらに、マッチしなかったサブマッチは空文字列扱いなのとの合わせ技で、掛け算と余分な文字の消去を一つのコマンドに纏めている。美しい。

Scheme

あなごるのScheme処理系はGaucheである。Gaucheゴルフでは、しばしば regexp-replace-all 系が強い。そこで、sed解の応用で先読みマッチを使って、

(while(print(regexp-replace-all*(read-line)#/S(?=.*\* (S*))/"\\1"#/\* S*|. /""))(flush))

(88B)

とした。(ゴルフ場のGaucheは0.9.3でエラー終了時にstdoutをflushしないので、(flush) する必要がある。read-line の結果を束縛する必要がない場合は port-map より短い。)

readしてevalするアプローチも試したが、そこまで縮まなかった。

(while(let*('string-size,#0=(-'#"~(read)"1)`(read)(r #0#))(format #t"~v,'Sd0
"((eval .`0).,r)""))(flush))

(105B)

Nibbles

どうすれば短くなりそうかずっと考えていてなかなか思い付けなかったが、sedでの戦いを経て最終日に気付いた。入力に応じて積か和をやると考えるからだるいのであって、和の積を取ればいいのだと。

まず、 "*" で分割して項の列にする。各項の 'S' を数えて(isAlphaを数えればOK)、和とする。productをとって、replicateして、0を継ぎ足せば終わり。Scheme風の擬似コードも併記しておく。(擬似コードはmapの引数順がNibblesの逆になっているので注意)

(replicate
 (product
  (map (lambda (term)
         (length
          (filter char-class:alpha? term)))
       (split first-line "*")))
 "S")
0
^`*.%@"*",|$\$a"S"0

(10B)

Nibblesにはnibble(=half byte)単位で書く本来の姿のbinary formと人間が読み書きするためのliterate formがあり、上記コードはliterate formのもの、長さはbinary formのものである。

トップレベルに複数式を書くと、型に応じて演算子が挿入される(Implicit Ops)。最初の式の型が文字列の場合、挿入されるのはappend。appendはcoersionしてくれるようなので、後ろの引数は数値の0でいい。数値の0は2 nibble、文字列では3 nibble、文字でも3 nibble。

また、first-lineだけを使っている場合等は自動で行単位のmapにしてくれる(Auto Map)。そして、評価結果を出力する方式なので、出力のためのコードも必要ない。いい言語だ。

(追記と微修正 2023-12-19) tailsさんによって激縮みしていた。

%@"*" |$\$a.$_$

(7B)

Scheme擬似コードにすると、

(split first-line "*")
(lambda (term)
  (filter char-class:alpha? term))
(lambda (r ss)
  (map (lambda (r1)
         ss)
       r))
first-int

トップレベルに式が続いているので、Implicit Opsでそれぞれmap、foldl1、appendが挿入される。挿入後のコードはこんな感じ。

(append (foldl1 (lambda (r ss)
                  (map (lambda (r1)
                         ss)
                       r))
                (map (lambda (term)
                       (filter char-class:alpha? term))
                     (split first-line "*")))
        first-int)

前述のsed解や後述の俺のHaskell解と似たようなアプローチで、数値を経由するのは甘え、Sの列をmapでSの列にすれば掛け算で、それもimplicitなfoldl1でできると。確かに。foldl1の結果の型が [Char] なのに第二引数の型が [[Char]] で型が合ってないように見えるけど、coercionされるっぽい(+(concat)を挿入しても動いた)。あとfirst-intで0が出てくるのも気付かなかった。すごい。

(追記ここまで)

Haskell

Nibblesと同じ方針で考えたが、split相当がなくてなかなか縮まなかった。当初のコード:

main=interact$(>>=f.foldr g[[]]).lines
g '*'r=[]:r
g c(t:r)=(c:t):r
f l=([1..product$map(\s->sum[1|'S'<-s])l]>>"S")++"0\n"

(122B)

行単位の処理だが、改行の追加が安くできるので m@main=getLine>>=putStrLn...>>m 形式より interact>>= を使った形式の方が短い。教科書にもそう書いてある。length$filter ... より sum[1| ... ] とか、 replicate n x より [1..n]>>[x] とか。

splitを span で代用。'*'がなかった場合は "S" を補って1との積として

m@main=getLine>>=putStr.f.span(/='*')>>m
f(p,"")=f(p,"S")
f(p,q)=([1..g p*g q]>>"S")++"0\n"
g p=sum[1|'S'<-p]

(109B)

数値に直して計算するより、sedでの掛け算みたいなのをリスト内包表記でやった方が短いんじゃないか?

main=interact$(>>=f.span(/='*')).lines
f(p,"")=f(p,"S")
f(p,q)=['S'|'S'<-p,'S'<-q]++"0\n"

(89B)

パターンマッチで場合分けしてるのが高いので、無理矢理まとめると

main=interact$(>>=f.span(/='*')).lines
f(p,q)=['S'|'S'<-p,'S'<-max" S"q]++"0\n"

(79B)

でここまで。時間がなかった割に自分なりにはかなり縮められたが、Haskell golfの強い人が来たらもっと縮みそうな気もする。

Befunge

Sの数は数えるとして、足し算と掛け算はどうするか。Befungeでは丁度 + が足し算、* が掛け算の命令なので、p 命令のソースコード書き換えを利用する。Ruby等のeval解法と似ている。

<+1_v#%2~
^   >~$~52p~41p0
   v>
v#:<-1,"S"_
<vp14">",%+1:~.
v<

(63B)

1行目で1文字ずつ読みながらSを数えている。Sと0の判別は2の剰余でよい。左側の項を読み終えると、2行目に移動する。空白を読み飛ばしつつ、演算子記号を読み込んで、後で実行する場所に書き込んでおく(~52p)。右側の項を読むのに1行目の処理を使い回したいので、その出口から2行目の処理に入るための > を読み込んだ空白で上書きする(~41p)。これで右側の項を読み込んだ後は3行目に移り、書き込んでいた演算子が実行される。4行目でその数だけSを出力、5行目で書き換えた所を元に戻す等の後処理。終了判定にはBefungeは0で剰余を取ると終了するのを利用し、EOF(-1)が入ってくるであろうタイミング(この問題は最後の改行が無いため、改行を読む部分)で 1+% すると終了できる(%+1 の部分、右から左へ実行)。

ゴルフ場のBefunge処理系は . の数値出力の後にスペースを出力しないようにカスタマイズされているので、それも利用する。Befunge-98の方にはこのようなカスタマイズはされていないので注意。

最初はこのように演算子の書き込みと分岐のための書き込みを別々にしていたが、同じ場所に書き込んでしまえば一度にできることに気付く。あとはギュッと詰め込むと

<|%2~
^>~~11p~-
v>">"11p
<-1,"S"_v#:
v,%+1:~.<
v>1+

(51B)

Befungeのこの解法、ゴルフでは珍しく p をコード書き換えに使っているBefungeらしいコードなので気に入っていて、あまり他の解法を試してない。多言語で縮めたいのもあってサクっと切り上げた。(時系列的には先の方にやった)

Befunge-98

大筋はBefungeと同じ。Befunge-98はライブラリ(Fingerprint)がいろいろ使えるため、問題によっては解法が大きく異なってくるが、今回はそういうのは無さそうと判断。

<1wS'~+
v >~~21p~-
 v>'>21p
:<-1,S'_v#
v,~@#,0'<

(48B)

Befunge-98はBefunge-93から多数の変更・追加が入っているが、ゴルフ的に面白いのは w 命令。1命令で大小比較と三分岐を行う。これがあるので、分岐が絡むだけでBefunge-98ゴルフはBefungeからの自明な移植では済まないことが多い。

また、ゼロ除算の結果が0と定義されて終了技が使えなくなったのは注意。ただ、文字入力の ~ のEOFでの挙動も変更されて、-1 を返すのではなくreflectするようになったので、EOFでの分岐はやりやすくなった。