Passed
Pull Request — master (#193)
by Vincent
11:12
created

initialize(AI)   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
eloc 3
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-2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.araknemu.game.fight.ai.action;
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.map.FightCell;
29
import fr.quatrevieux.araknemu.game.fight.turn.action.Action;
30
31
import java.util.ArrayList;
32
import java.util.List;
33
import java.util.Optional;
34
import java.util.function.Predicate;
35
import java.util.function.ToDoubleFunction;
36
37
/**
38
 * Try to select the best move action
39
 *
40
 * - 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
41
 * - Sort cell by the score (ascending, lower score cells selected first)
42
 * - Iterates selected cells
43
 * - Filter cells (using filter predicate)
44
 * - Try to find the path
45
 * - If a path is found, and can be performed by the current number of MPs, return the move action
46
 */
47
public final class Movement implements ActionGenerator {
48
    private final ToDoubleFunction<CoordinateCell<FightCell>> scoreFunction;
49
    private final Predicate<ScoredCell> filter;
50
51
    private Pathfinder<FightCell> pathfinder;
52
53
    /**
54
     * Creates the Movement action generator
55
     *
56
     * @param scoreFunction The score function. The returned score is used for select the best cell. Higher score are selected first.
57
     * @param filter The selection cell filter
58
     */
59 1
    public Movement(ToDoubleFunction<CoordinateCell<FightCell>> scoreFunction, Predicate<ScoredCell> filter) {
60 1
        this.scoreFunction = scoreFunction;
61 1
        this.filter = filter;
62 1
    }
63
64
    @Override
65
    public void initialize(AI ai) {
66 1
        this.pathfinder = new Decoder<>(ai.map()).pathfinder();
67 1
    }
68
69
    @Override
70
    public Optional<Action> generate(AI ai) {
71 1
        final int movementPoints = ai.turn().points().movementPoints();
72 1
        final List<ScoredCell> selectedCells = selectCells(ai, movementPoints);
73
74 1
        final CoordinateCell<FightCell> currentCell = ai.fighter().cell().coordinate();
75 1
        final ScoredCell currentCellScore = new ScoredCell(currentCell, scoreFunction.applyAsDouble(currentCell));
76 1
        final boolean currentCellIsValid = filter.test(currentCellScore);
77
78 1
        selectedCells.sort(ScoredCell::compareTo);
79
80 1
        for (ScoredCell cell : selectedCells) {
81 1
            if ((currentCellIsValid && currentCellScore.score() >= cell.score()) || !filter.test(cell)) {
82 1
                continue;
83
            }
84
85
            final Path<FightCell> path;
86
87
            try {
88 1
                path = pathfinder.findPath(ai.fighter().cell(), cell.coordinates.cell());
89
            } catch (PathException e) {
90
                // No valid path can be found
91
                continue;
92 1
            }
93
94
            // The path contains, as first step, the current cell.
95
            // So this steps must not be considered in MPs
96 1
            if (path.size() - 1 > movementPoints) {
97 1
                continue;
98
            }
99
100 1
            return Optional.of(ai.turn().actions().move().create(path));
101
        }
102
103 1
        return Optional.empty();
104
    }
105
106
    /**
107
     * Select all reachable cells for movement
108
     */
109
    private List<ScoredCell> selectCells(AI ai, int movementPoints) {
110 1
        final CoordinateCell<FightCell> currentCell = ai.fighter().cell().coordinate();
111 1
        final List<ScoredCell> selectedCells = new ArrayList<>();
112
113 1
        for (FightCell cell : ai.map()) {
114 1
            if (!cell.walkable()) {
115 1
                continue;
116
            }
117
118 1
            final CoordinateCell<FightCell> coordinates = cell.coordinate();
119
120 1
            if (coordinates.distance(currentCell) > movementPoints) {
121 1
                continue;
122
            }
123
124 1
            selectedCells.add(new ScoredCell(coordinates, scoreFunction.applyAsDouble(coordinates)));
125 1
        }
126
127 1
        return selectedCells;
128
    }
129
130
    public static final class ScoredCell implements Comparable<ScoredCell> {
131
        private final CoordinateCell<FightCell> coordinates;
132
        private final double score;
133
134 1
        public ScoredCell(CoordinateCell<FightCell> coordinates, double score) {
135 1
            this.coordinates = coordinates;
136 1
            this.score = score;
137 1
        }
138
139
        /**
140
         * Get the cell coordinates
141
         */
142
        public CoordinateCell<FightCell> coordinates() {
143 1
            return coordinates;
144
        }
145
146
        /**
147
         * Get the cell score
148
         * Higher score are prioritized
149
         */
150
        public double score() {
151 1
            return score;
152
        }
153
154
        @Override
155
        public int compareTo(ScoredCell o) {
156 1
            return Double.compare(o.score, score);
157
        }
158
    }
159
}
160