// Exemple 4 : Missionnaires-Cannibales simplifié avec Store (2M/2C, 2 étapes)
// État = tableau [M_left, C_left] (missionnaires et cannibales sur la rive gauche)
// On modélise une transition élémentaire (un aller + un retour) qui FAIT PROGRESSER
// l'état. La résolution complète [2,2] -> [0,0] est impossible en 2 étapes ; on cherche
// donc simplement une transition valide (aller+retour) aboutissant à un état != [2,2].
using (var ctx = new Microsoft.Z3.Context())
{
var solver = ctx.MkSolver();
int N = 2; // 2 missionnaires, 2 cannibales
int BOAT = 2; // capacité barque
// État initial : [2, 2] encodé comme Store sur un tableau symbolique
var initState = ctx.MkArrayConst("init", ctx.IntSort, ctx.IntSort);
initState = ctx.MkStore(ctx.MkStore(initState, ctx.MkInt(0), ctx.MkInt(N)),
ctx.MkInt(1), ctx.MkInt(N));
// Variables symboliques pour les deltas de la transition 1 (aller)
var deltaM1 = ctx.MkIntConst("deltaM1"); // missionnaires qui traversent
var deltaC1 = ctx.MkIntConst("deltaC1"); // cannibales qui traversent
// Contraintes sur delta1 : entre 1 et BOAT personnes
solver.Assert(ctx.MkGe(deltaM1, ctx.MkInt(0)));
solver.Assert(ctx.MkGe(deltaC1, ctx.MkInt(0)));
solver.Assert(ctx.MkGt(ctx.MkAdd(deltaM1, deltaC1), ctx.MkInt(0))); // au moins 1
solver.Assert(ctx.MkLe(ctx.MkAdd(deltaM1, deltaC1), ctx.MkInt(BOAT))); // max boat
solver.Assert(ctx.MkLe(deltaM1, ctx.MkInt(N)));
solver.Assert(ctx.MkLe(deltaC1, ctx.MkInt(N)));
// État après aller : state1 = store(store(init, 0, M-dM), 1, C-dC)
var m0 = (ArithExpr)ctx.MkSelect(initState, ctx.MkInt(0));
var c0 = (ArithExpr)ctx.MkSelect(initState, ctx.MkInt(1));
var state1 = ctx.MkStore(ctx.MkStore(initState, ctx.MkInt(0),
ctx.MkSub(m0, deltaM1)), ctx.MkInt(1), ctx.MkSub(c0, deltaC1));
// Sécurité après aller
var m1 = (ArithExpr)ctx.MkSelect(state1, ctx.MkInt(0));
var c1 = (ArithExpr)ctx.MkSelect(state1, ctx.MkInt(1));
solver.Assert(ctx.MkGe(m1, ctx.MkInt(0))); // pas de négatif
solver.Assert(ctx.MkGe(c1, ctx.MkInt(0)));
// Si missionnaires > 0, ils doivent être >= cannibales
solver.Assert(ctx.MkImplies(ctx.MkGt(m1, ctx.MkInt(0)), ctx.MkGe(m1, c1)));
// Pareil sur la rive droite
var m1r = ctx.MkSub(ctx.MkInt(N), m1);
var c1r = ctx.MkSub(ctx.MkInt(N), c1);
solver.Assert(ctx.MkImplies(ctx.MkGt(m1r, ctx.MkInt(0)), ctx.MkGe(m1r, c1r)));
// Variables pour le retour (delta2 = personnes qui reviennent droite -> gauche)
var deltaM2 = ctx.MkIntConst("deltaM2");
var deltaC2 = ctx.MkIntConst("deltaC2");
solver.Assert(ctx.MkGe(deltaM2, ctx.MkInt(0)));
solver.Assert(ctx.MkGe(deltaC2, ctx.MkInt(0)));
solver.Assert(ctx.MkGt(ctx.MkAdd(deltaM2, deltaC2), ctx.MkInt(0)));
solver.Assert(ctx.MkLe(ctx.MkAdd(deltaM2, deltaC2), ctx.MkInt(BOAT)));
// Validité du retour : on ne peut ramener que des personnes réellement présentes
// sur la rive droite après l'aller (soit delta1 au plus).
solver.Assert(ctx.MkLe(deltaM2, deltaM1));
solver.Assert(ctx.MkLe(deltaC2, deltaC1));
// État après retour
var state2 = ctx.MkStore(ctx.MkStore(state1, ctx.MkInt(0),
ctx.MkAdd(m1, deltaM2)), ctx.MkInt(1), ctx.MkAdd(c1, deltaC2));
var m2 = (ArithExpr)ctx.MkSelect(state2, ctx.MkInt(0));
var c2 = (ArithExpr)ctx.MkSelect(state2, ctx.MkInt(1));
solver.Assert(ctx.MkGe(m2, ctx.MkInt(0)));
solver.Assert(ctx.MkGe(c2, ctx.MkInt(0)));
solver.Assert(ctx.MkLe(m2, ctx.MkInt(N)));
solver.Assert(ctx.MkLe(c2, ctx.MkInt(N)));
// Contrainte de progression : l'état final doit différer de l'état initial [N, N].
// Sans elle, le solveur trouve un aller-retour « sur place » (delta1 == delta2).
solver.Assert(ctx.MkOr(ctx.MkNot(ctx.MkEq(m2, ctx.MkInt(N))),
ctx.MkNot(ctx.MkEq(c2, ctx.MkInt(N)))));
if (solver.Check() == Status.SATISFIABLE)
{
var model = solver.Model;
// Toutes les valeurs affichées sont évaluées contre le modèle pour obtenir
// des entiers concrets (les Expr symboliques non évaluées imprimeraient du SMT-LIB).
var dm1 = model.Eval(deltaM1);
var dc1 = model.Eval(deltaC1);
var dm2 = model.Eval(deltaM2);
var dc2 = model.Eval(deltaC2);
var m0v = model.Eval(m0);
var c0v = model.Eval(c0);
var m1v = model.Eval(m1);
var c1v = model.Eval(c1);
var m2v = model.Eval(m2);
var c2v = model.Eval(c2);
Console.WriteLine("=== Missionnaires-Cannibales (2M/2C, Store-based) ===");
Console.WriteLine($"État initial : [{m0v}M, {c0v}C] sur la rive gauche");
Console.WriteLine($"Aller (delta) : -{dm1}M, -{dc1}C");
Console.WriteLine($"État après aller : [{m1v}M, {c1v}C] sur la rive gauche");
Console.WriteLine($"Retour (delta) : +{dm2}M, +{dc2}C");
Console.WriteLine($"État après retour : [{m2v}M, {c2v}C] sur la rive gauche");
Console.WriteLine($"\nTransition store-based valide : [{m0v},{c0v}] -> [{m1v},{c1v}] -> [{m2v},{c2v}].");
Console.WriteLine("La résolution complète [2,2] -> [0,0] nécessite davantage d'étapes.");
}
else
{
Console.WriteLine("UNSAT : aucune transition aller+retour ne fait progresser l'état [2,2].");
}
}