Skip to content
Snippets Groups Projects
Verified Commit 85b61f3c authored by David Bergström's avatar David Bergström
Browse files

Merge replay related feature branch into master

Adds functionality for reading and replaying replay-files, as well as
relevant documentation. See merge request !6 for more details. Closes
issue #16.
parents 70768e2b 9f93af25
No related branches found
No related tags found
1 merge request!6Replays
Pipeline #17344 passed
Showing with 420 additions and 6 deletions
build/
.idea
venv
.vs
_autosummary/
_build/
venv
.idea
{% extends "!layout.html" %}
{% block menu %}
{{ super() }}
<a href="py-modindex.html">modindex</a>
{% endblock %}
\ No newline at end of file
......@@ -46,3 +46,6 @@ Race
The following attribute means a randomly selected race from the three above:
.. attribute:: Race.Random
.. toctree::
\ No newline at end of file
......@@ -38,6 +38,27 @@ The source code for all additional dependencies (including pybind11) are registe
The system is built using cmake, see the file `CMakeLists.txt <https://gitlab.liu.se/starcraft-ai-course/pycommandcenter/blob/master/CMakeLists.txt>`_ in the project root.
If a function already exist but you can't use it in python, check and see if it's exist in the library files (pybind).
#. Create a declaration of your function in the header file.
#. Create a definition of your function in the source file.
#. Depending on which object you decided to extend, you should also add this function to the library file. The library is the pybind, making it possible to use your function in python.
#. Update the documentation. There are instructions on how you build and test the documentation.
Example:
We want to add function to unit, returning a bool depending on if it's holding a mineral. We have discovered a function in the SC2 API containing this information (sc2_client.ccp). This is a helper function, it doesn't belong to any object.
#. In unit.h: bool isCarryingMinerals() const;
#. In unit.cpp: bool Unit::isCarryingMinerals() const { return sc2::IsCarryingMinerals(\*m_unit); }
#. We can access m_unit in the unit file and with sc2::Function() we can access any function in the blizzard API that doesn't belong to any object. The same goes for any object, for example sc2::Point3D makes us able to access the Point3D object.
#. In lib_unit.cpp: .def_property_readonly("is_carrying_minerals", &Unit::isCarryingMinerals)
#. In the folder named docs we update the documentation. In this case, we update the file unit.rst.
Common problems:
#. The return in python is a memory address: Make sure that it returns a correct type.
#. The compiler complains about files I have not even touched: Make sure that the startUp is library and you have release x64. If you just added a new function in pybind, check it.
#. The bot crashes whit a exit code -1073741819 (0xC0000005): Make sure that you dont read null values.
Document the new features / changes you have made
-------------------------------------------------
......@@ -61,3 +82,5 @@ Read the comments on your merge request and make the necessary changes
In this step someone will review your changes and make comments. These comments might ask you to make changes to the code or documentation. These changes must be done before the merge request is accepted. Therefore it is important to make merge requests several days before eventual deadlines.
Once the merge request has been accepted the code is now part of PyCommandCenter. Congratulations and thank you for your contribution to the project!
.. toctree::
\ No newline at end of file
......@@ -81,3 +81,4 @@ below:
print(p1.distance(p2)) # prints: 2.8284...
.. toctree::
\ No newline at end of file
......@@ -150,3 +150,4 @@ to:
where ``SomeOtherBot`` is a bot defined in the same way as ``MyAgent``.
.. toctree::
\ No newline at end of file
......@@ -188,3 +188,5 @@ BuildingPlacer
If you want to place a town hall, take a look at attribute `depot_location` of :class:`library.BaseLocation`.
If you want to place a refinery, take a look at attribute `geysers` of :class:`library.BaseLocation` and the method build_target of :class:`library.Unit`.
.. toctree::
\ No newline at end of file
......@@ -191,3 +191,4 @@ The debug-methods are a great tool for speeding up the process.
Set the amount (Float) of shield to the unit
.. toctree::
\ No newline at end of file
IDAReplayObserver
=================
.. class:: library.IDAReplayObserver
This is a class for following a replay.
Inherited methods:
.. method:: IDAReplayObserver.on_game_start(self)
This method is called when a replay has started, when you inherit it you have to
call the parent's on_game_start method in order to make it work (see
:ref:`replays`).
.. method:: IDAReplayObserver.on_step(self)
This method is called on every tick of the replay, when you inherit it you
have to call the parent's on_step method in order to make it work (see
:ref:`replays`).
.. method:: IDAReplayObserver.on_game_end(self)
This method is called when the replay has ended, when you inherit it you have to
call the parent's on_game_start method in order to make it work (see
:ref:`replays`).
.. method:: IDAReplayObserver.on_unit_created(self)
This method is called when the an unit is created, that includes when an unit leaves a refinery. This only works if replay perspective is not set to 0.
.. method:: IDAReplayObserver.on_unit_destroyed(self)
This unit is called when a unit is destroyed.
Methods:
.. method:: IDAReplayObserver.get_all_units(self) -> List[library.ReplayUnit]
Retrieves a list of all visible units
.. method:: IDAReplayObserver.get_player_race(self, player_id) -> library.Race
Returns the players race
.. toctree::
\ No newline at end of file
......@@ -22,13 +22,18 @@ Table of contents
:maxdepth: 3
gettingstarted
Library<_autosummary/library>
Coordinator<_autosummary/library.Coordinator>
helpers
idabot
unit
types
coordinates
constants
replays
idareplayobserver
replayunit
.. autosummary::
:toctree: _autosummary
:hidden:
......
.. _replays:
Replays
=======
This page will describe two different techniques for handling replays. The
first technique parses the information saved in the replay file. The second
uses the information in the replay file to actually replay the game. The
different techniques have certain advantages and disadvantages. The main
one is that the reader is a lot faster while replaying the game provides
a lot more information.
SC2Reader
---------
SC2Reader_ is a library that can be used to implement the first technique.
Here is a short example that uses SC2Reader to extract the build order.
.. _SC2Reader: https://github.com/ggtracker/sc2reader
.. code-block:: python
import sc2reader
import json
from sc2reader.factories import SC2Factory
# The path to the replays
PATH = "../Replays"
def main():
# Creates a generator of all the replays in the dictionary
replays = sc2reader.load_replays(PATH, load_level=3)
games = []
# Loops through every relay
for replay in replays:
building_order = []
if is_terran_vs_terran(replay):
# Loops through every event
for event in replay.events:
# Check if the event is that a building is crated
if type(event) == sc2reader.events.tracker.UnitInitEvent:
if event.unit.name not in building_order:
building_order.append(event.unit.name)
games.append(building_order)
return games
def is_terran_vs_terran(replay):
try:
return sc2reader.constants.LOCALIZED_RACES[replay.players[0].play_race] == "Terran" \
and sc2reader.constants.LOCALIZED_RACES[replay.players[1].play_race] == "Terran"
except(KeyError):
return False
if __name__ == '__main__':
games = main()
text = json.dumps(games)
print("Res: " + text)
SC2Reader has good documation_ that also describe what information
could be found. `What is in a Replay`_ and Events_ gives a good idea
if the information you want could be collected with SC2Reader.
.. _documation: https://sc2reader.readthedocs.io/en/latest/index.html
.. _What is in a Replay: https://sc2reader.readthedocs.io/en/latest/articles/whatsinareplay.html
.. _Events: https://sc2reader.readthedocs.io/en/latest/events/index.html
ReplayObserver
--------------
This is the second technique it is much like using a bot but with the difference
that no action can be preform just observations.
.. code-block:: python
from library import *
class MyObserver(ReplayObserver):
def __init__(self):
ReplayObserver.__init__(self)
def on_game_start(self):
ReplayObserver.on_game_start(self)
def on_step(self):
ReplayObserver.on_step(self)
def main():
coordinator = Coordinator(r"D:/StarCraft II/Versions/Base69232/SC2_x64.exe")
if coordinator.load_replay_list("D:/Replays/"):
observer = MyObserver()
coordinator.add_replay_observer(observer)
while coordinator.update():
pass
else:
print("No replays found")
if __name__ == "__main__":
main()
.. toctree::
\ No newline at end of file
ReplayUnit
==========
.. class:: library.ReplayUnit
An instance of the class Unit represents one unit in a replay. A ReplayUnit is a
:class:`library.Unit` white some limitations.
It is possible to use ReplayUnit as keys in a dictionary, which might be helpful
for bookkeeping.
Properties:
.. autoattribute:: buffs
Returns a list of BuffID
.. autoattribute:: build_percentage
.. autoattribute:: energy
.. autoattribute:: facing
Returns the direction the unit is facing
.. autoattribute:: hit_points
.. autoattribute:: max_hit_points
.. autoattribute:: id
.. autoattribute:: is_alive
.. autoattribute:: is_blip
Returns true if unit is a "blip" - a ping on the map.
.. autoattribute:: is_being_constructed
Returns build_progress > 0
.. autoattribute:: is_burrowed
.. autoattribute:: is_cloaked
.. autoattribute:: is_completed
Returns build_progress >= 1
.. autoattribute:: is_flying
.. autoattribute:: is_idle
.. autoattribute:: is_powered
.. autoattribute:: is_training
.. autoattribute:: is_valid
.. attribute:: ReplayUnit.player
Returns the constant corresponding to player which this unit belongs to.
See :ref:`playerconstants` for more information.
.. autoattribute:: position
.. autoattribute:: current_ability_id
.. autoattribute:: progress
Returns the progress of currently used ability (-1 if not using ability)
.. autoattribute:: radius
Retruns the radius of the unit
.. autoattribute:: shields
.. autoattribute:: tile_position
.. autoattribute:: unit_type
Returns the :class:`library.UnitType` of the unit
.. autoattribute:: weapon_cooldown
.. autoattribute:: is_carrying_minerals
Returns if this unit is currently holding minerals
.. toctree::
\ No newline at end of file
......@@ -53,3 +53,4 @@ EffectID is for things like ravager bile, or fungal or even a scan.
:members:
:undoc-members:
.. toctree::
\ No newline at end of file
......@@ -133,3 +133,5 @@ Unit
.. automethod:: stop_dance
Stop and dance
.. toctree::
\ No newline at end of file
#include "library.h"
namespace py = pybind11;
void define_replay_unit(py::module & m)
{
py::class_<ReplayUnit>(m, "ReplayUnit")
.def_property_readonly("id", &ReplayUnit::getID)
.def_property_readonly("unit_type", &ReplayUnit::getType, "The :class :`library.UnitType` of the unit")
.def_property_readonly("position", &ReplayUnit::getPosition, "The :class:`library.Point2D` of the unit")
.def_property_readonly("tile_position", &ReplayUnit::getTilePosition, "The :class:`library.Point2DI` of the unit")
.def_property_readonly("hit_points", &ReplayUnit::getHitPoints)
.def_property_readonly("shields", &ReplayUnit::getShields)
.def_property_readonly("energy", &ReplayUnit::getEnergy)
.def_property_readonly("player", &ReplayUnit::getPlayer)
.def_property_readonly("build_percentage", &ReplayUnit::getBuildPercentage)
.def_property_readonly("weapon_cooldown", &ReplayUnit::getWeaponCooldown)
.def_property_readonly("is_completed", &ReplayUnit::isCompleted)
.def_property_readonly("is_being_constructed", &ReplayUnit::isBeingConstructed)
.def_property_readonly("is_cloaked", &ReplayUnit::isCloaked)
.def_property_readonly("is_flying", &ReplayUnit::isFlying)
.def_property_readonly("buffs", &ReplayUnit::buffs)
.def_property_readonly("is_alive", &ReplayUnit::isAlive)
.def_property_readonly("is_powered", &ReplayUnit::isPowered)
.def_property_readonly("is_idle", &ReplayUnit::isIdle)
.def_property_readonly("is_burrowed", &ReplayUnit::isBurrowed)
.def_property_readonly("is_valid", &ReplayUnit::isValid)
.def_property_readonly("is_training", &ReplayUnit::isTraining)
.def_property_readonly("is_blip", &ReplayUnit::isBlip)
.def_property_readonly("target", &ReplayUnit::getTarget)
.def_property_readonly("has_target", &ReplayUnit::hasTarget)
.def_property_readonly("max_hit_points", &ReplayUnit::getMaxHitPoints)
.def_property_readonly("progress", &ReplayUnit::getProgress)
.def_property_readonly("current_ability_id", &ReplayUnit::getCurrentAbilityID, "The AbilityID of currently used ability")
.def_property_readonly("facing", &ReplayUnit::getFacing)
.def_property_readonly("radius", &ReplayUnit::getRadius)
.def_property_readonly("is_carrying_minerals", &ReplayUnit::isCarryingMinerals)
.def("__hash__", [](const ReplayUnit & unit) { return std::hash<const sc2::Unit *>{}(unit.getUnitPtr()); })
.def(py::self == py::self)
.def("__repr__", [](const ReplayUnit & unit) { return "<Unit of type: '" + unit.getType().getName() + "'>"; })
;
}
......@@ -26,5 +26,7 @@ void define_tech_tree(py::module & m)
py::class_<TechTree>(m, "TechTree")
.def("get_data", py::overload_cast<const UnitType &>(&TechTree::getData, py::const_))
.def("get_data", py::overload_cast<const CCUpgrade &>(&TechTree::getData, py::const_));
.def("get_data", py::overload_cast<const CCUpgrade &>(&TechTree::getData, py::const_))
.def("suppress_warnings", &TechTree::setSuppressWarnings, "Suppress type and uppgrade warnings" ,"b"_a)
;
}
......@@ -8,6 +8,7 @@ PYBIND11_MODULE(library, m)
define_typeenums(m);
define_unit(m);
define_replay_unit(m);
define_unittype(m);
define_util(m);
define_point(m);
......@@ -26,7 +27,11 @@ PYBIND11_MODULE(library, m)
.def("launch_starcraft", &sc2::Coordinator::LaunchStarcraft)
.def("start_game", &sc2::Coordinator::StartGame, "map_path"_a)
.def("update", &sc2::Coordinator::Update)
.def("set_real_time", &sc2::Coordinator::SetRealtime);
.def("set_real_time", &sc2::Coordinator::SetRealtime)
.def("load_replay_list",&sc2::Coordinator::SetReplayPath, "replay_path"_a)
.def("add_replay_observer",&sc2::Coordinator::AddReplayObserver, "replay_observer"_a)
.def("set_replay_perspective",&sc2::Coordinator::SetReplayPerspective, "perspective"_a)
;
py::enum_<sc2::Race>(m, "Race")
.value("Terran", sc2::Race::Terran)
......@@ -121,6 +126,33 @@ PYBIND11_MODULE(library, m)
// API extended summer 2020
py::class_<sc2::ReplayObserver>(m, "ReplayObserver")
.def(py::init());
py::class_<IDAReplayObserver, PyReplayObserver, sc2::ReplayObserver>(m, "IDAReplayObserver")
.def(py::init())
.def("on_game_start", &IDAReplayObserver::OnGameStart)
.def("on_step", &IDAReplayObserver::OnStep)
.def("on_game_end", &IDAReplayObserver::OnGameEnd)
.def("get_all_units", &IDAReplayObserver::GetAllUnits, "Returns a list of all units")
.def("get_player_race", &IDAReplayObserver::GetPlayerRace,"player_id"_a)
.def("get_replay_path", &IDAReplayObserver::GetReplayPath)
.def("get_result_for_player", &IDAReplayObserver::GetResultForPlayer, "player_id"_a)
.def("on_unit_destroyed", &IDAReplayObserver::OnReplayUnitDestroyed, "unit"_a)
.def("on_unit_created", &IDAReplayObserver::OnReplayUnitCreated, "unit"_a)
.def_property_readonly("tech_tree", &IDAReplayObserver::GetTechTree)
;
py::enum_<sc2::GameResult>(m, "GameResult")
.value("Win", sc2::GameResult::Win)
.value("Loss", sc2::GameResult::Loss)
.value("Tie", sc2::GameResult::Tie)
.value("Undecided", sc2::GameResult::Undecided);
py::class_<sc2::PlayerSetup>(m, "PlayerSetup");
py::enum_<sc2::Difficulty>(m, "Difficulty")
......
......@@ -3,6 +3,7 @@
#include <pybind11/pybind11.h>
#include <sc2api/sc2_api.h>
#include "../src/IDABot.h"
#include "../src/IDAReplayObserver.h"
#include <iostream>
#include <pybind11/stl.h> /* Automatic conversion from std::vector to Python lists */
#include <pybind11/operators.h> /* Convenient operator support */
......@@ -21,7 +22,7 @@ public:
}
Coordinator(std::string path) : sc2::Coordinator()
{
{
std::vector<std::string> arguments = { "pycommandcenter", "-e", path };
CustomLoadSettings(arguments);
}
......@@ -61,10 +62,67 @@ public:
}
};
class PyReplayObserver : public IDAReplayObserver
{
public:
using IDAReplayObserver::IDAReplayObserver;
void OnGameStart() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_game_start",
OnGameStart
);
}
void OnStep() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_step",
OnStep
);
}
void OnGameEnd() override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_game_end",
OnGameEnd
);
}
void OnReplayUnitDestroyed(const ReplayUnit *unit) override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_unit_destroyed",
OnReplayUnitDestroyed,
unit
);
}
void OnReplayUnitCreated(const ReplayUnit *unit) override
{
PYBIND11_OVERLOAD_NAME(
void,
IDAReplayObserver,
"on_unit_created",
OnReplayUnitCreated,
unit
);
}
};
// The functions below are all defined in different .cpp files, in order
// to keep compilation snappy
void define_typeenums(pybind11::module & m);
void define_unit(pybind11::module & m);
void define_replay_unit(pybind11::module & m);
void define_unittype(pybind11::module &m);
void define_util(pybind11::module &m);
void define_point(pybind11::module &m);
......
......@@ -72,12 +72,11 @@ void IDABot::OnStep()
m_map.onFrame();
m_unitInfo.onFrame();
m_bases.onFrame();
// -----------------------------------------------------------------
// Draw debug interface, and send debug interface to the Sc2 client.
// -----------------------------------------------------------------
Debug()->SendDebug();
m_buildingPlacer.drawReservedTiles();
m_buildingPlacer.drawReservedTiles();
}
void IDABot::setUnits()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment