Nota 1: Nos exercícios com imagens, as imagens devem ser guardadas em modo
Git LFS na pasta solucoes/02/
e devem ter como nome o número do exercício.
Nota 2: Nos exercícios em que faça sentido submeter uma solução ou projeto
.NET Console App completo, podes colocar o mesmo numa pasta com o número
do exercício + underscore + nº da solução, por exemplo solucoes/02/005_sol1
.
Nota 3: Nos exercícios em Unity deves seguir a mesma lógica que no caso
anterior, mas deves também incluir nessa mesma pasta os ficheiros .gitignore
e .gitattributes
disponíveis em
https://github.com/VideojogosLusofona/unity_git.
1 - Considera o seguinte código:
class Program
{
static void Main(string[] args)
{
int[] arrayOfInts = new int[] { 1, 2, 3, 4 };
foreach (int i in arrayOfInts)
{
Console.WriteLine(i);
}
}
}
- Converte o código apresentado para que faça uso explícito do enumerador devolvido pelo array.
- Qual é ou quais são as vantagens do uso de
foreach
sobre o uso explícito de enumeradores? - Qual é a interface que a classe
array
implementa que a obriga a ter um método para devolver um enumerador? Esta implementação é direta ou através de outras interfaces intermédias? Se se verificar este último caso, indica essa hierarquia de interfaces. - Qual é o design pattern que propõe esta forma de aceder sequencialmente aos elementos de um objeto agregado/coleção, independentemente da forma como o mesmo foi implementado.
2 - Considera o seguinte código inicial para um jogo de real-time strategy (RTS):
public struct Vector2
{
public float X { get; }
public float Y { get; }
public Vector2(float x, float y)
{
X = x;
Y = y;
}
}
public interface IUnit
{
string UnitName { get; }
Vector2 Position { get; }
float Health { get; }
void Move(Vector2 newPosition);
}
public class Unit : IUnit
{
public string UnitName { get; private set; }
public Vector2 Position { get; private set; }
public float Health { get; private set; }
public Unit(string name, Vector2 position, float health)
{
UnitName = name;
Position = position;
Health = health;
}
public void Move(Vector2 newPosition)
{
Position = newPosition;
}
public override string ToString()
{
return $"{UnitName} at ({Position.X:f1}, {Position.Y:f1}) " +
$"with {Health:f1} HP";
}
}
Chegaste a um ponto no desenvolvimento do teu jogo em que pretendes ter
batalhões ou grupos de unidades que funcionem de forma semelhante às unidades
individuais, nomeadamente que implementem a interface IUnit
. Queres ainda
permitir a existência de sub-batalhões, ou seja, sub-grupos dentro dos grupos
de unidades. O funcionamento das propriedades e dos métodos de um grupo de
unidades deve ser o seguinte:
Name
- Um grupo de unidades deve ter o nomeGroup of x units
, em quex
representa o número de unidades no grupo (incluíndo unidades em sub-grupos).Position
- Deve representar o centroide (média da posição x e média da posição y) das posições das unidades no grupo. A posição de um sub-grupo deve contar apenas como uma unidade.Health
- Média daHealth
das unidades individuais. AHealth
de um sub-grupo deve contar apenas como uma unidade.Move()
- A posição dada é o novo centroide do grupo. Todas as unidades individuais devem manter as posições relativas entre si. Para o efeito deve ser determinado o vetor de movimento entre os dois centroides (= newPos - oldPos
), e este vector deve ser adicionado à posição atual de cada unidade no grupo, como ilustrado na seguinte imagem:
Autoria dos ícones na imagem: Eucalyp (Flaticon)
- Qual o design pattern ideal para resolveres este problema?
- Aplica e adapta o design pattern em questão para resolveres o problema, minimizando dentro do possível alterações ao código existente. Nota que os design patterns apenas oferecem soluções gerais, pelo que não tens de seguir exatamente o respetivo template, sobretudo se isso fizer sentido e melhorar a tua solução final.
- Cria uma classe
Program
com o métodoMain()
para testares a solução desenvolvida em modo consola e sem interação com o utilizador.
3 - Adapta o código da solução anterior para uso num novo projeto 2D em Unity.
O projeto deve ser muito simples (sem animações, som, etc), e permitir a
seleção de uma unidade com o botão esquerdo do rato (eliminando a seleção
anterior) e o agrupamento de várias unidades com a combinação
Ctrl+LeftMouseButton
. Clicar numa zona sem unidades tem o efeito de mover
a(s) unidade(s) selecionada(s) para essa localização. As várias ações no jogo
devem ser descritas na consola do Unity com o método Debug.Log()
, fazendo uso
do(s) override(s) de ToString()
da(s) classe(s) que representa(m)
unidade(s).
Descreve ainda quais as principais alterações que tiveste de fazer no código original para o mesmo ser adaptado ao Unity.
Nota: Para resolução deste exercício não deves usar a hierarquia de game objects do Unity para agrupar as unidades. Deves manter, dentro do possível, a estrutura original do código do exercício anterior.
4 - Resolve o problema anterior usando a hierarquia de game objects do Unity para agrupar e mover as unidades em grupo.
5 - Considera as co-rotinas do Unity e responde às seguintes questões:
- Apresenta o código de uma co-rotina que verifique, de 5 em 5 segundos, se a
variável de instância
health
é inferior a 25.0f, e em caso afirmativo, imprima "Low health" na consola. - Considera a variável de instância
healthCheck
do tipoIEnumerator
, cujo valor é obtido com a invocação da co-rotina da alínea anterior. Apresenta uma linha de código para dar início à co-rotina, e outra linha de código para parar a co-rotina.
6 - Como podes fazer com que uma co-rotina seja invocada com um período de tempo aleatório entre um dado mínimo e máximo? Apresenta um exemplo em código.
Nota: Este exercício tem múltiplas formas de ser resolvido, sendo apropriado para várias soluções.
7 - Modifica a seguinte linha do seguinte código de modo a que respeite o dependency inversion principle, tendo em conta que a coleção em questão terá de suportar a adição, remoção e contagem de itens:
List<string> containerOfStrings = new List<string>();
Justifica a tua resposta.
8 - Considera a seguinte classe:
[Awesome] public class BestEverClass { }
Responde às seguintes questões:
- Cria um atributo
chamado
AwesomeAttribute
, sem parâmetros, aplicável apenas a classes. - Na classe
Program
, métodoMain()
:- Cria um array de
object
com os seguintes conteúdos:- Índice 0: instância de
string
com a frase "String de teste". - Índice 1: instância de
Random
. - Índice 2: instãncia de
BestEverClass
.
- Índice 0: instância de
- Percorre o array com um
foreach
, obtendo para cada objeto o seuType
, e analisando através de reflexão, se o tipo em questão tem ou não o atributoAwesomeAttribute
, mostrando essa informação no ecrã. Para o caso do array em questão, deverá aparecer no ecrã o seguinte:
- Cria um array de
Type 'string' is not awesome :(
Type 'Random' is not awesome :(
Type 'BestEverClass' is awesome :)
Nota: Exercício de dificuldade elevada que requer ir um pouco além da matéria lecionada nas aulas.
9 - Cria um programa em C# que aceite como único argumento da linha de comandos
um nome de ficheiro e imprima os conteúdos desse ficheiro no ecrã (semelhante
à instrução cat
do Linux/Bash/Powershell).
O programa deve tratar separadamente todas as exceções que podem ocorrer quando o ficheiro é aberto para leitura, apresentando uma mensagem de erro adequada e simples em cada caso.
10 - Considera a seguinte struct
:
public struct TilePosition
{
public static int XMax { get; internal set; }
public static int YMax { get; internal set; }
public int X { get; }
public int Y { get; }
public TilePosition(int x, int y)
{
// Código do construtor
}
}
Responde às seguintes questões:
- Adiciona o código ao construtor, de modo as propriedades
X
eY
sejam inicializadas com valores entre 0 eXMax
ouYMax
, conforme o caso. Se os parâmetrosx
ey
estiverem fora do respetivo intervalo, deve ser lançada a exceçãoInvalidTilePositionException
, que aceita como argumentos doisbools
, que indiquem respetivamente se os parâmetrosx
ey
são inválidos. - Cria a classe
InvalidTilePositionException
, fazendo override da propriedadeMessage
de modo a que indique quais os argumentos inválidos (x
,y
ou ambos). - Na classe
Program
, métodoMain()
, testa as várias possibilidades (parâmetros válidos, um parâmetro inválido, ambos os parâmetros inválidos), capturando devidamente a exceção.
11 - Considera um jogo tile-based, em que cada tile tem as seguintes propriedades:
DefensePoints
- Bónus de defesa (DEF)EssentialPoints
- Bónus de extração de bens essenciais (ESS)LuxuryPoints
- Bónus de extração de bens de luxo (LUX)
Inicialmente existem os seguintes tipos base de tile com as características indicadas:
- Grassland: DEF = 0, ESS = 3, LUX = 0
- Desert: DEF = -1, ESS = 0, LUX = 1
- Jungle: DEF = 1, ESS = 1, LUX = 1
- Mountain: DEF = 3, ESS = 1, LUX = 1
Além disso, estes tiles podem ainda ter especializações particulares que podem alterar as suas características conforme indicado:
- Vineyard: ESS+1, LUX+1
- Sheep: ESS+3
- Gold: LUX+4
- Fortress: DEF+3, LUX-1
É possível cada tile ter zero, uma ou mais especializações. Propõe uma solução para este problema, composta pelos seguintes elementos:
- Diagrama UML completo.
- Código C#, no qual se inclui uma classe
Program
para testar o código.
12 - O teu jogo é shooter de sucesso, e a classe base de todas as armas é
IWeapon
, cuja interface está definida da seguinte forma:
interface IWeapon
{
// This property is true if weapon is in alternate firing mode, false
// otherwise
bool IsAlternate { get; }
// Reload the weapon
void Reload();
// Shoot the weapon, return true if any rounds left to shoot in current
// firing mode, false otherwise
bool Shoot();
// Switch between main and alternate firing modes
void SwitchFireMode();
}
Os teus investidores exigem um DLC que inclua melee weapons, ou seja, armas para combate corpo a corpo. Os investidores tomaram a liberdade de adquirir um pacote de assets para esse fim, e querem integra-lo no jogo. A interface das melee weapons é a seguinte:
interface IMelee
{
// Attacks return true if they hit opponent
bool AttackFromAbove();
bool AttackFromBelow();
bool AttackFromTheLeft();
bool AttackFromTheRight();
}
Como podes resolver este problema? Escreve código para esse fim e justifica as tuas opções.
13 - Cria uma facade para o
sistema de input do Unity
com um único método que devolve um
Vector2
. Esta
facade deve contemplar input de teclado, rato e controlador, e o Vector2
devolvido deve representar a direção do movimento num plano 2D.
Para testar a facade, cria uma scene contendo um elemento
UI.Text
que mostre
o valor do vetor em cada momento
14 - Que princípio de programação orientada a objetos está a ser violado pelo seguinte método? Como podes corrigir a situação?
public static float Average(List<int> values)
{
float sum = 0f;
foreach (int n in values)
{
sum += n;
}
return sum / values.Count;
}
15 - No código utilizado na questão anterior está a ser utilizado um pattern que está incorporado de forma nativa na linguagem C#. Que pattern se trata e em que consiste exatamente?
16 - Considera a seguinte interface:
public interface IWorld
{
Vector3 Dimensions { get; }
IEnumerable<Player> AllPlayers { get; }
int NumPlayers { get; }
int NumNPCs { get; }
int NumTraps { get; }
float Score { get; }
GameState State { get; }
bool IsGameRunning { get; }
bool IsNaturalDisasterOcurring { get; }
float GameSpeed { get; set; }
void AddPlayer(IPlayer player);
void RemovePlayer(IPlayer player);
void AddNPC(INPC npc);
void RemoveNPC(INPC npc);
void AddTrap(ITrap trap);
void RemoveTrap(ITrap trap);
void SetTexture(Vector3 position, Texture texture);
Texture GetTexture(Vector3 position);
void StartGame();
void EndGame();
void PauseGame();
void StartNaturalDisaster(IDisaster disaster, float duration);
void StopAllNaturalDisasters();
void SaveGame(string fileName);
void LoadGame(string fileName);
event EventHandler GameStarted;
event EventHandler GameFinished;
event Action<IPlayer> NPCAdded;
event ActionI<Player> NPCRemoved;
}
Que princípios de design estão a ser violados? Em concreto, quais são as consequências e problemas a que estão sujeitas as classes que implementam esta interface? Como poderias resolver o problema (ao nível das interfaces)?
17 - Usando as interfaces nativas do C# para implementação do Observer
pattern, nomeadamente
IObserver<T>
e
IObservable<T>
,
implementa:
- Um sujeito/observável que leia teclas do teclado (suprimindo a tecla lida de modo a que não apareça no ecrã) e notifique os observadores cada vez que uma tecla é premida.
- Um observador que indique no ecrã a tecla lida (e.g.
Detetada tecla 'R'
). - Um observador que guarde o carácter associado à tecla num ficheiro (em modo append).
O programa deve terminar quando for pressionada a tecla Escape.
18 - Resolve o exercício anterior com recurso a eventos.
19 - Considera a seguinte classe:
using UnityEngine;
public static class VectorOperations
{
// Normalized direction between two game objects
public static Vector2 Direction(Vector2 from, Vector2 to)
{
return (to - from) / (to - from).magnitude;
}
// Distance between two game objects
public static float Distance(Vector2 obj1, Vector2 obj2)
{
return (obj1 - obj2).magnitude;
}
// Convert angle in degrees into normalized vector
public static Vector2 Deg2Vec(float angle)
{
float angleRad = angle * Mathf.Deg2Rad;
return new Vector2(Mathf.Cos(angleRad), Mathf.Sin(angleRad));
}
// Determine angle of vector in degrees
public static float Vec2Deg(Vector2 vector)
{
return Mathf.Atan2(vector.y, vector.x) * Mathf.Rad2Deg;
}
}
Responde às seguintes questões:
- Simplifica os métodos usando notação lambda (
=>
). - Declara, para cada método, um delegate personalizado compatível.
- Indica, para cada método, um delegate pré-definido do C# que seja compatível.
- Assumindo que estás num método noutra classe, escreve quatro linhas de código nas quais declaras quatro variáveis do tipo delegate pré-definido que indicaste na alínea anterior, atribuindo-lhes o respetivo método compatível.
- Repete a alínea anterior mas considerando os delegates personalizados que declaraste na alínea 2 (e não os delegates pré-definidos do C#).
20 - Qual é ou quais são as diferenças entre delegates e eventos no C#?
21 - Que design pattern é explicitamente implementado pelos eventos do C#? Explica o teu raciocínio.
22 - Considera a seguinte classe C# para uso no Unity:
public class PlayerStats : MonoBehaviour {
private Player player;
private void Awake()
{
player = GameObject.FindWithTag("Player").GetComponent<Player>();
}
private void UpdatePowerUpStats(float powerUpMagnitude)
{
// Código que atualiza as estatísticas de power-ups do player
}
}
Responde às seguintes questões:
- Indica um delegate pré-definido do C# compatível com o método
UpdatePowerUpStats
. - Considera que a classe
Player
tem um evento nativo do C# chamadoPickedUpAPowerUp
. Completa a classePlayerStats
de modo a que o métodoUpdatePowerUpStats
seja notificado desse evento quando a instância dePlayerStats
estiver ativa. - Responde à questão anterior considerando que o evento
PickedUpAPowerUp
é do tipoUnityEvent<float>
.
23 - Cria os seguintes métodos de extensão para a classe
string
:
- Método que conta o número de palavras numa string. Podes usar o método
Split()
para dividir a string em várias palavras, mas certifica-te que todos os caracteres de whitespace (i.e. espaços, tabs, new lines, etc) são usados como separadores de palavras (estuda a documentação do método de modo para perceberes como). Deves ainda ignorar palavras com comprimento zero. - Método que conte o número de frases numa string. Podes assumir que uma frase termina com um ponto final. Frases de comprimento zero ou só com whitespace não devem ser contadas.
- Método que conte o número de parágrafos numa string. Podes assumir que um
parágrafo termina com uma nova linha (carácter
\n
). Parágrafos com comprimento zero ou só com whitespace não devem ser contados.
Para testares os métodos, cria um programa que conte as palavras, frases e parágrafos num ficheiro de texto, cujo nome deve ser passado como argumento na linha de comandos. Neste programa deves apanhar e tratar todas as exceções que possam ser lançadas pelo código de leitura do ficheiro.
24 - Cria um programa que apresente as seguintes estatísticas sobre um projeto em C#:
- Número de linhas de código
- Número de linhas em branco (apenas whitespace)
- Número de linhas que são comentários (i.e. linhas que começam com
//
, ignorando comentários do estilo/* ... */
)
Para o efeito deves criar três métodos de extensão apropriados com os nomes
IsLineOfCode()
, IsBlank()
e IsComment()
, que devolvem um booleano
indicando se a linha (string) é do tipo em questão.
O programa deve receber como argumento da linha de comando uma pasta ou um
nome de ficheiro. Se for indicada uma pasta, o programa deve indicar a
estatística global para todos os ficheiros .cs
na pasta e respetivas
sub-pastas. Caso seja indicado um ficheiro, o programa deve certificar-se que o
mesmo tem extensão .cs
e mostrar as estatísticas apenas para esse ficheiro.
Este programa deve ser estruturado de acordo com as melhores práticas de tratamento de exceções, design de classes e design patterns.
Apresenta também o diagrama UML simples (sem campos e métodos) da solução.
25 - Simplifica ao máximo a seguinte struct usando lambdas e eventualmente operadores ternários:
public struct GameMap
{
private float topScore;
private int gamesPlayed;
private int gamesWon;
public string Name { get; }
public string Filename { get; }
public float SuccessRate
{
get {
if (gamesPlayed == 0)
return 0f;
else
return gamesWon / (float) gamesPlayed;
}
}
public float TopScore {
get
{
return topScore;
}
set
{
if (value > topScore)
{
topScore = value;
}
}
}
public GameMap(string name, string filename)
{
Name = name;
Filename = filename;
gamesPlayed = 0;
gamesWon = 0;
topScore = 0;
}
public void GamePlayed(bool won)
{
gamesPlayed++;
if (won)
{
gamesWon++;
}
}
}
26 - Simplifica ao máximo o seguinte código usando lambdas e eventualmente operadores ternários:
public struct Bullet
{
private float calibre;
public float Calibre
{
get { return calibre; }
set { if (value < 0.1f) calibre = 0.1f; else calibre = value; }
}
}
public class Weapon
{
public float Value { get; }
public Weapon(float value) { Value = value; }
}
public class Gun : Weapon
{
private Bullet[] bullets;
public Gun(float value, int numBullets, float calibre) : base(value)
{
bullets = new Bullet[numBullets];
for (int i = 0; i < numBullets; i++)
{
bullets[i] = new Bullet() { Calibre = calibre };
}
}
}
27 - Simplifica ao máximo o seguinte código usando lambdas e eventualmente operadores ternários:
public struct Passenger
{
private double weight;
public double Weight
{
get { return weight; }
set { if (value < 5) weight = 5; else weight = value; }
}
}
public class Vehicle
{
public double Value { get; }
public Vehicle(double value) { Value = value; }
}
public class Car : Vehicle
{
private Passenger[] passengers;
public Car(double value, int numPassengers, float avgWeight) : base(value)
{
Random r = new Random();
passengers = new Passenger[numPassengers];
for (int i = 0; i < numPassengers; i++)
{
passengers[i] = new Passenger()
{
Weight = avgWeight + r.Next(-10, 10)
};
}
}
}
28 - Considera o seguinte código:
public class EventProducer
{
public string Str { get; private set; }
public int N { get; private set; }
protected virtual void OnProducedEvent()
{
ProducedEvent?.Invoke(Str, N);
}
public event Action<string, int> ProducedEvent;
}
O código anterior funciona perfeitamente, mas tens um patrão chato que exige
que uses delegates propositadamente criados para uso em eventos. Assim sendo,
substitui o delegate pré-definido
Action<T1,T2>
pelo delegate pré-definido
EventHandler<TEventArgs>
,
alterando o código em conformidade. Poderá ser necessário criar uma classe
adicional.
29 - O ficheiro 029.tsv
contém dados, separados por tabs,
sobre todos os exoplanetas conhecidos (a 30-11-2018). Cada linha contém a
seguinte informação:
- ID do planeta
- Nome do sistema estelar a que o planeta pertence
- Letra do planeta dentro do seu sistema estelar
- Nome do planeta
- Método de deteção do planeta
- Período orbital em dias
- Distância em parsecs
- Temperatura em Kelvins
- Telescópio responsável pela descoberta
As linhas que começam com o carácter #
são comentários e devem ser ignoradas.
Cria um projeto no Visual Studio que respeite os seguintes requisitos:
- Enumeração que contenha os diferentes métodos de descoberta de exoplanetas.
- Struct imutável
Planet
, que contém toda a informação sobre um planeta sob a forma de propriedades só de leitura e um construtor para inicializar essas mesmas propriedades. Cada propriedade deve ser do tipo apropriado ao dado que reporta. - A struct
Planet
deve ter overrides deGetHashCode()
eEquals()
de modo a que a igualdade entre planetas dependa apenas do respetivo ID. - Classe
PlanetLoader
, com um único métodoLoadPlanets()
. Este método aceita um parâmetro o nome de ficheiro que contém os dados sobre os planetas, devolvendo umIEnumerable<Planet>
com todos os planetas lidos. Tanto a classe como o método devem serstatic
. - Classe
Program
, com métodoMain()
, no qual é invocado o métodoLoadPlanets()
, e posteriormente mostrada no ecrã a seguinte informação, obtida com o uso de Lambdas e LINQ (sintaxe fluente ou expressões query, sem preferência):- Número de exoplanetas existentes.
- Método mais comum de deteção.
- Máximo de exoplanetas em torno da mesma estrela.
- Média do período orbital.
- Planeta mais longínquo (e a respetiva distância).
- Média da temperatura dos planetas cuja estrela tem pelo menos dois planetas.
- Telescópio com menos descobertas.
- Todas as possíveis exceções devem ser tratadas ao nível da classe
Program
. - Todas as classes e respetivos membros devem estar documentados com comentários XML.
30 - Desenha o diagrama UML completo do problema anterior (não requer resolução do problema).
31 - Considera o seguinte método:
public bool HasWeapons(int npcId)
{
return npcs[npcId].Bag.Any(item => item.Category == ItemCategory.Weapon);
}
Responde às seguintes questões:
- Qual é a tua interpretação sobre o que o código está a fazer?
- Qual é o tipo da propriedade
Category
? - Que princípio ou princípios de design estão a ser violados neste código?
Que problemas poderão ocorrer se algum dos objetos manipulados for
null
? - Simplifica o código usando operadores para tratamento de
null
s e expressões Lambda.
32 - Considera a seguinte enumeração:
enum Monster { Troll, Ogre, Elf, Demon, Vampire, Werewolf, Minion }
Responde às seguintes questões:
- Declara uma lista de
Monster
na qual seja possível também introduzirnulls
. - Assume que a variável
monst
é do tipoMonster
. Escreve uma linha de código onde atribuis à variávelmonst
o valor do primeiro elemento da lista da alínea anterior, tendo em conta que se este valor fornull
, o valor efetivamente a atribuir seráMinion
. - Escreve o código de um método que receba a lista da primeira alínea e
devolva um inteiro indicando quantos
nulls
existem na lista. O método deve ser o mais compacto possível, fazendo uso Lambdas e LINQ.
33 - Considera o tipo int
e responde às seguintes questões:
- Escreve o código dos seguintes métodos de extensão para
int
, bem como da classe que os contém:bool IsEven()
: Devolvetrue
se inteiro for par,false
caso contrário.bool IsZeroOrPositive()
: Devolvetrue
se inteiro for zero ou positivo,false
caso contrário.bool IsDivisorOf(int otherInt)
: Devolvetrue
se inteiro for divisor do inteiro passado como argumento,false
caso contrário.
- Considera o inteiro 8. Como invocarias o método
IsDivisorOf(15)
nesse valor? E qual seria o resultado? - Considera a variável
setOfInts
do tipoIEnumerable<int>
. Usando expressões Lambda e LINQ, escreve as seguintes expressões:- Expressão que resulte no número (quantidade) de inteiros pares
existentes em
setOfInts
. - Expressão que resulte num
IEnumerable<int>
só com divisores de 60.
- Expressão que resulte no número (quantidade) de inteiros pares
existentes em
- Qual teria de ser o tipo da variável
setOfIntsOrNulls
de modo a que possa também conternulls
? - Considerando que a variável
setOfIntsorNulls
é do tipo que indicaste na alínea anterior, escreve uma expressão, usando expressões Lambda e LINQ, que indique se o enumerável contém ou nãonulls
. - Escreve o código para percorrer todos os elementos de
setOfIntsorNulls
e efetuar a operação XOR (^
) entre todos os seus valores, guardando o resultado na variávelxored
, do tipoint
. Para o efeito podes assumir que esta variável já existe e foi inicializada a zero. Osnulls
devem ser considerados como tendo o valor zero para efeitos de XOR. Usa, sempre que possível, operadores para tratamento denulls
.
34 - Considera a seguinte classe C# para uso no Unity:
public class Player : MonoBehaviour {
// ... outras partes do código ...
private void OnTriggerEnter(Collider other) {
if (other.tag == "PowerUp") {
// Evento lançado aqui
}
}
// ... outras partes do código ...
public event Action<float> PickedUpAPowerUp;
}
Responde às seguintes questões:
- Cria um método cujo único propósito seja lançar o evento
PickedUpAPowerUp
, seguindo as boas práticas para o efeito. Este método é invocado, por exemplo, na linha de código que diz// Evento lançado aqui
(não é preciso mostrar essa invocação). - Identifica o uso de delegates no código apresentado, indicando se os mesmos (caso existam) são ou não nativos do C#.
35 - Considera a variável lstStr
do tipo List<string>
. O tipo
List<T>
tem vários métodos cujo nome começa com Find
, os quais têm como objetivo
encontrar elementos na lista que obedeçam a um dado predicado. Este predicado
fornece a estratégia de procura na forma de um delegate. Responde às
seguintes questões:
- Em que design pattern foi inspirada esta forma de procurar elementos numa lista?
- Usando o método
FindAll()
, escreve uma expressão que resulte numa lista contendo apenas as strings que contenham a sub-string"LP2"
, partindo da lista originallstStr
.
36 - Resolve a 2ª alínea do exercício anterior usando LINQ.
37 - Estás a desenvolver um RPG e tens NPCs com três categorias base que os definem:
- Movimento
- Combate
- Interação (com o jogador)
Podem existir milhares de NPCs diferentes. Para atingir essa diversidade, cada NPC individual pode utilizar diferentes e complexas estratégias que definem as suas três categorias base. Para efeitos deste exercício fica uma lista de alguns dos NPCs que vão aparecer no jogo final, bem como as estratégias que os definem em cada categoria base:
- B.J. King: movimento solto, combate confinado, interação agressiva
- J.P. Morgan: movimento solto, combate melee, interação afável
- M. Faraday: movimento rígido, combate à distância, interação rabugenta
- J. Austen: movimento gracioso, não combate, interação inteligente
- A. Frank: movimento gracioso, combate inteligente, interação afável
- M. Thatcher: movimento rígido, combate confinado, interação agressiva
- J. Reno: movimento rígido, combate confinado, interação inteligente
- J. Goodall: movimento solto, combate melee, interação inteligente
- A. Rand: movimento gracioso, combate confinado, interação rabugenta
- F. Kahlo: movimento gracioso, não combate, interação rabugenta
- M.L. King: movimento rígido, combate inteligente, interação inteligente
- D. Gea: movimento solto, combate à distância, interação afável
- A. Murray: movimento gracioso, combate confinado, interação agressiva
- L. Bird: movimento solto, combate à distância, interação rabugenta
- R. Polanski: movimento rígido, combate inteligente, interação inteligente
- A. Lavigne: movimento gracioso, não combate, interação afável
- K. Hammett: movimento solto, combate melee, interação agressiva
Responde às seguintes questões:
- Indica, justificando, um design pattern apropriado para implementar este sistema.
- Apresenta um diagrama UML com a informação mínima e essencial que demonstre como o design pattern indicado na alínea anterior pode ser utilizado para implementar este sistema.
- Apresenta uma implementação muito simples em C# deste sistema baseada na
resposta dada nas alíneas anteriores. As "estratégias complexas" devem ser
confinadas a simples
Console.WriteLine()
.
Sugestão: Submeter soluções parciais, uma alínea de cada vez, e esperar por feedback antes de responder à alínea seguinte.
38 - Estás a desenvolver um jogo em que os passos seguidos por uma arma ao realizar um ataque são sempre os mesmos, nomeadamente:
- Decrementar munições (dependendo de quantas foram gastas).
- Determinar probabilidade de sucesso.
- Determinar estrago infligido no inimigo.
- Determinar tempo de cooldown.
Existem diferentes tipos de arma, que apesar de seguirem estes quatro passos durante um ataque, poderão personalizá-los de formas distintas. É expectável que o tempo de cooldown para a maior parte das armas seja zero. Numa primeira versão o jogo terá duas armas, melee e pistol. A arma melee tem tempo de cooldown igual a zero.
Responde às seguintes questões:
- Que design pattern te parece mais apropriado para resolver este problema?
- Apresenta o diagrama UML de uma possível solução, contendo a informação mínima e necessária que realce o pattern utilizado.
- Apresenta uma implementação mínima da solução proposta na alínea anterior,
que faça uso de
Console.WriteLine()
para exemplificar os passos do algoritmo de combate seguido por cada arma.
39 - Resolve o problema anterior usando delegates. Em que condições é possível usar delegates para este efeito e qual a vantagem de os usar neste caso.
40 - Ainda na sequência dos dois problemas anteriores, considera que as armas podem realizar os passos (ações) indicados anteriormente sem nenhuma obrigatoriedade ou ordem específica, sendo possível inclusive repetir ações. Existem ainda duas ações adicionais possíveis, em concreto:
- Determinar estrago na arma.
- Determinar estrago infligido no próprio jogador, devido por exemplo a sobreutilização ou backfire da arma.
Responde às seguintes questões:
- Que design pattern te parece mais apropriado para resolver este problema?
- Apresenta o diagrama UML de uma possível solução, contendo a informação mínima e necessária que realce o pattern utilizado.
- Apresenta uma implementação mínima da solução proposta na alínea anterior,
que faça uso de
Console.WriteLine()
para exemplificar os passos do algoritmo de combate seguido por cada arma.
41 - Que princípios gerais de programação por objetos é que estão a ser violados no seguinte diagrama UML? Justifica a tua resposta.
42 - Que princípios gerais de programação por objetos é que estão a ser violados no seguinte diagrama UML? Justifica a tua resposta.
43 - Na questão anterior, em que circunstâncias é que o Decorator pattern poderia oferecer uma boa solução?
44 - Na questão 42, em que circunstâncias é que o Strategy pattern poderia oferecer uma boa solução?
45 - Compara a documentação dos delegates pré-definidos
Action
e
ThreadStart
.
Porque razão não é possível utilizar o delegate Action
em threads, tendo em
conta que ambos os delegates devolvem void
e não aceitam parâmetros.
46 - Considera a seguinte enumeração:
public enum Quadrilateral { Square, Rectangle, Trapezoid, Rhombus, Kite }
Escreve um programa com duas threads. Uma das threads coloca aleatoriamente e com igual probabilidade itens desta enumeração numa pilha (stack) thread-safe, com intervalos de colocação aleatórios entre 0 a 8 segundos. A outra thread, que pode ser a thread principal, retira todos os itens em intervalos regulares de 10 segundos.
Após a colocação de um item, existe uma probabilidade de 5% da primeira
thread decidir terminar o programa. Para o efeito, esta thread coloca
null
na pilha e finaliza a sua execução. A thread principal, ao capturar
este null
durante o desempilhamento, também finaliza a sua execução. Atenção
que as enumerações são tipos de valor e não aceitam diretamente o valor null
.
A visualização do programa deve ser feita em modo de consola, com a consola dividida em duas colunas:
- Os itens colocados na pilha pela primeira thread são indicados na primeira coluna, até um máximo de 30 itens. Após a colocação de 30 itens, a coluna deve ser limpa e a indicação dos itens deve recomeçar no início da coluna.
- Quando a thread principal retira os itens da pilha, deve indicar os itens retirados na segunda coluna. Antes de começar novo desempilhamento, a thread deve limpar a coluna.
Este programa deve ser estruturado de acordo com as melhores práticas de tratamento de exceções, design de classes e design patterns. Quaisquer variáveis de tipos não thread-safe partilhadas entre threads devem estar guardadas por locks.
47 - Implementa um jogo em modo consola, do estilo Space Invaders, no qual
o jogador controla uma defesa anti-aérea ^
que dispara um tiro |
com
frequência máxima de um tiro por segundo. O tiro viaja à velocidade de 1
carácter de consola por cada 150 milissegundos. Deve também existir um alvo O
,
que é destruído quando é atingido por um tiro, reaparecendo um novo alvo após 1
segundo.
O jogo deve ser implementado com as seguintes threads, que comunicam entre si
através de instâncias da fila thread-safe
BlockingCollection<T>
:
- A Thread de input lê do teclado e envia as teclas lidas para uma fila thread-safe. São reconhecidas apenas as teclas Espaço, Seta direita, Seta esquerda e Escape. As restantes teclas são ignoradas. A thread deve terminar a sua execução quando detetar a tecla_Escape_, enviando a mesma para a fila antes de terminar.
- A Thread Jogador controla o jogador
^
, obtendo as teclas da fila. O jogador pode andar para a esquerda e para a direita e disparar tiros|
quando deteta a tecla Espaço. O jogador não pode disparar mais do que um tiro por segundo, devendo ignorar a tecla Espaço durante esse período. Para disparar o tiro, a thread Jogador lança uma nova thread por cada tiro disparado, passando-lhe a posição atual do jogador. A thread Jogador não tem de esperar (fazer join) das threads de disparo, e termina quando detetar a tecla Escape na fila thread-safe. - Cada Thread de disparo é responsável pela renderização e deteção de
colisão de um tiro. Cada tiro sobe na vertical à velocidade de 1 caráter de
consola por 150 milissegundos. Cada uma destas threads termina quando o
respetivo tiro desaparece do ecrã ou quando o mesmo colide com o alvo
O
. Neste último caso, antes de terminar, a thread deve: a) retirar o alvo destruído do ecrã; b) esperar 1 segundo; e, c) colocar aleatoriamente um novo alvo no ecrã.
Este programa deve ser estruturado de acordo com as melhores práticas de tratamento de exceções, design de classes e design patterns. Quaisquer variáveis de tipos não thread-safe partilhadas entre threads devem estar guardadas por locks.
48 - Cria as seguintes classes:
- Classe
AddManager
, com uma propriedade só de leitura de nomeTotal
do tipoint
(suportada numa variável de instância privada de nometotal
), e com um métodoAddToTotal()
, que aceita um inteiro e adiciona-o à variáveltotal
. Esta classe deve ser um singleton com inicialização lazy, e tanto a sua inicialização como a modificação da variáveltotal
(dentro do métodoAddToTotal()
) devem ser thread-safe. - Classe
Adder
, com o métodoLetsAdd()
, que cria e lança uma thread que invoca 1000 vezes o métodoAddToTotal()
da instância solitária deAddManager
, passando-lhe inteiros aleatórios entre 0 e 100. A classe tem também a propriedade auto-implementadaPartial
, na qual a thread guarda o total parcial relativo aos inteiros que somou. O métodoLetsAdd()
retorna a instância da thread criada e lançada. - Classe
Program
, com o métodoMain()
, no qual: a) são criadas 20 instâncias deAdder
; b) é invocado o métodoLetsAdd()
em cada delas, sendo mantidas referência às threads devolvidas; c) é feita uma espera (com join) em todas as threads devolvidas; d) é realizada e mostrada no ecrã a soma doPartial
de todas as instâncias deAdder
; e) é mostrado no ecrã o valorTotal
da instância solitária deAddManager
; e, f) é indicado no ecrã se as somas são iguais ou não.
Nota que, para o programa estar correto, as somas devem ser sempre iguais.
49 - Desenha o diagrama UML completo do problema anterior (não é necessário resolução do mesmo).
50 - Considera o método com assinatura void DoStuff()
. Assumindo que estás
noutro método da mesma classe, escreve código para:
- Declarar 100 threads que executem o método
DoStuff()
. - Iniciar as 100 threads de modo a que executem em paralelo.
- Esperar que a execução dessas mesmas threads termine.
51 - Considera a seguinte classe:
class ParallelCalculation
{
private double result;
public void AddToResult(double toAdd)
{
result += toAdd;
}
// Other code
public event Action Bingo;
}
- Modifica a classe de modo a que as operações realizadas pelo método
AddToResult()
sejam thread-safe. - Considera que
pc
é uma instância deParallelCalculation
, e que tens um método com assinaturavoid BingoHandler()
. Escreve uma linha de código na qual subscrevas o métodoBingoHandler()
no eventoBingo
. - Escreve o código do método
OnBingo()
da classeParallelCalculation
, usando as melhores práticas de visibilidade, extensão e tratamento denulls
.
52 - Considera a seguinte classe:
public class AnnotatedDouble
{
public double DoubleValue { get; set; }
public string Annotation { get; set; }
public override string ToString() => $"{DoubleValue:f2} ({Annotation})";
}
Responde às seguintes questões:
- Adiciona uma conversão definida pelo utilizador para converter
AnnotatedDouble
emdouble
(usando o valor da propriedadeDoubleValue
). - Adiciona uma conversão definida pelo utilizador para converter
AnnotatedDouble
emstring
(usando o valor da propriedadeAnnotation
). - Adiciona uma conversão definida pelo utilizador para converter
double
emAnnotatedDouble
, na qual a propriedadeDoubleValue
toma o valor dodouble
e a propriedadeAnnotation
é inicializada com uma string vazia""
. - Adiciona uma conversão definida pelo utilizador para converter uma string
em
AnnotatedDouble
, na qual o campoAnnotation
toma o valor dessa string, e o campodouble
é inicializado a zero. - Apresenta algumas linhas de código que exemplifiquem as conversões definidas nas alíneas anteriores.
53 - Considera a classe AnnotatedDouble
da questão anterior. Faz overload
dos seguintes operadores de modo a que realizem as operações indicadas:
- Operador
+
(adição): soma valores na propriedadeDoubleValue
; concatena strings na propriedadeAnnotation
. - Operador
-
(subtração): subtrai valores na propriedadeDoubleValue
; remove caracteres da propriedadeAnnotation
do primeiro operando que estejam presentes na propriedadeAnnotation
do segundo operando. - Operador
-
(negação): aplica negação à propriedadeDoubleValue
, inverte string na propriedadeAnnotation
(e.g."ola"
passa a ser"alo"
).
54 - Relativamente aos operadores implementados na alínea anterior, qual é o output do seguinte código:
ad1 = new AnnotatedDouble() { DoubleValue = -4.2, Annotation = "Negative" };
ad2 = new AnnotatedDouble() { DoubleValue = 9.653, Annotation = "Highest" };
Console.WriteLine(ad1 + ad2);
Console.WriteLine(ad2 + ad1);
Console.WriteLine(ad1 - ad2);
Console.WriteLine(-ad2);
Nota: Não é necessário resolver o problema anterior para responder a esta questão.
55 - Indica o principal problema da sobreutilização de overloading de operadores.
56 - Responde à questão 52 sem usar conversões definidas pelo utilizador,
ou seja, criando métodos To***()
e From***()
. Quais as vantagens e
desvantagens desta abordagem comparada com a abordagem utilizada na questão
anterior.
57 - Considera o seguinte tipo:
public struct Weird { }
Responde às seguintes questões:
- Implementa um indexador só de leitura no tipo
Weird
que aceite como índice uma variável do tipoobject
e devolva uma string contendo o resultado da invocação deToString()
na instância deobject
convertido em maiúsculas. Deves usar notação Lambda para simplificar a resolução do problema. - Considera o seguinte código:
Weird weirdVar;
string str = weirdVar["Hello world!"];
Console.WriteLine(str);
- O que vai ser impresso no ecrã?
- Porque é que não foi preciso instanciar
weirdVar
antes de usarmos o indexador (linha 2)? Teria sido necessário instanciarweirdVar
se o tipoWeird
fosse uma classe? - Na sequência do código apresentado, o que seria impresso no ecrã pela
seguinte linha de código:
Console.WriteLine(weirdVar[100]);
58 - Porque é que não é boa ideia implementar um indexador na forma pedida na questão anterior?
59 - Cria uma pequena simulação na qual três agentes se movem aleatoriamente num mundo de dimensão 40x20 toroidal com vizinhança de Moore. Os agentes devem ter as seguintes características:
Agente | Carácter | Intervalo mov. |
---|---|---|
1 | # | 0.2 segundos |
2 | $ | 0.5 segundos |
3 | @ | 1 segundo |
A coluna "Intervalo mov." representa o intervalo entre cada movimento de um agente.
A simulação deve ser implementada usando bons princípios de design de classes, e o game loop deve ter update fixo e renderização variável.
Nota: A mesma posição do mundo pode conter mais do que um agente, pelo que a solução deve contemplar este requisito.
60 - Os Autómatos Celulares Elementares (ACEs) são sistemas muito simples mas que produzem padrões muito complexos. Estes sistemas podem ser representados com um array de booleanos. Por exemplo, um ACE de tamanho 5 pode ter o seguinte aspeto gráfico:
.*..*
Neste exemplo o caráter .
corresponde a false
e o caráter *
a true
. Os
ACEs podem ter novas gerações, que dependem da geração anterior e de uma regra
de geração. Esta regra define como cada célula da nova geração é criada a
partir das suas progenitoras. Mais especificamente, cada célula da nova geração
depende da sua progenitora direta bem como das suas duas vizinhas. E como
sabemos o estado das vizinhas nas extremidades do array? Não há problema,
pois podemos considerar o mundo como toroidal. Por exemplo, o vizinho esquerdo
da célula na posição 0 do array é a célula na última posição desse mesmo
array.
Existem 256 regras, desde a regra 0 até à regra 255. Para entendermos como a regra funciona, é necessário converter o valor decimal da mesma em binário. Por exemplo, na regra 110, o valor decimal 110 corresponde ao valor binário 01101110. Após esta conversão, a regra 110 funciona da seguinte forma para determinar as gerações seguintes:
Possíveis estados dos progenitores | 111 | 110 | 101 | 100 | 011 | 010 | 001 | 000 |
---|---|---|---|---|---|---|---|---|
Novo estado da célula central segundo a regra 110 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 |
Para o exemplo apresentado, .*..*
, a próxima geração será **.**
, de acordo
com a seguinte análise:
- Célula 0 tem como progenitores
*.*
(101), logo o seu novo valor vai ser*
(1). Devido ao mundo ser toroidal, o progenitor esquerdo é a última célula do array. - Célula 1 tem como progenitores
.*.
(010), logo o seu valor vai ser*
(1). - Célula 2 tem como progenitores
*..
(100), logo o seu valor vai ser.
(0). - Célula 3 tem como progenitores
..*
(001), logo o seu valor vai ser*
(1). - Célula 4 tem como progenitores
.*.
(010), logo o seu valor vai ser*
(1). Devido ao mundo ser toroidal, o progenitor direito é a primeira célula do array.
Se deixarmos este ACE correr durante 5 gerações, vai ser impresso no ecrã o seguinte padrão:
.*..*
**.**
.***.
**.*.
*****
Desenvolve um programa que solicite ao utilizador a regra a utilizar (0 a 255), o tamanho do mundo (3 a 100) e o nº de gerações a produzir (3 a 1000), e mostre no ecrã a evolução do respetivo ACE.
A simulação deve ser implementada usando bons princípios de design de classes, fazendo obrigatoriamente uso de um game loop (basta ter passo fixo com sincronização) e de um double buffer (um array para a geração atual e outro para a próxima geração).
61 - Implementa a simulação Langton's ant fazendo uso de bons princípios de design de classes e design patterns apropriados ao problema. Na resposta deves indicar que princípios e patterns tiveste o cuidado de utilizar.
62 - Descreve, por palavras tuas, três dos principais problemas no uso de singletons. Qual é a principal vantagem no uso deste design pattern?
63 - O Factory method pattern é um caso específico de outro pattern. Indica qual e explica por palavras tuas as semelhanças e diferenças.
64 - Considera a classe NPC
, com três campos:
Strength
Dexterity
Charisma
Existem duas classes concretas de NPC
, nomeadamente Orc
e Human
. Os
primeiros têm ainda o campo Endurance
e os últimos o campo Wisdom
. Podem
existir vários NPCs num nível, sendo a especificação de níveis feita num
ficheiro de texto semelhante ao seguinte:
NPC: Human
Strength: 78
Dexterity: 51
Charisma: 67
Wisdom: 33
NPC: Human
Strength: 44
Dexterity: 40
Charisma: 75
Wisdom: 71
NPC: Orc
Strength: 91
Dexterity: 83
Charisma: 29
Endurance: 49
Cria um programa que leia um ficheiro semelhante, cujo nome deve ser indicado
como 1º argumento na linha de comandos, e instancie os NPCs especificados no
ficheiro usando uma simple factory. Os vários NPCs instanciados podem ser
indicados no ecrã para fins de debug (sugestão: criem e usem um método
ToString()
para a classe NPC
e respetivas classes derivadas). O programa
deve fazer uso de bons princípios de design de classes, design patterns
apropriados ao problema, devendo ainda verificar, usando exceções, possíveis
situações de erro (ficheiro não especificado, ficheiro não existe, ficheiro
com conteúdos inválidos/inesperados, etc).
65 - Considera os seguintes tipos:
public enum ScenarioAlphabet { X, Y, A, B }
public class ScenarioObject
{
public float Weight { get; private set; }
public string Name { get; private set; }
private Texture texture; // Texture é um tipo de valor
private List<string> attributes;
private ScenarioAlphabet[] secretPassphrase;
/* ...more code... */
public ScenarioObject Clone()
{
// Código do método
}
}
Estão a desenvolver um jogo no qual vão ter um número limitado de diferentes
tipos de objetos nos cenários. Os diferentes objetos são inicialmente
instanciados e guardados num dicionário de tipo
Dictionary<string, ScenarioObject>
. No jogo propriamente dito, todos os
objetos adicionais são criados a partir deste conjunto inicial de objetos. Por
exemplo:
Dictionary<string, ScenarioObject> prefabs;
// ...
ScenarioObject aTree = prefabs["Tree"].Clone();
Responde às seguintes questões:
- Qual é o design pattern que está aqui a ser utilizado?
- Apresenta o código do método
Clone()
da classeScenarioObject
.
66 - Ainda relativamente aos tipos ScenarioAlphabet
e ScenarioObject
apresentados na alínea anterior, adiciona um método à classe ScenarioObject
para conversão explícita de instâncias da mesma em string, tal que a
string gerada represente os conteúdos do array secretPassphrase
. Por
exemplo, se os conteúdos deste array forem
{ ScenarioAlphabet.A, ScenarioAlphabet.X, ScenarioAlphabet.X }
, a string
equivalente à instância original de ScenarioObject
será "AXX"
.
67 - No problema anterior, porque razão faz mais sentido a conversão ser explícita e não implícita?
E se quisermos converter strings em ScenarioObject
, a conversão deve ser
explícita ou implícita? Porquê? Com base nesta resposta, adiciona um método à
classe ScenarioObject
para conversão de strings em ScenarioObject
usando
a seguinte lógica:
- Propriedade
Weight
igual a 1.0f. - Propriedade
Name
igual à string a ser convertida. - Variável
texture
deve ter o seu valor por omissão. - Variável
attributes
deve ser uma lista vazia. - Variável
secretPassphrase
deve ser determinada usando a lógica inversa à que foi utilizada na alínea anterior. Caso a string contenha caracteres que nãoX
,Y
,A
eB
, os mesmos devem ser ignorados.
68 - Apresenta duas ou mais linhas de código que exemplifiquem as conversões definidas nas duas alíneas anteriores.
69 - Considera o seguinte código:
public abstract class GameCharacter
{
public int Health { get; protected set; }
public int Mana { get; protected set; }
public abstract void Attack();
public abstract void CastSpell();
}
public class Warrior : GameCharacter
{
public override void Attack()
{
// Specialized attack logic for warriors
}
public override void CastSpell()
{
// Warriors cannot cast spells, so this method throws an exception
throw new InvalidOperationException("Warriors cannot cast spells");
}
}
public class Mage : GameCharacter
{
public override void Attack()
{
// Mages are not good at physical attacks, so this method throws an exception
throw new InvalidOperationException("Mages cannot use physical attacks");
}
public override void CastSpell()
{
// Specialized spell casting logic for mages
}
}
Qual o princípio SOLID que está a ser violado por este código e como poderias resolver o problema?
70 - Considera o seguinte código:
public interface IGameCharacter
{
void Attack();
void CastSpell();
void UseItem();
}
public class Warrior : IGameCharacter
{
public void Attack()
{
// Specialized attack logic for warriors
}
public void CastSpell()
{
// Warriors cannot cast spells, so this method does nothing
}
public void UseItem()
{
// Specialized item usage logic for warriors
}
}
public class Mage : IGameCharacter
{
public void Attack()
{
// Mages are not good at physical attacks, so this method does nothing
}
public void CastSpell()
{
// Specialized spell casting logic for mages
}
public void UseItem()
{
// Mages do not use items, so this method does nothing
}
}
Qual o princípio SOLID que está a ser violado por este código e como poderias resolver o problema?
71 - Considera um jogo de estratégia em que as nações/fações podem ter diferentes algoritmos de inteligência artificial que controlam os seus seguintes aspetos:
- Governação: democracia, comunismo, fascismo
- Militar: agressivo, passivo, defensivo
- Trocas comerciais: ganancioso, aberto, fechado
Sugere um design pattern apropriado. Justifica a tua resposta e apresenta um diagrama UML de classes (simples) com a tua proposta de solução.
72 - O seguinte código é um esqueleto de um Object pool para GameObject
s do
Unity:
public class ObjectPool : MonoBehaviour
{
[SerializeField]
[Range(10, 100)]
private int poolSize;
[SerializeField]
private GameObject prefab;
private Stack<GameObject> pool;
private void Start()
{
// Initialize pool
}
public GameObject GetFromPool()
{
// Remove object from pool, set active, return it
}
public void ReturnToPool(GameObject gameObject)
{
// Deactivate object and return it to the pool
}
}
Completa os métodos Start()
, GetFromPool()
e ReturnToPool()
, assumindo que
os objetos reutilizáveis são todos criados à partida. Caso sejam pedidos mais
objetos do que aqueles inicialmente disponíveis, deve ser lançada uma
InvalidOperationException
.
73 - Relativamente ao Object Pool design pattern, descreve por palavras tuas:
- As circunstâncias em que é apropriado usá-lo.
- O principal benefício de usar o pattern quando as circunstâncias são apropriadas.
- O princípio (ou princípios) que é (são) quebrado(s) se usarmos o pattern quando as circunstâncias não o justificam.
74 - Considera o seguinte código:
public interface IMonster
{
// Returns a deep copy of this monster
IMonster Clone();
}
public interface IMonsterFactory
{
// Create a new monster of the specified type
IMonster Create(string monsterType);
}
Responde às seguintes questões:
- Apesar de termos ainda pouco código, que design patterns parecem estar envolvidos no mesmo? Justifica a tua resposta.
- Cria uma classe
MonsterFactory
concreta que implementaIMonsterFactory
, e que faz uso de um dicionário de monstros prontos a criar (por exemplo, do tipoDictionary<string, IMonster>
). Deve ser lançada a exceçãoArgumentException
caso o monstro não exista no dicionário.- Nota: podes ignorar o construtor e assumir que o dicionário de monstros prontos a usar já foi criado.
75 - Na continuação do problema anterior, considera a possível classe concreta
Monster
:
public class Monster : IMonster
{
public string Name { get; }
public float HP { get; private set; }
public float Shield { get; private set; }
public Image Mugshot { get; private set; }
private List<Power> powers;
private Dictionary<string, int> killsByEnemyName;
// Returns a deep copy of this monster
public IMonster Clone()
{
// Complete me!
}
}
Assume que:
Image
ePower
são tipos de referência.- Os monstros com o mesmo nome têm sempre a mesma
Mugshot
durante o jogo. - Quando um novo monstro é criado, tem os mesmos
Power
do monstro original; no entanto, a lista dePower
de cada monstro pode variar ao longo do tempo para cada monstro individual, sem afetar a lista dePower
dos restantes monstros. - Uma instância de
Power
pode ser modificada ao longo do tempo; no entanto, alterar umPower
de um monstro só altera essePower
para esse monstro específico. - A classe
Power
também tem um métodoClone()
que devolve uma cópia profunda da instância na qual é invocado. - O dicionário de kills por nome de inimigo é específico a cada monstro. Quando um novo monstro é criado, começa sem qualquer kill.
Responde às seguintes questões:
- Que campo de
Monster
, tipo de referência, é intrínseco? - Identifica todos campos intrínsecos e extrínsecos da classe
Monster
e explica qual a diferença. - Completa o método
Clone()
da classeMonster
de modo a que devolva um novo monstro baseado na instância no qual é invocado.