5D_Heredero_Louis_TM2022/python/lycacode_gen.py

285 lines
8.3 KiB
Python
Raw Normal View History

2022-09-22 11:13:15 +00:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module can be used to create and display Lycacodes
(C) 2022 Louis Heredero louis.heredero@edu.vs.ch
"""
import pygame
import numpy as np
import hamming
S = 600
class LycacodeError(Exception):
pass
class Lycacode:
RES = 3
BLOCKSIZE = 7
MODE_PERSON = 0
MODE_LOC = 1
MODE_LINK = 2
MODE_TEXT = 3
PERSON_STUDENT = 0
PERSON_TEACHER = 1
PERSON_OTHER = 2
BLACK = (158,17,26)
#BLACK = (0,0,0)
WHITE = (255,255,255)
OFFSETS = [(0,-1), (1,0), (0,1), (-1,0)]
MASKS = [
lambda x, y: x%3 == 0,
lambda x, y: y%3 == 0,
lambda x, y: (x+y)%3 == 0,
lambda x, y: (x%3)*(y%3)==0,
lambda x, y: (y//3+x//3)%2==0,
lambda x, y: (y%3-1)*(x%3-y%3-2)*(y%3-x%3-2)==0,
lambda x, y: (abs(13-x)+abs(13-y))%3==1,
lambda x, y: (1-x%2 + max(0, abs(13-y)-abs(13-x))) * (1-y%2 + max(0,abs(13-x)-abs(13-y))) == 0
]
FRAME = True
CIRCLES = True
DOTS = True
DB_SQUARES = False
def __init__(self, data, mode):
self.data = data
self.mode = mode
self.encode()
self.create_matrix()
def encode(self):
self.bits = f"{self.mode:02b}"
if self.mode == self.MODE_PERSON:
type_ = self.data["type"]
id_ = self.data["id"]
self.bits += f"{type_:02b}"
self.bits += f"{id_:020b}"
if type_ == self.PERSON_STUDENT:
year = self.data["year"]
class_ = self.data["class"]
self.bits += f"{year:03b}"
self.bits += f"{class_:04b}"
in1, in2 = self.data["initials"]
in1, in2 = ord(in1)-ord("A"), ord(in2)-ord("A")
self.bits += f"{in1:05b}"
self.bits += f"{in2:05b}"
# 83 left
elif type_ == self.PERSON_TEACHER:
# 100 left
pass
elif type_ == self.PERSON_OTHER:
# 100 left
pass
elif self.mode == self.MODE_LOC:
section = self.data["section"]
room = self.data["room"]
self.bits += f"{section:03b}"
self.bits += f"{room:09b}"
# 106 left
elif self.mode == self.MODE_LINK:
self.bits += f"{self.data:032b}"
# 86 left
elif self.mode == self.MODE_TEXT: # max 14 chars
data = self.data.encode("utf-8")
self.bits += f"{len(data):04b}"
self.bits += "".join(list(map(lambda b: f"{b:08b}", data)))
# 2 left
else:
raise LycacodeError(f"Invalid mode {self.mode}")
ds = self.BLOCKSIZE-self.BLOCKSIZE.bit_length()
total_bits = (self.RES**2 * 24 - 6)
data_bits = total_bits * ds // self.BLOCKSIZE
self.bits += "0"*(ds-len(self.bits)%ds)
s = ""
i = 0
left = data_bits-len(self.bits)
while len(s) < left:
s += f"{i:0b}"
i += 1
s = s[:left]
self.bits += s
self.bits = hamming.encode(self.bits, self.BLOCKSIZE)
def create_matrix(self):
R = self.RES
self.matrix = np.zeros([R*9, R*9])-1
self.matrix[R*4:R*5, :] = 0
self.matrix[:, R*4:R*5] = 0
self.matrix[R:R*2, R*3:R*6] = 0
self.matrix[R*3:R*6, R:R*2] = 0
self.matrix[-R*2:-R, -R*6:-R*3] = 0
self.matrix[-R*6:-R*3, -R*2:-R] = 0
self.matrix[R*4:R*5,R*4:R*5] = -2
self.matrix[0, R*4:R*5] = -2 # mask
self.matrix[-1, R*4:R*5] = -2 # mask
mask_area = np.where(self.matrix == 0, 1, 0)
self.matrix[R*4, R*4+1:R*5-1] = 1
self.matrix[R*5-1, R*4] = 1
self.matrix[R*4+1:R*5-1, R*4+1:R*5-1] = 1
bits = list(map(int, self.bits))
bits = np.reshape(bits, [-1,self.BLOCKSIZE]).T
bits = np.reshape(bits, [-1]).tolist()
for y in range(R*9):
for x in range(R*9):
if self.matrix[y,x] == 0:
self.matrix[y,x] = bits.pop(0)
if len(bits) == 0:
break
if len(bits) == 0:
break
self.matrix = np.where(self.matrix==-2, 0, self.matrix)
best = [None, None, None]
for i, mask in enumerate(self.MASKS):
score, matrix = self.evaluate(mask, mask_area)
if best[0] is None or score < best[0]:
best = (score, matrix, i)
self.matrix = best[1]
id_ = list(map(int, f"{best[2]:03b}"))
self.matrix[0, R*4:R*5] = id_ # mask
self.matrix[-1, R*4:R*5] = id_ # mask
def evaluate(self, mask, mask_area):
matrix = self.matrix.copy()
for y in range(self.matrix.shape[0]):
for x in range(self.matrix.shape[1]):
if mask_area[y][x] and mask(x,y):
matrix[y][x] = 1-matrix[y][x]
score = 0
# 3 or more of the same color (horizontal)
for y in range(self.matrix.shape[0]):
c = 0
col = -1
for x in range(self.matrix.shape[1]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 3 or more of the same color (vertical)
for x in range(self.matrix.shape[1]):
c = 0
col = -1
for y in range(self.matrix.shape[0]):
if matrix[y][x] == -1: continue
if col != matrix[y][x]:
c = 0
col = matrix[y][x]
c += 1
if c == 3:
score += 4
elif c > 3:
score += 2
# 2x2 blocks of the same color
for y in range(matrix.shape[0]-1):
for x in range(matrix.shape[1]-1):
if matrix[y][x] == -1: continue
zone = matrix[y:y+2, x:x+2]
if np.all(zone == zone[0,0]):
score += 2
# more dots/1s => higher score
total = matrix.shape[0]*matrix.shape[1]
dots = np.sum(matrix == 1)
percent = 100*dots//total
score += percent//5 * 2
return score, matrix
def display(self, surf):
R = self.RES
S = min(surf.get_size())
s = int(S/12/R)*R
O = (S-s*9)/2
surf.fill(self.WHITE)
# Frame
if self.FRAME:
pygame.draw.rect(surf, self.BLACK, [O-s, O-s, s*11, s*11])
pygame.draw.rect(surf, self.WHITE, [O-s*0.5, O-s*0.5, s*10, s*10])
# Cross
for i in range(4):
dx, dy = self.OFFSETS[i]
X, Y = S/2 + dx*s*3, S/2 + dy*s*3
if self.CIRCLES:
for j in range(3):
dx2, dy2 = self.OFFSETS[(i+j-1)%4]
pygame.draw.circle(surf, self.BLACK, [X+dx2*s, Y+dy2*s], 0.75*s)
pygame.draw.rect(surf, self.BLACK, [X-(1.5-abs(dx))*s, Y-(1.5-abs(dy))*s, s*(3-abs(dx)*2), s*(3-abs(dy)*2)])
pygame.draw.rect(surf, self.BLACK, [O, S/2-s/2, 9*s, s])
pygame.draw.rect(surf, self.BLACK, [S/2-s/2, O, s, 9*s])
# Dots
if self.DOTS:
for y in range(R*9):
for x in range(R*9):
if self.matrix[y, x] == 1:
pygame.draw.circle(surf, self.WHITE, [O+(x+0.5)*s/R, O+(y+0.5)*s/R], s/R/3)
if self.DB_SQUARES:
for y in range(9):
for x in range(9):
if self.matrix[y*R, x*R] != -1:
pygame.draw.rect(surf, (0,0,0), [O+x*s, O+y*s, s+1, s+1], 2)
if __name__ == "__main__":
import base
b = base.Base(S, S, "Lycacode generator")
code = Lycacode({
"type": Lycacode.PERSON_STUDENT,
"id": 16048,
"year": 5,
"class": 3,
"initials": "LH"
}, Lycacode.MODE_PERSON)
#code = Lycacode("Embarquement12", Lycacode.MODE_TEXT)
"""code = Lycacode({
"section": 4,
"room": 209
}, Lycacode.MODE_LOC)"""
#code = Lycacode(1, Lycacode.MODE_LINK)
code.display(b.w)
b.main()