#include "ale_interface.hpp"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <vector>
#include "common/ColourPalette.hpp"
#include "common/Constants.h"
#include "emucore/Console.hxx"
#include "emucore/Props.hxx"
#include "environment/ale_screen.hpp"
#include "games/RomSettings.hpp"
namespace ale {
std::string ALEInterface::welcomeMessage() {
std::ostringstream oss;
oss << "A.L.E: Arcade Learning Environment (version " << Version << ")\n"
<< "[Powered by Stella]\n"
<< "Use -help for help screen.";
return oss.str();
}
void ALEInterface::disableBufferedIO() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
std::cin.rdbuf()->pubsetbuf(0, 0);
std::cout.rdbuf()->pubsetbuf(0, 0);
std::cin.sync_with_stdio();
std::cout.sync_with_stdio();
}
void ALEInterface::createOSystem(std::unique_ptr<OSystem>& theOSystem,
std::unique_ptr<Settings>& theSettings) {
#if (defined(WIN32) || defined(__MINGW32__))
theOSystem.reset(new OSystemWin32());
theSettings.reset(new SettingsWin32(theOSystem.get()));
#else
theOSystem.reset(new OSystemUNIX());
theSettings.reset(new SettingsUNIX(theOSystem.get()));
#endif
theOSystem->settings().loadConfig();
}
bool ALEInterface::isSupportedRom(
std::unique_ptr<OSystem>& theOSystem) {
const Properties properties = theOSystem->console().properties();
const std::string md5 = properties.get(Cartridge_MD5);
bool found = false;
std::ifstream ss("md5.txt");
std::string item;
while (!found && std::getline(ss, item)) {
if (!item.compare(0, md5.size(), md5)) {
return true;
}
}
return false;
}
void ALEInterface::loadSettings(const std::string& romfile,
std::unique_ptr<OSystem>& theOSystem) {
std::string configFile = theOSystem->settings().getString("config", false);
if (!configFile.empty()) {
theOSystem->settings().loadConfig(configFile.c_str());
}
theOSystem->settings().validate();
theOSystem->create();
if (romfile == "") {
Logger::Error << "No ROM File specified." << std::endl;
exit(1);
} else if (!FilesystemNode::fileExists(romfile)) {
Logger::Error << "ROM file " << romfile << " not found." << std::endl;
exit(1);
} else if (theOSystem->createConsole(romfile)) {
if (!isSupportedRom(theOSystem)) {
const Properties properties = theOSystem->console().properties();
const std::string md5 = properties.get(Cartridge_MD5);
const std::string name = properties.get(Cartridge_Name);
Logger::Warning << std::endl;
Logger::Warning << "WARNING: Possibly unsupported ROM: mismatched MD5."
<< std::endl;
Logger::Warning << "Cartridge_MD5: " << md5 << std::endl;
Logger::Warning << "Cartridge_name: " << name << std::endl;
Logger::Warning << std::endl;
}
Logger::Info << "Running ROM file..." << std::endl;
theOSystem->settings().setString("rom_file", romfile);
} else {
Logger::Error << "Unable to create console for " << romfile << std::endl;
exit(1);
}
Logger::Info << "Random seed is "
<< theOSystem->settings().getInt("random_seed") << std::endl;
theOSystem->resetRNGSeed();
std::string currentDisplayFormat = theOSystem->console().getFormat();
theOSystem->colourPalette().setPalette("standard", currentDisplayFormat);
}
ALEInterface::ALEInterface() {
disableBufferedIO();
Logger::Info << welcomeMessage() << std::endl;
createOSystem(theOSystem, theSettings);
}
ALEInterface::ALEInterface(bool display_screen) {
disableBufferedIO();
Logger::Info << welcomeMessage() << std::endl;
createOSystem(theOSystem, theSettings);
this->setBool("display_screen", display_screen);
}
ALEInterface::~ALEInterface() {}
void ALEInterface::loadROM(std::string rom_file = "") {
assert(theOSystem.get());
if (rom_file.empty()) {
rom_file = theOSystem->romFile();
}
loadSettings(rom_file, theOSystem);
RomSettings* wrapper = buildRomRLWrapper(rom_file);
if (wrapper == NULL) {
Logger::Error << std::endl
<< "Attempt to wrap ROM " << rom_file << " failed." << std::endl;
if (isSupportedRom(theOSystem)) {
Logger::Error << "It seems the ROM is supported." << std::endl;
} else {
Logger::Error
<< "This ROM may not be supported." << std::endl
<< "For a list of supported ROMs see "
<< "https://github.com/mgbellemare/Arcade-Learning-Environment"
<< std::endl;
}
Logger::Error
<< "Perhaps the filename isn't what we expected." << std::endl
<< "ROM files should be named using snake case, "
<< "e.g., space_invaders.bin" << std::endl;
exit(1);
}
romSettings.reset(wrapper);
environment.reset(new StellaEnvironment(theOSystem.get(), romSettings.get()));
max_num_frames = theOSystem->settings().getInt("max_num_frames_per_episode");
environment->reset();
#ifndef __USE_SDL
if (theOSystem->p_display_screen != NULL) {
Logger::Error
<< "Screen display requires directive __USE_SDL to be defined."
<< std::endl;
Logger::Error << "Please recompile this code with flag '-D__USE_SDL'."
<< std::endl;
Logger::Error << "Also ensure ALE has been compiled with USE_SDL active "
"(see ALE makefile)."
<< std::endl;
exit(1);
}
#endif
}
std::string ALEInterface::getString(const std::string& key) {
assert(theSettings.get());
return theSettings->getString(key);
}
int ALEInterface::getInt(const std::string& key) {
assert(theSettings.get());
return theSettings->getInt(key);
}
bool ALEInterface::getBool(const std::string& key) {
assert(theSettings.get());
return theSettings->getBool(key);
}
float ALEInterface::getFloat(const std::string& key) {
assert(theSettings.get());
return theSettings->getFloat(key);
}
void ALEInterface::setString(const std::string& key, const std::string& value) {
assert(theSettings.get());
assert(theOSystem.get());
theSettings->setString(key, value);
theSettings->validate();
}
void ALEInterface::setInt(const std::string& key, const int value) {
assert(theSettings.get());
assert(theOSystem.get());
theSettings->setInt(key, value);
theSettings->validate();
}
void ALEInterface::setBool(const std::string& key, const bool value) {
assert(theSettings.get());
assert(theOSystem.get());
theSettings->setBool(key, value);
theSettings->validate();
}
void ALEInterface::setFloat(const std::string& key, const float value) {
assert(theSettings.get());
assert(theOSystem.get());
theSettings->setFloat(key, value);
theSettings->validate();
}
void ALEInterface::reset_game() { environment->reset(); }
bool ALEInterface::game_over() const { return environment->isTerminal(); }
int ALEInterface::lives() {
if (!romSettings.get()) {
throw std::runtime_error("ROM not set");
}
return romSettings->lives();
}
reward_t ALEInterface::act(Action action) {
reward_t reward = environment->act(action, PLAYER_B_NOOP);
if (theOSystem->p_display_screen != NULL) {
theOSystem->p_display_screen->display_screen();
while (theOSystem->p_display_screen->manual_control_engaged()) {
Action user_action = theOSystem->p_display_screen->getUserAction();
reward += environment->act(user_action, PLAYER_B_NOOP);
theOSystem->p_display_screen->display_screen();
}
}
return reward;
}
ModeVect ALEInterface::getAvailableModes() {
return romSettings->getAvailableModes();
}
void ALEInterface::setMode(game_mode_t m) {
ModeVect available = romSettings->getAvailableModes();
if (find(available.begin(), available.end(), m) != available.end()) {
environment->setMode(m);
} else {
throw std::runtime_error("Invalid game mode requested");
}
}
DifficultyVect ALEInterface::getAvailableDifficulties() {
return romSettings->getAvailableDifficulties();
}
void ALEInterface::setDifficulty(difficulty_t m) {
DifficultyVect available = romSettings->getAvailableDifficulties();
if (find(available.begin(), available.end(), m) != available.end()) {
environment->setDifficulty(m);
} else {
throw std::runtime_error("Invalid difficulty requested");
}
}
ActionVect ALEInterface::getLegalActionSet() {
if (!romSettings.get()) {
throw std::runtime_error("ROM not set");
}
return romSettings->getAllActions();
}
ActionVect ALEInterface::getMinimalActionSet() {
if (!romSettings.get()) {
throw std::runtime_error("ROM not set");
}
return romSettings->getMinimalActionSet();
}
int ALEInterface::getFrameNumber() { return environment->getFrameNumber(); }
int ALEInterface::getEpisodeFrameNumber() const {
return environment->getEpisodeFrameNumber();
}
const ALEScreen& ALEInterface::getScreen() { return environment->getScreen(); }
void ALEInterface::getScreenGrayscale(
std::vector<unsigned char>& grayscale_output_buffer) {
size_t w = environment->getScreen().width();
size_t h = environment->getScreen().height();
size_t screen_size = w * h;
pixel_t* ale_screen_data = environment->getScreen().getArray();
theOSystem->colourPalette().applyPaletteGrayscale(
grayscale_output_buffer, ale_screen_data, screen_size);
}
void ALEInterface::getScreenRGB(std::vector<unsigned char>& output_rgb_buffer) {
size_t w = environment->getScreen().width();
size_t h = environment->getScreen().height();
size_t screen_size = w * h;
pixel_t* ale_screen_data = environment->getScreen().getArray();
theOSystem->colourPalette().applyPaletteRGB(output_rgb_buffer,
ale_screen_data, screen_size);
}
const ALERAM& ALEInterface::getRAM() { return environment->getRAM(); }
void ALEInterface::saveState() { environment->save(); }
void ALEInterface::loadState() { environment->load(); }
ALEState ALEInterface::cloneState() { return environment->cloneState(); }
void ALEInterface::restoreState(const ALEState& state) {
return environment->restoreState(state);
}
ALEState ALEInterface::cloneSystemState() {
return environment->cloneSystemState();
}
void ALEInterface::restoreSystemState(const ALEState& state) {
return environment->restoreSystemState(state);
}
void ALEInterface::saveScreenPNG(const std::string& filename) {
ScreenExporter exporter(theOSystem->colourPalette());
exporter.save(environment->getScreen(), filename);
}
ScreenExporter*
ALEInterface::createScreenExporter(const std::string& filename) const {
return new ScreenExporter(theOSystem->colourPalette(), filename);
}
}