Solutions#

Lunar lander control#

Solution to Activity 4

  1. air_conditioner = 1;

  2. if (...) else ...

  3. a > b

  4. 5

  5. success = a > b;

Solution to Activity 5

int Control(int altitude)
{
    int thruster = 0;

    if (altitude > 100)
        thruster = 0;
    else if (altitude > 0)
        thruster = 1;
    else
        thruster = 0;
    
    return thruster;
}

void Test(int altitude)
{
    int thruster = Control(altitude);
    bool behaviorCorrect = (altitude > 100 && thruster == 0) ||
                           (altitude is <= 100 and > 0 && thruster == 1) ||
                           (altitude <= 0 && thruster == 0);
    var behaviorCorrectIcon = behaviorCorrect ? "βœ…" : "❌";
    Console.WriteLine($"For altitude {altitude}, your thruster is {thruster} |{behaviorCorrectIcon}|");
}

Test(150);
Test(100);
Test(50);
Test(0);
Test(-1);

Conveyor belt capacity check#

Solution to Activity 7

        flowchart TD
    a["`prompt for *motor count*`"] -->
    b["`prompt for *package weight*`"] -->
    c{"`is *package weight per motor* less or equal than 5.6?`"}

    c -- true --> d["`output *carrying is possible*`"]
    c -- false --> e["`output *carrying is not possible*`"]
    

Solution to Activity 19

  1. int array = 5

  2. string[] choices = ["black bird", "great tit", "falcon"]

  3. int[] part_ids= [3093, -49318, 3092.8];

  4. string[] names = []

Solution to Activity 9

const double packageWeightPerMotor = 5.6;

Console.Write("How many motors are carrying the packages? ");
var motorCount = int.Parse(Console.ReadLine());

Console.Write("How many kg of packages do we expect? ");
var packageWeight = double.Parse(Console.ReadLine());

Console.WriteLine(packageWeight / motorCount <= packageWeightPerMotor
    ? "Yes! The conveyor belt can carry the packages."
    : "No. The conveyor belt cannot carry the packages.");

Spare parts inventory assistant#

Solution to Activity 54

    • left code is shorter, because it does not use the variables used in the right. It looks more readable on the first sight.

    • right code separates control flow from the data. Data are the messages shown to the user.

    • If there is no separation, the long sentences may distract the programmer from the actual control flow logic and vice-versa.

    • If there is separation, then the data can be more conveniently modified.

    • this structure allows for adapting data without modifying the control flow, which is useful for, e.g., translation

Solution to Activity 15

// Assume values 
var altitude = 20;
var thruster = true;

var behaviorCorrect = (altitude > 100 && !thruster) ||
                      (altitude is <= 100 and > 0 && thruster) ||
                      (altitude <= 0 && !thruster);
if (behaviorCorrect)
    Console.WriteLine("βœ…");
else
    Console.WriteLine("❌");

Solution to Activity 12

  1. t == f

  2. t = f

  3. i >= c && i > k

  4. !t

  5. k != 2

  6. 10 < c || c <= 20

Solution to Activity 22

Without separation of data and logic

Console.WriteLine("Hej. Welcome to the spare parts inventory! ");
bool partAvailable = false;
while (!partAvailable)
{
    Console.Write("Which part do you need? ");
    var line = Console.ReadLine();
    if (line == "hydraulic pump" || line == "PLC module" || line == "servo motor")
    {
        Console.WriteLine($"I have got {line} here for you 😊. Bye!");
        partAvailable = true;
    }
    else  if (line == "Do you actually have any parts?" || line == "Is there anything in stock at all?")
    {
        Console.WriteLine("""
                          We have 3 part(s)!
                          hydraulic pump
                          PLC module
                          servo motor
                          """);
    }
    else
        Console.WriteLine($"I am afraid we don’t have any {line} in the inventory");
}

With separation:

// Configurable data
List<string> parts = [
    "hydraulic pump",
    "PLC module",
    "servo motor"
];
const string AssistantGreeting = "Hej. Welcome to the spare parts inventory!";
const string AssistantQuestion = "Which part do you need?";
const string AssistantReplyPositive = "I've got {0} here for you 😊. Bye!";
const string AssistantReplyNegative = "I am afraid we don’t have any {0} in the inventory πŸ˜”";
const string AssistantReplyNumberOfParts = "We have {0} part(s)!";
List<string> userQuestions =
[
    "Do you actually have any parts?",
    "Is there anything in stock at all?",
];

// Program logic
Console.WriteLine(AssistantGreeting);
bool partAvailable = false;
while (!partAvailable)
{
    Console.Write(AssistantQuestion + " ");
    var line = Console.ReadLine();
    if (parts.Contains(line))
    {
        Console.WriteLine(String.Format(AssistantReplyPositive, line));
        partAvailable = true;
    }
    else if (userQuestions.Contains(line))
    {
        Console.WriteLine($"""
        {String.Format(AssistantReplyNumberOfParts, parts.Count)}
        {string.Join("\n", parts)}
        """);
    }
    else
        Console.WriteLine(String.Format(AssistantReplyNegative, line));
}
For the curious

You see that the array userQuestions is not annotated with const, even we use it as a constant. An array cannot be constant in C#, because arrays must be initialized at runtime. Moreover: collection expressions are executed at runtime.

You can’t use a collection expression where a compile-time constant is expected, such as initializing a constant, or as the default value for a method argument.

Currency converter with GUI#

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="CurrencyConverter.MainWindow"
        Title="CurrencyConverter">
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBox Name="BaseCurrencyTextBox" TextChanged="BaseCurrencyTextBox_OnTextChanged">100.00</TextBox>
            <Label>DKK</Label>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Name="TargetCurrencyTextBlock" />
            <Label>EUR</Label>
        </StackPanel>
    </StackPanel>
</Window>
using System.Net.Http;
using System.Text.Json;
using Avalonia.Controls;

namespace CurrencyConverter;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void BaseCurrencyTextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
    {
        var client = new HttpClient();
        var json =
            await client.GetStringAsync("https://api.frankfurter.dev/v1/latest?base=DKK");

        var dkkInEur = JsonDocument.Parse(json).RootElement.GetProperty("rates").GetProperty("EUR").GetDouble();
        if (double.TryParse(BaseCurrencyTextBox.Text, out var dkkAmount))
        {
            var eurAmount = dkkAmount * dkkInEur;
            TargetCurrencyTextBlock.Text = $"{eurAmount:F2}";
        }
    }
}

RPSSL#

Solution to Activity 33 (RPSSL continued)

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="200"
        x:Class="RPSSL.MainWindow"
        Title="RPSSL">

    <StackPanel>
        <TextBlock Text="RPSSL" FontSize="30" HorizontalAlignment="Center" />
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Text="πŸ‘«:" />
            <TextBlock Name="HumanScoreTextBlock" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Text="πŸ€–:" />
            <TextBlock Name="AgentScoreTextBlock" />
        </StackPanel>

        <TextBlock Text="Choose a shape:" HorizontalAlignment="Center" />
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Name="RockButton" Click="ShapeButton_OnClick">πŸͺ¨</Button>
            <Button Name="ScissorsButton" Click="ShapeButton_OnClick">βœ‚</Button>
            <Button Name="PaperButton" Click="ShapeButton_OnClick">πŸ—’οΈ</Button>
            <Button Name="SpockButton" Click="ShapeButton_OnClick">πŸ––</Button>
            <Button Name="LizardButton" Click="ShapeButton_OnClick">🦎</Button>
        </StackPanel>
        <TextBlock Name="WinnerAnnounceTextBlock" FontSize="20" HorizontalAlignment="Center" />
    </StackPanel>
</Window>
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;

namespace RPSSL;

internal enum Shape
{
    Rock,
    Lizard,
    Scissors,
    Paper,
    Spock
}

public partial class MainWindow : Window
{
    private const uint WinningScore = 5;
    private uint _humanScore, _agentScore;

    public MainWindow()
    {
        InitializeComponent();
        UpdateUserInterface();
    }

    private void UpdateUserInterface()
    {
        HumanScoreTextBlock.Text = _humanScore.ToString();
        AgentScoreTextBlock.Text = _agentScore.ToString();

        if (_humanScore >= WinningScore)
            WinnerAnnounceTextBlock.Text = "πŸ‘« wins πŸŽ‰";
        if (_agentScore >= WinningScore)
            WinnerAnnounceTextBlock.Text = "πŸ€– wins πŸŽ‰";
    }

    private void ShapeButton_OnClick(object? sender, RoutedEventArgs e)
    {
        var button = sender as Button;
        Shape humanShape = 0, agentShape = 0;
        switch (button.Name)
        {
            case "RockButton":
                humanShape = Shape.Rock;
                break;
            case "Scissors":
                humanShape = Shape.Scissors;
                break;
            case "Paper":
                humanShape = Shape.Paper;
                break;
            case "Spock":
                humanShape = Shape.Spock;
                break;
            case "Lizard":
                humanShape = Shape.Lizard;
                break;
        }

        agentShape = Random.Shared.GetItems(Enum.GetValues<Shape>(), 1)[0];

        // Resolution
        switch (agentShape - humanShape)
        {
            case 0:
                break;
            case -4 or -2 or 1 or 3:
                ++_humanScore;
                break;
            default:
                ++_agentScore;
                break;
        }

        UpdateUserInterface();
    }
}

Inventory system#

Item sorter robot#

Solution to Activity 41 (Waving robot arm)

def f():
    offset = 45
    shoulder_positions = [d2r(-90 - offset), d2r(-90 + offset)]
    count = 0
    while (count < 3):
        movej([0, shoulder_positions[0], 0, d2r(-90), 0, 0])
        movej([0, shoulder_positions[1], 0, d2r(-90), 0, 0])
        count = count + 1
    end
end

Solution to Activity 42 (Finding a position in a coordinate frame)

[0, -.5, 0]. Actually every position with z=0, but the tool cannot always reach every z=0, because it has limits.

Solution to Activity 43 (Finding a feasible pose for the robot)

All other than 3. could be used for picking an object. The video shows only from the top, but the items could be also gripped from the side.

We see that all poses other than 3 rotate 90 and 180 degrees around the x and y axes, which turns the gripper into a feasible position. Note that if y is not large or small enough, these poses will not work.

Solution to Activity 44 (Moving between points on a coordinate system)

def f():
  # We move on a 2d plane, where only x and y changes
  # We move relative to X_ORIGIN and Y_ORIGIN.
  X_ORIGIN = 0
  Y_ORIGIN = -.4
  Z = .2
  RX = 0
  RY = d2r(180)
  RZ = 0

  # Unit distance between coordinates
  SEP = 0.1

  # Moves to the coordinates x and y relative to X_ORIGIN and Y_ORIGIN
  def move(x, y):
    movej(get_inverse_kin(p[X_ORIGIN + SEP * x, Y_ORIGIN + SEP * y, Z, RX, RY, RZ]))
  end

  move(1, 1)
  move(3, 3)
  move(4, 1)
  move(2, 3)
end

Item sorter robot – real#

Solution to Activity 45 (Required modifications)

  1. Gripper functionality

  2. GUI changes for IP address configuration