Semestrální práce spočívá ve vytvoření funkčního hráče (programu) pro Gemblo . Gemblo je desková hra pro 2-6 hráčů, která se hraje na hexagonálním gridu s kameny, které jsou podobné tetrisovým kostičkám. Začíná se z prázdné hrací desky, hráči postupně umisťují svoje kameny. Vyhrává hráč, který svojí barvou obsadí nejvíce buněk. Obtížnost hry spočívá v pravidlech pro umisťování kamenů: kameny stejné barvy musí mít mezi sebou minimální vzdálenost právě jedné hrany, kameny různých barev se mohou dotýkat.
Balíček obsahuje základní třídy a programy pro vyzkoušení Gemblo na vašem počítači:
V případě, že nastanou změny v zadání nebo bude např. upraveno/rozšířeno herní rozhraní, budou změny uvedeny v této sekci.
self.stones
).
self.board
není důležité, přístup do protihráčova self.board
ani nemáte.
move()
. Brute po každém tahu přepíše váš self.board
tak, aby oba hráči měli stejnou hrací desku.
self.player
, protihráč je pak -self.player
self.player
-self.player
self.board
(dictionary)
self.board[p][q]
self.stones
self.player
a/nebo -self.player
p,q = self.getStartCoordinates(self.player)
p,q=self.getStartCoordinates(self.player)
[ [p1,q1], … , [pn,qn] ]
board
: hrací deska, proměnná je typu dictionary
self.board[p][q]
: buňka na souřadnici (p,q)
self.algorithmName
: jméno vašeho algoritmu (pro turnaj), vyplňuje se ve tříde Player
self.size
: velikost hrací desky
self.stones
: moje hrací kameny
inBoard(p,q)
: vrací True, pokud buňka (p,q) leží uvnitř hrací desky
getScore(player)
: vrací počet kamenů obsazených daným hráčem
self.getScore(self.player)
- počet buněk s mými kameny
self.getScore(-self.player)
- počet buněk obsazených protihráčem
distance(p1,q1,p2,q2)
: vrací celočíselnou vzdálenost mezi dvěma buňkami (p1,q1) a (p2,q2)
saveImage(pngFile)
: uloží obraz hrací desky do PNG.
getStartCoordinates(player)
: vrátí startovní souřadnice hráče
p,q = getStartCoordinates(self.player)
- moje startovní souřadnice
rotateRight(p,q)
: realizuje jednu rotace souřadnice (p,q) doprava o 60 stupňů (rotace kolem bodu (0,0) )
rotateLeft(p,q)
: realizuje jednu rotace souřadnice (p,q) doleva o 60 stupňů (rotace kolem bodu (0,0) )
Soubor base.py
import copy import math from PIL import Image, ImageDraw """ This is the base class for Gemblo game. Note that this base.py is different from base.py for HW08. Use only this file for Gemblo. DO NOT MODIFY THIS FILE !!!! Brute ALWAYS replaces base.py by it's own version to ensure that both players use the same base.py For python experts: If you need to extend the Board class, make your own class (in player.py): class Board2(base.Board): def __init__(self, plater, size, stones): base.Board.__init__(self, player, size, stones) def yourfunctions(): pass and update the Player class like this: class Player(Board2): def __init__(self, player, size, stones): Board2.__init__(self, player, size ,stones) self.usedStone = [False]*len(self.stones) #all stones are free to use now """ 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; def updatePlayers(board, stones, value): """ fill the board by the stones with given value board: object of base.Board stones: [ [p1,q1], .... [pn,qn] ] - list of absolut coordinates of cells in the board value: color/value to be written to the board """ try: for i in range(len(stones)): p,q = stones[i] board.board[p][q] = value #we write directly without checking if (p,q) is in the board #we assume that player/brute handle it except: print("Error when writing stones to the board. The player.move() returned invalid stones") return False return True class Board: def __init__(self, player, size, stones): self.size = size self.board = {} self.stones = copy.deepcopy(stones) self.player = player #1 or -1 self.algorithmName = "name of your method"; self._playerName = "default" #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 getStartCoordinates(self, player): if player == 1: return 0,0 else: return (self.size)//2, self.size-1 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. Empty cells are white, -1 = red, 1 = green, other values according to this list -1 red, 0 = white, 1 = green """ cellRadius = 25 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", anchor="m") img.save(filename) def a2c(self,p,q): x = p z = q y = -x -z return x,y,z def c2a(self, x,y,z): p = x q = z return p,q def distance(self,p1,q1,p2,q2): """ return distance between two cells (p1,q1) and (p2,q2) """ x1,y1,z1 = self.a2c(p1,q1) x2,y2,z2 = self.a2c(p2,q2) dist = ( abs(x1-x2) + abs(y1-y2) + abs(z1-z2) ) // 2 return dist def getScore(self, whichPlayer): """ return number of cells for given player """ count = 0 for p in self.board: for q in self.board[p]: if self.board[p][q] == whichPlayer: count += 1 return count def move(self): """ this method will be called by Brute. Return one of these: None -> if you CANNOT place any stone [ [p1,q1], ... [pn,qn] ] list of absolut (p,q) coordinates where you want to place a stone. For example, if you want to place one-cell stone to position p=1, q=-2: return [ [1,-2 ] ] If you want to place '3-cell-I-stone' at (1,1), (2,1) and (3,1): return [ [1,1] , [2,1], [3,1] ] If your start position is not filled, you have to start there!! startp, startq = self.getStartCoordinates(self.player) if self.board[startp][startq] == 0 -> you have to place first stone there!! """ return None
move()
:
move()
vrací absolutní souřadnice kamene, který chce hráč položit na hrací desku ve formátu [ [p1,q1], … [pn,qn] ]
nebo []
pokud nelze žádný kamen umístit
return [ [0,0],[1,0] ]
move()
, provede Brute aktualizaci hracích desek obou hráčů (ukázka v game.py
)
timeout
v sekci Pravidla hry)
self.algorithmName
- uložte si tam jméno svého algoritmu (používá se v turnajovém módu)
self.algorithmName = “muj skvely program cislo 1”
Soubor player.py
import base class Player(base.Board): def __init__(self, player, size, stones): base.Board.__init__(self, player, size, stones) self.usedStone = [False]*len(self.stones) #all stones are free to use now self.algorithmName = "Gemblo master!" def move(self): """ return list of absolut values to board where you want to place a stone or [] if no stone can be placed """ startp, startq = self.getStartCoordinates(self.player) if self.board[startp][startq] == 0: #place some stone so the coordinates (startp, startq) is occupied #example: return [ [startp, startq] ] else: #place some stone anywhere according to rules #example: return [] if __name__ == "__main__": print("player")
python3 game.py
Soubor game.py
import base import player #student's player """ This program is a simplified version of the Gemblo game between two players. The game is simplified by assumptions: a) all players return valid stones that point to board b) no error occurs when calling .move c) rules of the games are NOT checked here The points a-c are however checked by Brute. """ size = 11 stones = base.loadStones("stones.txt") p1 = player.Player(1, size, stones) #player no. 1 p2 = player.Player(-1, size, stones) #player no -1 iteration = 0 while True: move1 = p1.move() #move1 should be a list of stones or [] if no stone can be placed base.updatePlayers(p2, move1, p1.player) #write stones to board of both players base.updatePlayers(p1, move1, p1.player) p1.saveImage("{}-a.png".format(iteration)) move2 = p2.move() base.updatePlayers(p1, move2, p2.player) #write stones to board of both players base.updatePlayers(p2, move2, p2.player) p2.saveImage("{}-b.png".format(iteration)) iteration+=1 if len(move1) == 0 and len(move2) == 0: print("End of game, both players return [] ") break