Created
May 18, 2013 12:40
-
-
Save frodosda/5604272 to your computer and use it in GitHub Desktop.
BreakOut Game Code - Stanford CS106A Assignment 3 - My Solutiion (Comments in Portuguese)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* File: Arkanoid.java | |
* ------------------- | |
* | |
* This recreates the BreakOut Game - also known as Arkanoid. After clicking to start the user can control the | |
* paddle with the mouse and keyboard and has to hit the ball in order to destroy bricks on top. | |
* | |
* Some bricks drop stars of diferrent colors. Each color when picked up by the paddle has a different effect. | |
* The bricks and the items add to the overall score (some stars actually decrease the score when picked up). | |
* | |
* The game ends when all the bricks are destroyed (victory) or when all the lives are spent (defeat). | |
*/ | |
import acm.graphics.*; | |
import acm.program.*; | |
import acm.util.*; | |
import java.applet.*; | |
import java.awt.*; | |
import java.awt.event.*; | |
public class Arkanoid extends GraphicsProgram { | |
/** Width and height of application window in pixels */ | |
public static final int APPLICATION_WIDTH = 400; | |
public static final int APPLICATION_HEIGHT = 600; | |
/** Dimensions of game board (usually the same) */ | |
private static final int WIDTH = APPLICATION_WIDTH; | |
private static final int HEIGHT = APPLICATION_HEIGHT; | |
/** Dimensions of the paddle */ | |
private static final int PADDLE_WIDTH = 60; | |
private static final int PADDLE_HEIGHT = 10; | |
/** Offset of the paddle up from the bottom */ | |
private static final int PADDLE_Y_OFFSET = 30; | |
/** Number of bricks per row */ | |
private static final int NBRICKS_PER_ROW = 10; | |
/** Number of rows of bricks */ | |
private static final int NBRICK_ROWS = 10; | |
/** Separation between bricks */ | |
private static final int BRICK_SEP = 4; | |
/** Width of a brick */ | |
private static final int BRICK_WIDTH = | |
(WIDTH - (NBRICKS_PER_ROW -1) * BRICK_SEP) / NBRICKS_PER_ROW; | |
/** Height of a brick */ | |
private static final int BRICK_HEIGHT = 8; | |
/** Radius of the ball in pixels */ | |
private static final int BALL_RADIUS = 8; | |
/** Offset of the top brick row from the top */ | |
private static final int BRICK_Y_OFFSET = 70; | |
/** Number of turns */ | |
private static final int NTURNS = 3; | |
/** Comprimento da GLabel que mostra as Vidas */ | |
private static final int VIDAS_LBL_WIDTH = 60; | |
/** Altura da GLabel as Vidas */ | |
private static final int VIDAS_LBL_HEIGHT = 30; | |
/** Runs the Breakout program. */ | |
public void run() { | |
addMouseListeners(); | |
addKeyListeners(); | |
criarNivel(); | |
correrJogo(); | |
} | |
private void criarNivel () { | |
criarTijolos(); | |
criarRaquete(); | |
criarBola(); | |
criarInfo(); | |
} | |
/** Cria o conjunto de Tijolos (10x10) do BreakOut centrados na horizontal e na posição vertical designada. | |
* Cada 2 filas possui uma cor diferente - do topo para baixo: VERMELHO, LARANJA, AMARELO, VERDE E AZUL CLARO. */ | |
private void criarTijolos () { | |
item = new GEstrela (30); // Define os itens (estrelas) que saem dos tijolos | |
itemNoEcra = false; | |
nTijolos = 0; // define como 0 o número inicial de tijolos /tela em branco) | |
int offsetX = BRICK_SEP / 2; // Espaço em branco a acrescentar na horizontal para que a figura fique centrada no ecrã | |
for (int i = 0; i < NBRICK_ROWS; i++) { // Cria cada fila de Tijolos | |
for (int j = 0; j < NBRICKS_PER_ROW; j++) { // Cria cada Coluna de Tijolos | |
// Define o espaçamento entre cada tijolo na horizontal e vertical | |
double dx = (j * BRICK_WIDTH) + (j * BRICK_SEP); | |
double dy = BRICK_Y_OFFSET + (BRICK_HEIGHT * i) + (i * BRICK_SEP); | |
// Cria os tijolos coloridos e adiciona-os na tela. | |
tijolo = new GRect (offsetX + dx, dy, BRICK_WIDTH, BRICK_HEIGHT); | |
tijolo.setFilled(true); | |
tijolo.setColor (corDaFila(i)); | |
add (tijolo); | |
nTijolos++; // conta o número de tijolos à medida que são criados | |
} | |
} | |
} | |
/** Vai atribuir as cores certas a cada fila de tijolos. | |
* @param numFila O número da fila em que os tijolos se encontram (sendo o número 0 a fila mais a topo) | |
* @return A cor adequada à fila */ | |
private Color corDaFila (int numFila) { | |
switch (numFila) { | |
case 0: case 1: return Color.RED; | |
case 2: case 3: return Color.ORANGE; | |
case 4: case 5: return Color.YELLOW; | |
case 6: case 7: return Color.GREEN; | |
case 8: case 9: return Color.CYAN; | |
default: return Color.BLACK; | |
} | |
} | |
/** Cria a Raquete centrada horizontalmente no ecrã e na zona inferior da tela */ | |
private void criarRaquete () { | |
nHitsRaquete = 0; // inicia a contagem de hits na raquete | |
raquete = new GRect (PADDLE_WIDTH, PADDLE_HEIGHT); | |
raquete.setFilled(true); | |
add (raquete, (WIDTH - PADDLE_WIDTH) / 2, HEIGHT - PADDLE_HEIGHT - PADDLE_Y_OFFSET); | |
} | |
/** Cria a bola e centra-a no ecrã */ | |
private void criarBola () { | |
double x = ((WIDTH - 2 * BALL_RADIUS) / 2); | |
double y = ((HEIGHT - 2 * BALL_RADIUS) / 2); | |
bola = new GOval (x, y, 2 * BALL_RADIUS, 2 * BALL_RADIUS); | |
bola.setFilled (true); | |
add (bola); | |
} | |
/** Cria uma GLabel no canto inferior esquerdo com o número de vidas que o jogador tem e outra no canto | |
* inferior direito com a pontuação até ao momento */ | |
private void criarInfo () { | |
// Define a pontuação inicial do jogador | |
score = 0; | |
vidas = new GLabel ("VIDAS: " + (NTURNS - vidasGastas), VIDAS_LBL_WIDTH, VIDAS_LBL_HEIGHT); | |
vidas.setFont("arial-bold-14"); | |
vidas.setColor(Color.RED); | |
add (vidas, 10 , HEIGHT - vidas.getAscent() /2); | |
pontuacao = new GLabel ("PONTUAÇÃO: " + score); | |
pontuacao.setFont("arial-bold-14"); | |
pontuacao.setColor(Color.BLUE); | |
add (pontuacao, WIDTH - pontuacao.getWidth() - 10 , HEIGHT - vidas.getAscent() /2); | |
} | |
/** Método que vai correr o jogo em si - contém o ciclo while principal, que será responsável pela animação | |
* dos movimentos no jogo - quer da bola, quer dos itens */ | |
private void correrJogo () { | |
// define o número inicial de vidas gastas | |
vidasGastas = 0; | |
// Define a direcão inicial da bola e a velocidade da mesma | |
vx = rGen.nextDouble(1.0, 3.0); // direcção horizontal aleatória | |
if (rGen.nextBoolean(0.5)) vx = -vx; | |
vy = 2.0; // direcção vertical | |
mensagemInicial(); | |
// O jogo corre até se gastarem todas as vidas (derrota) ou até se destruirem todos os tijolos (vitória) | |
while (vidasGastas < NTURNS && nTijolos > 0) { | |
moverBola(); | |
verificarColisaoBola(); | |
moverItens(); | |
verificarColisaoItens(); | |
} | |
fimDoJogo(); // corre as rotinas do final do jogo | |
} | |
/** Cria uma mensagem inicial ao jogador esperando que este carregue com o rato para iniciar o jogo */ | |
private void mensagemInicial() { | |
GLabel mensInicial = new GLabel ("CLICA PARA COMEÇAR", 300, 300); | |
mensInicial.setFont("arial-bold-30"); | |
mensInicial.setColor (Color.BLUE); | |
add (mensInicial, (WIDTH - mensInicial.getWidth()) / 2, (HEIGHT - mensInicial.getAscent()) / 2); | |
waitForClick(); | |
remove (mensInicial); | |
} | |
/** Método que cria o movimento da bola e muda-a de direcção sempre que faz ricochete nas paredes. */ | |
private void moverBola () { | |
// Faz com que a bola mude de direcção quando bate numa parede | |
if (bola.getX() >= WIDTH - bola.getWidth()) { // Parede Este | |
// Previne bug em que a bola por vezes ultrapassava parede (devido aos valores vx elevados) | |
bola.setLocation(WIDTH - bola.getWidth(), bola.getY()); | |
vx = -vx; | |
} else if (bola.getX() <= 0) { // Parede Oeste | |
bola.setLocation(0, bola.getY()); | |
vx = -vx; | |
} else if (bola.getY() >= (HEIGHT - bola.getHeight()) || bola.getY() <= 0) { // Parede Sul e Norte | |
vy = -vy; | |
if (bola.getY() >= (HEIGHT - bola.getHeight())){ | |
perderVida(); // Se a bola ultrapassa a Parede Sul o jogador gasta uma vida | |
} | |
} | |
bola.move(vx, vy); // faz a bola mover-se na direcção X e Y | |
pause(10); // pausa o programa durante x tempo para dar o efeito da animação | |
} | |
/** Verifica se a bola colidiu ou não com um objecto (neste caso a raquete ou um tijolo), assumindo o | |
* comportamento adequado em função do objecto em causa */ | |
private void verificarColisaoBola () { | |
GObject objColisao = getObjColisao(); // O objecto com que a bola colidiu | |
AudioClip clipRicochete = MediaTools.loadAudioClip("bounce.au"); // Som de Colisões | |
if (objColisao == raquete) { // Colisão com a raquete | |
vy = -vy; // Muda a tranjectória vertical da bola | |
// sobe ligeiramente a bola na altura da colisão para evitar que o a bola se "cole" à raquete quando | |
// se move esta última rapidamente - evita sobreponição de "colisões" com alterações de vy constantes | |
bola.setLocation(bola.getX(),bola.getY() - PADDLE_HEIGHT / 2); | |
// Altera a direcção da bola quando esta bate no bordo da raquete mais distante ao da direcção da bola | |
if ((bola.getX() < raquete.getX() + 0.20 * raquete.getWidth() && vx > 0) | |
|| (bola.getX() > raquete.getX() + 0.80 * raquete.getWidth() && vx < 0)) { | |
vx = -vx; | |
} | |
clipRicochete.play(); | |
nHitsRaquete++; // Contabiliza o número de vezes que a bola bate na raquete | |
// Aumenta a velocidade da bola depois de 7, 21 e 45 hits seguidos | |
if (nHitsRaquete == 7 || nHitsRaquete == 21 || nHitsRaquete == 45) vx *= 2; | |
} else if (objColisao == vidas || objColisao == pontuacao || objColisao == item){ | |
// por agora nada acontece | |
} else if (objColisao != null) { // Colisão com um tijolo | |
remove (objColisao); // remove o tijolo | |
nTijolos--; // desconta 1 ao número de tijolos | |
vy = -vy; | |
clipRicochete.play(); | |
addPontuacao(objColisao.getColor()); // Adiciona os pontos correspondentes ao tijolo | |
// Cria a possibilidade de dropar um Item do tijolo, desde que não haja já um item no ecrã | |
if (itemNoEcra == false) itemAleatorio(); | |
} | |
} | |
/** Descobre se os limites da bola (cantos do GRect que envolve o GOval) colidiram com um objecto | |
* e identifica-o | |
* @return O objecto que colidiu com a bola (ou null caso não tenha ocorrido colisão) */ | |
private GObject getObjColisao () { | |
if (getElementAt (bola.getX(), bola.getY()) != null) { // Canto superior esquerdo | |
return getElementAt (bola.getX(), bola.getY()); | |
} else if (getElementAt (bola.getX() + bola.getWidth(), bola.getY()) != null) { // Superior dto | |
return getElementAt (bola.getX() + bola.getWidth(), bola.getY()); | |
} else if (getElementAt (bola.getX(), bola.getY() + bola.getWidth()) != null) { // Inf esq | |
return getElementAt (bola.getX(), bola.getY() + bola.getWidth()); | |
} else if (getElementAt (bola.getX() + bola.getWidth(), bola.getY() + bola.getWidth()) != null) { // Inf dto | |
return getElementAt (bola.getX() + bola.getWidth(), bola.getY() + bola.getWidth()); | |
} else { | |
return null; | |
} | |
} | |
/** Define uma dada probabilidade de um item sair e cria um item aleatóriamente quando isso acontece: | |
* Itens possíveis: Estrelas: Vermelha, Laranja, Verde, Magenta, Azul */ | |
private void itemAleatorio () { | |
boolean saiItem = rGen.nextBoolean(0.35); | |
if (saiItem == true) { | |
int numItem = rGen.nextInt(1, 5); | |
switch (numItem) { | |
case 1: item.setColor(Color.RED); break; | |
case 2: item.setColor(Color.ORANGE); break; | |
case 3: item.setColor(Color.GREEN); break; | |
case 4: item.setColor(Color.MAGENTA); break; | |
default: item.setColor(Color.BLUE); break; | |
} | |
add (item, bola.getX(), bola.getY()); | |
itemNoEcra = true; | |
} | |
} | |
/** Faz descer os itens até ao fundo do ecrã e remove-os quando lá chegam */ | |
private void moverItens () { | |
if (item.getY() - item.getHeight() / 2 < HEIGHT) { | |
item.move(0, 2); | |
} | |
if (item.getY() - item.getHeight() / 2 >= HEIGHT) { | |
remove (item); | |
itemNoEcra = false; | |
} | |
} | |
/** Verifica se os itens que caem dos tijolos colidiram com a Raquete de Jogo e em caso afirmativo remove os | |
* itens do ecrã e atribui ao jogador o bónus respectivo */ | |
private void verificarColisaoItens () { | |
GObject itemColisao = getRaqueteColisao(); | |
if (itemColisao == item) { | |
addBonusItem(itemColisao.getColor()); | |
remove (itemColisao); | |
itemNoEcra = false; | |
} | |
} | |
/** Divide a raquete em quatro pontos e detecta se qualquer um destes pontos colidiu com um objecto. | |
* @return O objecto que colidiu com a raquete (ou null caso não tenha ocorrido colisão) */ | |
private GObject getRaqueteColisao () { | |
// Divide a Raquete horizontalmente em 4 pontos | |
double raqEsq = raquete.getX(); | |
double raqCentrEsq = raquete.getX() + raquete.getWidth() / 3; | |
double raqCentrDto = raquete.getX() + 2 * raquete.getWidth() / 3; | |
double raqDto = raquete.getX() + raquete.getWidth(); | |
// Superficie vertical imediatamente superior à raquete (para evitar que detecte a própria raquete) | |
double raqSup = raquete.getY() - 1; | |
if (getElementAt (raqEsq, raqSup) != null) { | |
return getElementAt (raqEsq, raqSup); | |
}else if (getElementAt (raqCentrEsq, raqSup) != null) { | |
return getElementAt (raqCentrEsq, raqSup); | |
}else if (getElementAt (raqCentrDto, raqSup) != null) { | |
return getElementAt (raqCentrDto, raqSup); | |
}else if (getElementAt (raqDto, raqSup) != null) { | |
return getElementAt (raqDto, raqSup); | |
} else { | |
return null; | |
} | |
} | |
/** Adiciona um determinado efeito cada vez que um determinado item é apanhado pela raquete | |
* ESTRELA VERDE: Aumenta o tamanho da bola e diminui ligeiramente a velocidade (Vale 3000 pontos) | |
* ESTRELA VERMELHA: Diminui o tamanho da bola e aumenta a velocidade (Subtrai 3000 pontos) | |
* ESTRELA LARANJA: 75% de Diminuir a velocidade (4000 pontos) e 25% de Aumentar a Velocidade (-5000 pontos) | |
* ESTRELA AZUL: Aumenta o tamanho da raquete - até 3x mais (Vale 3000 pontos) | |
* ESTRELA MAGENTA: Diminui o tamanho da raquete - até 3x menos (Subtrai 3000 pontos) | |
* @param corEstrela A cor da estrela que cai */ | |
private void addBonusItem (Color corEstrela) { | |
if (corEstrela.equals(Color.GREEN)) { | |
bola.setSize(bola.getWidth() * 1.5, bola.getHeight() * 1.5); | |
bola.setColor(Color.GREEN); | |
vx /= 1.5; | |
score += 3000; | |
} else if (corEstrela.equals(Color.RED)) { | |
bola.setSize(bola.getWidth() / 1.5, bola.getHeight() / 1.5); | |
bola.setColor(Color.RED); | |
vx *= 2; | |
score -= 3000; | |
} else if (corEstrela.equals(Color.ORANGE)) { | |
boolean aleat = rGen.nextBoolean(0.75); | |
if (aleat == true) vx /= 2; score += 4000; | |
if (aleat == false) vx *= 2; score -= 5000; | |
} else if (corEstrela.equals(Color.BLUE)) { | |
if (raquete.getWidth() <= PADDLE_WIDTH * 3) { | |
raquete.setSize(raquete.getWidth() * 1.5, PADDLE_HEIGHT); | |
raquete.setColor(Color.BLUE); | |
score += 3000; | |
} | |
} else if (corEstrela.equals(Color.MAGENTA)) { | |
if (raquete.getWidth() >= PADDLE_WIDTH / 3) { | |
raquete.setSize(raquete.getWidth() / 1.5, PADDLE_HEIGHT); | |
raquete.setColor(Color.MAGENTA); | |
score -= 3000; | |
} | |
actualizarPontuacao(); // Actualiza a pontuação | |
} | |
} | |
/** Atribui uma determinada pontuação a cada tijolo dependendo da sua cor | |
* @param corTijolo A cor do tijolo atingido */ | |
private void addPontuacao (Color corTijolo) { | |
if (corTijolo.equals(Color.CYAN)) score += 100; | |
if (corTijolo.equals(Color.GREEN)) score += 200; | |
if (corTijolo.equals(Color.YELLOW)) score += 500; | |
if (corTijolo.equals(Color.ORANGE)) score += 1000; | |
if (corTijolo.equals(Color.RED)) score += 2500; | |
actualizarPontuacao(); // actualiza a pontuação no ecrã | |
} | |
/** Método coadjuvante que actualiza a pontuação no canto inferior direito do ecrã de jogo */ | |
private void actualizarPontuacao () { | |
pontuacao.setLabel(("PONTUAÇÃO: " + score)); | |
pontuacao.setLocation(WIDTH - pontuacao.getWidth() - 10 , HEIGHT - vidas.getAscent() /2); | |
} | |
/** Determina o que acontece quando o jogador perde uma vida. Prepara o jogo para uma nova tentativa, | |
* reinicializando a bola e a raquete e descontando uma das vidas. */ | |
private void perderVida () { | |
vidasGastas++; | |
vidas.setLabel("VIDAS: " + (NTURNS - vidasGastas)); // Actualiza informação das vidas | |
if (vidasGastas < NTURNS) { // Se o jogador ainda tiver vidas faz um mini-reset ao campo de jogo | |
remove (bola); | |
remove (raquete); | |
criarBola(); | |
criarRaquete(); | |
nHitsRaquete = 0; | |
vx = rGen.nextDouble(1.0, 3.0); | |
if (rGen.nextBoolean(0.5)) vx = -vx; | |
vy = -vy; // para que a bola caia na direcão certa | |
mensagemInicial(); | |
} | |
} | |
/** Cria as mensagens do Final do Jogo quer o resultado seja uma vitória ou uma derrota do jogador. | |
* Mostra essa mensagem juntamente com a pontuação adquirida */ | |
private void fimDoJogo () { | |
GLabel mensFinal = new GLabel (""); | |
mensFinal.setFont("arial-bold-30"); | |
pontuacao.setFont("arial-bold-30"); | |
score *= (NTURNS - vidasGastas); | |
if (vidasGastas == NTURNS) { // Mensagem de Derrota - Perdeu todas as vidas | |
mensFinal.setLabel("GAME OVER!"); | |
mensFinal.setColor (Color.RED); | |
} else if (nTijolos == 0) { // Mensagem de vitória - destruiu todos os tijolos | |
mensFinal.setLabel("GANHASTE!"); | |
mensFinal.setColor (Color.GREEN); | |
} | |
removeAll(); // remove todos os elementos do jogo | |
add (mensFinal, (WIDTH - mensFinal.getWidth()) / 2, (HEIGHT - mensFinal.getAscent()) / 2); | |
add (pontuacao, (WIDTH - pontuacao.getWidth()) / 2, (HEIGHT - pontuacao.getAscent()) / 1.5); | |
} | |
/**Alinha o centro horizontal da raquete sobre o ponteiro do rato, fazendo com que a raquete acompanhe | |
* o movimento horizontal do rato até que esta chegue aos limites do ecrã, nunca os ultrapassando */ | |
public void mouseMoved (MouseEvent e) { | |
double centroRaq = PADDLE_WIDTH / 2; // define o centro da raquete | |
// alinha o centro horizontal da raquete com o ponteiro do rato e fá-la acompanhar o seu movimento | |
raquete.setLocation(e.getX() - centroRaq, HEIGHT - PADDLE_HEIGHT - PADDLE_Y_OFFSET); | |
limitarRaquete(); // evita que a raquete ultrapasse os limites do ecrã | |
} | |
/** Permite o controlo da raquete com o teclado - utilizando a seta para a esquerda ou para a direita */ | |
public void keyPressed (KeyEvent e) { | |
switch (e.getKeyCode()) { | |
case KeyEvent.VK_LEFT: raquete.move(-WIDTH / 15, 0); break; | |
case KeyEvent.VK_RIGHT: raquete.move(WIDTH / 15, 0); break; | |
} | |
limitarRaquete(); // evita que a raqueta saia do ecrã | |
} | |
/** Evita que a Raquete saia do ecrã quando está a ser manipulada pelo rato ou pelo teclado */ | |
private void limitarRaquete () { | |
if (raquete.getX() < 0) { | |
raquete.setLocation(0, HEIGHT - PADDLE_HEIGHT - PADDLE_Y_OFFSET); | |
} else if (raquete.getX() + raquete.getWidth() > WIDTH) { | |
raquete.setLocation(WIDTH - raquete.getWidth(), HEIGHT - PADDLE_HEIGHT - PADDLE_Y_OFFSET); | |
} | |
} | |
private double vx, vy; // Variáveis que definem a deslocação / velocidade horizontal e vertical da bola | |
private boolean itemNoEcra; // Determina se o item que cai se encontra presente no ecrã de jogo | |
private int score; // A pontuação do jogo | |
private int nHitsRaquete; // O número de vezes que a bola bateu na raquete | |
private int nTijolos; // O número de tijolos em jogo | |
private int vidasGastas; // O número de vidas perdidas | |
private GEstrela item; // Variável de instância que define cada estrela | |
private GLabel pontuacao; // Variável de instância que define o texto com a pontuação | |
private GLabel vidas; // Variável de instância que define o texto com o número de vidas | |
private GRect tijolo; // Variável de instância que define cada tijolo | |
private GRect raquete; // Variável de instância que define a raquete | |
private GOval bola; // Variável de instância que define a bola | |
// Gerador de Números Aleatórios | |
private RandomGenerator rGen = RandomGenerator.getInstance(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Ficheiro: GEstrela | |
* | |
* Define uma nova classe GObject que aparece como uma estrela de 5 pontas | |
*/ | |
import acm.graphics.*; | |
public class GEstrela extends GPolygon { | |
/** | |
* Cria uma nova GEstrela centrada no ponto de origem com uma largura horizontal especifica. | |
* @param largura A largura da estrela | |
*/ | |
public GEstrela (double largura) { | |
double dx = largura / 2; | |
double dy = dx * GMath.tanDegrees(18); | |
double limite = largura / 2 - dy * GMath.tanDegrees(36); | |
addVertex (-dx, -dy); | |
int angulo = 0; | |
for (int i = 0; i < 5; i++) { | |
addPolarEdge (limite, angulo); | |
addPolarEdge (limite, angulo + 72); | |
angulo -= 72; | |
setFilled(true); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment