Skip to content

Instantly share code, notes, and snippets.

@deluxghost
Last active July 19, 2018 15:32
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 deluxghost/b1c5e2c6104001ac5750745dc961dfdd to your computer and use it in GitHub Desktop.
Save deluxghost/b1c5e2c6104001ac5750745dc961dfdd to your computer and use it in GitHub Desktop.
TF2 Metal Calculator
# -*- coding: utf-8 -*-
# LICENSE: GPLv3
from __future__ import unicode_literals
from collections import OrderedDict
import decimal
from decimal import Decimal as D
from decimal import ROUND_DOWN
import re
import sys
import traceback
__version__ = '1.1.1'
try:
import colorama
colorama.init()
COLOR = True
except ImportError:
COLOR = False
try:
basestring
except NameError:
basestring = str
if sys.version_info[0] == 2:
input = raw_input
operations = OrderedDict([
("+", lambda x, y: x + y),
("-", lambda x, y: x - y),
("/", lambda x, y: x / y),
("*", lambda x, y: x * y)
])
symbols = operations.keys()
class Metal(object):
scrap = None
def __init__(self, ref='0', rec='0', scrap='0', weapon='0'):
self.scrap = D('0')
weapon = D(weapon).quantize(D('1'), rounding=ROUND_DOWN)
self.scrap += weapon / D('2')
scrap = (D(scrap) * D('2')).quantize(D('1')) / D('2')
self.scrap += scrap
rec3 = ((D(rec) * D('3')).quantize(D('1')) / D('3')).quantize(D('.01'), rounding=ROUND_DOWN)
rec9 = ((D(rec) * D('9')).quantize(D('1')) / D('9')).quantize(D('.01'), rounding=ROUND_DOWN)
self.scrap += D('3') * (rec3 // D('1'))
self.scrap += rec3 % D('1') // D('0.33')
if rec9 > rec3:
self.scrap += D('0.5')
if rec9 < rec3:
self.scrap -= D('0.5')
ref9 = ((D(ref) * D('9')).quantize(D('1')) / D('9')).quantize(D('.01'), rounding=ROUND_DOWN)
ref18 = ((D(ref) * D('18')).quantize(D('1')) / D('18')).quantize(D('.01'), rounding=ROUND_DOWN)
self.scrap += D('9') * (ref9 // D('1'))
self.scrap += ref9 % D('1') // D('0.11')
if ref18 > ref9:
self.scrap += D('0.5')
if ref18 < ref9:
self.scrap -= D('0.5')
def strfref(self, fmt):
scrap = self.scrap
ref_w = (scrap * D('2')).quantize(D('1'))
ref_W = (scrap % D('1') * D('2')).quantize(D('1'))
ref_s = scrap.quantize(D('.1'))
ref_S = (scrap % D('3')).quantize(D('1'), rounding=ROUND_DOWN)
ref_c = (scrap // D('3') + ref_S * D('0.33') + ref_W * D('0.16')).quantize(D('.01'))
ref_C = (scrap % D('9') // D('3')).quantize(D('.01'))
ref_r = (scrap // D('9') + ref_C * D('0.33') + ref_S * D('0.11') + ref_W * D('0.05')).quantize(D('.01'))
ref_R = (scrap // D('9')).quantize(D('.01'))
i = 0
n = len(fmt)
output = list()
push = output.append
while i < n:
char = fmt[i]
i += 1
if char == '%':
if i < n:
char = fmt[i]
i += 1
if char == 'w':
push(str(normalize(ref_w)))
elif char == 'W':
push(str(normalize(ref_W)))
elif char == 's':
push(str(normalize(ref_s)))
elif char == 'S':
push(str(normalize(ref_S)))
elif char == 'c':
push(str(normalize(ref_c)))
elif char == 'C':
push(str(normalize(ref_C)))
elif char == 'r':
push(str(normalize(ref_r)))
elif char == 'R':
push(str(normalize(ref_R)))
elif char == '%':
push('%')
else:
push('%')
push(char)
else:
push('%')
else:
push(char)
output = ''.join(output)
return output
def __str__(self):
return self.strfref('%r ref')
def __repr__(self):
return self.strfref('Metal(%r)')
def __neg__(self):
data = -(self.scrap)
return Metal(scrap=data)
def __pos__(self):
return Metal(scrap=self.scrap)
def __add__(self, other):
if isinstance(other, Metal):
data = self.scrap + other.scrap
elif isinstance(other, (int, float, D)) or isinstance(other, basestring):
other = Metal(other)
data = self.scrap + other.scrap
else:
return NotImplemented
return Metal(scrap=data)
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
if isinstance(other, Metal):
data = self.scrap - other.scrap
elif isinstance(other, (int, float, D)) or isinstance(other, basestring):
other = Metal(other)
data = self.scrap - other.scrap
else:
return NotImplemented
return Metal(scrap=data)
def __rsub__(self, other):
metal = self.__sub__(other)
metal.scrap = -(metal.scrap)
return metal
def __mul__(self, other):
if isinstance(other, (int, float, D)) or isinstance(other, basestring):
other = D(other)
data = self.scrap * other
else:
return NotImplemented
return Metal(scrap=data)
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
if isinstance(other, Metal):
data = self.scrap / other.scrap
data = normalize(data.quantize(D('.01')))
return data
elif isinstance(other, (int, float, D)) or isinstance(other, basestring):
other = D(other)
data = self.scrap / other
return Metal(scrap=data)
return NotImplemented
def __div__(self, other):
return self.__truediv__(other)
def __eq__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap == other.scrap
def __ne__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap != other.scrap
def __ge__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap >= other.scrap
def __le__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap <= other.scrap
def __gt__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap > other.scrap
def __lt__(self, other):
if not isinstance(other, Metal):
return NotImplemented
return self.scrap < other.scrap
def __nonzero__(self):
return self.scrap > D('0')
def __bool__(self):
return self.__nonzero__()
class ParserError(Exception):
def __init__(self, message):
super(ParserError, self).__init__()
self.message = message
def __repr__(self):
classname = self.__class__.__name__
return '{0}({1})'.format(classname, repr(self.message))
def __str__(self):
classname = self.__class__.__name__
return self.message
def normalize(d):
return d.quantize(D('1')) if d == d.to_integral() else d.normalize()
def convert(expr):
words = [
'ref',
'refined',
'rec',
'reclaimed',
'scrap',
'wep',
'weapon'
]
expr = ''.join(expr.lower().split())
keywords = re.sub(r'[\d.]', ' ', expr).split()
for w in keywords:
if w not in words:
raise ParserError('Bad Currency')
if not keywords:
return D(expr)
ref = D('0')
rec = D('0')
scrap = D('0')
wep = D('0')
for w in keywords:
index = expr.find(w)
value = expr[:index]
if not value:
raise ParserError('Bad Number')
expr = expr[index + len(w):]
if w == 'ref' or w == 'refined':
ref += D(value)
elif w == 'rec' or w == 'reclaimed':
rec += D(value)
elif w == 'scrap':
scrap += D(value)
elif w == 'wep' or w == 'weapon':
wep += D(value)
if expr:
raise ParserError('Bad Currency')
return Metal(ref, rec, scrap, wep)
def lex(expr):
tokens = []
while expr:
char = expr[0]
expr = expr[1:]
if char == "(":
try:
paren, expr = lex(expr)
tokens.append(paren)
except ValueError:
raise ParserError('Paren Mismatch')
elif char == ")":
for i, v in enumerate(tokens):
if not isinstance(v, list) and v not in symbols:
tokens[i] = convert(v)
return tokens, expr
elif char in symbols:
tokens.append(char)
elif char.isspace():
pass
else:
try:
if tokens[-1] in symbols:
tokens.append(char)
elif isinstance(tokens[-1], list):
raise ParserError('Invalid Syntax')
else:
tokens[-1] += char
except IndexError:
tokens.append(char)
for i, v in enumerate(tokens):
if not isinstance(v, list) and v not in symbols:
tokens[i] = convert(v)
return tokens
def evaluate(tokens):
for symbol, func in operations.items():
try:
pos = tokens.index(symbol)
leftTerm = evaluate(tokens[:pos])
if leftTerm is None:
if symbol in '+-' and pos + 1 < len(tokens) and tokens[pos + 1] not in ['+', '-', '*', '/']:
leftTerm = D('0')
else:
raise ParserError('Bad Expression')
rightTerm = evaluate(tokens[pos + 1:])
if rightTerm is None:
raise ParserError('Bad Expression')
return func(leftTerm, rightTerm)
except ValueError:
pass
if len(tokens) is 1:
if isinstance(tokens[0], list):
return evaluate(tokens[0])
return tokens[0]
elif not tokens:
return None
else:
raise ParserError('Bad Expression')
def calc(expr):
try:
ans = evaluate(lex(expr))
if isinstance(ans, D):
ans = normalize(ans.quantize(D('.01')))
except decimal.InvalidOperation:
raise ParserError('Precision Overflow')
except decimal.DivisionByZero:
raise ParserError('Division by Zero')
except TypeError:
raise ParserError('Meaningless Operation')
return ans
def calc_str(expr):
try:
return calc(expr)
except ParserError as e:
return 'Error: {}'.format(e.message)
except Exception as e:
return 'Error: {}'.format(e.__class__.__name__)
def _print_color(color, *args, **kw):
args = list(args)
if args:
args[0] = colorama.Style.RESET_ALL + color + args[0]
args[-1] = args[-1] + colorama.Style.RESET_ALL
else:
args = [color + colorama.Style.RESET_ALL]
print(*args, **kw)
def _print_func(mode, *args, **kw):
if COLOR:
mode_list = {
'title': colorama.Fore.LIGHTBLUE_EX,
'info': colorama.Fore.LIGHTYELLOW_EX,
'error': colorama.Fore.LIGHTRED_EX,
'input': colorama.Style.RESET_ALL,
'output': colorama.Fore.LIGHTGREEN_EX,
'prompt': colorama.Fore.LIGHTMAGENTA_EX,
'equal': colorama.Fore.LIGHTBLUE_EX
}
_print_color(mode_list[mode], *args, **kw)
else:
print(*args, **kw)
def _term_handler(signal, frame):
sys.exit(0)
if __name__ == '__main__':
tool_name = 'TF2 Metal Calculator {}'.format(__version__)
import platform
import signal
signal.signal(signal.SIGINT, _term_handler)
if '--nocolor' in sys.argv:
COLOR = False
args = [a for a in sys.argv[1:] if not a.startswith('--') and a != '-']
args = [a for a in args if not a.startswith('-') or len(a) != 2 or a[1] in '0123456789']
if args:
for expr in args:
answer = calc_str(expr)
if isinstance(answer, Metal):
print(answer.strfref('%rref'))
elif isinstance(answer, D):
print('{}'.format(answer))
elif isinstance(answer, basestring):
print(answer)
sys.exit(0)
if platform.system() == 'Windows' and getattr(sys, 'frozen', False):
import ctypes
ctypes.windll.kernel32.SetConsoleTitleW(tool_name)
_print_func('title', '{} by deluxghost\nType "quit" to exit.\nType "help" to get more information.'.format(tool_name))
while True:
try:
_print_func('prompt', '>> ', end='')
expr = input().strip()
except EOFError:
break
if not expr:
continue
if expr.lower() == 'q' or expr.lower() == 'quit' or expr.lower() == 'exit':
break
elif expr.lower() == 'h' or expr.lower() == 'help' or expr == '?':
_print_func('info',
'Use ref, rec, scrap or wep as metal unit.\n'
'Examples:'
)
_print_func('prompt', '>> ', end='')
_print_func('input', '(2.33ref1rec * 3 + 3scrap) / 2')
_print_func('equal', '= ', end='')
_print_func('output', '4.16ref')
_print_func('prompt', '>> ', end='')
_print_func('input', '2.55ref * 4')
_print_func('equal', '= ', end='')
_print_func('output', '10.22ref')
continue
answer = calc_str(expr)
if isinstance(answer, Metal):
_print_func('equal', '= ', end='')
_print_func('output', answer.strfref('%rref'))
elif isinstance(answer, D):
_print_func('equal', '= ', end='')
_print_func('output', '{}'.format(answer))
elif isinstance(answer, basestring):
_print_func('error', answer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment