Index: /pom.xml
===================================================================
--- /pom.xml	(revision 18)
+++ /pom.xml	(revision 19)
@@ -7,5 +7,5 @@
 	<artifactId>runserver</artifactId>
 	<packaging>war</packaging>
-	<version>1.5.1</version> <!-- must equal ${geniusweb.version} -->
+	<version>1.5.2</version> <!-- must equal ${geniusweb.version} -->
 	<name>runserver Maven Webapp</name>
 	<url>http://maven.apache.org</url>
@@ -20,5 +20,5 @@
 		<jackson-2-version>2.9.10</jackson-2-version>
 		<tomcat.version>8.0.53</tomcat.version>
-		<geniusweb.version>1.5.1</geniusweb.version>
+		<geniusweb.version>1.5.2</geniusweb.version>
 	</properties>
 
Index: /src/main/webapp/newsession.xhtml
===================================================================
--- /src/main/webapp/newsession.xhtml	(revision 18)
+++ /src/main/webapp/newsession.xhtml	(revision 19)
@@ -40,5 +40,5 @@
 	<br /> Domain/Profile Server:
 	<input type="url" name="url" id="profilesserverurl"
-		value="localhost:8080/profilesserver-1.5.1"
+		value="localhost:8080/profilesserver-1.5.2"
 		pattern=".*:[0-9]+/profilesserver" size="30"
 		onchange="connectDomain()"> </input>
@@ -54,5 +54,5 @@
 		<br /> <b>Participants</b> <br /> Parties Server: <input type="url"
 			name="url" id="partiesserverurl"
-			value="localhost:8080/partiesserver-1.5.1"
+			value="localhost:8080/partiesserver-1.5.2"
 			pattern=".*:[0-9]+/partiesserver" size="30"
 			onchange="connectParties()"> </input> <br /> <br /> <b>Party
@@ -294,8 +294,8 @@
 	 refresh table: copy all parties elements in there.
 	 Typically parties is something like
-	 [{"uri":"http:130.161.180.1:8080/partiesserver/run/randomparty-1.5.1",
+	 [{"uri":"http:130.161.180.1:8080/partiesserver/run/randomparty-1.5.2",
 		 "capabilities":{"protocols":["SAOP"]},
 		 "description":"places random bids until it can accept an offer with utility >0.6",
-		 "id":"randomparty-1.5.1",
+		 "id":"randomparty-1.5.2",
 		 "partyClass":"geniusweb.exampleparties.randomparty.RandomParty"},
 		 		 ...]
@@ -632,6 +632,6 @@
 	function init() {
 		selectProtocol();
-		document.getElementById("partiesserverurl").value =window.location.hostname+":8080/partiesserver-1.5.1"
-		document.getElementById("profilesserverurl").value =window.location.hostname+":8080/profilesserver-1.5.1"
+		document.getElementById("partiesserverurl").value =window.location.hostname+":8080/partiesserver-1.5.2"
+		document.getElementById("profilesserverurl").value =window.location.hostname+":8080/profilesserver-1.5.2"
 		connectDomain();
 		connectParties();
Index: /src/main/webapp/newtournament.xhtml
===================================================================
--- /src/main/webapp/newtournament.xhtml	(revision 18)
+++ /src/main/webapp/newtournament.xhtml	(revision 19)
@@ -585,6 +585,6 @@
 	*/
 	function init() {
-		document.getElementById("partiesserverurl").value =window.location.hostname+":8080/partiesserver-1.5.1";
-		document.getElementById("profilesserverurl").value =window.location.hostname+":8080/profilesserver-1.5.1";
+		document.getElementById("partiesserverurl").value =window.location.hostname+":8080/partiesserver-1.5.2";
+		document.getElementById("profilesserverurl").value =window.location.hostname+":8080/profilesserver-1.5.2";
 
 		selectProtocol();
Index: /src/main/webapp/plotlog.xhtml
===================================================================
--- /src/main/webapp/plotlog.xhtml	(revision 18)
+++ /src/main/webapp/plotlog.xhtml	(revision 19)
@@ -87,6 +87,7 @@
         */
         function processLogFile(json) {
+        	
         	if (json['SAOPState']!=undefined) {
-        		processSAOP(json['SAOPState']);
+        		processStandard(json['SAOPState'], 'SAOP');
         	} else if (json['SHAOPState']!=undefined) {
         		processSHAOP(json['SHAOPState']);
@@ -94,5 +95,5 @@
         		// just use the SAOP processor, which shows the
         		// utilities of all Offers in sequence.
-        		processSAOP(json['MOPACState']);
+        		processStandard(json['MOPACState'], 'MOPAC');
         	} else{
             	setStatus("Unknown log file contents "+Object.keys(json));
@@ -101,7 +102,8 @@
         
         /**
-        Handle SAOP protocol result.
-        */
-        function processSAOP(json) {
+        Handle standard protocol result.
+        @param protocol either SAOP or MOPAC or another SAOP-like protocol
+        */
+        function processStandard(json, protocol) {
         	var partyprofiles={}
         	var profs=json['partyprofiles'];
@@ -110,5 +112,5 @@
         	}
         	
-        	getProfiles(json, partyprofiles);
+        	getProfiles(json, partyprofiles, protocol);
         }
         
@@ -129,5 +131,5 @@
 			
         	
-        	getProfiles(json, partyprofiles);
+        	getProfiles(json, partyprofiles, "SHAOP");
         }
 
@@ -148,12 +150,13 @@
         /**
         Get the partyprofiles.
-        @json the json protocol result
-        @partyprofileURIs dict with the websocket addresses for each party'as profile
-        @partyprofiles dict with each party's profile. Initially should be {}
-        */
-        function getProfiles(json, partyprofileURIs, partyprofiles={}) {
+        @param json the json protocol result
+        @param partyprofileURIs dict with the websocket addresses for each party'as profile
+        @param partyprofiles dict with each party's profile. Initially should be {}
+        @param protocol protocolname, eg "SAOP"
+        */
+        function getProfiles(json, partyprofileURIs, protocol, partyprofiles={} ) {
         	var keys = Object.keys(partyprofileURIs);
 			if (keys.length==0) {
-				plotResults(computeUtils(json, partyprofiles), isAgreement(json));
+				plotResults(computeUtils(json, partyprofiles), isAgreement(json, protocol));
 				return;
 			}
@@ -178,5 +181,5 @@
             	}
             	partyprofiles[party]=profile['LinearAdditiveUtilitySpace'];
-            	getProfiles(json, partyprofileURIs, partyprofiles);
+            	getProfiles(json, partyprofileURIs, protocol, partyprofiles);
             };
             ws.onerror=function(event) {
@@ -186,10 +189,27 @@
         
         /**
-        @return true if there is agreement
-        */
-        function isAgreement(logfile) {
-        	var actions=logfile['actions'];
-			var lastact = actions[actions.length -1];
-			return 'accept' in lastact;
+        @param logfile the logfile content, but below the protocol header
+        @param protocol the protocol name, eg SAOP, SHAOP, MOPAC
+        @return 'Yes' if there is agreement, 'No' if there is no agreement at all, 
+        or 'Partial'  if part of the parties agreed and part did not.
+        or '?' if unsupported protocol
+        */
+        function isAgreement(logfile, protocol) {
+			switch (protocol) {
+				case "SAOP":
+				case "SHAOP":
+					var actions=logfile['actions'];
+					var lastact = actions[actions.length -1];
+					return 'accept' in lastact ? 'Yes':'No';
+				case "MOPAC":
+					var finalstate=logfile['phase']['partyStates'];
+					var nagrees=Object.keys(finalstate['agreements']).length;
+					var nparties=Object.keys(finalstate['powers']).length;
+					if (nagrees==nparties) return "Yes";
+					if (nagrees==0) return "No"
+					return "Partial";
+				default:
+					return "?";
+				}
         }
         /**
@@ -235,5 +255,5 @@
         /**
         @param utilities the dict with key=party id  of the utilities of all steps for each party )
-        @param isAgreement true iff there is agreement reached
+        @param isAgreement either Yes, No, Partial or ?
         */
         
@@ -241,5 +261,5 @@
         	setStatus("Plotting" )
         	
-            document.getElementById('agreement').innerHTML=isAgreement?"Yes":"No";
+            document.getElementById('agreement').innerHTML=isAgreement;
 
         	var parties=Object.keys(utilities);
@@ -287,5 +307,5 @@
     						scaleLabel: {
     							display: true,
-    							labelString: 'Turn'
+    							labelString: 'Offer'
     						}
     					}],
Index: /src/main/webapp/utilstable.xhtml
===================================================================
--- /src/main/webapp/utilstable.xhtml	(revision 18)
+++ /src/main/webapp/utilstable.xhtml	(revision 19)
@@ -27,6 +27,6 @@
 		<thead>
 			<tr>
-				<th align="center">run nr</th>
-				<th align="center">accepted bid(s)</th>
+				<th align="center">sess / agree</th>
+				<th align="center">accepted bid in session</th>
 				<th align="center">party utility - penalty</th>
 			</tr>
@@ -206,54 +206,37 @@
 	    	for (var nr in results) {
 	    		var result = results[nr];
-	        	var row = table.insertRow(-1); //-1 = end
-	        	fillRow(row, nr, results[nr]);
+	        	fillBids(table, nr, result);
 	    	}
     	   	setStatus("done");
         }
 
-        /**
-        @return true if object1, object2 are 'deep equal'. That means,
-        if they are dicts then we check that the keys and their values are equal, 
-        recursively.
-        */
-       function deepEqual(object1, object2) {
-    	   const keys1 = Object.keys(object1);
-    	   const keys2 = Object.keys(object2);
-
-    	   if (keys1.length !== keys2.length) {
-    	     return false;
-    	   }
-
-    	   for (const key of keys1) {
-    	     const val1 = object1[key];
-    	     const val2 = object2[key];
-    	     const areObjects = isObject(val1) && isObject(val2);
-    	     if (
-    	       areObjects && !deepEqual(val1, val2) ||      !areObjects && val1 !== val2
-    	     ) {
-    	       return false;
-    	     }
-    	   }
-
-    	   return true;
-    	 }
-
-    	 function isObject(object) {
-    	   return object != null && typeof object === 'object';
-    	 } 
-    	 
-    	 /**
-    	 @param list the list to add value to
-    	 @param value the value to add
-    	 @return list with the value added, but only if not already in list.
-    	 Uses deepEqual to check equality of list members 
-    	 */
-    	 function addSet(list, value) {
-    		 for (const v of list) {
-    			 if (deepEqual(v, value))
-    				 return list;
-    		 }
-    		 return list.concat(value);
-    	 }
+       /**
+       @param table the table to add the bid results to
+       @param nr the session number
+       @param results the results for this sess
+       Add rows for all the bids 
+       */
+       function fillBids(table, nr, result) {
+			// collect unique bids in agreements.
+			var agreements = result['agreements'];
+			
+			if (Object.keys(agreements).length == 0)  {
+				fillRow(table.insertRow(-1), nr, result, {});
+				return;
+			}
+				
+			var bids = [];
+			for (const pid in agreements)  {
+				bids=addSet(bids, agreements[pid]['issuevalues']);
+			}
+	
+			var dealnr='A'.charCodeAt(0);
+			for (const bid of bids) {
+				fillRow(table.insertRow(-1), nr+String.fromCharCode(dealnr++), result, bid);
+			}
+       }
+       	
+
+      
 
         /**
@@ -262,6 +245,7 @@
         @param result a single json result from the APP results section. 
         Contains participants, agreements and error
-        */
-        function fillRow(row, nr, result) {
+        @param agreedbid the bid to show in this row. If {}, no agreement was reached.
+        */
+        function fillRow(row, nr, result, agreedbid) {
         	row.insertCell(0).innerHTML = nr;
         	
@@ -275,30 +259,26 @@
         		return;
         	}
-
-        	// collect unique bids in agreements.
-        	var agreements = result['agreements'];
-        	var bids = [];
-        	for (const pid in agreements) {
-        		bids=addSet(bids, agreements[pid]['issuevalues']);
-        	}
-        		
-        		
+	
         	// a dict, keys are party ids. 
-        	// FIXME this does not work ok with MOPAC.
-        	row.insertCell(-1).innerHTML = JSON.stringify(bids);
-        	var agreedbid={};
-        	if (bids.length>0) agreedbid = bids[0];
+        	row.insertCell(-1).innerHTML = JSON.stringify(agreedbid);
         	
         	// fill in the columns. If SHAOP, only the even parties 
-        	var stepsize = SHAOP? 2:1;
-        	for (var n=0; n<result['participants'].length; n=n+stepsize) {
-        		var party =  result['participants'][n]
-        		var profile =party['profile'];
-            	var penalty=result['penalties'][n];
+        	// FIXME this can't be with a MAP
+        	const participants = result['participants'];
+        	for (var pid in participants) {
+        		var partyandprofile = result['participants'][pid];
+        		var profile =partyandprofile['profile'];
+            	var penalty=result['penalties'][pid];
+            	if (penalty==undefined) penalty=0;
         		// make short name for readability
-        		var partyname = party['party']['partyref'];
+        		var partyname = partyandprofile['party']['partyref'];
         		partyname = partyname.split('/');
         		partyname = partyname[partyname.length-1];
-        		addUtilityCell(row.insertCell(-1), agreedbid, partyname, profile,penalty);
+        		
+        		// we set bid to agreedbid only if party accepted agreedbid.
+         		var bid = {};
+        		if (pid in result['agreements'] && deepEqual(agreedbid, result['agreements'][pid]['issuevalues'] ) )
+        				bid = agreedbid;
+         		addUtilityCell(row.insertCell(-1), bid, partyname, profile, penalty);
         	}
         }
@@ -410,4 +390,49 @@
 	 	}
  	
+	 	/************ util functions to compare dict objects **************/
+	 	/**
+        @return true if object1, object2 are 'deep equal'. That means,
+        if they are dicts then we check that the keys and their values are equal, 
+        recursively.
+        */
+       function deepEqual(object1, object2) {
+    	   const keys1 = Object.keys(object1);
+    	   const keys2 = Object.keys(object2);
+
+    	   if (keys1.length !== keys2.length) {
+    	     return false;
+    	   }
+
+    	   for (const key of keys1) {
+    	     const val1 = object1[key];
+    	     const val2 = object2[key];
+    	     const areObjects = isObject(val1) && isObject(val2);
+    	     if (
+    	       areObjects && !deepEqual(val1, val2) ||      !areObjects && val1 !== val2
+    	     ) {
+    	       return false;
+    	     }
+    	   }
+
+    	   return true;
+    	 }
+
+    	 function isObject(object) {
+    	   return object != null && typeof object === 'object';
+    	 } 
+    	 
+    	 /**
+    	 @param list the list to add value to
+    	 @param value the value to add
+    	 @return list with the value added, but only if not already in list.
+    	 Uses deepEqual to check equality of list members 
+    	 */
+    	 function addSet(list, value) {
+    		 for (const v of list) {
+    			 if (deepEqual(v, value))
+    				 return list;
+    		 }
+    		 return list.concat(value);
+    	 }
  	
     ]]>
