com.mycompany.wolf.Room.java Source code

Java tutorial

Introduction

Here is the source code for com.mycompany.wolf.Room.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.mycompany.wolf;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import com.google.gson.Gson;
import com.mycompany.work.framework.spring.SpringContext;
import com.mycompany.work.util.JsonUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.websocket.Session;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;

/**
 *
 * @author Administrator
 */
public class Room {

    private static final String VILLAGER = "villager";
    private static final String WOLF = "wolf";
    private static final String WITCH = "witch";
    private static final String HUNTER = "hunter";
    private static final String SEER = "seer";

    private static final long COMPETE_ROLE_DURATION = TimeUnit.SECONDS.toMillis(15);
    private static final long WOLVIES_KILL_VILLAGERS_DURATION = TimeUnit.SECONDS.toMillis(15);
    private static final long WITCH_SAVE_DURATION = TimeUnit.SECONDS.toMillis(15);
    private static final long HUNTER_KILL_DURATION = TimeUnit.SECONDS.toMillis(15);

    private final ScheduledExecutorService scheduledExecutorService = SpringContext
            .getBean(ScheduledExecutorService.class);

    public String roomId = UUID.randomUUID().toString();

    private final List<Session> sessions = new LinkedList<>();

    private final Map<String, RoleCompetition> competeRoles = new LinkedHashMap();
    private final Map<String, WolfVoting> wolfVotings = new LinkedHashMap<>();
    private final Map<String, WitchSaving> witchSavings = new LinkedHashMap<>();
    private final Map<String, WitchPoisoning> witchPoisonings = new LinkedHashMap<>();
    private final Map<String, HunterKilling> hunterKillings = new LinkedHashMap<>();
    private final Map<String, SeerForcasting> seerForcastings = new LinkedHashMap<>();
    private final Map<String, PlayerVoting> playerVotings = new LinkedHashMap<>();

    private String theVoted;
    private final Set<String> dead = new HashSet();
    private final Set<String> newlyDead = new HashSet();

    private int turnOffset;
    private int firstTurn;

    private final Object mutex = new Object();

    public void addPlayer(Session session) {
        synchronized (mutex) {
            sessions.add(session);
            List<Map<String, String>> roomInfo = sessions.stream()
                    .map(s -> ImmutableMap.of("playerId", getPlayerId(s)))
                    .collect(Collectors.toCollection(LinkedList::new));
            Map<String, Object> m = ImmutableMap.of("code", "enterResp", "properties",
                    ImmutableMap.of("roomInfo", roomInfo));
            String json = new Gson().toJson(m);
            sessions.stream().forEach(s -> {
                s.getAsyncRemote().sendText(json);
            });
        }
    }

    public void removePlayer(String playerId) throws IOException {
        synchronized (mutex) {
            sessions.removeIf(equalityPredicate(playerId));
            List<Map<String, String>> roomInfo = sessions.stream()
                    .map(s -> ImmutableMap.of("playerId", getPlayerId(s)))
                    .collect(Collectors.toCollection(LinkedList::new));
            Map<String, Object> m = ImmutableMap.of("code", "exitResp", "properties", roomInfo);
            String json = new Gson().toJson(m);
            sessions.stream().forEach(s -> {
                s.getAsyncRemote().sendText(json);
            });
        }
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(3000).setConnectTimeout(3000).build();
        CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig)
                .build();
        httpclient.start();
        try {
            String routerAddress = SpringContext.getBean("routerAddress");

            httpclient.execute(new HttpGet(routerAddress), new FutureCallback<HttpResponse>() {
                @Override
                public void completed(HttpResponse t) {
                }

                @Override
                public void failed(Exception excptn) {
                }

                @Override
                public void cancelled() {
                }
            });
        } finally {
            httpclient.close();
        }
    }

    public void prepare(String playerId, boolean flag) {
        Session session = sessions.stream().filter(s -> Objects.equals(getPlayerId(s), playerId)).findAny()
                .orElse(null);
        if (session != null) {
            session.getUserProperties().put("prepared", flag);
            Map<String, Object> m = ImmutableMap.of("code", "prepareResp", "properties",
                    ImmutableMap.of("playerId", playerId, "prepare", Boolean.toString(flag)));
            String json = new Gson().toJson(m);
            sessions.stream().forEach(s -> {
                s.getAsyncRemote().sendText(json);
            });
            if (sessions.size() == availableCount()
                    && sessions.stream().allMatch(s -> Boolean.TRUE.equals(isPrepared(s)))) {
                notifyCompeteRole();
            }
        }
    }

    public boolean contains(String playerId) {
        synchronized (mutex) {
            return sessions.stream().anyMatch(equalityPredicate(playerId));
        }
    }

    public int availableCount() {
        return 12;
    }

    public int count() {
        synchronized (mutex) {
            return sessions.size();
        }
    }

    public boolean isEmpty() {
        synchronized (mutex) {
            return sessions.isEmpty();
        }
    }

    /**
     * 
     */
    private void notifyCompeteRole() {
        Map<String, Object> m = ImmutableMap.of("code", "notifyCompeteRole", "properties",
                ImmutableMap.of("availableRoles", availableRoles()));
        String json = new Gson().toJson(m);
        sessions.stream().forEach(s -> {
            s.getAsyncRemote().sendText(json);
        });
        ScheduledFuture[] holder = new ScheduledFuture[1];
        holder[0] = scheduledExecutorService.schedule(() -> {
            holder[0].cancel(true);
            assignRoles();
            notifyWolvesKillVillagers();
        }, COMPETE_ROLE_DURATION, TimeUnit.MILLISECONDS);
    }

    public void competeRole(String playerId, String role) {
        competeRoles.put(playerId, new RoleCompetition(playerId, role));
    }

    private Collection<String> availableRoles() {
        return Arrays.asList("wolf", "witch");
    }

    /**
     * 
     */
    private void assignRoles() {
        Multiset<String> roleCounts = HashMultiset.create(roleCounts());
        Map<String, String> roleMap = new HashMap();
        competeRoles.values().stream().filter(c -> roleCounts.remove(c.role)).forEach(c -> {
            roleMap.put(c.playerId, c.role);
        });
        List<String> restPlayerId = sessions.stream().map(s -> getPlayerId(s)).filter(s -> !roleMap.containsKey(s))
                .collect(Collectors.toList());
        Collections.shuffle(restPlayerId);
        Iterator<String> restRoleIt = roleCounts.iterator();
        Iterator<String> restPlayerIdIt = restPlayerId.iterator();
        for (; restRoleIt.hasNext();) {
            String role = restRoleIt.next();
            String playerId = restPlayerIdIt.next();
            roleMap.put(playerId, role);
        }
        sessions.stream().forEach(s -> {
            s.getUserProperties().put("role", roleMap.get(getPlayerId(s)));
        });

        List<ImmutableMap<String, String>> assignedRoles = roleMap.entrySet().stream()
                .map(entry -> ImmutableMap.of("playerId", entry.getKey(), "role", entry.getValue()))
                .collect(Collectors.toCollection(LinkedList::new));
        Map<String, Object> assignRoles = ImmutableMap.of("code", "assignRoles", "properties", assignedRoles);
        String jsonText = JsonUtils.toString(assignRoles);
        sessions.stream().forEach(s -> {
            s.getAsyncRemote().sendText(jsonText);
        });
    }

    private Multiset<String> roleCounts() {
        return ImmutableMultiset.<String>builder().addCopies(HUNTER, 1).addCopies(WITCH, 1).addCopies(SEER, 1)
                .addCopies(WOLF, availableCount() / 3)
                .addCopies(VILLAGER, availableCount() - 3 - (availableCount() / 3)).build();
    }

    /**
     * ??
     */
    private void notifyWolvesKillVillagers() {
        wolfVotings.clear();
        witchPoisonings.clear();
        seerForcastings.clear();
        newlyDead.clear();

        Map<String, Object> assignRoles = ImmutableMap.of("code", "notifyWolvesKillVillagers");
        String jsonText = JsonUtils.toString(assignRoles);
        sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));
        ScheduledFuture[] holder = new ScheduledFuture[1];
        holder[0] = scheduledExecutorService.schedule(() -> {
            holder[0].cancel(true);
            notifyWitchSave();
        }, WOLVIES_KILL_VILLAGERS_DURATION, TimeUnit.MILLISECONDS);
    }

    public void wolfVote(Session session, String votedPlayerId) {
        final String playerId = getPlayerId(session);
        if (WOLF.equals(session.getUserProperties().get("role")) && !dead.contains(playerId)) {
            wolfVotings.put(playerId, new WolfVoting(votedPlayerId));
        }
    }

    private void notifyWitchSave() {
        ScheduledFuture[] holder = new ScheduledFuture[1];
        holder[0] = scheduledExecutorService.schedule(() -> {
            holder[0].cancel(true);
            notifyHunterKillIfDead();
        }, WITCH_SAVE_DURATION, TimeUnit.MILLISECONDS);

        /*
         *  
         */
        List<Map.Entry<String, Long>> top2 = wolfVotings.values().stream()
                .collect(Collectors.groupingBy(wolfVoting -> wolfVoting.playerId, Collectors.counting())).entrySet()
                .stream()
                .sorted(Comparator.comparingLong((Map.Entry<String, Long> entry) -> entry.getValue()).reversed())
                .limit(2).collect(Collectors.toList());
        if (top2.size() == 1 || (top2.size() > 1 && top2.get(0).getValue().compareTo(top2.get(1).getValue()) > 0)) {
            theVoted = top2.get(0).getKey();
        } else if (top2.size() > 1) {
            theVoted = top2.get(0).getKey();
        } else {
            List<String> undead = sessions.stream()
                    .filter(session -> !WOLF.equals(session.getUserProperties().get("role")))
                    .map(session -> getPlayerId(session)).filter(((Predicate<String>) dead::contains).negate())
                    .collect(Collectors.toList());
            theVoted = undead.get(new Random().nextInt(undead.size()));
        }
        newlyDead.add(theVoted);

        /*
         * ? 
         */
        witchPoisonings.values().stream().map(witchPoisoning -> witchPoisoning.playerId).forEach(newlyDead::add);

        Map<String, Object> notifyWolfVoted = ImmutableMap.of("code", "notifyWolfVoted", "properties",
                ImmutableMap.of("playerId", theVoted));
        String jsonText = JsonUtils.toString(notifyWolfVoted);
        sessions.stream().forEach(s -> {
            s.getAsyncRemote().sendText(jsonText);
        });
    }

    public void witchSave(Session session, String savedPlayerId) {
        final String playerId = getPlayerId(session);
        if (WITCH.equals(session.getUserProperties().get("role")) && !dead.contains(playerId)) {
            witchSavings.put(playerId, new WitchSaving(savedPlayerId));
        }
    }

    public void witchPoison(Session session, String poisonedPlayerId) {
        final String playerId = getPlayerId(session);
        if (WITCH.equals(session.getUserProperties().get("role")) && !dead.contains(playerId)) {
            witchPoisonings.put(playerId, new WitchPoisoning(poisonedPlayerId));
        }
    }

    /**
     * ?
     */
    private void notifyHunterKillIfDead() {
        if (contains(dead, HUNTER)) {
            hunterKillings.clear();

            Map<String, Object> assignRoles = ImmutableMap.of("code", "notifyHunterKill");
            String jsonText = JsonUtils.toString(assignRoles);
            sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));
            ScheduledFuture[] holder = new ScheduledFuture[1];
            holder[0] = scheduledExecutorService.schedule(() -> {
                holder[0].cancel(true);
                notifyDayBreak();
            }, HUNTER_KILL_DURATION, TimeUnit.MILLISECONDS);
        } else {
            notifyDayBreak();
        }
    }

    private boolean contains(Collection<String> playerIds, String role) {
        return playerIds.stream().anyMatch(pid -> {
            Session session = sessions.stream().filter(s -> Objects.equals(getPlayerId(s), pid)).findAny()
                    .orElse(null);
            if (session != null) {
                return Objects.equals(session.getUserProperties().get("role"), role);
            }
            return false;
        });
    }

    public void hunterKills(Session session, String killedPlayerId) {
        final String playerId = getPlayerId(session);
        if (HUNTER.equals(session.getUserProperties().get("role")) && !dead.contains(playerId)) {
            hunterKillings.put(playerId, new HunterKilling(killedPlayerId));
        }
    }

    public void seerForecasts(Session session, String forecastedPlayerId) {
        final String playerId = getPlayerId(session);
        if (SEER.equals(session.getUserProperties().get("role")) && !dead.contains(playerId)
                && !seerForcastings.containsKey(playerId) //???
        ) {
            seerForcastings.put(playerId, new SeerForcasting(forecastedPlayerId));
            Session forecastedSession = sessions.stream()
                    .filter(s -> Objects.equals(getPlayerId(s), forecastedPlayerId)).findAny().orElse(null);
            Map<String, Object> forecastResp = ImmutableMap.of("code", "seerForecastResp", "properties",
                    ImmutableMap.of("playerId", forecastedPlayerId, "role",
                            forecastedSession.getUserProperties().get("role")));
            String forecastRespJson = JsonUtils.toString(forecastResp);
            session.getAsyncRemote().sendText(forecastRespJson);
        }
    }

    private void notifyDayBreak() {
        hunterKillings.values().stream().map(hunterKilling -> hunterKilling.playerId).forEach(newlyDead::add);

        dead.addAll(newlyDead);

        firstTurn = IntStream.range(0, sessions.size())
                .filter(i -> Objects.equals(theVoted, getPlayerId(sessions.get(i)))).findAny().orElse(0);
        turnOffset = 0;

        Map<String, Object> notifyDead = ImmutableMap.of("code", "notifyDayBreak", "properties",
                ImmutableMap.of("dead", dead, "newlyDead", newlyDead));
        String jsonText = JsonUtils.toString(notifyDead);
        sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));

        notifyNextTurn();
    }

    /**
     * ????
     */
    private void notifyNextTurn() {
        Map<String, Object> nextPlayer = ImmutableMap.of("code", "notifyNextTurn", "properties",
                ImmutableMap.of("playerId", getPlayerId(sessions.get(turn()))));
        String jsonText = JsonUtils.toString(nextPlayer);
        sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));
        ScheduledFuture[] holder = new ScheduledFuture[1];
        holder[0] = scheduledExecutorService.schedule(() -> {
            holder[0].cancel(true);
            turnOffset++;
            if (turnOffset < sessions.size()) {
                notifyNextTurn();
            } else {
                notifyPlayersVote();
            }
        }, WOLVIES_KILL_VILLAGERS_DURATION, TimeUnit.MILLISECONDS);
    }

    public void enableMicrophone(Session session, boolean flag) {
        if (Objects.equals(getPlayerId(session), getPlayerId(sessions.get(turn())))) {
            Map<String, Object> enableMicrophone = ImmutableMap.of("code", "enableMicrophone", "properties", flag);
            String jsonText = JsonUtils.toString(enableMicrophone);
            sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));
        }
    }

    private void notifyPlayersVote() {
        playerVotings.clear();

        Map<String, Object> assignRoles = ImmutableMap.of("code", "notifyPlayersVote");
        String jsonText = JsonUtils.toString(assignRoles);
        sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));
        ScheduledFuture[] holder = new ScheduledFuture[1];
        holder[0] = scheduledExecutorService.schedule(() -> {
            holder[0].cancel(true);
            notifySomeoneBeVoted();
        }, WOLVIES_KILL_VILLAGERS_DURATION, TimeUnit.MILLISECONDS);
    }

    public void playerVote(Session session, String votedPlayerId) {
        final String playerId = getPlayerId(session);
        if (!dead.contains(playerId)) {
            playerVotings.put(playerId, new PlayerVoting(votedPlayerId));
        }
    }

    private void notifySomeoneBeVoted() {
        Collection<String> newlyDead = new LinkedHashSet<>();

        List<Map.Entry<String, Long>> top2 = playerVotings.values().stream()
                .collect(Collectors.groupingBy(playerVoting -> playerVoting.playerId, Collectors.counting()))
                .entrySet().stream()
                .sorted(Comparator.comparingLong((Map.Entry<String, Long> entry) -> entry.getValue()).reversed())
                .limit(2).collect(Collectors.toList());
        if (top2.size() == 1
                || (top2.size() == 2 && top2.get(0).getValue().compareTo(top2.get(1).getValue()) > 0)) {
            newlyDead.add(top2.get(0).getKey());
            this.dead.add(top2.get(0).getKey());

            if (sessions.stream().noneMatch(session -> WOLF.equals(session.getUserProperties().get("role")))) {
                wolvesLose();
            }
        }

        Map<String, Object> notifyDead = ImmutableMap.of("code", "notifyDead", "properties", newlyDead);
        String jsonText = JsonUtils.toString(notifyDead);
        sessions.stream().forEach(s -> s.getAsyncRemote().sendText(jsonText));

        notifyWolvesKillVillagers();
    }

    private int turn() {
        return (firstTurn + turnOffset) % sessions.size();
    }

    private static String getPlayerId(Session session) {
        return (String) session.getUserProperties().get("playerId");
    }

    private static Boolean isPrepared(Session session) {
        return (Boolean) session.getUserProperties().get("prepared");
    }

    private static Predicate<Session> equalityPredicate(String playerId) {
        return session -> Objects.equals(getPlayerId(session), playerId);
    }

    private void wolvesWin() {
    }

    private void wolvesLose() {
    }

    private static class RoleCompetition {
        private final String playerId, role;

        public RoleCompetition(String playerId, String role) {
            this.playerId = playerId;
            this.role = role;
        }
    }

    private static class WolfVoting {
        private final String playerId;

        public WolfVoting(String playerId) {
            this.playerId = playerId;
        }
    }

    private static class WitchSaving {
        private final String playerId;

        public WitchSaving(String playerId) {
            this.playerId = playerId;
        }
    }

    private static class WitchPoisoning {
        private final String playerId;

        public WitchPoisoning(String playerId) {
            this.playerId = playerId;
        }
    }

    private static class HunterKilling {
        private final String playerId;

        public HunterKilling(String playerId) {
            this.playerId = playerId;
        }
    }

    private static class SeerForcasting {
        private final String playerId;

        public SeerForcasting(String playerId) {
            this.playerId = playerId;
        }
    }

    private static class PlayerVoting {
        private final String playerId;

        public PlayerVoting(String playerId) {
            this.playerId = playerId;
        }
    }

}