/** * Copyright (C) 2017 Jan Schäfer (jansch@users.sourceforge.net) * * 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.jskat.control.iss; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jskat.data.GameAnnouncement; import org.jskat.data.GameAnnouncement.GameAnnouncementFactory; import org.jskat.data.SkatGameData; import org.jskat.data.Trick; import org.jskat.data.iss.ChatMessage; import org.jskat.data.iss.GameStartInformation; import org.jskat.data.iss.MoveInformation; import org.jskat.data.iss.MovePlayer; import org.jskat.data.iss.MoveType; import org.jskat.data.iss.PlayerStatus; import org.jskat.data.iss.TablePanelStatus; import org.jskat.util.Card; import org.jskat.util.CardList; import org.jskat.util.GameType; import org.jskat.util.Player; import org.jskat.util.rule.SkatRule; import org.jskat.util.rule.SkatRuleFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Parses ISS messages */ public class MessageParser { private static Logger log = LoggerFactory.getLogger(MessageParser.class); /** * table .1 bar state --> was cut away before <br> * --> params: <br> * 3 <br> * bar xskat xskat:2 . <br> * bar . 0 0 0 0 0 0 1 0 <br> * xskat $ 0 0 0 0 0 0 1 1 <br> * xskat:2 $ 0 0 0 0 0 0 1 1 <br> * . . 0 0 0 0 0 0 0 0 false <br> */ static TablePanelStatus getTableStatus(final String loginName, final List<String> params) { final TablePanelStatus status = new TablePanelStatus(); status.setLoginName(loginName); status.setMaxPlayers(Integer.parseInt(params.get(0))); // get player status for (int i = 0; i < status.getMaxPlayers(); i++) { // parse only non empty seats if (!".".equals(params.get(i * 10 + 5))) { //$NON-NLS-1$ // there is player information final PlayerStatus playerStatus = parsePlayerStatus(params .subList(i * 10 + 5, i * 10 + 16)); // has player left already if (".".equals(params.get(i + 1))) { //$NON-NLS-1$ playerStatus.setPlayerLeft(true); } status.addPlayer(playerStatus.getName(), playerStatus); } } return status; } /** * table .1 bar state <br> * 3 <br> * bar xskat xskat:2 . <br> * bar . 0 0 0 0 0 0 1 0 <br> * --> params:<br> * xskat $ 0 0 0 0 0 0 1 1 <br> * xskat:2 $ 0 0 0 0 0 0 1 1 <br> * . . 0 0 0 0 0 0 0 0 false <br> */ private static PlayerStatus parsePlayerStatus(final List<String> params) { final PlayerStatus status = new PlayerStatus(); status.setName(params.get(0)); status.setIP(params.get(1)); status.setGamesPlayed(Integer.parseInt(params.get(2))); status.setGamesWon(Integer.parseInt(params.get(3))); status.setLastGameResult(Integer.parseInt(params.get(4))); status.setTotalPoints(Integer.parseInt(params.get(5))); status.setSwitch34(Integer.parseInt(params.get(6)) == 1); // ignore next information, unknown purpose status.setTalkEnabled(Integer.parseInt(params.get(8)) == 1); status.setReadyToPlay(Integer.parseInt(params.get(9)) == 1); return status; } static GameStartInformation getGameStartStatus(final String loginName, final List<String> params) { log.debug("game start parameter: " + params); //$NON-NLS-1$ final GameStartInformation status = new GameStartInformation(); status.setLoginName(loginName); status.setGameNo(Integer.parseInt(params.get(0))); status.putPlayerName(Player.FOREHAND, params.get(1)); status.putPlayerTime(Player.FOREHAND, Double.valueOf(params.get(2))); status.putPlayerName(Player.MIDDLEHAND, params.get(3)); status.putPlayerTime(Player.MIDDLEHAND, Double.valueOf(params.get(4))); status.putPlayerName(Player.REARHAND, params.get(5)); status.putPlayerTime(Player.REARHAND, Double.valueOf(params.get(6))); return status; } static MoveInformation getMoveInformation(final List<String> params) { final MoveInformation info = new MoveInformation(); getMovePlayer(params.get(0), info); // FIXME Unhandled moves final String move = params.get(1); log.debug("Move: " + move); //$NON-NLS-1$ if ("y".equals(move)) { //$NON-NLS-1$ // holding bid move info.setType(MoveType.HOLD_BID); } else if ("p".equals(move)) { //$NON-NLS-1$ // pass move info.setType(MoveType.PASS); } else if ("s".equals(move)) { //$NON-NLS-1$ // skat request move info.setType(MoveType.SKAT_REQUEST); } else if ("??.??".equals(move)) { //$NON-NLS-1$ // hidden skat given to a player info.setType(MoveType.PICK_UP_SKAT); } else if (move.startsWith("TI.")) { //$NON-NLS-1$ // time out for player info.setType(MoveType.TIME_OUT); info.setTimeOutPlayer(parseTimeOut(move)); } else if (move.equals("RE")) { //$NON-NLS-1$ // resigning of player info.setType(MoveType.RESIGN); } else if (move.startsWith("SC")) { //$NON-NLS-1$ // declarer shows cards info.setType(MoveType.SHOW_CARDS); if (move.length() > 2) { // declarer cards follow, SC could also stand allone info.setOuvertCards(parseSkatCards(move.substring(move .indexOf(".") + 1))); //$NON-NLS-1$ } } else if (move.startsWith("LE.")) { //$NON-NLS-1$ // one player left the table during the game info.setType(MoveType.LEAVE_TABLE); info.setLeavingPlayer(parseLeaveTable(move)); } else { // extensive parsing needed // test card move final Card card = Card.getCardFromString(move); if (card != null) { // card play move info.setType(MoveType.CARD_PLAY); info.setCard(card); } else { // card parsing failed // test bidding int bid = -1; try { bid = Integer.parseInt(move); } catch (final NumberFormatException e) { bid = -1; } if (bid > -1) { // bidding info.setType(MoveType.BID); info.setBidValue(Integer.parseInt(move)); } else { if (move.length() == 95) { // card dealing info.setType(MoveType.DEAL); info.setDealCards(parseCardDeal(move)); } else if (move.length() == 5) { // open skat given to a player info.setType(MoveType.PICK_UP_SKAT); info.setSkat(parseSkatCards(move)); } else { // game announcement info.setType(MoveType.GAME_ANNOUNCEMENT); parseGameAnnoucement(info, move); } } } } return info; } static void parsePlayerTimes(final List<String> params, final MoveInformation info) { // parse player times info.putPlayerTime(Player.FOREHAND, new Double(params.get(params.size() - 3))); info.putPlayerTime(Player.MIDDLEHAND, new Double(params.get(params.size() - 2))); info.putPlayerTime(Player.REARHAND, new Double(params.get(params.size() - 1))); } private static CardList parseSkatCards(final String move) { final StringTokenizer token = new StringTokenizer(move, "."); //$NON-NLS-1$ final CardList result = new CardList(); while (token.hasMoreTokens()) { result.add(Card.getCardFromString(token.nextToken())); } return result; } private static void getMovePlayer(final String movePlayer, final MoveInformation info) { log.debug("Move player: " + movePlayer); //$NON-NLS-1$ if ("w".equals(movePlayer)) { //$NON-NLS-1$ // world move info.setMovePlayer(MovePlayer.WORLD); } else if ("0".equals(movePlayer)) { //$NON-NLS-1$ // fore hand move info.setMovePlayer(MovePlayer.FOREHAND); } else if ("1".equals(movePlayer)) { //$NON-NLS-1$ // middle hand move info.setMovePlayer(MovePlayer.MIDDLEHAND); } else if ("2".equals(movePlayer)) { //$NON-NLS-1$ // rear hand move info.setMovePlayer(MovePlayer.REARHAND); } } /** * <game-type> :: (G | C | S | H | D | N) (type Grand .. Null) [O] (ouvert) * [H] (hand, not given if O + trump game) [S] (schneider announced, only in * H games, not if O or Z) [Z] (schwarz announced, only in H games) */ private static GameAnnouncement parseGameAnnoucement( final MoveInformation info, final String move) { final StringTokenizer annToken = new StringTokenizer(move, "."); //$NON-NLS-1$ final String gameTypeString = annToken.nextToken(); final GameAnnouncementFactory factory = GameAnnouncement.getFactory(); // at first the game type GameType gameType = null; if (gameTypeString.startsWith("G")) { //$NON-NLS-1$ gameType = GameType.GRAND; } else if (gameTypeString.startsWith("C")) { //$NON-NLS-1$ gameType = GameType.CLUBS; } else if (gameTypeString.startsWith("S")) { //$NON-NLS-1$ gameType = GameType.SPADES; } else if (gameTypeString.startsWith("H")) { //$NON-NLS-1$ gameType = GameType.HEARTS; } else if (gameTypeString.startsWith("D")) { //$NON-NLS-1$ gameType = GameType.DIAMONDS; } else if (gameTypeString.startsWith("N")) { //$NON-NLS-1$ gameType = GameType.NULL; } factory.setGameType(gameType); boolean handGame = false; boolean ouvertGame = false; boolean schwarzGame = false; // parse other game modifiers for (int i = 1; i < gameTypeString.length(); i++) { final char mod = gameTypeString.charAt(i); if (mod == 'O') { factory.setOuvert(Boolean.TRUE); ouvertGame = true; } else if (mod == 'H') { factory.setHand(Boolean.TRUE); handGame = true; } else if (mod == 'S') { factory.setSchneider(Boolean.TRUE); } else if (mod == 'Z') { factory.setSchwarz(Boolean.TRUE); schwarzGame = true; } } if (gameType != GameType.NULL && handGame && schwarzGame) { factory.setSchneider(true); } if (gameType != GameType.NULL && ouvertGame) { factory.setHand(true); handGame = true; } if (gameType != GameType.NULL && ouvertGame && handGame) { factory.setSchneider(true); factory.setSchwarz(true); } if (annToken.hasMoreTokens()) { CardList showedCards = new CardList(); while (annToken.hasMoreTokens()) { showedCards.add(Card.getCardFromString(annToken.nextToken())); } CardList discardedCards = new CardList(); CardList ouvertCards = new CardList(); if (handGame) { ouvertCards.addAll(showedCards); } else if (showedCards.size() == 2) { discardedCards.addAll(showedCards); } else { discardedCards.add(showedCards.get(0)); discardedCards.add(showedCards.get(1)); for (int i = 2; i < showedCards.size(); i++) { ouvertCards.add(showedCards.get(i)); } } info.setOuvertCards(ouvertCards); factory.setDiscardedCards(discardedCards); } final GameAnnouncement ann = factory.getAnnouncement(); info.setGameAnnouncement(ann); return ann; } private static List<CardList> parseCardDeal(final String move) { if (move.contains("|") && move.contains("??")) { //$NON-NLS-1$ return parseCardDealFromISSMessage(move); } return parseCardDealFromSummary(move); } /** * During playing on ISS the deal is send in the following format<br /> * ??.??.??.??.??.??.??.??.??.??|D9.S9.ST.S8.C9.DT.DQ.CJ.SA.HA|??.??.??.??.? * ?.??.??.??.??.??|??.??<br /> * ?? - hidden card<br /> * fore hand cards|middle hand cards|rear hand cards|skat */ private static List<CardList> parseCardDealFromISSMessage(final String move) { final StringTokenizer handTokens = new StringTokenizer(move, "|"); //$NON-NLS-1$ final List<CardList> result = new ArrayList<CardList>(); while (handTokens.hasMoreTokens()) { result.add(parseHand(handTokens.nextToken())); } return result; } /** * In game summary the deal is send in a different format<br /> * CQ.H7.C8.C9.CT.SQ.DK.H8.H9.SA.S9.HK.HQ.DJ.CJ.S8.DT.HT.ST.D7.CK.S7.C7.DQ. * SJ.HA.CA.DA.D8.D9.SK.HJ<br /> * no hidden cards<br /> * first 10 cards fore hand<br /> * next 10 cards middle hand<br /> * next 10 cards rear hand<br /> * last 2 cards skat * * @param move */ private static List<CardList> parseCardDealFromSummary(final String move) { final List<CardList> result = new ArrayList<CardList>(); // fore hand result.add(parseHand(move.substring(0, 29))); // middle hand result.add(parseHand(move.substring(30, 59))); // rear hand result.add(parseHand(move.substring(60, 89))); // skat result.add(parseHand(move.substring(90))); return result; } private static CardList parseHand(final String hand) { final StringTokenizer cardTokens = new StringTokenizer(hand, "."); //$NON-NLS-1$ final CardList result = new CardList(); while (cardTokens.hasMoreTokens()) { result.add(Card.getCardFromString(cardTokens.nextToken())); } return result; } /** * TI.0 */ private static Player parseTimeOut(final String timeOut) { return extractPlayer(timeOut); } /** * LE.0 */ private static Player parseLeaveTable(final String leaveTable) { return extractPlayer(leaveTable); } private static Player extractPlayer(final String playerAtLastCharacter) { Player result = null; switch (playerAtLastCharacter.charAt(3)) { case '0': result = Player.FOREHAND; break; case '1': result = Player.MIDDLEHAND; break; case '2': result = Player.REARHAND; break; } return result; } static SkatGameData parseGameSummary(final String gameSummary) { final SkatGameData result = new SkatGameData(); final Pattern summaryPartPattern = Pattern.compile("(\\w+)\\[(.*?)\\]"); //$NON-NLS-1$ final Matcher summaryPartMatcher = summaryPartPattern .matcher(gameSummary); while (summaryPartMatcher.find()) { log.debug(summaryPartMatcher.group()); final String summaryPartMarker = summaryPartMatcher.group(1); final String summeryPart = summaryPartMatcher.group(2); parseSummaryPart(result, summaryPartMarker, summeryPart); } return result; } private static void parseSummaryPart(final SkatGameData result, final String summaryPartMarker, final String summaryPart) { if ("P0".equals(summaryPartMarker)) { //$NON-NLS-1$ result.setPlayerName(Player.FOREHAND, summaryPart); } else if ("P1".equals(summaryPartMarker)) { //$NON-NLS-1$ result.setPlayerName(Player.MIDDLEHAND, summaryPart); } else if ("P2".equals(summaryPartMarker)) { //$NON-NLS-1$ result.setPlayerName(Player.REARHAND, summaryPart); } else if ("MV".equals(summaryPartMarker)) { //$NON-NLS-1$ parseMoves(result, summaryPart); } else if ("R".equals(summaryPartMarker)) { //$NON-NLS-1$ parseGameResult(result, summaryPart); } } private static void parseMoves(final SkatGameData result, final String summaryPart) { // FIXME (jansch 12.02.2012) parse moves correctly final GameAnnouncementFactory factory = GameAnnouncement.getFactory(); factory.setGameType(GameType.PASSED_IN); result.setAnnouncement(factory.getAnnouncement()); final StringTokenizer token = new StringTokenizer(summaryPart); while (token.hasMoreTokens()) { final List<String> moveToken = new ArrayList<String>(); moveToken.add(token.nextToken()); moveToken.add(token.nextToken()); final MoveInformation moveInfo = getMoveInformation(moveToken); switch (moveInfo.getType()) { case DEAL: for (final Player player : Player.values()) { result.addDealtCards(player, moveInfo.getCards(player)); } result.setDealtSkatCards(moveInfo.getSkat()); break; case BID: result.addPlayerBid(moveInfo.getPlayer(), moveInfo.getBidValue()); break; case HOLD_BID: result.addPlayerBid(moveInfo.getPlayer(), result.getMaxBidValue()); break; case PASS: result.setPlayerPass(moveInfo.getPlayer(), true); break; case GAME_ANNOUNCEMENT: result.setAnnouncement(moveInfo.getGameAnnouncement()); if (!moveInfo.getGameAnnouncement().isHand()) { result.setDiscardedSkat(moveInfo.getPlayer(), moveInfo .getGameAnnouncement().getDiscardedCards()); } break; case CARD_PLAY: if (result.getTricks().size() == 0) { // no tricks played so far result.addTrick(new Trick(0, Player.FOREHAND)); } else if (result.getCurrentTrick().getThirdCard() != null) { // last card of trick is played // set trick winner result.getCurrentTrick().setTrickWinner( moveInfo.getPlayer()); // create next trick result.addTrick(new Trick(result.getTricks().size(), moveInfo.getPlayer())); } result.addTrickCard(moveInfo.getCard()); if (result.getTricks().size() == 10 && result.getCurrentTrick().getThirdCard() != null) { // set the trick winner of the last trick final SkatRule skatRules = SkatRuleFactory .getSkatRules(result.getGameType()); result.setTrickWinner(9, skatRules.calculateTrickWinner( result.getGameType(), result.getCurrentTrick())); } break; } } } private static void parseGameResult(final SkatGameData result, final String summaryPart) { final StringTokenizer token = new StringTokenizer(summaryPart); while (token.hasMoreTokens()) { parseResultToken(result, token.nextToken()); } } private static void parseResultToken(final SkatGameData gameData, final String token) { // from ISS source code // return "d:"+declarer + (penalty ? " penalty" : (declValue > 0 ? " // win" : " loss")) // + " v:" + declValue // + " m:" + matadors + (overbid ? " overbid" : " bidok") // + " p:" + declCardPoints + " t:" + declTricks // + " s:" + (schneider ? '1' : '0') + " z:" + (schwarz ? '1' : // '0') // + " p0:" + penalty0 + " p1:" + penalty1 + " p2:" + penalty2 // + " l:" + this.left + " to:" + this.timeout + " r:" + (resigned // ? '1' : '0'); if (token.startsWith("d:")) { //$NON-NLS-1$ parseDeclarerToken(gameData, token); } else if ("penalty".equals(token)) { //$NON-NLS-1$ // FIXME (jan 07.12.2010) handle this token } else if ("loss".equals(token)) { //$NON-NLS-1$ gameData.getResult().setWon(false); } else if ("win".equals(token)) { //$NON-NLS-1$ gameData.getResult().setWon(true); } else if (token.startsWith("v:")) { //$NON-NLS-1$ gameData.getResult().setGameValue( Integer.parseInt(token.substring(2))); } else if (token.startsWith("p:")) { //$NON-NLS-1$ final int declarerPoints = Integer.parseInt(token.substring(2)); gameData.setDeclarerScore(declarerPoints); gameData.getResult().setFinalDeclarerPoints(declarerPoints); gameData.getResult().setFinalOpponentPoints(120 - declarerPoints); } else if ("overbid".equals(token)) { //$NON-NLS-1$ gameData.getResult().setOverBidded(true); } else if ("s:1".equals(token)) { //$NON-NLS-1$ gameData.getResult().setSchneider(true); } else if ("z:1".equals(token)) { //$NON-NLS-1$ gameData.getResult().setSchwarz(true); } } private static void parseDeclarerToken(final SkatGameData result, final String token) { if ("d:0".equals(token)) { //$NON-NLS-1$ result.setDeclarer(Player.FOREHAND); } else if ("d:1".equals(token)) { //$NON-NLS-1$ result.setDeclarer(Player.MIDDLEHAND); } else if ("d:2".equals(token)) { //$NON-NLS-1$ result.setDeclarer(Player.REARHAND); } } /** * table .5 foo tell foo asdf jklö<br> * <br> * table .5 foo tell --> was cut before<br> * params: foo -> first is talker<br> * remainings is the chat message<br> * asdf jklö */ static ChatMessage getTableChatMessage(final String tableName, final List<String> detailParams) { final StringBuffer text = new StringBuffer(); text.append(detailParams.get(0)).append(": "); //$NON-NLS-1$ for (int i = 1; i < detailParams.size(); i++) { text.append(detailParams.get(i)).append(' '); } return new ChatMessage(tableName, text.toString()); } }