from statistics import mean, median
import time

import pygame
import sys
import copy
import math

NEGRU = (0, 0, 0)
ROSU = (255, 0, 0)
VERDE = (0, 255, 0)
ALB = (255, 255, 255)

LINE_WIDTH = 1

# dimensiunea ferestrei in pixeli
L = 75
CELL_SIZE = (L, L)
DISPLAY_SIZE = (L*5, L*10)

class Buton:
	def __init__(self, display=None, left=0, top=0, w=0, h=0, culoareFundal=(53, 80, 115), culoareFundalSel=(89, 134, 194), text="", font="arial", fontDimensiune=16, culoareText=(255, 255, 255), valoare=""):
		self.display = display
		self.culoareFundal = culoareFundal
		self.culoareFundalSel = culoareFundalSel
		self.text = text
		self.font = font
		self.w = w
		self.h = h
		self.selectat = False
		self.fontDimensiune = fontDimensiune
		self.culoareText = culoareText
		# creez obiectul font
		fontObj = pygame.font.SysFont(self.font, self.fontDimensiune)
		self.textRandat = fontObj.render(self.text, True, self.culoareText)
		self.dreptunghi = pygame.Rect(left, top, w, h)
		# aici centram textul
		self.dreptunghiText = self.textRandat.get_rect(
			center=self.dreptunghi.center)
		self.valoare = valoare

	def selecteaza(self, sel):
		self.selectat = sel
		self.deseneaza()

	def selecteazaDupacoord(self, coord):
		if self.dreptunghi.collidepoint(coord):
			self.selecteaza(True)
			return True
		return False

	def updateDreptunghi(self):
		self.dreptunghi.left = self.left
		self.dreptunghi.top = self.top
		self.dreptunghiText = self.textRandat.get_rect(
			center=self.dreptunghi.center)

	def deseneaza(self):
		culoareF = self.culoareFundalSel if self.selectat else self.culoareFundal
		pygame.draw.rect(self.display, culoareF, self.dreptunghi)
		self.display.blit(self.textRandat, self.dreptunghiText)

class GrupButoane:
	def __init__(self, listaButoane=[], indiceSelectat=0, spatiuButoane=10, left=0, top=0):
		self.listaButoane = listaButoane
		self.indiceSelectat = indiceSelectat
		self.listaButoane[self.indiceSelectat].selectat = True
		self.top = top
		self.left = left
		leftCurent = self.left
		for b in self.listaButoane:
			b.top = self.top
			b.left = leftCurent
			b.updateDreptunghi()
			leftCurent += (spatiuButoane+b.w)

	def selecteazaDupacoord(self, coord):
		for ib, b in enumerate(self.listaButoane):
			if b.selecteazaDupacoord(coord):
				self.listaButoane[self.indiceSelectat].selecteaza(False)
				self.indiceSelectat = ib
				return True
		return False

	def deseneaza(self):
		# atentie, nu face wrap
		for b in self.listaButoane:
			b.deseneaza()

	def getValoare(self):
		return self.listaButoane[self.indiceSelectat].valoare

# ECRAN INITIAL

def deseneaza_alegeri(display, tabla_curenta):
	btn_alg = GrupButoane(
		top=30,
		left=30,
		listaButoane=[
			Buton(display=display, w=80, h=30, text="minimax", valoare="1"),
			Buton(display=display, w=80, h=30, text="alphabeta", valoare="2")
		],
		indiceSelectat=1)
	btn_juc = GrupButoane(
		top=100,
		left=30,
		listaButoane=[
			Buton(display=display, w=50, h=30, text="White", valoare="W"),
			Buton(display=display, w=50, h=30, text="Black", valoare="B")
		],
		indiceSelectat=0)
	btn_dificultate = GrupButoane(
		top=170,
		left=30,
		listaButoane=[
			Buton(display=display, w=60, h=30, text="Easy", valoare=1),
			Buton(display=display, w=60, h=30, text="Medium", valoare=3),
			Buton(display=display, w=60, h=30, text="Hard", valoare=5)
		],
		indiceSelectat=0)
	ok = Buton(display=display, top=240, left=30, w=40,
			   h=30, text="ok", culoareFundal=(155, 0, 55))
	btn_alg.deseneaza()
	btn_juc.deseneaza()
	btn_dificultate.deseneaza()
	ok.deseneaza()
	while True:
		for ev in pygame.event.get():
			if ev.type == pygame.QUIT:
				pygame.quit()
				sys.exit()
			elif ev.type == pygame.MOUSEBUTTONDOWN:
				pos = pygame.mouse.get_pos()
				if not btn_alg.selecteazaDupacoord(pos):
					if not btn_juc.selecteazaDupacoord(pos):
						if not btn_dificultate.selecteazaDupacoord(pos):
							if ok.selecteazaDupacoord(pos):
								display.fill((0, 0, 0))  # stergere ecran
								tabla_curenta.deseneaza_grid()
								return btn_juc.getValoare(), btn_alg.getValoare(), btn_dificultate.getValoare()
		pygame.display.update()

class Joc:
	"""
	Clasa care defineste jocul. Se va schimba de la un joc la altul.
	"""
	NR_LINII = 10
	NR_COLOANE = 5
	JMIN = None
	JMAX = None
	GOL = '#'
	INACCESIBIL = "X"
	WHITE = "W"
	BLACK = "B"

	@classmethod
	def initializeaza(cls, display, NR_LINII=NR_LINII, NR_COLOANE=NR_COLOANE, dim_celula=L):
		cls.display = display
		cls.dim_celula = dim_celula
		cls.white_circle_iamge = pygame.image.load('white_circle.png')
		cls.white_circle_iamge = pygame.transform.scale(cls.white_circle_iamge, (dim_celula, math.floor(
			dim_celula*cls.white_circle_iamge.get_height()/cls.white_circle_iamge.get_width())))
		cls.black_circle_image = pygame.image.load('black_circle.png')
		cls.black_circle_image = pygame.transform.scale(cls.black_circle_image, (dim_celula, math.floor(
			dim_celula*cls.black_circle_image.get_height()/cls.black_circle_image.get_width())))
		cls.celuleGrid = []  # este lista cu patratelele din grid
		for linie in range(NR_LINII):
			cls.celuleGrid.append([])
			for coloana in range(NR_COLOANE):
				patrat = pygame.Rect(coloana*(dim_celula),
									 linie*(dim_celula), dim_celula, dim_celula)
				cls.celuleGrid[linie].append(patrat)

	def deseneaza_linii_grid(self):
		# desenez liniile tablei
		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][0].center,
						 self.__class__.celuleGrid[2][2].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][0].center,
						 self.__class__.celuleGrid[0][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[1][1].center,
						 self.__class__.celuleGrid[1][3].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][1].center,
						 self.__class__.celuleGrid[1][1].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][2].center,
						 self.__class__.celuleGrid[9][2].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][3].center,
						 self.__class__.celuleGrid[1][3].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[0][4].center,
						 self.__class__.celuleGrid[2][2].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[2][0].center,
						 self.__class__.celuleGrid[2][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[3][0].center,
						 self.__class__.celuleGrid[3][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[4][0].center,
						 self.__class__.celuleGrid[4][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[5][0].center,
						 self.__class__.celuleGrid[5][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[6][0].center,
						 self.__class__.celuleGrid[6][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[7][0].center,
						 self.__class__.celuleGrid[7][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[2][0].center,
						 self.__class__.celuleGrid[7][0].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[2][1].center,
						 self.__class__.celuleGrid[7][1].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[2][3].center,
						 self.__class__.celuleGrid[7][3].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[2][4].center,
						 self.__class__.celuleGrid[7][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[7][2].center,
						 self.__class__.celuleGrid[9][0].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[7][2].center,
						 self.__class__.celuleGrid[9][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[8][1].center,
						 self.__class__.celuleGrid[8][3].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[9][0].center,
						 self.__class__.celuleGrid[9][4].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[8][1].center,
						 self.__class__.celuleGrid[9][1].center,
						 width=LINE_WIDTH)

		pygame.draw.line(self.__class__.display, NEGRU,
						 self.__class__.celuleGrid[8][3].center,
						 self.__class__.celuleGrid[9][3].center,
						 width=LINE_WIDTH)

		pygame.display.update()

	# tabla de exemplu este ["#","x","#","0",......]
	def deseneaza_grid(self, marcaj=None, posibile_pozitii=[]):

		for linie in range(Joc.NR_LINII):
			for coloana in range(Joc.NR_COLOANE):
				if marcaj == (linie, coloana):
					# daca am o patratica selectata, o desenez cu rosu
					culoare = ROSU
				elif (linie, coloana) in posibile_pozitii:
					culoare = VERDE
				else:
					# altfel o desenez cu alb
					culoare = ALB
				# alb = (255,255,255)
				pygame.draw.rect(self.__class__.display, culoare,
								 self.__class__.celuleGrid[linie][coloana])

		self.deseneaza_linii_grid()

		for linie in range(Joc.NR_LINII):
			for coloana in range(Joc.NR_COLOANE):
				if self.matr[linie][coloana] == self.__class__.WHITE:
					self.__class__.display.blit(self.__class__.white_circle_iamge, (coloana*(self.__class__.dim_celula), linie*(
						self.__class__.dim_celula) + (self.__class__.dim_celula-self.__class__.white_circle_iamge.get_height())//2))
				elif self.matr[linie][coloana] == self.__class__.BLACK:
					self.__class__.display.blit(self.__class__.black_circle_image, (coloana*(self.__class__.dim_celula), linie*(
						self.__class__.dim_celula)+(self.__class__.dim_celula-self.__class__.black_circle_image.get_height())//2))
		# pygame.display.flip() # !!! obligatoriu pentru a actualiza interfata (desenul)

		pygame.display.update()

	@classmethod
	def matrice_initiala(cls):
		matr = []
		for i in range(4):
			matr.append([cls.BLACK] *
						cls.NR_COLOANE)
		for i in range(cls.NR_LINII-8):
			matr.append([cls.GOL] *
						cls.NR_COLOANE)
		for i in range(4):
			matr.append([cls.WHITE] *
						cls.NR_COLOANE)

		matr[1][0] = cls.INACCESIBIL
		matr[1][4] = cls.INACCESIBIL

		matr[8][0] = cls.INACCESIBIL
		matr[8][4] = cls.INACCESIBIL

		matr[3][0] = cls.GOL
		matr[3][4] = cls.GOL

		matr[6][0] = cls.GOL
		matr[6][4] = cls.GOL

		return matr

	def __init__(self, tabla=None):
		if tabla:
			self.matr = tabla
		else:
			self.matr = self.__class__.matrice_initiala()

	@classmethod
	def jucator_opus(cls, jucator):
		return cls.JMAX if jucator == cls.JMIN else cls.JMIN

	@classmethod
	def directii(cls, x, y):
		# directiile in care se poate deplasa de la coordonatele (x, y)
		sus = (-1, 0)
		dreapta = (0, 1)
		jos = (1, 0)
		stanga = (0, -1)
		directii_normale = [sus, jos, stanga, dreapta]

		if (x, y) in [(0, 0), (1, 1)]:
			return [(1, 1), (-1, -1), sus, dreapta]

		if (x, y) == (2, 2):
			return [(-1, -1), (-1, 1)] + directii_normale

		if (x, y) in [(0, 4), (1, 3)]:
			return [(-1, 1), (1, -1), sus, stanga]

		if (x, y) == (7, 2):
			return [(1, 1), (1, -1)] + directii_normale

		if (x, y) in [(8, 1), (9, 0)]:
			return [(1, -1), (-1, 1), dreapta, jos]

		if (x, y) in [(8, 3), (9, 4)]:
			return [(1, 1), (-1, -1), stanga, jos]

		if x == 2:
			return [stanga, dreapta, jos]

		if x == 7:
			return [stanga, dreapta, sus]

		return directii_normale

	def final(self, jucator):
		nr_piese_JMAX = 0
		nr_piese_JMIN = 0
		castiga_alb = True
		castiga_negru = True

		# verific daca un triunghi a fost cucerit de adversar
		for i in range(2):
			for j in range(self.NR_COLOANE):
				if self.matr[i][j] != self.WHITE and self.matr[i][j] != self.INACCESIBIL:
					castiga_alb = False
					break
		if self.matr[2][2] != self.WHITE and self.matr[2][2] != self.INACCESIBIL:
			castiga_alb = False
		if castiga_alb:
			print("Alb a cucerit triunghiul negru")
			return self.WHITE

		for i in range(self.NR_LINII-2, self.NR_LINII):
			for j in range(self.NR_COLOANE):
				if self.matr[i][j] != self.BLACK and self.matr[i][j] != self.INACCESIBIL:
					castiga_negru = False
					break
		if self.matr[7][2] != self.BLACK and self.matr[7][2] != self.INACCESIBIL:
			castiga_negru = False
		if castiga_negru:
			print("Negru a cucerit triunghiul alb")
			return self.BLACK

		# calculez cate piese are jucatorul si calculatorul
		for i in range(self.NR_LINII):
			for j in range(self.NR_COLOANE):
				if self.matr[i][j] == self.JMAX:
					nr_piese_JMAX += 1
				if self.matr[i][j] == self.JMIN:
					nr_piese_JMIN += 1
		if nr_piese_JMAX == 0:
			print("Calculatorul nu mai are piese")
			return self.JMIN
		if nr_piese_JMIN == 0:
			print("Jucatorul nu mai are piese")
			return self.JMAX

		# verific daca jucatorul curent nu mai are mutari, daca da a castigat adversarul
		mutari = self.mutari(jucator)
		if len(mutari) == 0:
			print(jucator + " nu mai are mutari")
			return self.jucator_opus(jucator)

		return False

	def pozitii_in_care_poate_muta(self, x, y):
		# pozitiile in care poate muta jucatorul pentru a le colora cu verde si afisa in interfata
		# se refera strict la locuri pe tabla nu la starile rezultate
		jucator = self.matr[x][y]
		inamic = self.jucator_opus(jucator)
		pozitii = []

		are_capturi = self.are_capturi(jucator)

		if are_capturi:
			if self.are_capturi_din_punct(inamic, x, y):
				directii = Joc.directii(x, y)
				for (i, j) in directii:
					x_nou = x + i
					y_nou = y + j
					x_dupa = x + 2*i
					y_dupa = y + 2*j
					if x_nou < 0 or y_nou < 0 or x_nou >= Joc.NR_LINII or y_nou >= Joc.NR_COLOANE:
						continue
					if x_dupa < 0 or y_dupa < 0 or x_dupa >= Joc.NR_LINII or y_dupa >= Joc.NR_COLOANE:
						continue

					if self.matr[x_nou][y_nou] == inamic and self.matr[x_dupa][y_dupa] == Joc.GOL:
						pozitii.append((x_dupa, y_dupa))
				return pozitii
		else:
			directii = Joc.directii(x, y)
			for (i, j) in directii:
				x_nou = x + i
				y_nou = y + j
				if x_nou < 0 or y_nou < 0 or x_nou >= Joc.NR_LINII or y_nou >= Joc.NR_COLOANE:
					continue

				if self.matr[x_nou][y_nou] == Joc.GOL:
					# se poate muta
					copie_matr = copy.deepcopy(self.matr)
					copie_matr[x][y] = Joc.GOL
					copie_matr[x_nou][y_nou] = jucator
					pozitii.append((x_nou, y_nou))

		return pozitii

	def are_capturi_din_punct(self, inamic, x, y, matrice=None):
		# returneaza daca din punctul (x, y) se poate realiza o captura
		if matrice == None:
			matrice = self.matr

		# verific daca piesa aceasta poate captura
		directii = Joc.directii(x, y)
		for (i, j) in directii:
			x_nou = x + i
			y_nou = y + j
			x_dupa = x + 2*i
			y_dupa = y + 2*j
			if x_nou < 0 or y_nou < 0 or x_nou >= Joc.NR_LINII or y_nou >= Joc.NR_COLOANE:
				continue
			if x_dupa < 0 or y_dupa < 0 or x_dupa >= Joc.NR_LINII or y_dupa >= Joc.NR_COLOANE:
				continue

			if matrice[x_nou][y_nou] == inamic and matrice[x_dupa][y_dupa] == Joc.GOL:
				return True

		return False

	def stari_dupa_capturi(self, jucator, inamic, x, y, matrice):
		# genereaza starile din (x, y) care rezulta cu ajutorul unei capturi
		# captura poate fi multipla
		l_mutari = []
		if self.are_capturi_din_punct(inamic, x, y, matrice):
			directii = Joc.directii(x, y)
			for (i, j) in directii:
				x_nou = x + i
				y_nou = y + j
				x_dupa = x + 2*i
				y_dupa = y + 2*j
				if x_nou < 0 or y_nou < 0 or x_nou >= Joc.NR_LINII or y_nou >= Joc.NR_COLOANE:
					continue
				if x_dupa < 0 or y_dupa < 0 or x_dupa >= Joc.NR_LINII or y_dupa >= Joc.NR_COLOANE:
					continue

				if matrice[x_nou][y_nou] == inamic and matrice[x_dupa][y_dupa] == Joc.GOL:
					# poate captura
					copie_matr = copy.deepcopy(matrice)
					copie_matr[x][y] = Joc.GOL
					copie_matr[x_nou][y_nou] = Joc.GOL
					copie_matr[x_dupa][y_dupa] = jucator
					# Continua recursia la (x_dupa, y_dupa)
					l_mutari.extend(self.stari_dupa_capturi(
						jucator, inamic, x_dupa, y_dupa, copie_matr))
			return l_mutari
		else:
			# opreste recursia si returneaza jocul obtinut
			return [Joc(matrice)]

	def mutari_cu_capturi(self, jucator):
		# returneaza daca exista capturi in jocul curent si o lista cu starile de dupa capturi
		l_mutari = []
		are_capturi = False
		inamic = Joc.jucator_opus(jucator)
		for i in range(self.NR_LINII):
			for j in range(self.NR_COLOANE):
				if self.matr[i][j] == jucator:
					if self.are_capturi_din_punct(inamic, i, j):
						are_capturi = True
						l_mutari.extend(self.stari_dupa_capturi(
							jucator, inamic, i, j, self.matr))
		return are_capturi, l_mutari

	def are_capturi(self, jucator):
		# returneaza daca exista capturi in jocul curent
		are_capturi = False
		inamic = Joc.jucator_opus(jucator)
		for i in range(self.NR_LINII):
			for j in range(self.NR_COLOANE):
				if self.matr[i][j] == jucator:
					if self.are_capturi_din_punct(inamic, i, j):
						are_capturi = True
		return are_capturi

	def mutari_fara_capturi(self, jucator):
		l_mutari = []
		for x in range(self.NR_LINII):
			for y in range(self.NR_COLOANE):
				if self.matr[x][y] == jucator:
					directii = Joc.directii(x, y)
					for (i, j) in directii:
						x_nou = x + i
						y_nou = y + j
						if x_nou < 0 or y_nou < 0 or x_nou >= Joc.NR_LINII or y_nou >= Joc.NR_COLOANE:
							continue

						if self.matr[x_nou][y_nou] == Joc.GOL:
							# se poate muta
							copie_matr = copy.deepcopy(self.matr)
							copie_matr[x][y] = Joc.GOL
							copie_matr[x_nou][y_nou] = jucator
							l_mutari.append(Joc(copie_matr))

		return l_mutari

	def mutari(self, jucator):  # jucator = simbolul jucatorului care muta
		are_capturi_din_punct, l_mutari = self.mutari_cu_capturi(jucator)
		if are_capturi_din_punct:
			return l_mutari
		else:
			return self.mutari_fara_capturi(jucator)

	def estimeaza_scor(self, adancime, jucator, estimare=2):
		t_final = self.final(jucator)
		# if (adancime==0):
		if t_final == self.__class__.JMAX:
			return (990+adancime)
		elif t_final == self.__class__.JMIN:
			return (-999-adancime)
		elif t_final == 'remiza':
			return 0
		else:
			if estimare == 1:
				# returnez nr de piese ale computerului - nr de piese ale jucatorului
				nr_piese_JMAX = 0
				nr_piese_JMIN = 0
				for i in range(self.NR_LINII):
					for j in range(self.NR_COLOANE):
						if self.matr[i][j] == self.JMAX:
							nr_piese_JMAX += 1
						if self.matr[i][j] == self.JMIN:
							nr_piese_JMIN += 1
				return nr_piese_JMAX - nr_piese_JMIN
			else:
				# returnez (nr de piese ale computerului - nr de piese ale jucatorului) * 5 +
    			# + (avansul pieselor calculatorului - avansul pieselor jucatorului)
				nr_piese_JMAX = 0
				nr_piese_JMIN = 0
				for i in range(self.NR_LINII):
					for j in range(self.NR_COLOANE):
						if self.matr[i][j] == self.JMAX:
							nr_piese_JMAX += 1
						if self.matr[i][j] == self.JMIN:
							nr_piese_JMIN += 1

				avans_JMAX = 0
				avans_JMIN = 0
				for i in range(self.NR_LINII):
					for j in range(self.NR_COLOANE):
						if self.matr[i][j] == self.JMAX:
							avans_JMAX += i
						if self.matr[i][j] == self.JMIN:
							avans_JMIN += self.NR_LINII-1-i
				return (nr_piese_JMAX - nr_piese_JMIN) * 5 + avans_JMAX - avans_JMIN

	def sirAfisare(self):
		sir = "  |"
		sir += " ".join([str(i) for i in range(self.NR_COLOANE)])+"\n"
		sir += "-"*(self.NR_COLOANE+1)*2+"\n"
		for i in range(self.NR_LINII):  # itereaza prin linii
			sir += str(i)+" |"+" ".join([str(x) for x in self.matr[i]])+"\n"
		return sir

	def __str__(self):
		return self.sirAfisare()

	def __repr__(self):
		return self.sirAfisare()

class Stare:
	"""
	Clasa folosita de algoritmii minimax si alpha-beta
	Are ca proprietate tabla de joc
	Functioneaza cu conditia ca in cadrul clasei Joc sa fie definiti JMIN si JMAX (cei doi jucatori posibili)
	De asemenea cere ca in clasa Joc sa fie definita si o metoda numita mutari() care ofera lista cu configuratiile posibile in urma mutarii unui jucator
	"""

	def __init__(self, tabla_joc: Joc, j_curent, adancime, parinte=None, estimare=None):
		self.tabla_joc = tabla_joc
		self.j_curent = j_curent
		self.parinte = parinte

		# adancimea in arborele de stari
		self.adancime = adancime

		# estimarea favorabilitatii starii (daca e finala) sau al celei mai bune stari-fiice (pentru jucatorul curent)
		self.estimare = estimare

		# lista de mutari posibile din starea curenta
		self.mutari_posibile = []

		# cea mai buna mutare din lista de mutari posibile pentru jucatorul curent
		self.stare_aleasa = None

	def mutari(self):
		l_mutari = self.tabla_joc.mutari(self.j_curent)
		juc_opus = Joc.jucator_opus(self.j_curent)
		l_stari_mutari = [
			Stare(mutare, juc_opus, self.adancime-1, parinte=self) for mutare in l_mutari]

		# print(f"Din starea {self} se poate ajunge in starile {l_stari_mutari}")
		return l_stari_mutari

	def __str__(self):
		sir = str(self.tabla_joc) + "(Juc curent:"+self.j_curent+")\n"
		return sir

""" Algoritmul MinMax """

def min_max(stare: Stare):

	if stare.adancime == 0 or stare.tabla_joc.final(stare.j_curent):
		stare.estimare = stare.tabla_joc.estimeaza_scor(
			stare.adancime, stare.j_curent)
		return stare

	# calculez toate mutarile posibile din starea curenta
	stare.mutari_posibile = stare.mutari()

	# aplic algoritmul minimax pe toate mutarile posibile (calculand astfel subarborii lor)
	mutariCuEstimare = [min_max(mutare) for mutare in stare.mutari_posibile]

	if stare.j_curent == Joc.JMAX:
		# daca jucatorul e JMAX aleg starea-fiica cu estimarea maxima
		stare.stare_aleasa = max(mutariCuEstimare, key=lambda x: x.estimare)
	else:
		# daca jucatorul e JMIN aleg starea-fiica cu estimarea minima
		stare.stare_aleasa = min(mutariCuEstimare, key=lambda x: x.estimare)
	stare.estimare = stare.stare_aleasa.estimare
	return stare

def alpha_beta(alpha, beta, stare: Stare):
	if stare.adancime == 0 or stare.tabla_joc.final(stare.j_curent):
		stare.estimare = stare.tabla_joc.estimeaza_scor(
			stare.adancime, stare.j_curent)
		return stare

	if alpha > beta:
		return stare  # este intr-un interval invalid deci nu o mai procesez

	stare.mutari_posibile = stare.mutari()

	if stare.j_curent == Joc.JMAX:
		estimare_curenta = float('-inf')

		for mutare in stare.mutari_posibile:
			# calculeaza estimarea pentru starea noua, realizand subarborele
			stare_noua = alpha_beta(alpha, beta, mutare)

			if (estimare_curenta < stare_noua.estimare):
				stare.stare_aleasa = stare_noua
				estimare_curenta = stare_noua.estimare
			if(alpha < stare_noua.estimare):
				alpha = stare_noua.estimare
				if alpha >= beta:
					break

	elif stare.j_curent == Joc.JMIN:
		estimare_curenta = float('inf')

		for mutare in stare.mutari_posibile:

			stare_noua = alpha_beta(alpha, beta, mutare)

			if (estimare_curenta > stare_noua.estimare):
				stare.stare_aleasa = stare_noua
				estimare_curenta = stare_noua.estimare

			if(beta > stare_noua.estimare):
				beta = stare_noua.estimare
				if alpha >= beta:
					break
	stare.estimare = stare.stare_aleasa.estimare

	return stare

def afis_daca_final(stare_curenta: Stare, timpi_gandire_calculator):
	final = stare_curenta.tabla_joc.final(stare_curenta.j_curent)
	if(final):
		if (final == "remiza"):
			print("Remiza!")
		else:
			print("A castigat "+final)

			# afisam date despre timpii de gandire ai calculatorului
			print(f"Timp minim de gantire {min(timpi_gandire_calculator)}")
			print(f"Timp maxim de gantire {max(timpi_gandire_calculator)}")
			print(f"Mediana timpilor de gantire {median(timpi_gandire_calculator)}")
			print(f"Media timpilor de gantire {mean(timpi_gandire_calculator)}")
		return True

	return False

def muta_piesa(de_mutat, linie, coloana, stare_curenta: Stare, cu_captura=False):

	stare_actualizata = Stare(copy.deepcopy(stare_curenta.tabla_joc),
							  stare_curenta.j_curent, stare_curenta.adancime, stare_curenta)

	stare_actualizata.tabla_joc.matr[de_mutat[0]][de_mutat[1]] = Joc.GOL
	x, y = de_mutat
	de_mutat = False

	# verific daca captureaza o piesa
	if cu_captura:
		x_sters = int((x + linie) / 2)
		y_sters = int((y + coloana) / 2)
		stare_actualizata.tabla_joc.matr[x_sters][y_sters] = Joc.GOL

	# plasez simbolul pe "tabla de joc"
	stare_actualizata.tabla_joc.matr[linie][coloana] = Joc.JMIN
	stare_actualizata.tabla_joc.deseneaza_grid()

	return stare_actualizata

def main():
	# initializare tabla
	tabla_curenta = Joc()
	print("Tabla initiala")
	print(str(tabla_curenta))

	# setari interf grafica
	pygame.init()
	pygame.display.set_caption('ASTAR Bina Mircea')

	ecran = pygame.display.set_mode(size=DISPLAY_SIZE)
	Joc.initializeaza(ecran)
	# initializare algoritm

	Joc.JMIN, tip_algoritm, ADANCIME_MAX = deseneaza_alegeri(
		ecran, tabla_curenta)

	Joc.JMAX = 'W' if Joc.JMIN == 'B' else 'B'

	# creare stare initiala
	stare_curenta = Stare(tabla_curenta, "W", ADANCIME_MAX)

	de_mutat = False
	posibile_mutari = []
	captura_in_progres = False
	tabla_curenta.deseneaza_grid()
	timpi_gandire_calculator = []
	t_inainte = int(round(time.time() * 1000))

	while True:

		if (stare_curenta.j_curent == Joc.JMIN):
			# muta jucatorul
			# [MOUSEBUTTONDOWN, MOUSEMOTION,....]
			# l=pygame.event.get()
			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					pygame.quit()  # inchide fereastra
					sys.exit()

				elif event.type == pygame.KEYUP and event.key == pygame.K_r:
					print("RESTART JOC")
					tabla_curenta = Joc()
					stare_curenta = Stare(tabla_curenta, "W", ADANCIME_MAX)
					stare_curenta.tabla_joc.deseneaza_grid()

				elif event.type == pygame.KEYUP and event.key == pygame.K_u:
					print("UNDO JOC")
					if stare_curenta.parinte == None:
						print("Nu se poate da undo pentru ca este in starea initiala")
						continue
					stare_curenta = stare_curenta.parinte
					stare_curenta.tabla_joc.deseneaza_grid()

				elif event.type == pygame.MOUSEBUTTONDOWN:  # click

					pos = pygame.mouse.get_pos()  # coordonatele clickului

					for linie in range(Joc.NR_LINII):
						for coloana in range(Joc.NR_COLOANE):

							# verifica daca punctul cu coord pos se afla in dreptunghi(celula)
							if Joc.celuleGrid[linie][coloana].collidepoint(pos):
								###############################
								if stare_curenta.tabla_joc.matr[linie][coloana] == Joc.JMIN:
									if (de_mutat and linie == de_mutat[0] and coloana == de_mutat[1]):
										# daca am facut click chiar pe patratica selectata, o deselectez
										de_mutat = False
										posibile_mutari = []
										stare_curenta.tabla_joc.deseneaza_grid()
									else:
										de_mutat = (linie, coloana)
										posibile_mutari = stare_curenta.tabla_joc.pozitii_in_care_poate_muta(
											linie, coloana)
										# desenez gridul cu patratelul marcat
										stare_curenta.tabla_joc.deseneaza_grid(
											de_mutat, posibile_mutari)

								elif stare_curenta.tabla_joc.matr[linie][coloana] == Joc.GOL and de_mutat and (linie, coloana) in posibile_mutari:
									x, y = de_mutat

									# jucatorul muta o piesa intr-o pozitie posibila
									if captura_in_progres:
										stare_curenta = muta_piesa(
											de_mutat, linie, coloana, stare_curenta, cu_captura=True)
										if not stare_curenta.tabla_joc.are_capturi_din_punct(Joc.JMAX, linie, coloana):
											captura_in_progres = False
										else:
											posibile_mutari = stare_curenta.tabla_joc.pozitii_in_care_poate_muta(
												linie, coloana)
											t_dupa = int(
												round(time.time() * 1000))
											print("Jucatorul a \"gandit\" timp de " +
												  str(t_dupa-t_inainte)+" milisecunde.")

									else:
										if stare_curenta.tabla_joc.are_capturi_din_punct(Joc.JMAX, x, y):
											captura_in_progres = True
											posibile_mutari = stare_curenta.tabla_joc.pozitii_in_care_poate_muta(
												linie, coloana)

										stare_curenta = muta_piesa(
											de_mutat, linie, coloana, stare_curenta, cu_captura=captura_in_progres)

										if not captura_in_progres:
											t_dupa = int(
												round(time.time() * 1000))
											print("Jucatorul a \"gandit\" timp de " +
												  str(t_dupa-t_inainte)+" milisecunde.")

									# afisarea starii jocului in urma mutarii utilizatorului
									print("\nTabla dupa mutarea jucatorului")
									print(str(stare_curenta))

									if not stare_curenta.tabla_joc.are_capturi_din_punct(Joc.JMAX, linie, coloana):
										captura_in_progres = False

									# testez daca jocul a ajuns intr-o stare finala
									# si afisez un mesaj corespunzator in caz ca da
									if (afis_daca_final(stare_curenta, timpi_gandire_calculator)):
										break

									# S-a realizat o mutare. Schimb jucatorul cu cel opus
									if not captura_in_progres:
										stare_curenta.j_curent = Joc.jucator_opus(
											stare_curenta.j_curent)

		# --------------------------------
		else:  # jucatorul e JMAX (calculatorul)
			# Mutare calculator

			# preiau timpul in milisecunde de dinainte de mutare
			t_inainte = int(round(time.time() * 1000))
			if tip_algoritm == '1':
				stare_actualizata = min_max(stare_curenta)
			else:  # tip_algoritm==2
				stare_actualizata = alpha_beta(-500, 500, stare_curenta)
			stare_curenta.tabla_joc = stare_actualizata.stare_aleasa.tabla_joc
			print("Tabla dupa mutarea calculatorului")
			print(str(stare_curenta))

			stare_curenta.tabla_joc.deseneaza_grid()
			# preiau timpul in milisecunde de dupa mutare
			t_dupa = int(round(time.time() * 1000))
			timpi_gandire_calculator.append(t_dupa-t_inainte)
			print("Calculatorul a \"gandit\" timp de " +
				  str(t_dupa-t_inainte)+" milisecunde.")

			if (afis_daca_final(stare_curenta, timpi_gandire_calculator)):
				break

			# S-a realizat o mutare. Schimb jucatorul cu cel opus
			stare_curenta.j_curent = Joc.jucator_opus(stare_curenta.j_curent)

			# incep timerul pt jucator
			t_inainte = int(round(time.time() * 1000))

if __name__ == "__main__":
	main()
	while True:
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				pygame.quit()
				sys.exit()


from datetime import datetime, timedelta
from dataclasses import dataclass

@dataclass
class Item:
    x:int
    y:int
    move:int=0

class N(Item):
    def moveup(self):
        self.y  = self.y + 1
        self.move += 1
        return N(self.x, self.y)

class E(Item):
    def moveright(self):
        self.x += 1
        self.move +=1
        return E(self.x, self.y)

#global variables
Ngroup, Egroup, Infi = [], [], []
nItems = []
eItems = []
coordinateArray = []

def inputvalue():
    global Ngroup, Egroup
    print('Please input a integer for N between 0 and 50\n')
    total = input() # 6
    print(f'Please input {total} coordinates in the format "N 3 2,E 5 10" \n')
    # E 3 5, N 5 3, E 4 6, E 10 4, N 11 2, N 8 1
    coordinates = input()
    items = coordinates.strip().split(',')
    itemsarray =[i.strip().replace(' ','_') for i in items]
    print(f'items are {itemsarray}')
    # items are ['E_3_5', 'N_5_3', 'E_4_6', 'E_10_4', 'N_11_2', 'N_8_1']
    for i in itemsarray:
        c = i.split('_')
        if i.startswith('N'):
            Ngroup.append(c)
        elif i.startswith('E'):
            Egroup.append(c)
        else:
            print(i)
            print(f'{i} is not allowed, do the input again')
            inputvalue()
    print(f'{Ngroup} for Ngroup')
    print(f'{Egroup} for Egroup')
    # [['N', '5', '3'], ['N', '11', '2'], ['N', '8', '1']] for Ngroup
    # [['E', '3', '5'], ['E', '4', '6'], ['E', '10', '4']] for Egroup

    # Step 2
    doLogics()

def doLogics():
    print('Start comparing...')
    global Ngroup, Egroup, nItems, eItems
    if Ngroup == [] and Egroup != []:
        EmptyTheArrayAndReturn(Egroup)
    if Ngroup != [] and Egroup == []:
        EmptyTheArrayAndReturn(Ngroup)

    if Ngroup != [] and Egroup != []:
        consider3senarios()

    if Ngroup != [] and Egroup != []:
        doDetailCalculation(Ngroup,Egroup)

    print(f'Infinite are {Infi}')

def doDetailCalculation(Ngroup, Egroup):
    # turn all array items into N or E instances
    for n in Ngroup:
        nitem= N(x=int(n[1]), y=int(n[2]))
        nItems.append(nitem)
        coordinateArray.append((nitem.x, nitem.y))
    for e in Egroup:
        eitem = E(x=int(e[1]), y=int(e[2]))
        eItems.append(eitem)
        coordinateArray.append((eitem.x, eitem.y))

    print(f'CoordinateArray has {coordinateArray},\n nitems has {nItems},\n eitems has {eItems}')
    #CoordinateArray has [(5, 3), (11, 2), (8, 1), (3, 5), (4, 6), (10, 4)],
    #nitems has [N(x=5, y=3, move=0), N(x=11, y=2, move=0), N(x=8, y=1, move=0)],
    #eitems has [E(x=3, y=5, move=0), E(x=4, y=6, move=0), E(x=10, y=4, move=0)]

    # when all leftover Ns or Es will extend infinitely... use Timer to stop the program
    timer = datetime.now()
    while nItems != [] and eItems != []:
        # all N or E instances move one step at a time
        oneMove()

        start = datetime.now()
        delta = (start - timer)
        if (delta.total_seconds()) > 1:
            print(f'Take too long, looks like {eItems} and {nItems} are all infinite')
            break
    # coming out of the 'while' loop ...
    if nItems == [] and eItems != []:    # Infi item format is [['N', '5', '3'], ['N', '11', '2'], ['N', '8', '1']]
        for e in eItems:                  # eItems format is [E(x=3, y=5, move=0), E(x=4, y=6, move=0), E(x=10, y=4, move=0)]
            original = ['E',f'{e.x-e.move}',f'{e.y}']
            Infi.append(original)

    if eItems == [] and nItems != []:
        for n in nItems:
            original = ['N',f'{n.x}',f'{n.y-n.move}']
            Infi.append(original)

def consider3senarios():
    global Ngroup,Egroup
    '''One'''
    # find the largest Y in Egroup --> remove items in Ngroup that is above it:
    Ylargest = max([int(e[2]) for e in Egroup])
    print(f'Ylargest in Egroup is {Ylargest}')
    for n in Ngroup:
        # print(f'checking {n} in Ngroup to remove the top items')
        if int(n[2]) > Ylargest:
            Infi.append(n)
            # Ngroup.remove(n) # can't do this when iterate through this list
    # remove duplicate  items from Ngroup
    Ngroup = [elm for elm in Ngroup if elm not in Infi]

    '''Two'''
    # find the smallest X in Egroup --> remove items in Ngroup that is on its left side:
    if Ngroup != []:
        Xsmallest = min([int(e[1]) for e in Egroup])
        print(f'Xsmallest in Egroup is {Xsmallest}')
        for n in Ngroup:
            # print(f'checking {n} in Ngroup to remove the most left items')
            if int(n[1]) < Xsmallest:
                Infi.append(n)
        # remove the items from Ngroup if they are already moved to Infi array
        Ngroup = [elm for elm in Ngroup if elm not in Infi]

        if Ngroup == []:
            EmptyTheArrayAndReturn(Egroup)
        else:
            '''Three'''
            Xlargest = max([int(n[1]) for n in Ngroup])
            print(f'Xlargest in Ngroup is {Xlargest}')
            for e in Egroup:
                # print(f'checking {n} in Egroup to remove the item with larger x')
                if int(e[1]) > Xlargest:
                    Infi.append(e)
            # remove items from Egroup if they are in Infi array already
            Egroup = [elm for elm in Egroup if elm not in Infi]

    else:
        EmptyTheArrayAndReturn(Egroup)
    Ngroup = [elm for elm in Ngroup if elm not in Infi]
    Egroup = [elm for elm in Egroup if elm not in Infi]
    print(f'After 3 Senarios --> Infi has {Infi}, Ngroup has {Ngroup}, Egroup has {Egroup}')

def oneMove():
    global nItems,eItems
    print(f'moving one step now....')
    ''' N items change y->y+1, movement add 1;
            add (x, y+1) into zone[] if the tuple not existed yet
            else Stop, count movement, add n to infi[]
            E itmes change x-> x+1, movement add 1;
            add (x+1, y) into zone[] if the tuple not existed yet
            else stop, count movement, add n to infi[]
            update Ngroup and Egroup by comparing with infi[]
    '''
    # global a, b
    tempStore = []
    removeN=[]
    removeE = []
    print(f'In onemove() nItems is {nItems}')
    # In onemove() nItems is [N(x=5, y=3, move=0), N(x=11, y=2, move=0), N(x=8, y=1, move=0)]
    print(f'In onemove() eItems is {eItems}')
    # In onemove() eItems is [E(x=3, y=5, move=0), E(x=4, y=6, move=0), E(x=10, y=4, move=0)]
    for n in nItems:#N(x=5, y=3, move=0)
        a = n.moveup() #a is N(x=8, y=62, move=0)
        # After oneMove UP,n in nItems is N(x=5, y=4, move=1)
        # After oneMove UP,n in nItems is N(x=11, y=3, move=1)
        # After oneMove UP,n in nItems is N(x=8, y=2, move=1)
        aCor =(a.x, a.y)
        if aCor not in coordinateArray: #CoordinateArray has [(5, 3), (11, 2), (8, 1), (3, 5), (4, 6), (10, 4)]
            # print(f'line 186, add new coordinates to "coordinateArray"...')
            coordinateArray.append(aCor)
            print(f'CoordianteArray in 187 line has {coordinateArray}')
            tempStore.append(aCor)
            print(f'add new aCor to tempStore in 189 has {tempStore} items')
            # add new aCor to tempStore in 189 has [(5, 4), (11, 3), (8, 2)] items -> this is after all items are looped through
        else:
            # to remove collided items
            print(f'If aCor is already inside the coordiateArray.... line 192')
            original = f'N{n.x}{n.y-n.move}'
            print(f'original in 192 is {original}')
            print(f'{original} has {n.move} grass')
            # for remove it from nItems later
            removeN.append(n)
            print(f'removeN in 200 has {removeN}')
    for e in eItems:#E(x=3, y=5, move=0)
        b = e.moveright()
        bCor = (b.x, b.y)
        if bCor not in coordinateArray:
            coordinateArray.append(bCor)
        else:
            # if bCor is already stored in coordinateArray, check if it is just stored within this onemove()
            if bCor not in tempStore: # SO the bCor tuple has been stored in the previous oneMove()...
                # remove this b from eItem
                original = f'E{e.x - e.move}{b.y}'
                print(f'original in 210 is {original}')
                print(f'{original} has {e.move} grass')
                # for remove it from eItems later
                removeE.append(e)
                print(f'removeE in 215 has {removeE}')
            else: # so the bCor is just being stored in this session of oneMove()
                print(f'OOPS! E instance joined N instance in this {bCor} spot.')
    eItems = [i for i in eItems if i not in removeE] # eItems and nItems has E and N instances that are kept being updated after the oneMove()
    print(f'eItems has {eItems} LEFT')
    nItems = [i for i in nItems if i not in removeN]
    print(f'nItems has {nItems} LEFT')
    #eItems has [E(x=6, y=5, move=3), E(x=7, y=6, move=3), E(x=13, y=4, move=3)] LEFT
    #nItems has [N(x=5, y=6, move=3), N(x=11, y=5, move=3), N(x=8, y=4, move=3)] LEFT

# use this format for 2nd input:
# E 3 5, N 5 3, E 4 6, E 10 4, N 11 2, N 8 1

def EmptyTheArrayAndReturn(arr):
    Infi.extend(arr)
    print(f'EmptyTheArray from {arr} and returned -- Infi array has {Infi}')
    return

if __name__ =="__main__":
    inputvalue()


import math
import logging
import torch
import torch.nn as nn
from torch.nn import functional as F
logger = logging.getLogger(__name__)

########################################################################################################
# MHA: Multi-head Attention + Rotary Encoding
########################################################################################################

class RotaryEmbedding(torch.nn.Module):
    def __init__(self, dim, base=10000):
        super().__init__()
        inv_freq = 1. / (base ** (torch.arange(0, dim, 2).float() / dim))
        self.register_buffer('inv_freq', inv_freq)
        self.seq_len_cached = None
        self.cos_cached = None
        self.sin_cached = None
    def forward(self, x, seq_len=None):
        if seq_len != self.seq_len_cached:
            self.seq_len_cached = seq_len
            t = torch.arange(seq_len, device=x.device)
            freqs = torch.einsum('i,j->ij', t, self.inv_freq)
            emb = torch.cat((freqs, freqs), dim=-1).to(x.device)
            self.cos_cached = emb.cos()
            self.sin_cached = emb.sin()
        return self.cos_cached, self.sin_cached
def rotate_half(x):
    x1, x2 = x[..., :x.shape[-1] // 2], x[..., x.shape[-1] // 2:]
    return torch.cat((-x2, x1), -1)
@torch.jit.script
def apply_rotary_pos_emb(q, k, cos, sin):
    cos, sin = cos[...,:q.shape[-2],:], sin[...,:q.shape[-2],:]
    return (q * cos) + (rotate_half(q) * sin), (k * cos) + (rotate_half(k) * sin)

class MHA(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        assert config.n_attn % config.n_head == 0
        self.n_head = config.n_head
        self.ctx_len = config.ctx_len
        self.head_size = config.n_attn // config.n_head

        self.query = nn.Linear(config.n_embd, config.n_attn)
        self.key = nn.Linear(config.n_embd, config.n_attn)
        self.value = nn.Linear(config.n_embd, config.n_attn)
        self.register_buffer("mask", torch.tril(torch.ones(config.ctx_len, config.ctx_len)))

        if self.config.ROTARY_POS_EMB:
            self.rotary_ndims = int(self.head_size * 0.5)
            self.rotary_emb = RotaryEmbedding(self.rotary_ndims)

        self.output = nn.Linear(config.n_attn, config.n_embd)

    def forward(self, x):
        B, T, C = x.size()

        q = self.query(x).view(B, T, self.n_head, self.head_size).transpose(1, 2)       # (B, T, C) -> (B, nh, T, hs)
        k = self.key(x).view(B, T, self.n_head, self.head_size).transpose(1, 2)         # (B, T, C) -> (B, nh, T, hs)
        v = self.value(x).view(B, T, self.n_head, self.head_size).transpose(1, 2)       # (B, T, C) -> (B, nh, T, hs)

        if self.config.ROTARY_POS_EMB:
            q, query_pass = q[..., :self.rotary_ndims], q[..., self.rotary_ndims:]
            k, key_pass = k[..., :self.rotary_ndims], k[..., self.rotary_ndims:]
            cos, sin = self.rotary_emb(q, seq_len=T)
            q, k = apply_rotary_pos_emb(q, k, cos, sin)                                     # rotary encoding
            q = torch.cat((q, query_pass), dim=-1)
            k = torch.cat((k, key_pass), dim=-1)

        att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))                 # self-attention: (B, nh, T, hs) * (B, nh, hs, T) -> (B, nh, T, T)
        att = att.masked_fill(self.mask[:T,:T] == 0, float('-inf'))                     # causal mask
        att = F.softmax(att, dim = -1)                                                  # softmax

        x = att @ v                                                                     # (B, nh, T, T) * (B, nh, T, hs) -> (B, nh, T, hs)
        x = x.transpose(1, 2).contiguous().view(B, T, -1)                               # (B, nh, T, hs) -> (B, T, nh, hs) -> (B, T, C)

        x = self.output(x)
        return x

class GeGLU(torch.nn.Module):
    def __init__(self, config):
        super().__init__()
        hidden_sz = 3 * config.n_ffn
        self.key = nn.Linear(config.n_embd, hidden_sz)
        self.value = nn.Linear(config.n_embd, hidden_sz)
        self.weight = nn.Linear(hidden_sz, config.n_embd)

    def forward(self, x):
        B, T, C = x.size()
        k = self.key(x)
        v = self.value(x)
        y = self.weight(F.gelu(k) * v)
        return y

########################################################################################################
# The GPT Model with our blocks
########################################################################################################

class GPTConfig:
    def __init__(self, vocab_size, ctx_len, **kwargs):
        self.vocab_size = vocab_size
        self.ctx_len = ctx_len
        for k,v in kwargs.items():
            setattr(self, k, v)

class Block(nn.Module):
    def __init__(self, config, layer_id):
        super().__init__()
        self.config = config
        self.layer_id = layer_id

        if (not self.config.USE_POST_LN) and (self.config.USE_SMALL_EMB) and (self.layer_id == 0): # LN(SmallInit(Emb))
            self.lnPre = nn.LayerNorm(config.n_embd)

        self.ln1 = nn.LayerNorm(config.n_embd)
        self.ln2 = nn.LayerNorm(config.n_embd)

        self.att = MHA(config)
        self.ffn = GeGLU(config)

    def forward(self, x):
        if self.config.USE_POST_LN:
            x = self.ln1(x)
            x = x + self.att(x)
            x = self.ln2(x)
            x = x + self.ffn(x)
        else:
            if self.config.USE_SMALL_EMB and self.layer_id == 0: # LN(SmallInit(Emb))
                x = self.lnPre(x)

            x = x + self.att(self.ln1(x))
            x = x + self.ffn(self.ln2(x))

        return x

class GPT(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.ctx_len = config.ctx_len

        self.emb = nn.Embedding(config.vocab_size, config.n_embd)
        if not self.config.ROTARY_POS_EMB:
            self.pos_emb = nn.Parameter(torch.zeros(1, config.ctx_len, config.n_embd)) # note: i initialize abs.pos.emb to zero

        self.blocks = nn.Sequential(*[Block(config, i) for i in range(config.n_layer)])
        self.ln_out = nn.LayerNorm(config.n_embd)
        self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)

        self.apply(self._init_weights)
        logger.info("number of parameters: %e", sum(p.numel() for p in self.parameters()))

    def _init_weights(self, module):

        if isinstance(module, (nn.Linear)):
            module.weight.data.normal_(mean=0.0, std=0.02)

        if isinstance(module, (nn.Embedding)):
            if self.config.USE_SMALL_EMB:
                nn.init.uniform_(module.weight, a=-1e-4, b=1e-4) # SmallInit(Emb)
            else:
                module.weight.data.normal_(mean=0.0, std=0.02)

        if isinstance(module, nn.Linear) and module.bias is not None:
            module.bias.data.zero_()

    def configure_optimizers(self, train_config):
        decay = set() # separate out all parameters to those that will and won't experience regularizing weight decay
        no_decay = set()
        whitelist_weight_modules = (nn.Linear, )
        blacklist_weight_modules = (nn.LayerNorm, nn.Embedding)
        for mn, m in self.named_modules():
            for pn, p in m.named_parameters():
                fpn = '%s.%s' % (mn, pn) if mn else pn # full param name
                if pn.endswith('bias') or ('time' in fpn) or ('head' in fpn) or ('scale' in fpn) or ('pos_emb' in fpn):
                    no_decay.add(fpn)
                elif pn.endswith('weight') and isinstance(m, whitelist_weight_modules):
                    decay.add(fpn)
                elif pn.endswith('weight') and isinstance(m, blacklist_weight_modules):
                    no_decay.add(fpn)
        param_dict = {pn: p for pn, p in self.named_parameters()} # validate that we considered every parameter
        inter_params = decay & no_decay
        union_params = decay | no_decay
        assert len(inter_params) == 0, "parameters %s made it into both decay/no_decay sets!" % (str(inter_params), )
        assert len(param_dict.keys() - union_params) == 0, "parameters %s were not separated into either decay/no_decay set!" \
                                                    % (str(param_dict.keys() - union_params), )
        optim_groups = [
            {"params": [param_dict[pn] for pn in sorted(list(decay))], "weight_decay": train_config.weight_decay},
            {"params": [param_dict[pn] for pn in sorted(list(no_decay))], "weight_decay": 0.0},
        ]
        optimizer = torch.optim.AdamW(optim_groups, lr=train_config.learning_rate, betas=train_config.betas, eps=train_config.eps)
        return optimizer

    def forward(self, idx, targets=None):
        B, T = idx.size()
        assert T <= self.ctx_len, "Cannot forward, because len(input) > model ctx_len."

        x = self.emb(idx)
        if not self.config.ROTARY_POS_EMB:
            x = x + self.pos_emb[:, :T, :]

        x = self.blocks(x)

        x = self.ln_out(x)

        x = self.head(x)

        # print(self.emb.weight.detach().cpu().numpy()) # <-------- Show embedding matrix changes

        loss = None
        if targets is not None:
            loss = F.cross_entropy(x.view(-1, x.size(-1)), targets.view(-1))
        return x, loss


import numpy as np
import pdb
import cv2

# from .visualization.visualizer import Visualizer
from .kitti_utils import draw_projected_box3d, init_bev_image, draw_bev_box3d

def box3d_to_corners(locs, dims, roty):
	# 3d bbox template
	h, w, l = dims
	x_corners = [l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2, -l / 2, -l / 2]
	y_corners = [0, 0, 0, 0, -h, -h, -h, -h]
	z_corners = [w / 2, -w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2]

	# rotation matirx
	R = np.array([[np.cos(roty), 0, np.sin(roty)],
				  [0, 1, 0],
				  [-np.sin(roty), 0, np.cos(roty)]])

	corners3d = np.vstack([x_corners, y_corners, z_corners])  # (3, 8)
	corners3d = np.dot(R, corners3d).T
	corners3d = corners3d + locs

	return corners3d

def show_image_with_boxes(output, target, vis_path):
    # output Tensor:
    # clses, pred_alphas, pred_box2d, pred_dimensions, pred_locations, pred_rotys, scores
    image = target[0]['ori_img'].numpy().astype(np.uint8)
    output = output.cpu().float().numpy()

    # filter results with visualization threshold
    vis_thresh = 0.1
    output = output[output[:, -1] > vis_thresh]
    ID_TYPE_CONVERSION = {0:'Car'}

	# predictions
    clses = output[:, 0]
    box2d = output[:, 2:6]
    dims = output[:, 6:9]
    locs = output[:, 9:12]
    rotys = output[:, 12]
    score = output[:, 13]

    # ground-truth
    valid_mask = target[0]['reg_mask']
    num_gt = valid_mask.sum()
    gt_locs = target[0]['locations'][valid_mask]
    gt_dims = target[0]['dimensions'][valid_mask]
    gt_rotys = target[0]['rotys'][valid_mask]

    # print('detections / gt objs: {} / {}'.format(box2d.shape[0], num_gt))

    img3 = image.copy() # for 3d bbox
    img4 = init_bev_image() # for bev

    font = cv2.FONT_HERSHEY_SIMPLEX
    pred_color = (0, 255, 0)
    gt_color = (255, 0, 0)

    # plot prediction
    for i in range(box2d.shape[0]):

        corners3d = box3d_to_corners(locs[i], dims[i], rotys[i])
        img4 = draw_bev_box3d(img4, corners3d[np.newaxis, :], thickness=2, color=pred_color, scores=None)

    # plot ground-truth
    for i in range(num_gt):

        # 3d bbox template
        h, l, w = gt_dims[i]

        x_corners = [l / 2, l / 2, -l / 2, -l / 2, l / 2, l / 2, -l / 2, -l / 2]
        y_corners = [0, 0, 0, 0, -h, -h, -h, -h]
        z_corners = [w / 2, -w / 2, -w / 2, w / 2, w / 2, -w / 2, -w / 2, w / 2]

        # rotation matirx
        roty = gt_rotys[i]
        R = np.array([[np.cos(roty), 0, np.sin(roty)],
                        [0, 1, 0],
                        [-np.sin(roty), 0, np.cos(roty)]])

        corners3d = np.vstack([x_corners, y_corners, z_corners])  # (3, 8)
        corners3d = np.dot(R, corners3d).T
        corners3d = corners3d + gt_locs[i].numpy() + np.array([0, h / 2, 0]).reshape(1, 3)

        img4 = draw_bev_box3d(img4, corners3d[np.newaxis, :], thickness=2, color=gt_color, scores=None)

    img4 = cv2.resize(img4, (img3.shape[0], img3.shape[0]))
    stack_img = np.concatenate([img3, img4], axis=1)

    cv2.imwrite("{}/{}.png".format(vis_path, target[0]['filename']), cv2.cvtColor(stack_img,cv2.COLOR_RGB2BGR))


from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
    path('login/', views.userLogin, name="login"),
    path('signup/', views.userSignup, name="signup"),
    path('logout/', views.logoutView, name="logout"),
    path('home/', views.home, name="home"),
    path('profile/', views.profile, name="profile"),
    path('profile/update', views.profileUpdate, name="updateProfile"),
]


import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Conv2D, Conv2DTranspose, Dropout
import matplotlib.pyplot as plt
import numpy as np
from vis.utils import utils
from vis.visualization import visualize_activation, get_num_filters
from vis.input_modifiers import Jitter
import random
import math
import os.path

# Model configuration
img_width, img_height = 28, 28
batch_size = 1000
no_epochs = 15
no_classes = 10
validation_split = 0.2
verbosity = 1

# Load MNIST dataset
(input_train, target_train), (input_test, target_test) = mnist.load_data()

# Reshape data
input_train = input_train.reshape(input_train.shape[0], img_width, img_height, 1)
input_test = input_test.reshape(input_test.shape[0], img_width, img_height, 1)
input_shape = (img_width, img_height, 1)

# Parse numbers as floats
input_train = input_train.astype('float32')
input_test = input_test.astype('float32')

# Normalize data
input_train = input_train / 255
input_test = input_test / 255

# Create the model
model = Sequential()
model.add(Conv2D(56, kernel_size=(3, 3), activation='relu', kernel_initializer='he_normal', input_shape=input_shape, name='conv_layer'))
model.add(Dropout(0.5))
model.add(Conv2DTranspose(56, kernel_size=(3,3), activation='relu', kernel_initializer='he_normal', name='conv_t_layer'))
model.add(Dropout(0.5))
model.add(Conv2D(1, kernel_size=(3, 3), activation='sigmoid', padding='same'))

# Compile and fit data
model.compile(optimizer='adam', loss='binary_crossentropy')
model.fit(input_train, input_train,
                epochs=no_epochs,
                batch_size=batch_size,
                validation_split=validation_split)

# Define the folder name to save into
folder_name = 'filter_visualizations'

# Iterate over multiple layers
for layer_nm in ['conv_layer', 'conv_t_layer']:

  # Find the particular layer
  layer_idx = utils.find_layer_idx(model, layer_nm)

  # Get the number of filters in this layer
  num_filters = get_num_filters(model.layers[layer_idx])

  # Draw filters randomly
  rows = 10
  cols = 5
  total = rows * cols
  drawn_filters = random.choices(np.arange(num_filters), k=total)

  # Visualize each filter
  plots = []
  for filter_id in drawn_filters:
    img = visualize_activation(model, layer_idx, filter_indices=filter_id, input_modifiers=[Jitter(16)])[:, :, 0]
    plots.append(img)
    print(f'Saved layer {layer_nm}/{filter_id} to file!')

  # Matplotlib plotting
  fig, axes = plt.subplots(rows, cols)
  for i in range(0, len(plots)):
    row = math.floor(i/cols)
    col = i % cols
    print(f'Row/col {row},{col}')
    axes[row, col].set_facecolor('white')
    axes[row, col].imshow(plots[i], cmap='gray')
  fig.suptitle(f'Filter visualizations, Dropout, layer  = {layer_nm}')
  plt.show()


import numpy as np
import math
from sigmoid import sigmoid, sigmoidDerivative
from scipy import io

def forwardPropogation (a, theta, m, currentIter, allA, allZ):
    if (currentIter >= len(theta)):
        return (a, allA, allZ)
    a = np.concatenate((np.ones((m,1), dtype=int), a), axis=1)
    z = a.dot(np.transpose(theta[currentIter]))
    a2 = sigmoid(z)
    allA.append(a2)
    allZ.append(z)
    return forwardPropogation(a2, theta, m, currentIter + 1, allA, allZ)

def backPropogation(allA, allZ, theta, lambdaVal, h, y, layers, m):
    delta = [0] * layers
    delta[layers-1] = h - y
    i = layers - 2
    while (i > 0):
        delta[i] = deltaCalculation(theta[i], delta[i+1], allZ[i - 1])
        i -= 1
    thetaGrad = [0] * (layers - 1)
    for i in range(0, layers - 1):
        currThetaGrad = 0
        a = np.concatenate((np.ones((allA[i].shape[0], 1), dtype=int), allA[i]), axis=1)
        if (i == 0):
            currThetaGrad = thetaGrad[i] + np.transpose(delta[i + 1][:,1:]).dot(a)
        else:
            currThetaGrad = thetaGrad[i] + np.transpose(delta[i + 1]).dot(a)
        currThetaGrad = currThetaGrad / m
        currThetaGrad[:,1:] = currThetaGrad[:,1:] + ((float(lambdaVal) / float(m)) * theta[i][:,1:])
        thetaGrad[i] = currThetaGrad
    return thetaGrad

def deltaCalculation(theta, prevDelta, z):
    z = np.concatenate((np.ones((z.shape[0], 1), dtype=int), z), axis = 1)
    return prevDelta.dot(theta) * sigmoidDerivative(z)

def costFunction(parameters, layers, X, y, lambdaVal):
    thetaSizes = []
    for i in range(0, len(layers) - 1):
        thetaSizes.append(layers[i + 1] *(layers[i] + 1))

    theta = []
    for i in range(0, len(thetaSizes)):
        startLength = 0 if (i == 0) else thetaSizes[i-1]
        theta.append(np.reshape(parameters[startLength:startLength + thetaSizes[i]], (layers[i + 1], (layers[i] + 1)), order = "F"))

    m = X.shape[0]

    reshapeY = np.zeros((m, layers[len(layers) - 1]), dtype=int)
    for i in range(0,m):
        reshapeY[i, int(y[i,0]-1)] = 1

    [h, allA, allZ] = forwardPropogation(X, theta, m, 0, [X], [])

    sumOverNodes = np.sum((reshapeY * np.log(h)) + ((1 - reshapeY) * np.log(1 - h)),axis=1)
    cost = 1
    # print bool(np.log(1-h)[0][0]
    if np.sum(reshapeY * np.log(h)) != 0:
      cost = -1 * np.sum(sumOverNodes, axis=0) / m
    regularization = 0
    for currTheta in theta:
        regularization += (sum(sum(currTheta[:,1:] * currTheta[:,1:])))
    regularization = regularization * (float(lambdaVal) / (2*float(m)))
    cost = cost + regularization

    ##########################
    #####Back Propogation#####
    ##########################
    thetaGrad = []
    for currTheta in theta:
        thetaGrad.append(np.zeros(currTheta.shape, dtype=int))

    thetaGrad = backPropogation(allA, allZ, theta, lambdaVal, h, reshapeY, len(layers), m)
    resultTheta = np.concatenate((thetaGrad[0].T.ravel(), thetaGrad[1].T.ravel()), axis=0)
    return (cost, resultTheta)
def CostFunctionTest():
    # data = io.loadmat('TestData/ex4data1.mat')
    # X = data['X']
    # y = data['y']

    # data = io.loadmat('TestData/ex4weights.mat')
    # theta1 = data['Theta1']
    # theta2 = data['Theta2']
    # theta1 = (theta1.T).ravel()
    # theta2 = (theta2.T).ravel()
    data = io.loadmat('vars.mat',  struct_as_record=True)
    # params = np.concatenate((theta1, theta2), axis=0)
    params = data['nn_params']
    X = data['X']
    y = data['y']

    costFunction(params, [3,5,3] , X, y, 3)

# CostFunctionTest()


"""
Team members:
Humberto Inzunza Velarde
Saif Azimi
Github Repository:
https://github.com/sazimi13/ee250-final
"""

from base64 import decode
import paho.mqtt.client as mqtt
import mplfinance as mpf
import time
import pandas as pd
from io import BytesIO
import os
import smtplib
from email.message import EmailMessage
import json
import pickle

# Name of the settings file. Must match path and name in vm_subscriber.py
settings_file = 'settings.json'
# Variable tht will hold the pandas dataframe
df = 0
ema = 0
lr = 0
# Flag to indicate if the stock_response_callback received data to avoid
# calling mpf.plot() outside of the main thread (could cause errors according to the library)
got_data = False
got_ema = False
got_lr = False

def read_settings(key):
    with open(settings_file, 'r') as file:
        settings = json.load(file)
        return settings[key]

# Callback for the stock response
def stock_response_callback(client, userdata, msg):
    global df
    df = pd.read_csv(BytesIO(msg.payload))
    df.index = pd.DatetimeIndex(df['timestamp'])
    df = (df[::-1])
    global got_data
    got_data = True

def ema_response_callback(client, userdata, msg):
    global got_ema
    got_ema = True
    global ema
    ema = pickle.loads(msg.payload)

def lr_response_callback(client, userdata, msg):
    global got_lr
    got_lr = True
    global lr
    lr = pickle.loads(msg.payload)

def open_position_response_callback(client, userdata, msg):
    open_position = pickle.loads(msg.payload)
    print()
    print("Current position in {}:".format(read_settings("current_symbol")))
    print("Shares: {0}\tAverage price: ${1:.2f}".format(open_position["shares"], open_position["avg_price"]))

def send_email(symbol):
    msg = EmailMessage()
    msg['Subject'] = "Stock Information"
    msg['From'] = "RPI"
    msg['To'] = "sazimi@usc.edu"

    with open(symbol + ".png", "rb") as f:
        file_data = f.read()
        file_name = f.name
        msg.add_attachment(file_data, maintype="image", subtype="png", filename = file_name)
    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.login('ee250final@gmail.com','USCee250!')
        server.send_message(msg)
    os.remove(symbol + ".png")

def on_connect(client, userdata, flags, rc):
    print("Connected to server (i.e., broker) with result code "+str(rc))

    #subscribe to the ultrasonic ranger topic here
    client.subscribe('inzunzav/stock_response')
    client.message_callback_add('inzunzav/stock_response', stock_response_callback)
    client.subscribe('inzunzav/ema_response')
    client.message_callback_add('inzunzav/ema_response', ema_response_callback)
    client.subscribe('inzunzav/lr_response')
    client.message_callback_add('inzunzav/lr_response', lr_response_callback)
    client.subscribe('inzunzav/open_position_response')
    client.message_callback_add('inzunzav/open_position_response', open_position_response_callback)

#Default message callback. Please use custom callbacks.
def on_message(client, userdata, msg):
    pass
    #print("on_message: " + msg.topic + " " + str(msg.payload, "utf-8"))

if __name__ == '__main__':
    #this section is covered in publisher_and_subscriber_example.py
    client = mqtt.Client()
    client.on_message = on_message
    client.on_connect = on_connect
    client.connect(host="broker.emqx.io", port=1883, keepalive=60)
    client.loop_start()

    while True:
        if(got_data == True and got_ema == True and got_lr == True):
            current_symbol = read_settings("current_symbol")
            ema_plot = mpf.make_addplot(ema, type='line', color='cyan')
            lr_plot = mpf.make_addplot(lr, type='line', color='orange')
            mpf.plot(df, type='candle', style='yahoo', title="Symbol = " + current_symbol, addplot = [ema_plot, lr_plot])
            mpf.plot(df, type='candle', style='yahoo', title="Symbol = " + current_symbol, addplot = [ema_plot, lr_plot], savefig=current_symbol + ".png")
            send_email(read_settings("current_symbol"))
            got_data = False
            got_ema = False
            got_lr = False
        time.sleep(1)


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :
使用pytorch进行梯度下降.py
@Contact :   huanghoward@foxmail.com
@Modify Time :    2022/4/8 16:08
------------
"""
import matplotlib.pyplot as plt
import numpy as np
import torch

if __name__ == '__main__':
    A = torch.tensor(np.random.random([1, 5]))
    x = torch.tensor(np.random.random([5, 1]), requires_grad=True)
    b = torch.tensor(0)
    history = []
    for i in range(200):
        y = torch.pow(torch.matmul(A, x) + b, 2)
        t = 0.005
        history.append(y.data)
        y.backward()
        with torch.no_grad():
            x = x - t * x.grad
            x = x.clone().detach().requires_grad_(True)
            x.grad = None

    plt.plot(history)
    plt.show()


import random
import matplotlib.pyplot as plt
import numpy as np

def peut_placer(grille, bateau, position, direction):
	"""Retourne vrai si il est possible de placer le bateau sur la grille
	à la position et à la direction données en paramètre de la fonction (1-horizontale, 2-verticale)."""
	taille_bateau = bateau%10                   								# bateau%10 car dans main.py nous utilisons cette liste de bateaux [2,3,13,4,5] or 13 représente un bateau de taille 3
	if direction==1:                            								# Si direction horizontale
		if position[1]+taille_bateau<=10: 										# Si il y a assez de place pour placer le bateau sur la ligne
			for i in range(position[1],position[1]+taille_bateau): 				# Pour les cases pouvant être occupées par le bateau courant
				if grille[position[0],i]!=0: 									# Teste si la case n'est pas déjà occupée par un autre bateau, si non retourne faux
					return False
		else: return False 														# Si il n'y a pas assez de place pour placer le bateau retourne faux
	else:                                       								# Si direction verticale
		if position[0]+taille_bateau<=10: 										# Si il y a assez de place pour placer le bateau sur la colonne
			for i in range(position[0],position[0]+taille_bateau): 				# Pour les cases pouvant être occupée par le bateau courant
				if grille[i,position[1]]!=0: 									# Teste si la case n'est pas déjà occupée par un autre bateau, si non retourne faux
					return False
		else: return False 														# Si il n'y a pas assez de place pour placer le bateau retourne faux
	return True

def place(grille, bateau, position, direction):
	"""Si l'operation est possible, rend la grille modifiée
	où le bateau a été place comme indiqué."""
	taille_bateau = bateau%10 													# bateau%10 car dans main.py nous utilisons cette liste de bateaux [2,3,13,4,5] or 13 représente un bateau de taille 3
	if not(peut_placer(grille, bateau, position, direction)): 					# Fait appel à la fonction peut_placer pour savoir si l'opération est possible
		return
	if direction==1: 															# Si la direction est horizontale
		grille[position[0],(position[1]):(position[1]+taille_bateau)] = bateau 	# On place le bateau sur les n cases consécutives correspondant à la taille du bateau à partir de la position entrée en paramètre de la fonction
	else: 																		# Si la direction est verticale
		grille[(position[0]):(position[0]+taille_bateau),position[1]] = bateau 	# On place le bateau sur les n cases consécutives correspondant à la taille du bateau à partir de la position entrée en paramètre de la fonction

def place_alea(grille, bateau):
	"""Place aleatoirement le bateau dans la grille
	en tirant uniformement une position et une direction aléatoires
	jusqu'à ce que le positionnement choisi soit admissible et effectué."""
	position = (random.randint(0,9), random.randint(0,9)) 						# On choisit une position aléatoirement
	direction = random.randint(1,2) 											# On choisit une direction aléatoirement
	if peut_placer(grille, bateau, position, direction)==True: 					# Si le bateau peut être placé à la position et à la direction choisies aléatoirement
		place(grille, bateau, position, direction) 								# Alors on place le bateau dans la grille
	else: place_alea(grille, bateau) 											# Si le bateau ne peut pas être placé, on rappelle la fonction jusqu'à ce que le bateau puisse être placé

def affiche(grille):
	"""Affichage de la grille de jeu."""
	grid = plt.imshow(grille, origin=None) 										# Grid represente l'image de la grille de jeu entrée en parametre
	grid.axes.get_xaxis().set_visible(False) 									# Désactive l’axe pour l’axe X
	grid.axes.get_yaxis().set_visible(False) 									# Désactive l’axe pour l’axe Y
	plt.show() 																	# Affiche l'image de la grille

def eq(grilleA, grilleB):
	"""Teste l'égalité entre deux grilles."""
	return np.array_equal(grilleA, grilleB) 									# Retourne vrai les 2 grilles sont identiques

def genere_grille_bateaux(bateaux):
	"""Retourne une grille aleatoire comprenant une liste donnee des bateaux et
	renvoie la liste des bateaux placés comprenant les positions et directions."""
	bateaux_places = [] 														# On initialise une liste vide qui contiendra des tuples. Chaque tuple correspondant au bateau placé dans la grille, a sa position, ainsi qu'à sa direction
	grille = np.zeros((10,10)) 													# On initialise une grille 10x10 avec 0 dans toutes ses cases
	for b in bateaux: 															# Pour tous les bateau à placer. bateaux : liste des bateaux à placer dans la grille
		position = (random.randint(0,9), random.randint(0,9)) 					# On choisit une position aléatoire
		direction = random.randint(1,2) 										# On choisit une direction aléatoire
		while peut_placer(grille, b, position, direction)==False: 				# Tant qu'il n'est pas possible de placer le bateau sur la grille
			position = (random.randint(0,9), random.randint(0,9)) 				# On choisit aléatoirement une position
			direction = random.randint(1,2) 									# On choisit aleatoirement une direction, jusqu'à ce que le bateau puisse être placé dans la grille
		place(grille, b, position, direction) 									# Ainsi quand cela est possible on place le bateau dans la grille
		bateaux_places.append((b, position, direction)) 						# Et on l'ajoute à la liste des bateaux qui on deja été placés
	return grille, bateaux_places 												# Retourne la grille ainsi que la liste des bateaux placés contenant des tuples correspondant au bateau, à sa position, ainsi qu'à sa direction dans la grille

def genere_grille():
	"""Retourne une grille aleatoire comprenant l'ensemble des bateaux."""
	grille = np.zeros((10,10)) 													# On initialise une grille 10x10 avec 0 dans toutes ses cases
	bateaux = [2, 3, 13, 4, 5]  												# Liste des bateaux à placer dans la grille
	for b in bateaux: 															# Pour chaque bateau de la liste
		place_alea(grille, b) 													# On le place aléatoirement dans la grille
	return grille 																# On retourne la grille

def facons_placer_bateau(grille, bateau):
	"""Permet de calculer le nombre de facons de placer
	un bateau entré en parametre sur une grille vide."""
	total_facons=0  															# Compteur
	for d in [1,2]: 															# Pour chaque direction
		for i in range(0, 10): 													# Pour chaque ligne
			for j in range(0,10): 												# Pour chaque colonne
				if peut_placer(grille, bateau, (i,j), d): 						# Si il est possible de placer le bateau entré en paramètre sur la grille à la position et à la direction courante
					total_facons+=1 											# On incrémente donc notre compteur
	return total_facons 														# Retourne le nombre de possibilité

def grille_tinder(grille):
	"""Prend en parametre une grille, genere des grilles aleatoirement
	jusqu'a ce que la grille generee soit egale a la grille passee
	en parametre et renvoie le nombre de grilles generees."""
	potential_matches=0 														# Compteur des grilles générées jusqu'à qu'une grille générées corresponde à la grille entrée en parametre de la fonction
	grille_match=None 															# Variable qui contiendra la grille courante
	while not(eq(grille_match,grille)): 										# On génère une nouvelle grille jusqu'à ce que la grille courante soit égale à la grille entrée en paramètre
		grille_match=genere_grille() 											# Génère une grille aléatoire
		potential_matches+=1 													# On incrémente le compteur à chaque nouvelle grille générée
	return potential_matches 													# On retourne le nombre de grilles générées

def facons_placer_bateaux_1(grille, bateaux):
	"""Permet de calculer le nombre de facons de placer une liste de bateaux
	sur une grille vide."""
	total = 0 																	# Compteur
	for b in bateaux: 															# Pour chaque bateau de la liste de bateau entrée en paramètre
		for d in [1,2]: 														# Pour chaque direction
			for i in range(0, 10): 												# Pour chaque ligne de la grille
				for j in range(0,10): 											# Pour chaque colonne de la grille
					if peut_placer(grille, b, (i,j), d): 						# On teste si le bateau peut y être placer
						total += 1 												# Si oui, on incrémente notre compteur
						bateaux1 = bateaux[1:] 									# On initialise une variable bateaux1 contenant la liste des bateaux entrée en paramètre à laquelle on retire le premier élément
						if bateaux1!=[]: 										# Si bateaux1 ne correspond pas à la liste vide
							place(grille, b, (i,j), d) 							# On place dans la grille le bateau courant à la position et à la direction courante
							total += facons_placer_bateaux_1(grille, bateaux1) 	# On incrémente la valeur du compteur avec le retour de la fonction appelé cette fois avec la liste bateaux1
							grille = np.zeros((10,10)) 							# On réinitialise la grille
	return total 																# On retourne le compteur du nombre de facons de placer une liste de bateaux

def facons_placer_bateaux_2(bateaux):           								# Pas efficace, beaucoup trop long
	"""Permet d'approximer le nombre total de grilles pour une liste de bateaux."""
	i = 0 																		# Compte le nombre de fois consécutives qu'une grille est déjà été générée
	grilles = [] 																# On initialise une liste vide qui contiendra les différentes grilles générées (elles seront uniques)
	grille=[]
	grille.append(genere_grille_bateaux(bateaux)[0].tolist()) 					# On initialise une variable qui contiendra la grille courante, une grille générée aléatoirement avec la liste de bateaux entrée en paramètre
	while i<=10: 																# Si on gérère consécutivement 10 grilles déjà présentent dans la liste alors on quitte la boucle
		if grille not in grilles : 												# Si la grille courante n'a pas encore été générée, c'est à dire qu'elle n'est pas encore présente dans la liste
			grilles.append(grille) 												# Alors on l'ajoute à la liste
			i=0 																# Et on remet le compteur à 0
		else : 																	# Si la grille a déjà été générée
			i+=1 																# Alors on incrémente le compteur
		grille = []
		grille.append(genere_grille_bateaux(bateaux)[0].tolist()) 				# Grille prend la valeur d'une nouvelle grille aléatoire
	return len(grilles) 														# On retourne la taille de la liste, c'est à dire le nombre de grilles différentes générées

def facons_placer_bateaux_3(etapes):
	"""Permet d'approximer le nombre total de grilles pour une liste de bateaux."""
	total_facons = 0 															# Compteur du nombre de total de façons de placer une liste de bateaux
	for e in range(0,etapes): 													# On répète les instructions suivantes n fois correspondant au nombre d'étapes entré en paramètre
		facons = 1 																# Initialisation du compteur du nombre de façons de placer liste de bateaux sur une grille
		bateaux = [2, 3, 13, 4, 5] 												# Liste des bateaux à placer dans la grille
		grille = np.zeros((10,10)) 												# On initialise une grille 10x10 avec 0 dans toutes les cases
		for b in bateaux: 														# Pour chaque bateau de la liste
			facons *= facons_placer_bateau(grille, b) 							# On multiplie le compteur par le nombre de façons de placer le bateau courant sur la grille
			place_alea(grille, b) 												# On place aléatoirement le bateau courant sur la grille
		total_facons += facons 													# On ajoute au compteur total le nombre de façons de placer une liste de bateaux durant une étape
	return total_facons//etapes 												# On retourne le nombre de façons totales trouvées divisé par le nombre d'étapes réalisées


from lib2to3.pgen2 import token
import nltk
import spacy
import re
import preprocessor as p
import pandas as pd
import numpy as np
import tensorflow as tf
from keras.models import load_model
from keras.preprocessing.sequence import pad_sequences
from keras.preprocessing.text import Tokenizer
from flask import Flask, render_template, url_for, request, jsonify
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

def Preprocessing(tokenizer, input):
    #cleanedinput = clean_text(input)
    cleanedinput = tweetcleaning(input)
    inputpadded = textpadding(tokenizer, cleanedinput)

    return inputpadded

def clean_text(input):
    # Initialize
    nlp = spacy.load("en_core_web_sm")
    stop_words = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're", "you've", "you'll", "you'd", 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', "she's", 'her', 'hers', 'herself', 'it', "it's", 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', "that'll", 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or',
                  'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'nor', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'should', "should've", 'now', 'd', 'll', 'm', 'o', 're', 've', 'y', 'ma']
    text_cleaning_regex = "@S+|https?:S+|http?:S|[^A-Za-z0-9]+"
    tokens = []
    input = input.lower()
    doc = nlp(input)
    for token in doc:
        if not token.is_punct and not token.is_space and not token.is_digit and not token.like_url:
            if token.text not in stop_words:
                tokens.append(token.lemma_)
    input = " ".join(tokens)
    # Passing the text through the regex equation
    input = p.clean(input)
    input = re.sub(text_cleaning_regex, ' ', str(input)).strip()
    # Converting text to array
    ar = []
    ar.append(input)
    return ar

def tweetcleaning(text):
    text = text.lower()
    text = p.clean(text)
    # # Converting text to array
    ar = []
    ar.append(text)
    return ar

def textpadding(tokenizer, input):
    # Converting the text to a list of numerical sequences and padding them with 50 as the maxlength
    sequence_input = tokenizer.texts_to_sequences(input)
    x_input_pad = pad_sequences(sequence_input, maxlen=30)

    return x_input_pad


from PIL import Image
import os

NIVEIS_DE_CINZA = 256 #rk

def to_gray_scale(img: Image) -> list:
    img_gray = Image.open(img.filename).convert('L')
    img_gray.save('./gray_scale/' + img.filename.split('/')[-1])

    return Image.open('./gray_scale/' + img.filename.split('/')[-1])

def get_histogram(gray_img_matrix: list, img_lines: int, img_columns: int) -> list:
    histogram = [0] * NIVEIS_DE_CINZA
    for l in range (img_lines):
        for c in range (img_columns):
            histogram[ gray_img_matrix[l][c] ] += 1

    return histogram

def get_pr_rk(nk: list) -> list:
    pr_rk = []
    sum_rk = sum(nk)
    for value in nk:
        pr_rk.append( value/sum_rk )

    return  pr_rk

def get_freq(pr_rk: list) -> list:
    freq = []
    freq.append( pr_rk[0] )
    for rk in range (1, NIVEIS_DE_CINZA):
        freq.append( sum( pr_rk[ : rk+1 ] ) )

    return freq

def get_eq(freq: list) -> list:
    eq = []
    for value in freq:
        eq.append( (NIVEIS_DE_CINZA-1)*value )

    return eq

def get_new_rk(eq: list) -> list:
    new_rk = []
    for value in eq:
        new_rk.append( int(value) )

    return new_rk

def equalize(original_img: list, new_rk: list, img_lines: int, img_columns: int):

    new_img = []

    for line in original_img:
        for pixel_value in line:
            new_img.append( new_rk[ pixel_value ] )

    # converte a lista anterior para uma lista 2D:
    new_img = [new_img[offset: offset + img_columns] for offset in range(0, img_columns*img_lines, img_columns)]

    return new_img


import pygame
import time
import game
import map
import statistics
from organisms import *

# Initialize pygame
pygame.init()
pygame.display.set_caption("Ecosystem Simulator")
#Label Font
label_font = pygame.font.SysFont("Calibri", 20)

# Game_Loop

while config.game_running:

    game.screen.blit(game.universe_screen, (map.x_pos_map,map.y_pos_map))
    #game.handle_events() Disabled
    map.Map.render_map()
    population_label = label_font.render(f"Evolution: {config.evolution} \
        Present Rabbit Population: {len(Rabbit.rabbit_list)}", 1, (0,0,0))
    game.screen.blit(population_label, (10, 10))

    #Present Universe State
    Organism.universe_matrix = game.Universe(Grass.grass_list, Rabbit.rabbit_list, Fox.fox_list).universe_matrix

    #Grass God
    Grass.grass_populator()
    Grass.new_grass_generator()

    for rabbit in Rabbit.rabbit_list:
        print(rabbit.id, rabbit.gender,  rabbit.fitness)

    # Population explosion kills all animals and jumps to next evolution
    # or when either Rabbits or Foxes are extinct or the System is started initially
    if len(Rabbit.rabbit_list) >= config.population_limit or len(Fox.fox_list) >= config.population_limit \
         or not Rabbit.rabbit_list or not Fox.fox_list:

        Organism.dead_list.extend(Rabbit.rabbit_list)
        Rabbit.rabbit_list = []
        Organism.dead_list.extend(Fox.fox_list)
        Fox.fox_list = []

        if config.days > 0:
            Organism.save_fittest_creatures(Rabbit)

        Organism.birth(Rabbit, config.rabbit_initial_N, Rabbit.rabbit_list)
        Organism.birth(Fox, config.fox_initial_N, Fox.fox_list)

        if config.days == 0:
            Organism.birth(Grass, config.grass_initial_quantity, Grass.grass_list)

        config.evolution += 1
        config.days = 0 #This has to be defined here to reset days counter when new evolution occurs,
                        #while avoiding the new generation of pioneer Grass as defined in above condition

    #This is defining the universal state of the simulation as a matrix
    #Every intelligent creature then gets a slice from the Universal matrix based
    #on their position in the universe along with the radius of their vision
    Organism.Brain.universe_matrix = game.Universe().universe_matrix

    #Rabbit God
    Rabbit.live(population_list= Rabbit.rabbit_list,
              food_list=Grass.grass_list, creature_class=Rabbit)

    #Fox God
    Fox.live(population_list=Fox.fox_list,
            food_list=Rabbit.rabbit_list, creature_class=Fox)

    # Days counter
    config.days += 1

    #Check if simulation has been stopped manually
    for event in pygame.event.get():
        if event.type == pygame.QUIT:

            #This is done to handle the case of manually closing the simulation,
            #since manually closing would mean that all creatures are not in dead_list
            Organism.dead_list.extend(Rabbit.rabbit_list)
            Organism.save_fittest_creatures(Rabbit)
            config.game_running = False
            statistics.print_stats()

    time.sleep(0.0001)

    pygame.display.update()


import torch
from torch import nn
import numpy as np

class GRUNet(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, washout=0):
        super(GRUNet, self).__init__()
        self.hidden_dim = hidden_dim
        self.washout = washout
        self.gru = nn.GRU(input_dim, hidden_dim, batch_first=True, bias=True)
        self.w_out = torch.rand(hidden_dim, output_dim)

    def forward(self, x, h):
        out, h = self.gru(x, h)
        out = torch.mm(out.reshape(-1, self.hidden_dim)[self.washout:], self.w_out)
        return out, h

    def init_hidden(self, batch_size):
        weight = next(self.parameters()).data
        hidden = weight.new(1, batch_size, self.hidden_dim).zero_()
        return hidden

def generate_series(output_weight, length, reservoir):
    hidden_weights, input_weights = reservoir
    hidden_dim = output_weight.shape[0]
    model = GRUNet(1, hidden_dim, 1)
    model.gru.weight_ih_l0 = nn.Parameter(input_weights.reshape(3 * hidden_dim, -1), requires_grad=True)
    model.gru.weight_hh_l0 = nn.Parameter(hidden_weights.reshape(3 * hidden_dim, -1), requires_grad=True)
    h = model.init_hidden(1)
    y = 0.
    time_series = [y]
    for i in range(length):
        out, h = model(torch.tensor(y).reshape(1,-1,1).float(), h)
        new_y = out.reshape(-1).item()
        time_series.append(new_y)
        y = new_y
    return np.array(time_series)

# A cycle reservoir just like the one for ESNs
def cycle_reservoir_construction(us, vs, reservoir_size=100):
    hw = torch.zeros((3, reservoir_size, reservoir_size))
    iw = torch.zeros((3, reservoir_size, 1))
    for i in range(3):
        idxs = np.arange(0, reservoir_size-1)
        hw[i, idxs+1, idxs] = us[i]
        hw[i, 0, reservoir_size-1] = us[i]
        # Random Signed Input weights
        c = torch.randint(0,10,(reservoir_size, 1))
        v = torch.full((reservoir_size, 1), vs[i])
        iw[i] = torch.where(c % 2 == 0, v, v*-1)
    return hw, iw

def default_reservoir(reservoir_size=100):
    return cycle_reservoir_construction([0.9, 0.8, 0.7], [0.02, 0.03, 0.04], reservoir_size)

# Trained with hillclimbing algorithm
def kepler_reservoir(reservoir_size=100):
    return cycle_reservoir_construction([0.03478323, 1.14405769, 1.47451068], [0.00801442, 0.60542253, 0.11416187],
                                        reservoir_size)

def cauchy_reservoir(reservoir_size=100):
    return cycle_reservoir_construction([0.69069763, 0.81299484, 0.85773752], [0.60836169, 0.88066761, 1.2982216],
                                        reservoir_size)


#!/usr/bin/python3
""" function that queries the Reddit API,
    parses the title of all hot articles,
    and prints a sorted count of given keywords """
import requests

def count_words(subreddit, word_list, after='', dic={}):
    """function that queries the Reddit API """
    if len(dic) <= 0:
        for word in word_list:
            dic[word] = 0
    if after is None:
        word_list = [[key, value] for key, value in dic.items()]
        word_list = sorted(word_list, key=lambda x: (-x[1], x[0]))
        for word in word_list:
            if word[1]:
                print("{}: {}".format(word[0].lower(), word[1]))
        return None
    headers = {'User-Agent': 'pinguin'}
    res = requests.get(
        'https://www.reddit.com/r/{}/hot.json?after={}'.
        format(subreddit, after), headers=headers, allow_redirects=False)
    if res.status_code != 200:
        return None
    info = res.json()
    data1 = info.get('data')
    after = data1.get('after')
    chi = data1.get('children')
    for i in chi:
        title = i.get('data').get('title')
        lc = [letter.lower() for letter in title.split(' ')]
        for word in word_list:
            dic[word] += lc.count(word.lower())
    count_words(subreddit, word_list, after, dic)


import torch
import torch.nn as nn

def batched_index_select(x, idx):
    r"""fetches neighbors features from a given neighbor idx

    Args:
        x (Tensor): input feature Tensor
                :math:`\mathbf{X} \in \mathbb{R}^{B \times C \times N \times 1}`.
        idx (Tensor): edge_idx
                :math:`\mathbf{X} \in \mathbb{R}^{B \times N \times l}`.
    Returns:
        Tensor: output neighbors features
            :math:`\mathbf{X} \in \mathbb{R}^{B \times C \times N \times k}`.
    """

    x = x.unsqueeze(0).unsqueeze(-1)
    batch_size, num_vertices_reduced, num_dims = x.shape[:3]
    idx = idx.unsqueeze(0)
    _, num_vertices, k = idx.shape
    idx_base = torch.arange(0, batch_size, device=idx.device).view(-1, 1, 1) * num_vertices_reduced
    idx = idx + idx_base
    idx = idx.contiguous().view(-1)

    x = x.transpose(2, 1)
    feature = x.contiguous().view(batch_size * num_vertices_reduced, -1)[idx, :]
    feature = feature.view(batch_size, num_vertices, k, num_dims).permute(0, 3, 1, 2).contiguous()
    return feature

class EdgeConv2d(nn.Module):
    """
    Edge convolution layer (with activation, batch normalization) for dense data type
    """
    def __init__(self, in_channels, out_channels):
        super(EdgeConv2d, self).__init__()
        self.nn = nn.Sequential(
            nn.Conv2d(in_channels * 2, out_channels, 1, bias=True, groups=4),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=False),
        )

    def forward(self, x, edge_index):
        x_i = batched_index_select(x, edge_index[1])
        x_j = batched_index_select(x, edge_index[0])
        max_value, _ = torch.max(self.nn(torch.cat([x_i, x_j - x_i], dim=1)), -1, keepdim=True)
        return max_value


import numpy as np
import sympy as sym

def trazador_lineal(xi,fi):
    n = len(xi)
    x = sym.Symbol('x')
    px_table = []

    tramo = 1
    while not(tramo>=n):
        numerador = fi[tramo]-fi[tramo-1]
        denominador = xi[tramo]-xi[tramo-1]
        m = numerador/denominador
        pxtramo = fi[tramo-1] + m*(x-xi[tramo-1])
        px_table.append(pxtramo)
        tramo = tramo + 1
    return(px_table)


import itertools
from math import sqrt

class FunctionBuilder:
    """umm...

    Examples:
    >>> x = FunctionBuilder('x')
    >>> f = (x + 5) * (x - 3)
    >>> f(x=0)
    -15
    >>> f(x=3)
    0
    >>> f(x=4)
    9
    >>> f(x=5)
    20

    >>> f = x**2 - 4 * x + 4
    >>> f(x=0)
    4
    >>> f(x=2)
    0
    >>> f(x=6)
    16

    >>> f = 5 - (x % 8 + 17 // x)
    >>> f(x=4)
    -3

    >>> x = FunctionBuilder('x')
    >>> y = FunctionBuilder('y')
    >>> f = 4*x - y**2 + y//7
    >>> f(x=2, y=8)
    -55

    """
    __slots__ = ('_operations', 'name')

    def __init__(self, name):
        self._operations = []
        self.name = name

    def __copy__(self):
        f = FunctionBuilder(self.name)
        f._operations = self._operations[:]
        return f

    def __call__(self, **kwargs):
        result = kwargs[self.name]
        for op, other in self._operations:
            if isinstance(other, FunctionBuilder):
                other = other(**kwargs)
            result = op(result, other)
        return result

    def _(func):
        def fred_johnson(self, other):
            f = self.__copy__()
            f._operations.append((func, other))
            return f
        return fred_johnson

    __add__ = _(lambda x, y: x + y)
    __radd__ = _(lambda x, y: y + x)
    __sub__ = _(lambda x, y: x - y)
    __rsub__ = _(lambda x, y: y - x)
    __mul__ = _(lambda x, y: x * y)
    __rmul__ = _(lambda x, y: y * x)
    __truediv__ = _(lambda x, y: x / y)
    __rtruediv__ = _(lambda x, y: y / x)
    __floordiv__ = _(lambda x, y: x // y)
    __rfloordiv__ = _(lambda x, y: y // x)
    __mod__ = _(lambda x, y: x % y)
    __rmod__ = _(lambda x, y: y % x)
    __pow__ = _(lambda x, y: x ** y)
    __rpow__ = _(lambda x, y: y ** x)

def vars(names):
    return [FunctionBuilder(name) for name in names]

def sieve_of_eratosthenes(limit):
    """Return the prime numbers less than limit

    >>> sieve_of_eratosthenes(7)
    [2, 3, 5]

    """
    # Start with a list of all integers up to max
    l = list(range(limit))
    l[1] = 0 # 1 is not prime
    stop = sqrt(limit)
    for n in l:
        if n != 0:
            m = n * n
            while m < limit:
                l[m] = 0
                m += n
    return [x for x in l if x != 0]

def primes(max=None):
    """Generate prime numbers

    Stolen from
    http://code.activestate.com/recipes/117119-sieve-of-eratosthenes/

    >>> list(primes(5))
    [2, 3, 5]

    """
    yield 2
    D = {}
    for q in itertools.count(3, 2):
        if max and q > max:
            raise StopIteration
        p = D.pop(q, None)
        if p is None:
            # q is prime
            yield q
            D[q * q] = 2 * q
        else:
            # q is composite
            x = p + q
            while x in D:
                x += p
            D[x] = p

def factors(x):
    """Return all factors of n"""
    factors = set()
    for n in range(1, int(sqrt(x)) + 1):
        if x % n == 0:
            factors.add(n)
            factors.add(x // n)
    return factors

def prime_factors(n):
    prime_generator = primes()
    while n > 1:
        p = next(prime_generator)
        while n % p == 0:
            n = n // p
            yield p

if __name__ == '__main__':
    import doctest
    doctest.testmod()


"""
Provides functions for the discovery of fibre hubs
"""

import sys
import time
import json
import fibre.protocol
import re
import time
import os
import itertools
import struct
import functools

def noprint(x):
  pass

class SimpleDeviceProperty(property):
    """
    Used internally by dynamically created objects to translate
    property assignments and fetches into endpoint operations on the
    object's associated channel
    """
    def __init__(self, channel, id, type, struct_format, can_read, can_write):
        self._channel = channel
        self._id = id
        self._type = type
        self._struct_format = struct_format
        property.__init__(self,
            self.fget if can_read else None,
            self.fset if can_write else None)

    def fget(self, obj):
        size = struct.calcsize(self._struct_format)
        buffer = self._channel.remote_endpoint_operation(self._id, None, True, size)
        return struct.unpack(self._struct_format, buffer)[0]

    def fset(self, obj, value):
        value = self._type(value)
        buffer = struct.pack(self._struct_format, value)
        # TODO: Currenly we wait for an ack here. Settle on the default guarantee.
        self._channel.remote_endpoint_operation(self._id, buffer, True, 0)

def call_remote_function(channel, trigger_id, arg_properties, *args):
    """
    Used internally by the dynamically created objects to translate
    function calls into endpoint operations on the associated channel
    """
    if (len(arg_properties) != len(args)):
        raise TypeError("expected {} arguments but have {}".format(len(arg_properties), len(args)))
    for i in range(len(args)):
        arg_properties[i].fset(None, args[i])
    channel.remote_endpoint_operation(trigger_id, None, True, 0)

def setattr_or_raise_if_undefined(self, name, value):
    """
    If employed as an object's __setattr__ function, this function
    makes sure that an assignment to an undefined attribute doesn't
    create a new attribute but instead raises an exception
    """
    # We can't use hasattr here because internally it fetches the property
    # value, creating unnecessary bus traffic
    if name in dir(self):
        object.__setattr__(self, name, value)
    else:
        raise TypeError('Cannot set name %r on object of type %s' % (
                        name, self.__class__.__name__))

def create_property(name, json_data, channel, printer):
    """
    Dynamically creates a property based on a JSON definition
    """
    name = name or "[anonymous]"

    type_str = json_data.get("type", None)
    if type_str is None:
        printer("property {} has no specified type".format(name))
        return None

    if type_str == "float":
        property_type = float
        struct_format = "<f"
    elif type_str == "bool":
        property_type = bool
        struct_format = "<?"
    elif type_str == "int8":
        property_type = int
        struct_format = "<b"
    elif type_str == "uint8":
        property_type = int
        struct_format = "<B"
    elif type_str == "int16":
        property_type = int
        struct_format = "<h"
    elif type_str == "uint16":
        property_type = int
        struct_format = "<H"
    elif type_str == "int32":
        property_type = int
        struct_format = "<i"
    elif type_str == "uint32":
        property_type = int
        struct_format = "<I"
    else:
        printer("property {} has unsupported type {}".format(name, type_str))
        return None

    id_str = json_data.get("id", None)
    if id_str is None:
        printer("property {} has no specified ID".format(name))
        return None

    access_mode = json_data.get("access", "r")
    return SimpleDeviceProperty(channel, id_str, property_type,
                                struct_format,
                                'r' in access_mode,
                                'w' in access_mode)

def create_function(name, json_data, channel, printer):
    """
    Dynamically creates a function based on a JSON definition
    """
    id_str = json_data.get("id", None)
    if id_str is None:
        printer("function {} has no specified ID".format(name))
        return None

    inputs = []
    for param in json_data.get("arguments", []):
        param["mode"] = "r"
        inputs.append(create_property(json_data["name"], param, channel, printer))
    return functools.partial(call_remote_function, channel, id_str, inputs)

def create_object(name, json_data, namespace, channel, printer=noprint):
    """
    Creates an object that implements the specified JSON type description by
    communicating with the provided device object
    """
    if not namespace is None:
        namespace = namespace + "." + name
    else:
        namespace = name

    # Build attribute list from JSON
    attributes = {"__setattr__": setattr_or_raise_if_undefined}
    for member in json_data.get("members", []):
        member_name = member.get("name", None)
        if member_name is None:
            printer("ignoring unnamed attribute in {}".format(namespace))
            continue

        type_str = member.get("type", None)
        if type_str is None:
            printer("member {} has no specified type".format(member_name))
            continue

        if type_str == "object":
            attribute = create_object(member_name, member, namespace, channel, printer=printer)
        elif type_str == "function":
            attribute = create_function(member_name, member, channel, printer)
        else:
            attribute = create_property(member_name, member, channel, printer)

        if not attribute is None:
            attributes[member_name] = attribute

    # Create a type from the property list and instantiate it
    jit_type = type(namespace, (object,), attributes)
    new_object = jit_type()
    return new_object

def object_from_channel(channel, printer=noprint):
    """
    Inits an object from a given channel.
    This queries the endpoint 0 on that channel to gain information
    about the interface, which is then used to init the corresponding object.
    """
    printer("Connecting to device on " + channel._name)
    try:
        json_bytes = channel.remote_endpoint_read_buffer(0)
    except (fibre.protocol.TimeoutException, fibre.protocol.ChannelBrokenException):
        raise fibre.protocol.DeviceInitException("no response - probably incompatible")
    json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes)
    channel._interface_definition_crc = json_crc16
    try:
        json_string = json_bytes.decode("ascii")
    except UnicodeDecodeError:
        raise fibre.protocol.DeviceInitException("device responded on endpoint 0 with something that is not ASCII")
    printer("JSON: " + json_string)
    try:
        json_data = json.loads(json_string)
    except json.decoder.JSONDecodeError:
        raise fibre.protocol.DeviceInitException("device responded on endpoint 0 with something that is not JSON")
    json_data = {"name": "fibrehub", "members": json_data}
    return create_object("fibrehub", json_data, None, channel, printer=printer)