Exemplo n.º 1
0
    def __init__(self, thread_id, connection):
        super().__init__()

        self._connection = connection
        self._connected = True
        self.connection_open = EventHook()
        self.connection_closed = EventHook()
        self.end_character = bytes(chr(93), 'UTF-8')
        self.message_received = EventHook()
        self.start_character = bytes(chr(91), 'UTF-8')
        self.thread_id = thread_id
        self._filter_tree = None
Exemplo n.º 2
0
    def __init__(self, player1, board1, player2, board2):
        self._currentPlayerBoard = (player1, board1)
        self._otherPlayerBoard = (player2, board2)

        #wall, attack, link and fusion counting fields
        self.numWall = 0
        self.numAttack = 0
        self.numLink = 0
        self.numFusion = 0
        
        #event emitters
        self.switchTurn = EventHook()
        
        #event handlers
        board1.wallMade.addHandler(self._wallMade)
        board2.wallMade.addHandler(self._wallMade)
        board1.attackMade.addHandler(self._attackMade)
        board2.attackMade.addHandler(self._attackMade)
        board1.fusionMade.addHandler(self._fusionMade)
        board2.fusionMade.addHandler(self._fusionMade)
        player1.justDied.addHandler(self._playerJustDied)
        player2.justDied.addHandler(self._playerJustDied)
        
        # When board1 decides it's time to attack, it will call
        # damageCalculate on board2. Then board2's attackReceived event
        # will trigger with the details of the attack; we listen to it
        # to see if the player took damage.
        board1.attackNow.addHandler(board2.damageCalculate)
        board2.attackReceived.addHandler(self._attackReceived)

        # And the same for the other board.
        board2.attackNow.addHandler(board1.damageCalculate)
        board1.attackReceived.addHandler(self._attackReceived)
Exemplo n.º 3
0
 def __init__(self):
     # event broadcasters - registered listeners persist after game reset
     #  so they are initialized in __init__ rather than init
     self.onChoiceChange = EventHook()  # called when choiceList changes
     self.onGameModeChange = EventHook()  # called when gameMode changes
     self.onAddLangNode = EventHook(
     )  # called when addLangNode is called
     self.onCharacterSwitch = EventHook(
     )  # called when activeProtagonistInd changes
     self.onEnterArea = EventHook()  # called when enterArea is called
     self.onEnterRoom = EventHook()  # called when enterRoom is called
     self.onClearBuffer = EventHook(
     )  # called when clearBuffer is called
     self.onSettingChange = EventHook()  # called when a setting changes
     self.onInventoryChange = EventHook(
     )  # called when inventory changes
     self.init()
Exemplo n.º 4
0
    def __init__(self, height, width):

        # The grid is an array of pointers to pieces. The same
        # piece may be pointed to from multiple squares (for big pieces).
        # If an entry is None, there is nothing in that square.
        # Orientation: row 0 = middle of board, height = the hero's belly
        # (0,0) = bottom left, going up. (view from second player's).
        self.grid = np.ndarray((height, width), dtype=object)

        # A set of pieces on the board.
        self.units = set()

        # Dimensions (in logical squares) of the board.
        self.height = height
        self.width = width

        #Set of attack formations
        self.currentAttacks = set()

        # Event handlers that will be triggered each time a piece is
        # changed (added, removed, or moved). The callbacks should take
        # one unnamed argument, which is a set of pieces to which they apply.
        # Pieces that have been removed will have position set to None.
        self.pieceUpdated = EventHook()

        # The set of pieces that have been updated since the last time
        # the pieceUpdated handler was triggered.
        self._updatedPieces = set()

        # Keeps track of piece positions so that we can detect if they
        # have changed.
        self._piecePositions = {}
        
        # Event handler that will be triggered each time
        # when an attack formation is created
        self.attackMade = EventHook()
        
        # Event handler that will be triggered each time 
        # when a wall is created
        self.wallMade = EventHook()
        
        # Event handler that will be triggered when fusion is made
        # TODO. NOT IMPLEMENTED
        self.fusionMade = EventHook()
        
        # Event handler that will be triggered when units attack 
        self.attackNow = EventHook()
        
        # Event handler that will be triggered when an emeny unit attacks.
        # The argument to the event handler is a list of AttackSummary.
        self.attackReceived = EventHook()
        
        # Event handler that will be triggered when the turn begins, 
        #after all currentAttacks have been updated
        self.turnBegun = EventHook()
Exemplo n.º 5
0
    def __init__(self, description, unitFactory,
                 baseWeights=[], baseNames=[],
                 specialWeights=[], specialNames=[], specialRarity=[]):
        """
        Parameters:
        baseWeights: 3 non-negative numbers sum to 3
        baseNames: 3 names of base units, possibly with repeats. 
            manaFactor: dict. Whenever a player does a mana-generating action (eg: link), 
		the amount of mana they get for it will be multiplied 
		by the number specified
	    maxUnitTotal: max total number of units the player can call
	    manaFactor: factors to be used in calculations. Choices are 1, 1.2, 1.5, 2
		higher factor means mana fills up faster when making links/fusions/etc..
	    maxLife: maximum life
	    maxMoves: max number of moves per turn
	    wall: dictionary of wall properties. Include
		image
		toughness
		maxToughness
        """
        self.description = description
        self.name = description.get('name', '')
        self.maxLife = int(description['maxLife'])
        self.maxMoves = int(description['maxMoves'])
        self.maxMana = int(description['maxMana'])
        self.maxUnitTotal = int(description['maxUnitTotal'])
        self.race = description['race']
        self.wallDescription = description['wall']

        self.manaFactor = {}
        for key, value in description['manaFactor'].items():
            self.manaFactor[key] = int(value)
        
        #weight distribution of base units. Integers, sum to 3
        self.baseWeights = np.array(baseWeights)
        self.baseNames = baseNames
        self.baseColor = ['red', 'white', 'blue']

        #weight distribution of special unit type. Integers, sum to 10
        self.specialWeights = np.array(specialWeights)
        self.specialNames = specialNames
        #how rare a special unit is. 10 = common, 5 = uncommon, 3 = rare
        self.specialRarity = np.array(specialRarity)

        self.unitFactory = unitFactory

        #set effective params
        self._mana = 0
        self._life = self.maxLife
        self._usedMoves = 0
        self._calledUnit = 0
        self._calledFatties = 0
        # After losing special units, there is a temporary penalty to
        # its weight; effWeights keeps track of the possibly penalized value.
        self.effWeights = self.specialWeights.copy() #effective unit weights

        # Event emitters
        self.doneTurn = EventHook()
        self.justDied = EventHook()

        # Callbacks take a single argument that is the new value
        self.manaChanged = EventHook()
        self.lifeChanged = EventHook()
        self.moveChanged = EventHook()
        self.unitChanged = EventHook()
Exemplo n.º 6
0
class Player(object):
    """ Model class. Has player data: life, number of moves, special equip,
    champions available, mana,
    Has unit generating function. """

    def __init__(self, description, unitFactory,
                 baseWeights=[], baseNames=[],
                 specialWeights=[], specialNames=[], specialRarity=[]):
        """
        Parameters:
        baseWeights: 3 non-negative numbers sum to 3
        baseNames: 3 names of base units, possibly with repeats. 
            manaFactor: dict. Whenever a player does a mana-generating action (eg: link), 
		the amount of mana they get for it will be multiplied 
		by the number specified
	    maxUnitTotal: max total number of units the player can call
	    manaFactor: factors to be used in calculations. Choices are 1, 1.2, 1.5, 2
		higher factor means mana fills up faster when making links/fusions/etc..
	    maxLife: maximum life
	    maxMoves: max number of moves per turn
	    wall: dictionary of wall properties. Include
		image
		toughness
		maxToughness
        """
        self.description = description
        self.name = description.get('name', '')
        self.maxLife = int(description['maxLife'])
        self.maxMoves = int(description['maxMoves'])
        self.maxMana = int(description['maxMana'])
        self.maxUnitTotal = int(description['maxUnitTotal'])
        self.race = description['race']
        self.wallDescription = description['wall']

        self.manaFactor = {}
        for key, value in description['manaFactor'].items():
            self.manaFactor[key] = int(value)
        
        #weight distribution of base units. Integers, sum to 3
        self.baseWeights = np.array(baseWeights)
        self.baseNames = baseNames
        self.baseColor = ['red', 'white', 'blue']

        #weight distribution of special unit type. Integers, sum to 10
        self.specialWeights = np.array(specialWeights)
        self.specialNames = specialNames
        #how rare a special unit is. 10 = common, 5 = uncommon, 3 = rare
        self.specialRarity = np.array(specialRarity)

        self.unitFactory = unitFactory

        #set effective params
        self._mana = 0
        self._life = self.maxLife
        self._usedMoves = 0
        self._calledUnit = 0
        self._calledFatties = 0
        # After losing special units, there is a temporary penalty to
        # its weight; effWeights keeps track of the possibly penalized value.
        self.effWeights = self.specialWeights.copy() #effective unit weights

        # Event emitters
        self.doneTurn = EventHook()
        self.justDied = EventHook()

        # Callbacks take a single argument that is the new value
        self.manaChanged = EventHook()
        self.lifeChanged = EventHook()
        self.moveChanged = EventHook()
        self.unitChanged = EventHook()

    @property
    def mana(self):
        return self._mana

    @mana.setter
    def mana(self, m):
        m = min(m, self.maxMana)
        self._mana = m
        self.manaChanged.callHandlers(m)

    @property
    def usedMoves(self):
        return self._usedMoves
        
    @usedMoves.setter
    def usedMoves(self, u):
        self._usedMoves = u
        self.moveChanged.callHandlers(u)

    @property
    def life(self):
        return self._life

    @life.setter
    def life(self, l):
        delta = l - self._life
        self._life = l

        logging.debug('updated life to %d' % self._life)

        self.lifeChanged.callHandlers(l)
        if delta < 0: #loss of life contributes towards increase in mana
            self.mana = self.mana - delta
        if self._life <= 0: #if dead
            self.justDied.callHandlers()

    @property
    def calledUnit(self):
        return self._calledUnit
    
    @calledUnit.setter
    def calledUnit(self, u):
        self._calledUnit = u
        self.unitChanged.callHandlers(u)
	    
    def setGameManager(self, gameManager):
        self.gameManager = gameManager

    def getSpecialUnit(self):
        """ Return a special unit. Relative probability of specific types is
            innerproduct(effweight, specialRarity). The color is chosen
            proportionally to baseWeight
        """
        effOdds = self.specialRarity * self.effWeights
        weights = [np.random.uniform()*x for x in effOdds]
        unitName = self.specialNames[np.argmax(weights)]
        colorweights = [np.random.uniform()*x for x in self.baseWeights]
        unit = self.unitFactory.create(unitName, self.baseColor[np.argmax(colorweights)], player=self)
        return unit

    def getBaseUnit(self):
        """ Return a base unit. Relative probability of specific types is
        baseWeights
        """
        weights = [np.random.uniform()*x for x in self.baseWeights]
        i = np.argmax(weights)
        unitName = self.baseNames[i]
        unit = self.unitFactory.create(unitName, self.baseColor[i], player = self)
        return unit

    def getRandomUnit(self, enemyFatties):
        """ Return a random unit
        
            Probability of being special is 
            0.1*innerproduct(effWeights, specialRarity)/100*
            max((enemyFatties+1)/(self._calledFatties+1), 1)
            
        """
        specialOdds = self.specialRarity * self.effWeights
        fattyBoost = max((1. + enemyFatties)/(1 + self._calledFatties), 1.)
        if np.random.uniform() < 0.1*specialOdds/100*fattyBoost:
            unit = self.getSpecialUnit()
        else:
            unit = self.getBaseUnit()
        return unit
Exemplo n.º 7
0
class Board:
    """Represents one player's board.
    """

    def __init__(self, height, width):

        # The grid is an array of pointers to pieces. The same
        # piece may be pointed to from multiple squares (for big pieces).
        # If an entry is None, there is nothing in that square.
        # Orientation: row 0 = middle of board, height = the hero's belly
        # (0,0) = bottom left, going up. (view from second player's).
        self.grid = np.ndarray((height, width), dtype=object)

        # A set of pieces on the board.
        self.units = set()

        # Dimensions (in logical squares) of the board.
        self.height = height
        self.width = width

        #Set of attack formations
        self.currentAttacks = set()

        # Event handlers that will be triggered each time a piece is
        # changed (added, removed, or moved). The callbacks should take
        # one unnamed argument, which is a set of pieces to which they apply.
        # Pieces that have been removed will have position set to None.
        self.pieceUpdated = EventHook()

        # The set of pieces that have been updated since the last time
        # the pieceUpdated handler was triggered.
        self._updatedPieces = set()

        # Keeps track of piece positions so that we can detect if they
        # have changed.
        self._piecePositions = {}
        
        # Event handler that will be triggered each time
        # when an attack formation is created
        self.attackMade = EventHook()
        
        # Event handler that will be triggered each time 
        # when a wall is created
        self.wallMade = EventHook()
        
        # Event handler that will be triggered when fusion is made
        # TODO. NOT IMPLEMENTED
        self.fusionMade = EventHook()
        
        # Event handler that will be triggered when units attack 
        self.attackNow = EventHook()
        
        # Event handler that will be triggered when an emeny unit attacks.
        # The argument to the event handler is a list of AttackSummary.
        self.attackReceived = EventHook()
        
        # Event handler that will be triggered when the turn begins, 
        #after all currentAttacks have been updated
        self.turnBegun = EventHook()

    def __getitem__(self, item):
        '''Return the corresponding sub-table of grid.
        Throws an error if index out of bound '''
        i, j = item
        if isinstance(i, tuple) or isinstance(i, list):
            imax = max(i)
        else:
            imax = i
        if isinstance(j, tuple) or isinstance(j, list):
            jmax = max(j)
        else:
            jmax = j
        if(imax < self.grid.shape[0] and jmax < self.grid.shape[1]):
            return self.grid[i,j]
        else:
            return None

    def getPieces(self, rows, cols):
        """ Return a LIST of pieces on board in range [rows] x [cols]
        remove None and redundancies . Items scanned by column then by row, in the input order
        """
        units = list()
        for j in cols:
            for i in rows:
                   if self[i,j] is not None:
                       if self[i,j] not in units:
                           units.append(self[i,j])
        return units

    def __setitem__(self, item, unit):
        """ make grid at location item points to unit """
        i,j = item
        if isinstance(i, tuple) or isinstance(i, list):
            imax = max(i)
        else:
            imax = i
        if isinstance(j, tuple) or isinstance(j, list):
            jmax = max(j)
        else:
            jmax = j
        if imax >= self.grid.shape[0] or jmax >= self.grid.shape[1]:
            raise IndexError("Index larger than board size")
        self.grid[i,j] = unit

    @property
    def boardHeight(self):
        """Returns the first empty row in each column.

        The return value is a numpy array, with one entry per column."""
        heightA = np.zeros(self.grid.shape[1], dtype = 'int8')
        for j in range(self.grid.shape[1]):
            for i in reversed(range(self.grid.shape[0])):
                if self[i,j] != None:
                    heightA[j] = i+1
                    break
        return heightA

    def normalize(self):
        """Updates the board by sliding pieces by priority, find and update links,
        and iterate these 2 steps until no more updates required.
        """
        madeStuff = 1
        while(madeStuff > 0):
            madeStuff = 0
            self._shiftByPriority() #shift higher priority guys to front
            self._reportPieceUpdates()
            create = self._createFormations() #check and make new formations
            self._reportPieceUpdates()
            #if created new things, need to shift by priority again            
            madeStuff += create
            createWall = self._mergeWalls() #if created walls            
            madeStuff += createWall
            self._reportPieceUpdates()

    def _reportPieceUpdates(self):
        """Trigger pieceUpdated events.

       The pieces to be updated are everything that is currently
       in self._updatedPieces, together with the pieces that
       have changed their position since we were last called.
       """

        moved = set()
        for u in self.units:
            if u in self._piecePositions and self._piecePositions[u] != u.position:
                u.oldPosition = self._piecePositions[u]
                moved.add(u)

        self._updatedPieces.update(moved)

        if self._updatedPieces:
            # Pass a copy, so that it isn't cleared when we clear our version.
            self.pieceUpdated.callHandlers(self._updatedPieces.copy())

        self._updatedPieces.clear()

        # Record the current positions of all pieces, for use the next time
        # we get called.
        self._piecePositions.clear()
        for u in self.units:
            # Copy the list, in case the original changes.
            self._piecePositions[u] = list(u.position)

    def _mergeWalls(self):
        """Merges pairs of vertically adjacent walls.
        Does one merging per column, from bottom row to top

        ALSO: merge units that can be merged (such as fusing)

        Returns true if anything changed.
        """
        #cycle through units by column over (j), then over row (i)
        updated = False
        for j in range(self.grid.shape[1]):
            for i in range(self.boardHeight[j]):
                unit = self[i,j]
                if unit is not None:
                    unitTop = self[i+unit.size[0],j]
                    if unitTop and unit.canMerge(unitTop):
                        updated = True
                        self._deleteFromGrid(unitTop)
                        unit.merge(unitTop)
                        self._deletePiece(unitTop)
                        break
        return updated

    def _shiftByPriority(self):
        """Shift pieces by priority.

        Return true if anything changed.
        """
        #piece.slidePriority: integer. higher = should be more at the bottom.
        #sort by (row,col) in increasing order, then by priority
        #unitList = sorted(unitList, key = lambda piece: piece.slidePriority, reverse = True)
        updated = False
        nrow = self.grid.shape[0]
        ncol = self.grid.shape[1]
        #keep a fatty list
        fatty = set()
        #sort the pieces on the board one column at a time by priority
        for j in range(ncol):  #for each column
            #get the units in column j
            unitCol = self.getPieces(range(nrow), [j])
            if not unitCol: #column j has no unit
                continue 
            #sort by decreasing priority
            unitCol = sorted(unitCol, key = lambda piece: piece.slidePriority, reverse = True)
            #copy this over to the board
            self[range(nrow), j] = None
            i = 0
            for unit in unitCol:
                self[range(i, i+unit.size[0]), j] = unit
                # If in the correct reference column, check if the unit has moved
                if i != unit.position[0] and j == unit.position[1]:
                    updated = True
                    unit.position[0] = i
                if unit.size == (2, 2):
                    fatty.add(unit)
                #check the next height level
                i += unit.size[0]
        #check for fatty disalignment
        trynum = 0
        while self._doAlignFatty(fatty):
            trynum = trynum + 1
            logging.debug( 'Done fatty alignment trial number %s' % trynum)
            self.dumpPosition()
            if(trynum > 100):
                logging.error('shiftByPriority attempting to realign fatty over 100 times')
                raise IndexError("Attempted to realign fatty over 100 times")
        return updated

    def _unitIsHere(self, unit, pos):
        """Returns true if unit.position is pos, and
        board corresponding squares point to unit."""
        if unit.position != pos:
            return False
        for i in range(0, unit.size[0]):
            for j in range(0, unit.size[1]):
                if self[pos[0] + i, pos[1]+j] != unit:
                    return False
        return True
        
    def _doAlignFatty(self, fatList):
        """ correct guys in the fatList if unaligned
        these guys have to have size 2x2
        return True if did something """
        updated = False
        for unit in fatList:
            if not self._unitIsHere(unit, unit.position): #if not aligned
                logging.debug("Fatty named %s at position %s is not aligned." % (unit.name, str(unit.position)))
                #flag updated as True since we'll need another iteration
                updated = True
                #look up where the top corner is in the column
                j = unit.position[1]+1
                topCornerLoc = None
                for i in reversed(range(self.grid.shape[0])):
                    if self[i, j] == unit:
                        topCornerLoc = i
                        break           
                #if top corner is higher
                logging.debug("His current top corner is in row %s. Expected row is %s" % (topCornerLoc, unit.position[0]+1))
                if topCornerLoc > unit.position[0]+1:
                    #shift the leftside up as far as possible
                    delta = topCornerLoc - (unit.position[0]+1)
                    maxShift = 0      
                    for i in range(delta+1):
                        if(self._canShiftUp(j-1,unit.position[0]+1+i, topCornerLoc)):
                            maxShift = i
                    #shift up by maxshift
                    logging.debug("We need to shift his leftside up by %s. We can shift by %s" % (delta, maxShift))
                    if(maxShift > 0):
                        shifted = self._doShiftUp(j-1, unit.position[0], unit.position[0]+maxShift)
                        logging.debug("Successfully shifted: %s" % shifted)
                    if maxShift == 0 or not shifted: 
                        #if cannot shift the left side up, then shift the rightside down
                        logging.debug("Cannot shift the left side up. Shifting the right side down")
                        self._doShiftDown(j, topCornerLoc-1,unit.position[0], 2)
                #if top corner is lower
                if topCornerLoc < unit.position[0] +1:
                    #shift the rightside up as far as possible
                    delta = unit.position[0]+1 - topCornerLoc
                    maxShift = 0
                    for i in range(delta+1):
                        if(self._canShiftUp(j, topCornerLoc-1, topCornerLoc-1+maxShift)):
                            maxShift = i
                    #shift up by maxshift
                    logging.debug("We need to shift his rightside up by %s. We can shift by %s" % (delta, maxShift))
                    if(maxShift > 0):                    
                        shifted = self._doShiftUp(j, topCornerLoc-1, topCornerLoc-1+maxShift)
                        logging.debug("Successfully shifted: %s" % shifted)
                    if maxShift == 0 or not shifted:
                        #then shift the leftside down
                        logging.debug("Cannot shift right side up. Shifting the left side down")
                        self._doShiftDown(j-1,unit.position[0], topCornerLoc+maxShift,2)
                if self._unitIsHere(unit, unit.position):
                    logging.debug('Successfully realigned fatty to position %s' % str(unit.position))
                else:
                    logging.debug('Alignment not successful for fatty at position %s' % str(unit.position))
        return updated

    def _canShiftUp(self, col, oldRow, newRow):
        """ return True if in column col, can shift item from position oldRow
        to newRow (by making None's and move the objects behind one up)
        """
        #check how many empty squares there are behind
        empty = 0
        for i in range(oldRow, self.grid.shape[0]):
            if self[i, col] is None:
                empty += 1
        if newRow - oldRow <= empty: #if shift up by an amount < empty:
            return True
        else:
            return False
    
    def _findBlockSize(self, col, oldRow):
        """Returns the size of the continuous block starting at (oldRow,col)"""
        size = 0
        for i in range(oldRow, self.grid.shape[0]):
            if self[i,col] is None:
                return size
            else:
                size += 1
        return size

    def _doShiftUp(self, col, oldRow, newRow):
        """ Shift an object at (oldRow, col) to (newRow, col),
        create None in the intermediate rows, 
        and shift all the guys behind him as well.
        Assuming that things behind him are in a continuous column.
        Shift into empty squares if possible.
        Return True if did some changes
        """
        shifted = False
        delta = newRow - oldRow        
        while(delta > 0):
            #find the size of the continuous block
            blockSize = self._findBlockSize(col, oldRow)
            if blockSize == 0:
                delta -=1
                oldRow += 1
            else:
                #shift the empty cell down. Equivalently, shift
                #the current block up by one.
                self._doShiftDown(col, oldRow + blockSize, oldRow, 1)
                delta -= 1
                oldRow += 1        
                shifted = True
        return shifted

    def _doShiftDown(self, col, oldRow, newRow, size):
        """ Shift an object of size size, from (oldRow, col) DOWN to (newRow, col).
        Requires newRow < oldRow
        For all objects which get displaced, put them in the same order in the
        vacant rows. Effectively: swap the blocks
        """
        if(oldRow >= self.grid.shape[0]):
            raise IndexError("try shifting blocks outside of the board down")
        #identify current top and bottom blocks
        topBlock = self[range(oldRow, oldRow+size), col]
        botBlock = self[range(newRow, oldRow), col]
        #copy bottom out
        bottemp = botBlock.copy()
        #shift top down
        self[range(newRow, newRow+size), col] = topBlock
        #update unit position if its base is in this column
        for i in reversed(range(newRow, newRow+size)):
            unit = self[i,col]
            if unit is None:
                continue
            if unit.position[1] == col:
                unit.position[0] = i
        #shift bottom block up
        self[range(newRow+size, oldRow+size), col] = bottemp
        #update unit position if its base is in this column
        for i in reversed(range(newRow+size, oldRow+size)):
            unit = self[i,col]
            if unit is None:
                continue
            if unit.position[1] == col:
                unit.position[0] = i

    def rowToAdd(self, piece, col):
        """Returns the row at which the piece will be added, if
        it is added to the given column.

        If the returned value plus the height of the piece is larger
        than self.height, then the piece cannot be added in the given
        column.
        """

        # If the piece belongs to the board, remove it from the
        # grid first, just in case it's already occupying the column
        # that we're adding it to.
        deleted = False
        if piece in self.units and self.grid[tuple(piece.position)] != None:
            deleted = True
            self._deleteFromGrid(piece)

        fat = piece.size[1]
        row = int(max(self.boardHeight[range(col, min(col+fat,self.width))]))       

        if deleted:
            self._addToGrid(piece)
        return row

    def canAddPiece(self, piece, col):
        """Checks whether the given piece fits in the given column."""

        tall = piece.size[0]
        fat = piece.size[1]

        if col + fat > self.grid.shape[1]:
            return False

        row = self.rowToAdd(piece, col)
        return row + tall <= self.grid.shape[0]

    def addPiece(self, piece, col):
        """Add a piece to the given column.

        Raise an IndexError if the piece doesn't fit at the
        given position.
        Note that this function does not normalize the board
        automatically.
        """

        if not self.canAddPiece(piece, col):
            logging.error("Trying to add piece outside of board")
            raise IndexError("Trying to add piece outside of board")
            
        row = self.rowToAdd(piece, col)
        self._appearPiece(piece, [row, col])

    def addPieceAtPosition(self, piece, row, col):
        """Add a piece at the given position.

        Raise an IndexError if the piece doesn't fit at the
        given position.
        Note that this function does not normalize the board
        automatically.
        """
        
        try:
            self._appearPiece(piece, [row, col])
        except ValueError:
            raise IndexError("Trying to appear piece in an invalid position.")

    def deletePiece(self, piece):
        """Delete a piece and then normalize."""
        self._deletePiece(piece)
        self.normalize()

    def movePiece(self, piece, toColumn):
        """Moves a piece to a new column, then normalizes.

        Raises an IndexError if the move was illegal."""

        if self.canAddPiece(piece, toColumn):
            self._deleteFromGrid(piece)
            self.addPiece(piece, toColumn)
            self.normalize()
        else:
            logging.error("Trying to add piece outside of board")

    def _deleteFromGrid(self, piece):
        """At the piece's current position, set the grid to None.

        Raises ValueError if the grid and the piece don't
        agree on where the piece is."""

        tall = piece.size[0]
        fat = piece.size[1]
        row = piece.position[0]
        col = piece.position[1]

        # Raise an error if the piece thinks it's in a position that
        # actually belongs to another piece.  (If the piece thinks it's
        # in a position that's actually empty, don't complain.)
        region = self.grid[row:(row+tall), col:(col+fat)].reshape(-1)
        if any([u != None and u != piece for u in region]):
            raise ValueError("Piece and board disagree on position", piece, piece.position)

        self.grid[row:(row+tall), col:(col+fat)] = None

    def _addToGrid(self, piece):
        """Add piece to the position supplied

        Raises ValueError if the position is already occupied."""

        tall = piece.size[0]
        fat = piece.size[1]
        row = piece.position[0]
        col = piece.position[1]

        # We want to check if there are any non-None elements in
        # the region. Unfortunately, any(region != None) doesn't
        # work because the '!= None' comparison doesn't check
        # elementwise.
        region = self.grid[row:(row+tall), col:(col+fat)].reshape(-1)
        occupied = map(lambda x: x != None, region)
        if any(occupied):
            raise ValueError("Position is already occupied")

        self.grid[row:(row+tall), col:(col+fat)] = piece

    def _deletePiece(self, piece):
        """Remove a piece from the board.

        Raise a ValueError if the piece is not on the board.
        Note that this function does not normalize the board
        automatically.
        """
        if piece not in self.units:
            raise ValueError("Tried to remove a non-existent piece")

        self.units.remove(piece)
        self._deleteFromGrid(piece)
        self._updatedPieces.add(piece)
        self.currentAttacks.discard(piece)
        piece.oldPosition = piece.position
        piece.position = None

    def _piecesInRegion(self, offset, regionSize):
        """The list of pieces in the rectangle of the given size
        at the given offset."""

        row = offset[0]
        col = offset[1]
        return self.getPieces(range(row,(row+regionSize[0])),
                              range(col,(col+regionSize[1])))

    def _regionEmpty(self, offset, regionSize):
        return bool(self._piecesInRegion(offset, regionSize))

    def _regionFull(self, offset, regionSize):
        """Checks whether the given region is full of pieces.

        Returns false if regionSize is 0 in either coordinate
        """
        if min(regionSize) == 0:
            return False

        row = offset[0]
        col = offset[1]
        return (row + regionSize[0] <= self.height and
                 col + regionSize[1] <= self.width and
                 None not in self.grid[row:(row+regionSize[0]),
                                       col:(col+regionSize[1])].flatten().tolist())
                # I'm not sure why flatten().tolist() is necessary, but it seems
                # like "None in array([None])" returns false, while None in [None]
                # returns true.

    def _chargers(self, piece):
        """The list of pieces in the given piece's charging region."""

        # Add piece.size[0] because the charge region starts from the square
        # behind the piece.
        chargePosition = (piece.position[0] + piece.size[0], piece.position[1])
        return self._piecesInRegion(chargePosition, piece.chargingRegion())

    def _chargeFull(self, piece):
        """Checks whether the piece's charging region is full."""

        chargePosition = (piece.position[0] + piece.size[0], piece.position[1])
        return self._regionFull(chargePosition, piece.chargingRegion())

    def _transformers(self, piece):
        """The list of pieces in the given piece's transform region."""

        # Add piece.size[1] because the charge region starts from the square
        # to the right of the piece.
        transformPosition = (piece.position[0], piece.position[1] + piece.size[1])
        return self._piecesInRegion(transformPosition, piece.transformingRegion())

    def _transformFull(self, piece):
        """Checks whether the piece's transform region is full. """            
        
        transformPosition = (piece.position[0], piece.position[1] + piece.size[1])
        return self._regionFull(transformPosition, piece.transformingRegion())

    def _createFormations(self):
        """Create walls and charging formations.

       Return true if any formations were created."""

        unitList = sorted(list(self.units), key = lambda piece: piece.position[0])
        # a set of units to charge
        chargingPieces = set()
        # a set of units used as chargers
        chargerPieces = set()
        # a set of pieces to be transformed (into walls)
        transformingPieces = set()
        # counter for number of walls formed
        wallCount = 0
        
        for unit in unitList:
            # Look at the pieces in the charging region.  If there are some,
            # and they are all good then the unit gets charged.
            chargers = self._chargers(unit)
            if self._chargeFull(unit) and all(unit.canCharge(x) for x in chargers):
                chargingPieces.add(unit)
                chargerPieces.update(set(chargers))

        #sort by increasing column (first key), and increasing row (second key)
        unitList = sorted(list(self.units), key = lambda piece: piece.position[0])
        unitList = sorted(unitList, key = lambda piece: piece.position[1])
        for unit in unitList:
            transformers = self._transformers(unit)                
            if self._transformFull(unit) and all(unit.canTransform(x) for x in transformers):
                if unit not in transformingPieces:                     
                    wallCount += 1
                #mark the unit and _all_of its transformers as transforming
                transformingPieces.add(unit)
                transformingPieces.update(transformers)

        # Create charging formations. Resolve multiChargeable conflicts here.
        # To avoid update order problems, we sort chargingPieces by increasing column (first key)
        # and increasing row (second key)
        chargingPieces = sorted(list(chargingPieces), key = lambda piece: piece.position[0])
        chargingPieces = sorted(chargingPieces, key = lambda piece: piece.position[1])
        chargedPieces = []
        
        # chargingChargers are pieces that are being charged and
        # being used to charge another piece.
        # transformingChargers are pieces that are transforming and
        # being used to charge another piece.
        transformingChargers = chargerPieces.intersection(transformingPieces)
        
        for unit in chargingPieces:
            # Ignore the unit if it has been removed from the board.
            # This could happen if it was used to charge something else.
            if unit in self.units:
                charged = unit.charge()
                chargedEndPosition = charged.position[0]+charged.size[0]
                chargers = set(self._chargers(unit))
                # Delete all chargers unless the unit that was charged is
                # multichargeable.
                # If also transforming, make a ghost piece at the end of
                # the charged formation. The ghost acts as a placeholder
                # for the wall that will eventually go there.
                for x in chargers:
                    if x in self.units:
                        if x in transformingChargers:
                            ghost = GhostPiece(x)
                            ghost.position[0] = chargedEndPosition
                            transformingPieces.add(ghost)
                            transformingChargers.add(ghost)
                            transformingPieces.remove(x)
                            self._deletePiece(x)
                        elif not unit._multiChargeable:                        
                            self._deletePiece(x)
                        else:
                            if x not in chargingPieces:
                                self._deletePiece(x)

                # If the unit is also transforming, create a ghost piece
                # at the end of the charging formation
                # and mark it as transforming
                if unit in transformingPieces:
                    ghost = GhostPiece(unit)
                    ghost.position[0] = chargedEndPosition
                    transformingPieces.add(ghost)
                    transformingChargers.add(ghost)
                    transformingPieces.remove(unit)

                # Replace the piece with its charged version 
                self._replacePiece(unit, charged)
                chargedPieces.append(charged)
        #only call handler if some charged pieces were made
        if len(chargedPieces) > 0:
            self.attackMade.callHandlers(set(chargedPieces))
        #update the set of currentAttacks. 
            self.currentAttacks.update(set(chargedPieces))
        
        # Create walls.
        transformedPieces = []
        for unit in transformingPieces:
            transformed = unit.transform()        

            #Vanilla case: unit is not involved in charging. 
            #Then, we will replace the unit with a wall at its current position
            if unit not in transformingChargers:
                self._replacePiece(unit, transformed)
                transformedPieces.append(transformed)

            # Case 2: Unit was used as a charger. By the time we get here,
            # it was replaced by a ghost whose current position
            # is just behind the charged formation. In this case, we try to
            # add a wall at the ghost's position while shifting everyone back
            # (while also ensuring that fatties fit).
            # If there is no room, we do not make the wall.
            if unit in transformingChargers:
                #check if we can shift items up
                oldRow = unit.position[0]
                newRow = unit.position[0]+1
                col = unit.position[1]
                if self._canInsertPiece(unit, unit.position):
                    #do the insertion. Leave fatty disalignment alone, it should be resolved by board normalize()
                    self._doShiftUp(col, oldRow, newRow)
                    self._appearPiece(transformed, unit.position)
                    transformedPieces.append(transformed)
        #only call handler if some wall was made. 
        if len(transformedPieces) > 0:
            self.wallMade.callHandlers([set(transformedPieces), wallCount])
                
        return bool(chargedPieces or transformedPieces)

    def _canInsertPiece(self, piece, position):
        """ Return True if piece 
        can be inserted to position (row, col) by shifting up pieces behind him
        And that fatty alignment is not perturbed. Return False otherwise.
        Used in the wall creation step of _createFormations.
        """
        pHeight = piece.size[0]
        pWidth = piece.size[1]
        row = position[0]
        #check that there is room to shift up.
        for col in range(position[1], position[1]+pWidth):
            if not self._canShiftUp(col, row, row+pHeight): #if there is no room
                return False
            else: #check if fatty is messed up
                pieceList = set(self._piecesInRegion(offset = (row, col), regionSize = (self.height-row, 1)))
                for x in pieceList:
                    if x.size == (2,2): #if the unit is a fatty
                        #find his corner position
                        fattyRow, fattyCol = x.position
                        #if cannot shift the Fatty
                        if not self._canShiftUp(fattyCol, fattyRow, fattyRow+pHeight): 
                            return False
                return True
                    
    
    def _existPiece(self, piece):
        """ Return True if piece is existing on board at its stored position
        
        Only check the head square. 
        """

        return self.grid[piece.position] is piece

    def _appearPiece(self, piece, pos):
        """Place a new piece in the given position."""
        self.units.add(piece)                
        piece.position = pos
        self._addToGrid(piece)
        self._updatedPieces.add(piece)

    def _replacePiece(self, old, new):
        """Replaces an old piece with a new one."""
        pos = old.position
        self._deletePiece(old)        
        self._appearPiece(new, pos)

    def colToAdd(self, piece):
        """ Return the column value if the piece can be added
        without creating formations/walls. 
        
        Return None if cannot be added anywhere.
        """
        #logging.debug(str([u.position for u in self.units]))
        # choose a random ordering of the columns
        columnList = list(np.random.permutation(self.width))
        for col in columnList:
            # Make a copy of the current board, then add a copy of the
            # piece in the current column.  If no formations are created,
            # then the column is ok.
            if self.canAddPiece(piece, col):
                boardCopy = self.ghostBoard()
                ghost = GhostPiece(piece)
                boardCopy.addPiece(ghost, col)
                if not boardCopy._createFormations():
                    return col
        return None

    def ghostBoard(self):
        """ Return a hard copy of board with ghost pieces """ 
        boardCopy = Board(self.height, self.width)
        for u in self.units:
            ghost = GhostPiece(u)
            boardCopy._appearPiece(ghost, u.position)
        return boardCopy
    
    def selfConsistent(self):
        """ Return true if board and units agree on their positions"""
        for u in self.units:
            row,col = u.position
            uheight, uwidth = u.size
            #check all squares that this unit should occupy
            for i in range(row, row + uheight):
                for j in range(col, col + uwidth):
                    if self[i,j] != u:
                        return False
        return True
    
    def beginTurn(self):
        """ This function is called by gameManager at the start of this board's turn.
            All charging units are updated.
            If there are units attacking this turn, the board emits the attackNow event, passing the set of attacking units.
        """
        attackGuys = set()
        for x in self.currentAttacks:
            x.update()
            if x.readyToAttack():
                    attackGuys.add(x)
        # Remove the attackGuys from the list of currentAttacks.
        self.currentAttacks.difference_update(attackGuys)
        
        self.turnBegun.callHandlers()
        
        if len(attackGuys) > 0: # Send off the attackGuys to eventHandlers.
            self.attackNow.callHandlers(set(attackGuys))
        # Now, we remove the attackGuys from the board.
        for x in attackGuys:
            self._deletePiece(x)
        self.normalize()

    def damageCalculate(self, attackEnemies):
        """ Handle damage calculations done on this board by attackEnemies """

        # TODO: this doesn't currently define the order of attacking units.
        summaries = []
        for enemy in attackEnemies:
            col = enemy.column
            # Find all the units in front of the enemy.
            defendUnits = self._piecesInRegion(offset = [0,col], regionSize = [self.height, enemy.width])
            defendUnits = sorted(list(defendUnits), key = lambda piece: piece.row)

            # TODO: if there are two units at the same height, and there
            # is not enough strength to kill both of them, then the damage
            # should be split.
            enemyDead = False
            summary = AttackSummary(enemy)
            summaries.append(summary)
            for defender in defendUnits:
                oldDefToughness = defender.toughness
                oldEneToughness = enemy.toughness
                defender.toughness, defenderDead = defender.damage(enemy.toughness)
                enemy.toughness, enemyDead = enemy.damage(oldDefToughness)
                damage = oldEneToughness - enemy.toughness

                summary.add(defender, damage, defenderDead)

                if defenderDead:
                    self._deletePiece(defender)
                if enemyDead:
                    break # No more attacking

            if not enemyDead:
                # Any leftover damage goes to the player.
                summary.add(None, enemy.toughness, False)
                
        self.attackReceived.callHandlers(summaries)
        self._reportPieceUpdates()
    
    def dumpPosition(self):
        ''''This prints out the board and the sizes of the units.
        Sorted by columns
        '''
        unitSorted = sorted(self.units, key = lambda unit: unit.position[1])
        print str([(u.position, str(u.size)) for u in unitSorted])
Exemplo n.º 8
0
class GameManager(object):
    """Runs the game.

    Keeps track of whose turn it is and how many moves they have left.
    Also manages the summoning of pieces.

    Events:
        switchTurn: triggered whenever a player ends their turn.
        
    Events handled:
        wallMade, chargeMade, and fusionMade from board:
            this information is used to make free moves and update mana.
    """

    def __init__(self, player1, board1, player2, board2):
        self._currentPlayerBoard = (player1, board1)
        self._otherPlayerBoard = (player2, board2)

        #wall, attack, link and fusion counting fields
        self.numWall = 0
        self.numAttack = 0
        self.numLink = 0
        self.numFusion = 0
        
        #event emitters
        self.switchTurn = EventHook()
        
        #event handlers
        board1.wallMade.addHandler(self._wallMade)
        board2.wallMade.addHandler(self._wallMade)
        board1.attackMade.addHandler(self._attackMade)
        board2.attackMade.addHandler(self._attackMade)
        board1.fusionMade.addHandler(self._fusionMade)
        board2.fusionMade.addHandler(self._fusionMade)
        player1.justDied.addHandler(self._playerJustDied)
        player2.justDied.addHandler(self._playerJustDied)
        
        # When board1 decides it's time to attack, it will call
        # damageCalculate on board2. Then board2's attackReceived event
        # will trigger with the details of the attack; we listen to it
        # to see if the player took damage.
        board1.attackNow.addHandler(board2.damageCalculate)
        board2.attackReceived.addHandler(self._attackReceived)

        # And the same for the other board.
        board2.attackNow.addHandler(board1.damageCalculate)
        board1.attackReceived.addHandler(self._attackReceived)

    @property
    def currentPlayer(self):
        return self._currentPlayerBoard[0]

    @property
    def currentBoard(self):
        return self._currentPlayerBoard[1]
    
    @property
    def otherPlayer(self):
        return self._otherPlayerBoard[0]
    
    @property
    def otherBoard(self):
        return self._otherPlayerBoard[1]
    
    def movePiece(self, piece, col):
        """Puts piece to column col.

        Also recalculates the number of turns that the current
        player has left, and switches players if necessary.
        Returns True if the move succeeded, and False if it was
        an illegal move.
        """
        if piece is None:
            return False
        if piece.position[1] == col: #dropping in the same position
            return False
        if self.currentBoard.canAddPiece(piece, col):
            self.currentBoard.movePiece(piece, col)
            self._updateMoves()
            return True
        else:
            return False

    def deletePiece(self, position):
        """Deletes the piece at the given position.

        Also recalculates the number of turns that the current
        player has left, and switches players if necessary.
        """
        if self.currentBoard[position] is not None:
            logging.debug("Deleting the piece %s at position %s"
                          % (self.currentBoard[position], position))

            self.currentBoard.deletePiece(self.currentBoard[position])
            self._updateMoves(offset = 0)
        else:
            logging.debug("Tried to delete an empty square %s" % position)

    def canPickUp(self, position):
        """Checks if the current player can pick up a given piece."""

        pass

    def endTurn(self):
        """Ends the active player's turn.

        If the player has moves left, increases that player's mana.
        Toggles the current player. 
        Triggers the switchTurn event.
        """
        moveLeft = self.currentPlayer.maxMoves - self.currentPlayer.usedMoves
        if moveLeft > 0:
            self._updateMana("move", moveLeft)
        self.currentPlayer.usedMoves = 0 #reset number of moves
        
        tmp = self._currentPlayerBoard
        self._currentPlayerBoard = self._otherPlayerBoard
        self._otherPlayerBoard = tmp

        self.switchTurn.callHandlers()
        #begin the new turn
        self.currentBoard.beginTurn()    

    def _wallMade(self, walls):
        """Count the number of walls made in board """
        self.numWall += walls[1]

    def _attackMade(self, newAttacks):
        """Count number of attacks and links made in board """
        self.numAttack += len(newAttacks)
        
        #count links: iterate over all charging formations, find friends
        oldAttacks = self.currentBoard.currentAttacks
        for unit in newAttacks:
            if self._countLinks(unit, oldAttacks.union(newAttacks)) > 0:
                self.numLink += 1
                
    def _updateMana(self, evt, num):
        """ Update currentPlayer's mana depend on the event triggered. 
        
        Event types: "link", "fuse", "move", "useMana" """

        if evt == "useMana": # We should only be here if the mana is full.
            self.currentPlayer.mana = 0
        else:
            logging.debug('updating mana for event "%s" x%s' % (evt, num))
            factor = 0
            try:
                factor = self.currentPlayer.manaFactor[evt]
            except KeyError:
                logging.error('player description missing manaFactor ' + evt)

            logging.debug('increment is %s' % (factor * num))
            self.currentPlayer.mana += factor * num
            logging.debug('new mana is %s' % self.currentPlayer.mana)
            
    def useMana(self):
        """ Use mana if allowed. Return True if can use, False otherwise """
        if self.currentPlayer.mana >= self.currentPlayer.maxMana:
            self._updateMana("useMana", 0)
            return True
        return False
    
    def _fusionMade(self, fusion):
        """Count number of fusions """
        self.numFusion += len(fusion)
    
    def _countLinks(self, unit, unitList):
        """Return the number of links for a unit.

        The number of links for a unit is the number of other charging units
        that have the same color and will attack on the same turn.  A unit
        isn't counted as a link for itself."""

        linkNum = 0
        unitList.discard(unit)
        for poozer in unitList:
            if (poozer.color == unit.color) and (poozer.turn == unit.turn):
                linkNum += 1
        return linkNum

    def _updateMoves(self, offset = 1):
        """ 
        Computes the new number of moves, including free moves earned.
        Update mana, number of units can call,
        reset parameters, and ends turn if no moves left.
        
        offset = 1 by default, 0 if came from deleting pieces
        """
        self.currentPlayer.usedMoves += 1
        if self.numWall > 0 or self.numAttack > 0:
            freeMoves = self.numWall + self.numAttack - offset
            self.currentPlayer.usedMoves -= freeMoves
            #update mana for moves
            self._updateMana("move", freeMoves)        
            #reset numWall and numAttack
            self.numWall = 0
            self.numAttack = 0

        self._updateMana("link", self.numLink)
        self.numLink = 0
        self._updateMana("fuse", self.numFusion)
        self.numFusion = 0
        
        self.currentPlayer.calledUnit = len(self.currentBoard.units)
        
        if self.currentPlayer.usedMoves == self.currentPlayer.maxMoves:
            self.endTurn()
    
    def _attackReceived(self, summaries):
        """Someone was just attacked (but not necessarily damaged).

        Check if the attack got through.
        """

        for summary in summaries:
            if summary.attacks:
                attack = summary.attacks[-1]
                logging.debug(attack.defender)

                # None is the sentinal value meaning the attack got through to the player.
                if attack.defender is None:
                    self.otherPlayer.life -= attack.damageDealt

    #TODO
    def _playerJustDied(self): 
        """Player just died event handler."""
        logging.debug("Player %s just died" % self.otherPlayer.name)
        pass
        
    
    def callPieces(self):
        """Current player wants to call some pieces.

        Generate units one at a time, and check that they can be fit on board.
        
        Chance of getting fatties is coupled with the opponent's number of 
        fatties ever generated
        
        Debugging mode: store the configuration of the board after generated.
        """

        pieceLeft = self.currentPlayer.maxUnitTotal - len(self.currentBoard.units)
        logging.debug('Calling %d pieces for player %s' % (pieceLeft, str(self.currentPlayer)))
        addedPieces = pieceLeft > 0
        retries = 10
        while pieceLeft > 0:
            unit = self.currentPlayer.getRandomUnit(self.otherPlayer._calledFatties)
            col = self.currentBoard.colToAdd(unit)
            if col is None:
                logging.warning('A piece with dimensions %d x %d did not fit on the board' % (unit.size[0], unit.size[1]))
                retries -= 1
                if(retries == 0):
                    logging.debug('Board is full while there are %d pieces left to call' % pieceLeft)
                    break
            else:
                self.currentBoard.addPiece(unit, col)
                pieceLeft = pieceLeft - 1
                if(unit.size == (2,2)):
                    self.currentPlayer._calledFatties += 1

        # Assuming colToAdd works correctly, normalizing the board
        # should not actually change anything.  However, we need to call
        # it to make sure listeners get notified of all the new pieces.
        self.currentBoard.normalize()

        if addedPieces:
            self._updateMoves()
Exemplo n.º 9
0
class SocketClient(threading.Thread):
    def __init__(self, thread_id, connection):
        super().__init__()

        self._connection = connection
        self._connected = True
        self.connection_open = EventHook()
        self.connection_closed = EventHook()
        self.end_character = bytes(chr(93), 'UTF-8')
        self.message_received = EventHook()
        self.start_character = bytes(chr(91), 'UTF-8')
        self.thread_id = thread_id
        self._filter_tree = None

    def destined_for_me(self, socket_message):
        # Using the filter set, determine if the message is destined for the client.
        visitor = filter_message_visitor(socket_message)
        return visitor.visit(self._filter_tree)

    def disconnect(self):
        # Close the connection and fire the connection closed event.
        self._connection.close()
        self.connection_closed.fire(self)

    def run(self):
        self.set_filter("1")

        # Fire the connection open event.
        self.connection_open.fire(self)

        # Try to read from the socket connection.
        try:
            read_buffer = bytearray()

            # Read from the connection until instructed not to.
            while self._connected:
                # Read a singular byte from the socket.
                socket_byte = self._connection.recv(1)

                # If nothing was read from the socket. The socket likely closed.
                if not socket_byte:
                    self.disconnect()
                    return

                if socket_byte == self.start_character:
                    # If the byte read is the start character. Clear the buffer.
                    read_buffer = bytearray()
                elif socket_byte == self.end_character:
                    # If the byte read is the end character. Send the message.
                    self.message_received.fire(self, read_buffer.decode('UTF-8'))
                    read_buffer = bytearray()
                else:
                    # Any other character simply append to the buffer.
                    read_buffer.extend(socket_byte)
        # If there is an OS error exit.
        except OSError:
            pass

    def set_filter(self, filter_message):
        # Set the filter for the client. Use the new filter to build a parse tree.
        input_string = InputStream(filter_message)
        lexer = filter_messageLexer(input_string)
        stream = CommonTokenStream(lexer)
        parser = filter_messageParser(stream)
        self._filter_tree = parser.prog()

    def send_message(self, message):
        # Translate the message to bytes and send the message.
        bytes_to_send = bytes(str(message), 'UTF-8')
        self._connection.send(bytes_to_send)