PerkunWars for developers

perkunwars is a multiprocess application. Each NPC in the game is controlled by a separate process (we use fork) communicating with the parent through pipes. In the file src/perkun_wars.cc the pipes are created first, then we do a fork, then for each child we run the function main_perkun, which instantiates a class npc. This class as shown in the file inc/perkun_wars.h is inherited from the class perkun::optimizer_with_all_data. Some virtual functions are redefined. In particular get_input (reading input from the parent through pipes) and execute (executing the selected action - notifying the parent through pipes about it).


class npc: public perkun::optimizer_with_all_data
{
private:
	int parent_to_child_fd, child_to_parent_fd;
	const std::string name, mode_name;
	const perkun::action * last_action;
	
	void chat(const std::string & w1, const std::string & w2);
	
protected:
	virtual void on_error_in_populate_belief_for_consequence(
		const perkun::belief & b1, const perkun::action & a, 
		const perkun::visible_state & vs, perkun::belief & target) const;
	
public:
	virtual void print_current_belief() const;
	
	virtual void get_input(std::map<perkun::variable*, perkun::value*> & m, bool & flag_eof);
	
	virtual void execute(const perkun::action * a);
	
	npc(const std::string & n, const std::string &m, int pc, int cp): 
		name(n),
		mode_name(m),
		parent_to_child_fd(pc), child_to_parent_fd(cp), last_action(NULL) {}
};

In the file src/perkun_wars.cc, function main_perkun we execute the method parse of the instantiated class npc. Parsing a perkun specification means executing it.


void main_perkun(const std::string & hero_name, const std::string & mode_name,
	const std::string & perkun_specification_name, 
	int hero_index, int mode_index)
{
	close(parent_to_child_pipefd[hero_index][mode_index][1]);
	close(child_to_parent_pipefd[hero_index][mode_index][0]);

	npc my_npc(hero_name, mode_name,
			   parent_to_child_pipefd[hero_index][mode_index][0], 
			   child_to_parent_pipefd[hero_index][mode_index][1]);
	std::stringstream s;
	s << DATADIR << "/perkun_wars/" << perkun_specification_name;
	
	std::cout << hero_name << " process running " << s.str() << "\n";
	my_npc.parse(s.str().c_str());
	
	close(parent_to_child_pipefd[hero_index][mode_index][0]);
	close(child_to_parent_pipefd[hero_index][mode_index][1]);
	_exit(EXIT_SUCCESS);
}

In the Perkun Wars there are three NPCs - Dorban, Pregor and Thragos. Each NPC process is running a Perkun interpreter (instantiating a class inherited from perkun::optimizer_with_all_data).

Dorban is a witcher, he is controlled by the file dorban_general.perkun. Pregor is a human, he is controlled by the file pregor_general.perkun. Thragos is also a human, he is controlled by the file thragos_general.perkun. These files can be found in the folder perkun_wars-0.0.3/perkun/final_code. They are generated with Prolog.

Payoff functions


payoff
{
set({where_is_Dorban=>place_Wyzima, do_I_see_vampire=>false}, 0.0);
set({where_is_Dorban=>place_Wyzima, do_I_see_vampire=>true}, 100.0);
set({where_is_Dorban=>place_Shadizar, do_I_see_vampire=>false}, 0.0);
set({where_is_Dorban=>place_Shadizar, do_I_see_vampire=>true}, 100.0);
set({where_is_Dorban=>place_Novigrad, do_I_see_vampire=>false}, 0.0);
set({where_is_Dorban=>place_Novigrad, do_I_see_vampire=>true}, 100.0);
}

Note that Dorban is getting 100.0 points when he can see the vampire. This is the reason why Dorban is hunting the vampire! Perkun attempts to maximize the expected value of the payoff function.

Take a look at the payoff function for Pregor:


payoff
{
set({where_is_Pregor=>place_Wyzima, do_I_see_vampire=>false}, 100.0);
set({where_is_Pregor=>place_Wyzima, do_I_see_vampire=>true}, 0.0);
set({where_is_Pregor=>place_Shadizar, do_I_see_vampire=>false}, 100.0);
set({where_is_Pregor=>place_Shadizar, do_I_see_vampire=>true}, 0.0);
set({where_is_Pregor=>place_Novigrad, do_I_see_vampire=>false}, 100.0);
set({where_is_Pregor=>place_Novigrad, do_I_see_vampire=>true}, 0.0);
}

Note that Pregor is getting 100.0 points when he cannot see the vampire. This is the reason why Pregor is avoiding the vampire. The same holds for Thragos:


payoff
{
set({where_is_Thragos=>place_Wyzima, do_I_see_vampire=>false}, 100.0);
set({where_is_Thragos=>place_Wyzima, do_I_see_vampire=>true}, 0.0);
set({where_is_Thragos=>place_Shadizar, do_I_see_vampire=>false}, 100.0);
set({where_is_Thragos=>place_Shadizar, do_I_see_vampire=>true}, 0.0);
set({where_is_Thragos=>place_Novigrad, do_I_see_vampire=>false}, 100.0);
set({where_is_Thragos=>place_Novigrad, do_I_see_vampire=>true}, 0.0);
}

Therefore there are two NPCs avoiding the vampire and one NPC hunting it. When you attack a vampire and there are some NPCs around - they are going to help you. The best strategy is to wait with the humans for the vampire. When he comes - attack him (the humans will help you). Hunting the vampire without any help is a bad strategy.

Take a look at the file src/perkun_wars.cc. It creates the pipes and then forks to three child processes that communicate with the main process through these pipes.

Take a look at the file inc/perkun_wars.h. You will find a class inherited from perkun::optimizer_with_all_data. The instance of this class is running the Perkun interpreter.