58
Simulação de Esportes: Futebol Simples 1. Visão Geral Projetar uma IA para esportes de equipe, particularmente a IA para jogar futebol, não é fácil. Para se criar agentes capazes de jogar um jogo como um profissional humano joga, toma-se uma grande quantia de trabalho duro. Muitas equipes de alta tecnologia de universidades notáveis ao redor do mundo têm competido em um torneio de futebol robótico, a Robocup, desde os anos noventa. Embora a ambiciosa meta do torneio seja produzir robôs capazes de vencer a Copa do Mundo do ano 2050 (eu não estou brincando), há também um torneio de futebol simulado além do robótico, onde times de jogadores de futebol simulados competem em um gramado virtual. Muitas dessas equipes usam tecnologias de IA de última geração, muitas delas especialmente desenvolvidas para o futebol. Se você for ver um torneio, você pode escutar, entre os gritos da torcida, equipes discutindo os méritos da aprendizagem fuzzy-Q, o projeto de gráficos de coordenação multi-agente e o posicionamento estratégico baseado na situação. Felizmente, como programadores de jogo, nós não temos que nos importar com todos os detalhes de um ambiente de futebol simulado de verdade. Nossa meta não é vencer a Copa do Mundo, mas produzir agentes capazes de jogar futebol bem o bastante para fornecer um entretenimento desafiador ao jogador. Este capítulo levará você através da criação dos agentes de jogo capazes de jogar uma versão simplificada do futebol – o Futebol Simples - usando apenas as habilidades que você aprendeu até aqui neste livro. Minha intenção não é demonstrar como cada tática e habilidade deve ser modelada, mas mostrar a você como projetar e implementar uma estrutura IA de esportes de equipe capaz de suportar suas próprias idéias. Com isto em mente, eu mantive o ambiente do jogo e as regras para o Futebol Simples, bem… bem simples. Eu também escolhi omitir algumas táticas óbvias. Parcialmente porque isto reduz a complexidade da IA e, portanto facilita a sua compreensão do fluxo da lógica da máquina de estados, mas principalmente porque isto dará a você a oportunidade de consolidar as habilidades que você tem aprendido em um projeto de IA para um jogo de verdade, se você aceitar o desafio de seguir este capítulo até o fim. Quando você terminar este capítulo, você estará apto a criar agentes de IA capazes de jogar a maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira – entre outros, você poderá codificar uma IA de entretenimento para o que escolher.

Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Embed Size (px)

Citation preview

Page 1: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Simulação de Esportes: Futebol Simples

1. Visão Geral

Projetar uma IA para esportes de equipe, particularmente a IA para jogar futebol, não é fácil. Para se criar agentes capazes de jogar um jogo como um profissional humano joga, toma-se uma grande quantia de trabalho duro. Muitas equipes de alta tecnologia de universidades notáveis ao redor do mundo têm competido em um torneio de futebol robótico, a Robocup, desde os anos noventa. Embora a ambiciosa meta do torneio seja produzir robôs capazes de vencer a Copa do Mundo do ano 2050 (eu não estou brincando), há também um torneio de futebol simulado além do robótico, onde times de jogadores de futebol simulados competem em um gramado virtual. Muitas dessas equipes usam tecnologias de IA de última geração, muitas delas especialmente desenvolvidas para o futebol. Se você for ver um torneio, você pode escutar, entre os gritos da torcida, equipes discutindo os méritos da aprendizagem fuzzy-Q, o projeto de gráficos de coordenação multi-agente e o posicionamento estratégico baseado na situação.

Felizmente, como programadores de jogo, nós não temos que nos importar com todos os detalhes de um ambiente de futebol simulado de verdade. Nossa meta não é vencer a Copa do Mundo, mas produzir agentes capazes de jogar futebol bem o bastante para fornecer um entretenimento desafiador ao jogador. Este capítulo levará você através da criação dos agentes de jogo capazes de jogar uma versão simplificada do futebol – o Futebol Simples - usando apenas as habilidades que você aprendeu até aqui neste livro.

Minha intenção não é demonstrar como cada tática e habilidade deve ser modelada, mas mostrar a você como projetar e implementar uma estrutura IA de esportes de equipe capaz de suportar suas próprias idéias. Com isto em mente, eu mantive o ambiente do jogo e as regras para o Futebol Simples, bem… bem simples. Eu também escolhi omitir algumas táticas óbvias. Parcialmente porque isto reduz a complexidade da IA e, portanto facilita a sua compreensão do fluxo da lógica da máquina de estados, mas principalmente porque isto dará a você a oportunidade de consolidar as habilidades que você tem aprendido em um projeto de IA para um jogo de verdade, se você aceitar o desafio de seguir este capítulo até o fim.

Quando você terminar este capítulo, você estará apto a criar agentes de IA capazes de jogar a maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira – entre outros, você poderá codificar uma IA de entretenimento para o que escolher.

Page 2: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

2. O Ambiente e as Regras do Futebol Simples As regras do jogo foram descomplicadas. Há dois times: vermelho e azul. Cada time contém

quatro jogadores de campo e um goleiro. O objetivo do jogo é registrar tantos gols quanto possível. Um gol é registrado ao se chutar a bola sobre a linha de gol do time adversário.

Os lados do campo de Futebol Simples são murados (como no hóquei no gelo), assim a bola não pode sair, ela simplesmente rebate nos muros. Isto significa que diferentemente do futebol normal, não haverá escanteios ou cobranças de lateral. Ah, não há definitivamente nenhuma regra de bola fora! A Figura 4.1 mostra a aparência do começo de um jogo normal.

Figura 4.1: Posições para cobrança de meio de campo (jogadores são mostrados em tamanho aumentado por clareza)

O ambiente de jogo consiste dos seguintes itens:

• Um campo de futebol (Soccer Pitch);

• Duas áreas de gol (Goal);

• Uma bola (Soccer Ball);

• Dois times (Soccer Team);

• Oito jogadores de campo (Field Player);

• Dois goleiros (GoalKeeper).

Cada tipo de item é encapsulado como um objeto. Você pode ver como todos eles são relacionados uns com os outros vendo o diagrama de classes UML simplificado mostrado na Figura 4.2.

Page 3: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.2: A hierarquia de objetos em alto nível do Futebol Simples

Os objetos dos jogadores e goleiros são parecidos aos agentes de jogo que você já encontrou neste livro. Eu os descreverei em detalhes muito brevemente, mas primeiro gostaria de mostrar a você como o campo de futebol, as áreas e bola de futebol são implementados. Isto deve ajudar você a entender um pouco o ambiente que os agentes de jogo ocupam e então, eu poderei passar para a IA mesmo.

2.1 O Campo de Futebol O campo de futebol é uma área retangular cercada por muros. Em cada ponta do campo, há uma

área de gol posicionada centralmente. Veja a Figura 4.1. O pequeno círculo no centro do campo é conhecido como meio-de-campo. A bola é posicionada no meio-de-campo antes do começo da disputa. Quando um gol é registrado, ambos os times perdem o controle da bola e ela é posicionada no meio-de-campo pronta para outra "cobrança de meio-de-campo". (Para os fãs do futebol entre meus leitores, por favor, perdoem minhas descrições elaboradas, mas se eu não fizer isto, eu sei que no momento em que este livro for lançado eu receberei dezenas de e-mails dos habitantes de algum vale perdido no Himalaia, indignados por não saberem sobre que raios eu estou falando!).

Page 4: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

O campo é encapsulado pela classe SoccerPitch. Uma única instância desta classe é instancionada no main.cpp. O objeto SoccerPitch possui instâncias dos objetos SoccerTeam, SoccerBall e Goal.

Aqui está a declaração da classe:

class SoccerPitch { public: SoccerBall* m_pBall; SoccerTeam* m_pRedTeam; SoccerTeam* m_pBlueTeam; Goal* m_pRedGoal; Goal* m_pBlueGoal;

Esses primeiros atributos são auto-explicativos e eu descreverei as classes relevantes em detalhes em breve.

//container for the boundary walls std::vector<Wall2D> m_vecWalls;

Os limites do campo no ambiente do Futebol Simples são representados por Wall2Ds. Os muros (walls) são descritos por um segmento de linha com dois finais e uma normal para representar a direção da face do segmento de linha. Você pode lembrar da descrição do comportamento de direcionamento wall avoidance.

//defines the dimensions of the playing area Region* m_pPlayingArea;

Um objeto Region é utilizado para descrever as dimensões do campo de futebol. Uma Region armazena as posições de cima, da esquerda, de baixo e da direita da área declarada e também um número de identificação (ID).

std::vector<Region*> m_Regions;

Os jogadores de futebol têm que saber onde eles estão no campo, além de suas coordenadas x, y para uma posição muito específica, isso também é útil para dividir o campo em regiões onde os jogadores possam implementar estratégias. Para facilitar isto, o campo é dividido em dezoito áreas como mostradas na Figura 4.3.

Page 5: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.3: O campo dividido em regiões

No começo de um jogo, cada jogador é designado a uma região para esta ser sua posição de jogo. Esta região será para onde ele voltará depois que um gol é registrado ou quando ele termina de fazer uma jogada com a bola. A região de posição de um jogador pode variar durante um jogo dependendo da estratégia do time. Por exemplo, quando atacar, é mais vantajoso para o time ocupar posições além do meio de campo do que quando defendendo.

bool m_bGameOn;

Os times podem pesquisar este valor para ver se o jogo está correndo ou não. (O jogo não está correndo se um gol foi recentemente registrado e todos os jogadores estão voltando para suas posições para cobrança de meio de campo).

bool m_bGoalKeeperHasBall;

Este valor é atribuindo como true se o goleiro de um dos times tiver a bola. Os jogadores podem pesquisar este valor para selecionar um comportamento apropriado. Por exemplo, se um goleiro tem posse de bola, um adversário muito próximo não tentará chutá-la.

/* EXTRANEOUS DETAIL OMITTED */ public: SoccerPitch(int cxClient, int cyClient); ~SoccerPitch(); void Update(); bool Render();

Page 6: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

/* EXTRANEOUS DETAIL OMITTED */ };

As funções SoccerPitch::Update e SoccerPitch::Render são o topo da hierarquia de atualização e renderização. A cada passo de atualização, esses métodos são chamados de dentro o loop principal do jogo, assim, é feita a chamada aos métodos Render e Update de cada uma das outras entidades do jogo.

2.2 As Áreas de Gol Uma área de gol na vida real em um campo de futebol é definida por uma trave esquerda e uma

direita. Um gol é registrado se qualquer parte da bola cruza a linha do gol - a linha que conecta as duas traves. Uma área retangular em frente de cada área é desenhada na cor do time relevante para tornar a distinção de cada lado dos times mais fácil. A linha de gol é a linha que descreve a parte de trás desta área retangular.

Aqui a declaração da classe:

class Goal { private: Vector2D m_vLeftPost; Vector2D m_vRightPost; //a vector representing the facing direction of the goal Vector2D m_vFacing; //the position of the center of the goal line Vector2D m_vCenter; //each time Scored() detects a goal this is incremented int m_iNumGoalsScored; public: Goal(Vector2D left, Vector2D right):m_vLeftPost(left), m_vRightPost(right), m_vCenter((left+right)/2.0), m_iNumGoalsScored(0) { m_vFacing = Vec2DNormalize(right-left).Perp(); } //Given the current ball position and the previous ball position, //this method returns true if the ball has crossed the goal line //and increments m_iNumGoalsScored inline bool Scored(const SoccerBall*const ball); /* ACCESSOR METHODS OMITTED */ };

A cada etapa de atualização, o método Scored de cada área de gol dos dois times é chamado de dentro do SoccerPitch::Update. Se um gol é detectado, então os jogadores e a bola são restituídos a suas posições iniciais para a cobrança de meio de campo.

Page 7: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

2.3 A Bola de Futebol Uma bola de futebol é um pouco mais interessante. Os dados e métodos que encapsulam uma bola

de futebol são codificados na classe SoccerBall. Uma bola de futebol move-se, assim sua classe é herdeira da classe MovingEntity que nós utilizados no Capítulo 3. Além da funcionalidade fornecida pela MovingEntity, a SoccerBall também possui atributos para gravação da última posição atualizada da bola e métodos para chutar, testar colisões e cálculos da posição futura dela.

Quando uma bola de futebol real é chutada, ela gentilmente desacelera até repousar, por causa da fricção do chão e a resistência de ar que agem sobre ela. As bolas do Futebol Simples não vivem no mundo real, mas nós podemos modelar um efeito parecido ao aplicar uma desaceleração constante (uma aceleração negativa) no movimento da bola. A quantia de desaceleração é ajustada no Params.ini como o valor Friction.

Aqui está a declaração completa da classe SoccerBall seguida por descrições de alguns de seus métodos importantes.

class SoccerBall : public MovingEntity { private: //keeps a record of the ball's position at the last update Vector2D m_vOldPos; //a pointer to the player(or goalkeeper) who possesses the ball PlayerBase* m_pOwner; //a local reference to the walls that make up the pitch boundary //(used in the collision detection) const std::vector<Wall2D>& m_PitchBoundary; //tests to see if the ball has collided with a wall and reflects //the ball's velocity accordingly void TestCollisionWithWalls(const std::vector<Wall2D>& walls);

A bola de futebol apenas testa colisões com os limites do campo; ela não testa colisões contra os jogadores, pois a bola deve ser capaz de mover-se livremente através do campo e por seus "pés".

public: SoccerBall(Vector2D pos, double BallSize, double mass, std::vector<Wall2D>& PitchBoundary): //set up the base class MovingEntity(pos, BallSize, Vector2D(0,0), -1.0, //max speed - unused Vector2D(0,1), mass, Vector2D(1.0,1.0), //scale - unused 0, //turn rate - unused 0), //max force - unused m_PitchBoundary(PitchBoundary), m_pOwner(NULL) {} //implement base class Update

Page 8: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

void Update(double time_elapsed); //implement base class Render void Render(); //a soccer ball doesn't need to handle messages bool HandleMessage(const Telegram& msg){return false;} //this method applies a directional force to the ball (kicks it!) void Kick(Vector2D direction, double force); //given a kicking force and a distance to traverse defined by start //and finish points, this method calculates how long it will take the //ball to cover the distance. double TimeToCoverDistance(Vector2D from, Vector2D to, double force)const; //this method calculates where the ball will be at a given time Vector2D FuturePosition(double time)const; //this is used by players and goalkeepers to "trap" a ball -- to stop //it dead. The trapping player is then assumed to be in possession of //the ball and m_pOwner is adjusted accordingly void Trap(PlayerBase* owner){m_vVelocity.Zero(); m_pOwner = owner;} Vector2D OldPos()const{return m_vOldPos;} //this places the ball at the desired location and sets its velocity to zero void PlaceAtPosition(Vector2D NewPos); };

Antes de descrever as classes dos jogadores e do time, eu gostaria de explicar alguns métodos públicos da SoccerBall para garantir que você compreende a matemática que eles contêm. Esses métodos são frequentemente utilizados por jogadores para prever onde a bola estará no futuro ou para prever quanto tempo ela levará para alcançar uma posição. Quando você projeta uma IA para jogos/simulações de esportes você terá que usar muito a sua habilidade com matemática e física. Ah sim! Se você não sabe a teoria, agora é a hora de retornar ao Capítulo 1 e ler sobre isto; de outro modo você estará mais perdido que um rapper em uma floresta chuvosa.

Nota 3D Mesmo que esta demonstração tenha sido codificada em 2D, você provavelmente aplicará as mesmas técnicas em um jogo 3D. Assim pode haver um pouco mais de complexidade por a bola poder quicar e se deslocar acima da cabeça dos jogadores, então você terá que adicionar habilidades adicionais aos jogadores para fazer “balõezinhos” e "cabeceadas" com a bola, mas estas são considerações principalmente físicas. A AI é mais ou menos a mesma; você terá apenas de adicionar mais alguns estados a FSM e alguma lógica adicional para testar a altura da bola para fazer coisas como interceptações.

2.3.1 SoccerBall::FuturePosition

Dado um comprimento de tempo como parâmetro, o FuturePosition calcula onde a bola estará é nesse tempo futuro - assumindo uma trajetória contínua e ininterrupta. Não esqueça que a bola também sofre uma força friccional com o chão, que deve ser levada em consideração. A força friccional é expressa como uma aceleração constante que age contrariamente à direção para onde a bola está se movendo (desaceleração, em outras palavras). Esta constante é definida no params.ini como Friction.

Page 9: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Para determinar a posição Pt da bola no tempo t, devemos calcular o quanto longe ela se deslocará usando a equação 1.87 de Capítulo 1:

(4.1)

Onde ∆x é a distância percorrida, u é a velocidade da bola quando chutada e a é a desaceleração devido fricção.

Figura 4.4: Cálculo da distância percorrida

Uma vez que a distância percorrida foi calculada, nós sabemos quanto adicionar na posição da bola, mas não em qual direção. Entretanto, nós sabemos que a bola está viajando na direção de seu vetor de velocidade. Portanto, se nós normalizarmos o vetor de velocidade da bola e multiplicar ele pela distância percorrida, nós acabamos com um vetor que nos dá a distância e a direção. Se este vetor é adicionado a posição da bola, o resultado é a posição prevista. Aqui o cálculo em código:

Vector2D SoccerBall::FuturePosition(double time)const { //using the equationx=ut+ 1/2at^2, where x = distance, a = friction //u = start velocity //calculate the ut term, which is a vector Vector2D ut = m_vVelocity * time; //calculate the 1/2at^2 term, which is scalar double half_a_t_squared = 0.5 * Prm.Friction * time * time; //turn the scalar quantity into a vector by multiplying the value with //the normalized velocity vector (because that gives the direction) Vector2D ScalarToVector = half_a_t_squared * Vec2DNormalize(m_vVelocity); //the predicted position is the ball's position plus these two terms return Pos() + ut + ScalarToVector; }

Note Muitos dos métodos e funções mostradas por todo este livro contêm variáveis temporárias desnecessárias. Elas estão lá só para auxiliar a sua compreensão, além disso, sua remoção frequentemente ofusca o cálculo ou torna a linha de código muito longa para caber nas páginas deste livro.

Page 10: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

2.3.2 SoccerBall::TimeToCoverDistance

Dadas duas posições, A e B e uma força de chute, este método retorna um número indicando quanto tempo a bola levará para percorrer os dois pontos. Certamente, se dada uma grande distância e uma pequena força de chute, não pode ser possível à bola cobrir essa distância de qualquer maneira. Neste caso, o método retorna um valor negativo.

Desta vez a equação a se usar é esta:

(4.2)

Rearranjando as variáveis da equação em relação ao tempo temos:

(4.3)

Nós sabemos que a = fricção, assim nós temos que encontrar o v e o u, onde v = velocidade no ponto B, e u é a velocidade da bola imediatamente depois de ter sido chutada. No Futebol Simples, as velocidades não são cumulativas. A bola é assumida como sempre estando em velocidade zero imediatamente antes de um chute. Embora isso tecnicamente seja não-realista - se a bola foi passada ao jogador para ele apenas chutar, ela não terá uma velocidade zero - na prática, este método resulta em cálculos mais fáceis, enquanto ainda assim parece realista ao observador. Com isto em mente, u é igual à aceleração instantânea aplicada à bola pela força do chute. Portanto:

(4.4)

Agora que u e a foram calculados, nós apenas temos que calcular o v, e todos os três valores podem ser colocados na equação (4.3) para resolver ∆t. Para determinar o v (a velocidade no ponto B), a seguinte equação é utilizada:

(4.5)

Tirando a raiz quadrada de ambos os lados, temos:

(4.6)

Não esqueça que ∆x é a distância entre A e B. Se o termo u2 + 2a∆x é negativo, a velocidade não é um número real (você não pode calcular a raiz quadrada de um número negativo… bem, você pode, para isso servem os números complexos, mas para os propósitos deste livro nós fingiremos que você não pode). Isto significa que a bola não pode cobrir a distância de A até B. Se o termo é positivo, então nós encontramos v e assim é muito simples colocar todos os valores dentro da equação (4.3) para resolver ∆t.

Abaixo está o código fonte para você examinar.

double SoccerBall::TimeToCoverDistance(Vector2D A, Vector2D B, double force)const { //this will be the velocity of the ball in the next time step *if* //the player was to make the pass. double speed = force / m_dMass;

Page 11: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

//calculate the velocity at B using the equation // // v^2 = u^2 + 2ax // //first calculate s (the distance between the two positions) double DistanceToCover = Vec2DDistance(A, B); double term = speed*speed + 2.0*DistanceToCover*Prm.Friction; //if (u^2 + 2ax) is negative it means the ball cannot reach point B. if (term <= 0) return -1.0; double v = sqrt(term); //it's possible for the ball to reach B and we know its speed when it //gets there, so now it's easy to calculate the time using the equation // // t = v-u // --- // a // return (v-speed)/Prm.Friction; }

Page 12: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

3. O Projeto de IA Há dois tipos de jogadores de futebol em um time de Futebol Simples: os jogadores de campo e os

goleiros. Ambos estes tipos derivam da mesma classe base, PlayerBase. Ambos usam uma versão resumida da classe SteeringBehaviors que você viu no último capítulo e ambas têm suas próprias máquinas de estados finitos, com seu próprio conjunto de estados. Veja a Figura 4.5.

Page 13: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.5: Relacionamento das classes no nível do agente

Page 14: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Nem todos os métodos de todas as classes são mostrados, mas se têm uma boa idéia do projeto. A maioria dos métodos que são listados na PlayerBase e SoccerTeam compreende a interface que a máquina de estados de um jogador usa para direcionar sua lógica de IA. (Eu omiti os parâmetros dos métodos para permitir que o diagrama coubesse em uma página!).

Note como um SoccerTeam também possui uma StateMachine, dando a um time a capacidade de mudar seu comportamento dependendo do estado atual do jogo. Implementar uma IA no nível do time além do nível do jogador cria o que é conhecido como IA enfileirada. Este tipo de IA é utilizada em todos os tipos de jogos de computador. Você freqüentemente encontrará uma IA enfileirada em jogos de estratégia em tempo real (RTS) aonde a IA de inimigo é comumente implementada em várias camadas, digamos, das unidades, das tropas e níveis de comandante.

Note também como os jogadores e seus times têm a capacidade de enviar mensagens. As mensagens podem ser passadas do jogador para jogador (incluindo goleiros) ou do time de futebol para o jogador. Nesta demonstração os jogadores não passam mensagens para seu time. (Embora não haja razão para que eles não possam. Se você tem um bom motivo para que seus jogadores contatem seu time, siga em frente.) Todas as mensagens enviadas para os jogadores de campo ou goleiros são tratadas via o respectivo estado global de cada classe, como você poderá ver mais tarde neste capítulo.

Já que o estado do time dos jogadores dita em alguma extensão como os jogadores devem proceder, sua jornada dentro das entranhas da IA do Futebol Simples será provavelmente melhor iniciada pela descrição da classe SoccerTeam. Depois que você compreender o que ela faz em cada etapa de atualização, eu descreverei como os jogadores e goleiros fazem suas mágicas do futebol.

3.1 A classe do time: SoccerTeam A classe SoccerTeam possui suas próprias instâncias dos jogadores que compreendem o time de

futebol. Ela tem ponteiros para o campo de futebol, o time adversário e a área de gol de cada do time. Adicionalmente, ela possui ponteiros para os jogadores “chave” no campo. Os jogadores individuais podem pesquisar seu time de futebol e usar estas informações na lógica de sua máquina de estados.

Primeiramente, eu descreverei os papéis destes jogadores chaves e então vamos discutir os vários estados que um time de Futebol Simples utiliza. Aqui está como os ponteiros dos jogadores chaves são declarados no protótipo de classe:

class SoccerTeam { private: /* EXTRANEOUS DETAIL OMITTED */ //pointers to "key" players PlayerBase* m_pReceivingPlayer; PlayerBase* m_pPlayerClosestToBall; PlayerBase* m_pControllingPlayer; PlayerBase* m_pSupportingPlayer; /* EXTRANEOUS DETAIL OMITTED */ };

3.1.1 O Jogador Receptor (m_pReceivingPlayer)

Quando um jogador chuta a bola para outro jogador, o jogador que está esperando receber a bola é, nada surpreendente, conhecido como receptor. Sempre poderá haver apenas um receptor alocado em um tempo qualquer. Se não há receptor alocado, este valor é atribuindo como NULL.

Page 15: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

3.1.2 O Jogador Mais Próximo da Bola (m_pPlayerClosestToBall)

Este ponteiro aponta para o membro do time que está atualmente mais perto da bola. Como você pode imaginar, conhecer este tipo de informação é útil quando um jogador tem que decidir se ele deve pegar a bola ou deixar ela para outro membro de se time pegar. A cada passo do tempo, o time de futebol calculará qual jogador é o mais próximo e manterá este ponteiro continuamente atualizado. Portanto, durante um jogo, o m_pPlayerClosestToBall nunca será NULL.

3.1.3 O Jogador que Domina a Bola (m_pControllingPlayer)

O jogador que domina a bola é o jogador que está no comando da bola de futebol. Um exemplo óbvio de um jogador com domínio de bola é aquele que fará um passe para um companheiro de equipe. Um exemplo menos óbvio é o jogador esperando receber a bola uma vez que o passe foi feito. No último exemplo, ainda que a bola possa estar longe do jogador receptor, é dito que este jogador tem a posse de bola, a menos que ela seja interceptada por um adversário, assim o receptor é o próximo jogador capaz de chutar a bola. O jogador com domínio de bola, quando avança em campo adversário, é frequentemente chamado como jogador atacante, ou ainda mais simplesmente, como atacante. Se o time não controla a bola, este ponteiro é atribuído como NULL.

3.1.4 O Jogador de Suporte (m_pSupportingPlayer)

Quando um jogador ganha o controle da bola, o time designará um jogador de suporte. O jogador de suporte tentará se mover para uma posição útil mais afastada do atacante e mais dentro do campo adversário. As posições de suporte são classificadas baseadas em certas qualidades tal como quão fácil é para o atacante passar a bola para ela e a probabilidade de registrar um gol dessa posição. Por exemplo, a posição B na Figura 4.6 pode ser considerada uma boa posição de suporte (boa visão da área do gol adversário e facilidade do passe), a posição C é uma posição de suporte mais ou menos (pequena visão da área do gol do adversário e potencial de passe fraco) e a posição D é uma posição de suporte muito ruim (potencial de passe pequeno, nenhuma visão do gol e não é mais avançada que o atacante).

Figura 4.6: Posições de suporte — o bom, o mau e o feio

Page 16: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Se não há alocado nenhum jogador de suporte, este ponteiro apontará para NULL. As posições de suporte são calculadas por uma série amostras de posições no campo e de vários testes que ocorrem nelas, resultando em um resultado cumulativo. A posição com o resultado o mais alto é denominada o melhor ponto de suporte, ou BSS (best supporting spot) como eu chamo. Isto é obtido com a ajuda de uma classe chamada SupportSpotCalculator. Eu irei agora dar uma pequena explicação, mas importante, de como esta classe opera.

Cálculo do Melhor Ponto de Suporte

A classe SupportSpotCalculator calcula o BSS pela classificação de um número de pontos de amostra posicionados na metade do campo adversário. As posições padrão dos pontos (do time vermelho) são mostradas na Figura 4.7.

Figura 4.7: O time vermelho considera estes os pontos de suporte em potencial.

Como você pode ver, todos os pontos estão localizados na metade adversário do campo. Não há necessidade de posições de amostra no campo do próprio time, pois o jogador de suporte sempre estará tentando encontrar a localização que lhe dará a melhor oportunidade de um chute a gol, e inevitavelmente esta estará situada próxima ao gol do adversário.

Um ponto de suporte tem uma posição e uma classificação, da seguinte maneira:

struct SupportSpot { Vector2D m_vPos; double m_dScore; SupportSpot(Vector2D pos, double val):m_vPos(pos), m_dScore(value) {} };

Os pontos são classificados pelo exame de cada um a cada turno, classificando eles por uma qualidade em particular, tal como se é possível ou não fazer um gol a partir desse ponto ou o quanto longe

Page 17: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

do jogador com a bola o ponto está situado. As classificações para cada qualidade são acumuladas e o ponto com a mais alta classificação é marcado como o melhor ponto de suporte. O jogador de suporte pode então se mover para uma posição BSS em prontidão para um passe do atacante.

Nota Não é essencial que o BSS seja calculado a cada passo de atualização; portanto o número de vezes que o cálculo é feito é regulado por SupportSpotUpdateFreq que dá um valor em vezes por segundo. O valor padrão, está no params.ini e é uma vez por segundo.

Para determinar exatamente quais dessas qualidades devem ser consideradas, você tem que pensar como um jogador de futebol. Se você está avançando em campo adversário tentando se colocar em uma posição de suporte vantajosa, que fatores você vai considerar? Provavelmente você pode valorizar posições onde seus companheiros de equipe podem passar a bola para você. Em seu mapa mental do campo de futebol, você pode se imaginar em cada localização e considerar como boas posições para ir, aquelas de onde você acha que estará seguro para o atacante lhe passar a bola. O SupportSpotCalculator faz o mesmo, dando a cada ponto que satisfaz essa condição uma classificação equivalente ao valor: Spot_CanPassScore (atribuído como 2.0 no params.ini). A Figura 4.8 mostra uma posição típica durante um jogo, todos os pontos realçados foram classificados como potenciais de passe.

Figura 4.8: Pontos classificados como potenciais para o passe

Em adição, as posições de onde um gol pode ser feito são dignas de atenção. Portanto o SupportSpotCalculator designa uma classificação de Spot_CanScoreFromPositionScore para cada ponto fazendo o teste chute-a-gol-é-possível. Eu não sou nenhum jogador de futebol experiente (longe disso!), mas eu reconheço a que a capacidade de se fazer um passe para um ponto deve ser classificada como maior que capacidade de se fazer gol desse ponto - afinal, o atacante deve ser capaz de passar a bola para o jogador de suporte antes que ele tente fazer um gol. Com isso em mente, o valor padrão do Spot_CanScoreFromPositionScore é 1.0. A Figura 4.9 mostra a mesma posição da Figura 4.8 com os pontos classificados como potenciais de chute a gol.

Page 18: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.9: Pontos classificados como potenciais de chute a gol

Outra consideração que um jogador de suporte pode fazer é procurar por uma posição com uma distância específica de seu companheiro de equipe. Ela não pode ser longe demais que torne o passe arriscado e difícil, e nem tão perto também que torne o passe desnecessário.

Eu utilizei um valor de 200 pixeis como a melhor distância que um jogador de suporte deve estar afastado de jogador com a bola. Nessa distância um ponto receberá a melhor classificação como Spot_DistFromControllingPlayerScore (padrão 2.0), embora sejam dados valores menores para as distâncias mais afastadas ou mais próximas. Veja a Figura 4.10.

Figura 4.10: Pontos classificados de acordo com sua distância do atacante. Quanto maior o ponto, maior a classificação.

Page 19: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Quando cada posição foi examinada e todas as classificações foram acumuladas, o ponto com a classificação mais alta é considerado o melhor ponto de suporte e o atacante de suporte irá para essa posição em prontidão para receber um passe.

Este procedimento de determinar o BSS é feito no método SupportSpotCalculator::DetermineBestSupportingPosition. Aqui está o código fonte para você examinar:

Vector2D SupportSpotCalculator::DetermineBestSupportingPosition() { //only update the spots every few frames if (!m_pRegulator->AllowCodeFlow()&& m_pBestSupportingSpot) { return m_pBestSupportingSpot->m_vPos; } //reset the best supporting spot m_pBestSupportingSpot = NULL; double BestScoreSoFar = 0.0; std::vector<SupportSpot>::iterator curSpot; for (curSpot = m_Spots.begin(); curSpot != m_Spots.end(); ++curSpot) { //first remove any previous score. (the score is set to one so that //the viewer can see the positions of all the spots if he has the //aids turned on) curSpot->m_dScore = 1.0; //Test 1. is it possible to make a safe pass from the ball's position //to this position? if(m_pTeam->isPassSafeFromAllOpponents(m_pTeam->ControllingPlayer()->Pos(), curSpot->m_vPos, NULL, Prm.MaxPassingForce)) { curSpot->m_dScore += Prm.Spot_PassSafeStrength; } //Test 2. Determine if a goal can be scored from this position. if(m_pTeam->CanShoot(curSpot->m_vPos, Prm.MaxShootingForce)) { curSpot->m_dScore += Prm.Spot_CanScoreStrength; } //Test 3. calculate how far this spot is away from the controlling //player. The farther away, the higher the score. Any distances farther //away than OptimalDistance pixels do not receive a score. if (m_pTeam->SupportingPlayer()) { const double OptimalDistance = 200.0; double dist = Vec2DDistance(m_pTeam->ControllingPlayer()->Pos(), curSpot->m_vPos); double temp = fabs(OptimalDistance - dist); if (temp < OptimalDistance) { //normalize the distance and add it to the score curSpot->m_dScore += Prm.Spot_DistFromControllingPlayerStrength *

Page 20: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

(OptimalDistance-temp)/OptimalDistance; } } //check to see if this spot has the highest score so far if (curSpot->m_dScore > BestScoreSoFar) { BestScoreSoFar = curSpot->m_dScore; m_pBestSupportingSpot = &(*curSpot); } } return m_pBestSupportingSpot->m_vPos; }

Bem, eu acho que a "pequena explicação" para o assunto dos pontos de suporte ficou um pouco grande demais! Antes de eu me distrair, eu estava contando para você como a classe SoccerTeam faz as coisas, lembra? Como eu tinha mencionado, um SoccerTeam possui uma máquina de estados. Isto dá a ela a capacidade de mudar seu comportamento dependendo em qual estado ela está. Vamos agora dar uma olhada de perto nos estados disponíveis para o time e como eles podem afetar o comportamento de seus jogadores.

3.1.5 Os estados do SoccerTeam

Em qualquer momento, um time de futebol pode estar em um de três estados: Defending (defendendo), Attacking (atacando) ou PrepareForKickOff (se preparando para a cobrança de meio de campo). Eu mantive a lógica destes estados muito simples - minha intenção é mostrar a você como implementar uma IA enfileirada e não mostrar como criar táticas de futebol complexas - embora eles possam ser facilmente estendidos e modificados para criar quase qualquer tipo de comportamento de time que você possa imaginar.

Como eu mencionei mais anteriormente, os jogadores usam a idéia de "regiões" para se posicionarem corretamente no campo. Os estados do time usam essas regiões para controlar onde os jogadores devem ir se eles não estão em posse da bola ou dando suporte/atacando. Quando se defendendo, por exemplo, é mais sensato para um time de futebol recuar seus jogadores para eles ficarem mais pertos do gol do próprio time, e quando atacando, os jogadores devem ir para o meio de campo adversário, mais perto do gol do adversário.

Aqui estão as descrições de cada estado do time em detalhes.

PrepareForKickOff

Um time entra neste estado imediatamente depois que um gol foi registrado. O método Enter atribui a todos os ponteiros de jogadores chaves para NULL, muda suas regiões para as de cobrança de meio de campo, e envia para cada jogador uma mensagem solicitando que eles retornem para suas regiões. Algo como isto:

void PrepareForKickOff::Enter(SoccerTeam* team) { //reset key player pointers team->SetControllingPlayer(NULL); team->SetSupportingPlayer(NULL); team->SetReceiver(NULL); team->SetPlayerClosestToBall(NULL);

Page 21: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

//send Msg_GoHome to each player. team->ReturnAllFieldPlayersToHome(); }

A cada ciclo do Execute, o time espera até que todos os jogadores de ambos os times estejam situados dentro de suas regiões, então se muda o estado para Defending e a disputa recomeça.

void PrepareForKickOff::Execute(SoccerTeam* team) { //if both teams in position, start the game if (team->AllPlayersAtHome() && team->Opponents()->AllPlayersAtHome()) { team->ChangeState(team, Defending::Instance()); } }

Defending

O método Enter do estado Defending de um time muda as posições padrão de todos os membros do time para que sejam localizadas na metade do campo pertencente a esse time. Trazendo todos os jogadores para perto do próprio gol, tornando mais difícil para o time adversário manobrar a bola e marcar um gol. A Figura 4.11 mostra a posição padrão do time vermelho quando eles estão no estado Defending.

Figura 4.11: Jogadores em suas posições padrão no estado Defending

void Defending::Enter(SoccerTeam* team) { //these define the home regions for this state of each of the players const int BlueRegions[TeamSize] = {1,6,8,3,5}; const int RedRegions[TeamSize] = {16,9,11,12,14};

Page 22: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

//set up the player's home regions if (team->Color() == SoccerTeam::blue) { ChangePlayerHomeRegions(team, BlueRegions); } else { ChangePlayerHomeRegions(team, RedRegions); } //if a player is in either the Wait or ReturnToHomeRegion states, its //steering target must be updated to that of its new home region team->UpdateTargetsOfWaitingPlayers(); }

O método Execute do estado Defending continuamente pesquisa se o time ganhou a posse de bola. Assim que o time consegue, o time muda de estado para Attacking.

Attacking

Como o método Enter do estado Attacking é quase idêntico ao do estado Defending, eu não vou desperdiçar espaço mostrando-o aqui. A única diferença é que os jogadores são designados para regiões padrões diferentes. As regiões designadas aos jogadores do time vermelho quando em Attacking são mostrados na Figura 4.12.

Figura 4.12: Jogadores em suas posições padrão no estado Attacking

Como você pode ver, os jogadores se posicionam muito mais pertos do gol do adversário. Isto dá a eles uma chance maior de manter a bola na metade do campo adversário e, portanto, mais oportunidade de marcar um gol. Note como um jogador é mantido atrás, posicionado na frente do goleiro, para que forneça uma pequena defesa se o adversário contra-atacar.

O método Execute do estado Attacking também é parecido com o do estado Defending, mas com uma adição. Quando um time ganha o controle da bola, o time imediatamente itera através de todos os

Page 23: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

jogadores para determinar qual irá fornecer o melhor suporte ao atacante. Uma vez que um jogador de suporte for escolhido, ele alegremente irá para o melhor ponto de suporte, como determinado pelo processo que nós discutimos anteriormente.

void Attacking::Execute(SoccerTeam* team) { //if this team is no longer in control change states if (!team->InControl()) { team->ChangeState(team, Defending::Instance()); return; } //calculate the best position for any supporting attacker to move to team->DetermineBestSupportingPosition(); }

Isso é o bastante sobre a classe SoccerTeam por agora. Vamos ver como os jogadores são implementados.

3.2 A classe dos jogadores de campo: FieldPlayer Os jogadores de campo são os caras que correm pelo campo, passando a bola e a chutando no gol

de seu adversário. Há dois tipos de jogadores de campo: attackers (atacantes) e defenders (defensores). Ambos são instâncionados como objetos da mesma classe, FieldPlayer, mas um atributo enumerado determina seu papel. Os defensores ficam principalmente atrás do campo, protegendo o gol do time, os atacantes têm mais liberdade para avançar mo campo, para o gol do adversário.

3.2.1 O movimento dos jogadores de campo

Um jogador de campo tem a velocidade alinhada a sua frente e utiliza os comportamentos de direcionamento para se mover entre as posições e para pegar a bola. Quando imóvel, um jogador de campo se mantém olhando a bola. Ele não faz isto para perceber a bola, pois ele sempre sabe onde a bola está (ao pesquisar o mundo do jogo diretamente), mas porque isto dá uma oportunidade melhor de passar ela imediatamente depois de uma interceptação e porque isto parece melhor para nossos olhos humanos. Lembre-se, isto é sobre a criação de ilusão de inteligência, e não IA de verdade como estudada por acadêmicos. A maioria dos jogadores humanos pensará que se um jogador de computador está seguindo a bola com sua cabeça, então ele deve estar "observando" a bola. Ao se criar jogadores que sempre seguem a bola nós também garantimos que nada de estranho aconteça – como um jogador receber e controlar a bola quando esta de frente para a direção contrária. Este tipo de coisa pode quebrar a ilusão, deixando o jogador humano se sentindo enganado e insatisfeito. Eu tenho certeza que você já experimentou este sentimento quando jogava alguma vez. Basta apenas um pequeno evento decepcionante para danificar a confiança do jogador na IA.

Os jogadores de campo se movem pelo campo utilizando os comportamentos arrive e seek para se direcionar ao alvo do comportamento de direcionamento ou usam o pursuit para pegar a bola prevendo sua posição no futuro. Qualquer comportamento de direcionamento necessário é tipicamente ligado no método Enter do estado e desligado no método Exit, o que nos leva a discutir os estados que um jogador de campo pode ocupar.

3.2.2 Os estados dos jogadores de campo

Na vida real, os jogadores de futebol devem aprender um conjunto de habilidades para que controlem a bola o bastante para que o time jogue coordenadamente e marque gols. Eles fazem isto por

Page 24: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

incontáveis horas de treino e repetição dos mesmos movimentos. Os jogadores do Futebol Simples não têm que treinar, mas eles confiam em você, o programador, para conferir a eles as habilidades que eles necessitam para jogar bem.

Uma máquina de estados finitos de um jogador de campo utiliza oito estados:

• GlobalPlayerState (o estado global do jogador); • Wait (esperar); • ReceiveBall (receber a bola); • KickBall (chutar a bola); • Dribble (driblar); • ChaseBall (pegar a bola) ; • ReturnToHomeRegion (retornar a região padrão); • SupportAttacker (dar suporte ao atacante).

As mudanças de estado são feitas pela lógica dos próprios estados ou quando um jogador recebe uma mensagem de um outro jogador (para receber a bola, por exemplo).

GlobalPlayerState

O propósito principal do estado global do jogador de campo é tratar as mensagens. Embora muito de comportamento de um jogador seja implementado pela lógica contida dentro de cada um de seus estados, também é desejável se implementar alguma forma de cooperação entre os jogadores via um sistema de comunicação. Um bom exemplo disso é quando um jogador de suporte se encontra em uma posição vantajosa e solicita um passe de um companheiro de equipe. Para facilitar a comunicação do jogador, o sistema de mensagens que você aprendeu no Capítulo 2 é implementado.

Há cinco mensagens utilizadas no Futebol Simples. Elas são:

• Msg_SupportAttacker

• Msg_GoHome

• Msg_ReceiveBall

• Msg_PassToMe

• Msg_Wait

As mensagens são enumeradas no arquivo SoccerMessages.h. Vamos ver como cada uma delas é processada.

bool GlobalPlayerState::OnMessage(FieldPlayer* player, const Telegram& telegram) { switch(telegram.Msg) { case Msg_ReceiveBall: { //set the target player->Steering()->SetTarget(*(Vector2D*)(telegram.ExtraInfo)); //change state player->ChangeState(player, ReceiveBall::Instance()); return true; } break;

Page 25: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

A Msg_ReceiveBall é enviada ao jogador receptor quando um passe é feito. A posição do alvo do passe é armazenada como o alvo do comportamento de direcionamento do receptor. O jogador receptor reconhece a mensagem mudando para o estado ReceiveBall.

case Msg_SupportAttacker: { //if already supporting just return if (player->CurrentState() == SupportAttacker::Instance()) return true; //set the target to be the best supporting position player->Steering()->SetTarget(player->Team()->GetSupportSpot()); //change the state player->ChangeState(player, SupportAttacker::Instance()); return true; } break;

A Msg_SupportAttacker é enviada pelo jogador com posse de bola para solicitar suporte quando tenta avançar pelo campo. Quando um jogador recebe esta mensagem, ele ajusta seu alvo de direcionamento ao melhor ponto de suporte e então muda de estado para SupportAttacker.

case Msg_GoHome: { player->SetDefaultHomeRegion(); player->ChangeState(player, ReturnToHomeRegion::Instance()); return true; } break;

Quando um jogador recebe esta mensagem, ele retorna para sua região padrão. Ela é freqüentemente transmitida pelos goleiros antes um tiro de meta e pelo "campo" para os jogadores voltarem as suas posições para a cobrança de meio de campo após um gol.

case Msg_Wait: { //change the state player->ChangeState(player, Wait::Instance()); return true; } break;

A Msg_Wait instrui um jogador para ele esperar em sua posição atual.

case Msg_PassToMe: { //get the position of the player requesting the pass

Page 26: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

FieldPlayer* receiver = (FieldPlayer*)(telegram.ExtraInfo); //if the ball is not within kicking range or the player does not have //a window within which he can make the kick, this player cannot pass //the ball to the player making the request. if (!player->BallWithinKickingRange()) { return true; } //make the pass player->Ball()->Kick(receiver->Pos() - player->Ball()->Pos(), Prm.MaxPassingForce); //let the receiver know a pass is coming Dispatch->DispatchMsg(SEND_MSG_IMMEDIATELY, player->ID(), receiver->ID(), Msg_ReceiveBall, NO_SCOPE, &receiver->Pos()); //change state player->ChangeState(player, Wait::Instance()); player->FindSupport(); return true; } break;

A Msg_PassToMe é utilizada em várias situações, principalmente quando um jogador de suporte está dentro de posição e acha que dela tem uma boa oportunidade de marcar um gol. Quando um jogador recebe esta mensagem, ele passa a bola para o jogador que solicitou (se o passe pode ser feito seguramente).

}//end switch return false; }

Além do OnMessage, o estado global também implementa o método Execute. Este diminui a velocidade máxima de um jogador se ele está próximo à bola para simular a maneira que jogadores de futebol se movem quando têm a posse de bola.

void GlobalPlayerState::Execute(FieldPlayer* player) { //if a player is in possession and close to the ball reduce his max speed if((player->BallWithinReceivingRange()) && (player->Team()->ControllingPlayer() == player)) { player->SetMaxSpeed(Prm.PlayerMaxSpeedWithBall); } else { player->SetMaxSpeed(Prm.PlayerMaxSpeedWithoutBall);

Page 27: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

} }

ChaseBall

Quando um jogador está no estado ChaseBall, ele usará o seek para chegar na posição atual da bola, tentando mantê-la dentro de um alcance de chute.

Quando um jogador entra neste estado seu comportamento seek é ativado assim:

void ChaseBall::Enter(FieldPlayer* player) { player->Steering()->SeekOn(); }

Durante uma atualização do método Execute, um jogador mudará de estado para KickBall se a bola estiver dentro do seu alcance de chute. Se a bola não estiver dentro desse alcance, o jogador continuará a correr atrás dela enquanto ele for o jogador do time mais próximo da bola.

void ChaseBall::Execute(FieldPlayer* player) { //if the ball is within kicking range the player changes state to KickBall. if (player->BallWithinKickingRange()) { player->ChangeState(player, KickBall::Instance()); return; } //if the player is the closest player to the ball then he should keep //chasing it if (player->isClosestTeamMemberToBall()) { player->Steering()->SetTarget(player->Ball()->Pos()); return; } //if the player is not closest to the ball anymore, he should return back //to his home region and wait for another opportunity player->ChangeState(player, ReturnToHomeRegion::Instance()); }

Quando um jogador sai deste estado, o comportamento seek é desativado.

void ChaseBall::Exit(FieldPlayer* player) { player->Steering()->SeekOff(); }

Wait

Quando estiver no estado Wait, um jogador ficará posicionado no alvo de seu comportamento de direcionamento. Se o jogador for empurrado para fora dessa posição por outro jogador, ele voltará para ela de novo.

Page 28: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Há um par de condições de saída para este estado:

• Se um jogador que espera se encontra mais avançado que um companheiro que está controlando a bola, ele vai passar uma mensagem para o companheiro com uma solicitação para ele para passar a bola. Isto é porque é desejável se colocar a bola como o mais avançado no campo possível e o mais rápido que se puder. Se for seguro, o companheiro fará o passe e o jogador em espera mudará de estado para receber a bola. • Se a bola ficar mais perto do jogador em espera do que de qualquer outro companheiro e não há um jogador receptor, este mudará de estado para ChaseBall.

void Wait::Execute(FieldPlayer* player) { //if the player has been jostled out of position, get back in position if (!player->AtTarget()) { player->Steering()->ArriveOn(); return; } else { player->Steering()->ArriveOff(); player->SetVelocity(Vector2D(0,0)); //the player should keep his eyes on the ball! player->TrackBall(); } //if this player's team is controlling AND this player is not the attacker //AND is farther up the field than the attacker he should request a pass. if ( player->Team()->InControl() && (!player->isControllingPlayer()) && player->isAheadOfAttacker() ) { player->Team()->RequestPass(player); return; } if (player->Pitch()->GameOn()) { //if the ball is nearer this player than any other team member AND //there is not an assigned receiver AND neither goalkeeper has //the ball, go chase it if (player->isClosestTeamMemberToBall() && player->Team()->Receiver() == NULL && !player->Pitch()->GoalKeeperHasBall()) { player->ChangeState(player, ChaseBall::Instance()); return; } }

}

Page 29: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

ReceiveBall

Um jogador entra no estado ReceiveBall quando ele processa uma mensagem Msg_ReceiveBall. Esta mensagem é enviada ao jogador receptor pelo jogador que fez o passe. O campo ExtraInfo do Telegram contém a posição alvo da bola para que o alvo de direcionamento do jogador receptor possa ser ajustado de acordo, permitindo o receptor ir para essa posição, pronto para interceptar a bola.

Sempre se deve ter apenas um jogador de cada time no estado de ReceiveBall - não é uma boa tática ter dois ou mais jogadores tentando interceptar o mesmo passe, assim a primeira coisa que o método Enter deste estado faz é atualizar os ponteiros apropriados do SoccerTeam para permitir que os outros membros do time os pesquisem se necessário.

Para criar uma jogada mais interessante e natural, há dois métodos de se receber uma bola. Um método usa o comportamento arrive para direcionar o jogador até a posição alvo da bola; o outro usa o comportamento pursuit para correr atrás da bola. Um jogador escolhe entre eles dependendo do valor ChanceOfUsingArriveTypeReceiveBehavior, levando em conta se há ou não um jogador adversário dentro de um raio de ameaça, e se o receptor está ou não posicionado no terço de campo mais próximo do gol do adversário (eu chamo esta área de "hot region", região quente).

void ReceiveBall::Enter(FieldPlayer* player) { //let the team know this player is receiving the ball player->Team()->SetReceiver(player); //this player is also now the controlling player player->Team()->SetControllingPlayer(player); //there are two types of receive behavior. One uses arrive to direct //the receiver to the position sent by the passer in its telegram. The //other uses the pursuit behavior to pursue the ball. //This statement selects between them dependent on the probability //ChanceOfUsingArriveTypeReceiveBehavior, whether or not an opposing //player is close to the receiving player, and whether or not the receiving //player is in the opponent's "hot region" (the third of the pitch closest //to the opponent's goal) const double PassThreatRadius = 70.0; if ((player->InHotRegion() || RandFloat() < Prm.ChanceOfUsingArriveTypeReceiveBehavior) && !player->Team()->isOpponentWithinRadius(player->Pos(), PassThreatRadius)) { player->Steering()->ArriveOn(); } else { player->Steering()->PursuitOn(); }

}

O método Execute é simples. Um jogador receptor de moverá para dentro de uma posição e permanecerá lá a menos que a bola de futebol fique dentro de uma distância especificada dele ou se seu time perdeu o controle da bola, assim o jogador mudará de estado para ChaseBall.

void ReceiveBall::Execute(FieldPlayer* player) { //if the ball comes close enough to the player or if his team loses control //he should change state to chase the ball if (player->BallWithinReceivingRange() || !player->Team()->InControl()) {

Page 30: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

player->ChangeState(player, ChaseBall::Instance()); return; } //the player's target must be continuously updated with the ball position //if the pursuit steering behavior is used to pursue the ball. if (player->Steering()->PursuitIsOn()) { player->Steering()->SetTarget(player->Ball()->Pos()); } //if the player has "arrived" at the steering target he should wait and //turn to face the ball if (player->AtTarget()) { player->Steering()->ArriveOff(); player->Steering()->PursuitOff(); player->TrackBall(); player->SetVelocity(Vector2D(0,0)); } }

KickBall

Se há uma coisa que jogadores de futebol fazem mais do que ficarem bêbados e se abraçarem, é chutar bolas de futebol. Ah sim. Eles adoram isso. Os jogadores de Futebol Simples não são diferentes. Bem, eu creio que eles não fiquem bêbados e se abracem, mas eles se divertem com um bom chute.

Um jogador de Futebol Simples deve ser capaz de controlar e chutar a bola de várias maneiras. Ele deve ser capaz de tentar chutar no gol do adversário, ter as habilidades necessárias para passar a bola para outro jogador e estar apto para driblar. Quando um jogador obtém o controle da bola ele deve selecionar a opção mais apropriada para usar na hora.

O estado KickBall implementa a lógica para chutes de gol e passes. Se por alguma razão um jogador não pode chutar ou se um passe não é necessário, o estado do jogador será mudado para Dribble. Um jogador não pode permanecer no estado de KickBall por mais que um ciclo de atualização; seja a bola chutada ou não, o jogador sempre mudará de estado através da lógica deste estado. Um jogador entra neste estado se a bola fica dentro da PlayerKickingDistance de sua posição.

Vamos ver o código de fonte:

void KickBall::Enter(FieldPlayer* player) { //let the team know this player is controlling player->Team()->SetControllingPlayer(player); //the player can only make so many kick attempts per second. if (!player->isReadyForNextKick()) { player->ChangeState(player, ChaseBall::Instance()); } }

O método Enter primeiro deixa o time saber que este jogador é o jogador com a bola e então checa se é permitido chutar a bola nesta etapa de atualização. Os jogadores são apenas permitidos a chutar a bola algumas vezes por segundo, em uma frequência armazenada na variável PlayerKickFrequency. Se o jogador não pode tentar chutar, seu estado é mudado para ChaseBall e ele continuará a perseguir a bola.

Page 31: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

O número de vezes que um jogador pode chutar a bola por segundo é restringido para prevenir anomalias no comportamento. Por exemplo, sem nenhuma restrição, uma situação pode ocorrer onde a bola é chutada, o jogador entra no estado de espera, e então, por causa da bola ainda estar em seu alcance de chute, o jogador a chuta outra vez. Por causa da maneira como a física da bola é tratada, isto pode resultar em um movimento não natural e estranho da bola.

void KickBall::Execute(FieldPlayer* player) { //calculate the dot product of the vector pointing to the ball //and the player's heading Vector2D ToBall = player->Ball()->Pos() - player->Pos(); double dot = player->Heading().Dot(Vec2DNormalize(ToBall)); //cannot kick the ball if the goalkeeper is in possession or if it's //behind the player or if there is already an assigned receiver. So just //continue chasing the ball if (player->Team()->Receiver() != NULL || player->Pitch()->GoalKeeperHasBall() || (dot<0) ) { player->ChangeState(player, ChaseBall::Instance()); return; }

Quando o método Execute é iniciado, o produto escalar entre a frente do jogador e o vetor apontando para bola é calculado para determinar se a bola está atrás ou na frente do jogador. Se a bola está atrás, ou se já há um jogador esperando para receber a bola, ou um dos goleiros têm a bola, o estado do jogador é mudado de modo que ele continua a seguir a bola.

Se o jogador é capaz de chutar a bola, a lógica do estado determina se há ou não a possibilidade de se fazer um gol. Afinal, o gol é o objetivo do jogo, assim naturalmente esta deve ser a primeira coisa a se considerar quando um jogador obtém o controle da bola.

/* Attempt a shot at the goal */ //the dot product is used to adjust the shooting force. The more //directly the ball is ahead of the player, the more forceful the kick double power = Prm.MaxShootingForce * dot;

Note como a potência do chute é proporcional ao quando a bola está diretamente na frente do jogador. Se a bola está situada ao lado, a potência com a qual o chute será feito é reduzida.

//if a shot is possible, this vector will hold the position along the //opponent's goal line the player should aim for. Vector2D BallTarget; //if it's determined that the player could score a goal from this position //OR if he should just kick the ball anyway, the player will attempt //to make the shot if (player->Team()->CanShoot(player->Ball()->Pos(), power, BallTarget) || (RandFloat() < Prm.ChancePlayerAttemptsPotShot)) {

Page 32: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

O método CanShoot determina se há um chute em potencial para o gol. (Você encontrará uma descrição detalhada do método CanShoot no fim deste capítulo.) Se há um chute em potencial, o CanShoot retornará true e armazenará a posição que jogador deve usar no vetor BallTarget. Se ele retorna false, nós checamos para ver se é ou não possível se fazer um pontapé "cosmético" (o BallTarget manterá a localização da última posição encontrada válida pelo CanShoot, assim nós sabemos que o chute vai falhar com certeza). A razão para se fazer um chute ocasional é dar mais vida ao jogo, fazer isto torna o jogo mais excitante ao observador humano; ele pode rapidamente ficar entediado se os jogadores de computador sempre marcarem gols quando tentarem. O chute aleatório ocasional introduz uma pequena incerteza e torna o jogo uma experiência muito mais agradável.

//add some noise to the kick. We don't want players who are //too accurate! The amount of noise can be adjusted by altering //Prm.PlayerKickingAccuracy BallTarget = AddNoiseToKick(player->Ball()->Pos(), BallTarget); //this is the direction the ball will be kicked Vector2D KickDirection = BallTarget - player->Ball()->Pos(); player->Ball()->Kick(KickDirection, power);

A bola é chutada ao se chamar o método SoccerBall::Kick com a direção desejada. Por causa de jogadores perfeitos fazerem chutes perfeitos todo o tempo tornando o futebol muito não-realista, uma quantia de aleatoriedade é adicionada a direção do chute. Isto garante que os jogadores ocasionalmente farão chutes ruins.

//change state player->ChangeState(player, Wait::Instance()); player->FindSupport(); return; }

Uma vez que a bola foi chutada, o jogador muda para o estado Wait e solicita o suporte de outro companheiro chamando o método PlayerBase::FindSupport. O FindSupport "pede" para o time determinar o melhor companheiro para fornecer o suporte, e para envia uma solicitação via o sistema de mensagens para entrar no estado de SupportAttacker. O estado então retorna ao controle do método Update do jogador.

Se não é possível se fazer um chute a gol, o jogador considera um passe. Um jogador apenas considerará esta opção se ele é ameaçado por um jogador adversário. Um jogador é considerado como ameaçado por outro quando os dois estão a menos que do que o valor de PlayerComfortZone em pixels um do outro e o adversário está na frente do plano de frente do jogador. O valor padrão é determinado no params.ini em 60 pixels. Um valor maior resultará nos jogadores fazendo mais passes e um valor menor resultará em mais disputas pela bola entre jogadores.

/* Attempt a pass to a player */ //if a receiver is found, this will point to it PlayerBase* receiver = NULL; power = Prm.MaxPassingForce * dot; //test if there are any potential candidates available to receive a pass

Page 33: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

if (player->isThreatened() && player->Team()->CanPass(player, receiver, BallTarget, power, Prm.MinPassDist)) { //add some noise to the kick BallTarget = AddNoiseToKick(player->Ball()->Pos(), BallTarget); Vector2D KickDirection = BallTarget - player->Ball()->Pos(); player->Ball()->Kick(KickDirection, power); //let the receiver know a pass is coming Dispatch->DispatchMsg(SEND_MSG_IMMEDIATELY, player->ID(), receiver->ID(), Msg_ReceiveBall, NO_SCOPE, &BallTarget);

O método FindPass examina todos os jogadores do time para encontrar o companheiro mais distante na frente do campo em uma posição onde um passe possa ser feito sem ser interceptado. (Uma descrição detalhada do FindPass pode ser encontrada no fim deste capítulo.) Se um passe válido é encontrado, o chute é feito (com a aleatoriedade já adicionada como antes), e o receptor é notificado pelo envio de uma mensagem para mudar de estado para ReceiveBall.

//the player should wait at his current position unless instructed //otherwise player->ChangeState(player, Wait::Instance()); player->FindSupport(); return; }

Se a lógica de jogo flui para este ponto, então nem um passe apropriado e nem uma tentativa de gol foram encontradas. Como o jogador ainda tem a bola, ele entra no estado de Dribble. (Porém lembre-se que não é só esta a maneira de se fazer passes – os companheiros de equipe podem solicitar passes aos jogadores enviando a eles a mensagem apropriada).

//cannot shoot or pass, so dribble the ball upfield else { player->FindSupport(); player->ChangeState(player, Dribble::Instance()); } }

Dribble

O verbo dribble em inglês significa “babar” e o substantivo significa “baba”… mas a palavra também foi adotada pelo jogo de futebol para descrever a arte de mover uma bola ao longo do campo em uma série de chutes e batidas. Usando esta habilidade, um jogador é capaz de girar ou se mover agilmente ao redor de um adversário enquanto retém o controle da bola. (N.T. No Brasil o termo drible é entendido

Page 34: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

apenas como o ato de um jogador despistar o adversário quando este se aproxima para tomar a bola, como ao se fazer a finta. No inglês o termo drible é mais amplo, se referindo ao que o termo brasileiro se refere e o ato de simplesmente conduzir a bola pelo campo).

Por causa de um dos exercícios no fim deste capítulo ser para você tentar melhorar esta habilidade, eu apenas implementei um método simples de driblar, dando a um jogador somente a capacidade de se mover ao longo do jogo em uma velocidade razoável.

O método Enter simplesmente deixa o resto do time saber que o jogador que está driblado é o jogador em controle da bola.

void Dribble::Enter(FieldPlayer* player) { //let the team know this player is controlling player->Team()->SetControllingPlayer(player); }

O método Execute contém a maioria da lógica da IA. Primeiro, um teste é feito para ver se a bola está entre o jogador e sua área de gol (atrás do jogador). Esta situação é indesejável, pois o jogador deseja mover a bola o mais avançado no campo possível. Portanto o jogador deve girar enquanto ainda retém o controle da bola. Para fazer isso, os jogadores fazem uma série de chutes muito pequenos em uma direção

de (45 graus) em relação a sua frente. Depois de fazer cada pequeno chute, o jogador muda de estado para ChaseBall. Quando feito várias vezes em rápidas sucessões, isto tem o efeito de girar o jogador e a bola até que eles estejam de frente para a direção correta (para o gol do adversário).

Se a bola está posicionada na frente do jogador, o jogador vai empurrá-la a uma distância curta e então mudar seu estado para ChaseBall para seguir esta.

void Dribble::Execute(FieldPlayer* player) { double dot = player->Team()->HomeGoal()->Facing().Dot(player->Heading()); //if the ball is between the player and the home goal, it needs to swivel //the ball around by doing multiple small kicks and turns until the player //is facing in the correct direction if (dot < 0) { //the player's heading is going to be rotated by a small amount (Pi/4) //and then the ball will be kicked in that direction Vector2D direction = player->Heading(); //calculate the sign (+/–) of the angle between the player heading and the //facing direction of the goal so that the player rotates around in the //correct direction double angle = QuarterPi * -1 * player->Team()->HomeGoal()->Facing().Sign(player->Heading()); Vec2DRotateAroundOrigin(direction, angle); //this value works well when the player is attempting to control the //ball and turn at the same time const double KickingForce = 0.8; player->Ball()->Kick(direction, KickingForce); } //kick the ball down the field else

Page 35: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

{ player->Ball()->Kick(player->Team()->HomeGoal()->Facing(), Prm.MaxDribbleForce); } //the player has kicked the ball so he must now change state to follow it player->ChangeState(player, ChaseBall::Instance()); return; }

SupportAttacker

Quando um jogador obtém o controle da bola ele imediatamente solicita suporte ao chamar o método PlayerBase::FindSupport. O FindSupport examina cada membro do time para determinar qual jogador está mais próximo do melhor ponto de suporte (calculado pelo SupportSpotCalculator) e então envia uma mensagem ao jogador para que ele mude de estado para SupportAttacker.

Ao entrar neste estado, o jogador liga o comportamento arrive é muda seu alvo de direcionamento para a localização do BSS.

void SupportAttacker::Enter(FieldPlayer* player) { player->Steering()->ArriveOn(); player->Steering()->SetTarget(player->Team()->GetSupportSpot()); }

Há um número de condições que fazem a lógica do método Execute. Vamos ver cada uma delas.

void SupportAttacker::Execute(FieldPlayer* player) { //if his team loses control go back home if (!player->Team()->InControl()) { player->ChangeState(player, ReturnToHomeRegion::Instance()); return; }

Se time do jogador perde o controle, o jogador deve mudar de estado para voltar para a sua posição padrão.

//if the best supporting spot changes, change the steering target if (player->Team()->GetSupportSpot() != player->Steering()->Target()) { player->Steering()->SetTarget(player->Team()->GetSupportSpot()); player->Steering()->ArriveOn(); }

Como você viu, a posição do melhor ponto de suporte muda segundo muitos fatores, assim qualquer jogador de suporte deve sempre certificar-se que seu alvo de direcionamento é mantido atualizado com a posição mais recente.

Page 36: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

//if this player has a shot at the goal AND the attacker can pass //the ball to him the attacker should pass the ball to this player if(player->Team()->CanShoot(player->Pos(), Prm.MaxShootingForce) ) { player->Team()->RequestPass(player); }

Um jogador de suporte gasta a maioria de seu tempo na metade de campo adversária. Portanto ele deve sempre estar atento à possibilidade de um chute no gol do adversário. Essas poucas linhas usam o método SoccerTeam::CanShoot para determinar se há um chute a gol em potencial. Se o resultado é afirmativo, o jogador solicita um passe ao jogador que controla a bola. Assim, se o RequestPass determina que é possível um passe do jogador no controle para este jogador sem ser interceptado, uma mensagem Msg_ReceiveBall é enviada e o jogador mudará de estado para estar em prontidão para receber a bola.

//if this player is located at the support spot and his team still has //possession, he should remain still and turn to face the ball if (player->AtTarget()) { player->Steering()->ArriveOff(); //the player should keep his eyes on the ball! player->TrackBall(); player->SetVelocity(Vector2D(0,0)); //if not threatened by another player request a pass if (!player->isThreatened()) { player->Team()->RequestPass(player); } } }

Finalmente, se o jogador de suporte alcança a posição do BSS, ele espera e garante que está sempre de frente para a bola. Se não há adversários dentro de sua vizinhança e se ele não se sente ameaçado, ele solicita um passe do jogador em controle da bola.

Note Note que uma requisição por passe não significa que o passe será feito. Um passe somente é feito se ele é considerado seguro de interceptações.

3.3 A classe dos goleiros: GoalKeeper O trabalho de um goleiro é impedir que a bola atravesse a linha de gol. Para fazer isso, um goleiro

utiliza um conjunto de habilidades diferentes das de um jogador de campo e, portanto, é implementado como uma classe separada, GoalKeeper. Um goleiro se moverá para frente e para trás na área até que a bola fique dentro de um alcance específico, nesse ponto ele se moverá em direção à bola em uma tentativa de interceptá-la. Se um goleiro obtém a posse da bola, ele a coloca em jogo novamente chutando-a para um membro de seu time apropriado.

Um goleiro de Futebol Simples é designado à região que sobrepõe o gol de seu time. Portanto o goleiro vermelho é designado para a região 16 e o goleiro azul para a região 1.

Page 37: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

3.3.1 O movimento do goleiro

Além de ter um conjunto completamente diferente de estados do de um jogador de campo, a classe GoalKeeper deve empregar um tipo de movimento ligeiramente diferente. Se você observar um goleiro jogando futebol você notará que ele está quase sempre olhando diretamente para a bola e muitos de seus movimentos são do lado para o lado, em vez de para frente e para trás como um jogador de campo. Por causa de uma entidade usar os comportamentos de direcionamento com a velocidade alinhada a frente, um goleiro utiliza outro vetor, o m_vLookAt, para indicar a sua direção de frente, e é este vetor que é passado a função Render para transformar os vértices do goleiro. O resultado final é uma entidade que está sempre de frente para a bola e pode se mover lateralmente assim como ao longo de seu eixo de frente. Veja a Figura 4.13.

Figura 4.13: O movimento do goleiro

3.3.2 Os estados do goleiro

O goleiro utiliza cinco estados. São eles:

• GlobalKeeperState (o estado global do goleiro); • TendGoal (proteger o gol); • ReturnHome (retornar a região padrão); • PutBallBackInPlay (colocar a bola em jogo); • InterceptBall (interceptar a bola).

Vamos ver cada um destes estados em detalhes para ver que o que faz um goleiro a cada passo de atualização.

GlobalKeeperState

Como o estado global do jogador de campo, o estado global do goleiro é utilizado para tratar todas as mensagens que ele pode receber. Um goleiro unicamente escuta duas mensagens: Msg_GoHome e Msg_ReceiveBall.

Page 38: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Eu acho que o código pode falar por si mesmo:

bool GlobalKeeperState::OnMessage(GoalKeeper* keeper, const Telegram& telegram) { switch(telegram.Msg) { case Msg_GoHome: { keeper->SetDefaultHomeRegion(); keeper->ChangeState(keeper, ReturnHome::Instance()); } break; case Msg_ReceiveBall: { keeper->ChangeState(keeper, InterceptBall::Instance()); } break; }//end switch return false; }

TendGoal

Quando no estado de TendGoal, um goleiro se moverá lateralmente através da frente da área do gol, tentando manter seu corpo entre a bola e uma posição localizada atrás da linha de gol. Aqui está o método Enter do estado:

void TendGoal::Enter(GoalKeeper* keeper) { //turn interpose on keeper->Steering()->InterposeOn(Prm.GoalKeeperTendingDistance); //interpose will position the agent between the ball position and a target //position situated along the goal mouth. This call sets the target keeper->Steering()->SetTarget(keeper->GetRearInterposeTarget()); }

Primeiro, o comportamento de direcionamento interpose é ativado. O interpose retornará uma força de direcionamento que tentará posicionar o goleiro entre a bola e uma posição situada atrás do gol. Esta posição é determinada pelo método GoalKeeper::GetRearInterposeTarget, que designa uma posição ao alvo proporcionalmente espaçada entre o comprimento da linha de gol e a posição da bola em relação a largura do campo. (Eu espero que a sentença faça sentido, pois eu agonizei por quase dez minutos e isso foi o melhor que eu pude fazer!) Felizmente a Figura 4.14 ajudará você a compreender. Da perspectiva do goleiro, a bola está espaçada à esquerda do campo, o mesmo espaçamento esquerdo proporcional a linha de gol é o alvo de trás do interpose. Como a bola move-se para a direita do goleiro, o alvo de trás do interpose move-se à direita da área do gol junto com a bola.

Page 39: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.14: Proteção do gol

A seta preta de duas pontas indica a distância que o goleiro tenta manter entre ele e a rede. Este valor é definido no params.ini pelo GoalKeeperTendingDistance.

Vamos ver o método Execute.

void TendGoal::Execute(GoalKeeper* keeper) { //the rear interpose target will change as the ball's position changes //so it must be updated each update step keeper->Steering()->SetTarget(keeper->GetRearInterposeTarget()); //if the ball comes in range the keeper traps it and then changes state //to put the ball back in play if (keeper->BallWithinPlayerRange()) { keeper->Ball()->Trap(); keeper->Pitch()->SetGoalKeeperHasBall(true); keeper->ChangeState(keeper, PutBallBackInPlay::Instance()); return; } //if ball is within a predefined distance, the keeper moves out from //position to try to intercept it. if (keeper->BallWithinRangeForIntercept()) { keeper->ChangeState(keeper, InterceptBall::Instance()); }

Primeiro, um teste é feito para ver se a bola é está o bastante perto do goleiro para ele agarrá-la. Se sim, a bola pega e o goleiro muda de estado para PutBallBackInPlay. Asseguir, se a bola está dentro do

Page 40: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

alcance de interceptação, mostrado na Figura 4.14 como a área em cinza claro e definido no params.ini como GoalKeeperInterceptRange, o goleiro muda de estado para InterceptBall.

//if the keeper has ventured too far away from the goal line and there //is no threat from the opponents he should move back toward it if (keeper->TooFarFromGoalMouth() && keeper->Team()->InControl()) { keeper->ChangeState(keeper, ReturnHome::Instance()); return; } }

Ocasionalmente, a mudança de estados de InterceptBall para TendGoal, pode deixar o goleiro muito afastado do gol. As últimas linhas do código testam essa eventualidade e, se for seguro fazer, mudam o estado do goleiro para ReturnHome.

O método TendGoal::Exit é muito simples; ele somente desativa o comportamento de direcionamento interpose.

void TendGoal::Exit(GoalKeeper* keeper) { keeper->Steering()->InterposeOff(); }

ReturnHome

O estado ReturnHome move o goleiro de volta a sua região padrão. Quando a região padrão é alcançada ou se os adversários ganham o controle da bola, o goleiro entra no estado de TendGoal.

void ReturnHome::Enter(GoalKeeper* keeper) { keeper->Steering()->ArriveOn(); } void ReturnHome::Execute(GoalKeeper* keeper) { keeper->Steering()->SetTarget(keeper->HomeRegion()->Center()); //if close enough to home or the opponents get control over the ball, //change state to tend goal if (keeper->InHomeRegion() || !keeper->Team()->InControl()) { keeper->ChangeState(keeper, TendGoal::Instance()); } } void ReturnHome::Exit(GoalKeeper* keeper) { keeper->Steering()->ArriveOff(); }

PutBallBackInPlay

Quando um goleiro ganha a posse da bola, ele entra no estado de PutBallBackInPlay. Um par de coisas acontece no método Enter deste estado. Primeiro, o goleiro deixa seu time saber que ele têm a bola,

Page 41: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

então todo os jogadores de campo são instruidos a voltar para suas regiões padrão via a chamada ao método SoccerTeam::ReturnAllFieldPlayersToHome. Isso garante que há bastante espaço livre entre o goleiro e os jogadores para fazer um tiro de meta.

void PutBallBackInPlay::Enter(GoalKeeper* keeper) { //let the team know that the keeper is in control keeper->Team()->SetControllingPlayer(keeper); //send all the players home keeper->Team()->Opponents()->ReturnAllFieldPlayersToHome(); keeper->Team()->ReturnAllFieldPlayersToHome(); }

O goleiro agora espera até que todos os outros jogadores movam-se o longe o bastante para que ele possa fazer um passe limpo para um de seus companheiros de time. Tão logo que uma oportunidade aconteça, o goleiro passa a bola, enviando uma mensagem ao jogador receptor para deixá-lo saber que a bola está a seu caminho, e então volta ao seu estado de proteger o gol.

void PutBallBackInPlay::Execute(GoalKeeper* keeper) { PlayerBase* receiver = NULL; Vector2D BallTarget; //test if there are players farther forward on the field we might //be able to pass to. If so, make a pass. if (keeper->Team()->FindPass(keeper, receiver, BallTarget, Prm.MaxPassingForce, Prm.GoalkeeperMinPassDist)) { //make the pass keeper->Ball()->Kick(Vec2DNormalize(BallTarget - keeper->Ball()->Pos()), Prm.MaxPassingForce); //goalkeeper no longer has ball keeper->Pitch()->SetGoalKeeperHasBall(false); //let the receiving player know the ball's comin' at him Dispatcher->DispatchMsg(SEND_MSG_IMMEDIATELY, keeper->ID(), receiver->ID(), Msg_ReceiveBall, &BallTarget); //go back to tending the goal keeper->GetFSM()->ChangeState(TendGoal::Instance()); return; } keeper->SetVelocity(Vector2D()); }

Page 42: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

InterceptBall

Um goleiro tentará interceptar a bola se os adversários têm controle dela e se eles entrarem em uma "área de ameaça" - a área cinza mostrada na Figura 4.15. Ele usa o comportamento de direcionamento pursuit para ir até a bola.

Figura 4.15: A “área de ameaça” do goleiro

void InterceptBall::Enter(GoalKeeper* keeper) { keeper->Steering()->PursuitOn(); }

Como o goleiro move-se para frente, em direção a bola, ele se mantém testando a distância do gol para garantir-se que não andou muito longe. Se o goleiro se encontra fora da área do gol ele muda de estado para ReturnHome. Há uma exceção para isto: Se o goleiro está fora da área do gol mas ainda está perto do jogador no campo com a bola, ele se mantém perseguindo esta.

Se a bola está dentro do alcance do goleiro, ele pára a bola usando o método SoccerBall::Trap, deixado todos saberem que ele está em posse, e muda de estado para colocar a bola novamente em jogo.

void InterceptBall::Execute(GoalKeeper* keeper) { //if the goalkeeper moves too far away from the goal he should return to his //home region UNLESS he is the closest player to the ball, in which case //he should keep trying to intercept it. if (keeper->TooFarFromGoalMouth() && !keeper->ClosestPlayerOnPitchToBall()) { keeper->ChangeState(keeper, ReturnHome::Instance()); return; } //if the ball becomes in range of the goalkeeper's hands he traps the //ball and puts it back in play

Page 43: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

if (keeper->BallWithinPlayerRange()) { keeper->Ball()->Trap(); keeper->Pitch()->SetGoalKeeperHasBall(true); keeper->ChangeState(keeper, PutBallBackInPlay::Instance()); return; } }

O método Exit do InterceptBall desliga o comportamneto pursuit.

3.4 Métodos importantes usados pela IA Um número de métodos da classe SoccerTeam são utilizados frequentemente pela IA, assim uma

descrição completa é importante para sua completa compreensão de como a IA funciona. Com isto em mente, eu gastarei as próximas páginas levando você através de cada um, passo a passo. Vista seu traje de matemático novamente…

3.4.1 SoccerTeam::isPassSafeFromAllOpponents

Um jogador de futebol, seja qual for seu papel no jogo, está continuamente avaliando sua posição em relação aos que estão ao seu redor e fazendo julgamentos baseados nessas avaliações. Um cálculo que a IA usa frequentemente é determinar se um passe de posição A para uma B pode ser interceptado por qualquer jogador adversário em qualquer ponto da trajetória da bola. Ela precisa desta informação para julgar se é ou não possível se fazer o passe, se ela deve solicitar um passe do atacante atual, ou se há uma oportunidade de marcar um gol.

Considere a Figura 4.16. O jogador A gostaria de saber se pode passar a bola para o jogador B sem esta ser interceptada por qualquer dos adversários W, X, Y ou Z. Para determinar isto se deve considerar cada adversário por vez e calcular se uma interceptação é possível. No SoccerTeam::isPassSafeFromOpponent é onde todo esse trabalho é feito.

Page 44: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.16: O jogador A passando diretamente para o jogador B

O método toma como parâmetros a posição inicial e final do passe, um ponteiro para o adversário a ser considerado, um ponteiro para o receptor da bola e a força com que a bola será chutada. O método é chamado para cada adversário do time adversário pelo método SoccerTeam::isPassSafeFromAllOpponents.

bool SoccerTeam::isPassSafeFromOpponent(Vector2D from, Vector2D target, const PlayerBase* const receiver, const PlayerBase* const opp, double PassingForce)const { //move the opponent into local space. Vector2D ToTarget = target - from; Vector2D ToTargetNormalized = Vec2DNormalize(ToTarget); Vector2D LocalPosOpp = PointToLocalSpace(opp->Pos(), ToTargetNormalized, ToTargetNormalized.Perp(), from);

A primeira etapa é assumir que A está olhando diretamente para a posição "alvo" (neste exemplo, a posição do jogador B) e move o adversário para dentro do sistema de coordenadas locais de A. A Figura 4.17 mostra como todos os jogadores adversários da Figura 4.16 são posicionados quando movidos para dentro do espaço local do jogador A.

Page 45: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.17: Jogadores transformados no espaço local de A

//if opponent is behind the kicker then pass is considered okay (this is //based on the assumption the ball is going to be kicked with a //velocity greater than the opponent's max velocity) if ( LocalPosOpp.x < 0 ) { return true; }

Pode-se supôr que a bola sempre será chutada com uma velocidade inicial maior que a velocidade máxima de um jogador. Se isto for verdadeiro, qualquer adversário situado atrás do eixo local y do jogador que vai chutar pode ser descartado de considerações posteriores. Portanto, no exemplo da Figura 4.17, o W pode ser descartado.

Asseguir, qualquer adversário mais afastado do que A do alvo é testado. Se a situação é como mostrada na Figura 4.16 e a localização do alvo do passe é situada nos pés do receptor, então qualquer adversário mais afastado que isso pode ser imediatamente descartado. Entretanto, este método também é chamado para testar a validade dos passes em potenciais que estão situadas em ambos os lados do jogador receptor, tal como mostrado na Figura 4.18.

Page 46: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.18: Passes para ambos os lados do jogador receptor também são possíveis.

Nesta situação um teste adicional deve ser faito para testar se o adversário está mais afastado da posição do alvo do que o receptor. Se sim, então o adversário pode ser descartado.

//if the opponent is farther away than the target we need to consider if //the opponent can reach the position before the receiver. if (Vec2DDistanceSq(from, target) < Vec2DDistanceSq(opp->Pos(), from)) { //this condition is here because sometimes this function may be called //without reference to a receiver. (For example, you may want to find //out if a ball can reach a position on the field before an opponent //can get to it) if (receiver) { if (Vec2DDistanceSq(target, opp->Pos()) > Vec2DDistanceSq(target, receiver->Pos())) { return true; } } else { return true; } }

A melhor chance de um adversário situado entre as duas condições prévias têm de interceptar a bola é correr ao ponto onde a trajetória da bola é perpendicular a posição dele, mostrado como os pontos Yp e Xp para os jogadores Y e X respectivamente na Figura 4.19.

Page 47: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.19: Teste de pontos de interceptação

Para interceptar a bola, um adversário deve ser capaz de alcançar este ponto antes da bola passar ali. Para mostrar a você como calcular se isto é possível, vamos examinar o caso do adversário Y.

Primeiramente, o tempo levado para bola cobrir a distância de A a Yp é determinada ao se chamar o SoccerBall::TimeToCoverDistance. Este método foi descrito em detalhes mais cedo, assim você deveria compreender como ele funciona. Dado este tempo, é possível se calcular o quanto longe o adversário Y pode andar antes da bola alcançar o ponto Yp (tempo * velocidade). Eu chamo esta distância de alcance do Y, porque esta é a distância que Y pode percorrer em qualquer direção na quantia especificada de tempo. Neste alcance deve-se adicionar o raio da bola de futebol e o raio do círculo limitante do jogador. Este valor de alcance agora representa o "alcance" do jogador no tempo que a bola leva para alcançar o Yp.

Os alcances de Y e X são mostrados pelos círculos pontilhados na Figura 4.20. Se o círculo descrito por o alcance de um adversário intersecta o eixo x, isto indica que o adversário é capaz de interceptar a bola dentro do tempo fornecido. Portanto, neste exemplo, pode-se concluir que o adversário Y não é uma ameaça, mas o X é.

Page 48: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.20: Alcance de movimento dos adversários

Aqui o último trecho de código para você examinar.

//calculate how long it takes the ball to cover the distance to the //position orthogonal to the opponent's position double TimeForBall = Pitch()->Ball()->TimeToCoverDistance(Vector2D(0,0), Vector2D(LocalPosOpp.x, 0), PassingForce); //now calculate how far the opponent can run in this time double reach = opp->MaxSpeed() * TimeForBall + Pitch()->Ball()->BRadius()+ opp->BRadius(); //if the distance to the opponent's y position is less than his running //range plus the radius of the ball and the opponent's radius, then the //ball can be intercepted if ( fabs(LocalPosOpp.y) < reach ) { return false; } return true; }

Page 49: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Nota Técnicamente falando, os alcances mostrados na Figura 4.20 estão incorretos. Eu assumi que o adversário leva um tempo zero para rotacionar ao ponto de interceptação. Para ser preciso, o tempo necessário para se rotacionar deve ser levado em consideração, nesse caso o alcance é decrito como uma elipse em vez de um círculo. Assim:

Obviamente, é mais custoso se calcular intersecções elipse-linha, por isso os círculos são usados.

3.4.2 SoccerTeam::CanShoot

Uma habilidade muito importante que um jogador de futebol tem, certamente, é a capacidade de marcar gols. Um jogador que está em posse da bola pode pesquisar o método SoccerTeam::CanShoot para ver se é capaz de marcar um meta da posição atual da bola e um valor representando a força com que o jogador vai chutar a bola. Se o método determina que o jogador é capaz de fazer um gol, ele retornará true e armazena a posição que jogador deve chutar em um vetor, ShotTarget.

O método funciona ao aleatóriamente selecionar um número de posições ao longo da linha de gol e testar cada uma delas para ver se a bola pode ser chutada nesse ponto sem ser interceptada por qualquer jogador adversário. Veja a Figura 4.22.

Page 50: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Figura 4.22: Alvos de chute selecionados aleatóriamente

Aqui está o código. (Note como é garantido que a força do chute é o bastante para passar a linha do gol).

bool SoccerTeam::CanShoot(Vector2D BallPos, double power Vector2D& ShotTarget)const { //the number of randomly created shot targets this method will test int NumAttempts = Prm.NumAttemptsToFindValidStrike; while (NumAttempts--) { //choose a random position along the opponent's goal mouth. (making //sure the ball's radius is taken into account) ShotTarget = OpponentsGoal()->Center(); //the y value of the shot position should lie somewhere between the two //goal posts (taking into consideration the ball diameter) int MinYVal = OpponentsGoal()->LeftPost().x + Pitch()->Ball()->BRadius(); int MaxYVal = OpponentsGoal()->RightPost().x - Pitch()->Ball()->BRadius(); ShotTarget.x = RandInt(MinYVal, MaxYVal); //make sure striking the ball with the given power is enough to drive //the ball over the goal line. double time = Pitch()->Ball()->TimeToCoverDistance(BallPos, ShotTarget, power); //if so, this shot is then tested to see if any of the opponents //can intercept it. if (time > 0)

Page 51: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

{ if (isPassSafeFromAllOpponents(BallPos, ShotTarget, NULL, power)) { return true; } } } return false; }

3.4.3 SoccerTeam::FindPass

O método FindPass é chamado por um jogador para determinar se um passe para um companheiro é possível e, se sim, de qual posição e qual companheiro é o melhor para passar a bola.

O método toma como parâmetros um ponteiro ao jogador solicitante da passagem; uma referência para um ponteiro que apontará para o jogador receptor (se um passe é encontrado); uma referência para um vetor, PassTarget, o qual terá a posição para onde o passe será feito; a força com a qual a bola deve ser chutada; e um valor representando a distância mínima que um receptor deve estar do jogador passante, MinPassingDistance.

O método então itera através de todos os campanheiros possíveis de passe e chama o GetBestPassToReceiver para os que estão a menos que a MinPassingDistance do passante. O GetBestPassToReceiver examina um número de localizações de passes em potencial para companheiros em consideração e, se um passe pode ser feita de modo seguro, ele armazena a melhor oportunidade no vetor BallTarget.

Depois que todos os companheiros foram considerados, se um passe válido foi encontrado, o que está mais perto do gol adversário é designado ao PassTarget e o ponteiro ao jogador que deve receber o passe é designado ao receptor. O método então retorna true.

Aqui está o código para você para examinar.

bool SoccerTeam::FindPass(const PlayerBase*const passer, PlayerBase*& receiver, Vector2D& PassTarget, double power, double MinPassingDistance)const { std::vector<PlayerBase*>::const_iterator curPlyr = Members().begin(); double ClosestToGoalSoFar = MaxDouble; Vector2D BallTarget; //iterate through all this player's team members and calculate which //one is in a position to be passed the ball for (curPlyr; curPlyr != Members().end(); ++curPlyr) { //make sure the potential receiver being examined is not this player //and that it's farther away than the minimum pass distance if ( (*curPlyr != passer) && (Vec2DDistanceSq(passer->Pos(), (*curPlyr)->Pos()) > MinPassingDistance*MinPassingDistance)) { if (GetBestPassToReceiver(passer, *curPlyr, BallTarget, power)) { //if the pass target is the closest to the opponent's goal line found //so far, keep a record of it double Dist2Goal = fabs(BallTarget.x - OpponentsGoal()->Center().x);

Page 52: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

if (Dist2Goal < ClosestToGoalSoFar) { ClosestToGoalSoFar = Dist2Goal; //keep a record of this player receiver = *curPlyr; //and the target PassTarget = BallTarget; } } } }//next team member if (receiver) return true; else return false; }

3.4.4 SoccerTeam::GetBestPassToReceiver

Dado o jogador que vai passar a bola e o receptor, este método examina as várias posições diferentes situadas ao redor do receptor para testar se um passe pode ser feito seguramente para alguma delas. Se um passe pode ser feito, o método armazena o melhor passe - o com a posição mais perto do gol do adversário - no parâmetro PassTarget, e retorna true.

Deixe-me explicar a você através do algoritmo, usando a situação mostrada na Figura 4.23.

Figura 4.23: Uma típica situação de passe

Page 53: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

bool SoccerTeam::GetBestPassToReceiver(const PlayerBase* const passer, const PlayerBase* const receiver, Vector2D& PassTarget, double power)const {

Primeiramente, o método calcula quanto tempo à bola levará para alcançar a posição do receptor e imediatamente retorna false se é impossível ela alcançar este ponto com a força do chute.

//first, calculate how much time it will take for the ball to reach //this receiver double time = Pitch()->Ball()->TimeToCoverDistance(Pitch()->Ball()->Pos(), receiver->Pos(), power); //return false if ball cannot reach the receiver after having been //kicked with the given power if (time <= 0) return false;

Agora é possível se calcular o quanto longe o receptor é capaz de se mover dentro deste tempo, usando a equação ∆x = v∆t. Os pontos de interceptação das tangentes da bola com este círculo de alcance representam os limites do alcance de passe do receptor. Veja a Figura 4.24.

Figura 4.24: Os limites do alcance do receptor

//the maximum distance the receiver can cover in this time double InterceptRange = time * receiver->MaxSpeed();

Page 54: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Em outras palavras, assumindo que não se desperdiçará tempo girando ou acelerando até a velocidade máxima, o receptor pode alcançar as posições ip1 ou ip2 somente a tempo de interceptar a bola. Entretanto na realidade, esta distância é frequêntemente muito grande, especialmente se a distância entre o receptor e passante alcança os limites de onde um passe pode ser feito (frequêntemente ip1 e ip2 acabarão situados fora da área jogável). É muito melhor considerar passes para posições que ficam bem dentro desta região. Isso reduz a oportunidade de adversários interceptarem a bola e também torna o passe menos sujeito as dificuldades imprevistas (tal como o receptor ter que manobrar ao redor de adversários para alcançar o alvo do passe). Isso também dá ao receptor algum "espaço" e algum tempo, com os quais ele pode alcançar a posição e então se orientar apropriadamente para receber o passe. Com isto em mente, o alcance de interceptação é reduzido para aproximadamente um terço de seu tamanho original. Veja a Figura 4.25.

Figura 4.25: O alcance do receptor é reduzido.

//Scale down the intercept range const double ScalingFactor = 0.3; InterceptRange *= ScalingFactor;

Como você pode ver, isso parece muito mais razoável e mais parecido ao tipo de alcance de passe que um jogador de futebol humano poderia considerar. O próximo passo é calcular as posições de ip1 e ip2. Essas serão consideradas como alvos de passe em potencial. Em adição, o método também considerará uma passagem diretamente para a localização atual do receptor. Essas três posições são armazenadas no array Passes.

//calculate the pass targets that are positioned at the intercepts //of the tangents from the ball to the receiver's range circle. Vector2D ip1, ip2; GetTangentPoints(receiver->Pos(),

Page 55: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

InterceptRange, Pitch()->Ball()->Pos(), ip1, ip2); const int NumPassesToTry = 3; Vector2D Passes[NumPassesToTry] = {ip1, receiver->Pos(), ip2};

Finalmente, o método itera através de cada passe potencial para garantir que a posição é localizada dentro da área jogável e para garantir que ela é segura de uma tentativa de intercepção por quaisquer adversários.

O loop toma uma nota do melhor passe válido que ela examina e retorna o resultado de acordo.

// this pass is the best found so far if it's: // // 1. Farther upfield than the closest valid pass for this receiver // found so far // 2. Within the playing area // 3. Cannot be intercepted by any opponents double ClosestSoFar = MaxDouble; bool bResult = false; for (int pass=0; pass<NumPassesToTry; ++pass) { double dist = fabs(Passes[pass].x - OpponentsGoal()->Center().x); if ((dist < ClosestSoFar) && Pitch()->PlayingArea()->Inside(Passes[pass]) && isPassSafeFromAllOpponents(Pitch()->Ball()->Pos(), Passes[pass], receiver, power)) { ClosestSoFar = dist; PassTarget = Passes[pass]; bResult = true; } } return bResult; }

Page 56: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

4. Uso de estimativas e suposições Você provavelmente notou que eu usei muitas estimativas e suposições nos cálculos descritos neste capítulo. A princípio, isso pode parecer como uma coisa ruim, pois, como programadores, nós somos utilizados para fazer tudo funcionar "como é", como relógios automáticos perfeitos.

Algumas vezes, entretanto, é benéfico se projetar a IA de seus jogos de tal maneira que ela cometa erros ocasionais. Isto na criação de IA para jogos de computador pode ser uma Boa Coisa. Por quê? Porque isso é mais realista. Seres humanos cometem erros e maus julgamentos o tempo todo e, portanto, o erro ocasional feito pela IA torna a experiência de entretenimento muito melhor na perspectiva de um jogador humano.

Há duas maneiras de se induzir erros. A primeira é fazer a IA "perfeita" e depois torná-la burra. A segundo é permitir que "erros" sejam cometidos ao se fazer suposições e estimativas quando se projeta o algoritmo que a IA usa. Você viu ambos estes métodos sendo utilizados no Futebol Simples. Um exemplo do primeiro é quando uma perturbação aleatória é utilizada para introduzir uma pequena quantia de erro na direção em que a bola é chutada. Um exemplo do último é onde um círculo é usado ao invés de elipses para descrever o alcance de interceptação de um adversário.

Quando decidir como criar erros e incertezas em sua IA, você deve examinar cada algoritmo apropriado cuidadosamente. Meu conselho é: Se um algoritmo é fácil de codificar e não requer muito tempo de processador, faça ele da menira "correta", faça-o perfeito e então o torne burro depois. Entretanto, veja se não é possível se fazer quaisquer suposições ou estimativas que ajudem a reduzir a complexidade do algoritmo. Se seu algoritmo pode ser simplificado dessa forma, codifique-o assim, então o teste para garantir a IA funciona satisfatóriamente.

Page 57: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

5. Conclusão O Futebol Simples demonstra como uma IA baseada em times para jogos esportivos pode ser criada usando-se apenas algumas técnicas de IA básicas. Certamente, com isso, o comportamento não fica particularmente sofisticado nem completo. Quando seu conhecimento de técnicas de IA e experiencia aumentarem você verá muitas áreas onde o projeto do Futebol Simples pode ser melhorado ou expandido. Para iniciantes, entretanto, você poderia gostar de tentar manualmente fazer alguns dos seguintes exercícios práticos.

5.1 A prática leva a perfeição Os seguintes exercícios foram projetados para reforçar cada uma das habilidades diferentes que

você aprendeu até aqui neste livro. Eu desejo que você se divirta completando eles.

1. O comportamento de drible como está é ruim: Se o jogador de suporte não pode se mover para dentro de uma posição conveniente rápido o bastante, o atacante feliz da vida levará a bola em uma linha reta diretamente as mãos do goleiro do time adversário (ou baterá na linha de fundo). Melhore este comportamento adicionando a lógica para prevenir o atacante de se mover na frente do jogador de suporte. Achou isso fácil? Que outras maneiras você acha que podem melhorar o comportamento de um atacante? Você pode criar jogadores que consigam driblar a bola em volta de adversários?

2. Além de mudar as regiões padrão, não há qualquer jogo defensivo implementado no código do exemplo. Crie jogadores que são capazes de se interpôr entre o adversário atacante e os jogadores de suporte.

3. Ajuste a calculadora de pontos de suporte para tentar diferentes esquemas de classificação. Há muitas opções que você experimentar aqui. Por exemplo, você podia classificar uma posição pela qualidade dela de ser equidistante de todos os adversários ou à qualidade de estar na frente da posição do jogador em controle da bola. Você podia até mesmo variar o esquema de classificação baseado nas posições do jogador com a posse de bola e do de suporte.

4. Crie estados adicionais no nível do time para implementar táticas mais variadas. Além de somente designar regiões padrões diferentes para os jogadores, crie estados que designem papéis para alguns dos jogadores também. Uma tática que pode ser usada no atacante do time adversário é designar jogadores para ficar em volta dele. Outra pode ser comandar alguns dos jogadores para ficarem pertos - para "marca" na terminologia de futebol - qualquer a um dos adversários que a IA identificar como uma ameaça (como um que esteja perto do gol por exemplo).

5. Mude o programa de modo que o jogador que vai passar a bola chute-a com a força correta requerida para ela chegar aos pés do receptor com uma velocidade de sua escolha.

6. Introduza a idéia de estamina. Todos os jogadores começam com a mesma quantia de estamina e quando eles correm pelo campo eles a gastam. Quanto menos estamina, mais lentos eles correm e menos força eles terão em seus chutes. Eles podem apenas recuperar sua estamina se ficarem imóveis.

7. Implemente um juíz. Isto não é tão fácil quanto parece. O juiz deve sempre tentar se posicionar onde ele pode ver a bola e os jogadores sem interferir na ação.

Nota Para ajudar você, eu deixei alguns códigos de debug no seu projeto do Futebol Simples. Quando você compila e roda o programa, você verá uma janela adicional na qual você pode colocar informações de debug. Qualquer informação enviada para esta janela é também enviada para um arquivo chamado DebugLog.txt que você verá depois que o programa é encerrado. (Porém esteja atento — se você colocar muita informação de debug, este arquivo de texto pode crescer rápidamente).

Page 58: Simulação de Esportes: Futebol Simplestulo 4.pdf... · maioria dos jogos de equipe. Hóquei no gelo, rugby, cricket, futebol americano e até capture-a-bandeira ... • Um campo

Para escrever no console de debug, use o seguinte:

debug_con << "This is a number:"<<3<<"";

O "" no fim da linha faz um recuo de linha. Você pode enviar qualquer tipo para o console que ele tem uma sobrecarga do operador <<.

Quando você termina de debugar você pode remover o console comentando a linha #define DEBUGno arquivo DebugConsole.h. Isto direcionará todos os cometários de debug para um stream sem serventia.

Em adição ao console de debug, o programa principal possui menus que dão um feedback visual de alguns conceitos chave.