-
-
Notifications
You must be signed in to change notification settings - Fork 988
/
callable_objects.cpp
402 lines (334 loc) · 13 KB
/
callable_objects.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
Copyright (C) 2009 - 2018 by Bartosz Waresiak <dragonking@o2.pl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
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 2 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.
See the COPYING file for more details.
*/
#include "ai/formula/ai.hpp"
#include "attack_prediction.hpp"
#include "game_board.hpp"
#include "ai/formula/callable_objects.hpp"
#include "ai/composite/contexts.hpp"
#include "resources.hpp"
#include "map/map.hpp"
#include "ai/game_info.hpp"
#include "ai/actions.hpp"
#include "units/formula_manager.hpp"
#include "units/unit.hpp"
#include "log.hpp"
#include "menu_events.hpp" // for fallback_ai_to_human_exception
static lg::log_domain log_formula_ai("ai/engine/fai");
#define DBG_AI LOG_STREAM(debug, log_formula_ai)
#define LOG_AI LOG_STREAM(info, log_formula_ai)
#define WRN_AI LOG_STREAM(warn, log_formula_ai)
#define ERR_AI LOG_STREAM(err, log_formula_ai)
namespace ai {
ai_context& get_ai_context(wfl::const_formula_callable_ptr for_fai) {
auto fai = std::dynamic_pointer_cast<const formula_ai>(for_fai);
assert(fai != nullptr);
return *std::const_pointer_cast<formula_ai>(fai)->ai_ptr_;
}
}
namespace wfl {
using namespace ai;
variant move_map_callable::get_value(const std::string& key) const
{
if(key == "moves") {
std::vector<variant> vars;
for(move_map::const_iterator i = srcdst_.begin(); i != srcdst_.end(); ++i) {
if( i->first == i->second || units_.count(i->second) == 0) {
auto item = std::make_shared<move_callable>(i->first, i->second);
vars.emplace_back(item);
}
}
return variant(vars);
} else if(key == "has_moves") {
return variant(!srcdst_.empty());
} else {
return variant();
}
}
void move_map_callable::get_inputs(formula_input_vector& inputs) const
{
add_input(inputs, "moves");
}
int move_callable::do_compare(const formula_callable* callable) const
{
const move_callable* mv_callable = dynamic_cast<const move_callable*>(callable);
if(mv_callable == nullptr) {
return formula_callable::do_compare(callable);
}
const map_location& other_src = mv_callable->src_;
const map_location& other_dst = mv_callable->dst_;
if (int cmp = src_.do_compare(other_src)) {
return cmp;
}
return dst_.do_compare(other_dst);
}
variant move_callable::execute_self(variant ctxt) {
ai_context& ai = get_ai_context(ctxt.as_callable());
move_result_ptr move_result = ai.execute_move_action(src_, dst_, true);
if(!move_result->is_ok()) {
LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
}
return variant(move_result->is_gamestate_changed());
}
int move_partial_callable::do_compare(const formula_callable* callable) const
{
const move_partial_callable* mv_callable = dynamic_cast<const move_partial_callable*>(callable);
if(mv_callable == nullptr) {
return formula_callable::do_compare(callable);
}
const map_location& other_src = mv_callable->src_;
const map_location& other_dst = mv_callable->dst_;
if (int cmp = src_.do_compare(other_src)) {
return cmp;
}
return dst_.do_compare(other_dst);
}
variant move_partial_callable::execute_self(variant ctxt) {
ai_context& ai = get_ai_context(ctxt.as_callable());
move_result_ptr move_result = ai.execute_move_action(src_, dst_, false);
if(!move_result->is_ok()) {
LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
}
return variant(move_result->is_gamestate_changed());
}
variant position_callable::get_value(const std::string& key) const {
if(key == "chance") {
return variant(chance_);
} else {
return variant();
}
}
void position_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "chance");
}
variant outcome_callable::get_value(const std::string& key) const {
if(key == "hitpoints_left") {
return variant(hitLeft_);
} else if(key == "probability") {
return variant(prob_);
} else if(key == "possible_status") {
return variant(status_);
} else {
return variant();
}
}
void outcome_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "hitpoints_left");
add_input(inputs, "probability");
add_input(inputs, "possible_status");
}
attack_callable::attack_callable(const map_location& move_from,
const map_location& src, const map_location& dst, int weapon)
: move_from_(move_from), src_(src), dst_(dst),
bc_(resources::gameboard->units(), src, dst, weapon, -1, 1.0, nullptr,
&*resources::gameboard->units().find(move_from))
{
type_ = ATTACK_C;
}
variant attack_callable::get_value(const std::string& key) const {
if(key == "attack_from") {
return variant(std::make_shared<location_callable>(src_));
} else if(key == "defender") {
return variant(std::make_shared<location_callable>(dst_));
} else if(key == "move_from") {
return variant(std::make_shared<location_callable>(move_from_));
} else {
return variant();
}
}
void attack_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "attack_from");
add_input(inputs, "defender");
add_input(inputs, "move_from");
}
int attack_callable::do_compare(const wfl::formula_callable* callable)
const {
const attack_callable* a_callable = dynamic_cast<const attack_callable*>(callable);
if(a_callable == nullptr) {
return formula_callable::do_compare(callable);
}
const map_location& other_from = a_callable->move_from();
if (int cmp = move_from_.do_compare(other_from)) {
return cmp;
}
const map_location& other_src = a_callable->src();
if (int cmp = src_.do_compare(other_src)) {
return cmp;
}
const map_location& other_dst = a_callable->dst();
if (int cmp = dst_.do_compare(other_dst)) {
return cmp;
}
const int other_weapon = a_callable->weapon();
if (int cmp = (this->weapon() - other_weapon)) {
return cmp;
}
const int other_def_weapon = a_callable->defender_weapon();
return this->defender_weapon() - other_def_weapon;
}
variant attack_callable::execute_self(variant ctxt) {
ai_context& ai = get_ai_context(ctxt.as_callable());
bool gamestate_changed = false;
move_result_ptr move_result;
if(move_from_ != src_) {
move_result = ai.execute_move_action(move_from_, src_, false);
gamestate_changed |= move_result->is_gamestate_changed();
if(!move_result->is_ok()) {
//move part failed
LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
}
}
if(!move_result || move_result->is_ok()) {
//if move wasn't done at all or was done successfully
attack_result_ptr attack_result = ai.execute_attack_action(src_, dst_, weapon());
gamestate_changed |= attack_result->is_gamestate_changed();
if(!attack_result->is_ok()) {
//attack failed
LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), attack_result->get_status()));
}
}
return variant(gamestate_changed);
}
variant attack_map_callable::get_value(const std::string& key) const {
if(key == "attacks") {
std::vector<variant> vars;
for(move_map::const_iterator i = ai_.get_srcdst().begin(); i != ai_.get_srcdst().end(); ++i) {
/* for each possible move check all adjacent tiles for enemies */
if(units_.count(i->second) == 0) {
collect_possible_attacks(vars, i->first, i->second);
}
}
/* special case, when unit moved toward enemy and can only attack */
for(unit_map::const_iterator i = resources::gameboard->units().begin(); i != resources::gameboard->units().end(); ++i) {
if (i->side() == ai_.get_side() && i->attacks_left() > 0) {
collect_possible_attacks(vars, i->get_location(), i->get_location());
}
}
return variant(vars);
} else {
return variant();
}
}
void attack_map_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "attacks");
}
/* add to vars all attacks on enemy units around <attack_position> tile. attacker_location is tile where unit is currently standing. It's moved to attack_position first and then performs attack.*/
void attack_map_callable::collect_possible_attacks(std::vector<variant>& vars, map_location attacker_location, map_location attack_position) const {
adjacent_loc_array_t adj;
get_adjacent_tiles(attack_position, adj.data());
for(unsigned n = 0; n < adj.size(); ++n) {
/* if adjacent tile is outside the board */
if (! resources::gameboard->map().on_board(adj[n]))
continue;
unit_map::const_iterator unit = units_.find(adj[n]);
/* if tile is empty */
if (unit == units_.end())
continue;
/* if tile is occupied by friendly or petrified/invisible unit */
if (!ai_.current_team().is_enemy(unit->side()) ||
unit->incapacitated() ||
unit->invisible(unit->get_location(), *resources::gameboard))
continue;
/* add attacks with default weapon */
auto item = std::make_shared<attack_callable>(attacker_location, attack_position, adj[n], -1);
vars.emplace_back(item);
}
}
variant recall_callable::get_value(const std::string& key) const {
if( key == "id")
return variant(id_);
if( key == "loc")
return variant(std::make_shared<location_callable>(loc_));
return variant();
}
void recall_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "id");
add_input(inputs, "loc");
}
variant recall_callable::execute_self(variant ctxt) {
ai_context& ai = get_ai_context(ctxt.as_callable());
recall_result_ptr recall_result = ai.check_recall_action(id_, loc_);
if(recall_result->is_ok()) {
recall_result->execute();
} else {
LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), recall_result->get_status()));
}
return variant(recall_result->is_gamestate_changed());
}
variant recruit_callable::get_value(const std::string& key) const {
if( key == "unit_type")
return variant(type_);
if( key == "recruit_loc")
return variant(std::make_shared<location_callable>(loc_));
return variant();
}
void recruit_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "unit_type");
add_input(inputs, "recruit_loc");
}
variant recruit_callable::execute_self(variant ctxt) {
ai_context& ai = get_ai_context(ctxt.as_callable());
recruit_result_ptr recruit_result = ai.check_recruit_action(type_, loc_);
//is_ok()==true means that the action is successful (eg. no unexpected events)
//is_ok() must be checked or the code will complain :)
if(recruit_result->is_ok()) {
recruit_result->execute();
} else {
LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function\n" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), recruit_result->get_status()));
}
//is_gamestate_changed()==true means that the game state was somehow changed by action.
//it is believed that during a turn, a game state can change only a finite number of times
return variant(recruit_result->is_gamestate_changed());
}
variant set_unit_var_callable::get_value(const std::string& key) const {
if(key == "loc")
return variant(std::make_shared<location_callable>(loc_));
if(key == "key")
return variant(key_);
if(key == "value")
return value_;
return variant();
}
void set_unit_var_callable::get_inputs(formula_input_vector& inputs) const {
add_input(inputs, "loc");
add_input(inputs, "key");
add_input(inputs, "value");
}
variant set_unit_var_callable::execute_self(variant ctxt) {
int status = 0;
unit_map::iterator unit;
unit_map& units = resources::gameboard->units();
/* if(!infinite_loop_guardian_.set_unit_var_check()) {
status = 5001; //exceeded nmber of calls in a row - possible infinite loop
} else*/ if((unit = units.find(loc_)) == units.end()) {
status = 5002; //unit not found
} else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) {
status = 5003;//unit does not belong to our side
}
if(status == 0) {
LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string() << "\n";
unit->formula_manager().add_formula_var(key_, value_);
return variant(true);
}
ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function" << std::endl;
return variant(std::make_shared<safe_call_result>(fake_ptr(), status));
}
variant fallback_callable::execute_self(variant) {
// We want give control of the side to human for the rest of this turn
throw fallback_ai_to_human_exception();
}
}