Skip to content

Instantly share code, notes, and snippets.

@cb109
Last active September 17, 2017 12:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cb109/8533611ffc05dbeab43a5d2ec97fe6d5 to your computer and use it in GitHub Desktop.
Save cb109/8533611ffc05dbeab43a5d2ec97fe6d5 to your computer and use it in GitHub Desktop.
The Patrician II: OCR based Trade Recommendations
_________________________________________________
___ __ _ _ ________
/ _ \___ _/ /_____(_)___(_)__ ____ / _/ _/
/ ___/ _ `/ __/ __/ / __/ / _ `/ _ \ _/ /_/ /
/_/ \_,_/\__/_/ /_/\__/_/\_,_/_//_/ /___/___/
_________________________________________________
You should buy in this town:
beer for max. 52
You should sell in this town:
cloth for min. 400
fish for min. 650
honey for min. 200
iron goods for min. 500
spices for min. 500
timber for min. 60
whale oil for min. 160
wine for min. 400
# -*- coding: utf-8 -*-
"""
Give live trading recommendations while playing The Patrician II.
The current 'Trading Goods' window's text is analyzed (OCR done via a
3rd party tool) and checked against a price table. Recommended buy/sell
actions are printed to the console.
Requirements:
OS:
- Windows
Software:
- Patrician II (english version)
- Capture2Text_CLI.exe
Download it from: http://capture2text.sourceforge.net
and make sure to add its directory to the PATH variable.
Usage:
- Start The Patrician II, load a game and while in a city, open the
'Trading Goods' window by clicking on a trade port building. Make
sure to not to overlap the window with your mouse, keep it
outside.
- Open a console and move it to a second screen.
- Run the script like:
$ python patrician2_trade_recommendations.py
The console should show trade recommendations.
"""
import os
import re
import subprocess
import time
HEADER = """
_________________________________________________
___ __ _ _ ________
/ _ \___ _/ /_____(_)___(_)__ ____ / _/ _/
/ ___/ _ `/ __/ __/ / __/ / _ `/ _ \ _/ /_/ /
/_/ \_,_/\__/_/ /_/\__/_/\_,_/_//_/ /___/___/
_________________________________________________
"""
DEBUG = False
WAIT_IN_BETWEEN_SECONDS = 2
# Note: Assuming a screen resolution of 1280x720.
# Format is: X1 Y1 X2 Y2
TRADING_GOODS_SCREEN_RECT = "311 331 569 758"
# Taken from: http://www.spieletipps.de/tipps-10027-patrizier-2-preisliste/
# good: [buy-price, sell-price]
TRADING_RANGES = {
"beer": [39, 56],
"tiles": [88, 110],
"cloth": [229, 286],
"fish": [479, 599],
"grain": [119, 149],
"hemp": [453, 566],
"honey": [121, 151],
"iron goods": [285, 356],
"leather": [241, 302],
"meat": [1041, 1302],
"iron ore": [953, 1191],
"pitch": [61, 76],
"pottery": [182, 227],
"salt": [31, 39],
"skins": [748, 935],
"spices": [296, 370],
"timber": [61, 76],
"whale oil": [91, 114],
"wine": [244, 305],
"wool": [928, 1160],
}
# For goods consisting of two words like 'iron goods' OCR sometimes does
# not recognize the separating space, so add aliases for this and other
# common mistakes.
for key, value in TRADING_RANGES.items():
if " " in key:
alias = key.replace(" ", "")
TRADING_RANGES[alias] = value
TRADING_RANGES["moat"] = TRADING_RANGES["meat"]
TRADING_RANGES["whalc oil"] = TRADING_RANGES["whale oil"]
TRADING_RANGES["whalcoil"] = TRADING_RANGES["whaleoil"]
pattern_digit = re.compile("\d+")
pattern_digit_only = re.compile("^\d+$")
pattern_good = re.compile("(?P<good>^[a-z ]+[^ \d][a-z])(?P<pricerange>.*)")
def get_text_from_trading_goods_screen():
args = [
"Capture2Text_CLI",
"-l", "English",
"--screen-rect", TRADING_GOODS_SCREEN_RECT,
"--line-breaks",
]
text = subprocess.check_output(args)
return text
def should_trade(mode, good, price, TRADING_RANGES=TRADING_RANGES):
"""Return price limit if we should trade, False otherwise."""
good_buy, good_sell = TRADING_RANGES[good]
if mode == "buy":
return good_buy if price <= good_buy else False
elif mode == "sell":
return good_sell if price >= good_sell else False
def compensate_common_mistakes(token):
# Round boxes around prices are often recognized as a "J" or "')".
removals = ["J", "'", ")", ",", "\\", "r"]
replacements = [
("o", "0"),
("O", "0"),
("s", "5"),
("s", "5"),
]
for removal in removals:
token = token.replace(removal, "")
for this, that in replacements:
token = token.replace(this, that)
token = token.strip()
return token
def analyze_text_for_recommandations(text):
recommendations = []
lines = text.split("\n")
for line in lines:
try:
digits = pattern_digit.findall(line)
if not digits:
continue
match = pattern_good.match(line)
if not match:
continue
good = match.group("good")
pricerange = match.group("pricerange")
tokens = pricerange.split(" ")
filtered_tokens = []
for token in tokens:
token = compensate_common_mistakes(token)
if not pattern_digit_only.match(token):
continue
filtered_tokens.append(token)
if DEBUG:
print(good, "tokens:", tokens)
print(good, "filtered_tokens:", filtered_tokens)
amount = buy_for = sell_for = None
in_stock = len(filtered_tokens) == 3
if in_stock:
amount, buy_for, sell_for = [int(t) for t in filtered_tokens]
else:
_, sell_for = [int(t) for t in filtered_tokens]
can_buy = amount is not None and buy_for is not None
can_sell = sell_for is not None
if can_buy:
price = should_trade("buy", good, buy_for)
if price:
recommendations.append(("buy", good, price))
if can_sell:
price = should_trade("sell", good, sell_for)
if price:
recommendations.append(("sell", good, price))
except (KeyError, ValueError) as err:
if DEBUG:
print(err)
return recommendations
def format_good(good, digits=12):
good = str(good)
add = digits - len(good)
return good + (" " * add)
def format_price(price, digits=4):
price = str(price)
add = digits - len(price)
return (" " * add) + price
def clear_screen():
os.system('cls')
def print_report(recommendations, clear=True):
to_buy = [rec for rec in recommendations if rec[0] == "buy"]
to_sell = [rec for rec in recommendations if rec[0] == "sell"]
if clear:
clear_screen()
print(HEADER)
if to_buy:
print("You should buy in this town:\n")
for _, good, price in to_buy:
print(" {} for max. {}".format(format_good(good),
format_price(price)))
print("")
if to_sell:
print("You should sell in this town:\n")
for _, good, price in to_sell:
print(" {} for min. {}".format(format_good(good),
format_price(price)))
if not recommendations:
print("Nothing to analyze. Please open a 'Trading Goods' window.")
print("")
def main():
time.sleep(WAIT_IN_BETWEEN_SECONDS)
output = get_text_from_trading_goods_screen()
recommendations = analyze_text_for_recommandations(output)
if not DEBUG:
print_report(recommendations)
while True:
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment