C Programming Project 01 Guidelines
C Programming Project 01 Guidelines
Contents
1 Overview 2
1.1 Plagiarism policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Submission instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Code Quality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Starter files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 Grading distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.6 Docker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.7 Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3 Part 2: CityBloxx 20
3.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.2 Introduction to CityBloxx . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3 Skeleton Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3.1 Struct Breakdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.3.2 Function Breakdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.4 Test Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.5 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1
CS200/EE201 Fall 2024-2025
1 Overview
• Readability: Use meaningful variable names as specified in the cpp coding standards and lectures.
Assure consistent indentation. We will be looking at the following:
– Variables: Use lowercase letters and separate words with under- scores. For example, my_variable
– Constants: Use uppercase letters and separate words with underscores. For example, MAX_SIZE,
PI
– Functions and Methods: Use lower camelCase (capitalize the first letter of each word, except
for the first word, without underscores). For example, myFunction(), calculateSum()
– Classes: Use CamelCase (capitalize the first letter of each word without underscores). For
example, MyClass, CarModel
• Modularity and Reusability: Break down your code into functions with single responsibilities.
• Code Readability: You should make sure that your code is readable. This means that your variables
have meaningful names
• Documentation (Optional): Comment your code adequately to explain the logic and flow.
• Error Handling: Appropriately handle possible errors and edge cases.
• Efficiency: Write code that performs well and avoids unnecessary computations.
2
CS200/EE201 Fall 2024-2025
mappings.c
contiguous_pointer.c
test
contains test files
Part02
CityBloxx.c
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
3
CS200/EE201 Fall 2024-2025
1.6 Docker
Please note that your assignments will be tested on Docker containers. As such, it is recommended that
you run your code on it at least once before submitting it. Should any errors arise on our end due to
incompatibility, you will be given the chance to contest your code.
1.7 Restrictions
Any violation of the below restrictions will result in a zero.
• You are not allowed to edit any structure provided.
• You are not allowed to declare your own structures.
• You are not allowed to edit function names, parameters, or return types.
• You are not allowed to declare helper functions unless specified.
• You are not allowed to declare any form of global or static variables unless specified.
• You are not allowed to edit the header files.
• You are not allowed to include any other libraries other than those already those provided in the
starter code.
• You are not allowed to include any libraries that are not already provided.
4
CS200/EE201 Fall 2024-2025
5
CS200/EE201 Fall 2024-2025
2.1 Stage 1
In the first stage of development, you aim to abstract contiguous memory across unique memory allocation
calls. This means that, for a given program, it must appear that the nth allocation is adjacent to the n-1th
allocation. Typically, the memory allocator allocates requested memory wherever it finds appropriate space
depending on memory availability and extent of fragmentation.
Rather than accessing scattered memory, the intention is to make it appear as if multiple independent
malloc calls are contiguously allocated, even if they are not actually contiguous inside the memory.
6
CS200/EE201 Fall 2024-2025
1 typedef struct {
2 void * mem_space ;
3 int bytes ;
4 } Block ;
A visual representation is shown, where a void pointer indicates the memory space where appropriate data
is being stored.
You have also been provided with an enumeration to allow type specification as input for memory
allocation to this layer.
1 typedef enum {
2 INT_TYPE ,
3 FLOAT_TYPE ,
4 DOUBLE_TYPE ,
5 CHAR_TYPE ,
6 } DataType ;
7
CS200/EE201 Fall 2024-2025
Library functions:
Implement the following functions, in ’block.c’, as described.
For zero-sized allocations, return NULL.
• Block* initializeBlock(DataType type, int units, void* data)
The origin block must be created before any further memory allocation for data can be performed.
Initialize Block 0, the origin block, as well as the memory space (size of the required memory space
is indicated by the given type and number of units) for storing the specified data.
Note: you may assume that the size of the data to be stored will not be greater than the size
(number of bytes) of the allocated memory.
• Block* blockMalloc(DataType type, int units)
Allocate a dynamic memory space of the specified size (bytes) and store its location in an instance
of a Block structure. The allocated memory must be initialized to 0.
• void blockFree(Block* block)
Deallocate all the allocated memory related to the specified block.
• void blockStoreData(Block* block, void* data)
Store data in the specified block from the memory space indicated by the given pointer. You can
assume that the pointer points to data of appropriate size with respect to the allocated memory
space inside the block.
• void blockAccessData(Block* block, void* dest, DataType type, int units)
Access data from the specified block and store it in the memory space indicated by the given pointer.
Note: The type and units represent the size of the destination memory space indicated by the
pointer.
• void blockRealloc(Block* block, DataType type, int new_units)
Given a block that points to a specific memory space, you must reallocate the memory space to be
of the new specified size while ensuring the data stored is not corrupted. You can assume that the
DataType specified will be the same as before. However, number of units may differ.
For zero-sized reallocations, do nothing.
Hint: be wary of overwriting more memory than allocated, i.e. the new size may or may not be
greater than the previous memory size.
8
CS200/EE201 Fall 2024-2025
9
CS200/EE201 Fall 2024-2025
Library functions:
Implement the following functions, in ’pointer.c’, as described.
For zero-sized allocations, return NULL.
• BlockPointer* initializeBlockPointer(DataType type, int units, void* data)
Initialize the block pointer that contains information regarding the origin block. You must allocate
the origin block through this initialization and store its details accordingly.
Note: The offset of the origin block is 0.
• BlockPointer* pointerMalloc(DataType type, int units, BlockPointer* origin)
Create an instance of a block pointer that contains information regarding the block through which
memory space of the specified size has been allocated.
Make sure to establish the linkage between this block pointer and the latest block pointer in the
chain originating from the origin.
• void pointerFree(BlockPointer* block_ptr, BlockPointer* origin)
Deallocate all allocated memory related to the specified block pointer. Upon deallocation of the
specified block pointer, connect its immediate predecessor and successor to each other.
• void pointerStoreData(BlockPointer* block_ptr, void* data)
Store the data in the memory location specified by the pointer to the allocated memory indicated by
the block pointer.
• void pointerAccessData(BlockPointer* block_ptr, void* dest)
Access data through the specified block pointer and store it in the memory space indicated by the
given pointer.
• void pointerRealloc(BlockPointer* block_ptr, BlockPointer* origin, DataType type,
int new_units)
Reallocate the memory space holding the data indicated by the specified block pointer to the
specified size. For zero-sized reallocations, do nothing.
Note: This does not ask you to reallocate the block pointer itself.
• BlockPointer* getNext(BlockPointer* block_ptr)
Given a block pointer, this function must return its immediate successor.
• BlockPointer* getPrevious(BlockPointer* block_ptr, BlockPointer* origin)
Given a block pointer, this function must return its immediate predecessor.
• void pointerCompleteDeallocate(BlockPointer* origin)
Given the origin block pointer, you must deallocate all allocated memory related to all block
pointers allocated during a program’s lifetime.
10
CS200/EE201 Fall 2024-2025
2.2 Stage 2
In this stage, you will develop the following two functionalities on top of the memory abstraction library.
a) A mapping to allow direct access to independent memory blocks – Part 1.3
b) A custom pointer type for sequential or independent access via querying the mappings library – Part 1.4
The custom pointer will allow traversing the entire allocated memory through “incrementing” and
“decrementing” operations. This is equivalent to pointer arithmetic, except in this case the contiguous
memory abstraction allows simply traversing the allocated memory, rather than performing actual
arithmetic. (Note: actual arithmetic would be impossible in this abstraction as the intention is to hold any
type of data in any allocated memory slot and the memory is not actually contiguous).
However, due to the nature of abstraction, the custom pointer type created above cannot independently
access specific known data, as the location of that data is unknown to the pointer. Hence, this requires
creating a mapping that allows the storage addresses of known data to be looked up.
11
CS200/EE201 Fall 2024-2025
12
CS200/EE201 Fall 2024-2025
1 typedef struct {
2 char * identifier ;
3 uintptr_t address ;
4 } Mappings ;
The mappings library stores the address of the identifier’s corresponding block pointer as an unsigned
integer rather than a pointer.
Note: uintptr_t is a data type defined in the stdint.h library, capable of holding memory addresses in
integer form. Moreover, the memory address can be explicitly type casted between a pointer type and an
uintptr_t type (in either direction).
Refer to the example provided under part 1.4 for further clarity.
13
CS200/EE201 Fall 2024-2025
Library functions:
Implement the following functions, in ’mappings.c’, as described.
Identifiers are passed as string literals (constants that exist throughout a program’s lifetime). Hence, it is
not compulsory to convert it to dynamic memory for this implementation.
• Mappings* initializeMap(int num_allocations, void* addr, char* identifier)
Initialized the map as an array capable of holding a num_allocations amount of mapping pairs
excluding the origin, where num_allocations represent the maximum memory allocation calls we
intend on making. Initialize the map to zero.
Note: The origin mapping pair is not included in the number of allocations being specified, as the
origin is data kept internally to track the beginning of this contiguous memory abstraction. It is not
“known” to any program that may be using these libraries.
• void deallocateMap(Mappings* map)
Deallocate all allocated memory space related to the map.
• Mappings* resizeMap(Mappings* map, int new_num_allocs)
To increase the number of allocations that can be made during a program’s lifetime, resize a given
map to a new size and copy all data present in the current map.
Free any allocated memory space that may no longer be in use.
Note: you may assume that the map will always be resized to a larger size.
• void makeEntry(Mappings** map, void* addr, char* identifier)
Make an entry in the given map of the given identifier-address pair at the next available position. If
the map reaches its maximum capacity (number of allocations limit), resize it to double its current
size.
You need not cater to cases of non-unique identifiers at this stage.
• void removeEntry(Mappings* map, void* addr, char* identifier)
Remove the identifier-address pair indicated by the specified identifier from the given map. The
origin mapping pair can only be removed once there are no other entries remaining in the map.
• void* getOrigin(Mappings* map)
Return the address of the origin block pointer as a void pointer. If an origin has not been defined,
return NULL.
• void* getPointer(Mappings* map, char* identifier)
Return the address corresponding to the specified identifier in a given map. If the identifier-address
pair does not exist, return NULL.
14
CS200/EE201 Fall 2024-2025
1 typedef struct {
2 BlockPointer * block_ptr ;
3 Mappings * identifier_map ;
4 } ContiguousPointer ;
The example below assumes that we initially intend on performing a maximum amount of three memory
allocations during a given program’s lifetime
(a) The origin identifier-address pair is (b) The map is updated upon another mem-
stored in the map on initialization. ory allocation.
15
CS200/EE201 Fall 2024-2025
(d) The third (and last) allocation is made and the map reaches its current capacity.
16
CS200/EE201 Fall 2024-2025
Library functions:
Implement the following functions, in ’contiguous_pointer.c’, as described.
• ContiguousPointer* initializeContiguous(int num_allocations)
Initialize the contiguous pointer as well as the origin block. The identifier for the origin block will be
“origin”.
The contiguous pointer will not point to the origin after initialization. It should be NULL.
Hint: do not forget to account for the null terminator when storing a C-string in a memory space.
• void contiguousMalloc(ContiguousPointer* c_ptr, DataType type, int units, char*
identifier)
Initialize a block pointer pointing to the specified size’s memory space and store the corresponding
mapping pair in the map.
The contiguous pointer must be updated accordingly to point to the block pointer corresponding to
the latest allocation.
Make sure to check for non-unique identifiers.
• void contiguousFree(ContiguousPointer* c_ptr, char* identifier)
Free the memory space corresponding to the identifier and remove the entry from the map. Update
the contiguous pointer to point to the first allocation.
Note: the first allocation does not refer to the origin. The origin should remain invisible to the
external program using the contiguous pointer.
• void storeData(ContiguousPointer* c_ptr, char* identifier, void* data)
Store the data from the specified memory location to the allocated memory indicated by the
identifier. Any attempts at overwriting the origin memory space should be prevented.
• void accessData(ContiguousPointer* c_ptr, char* identifier, void* dest)
Access data in the allocated memory corresponding to the identifier and store it in the destination
memory space. Any attempts at accessing the origin memory space should be prevented.
• void increaseAllocations(ContiguousPointer* c_ptr, int new_num_allocs)
Increase the total number of possible allocation calls to the specified amount.
• void incrementPointer(ContiguousPointer* c_ptr)
The contiguous pointer should be “incremented” to now point to the immediate successor of the
currently pointed-to memory space in the abstraction.
• void decrementPointer(ContiguousPointer* c_ptr)
The contiguous pointer should be “decremented” to now point to the immediate predecessor of the
currently pointed-to memory space in the abstraction.
The origin must remain inaccessible.
• void changePointer(ContiguousPointer* c_ptr, char* identifier)
Update the contiguous pointer to now point to the memory space corresponding to the specified
identifier.
The origin must remain inaccessible.
• void completeDeallocation(Contiguous** c_ptr)
Completely free all remaining allocated memory associated with the contiguous pointer and set it to
NULL.
17
CS200/EE201 Fall 2024-2025
2.3 Testing
You have been provided with a Makefile for part 1, which handles the compilation targets of your program
for you and allows selectively recompiling only those files where changes have been made.
The Make utility, which interprets the Makefile, is traditionally a Unix-based tool. Windows systems, by
default, will not recognize the make command given below. It is highly recommended that you do not
perform intermediate testing on Windows, even if you have installed the GNU Make tool. Instead, for
Windows users, I would recommend installing Windows Subsystem for Linux (WSL) for intermediate
convenient testing. However, make sure to test your program on docker atleast once before submitting as
we will be exclusively testing on docker.
To test your program through the Makefile, first change your directory to the ../Part01 directory. To do
this:
1. Use the ls command to view the files and subdirectories in your current directory.
2. Use the cd command followed by the directory name to change your current directory to the
intended one.
Look at the figure below to get an idea on how to change your directory to the intended directory.
Once you have entered the ../Part01 directory, run the following commands.
make
Whenever you edit your program files, you need to recompile the files using the above make command. The
Makefile will selectively recompile only those files that you edited.
make run_block
make run_pointer
18
CS200/EE201 Fall 2024-2025
make run_mappings
make run_contiguous
All object files (compiled program files), as well as Valgrind’s text file outputs, will be created in the
../Part01/bin directory. To clear these files, simply run:
make clean
19
CS200/EE201 Fall 2024-2025
3 Part 2: CityBloxx
3.1 Overview
In this assignment, you’ll delve into the world of dynamic 2D arrays by creating an engaging game called
CityBloxx. This project must be completed individually, and you are required to use C programming.
Our version of CityBloxx is a unique adaptation of the classic game, designed to provide a more engaging
and challenging experience. In this version, you’ll take on the role of the programmer, building and managing
the game’s core mechanics. You’ll be responsible for coding the game’s logic, including handling user input,
updating the game state, and displaying the results. This project will test your skills in C programming,
as you’ll need to implement a dynamic 2D array to represent the game grid, develop algorithms to manage
block placement, and ensure the game’s rules are correctly followed. By the end of this project, you’ll have
created a fully functional version of CityBloxx, showcasing your ability to apply programming concepts to
create an interactive and enjoyable game.
20
CS200/EE201 Fall 2024-2025
21
CS200/EE201 Fall 2024-2025
• int left_cord: this will be used to store the left coordinate of your previous block or latest landed
block.
• int right_cord: this will be used to store the right coordinate of your previous block or latest
landed block.
• int score: this variable will be used to keep track of your score during the game.
• int count: this variable will be used to keep track of your perfect landings, i.e, when both
coordinates of your new block are equal to coordinates of your previous block meaning that the
2 blocks are perfectly stacked onto each other. Please note that once a block is placed in a non
perfect manner, count must reset to its initial state and must only keep incrementing in the
case of continuous perfect landings.
• int height: this variable will be used to keep track of the height of your tower.
• int state: this variable will be used to keep track of the state of your tower. You may set this
to any arbitrary number to denote the 2 possible states (either fallen or complete).
In the end, the CityBloxx competition wasn’t just about winning; it was about pushing the boundaries
of what was possible and creating something beautiful that would stand the test of time.
And so, in this grand tale of architecture and engineering, every block told a story, and every story con-
tributed to the magnificent saga of the CityBloxx apartments. Thus, we are to test your building.
1. initialize_grid()
22
CS200/EE201 Fall 2024-2025
Your grid initially needs to have 7 rows and 70 columns as shown in figure 13 meaning that you
will declare a 7 by 70 grid. The function is pretty simple. In this function, you will be passed the
city grid and you will allocate memory to it as mentioned above. You need to dynamically allocate
memory. In addition to this, you must initialise the whole array with spaces since the testing will
be done by comparing text files. Don’t forget to set the rows and cols attribute of the city pointer itself.
2. simulate()
The simulate function in your code is designed to process moves from a file and simulate the CityBloxx
game accordingly. This function reads moves from a file, validates each move, updates the game grid
accordingly, and manages game state and scoring. It’s crucial to ensure the coordinates are correctly
deduced based on the input format defined by the game rules and examples provided.
The presence of a landed block is denoted by the following characters filling the array accordingly.
Each block takes up 4 rows and 10 columns.
Moreover, the following table consists of a few potential moves (moves are characters ranging from
a-z and A-Z, one on each line of a text file) and their generated coordinates. You are to deduce a
formula for generating these moves yourself. Be careful about edge cases and look at ascii values :)
Note: x1 denotes the left coordinate of the incoming block and can be easily deduced by looking at
the examples. x2 , however, is simply x1 + 9 since each block will take up space from column x1 to
x2 with x1 and x2 included.
23
CS200/EE201 Fall 2024-2025
Moreover, it is highly important to note that a policy passed by LDA does not allow you to build a
tower less than 20 units from the road. Thus, your habitable area is only 50 UNITS.
Invalid Block: Your first block must always land as long as its within the range of the grid. For
subsequent blocks, you must check whether stacking it would be a perfect stack or not. If yes, you
must increment count and update the score by multiplying count with 10 and adding it to the current
score. If not, you must set count = 0 and should update the score with a +5 award. However, if an
incoming block is invalid, your game must terminate with landing that block anywhere. The formula
to determine whether a block is valid or not is:
diff is the difference between the left coordinate of previous block and next block or the differ-
ence between the right coordinate of previous block and next block. You must check both cases.
Processing Moves: The moves are essentially characters, one on each line of the text file be-
ing passed in the function "moves". You must read the file iteratively and simulate the game. Here is
an example of what the input file looks like.
24
CS200/EE201 Fall 2024-2025
Invalid Move: You must check whether your move is within the range of the defined set of moves. If
not, you must ignore that move and move forward. You must also apply a -5 penalty on the player’s
current score.
Perfect Stackings: Once you calculate that the incoming block will be landing perfectly over
the previous block, you must increment count and update score by multiplying count to 10 and
adding to old score.
IMPORTANT:
In case, your game goes on perfectly, your state must be set to complete. If not, your state must be set
to fallen. You may use any arbitrary integers to denote these states according to your implementation.
After processing each move, you must extend your array upwards by 4 rows and fill empty spots by
spaces. You must ensure efficient dynamic allocation of new array and deletion of the old one. It is
recommended that you make a helper function for this purpose.
3. display()
This function is pretty simple and involves printing the contents of your grid onto the text file. The
file is named [Link] and is already included in the Assignment folder. Moreover, the part to be
careful about is printing the state and score correctly. Here is an example of what your output must
look like.
You must have noticed the tower state at the top. Hence, you must print "What a tower!" if your
player successfully stacks all blocks and game ends successfully. However, if the game terminates due
to invalid blocks, you must print "Tower fallen!". Then, in the same line, exactly 5 spaces away,
you must print the score in the exact format displayed in figure 17.
25
CS200/EE201 Fall 2024-2025
Note: You must ensure that everything is printed in the correct format, including all empty spots
denoted by spaces and all occupied spots correctly displayed by the relevant format. Otherwise your
test cases won’t pass, given that testing is being done by comparing text files.
If in any case, you’re stuck, you can open the solution files (5 files in total named as TestCasexSolution
where x is the relevant test case number) and compare your result manually.
4. Visualizer
In case, you’re struggling in interpreting any miscalculation in your coordinates, you can run the
visualizer provided. You are to run the following command in terminal before running the visualizer.
Upon running the visualizer, you will see your tower like this.
The x-axis denotes the coordinates (column number of start and end) of your blocks and the y-axis
denotes the height*4 + 7.
26
CS200/EE201 Fall 2024-2025
Moreover, in order to change the text file to visualize, you need to replace the following file name with
your desired filename. The python file is also included in your assignment folder.
Your solution is printed in the [Link] file and the test case solution is in the [Link]
files. You many comment out other test cases in the main function to check or dry run any specific
test case so that your [Link] file only displays the output for that specific test case. Testing,
however, on our end will be carried out by the default main function.
IMPORTANT: You must note that testing will be done automatically, and no partial marks
will be awarded. You will be given the marks the test case shows, and no exceptions will be made.
You also must not alter any code except the 2 functions you have to implement.
27
CS200/EE201 Fall 2024-2025
3.5 Testing
Testing is quite simple and you only need to run the following command after ensuring that you are in the
correct directory.
The terminal will show detail of which test case isn’t passing or if all are passing (if you’re very smart
;)). In case, you don’t understand why a particular test case isn’t passing or want to manually inspect the
[Link] file for that test case, you may temporarily comment out all other test cases and observe only
that particular case.
IMPORTANT: All required libraries and files have already been included for you. All of your must
28
CS200/EE201 Fall 2024-2025
be on [Link] and any other code must not be altered. Marking will be strictly done on the basis of
test cases and no partial marks for incomplete code will be given.
29