Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 14

- name: Install python3-tk
run: sudo apt-get install --fix-missing python3-tk

Expand Down
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ Linux is strongly recommended for development. Everything should work on other p

### Prerequisites:

- [node.js](http://nodejs.org/download/)
- [GNU Make](https://www.gnu.org/software/make/)
- [Python](http://www.python.org/download/) 2.7 32 bit. This particular version is required to be able to interact with storm.dll using ctypes.
- [Python](http://www.python.org/download/) 2.7 32 bit for PyAI compiler (interacting with storm.dll) and Python 3.6+ for build scripts

### Build

Expand Down
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ build/combined.pyai: build/terran.pyai build/zerg.pyai build/protoss.pyai
build/%.pyai: src/%
@echo Building $@ $<
@cp tools/config_$(config).json tools/config.json
@node tools/build_ai $(subst src/,,$<) $@;
@python3 tools/build_ai.py $(subst src/,,$<) $@;
@rm tools/config.json

clean:
Expand Down
36 changes: 36 additions & 0 deletions tools/abbrevs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import json
import re
import os

# Load abbreviations from JSON file
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'abbrevs.json'), 'r') as f:
abbrevs = json.load(f)

# Build abbreviation replacements
abbrevs_replacements = []

for key in abbrevs:
for short in abbrevs[key]:
abbrevs_replacements.append({
'short': short,
'long': key,
})

def expand(abbrev):
"""Expand a single abbreviation to its full form"""
for a in abbrevs_replacements:
abbrev = re.sub(r'^' + a['short'] + r'$', a['long'], abbrev, flags=re.IGNORECASE)
return abbrev

def parse(content):
"""Parse content and expand abbreviations in function arguments"""
def replace_abbrev(match):
prefix = match.group(1)
arg = match.group(2)
postfix = match.group(3)
arg = expand(arg)
return prefix + arg + postfix

content = re.sub(r"([,\(] *)([A-Za-z ']*?)([,\)])", replace_abbrev, content)
return content
67 changes: 67 additions & 0 deletions tools/ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import re
import sys
import os

# Add tools directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from race import Race

class AI:
def __init__(self, race_name, config):
self.race_name = race_name
self.race = Race(race_name, config)
self.src = ""
self.config = config

def build(self):
"""Build the AI script for the race"""
# Default boilerplate
if self.race_name == 'terran':
self.src = 'TMCx(1342, 101, aiscript):\n'
elif self.race_name == 'protoss':
self.src = 'PMCx(1343, 101, aiscript):\n'
elif self.race_name == 'zerg':
self.src = 'ZMCx(1344, 101, aiscript):\n'

self.src += self.race.load_contents('main')

# Add wait(1) before non-special lines
def add_wait(match):
return 'wait(1)\n' + match.group(0) + '\n'

self.src = re.sub(r'^(?!(TMCx|ZMCx|PMCx|\-\-|#|debug|random)).+$',
add_wait, self.src, flags=re.MULTILINE)

# Add debug blocks for verbosity >= 10
verbosity = self.config.get('verbosity', 0)
race_verbosity = self.config.get(self.race_name, {}).get('verbosity', 0)

if verbosity >= 10 or race_verbosity >= 10:
debug_count = 0

def get_code(num):
"""Generate a short code from a number"""
valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_'

tens = num // len(valid_chars)
remainder = num - (tens * len(valid_chars))

tens -= 1

tens_char = valid_chars[tens] if tens >= 0 else ''
return tens_char + valid_chars[remainder]

def add_debug(match):
nonlocal debug_count
debug_count += 1
block_name = f'd10_{debug_count}'
code = get_code(debug_count)

return (f'debug({block_name}, {code})\n'
f'--{block_name}--\n'
+ match.group(0) + '\n')

self.src = re.sub(r'^(?!(TMCx|ZMCx|PMCx|\-\-|#|debug|wait)).+$',
add_debug, self.src, flags=re.MULTILINE)

return self.src
63 changes: 63 additions & 0 deletions tools/buildConverter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import json
import re
import os
import sys

# Add tools directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import abbrevs

# Load units from JSON file
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, 'units.json'), 'r') as f:
units = json.load(f)

def is_unit(unit):
"""Check if the given string is a unit"""
return any(u['unit'] == unit for u in units)

def get_unit_info(unit):
"""Get information for a specific unit"""
for u in units:
if u['unit'] == unit:
return u
return None

def parse(content):
"""Parse build orders and convert them to script commands"""
owned = {}
supply_from_units = 0

def replace_build(match):
nonlocal supply_from_units
supply = int(match.group(1))
unit = match.group(2)

unit = abbrevs.expand(unit)

if unit not in owned:
owned[unit] = 0
owned[unit] += 1

wait_for_worker = int(supply - supply_from_units)

ret = f'build({wait_for_worker}, Peon, 80)\n'
ret += f'wait_buildstart({wait_for_worker}, Peon)\n'

if is_unit(unit):
unit_info = get_unit_info(unit)
# Use int() to match JavaScript's parseInt() behavior which truncates decimals
supply_from_units += int(float(unit_info['supply']))

ret += f'train({owned[unit]}, {unit})\n'
elif unit == 'Expand' or unit == 'expand':
ret += 'expand(1, gen_expansions_expansion)\n'
else:
ret += f'build({owned[unit]}, {unit}, 80)\n'
ret += f'wait_buildstart({owned[unit]}, {unit})\n'

return ret

content = re.sub(r'^(\d+) (.*)$', replace_build, content, flags=re.MULTILINE)

return content
46 changes: 46 additions & 0 deletions tools/build_ai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import sys
import os
import json
import subprocess
from datetime import datetime

# Add tools directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ai import AI

def build(input_race, output_file):
"""Build the AI script for the given race"""
# Load config
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'config.json')

with open(config_path, 'r') as f:
config = json.load(f)

# Build the AI
ai = AI(input_race, config)
src = ai.build()

# Get git commit hash
try:
result = subprocess.run(['git', 'rev-parse', 'HEAD'],
capture_output=True, text=True, check=True)
commit = result.stdout.strip()[:6]
except:
commit = '000000'

# Replace placeholders
src = src.replace('{commit}', commit)
src = src.replace('{now}', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))

# Write output
with open(output_file, 'w', encoding='utf-8') as f:
f.write(src)

if __name__ == '__main__':
if len(sys.argv) >= 3:
build(sys.argv[1], sys.argv[2])
else:
print(f'Usage: python3 {sys.argv[0]} race output')
sys.exit(1)
41 changes: 41 additions & 0 deletions tools/build_launcher_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
import os
import json
import sys

# Add tools directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import build_ai

races = ['zerg', 'terran', 'protoss']

for race in races:
builds_dir = os.path.join('src', race, 'builds')

if not os.path.exists(builds_dir):
continue

builds = [f for f in os.listdir(builds_dir) if f.endswith('.pyai')]

for build in builds:
print(race, build)

# Load default config
with open('tools/config_default.json', 'r') as f:
config = json.load(f)

# Set the specific build
if race not in config:
config[race] = {}
config[race]['useBuild'] = build

# Write temporary config
with open('tools/config.json', 'w') as f:
json.dump(config, f)

# Build the AI
output_dir = os.path.join('dist', 'BWAILauncher_package', race)
os.makedirs(output_dir, exist_ok=True)

output_file = os.path.join(output_dir, build.replace('.pyai', '.txt'))
build_ai.build(race, output_file)
18 changes: 18 additions & 0 deletions tools/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"srcPath": "src/",
"verbosity": 0,
"bonusWorkers": 0,
"difficulty": 0,
"terran": {
"verbosity": 0,
"useBuild": ""
},
"zerg": {
"verbosity": 0,
"useBuild": ""
},
"protoss": {
"verbosity": 0,
"useBuild": ""
}
}
Loading