Passed
Branch dev (9bb1d9)
by Etienne
01:47
created

handlePhaseChange()   C

Complexity

Conditions 10

Size

Total Lines 76
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 41
dl 0
loc 76
rs 5.9999
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like de.pewpewproject.lasertag.lasertaggame.state.management.server.implementation.MusicalChairsManager.handlePhaseChange() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package de.pewpewproject.lasertag.lasertaggame.state.management.server.implementation;
2
3
import de.pewpewproject.lasertag.LasertagMod;
4
import de.pewpewproject.lasertag.common.types.Tuple;
5
import de.pewpewproject.lasertag.lasertaggame.settings.SettingDescription;
6
import de.pewpewproject.lasertag.lasertaggame.state.management.server.IMusicalChairsManager;
7
import de.pewpewproject.lasertag.lasertaggame.state.management.server.synced.*;
8
import de.pewpewproject.lasertag.lasertaggame.state.server.IMusicalChairsState;
9
import de.pewpewproject.lasertag.lasertaggame.state.server.implementation.MusicalChairsState;
10
import de.pewpewproject.lasertag.lasertaggame.state.synced.ITeamsConfigState;
11
import de.pewpewproject.lasertag.lasertaggame.state.synced.implementation.TeamsConfigState;
12
import de.pewpewproject.lasertag.lasertaggame.team.TeamDto;
13
import net.minecraft.server.world.ServerWorld;
14
15
import java.util.ArrayDeque;
16
import java.util.Queue;
17
import java.util.UUID;
18
19
/**
20
 * Implementation of IMusicalChairsManager for the server lasertag game
21
 *
22
 * @author Étienne Muser
23
 */
24
public class MusicalChairsManager implements IMusicalChairsManager {
25
26
    /**
27
     * The number of lasertag ticks since the last phase change.
28
     * Initialize with -1 as there will be a tick at 0 seconds
29
     * into the game.
30
     */
31
    private long ticksSinceLastPhaseChange = -1L;
32
33
    private final ISettingsManager settingsManager;
34
35
    private final ITeamsManager teamsManager;
36
37
    private final IScoreManager scoreManager;
38
39
    private final IGameModeManager gameModeManager;
40
41
    private final ITeamsConfigState teamsConfigState;
42
43
    private final IEliminationManager eliminationManager;
44
45
    private final ILasertargetsManager lasertargetsManager;
46
47
    private final ServerWorld world;
48
49
    private final IMusicalChairsState musicalChairsState;
50
51
    public MusicalChairsManager(ISettingsManager settingsManager,
52
                                ITeamsManager teamsManager,
53
                                IScoreManager scoreManager,
54
                                IGameModeManager gameModeManager,
55
                                ITeamsConfigState teamsConfigState,
56
                                IEliminationManager eliminationManager,
57
                                ILasertargetsManager lasertargetsManager,
58
                                ServerWorld world
59
    ) {
60
        this.settingsManager = settingsManager;
61
        this.teamsManager = teamsManager;
62
        this.scoreManager = scoreManager;
63
        this.gameModeManager = gameModeManager;
64
        this.teamsConfigState = teamsConfigState;
65
        this.eliminationManager = eliminationManager;
66
        this.lasertargetsManager = lasertargetsManager;
67
        this.world = world;
68
        this.musicalChairsState = new MusicalChairsState();
69
    }
70
71
72
    @Override
73
    public synchronized void tick() {
74
75
        LasertagMod.LOGGER.info("[MusicalChairsManager] Tick...");
76
77
        // Increment time since last phase change
78
        ++ticksSinceLastPhaseChange;
79
80
        // If the phase is now over
81
        if (ticksSinceLastPhaseChange >= settingsManager.<Long>get(SettingDescription.PHASE_DURATION)) {
82
83
            LasertagMod.LOGGER.info("[MusicalChairsManager] Phase end.");
84
85
            // Reset the time since last phase change
86
            ticksSinceLastPhaseChange = 0L;
87
88
            // Handle phase change
89
            handlePhaseChange();
90
        }
91
    }
92
93
    @Override
94
    public synchronized long getPlayerTotalScore(UUID playerUuid) {
95
        return musicalChairsState.getPlayerOverallScore(playerUuid);
96
    }
97
98
    @Override
99
    public synchronized void onPlayerScored(UUID playerUuid, long score) {
100
101
        // Get the old score
102
        var oldScore = musicalChairsState.getPlayerOverallScore(playerUuid);
103
104
        // Calculate the new score
105
        var newScore = oldScore + score;
106
107
        // Set the new score
108
        musicalChairsState.setPlayerOverallScore(playerUuid, newScore);
109
    }
110
111
    @Override
112
    public synchronized void reset() {
113
        ticksSinceLastPhaseChange = -1L;
114
        musicalChairsState.reset();
115
    }
116
117
    private void handlePhaseChange() {
118
119
        // Reset the already hit by state of the lasertargets lasertargets
120
        lasertargetsManager.resetAlreadyHitBy();
121
122
        // If the scores should be reset at the end of the phase
123
        if (settingsManager.<Boolean>get(SettingDescription.RESET_SCORES_AT_PHASE_END)) {
124
125
            scoreManager.resetScores();
126
        }
127
128
        // Get the non-eliminated teams
129
        var nonEliminatedTeams = teamsConfigState.getTeams().stream()
130
                // Filter out empty teams
131
                .filter(team -> !teamsManager.getPlayersOfTeam(team).isEmpty())
132
                // Filter out eliminated teams
133
                .filter(eliminationManager::isTeamNotEliminated)
134
                // Filter out the spectators
135
                .filter(team -> !team.equals(TeamsConfigState.SPECTATORS))
136
                .toList();
137
138
        var sb = new StringBuilder("[MusicalChairsManager] Remaining teams: [");
139
        nonEliminatedTeams.forEach(t -> sb.append(t.name()).append(", "));
140
        sb.append("]");
141
        LasertagMod.LOGGER.info(sb.toString());
142
143
        // Get the teams with the fewest amount of points
144
        var toBeEliminatedTeams = nonEliminatedTeams.stream()
145
                // Map to (Score, Team) tuple
146
                .map(team -> new Tuple<>(teamsManager.getPlayersOfTeam(team).stream()
147
                        .map(scoreManager::getScore).mapToLong(a -> a)
148
                        .sum(), team))
149
                // Collect the teams with the least amount of points
150
                .collect(ArrayDeque::new,
151
                        (Queue<Tuple<Long, TeamDto>> queue, Tuple<Long, TeamDto> tuple) -> {
152
                            if (!queue.isEmpty() && queue.peek().x().compareTo(tuple.x()) > 0) {
153
                                queue.clear();
154
                            }
155
                            if (queue.isEmpty() || queue.peek().x().equals(tuple.x())) {
156
                                queue.offer(tuple);
157
                            }
158
                        },
159
                        (left, right) -> {
160
                            if (left.peek().x().compareTo(right.peek().x()) > 0) {
161
                                left.clear();
162
                            }
163
                            if (left.isEmpty() || left.peek().x().equals(right.peek().x()))
164
                            {
165
                                left.addAll(right);
166
                            }
167
                        })
168
                // Map back to stream of teams
169
                .stream()
170
                .map(Tuple::y)
171
                .toList();
172
173
        // If all teams are about to be eliminated
174
        if (nonEliminatedTeams.size() == toBeEliminatedTeams.size()) {
175
176
            LasertagMod.LOGGER.info("[MusicalChairsManager] Tie between all teams present. No team gets eliminated");
177
            LasertagMod.LOGGER.info("Number of remaining teams: " + nonEliminatedTeams.size());
178
            LasertagMod.LOGGER.info("Number of to be eliminated teams: " + toBeEliminatedTeams.size());
179
180
            // Eliminate no team - tiebreaker
181
            return;
182
        }
183
184
        // Eliminate all the teams that need to be eliminated
185
        toBeEliminatedTeams.forEach(team -> {
186
187
            LasertagMod.LOGGER.info("[MusicalChairsManager] Eliminating team '" + team.name() + "'.");
188
            eliminationManager.eliminateTeam(team);
189
        });
190
191
        // Check if game ended
192
        gameModeManager.getGameMode().checkGameOver(world.getServer());
193
    }
194
}