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:

  • 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

 
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
courses/b3b33alp/cviceni/faq/hexagrid.txt · Last modified: 2023/12/13 14:24 by petrapa6