Skip to content

Instantly share code, notes, and snippets.

@johnchen902
Last active March 11, 2019 13:38
Show Gist options
  • Save johnchen902/9c877b14eb272d0d0dbacf68847d83e2 to your computer and use it in GitHub Desktop.
Save johnchen902/9c877b14eb272d0d0dbacf68847d83e2 to your computer and use it in GitHub Desktop.
Girls Frontline Chip Calculator
#!/usr/bin/env python3
from dataclasses import dataclass
from ortools.sat.python import cp_model
from functools import reduce
### PARAMETERS
def get_available_area():
# This is 5-star AGS-30, change it for different HOC/stars.
return Mask("""
..xx....
.xxxx...
xxxxxx..
xxxxxxx.
.xxxxxxx
..xxxxxx
...xxxx.
....xx..
""")
def get_params(area):
# Minimum filled blocks, fully filled by default
minc = area.count()
# Minimum 殺傷
mind = 0
# Minimum 破防
minp = 0
# Minimum 精度
mina = 0
# Minimum 裝填
minr = 0
# Maximum calibration tickets (校準點券), default zero (i.e. no rotation)
maxt = 0
return minc, mind, minp, mina, minr, maxt
def get_chips():
chips = []
def add_chip(maskstr, **kwargs):
chips.append(Chip(Mask(maskstr), **kwargs))
# You can add a chip like this:
# a=精度 r=裝填 d=殺傷 p=破防
add_chip('''
xx
xxx
.x
''', a=15, r=6, d=9, p=13)
# You can also use a normal string
add_chip('.xx\nxx\n.xx', a=15, r=6, d=9, p=13)
# Stats can be written in any order
# Unspecified stats default to zero
add_chip('xx\n.xx\n.x', d=9, r=18)
# Some extra chips to make this example feasible
add_chip('.x\nxxx\nx')
add_chip('xx\n.xx\n..x')
add_chip('..x\nxxxx')
add_chip('xxxx\n.xx')
return chips
### END OF PARAMETERS
def iterate(func, init):
while True:
yield init
try:
init = func(init)
except ValueError:
break
@dataclass
class Mask:
mask: int
def __init__(self, mask):
if not isinstance(mask, int):
mask = Mask.parse_mask(mask)
self.mask = mask
def width(self):
for i in range(8): #fedcba9876543210
if self.mask & (0x0101010101010101 << (7 - i)):
return 8 - i
return 0
def height(self):
for i in range(8):
if self.mask & (0xff << (8 * (7 - i))):
return 8 - i
return 0
def shift_right(self):
if self.width() >= 8:
raise ValueError
return Mask(self.mask << 1)
def shift_down(self):
if self.height() >= 8:
raise ValueError
return Mask(self.mask << 8)
def translations(self):
return [right for down in iterate(Mask.shift_down, self)
for right in iterate(Mask.shift_right, down)]
def rotate_clockwise(self):
"""123 741
456 -> 852
789 963"""
width, height = self.width(), self.height()
newmask = 0
for j in range(height):
for i in range(width):
if self.mask & (1 << (j * 8 + i)):
j2, i2 = i, height - 1 - j
newmask |= 1 << (j2 * 8 + i2)
return Mask(newmask)
def rotations(self):
rotations = [self]
while True:
newmask = rotations[-1].rotate_clockwise()
if newmask == self:
break
rotations.append(newmask)
return rotations
def is_superset_of(self, other):
return (other.mask & ~self.mask) == 0
def count(self):
return bin(self.mask).count('1')
@staticmethod
def parse_mask(s, x='x'):
mask = 0
for i, line in enumerate(s.strip().split('\n')):
for j, c in enumerate(line.strip()):
if c == x:
mask += 1 << (i * 8 + j)
return mask
@dataclass
class Chip:
mask: Mask
d: int = 0 # 殺傷 (damage)
p: int = 0 # 破防 (penetration)
a: int = 0 # 精度 (accuracy)
r: int = 0 # 裝填 (reload/rate of fire)
t: int = 0 # 校準點券 (calibration tickets)
def with_mask(self, mask):
return Chip(mask, self.d, self.p, self.a, self.r, self.t)
def with_t(self, t):
return Chip(self.mask, self.d, self.p, self.a, self.r, t)
class ChipSolutionPrinter(cp_model.CpSolverSolutionCallback):
def __init__(self, area, chips):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__area = area
self.__chips = chips
self.__solution_count = 0
def __empty_result(self):
result = []
for i in range(8):
for j in range(8):
if self.__area.mask & (1 << (i * 8 + j)):
result.append('.')
else:
result.append(' ')
result.append('\n')
return result
def on_solution_callback(self):
self.__solution_count += 1
result = self.__empty_result()
print(f'Solution #{self.__solution_count}')
print(f'Label ID Dmg Pen Acc Rel')
idx = count = damage = penetration = accuracy = rof = tickets = 0
for var, chip in self.__chips:
if not self.Value(var):
continue
label = chr(ord('A') + idx)
chipid = int(str(var)[1 : str(var).find('_')])
rotatestr = '*' if chip.t else ''
print(f'{label} {chipid:6}{rotatestr:1} {chip.d:3} {chip.p:3} {chip.a:3} {chip.r:3}')
count += chip.mask.count()
damage += chip.d
penetration += chip.p
accuracy += chip.a
rof += chip.r
tickets += chip.t
for i in range(8):
for j in range(8):
if chip.mask.mask & (1 << (i * 8 + j)):
result[i * 9 + j] = label
idx += 1
print(f'Total -- {damage:3} {penetration:3} {accuracy:3} {rof:3}')
if tickets:
print(f'Calibration Tickets: {tickets}')
print(''.join(result).rstrip())
print()
def solution_count(self):
return self.__solution_count
def main():
area = get_available_area()
minc, mind, minp, mina, minr, maxt = get_params(area)
primary_chips = get_chips()
model = cp_model.CpModel()
chips = [] # [(var, chip)]
for i, chip in enumerate(primary_chips):
variants = []
for n_rotate, mask2 in enumerate(chip.mask.rotations()):
for j, mask3 in enumerate(mask2.translations()):
if not area.is_superset_of(mask3):
continue
chip3 = chip.with_mask(mask3)
if n_rotate:
chip3 = chip3.with_t(50)
var = model.NewBoolVar(f'x{i}_{n_rotate}_{j}')
chips.append((var, chip3))
variants.append(var)
model.AddSumConstraint(variants, 0, 1)
for i in range(64):
if not area.mask & (1 << i):
continue
elemvars = [var for var, chip in chips if chip.mask.mask & (1 << i)]
model.AddSumConstraint(elemvars, 0, 1)
model.Add(reduce(lambda x, y: x + y,
(var * chip.mask.count() for var, chip in chips))
>= minc)
model.Add(reduce(lambda x, y: x + y,
(var * chip.d for var, chip in chips)) >= mind)
model.Add(reduce(lambda x, y: x + y,
(var * chip.p for var, chip in chips)) >= minp)
model.Add(reduce(lambda x, y: x + y,
(var * chip.a for var, chip in chips)) >= mina)
model.Add(reduce(lambda x, y: x + y,
(var * chip.r for var, chip in chips)) >= minr)
model.Add(reduce(lambda x, y: x + y,
(var * chip.t for var, chip in chips)) <= maxt)
solver = cp_model.CpSolver()
solution_printer = ChipSolutionPrinter(area, chips)
status = solver.SearchForAllSolutions(model, solution_printer)
print(f'Status = {solver.StatusName(status)}')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment