Proof with auto.
intros c st st1 st2 E1 E2;
generalize dependent st2;
induction E1;
intros st2 E2; inv E2...
- (* E_Seq *)
assert (st' = st'0) as EQ1...
subst st'0...
- (* E_IfTrue *)
+ (* b evaluates to false (contradiction) *)
rewrite H in H5. inversion H5.
- (* E_IfFalse *)
+ (* b evaluates to true (contradiction) *)
rewrite H in H5. inversion H5.
- (* E_WhileFalse *)
+ (* b evaluates to true (contradiction) *)
rewrite H in H2. inversion H2.
(* E_WhileTrue *)
- (* b evaluates to false (contradiction) *)
rewrite H in H4. inversion H4.
- (* b evaluates to true *)
assert (st' = st'0) as EQ1...
subst st'0...
Qed.
(* ################################################################# *)
(** * Searching For Hypotheses *)
(** The proof has become simpler, but there is still an annoying amount of repetition. Let's start by tackling the contradiction cases. Each of them occurs in a situation where we have both  H1: beval st b = false  and  H2: beval st b = true  as hypotheses.  The contradiction is evident, but demonstrating it is a little complicated: we have to locate the two hypotheses [H1] and [H2] and do a [rewrite] following by an [inversion].  We'd like to automate this process.  (In fact, Coq has a built-in tactic [congruence] that will do the job in this case.  But we'll ignore the existence of this tactic for now, in order to demonstrate how to build forward search tactics by hand.)  As a first step, we can abstract out the piece of script in question by writing a little function in Coq's tactic programming language, Ltac. *)
Ltac rwinv H1 H2 := rewrite H1 in H2; inv H2.

Theorem ceval_deterministic'': forall c st st1 st2,
c / st \\ st1  ->
c / st \\ st2 ->
st1 = st2.