morphy.service.SeekService.java Source code

Java tutorial

Introduction

Here is the source code for morphy.service.SeekService.java

Source

/*
 *   Morphy Open Source Chess Server
 *   Copyright (C) 2016-2017 http://code.google.com/p/morphy-chess-server/
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *   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, see <http://www.gnu.org/licenses/>.
 */
package morphy.service;

import morphy.game.Seek;
import morphy.game.params.GameParams;
import morphy.game.request.MatchRequest;
import morphy.user.SocketChannelUserSession;
import morphy.user.UserSession;
import morphy.user.UserVars;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
 * Created by John on 09/23/2016.
 */
public class SeekService implements Service {
    public static final int MAX_SEEKS_PER_USER = 3;

    protected static Log LOG = LogFactory.getLog(SeekService.class);

    private static final SeekService singletonInstance = new SeekService();

    public static SeekService getInstance() {
        return singletonInstance;
    }

    private Integer highestSeekIndex = 0;
    private Stack<Integer> seekIndicesStack = new Stack<Integer>();
    private Map<Integer, Seek> seekMap;

    public SeekService() {
        this.seekMap = new HashMap<Integer, Seek>();
    }

    private int getNextAvailableSeekIndex() {
        if (seekIndicesStack.size() == 0) {
            seekIndicesStack.add(++highestSeekIndex);
        }
        return seekIndicesStack.pop();
    }

    public int registerSeek(Seek seek) {
        int seekIndex = this.getNextAvailableSeekIndex();
        seek.setSeekIndex(seekIndex);

        this.seekMap.put(seekIndex, seek);
        return this.notifyAllSeekers(seek);
    }

    public Seek[] getSeeksByUsername(String username) {
        List<Seek> seeksByUsername = new ArrayList<Seek>(MAX_SEEKS_PER_USER);
        Collection<Seek> allSeeks = this.seekMap.values();
        for (Seek seek : allSeeks) {
            if (username.equalsIgnoreCase(seek.getUserSession().getUser().getUserName())) {
                seeksByUsername.add(seek);
            }
        }
        return seeksByUsername.toArray(new Seek[seeksByUsername.size()]);
    }

    public void removeSeeksByUsername(String username) {
        Seek[] userSeeks = this.getSeeksByUsername(username);
        for (Seek seek : userSeeks) {
            this.unseek(seek.getSeekIndex());
        }
    }

    public boolean unseek(int seekIndex) {
        if (this.seekMap.containsKey(seekIndex)) {
            this.seekMap.remove(seekIndex);

            this.seekIndicesStack.add(seekIndex);
            return true;
        } else {
            return false;
        }
    }

    public Seek getSeekByIndex(int seekIndex) {
        if (this.seekMap.containsKey(seekIndex)) {
            return this.seekMap.get(seekIndex);
        } else {
            return null;
        }
    }

    public Seek[] findMatchingSeeks(Seek seek) {
        final Seek[] allSeeks = this.getAllSeeks();
        List<Seek> matchingSeeks = new ArrayList<Seek>();
        for (int i = 0; i < allSeeks.length; i++) {
            Seek curSeek = allSeeks[i];

            if (curSeek.getSeekIndex() == seek.getSeekIndex()) {
                continue;
            }

            GameService.UsersCannotPlayReason reason = GameService.getInstance()
                    .verifyUserCanPlay(seek.getUserSession(), curSeek.getUserSession());
            if (reason != GameService.UsersCannotPlayReason.NONE) {
                continue;
            }

            if (this.areSeeksIdentical(curSeek, seek)) {
                matchingSeeks.add(curSeek);
            }
        }
        return matchingSeeks.toArray(new Seek[matchingSeeks.size()]);
    }

    public boolean areSeeksIdentical(Seek a, Seek b) {
        return a.getSeekParams().getTime() == b.getSeekParams().getTime()
                && a.getSeekParams().getIncrement() == b.getSeekParams().getIncrement()
                && a.getSeekParams().getVariant() == b.getSeekParams().getVariant();
    }

    private int notifyAllSeekers(Seek seek) {
        int numSentTo = 0;

        UserService s = UserService.getInstance();
        UserSession[] arr = s.fetchAllUsersWithVariable("seek", "1");
        String line = generateSeekLine(seek);
        for (UserSession sess : arr) {
            // TODO: get rid of this downcast
            SocketChannelUserSession userSession = (SocketChannelUserSession) sess;

            if (seek.getUserSession() == userSession
                    && userSession.getUser().getUserVars().getVariables().get("showownseek").equals("0")) {
                continue;
            }

            if (userSession.isExamining() || userSession.isPlaying()) {
                // it looks like FICS does not send seek=1 message to people who are currently playing or examining.
                continue;
            }

            // TODO: add formula, rating checks

            userSession.send(line);
            numSentTo++;
        }
        return numSentTo;
    }

    private String generateSeekLine(Seek seek) {
        //GuestWNKT (++++) seeking 10 0 unrated blitz ("play 100" to respond)
        //GuestWNKT (++++) seeking 0 0 unrated untimed ("play 100" to respond)

        String variantName = seek.getSeekParams().getVariant().name();
        if (seek.getSeekParams().getTime() == 0 && seek.getSeekParams().getIncrement() == 0) {
            variantName = "untimed";
        }

        String seekFlags = "";
        if (seek.isUseManual()) {
            seekFlags += " m";
        }
        if (seek.isUseFormula()) {
            seekFlags += " f";
        }

        String seekLine = String.format("%s (++++) seeking %d %d %s %s%s (\"play %d\" to respond)",
                seek.getUserSession().getUser().getUserName(), seek.getSeekParams().getTime(),
                seek.getSeekParams().getIncrement(), seek.getSeekParams().isRated() ? "rated" : "unrated",
                variantName, seekFlags, seek.getSeekIndex());

        return seekLine;
    }

    public void tryAcceptSeek(UserSession userSession, Seek seek) {
        this.tryAcceptSeek(userSession, seek, null, null);
    }

    /**
     *
     * @param userSession
     * @param seek
     * @param messageToSendBuilder
     * @param messageToSendOtherPlayerBuilder
     * @return
     */
    public void tryAcceptSeek(UserSession userSession, Seek seek, StringBuilder messageToSendBuilder,
            StringBuilder messageToSendOtherPlayerBuilder) {
        if (GameService.getInstance().verifyUserCanPlay(userSession,
                seek.getUserSession()) != GameService.UsersCannotPlayReason.NONE) {
            return;
        }

        SeekService seekService = SeekService.getInstance();
        final UserSession otherPlayerUserSession = seek.getUserSession();

        if (messageToSendBuilder == null) {
            messageToSendBuilder = new StringBuilder();
        }

        if (messageToSendOtherPlayerBuilder == null) {
            messageToSendOtherPlayerBuilder = new StringBuilder();
        }

        if (seek.isUseManual()) {
            // TODO: be able to reuse code in MatchCommand... then we probably would not really need any of this...

            String infoLine = String.format("%s (%s)%s %s (%s) %s %s", userSession.getUser().getUserName(), "----",
                    (seek.getSeekParams().getColorRequested() != GameParams.ColorRequested.Neither
                            ? " [" + seek.getSeekParams().getColorRequested().getOppositeColor().name() + "]"
                            : ""),
                    otherPlayerUserSession.getUser().getUserName(), "----",
                    seek.getSeekParams().isRated() ? "rated" : "unrated", buildVariantString(seek));

            //
            // Send message to the initiating user
            //

            messageToSendBuilder.append("Issuing match request since the seek was set to manual.\n");
            messageToSendBuilder.append(String.format("Issuing: %s", infoLine));
            userSession.send(messageToSendBuilder.toString());

            //
            // Send message to the user that posted the seek
            //

            UserVars otherPlayerUserVars = otherPlayerUserSession.getUser().getUserVars();
            messageToSendOtherPlayerBuilder
                    .append(String.format("%s accepts your seek.\n\n", userSession.getUser().getUserName()));
            messageToSendOtherPlayerBuilder.append(String.format("Challenge: %s.", infoLine));
            messageToSendOtherPlayerBuilder
                    .append(String.format("\nYou can \"accept\" or \"decline\", or propose different parameters.%s",
                            (otherPlayerUserVars.getVariables().get("bell").equals("1") ? ((char) 7) : "")));
            otherPlayerUserSession.send(messageToSendOtherPlayerBuilder.toString());

            MatchRequest matchRequest = new MatchRequest(userSession, otherPlayerUserSession, seek.getSeekParams());
            matchRequest.setExtraInfo(infoLine);
            RequestService.getInstance().addRequest(userSession, otherPlayerUserSession, matchRequest);

        } else {
            //
            // remove all seeks for both players, they are starting a game.
            //
            seekService.removeSeeksByUsername(userSession.getUser().getUserName());
            seekService.removeSeeksByUsername(otherPlayerUserSession.getUser().getUserName());

            // prepended message is only sent to the other player
            messageToSendOtherPlayerBuilder
                    .append(String.format("%s accepts your seek.\n\n", userSession.getUser().getUserName()));

            //
            // start the game.
            //
            GameService gameService = GameService.getInstance();
            gameService.createGame(userSession, otherPlayerUserSession, seek.getSeekParams(), messageToSendBuilder,
                    messageToSendOtherPlayerBuilder);
        }
    }

    /*
     * used in tryAcceptSeek()
     */
    protected String buildVariantString(Seek seek) {
        // ... unrated untimed
        // ... unrated standard 999 999

        int timeMinutes = seek.getSeekParams().getTime();
        int incSeconds = seek.getSeekParams().getIncrement();

        if (timeMinutes == 0 && incSeconds == 0) {
            return "untimed";
        } else {
            return String.format("%s %d %d", seek.getSeekParams().getVariant().name(), timeMinutes, incSeconds);
        }
    }

    public Seek[] getAllSeeks() {
        Collection<Seek> seekCollection = this.seekMap.values();
        return seekCollection.toArray(new Seek[seekCollection.size()]);
    }

    public void dispose() {
        this.seekMap.clear();
        this.seekIndicesStack.clear();
        this.highestSeekIndex = null;

        if (LOG.isInfoEnabled()) {
            LOG.info("SeekService disposed.");
        }
    }
}