SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|

2023-05-01(Mon) つまらないFactorio

 一番最初のサラ地から遊びたいが製品版を買いたくないなぁ」ということで、5番目の体験版シナリオの既設の施設を全部回収し、サラ地状態にしてから始めることにした。意外と大した手間ではなかった。

  で、改めて、赤色の自動化サイエンスパック、緑色の物流サイエンスパックの量産ラインを完成。机上で苦労して設計したが、狙い通りのラインができた。概ね整然と構築できて満足。ちゃんとつまらないラインは作れるのだな。

  画像の説明

  で、結局、納得してクリアしたところで、遂に製品版を買ってしまう…。


2023-05-02(Tue) さらにつまらないFactorio

  製品版を買ってしまったところで、完全な初期状態から始める。久々の手作業での採掘がダルいが、最初から赤色の自動化サイエンスパック、緑色の物流サイエンスパックの量産を想定し、前回作ったラインを更に改善しつつ再構築。

  そして、灰色の軍事サイエンスパックの量産ラインを完成。設計にはだいぶ苦労したが、割と整然と増築できて満足。

  画像の説明


2023-05-03(Wed) ちょっとつまるFactorio

  水色の化学サイエンスパックの量産ラインを完成……灰色ほど苦労せずに作れたが、整然さは少し落ちた。で、ちょっとつまる。やはりラインに複数種のアイテムを載せるのはダメだなぁ。

  画像の説明


2023-05-05(Fri) 割合生産するFactorio

  丸二日考え込んで、遂に回路が完成した。

  Factorioを始めて、しばらくして気づいたのが「ベルトに複数のアイテムを混在させて運ぶと詰まってしまう」ということだ。それを防ぐには「ベルトに複数のアイテムを混在させない」ことだ。そうすれば「消費が遅く、生産能力が過剰」か「消費が早く、生産能力が不足」のどちらかにはなるものの、詰まってしまうことはなくなり、人手を介入する必要はなくなる。ここ数日はそれを目指して、赤色、緑色、灰色のサイエンスパックの量産ラインを完成させてきた。

  しかし、水色のサイエンスパックは、流体系の資源を使う都合から、ちょっと遠くでアイテムが生産することになった。

  ちょうど近くまで鉄鉱石を運ぶベルトが延びており、その片側が空いていたため、それを活用し、プラスチック棒と硫黄を混在して運ぶことにした。しかし、それをすると、どうしても詰まってしまうのだ。新たにベルトを敷けば済むのだが、どうにかする方法はないものなのかと。

  Factorioはプログラマ向けのゲームだ。プログラマは「こうしたい」と思いついたら最後、実現しないと気がすまない生き物なのである。そしてここから果てしない試行が始まってしまったのである。

  両アイテムが常にチェストに補充される状態にして、何らかの方法で必要な割合で取り出せるようにしたら? と思ったが、チェストの容量は有限なので、生産を抑制しない限りどちらかが余り、いつかはチェストの容量の上限に達してしまう。つまり、必要な量しか、組立機や化学プラントから取り出さないようにしなければならないことになる。今回の目標は、プラスチック棒と硫黄を6:1で取り出し、供給することだ。

  画像の説明

  そうなるとインサータの制御は必須なので、回路を組むのが必須になる。ループするベルトの上に取り出したいアイテムを見本として置き、それをベルトに検知させて、インサータに取り出させてはどうか? と思ったが、これもダメ。ベルト上をアイテムが通過するのには一定時間を要するし「消費が遅く、生産能力が過剰」な場合、ベルト上にアイテムが滞留し、インサータの動作回数が制御できない。パルスで動作をトリガする必要がある。

  やり方を思いついては、試してうまく動作せず、の繰り返し。数十時間を費やしても実現できない。回路も組めるプログラマとして、プライドはズタズタだ。どうやら、変数がなかったり、論理演算にクセがあったり、コンデンサのようなタイマがなかったりと、Factorioの回路を、既存の概念の範疇で捉えようとすることが誤りのように思えてきた。

  そして気づいたのが「赤い*」のAND動作のクセだ。%演算子でループするカウンタを作ったまではいいのだが、それを「赤い*」に食わせると意図しない動作になってしまう。散々アレコレやっているうちにようやく気づいた。「ゼロは偽ではなく、無信号だ」ということ。別の言い方をすると「ゼロは、ANDに偽の信号を入力するのでなく、信号線そのものを外す動作となる」ということだ。

  これに気づいたらだいぶ進捗した。ゼロを使わないようにすればいい。定数回路を使って、ゲタを履かせてやればいい。真偽はn>9で判定するようにし、ループするカウンタは10〜15で常に真、パルスは9と10を発生させて一瞬だけ真を発生させる。そのANDを取れば、10〜15の値がパルス出力される。それをインサータに入力し、動作条件をn>9とすれば6/6動作。n>14とすれば1/6動作となる。そいつを合流させれば6:1でアイテムを流せる。

  パルスの発生にはループ状のベルトの上に載せた石炭と石を使う。アイテムの消費が遅くベルトの先が詰まった場合には、ベルトに検知させてループ状のベルトの回転を止め、パルスの発生自体を遅延させる。これでようやく期待する回路を組むことができた。そこそこシンプルな形で。

  画像の説明

  というわけで、結論は「Factorioの回路を、既存の概念の範疇で捉えようとすることが誤り」であった気がしている。「プログラミング」を始めた、「電子工作」を始めた、と並列に「Factorio」を始めた、を並べられる気がしている。これは「チューリングマシン」レベルといえよう。実に10年に一度くらいの充実感を味わえた。

  すっかりゲームという枠を外れて熱中してしまった。本当に恐ろしい作品だなFactorioは……。


2023-05-10(Wed) 再帰で悩みまくる

  ワケあって、再帰プログラムを書く必要が生じたのだが、ちっとも目的の結果が出せない。そもそも、再帰がうまく書けてない。あまりの書けなさに自らショックを受けるほどに書けない。

  何度か書いたことはあるのだが、ちょっと難しいのは確か。こうなったら、回り道ではあるが、よく知っている構造についてまず書いてみて、それをベースにすればいいのではないか。と思い、ファイル/ディレクトリ構造を表示する、プログラムを書いてみた。

#!/usr/bin/ruby
 
def find(path)
    entry = {
        :type   => false,
        :name   => path.last,
    }
    unless(FileTest.directory?(path.join('/')))
        entry[:type] = 'file'
    else
        entry[:type] = 'dir'
        entry[:entries] = []
        Dir.open(path.join('/')).each {|e|
            e =~ /^\./ and next
            entry[:entries] << find(path + [e])                 # 再帰
        }
    end
    return(entry)
end
 
def show(entry, n = 0)
    puts('%s%4s: %s' % ["\t" * n, entry[:type], entry[:name]])
    if(entry[:type] == 'dir')
        entry[:entries].each {|entry1|
            show(entry1, n + 1)                                 # 再帰
        }
    end
end
 
entry = find(['fileA'])
show(entry)
 
puts
 
entry = find(['dirB'])
show(entry)
$ ./recursive 
file: fileA
 
 dir: dirB
	file: fileC
	 dir: dirD
		file: fileE
		file: fileF
	file: fileG

  これが大当たりで、これをベースにしたら、あっという間に目的のプログラムに近いものが書けてしまった。なんつうか、齢を取るってこういうことだよなぁ。


2023-05-12(Fri) ライン設計の計算機でFactorio

  なんで再帰プログラムが書きたかったのかというと、なんのこたーない、原因はFactorioなのであった。

  アイテムがつまらないラインを作れるようになると、次に浮上してくる問題がラインの稼働率が上がらないという現象だ。つまり、アイテムの生産速度を合わせないとボトルネックが発生してしまう。早速、手計算でやり始めたのだが、頭がコンガラがるし面倒くさすぎる。これは計算機にやらせるべき課題以外の何物でもない。

  つうわけで、目的のアイテムを材料まで分解しながら、速度を合わせるためには「組立機が何台設置したらいいのか」を計算させる計算機を作ってみた。この「材料まで分解していく」処理で再帰の手法を用いると、プログラムがスッキリと書けるのである。先日書いたプログラムがベースだが、面白いくらいにほとんどそのまんまである。

#!/usr/bin/ruby
 
#	Usage
#		$ ./fa_calc 歯車
#		$ ./fa_calc 赤パック '1.0/5'
 
@items = {
	'鉄板' => {
		:type => :mate,
	},
 
	'銅板' => {
		:type => :mate,
	},
 
	'歯車' => {
		:type => :prod,
		:num => 1,
		:time => 0.5,
		:parts => [
			{	:name	=> '鉄板',			:num	=>  2, },
		],
	},
 
	'赤パック' => {
		:type => :prod,
		:time => 5,
		:parts => [
			{	:name	=> '銅板',			:num	=>  1, },
			{	:name	=> '歯車',			:num	=>  1, },
		],
	},
}
 
def disasm(part_name, num = 1)
	prod = @items[part_name]
	parts = {
		:name	=> part_name,
		:num	=> (num = num.to_f),
	}
	if(prod[:type] == :mate)
		parts[:type] = :mate
	else
		parts[:type] = :prod
		parts[:list] = []
		prod_num = (prod[:num] || 1).to_f
		parts[:macs] = prod[:time] * num / prod_num
		prod[:parts].each {|part|
			parts[:list] << disasm(part[:name], part[:num] * num / prod_num)
		}
	end
	return(parts)
end
 
def show(parts, n = 0)
	if(parts[:type] == :mate)
		puts('%s%s: %s %5.2f' % ["\t" * n, parts[:type], parts[:name], parts[:num]])
	else
		puts('%s%s: %s %5.2f macs:%5.2f' % ["\t" * n, parts[:type], parts[:name], parts[:num], parts[:macs]])
		parts[:list].each {|part|
			show(part, n + 1)
		}
	end
end
 
part_name = ARGV[0]
num = eval(ARGV[1] || '1').to_f
puts("[%s x %5.2f]" % [part_name, num])
parts = disasm(part_name, num)
show(parts)
 
__END__

  結果はこんな感じ。

$ ./fa_calc 歯車
[歯車 x  1.00]
prod: 歯車  1.00 macs: 0.50
	mate: 鉄板  2.00
$ ./fa_calc 銅線
[銅線 x  1.00]
prod: 銅線  1.00 macs: 0.25
	mate: 銅板  0.50

  歯車を作るには、鉄板2枚が必要。銅線を作るには、銅板0.5枚が必要。ここで着目すべきは「macs」の値で、これが「組立機が何台設置したらいいのか」を示している。

$ ./fa_calc 歯車 2
[歯車 x  2.00]
prod: 歯車  2.00 macs: 1.00
	mate: 鉄板  4.00
$ ./fa_calc 銅線 4
[銅線 x  4.00]
prod: 銅線  4.00 macs: 1.00
	mate: 銅板  2.00

  歯車を2個/秒で作るには、組立機1台が必要。銅線を4個/秒で作るには、組立機1台が必要。歯車は0.5秒で1つ、銅線は0.5秒で2つ、という生産時間が加味された結果となる。

$ ./fa_calc 赤パック
[赤パック x  1.00]
prod: 赤パック  1.00 macs: 5.00
	mate: 銅板  1.00
	prod: 歯車  1.00 macs: 0.50
		mate: 鉄板  2.00

  そんなのは手計算できるが、赤パックになると既にややこしくなってくる。赤パックを1個/秒で作るには、組立機5台が必要だが、歯車が1個/秒で必要になるので、組立機0.5台が必要、という結果になる。

$ ./fa_calc 赤パック '1.0/5'
[赤パック x  0.20]
prod: 赤パック  0.20 macs: 1.00
	mate: 銅板  0.20
	prod: 歯車  0.20 macs: 0.10
		mate: 鉄板  0.40

  さすがに組立機5台で赤パックを1個/秒で作る必要はない、5秒に1個でいいよ、ということであればこうすればいい。

$ ./fa_calc 緑基板
[緑基板 x  1.00]
prod: 緑基板  1.00 macs: 0.50
	mate: 鉄板  1.00
	prod: 銅線  3.00 macs: 0.75
		mate: 銅板  1.50
$ ./fa_calc 緑基板 2
[緑基板 x  2.00]
prod: 緑基板  2.00 macs: 1.00
	mate: 鉄板  2.00
	prod: 銅線  6.00 macs: 1.50
		mate: 銅板  3.00

  さらに進む。緑基板を1個/秒で作るには、組立機0.5台、銅線の組立機0.75台が必要だが、材料として多用される緑基板はもっと早く作りたい。2個/秒で作りたいとなると、必要な銅線の組立機は1.5台となり、1台では足りず、銅線の作成がボトルネックになるという状況が明確に示される。

$ ./fa_calc 水パック '2.0/24'
[水パック x  0.08]
prod: 水パック  0.08 macs: 2.00
	prod: エンジン  0.17 macs: 1.67
		mate: 鋼材  0.17
		prod: 歯車  0.17 macs: 0.08
			mate: 鉄板  0.33
		prod: パイプ  0.33 macs: 0.17
			mate: 鉄板  0.33
	prod: 赤基板  0.25 macs: 1.50
		prod: 緑基板  0.50 macs: 0.25
			mate: 鉄板  0.50
			prod: 銅線  1.50 macs: 0.38
				mate: 銅板  0.75
		prod: プラ棒  0.50 macs: 0.25
			mate: 石油ガス  5.00
			mate: 石炭  0.25
		prod: 銅線  1.00 macs: 0.25
			mate: 銅板  0.50
	prod: 硫黄  0.08 macs: 0.04
		mate: 水  1.25
		mate: 石油ガス  1.25

  そして当面の課題だった「水パック」を計算してみる。着目すべきは、各項目の「macs」だ。エンジンで1.67、赤基板で1.5が出ている。自分が作ったラインでは、赤基板の作成がボトルネックになって水パックの作成速度が出ないのだが、エンジンは2台で作っているのに、赤基板を1台で作っている状況だ。まさに状況が明確に示されている。そうなんだよ、だから欲しかったんだよ、こんな計算機がさ。

  増設はスペース的にキビしいので、線路をどうにかしないといかんが、赤基板の工作機は2台あれば十分ということも分かる。そうすると緑基板が不足しないか気になるが、赤基板1.5台に対しての必要数は0.25台と出ているので、現状の兼用でも足りるかな、と判断できる。

$ ./fa_calc 水パック | sed 's/^\s*//' | sort 
[水パック x  1.00]
mate: 鋼材  2.00
mate: 水 15.00
mate: 石炭  3.00
mate: 石油ガス 15.00
mate: 石油ガス 60.00
mate: 鉄板  4.00
mate: 鉄板  4.00
mate: 鉄板  6.00
mate: 銅板  6.00
mate: 銅板  9.00
prod: エンジン  2.00 macs:20.00
prod: パイプ  4.00 macs: 2.00
prod: プラ棒  6.00 macs: 3.00
prod: 歯車  2.00 macs: 1.00
prod: 水パック  1.00 macs:24.00
prod: 赤基板  3.00 macs:18.00
prod: 銅線 12.00 macs: 3.00
prod: 銅線 18.00 macs: 4.50
prod: 硫黄  1.00 macs: 0.50
prod: 緑基板  6.00 macs: 3.00

  ちなみに、ゲーム中で示されるトータルコストは、上記のようにすることで本計算機でも類似の値を算出できる。ゲーム中で示される水パックのトータルコストは「鉄板6、銅板15、プラ棒6、硫黄1、エンジン2」だ。銅板は6と9として別計上されているが合計すれば15で合っている。鉄板は合計が14と違っているが、これはエンジンに使われている8が入ってないせいで、それを抜けば6で合っている。要するに、どこまで分解するかの違いだ。本プログラムならルールの記述により、鉄板を鉄鉱石と石炭まで分解することもできる。

  あまりに明確に結果が出てしまうので「ズルい」という感覚を受ける人も出てこようが、それはFactorioの本質をわかっていないと言えよう。こういう工夫をするところ、こういうプログラムを書くところからがFactorioなのだ。このプログラムを書いていた時間は、自分的にはFactorioで遊んでいた時間の一部なのである。

  さらにいえば、如何に整然とした、手のかからないラインを組み上げるかがFactorioの楽しみだと思っているが、現状それには遠く及ばない状況である。しかし、整然としたラインを組み上げることが楽しみだと思うからこそ、再帰を使ってプログラムを組む必要があったのだ。再帰を使わずにプログラムを組むこともできようし、むしろその方が読みやすいかもしれないが、それは自分にとって整然としたコードではないのだ。

  しかし、こんなに次々と新しい体験をさせてくれるゲームは始めてだな。ちょうどウランの採掘、精製を始めたところだが、使えるウランを取り出すのが如何に大変か、劣化ウランて、そういう意味だったのか、と気づかされたし、それより前には、フリップフロップを再習得させられたし、鉄道信号理論で閉塞区間についても学習させられたし……これから、原発の運営の難しさについても体験させてもらえるのだろうなぁ。

  スクリプトを置いておく。


2023-05-20(Sat) コンテナのPVのバックアップを考える

  ここんとこ、毎晩Factorioに夢中なのだが、イマイチ効率的に研究が進められていないせいか、バイターの攻撃が激しくなってきて、それへの対応に忙殺され、製造ラインを工夫している時間が作れない。

  しかしこれ、すごく普遍的な教訓をくれている気がする。というのも、そもそも一般にコンピュータシステムは、特定の作業を自動化するものであるが、自動化のレベルが低いほど、手作業でメンテする必要が生じてしまうものなのである。

  よく、システムの作りがイマイチな場合に「運用でカバー」などという口当たりのいいコトバでの対策が取られるが、それはつまりシステム屋としての敗北なのである。言い訳無用で恥なのである。「こんなこともあろうかと」の対極なのである。

  話をFactorioに戻すと、製造ラインを工夫している時間が作れない原因は、バイターの攻撃への対応の自動化のレベルが低いことであり、それはつまりFactorioプレイヤとしての敗北なのであり恥なのである。

  そんな気づきを得た矢先、自分が職場で運用している自作のチャットシステムがトラブってしまった。結構、ガチで使われているシステムなので、めちゃくちゃ焦る。原因はディスクフルでGDBMが壊れたこと。GDBMを直そうとしたがダメ。しかし、直近の発言を表示するための履歴DBだったので、消しても影響なく単に消したら復旧できた。それでも原因の調査、究明と対策で1時間くらいを要してしまった。あまり長時間止まるとTeams以下の存在になってしまうから沽券に関わる。

  そういうことなのだ。ちゃんと自動化しておけば、イザという時に焦らなくて済んだのだ。だから、これを機にバックアップ機構を作るべきだ……ということで作った。チャットシステム専用ではなく、コンテナのPVに対する汎用的な仕組みだ。リモートにrsyncするのが基本だが、ローカルにもできるようになっている。ちゃんと差分バックアップである。

  そもそも、ロクにrsyncを使ったことがなかったし、シェルスクリプトも苦手意識があったが、今回の開発を通じ、その両方に少し詳しくなれた気がする。しかし、それにしても手間取ったなぁ。昔に比べて思考力が落ちているせいなのか、自らへの期待値が上がっているせいなのか、それ以外なのか……よくわからないが、こういう開発は楽しくて仕方ないものの、ここまで手間取るかなぁ、という印象を受けた。

diff --git a/docker-compose.yml b/docker-compose.yml
+#           * Local Backup
+#           BACKUP_PATH: pv/backup
+#           BACKUP_EX: --exclude=backup
+# バックアップ先の領域として
+# mkdir pv/backup; chown 1000:1000 pv/backup しておくこと
+#           * Remote Backup
+#           BACKUP_HOST: user@rsync_ssh_host
+#           BACKUP_PATH: backup
+#           BACKUP_EX: --exclude=id_rsa*
+# バックアップ先への ssh アクセスのため
+# ssh-keygen -f pv/id_rsa; chown 1000:1000 pv/id_rsa しておくこと
+#           CYCLE1: 3600
+#           SCHED1: 300
diff --git a/Dockerfile b/Dockerfile
+#      if [ -v BACKUP_PATH ]; then
+#          if [ `date +%s` -ge $target1 ]; then
+#              ((target1 += CYCLE1))
+#              echo "`date`: Job easy cron 1 started."
+#              stamp=`date +%Y%m%d_%H%M`
+#              ssh_opt='-o StrictHostKeyChecking=no -o PasswordAuthentication=no'
+#              if [ -v BACKUP_HOST ]; then
+#                  coron=:
+#                  ssh_opr="ssh $ssh_opt $BACKUP_HOST"
+#              fi
+#              last_backup=`$ssh_opr ls $BACKUP_PATH | tail -1`
+#              backup_ex0='--exclude=hyperestraier'
+#              if [ -z "$last_backup" ]; then
+#                  gen=0
+#              else
+#                  gen=$((`$ssh_opr cat $BACKUP_PATH/$last_backup/.gen` + 1))
+#                  link_dest="--link-dest=../$last_backup"
+#              fi
+#              rsync -av --delete $backup_ex0 $BACKUP_EX -e "ssh $ssh_opt" pv $BACKUP_HOST$coron$BACKUP_PATH/$stamp $link_dest
+#              echo $gen > .gen; scp $ssh_opt .gen $BACKUP_HOST$coron$BACKUP_PATH/$stamp
+#          fi
+#      fi

  さて、差分バックアップができたところで、さらにやりたいことがあるのだが、それはまた後日。


2023-05-23(Tue) 名状しがたいバックアップ方法を開発

  こういうことはないだろうか?

  ロールプレイングゲームなど、プレイ途中、数十〜数百回ものセーブを繰り返しながら進んでいくゲームがあるとして「直前の記録をいくつか残したい」と思ったことは?

  最近のゲームでは少ないだろうが、例えば、売ってしまってはいけないアイテムを売ってしまい、その状態でセーブしたら、その後ゲームを進められなくなってしまう、などという状況がある。しかし、そういう場合、セーブエリア1,2, 3を順に使っていれば回避できる可能性がある。直前のセーブデータより、もうふたつ古い世代のセーブデータから始められるからだ。これは簡単に実行できる。

  では、だいぶ前のイベントをもう一度見たくなった時のために「現在までの中間地点付近の記録をまばらに残したい」と思ったことは?

  これを「何となく、ではなく」実現するのは難しい。例えば10章まで進んだ時点で5章のデータを残しておくことはできるが、20章まで進んだ時点では10章付近のデータが残っているべきなのだ。セーブエリアの数に制限がなければ考える必要はないが、6個とかに限定されるとやりくりする必要が生じる。

  この問題は、ゲームのセーブデータに限らない。例えば、自分はヴォーカルの練習の記録を残してあるが、もう始めて5年くらいになるので、最近、記録容量が溢れそうになっている。後で成長の記録を確認したいと思えば、古いものや、2年半くらい前のものは残しておきたい。しかし、具体的にはどのように消していったらいいのか?

  そしてPCのバックアップである。例えば、6回分の保持が可能な容量があるとして、直近の6世代を残すのはいい方法なのだろうか? それだと、1週間前にオペレーションミスしたことに気づいたら、あきらめるほかない。直前と、もうふたつ古い世代は残しておくとしても、残りの3回分は、もう少し他にやりようがあるのではないか?

  つうわけで作ってみた。結論からいうと、完全に気に入った結果にはなっていないが、とりあえず「直前3世代+まばら3世代」での結果については、そこそこ使える動きになっていると思う。

  画像の説明

  動きを図化するとこんな感じだ。横軸がバックアップ世代、縦軸が時間の経過を意味している。概ねどの時間に着目しても、その時点の中間点と、中間点以降での中間点あたりの世代がまばらに残される動きになっている。フラクタル的にも見えるな。

  クラス化してあるので、以下のように使う。stored=で最新の世代数を渡し、should_delete?で既存の世代を削除するべきかどうか訊き、肯なら削除すればいい。

backups = {}; last_gen = -1
Dir.glob('xxx_backups/*').each {|path|
    gen = open('%s/.gen' % path).read.to_i
    backups[gen] = path
    gen > last_gen and last_gen = gen
}
 
ebackup = EspBackup.new
ebackup.stored = last_gen
backups.each {|gen, path|
    ebackup.should_delete?(gen) and p('rm -rf %s' % path)
}

  クラスの内容は以下。

class EspBackup
 
    def initialize(sgens = 3, lgens = sgens)
        @sgens = sgens; @lgens = lgens
        @bhs = []               # 3: [ '1110', '1100', '1000', '0100', '0110', '0111', '0000' ]
        sgens.times {|i0|
            i = sgens - i0      # sgens..1
            @bhs << ('1' * i + '0' * (i0 + 1))
        }
        sgens.times {|i0|
            i = i0 + 1          # 1..sgens
            @bhs << ('0' + '1' * i + '0' * (sgens - i))
        }
        @bhs << '0' * (sgens + 1)
    end
 
    def stored=(gen)
        @rgens = {}
        @lgens.times {
            @rgens[gen] = true
            (gen -= 1) < 0 and return
        }
        bw = ('%b' % gen).size
        @bhs.each {|bh|
            (it = (bh + '0' * bw)[0, bw].to_i(2)) <= gen and @rgens[it] = true
            @rgens.size == @sgens + @lgens and return
        }
    end
 
    def rgens
        @rgens.keys.sort
    end
 
    def should_delete?(gen)
        !@rgens[gen]
    end
end

  とりあえず、それなりの結果にはなっているので実用に供しようと思うが、完全に気に入った結果にはなっていないのだよな。特に保持数を上げると不自然な結果になってしまう。大きな考え方はいいと思うのだが、フラクタルがヒントなのかなぁ。


2023-05-26(Fri) Factorioで打ち上げ

  一応、打ち上げ。一応、クリアだというのに、まったく終わった気がしないゲームというのは初めてだなw。

  画像の説明

  88時間半だった。ぜんぜん納得できてない。