/// <summary> /// Construtor dos nodos filhos /// </summary> /// <param name="parent"></param> /// <param name="state"></param> public BoardNode(BoardNode parent, int[] _Tabuleiro, int _corDoJogador) { this.tabuleiro = _Tabuleiro; this.pai = parent; for (int j = 0; j < _Tabuleiro.Length; j++) if (_Tabuleiro[j] == CorJogadorAdversario(_corDoJogador)) numeroPecasJogadorAdverario++; else if (_Tabuleiro[j] == _corDoJogador) numeroPecasJogador++; //this.profundidade = parent.profundidade + 1; }
/// <summary> /// Construtor do nodo pai /// </summary> /// <param name="state"></param> public BoardNode(int[] _Tabuleiro, int _corDoJogador) { this.tabuleiro = _Tabuleiro; //this.profundidade = 0; this.stepCost = 0; this.pai = null; for (int j = 0; j < _Tabuleiro.Length; j++) if (_Tabuleiro[j] == CorJogadorAdversario(_corDoJogador)) numeroPecasJogadorAdverario++; else if (_Tabuleiro[j] == _corDoJogador) numeroPecasJogador++; }
/// <summary> /// Algoritmo que realiza a jogada do computador. /// </summary> /// <param name="_Tabuleiro">aka Posição. Situacao atual do tabuleiro do jogo</param> /// <param name="_Profundidade">Profundidade da recursão. Esta ligada com a dificuldade /// do jogo (quanto mais profundo, mais dificil)</param> /// <param name="_udtJogador">Jogador que deve realizar a jogada.</param> /// <returns></returns> private int Minimax(BoardNode _Nodo, int _Profundidade, Jogador _udtJogadorMax, Jogador _udtJogadorMin, bool _isMax) { int numeroPecasJogadorAdverario = -1; //if (_Profundidade == 0) //{ // List<BoardNode> lstSucessores = new List<BoardNode>(); // lstSucessores = GeraMovimentos(_Nodo, _udtJogadorMax.CorDoJogador); // for (int i = 0; i < lstSucessores.Count; i++) // { // lstSucessores[i] = Minimax(lstSucessores[i], _Profundidade + 1, _udtJogadorMax, _udtJogadorMin, false); // } // lstSucessores.Sort(Comparer<BoardNode>.Default); // return lstSucessores[lstSucessores.Count - 1]; //} if (_Profundidade == m_ProfundidadeTotal) { for (int i = 0; i < _Nodo.Tabuleiro.Length; i++) if (_Nodo.Pai.Tabuleiro[i] == CorJogadorAdversario(this.CorDoJogador)) numeroPecasJogadorAdverario++; return CalculaValorJogada2(_Nodo.Tabuleiro, this.PecasRestantes, numeroPecasJogadorAdverario); } else { List<BoardNode> lstSucessores = new List<BoardNode>(); lstSucessores = GeraMovimentos(_Nodo, _udtJogadorMin.CorDoJogador); if (lstSucessores.Count == 0) { for (int i = 0; i < _Nodo.Tabuleiro.Length; i++) if (_Nodo.Pai.Tabuleiro[i] == CorJogadorAdversario(this.CorDoJogador)) numeroPecasJogadorAdverario++; return CalculaValorJogada2(_Nodo.Tabuleiro, this.PecasRestantes, numeroPecasJogadorAdverario); } else { for (int i = 0; i < lstSucessores.Count; i++) { lstSucessores[i].StepCost = Minimax(lstSucessores[i], _Profundidade + 1, _udtJogadorMin, _udtJogadorMax, !_isMax); } lstSucessores.Sort(Comparer<BoardNode>.Default); if (_isMax) { return lstSucessores[lstSucessores.Count - 1].StepCost; } else { return lstSucessores[0].StepCost; } } } }
private int MinimaxAlfaBeta(BoardNode _Nodo, int _Profundidade, Jogador _udtJogadorMax, Jogador _udtJogadorMin, bool _isMax, int _alpha, int _beta) { if (_Profundidade == m_ProfundidadeTotal) { return CalculaValorJogada2(_Nodo.Tabuleiro, _Nodo.NumeroPecasJogador, _Nodo.NumeroPecasJogadorAdverario); } else { List<BoardNode> lstSucessores = new List<BoardNode>(); int i = 0; if (_isMax) { lstSucessores = GeraMovimentos(_Nodo, _udtJogadorMax.CorDoJogador); } else { lstSucessores = GeraMovimentos(_Nodo, _udtJogadorMin.CorDoJogador); } int nroSucessores = lstSucessores.Count; if (lstSucessores.Count == 0) { return CalculaValorJogada2(_Nodo.Tabuleiro, _Nodo.NumeroPecasJogador, _Nodo.NumeroPecasJogadorAdverario); } else { if (_isMax) { while (i < nroSucessores && _beta > _alpha) { lstSucessores[i].StepCost = MinimaxAlfaBeta(lstSucessores[i], _Profundidade + 1, _udtJogadorMax, _udtJogadorMin, !_isMax, _alpha, _beta); if (lstSucessores[i].StepCost > _alpha) { _alpha = lstSucessores[i].StepCost; } i++; } return _alpha; } else { while (i < nroSucessores && _beta > _alpha) { lstSucessores[i].StepCost = MinimaxAlfaBeta(lstSucessores[i], _Profundidade + 1, _udtJogadorMax, _udtJogadorMin, !_isMax, _alpha, _beta); if (lstSucessores[i].StepCost < _beta) { _beta = lstSucessores[i].StepCost; } i++; } return _beta; } } } }
/// <summary> /// Gera uma lista de proximas confiruações de tabuleiro possiveis a partir de um estado atual /// do tabuliero e do jogador que está jogando. /// </summary> /// <returns></returns> private List<BoardNode> GeraMovimentos(BoardNode _nodoTabuleiro, int _corJogador) { #region Coments... /* * Uma peca pode jogar: * - quando um vizinho estiver vazio * * - quando um vizinho contiver uma peca do adversario e a proxima casa na reta que passa * por ambos estiver vazia. Essa proxima casa pode ser calculada pela soma/subtracao do * modulo da diferenca da casa da peca que esta jogando para a peca adversaria vizinha. * --subtrae quando vizinho MENOR endereco_peca_atual * --soma quando vizinho MAIR endereco_peca_atual * Exemplo: * Branca joga; * Branca em 19; preta em 13; * 13 < 19? entao tabuleiro[13 - |19 - 13|] = 7; 7 vazia? entao branca_19 come preta_13 * * Branca joga; * Branca 11; preta 17; * 11 < 17? senao tabuleiro[17 + |13 - 17|] = 21; 21 vazia? senao nao pode comer preta_17 * * Devemos bolar um geito (no pior dos casos muitos if's...) para tratar as bordas. * Exemplo: se 19 for comer 15, será testado a casa 11, mas nao é possivel realizar esse movimento * pelo tabuleiro. * SOLUCAO: a casa onde a pedra que está comendo a adversario vai parar deve continuar sendo vizinha * da casa onde estava a adversaria!!! :D * * Com isso, "é facil ver que" precisamos de duas listas: uma com movimentos para casas vazias e * outra com movimentos apenas de "comer pecas". Caso a lista de comer pecas seja diferente de NULL, * descartamos a primeira lista. Com isso, todos os proximos movimentos possiveis retornados serao * de comer. * Caso a lista de comer seja vazia, descartamos ela e retornamos a lista apenas com os movimentos * possiveis para vizinhos vazios. */ #endregion List<BoardNode> lstVizinhosVazios = new List<BoardNode>(); List<BoardNode> lstPecasAComer = new List<BoardNode>(); List<int> vizinhosDaPeca; List<int> vizinhosDoAdversario; int ProximaCasaLivre; int[] novoTabuleiro; for (int casa = 1; casa < _nodoTabuleiro.Tabuleiro.Length; casa++) { if (_nodoTabuleiro.Tabuleiro[casa] == _corJogador) { //vizinhosDaPeca = new List<int>(); //Lista com os vizinhos da casa atual vizinhosDaPeca = Vizinhos[casa].ToList<int>(); //Testa para cada vizinho da casa atual se ele esta vazio. Se estiver, cria um novo //estado de Tabuleiro, ocupando o vizinho vazio, esvaziando a casa antiga e gerando //um novo nodo na lista. foreach (int vizinho in vizinhosDaPeca) { /* * Vizinho tem tres possibilidades: * - ou esta VAZIO e a peca pode ocupar --> ocupa * - ou esta ocupado por peca do adversario e talvez possa comer --> tentar comer * - ou esta ocupado com propria peca --> nao faz nada */ if (_nodoTabuleiro.Tabuleiro[vizinho] == VAZIA) { //Aparentemente é necessario usar o CopyTo pois ao atribuir um array no outro, apenas //a referencia na mem é copiada ai os dois apontam pro mesmo lugar e da muita merda... novoTabuleiro = new int[26]; _nodoTabuleiro.Tabuleiro.CopyTo(novoTabuleiro, 0); novoTabuleiro[0] = NAO_COMEU; novoTabuleiro[casa] = VAZIA; novoTabuleiro[vizinho] = _corJogador; lstVizinhosVazios.Add(new BoardNode(_nodoTabuleiro, novoTabuleiro, this.CorDoJogador)); } //como já nao é vazio, ou é do mesmo time (entao == _corJogador) //ou entao != _corJogador else if (_nodoTabuleiro.Tabuleiro[vizinho] != _corJogador) { #region Coments... //Vizinho == Adversario /* * Re-explicando... * Se o vizinho contem um adversario e a proxima casa na mesma direcao do adversario * tambem estiver vazia, o jogador que esta jogando pode comer essa peca. * * Para comela, entretanto, essa terceira casa (a que deve estar livre) tem que ser acessivel * a partir da casa vizinha. Se nao, por exemplo, alguem que estiver na casa 19 pode tentar comer * alguem da casa 15 e ir parar na 11, o q é impossivel pelo tabuleiro. * * Para resolver isso, esta sendo testado se a proxima casa vazia é vizinha da casa com * a peca adversaria (a "passiva" q esta sendo comida...). Se for, entao o movimento * pode ser realizado. * * Para calcular o endereco dessa proxima casa basta acessar o indice do tabuleiro referente * a [end_vizinho +- |end_casa - end_vizinho|] => endereco do vizinho adversario menos ou mais o * modulo da diferenca do endereco da casa da peca atual com o endereco do vizinho. Menos se o * endereco do vizinho for menor que o da casa ("comendo para cima") ou mais se o endereco do vizinho * for maior que o da casa ("comendo para baixo"). * * Entao, cria-se um novo estado de tabuleiro com a casa da peca e do vizinho comido vazios, * a proxima casa depois do vizinho ocupada. */ #endregion vizinhosDoAdversario = Vizinhos[vizinho].ToList<int>(); if (vizinho < casa) { ProximaCasaLivre = vizinho - Math.Abs(casa - vizinho); if (ProximaCasaLivre >= 1 && ProximaCasaLivre <= 25) { if (_nodoTabuleiro.Tabuleiro[ProximaCasaLivre] == VAZIA && vizinhosDoAdversario.Contains(ProximaCasaLivre)) { //Aparentemente é necessario usar o CopyTo pois ao atribuir um array no outro, apenas //a referencia na mem é copiada ai os dois apontam pro mesmo lugar e da muita merda... novoTabuleiro = new int[26]; _nodoTabuleiro.Tabuleiro.CopyTo(novoTabuleiro, 0); novoTabuleiro[0] = COMEU_PECA; novoTabuleiro[casa] = VAZIA; novoTabuleiro[vizinho] = PECA_COMIDA; novoTabuleiro[ProximaCasaLivre] = _corJogador; lstPecasAComer.Add(new BoardNode(_nodoTabuleiro, novoTabuleiro, this.CorDoJogador)); } } } else if (vizinho > casa) { ProximaCasaLivre = vizinho + Math.Abs(casa - vizinho); if (ProximaCasaLivre >= 1 && ProximaCasaLivre <= 25) { if (_nodoTabuleiro.Tabuleiro[ProximaCasaLivre] == VAZIA && vizinhosDoAdversario.Contains(ProximaCasaLivre)) { //Aparentemente é necessario usar o CopyTo pois ao atribuir um array no outro, apenas //a referencia na mem é copiada ai os dois apontam pro mesmo lugar e da muita merda... novoTabuleiro = new int[26]; _nodoTabuleiro.Tabuleiro.CopyTo(novoTabuleiro, 0); novoTabuleiro[0] = COMEU_PECA; novoTabuleiro[casa] = VAZIA; novoTabuleiro[vizinho] = PECA_COMIDA; novoTabuleiro[ProximaCasaLivre] = _corJogador; lstPecasAComer.Add(new BoardNode(_nodoTabuleiro, novoTabuleiro, this.CorDoJogador)); } } } } else { //Aqui, o vizinho é do proprio time, nao faz nada. } } } } if (lstPecasAComer.Count == 0) return lstVizinhosVazios; else return lstPecasAComer; }