===== Hexagonální grid ===== ==== Souřadnicový systém ==== * Pozice každé buňky na hexagonálním gridu lze vyjádřit souřadnicí ''(p,q)'', kde ''p'' označuje sloupec a ''q'' řádek. * Horní levý roh má souřadnici ''(0,0)'' * Liché řádky (q=1,3..) jsou oproti sudým posunuty směrem doprava * Vzhledem k posunu buněk mezi řádky je osa 'p' nakloněna, viz obrázek: {{ :courses:b3b33alp:cviceni:hw8-pqsystem.png?400 |}} * Tento souřadnicový systém je zvolen proto, že umožňuje logické adresování buněk, je celočíselný a hlavně, podporuje rychlou operaci rotace buňky ==== Pomocné funkce ==== * Můžete si nakopírovat obsah následujícího programu do vašich programů pro úlohu HW09. * Následující program umožňuje načíst hrací desku ze souboru (metoda ''loadBoard()'' ) * Pro ukládání obrázků se používá knihovna [[ https://en.wikipedia.org/wiki/Python_Imaging_Library | Python Imaging Library ]] * Návod na instalaci: [[https://pillow.readthedocs.io/en/stable/installation.html ]] import copy import math from PIL import Image, ImageDraw # Uzitecne funkce a tridy pro praci s hexagonalnim gridem """ Vytvoreni prazneho gridu o velikosti 10x10: b = Board(10) # alternativa: nacteni ze souboru b = Board(0) b.loadBoard("soubor.txt") # prochazeni radku/sloupce: for p in b.board: for q in b.board[q]: print("bunka", b.board[p][q] ) # zjisteni, jestli je nejaka bunka uvnitr: p = 20 q = 10 if b.inBoard(p,q): print("Bunka je uvnitr") else: print("bunka je venku") # ulozeni do png, bunky budou obarveny dle jejich hodnoty (0 = bila) #provedene nejake obarveni, napriklad bunky, pro ktere neexistuje p+1 soused for p in b.board: for q in b.board[p]: if not b.inBoard(p+1, q): #pokud nema souseda p+11 b.board[p][q] = 3 b.saveImage("soubor.png") """ def loadStones(filename): f = open(filename,"r") stones = [] for line in f: coords = list(map(int, line.rstrip().split())) if len(coords) > 0: stones.append( [] ) for i in range(len(coords)//2): x = coords[2*i] y = coords[2*i+1] stones[-1].append([ x,y ] ) return stones; class Board: def __init__(self, size = 0): self.size = size self.board = {} #create empty board as a dictionary self.b2 = {} for p in range(-self.size,self.size): for q in range(-self.size, self.size): if self.inBoard(p,q): if not p in self.board: self.board[p] = {} self.board[p][q] = 0 if not q in self.b2: self.b2[q] = {} self.b2[q][p] = 0 #this is for visualization and to synchronize colors between png/js self._colors = {} self._colors[-1] = "#fdca40" #sunglow self._colors[0] = "#ffffff" #white self._colors[1] = "#947bd3" #medium purple self._colors[2] = "#ff0000" #red self._colors[3] = "#00ff00" #green self._colors[4] = "#0000ff" #blue self._colors[5] = "#566246" #ebony self._colors[6] = "#a7c4c2" #opan self._colors[7] = "#ADACB5" #silver metalic self._colors[8] = "#8C705F" #liver chestnut self._colors[9] = "#FA7921" #pumpkin self._colors[10] = "#566E3D" #dark olive green def inBoard(self,p,q): """ return True if (p,q) is valid coordinate """ return (q>= 0) and (q < self.size) and (p >= -(q//2)) and (p < (self.size - q//2)) def rotateRight(self,p,q): pp = -q qq = p+q return pp,qq def rotateLeft(self, p,q): pp = p+q qq = -p return pp, qq def saveImage(self, filename): """ draw actual board to png """ cellRadius = 60 cellWidth = int(cellRadius*(3**0.5)) cellHeight = 2*cellRadius width = cellWidth*self.size + cellRadius*3 height = cellHeight*self.size img = Image.new('RGB',(width,height),"white") draw = ImageDraw.Draw(img) lineColor = (50,50,50) for p in self.board: for q in self.board[p]: cx = cellRadius*(math.sqrt(3)*p + math.sqrt(3)/2*q) + cellRadius cy = cellRadius*(0*p + 3/2*q) + cellRadius pts = [] for a in [30,90,150,210,270,330]: nx = cx + cellRadius * math.cos(a*math.pi/180) ny = cy + cellRadius * math.sin(a*math.pi/180) pts.append(nx) pts.append(ny) color = "#ff00ff" #pink is for values out of range -1,..10 if self.board[p][q] in self._colors: color = self._colors[self.board[p][q]] draw.polygon(pts,fill=color) pts.append(pts[0]) pts.append(pts[1]) draw.line(pts,fill="black", width=1) draw.text([cx-3,cy-3], "{} {}".format(p,q), fill="black") img.save(filename) def loadBoard(self, filename): board = {} fread = open(filename, "rt") size = -1 for line in fread: p,q,value = list(map(int, line.strip().split())) size = max(size, q) if p not in board: board[p] = {} board[p][q] = value fread.close() self.board = board self.size = size+1 === Příklad === * Následující program vyplní první řadu hodnotou 1 board = Board(size) for p in board.board: if p.inBoard(p,0): p.board[p][0] = 1 board.saveImage("deska.png") * metoda 'inBoard(p,q)' vrací True, pokud souřadnice (p,q) odkazuje na správnou pozici uvnitř hrací desky * před zápisem do hrací desky, např. 'board[p][q] = 3' je vždy nutné zkontrolovat, zda-li je souřadnice (p,q) validní, tedy: #chci zapsat hodnotu 3 na souradnici p,q if board.inBoard(p,q): board.board[p][q] = 3 * vždy používejte 'inBoard(p,q)' před zápisem do hrací desky, předejdete tak situaci, že budete zapisovat mimo hrací desku * Vykreslení kamenů stones = loadStones("nejaky vstupni soubor") for si in range(len(stones)): board = Board(10) #velikost 10x10 for i in range(len(stones[si])): p,q = stones[si][i] #i-ta bunka si-teho kamene p += 10//3 #posuneme kamen doprosted hraci desky, q += 10//2 #abychom ho mohli cely vykreslit if board.inBoard(p,q): #kontrola, ze i-ta bunka kamene je v hraci desce board.board[p][q] = 1 #zapiseme board.saveImage("kamen-{}.png".format(si)) * Pokud chcete projít celou hrací desku, používejte vždy tuto konstrukci: for p in board.board: #vsechny sloupce v hraci deske for q in board.board[p]: #vsechny radky, ktere nalezi sloupci p print("Hodnota bunky ", p,q, " je ", board.board[p][q] ) * První for cyklus prochází validní 'p' souřadnice, druhý for cyklus prochází validní 'q' souřadnice na 'p'-tém sloupci === Doporučený postup === * Nejprve se seznamte se souřadnicovým systémem (p,q) pro hexagonální gridy * Udělejte si následující úkoly: * Vyplnit zadaný řádek hodnotou 1 * Vyplnit zadaný sloupec hodnotou 2 * Najděte buňky, které mají levého souseda mimo hrací desku. Obdobně, identifikujte buňky, které mají souseda vpravo/nahoře/dole mimo hrací desku * používejte saveImage() pro uložení do png * Práce s kameny: * vykreslete kamen na pozici (0,0) * napište metodu pro vycentrování kamene např. tak, aby jeho 'i'-tá část měla hodnotu (0,0) * vykreslete všechny možné pozice kamenu na hrací desce (uvažujte pouze translace). Pro tento úkol se hodí mít kamen vycentrovaný * Teprve až se důkladněte seznámíte s hrací deskou, začněte přemýšlet, jak vyřešit úlohu výplně hrací desky kameny