Massami welington kamigashima



Baixar 5.43 Mb.
Página17/25
Encontro07.10.2019
Tamanho5.43 Mb.
1   ...   13   14   15   16   17   18   19   20   ...   25

IMPLEMENTAÇÃO


A seguir são mostradas as técnicas, as principais rotinas e ferramentas utilizadas na implementação do motor e no desenvolvimento do protótipo.
      1. Técnicas e ferramentas utilizadas


O desenvolvimento do projeto foi efetuado na linguagem Java utilizando a versão 2.3 da API do Android conhecida como Gingerbread. O ambiente utilizado para o desenvolvimento da aplicação foi o IDE Eclipse versão Helios junto com o Android SDK e o Android Development Tools (ADT) for Eclipse. Para efetuar os testes e depuração da aplicação foi utilizado um simulador disponível no Android SDK em um computador utilizando as configurações de versão 2.3.3 e também um dispositivo real da marca Samsung modelo GT-I9100 conhecido como Galaxy S2, que também utiliza a versão 2.3.3 do Android.

O simulador foi testado em dois dispositivos, o primeiro, um desktop com processador Intel Core 2 Quad Q8300 de 2.5 Giga Hertz (GHz) cada processador, Graphics Processing Unit (GPU) de 1 Giga Byte (GB), memória Random Access Memory (RAM) de 4GB e resolução de 1360x768 pixels. Enquanto o segundo foi um notebook Intel i5 M480 com 2 processadores de 2.66GHz, GPU de 1.1GB, 4GB de memória RAM e resolução de 1366x768 pixels.

O dispositivo Samsung GT-I9100 possui um processador modelo ARM Cortex-A9 Dual Core 1.2GHz, GPU Mali-400MP e 1GB de memória RAM e resolução de 480x800 pixels.

      1. Inicialização da partida


As configurações do jogo se iniciam nos menus de seleção do tipo de jogo (Figura 16) e quantidade de adversários (Figura 17) definidos na função onCreate() da atividade Home e iniciando com a função startGame(). Esta última função (Quadro 13) recebe como parâmetros o nome do jogo, se o jogo será continuado ou iniciado novamente e a quantidade de jogadores que serão inseridos no jogo.

Ao iniciar uma nova partida, as informações do jogo anterior são excluídas da classe SharedPreferences do aplicativo e grava-se o nome do jogo atual que será inicializado e a quantidade de jogadores da partida durante a função commit().

Antes de iniciar uma nova instância de jogo, verifica-se se uma instância anterior já não foi criada, pois caso exista um Intent existente na memória, o aplicativo reaproveita o espaço e cria uma nova instância por cima da antiga.

public void startGame(String gameName, boolean cont, int players){

if (!cont){

SharedPreferences.Editor editor = settings.edit();

editor.putInt("playerCount",players);

editor.putString("gameName", gameName);

editor.remove("continue");

editor.remove("gameScore");

editor.remove("playersHand");

editor.remove("roundBet");

editor.remove("currentPlayer");

// Commit the edits!

editor.commit();

}

if (gameIntent == null)



gameIntent = new Intent(getWindow().getContext(), GameDisplay.class);

startActivity(gameIntent);

}

Quadro 13 – Inicialização do GameDisplay



Durante a inicialização do GameDisplay, as informações do jogo são recuperados utilizando a função getSharedPreferences(). A variável settings (Quadro 14) utilizada na aplicação permite obter os dados armazenados na aplicação utilizando getters dos tipos dos dados. Cada getter recebe dois valores como parâmetro, o nome da variável a ser recuperada e o valor padrão caso a variável não exista.

Ainda no Quadro 14, após o nome do jogo ser obtido, através de reflexão cria-se uma nova instância do jogo referenciado que será inserido dentro do contexto do GameView.

@Override

public void onCreate(Bundle state) {

super.onCreate(state);

settings = getSharedPreferences(Constants.PREF_FILE, 0);

// Recovers the values defined by the user, or get default values

int color = settings.getInt("CardBg", 0);

String playerName = settings.getString("PlayerName","Player 1");

String gameName = settings.getString("gameName", "");

boolean isContinue = settings.getBoolean("continue", false);

int count = settings.getInt("playerCount",1);

(...)

if (gameName != ""){

try {

Class c = Class.forName("engine.GameMode."+gameName);

game = (Game) c.newInstance();

} catch (Exception e){

e.printStackTrace();

}

}

setContentView(R.layout.game);



gameView = (GameView) findViewById(R.id.gameView);

if (count > 1) game.players.add(new Player("CPU"));

if (count > 2) game.players.add(new Player("CPU2"));

if (count > 3) game.players.add(new Player("CPU3"));

gameView.game = game;

gameView.setGame();

}

Quadro 14 – Inicialização de variáveis do jogo



Após a inicialização do GameView através da função findViewById(), esta nova View recebe através da função setGame() (Quadro 15) o jogo criado pela atividade e a partir desta função o jogo é inicializado. Nesta função primeiramente o jogo consulta as configurações definidas no arquivo attrs.xml e inicializando a pontuação do jogador e criando um novo baralho como pode ser visto no Quadro 16.

public void setGame(){

game.setValues(getResources());

// On creating view, the size is not set yet

Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))

.getDefaultDisplay();

if (display.getWidth() > display.getHeight()){

game.HEIGHT = display.getHeight();

game.WIDTH = display.getWidth();

} else {

game.HEIGHT = display.getWidth();

game.WIDTH = display.getHeight();

}

if (game.autoDraw){



time = SystemClock.uptimeMillis();

}

game.initRound();



}

Quadro 15 – setGame()

Após as configurações do jogo serem definidas, é necessário obter as dimensões da tela do dispositivo para permitir que os elementos do jogo sejam posicionados corretamente na tela. Neste momento ainda é necessário criar um objeto da classe Display para obter as dimensões de tela, pois a View somente terá um tamanho após a chamada da função onDraw(). Após esta chamada, as dimensões da tela podem ser obtidas através dos métodos getHeight() e getWidth(), devido na especificação do layout, esta View está definida com as propriedades layout_width=”match_parent” e layout_height=”match_parent” que determinam que a View ocupe todo o espaço da tela.

Caso o jogo esteja configurado com o valor autoDraw igual a true, a variável time é inicializado para começar a contagem de tempo entre a distribuição de cartas, este tempo definido na constante DRAWTIME inserido na classe Constants.

Por fim, a View chama a função initRound(), responsável por efetuar a inicialização das variáveis utilizadas em cada rodada do jogo, verificando a disponibilidade dos jogadores e iniciar a distribuição de cartas caso a variável autoDraw estiver ativa.

@Override



public void setValues(Resources res){

// Obtain values from res/values/attrs.xml

super.deckType = res.getInteger(R.PokerVars.deck_type);

super.playerMoney= res.getInteger(R.PokerVars.player_money);

super.joker = res.getBoolean(R.PokerVars.joker);

super.bet = res.getBoolean(R.PokerVars.bet);

super.autoDraw = res.getBoolean(R.PokerVars.auto_draw);

super.upcard = res.getBoolean(R.PokerVars.upcard);

super.minPlayers = res.getInteger(R.PokerVars.min_players);

super.maxPlayers = res.getInteger(R.PokerVars.max_players);

super.handLimit = res.getInteger(R.PokerVars.hand_limit);

super.restart_on_end = res.getBoolean(R.PokerVars.restart_on_end);



for (Player p : players){

p.coins = super.playerMoney;

}

deck = new Deck(joker,deckType);



for(Card card : deck.deck){

card.id=res.getIdentifier(card.getImage(),"drawable","engine.View");

}

deck.sort();



}

Quadro 16 – Definindo as propriedades do jogo

A função setGame() descrita na classe Game, deve ser sobrescrita nas classes específicas que herdam desta classe como efetuado na classe Poker exibida no Quadro 16. Nesta função, atribui-se para suas variáveis os valores do tipo PokerVars, descrito na seção anterior, inicializa-se o baralho, define-se o id da imagem que cada carta possui e por fim embaralha-se o baralho.

Ao distribuir as cartas para os jogadores, a rotina descrita no Quadro 17 se encarrega de contar a quantidade de cartas que devem ser distribuídas para cada jogador e multiplicadas pela quantidade de jogadores presentes. Com este valor, cada carta é retirada do baralho é inserida em uma fila. Utilizando-se sua posição na fila, determina-se o destino para qual esta carta deverá se movimentar e qual o ângulo final que a carta deverá rotacionar. Após concluída a geração da fila, a primeira carta é retirada da fila e inserida no vetor de cartas em movimento.



int limit = handLimit * players.size();

for (i = 0; i < limit; i++){

Card card = deck.push();

if (card == null)break;

setCardPosition(card);

toMove.add(card);

players.get(i%players.size()).hand.add(card);

}

deck.moving.add(toMove.remove(0));



Quadro 17 – Rotina de distribuição das cartas
      1. Algoritmos gráficos


Devido à disponibilidade de se utilizar imagens pré-desenvolvidas para serem desenhadas na aplicação, decidiu-se por utilizar os métodos de desenho na tela a partir de Bitmaps.

Esta seção trata das classes Game e GameView que são as responsáveis pelo processo de animação das cartas durante o jogo, sendo a primeira responsável pela alteração na transição e rotação das cartas e a segunda efetua o desenho do Bitmap na tela a partir dos parâmetros configurados no jogo.

Quando uma nova instância da classe GameView é chamada na atividade GameDisplay, a partir das variáveis disponíveis no momento, a classe chama a função onDraw() (Quadro 18) para desenhar os elementos na tela. Porém por padrão as Views somente redesenham a tela caso seja necessário (ao recuperar o foco na aplicação por exemplo). Então para garantir que a tela se mantenha sendo redesenhada constantemente, foi inserido a função update() no final do onDraw() que por sua vez ao finalizar sua execução invoca a função invalidate() que informa ao sistema que a tela precisa ser redesenhada, criando assim um ciclo de desenho e atualização constante enquanto a atividade estiver em foco.
@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (drawMode == GAME_MODE){

game.update();

}

(...)


update();

}

public void update() {

if (game.toMove.size() > 0){

long now = SystemClock.uptimeMillis();



if (now - time > Constants.DRAWTIME){

time = SystemClock.uptimeMillis();

game.deck.moving.add(game.toMove.remove(0));

}

}



fps.update();

invalidate();

}

Quadro 18 – Ciclo de desenho



O valor drawMode por padrão inicia com o valor GAME_MODE que significa que o jogo está em execução e as cartas precisam ser atualizadas caso necessário. Enquanto estiver em execução, é chamada a função update() do jogo que é responsável por alterar a posição x e y e ângulo das cartas em movimento. No Quadro 19, parte da rotina update() é exibida onde inicialmente, as variáveis moveX e moveY calculam a distância entre o ponto de origem das cartas, no caso o baralho, até o seu destino, valores toX e toY. Com base na maior distância, os movimentos de rotação e translação serão escalados para proporcionar uma animação mais suave, tendo como base o valor de movimento em pixels definido na constante Constants.CARDMOVE. Ao movimentar a carta, caso o próximo movimento x,y ou angular for menor que a distância até o destino, a posição da variável será substituída pelo valor de destino, evitando assim que os valores passem do objetivo.

for (Card current : deck.moving){

moveY = Math.abs(current.toY-startPos.top);

moveX = Math.abs(current.toX-startPos.left);
if (moveX < moveY){

move = (float) Math.floor(moveY/Constants.CARDMOVE);

} else {

move = (float) Math.floor(moveX/Constants.CARDMOVE);

}

if (current.toAngle != current.angle){

if (current.angle < current.toAngle){

current.angle += current.toAngle/move;

} else {

current.angle -= Math.abs(current.toAngle)/move;

}

if (Math.abs(current.toAngle-current.angle) <= Constants.CARDMOVE){



current.angle = current.toAngle;

}

}


if (moveX > moveY){

move = Constants.CARDMOVE/(moveX/moveY);

} else {

move = Constants.CARDMOVE;

}
if (current.y != current.toY){

if (current.toY < 0 && current.y - move > current.toY)

current.y -= move;

else if (current.toY > 0 && current.y + move< current.toY)

current.y += move;

if (Math.abs(current.toY-current.y) <= Constants.CARDMOVE)

current.y = current.toY;

}

(...)


Quadro 19 – Atualização da posição das cartas

Após cada carta em movimento ser reposicionada, é feita uma verificação (Quadro 20) se a posição atual da carta é a posição de destino. Caso afirmativo, a carta é exibida se ela veio do baralho, ou é escondida caso ela venha da mão do jogador. Ao chegar à posição final, a carta é inserida num vetor de cartas a serem removidas da lista de movimentações que ao final do ciclo de verificação de cartas a serem movimentadas, excluirá da lista as que já chegaram a seu destino.



if ((current.y == current.toY) && (current.x == current.toX)){

current.visible = (!current.visible);

removed.add(current);

}

(...)



if (removed.size() > 0)

deck.moving.removeAll(removed);

Quadro 20 – Verificação de posição

Com as cartas devidamente posicionadas, cabe à View desenhá-las na tela na função onDraw(). O trecho descrito no Quadro 21 descreve como cada carta em movimento é desenhada na tela durante a partida. Para cada carta, primeiramente é verificado se ela já possui um id de desenho, caso negativo, busca-se nos recursos do sistema, o id relacionado ao nome da imagem que a carta necessita6. Após essa verificação, caso o valor de i seja igual a zero e a carta estiver visível, cria-se um Bitmap baseado no id da carta, caso contrário, o verso da carta, que foi previamente definido, será exibido.

// Draw the moving cards

for(Card c : game.deck.moving){

if (c != game.selected && c != null){

if (c.id == 0){

c.id = getResources().getIdentifier(c.getImage(), "drawable", "engine.View");

}

if (c.visible && i == 0){

card = BitmapFactory.decodeResource(getResources(),c.id);

} else {

card = bg;

}
if (c.angle != 0){

Matrix matrix = new Matrix();

matrix.preTranslate(c.x, c.y);

matrix.postRotate(c.angle, c.x + (Constants.CARD_WIDTH/2), c.y + (Constants.CARD_HEIGHT/2));

canvas.drawBitmap(card, matrix, mPaint);

} else {

canvas.drawBitmap(card, c.x, c.y, mPaint);

}

}



i++;

}

Quadro 21 – Ciclo de desenho de cartas em movimento



Para desenhar a carta, após o Bitmap desta ser definido, primeiro é verificado se esta carta está rotacionada ou não. Caso esteja, é necessário criar uma matriz definindo sua posição x e y com a função preTranslate(), e rotacionando-a com a função postRotate() que recebe como parâmetros o ângulo da imagem e sua posição x e y de centro da imagem. Caso os valores de centro da imagem não forem informados, a imagem irá rotacionar em relação ao ponto (0,0) da tela, proporcionando um resultado indesejável para a situação. Com a matriz configurada, a função canvas.drawBitmap(), se encarregará de desenhar a imagem baseado nas informações da matriz.

Se a carta não necessitar ser rotacionada, a função drawBitmap() receberá nos parâmetros, o Bitmap a ser desenhado e sua posição na tela sem necessidade de mais tratamentos.


      1. Algoritmos de controle


Além da inicialização e rotinas de desenho, algumas rotinas de controle são fundamentais para o funcionamento do aplicativo sendo elas a função onBackPressed(), que sobrescreve o comportamento do aplicativo ao pressionar o botão “back” do dispositivo, a função onTouch(), disparada quando o usuário interage com a tela, onPause(), disparado quando a aplicação por algum motivo perde o foco, as rotinas de apostas e a verificação do vencedor da rodada roundWinner(), definida na classe Poker.
        1. onBackPressed()


O botão back, por padrão, permite ao usuário navegar entre atividades de forma rápida. Porém, como explicado na seção 2.4, ao pressionar o botão back, a atividade atual é destruída e a próxima atividade da pilha é carregada. Dependendo da forma que a atividade foi construída, este comportamento não é desejado em algumas ocasiões, por exemplo, quando em uma atividade múltiplas Views são utilizadas, ao exibir uma nova View por cima da inicial é natural que o usuário deseje retornar à View anterior ao pressionar o botão back. Em situações normais, isso não acontecerá e em vez disso, a atividade será destruída.

No projeto desenvolvido neste trabalho, a atividade GameDisplay possui duas camadas de telas, sendo a primeira a tela de jogo, onde as cartas são distribuídas e desenhadas, e uma segunda quando o botão de apostas é pressionado e uma nova camada é desenhada por cima exibindo os botões de seleção de apostas. Para evitar que o jogo termine quando o usuário pressiona o botão back durante a tela de apostas está sendo desenhada, a atividade sobrescreve essa função (Quadro 22) permitindo que ao pressionar o botão, a tela de apostas seja escondida e retorne ao jogo.

@Override

public void onBackPressed (){

if (gameView.drawMode == GameView.BET_MODE){

gameView.drawMode = GameView.GAME_MODE;

gameView.betView.setVisibility(View.GONE);

} else {

super.onBackPressed();

}

}



Quadro 22 – Função onBackPressed()
        1. onTouch()


Ao implementar a classe OnTouchListener na classe GameView, é necessário sobrescrever a função onTouch() que responde pelos eventos de toque do usuário na tela. A função recebe como parâmetro o evento MotionEvent que permite extrair os valores x e y da tela onde o evento foi disparado e o tipo de ação efetuado, sendo as ações utilizadas na implementação a ACTION_DOWN, ACTION_UP e ACTION_MOVE.

Quando a ação ACTION_DOWN é disparada (Quadro 23), uma área retangular relativa ao ícone de apostas é criada, e utilizando a função contains() da classe Rect, é verificado se as coordenadas x e y do evento de toque estão dentro da área do ícone. Esta função retorna true caso as coordenadas estejam dentro da área ou acima da fronteira lateral esquerda ou superior, enquanto retorna false caso estejam fora da área ou acima das fronteiras inferior e lateral direito.



int actionCode = event.getAction() & MotionEvent.ACTION_MASK;

if (actionCode == MotionEvent.ACTION_DOWN) {

int posX = (int) event.getX();

int posY = (int) event.getY();

if (game.bet){

Bitmap icon = BitmapFactory.decodeResource(getResources(),R.drawable.icocoins);

Rect betIcon = game.position(game.betPos, icon.getHeight(), icon.getWidth());

if (betIcon.contains(posX, posY)){



this.drawMode = BET_MODE;

for (int i = game.current; i > 0; i = (i+1)%game.players.size()){

if (game.addBet(game.players.get(i))){

currentBet = game.result + currentBet;

}

}

}



betText.setText(currentBet);

betView.setVisibility(View.VISIBLE);

}

} (...)


Quadro 23 – Verificação do botão de apostas

Caso o botão de apostas for pressionado e não seja a vez do jogador, as apostas dos jogadores anteriores a ele serão efetuadas e a tela de apostas se torna visível.

Ao disparar o evento de toque, as classes de jogo também são notificadas, sendo estas responsáveis por verificar se a coordenada onde o usuário pressionou consta alguma de suas cartas na mão permitindo assim que a carta seja selecionada e enquanto não for solta, o usuário poderá movê-la na tela. Ao disparar a ação ACTION_UP, caso a classe de jogo tenha regiões definidas, o desenvolvedor poderá configurar para a carta ser inserida e posicionada neste lugar, a mesa de jogo ou a pilha de descartes por exemplo.

        1. onPause()


Quando a atividade ou aplicação é destruída, movida de posição na pilha de atividades ou perca o foco por algum outro motivo como o dispositivo entrar em modo de repouso, antes disso o sistema chama a função onPause() junto com uma série de outros comandos padrões do sistema operacional. Neste momento é interessante que o aplicativo salve as informações do jogo de forma que quando a atividade seja restaurada, seja possível continuar o jogo do ponto onde ele foi interrompido.

O Quadro 24 demonstra como a aplicação armazena as variáveis do jogo na classe SharedPreferences que serão carregadas novamente na inicialização da atividade. O editor somente permite armazenar um conjunto limitado de tipos de dados (boolean, integer, String, float e long), sendo assim necessário armazenar os dados de forma que seja possível recuperar e tratar os dados posteriormente.

@Override

public void onPause(){

// Save game status

SharedPreferences.Editor editor = settings.edit();

String score = "";

String hands = "";

String names = "";

String handCards = "";

for (Player p : game.players){

handCards = "";

score += (score != "") ? ",":"";

score += p.coins;



for (Card c : p.hand){

handCards += (handCards != "") ? ",":"";

handCards += c.value + "-" + c.suit;

}

hands += (hands != "") ? "@":"";



hands += handCards;

names += (names != "") ? ",":"";

names += p.name;

}

editor.putBoolean("continue",true);



editor.putString("gameScore", score);

editor.putString("playersName",names);

editor.putString("playersHand",hands);

editor.putString("gameName", game.getClass().getSimpleName());

editor.putInt("roundBet", game.currentBet);

editor.putInt("currentPlayer", game.current);

editor.commit();

super.onPause();

}

Quadro 24 – onPause()


        1. roundWinner()


Na partida de pôquer desenvolvida no protótipo, foi elaborada uma rotina de avaliação das cartas da mão de cada jogador permitindo assim estimar o peso que suas cartas têm na rodada. Para se obter o resultado esperado, a função pode ser separada em várias etapas que permitem filtrar as possíveis combinações de cartas do jogo.

Inicialmente as cartas do jogador são ordenadas pelo valor de suas cartas utilizando a função sort() (Quadro 25) da classe Collections que segundo Android Developers (2011), utiliza um algoritmo similar ao MergeSort obtendo assim no pior caso, um gasto proporcional a n Log n interações.

(...)

ArrayList cards = (ArrayList) p.hand.clone();



// Optimized ArrayList sorter using java MergeSort

Collections.sort(cards,new Comparator(){



public int compare(Card card, Card card2) {

return (card.value > card2.value) ? 1:-1;

}

}

);



(...)

Quadro 25 – MergeSort de cartas

Após as cartas estarem ordenadas utiliza-se um laço for para verificar se as cartas da mão do jogador estão na sequência e se possuem o mesmo naipe. Com esta verificação, o jogo identifica se o jogador possui a sequência Royal Flush, Straight ou Flush concluindo a verificação nestes casos.

Caso o jogador não possua nenhuma das sequências anteriores, um vetor de 14 posições é criado para armazenar quantas cartas de cada valor foram encontradas. Após armazenar os valores no vetor, é verificado se algum dos índices do vetor possui dois ou mais valores armazenados nele, determinando assim caso o jogador possua uma quadra, trinca ou dupla de cartas. Se a mão do jogador não se enquadrar em nenhuma das regras anteriores, serão consideradas as cartas de maior valor que o jogador possui.

Os valores definidos na aplicação para distinguir o peso da sequência de cartas do jogador podem ser visualizados no Quadro 26. Caso haja empate na pontuação de dois ou mais jogadores, a função tierBreaker() analisa o valor das cartas dos jogadores empatados e define o jogador baseado em quem tiver as cartas com maior valor. Em caso de empate técnico, os jogadores tiverem as cartas de mesmo valor, o jogo considera empate e divide o valor da aposta entre os empatados.

private static final int PAIR = 200;

private static final int TWO_PAIRS = 300;

private static final int THREE_OF_A_KIND = 400;

private static final int STRAIGHT = 500;

private static final int FLUSH = 600;

private static final int FULL_HOUSE = 700;

private static final int FOUR_OF_A_KIND = 800;

private static final int STRAIGHT_FLUSH = 900;

private static final int ROYAL_FLUSH = 1000;

Quadro 26 – Lista de pontuações do jogo de pôquer





1   ...   13   14   15   16   17   18   19   20   ...   25


©aneste.org 2017
enviar mensagem

    Página principal