/* * Salliere Duplicate Bridge Scorer * * Copyright (C) 2007 Matthew Johnson * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License Version 2 as published by * the Free Software Foundation. This program is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. You should have received a * copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * To Contact me, please email src@matthew.ath.cx * */ package cx.ath.matthew.salliere; import cx.ath.matthew.debug.Debug; import static cx.ath.matthew.salliere.Gettext._; import java.text.DecimalFormat; import java.text.FieldPosition; import java.text.MessageFormat; import java.util.Arrays; public class Hand { public static final int AVERAGE=1; public static final int AVERAGE_MINUS=2; public static final int AVERAGE_PLUS=3; private boolean check_forced(String[] data, int ofs) { if ('!' == data[ofs].charAt(0)) { data[ofs] = data[ofs].substring(1); return true; } else return false; } String number = ""; String ns = ""; String ew = ""; String contract = ""; char declarer = ' '; int tricks; double nsscore; double ewscore; boolean forced_nsscore; boolean forced_ewscore; double nsmp; double ewmp; boolean forced_nsmp; boolean forced_ewmp; int ewavetype; int nsavetype; public Hand() { } public Hand(String[] data) throws HandParseException { if (data.length < 4) { String extra = ""; if (data.length > 0) extra = MessageFormat.format(_(" (board might be number {0})"), new Object[] { data[0] }); throw new HandParseException(_("Insufficient fields. I cannot parse a board without at least board number, pair numbers and contract")+extra); } this.number = data[0]; this.ns = data[1]; this.ew = data[2]; this.contract = data[3]; switch (data.length) { case 10: if (0 < data[9].length()) { forced_ewmp = check_forced(data, 9); try { this.ewmp = Double.parseDouble(data[9]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); throw new HandParseException(_("Invalid Number of Match Points: ")+data[9]); } } case 9: if (0 < data[8].length()) { forced_nsmp = check_forced(data, 8); try { this.nsmp = Double.parseDouble(data[8]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); throw new HandParseException(_("Invalid Number of Match Points: ")+data[8]); } } case 8: if (0 < data[7].length()) { forced_ewscore = check_forced(data, 7); try { this.ewscore = Double.parseDouble(data[7]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); String av = data[7].toLowerCase(); if (av.startsWith("av") && av.length() == 3) switch (av.charAt(2)) { case '=': ewavetype = AVERAGE; break; case '+': ewavetype = AVERAGE_PLUS; break; case '-': ewavetype = AVERAGE_MINUS; break; default: throw new HandParseException(_("Invalid Average: ")+data[7]); } else throw new HandParseException(_("Invalid Score: ")+data[7]); } } case 7: if (0 < data[6].length()) { forced_nsscore = check_forced(data, 6); try { this.nsscore = Double.parseDouble(data[6]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); String av = data[6].toLowerCase(); if (av.startsWith("av") && av.length() == 3) switch (av.charAt(2)) { case '=': nsavetype = AVERAGE; break; case '+': nsavetype = AVERAGE_PLUS; break; case '-': nsavetype = AVERAGE_MINUS; break; default: throw new HandParseException(_("Invalid Average: ")+data[6]); } else throw new HandParseException(_("Invalid Score: ")+data[6]); } } case 6: if (0 < data[5].length()) try { this.tricks = Integer.parseInt(data[5]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); throw new HandParseException(_("Invalid Number of Tricks: ")+data[5]); } case 5: if (data[4].length() > 0) this.declarer = data[4].charAt(0); } } public void score() throws ScoreException, ContractParseException, HandParseException { String[] n = number.split(":"); String[] v = n[n.length-1].split(";"); int num = 0; try { num = Integer.parseInt(v[0]); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); throw new HandParseException(_("Invalid Hand Number: ")+number); } int vul = Contract.NONE; if (v.length == 1) { switch (num%16) { // ns case 4: case 7: case 10: case 13: vul |= Contract.EAST; case 2: case 5: case 12: case 15: vul |= Contract.NORTH; break; // ew case 3: case 6: case 9: case 0: vul |= Contract.EAST; break; } } else { // split options String[] opts = v[1].split(","); for (String opt: opts) { String[] keyval = opt.split("="); // manual vulnerability if (keyval[0].toLowerCase().equals("vul") && 2 == keyval.length) { if (keyval[1].toLowerCase().equals("ew")) vul = Contract.EAST; else if (keyval[1].toLowerCase().equals("ns")) vul = Contract.NORTH; else if (keyval[1].toLowerCase().equals("all")) vul = Contract.NORTH | Contract.EAST; } } } if (Debug.debug) Debug.print("number="+number+", num="+num+", n="+Arrays.asList(n)+", v="+Arrays.asList(v)+", vul="+vul); Contract c; try { c = new Contract(contract, declarer, vul, tricks); if (!forced_nsscore) { if (nsscore != 0 && nsscore != c.getNSScore()) throw new ScoreException( MessageFormat.format( _("Calculated score as {0} for NS but hand says {1}."), new Object[] { c.getNSScore(), this })); nsscore = c.getNSScore(); } if (!forced_ewscore) { if (ewscore != 0 && ewscore != c.getEWScore()) throw new ScoreException( MessageFormat.format( _("Calculated score as {0} for EW but hand says {1}."), new Object[] { c.getEWScore(), this })); ewscore = c.getEWScore(); } if (tricks != 0 && tricks != c.getTricks()) throw new ScoreException( MessageFormat.format( _("Calculated tricks as {0} but hand says {1}."), new Object[] { c.getTricks(), this })); tricks = c.getTricks(); contract = c.getContract(); declarer = c.getDeclarer(); if (Debug.debug) Debug.print(this); } catch (NoContractException NCe) { if (Debug.debug) Debug.print(NCe); /* not assigning scores on hands which have no contract. Must be averages or something. */ } } public void check() throws HandParseException { try { if (tricks > 13) throw new HandParseException(MessageFormat.format(_("Cannot take {0} tricks!"), new Object[] { tricks })); Contract c = new Contract(contract, declarer, 0, tricks); if (!c.isPassOut()) switch (declarer) { case 'n': case 's': case 'e': case 'w': case 'N': case 'S': case 'E': case 'W': break; default: throw new HandParseException(MessageFormat.format(_("On board {0} declarer should be one of NSEW, it is actually {1}."), new Object[] { number, declarer})); } } catch (ContractParseException CPe) { if (Debug.debug) Debug.print(CPe); throw new HandParseException(MessageFormat.format(_("Failed to parse contract on board {0}: {1}"), new Object[] { number, contract})); } catch (NoContractException NCe) { if (Debug.debug) Debug.print(NCe); /* not assigning scores on hands which have no contract. Must be averages or something. */ } } public String getNumber() { return number; } public String getNS() { return ns; } public String getEW() { return ew; } public String getContract() { return contract; } public double getNSMP() { return nsmp; } public double getEWMP() { return ewmp; } public double getEWScore() { return ewscore; } public double getNSScore() { return nsscore; } public int getTricks() { return tricks; } public char getDeclarer() { return declarer; } public boolean isAveraged() { return !(0 == ewavetype && 0 == nsavetype); } public int getEWAverage() { return ewavetype; } public int getNSAverage() { return ewavetype; } public boolean hasForcedNSMP() { return forced_nsmp; } public boolean hasForcedEWMP() { return forced_ewmp; } public boolean hasForcedNSScore() { return forced_nsscore; } public boolean hasForcedEWScore() { return forced_ewscore; } public void setNSMP(double mp) { nsmp = mp; } public void setEWMP(double mp) { ewmp = mp; } public void setNumber(String number) { this.number = number; } public void setNS(String ns) { this.ns = ns; } public void setEW(String ew) { this.ew = ew; } public void setContract(String contract) { this.contract = contract; } public void setEWScore(double ewscore) { this.ewscore = ewscore; } public void setNSScore(double nsscore) { this.nsscore = nsscore; } public void setTricks(int tricks) { this.tricks = tricks; } public void setDeclarer(char declarer) { this.declarer = declarer; } public void setEWScore(String ewscore) throws HandParseException { if (0 < ewscore.length()) try { this.ewscore = Double.parseDouble(ewscore); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); String av = ewscore.toLowerCase(); if (av.startsWith("av") && av.length() == 3) switch (av.charAt(2)) { case '=': ewavetype = AVERAGE; break; case '+': ewavetype = AVERAGE_PLUS; break; case '-': ewavetype = AVERAGE_MINUS; break; default: throw new HandParseException(_("Invalid Average: ")+ewscore); } else throw new HandParseException(_("Invalid Score: ")+ewscore); } } public void setNSScore(String nsscore) throws HandParseException { if (0 < nsscore.length()) try { this.nsscore = Double.parseDouble(nsscore); } catch (NumberFormatException NFe) { if (Debug.debug) Debug.print(NFe); String av = nsscore.toLowerCase(); if (av.startsWith("av") && av.length() == 3) switch (av.charAt(2)) { case '=': nsavetype = AVERAGE; break; case '+': nsavetype = AVERAGE_PLUS; break; case '-': nsavetype = AVERAGE_MINUS; break; default: throw new HandParseException(_("Invalid Average: ")+nsscore); } else throw new HandParseException(_("Invalid Score: ")+nsscore); } } public void setForcedNSMP(boolean forced) { forced_nsmp = forced; } public void setForcedEWMP(boolean forced) { forced_ewmp = forced; } public void setForcedNSScore(boolean forced) { forced_nsscore = forced; } public void setForcedEWScore(boolean forced) { forced_ewscore = forced; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append(number); sb.append(": "); sb.append(ns); sb.append(_(" vs ")); sb.append(ew); sb.append(" "); sb.append(contract); sb.append(_(" by ")); sb.append(declarer); sb.append(_(" making ")); sb.append(tricks); sb.append(_(" tricks. ")); sb.append(nsscore); if (forced_nsscore) sb.append(_(" (forced)")); sb.append(_(" to NS ")); sb.append(ewscore); if (forced_ewscore) sb.append(_(" (forced)")); sb.append(_(" to EW ")); sb.append(nsmp); if (forced_nsmp) sb.append(_(" (forced)")); sb.append(_(" to NS ")); sb.append(ewmp); if (forced_ewmp) sb.append(_(" (forced)")); sb.append(_(" to EW.")); return sb.toString(); } public String[] export() { String[] rv = new String[10]; rv[0] = number; rv[1] = ns; rv[2] = ew; rv[3] = contract; rv[4] = ""+declarer; rv[5] = ""+tricks; DecimalFormat format = new DecimalFormat("0.##"); FieldPosition field = new FieldPosition(DecimalFormat.INTEGER_FIELD); StringBuffer tmp = new StringBuffer(); switch (nsavetype) { case AVERAGE: rv[6] = "av="; break; case AVERAGE_MINUS: rv[6] = "av-"; break; case AVERAGE_PLUS: rv[6] = "av+"; break; default: rv[6] = (forced_nsscore?"!":"") + format.format(nsscore, tmp, field).toString(); break; } tmp = new StringBuffer(); switch (ewavetype) { case AVERAGE: rv[7] = "av="; break; case AVERAGE_MINUS: rv[7] = "av-"; break; case AVERAGE_PLUS: rv[7] = "av+"; break; default: rv[7] = (forced_ewscore?"!":"") + format.format(ewscore, tmp, field).toString(); break; } tmp = new StringBuffer(); rv[8] = (forced_nsmp?"!":"") + format.format(nsmp, tmp, field).toString(); tmp = new StringBuffer(); rv[9] = (forced_ewmp?"!":"") + format.format(ewmp, tmp, field).toString(); return rv; } }