/* * Copyright 2010 Chad Retz * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.statmantis.mport.retro.event; import java.util.ArrayList; import java.util.List; import org.statmantis.model.Base; import org.statmantis.model.FieldLocation; import org.statmantis.model.GamePlayer; import org.statmantis.model.GamePosition; import org.statmantis.model.PlayEvent; import org.statmantis.model.PlayModifier; import org.statmantis.model.PlayModifierType; import org.statmantis.model.PlayRunnerAdvancement; import org.statmantis.model.PlayType; import org.statmantis.model.Position; import org.statmantis.mport.retro.RetrosheetRuntimeException; /** * Utilities for play events * * @author Chad Retz */ final class PlayUtils { public static GamePlayer getPlayerByFielderLocation(int position, GameContext context) { Position pos = Position.fromRetroId(position); GamePlayer found = null; Integer lastInningFound = null; List<GamePlayer> players = context.isHomeTeamBatting() ? context.getCurrentGame().getVisitorInfo().getGamePlayers() : context.getCurrentGame().getHomeInfo().getGamePlayers(); for (GamePlayer player : players) { if (player.getPositions() != null) { for (GamePosition playerPos : player.getPositions()) { if (playerPos.getPosition() == pos && (lastInningFound == null || lastInningFound < playerPos.getBeginningInning())) { found = player; lastInningFound = playerPos.getBeginningInning(); } } } } if (found == null) { throw new RetrosheetRuntimeException("Unable to find " + (context.isHomeTeamBatting() ? "visitor" : "home") + " player at position " + pos); } return found; } public static PlayEvent buildPlayEvent(String evt, GameContext context) { PlayType type = null; //let's break off the secondary event String secondaryEvent = null; int plusSign = evt.indexOf('+'); if (plusSign != -1) { secondaryEvent = evt.substring(plusSign + 1); evt = evt.substring(0, plusSign); } //and break off those modifiers String mods = null; int firstSlash = evt.indexOf('/'); if (firstSlash != -1) { mods = evt.substring(firstSlash + 1); evt = evt.substring(0, firstSlash); } //TODO: really...iterate every one every // time w/ regex matching...really? for (PlayType playType : PlayType.values()) { if (evt.matches(playType.getRetroId())) { type = playType; break; } } if (type == null) { throw new RetrosheetRuntimeException( "Unable to find play type for " + evt); } PlayEvent event = new PlayEvent(); event.setPlayType(type); event.setFielders(new ArrayList<GamePlayer>()); event.setModifiers(new ArrayList<PlayModifier>()); switch (type) { case FLY_OUT: event.getFielders().add(getPlayerByFielderLocation( Integer.parseInt(evt.substring(0, 1)), context)); event.setOut(true); break; case GROUND_OUT: case GROUND_OUT_DOUBLE_PLAY: case LINE_OUT_DOUBLE_PLAY: case GROUND_OUT_TRIPLE_PLAY: case LINE_OUT_TRIPLE_PLAY: event.setOut(true); boolean nextCharBaseRunner = false; for (int i = 0; i < evt.length(); i++) { char chr = evt.charAt(i); if (chr == '(') { nextCharBaseRunner = true; } else if (nextCharBaseRunner) { nextCharBaseRunner = false; Base runningFrom = Base.BASES.get(String.valueOf(chr)); Base runningTo = runningFrom.nextBase(); GamePlayer baseRunner = context.getOnBase().get(runningFrom); if (baseRunner == null) { throw new RetrosheetRuntimeException( "Base runner not found: " + evt); } if (event.getPrimaryBaseRunner() == null) { event.setPrimaryRunningFrom(runningFrom); event.setPrimaryRunningTo(runningTo); event.setPrimaryBaseRunner(baseRunner); } else if (event.getSecondaryBaseRunner() == null) { event.setSecondaryRunningFrom(runningFrom); event.setSecondaryRunningTo(runningTo); event.setSecondaryBaseRunner(baseRunner); } else { event.setTertiaryRunningFrom(runningFrom); event.setTertiaryRunningTo(runningTo); event.setTertiaryBaseRunner(baseRunner); } } else if (chr != ')') { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf( chr)), context)); } } break; case INTERFERENCE: //do nothing? break; case SINGLE: case DOUBLE: case TRIPLE: for (int i = 1; i < evt.length(); i++) { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf( evt.charAt(i))), context)); } break; case GROUND_RULE_DOUBLE: //do nothing? break; case ERROR: case ERROR_FOUL_FLY: GamePlayer fielder = getPlayerByFielderLocation( Integer.valueOf(String.valueOf(evt.charAt(evt.length() - 1))), context); event.getFielders().add(fielder); //add an error modifier too PlayModifier mod = new PlayModifier(); mod.setFielder(fielder); mod.setType(PlayModifierType.ERROR); break; case FIELDERS_CHOICE: event.setOut(true); event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(evt.charAt(2))), context)); break; case HOME_RUN: event = new PlayEvent(); char endingChar = evt.charAt(evt.length() - 1); if (Character.isDigit(endingChar)) { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(endingChar)), context)); } break; case HIT_BY_PITCH: //do nothing? break; case STRIKE_OUT: for (int i = 1; i < evt.length(); i++) { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(evt.charAt(i))), context)); } break; //TODO: case NO_PLAY: case INTENTIONAL_WALK: case WALK: //do nothing? break; case BALK: //do nothing? break; case CAUGHT_STEALING: event.setPrimaryRunningTo(Base.BASES.get(String.valueOf(evt.charAt(2)))); event.setPrimaryRunningFrom(event.getPrimaryRunningTo().previousBase()); event.setPrimaryBaseRunner(context.getOnBase().get(event.getPrimaryRunningFrom())); event.setOut(true); for (int i = 4; i < evt.length() - 1; i++) { char chr = evt.charAt(i); if (Character.isDigit(chr)) { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(chr)), context)); } else { //TODO: figure out how to represent error here event.setOut(false); } } break; case DEFENSIVE_INDIFFERENCE: //do nothing? break; case RUNNER_ADVANCEMENT: //do nothing? break; case PASSED_BALL: case WILD_PITCH: //do nothing? break; case PICKOFF: case PICKOFF_CAUGHT_STEALING: int baseIndex = type == PlayType.PICKOFF ? 2 : 4; event.setPrimaryRunningFrom(Base.BASES.get(String.valueOf(evt.charAt(baseIndex)))); event.setPrimaryBaseRunner(context.getOnBase().get(event.getPrimaryRunningFrom())); event.setOut(true); for (int i = baseIndex + 2; i < evt.length() - 1; i++) { char chr = evt.charAt(i); if (Character.isDigit(chr)) { event.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(chr)), context)); } else { //TODO: figure out how to represent error here event.setOut(false); } } break; case STOLEN_BASE: event = new PlayEvent(); event.setPrimaryRunningTo(Base.BASES.get(String.valueOf(evt.charAt(2)))); event.setPrimaryRunningFrom(event.getPrimaryRunningTo().previousBase()); event.setPrimaryBaseRunner(context.getOnBase().get(event.getPrimaryRunningFrom())); //TODO: handle multiple of these as delimited by a ';' break; } //handle the secondary event if (secondaryEvent != null) { event.setSecondaryEvent(buildPlayEvent(secondaryEvent, context)); } //handle the mods if (mods != null) { event.setModifiers(new ArrayList<PlayModifier>()); for (String mod : mods.split("/")) { event.getModifiers().add(buildPlayModifier(mod, context)); } } return event; } public static PlayModifier buildPlayModifier(String mod, GameContext context) { PlayModifier modifier = new PlayModifier(); //first we try simple hit locations for (FieldLocation location : FieldLocation.values()) { if (location.equals(mod)) { modifier.setLocation(location); //and that's all she wrote return modifier; } } //now actual modifiers String modToMatch = mod; for (int i = 1; i < mod.length(); i++) { if (Character.isDigit(mod.charAt(i))) { modToMatch = mod.substring(0, i); break; } } if (modToMatch == null) { throw new RetrosheetRuntimeException("Unable to find modifier for " + mod); } for (PlayModifierType type : PlayModifierType.values()) { for (String possible : type.getPossibleNames()) { if (modToMatch.equals(possible)) { modifier.setType(type); break; } } } //now some of these have special stuff switch (modifier.getType()) { case ERROR: case RELAY_THROW: if (mod.length() == 2) { modifier.setFielder(getPlayerByFielderLocation(Integer.valueOf( String.valueOf(mod.charAt(1))), context)); } break; case THROW: if (mod.length() == 3) { modifier.setBase(Base.BASES.get(String.valueOf(mod.charAt(2)))); } break; default: //everything else may have a location at the end if (modToMatch.length() < mod.length()) { modifier.setLocation(FieldLocation.LOCATIONS.get( mod.substring(modToMatch.length()))); } } return modifier; } public static PlayRunnerAdvancement buildPlayRunnerAdvancement(String adv, GameContext context) { PlayRunnerAdvancement advancement = new PlayRunnerAdvancement(); boolean modifiersStarted = false; int lastOpeningParenIndex = -1; for (int i = 0; i < adv.length(); i++) { char chr = adv.charAt(i); if (chr == '(') { lastOpeningParenIndex = i; modifiersStarted = true; advancement.setFielders(new ArrayList<GamePlayer>()); advancement.setModifiers(new ArrayList<PlayModifier>()); } else if (!modifiersStarted) { if (chr == 'X') { advancement.setOut(true); } else if (chr != '-') { if (advancement.getBaseFrom() == null) { advancement.setBaseFrom(Base.BASES.get( String.valueOf(chr))); } else { advancement.setBaseTo(Base.BASES.get( String.valueOf(chr))); } } } else if (chr == ')') { for (String possible : adv.substring(lastOpeningParenIndex + 1, i).split("/")) { //I figure we handle digits as fielders until we handle a modifier for (int j = 0; j < possible.length(); j++) { char val = possible.charAt(j); if (Character.isDigit(val)) { advancement.getFielders().add(getPlayerByFielderLocation( Integer.valueOf(String.valueOf(val)), context)); } else { advancement.getModifiers().add(buildPlayModifier( possible.substring(j), context)); break; } } } } } return advancement; } private PlayUtils() { } }