Skip to content

Commit

Permalink
Initial Python code
Browse files Browse the repository at this point in the history
  • Loading branch information
vontell committed Mar 16, 2023
0 parents commit 74360ec
Show file tree
Hide file tree
Showing 10 changed files with 783 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
venv/
105 changes: 105 additions & 0 deletions .replit
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# The command that runs the program. If the interpreter field is set, it will have priority and this run command will do nothing
run = "python3 main.py"

# The primary language of the repl. There can be others, though!
language = "python3"
entrypoint = "main.py"
# A list of globs that specify which files and directories should
# be hidden in the workspace.
hidden = ["venv", ".config", "**/__pycache__", "**/.mypy_cache", "**/*.pyc"]

# Specifies which nix channel to use when building the environment.
[nix]
channel = "stable-21_11"

# The command to start the interpreter.
[interpreter]
[interpreter.command]
args = [
"stderred",
"--",
"prybar-python3",
"-q",
"--ps1",
"\u0001\u001b[33m\u0002\u0001\u001b[00m\u0002 ",
"-i",
]
env = { LD_LIBRARY_PATH = "$PYTHON_LD_LIBRARY_PATH" }

[env]
VIRTUAL_ENV = "/home/runner/${REPL_SLUG}/venv"
PATH = "${VIRTUAL_ENV}/bin"
PYTHONPATH = "${VIRTUAL_ENV}/lib/python3.8/site-packages"
REPLIT_POETRY_PYPI_REPOSITORY = "https://package-proxy.replit.com/pypi/"
MPLBACKEND = "TkAgg"
POETRY_CACHE_DIR = "${HOME}/${REPL_SLUG}/.cache/pypoetry"

# Enable unit tests. This is only supported for a few languages.
[unitTest]
language = "python3"

# Add a debugger!
[debugger]
support = true

# How to start the debugger.
[debugger.interactive]
transport = "localhost:0"
startCommand = ["dap-python", "main.py"]

# How to communicate with the debugger.
[debugger.interactive.integratedAdapter]
dapTcpAddress = "localhost:0"

# How to tell the debugger to start a debugging session.
[debugger.interactive.initializeMessage]
command = "initialize"
type = "request"

[debugger.interactive.initializeMessage.arguments]
adapterID = "debugpy"
clientID = "replit"
clientName = "replit.com"
columnsStartAt1 = true
linesStartAt1 = true
locale = "en-us"
pathFormat = "path"
supportsInvalidatedEvent = true
supportsProgressReporting = true
supportsRunInTerminalRequest = true
supportsVariablePaging = true
supportsVariableType = true

# How to tell the debugger to start the debuggee application.
[debugger.interactive.launchMessage]
command = "attach"
type = "request"

[debugger.interactive.launchMessage.arguments]
logging = {}

# Configures the packager.
[packager]
language = "python3"
ignoredPackages = ["unit_tests"]

[packager.features]
enabledForHosting = false
# Enable searching packages from the sidebar.
packageSearch = true
# Enable guessing what packages are needed from the code.
guessImports = true

# These are the files that need to be preserved when this
# language template is used as the base language template
# for Python repos imported from GitHub
[gitHubImport]
requiredFiles = [".replit", "replit.nix", ".config", "venv"]

[languages]

[languages.python3]
pattern = "**/*.py"

[languages.python3.languageServer]
start = "pylsp"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Regression Games, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Python Bot (Regression Games)

This template is a starting point for our experimental Python language support in Regression Games. Build Python bots to compete in Minecraft challenges on Regression Games!

* See the [start.py](#start.py) file for starting code

## Requirements

To make a valid bot, you must:

* Have a file called `start.py`
* Have a function with signature `configure_bot(bot)`

## Known Limitations

Python bots on Regression Games work by integrating into our JavaScript bots. This means that the Python calls to the bot are complete via calls to a Node/JavaScript backend. There are some known limitations to the current setup.

* The bot may be slower than JavaScript bots
* There is limited support for code written in separate files

Please see this note for more limitations: https://regressiongg.notion.site/Python-Common-Errors-34ea3ed2e5de4cd29529c49638a92a42

_Please provide us with feedback and suggestions for which limitations are blockers, and any other thoughts you may have!_
22 changes: 22 additions & 0 deletions regression_games.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from rg_javascript import require, On

RG_BOT_VERSION = '1.10.0'
RG_CTF_UTILS_VERSION = '1.0.5'
MINEFLAYER_VERSION = '4.5.1'

# Because of how we load these JS modules to be used in Python, we
# define everything here to abstract away the "JS"-ness of it
# TODO: We can attach types here for easier development
mineflayer_pathfinder = require('mineflayer-pathfinder')
mineflayer = require('mineflayer', MINEFLAYER_VERSION)
rg_match_info = require('rg-match-info')
Vec3 = require('vec3').Vec3
RGBot = require('rg-bot', RG_BOT_VERSION).RGBot
FindResult = require('rg-bot', RG_BOT_VERSION).FindResult
RGCTFUtils = require('rg-ctf-utils', RG_CTF_UTILS_VERSION).RGCTFUtils
CTFEvent = require('rg-ctf-utils', RG_CTF_UTILS_VERSION).CTFEvent
armorManager = require('mineflayer-armor-manager')
Item = require('prismarine-item').Item
Entity = require('prismarine-entity').Entity
goals = mineflayer_pathfinder.goals
RGEventHandler = On
18 changes: 18 additions & 0 deletions replit.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{ pkgs }: {
deps = [
pkgs.python38Full
];
env = {
PYTHON_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
# Needed for pandas / numpy
pkgs.stdenv.cc.cc.lib
pkgs.zlib
# Needed for pygame
pkgs.glib
# Needed for matplotlib
pkgs.xorg.libX11
];
PYTHONBIN = "${pkgs.python38Full}/bin/python3.8";
LANG = "en_US.UTF-8";
};
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rg_javascript==1!1.0.4
193 changes: 193 additions & 0 deletions start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
"""
An example of a bot that uses a main loop to make decisions
on almost every tick of the game.
"""

import logging
import json
import traceback

logging.basicConfig(level=logging.NOTSET)

import os, sys
sys.path.append(os.path.dirname(__file__))


def configure_bot(bot):
"""
configure_bot is called by Regression games - this is where you configure
how your bot behaves
"""

from regression_games import RGBot, RGCTFUtils, armorManager, RGEventHandler, Vec3, Entity
from utilities import get_unbreakable_blocks, name_for_item, nearest_teammates, throttle_runtime
from strategy import handle_attack_flag_carrier, handle_attack_nearby_opponent, handle_bot_idle_position, handle_looting_items, handle_low_health, handle_placing_blocks, handle_scoring_flag, handle_collecting_flag


# Disable rg-bot debug logging. You can enable this to see more details about rg-bot api calls
bot.setDebug(False)

# Allow parkour so that our bots pathfinding will jump short walls and optimize their path for sprint jumps.
bot.allowParkour(True)

# We recommend disabling this on as you can't dig the CTF map. Turning this on can lead pathfinding to get stuck.
bot.allowDigWhilePathing(False)

# Setup the rg-ctf-utils with debug logging
rg_ctf_utils = RGCTFUtils(bot)
rg_ctf_utils.setDebug(True)

# Load the armor-manager plugin (https://github.com/PrismarineJS/MineflayerArmorManager)
bot.mineflayer().loadPlugin(armorManager)

# default to true in-case we miss the start
match_in_progress = True

# Information about the unbreakable block types
unbreakable = get_unbreakable_blocks(bot)

@RGEventHandler(bot, 'match_ended')
def match_ended(self, match_info, *args):
if match_info:
players = [p for p in match_info.players if p.username == bot.username()]
player = players[0] if players else None
if player:
points = player.metadata.score
captures = player.metadata.flagCaptures
print(f'The match has ended - I had {captures} captures and scored {points} points')
match_in_progress = False

@RGEventHandler(bot, 'match_started')
def match_started(self, match_info, *args):
print("The match has started")
match_in_progress = True

# Part of using a main loop is being careful not to leave it running at the wrong time.
# It is very easy to end up with 2 loops running by accident.
# Here we track the mainLoop instance count and update on key events.
main_loop_instance_tracker = 0


@RGEventHandler(bot, 'playerLeft')
def player_left(self, player, *args):
if (player.username == bot.username()):
print("I have left the match")
main_loop_instance_tracker += 1

@RGEventHandler(bot, 'end')
def end(self, *args):
print("I have disconnected")
main_loop_instance_tracker += 1

@RGEventHandler(bot, 'kicked')
def kicked(self, *args):
print("I have been kicked")
main_loop_instance_tracker += 1

@RGEventHandler(bot, 'death')
def death(self, *args):
print("I have died")
main_loop_instance_tracker += 1
try:
# Try to stop any goal currently going on
bot.mineflayer().pathfinder.setGoal(None)
bot.mineflayer().pathfinder.stop()
except Exception:
pass

# Take a look at the spawn event handler at the end
def main_loop():
current_main_loop_instance = main_loop_instance_tracker
is_active_function = lambda: match_in_progress and current_main_loop_instance == main_loop_instance_tracker
while is_active_function():

try:
# always throttle the runtime first to make sure we don't execute too frequently and waste CPU
throttle_runtime(bot)

if not bot.matchInfo():
print("Match info not available yet, waiting")
continue

#find out which team I'm on
my_team_name: str = bot.getMyTeam()
other_team_names: str = [t for t in bot.matchInfo().teams if t.name != my_team_name]
other_team_name = other_team_names[0].name if other_team_names else None

# get my current position and log information about my state
my_position: Vec3 = bot.position()
print(f'My team: {my_team_name}, my position: {bot.vecToString(my_position)}, my inventory: ${json.dumps([name_for_item(item) for item in bot.getAllInventoryItems()])}')

# find any opponents in range
opponent_names: list[str] = list(bot.getOpponentUsernames())
print(f'Found the following opponents: {opponent_names}')
print(opponent_names if opponent_names else ['...'])
opponents_results = bot.findEntities({
# opNames can be empty in practice mode where there is no other team
# if we don't pass some array to match, then this will return all entities instead
'entityNames': opponent_names if opponent_names else ['...'],
'attackable': True,
'maxCount': 3,
'maxDistance': 33, # Bots can only see ~30 +/1 blocks, so no need to search far
# override the default value function here as we aren't using this value in the sortValueFunction
'entityValueFunction': lambda entity_name: 0,
# just sort them by distance for now... We'll filter them by decision point later
'sortValueFunction': lambda distance, entity_value, health, defense, toughness: distance
})
opponents: list[Entity] = [o.result for o in opponents_results]

# find any teammates in range
teammates: list[Entity] = nearest_teammates(bot, 33, True)

# equip my best armor
bot.mineflayer().armorManager.equipAll()

# Only take 1 action per main loop pass. There are exceptions, but this is best practice as the
# game server can only process so many actions per tick
did_something: bool = False

if not did_something:
# Check if I'm low on health
did_something = handle_low_health(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# if someone has the flag, hunt down player with flag if it isn't a team-mate
did_something = handle_attack_flag_carrier(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# do I need to attack a nearby opponent
did_something = handle_attack_nearby_opponent(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# if I have the flag, go score
did_something = handle_scoring_flag(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# go pickup the loose flag
did_something = handle_collecting_flag(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# If no-one within N blocks, place blocks
did_something = handle_placing_blocks(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# see if we can find some items to loot
did_something = handle_looting_items(bot, rg_ctf_utils, opponents, teammates)

if not did_something:
# we had nothing to do ... move towards the middle
did_something = handle_bot_idle_position(bot, rg_ctf_utils, opponents, teammates)
except Exception as exc:
# if we get anything other than a pathfinding change error, log it so that we can fix our bot
if 'GoalChanged' not in str(exc) or 'PathStopped' not in str(exc):
print("An exception occurred while running this turn of logic")
print(traceback.format_exc())
# wait 1 seconds before looping again to avoid tight loops on errors
bot.wait(20)

print(f'Ended loop that ran for instance {main_loop_instance_tracker} of the bot')

@RGEventHandler(bot, 'spawn')
def on_spawn(self, *args):
bot.chat('I have come to win Capture The Flag with my main loop.')
main_loop()
Loading

0 comments on commit 74360ec

Please sign in to comment.