===== 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