Ejemplo n.º 1
0
//检查该点是否无气,并处理
void Go::isDie(int x, int y) {
	//是否在棋盘上(防止path数组越界。。。)
	if (path[x][y] || ans == false) {
		//已经走过(path数组标记),或已经发现气,取消检测
		return;
	}

	//为空应停止本次检查
	if (go[x][y].isEmpty()) {
		ans = false;
	}
	else {
		Cell ce = go[x][y];
		path[x][y] = true;//标记走过,带颜色的点
		cpath.push_back(ce);//记录每个标记(空不可标记,会影响后续检查),检查完后清理标记
		if (go[x][y].isSameColor(checkcolor)) {//相同颜色,继续拓展
			die.push_back(ce);//这样应该是复制一个对象,非引用(后面的函数调用使用修改ce不影响die存放的信息) = =
			int check[4][2];
			prepareExt(x, y, check);
			for (int i = 0; i < 4; i++) {
				if (isOnBoard(check[i][0], check[i][1])) {
					isDie(check[i][0], check[i][1]);
				}
			}
		}
	}
}
Ejemplo n.º 2
0
/*
  Clears a group of stones from the board

  param player: color of the group
  param x, y: coordinates of a stone within the group
*/
void GoBoard::clearGroup( Player player, int x, int y ) 
{
  //clear the current node
  grid[x][y]->setPlayer( EMPTY );

  //array's for the orthogonal neighbors
  int xnew[4] = { x-1, x, x+1, x };
  int ynew[4] = { y, y-1, y, y+1 };

  //iterate through each neighbor
  for( int i = 0; i < 4; i++ )
    {

      //make sure we don't go outside the board
      if( isOnBoard( xnew[i],ynew[i] ))
	{
	  //make sure the stone is of the correct player
	  if( grid[xnew[i]][ynew[i]]->isPlayer( player ))
	    {
	      //clear the stone and any stones of the same player attached to it
	      clearGroup( player, xnew[i], ynew[i] );
	    }
	}
    }
}
Ejemplo n.º 3
0
void Go::isBlackEmpty(int x, int y) {

	if (flagEmpty[x][y]) {
		return;
	}

	if (go[x][y].isEmpty()) {

		flagEmpty[x][y] = true;
		tcountEmpty++;
		int check[4][2];
		prepareExt(x, y, check);

		for (int i = 0; i < 4; i++) {
			if (isOnBoard(check[i][0], check[i][1])) {
				isBlackEmpty(check[i][0], check[i][1]);
			}
		}

	}
	else {
		if (go[x][y].isSameColor(s_black)) {
			flagBlack = true;
		}
		else {
			flagWrite = true;
		}
	}

}
Ejemplo n.º 4
0
/*
  Determines if the position is available to have a stone placed in it

  param x,y: integer coordinates of the stone to place

  return bool: whether a stone can be placed there
*/
bool GoBoard::isValidMove( int x, int y)
{
  if( isOnBoard( x, y ) && isPlayer( EMPTY, x, y ) && !isKo( x, y ))
    {
      return true;
    }
  return false;
}
Ejemplo n.º 5
0
void GraphicalBoardFrame::prepare()
{
    drawBoard(m_board);
    drawMove(m_candidate);

    if (isOnBoard(m_arrowRoot))
        drawArrow(m_arrowRoot, m_arrowDirection);

    generateBoardPixmap(&m_pixmap);
    update();
}
Ejemplo n.º 6
0
/*
  core function: recursively traces through every stone that is
  orthogonally connected starting at stone x,y. as it traverses it counts
  and updates the attributes Size, Liberties, and Owner, and returns them
  wrapped in a Group structure

  param x,y: coordinates of a stone within a group
  param currGroup: a copy of a Group object that holds all attributes
  
  returns currGroup after traversing all stones in the group
*/
Group GoBoard::getGroup( int x, int y, Group currGroup )
{
  //coordinates of all neighbor stones
  int xnew[] = { x-1, x, x+1, x };
  int ynew[] = { y, y-1, y, y+1 };

  //set as counted to prevent double count
  grid[x][y]->setCounted();

  //iterate through every neighbor
  for( int i = 0; i < 4; i++ )
    {
      //verify we're on the board
      if( isOnBoard( xnew[i], ynew[i] ))
	{
	  //verify we have the same player
	  if( grid[xnew[i]][ynew[i]]->isPlayer(currGroup.player))
	    {
	      //check if already counted
	      if( !grid[xnew[i]][ynew[i]]->isCounted())
		{
		  currGroup = getGroup( xnew[i], ynew[i], currGroup );
		}
	    }
	  //if it is not the same player then we have to determine if ownership
	  //of the group has changed for empty territories
	  else if( currGroup.owner == EMPTY || 
		   grid[xnew[i]][ynew[i]]->isPlayer(currGroup.owner))
	    {
	      //if we have run into the same owner, or the owner hasn't been set
	      //then the piece we've run into is the owner
	      currGroup.owner = (Player)grid[xnew[i]][ynew[i]]->getPlayer();
	    }
	  else
	    {
	      //if we've run into something different than the owner
	      //then the territory is contested, once it's marked as DOMI
	      //it can't be scored for either player
	      currGroup.owner = (Player)DOMI;
	    }
	}
    }

  //increment the group size and find any liberties next to the current stone
  currGroup.size += 1;
  currGroup.liberties += countNeighbors( EMPTY, x, y );

  //debug for the attributes of each stone as it's traversed
  //std::cout<<"x: "<< x <<" y: "<< y << " lib: "<<countNeighbors( EMPTY, x, y )<<std::endl;
  
  
  return currGroup;
}
Ejemplo n.º 7
0
/*
  Counts the all neighbors of a specific player around the
  provided stone.

  param target: the player type we are counting
  param x and y: the grid coordinates of the center stone

  return count: the number of neighbors of target color
*/
int GoBoard::countNeighbors( Player target, int x, int y )
{
  int count = 0;
  int xnew[4] = { x-1, x, x+1, x };
  int ynew[4] = { y, y-1, y, y+1 };

  for( int i = 0; i<4; i++ )
    {
      if( isOnBoard( xnew[i], ynew[i] ))
	{
	  if( grid[xnew[i]][ynew[i]]->isPlayer(target))
	    {
	      count += 1;
	    }
	}
    }
  return count;
}
Ejemplo n.º 8
0
//下棋后检查吃子
void Go::checkStarus(int x, int y) {

	int check[4][2];
	prepareExt(x, y, check);
	//此处4个检查应该互不影响
	for (int i = 0; i < 4; i++) {
		ans = true;//发现empty标记false;
		die.clear();//先删除上次状态
					//不在棋盘时,检查无意义
		if (isOnBoard(check[i][0], check[i][1])) {
			isDie(check[i][0], check[i][1]);
			chearPath();
			//检查玩之后,清理path待下次检查,返回die,ans标记是否有效
			if (ans) {
				for (Cell ce : die) {
					tp.push_back(ce);
				}
			}
		}

	}


}
Ejemplo n.º 9
0
int processPlace(PlayerState* ps, Command* cmd)
{
  if ( (ps == NULL) || (cmd == NULL) )
  {
    return (-1);
  }

  //check and convert the row and column fields
  if ( !isGoodRow(cmd->row) || !isGoodCol(cmd->col) )
  {
    return (-1);
  }

  //yes more non-switch statements
  if (cmd->shipNum == SHIP2)
  {
    //if its outside of the board area then error
    if (!isOnBoard(cmd->row, cmd->col, cmd->dir, 2) || (ps->ship2Life > 0))
    {
      return (-1);
    }

    //if the slots are already taken -- error 
    if (cmd->dir == DIR_R)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] = cmd->shipNum;
      //We can totally go overboard and assign this to be cmd->shipNum as well
      ps->ship2Life = 2;
    }
    else if (cmd->dir == DIR_D)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->ship2Life = 2;
    }
    else
    {
      //should never be here in this else
      return (-1);
    }
  }
  else if (cmd->shipNum == SHIP3)
  {
    if (!isOnBoard(cmd->row, cmd->col, cmd->dir, 3) || (ps->ship3Life > 0))
    {
      return (-1);
    }
      
    //if the slots are already taken -- error 
    if (cmd->dir == DIR_R)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] = cmd->shipNum;
      ps->ship3Life = 3;
    }
    else if (cmd->dir == DIR_D)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->ship3Life = 3;
    }
    else
    {
      //should never be here in this else
      return (-1);
    }
  }
  else if (cmd->shipNum == SHIP4)
  {
    if (!isOnBoard(cmd->row, cmd->col, cmd->dir, 4) || (ps->ship4Life > 0))
    {
      return (-1);
    }
 
    //if the slots are already taken -- error 
    if (cmd->dir == DIR_R)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 3] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 3] = cmd->shipNum;
      ps->ship4Life = 4;
    }
    else if (cmd->dir == DIR_D)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row) + 3][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 3][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->ship4Life = 4;
    }
    else
    {
      //should never be here in this else
      return (-1);
    }
 
  }
  else if (cmd->shipNum == SHIP5)
  {
    if (!isOnBoard(cmd->row, cmd->col, cmd->dir, 5) || (ps->ship5Life > 0))
    {
      return (-1);
    }

    //if the slots are already taken -- error 
    if (cmd->dir == DIR_R)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 3] != EMPTY_CELL)
           || ( ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 4] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 1] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 2] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 3] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col) + 4] = cmd->shipNum;
      ps->ship5Life = 5;
    }
    else if (cmd->dir == DIR_D)
    {
      if ( (ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] != EMPTY_CELL) 
          || ( ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
          || ( ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
          || ( ps->board[ROW_TO_NUM(cmd->row) + 3][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
          || ( ps->board[ROW_TO_NUM(cmd->row) + 4][COL_TO_NUM(cmd->col)] != EMPTY_CELL)
         )
      {
        return (-1);
      }

      ps->board[ROW_TO_NUM(cmd->row)][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 1][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 2][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 3][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->board[ROW_TO_NUM(cmd->row) + 4][COL_TO_NUM(cmd->col)] = cmd->shipNum;
      ps->ship5Life = 5;
    }
    else
    {
      //should never be here in this else
      return (-1);
    }
  }  
  else
  {
    return (-1);
  }

  return (0);
}
Ejemplo n.º 10
0
/*
  Probes adjecent stones and determines if any neighbor groups
  no longer have liberties

  param enemy: the color of the opponents stones
  param x, y: coordinates of the newly placed stone

  return Bool, KOflag, returns true if a state of KO may be possible
  this flag, combined with the flag in the main step function, determines
  if there is in fact a KO state.
*/
bool GoBoard::effectEnemies( Player enemy, int x, int y ) 
{
 
  
  //flags to see if a state of KO may be possible
  int killSize = 0; //size of last gorup killed, used to test for KO, and scoring
  int numKilled = 0; //groups killed, used to test for KO

  //create a group to be reused to test all enemies
  Group enemyGroup;
  enemyGroup.player = enemy;
  enemyGroup.owner = (Player)EMPTY;
  enemyGroup.size = 0;
  enemyGroup.liberties = 0;
  

  //array's to represent orthogonal neighbors
  int xnew[4] = { x-1, x, x+1, x };
  int ynew[4] = { y, y-1, y, y+1 };

  //iterate through each neighbor
  for( int i = 0; i < 4; i++ )
    {
      //make sure we don't go outside the board
      if( isOnBoard( xnew[i], ynew[i] ))
	{
	  
	  //see if the stone is an enemy and has NOT been counted yet
	  if( grid[xnew[i]][ynew[i]]->isPlayer( enemy ) && 
	      !grid[xnew[i]][ynew[i]]->isCounted() )
	    {
	      enemyGroup.size = 0;
	      enemyGroup.liberties = 0;
	      enemyGroup = getGroup( xnew[i], ynew[i], enemyGroup );
	      if( enemyGroup.liberties == 0 ) 
		{
		  //set the killSize when a group is killed
		  killSize = enemyGroup.size;

		  numKilled += 1;
		  
		  //sets the KO position temporarily
		  //will be cleared in the step function if
		  //a ko is not present
		  koPosX = xnew[i];
		  koPosY = ynew[i];
		  
		  switch( enemyGroup.player )
		    {
		    case(WHITE):
		      {
			blackPrisoners += killSize;
			break;
		      }
		    case(BLACK):
		      {
			whitePrisoners += killSize;
			break;
		      }
		    default:
		      {
			break;
		      }
		    }
		  clearGroup( enemy, xnew[i], ynew[i] );
		}
	    }
	}
    }

  //if a single group of size 1 is killed then we need to return that a
  //KO may be possible depending on the size of the player group
  if( numKilled == 1 && killSize == 1)
    {
      return true;
    }
  else
    {
      return false;
    }
}
Ejemplo n.º 11
0
void GraphicalBoardFrame::tileClicked(const QSize &tileLocation, const QMouseEvent * /* event */)
{
    Quackle::Board::TileInformation info(m_board.tileInformation(tileLocation.height(), tileLocation.width()));
    if (info.tileType == Quackle::Board::LetterTile)
        return;

    if (m_arrowRoot == tileLocation)
    {
        ++m_arrowDirection;
        if (m_arrowDirection == ArrowWorm)
            m_arrowDirection = s_firstArrowDirection;
    }
    else
        m_arrowDirection = s_firstArrowDirection;

    m_arrowRoot = tileLocation;

    Quackle::Move originalMove;
    Quackle::LetterString hoppedTiles;

    QSize currentTile(tileLocation);
    while (true)
    {
        const QSize previousTile = currentTile - arrowVector();

        bool stopHere;

        if (!isOnBoard(previousTile))
            stopHere = true;
        else
        {
            Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));

            stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
        }

        if (stopHere)
        {
            const bool horizontal = m_arrowDirection == ArrowRight;
            originalMove = Quackle::Move::createPlaceMove(currentTile.height(), currentTile.width(), horizontal, hoppedTiles);
            break;
        }

        hoppedTiles += QUACKLE_PLAYED_THRU_MARK;
        currentTile = previousTile;
    }

    prettifyAndSetLocalCandidate(originalMove);

    // TODO work some cleverness so we can do this!
    //emit setCandidateMove(Quackle::Move::createNonmove());

    for (QSize currentTile(0, 0); currentTile.height() < m_boardSize.height(); currentTile.setHeight(currentTile.height() + 1))
        for (currentTile.setWidth(0); currentTile.width() < m_boardSize.width(); currentTile.setWidth(currentTile.width() + 1))
            if (currentTile != tileLocation)
            {
                TileWidget *tile = tileAt(currentTile);
                if (!tile)
                    continue;

                tile->setArrowDirection(NoArrow);
            }

    prepare();
}
Ejemplo n.º 12
0
void GraphicalBoardFrame::appendHandler(const QString &text, bool shiftPressed)
{
    if (!isOnBoard(m_arrowRoot))
        return;

    if (!hasCandidate())
        return;

    Quackle::LetterString appendedLetterString(QuackleIO::Util::encode(text));

    if (appendedLetterString.length() == 0 || Quackle::String::front(appendedLetterString) < QUACKLE_FIRST_LETTER)
        return;
    
    if (shiftPressed)
        appendedLetterString = Quackle::String::setBlankness(appendedLetterString);
    else
        appendedLetterString = Quackle::String::clearBlankness(appendedLetterString);

    Quackle::Move newCandidate(m_candidate);
    Quackle::LetterString newTiles(m_candidate.tiles() + appendedLetterString);
    if (!m_ignoreRack && !m_rack.contains(Quackle::String::usedTiles(newTiles)))
    {
        Quackle::LetterString blankedNewTiles(m_candidate.tiles() + Quackle::String::setBlankness(appendedLetterString));

        if (m_rack.contains(Quackle::String::usedTiles(blankedNewTiles)))
        {
            newTiles = blankedNewTiles;
        }
    }

    newCandidate.setTiles(newTiles);

    Quackle::LetterString hoppedTiles;

    QSize currentTile(m_arrowRoot);
    while (true)
    {
        const QSize nextTile = currentTile + arrowVector();

        bool stopHere = false;
        bool resetArrowAfter = false;

        if (!isOnBoard(nextTile))
        {
            stopHere = true;
        }
        else
        {
            Quackle::Board::TileInformation nextTileInformation(m_board.tileInformation(nextTile.height(), nextTile.width()));

            if (nextTileInformation.tileType != Quackle::Board::LetterTile)
                stopHere = true;
        }

        if (stopHere)
        {
            newCandidate.setTiles(newCandidate.tiles() + hoppedTiles);

            if (resetArrowAfter)
                resetArrow();
            else
                m_arrowRoot = nextTile;

            break;
        }

        hoppedTiles += QUACKLE_PLAYED_THRU_MARK;
        currentTile = nextTile;
    }

    prettifyAndSetLocalCandidate(newCandidate);
}
Ejemplo n.º 13
0
void GraphicalBoardFrame::backspaceHandler()
{
    unsigned int hoppedTiles = 0;
    QSize currentTile(m_arrowRoot);
    while (true)
    {
        const QSize previousTile = currentTile - arrowVector();

        bool stopHere;

        if (!isOnBoard(previousTile))
            stopHere = true;
        else
        {
            Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));

            stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
        }

        ++hoppedTiles;

        if (stopHere)
        {
            m_arrowRoot = previousTile;
            break;
        }

        currentTile = previousTile;
    }

    Quackle::LetterString tiles(m_candidate.tiles());
    
    if (hoppedTiles > tiles.length())
        tiles = Quackle::LetterString();
    else
        tiles = Quackle::String::left(m_candidate.tiles(), m_candidate.tiles().length() - hoppedTiles);
    
    if (tiles.empty())
    {
        Quackle::Move originalMove;
        Quackle::LetterString hoppedLetters;

        currentTile = m_arrowRoot;
        while (true)
        {
            const QSize previousTile = currentTile - arrowVector();
    
            bool stopHere;

            if (!isOnBoard(previousTile))
                stopHere = true;
            else
            {
                Quackle::Board::TileInformation previousTileInformation(m_board.tileInformation(previousTile.height(), previousTile.width()));

                stopHere = previousTileInformation.tileType != Quackle::Board::LetterTile;
            }

            if (stopHere)
            {
                const bool horizontal = m_arrowDirection == ArrowRight;
                m_candidate = Quackle::Move::createPlaceMove(currentTile.height(), currentTile.width(), horizontal, hoppedLetters);
                break;
            }

            hoppedLetters += QUACKLE_PLAYED_THRU_MARK;
            currentTile = previousTile;
        }
    }
    else
    {
        m_candidate.setTiles(tiles);
    }

    ensureCandidatePlacedProperly();
    prettifyAndSetLocalCandidate(m_candidate);
}