Jogos em AdvPL – Tetris

Introdução

Como eu havia dito, no primeiro post deste blog, em AdvPL muita coisa pode ser feita, até um ERP. E, para dar ênfase em outras características do AdvPL, resolvi fazer algumas revisões e publicar um programinha interessante … um clone do famoso jogo Tetris. Sim, aquele que os blocos caem e você precisa alinhá-los para eliminar linhas da tela 😉

Tetris - Versão Final

O Algoritmo

Não foi tão difícil fazer o jogo funcionar, foi mais trabalhoso o refactoring para publicação e as explicações de como ele funciona por dentro do que bolar a lógica e interface do game. Ao ser executado, o programa já abre direto uma caixa de interface com uma peça sorteada em queda progressiva em intervalos de 1 segundo, mostrando a próxima peça a ser usada, atualizando um score lateral e um contador de tempo de jogo. Você pode usar as letras ASDW ou JKLI para mover a peça em queda para a esquerda, para baixo, para a direita e rotacioná-la, respectivamente, e a barra de espaços para dropar a peça até a linha inferior que ela pode alcançar na tela, sendo possível neste ponto ainda mover a peça para a direita ou esquerda, ou mesmo rotacioná-la caso exista espaço hábil para tais operações.Ainda pode ser usada a letra P para colocar e retirar o jogo de modo “Pause”. E, para sair do jogo instantaneamente, pressione a tecla [ESC].

Na versão inicial, o jogo não foi montado usando orientação a objeto. Ele ainda será passado a limpo, com classes e propriedades, mas já funciona muito bem com o paradigma estruturado. Em poucas linhas, o painel de jogo possui uma representação em memória usando array de strings, onde cada elemento do array representa uma linha da tela, e a sequência de números dentro de cada linha corresponde a um quadradinho colorido. A tela do jogo é desenhada na interface do Advpl em um Grid de 20 x 10 imagens, onde o jogo lida com a aplicação de peças e movimento de peças dentro desse array, e a camada de interface apenas atualiza essa matriz na interface trocando os resources das imagens. Como cada resource possui uma cor diferente, com uma matriz de 200 quadradinhos na tela e oito imagens, é possível montar a interface.

O miolo do jogo consiste em trabalhar com um array de strings, com 20 linhas, contendo em cada linha uma string com um valor numérico, onde “0” significa um espaço não preenchido, e cada valor maior que zero representa uma imagem 10×10 de uma cor diferente na respectiva posição do grid de bitmaps da interface. Como todo o jogo é baseado em array de strings, cada peça e sua respectiva representação de blocos é feita em um array multi-dimensional de peças, onde cada peça é representada por um grid de string 4×4 com “0” e “1”.

As funções de trabalho com as matrizes devem ser capazes de remover ou colocar uma peça do array que representa a interface, e no caso de colocar a peça, a função somente deve conseguir realizar esta operação caso as posições dos quadrados usados pela peça estejam vazias no Grid. A animação consiste apenas em remover a peça em jogo da posição atual do grid no array, inseri-la em uma nova posição, e repintar o grid de interface. Esta pintura é realizada simplesmente setando novamente o nome do resource (bitmap) usado naquela posição, caso ele esteja diferente do recurso indicado no array.

Cada STATIC FUNCTION do código tem um propósito distinto, e para economizar com a passagem de parâmetros, como este fonte é executado sem recursão, as variáveis contendo o estado do jogo também são STATIC. A interface para obter as teclas pressionadas foi feita com botões, criados fora da área visível da tela, onde cada letra usada é indicada no prompt do botão como uma tecla de atalho ( prefixada com & ). Neste caso especifico, quando o foco da interface está em um componente que não permite edição de conteúdo, o uso das teclas de atalho não precisa ser concomitante com a tecla [ALT], o que torna essa idéia de input de interface viável no AdvPL.

Dentro do fonte AdvPL as partes do código e suas funcionalidades estão bem documentadas. É claro que não é um fonte arroz com feijão, esse prato têm uns temperos um pouco mais “puxados”, e como ele não foi escrito usando orientação a objeto, ele requer um pouco mais de atenção para ser assimilado na íntegra. Posteriormente eu vou fazer a segunda versão desse clone, com todas as funcionalidades do primeiro, porém usando Orientação a Objetos do AdvPL, fazendo isso vai ficar muito visível o nível de clareza da aplicação quando utilizamos a Orientação a Objetos.

Fontes e Patch

O fonte deste aplicativo e os bitmaps (resources) necessários estão no GitHub https://github.com/siga0984/Tetris , bem como um patch gerado para o Protheus 11 ( RPO TOP , Português ) com apenas o fonte Tetris.PRW e as imagens do projeto. Para gerar a aplicação, basta ter um Protheus 10 ou superior, criar um projeto em branco, baixar o PRW e as imagens do GitHub, acrescentar o fonte no projeto, e acrescentar todas as imagens como “resources”. Para executá-lo, basta chamar o SmartClient, informando a função U_TETRIS

E, pra você que está se coçando de curiosidade, o fonte com todos os comentários e afins, ficou com pouco mais de 700 linhas. Segue abaixo o fonte AdvPL do clone do Tetris.

#include "protheus.ch"
/* ========================================================
Função U_TETRIS
Autor Júlio Wittwer
Data 03/11/2014
Versão 1.150226
Descriçao Réplica do jogo Tetris, feito em AdvPL
Para jogar, utilize as letras :
A ou J = Move esquerda
D ou L = Move Direita
S ou K = Para baixo
W ou I = Rotaciona sentido horario
Barra de Espaço = Dropa a peça
Pendencias
Fazer um High Score
Cores das peças
O = Yellow
I = light Blue
L = Orange
Z = Red
S = Green
J = Blue
T = Purple
======================================================== */
STATIC _aPieces := LoadPieces() // Array de peças do jogo 
STATIC _aBlockRes := { "BLACK","YELOW2","LIGHTBLUE2","ORANGE2","RED2","GREEN2","BLUE2","PURPLE2" }
STATIC _nGameClock // Tempo de jogo 
STATIC _nNextPiece // Proxima peça a ser usada
STATIC _GlbStatus := 0 // 0 = Running 1 = PAuse 2 == Game Over
STATIC _aBMPGrid := array(20,10) // Array de bitmaps de interface do jogo 
STATIC _aBMPNext := array(4,5) // Array de botmaps da proxima peça
STATIC _aNext := {} // Array com a definição e posição da proxima peça
STATIC _aDropping := {} // Array com a definição e posição da peça em jogo
STATIC _nScore := 0 // pontuação da partida
STATIC _oScore // label para mostrar o score e time e mensagens
STATIC _aMainGrid := {} // Array de strings com os blocos da interface representados em memoria
STATIC _oTimer // Objeto timer de interface para a queda automática da peça em jogo
 
// =======================================================
USER Function Tetris()
Local nC , nL
Local oDlg
Local oBackGround , oBackNext
Local oFont , oLabel , oMsg
// Fonte default usada na caixa de diálogo 
// e respectivos componentes filhos
oFont := TFont():New('Courier new',,-16,.T.,.T.)
DEFINE DIALOG oDlg TITLE "Tetris AdvPL" FROM 10,10 TO 450,365 ;
 FONT oFont COLOR CLR_WHITE,CLR_BLACK PIXEL
// Cria um fundo cinza, "esticando" um bitmap
@ 8, 8 BITMAP oBackGround RESOURCE "GRAY" ;
SIZE 104,204 Of oDlg ADJUST NOBORDER PIXEL
// Desenha na tela um grid de 20x10 com Bitmaps
// para ser utilizado para desenhar a tela do jogo
For nL := 1 to 20
 For nC := 1 to 10
 
 @ nL*10, nC*10 BITMAP oBmp RESOURCE "BLACK2" ;
 SIZE 10,10 Of oDlg ADJUST NOBORDER PIXEL
 
 _aBMPGrid[nL][nC] := oBmp
 
 Next
Next
 
// Monta um Grid 4x4 para mostrar a proxima peça
// ( Grid deslocado 110 pixels para a direita )
@ 8, 118 BITMAP oBackNext RESOURCE "GRAY" ;
 SIZE 54,44 Of oDlg ADJUST NOBORDER PIXEL
For nL := 1 to 4
 For nC := 1 to 5
 
 @ nL*10, (nC*10)+110 BITMAP oBmp RESOURCE "BLACK" ;
 SIZE 10,10 Of oDlg ADJUST NOBORDER PIXEL
 
 _aBMPNext[nL][nC] := oBmp
 
 Next
Next
// Label fixo, título do Score.
@ 80,120 SAY oLabel PROMPT "[Score]" SIZE 60,10 OF oDlg PIXEL
 
// Label para Mostrar score, timers e mensagens do jogo
@ 90,120 SAY _oScore PROMPT " " SIZE 60,120 OF oDlg PIXEL
 
// Define um timer, para fazer a peça em jogo
// descer uma posição a cada um segundo
// ( Nao pode ser menor, o menor tempo é 1 segundo )
_oTimer := TTimer():New(1000, ;
 {|| MoveDown(.f.) , PaintScore() }, oDlg )
// Botões com atalho de teclado
// para as teclas usadas no jogo
// colocados fora da area visivel da caixa de dialogo
@ 480,10 BUTTON oDummyBtn PROMPT '&A' ;
 ACTION ( DoAction('A'));
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&S' ;
 ACTION ( DoAction('S') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&D' ;
 ACTION ( DoAction('D') ) ;
 SIZE 1, 1 OF oDlg PIXEL
 
@ 480,20 BUTTON oDummyBtn PROMPT '&W' ;
 ACTION ( DoAction('W') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&J' ;
 ACTION ( DoAction('J') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&K' ;
 ACTION ( DoAction('K') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&L' ;
 ACTION ( DoAction('L') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&I' ;
 ACTION ( DoAction('I') ) ;
 SIZE 1, 1 OF oDlg PIXEL
 
@ 480,20 BUTTON oDummyBtn PROMPT '& ' ; // Espaço = Dropa
 ACTION ( DoAction(' ') ) ;
 SIZE 1, 1 OF oDlg PIXEL
@ 480,20 BUTTON oDummyBtn PROMPT '&P' ; // Pause
 ACTION ( DoPause() ) ;
 SIZE 1, 1 OF oDlg PIXEL
// Na inicialização do Dialogo uma partida é iniciada
oDlg:bInit := {|| Start() }
ACTIVATE DIALOG oDlg CENTER
Return
/* ------------------------------------------------------------
Função Start() Inicia o jogo
------------------------------------------------------------ */
STATIC Function Start()
Local aDraw
// Inicializa o grid de imagens do jogo na memória
// Sorteia a peça em jogo
// Define a peça em queda e a sua posição inicial
// [ Peca, direcao, linha, coluna ]
// e Desenha a peça em jogo no Grid
// e Atualiza a interface com o Grid
InitGrid()
nPiece := randomize(1,len(_aPieces)+1)
_aDropping := {nPiece,1,1,6}
SetGridPiece(_aDropping,_aMainGrid)
PaintMainGrid()
// Sorteia a proxima peça e desenha 
// ela no grid reservado para ela 
InitNext()
_nNextPiece := randomize(1,len(_aPieces)+1)
aDraw := {_nNextPiece,1,1,1}
SetGridPiece(aDraw,_aNext)
PaintNext()
// Inicia o timer de queda automática da peça em jogo
_oTimer:Activate()
// Marca timer do inicio de jogo 
_nGameClock := seconds()
Return
/* ----------------------------------------------------------
Inicializa o Grid na memoria
Em memoria, o Grid possui 14 colunas e 22 linhas
Na tela, são mostradas apenas 20 linhas e 10 colunas
As 2 colunas da esquerda e direita, e as duas linhas a mais
sao usadas apenas na memoria, para auxiliar no processo
de validação de movimentação das peças.
---------------------------------------------------------- */
STATIC Function InitGrid()
_aMainGrid := array(20,"11000000000011")
aadd(_aMainGrid,"11111111111111")
aadd(_aMainGrid,"11111111111111")
return
STATIC Function InitNext()
_aNext := array(4,"00000")
return
//
// Aplica a peça no Grid.
// Retorna .T. se foi possivel aplicar a peça na posicao atual
// Caso a peça não possa ser aplicada devido a haver
// sobreposição, a função retorna .F. e o grid não é atualizado
//
STATIC Function SetGridPiece(aOnePiece,aGrid)
Local nPiece := aOnePiece[1] // Numero da peça
Local nPos := aOnePiece[2] // Posição ( para rotacionar ) 
Local nRow := aOnePiece[3] // Linha atual no Grid
Local nCol := aOnePiece[4] // Coluna atual no Grid
Local nL , nC
Local aTecos := {}
Local cTeco, cPeca , cPieceStr
cPieceStr := str(nPiece,1)
For nL := nRow to nRow+3
 cTeco := substr(aGrid[nL],nCol,4)
 cPeca := _aPieces[nPiece][1+nPos][nL-nRow+1]
 For nC := 1 to 4
 If Substr(cPeca,nC,1) == '1'
 If substr(cTeco,nC,1) != '0'
 // Vai haver sobreposição,
 // Nao dá para desenhar a peça
 Return .F.
 Endif
 cTeco := Stuff(cTeco,nC,1,cPieceStr)
 Endif
 Next
 // Array temporario com a peça já colocada
 aadd(aTecos,cTeco)
Next
// Aplica o array temporario no array do grid
For nL := nRow to nRow+3
 aGrid[nL] := stuff(_aMainGrid[nL],nCol,4,aTecos[nL-nRow+1])
Next
Return .T.
/* ----------------------------------------------------------
Função PaintMainGrid()
Pinta o Grid do jogo da memória para a Interface
Release 20150222 : Optimização na camada de comunicação, apenas setar
o nome do resource / bitmap caso o resource seja diferente do atual.
---------------------------------------------------------- */
STATIC Function PaintMainGrid()
Local nL, nc , cLine, nPeca
for nL := 1 to 20
 cLine := _aMainGrid[nL]
 For nC := 1 to 10
 nPeca := val(substr(cLine,nC+2,1))
 If _aBMPGrid[nL][nC]:cResName != _aBlockRes[nPeca+1]
 // Somente manda atualizar o bitmap se houve
 // mudança na cor / resource desta posição
 _aBMPGrid[nL][nC]:SetBmp(_aBlockRes[nPeca+1])
 endif
 Next
Next
Return
// Pinta na interface a próxima peça 
// a ser usada no jogo 
STATIC Function PaintNext()
Local nL, nC, cLine , nPeca
For nL := 1 to 4
 cLine := _aNext[nL]
 For nC := 1 to 5
 nPeca := val(substr(cLine,nC,1))
 If _aBMPNext[nL][nC]:cResName != _aBlockRes[nPeca+1]
 _aBMPNext[nL][nC]:SetBmp(_aBlockRes[nPeca+1])
 endif
 Next
Next
Return
/* -----------------------------------------------------------------
Carga do array de peças do jogo 
Array multi-dimensional, contendo para cada 
linha a string que identifica a peça, e um ou mais
arrays de 4 strings, onde cada 4 elementos 
representam uma matriz binaria de caracteres 4x4 
para desenhar cada peça
Exemplo - Peça "O"
aLPieces[1][1] C "O"
aLPieces[1][2][1] "0000" 
aLPieces[1][2][2] "0110" 
aLPieces[1][2][3] "0110" 
aLPieces[1][2][4] "0000"
----------------------------------------------------------------- */
STATIC Function LoadPieces()
Local aLPieces := {}
// Peça "O" , uma posição
aadd(aLPieces,{'O', { '0000','0110','0110','0000'}})
// Peça "I" , em pé e deitada
aadd(aLPieces,{'I', { '0000','1111','0000','0000'},;
 { '0010','0010','0010','0010'}})
// Peça "S", em pé e deitada
aadd(aLPieces,{'S', { '0000','0011','0110','0000'},;
 { '0010','0011','0001','0000'}})
// Peça "Z", em pé e deitada
aadd(aLPieces,{'Z', { '0000','0110','0011','0000'},;
 { '0001','0011','0010','0000'}})
// Peça "L" , nas 4 posições possiveis
aadd(aLPieces,{'L', { '0000','0111','0100','0000'},;
 { '0010','0010','0011','0000'},;
 { '0001','0111','0000','0000'},;
 { '0110','0010','0010','0000'}})
// Peça "J" , nas 4 posições possiveis
aadd(aLPieces,{'J', { '0000','0111','0001','0000'},;
 { '0011','0010','0010','0000'},;
 { '0100','0111','0000','0000'},;
 { '0010','0010','0110','0000'}})
// Peça "T" , nas 4 posições possiveis
aadd(aLPieces,{'T', { '0000','0111','0010','0000'},;
 { '0010','0011','0010','0000'},;
 { '0010','0111','0000','0000'},;
 { '0010','0110','0010','0000'}})
Return aLPieces
/* ----------------------------------------------------------
Função MoveDown()
Movimenta a peça em jogo uma posição para baixo.
Caso a peça tenha batido em algum obstáculo no movimento
para baixo, a mesma é fica e incorporada ao grid, e uma nova
peça é colocada em jogo. Caso não seja possivel colocar uma
nova peça, a pilha de peças bateu na tampa -- Game Over
---------------------------------------------------------- */
STATIC Function MoveDown(lDrop)
Local aOldPiece
 
If _GlbStatus != 0
 Return
Endif
// Clona a peça em queda na posição atual
aOldPiece := aClone(_aDropping)
If lDrop
 
 // Dropa a peça até bater embaixo
 // O Drop incrementa o score em 1 ponto 
 // para cada linha percorrida. Quando maior a quantidade
 // de linhas vazias, maior o score acumulado com o Drop
 
 // Guarda a peça na posição atual
 aOldPiece := aClone(_aDropping)
 
 // Remove a peça do Grid atual
 DelPiece(_aDropping,_aMainGrid)
 
 // Desce uma linha pra baixo
 _aDropping[3]++
 
 While SetGridPiece(_aDropping,_aMainGrid)
 
 // Encaixou, remove e tenta de novo
 DelPiece(_aDropping,_aMainGrid)
 
 // Guarda a peça na posição atual
 aOldPiece := aClone(_aDropping)
 
 // Desce a peça mais uma linha pra baixo
 _aDropping[3]++
// Incrementa o Score
 _nScore++
 
 Enddo
 
 // Nao deu mais pra pintar, "bateu"
 // Volta a peça anterior, pinta o grid e retorna
 // isto permite ainda movimentos laterais
 // caso tenha espaço.
 
 _aDropping := aClone(aOldPiece)
 SetGridPiece(_aDropping,_aMainGrid)
 PaintMainGrid()
 
Else
 
 // Move a peça apenas uma linha pra baixo
 
 // Primeiro remove a peça do Grid atual
 DelPiece(_aDropping,_aMainGrid)
 
 // Agora move a peça apenas uma linha pra baixo
 _aDropping[3]++
 
 // Recoloca a peça no Grid
 If SetGridPiece(_aDropping,_aMainGrid)
 
 // Se deu pra encaixar, beleza
 // pinta o novo grid e retorna
 PaintMainGrid()
 Return
 
 Endif
 
 // Opa ... Esbarrou em alguma coisa
 // Volta a peça pro lugar anterior
 // e recoloca a peça no Grid
 _aDropping := aClone(aOldPiece)
 SetGridPiece(_aDropping,_aMainGrid)
// Incrementa o score em 4 pontos 
 // Nao importa a peça ou como ela foi encaixada
 _nScore += 4
// Agora verifica se da pra limpar alguma linha
 ChkMainLines()
 
 // Pega a proxima peça
 nPiece := _nNextPiece
 _aDropping := {nPiece,1,1,6} // Peca, direcao, linha, coluna
If !SetGridPiece(_aDropping,_aMainGrid)
 
 // Acabou, a peça nova nao entra (cabe) no Grid
 // Desativa o Timer e mostra "game over"
 // e fecha o programa
_GlbStatus := 2 // GAme Over
// volta os ultimos 4 pontos ... 
 _nScore -= 4
// Cacula o tempo de operação do jogo 
 _nGameClock := round(seconds()-_nGameClock,0)
 If _nGameClock < 0 
 // Ficou negativo, passou da meia noite 
 _nGameClock += 86400
 Endif
// Desliga o timer de queda de peça em jogo
 _oTimer:Deactivate() 
 
 Endif
 
 // Se a peca tem onde entrar, beleza
 // -- Repinta o Grid -- 
 PaintMainGrid()
// Sorteia a proxima peça
 // e mostra ela no Grid lateral
If _GlbStatus != 2 
 // Mas apenas faz isso caso nao esteja em game over
 InitNext()
 _nNextPiece := randomize(1,len(_aPieces)+1)
 SetGridPiece( {_nNextPiece,1,1,1} , _aNext)
 PaintNext()
 Else
 // Caso esteja em game over, apenas limpa a proxima peça
 InitNext()
 PaintNext()
 Endif
 
 
Endif
Return
/* ----------------------------------------------------------
Recebe uma ação da interface, através de uma das letras
de movimentação de peças, e realiza a movimentação caso
haja espaço para tal.
---------------------------------------------------------- */
STATIC Function DoAction(cAct)
Local aOldPiece
// conout("Action = ["+cAct+"]")
If _GlbStatus != 0 
 Return
Endif
// Clona a peça em queda
aOldPiece := aClone(_aDropping)
if cAct $ 'AJ'
// Movimento para a Esquerda (uma coluna a menos)
 // Remove a peça do grid
 DelPiece(_aDropping,_aMainGrid)
 _aDropping[4]--
 If !SetGridPiece(_aDropping,_aMainGrid)
 // Se nao foi feliz, pinta a peça de volta
 _aDropping := aClone(aOldPiece)
 SetGridPiece(_aDropping,_aMainGrid)
 Endif
 // Repinta o Grid
 PaintMainGrid()
 
Elseif cAct $ 'DL'
// Movimento para a Direita ( uma coluna a mais )
 // Remove a peça do grid
 DelPiece(_aDropping,_aMainGrid)
 _aDropping[4]++'
 If !SetGridPiece(_aDropping,_aMainGrid)
 // Se nao foi feliz, pinta a peça de volta
 _aDropping := aClone(aOldPiece)
 SetGridPiece(_aDropping,_aMainGrid)
 Endif
 // Repinta o Grid
 PaintMainGrid()
 
Elseif cAct $ 'WI'
 
 // Movimento para cima ( Rotaciona sentido horario )
 
 // Remove a peça do Grid
 DelPiece(_aDropping,_aMainGrid)
 
 // Rotaciona
 _aDropping[2]--
 If _aDropping[2] < 1
 _aDropping[2] := len(_aPieces[_aDropping[1]])-1
 Endif
 
 If !SetGridPiece(_aDropping,_aMainGrid)
 // Se nao consegue colocar a peça no Grid
 // Nao é possivel rotacionar. Pinta a peça de volta
 _aDropping := aClone(aOldPiece)
 SetGridPiece(_aDropping,_aMainGrid)
 Endif
 
 // E Repinta o Grid
 PaintMainGrid()
 
ElseIF cAct $ 'SK'
 
 // Desce a peça para baixo uma linha intencionalmente 
 MoveDown(.F.)
 
 // se o movimento foi intencional, ganha + 1 ponto 
 _nScore++
 
ElseIF cAct == ' '
 
 // Dropa a peça - empurra para baixo até a última linha
 // antes de baer a peça no fundo do Grid
 MoveDown(.T.)
 
Endif
// Antes de retornar, repinta o score
PaintScore()
Return .T.
Static function DoPause()
If _GlbStatus == 0
 // Pausa
 _GlbStatus := 1
 _oTimer:Deactivate()
Else
 // Sai da pausa
 _GlbStatus := 0
 _oTimer:Activate()
Endif
// Antes de retornar, repinta o score
PaintScore()
Return
/* -----------------------------------------------------------------------
Remove uma peça do Grid atual
----------------------------------------------------------------------- */
STATIC Function DelPiece(aPiece,aGrid)
Local nPiece := aPiece[1]
Local nPos := aPiece[2]
Local nRow := aPiece[3]
Local nCol := aPiece[4]
Local nL, nC
Local cTeco, cPeca
// Como a matriz da peça é 4x4, trabalha em linhas e colunas
// Separa do grid atual apenas a área que a peça está ocupando
// e desliga os pontos preenchidos da peça no Grid.
For nL := nRow to nRow+3
 cTeco := substr(aGrid[nL],nCol,4)
 cPeca := _aPieces[nPiece][1+nPos][nL-nRow+1]
 For nC := 1 to 4
 If Substr(cPeca,nC,1)=='1'
 cTeco := Stuff(cTeco,nC,1,'0')
 Endif
 Next
 aGrid[nL] := stuff(_aMainGrid[nL],nCol,4,cTeco)
Next
Return
/* -----------------------------------------------------------------------
Verifica se alguma linha esta completa e pode ser eliminada
----------------------------------------------------------------------- */
STATIC Function ChkMainLines()
Local nErased := 0
For nL := 20 to 2 step -1
 
 // Sempre varre de baixo para cima
 // Pega uma linha, e remove os espaços vazios
 cTeco := substr(_aMainGrid[nL],3)
 cNewTeco := strtran(cTeco,'0','')
 
 If len(cNewTeco) == len(cTeco)
 // Se o tamanho da linha se manteve, não houve
 // nenhuma redução, logo, não há espaços vazios
 // Elimina esta linha e acrescenta uma nova linha
 // em branco no topo do Grid
 adel(_aMainGrid,nL)
 ains(_aMainGrid,1)
 _aMainGrid[1] := "11000000000011"
 nL++
 nErased++
 Endif
 
Next
// Pontuação por linhas eliminadas 
// Quanto mais linhas ao mesmo tempo, mais pontos
If nErased == 4
 _nScore += 100
ElseIf nErased == 3
 _nScore += 50
ElseIf nErased == 2
 _nScore += 25
ElseIf nErased == 1
 _nScore += 10
Endif
Return
/* ------------------------------------------------------
Seta o score do jogo na tela
Caso o jogo tenha terminado, acrescenta 
a mensagem de "GAME OVER"
------------------------------------------------------*/
STATIC Function PaintScore()
If _GlbStatus == 0
// JOgo em andamento, apenas atualiza score e timer
 _oScore:SetText(str(_nScore,7)+CRLF+CRLF+;
 '[Time]'+CRLF+str(seconds()-_nGameClock,7,0)+' s.')
ElseIf _GlbStatus == 1
// Pausa, acresenta a mensagem de "GAME OVER"
 _oScore:SetText(str(_nScore,7)+CRLF+CRLF+;
 '[Time]'+CRLF+str(seconds()-_nGameClock,7,0)+' s.'+CRLF+CRLF+;
 "*********"+CRLF+;
 "* PAUSE *"+CRLF+;
 "*********")
ElseIf _GlbStatus == 2
// Terminou, acresenta a mensagem de "GAME OVER"
 _oScore:SetText(str(_nScore,7)+CRLF+CRLF+;
 '[Time]'+CRLF+str(_nGameClock,7,0)+' s.'+CRLF+CRLF+;
 "********"+CRLF+;
 "* GAME *"+CRLF+;
 "********"+CRLF+;
 "* OVER *"+CRLF+;
 "********")
Endif
Return

Conclusão

Para se tirar o melhor de cada ferramenta, devemos conhecê-la, e quanto mais profundamente a conhecemos, melhores são os resultados que podemos obter dela. Espero que isso estimulem vocês a quererem saber mais, e colocar em prática o seu conhecimento. E, aprender também pode ser divertido !!! Espero que todos gostem !! Até o próximo post, pessoal 😉

Referências

TDN – Totvs Development Network. TOTVS, 2015. Disponível em http://tdn.totvs.com/display/tec/AdvPL . Acesso em: 26 fev. 2015.

TETRIS. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2015. Disponível em: [http://pt.wikipedia.org/w/index.php?title=Tetris&oldid=41347620]. Acesso em: 23 fev. 2015.

Tetris. (2015, February 16). In Wikipedia, The Free Encyclopedia. Retrieved 04:38, February 23, 2015, from http://en.wikipedia.org/w/index.php?title=Tetris&oldid=647443725

12 respostas em “Jogos em AdvPL – Tetris

  1. A incrível versatilidade e competência de um especialista em diversas linguagens de computação e gênio de conjugações e de soluções em relações sociais, tecnológicas e empresariais !
    (Ah, trabalhei bem perto do JULIO LOUCO, além de eu ser o SIGA0983 ! )

    Curtido por 1 pessoa

  2. Excelente blog! Parabéns! Uma dúvida e um desafio: Sabe se seria possível chamar um fonte via parâmetros da SX6? Tenho uma situação em que não tenho ponto de entrada e somente a checagem de parâmetros pode auxiliar na posição que preciso. Desafio lançado. Grato.

    Curtido por 1 pessoa

    • Olá Allan, boa tarde,

      Rapaz, a função GetMv(), usada para ler dados do SX6 não faz internamente nenhuma macro ou chamada de função customizada 😉

      Abraços

      Curtir

  3. Pingback: Tetris em AdvPL, aprenda a desenvolver esse jogo

Deixe um comentário