Capítulo 11 — Quando existem alternativas
Série: Do zero ao jogo comercial com C++ e SFML
Nosso programa já sabe perguntar. Agora ele precisa escolher entre dois caminhos diferentes.
No capítulo anterior nosso programa finalmente começou a tomar decisões.
Aprendemos a utilizar:
if
para executar um bloco somente quando uma condição fosse verdadeira.
Por exemplo:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
}
Essa regra já resolve metade do problema.
Se o jogador possuir moedas suficientes:
playerCoins >= itemPrice
↓
true
↓
realizar compra
Mas e se não possuir?
Atualmente nosso programa simplesmente continua.
Nenhuma mensagem.
Nenhuma reação.
Nenhuma ação alternativa.
Queremos representar:
Possui moedas suficientes?
│
├── SIM → comprar o item
│
└── NÃO → informar que faltam moedas
Agora existem duas alternativas.
E é exatamente esse problema que nos leva ao:
else
1. Relembrando a limitação do if
Considere:
int playerCoins = 30;
int itemPrice = 50;
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
}
A condição:
playerCoins >= itemPrice
é avaliada como:
30 >= 50
↓
false
Então o bloco não executa.
O programa simplesmente segue adiante.
Mas talvez nossa regra exija uma resposta diferente quando a condição for falsa.
2. Precisamos representar o outro caminho
Queremos algo conceitualmente assim:
SE playerCoins >= itemPrice
comprar
SENÃO
informar que faltam moedas
Em C++, essa segunda alternativa pode ser representada com:
else
A estrutura básica fica:
if (condition)
{
// executado quando a condição é verdadeira
}
else
{
// executado quando a condição é falsa
}
Agora nosso programa possui dois caminhos possíveis.
3. Nosso primeiro if com else
#include <iostream>
int main()
{
int playerCoins = 30;
int itemPrice = 50;
if (playerCoins >= itemPrice)
{
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
return 0;
}
Como:
30 >= 50
é falso, o resultado será:
Not enough coins.
Se alterarmos:
int playerCoins = 80;
teremos:
80 >= 50
↓
true
Então:
Item purchased.
4. Apenas um dos dois blocos será executado
Essa é a principal diferença em relação a dois if independentes.
Com:
if (condition)
{
// caminho A
}
else
{
// caminho B
}
temos:
condition
│
├── true
│ ↓
│ bloco do if
│
└── false
↓
bloco do else
Uma condição.
Dois caminhos.
Apenas um deles é escolhido.
5. else não possui uma nova condição
Observe:
if (playerCoins >= itemPrice)
{
}
else
{
}
O else não precisa escrever:
playerCoins < itemPrice
Ele significa:
execute este bloco quando a condição do
ifanterior for falsa.
Portanto:
if
↓
condição verdadeira
else
↓
a mesma condição foi falsa
6. Transformando a compra em uma regra completa
Agora podemos escrever:
#include <iostream>
int main()
{
int playerCoins = 80;
int itemPrice = 50;
std::cout << "Coins Before Purchase: " << playerCoins << std::endl;
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
std::cout << "Coins After Purchase: " << playerCoins << std::endl;
return 0;
}
Resultado:
Coins Before Purchase: 80
Item purchased.
Coins After Purchase: 30
Agora altere:
int playerCoins = 30;
Resultado:
Coins Before Purchase: 30
Not enough coins.
Coins After Purchase: 30
Temos dois cenários completos.
7. O estado muda apenas no caminho correto
Observe:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
Quando existem moedas suficientes:
80 - 50
=
30
Quando não existem:
30
↓
continua 30
Isso representa uma regra muito mais adequada.
Não apenas mostramos uma mensagem diferente.
Também impedimos uma alteração inválida de estado.
8. O else representa uma alternativa, não um erro
É importante não pensar em else como:
algo deu errado
Ele apenas representa:
a condição anterior foi falsa
Por exemplo:
if (playerHealth > 0)
{
std::cout << "Player is alive." << std::endl;
}
else
{
std::cout << "Player is defeated." << std::endl;
}
Nenhum dos dois caminhos é tecnicamente um "erro".
São apenas estados diferentes.
9. Um exemplo com vida
#include <iostream>
int main()
{
int playerHealth = 25;
if (playerHealth > 0)
{
std::cout << "Player is alive." << std::endl;
}
else
{
std::cout << "Player is defeated." << std::endl;
}
return 0;
}
Com:
Health = 25
resultado:
Player is alive.
Agora:
int playerHealth = 0;
Resultado:
Player is defeated.
10. A regra precisa estar bem definida
Observe:
if (playerHealth > 0)
Isso implica que qualquer valor:
<= 0
cairá no else.
Então:
0
-1
-50
todos produziriam:
Player is defeated.
Talvez isso seja adequado.
Talvez não.
O ponto importante é:
o
elsecobre tudo que não satisfaz a condição doif.
Por isso a condição precisa representar corretamente a regra.
11. Duas alternativas precisam ser realmente complementares
Considere:
if (playerCoins >= itemPrice)
{
// pode comprar
}
else
{
// não pode comprar
}
Aqui temos uma divisão clara:
playerCoins >= itemPrice
ou:
não playerCoins >= itemPrice
Para números neste contexto, o segundo caminho corresponde ao caso em que as moedas são insuficientes.
Isso torna if/else uma boa representação.
12. Dois if independentes não significam a mesma coisa
Compare:
if (playerCoins >= itemPrice)
{
std::cout << "Item purchased." << std::endl;
}
if (playerCoins < itemPrice)
{
std::cout << "Not enough coins." << std::endl;
}
com:
if (playerCoins >= itemPrice)
{
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
Nosso segundo código expressa melhor a intenção:
existem duas alternativas para a mesma decisão.
No primeiro, temos duas decisões independentes.
13. Por que isso importa?
Quando escrevemos:
if (condition)
{
}
else
{
}
a própria estrutura comunica:
um caminho ou o outro
Isso ajuda quem lê o código a compreender que os dois blocos pertencem à mesma decisão.
Estrutura de código também comunica intenção.
14. Calculando quanto falta para comprar
Já podemos fazer uma pequena melhoria.
Quando o jogador não possui moedas suficientes:
else
{
std::cout << "Not enough coins." << std::endl;
}
podemos calcular:
int missingCoins = itemPrice - playerCoins;
Então:
else
{
int missingCoins = itemPrice - playerCoins;
std::cout << "Not enough coins." << std::endl;
std::cout << "Missing Coins: " << missingCoins << std::endl;
}
Se:
playerCoins = 30
itemPrice = 50
temos:
50 - 30 = 20
Resultado:
Not enough coins.
Missing Coins: 20
15. O cálculo pertence ao caminho em que faz sentido
Observe onde colocamos:
int missingCoins = itemPrice - playerCoins;
Ele está dentro do:
else
Isso é interessante.
Só precisamos calcular missingCoins quando realmente faltam moedas.
Estamos começando a perceber que blocos diferentes podem possuir trabalho diferente.
16. Variáveis dentro de blocos
Considere:
else
{
int missingCoins = itemPrice - playerCoins;
std::cout << missingCoins << std::endl;
}
A variável:
missingCoins
é criada dentro daquele bloco.
Ainda teremos um capítulo específico sobre escopo.
Então não aprofundaremos isso agora.
Por enquanto, observe apenas:
declaramos a variável perto do lugar onde ela é necessária.
Essa decisão já melhora a leitura.
17. Não vamos antecipar o capítulo de escopo
Talvez você tente utilizar:
missingCoins
fora do bloco e encontre um erro.
Isso acontece porque a existência e a visibilidade de variáveis dependem do escopo.
Esse assunto será estudado formalmente no Capítulo 20.
Por enquanto, mantenha:
missingCoins
dentro do bloco em que ela é utilizada.
18. Aplicando dano com dois resultados
Imagine:
int playerHealth = 25;
int damage = 40;
Primeiro:
int newHealth = playerHealth - damage;
Teremos:
25 - 40 = -15
Podemos utilizar:
if (newHealth > 0)
{
playerHealth = newHealth;
std::cout << "Player survived." << std::endl;
}
else
{
playerHealth = 0;
std::cout << "Player defeated." << std::endl;
}
Agora existem duas alternativas claras.
19. Implementação do cenário
#include <iostream>
int main()
{
int playerHealth = 25;
int damage = 40;
int newHealth = playerHealth - damage;
if (newHealth > 0)
{
playerHealth = newHealth;
std::cout << "Player survived." << std::endl;
}
else
{
playerHealth = 0;
std::cout << "Player defeated." << std::endl;
}
std::cout << "Health: " << playerHealth << std::endl;
return 0;
}
Resultado:
Player defeated.
Health: 0
Agora altere:
int damage = 10;
Resultado:
Player survived.
Health: 15
Dois caminhos.
Um único resultado final coerente.
20. Cuidado com a regra de vida igual a zero
Na condição:
if (newHealth > 0)
se:
newHealth = 0
o resultado será falso.
Então o jogador será considerado derrotado.
Isso corresponde à regra:
Health > 0 → vivo
Health <= 0 → derrotado
Se nosso Game Design fosse diferente, a condição também precisaria ser diferente.
Código e regra precisam concordar.
21. else depende imediatamente do if
A estrutura é:
if (condition)
{
}
else
{
}
O else pertence ao if correspondente.
Não podemos simplesmente escrever:
else
{
}
sozinho.
Ele representa a alternativa de uma decisão anterior.
22. Erro proposital — else sem if
Experimente:
#include <iostream>
int main()
{
else
{
std::cout << "Alternative." << std::endl;
}
return 0;
}
Faça Build.
O compilador não conseguirá interpretar esse else corretamente porque não existe um if associado.
Use nossa metodologia:
erro
↓
linha
↓
estrutura esperada
↓
estrutura existente
↓
correção
Depois restaure:
if (true)
{
}
else
{
}
O exemplo acima é apenas para demonstrar a estrutura; não precisamos preservar um if (true) no código real.
23. Erro proposital — ponto e vírgula continua perigoso
Considere:
if (playerCoins >= itemPrice);
{
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
Esse código não representa nossa intenção e pode inclusive gerar erro de compilação devido à forma como o else passa a se associar.
O problema começa aqui:
if (playerCoins >= itemPrice);
Aquele:
;
encerra a instrução controlada pelo if.
Não coloque ; após a condição.
24. O formato que adotaremos
Sempre:
if (condition)
{
// caminho verdadeiro
}
else
{
// caminho falso
}
Chaves presentes.
Indentação consistente.
Nenhuma tentativa de comprimir tudo em uma linha.
25. Não faremos isto
Mesmo sendo possível encontrar:
if (playerCoins >= itemPrice)
playerCoins -= itemPrice;
else
std::cout << "Not enough coins." << std::endl;
em nosso projeto preferiremos:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
O custo de algumas linhas extras é pequeno.
O ganho de estrutura visual é grande.
26. Também não precisamos repetir a condição inversa
Evite escrever algo como:
if (playerCoins >= itemPrice)
{
std::cout << "Can buy." << std::endl;
}
else
{
if (playerCoins < itemPrice)
{
std::cout << "Cannot buy." << std::endl;
}
}
Esse segundo teste é redundante.
Se chegamos ao else, já sabemos que:
playerCoins >= itemPrice
foi falso.
Para essa decisão binária simples, basta:
if (...)
{
}
else
{
}
27. O else reduz duplicação de raciocínio
Compare mentalmente:
perguntar se pode comprar
depois perguntar novamente se não pode comprar
com:
perguntar uma vez
se sim → A
senão → B
A segunda estrutura representa diretamente a decisão.
Menos condições desnecessárias também significam menos lugares para cometer erros.
28. Um exemplo com experiência
Considere:
const int EXPERIENCE_REQUIRED = 100;
int playerExperience = 80;
Podemos escrever:
if (playerExperience >= EXPERIENCE_REQUIRED)
{
std::cout << "Experience requirement reached." << std::endl;
}
else
{
int missingExperience = EXPERIENCE_REQUIRED - playerExperience;
std::cout << "More experience required." << std::endl;
std::cout << "Missing Experience: " << missingExperience << std::endl;
}
Resultado:
More experience required.
Missing Experience: 20
29. Testando o limite exato
Agora:
int playerExperience = 100;
Temos:
100 >= 100
↓
true
Então:
Experience requirement reached.
Essa é uma boa oportunidade para testar valores de fronteira.
30. Valores de fronteira importam
Se a regra é:
playerExperience >= EXPERIENCE_REQUIRED
devemos testar pelo menos:
99
100
101
Por quê?
Porque estamos interessados especialmente no ponto onde o comportamento muda.
99 → falso
100 → verdadeiro
101 → verdadeiro
Essa forma de pensar será muito importante em QA e automação de testes mais adiante.
31. Nosso primeiro pensamento sobre cenários
Para uma condição:
playerCoins >= itemPrice
podemos testar:
abaixo do preço
igual ao preço
acima do preço
Por exemplo:
49
50
51
com:
itemPrice = 50
Resultados esperados:
49 → else
50 → if
51 → if
Já estamos pensando em comportamento de fronteira sem precisar de um framework de testes.
32. if/else começa a aproximar código e regra de negócio do jogo
Considere:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
Isso representa uma regra.
Não é apenas sintaxe.
Temos:
pré-condição
↓
moedas suficientes?
caminho válido
↓
realizar compra
caminho inválido
↓
recusar compra
Mais tarde sistemas completos serão formados por regras muito mais sofisticadas.
Mas a base já está aqui.
33. Ainda não precisamos de ShopSystem
Nosso exemplo de compra possui:
int playerCoins;
int itemPrice;
e uma decisão.
Não precisamos criar:
ShopSystem
StoreManager
PurchaseService
TransactionProcessor
EconomyEngine
Esse seria exatamente o tipo de abstração prematura que queremos evitar.
Primeiro precisamos entender o comportamento.
34. Ainda não precisamos de funções
O código está começando a repetir padrões:
condição
↓
ação A
↓
ação B
Isso futuramente contribuirá para o crescimento do main.cpp.
Mas ainda não chegamos ao capítulo de funções.
Por enquanto é útil enxergar esse crescimento.
A necessidade de organização precisa ser sentida antes da abstração aparecer.
35. E se houver três possibilidades?
Agora surge uma pergunta natural.
Imagine Health:
Health > 70 → estado saudável
Health entre 30 e 70 → estado ferido
Health abaixo de 30 → estado crítico
Temos:
mais de dois caminhos
Um simples:
if (...)
{
}
else
{
}
possui apenas dois ramos.
Mas C++ permite construir cadeias de decisões.
36. else if existe
Você provavelmente encontrará:
if (conditionA)
{
}
else if (conditionB)
{
}
else
{
}
Isso permite representar mais de duas situações.
Mas não vamos aprofundar cadeias complexas neste artigo.
Nosso objetivo central foi entender corretamente a relação:
if
↕
else
O próximo capítulo abordará o problema de muitas opções, incluindo quando estruturas alternativas começam a ser úteis.
37. Por que não aprofundar tudo agora?
Poderíamos colocar neste mesmo artigo:
if
else
else if
nested if
logical AND
logical OR
logical NOT
switch
ternary operator
Mas teríamos novamente um capítulo grande demais e uma progressão pior.
Nossa sequência continua deliberadamente incremental.
Hoje:
uma condição
↓
dois caminhos
Depois:
múltiplas opções
38. Microdesafio — moedas suficientes
Comece:
int playerCoins = 75;
int itemPrice = 50;
Se puder comprar:
Item purchased.
Caso contrário:
Not enough coins.
Uma possível solução:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
39. Microdesafio — mostrar quanto falta
Agora, no caminho falso:
int missingCoins = itemPrice - playerCoins;
Mostre:
Missing Coins: 20
quando:
playerCoins = 30
itemPrice = 50
40. Microdesafio — vida
Comece:
int playerHealth = 20;
int damage = 25;
Calcule:
int newHealth = playerHealth - damage;
Se:
newHealth > 0
mostre:
Player survived.
Caso contrário:
Player defeated.
e defina:
playerHealth = 0;
quando derrotado.
41. Microdesafio — experiência
Comece:
const int EXPERIENCE_REQUIRED = 100;
int playerExperience = 70;
Se tiver experiência suficiente:
Experience requirement reached.
Caso contrário, calcule:
int missingExperience = EXPERIENCE_REQUIRED - playerExperience;
Resultado esperado:
More experience required.
Missing Experience: 30
42. Código final do capítulo
Vamos manter um cenário que reúne compra e progressão sem criar sistemas prematuramente:
#include <iostream>
int main()
{
const int EXPERIENCE_REQUIRED = 100;
int playerCoins = 30;
int itemPrice = 50;
int playerExperience = 80;
std::cout << "=== PURCHASE ===" << std::endl;
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
std::cout << "Coins Left: " << playerCoins << std::endl;
}
else
{
int missingCoins = itemPrice - playerCoins;
std::cout << "Not enough coins." << std::endl;
std::cout << "Missing Coins: " << missingCoins << std::endl;
}
std::cout << std::endl;
std::cout << "=== EXPERIENCE ===" << std::endl;
if (playerExperience >= EXPERIENCE_REQUIRED)
{
std::cout << "Experience requirement reached." << std::endl;
}
else
{
int missingExperience = EXPERIENCE_REQUIRED - playerExperience;
std::cout << "More experience required." << std::endl;
std::cout << "Missing Experience: " << missingExperience << std::endl;
}
return 0;
}
Resultado:
=== PURCHASE ===
Not enough coins.
Missing Coins: 20
=== EXPERIENCE ===
More experience required.
Missing Experience: 20
43. Agora teste o caminho verdadeiro
Altere:
int playerCoins = 80;
int playerExperience = 120;
Resultado esperado:
=== PURCHASE ===
Item purchased.
Coins Left: 30
=== EXPERIENCE ===
Experience requirement reached.
O mesmo programa agora percorre caminhos diferentes de acordo com seu estado.
44. Uma entrada, uma decisão, um caminho
Podemos resumir:
ESTADO
↓
CONDIÇÃO
↓
true / false
↓
ESCOLHA DE CAMINHO
↓
AÇÃO
↓
NOVO ESTADO
Esse padrão aparecerá incontáveis vezes durante o desenvolvimento do jogo.
45. Validando os cenários
Não teste apenas uma combinação.
Para a compra:
Item Price = 50
teste:
Coins = 49
Coins = 50
Coins = 51
Esperado:
49 → else
50 → if
51 → if
Para experiência:
Required = 100
teste:
Experience = 99
Experience = 100
Experience = 101
Esperado:
99 → else
100 → if
101 → if
Testar bordas começa a se tornar um hábito.
46. Erros comuns deste capítulo
Alguns problemas que você deve reconhecer:
Usar = em vez de ==
if (playerLevel = 2)
quando queria:
if (playerLevel == 2)
Colocar ; após o if
if (condition);
Esquecer as chaves
Mesmo quando tecnicamente possível, não seguiremos esse estilo.
Repetir a condição inversa dentro do else
else
{
if (...)
{
}
}
quando o segundo teste é desnecessário.
Alterar estado no caminho errado
Por exemplo, descontar moedas fora do bloco que validou a compra.
47. Nosso estado arquitetural continua simples
Nada mudou na estrutura:
CppGameEngine/
└── main.cpp
Adicionamos comportamento.
Não adicionamos infraestrutura.
Essa diferença é importante.
Estamos deixando o software crescer em capacidade, e não apenas em quantidade de arquivos.
48. Git
Depois de validar todos os cenários:
git status
Adicione:
git add CppGameEngine/main.cpp
Ajuste o caminho se necessário.
Commit sugerido:
git commit -m "feat: add alternative gameplay decision paths"
A mudança representa:
nossas decisões agora podem executar caminhos diferentes para condições verdadeiras e falsas.
49. Atualizando o CHANGELOG
Em [Unreleased]:
### Added
- Added alternative decision paths using `if` and `else`.
- Added purchase feedback for successful and insufficient coin scenarios.
- Added missing coin calculation for failed purchases.
- Added experience requirement feedback for both decision outcomes.
- Added boundary scenario validation for conditional rules.
Depois:
git add CHANGELOG.md
git commit -m "docs: update changelog with alternative decision paths"
Não precisamos atualizar o ROADMAP técnico neste capítulo.
50. O que aprendemos?
No Capítulo 10 nosso programa aprendeu:
SE condição for verdadeira → execute
Agora evoluímos para:
SE condição for verdadeira → execute A
SENÃO → execute B
Conhecemos:
if (condition)
{
}
else
{
}
e aprendemos que:
o
ifrepresenta o caminho verdadeiro;o
elserepresenta o caminho quando a condição é falsa;apenas um dos dois blocos é executado;
elsenão precisa repetir a condição inversa;dois
ifindependentes não comunicam necessariamente a mesma intenção queif/else;estado deve ser modificado apenas no caminho que representa uma operação válida;
regras de fronteira precisam ser testadas;
valores imediatamente abaixo, iguais e acima de um limite são particularmente importantes;
blocos diferentes podem possuir cálculos específicos de cada caminho;
não precisamos criar sistemas, funções ou classes prematuramente.
Conclusão
Nosso programa já conseguia perguntar:
Player possui Coins suficientes?
Antes ele sabia apenas:
SIM → comprar
Agora sabe representar:
SIM → comprar
NÃO → informar que faltam moedas
Isso parece simples, mas muda bastante nosso modelo de execução.
Agora temos:
estado
↓
condição
↓
escolha
├── caminho A
└── caminho B
↓
resultado
Jogos são formados por escolhas desse tipo o tempo inteiro.
Existe espaço no inventário?
├── sim → coletar
└── não → recusar
Jogador possui a chave?
├── sim → abrir
└── não → manter fechado
Crop está madura?
├── sim → colher
└── não → continuar crescendo
Mas nem toda situação possui apenas duas alternativas.
Imagine um menu:
1 → New Game
2 → Load Game
3 → Settings
4 → Exit
Ou um estado:
SPRING
SUMMER
AUTUMN
WINTER
Ou uma seleção:
Sword
Axe
Pickaxe
Hoe
Watering Can
Poderíamos criar uma longa sequência de decisões.
Mas talvez exista uma estrutura mais apropriada quando um único valor precisa ser comparado contra várias opções.
Esse será nosso próximo problema.
Próximo capítulo
Capítulo 12 — Muitas opções: quando switch ajuda
Série: Do zero ao jogo comercial com C++ e SFML
No próximo artigo partiremos de um problema como:
opção == 1?
opção == 2?
opção == 3?
opção == 4?
e analisaremos o que acontece quando começamos a empilhar várias decisões para representar opções discretas.
Só então conheceremos:
switch
e conceitos como:
case
break
default
Também veremos algo essencial:
switchnão substituiif.
Ele resolve melhor alguns tipos específicos de problema.
Nosso programa já sabe escolher entre dois caminhos.
No próximo capítulo ele aprenderá a lidar com muitas opções de maneira mais organizada.
Comentários
Ainda não há comentários. Sê o primeiro a comentar!