Author: dhains
Date: Mon Mar 24 10:02:49 2008
New Revision: 25052
URL: http://svn.gna.org/viewcvs/wesnoth?rev=25052&view=rev
Log:
Support for formula scripts (only team formulas for now) added to WML. Formula
AI can now be written in a script completely seperate from WML and 'plugged'
into a WML scenario cfg.
For an example, the opening formula AI strategy was converted to a script
(opening.fai) and added to scenario-formula.cfg. Scripts are still pretty
rough looking, comments and parsing improvements will come shortly to pretty
them up.
The embedded Formula AI in WML is still supported as well.
Added:
trunk/data/opening.fai
Modified:
trunk/data/scenario-formula.cfg
trunk/src/formula.cpp
trunk/src/formula_ai.cpp
Added: trunk/data/opening.fai
URL:
http://svn.gna.org/viewcvs/wesnoth/trunk/data/opening.fai?rev=25052&view=auto
==============================================================================
--- trunk/data/opening.fai (added)
+++ trunk/data/opening.fai Mon Mar 24 10:02:49 2008
@@ -1,0 +1,75 @@
+def opening(ai*)
+ if(turn = 1, [
+ recruit('Skeleton Archer', loc(11,21)),
+ recruit('Dark Adept', loc(11,22)),
+ recruit('Dark Adept', loc(10,22)),
+ recruit('Skeleton Archer', loc(9,22)),
+ recruit('Ghost', loc(11,24)),
+ move(loc(11,23), loc(14,22)) ],
+ if(turn = 2, [
+ move(loc(11,21),loc(13,17)),
+ if(unit_at(loc(11,22)).total_movement = 6,
+ move(loc(11,22),loc(13,18)),
+ move(loc(11,22),loc(15,19))),
+ move(loc(10,22),loc(7,19)),
+ move(loc(9,22),loc(4,22)),
+ move(loc(11,24),loc(18,24)),
+ move(loc(14,22),loc(11,23)),
+ recruit('Dark Adept', loc(11,21)),
+ recruit('Dark Adept', loc(11,22)) ],
+ if(turn = 3, [
+ move(loc(18,24),loc(20,22)),
+ move(loc(15,19),loc(17,17)),
+ move(loc(4,22),loc(5,18)),
+ recruit('Skeleton Archer', loc(11,21)) ],
+ if(turn = 4, [
+ recruit('Skeleton Archer', loc(11,21)) ],
+ [])))); def rate_position_defensiveness(ai*,src,dst)
+ units_can_reach(ai.my_moves, dst).size -
units_can_reach(ai.enemy_moves, dst).size; def
rate_position_danger(ai*,unit,dst)
+ (100*sum(map(units_can_reach(ai.enemy_moves, dst), 'enemy',
max_possible_damage(enemy, unit)*defense_on(unit, dst))))
+ / unit.hitpoints; def build_attacks(attack_move)
+ if(attack_move,
+ map(attack_move.movements, attack(src, dst,
attack_move.target)),
+ []); def targets(ai)
+ ai.enemy_and_unowned_villages; def distance_to_target(ai,dst)
+ min(map(targets(ai), distance_between(dst, self))); def
move_to_targets(ai*)
+ find(moves, src != my_leader.loc and rate_position_defensiveness(ai,
src, dst) > 0)
+ where moves = sort(my_moves.moves, distance_to_target(ai,
a.dst) < distance_to_target(ai, b.dst)); def get_village_captures(ai)
+ sum(map(ai.enemy_and_unowned_villages, 'village',
map(units_can_reach(ai.my_moves, village), move(loc, village))), []); def
get_village_garrisons(ai*)
+ sum(map(my_villages, 'village', map(units_can_reach(ai.my_moves,
village), move(loc, village))), []); def uncontended_captures(ai)
+ filter(get_village_captures(ai), (src != ai.my_leader.loc) and
(units_can_reach(ai.enemy_moves, dst).empty)); def move_to_keep(ai*)
+ if(keep_in_range,
+ move(my_leader.loc, keep_in_range),
+ null())
+ where keep_in_range = find(unit_moves(my_leader.loc), 'moveto',
find(keeps, moveto = self)); def village_value(ai*) 400; def
rate_healing(unit*)
+ value * ((min(max_hitpoints - hitpoints, 8)*100)/max_hitpoints); def
rate_village_garrison(ai*, move_from, village)
+ if(find(enemy_moves, dst = village),
+ village_value(ai),
+ 0); def rate_village_capture(ai*,src,dst) village_value(ai); def
+ rate_village_proximity(ai*, unit, dst)
+ if(distance = 1,
+ 0,
+ village_value(ai)/(distance/unit.total_movement + 1))
+ where distance = distance_to_nearest_unowned_village(dst); def
rate_move(ai*,src,dst)
+ if(is_village(map, dst),
+ rate_healing(u) + if(find(my_villages, self = dst),
+ rate_village_garrison(ai, src, dst),
+ rate_village_capture(ai, src, dst)),
+ rate_village_proximity(ai, u, dst)) - (danger*u.value)/2
+ where u = unit_at(src), danger = rate_position_danger(ai,
unit_at(src), dst); def rate_attack(ai*,attack)
+ if(attack,
+ (attack.chance_to_kill*attack.target_value +
(((attack.avg_damage_inflicted*100)/unit_at(attack.target).max_hitpoints)*attack.target_value)
+ sum(map(attack.movements, rate_move(ai, src, dst))))/attack.movements.size,
+ null()); def get_best_move(ai*, candidate_moves)
+ if(rate_attack(ai, best_attack) >
+ if(best_move, rate_move(ai, best_move.src,
+ best_move.dst),
+ 0),
+ build_attacks(best_attack),
+ [best_move])
+ where best_attack = choose(attacks, rate_attack(ai, self)),
+ best_move = choose(candidate_moves, rate_move(ai, src, dst)); def
move_leader_to_keep(ai*)
+ if(dest,
+ [move(my_leader.loc, dest)],
+ [])
+ where dest = find(unit_moves(my_leader.loc), 'dst', find(keeps,
'keep', keep = dst)); if(vars.done_opening != 1, [set_var('done_opening', 1)] +
opening(self),
+ if(my_leader.loc and find(keeps, self = my_leader.loc) = null(),
move_leader_to_keep(self), []) + get_best_move(self, filter(my_moves.moves, src
!= my_leader.loc)) + [ recruit('Skeleton Archer', loc(11,21)), recruit('Dark
Adept', loc(11,22)) ])
Modified: trunk/data/scenario-formula.cfg
URL:
http://svn.gna.org/viewcvs/wesnoth/trunk/data/scenario-formula.cfg?rev=25052&r1=25051&r2=25052&view=diff
==============================================================================
--- trunk/data/scenario-formula.cfg (original)
+++ trunk/data/scenario-formula.cfg Mon Mar 24 10:02:49 2008
@@ -71,208 +71,9 @@
ai_algorithm=formula_ai
[ai]
- [function]
- name=make_func
- input=""
- formula="def adder(x,y) x + y"
- [/function]
- [function]
- name=opening
- inputs="ai"
- formula="
- if(ai.turn = 1,
- #turn 1
- [ recruit('Skeleton Archer', loc(11,21)),
- recruit('Dark Adept', loc(11,22)),
- recruit('Dark Adept', loc(10,22)),
- recruit('Skeleton Archer', loc(9,22)),
- recruit('Ghost', loc(11,24)),
- move(loc(11,23), loc(14,22)) ],
- if(ai.turn = 2,
- #turn 2
- [ move(loc(11,21),loc(13,17)),
- if(unit_at(loc(11,22)).total_movement = 6,
- move(loc(11,22),loc(13,18)),
- move(loc(11,22),loc(15,19))),
- move(loc(10,22),loc(7,19)),
- move(loc(9,22),loc(4,22)),
- move(loc(11,24),loc(18,24)),
- move(loc(14,22),loc(11,23)),
- recruit('Dark Adept', loc(11,21)),
- recruit('Dark Adept', loc(11,22)) ],
- if(ai.turn = 3,
- #turn 3
- [ move(loc(18,24),loc(20,22)),
- move(loc(15,19),loc(17,17)),
- move(loc(4,22),loc(5,18)),
- recruit('Skeleton Archer', loc(11,21)) ],
- if(ai.turn = 4,
- #turn 4
- [ recruit('Skeleton Archer', loc(11,21)) ],
- []))))"
- [/function]
-
- #this function is not used; is just an example of a
stub function
- #that might be expanded to have a full table of good
unit matchups
- [function]
- name=rate_unit_matchup
- inputs="unit,against"
- formula="switch(unit, 'Elvish Fighter',
- switch(against,
-
'Elvish Fighter', 0,
-
'Elvish Archer', -5,
-
'Elvish Shaman', 2,
-
'Elvish Scout', 2,
-
'Wose', -2,
-
0),
- 'Elvish
Archer',
-
switch(against,
-
'Elvish Fighter', 5,
-
'Elvish Archer', 0,
-
'Elvish Shaman', 2,
-
'Elvish Scout', 4,
-
'Wose', -8,
-
0),
- 0)"
- [/function]
-
- [function]
- name=rate_position_defensiveness
- inputs="ai*,src,dst"
- formula="units_can_reach(ai.my_moves, dst).size -
units_can_reach(ai.enemy_moves, dst).size"
- [/function]
-
- [function]
- name=rate_position_danger
- inputs="ai*,unit,dst"
- formula="(100*sum(map(units_can_reach(ai.enemy_moves, dst),
'enemy', max_possible_damage(enemy, unit)*chance_to_hit(unit, dst)))) /
unit.hitpoints"
- [/function]
-
- [function]
- name=build_attacks
- inputs=attack_move
- formula="if(attack_move, map(attack_move.movements,
attack(src, dst, attack_move.target)), [])"
- [/function]
-
- [function]
- name=targets
- inputs="ai"
- formula="ai.enemy_and_unowned_villages"
- [/function]
-
- [function]
- name=distance_to_target
- inputs="ai,dst"
- formula="min(map(targets(ai), distance_between(dst, self)))"
- [/function]
-
- [function]
- name=move_to_targets
- inputs=ai*
- formula="
- find(moves, src != my_leader.loc and
rate_position_defensiveness(ai, src, dst) > 0)
- where moves = sort(my_moves.moves,
distance_to_target(ai, a.dst) < distance_to_target(ai, b.dst))"
- # if(moves, choose(moves, -distance_to_target(ai, dst)),
null())
- # where moves = filter(my_moves.moves, src != my_leader.loc
and rate_position_defensiveness(ai, src, dst) > 0)
- [/function]
-
- [function]
- name=get_village_captures
- inputs="ai"
- formula="
- sum(map(ai.enemy_and_unowned_villages, 'village',
map(units_can_reach(ai.my_moves, village), move(loc, village))), [])"
- [/function]
-
- [function]
- name=get_village_garrisons
- inputs="ai*"
- formula="
- sum(map(my_villages, 'village',
map(units_can_reach(ai.my_moves, village), move(loc, village))), [])"
- [/function]
-
- [function]
- name=uncontended_captures
- inputs=ai
- formula="filter(get_village_captures(ai), (src !=
ai.my_leader.loc) and (units_can_reach(ai.enemy_moves, dst).empty))"
- [/function]
-
- [function]
- name=move_to_keep
- inputs="ai*"
- formula="if(keep_in_range, move(my_leader.loc, keep_in_range),
null())
- where keep_in_range =
find(unit_moves(my_leader.loc), 'moveto', find(keeps, moveto = self))"
- [/function]
-
- [function]
- name=village_value
- inputs="ai*"
- formula="400"
- [/function]
-
- [function]
- name=rate_healing
- inputs="unit*"
- formula="value * ((min(max_hitpoints - hitpoints,
8)*100)/max_hitpoints)"
- [/function]
-
- [function]
- name=rate_village_garrison
- inputs="ai*,move_from,village"
- formula="if(find(enemy_moves, dst = village),
village_value(ai), 0)"
- [/function]
-
- [function]
- name=rate_village_capture
- inputs="ai*,src,dst"
- formula="village_value(ai)"
- [/function]
-
- [function]
- name=rate_village_proximity
- inputs="ai*,unit,dst"
- precondition="ai and unit and unit.total_movement and dst"
- formula="if(distance = 1, 0,
village_value(ai)/(distance/unit.total_movement + 1))
- where distance =
distance_to_nearest_unowned_village(dst)"
- [/function]
-
- [function]
- name=rate_move
- inputs="ai*,src,dst"
- formula="if(is_village(map, dst), rate_healing(u) +
if(find(my_villages, self = dst), rate_village_garrison(ai, src, dst),
rate_village_capture(ai, src, dst)), rate_village_proximity(ai, u, dst)) -
(danger*u.value)/2
- where u = unit_at(src),
- danger = rate_position_danger(ai,
unit_at(src), dst)"
- [/function]
-
- [function]
- name=rate_attack
- inputs="ai*,attack"
- formula="if(attack, (attack.chance_to_kill*attack.target_value
+
(((attack.avg_damage_inflicted*100)/unit_at(attack.target).max_hitpoints)*attack.target_value)
+ sum(map(attack.movements, rate_move(ai, src, dst))))/attack.movements.size,
null())"
- [/function]
-
- [function]
- name=get_best_move
- inputs="ai*,candidate_moves"
- formula="if(rate_attack(ai, best_attack) > if(best_move,
rate_move(ai, best_move.src, best_move.dst), 0),
- build_attacks(best_attack), [best_move])
- where best_attack = choose(attacks,
rate_attack(ai, self)),
- best_move =
choose(candidate_moves, rate_move(ai, src, dst))"
- [/function]
-
- [function]
- name=move_leader_to_keep
- inputs="ai*"
- formula="
- if(dest, [move(my_leader.loc, dest)], [])
- where dest = find(unit_moves(my_leader.loc), 'dst',
find(keeps, 'keep', keep = dst))"
- [/function]
-
- # The AI entry point. Currently we just do our
hard-wired opening moves and then fallback to the
- # default C++ AI.
- move="if(vars.done_opening != 1, [set_var('done_opening', 1)] +
opening(self),
- if(my_leader.loc and find(keeps, self =
my_leader.loc) = null(), move_leader_to_keep(self), []) +
- get_best_move(self, filter(my_moves.moves, src !=
my_leader.loc)) +
- [ recruit('Skeleton Archer', loc(11,21)), recruit('Dark
Adept', loc(11,22)) ]
- )"
+ [team_formula]
+ rulebase=opening
+ [/team_formula]
[/ai]
[/side]
[/test]
Modified: trunk/src/formula.cpp
URL:
http://svn.gna.org/viewcvs/wesnoth/trunk/src/formula.cpp?rev=25052&r1=25051&r2=25052&view=diff
==============================================================================
--- trunk/src/formula.cpp (original)
+++ trunk/src/formula.cpp Mon Mar 24 10:02:49 2008
@@ -431,7 +431,12 @@
while((i1-> type != TOKEN_RPARENS) && (i1 != i2)) {
if(i1->type == TOKEN_IDENTIFIER) {
- res->push_back(std::string(i1->begin, i1->end));
+ if(std::string((i1+1)->begin, (i1+1)->end) == "*") {
+ res->push_back(std::string(i1->begin, i1->end)
+ std::string("*"));
+ ++i1;
+ } else {
+ res->push_back(std::string(i1->begin, i1->end));
+ }
} else if (i1->type == TOKEN_COMMA) {
//do nothing
} else {
Modified: trunk/src/formula_ai.cpp
URL:
http://svn.gna.org/viewcvs/wesnoth/trunk/src/formula_ai.cpp?rev=25052&r1=25051&r2=25052&view=diff
==============================================================================
--- trunk/src/formula_ai.cpp (original)
+++ trunk/src/formula_ai.cpp Mon Mar 24 10:02:49 2008
@@ -19,6 +19,7 @@
#include "formula_callable.hpp"
#include "formula_function.hpp"
#include "pathutils.hpp"
+#include "filesystem.hpp"
namespace {
using namespace game_logic;
@@ -550,6 +551,12 @@
ai_function_symbol_table function_table(*this);
const config& ai_param = current_team().ai_parameters();
+ config::const_child_itors team_formula =
ai_param.child_range("team_formula");
+ if(team_formula.first != team_formula.second) {
+ std::string rulebase = (**team_formula.first)["rulebase"];
+ const t_string& formula_string = read_file(std::string("data/")
+ rulebase + std::string(".fai"));
+ move_formula_ =
game_logic::formula::create_optional_formula(formula_string, &function_table);
+ } else {
config::const_child_itors functions = ai_param.child_range("function");
for(config::const_child_iterator i = functions.first; i !=
functions.second; ++i) {
const t_string& name = (**i)["name"];
@@ -562,7 +569,7 @@
recruit_formula_ =
game_logic::formula::create_optional_formula(current_team().ai_parameters()["recruit"],
&function_table);
move_formula_ =
game_logic::formula::create_optional_formula(current_team().ai_parameters()["move"],
&function_table);
-
+ }
//execute units formulas first
std::vector<gamemap::location> formula_unit_loc;
for(unit_map::const_iterator i = units_.begin(); i != units_.end();
++i) {
@@ -574,15 +581,15 @@
for(std::vector<gamemap::location>::const_iterator i =
formula_unit_loc.begin() ; i != formula_unit_loc.end() ; ++i)
{
- unit_map::const_iterator unit_it = units_.find(*i);
- if ( unit_it != units_.end() )
- {
- game_logic::const_formula_ptr formula(new
game_logic::formula(unit_it->second.get_formula(), &function_table));
- game_logic::map_formula_callable callable(this);
- callable.add_ref();
- callable.add("me", variant(new
unit_callable(*unit_it, current_team(), get_info().team_num)));
- make_move(formula, callable);
- }
+ unit_map::const_iterator unit_it = units_.find(*i);
+ if ( unit_it != units_.end() )
+ {
+ game_logic::const_formula_ptr formula(new
game_logic::formula(unit_it->second.get_formula(), &function_table));
+ game_logic::map_formula_callable callable(this);
+ callable.add_ref();
+ callable.add("me", variant(new unit_callable(*unit_it,
current_team(), get_info().team_num)));
+ make_move(formula, callable);
+ }
}
game_logic::map_formula_callable callable(this);
_______________________________________________
Wesnoth-commits mailing list
[email protected]
https://mail.gna.org/listinfo/wesnoth-commits