/* $Id$ */ /* Copyright (C) 2003 - 2009 by David White 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 version 2 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. */ /** * @file ai_python.cpp * Interface to python for ai-scripts. * * Note this file isn't C-style cast safe: * PyObject_NEW this macro contains 2 casts * Py_DECREF this macro contains 3 casts * Py_InitModule3 this macro contains 1 cast * Py_XDECREF this macro contains 3 casts */ /* Note about Python exceptions: Originally, runtime exceptions were used for things like using out of bounds coordinates or moving a unit which already got killed, and were supposed to be catched by scripts. The subset of the Python language allowed in scripts now has no exceptions anymore, to keep things simple. Therefore the API was changed so scripts are not supposed to catch exceptions anymore. This means, invalid positions or units will now only be indicated by things like return values, but are ignored otherwise. Most API functions still will cause exceptions (like if the game ends, or the wrong number of parameters is passed to a function) - but those are not supposed to be catched by user scripts. */ #ifdef HAVE_PYTHON #include "global.hpp" #include "ai.hpp" #include "ai_python.hpp" #include "attack_prediction.hpp" #include "gamestatus.hpp" #include "filesystem.hpp" #include "random.hpp" #include "log.hpp" #include "map.hpp" #include "game_end_exceptions.hpp" #include "game_events.hpp" #include "game_config.hpp" #include "settings.hpp" #include "game_preferences.hpp" #include #include #include #define LOG_AI LOG_STREAM(info, ai) #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 4 #define Py_ssize_t int #endif static python_ai* running_instance; bool python_ai::init_ = false; // Appeace gcc 2.4 which doesn't want us pass inline strings to the Python API. #define CC(x) const_cast(x) #define STRINGVALUE CC("s") #define INTVALUE CC("i") #define DOUBLEVALUE CC("d") #define NOVALUE CC("") #define OBVALUE CC("O!") #define GSDEF(x, func, doc) \ {CC(x), reinterpret_cast(func), NULL,\ CC(doc), NULL }, static PyObject* wrap_unittype(const unit_type& type); static PyObject* wrap_attacktype(const attack_type& type); static PyObject* wrapper_unittype_resistance_against(wesnoth_unittype* type, PyObject* args); static PyObject* wrapper_unittype_get_name(wesnoth_unittype* unit, void* /*closure*/) { return Py_BuildValue(STRINGVALUE, unit->unit_type_->id().c_str()); } #define ut_get_general( t, n, x ) \ static PyObject* wrapper_unittype_get_##n( wesnoth_unittype* type, void* /*closure*/ ) \ { \ return Py_BuildValue(t,type->unit_type_->x); \ } #define ut_get( x ) \ ut_get_general(INTVALUE, x, x() ) #define ut_get_ability( x ) \ ut_get_general(INTVALUE, x, has_ability(#x)) #define ut_get_ability_by_id( x ) \ ut_get_general(INTVALUE, x, has_ability_by_id(#x)) ut_get_ability( heals ) ut_get_ability( regenerate ) ut_get_ability( leadership ) ut_get_ability( illuminates ) ut_get_ability( skirmisher ) ut_get_ability( teleport ) ut_get_ability_by_id( curing ) ut_get_ability_by_id( steadfast ) ut_get( not_living ) ut_get( can_advance ) ut_get( has_zoc ) ut_get( level ) ut_get( movement ) ut_get( cost ) ut_get( alignment ) ut_get( hitpoints ) static PyObject* wrapper_unittype_get_usage( wesnoth_unittype* type, void* /*closure*/ ) { return Py_BuildValue(STRINGVALUE, type->unit_type_->usage().c_str()); } #define ut_gs( x, doc ) \ {CC(#x), reinterpret_cast(wrapper_unittype_get_##x), NULL, CC(doc), NULL}, static PyGetSetDef unittype_getseters[] = { ut_gs(name, "Name of the unit type." ) ut_gs(heals, "If type can heal others (either healing or curing ability)." ) ut_gs(curing, "If type has curing ability (remove poison from others)." ) ut_gs(regenerate, "If type has regenerate ability." ) ut_gs(leadership, "If type has leadership ability." ) ut_gs(illuminates, "If type has illuminates ability." ) ut_gs(skirmisher, "If type has skirmisher ability." ) ut_gs(teleport, "If type has teleport ability." ) ut_gs(steadfast, "If type has steadfast ability." ) ut_gs(not_living, "If type has not-living ability." ) ut_gs(can_advance, "If type can advance." ) ut_gs(has_zoc, "If type has a ZOC." ) ut_gs(level, "Level of the type." ) ut_gs(hitpoints, "Hitpoints of the type." ) ut_gs(usage, "AI's usage hint of the type, one of: 'archer', 'fighter', " "'healer', 'mixed fighter', 'scout'." ) ut_gs(movement, "Movement points of the type." ) ut_gs(cost, "Cost of the type." ) ut_gs(alignment, "Alignment of the type: 0=lawful, 1=neutral, 2=chaotic." ) { NULL, NULL, NULL, NULL, NULL } }; PyObject* python_ai::unittype_advances_to( wesnoth_unittype* type, PyObject* args ) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; PyObject* list = PyList_New(type->unit_type_->advances_to().size()); int r; for (size_t advance = 0; advance < type->unit_type_->advances_to().size(); advance++) { std::map::const_iterator t = unit_type_data::types().find_unit_type(type->unit_type_->advances_to()[advance]); assert(t != unit_type_data::types().end()); r = PyList_SetItem(list,advance,wrap_unittype(t->second)); } return list; } static PyObject* wrapper_unittype_attacks( wesnoth_unittype* type, PyObject* args ) { if ( !PyArg_ParseTuple( args, NOVALUE ) ) return NULL; const std::vector& attacks = type->unit_type_->attacks(); PyObject* list = PyList_New(attacks.size()); for ( size_t attack = 0; attack < attacks.size(); attack++) PyList_SetItem(list,attack,wrap_attacktype(attacks[attack])); return list; } #define MDEF(name, func, docs) { CC(name), reinterpret_cast(func),\ METH_VARARGS, CC(docs) }, static PyMethodDef unittype_methods[] = { MDEF("advances_to", python_ai::unittype_advances_to, "Returns: unittype[]\n" "Returns a list of wesnoth.unittype of possible advancements.") MDEF("attacks", wrapper_unittype_attacks, "Returns: attacktype[]\n" "Returns list of possible attack types.\n") MDEF( "movement_cost", python_ai::wrapper_unittype_movement_cost, "Parameters: location\n" "Returns: cost\n" "Returns the cost of moving over the given location.") MDEF( "defense_modifier", python_ai::wrapper_unittype_defense_modifier, "Parameters: location\n" "Returns: percent\n" "Returns the defense modifier in % (probability the unit will be hit)" " on the given location.") MDEF("damage_from", wrapper_unittype_resistance_against, "Parameters: attacktype\n" "Returns: percent\n" "Returns the damage in percent a unit of this type receives when " "attacked with the given attack type. (0 means no damage at all, 100 " "means full damage, 200 means double damage.)") { NULL, NULL, 0, NULL } }; static int unittype_internal_compare(wesnoth_unittype* left, wesnoth_unittype* right) { return reinterpret_cast(left->unit_type_) - reinterpret_cast(right->unit_type_); } static PyTypeObject wesnoth_unittype_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.unittype"), /* tp_name*/ sizeof(wesnoth_unittype), /* tp_basicsize*/ 0, /* tp_itemsize*/ 0, /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ reinterpret_cast (unittype_internal_compare), /* tp_compare*/ 0, /* tp_repr*/ 0, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Describes a unit type."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ unittype_methods, /* tp_methods */ 0, /* tp_members */ unittype_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; static PyObject* wrap_unittype(const unit_type& type) { wesnoth_unittype* wrap = reinterpret_cast(PyObject_NEW(wesnoth_unittype, &wesnoth_unittype_type)); if (wrap) wrap->unit_type_ = &type; return reinterpret_cast(wrap); } typedef struct { PyObject_HEAD const attack_type* attack_type_; } wesnoth_attacktype; #define at_get_general( t, n, x ) \ static PyObject* attacktype_get_##n(wesnoth_attacktype* type, void* /*closure*/) \ { \ return Py_BuildValue(t,type->attack_type_->x); \ } #define at_get( t, x ) \ at_get_general( t, x, x() ) #define at_get_ability_by_id(x) \ at_get_general(INTVALUE, x, has_special_by_id(#x)) #define at_get_string_prop( x ) \ at_get_general(STRINGVALUE, x, x().c_str() ) at_get(INTVALUE, damage ) at_get(INTVALUE, num_attacks ) at_get(DOUBLEVALUE, attack_weight ) at_get(DOUBLEVALUE, defense_weight ) at_get_ability_by_id(backstab ) at_get_ability_by_id(slow ) at_get_ability_by_id(berserk ) at_get_ability_by_id(stones ) at_get_ability_by_id(plague ) at_get_ability_by_id(marksman ) at_get_ability_by_id(magical ) at_get_ability_by_id(charge ) at_get_ability_by_id(drains ) at_get_ability_by_id(poison ) at_get_string_prop( name ) at_get_string_prop( range ) static void wesnoth_attacktype_dealloc(wesnoth_attacktype* self) { delete self->attack_type_; self->ob_type->tp_free(reinterpret_cast(self)); } #define at_gs( x, doc ) \ {CC(#x), reinterpret_cast(attacktype_get_##x), NULL, CC(doc), NULL}, static PyGetSetDef attacktype_getseters[] = { at_gs( name, "Name of the attack." ) at_gs( damage, "Attack damage." ) at_gs( num_attacks, "Number of hits." ) at_gs( attack_weight, "AI setting, floating point number." ) at_gs( defense_weight, "AI setting, floating point number." ) at_gs( backstab, "This attack has backstab." ) at_gs( slow, "This attack causes slow." ) at_gs( berserk, "This attack uses berserk." ) at_gs( stones, "This attack has 'stones' special." ) at_gs( plague, "This attack has 'plague' special." ) at_gs( marksman, "This attack has 'marksman' special." ) at_gs( magical, "This attack is magical." ) at_gs( charge, "This attack has the 'charge' special." ) at_gs( drains, "This attack has the 'drains' special." ) at_gs( poison, "This attack has the 'poison' special." ) at_gs( range, "String with the name of the attack range." ) { NULL, NULL, NULL, NULL, NULL } }; static PyMethodDef attacktype_methods[] = { { NULL, NULL, 0, NULL } }; static PyTypeObject wesnoth_attacktype_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.attacktype"), /* tp_name*/ sizeof(wesnoth_attacktype), /* tp_basicsize*/ 0, /* tp_itemsize*/ reinterpret_cast (wesnoth_attacktype_dealloc), /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ 0, /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Describes an attack type."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ attacktype_methods, /* tp_methods */ 0, /* tp_members */ attacktype_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; static PyObject* wrap_attacktype(const attack_type& type) { wesnoth_attacktype* attack; attack = static_cast(PyObject_NEW(wesnoth_attacktype, &wesnoth_attacktype_type)); attack->attack_type_ = new attack_type(type); return reinterpret_cast(attack); } #define u_check if (!running_instance->is_unit_valid(unit->unit_)) Py_RETURN_NONE bool python_ai::is_unit_valid(const unit* unit) { bool ret = false ; if (unit) { Py_BEGIN_ALLOW_THREADS for(unit_map::const_iterator i = running_instance->get_info().units.begin(); \ i != running_instance->get_info().units.end(); ++i) { if (unit == &i->second) { ret = true ; break ; } } Py_END_ALLOW_THREADS } return ret ; } static PyObject* unit_get_name(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(STRINGVALUE, lexical_cast(unit->unit_->underlying_id()).c_str()); } static PyObject* unit_is_enemy(wesnoth_unit* unit, void* /*closure*/) { u_check; bool enemy ; Py_BEGIN_ALLOW_THREADS enemy = running_instance->current_team().is_enemy( unit->unit_->side() ) ; Py_END_ALLOW_THREADS return PyBool_FromLong( enemy ) ; } static PyObject* unit_can_recruit(wesnoth_unit* unit, void* /*closure*/) { u_check; return PyBool_FromLong( unit->unit_->can_recruit() ) ; } static PyObject* unit_side(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, unit->unit_->side()); } static PyObject* unit_movement_left(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, unit->unit_->movement_left()); } static PyObject* unit_max_movement(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, unit->unit_->total_movement()); } static PyObject* unit_can_attack(wesnoth_unit* unit, void* /*closure*/) { u_check; bool bValue ; Py_BEGIN_ALLOW_THREADS bValue = unit->unit_->attacks_left() ; Py_END_ALLOW_THREADS return PyBool_FromLong( bValue ) ; } static PyObject* unit_hitpoints(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, static_cast(unit->unit_->hitpoints())); } static PyObject* unit_max_hitpoints(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, static_cast(unit->unit_->max_hitpoints())); } static PyObject* unit_experience(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, static_cast(unit->unit_->experience())); } static PyObject* unit_max_experience(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, static_cast(unit->unit_->max_experience())); } static PyObject* unit_poisoned(wesnoth_unit* unit, void* /*closure*/) { u_check; return Py_BuildValue(INTVALUE, utils::string_bool(unit->unit_->get_state("poisoned"))); } static PyObject* unit_slowed(wesnoth_unit* unit, void* /*closure*/) { u_check; return PyBool_FromLong( utils::string_bool( unit->unit_->get_state("poisoned") ) ) ; } static PyObject* unit_stoned(wesnoth_unit* unit, void* /*closure*/) { u_check; return PyBool_FromLong( utils::string_bool( unit->unit_->get_state("stoned") ) ) ; } // @todo: Make Py_RETURN_TRUE and Py_RETURN_FALSE work properly with // gcc 4.3.x series compilers. Latest compilers are much more strict. This // will likely require a change from the python folks. Until then, use the // version below. static PyObject* unit_query_valid(wesnoth_unit* unit, void* /*closure*/) { u_check ; // if( running_instance->is_unit_valid(unit->unit_) ) // Py_RETURN_TRUE ; // Py_RETURN_FALSE ; return PyBool_FromLong( running_instance->is_unit_valid(unit->unit_) == true ? 1 : 0 ) ; } static PyGetSetDef unit_getseters[] = { GSDEF("name", unit_get_name, "Name of the unit (''description'' from WML).") GSDEF("is_enemy",unit_is_enemy, "True if this is an enemy unit, False if it is allied.") GSDEF("can_recruit", unit_can_recruit, "If the unit can recruit.") GSDEF("hitpoints", unit_hitpoints, "Current hitpoints of the unit.") GSDEF("max_hitpoints", unit_max_hitpoints, "Maximum hitpoints of the unit.") GSDEF("experience", unit_experience, "Current experience of the unit.") GSDEF("max_experience", unit_max_experience, "Maximum experience of the unit.") GSDEF("is_valid",unit_query_valid, "Indicates if the unit is still valid in the game. This is the only accessible " "field of an invalid unit, all others trigger an exception.") GSDEF("side", unit_side, "The side of the unit, starting with 1.") GSDEF("movement_left", unit_movement_left, "How many movement points the unit has left.") GSDEF("max_movement", unit_max_movement, "Maximum movement points of the unit.") GSDEF("can_attack", unit_can_attack, "If the unit can still attack.") GSDEF("poisoned", unit_poisoned, "If the unit is poisoned.") GSDEF("slowed", unit_slowed, "If the unit is slowed.") GSDEF("stoned", unit_stoned, "If the unit is stoned.") {NULL, NULL, NULL, NULL, NULL } }; static PyObject* wrapper_unit_type( wesnoth_unit* unit, PyObject* args ) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; u_check; assert(unit->unit_->type()); return wrap_unittype(*unit->unit_->type()); } static PyObject* wrapper_unit_attacks( wesnoth_unit* unit, PyObject* args ) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; u_check; PyObject* list = PyList_New(unit->unit_->attacks().size()); for ( size_t attack = 0; attack < unit->unit_->attacks().size(); attack++) PyList_SetItem(list,attack,wrap_attacktype(unit->unit_->attacks()[attack])); return list; } static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args ) { wesnoth_attacktype* attack; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_attacktype_type, &attack ) ) return NULL; u_check; static map_location no_loc; return Py_BuildValue(INTVALUE,unit->unit_->damage_from(*attack->attack_type_,true,no_loc)); } static PyObject* wrapper_unittype_resistance_against( wesnoth_unittype* type, PyObject* args ) { wesnoth_attacktype* attack; if (!PyArg_ParseTuple(args, OBVALUE, &wesnoth_attacktype_type, &attack)) return NULL; return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().resistance_against(*attack->attack_type_)); } static PyMethodDef unit_methods[] = { MDEF("type", wrapper_unit_type, "Returns: unittype\n" "Returns the type of the unit.") MDEF("attacks", wrapper_unit_attacks, "Returns: attacktype[]\n" "Returns list of possible attack types.\n") MDEF("movement_cost", python_ai::wrapper_unit_movement_cost, "Parameters: location\n" "Returns: cost\n" "Returns the cost of moving over the given location.") MDEF("defense_modifier", python_ai::wrapper_unit_defense_modifier, "Parameters: location\n" "Returns: percent\n" "Returns the defense modifier in % (probability the unit will be hit) on the given location.") MDEF("damage_from", wrapper_unit_damage_from, "Parameters: attacktype\n" "Returns: percent\n" "Returns the damage in percent the unit receives when attacked with " "the given attack type. (0 means no damage at all, 100 means full " "damage, 200 means double damage.)") MDEF("find_path", python_ai::wrapper_unit_find_path, "Parameters: location from, location to, float max_cost = unit.movement_left\n" "Returns: location[] path\n" "Finds a path from 'from' to 'to' costing less than 'max_cost' " "movement points to reach and returns it as a list of locations. " "path[0] will be 'from', path[-1] will be 'to'. " "If no path can be found (for example, if the target is not reachable, " "or it would cost more than max_cost), an empty list is returned.") MDEF("attack_statistics", python_ai::wrapper_unit_attack_statistics, "Parameters: location from, location to, int attack = -1\n" "Returns: own_hp, enemy_hp\n" "Returns two dictionaries with the expected battle results when the " "unit attacks from 'from' to the unit at 'to', optionally using the " "attack with index 'attack', or if no attack is given the attack which " "would be presented to the player in the attack dialog. The " "dictionaries contain the expected hitpoints after " "the fight, as a mapping from hitpoints to percent, where percent are " "specified as floating point value from 0 to 1. For example, a return of: " "{0:1}, {50:0.5, 40:0.5} would mean, the attacking unit " "is certain to die (probability for 0 hitpoints is 1), and the enemy " "unit will either remain at 50 or 40 HP after the fight, with equal " "probability of 0.5.") {NULL, NULL, 0, NULL} }; static int unit_internal_compare(wesnoth_unit* left, wesnoth_unit* right) { return reinterpret_cast(left->unit_) - reinterpret_cast(right->unit_); } static PyTypeObject wesnoth_unit_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.unit"), /* tp_name*/ sizeof(wesnoth_unit), /* tp_basicsize*/ 0, /* tp_itemsize*/ 0, /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ reinterpret_cast(unit_internal_compare), /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Represents a single unit. Trying to use a method or access a property, " "with the exception of is_valid, will result in an exception if the unit " "is invalid (was destroyed last move, and so on)."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ unit_methods, /* tp_methods */ 0, /* tp_members */ unit_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; typedef struct { PyObject_HEAD map_location* location_; } wesnoth_location; static void wesnoth_location_dealloc(wesnoth_location* self) { delete self->location_; self->location_ = NULL; PyObject_Del(reinterpret_cast(self)); } static PyObject* location_get_x(wesnoth_location* location, void* /*closure*/) { return Py_BuildValue(INTVALUE, location->location_->x); } static PyObject* location_get_y(wesnoth_location* location, void* /*closure*/) { return Py_BuildValue(INTVALUE, location->location_->y); } static int location_internal_compare(wesnoth_location* left, wesnoth_location* right) { if (*left->location_ == *right->location_) return 0; return *left->location_ < *right->location_ ? -1 : 1; } static long location_internal_hash(wesnoth_location* obj) { // Never return -1, which is reserved for raising an exception. // Note that both x and y can get values < 0, // e.g. when checking all positions in a certain radius at the map border. unsigned char x = static_cast(obj->location_->x); unsigned char y = static_cast(obj->location_->y); return (x << 8) + y; } static PyGetSetDef location_getseters[] = { GSDEF("x", location_get_x, "X position, starting with 0 for leftmost column.") GSDEF("y", location_get_y, "Y position, starting with 0 for topmost row.") { NULL, NULL, NULL, NULL, NULL } }; static PyObject* wrapper_location_adjacent_to( wesnoth_location* left, PyObject* args ); static PyObject* wrapper_location_distance_to( wesnoth_location* left, PyObject* args ); static PyMethodDef location_methods[] = { MDEF("adjacent_to", wrapper_location_adjacent_to, "Parameters: location\n" "Returns: result\n" "Returns True if the location is adjacent to this one, False otherwise.") MDEF("distance_to", wrapper_location_distance_to, "Parameters: location\n" "Returns: int distance\n" "Returns the distance in hexes to the other location.") { NULL, NULL, 0, NULL } }; static PyTypeObject wesnoth_location_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.location"), /* tp_name*/ sizeof(wesnoth_location), /* tp_basicsize*/ 0, /* tp_itemsize*/ reinterpret_cast(wesnoth_location_dealloc), /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ reinterpret_cast(location_internal_compare), /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ reinterpret_cast(location_internal_hash), /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Represents a single location on the map."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ location_methods, /* tp_methods */ 0, /* tp_members */ location_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; static PyObject *wrap_location(const map_location& loc) { wesnoth_location* location; location = reinterpret_cast(PyObject_NEW(wesnoth_location, &wesnoth_location_type)); location->location_ = new map_location(loc.x, loc.y); return reinterpret_cast(location); } void recalculate_movemaps() { python_ai *ai = running_instance; ai->src_dst_.clear(); ai->dst_src_.clear(); ai->possible_moves_.clear(); ai->calculate_possible_moves(ai->possible_moves_, ai->src_dst_, ai->dst_src_, false); ai->enemy_src_dst_.clear(); ai->enemy_dst_src_.clear(); ai->enemy_possible_moves_.clear(); ai->calculate_possible_moves(ai->enemy_possible_moves_, ai->enemy_src_dst_, ai->enemy_dst_src_, true); } static PyObject* wrapper_location_adjacent_to( wesnoth_location* left, PyObject* args ) { wesnoth_location* right; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &right ) ) return NULL; return Py_BuildValue(INTVALUE, tiles_adjacent(*left->location_,*right->location_) ? 1 : 0); } static PyObject* wrapper_location_distance_to( wesnoth_location* left, PyObject* args ) { wesnoth_location* right; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &right ) ) return NULL; return Py_BuildValue(INTVALUE, distance_between(*left->location_,*right->location_)); } typedef struct { PyObject_HEAD const gamemap* map_; } wesnoth_gamemap; static PyObject* gamemap_get_x(wesnoth_gamemap* map, void* /*closure*/) { return Py_BuildValue(INTVALUE, map->map_->w()); } static PyObject* gamemap_get_y(wesnoth_gamemap* map, void* /*closure*/) { return Py_BuildValue(INTVALUE, map->map_->h()); } static PyGetSetDef gamemap_getseters[] = { GSDEF("x", gamemap_get_x, "Width of the map in hexes.") GSDEF("y", gamemap_get_y, "Height of the map in hexes.") { NULL, NULL, NULL, NULL, NULL }, }; static PyObject* wrapper_getmap_is_village( wesnoth_gamemap* map, PyObject* args ) { wesnoth_location* location; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) ) return NULL; return Py_BuildValue(INTVALUE, map->map_->is_village(*location->location_) ? 1 : 0); } static PyObject* wrapper_getmap_is_keep( wesnoth_gamemap* map, PyObject* args ) { wesnoth_location* location; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) ) return NULL; return Py_BuildValue(INTVALUE, map->map_->is_keep(*location->location_) ? 1 : 0); } static PyObject* wrapper_getmap_is_castle( wesnoth_gamemap* map, PyObject* args ) { wesnoth_location* location; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) ) return NULL; return Py_BuildValue(INTVALUE, map->map_->is_castle(*location->location_) ? 1 : 0); } static PyObject* wrapper_getmap_is_border( wesnoth_gamemap* map, PyObject* args ) { wesnoth_location* location; bool on_board, on_board_without_borders; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) ) return NULL; Py_BEGIN_ALLOW_THREADS on_board = map->map_->on_board_with_border(*location->location_); on_board_without_borders = map->map_->on_board(*location->location_); Py_END_ALLOW_THREADS return Py_BuildValue(INTVALUE, on_board && !on_board_without_borders ? 1 : 0); } static PyMethodDef gamemap_methods[] = { MDEF("is_village", wrapper_getmap_is_village, "Parameters: location\n" "Returns: result\n" "Returns True if a village is at the given location, False otherwise.") MDEF("is_keep", wrapper_getmap_is_keep, "Parameters: location\n" "Returns: result\n" "Returns True if a keep (where a leader must stand to recruit) is at " "the given location, False otherwise.") MDEF("is_castle", wrapper_getmap_is_castle, "Parameters: location\n" "Returns: result\n" "Returns True if the given location is a castle tile (where units are " "recruited to), False otherwise.") MDEF("is_border", wrapper_getmap_is_border, "Parameters: location\n" "Returns: result\n" "Returns True if the given location is a border tile, False otherwise.") { NULL, NULL, 0, NULL } }; static PyTypeObject wesnoth_gamemap_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.gamemap"), /* tp_name*/ sizeof(wesnoth_gamemap), /* tp_basicsize*/ 0, /* tp_itemsize*/ 0, /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ 0, /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Represents the current map."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ gamemap_methods, /* tp_methods */ 0, /* tp_members */ gamemap_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; static PyObject* wrapper_team_name(wesnoth_team* team, void* /*closure*/) { return Py_BuildValue(STRINGVALUE, team->team_->team_name().c_str()); } static PyObject* wrapper_team_income(wesnoth_team* team, void* /*closure*/) { return Py_BuildValue(INTVALUE, team->team_->income()); } static PyObject* wrapper_team_gold(wesnoth_team* team, void* /*closure*/) { return Py_BuildValue(INTVALUE, team->team_->gold()); } static PyObject* wrapper_team_is_enemy(wesnoth_team* team, void* /*closure*/) { int result; // Find side number of team int side = 0; Py_BEGIN_ALLOW_THREADS for (size_t t = 0; t < running_instance->get_teams().size(); t++) { if (team->team_ == &running_instance->get_teams()[t]) { side = 1 + t; break; } } result = running_instance->current_team().is_enemy(side) == true ? 1 : 0; Py_END_ALLOW_THREADS return PyBool_FromLong( result ) ; } static PyObject* wrapper_team_side(wesnoth_team* team, void* /*closure*/) { int side = 0; Py_BEGIN_ALLOW_THREADS for (size_t t = 0; t < running_instance->get_teams().size(); t++) { if (team->team_ == &running_instance->get_teams()[t]) { side = 1 + t; break; } } Py_END_ALLOW_THREADS return Py_BuildValue(INTVALUE, side); } static int wrapper_team_internal_compare(wesnoth_team* left, wesnoth_team* right) { return reinterpret_cast(left->team_) - reinterpret_cast(right->team_); } static PyObject* wrapper_team_owns_village( wesnoth_team* team, PyObject* args ) { wesnoth_location* location; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &location ) ) return NULL; return PyBool_FromLong( team->team_->owns_village(*location->location_) ? 1 : 0) ; } PyObject* python_ai::wrapper_team_recruits( wesnoth_team* team, PyObject* args ) { if ( !PyArg_ParseTuple( args, NOVALUE) ) return NULL; PyObject* list = PyList_New(team->team_->recruits().size()); int r; int idx = 0; for (std::set::const_iterator recruit = team->team_->recruits().begin(); recruit != team->team_->recruits().end(); ++recruit) { std::map::const_iterator t = unit_type_data::types().find_unit_type(*recruit); assert(t != unit_type_data::types().end()); r = PyList_SetItem(list,idx++,wrap_unittype(t->second)); } return list; } PyObject *python_ai::wrapper_team_targets(PyObject *, PyObject *args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; PyObject* dict = PyDict_New(); /** * @todo FIXME: There should be a C++ method to return all targets instead, * for now it's just copy&pasted. */ std::vector& team_targets = running_instance->current_team().targets(); unit_map &units = running_instance->get_info().units; for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) { // explicit targets for this team for(std::vector::iterator j = team_targets.begin(); j != team_targets.end(); ++j) { if(u->second.matches_filter(&(j->criteria), u->first)) { PyDict_SetItem(dict, wrap_location(u->first), PyLong_FromLong(static_cast(j->value))); } } } return dict; } static PyMethodDef team_methods[] = { MDEF("owns_village", wrapper_team_owns_village, "Parameters: location\n" "Returns: result\n" "True if the team owns a village at the given location.") MDEF("recruits", python_ai::wrapper_team_recruits, "Returns: recruits\n" "Returns a list of wesnoth.unittype objects" " of all possible recruits for this team.") MDEF("targets", python_ai::wrapper_team_targets, "Returns: targets\n" "Returns a dictionary containing all WML targets for the " "team, mapping their locations to the scores in WML.") { NULL, NULL, 0, NULL } }; static PyGetSetDef team_getseters[] = { GSDEF("name", wrapper_team_name, "The name of this team.") GSDEF("gold", wrapper_team_gold, "The current amount of gold this team has.") GSDEF("income", wrapper_team_income, "The current per-turn income if this team.") GSDEF("side", wrapper_team_side, "Side number of this team, starting with 1.") GSDEF("is_enemy", wrapper_team_is_enemy, "Whether this team is an enemy.") { NULL, NULL, NULL, NULL, NULL } }; static PyTypeObject wesnoth_team_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.team"), /* tp_name*/ sizeof(wesnoth_team), /* tp_basicsize*/ 0, /* tp_itemsize*/ 0, /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ reinterpret_cast(wrapper_team_internal_compare), /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("Represents one team/player/side."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ team_methods, /* tp_methods */ 0, /* tp_members */ team_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; PyObject* python_ai::wrapper_unit_movement_cost( wesnoth_unit* unit, PyObject* args ) { gamemap const &map = running_instance->get_info().map; wesnoth_location* loc; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) ) return NULL; u_check ; return Py_BuildValue(INTVALUE,unit->unit_->movement_cost(map.get_terrain(*loc->location_))); } PyObject* python_ai::wrapper_unit_defense_modifier( wesnoth_unit* unit, PyObject* args ) { gamemap const &map = running_instance->get_info().map; wesnoth_location* loc; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) ) return NULL; u_check ; return Py_BuildValue(INTVALUE,unit->unit_->defense_modifier(map.get_terrain(*loc->location_))); } PyObject* python_ai::wrapper_unittype_movement_cost( wesnoth_unittype* type, PyObject* args ) { gamemap const &map = running_instance->get_info().map; wesnoth_location* loc; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) ) return NULL; return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().movement_cost( map, map.get_terrain(*loc->location_))); } PyObject* python_ai::wrapper_unittype_defense_modifier( wesnoth_unittype* type, PyObject* args ) { gamemap const &map = running_instance->get_info().map; wesnoth_location* loc; if ( !PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &loc ) ) return NULL; return Py_BuildValue(INTVALUE,type->unit_type_->movement_type().defense_modifier( map, map.get_terrain(*loc->location_))); } typedef struct { PyObject_HEAD const gamestatus* status_; } wesnoth_gamestatus; static PyMethodDef gamestatus_methods[] = { { NULL, NULL, 0, NULL } }; static PyObject* wrapper_gamestatus_turn(wesnoth_gamestatus* status, void* /*closure*/) { return Py_BuildValue(INTVALUE, status->status_->turn()); } static PyObject* wrapper_gamestatus_number_of_turns(wesnoth_gamestatus* status, void* /*closure*/) { return Py_BuildValue(INTVALUE, status->status_->number_of_turns()); } static PyObject* wrapper_gamestatus_lawful_bonus(wesnoth_gamestatus* status, void* /*closure*/) { return Py_BuildValue(INTVALUE, status->status_->get_time_of_day().lawful_bonus); } static PyObject* wrapper_gamestatus_previous_lawful_bonus(wesnoth_gamestatus* status, void* /*closure*/) { return Py_BuildValue(INTVALUE, status->status_->get_previous_time_of_day().lawful_bonus); } static PyObject* wrapper_gamestatus_gold_per_village(wesnoth_gamestatus* /*status*/, void* /*closure*/) { return Py_BuildValue(INTVALUE, settings::get_village_gold("")); } static PyGetSetDef gamestatus_getseters[] = { GSDEF("turn", wrapper_gamestatus_turn, "The current turn.") GSDEF("number_of_turns", wrapper_gamestatus_number_of_turns, "The maximum number of turns of the whole game.") GSDEF("lawful_bonus", wrapper_gamestatus_lawful_bonus, "The bonus for lawful units in the current turn. This is the percentage to add to " "the attack damage of lawful units (alignment = 0), and to subtract from chaotic " "units (alignment = 2). Neutral units (alignment = 1) are not affected.") GSDEF("previous_lawful_bonus", wrapper_gamestatus_previous_lawful_bonus, "The value of lawful_bonus in the previous turn.") GSDEF("gold_per_village", wrapper_gamestatus_gold_per_village, "The gold a village generates each turn.") { NULL, NULL, NULL, NULL, NULL } }; static PyTypeObject wesnoth_gamestatus_type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size*/ CC("ai.gamestatus"), /* tp_name*/ sizeof(wesnoth_gamestatus), /* tp_basicsize*/ 0, /* tp_itemsize*/ 0, /* tp_dealloc*/ 0, /* tp_print*/ 0, /* tp_getattr*/ 0, /* tp_setattr*/ 0, /* tp_compare*/ 0, /* tp_repr*/ 0, //UniConvert, /* tp_as_number*/ 0, /* tp_as_sequence*/ 0, /* tp_as_mapping*/ 0, /* tp_hash */ 0, /* tp_call*/ 0, /* tp_str*/ 0, /* tp_getattro*/ 0, /* tp_setattro*/ 0, /* tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /* tp_flags*/ CC("This class has information about the game status."), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ gamestatus_methods, /* tp_methods */ 0, /* tp_members */ gamestatus_getseters, /* tp_getset */ NULL, /* tp_base */ NULL, /* tp_dict */ NULL, /* tp_descr_get */ NULL, /* tp_descr_set */ 0, /* tp_dictoffset */ NULL, /* tp_init */ NULL, /* tp_alloc */ NULL, /* tp_new */ NULL, /* tp_free */ NULL, /* tp_is_gc */ NULL, /* tp_bases */ NULL, /* tp_mro */ NULL, /* tp_cache */ NULL, /* tp_subclasses */ NULL, /* tp_weaklist */ NULL, /* tp_del */ #if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >= 6 0, /* tp_version_tag */ #endif }; static PyObject* wrap_move_map(const ai_interface::move_map& wrap) { PyObject* dict = PyDict_New(); PyObject* list; PyObject *loc, *loc2; ai_interface::move_map::const_iterator pos; for (pos = wrap.begin(); pos != wrap.end(); pos++) { loc = wrap_location(pos->first); list = PyDict_GetItem(dict,loc); if (!list) { list = PyList_New(0); PyDict_SetItem(dict, loc, list); Py_DECREF(list); } loc2 = wrap_location(pos->second); PyList_Append(list, loc2); Py_DECREF(loc2); Py_DECREF(loc); } return dict; } PyObject* python_ai::wrapper_log_message(PyObject* /*self*/, PyObject* args) { const char* msg; if ( !PyArg_ParseTuple( args, STRINGVALUE, &msg ) ) return NULL; Py_BEGIN_ALLOW_THREADS running_instance->log_message(msg); Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } PyObject* python_ai::wrapper_log(PyObject* /*self*/, PyObject* args) { const char *msg; if (!PyArg_ParseTuple( args, STRINGVALUE, &msg)) return NULL; Py_BEGIN_ALLOW_THREADS LOG_AI << msg; Py_END_ALLOW_THREADS Py_INCREF(Py_None); return Py_None; } PyObject* python_ai::wrapper_get_units(PyObject* /*self*/, PyObject* args) { if ( !PyArg_ParseTuple( args, NOVALUE) ) return NULL; PyObject* dict = PyDict_New(); if ( !dict ) { Py_INCREF(Py_None); return Py_None; } PyObject* key; wesnoth_unit* unit; int ret; for(unit_map::const_iterator i = running_instance->get_info().units.begin(); i != running_instance->get_info().units.end(); ++i) { key = wrap_location(i->first); unit = static_cast(PyObject_NEW(wesnoth_unit, &wesnoth_unit_type)); unit->unit_ = &i->second; ret = PyDict_SetItem(dict,reinterpret_cast(key),reinterpret_cast(unit)); Py_DECREF(unit); Py_DECREF(key); } return dict; } PyObject* python_ai::wrapper_get_location(PyObject* /*self*/, PyObject* args) { int x, y; if (!PyArg_ParseTuple( args, CC("ii"), &x, &y )) return NULL; if (x < 0 || x >= running_instance->get_info().map.w()) Py_RETURN_NONE; if (y < 0 || y >= running_instance->get_info().map.h()) Py_RETURN_NONE; map_location loc(x,y); return wrap_location(loc); } PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; wesnoth_gamemap* map = static_cast(PyObject_NEW(wesnoth_gamemap, &wesnoth_gamemap_type)); map->map_ = &running_instance->get_info().map; return reinterpret_cast(map); } PyObject* python_ai::wrapper_get_teams(PyObject* /*self*/, PyObject* args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; PyObject* list = PyList_New(running_instance->get_info().teams.size()); wesnoth_team* the_team; for (size_t team = 0; team < running_instance->get_info().teams.size(); team++) { the_team = static_cast(PyObject_NEW(wesnoth_team, &wesnoth_team_type)); the_team->team_ = &running_instance->get_info().teams[team]; PyList_SetItem(list,team,reinterpret_cast(the_team)); } return list; } PyObject* python_ai::wrapper_get_current_team(PyObject* /*self*/, PyObject* /*args*/) { wesnoth_team* the_team; the_team = static_cast(PyObject_NEW(wesnoth_team, &wesnoth_team_type)); the_team->team_ = &running_instance->current_team(); return reinterpret_cast(the_team); } PyObject* python_ai::wrapper_get_src_dst(PyObject* /*self*/, PyObject* /*args*/) { return wrap_move_map(running_instance->src_dst_); } PyObject* python_ai::wrapper_get_dst_src(PyObject* /*self*/, PyObject* /*args*/) { return wrap_move_map(running_instance->dst_src_); } PyObject* python_ai::wrapper_get_enemy_src_dst(PyObject* /*self*/, PyObject* /*args*/) { return wrap_move_map(running_instance->enemy_src_dst_); } PyObject* python_ai::wrapper_get_enemy_dst_src(PyObject* /*self*/, PyObject* /*args*/) { return wrap_move_map(running_instance->enemy_dst_src_); } PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args) { wesnoth_location* from; wesnoth_location* to; if (!PyArg_ParseTuple( args, CC("O!O!"), &wesnoth_location_type, &from, &wesnoth_location_type, &to ) ) return NULL; PyThreadState *_save ; Py_UNBLOCK_THREADS ; bool valid = false; ai_interface::move_map::const_iterator pos; for (pos = running_instance->src_dst_.begin(); pos != running_instance->src_dst_.end(); pos++) { if ( pos->first == ( *from->location_ ) ) { valid = true; if ( pos->second == ( *to->location_ ) ) break; } } if (!valid || pos == running_instance->src_dst_.end() ) { Py_BLOCK_THREADS ; Py_RETURN_NONE ; } location location; try { location = running_instance->move_unit_partial( *from->location_, *to->location_, running_instance->possible_moves_ ) ; } catch( end_level_exception& e ) { running_instance->exception = e ; Py_BLOCK_THREADS ; PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ; return NULL ; } recalculate_movemaps(); Py_BLOCK_THREADS ; PyObject* loc = wrap_location(location); return loc; } PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args) { wesnoth_location* from; wesnoth_location* to; int weapon = -1; // auto-choose if (!PyArg_ParseTuple( args, CC("O!O!|i"), &wesnoth_location_type, &from, &wesnoth_location_type, &to, &weapon ) ) return NULL; PyThreadState *_save ; Py_UNBLOCK_THREADS ; /** * @todo FIXME: Remove this check and let the C++ code do the check if the * attack is valid at all (there may be ranged attacks or similar later, * and then the code below will horribly fail). */ if (!tiles_adjacent(*from->location_, *to->location_)) { Py_BLOCK_THREADS ; Py_RETURN_NONE; } // check if units actually exist bool fromexists = false; bool toexists = false; for(unit_map::const_iterator i = running_instance->get_info().units.begin(); i != running_instance->get_info().units.end(); ++i) { if (i->first == *from->location_) { fromexists = true; } else if (i->first == *to->location_) { toexists = true; } if( fromexists && toexists ) break ; } if (!fromexists || !toexists) { Py_BLOCK_THREADS ; Py_RETURN_NONE; } info& inf = running_instance->get_info(); battle_context bc( inf.map, inf.teams, inf.units, inf.state, *from->location_, *to->location_, weapon); try { running_instance->attack_enemy( *from->location_, *to->location_, bc.get_attacker_stats().attack_num, bc.get_defender_stats().attack_num ) ; } catch( end_level_exception& e ) { running_instance->exception = e ; Py_BLOCK_THREADS ; PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ; return NULL ; } recalculate_movemaps(); Py_BLOCK_THREADS ; Py_INCREF(Py_None); return Py_None; } PyObject* python_ai::wrapper_get_adjacent_tiles(PyObject* /*self*/, PyObject* args) { wesnoth_location* where; if (!PyArg_ParseTuple( args, OBVALUE, &wesnoth_location_type, &where)) return NULL; gamemap const &map = running_instance->get_info().map; PyObject* list = PyList_New(0); map_location loc[6]; get_adjacent_tiles(*where->location_,loc); for ( int tile = 0; tile < 6; tile++ ) if (loc[tile].valid(map.w(), map.h())) { PyObject *o = wrap_location(loc[tile]); PyList_Append(list, o); Py_DECREF(o); } return list; } PyObject* python_ai::wrapper_recruit_unit(PyObject* /*self*/, PyObject* args) { wesnoth_location* where; const char* name; if (!PyArg_ParseTuple( args, CC("sO!"), &name, &wesnoth_location_type, &where)) return NULL; int r ; PyThreadState *_save ; Py_UNBLOCK_THREADS ; try { r = running_instance->recruit(name,*where->location_) == true ? 1 : 0 ; } catch( end_level_exception& e ) { running_instance->exception = e ; Py_BLOCK_THREADS ; PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ; return NULL ; } recalculate_movemaps(); Py_BLOCK_THREADS ; return Py_BuildValue(INTVALUE, r); } PyObject* python_ai::wrapper_get_gamestatus(PyObject* /*self*/, PyObject* args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; wesnoth_gamestatus* status; status = static_cast(PyObject_NEW(wesnoth_gamestatus, &wesnoth_gamestatus_type)); status->status_ = &running_instance->get_info().state; return reinterpret_cast(status); } PyObject* python_ai::wrapper_unit_find_path(wesnoth_unit* self, PyObject* args) { wesnoth_location* from; wesnoth_location* to; if( !running_instance->is_unit_valid(self->unit_) ) Py_RETURN_NONE ; double max_cost = self->unit_->movement_left(); if ( !PyArg_ParseTuple( args, CC("O!O!|d"), &wesnoth_location_type, &from, &wesnoth_location_type, &to, &max_cost ) ) return NULL; // Save thread state and release GIL PyThreadState *_save; Py_UNBLOCK_THREADS info& inf = running_instance->get_info(); max_cost += 1; // should be at least 1 const shortest_path_calculator calc(*self->unit_, running_instance->current_team(), inf.units, inf.teams, inf.map); const paths::route& route = a_star_search(*from->location_, *to->location_, max_cost, &calc, inf.map.w(), inf.map.h()); // Lock the GIL again Py_BLOCK_THREADS PyObject* steps = PyList_New(route.steps.size()); for (size_t step = 0; step < route.steps.size(); step++) PyList_SetItem(steps,step,wrap_location(route.steps[step])); return steps; } PyObject* python_ai::wrapper_unit_attack_statistics(wesnoth_unit* self, PyObject* args) { wesnoth_location* from; wesnoth_location* to; int weapon = -1; if (!PyArg_ParseTuple(args, CC("O!O!|i"), &wesnoth_location_type, &from, &wesnoth_location_type, &to, &weapon)) return NULL; if (!running_instance->is_unit_valid(self->unit_)) Py_RETURN_NONE; if (weapon < -1 || weapon >= static_cast(self->unit_->attacks().size())){ Py_RETURN_NONE; } PyThreadState *_save; Py_UNBLOCK_THREADS info& inf = running_instance->get_info(); // We need to temporarily move our unit to where the attack calculation // is supposed to take place. std::pair *temp = inf.units.extract(*from->location_); std::pair *backup = temp; std::pair replace(*from->location_,*self->unit_); inf.units.add(&replace); battle_context bc( inf.map, inf.teams, inf.units, inf.state, *from->location_, *to->location_, weapon); unsigned int i; std::vectorattacker = bc.get_attacker_combatant().hp_dist; Py_BLOCK_THREADS PyObject* adict = PyDict_New(); for (i = 0; i < attacker.size(); i++) { if (attacker[i] > 0) PyDict_SetItem(adict, PyInt_FromLong(i), PyFloat_FromDouble(attacker[i])); } std::vectordefender = bc.get_defender_combatant().hp_dist; PyObject* ddict = PyDict_New(); for (i = 0; i < defender.size(); i++) { if (defender[i] > 0) PyDict_SetItem(ddict, PyInt_FromLong(i), PyFloat_FromDouble(defender[i])); } // Restore old position again Py_UNBLOCK_THREADS temp = inf.units.extract(*from->location_); if (backup) inf.units.add(backup); Py_BLOCK_THREADS return Py_BuildValue(CC("(OO)"), adict, ddict); } PyObject* python_ai::wrapper_set_variable(PyObject* /*self*/, PyObject* args) { char const *variable; PyObject *value; if (!PyArg_ParseTuple(args, CC("sO"), &variable, &value)) return NULL; PyObject *so = PyMarshal_WriteObjectToString(value, Py_MARSHAL_VERSION); if (so) { char *cs; Py_ssize_t len; PyString_AsStringAndSize(so, &cs, &len); PyThreadState *_save ; Py_UNBLOCK_THREADS ; std::string s; char const *digits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; int i; for (i = 0; i < len; i++) { unsigned char x = cs[i]; s += std::string(digits[x >> 4]) + std::string(digits[x & 15]); } config const &old_memory = running_instance->current_team().ai_memory(); config new_memory(old_memory); new_memory[variable] = s; running_instance->current_team().set_ai_memory(new_memory); Py_BLOCK_THREADS ; Py_DECREF(so); } else { // If the marshal request generated an exception, we need to // return NULL and not None or the exception stack gets hosed! if( PyErr_Occurred() ) { return NULL ; } } Py_INCREF(Py_None) ; return Py_None ; } PyObject* python_ai::wrapper_get_variable(PyObject* /*self*/, PyObject* args) { char const *variable; PyObject *default_value = Py_BuildValue( STRINGVALUE, "" ) ; // If a default value was not provided, see if we were called with // just the value to find. if (!PyArg_ParseTuple(args, CC("s|O"), &variable, &default_value)) return NULL; PyThreadState *_save ; Py_UNBLOCK_THREADS config const &memory = running_instance->current_team().ai_memory(); char const *s = memory[variable].c_str(); if (s && s[0]) { int i; int len = strlen(s); char *data = new char[len / 2]; for (i = 0; i < len; i += 2) { int v1 = s[i] - '0'; int v2 = s[i + 1] - '0'; if (v1 > 9) v1 += 10 + '0' - 'a'; if (v2 > 9) v2 += 10 + '0' - 'a'; char c = v1 * 16 + v2; data[i / 2] = c; } Py_BLOCK_THREADS ; Py_DECREF( default_value ) ; PyObject *ret = PyMarshal_ReadObjectFromString(data, len / 2); delete[] data; return ret ; } else if( default_value ) { // Value did not exist - do return default value Py_BLOCK_THREADS ; return default_value ; } Py_BLOCK_THREADS ; Py_DECREF( default_value ) ; Py_INCREF(Py_None); return Py_None; } PyObject* python_ai::wrapper_get_version(PyObject* /*self*/, PyObject* args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; return Py_BuildValue(CC("s#"), game_config::version.data(), game_config::version.size()); } PyObject* python_ai::wrapper_raise_user_interact(PyObject* /*self*/, PyObject* args) { if (!PyArg_ParseTuple(args, NOVALUE)) return NULL; PyThreadState *_save ; Py_UNBLOCK_THREADS ; try { running_instance->raise_user_interact() ; } catch( end_level_exception& e ) { running_instance->exception = e ; Py_BLOCK_THREADS ; PyErr_SetString( PyExc_RuntimeError, "C++ exception!" ) ; return NULL ; } Py_BLOCK_THREADS ; Py_INCREF(Py_None); return Py_None; } PyObject* python_ai::wrapper_test_move(PyObject* /*self*/, PyObject* args) { wesnoth_location* from; // loc initialises to x=-1000, y=-1000. i.e. temporarily moves the unit off the map map_location loc; wesnoth_location* to = reinterpret_cast(wrap_location(loc)); if (!PyArg_ParseTuple(args, CC("O!|O!"), &wesnoth_location_type, &from, &wesnoth_location_type, &to)) return NULL; // Trivial case, just return the current move_maps if (*from->location_ == *to->location_) { // PyTuple_New creates a new reference (which we will return) PyObject* ret = PyTuple_New(2); /* wrap_move_map creates a new reference, PyTuple_SetItem takes away * a reference - so this should cancel out and we don't need to * change reference counts ourselves. */ PyTuple_SetItem(ret, 0, wrap_move_map(running_instance->enemy_src_dst_)); PyTuple_SetItem(ret, 1, wrap_move_map(running_instance->enemy_dst_src_)); return ret; } PyThreadState *_save ; Py_UNBLOCK_THREADS ; info& inf = running_instance->get_info(); unit_map::iterator u_it = inf.units.find(*from->location_); if (u_it == inf.units.end()) { Py_BLOCK_THREADS ; Py_RETURN_NONE; } // Temporarily move our unit to the specified location, storing any // unit that might happen to be there already. std::pair *temp = inf.units.extract(*to->location_); std::pair *backup = temp; std::pair *original = inf.units.extract(*from->location_); std::pair test(*to->location_, u_it->second); inf.units.add(&test); ai_interface::move_map test_src_dst; ai_interface::move_map test_dst_src; std::map possible_moves; running_instance->calculate_moves(inf.units, possible_moves, test_src_dst, test_dst_src, true); // Restore old positions again temp = inf.units.extract(*to->location_); inf.units.add(original); if (backup) inf.units.add(backup); Py_BLOCK_THREADS ; PyObject* srcdst = wrap_move_map(test_src_dst); PyObject* dstsrc = wrap_move_map(test_dst_src); // possible_moves not used PyObject* ret = PyTuple_New(2); PyTuple_SetItem(ret, 0, srcdst); PyTuple_SetItem(ret, 1, dstsrc); return ret; } PyObject* python_ai::wrapper_get_random(PyObject* /*self*/, PyObject* args) { int a, b, r; if (!PyArg_ParseTuple(args, CC("ii"), &a, &b)) return NULL; r = get_random() % (b-a+1) + a; return Py_BuildValue(INTVALUE, r); } static PyMethodDef wesnoth_python_methods[] = { MDEF("log_message", python_ai::wrapper_log_message, "Parameters: string\n" "Logs a message, displayed as a chat message, if debug is enabled." ) MDEF("log", python_ai::wrapper_log, "Parameters: string\n" "Writes a debug message to the AI log. This should be used instead of " "print to not clutter stdout if AI logging is disabled.") MDEF("get_random", python_ai::wrapper_get_random, "Parameters: a, b\n" "Returns: random_number\n" "Get random number in the range [a, b].") MDEF("get_units", python_ai::wrapper_get_units, "Returns: units\n" "Returns a dictionary containing (location, unit) pairs.") MDEF("get_location", python_ai::wrapper_get_location, "Parameters: x, y\n" "Returns: location\n" "Returns a wesnoth.location object pointing to position (x, y) on the map.") MDEF("get_map", python_ai::wrapper_get_map, "Returns: map\n" "Returns a wesnoth.gamemap object representing the current map.") MDEF("get_teams", python_ai::wrapper_get_teams, "Returns: teams\n" "Returns a list containing wesnoth.team objects, representing the current teams in the game.") MDEF("get_current_team", python_ai::wrapper_get_current_team, "Returns: team\n" "Returns a wesnoth.team object representing the current team.") MDEF("get_destinations_by_unit", python_ai::wrapper_get_src_dst, "Returns: moves\n" "Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, " "values are lists of possible destinations for this unit.") MDEF("get_units_by_destination", python_ai::wrapper_get_dst_src, "Returns: moves\n" "Returns a dictionary. Keys are wesnoth.location objects pointing to positions the " "player's units can reach, values are lists of locations where units that can reach " "this position are.") MDEF("get_enemy_destinations_by_unit", python_ai::wrapper_get_enemy_src_dst, "Returns: moves\n" "Returns a dictionary. Keys are wesnoth.location objects pointing to player's units, " "values are lists of possible destinations for this unit.") MDEF("get_enemy_units_by_destination", python_ai::wrapper_get_enemy_dst_src, "Returns: moves\n" "Returns a dictionary. Keys are wesnoth.location objects pointing to positions the " "enemie's units can reach, values are lists of locations where units that can reach " "this position are.") MDEF("move_unit", python_ai::wrapper_move_unit, "Parameters: location from, location to\n" "Returns: new_position\n" "Moves the unit on 'from' to the location specified by 'to', and " "returns a wesnoth.location object representing the position the unit was moved to.") MDEF("attack_unit", python_ai::wrapper_attack_unit, "Parameters: location attacker, location defender, int weapon = -1\n" "Unit at position 'attacker' attacks unit at position 'defender' with weapon 'weapon'. " "The weapon parameter is optional, and the same weapon which would be " "highlighted for a human player is used if it is omitted.") MDEF("get_adjacent_tiles", python_ai::wrapper_get_adjacent_tiles, "Parameters: location\n" "Returns: positions\n" "Returns a list of wesnoth.location representing tiles adjacent to the specified location.") MDEF("recruit_unit", python_ai::wrapper_recruit_unit, "Parameters: string name, location where\n" "Returns: result\n" "Recruits the unit of type 'name', at the location 'where'. Returns 1 on success, 0 on failure.") MDEF("get_gamestatus", python_ai::wrapper_get_gamestatus, "Returns: status\n" "Returns a wesnoth.gamestatus object representing the current game status.") MDEF("set_variable", python_ai::wrapper_set_variable, "Parameters: variable, value\n" "Sets a persistent variable 'variable' to 'value'. This can be " "used to make the AI save strings (and other python values which can " "be marshalled) over multiple turns.") MDEF("get_variable", python_ai::wrapper_get_variable, "Parameters: variable, default value\n" "Returns: value or default value\n" "Retrieves a persistent variable 'variable' from the AI, which has " "previously been set with set_variable - or None if it can't be found and\n" "default value has not been set. If a default value is set and the value" "can not be found, the default value is returned.") MDEF("get_version", python_ai::wrapper_get_version, "Returns a string containing current Wesnoth version") MDEF("raise_user_interact", python_ai::wrapper_raise_user_interact, "Function which should be called frequently to allow the user to interact with the interface.\n" "This function will make sure that interaction doesn't occur too often, " "so there is no problem with calling it very regularly.") MDEF("test_move", python_ai::wrapper_test_move, "Parameters: from location, to location (optional)\n" "Returns: (enemy_destinations_by_unit, enemy_units_by_destination) or None\n" "Returns two dictionaries, representing the possible enemy moves if " "the unit were to move from 'from location' to 'test location'. " "If 'to location' is not given, it returns the possible enemy moves ignoring this unit. " "The results will be the same as if wesnoth.get_enemy_destinations_by_unit() " "and wesnoth.get_enemy_units_by_destination() were called after actually " "performing the move.\n" "Returns None if there is no unit at 'from location'.") { NULL, NULL, 0, NULL} }; #define Py_Register( x, n ) { \ PyType_Ready(&x); \ Py_INCREF(&x); \ PyTypeObject *type = &x; \ PyObject* pyob = reinterpret_cast(type); \ PyModule_AddObject(module, CC(n), pyob); } void python_ai::initialize_python() { if (init_) return; init_ = true; Py_Initialize( ); PyObject* module = Py_InitModule3(CC("ai"), wesnoth_python_methods, CC("This is the wesnoth AI module. " "The python script will be executed once for each turn of the side with the " "python AI using the script.")); Py_Register(wesnoth_unit_type, "unit"); Py_Register(wesnoth_location_type, "location"); Py_Register(wesnoth_gamemap_type, "gamemap"); Py_Register(wesnoth_unittype_type, "unittype"); Py_Register(wesnoth_team_type, "team"); Py_Register(wesnoth_attacktype_type, "attacktype"); Py_Register(wesnoth_gamestatus_type, "gamestatus"); } /*** * Invoke the named python script using Wesnoth's builtin Python interpreter. */ void python_ai::invoke(std::string name) { initialize_python(); PyErr_Clear(); PyObject* globals = PyDict_New(); PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins()); std::string python_code; python_code += "import sys\n" "backup = sys.path[:]\n" "sys.path.append(\"" + game_config::path + "/data/ai/python\")\n" "sys.path.append(\"" + game_config::path + "/data/tools/wesnoth\")\n" "try:\n" "\timport " + name + "\n" "finally:\n" "\tsys.path = backup\n"; PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input, globals, globals); Py_XDECREF(ret); Py_DECREF(globals); } /*** * Invoke the Wesnoth's builtin interactive Python interpreter. */ int python_ai::run_shell() { initialize_python(); PyErr_Clear(); PyObject* globals = PyDict_New(); PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins()); std::string python_code; // Inspired by Django shell command implementation. python_code += "import sys\n" "import code\n" "import ai\n" "sys.path.append(\"" + game_config::path + "/data/ai/python\")\n" "sys.path.append(\"" + game_config::path + "/data/tools/wesnoth\")\n" "imported_objects = {}\n" "try: # Try activating rlcompleter, because it's handy.\n" "\timport readline\n" "except ImportError:\n" "\tpass\n" "else:\n" "\timport rlcompleter\n" "\treadline.set_completer(rlcompleter.Completer(imported_objects).complete)\n" "\treadline.parse_and_bind('tab:complete')\n" "banner= 'Wesnoth %s\\nPython %s' % (ai.get_version(), sys.version)\n" "code.interact(banner, local=imported_objects)\n" ; PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input, globals, globals); if (PyErr_Occurred()) PyErr_Print(); int cret = (int)PyInt_AsLong(ret); Py_XDECREF(ret); Py_DECREF(globals); return cret; //Py_Main(argc, argv); } python_ai::python_ai(ai_interface::info& info) : ai_interface(info), exception(QUIT), src_dst_(), dst_src_(), possible_moves_(), enemy_src_dst_(), enemy_dst_src_(), enemy_possible_moves_() { LOG_AI << "Running Python instance.\n"; running_instance = this; initialize_python(); calculate_possible_moves(possible_moves_,src_dst_,dst_src_,false); calculate_possible_moves(enemy_possible_moves_,enemy_src_dst_,enemy_dst_src_,true); } python_ai::~python_ai() { // This is called whenever the AI is destroyed after its turn - // the Python interpreter itself will be auto cleaned up at program exit. LOG_AI << "Closing Python instance.\n"; running_instance = NULL; } void python_ai::play_turn() { game_events::fire("ai turn"); std::string script_name = current_team().ai_parameters()["python_script"]; if (script_name.substr(script_name.length() - 3) != ".py") { // Make sure the script ends in .py here - // Wesnoth will not execute any other files. std::cerr << "\"" << script_name << "\" is not a valid script name.\n"; return; } std::string script = get_binary_file_location("data", "ai/python" + script_name); PyErr_Clear(); PyObject* globals = PyDict_New(); PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins()); std::string path("."); if (!game_config::path.empty()) path = game_config::path; LOG_AI << "Executing Python script \"" << script << "\".\n"; LOG_AI << "Python path: \"" << path << "/data/ai/python" << "\"\n"; // Run the python script. We actually execute a short inline python script, // which sets up the module search path to the data path, // runs the script, and then resets the path. std::string runSafe = preferences::run_safe_python()?"True":"False" ; std::string python_code; python_code += "err = \"unknown error\"\n" "try:\n" "\timport sys, traceback\n" "\ttry:\n" "\t\tsys.stderr = file(\"pyerr.txt\", \"wb\")\n" "\texcept IOError:\n" "\t\tsys.stderr.write(\"Python: Could not create pyerr.txt in current directory.\\n\")\n" "\tbackup = sys.path[:]\n" "\tsys.path.append(\"" + path + "/data/ai/python\")\n" "\tsys.path.append(\"data/ais\")\n" "\ttry:\n" "\t\timport parse, safe\n" "\t\tparse.pathes = [\"" + path + "\"]\n" "\t\tcode, context = parse.parse(\"" + script + "\")\n" "\t\tsafe.safe_exec(code, context, " + runSafe + ")\n" "\texcept:\n" "\t\terr = str(traceback.format_exc())\n" "\t\traise\n" "finally:\n" "\tsys.path = backup\n" ; PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input, globals, globals); if (PyErr_Occurred()) { // RuntimeError is the game-won exception, no need to print it. // Anything else likely is a mistake by the script author. if (!PyErr_ExceptionMatches(PyExc_RuntimeError)) { LOG_AI << "Python script has crashed.\n"; std::cerr << "Python version: " << Py_GetVersion() << "\n"; PyObject *err = PyDict_GetItemString(globals, "err"); std::cerr << (err ? PyString_AsString(err) : std::string("internal error (wrong python version?)")) << std::endl; } // Otherwise, re-throw the exception here, // so it will get handled properly further up. else { LOG_AI << "Python script has been interrupted.\n"; Py_XDECREF(ret); Py_DECREF(globals); throw exception; } } Py_XDECREF(ret); Py_DECREF(globals); } #endif // HAVE_PYTHON