Prolog の文法でなろう小説っぽいゲームブック風の何かを書こうとした残骸
2021-04-20
安価・お題で短編小説を書こう!9
https://mevius.5ch.net/test/read.cgi/bookall/1601823106/
>>620
締め切りに間に合いませんでしたので、供養枠での投稿です
使用お題→『画鋲』『奴隷』『戦闘員』『ゲームブック』
【Prolog の文法でなろう小説っぽいゲームブック風の何かを書こうとした残骸】
小説として仕上げたかったのですが、これは小説ではないです。
Prolog のプログラムとして書かれた本編、その日本語訳、最後に説明の、三部構成でお送りします。
--ここで区切ります
これはなんですか: Prolog の文法で書かれたなろう小説っぽいゲームブック風の何かです
異世界転移した(主人公).
% 種類(名前, 費用/売り値, 強さ, 落とす武器(敵のみ)).
人(X, Y, Z) :- 男(X, Y, Z).
人(X, Y, Z) :- 女(X, Y, Z).
男(主人公, 0, 1).
男(若い戦闘員, 10, 1).
男(老練な戦闘員, 30, 3).
男(最強の戦闘員, 50, 5).
女(猫耳のじゃロリメイド, 100, 1).
女(盾ロール悪役公爵家令嬢, 200, 1).
女(ぽわぽわお姉様シスター, 300, 1).
女(ツンデレハイエルフ姫, 400, 1).
敵(若い盗賊, 10, 1, 画鋲).
敵(老練な盗賊, 30, 3, 木刀).
敵(最強の盗賊, 50, 5, 銅剣).
敵(ゴブ, 60, 6, 石).
敵(スラ, 70, 7, 石).
敵(ミノ, 80, 8, 石).
敵(デビ, 90, 9, 妖刀).
敵(ボス, 100, 10, 魔剣).
武器(石, 0, 0).
武器(画鋲, 0, 1).
武器(木刀, 0, 2).
武器(銅剣, 0, 3).
武器(妖刀, 0, 4).
武器(魔剣, 0, 5).
武器(聖剣, 1000, 10).
食べ物(オムライス, 5).
収入(バイト代, 1).
支出(寄付金, 2).
支出(税金, 10).
% 場所(名前, 男/女/敵/武器).
場所(森の中, 若い盗賊).
場所(森の奥, 老練な盗賊).
場所(盗賊の隠れ家, 最強の盗賊).
場所(街, 石).
場所(奴隷商会一階, 若い戦闘員).
場所(奴隷商会二階, 老練な戦闘員).
場所(奴隷商会三階, 最強の戦闘員).
場所(メイド喫茶, 猫耳のじゃロリメイド).
場所(公爵家, 盾ロール悪役公爵家令嬢).
場所(寂れた教会, ぽわぽわお姉様シスター).
場所(エルフの城, ツンデレハイエルフ姫).
場所(ダンジョン一階, ゴブ).
場所(ダンジョン二階, スラ).
場所(ダンジョン三階, ミノ).
場所(ダンジョン四階, デビ).
場所(ダンジョン五階, ボス).
場所(城の武器庫, 聖剣).
% 移動できる(現在地, 移動先).
移動できる(X, Y) :- 隣接する(X, Y).
移動できる(X, Y) :- 隣接する(Y, X).
隣接する(森の中, 森の奥).
隣接する(森の中, 街).
隣接する(森の中, ダンジョン一階).
隣接する(森の奥, 盗賊の隠れ家).
隣接する(街, 奴隷商会一階).
隣接する(街, メイド喫茶).
隣接する(街, 公爵家).
隣接する(街, 寂れた教会).
隣接する(街, エルフの城).
隣接する(奴隷商会一階, 奴隷商会二階).
隣接する(奴隷商会二階, 奴隷商会三階).
隣接する(ダンジョン一階, ダンジョン二階).
隣接する(ダンジョン二階, ダンジョン三階).
隣接する(ダンジョン三階, ダンジョン四階).
隣接する(ダンジョン四階, ダンジョン五階).
隣接する(エルフの城, 城の武器庫).
% チームの(メンバーの)強さの合計(チームの名簿, 強さの合計).
sum_team_power([], 0).
sum_team_power([H | T], Sum) :- 人(H, _, P), sum_team_power(T, TP), Sum is P + TP.
現在地(状態(Place, _, _, _), Place).
所持金に足す(OldState, Income, NewState) :-
状態(Place, OldWallet, Weapon, Team) = OldState,
NewWallet is OldWallet + Income,
NewState = 状態(Place, NewWallet, Weapon, Team).
所持金から引く(OldState, Outgo, NewState) :-
状態(Place, OldWallet, Weapon, Team) = OldState,
OldWallet >= Outgo,
NewWallet is OldWallet - Outgo,
NewState = 状態(Place, NewWallet, Weapon, Team).
:- discontiguous(行動する/3).
雇っていない(状態(Place, _, _, Team), Person, Cost) :-
場所(Place, Person),
人(Person, Cost, _),
not(member(Person, Team)). % non-ISO, deprecated
チームの名簿に加える(OldState, NewPerson, NewState) :-
状態(Place, Wallet, Weapon, OldTeam) = OldState,
append(OldTeam, [NewPerson], NewTeam),
NewState = 状態(Place, Wallet, Weapon, NewTeam).
行動する(OldState, NewState, S) :-
雇っていない(OldState, Person, Cost),
所持金から引く(OldState, Cost, State1),
チームの名簿に加える(State1, Person, NewState),
atom_concat(Person, 'を雇う', S).
倒せる(状態(Place, _, Weapon, Team), Enemy, Price, NewWeapon) :-
場所(Place, Enemy),
敵(Enemy, Price, EP, NewWeapon),
武器(Weapon, _, WP),
sum_team_power(Team, TP),
WP + TP >= EP.
より良い武器(W1, W2, BetterWeapon) :-
武器(W1, _, WP1),
武器(W2, _, WP2),
WP1 > WP2 -> BetterWeapon = W1; BetterWeapon = W2.
より良い武器に変更する(OldState, NewWeapon, NewState) :-
状態(Place, Wallet, OldWeapon, Team) = OldState,
より良い武器(OldWeapon, NewWeapon, BetterWeapon),
NewState = 状態(Place, Wallet, BetterWeapon, Team).
行動する(OldState, NewState, S) :-
倒せる(OldState, Enemy, Price, NewWeapon),
所持金に足す(OldState, Price, State1),
より良い武器に変更する(State1, NewWeapon, NewState),
atom_concat(Enemy, 'を倒す', S).
より良い武器がある(状態(Place, _, OldWeapon, _), NewWeapon, Cost) :-
場所(Place, NewWeapon),
武器(NewWeapon, Cost, NewWP),
武器(OldWeapon, _, OldWP),
NewWP > OldWP.
武器を変更する(OldState, NewWeapon, NewState) :-
状態(Place, Wallet, _, Team) = OldState,
NewState = 状態(Place, Wallet, NewWeapon, Team).
行動する(OldState, NewState, S) :-
より良い武器がある(OldState, NewWeapon, Cost),
所持金から引く(OldState, Cost, State1),
武器を変更する(State1, NewWeapon, NewState),
atom_concat(NewWeapon, 'を買う', S).
行動する(OldState, NewState, S) :-
現在地(OldState, メイド喫茶),
食べ物(Food, Cost),
所持金から引く(OldState, Cost, NewState),
atom_concat(Food, 'を食べる', S).
行動する(OldState, NewState, 'バイトする') :-
現在地(OldState, 公爵家),
収入(バイト代, Income),
所持金に足す(OldState, Income, NewState).
行動する(OldState, NewState, '寄付する') :-
現在地(OldState, 寂れた教会),
支出(寄付金, Outgo),
所持金から引く(OldState, Outgo, NewState).
行動する(OldState, NewState, '納税する') :-
現在地(OldState, エルフの城),
支出(税金, Outgo),
所持金から引く(OldState, Outgo, NewState).
行動する(OldState, NewState, S) :-
状態(OldPlace, Wallet, Weapon, Team) = OldState,
移動できる(OldPlace, NewPlace),
NewState = 状態(NewPlace, Wallet, Weapon, Team),
atom_concat(NewPlace, 'へ移動する', S).
start :-
異世界転移した(主人公),
game_loop(状態(森の中, 0, 石, [主人公])).
game_loop(State) :-
write(State), nl,
bagof(action(New, Str), 行動する(State, New, Str), Actions),
write_menu(Actions),
read(X),
(integer(X), nth1(X, Actions, action(NewState, _)); NewState = State),
game_loop(NewState).
write_menu(Actions) :- write_nth_menu(1, Actions).
write_nth_menu(_, []).
write_nth_menu(Index, [action(_, S) | T]) :-
write(Index-S), nl,
Index1 is Index + 1,
write_nth_menu(Index1, T).
--ここで区切ります
これはなんですか: 上記の何かの日本語訳です
主人公は異世界転移した。
男であるなら、人である。
女であるなら、人である。
主人公は、費用 0、強さ 1 の男である。
若い戦闘員は、費用 10、強さ 1 の男である。
老練な戦闘員は、費用 30、強さ 3 の男である。
最強の戦闘員は、費用 50、強さ 5 の男である。
猫耳のじゃロリメイドは、費用 100、強さ 1 の女である。
盾ロール悪役公爵家令嬢は、費用 200、強さ 1 の女である。
ぽわぽわお姉様シスターは、費用 300、強さ 1 の女である。
ツンデレハイエルフ姫は、費用 400、強さ 1 の女である。
若い盗賊は、売り値 10、強さ 1 の、画鋲を落とす敵である。
老練な盗賊は、売り値 30、強さ 3 の、木刀を落とす敵である。
最強の盗賊は、売り値 50、強さ 5 の、銅剣を落とす敵である。
ゴブは、売り値 60、強さ 6 の、石を落とす敵である。
スラは、売り値 70、強さ 7 の、石を落とす敵である。
ミノは、売り値 80、強さ 8 の、石を落とす敵である。
デビは、売り値 90、強さ 9 の、妖刀を落とす敵である。
ボスは、売り値 100、強さ 10 の、魔剣を落とす敵である。
石は、費用 0、強さ 0 の武器である。
画鋲は、費用 0、強さ 1 の武器である。
木刀は、費用 0、強さ 2 の武器である。
銅剣は、費用 0、強さ 3 の武器である。
妖刀は、費用 0、強さ 4 の武器である。
魔剣は、費用 0、強さ 5 の武器である。
聖剣は、費用 1000、強さ 10 の武器である。
オムライスは、費用 5 の食べ物である。
バイト代は、売り値 1 の収入である。
寄付金は、費用 2 の支出である。
税金は、費用 10 の支出である。
森の中は、若い盗賊のいる場所である。
森の奥は、老練な盗賊のいる場所である。
盗賊の隠れ家は、最強の盗賊のいる場所である。
街は、石のある場所である。
奴隷商会一階は、若い戦闘員のいる場所である。
奴隷商会二階は、老練な戦闘員のいる場所である。
奴隷商会三階は、最強の戦闘員のいる場所である。
メイド喫茶は、猫耳のじゃロリメイドのいる場所である。
公爵家は、盾ロール悪役公爵家令嬢のいる場所である。
寂れた教会は、ぽわぽわお姉様シスターのいる場所である。
エルフの城は、ツンデレハイエルフ姫のいる場所である。
ダンジョン一階は、ゴブのいる場所である。
ダンジョン二階は、スラのいる場所である。
ダンジョン三階は、ミノのいる場所である。
ダンジョン四階は、デビのいる場所である。
ダンジョン五階は、ボスのいる場所である。
城の武器庫は、聖剣のある場所である。
X が Y に隣接するなら、X から Y に移動できる。
Y が X に隣接するなら、X から Y に移動できる。
森の中は、森の奥に隣接する。
森の中は、街に隣接する。
森の中は、ダンジョン一階に隣接する。
森の奥は、盗賊の隠れ家に隣接する。
街は、奴隷商会一階に隣接する。
街は、メイド喫茶に隣接する。
街は、公爵家に隣接する。
街は、寂れた教会に隣接する。
街は、エルフの城に隣接する。
奴隷商会一階は、奴隷商会二階に隣接する。
奴隷商会二階は、奴隷商会三階に隣接する。
ダンジョン一階は、ダンジョン二階に隣接する。
ダンジョン二階は、ダンジョン三階に隣接する。
ダンジョン三階は、ダンジョン四階に隣接する。
ダンジョン四階は、ダンジョン五階に隣接する。
エルフの城は、城の武器庫に隣接する。
誰もいないチームの強さの合計は、0 である。
チームの名簿の先頭に H という強さが P の人がいて、H を除くチームの強さの合計が TP なら、チームの強さの合計は、P + TP である。
(現在の状態の)現在地(Place)が、現在地である。
現在の所持金に収入(Income)を足すことが、所持金に足すことである。
現在の所持金が支出(Outgo)以上であり、その所持金から支出を引くことが、所持金から引くことである。
現在地にいる人が現在のチームの名簿に含まれていないことが、その人を雇っていないことである。
現在のチームの名簿に、ある人(NewPerson)を加えること(append)が、その人をチームの名簿に加えることである。
現在地にいる人を雇っておらず、その人の費用を所持金から引いて、その人をチームの名簿に加えることが、行動する(雇う)ことである。
現在地にいる敵について、武器の強さと、チームの強さの合計を足して、それがその敵の強さ以上なら、その敵を倒せる。
強さの大きい方が、より良い武器である。
現在持っている武器と、ある武器(NewWeapon)を比べて、現在持っている武器をより良い武器(BetterWeapon)に変更することが、より良い武器に変更することである。
現在地にいる敵を倒せて、その敵の売り値を所持金に足して、現在持っている武器と敵が落とす武器を比べて、より良い武器に変更することが、行動する(倒す)ことである。
現在地にある武器について、その武器の強さが現在持っている武器の強さよりも大きいことが、より良い武器があることである。
現在持っている武器を、ある武器(NewWeapon)に変更することが、武器を変更することである。
現在地により良い武器があって、その武器の費用を所持金から引いて、その武器に武器を変更することが、行動する(買う)ことである。
メイド喫茶が現在地で、ある食べ物の費用を所持金から引くことが、行動する(食べる)ことである。
公爵家が現在地で、バイト代の売り値を所持金に足すことが、行動する(バイトする)ことである。
寂れた教会が現在地で、寄付金の費用を所持金から引くことが、行動する(寄付する)ことである。
エルフの城が現在地で、税金の費用を所持金から引くことが、行動する(納税する)ことである。
現在地からある場所に移動できて、現在地をその場所に変更することが、行動する(移動する)ことである。
主人公が異世界転移して、現在地が森の中で、所持金が 0 で、現在持っている武器が石で、チームの名簿に主人公がいる状態で、game_loop を開始することが、start である。
game_loop/1 以降の日本語訳は省略します。行動することで現在の状態が変化して、ゲームが進行します。
--ここで区切ります
これはなんですか: 上記の何かの説明です
お題スレで『ゲームブック』と言えば、スレ5→143 が思い浮かびます。
パクるわけにも行かず、他に何かできないかと思って書いたのが、上記の名状しがたい何かです。
主人公はあなたです。
あなたは異世界転移しました。
あなたは、今、森の中にいて、所持金はゼロで、手に石を握っており、目の前には若い盗賊がいます。
この盗賊を倒してお金を稼ぐか、彼を無視して街へ移動するか、それはあなた次第です。
ただし、前述の通り、あなたのお財布は空っぽなので、あなたはどうしても彼を倒すことになるでしょう。
稼いだお金で仲間を集め、より強い敵を倒しましょう。敵が上等な武器を落とすこともあります。それはあなたの物です。
街には奴隷商会があり、メイド喫茶があり、公爵家があります。
奴隷の戦闘員たちはとても優秀です。
『猫耳のじゃロリメイド』はかわいいです。
『盾ロール悪役公爵家令嬢』は多分誤字です。
寂れた教会では『ぽわぽわお姉様シスター』があなたの助けを待っています。
お城に行けば『ツンデレハイエルフ姫』との謁見です。彼女は退屈しています。また、お城の武器庫には、聖剣が眠っているそうです。
森の奥に巣くう盗賊たちを一掃し、ダンジョンのモンスターたちに挑みましょう。
この世界はあなたの活躍を待ち望んでいます。
細かい事情は、Prolog と日本語で書いておきました。
なあに、難しいことはありません。ただコンピュータのプログラミングと一階述語論理に精通していればいいだけです。
ちっぽけな世界です。
健闘を祈ります。
*
Prolog とは、論理プログラミング言語の一つです。
普通のプログラミング言語では、コンピュータに向かって、ああしろ、こうしろ、という命令を与える形でプログラムを記述します。
これがなかなか面倒で、何しろコンピュータというものは、言われたことしかやりません。一から十まで命令する必要があります。
あれをやって、これをやって、それをやって……命令してる間に日が暮れるわ。
論理プログラミング言語では、論理を記述することで、コンピュータに仕事をさせます。
論理というのは『ソクラテスは人間である。人間は死ぬ。だからソクラテスは死ぬ』みたいなやつです。
この論理を与えると、Prolog の中でよろしくやってくれて、人間は一々命令する必要がありません。
いや論理を書くのも面倒だろう……それはそうなのですが、細かく命令するよりは(数学者に対して)易しい、ということです。
一階述語論理というのは……正直筆者にもよく分からないのですが……。
『論理』は、さっき説明しました。『述語』は、『である』とか『死ぬ』とかのことです。『一階』は、『二階』とか『三階』とか『高階』なんかよりも簡単という意味です。
一階述語論理に詳しい人は、Prolog でプログラミングすることなんて朝飯前です。なぜなら、Prolog は、この一階述語論理に基づいて設計されているからです。
『小説 述語論理』とかで検索すると、この論理をテーマに書かれた小説が見付かります。
……よう書いたな。中身は一応、普通の推理小説みたいな感じっぽいですが、作者の頭はどうなっているのだろうか。
さて、今更ですが、この作品のテーマは、述語論理で小説を書いてみることでした。
この作品は小説になっているでしょうか。
筆者には、とてもそうは思えません。やっぱり述語論理だけで小説を書くのは難しいようです。
小説とはなんであるか。
Prolog 風に書けば『小説(X) :- ……』みたいな。
この定義を考えてみることも、面白そうです。
最後に、本作の遊び方について。
本作を Prolog のプログラムとして実行すると、遠く二十世紀の、昭和の昔のテキストアドベンチャーゲームみたいな感じで遊べます。
一口に Prolog と言っても色々あって、本作は、SWI-Prolog という Prolog 処理系で動作を確認しています。
ここからリンクを張ったりはしませんが、『SWI-Prolog SWISH』で検索すると、(パソコンの)Web ブラウザから使える Prolog の実行環境が見付かると思います。
当該ページを開くと、『Create a [Program|Notebook] here』というボタンがどこかにあるはずです。その『Program』の方を押します。
するとプログラムを書き込むテキストエリアが開きますので、本作の『異世界転移した(主人公).』の行から、最後の『write_nth_menu(Index1, T).』の行まで、コピーして貼り付けてください。
そうしたら、画面のどこかに、左側に『?-』と書かれたテキストエリアがありますので、そこに『start.』と入力して、『Run!』と書かれたボタンを押します。
これで本作のプログラムが起動し、主人公の状態(現在地、所持金、武器、チームの名簿)と、取れる行動の選択肢が表示されます。
数字を入力して『Send』ボタンを押すと、盗賊を倒したり、街に移動したりできます。
倒せない敵は選択肢として表示されません。また、所持金が足りないと、メイドさんにもお姫様にも会えません。
男の戦闘員を雇えば簡単、ハーレムパーティーは少しだけ面倒なように設計されています。
--ここで区切ります--