Passed
Branch master (3c0c2d)
by Vincent
11:07
created

generate(AI)   B

Complexity

Conditions 6

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0585

Importance

Changes 0
Metric Value
cc 6
eloc 20
dl 0
loc 35
ccs 15
cts 17
cp 0.8824
crap 6.0585
rs 8.4666
c 0
b 0
f 0
1
/*
2
 * This file is part of Araknemu.
3
 *
4
 * Araknemu is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * Araknemu is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with Araknemu.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2021 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight.ai.action.util;
21
22
import fr.arakne.utils.maps.CoordinateCell;
23
import fr.arakne.utils.maps.path.Decoder;
24
import fr.arakne.utils.maps.path.Path;
25
import fr.arakne.utils.maps.path.PathException;
26
import fr.arakne.utils.maps.path.Pathfinder;
27
import fr.quatrevieux.araknemu.game.fight.ai.AI;
28
import fr.quatrevieux.araknemu.game.fight.ai.action.ActionGenerator;
29
import fr.quatrevieux.araknemu.game.fight.map.FightCell;
30
import fr.quatrevieux.araknemu.game.fight.turn.action.Action;
31
32
import java.util.ArrayList;
33
import java.util.List;
34
import java.util.Optional;
35
import java.util.function.Predicate;
36
import java.util.function.ToDoubleFunction;
37
38
/**
39
 * Try to select the best move action
40
 *
41
 * - Select reachable (i.e. walkable cells in MP range) cells. Note : pathfinding is not performed, so it's not warranty that cells are really reachable
42
 * - Sort cell by the score (ascending, lower score cells selected first)
43
 * - Iterates selected cells
44
 * - Filter cells (using filter predicate)
45
 * - Try to find the path
46
 * - If a path is found, and can be performed by the current number of MPs, return the move action
47
 */
48
public final class Movement implements ActionGenerator {
49
    private final ToDoubleFunction<CoordinateCell<FightCell>> scoreFunction;
50
    private final Predicate<ScoredCell> filter;
51
52
    private Pathfinder<FightCell> pathfinder;
53
54
    /**
55
     * Creates the Movement action generator
56
     *
57
     * @param scoreFunction The score function. The returned score is used for select the best cell. Higher score are selected first.
58
     * @param filter The selection cell filter
59
     */
60 1
    public Movement(ToDoubleFunction<CoordinateCell<FightCell>> scoreFunction, Predicate<ScoredCell> filter) {
61 1
        this.scoreFunction = scoreFunction;
62 1
        this.filter = filter;
63 1
    }
64
65
    @Override
66
    public void initialize(AI ai) {
67 1
        this.pathfinder = new Decoder<>(ai.map()).pathfinder();
68 1
    }
69
70
    @Override
71
    public Optional<Action> generate(AI ai) {
72 1
        final int movementPoints = ai.turn().points().movementPoints();
73 1
        final List<ScoredCell> selectedCells = selectCells(ai, movementPoints);
74
75 1
        final CoordinateCell<FightCell> currentCell = ai.fighter().cell().coordinate();
76 1
        final ScoredCell currentCellScore = new ScoredCell(currentCell, scoreFunction.applyAsDouble(currentCell));
77 1
        final boolean currentCellIsValid = filter.test(currentCellScore);
78
79 1
        selectedCells.sort(ScoredCell::compareTo);
80
81 1
        for (ScoredCell cell : selectedCells) {
82 1
            if ((currentCellIsValid && currentCellScore.score() >= cell.score()) || !filter.test(cell)) {
83 1
                continue;
84
            }
85
86
            final Path<FightCell> path;
87
88
            try {
89 1
                path = pathfinder.findPath(ai.fighter().cell(), cell.coordinates.cell());
90
            } catch (PathException e) {
91
                // No valid path can be found
92
                continue;
93 1
            }
94
95
            // The path contains, as first step, the current cell.
96
            // So this steps must not be considered in MPs
97 1
            if (path.size() - 1 > movementPoints) {
98 1
                continue;
99
            }
100
101 1
            return Optional.of(ai.turn().actions().move().create(path));
102
        }
103
104 1
        return Optional.empty();
105
    }
106
107
    /**
108
     * Select all reachable cells for movement
109
     */
110
    private List<ScoredCell> selectCells(AI ai, int movementPoints) {
111 1
        final CoordinateCell<FightCell> currentCell = ai.fighter().cell().coordinate();
112 1
        final List<ScoredCell> selectedCells = new ArrayList<>();
113
114 1
        for (FightCell cell : ai.map()) {
115 1
            if (!cell.walkable()) {
116 1
                continue;
117
            }
118
119 1
            final CoordinateCell<FightCell> coordinates = cell.coordinate();
120
121 1
            if (coordinates.distance(currentCell) > movementPoints) {
122 1
                continue;
123
            }
124
125 1
            selectedCells.add(new ScoredCell(coordinates, scoreFunction.applyAsDouble(coordinates)));
126 1
        }
127
128 1
        return selectedCells;
129
    }
130
131
    public static final class ScoredCell implements Comparable<ScoredCell> {
132
        private final CoordinateCell<FightCell> coordinates;
133
        private final double score;
134
135 1
        public ScoredCell(CoordinateCell<FightCell> coordinates, double score) {
136 1
            this.coordinates = coordinates;
137 1
            this.score = score;
138 1
        }
139
140
        /**
141
         * Get the cell coordinates
142
         */
143
        public CoordinateCell<FightCell> coordinates() {
144 1
            return coordinates;
145
        }
146
147
        /**
148
         * Get the cell score
149
         * Higher score are prioritized
150
         */
151
        public double score() {
152 1
            return score;
153
        }
154
155
        @Override
156
        public int compareTo(ScoredCell o) {
157 1
            return Double.compare(o.score, score);
158
        }
159
    }
160
}
161