Downloading and saving your code

Run cs1010, select Download CS1010 content, then choose Labs and Lab 2 to download the starter code. This lab is not graded; the supplied tests are for self-checking. You can still choose Submit/save current work in cs1010 if you would like to save your code on GitHub.

Include -std=c23 during your compilation

When you compile your code for this lab, please include the flag -std=c23 in your Clang options, for example, clang -std=c23 main.c. Otherwise, the compiler will ask you to include stdbool.h if you use booleans anywhere, which is not required from C23 onwards.

Preface

In Lab 2, we’re slowly building up to bigger and bigger programs (especially with more language features). So this lab:

  1. We will tie up conditionals with a few other mechanics that you’ll need to know. We will do this via experimentation for you again.
  2. We’ll start getting you to try to apply both behavioral and data abstraction by first cleaning up code that we’ll provide.
  3. And then, a little more problem-solving practice as usual.

Activity 1: Short-Circuiting in Conditionals

We’re going to start by getting you to divine some behaviour from a few snippets of code. The code might/might not line up with your expectations. Try to figure out what is going on.

In part-1.c:

#include <stdbool.h>
#include <stdio.h>
 
bool foo(){
	printf("foo called returning true\n");
	return true;
}
 
bool bar(){
	printf("bar called returning false\n");
	return false;
}
 
int main(){
	if(foo() && bar()){
		printf("line A reached\n");
	}
 
	printf("BOOP\n");
 
	if(foo() || bar()){
		printf("line B reached\n");
	}
}

Part 1

Before you compile and run the code, make a prediction on the exact output you should see on the screen. Take some time to trace it out line by line if needed, then run it and compare it against your predictions. Was it any different?

Hint: Most likely, you predicted more lines than there would be. Which lines were missing? Why do you think they were missing? Can you try to figure that out based on the return values and the logical operators being used?

Hint 2: What’s the name of this activity? (That’s the actual name of the mechanic.)

Part 2

In part-2.c:

#include <stdbool.h>
#include <stdio.h>
 
bool foo(){
	printf("foo called returning true\n");
	return true;
}
 
bool bar(){
	printf("bar called returning false\n");
	return false;
}
 
int main(){
	if(bar() && foo()){
		printf("line C reached\n");
	}
 
	printf("BOOP\n");
 
	if(bar() || foo()){
		printf("line D reached\n");
	}
}

What about if we had swapped the function calls around? Based on your understanding of part 1, try to now predict the behaviour again.

Activity 2: Missing Braces

In braces.c:

#include <stdio.h>
#include <ctype.h>
 
int main(){
   char ch = getchar(); // reads a character from input
   if(isdigit(ch))
       printf("is a digit\n");
       printf("is this line printed?");
   return 0;
}

Before you compile and run the code, make a prediction on the exact output you should see on the screen. Take some time to trace it out line by line if needed. Does it even compile?

Compile, run the program, then enter a character and press the return/enter key. Does the program behave as expected? What happens if you enter a digit? What happens if you enter a non-digit?

Note: This happens to be a pretty big gotcha in a lot of C code out there.


Activity 3: Functions

Now let’s look at some code snippets that would benefit from us making some functions.

Part 1

In clamp.c:

#include <stdio.h>
#include "input.h"
 
int main() {
 int score1 = read_int();
 if (score1 < 0) {
   score1 = 0;
 } else if (score1 > 100) {
   score1 = 100;
 }
 
 int score2 = read_int();
 if (score2 < 0) {
   score2 = 0;
 } else if (score2 > 100) {
   score2 = 100;
 }
 
 int score3 = read_int();
 if (score3 < 0) {
   score3 = 0;
 } else if (score3 > 100) {
   score3 = 100;
 }
 
 printf("%d %d %d\n", score1, score2, score3);
 return 0;
}

Refactor the code with the use of some functions.

Part 2

In point.c:

#include "input.h"
#include <stdbool.h>
#include <stdio.h>
 
typedef struct {
 int x;
 int y;
} Point;
 
int main() {
 Point p;
 p.x = read_int();
 p.y = read_int();
 
 Point q;
 q.x = read_int();
 q.y = read_int();
 
 int dx = q.x - p.x;
 int dy = q.y - p.y;
 
 bool moving_right = dx > 0;
 bool moving_up = dy > 0;
 bool on_same_x = dx == 0;
 bool on_same_y = dy == 0;
 
 if (on_same_x && on_same_y) {
   printf("same point\n");
 } else if (on_same_x) {
   printf("vertical movement\n");
 } else if (on_same_y) {
   printf("horizontal movement\n");
 } else if (moving_right && moving_up) {
   printf("northeast\n");
 } else if (!moving_right && moving_up) {
   printf("northwest\n");
 } else if (moving_right && !moving_up) {
   printf("southeast\n");
 } else {
   printf("southwest\n");
 }
 return 0;
}

Refactor the code with the use of some functions.

Activity 4: Structs

Now let’s look at some code snippets that would benefit from us making some structs. For each of the examples below, try to create a useful struct abstraction.

Part 1

In colour.c

#include <stdio.h>
#include "input.h"
 
int main() {
   int r = read_int();
   int g = read_int();
   int b = read_int();
 
   if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
       printf("invalid color\n");
   } else if (r == g && g == b) {
       printf("grayscale\n");
   } else if (r > g && r > b) {
       printf("red dominant\n");
   } else if (g > r && g > b) {
       printf("green dominant\n");
   } else {
       printf("blue dominant\n");
   }
   return 0;
}

Note

There’s also a small logical bug in the program related to dominant colors. Can you spot it?

Part 2

In time.c

#include <stdio.h>
#include <stdbool.h>
#include "input.h"
 
int main() {
   int h1 = read_int();
   int m1 = read_int();
   int h2 = read_int();
   int m2 = read_int();
 
   if (h1 < 0 || h1 > 23 || m1 < 0 || m1 > 59 ||
       h2 < 0 || h2 > 23 || m2 < 0 || m2 > 59) {
       printf("invalid\n");
   } else if (h1 < h2 || (h1 == h2 && m1 < m2)) {
       printf("first is earlier\n");
   } else if (h1 == h2 && m1 == m2) {
       printf("same time\n");
   } else {
       printf("second is earlier\n");
   }
   return 0;
}

Problem Solving Practice 1: Converting an Aircraft’s Attitude

An aircraft needs to keep track of its attitude: which way it is tilted and which way it is facing.

One familiar way to describe attitude is with three Euler angles:

  • roll, rotation about the forward axis
  • pitch, rotation about the side-to-side axis
  • yaw, rotation about the vertical axis

Euler Angles: Image Source

Euler angles are convenient for us to understand. Flight-control software, however, often stores the same attitude as a quaternion. A quaternion has four components: w, x, y, and z.

Imagine that an aircraft’s flight controller stores its attitude as a quaternion, while its ground station displays roll, pitch, and yaw to the operator. The two programs need to convert the same attitude in both directions.

The starter code in problem-solving-1/ provides these types:

typedef struct {
	double roll;
	double pitch;
	double yaw;
} EulerAngles;
 
typedef struct {
	double w;
	double x;
	double y;
	double z;
} Quaternion;

All angles in this exercise are in radians. You do not need to derive the conversion formulae, but you will need to translate them carefully into C.

The inverse-conversion formula below assumes that the input quaternion is normalized, meaning that w*w + x*x + y*y + z*z is 1 (up to floating-point rounding). The supplied quaternion test cases are normalized.

To convert roll, pitch, and yaw into a quaternion, first use the following abbreviations:

The subscripts tell you which angle each value belongs to. For example, is the cosine of half the roll angle, while is the sine of half the yaw angle.

Use those values to calculate the four quaternion components:

Implement:

Quaternion euler_to_quaternion(EulerAngles attitude);

Try to implement helper functions or structs here. Notice that, for each angle, we compute the sine and cosine of half the angle.

Return the four calculated components together in a Quaternion.

Begin with the attitude {0.0, 0.0, 0.0}. It should produce the quaternion {1.0, 0.0, 0.0, 0.0}.

To convert a quaternion back into Euler angles, use these formulae:

Before calculating pitch, clamp to the range from to . This means replacing a value below with , replacing a value above with , and otherwise leaving it unchanged. This prevents small floating-point rounding errors from giving asin an input outside its valid range.

In C, the inverse sine function is called asin, and the two-argument arctangent function is called atan2. Both are declared in the <math.h> header file.

Implement:

EulerAngles quaternion_to_euler(Quaternion attitude);

Return the three calculated angles together in an EulerAngles.

The supplied program reads one conversion per line in either of these forms:

euler <roll> <pitch> <yaw>
quaternion <w> <x> <y> <z>

For euler, it prints w, x, y, and z in that order. For quaternion, it prints roll, pitch, and yaw in that order. Each value is printed to five decimal places. For example:

euler 0 0 0

produces:

1.00000 0.00000 0.00000 0.00000

Run make test to test both conversion functions using the paired input and output files in test/test_cases/. Inspect those cases, and add your own if you want to check more attitudes.

Problem Solving Practice 2: Orienting a QR Code

The previous activities used functions and structs mostly separately. Now we will combine them in an interesting (and genuinely real-world way): fixing a QR code’s orientation.

We are going to represent a QR code with a struct, and you will determine its orientation using conditionals, and put together (compose) image-related functions to orient it correctly.

An upright QR code has large finder patterns in its top-left, top-right, and bottom-left corners. The bottom-right corner is the only corner without a finder pattern. QR Image Format

Each test image is a valid QR code rotated by 0, 90, 180, or 270 degrees.

For example, the QR code below has been rotated twice, and to read it correctly, it must be oriented such that the finder patterns are in the top-left, top-right, and bottom-left corners.

The starter code for this activity is in problem-solving-2/. It provides this type:

typedef struct {
	image_t image;
} QRCode;

You do not need to know how image_t represents an image (yay abstraction!). You will work with the complete QR code through the QRCode struct and the following supplied functions:

bool top_left_has_finder_pattern(image_t image);
bool top_right_has_finder_pattern(image_t image);
bool bottom_left_has_finder_pattern(image_t image);
bool bottom_right_has_finder_pattern(image_t image);
 
void rotate_90_clockwise(image_t image);

The rotation function changes the represented image in place by one clockwise quarter-turn.

We will use one character to describe the direction in which the top of the original QR code currently points:

CharacterCurrent OrientationCorner Without a Finder Pattern
UUprightBottom-right
R90 degrees clockwise from uprightBottom-left
D180 degrees from uprightTop-left
L90 degrees anticlockwise from uprightTop-right

Part 1 Plan the Cases

Before writing code, draw a table with these three columns: corner without a finder pattern, orientation character, and number of clockwise quarter-turns needed to become upright. Fill in all four possible orientations.

Check that each possible missing corner appears exactly once.

Part 2 Determine the Current Orientation

Implement the following function:

char current_orientation(QRCode qr);

Use the supplied finder-pattern functions to check the corners of qr.image. Return the corresponding character from the table above. This function should only determine the current orientation. It should not rotate the image or print anything.

Part 3: Orient the QR Code

Implement the following function:

void orient_qr(QRCode qr);

Call the function you implemented (current_orientation) once, then use the result and your table from Part 1 to decide what to do. Use only the supplied rotate_90_clockwise function (you may add more abstraction if you want) so that the final image is upright.

Part 4: Test the Result

The exercise includes four input images in the same directory as the C files:

  • qr-upright.bmp
  • qr-90-clockwise.bmp
  • qr-180.bmp
  • qr-90-anticlockwise.bmp

Compile the program, pass it an input filename and a different output filename, then open the result with feh. For example:

make
./orient qr-90-clockwise.bmp qr-output.bmp
feh ./qr-output.bmp

Check by eye that the output finder patterns are in the top-left, top-right, and bottom-left corners. Press q to close feh.

Try more than one input, including qr-upright.bmp, which should be left unchanged.