Abstractly,
A Map Generation Speedrun with Answer Set Programming, 2011
Answer Set Programming for Procedural Content Generation: A Design Space Approach, 2011
The composition of most styles of music is governed by rules... by formalising these rules in a suitable logical language, powerful and expressive intelligent composition tools can be easily built.
Automatic Music Composition using Answer Set Programming, 2010
Appropriate when:
“Generate and test”
Trial and error is also a heuristic method of problem solving, repair, tuning, or obtaining knowledge. In the field of computer science, the method is called generate and test (Brute force). In elementary algebra, when solving equations, it is guess and check.
There are 15 puppies and birds at a pet shop. There are 42 legs altogether. How many puppies are there?
animal(puppy).
animal(bird).
legs(puppy,4).
legs(bird,2).
animal_n(1..15).
bipedal(A) :- animal(A), legs(A,2).
“A thing A is bipedal if it is a two-legged animal.”
bipedal(A) :- animal(A), legs(A,2).
$ clingo animals.lp clingo version 5.4.0 Reading from animals.lp Solving... Answer: 1 animal_n(1) animal_n(2) animal_n(3) animal_n(4) animal_n(5) animal_n(6) animal_n(7) animal_n(8) animal_n(9) animal_n(10) animal_n(11) animal_n(12) animal_n(13) animal_n(14) animal_n(15) legs(puppy,4) legs(bird,2) animal(puppy) animal(bird) bipedal(bird) SATISFIABLE Models : 1 Calls : 1 Time : 0.024s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s) CPU Time : 0.001s
Define search space, or “possible worlds”:
animal(bird).
{ animal(puppy) }.
$ clingo animals.lp clingo version 5.4.0 Reading from animals.lp Solving... Answer: 1 animal(bird) Answer: 2 animal(bird) animal(puppy) SATISFIABLE Models : 2 Calls : 1 Time : 0.021s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s) CPU Time : 0.001s
Generate every possible pet shop… all \(2^{15}\) of them:
{ animal_at_shop(A, N) : animal(A) } = 1 :- animal_n(N).
SATISFIABLE Models : 32768 Calls : 1 Time : 3.294s (Solving: 3.27s 1st Model: 0.00s Unsat: 0.00s) CPU Time : 0.249s
Remove possible worlds:
animal(bird).
{ animal(puppy) }.
:- animal(puppy).
$ clingo -n 0 animals.lp clingo version 5.4.0 Reading from animals.lp Solving... Answer: 1 animal(bird) SATISFIABLE Models : 1 Calls : 1 Time : 0.013s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s) CPU Time : 0.001s
Remove every world without the right number of legs:
correct_number_of_legs :- legs_total(42).
:- not correct_number_of_legs.
legs_total(L) :-
L = #sum { N,M : animal_at_shop(A, M), legs(A, N) }.
animal_count(A,N) :-
N = #count { M : animal_at_shop(A, M) }, animal(A).
#show animal_count/2.
#project animal_count(A,N).
#minimize { D : unassigned_count(D) }.
animal(puppy;bird).
legs(puppy,4).
legs(bird,2).
animal_n(1..15).
{ animal_at_shop(A, N) : animal(A) } = 1 :- animal_n(N).
correct_number_of_legs :- legs_total(42).
:- not correct_number_of_legs.
legs_total(L) :- L = #sum { N,M : animal_at_shop(A, M), legs(A, N) }.
animal_count(A,N) :- N = #count { M : animal_at_shop(A, M) }, animal(A).
#show animal_count/2.
#project animal_count(A,N).
$ clingo -n 0 animals.lp --project clingo version 5.4.0 Reading from animals.lp Solving... Answer: 1 animal_count(puppy,6) animal_count(bird,9) SATISFIABLE Models : 1 Calls : 1 Time : 0.271s (Solving: 0.26s 1st Model: 0.00s Unsat: 0.26s) CPU Time : 0.268s
sprint(1..1).
story(1..2).
story_weight(2,1).
story_depends_on(1,2).
sprint_capacity(1,1).
Each story is assigned to exactly one sprint:
{ assign(T,S) : sprint(S) } = 1 :- story(T).
Sum story weights, grouping by assigned sprint:
sprint_total(S,To) :-
To = #sum { W,T : story_weight(T,W), assign(T,S) }, sprint(S).
The sum of story weights cannot exceed sprint capacity, unless they’re unassigned:
sprint(unassigned).
:- sprint_total(S,A), sprint(S),
sprint_capacity(S,E), A > E, S != unassigned.
unassigned_count(D) :- D = #count { T : assign(T,unassigned) }.
#minimize { D : unassigned_count(D) }.
Given a story dependency, the dependent story cannot be assigned to a later sprint than its dependency:
:- assign(T1,S1), assign(T2,S2),
story_depends_on(T1,T2), S1 < S2.
Story 1 must always be assigned to sprint 2:
:- not assign(1,2).
Then suddenly, he was struck by a powerful but simple little truth, and it was this: That English grammar is governed by rules that are almost mathematical in their strictness! Given the words, and given the sense of what is to be said, then there is only one correct order in which those words can be arranged. Therefore, it stands to reason that an engine built along the lines of the electric computer could be adjusted to arrange words (instead of numbers) in their right order according to the rules of grammar. Then feed it with plots and leave it to write the sentences.
#const t_max=15.
time(0..t_max).
t_max
time pointsEvents happen only under specific conditions
{ happens(T,E) } :- time(T), possible(T,E).
Only one event happens at a time
:- time(T), not { happens(T,E) } = 1.
Facts hold as a result of things happening
initiated(T,F) :- happens(T,E), initiates(T,E,F).
holds(T+1,F) :- time(T), happens(T,E), initiates(T,E,F).
Facts are inertial; they continue to hold without external influence
terminated(T,F) :- happens(T,E), terminates(T,E,F).
holds(T+1,F) :- time(T), holds(T,F), not terminated(T,F).
possible
, initiates
, terminates
character(rey).
character(kylo).
character(palpatine).
place(pasaana).
place(kijimi).
place(endor).
place(exegol).
place(resistance_base).
place(tatooine).
always_reachable(resistance_base,pasaana).
always_reachable(pasaana,kijimi).
always_reachable(kijimi,endor).
always_reachable(exegol,endor).
always_reachable(exegol,kijimi).
always_reachable(exegol,tatooine).
holds(0,reachable(P1,P2)) :- always_reachable(P1,P2),
place(P1), place(P2).
holds(0,at(kylo,exegol)).
holds(0,at(rey,resistance_base)).
holds(0,at(palpatine,exegol)).
holds(0,alive(C)) :- character(C).
possible(T,moves(C,P1,P2)) :-
holds(T,at(C,P1)), not holds(T,at(C,P2)),
holds(T,reachable(P1,P2)),
holds(T,alive(C)),
character(C), place(P1), place(P2).
initiates(T,moves(C,P1,P2),at(C,P2)) :-
possible(T,moves(C,P1,P2)).
terminates(T,moves(C,P1,P2),at(C,P1)) :-
possible(T,moves(C,P1,P2)).
$ clingo disney.lp
clingo version 5.4.0
Reading from disney.lp
Solving...
Answer: 1
happens(0,moves(kylo,exegol,pasaana)) happens(2,moves(rey,resistance_base,pasaana)) happens(3,moves(palpatine,exegol,tatooine)) happens(1,moves(kylo,pasaana,endor)) happens(4,moves(kylo,endor,tatooine)) happens(5,moves(kylo,tatooine,exegol))
SATISFIABLE
Models : 1+
Calls : 1
Time : 0.021s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time : 0.021s
happens(0,moves(rey,resistance_base,pasaana))
happens(1,moves(rey,pasaana,kijimi))
happens(2,moves(rey,kijimi,endor))
happens(3,moves(palpatine,exegol,kijimi))
happens(4,moves(kylo,exegol,kijimi))
happens(5,moves(palpatine,kijimi,endor))
item(wayfinder).
holds(0,has(endor,wayfinder)).
possible(T,finds(C,I,P)) :-
holds(T,at(C,P)),
not holds(T,has(C,I)), holds(T,has(P,I)),
holds(T,alive(C)),
character(C), item(I), place(P), time(T).
initiates(T,finds(C,I,P),has(C,I)) :- possible(T,finds(C,I,P)).
terminates(T,finds(C,I,P),has(P,I)) :- possible(T,finds(C,I,P)).
happens(0,moves(kylo,exegol,endor))
happens(1,moves(palpatine,exegol,endor))
happens(2,finds(palpatine,wayfinder,endor))
happens(3,moves(rey,resistance_base,pasaana))
holds(T,reachable(P,exegol)) :-
holds(T-1, has(rey,wayfinder)), time(T), place(P).
No one goes into Exegol until then…
happens(0,moves(rey,resistance_base,pasaana))
happens(1,moves(rey,pasaana,kijimi))
happens(2,moves(rey,kijimi,endor))
happens(3,finds(rey,wayfinder,endor))
happens(4,moves(palpatine,exegol,endor))
happens(5,moves(rey,endor,exegol))
side(light).
side(dark).
holds(0,alignment(rey,light)).
holds(0,alignment(kylo,dark)).
holds(0,alignment(palpatine,dark)).
possible(T,turns(C1,C2,S1,S2)) :-
holds(T,alignment(C2,S1)), not holds(T,alignment(C2,S2)),
holds(T,at(C1,P)), holds(T,at(C2,P)),
holds(T,alignment(C1,S2)),
holds(T,alive(C1)), holds(T,alive(C2)),
place(P), character(C1), character(C2), side(S1), side(S2).
initiates(T,turns(C1,C2,S1,S2),alignment(C2,S2)) :-
possible(T,turns(C1,C2,S1,S2)).
terminates(T,turns(C1,C2,S1,S2),alignment(C2,S1)) :-
possible(T,turns(C1,C2,S1,S2)).
happens(0,moves(rey,resistance_base,pasaana))
happens(1,moves(rey,pasaana,kijimi))
happens(2,moves(rey,kijimi,endor))
happens(3,moves(palpatine,exegol,endor))
happens(4,moves(kylo,exegol,endor))
happens(5,turns(palpatine,rey,light,dark))
possible(T,lightsaber_duel(C1,C2)) :-
C1 != C2,
holds(T,alignment(C1,S1)), holds(T,alignment(C2,S2)), S1 != S2,
holds(T,at(C1,P)), holds(T,at(C2,P)),
holds(T,alive(C1)), holds(T,alive(C2)),
place(P), character(C1), character(C2),
side(S1), side(S2), time(T).
terminates(T,lightsaber_duel(C1,C2),alive(C2)) :-
possible(T,lightsaber_duel(C1,C2)).
climax :- happens(T,lightsaber_duel(C1,C2)),
character(C1), character(C2).
:- not climax.
rey_reaches_exegol :- holds(t_max+1,at(rey,exegol)).
:- not rey_reaches_exegol.
turn :- happens(T,turns(C1,C2,S1,S2)),
character(C1), character(C2), side(S1), side(S2).
:- not turn.
happens(0,moves(rey,resistance_base,pasaana))
happens(1,moves(kylo,exegol,kijimi))
happens(2,moves(rey,pasaana,kijimi))
happens(3,moves(palpatine,exegol,kijimi))
happens(4,turns(rey,palpatine,dark,light))
happens(5,lightsaber_duel(rey,kylo))
{ pick_reason(T,plot_point_reversal(C,R)) : reason(R) } = 1 :-
character(C), time(T).
possible(T,plot_point_reversal(C,R)) :-
not holds(T,alive(C)),
pick_reason(T,plot_point_reversal(C,R)),
place(P), character(C), time(T).
initiates(T,plot_point_reversal(C,R),alive(C)) :-
possible(T,plot_point_reversal(C,R)).
anticlimax :- happens(T,plot_point_reversal(C,R)),
character(C), reason(R).
:- not anticlimax.
happens(0,moves(palpatine,exegol,tatooine))
happens(1,moves(kylo,exegol,endor))
happens(2,moves(rey,resistance_base,pasaana))
happens(3,moves(rey,pasaana,kijimi))
happens(4,moves(rey,kijimi,endor))
happens(5,lightsaber_duel(kylo,rey))
happens(6,plot_point_reversal(rey,it_was_all_a_dream))
happens(7,lightsaber_duel(kylo,rey))
happens(8,plot_point_reversal(rey,the_dark_side_of_the_force_is_a_pathway_to_many_abilities_some_consider_to_be_unnatural))
happens(9,finds(rey,wayfinder,endor))
happens(10,turns(rey,kylo,dark,light))
happens(11,moves(kylo,endor,exegol))
happens(12,moves(kylo,exegol,tatooine))
happens(13,lightsaber_duel(palpatine,kylo))
happens(14,moves(rey,endor,exegol))
happens(15,moves(palpatine,tatooine,exegol))
happens(0,moves(rey,resistance_base,pasaana))
happens(1,moves(rey,pasaana,kijimi))
happens(2,moves(rey,kijimi,endor))
happens(3,finds(rey,wayfinder,endor))
happens(4,moves(kylo,exegol,endor))
happens(5,moves(rey,endor,exegol))
happens(6,moves(rey,exegol,kijimi))
happens(7,moves(rey,kijimi,exegol))
happens(8,moves(kylo,endor,exegol))
happens(9,lightsaber_duel(rey,kylo))
happens(10,lightsaber_duel(rey,palpatine))
happens(11,plot_point_reversal(kylo,it_was_a_hologram))
happens(12,moves(rey,exegol,tatooine))
happens(13,plot_point_reversal(palpatine,it_was_a_hologram))
happens(14,moves(rey,tatooine,exegol))
happens(15,turns(rey,palpatine,dark,light))
RoleModel: Towards a Formal Model of Dramatic Roles for Story Generation
Yes, but expect lots of writing...
PCG promises a free lunch.
— Kate Compton, actual doctor of weird ai (@GalaxyKate) March 9, 2020
It delivers an angry cow and a small knife, and a promise that if you can make this work, you'll eat like a king.
--rand-freq=1 --seed=39403
Solving... UNSATISFIABLE Models : 0
Removing a goal can make a predicate at most more general, never more specific.