ICS 33 Fall 2026
Project 0: History of Modern
Due date and time: Friday, October 2, 11:59pm
Final late work deadline: Late submissions are not accepted
You must install Visual Studio Code and complete the Python Setup for ICS 33 instructions before you start!
To start your project in Visual Studio Code (only after finishing setup): Click here
Introduction
Given your successful completion of prerequisite coursework, you will have built your programming skills to the point where you can do a lot more than you could do when you wrote your first program.
As you continue to climb the skills ladder in this course and beyond, you'll find yourself solving problems that are gradually larger, gradually more complex, require gradually larger teams of people to solve (and a gradually higher percentage of time spent coordinating with those people), and present gradually more ways to make mistakes both large and small. Scale introduces constraints, which means that we need tools that are designed to work within those constraints. This quarter, we'll begin an exploration of those tools, so you can hit the ground running when you reach the point where you need them. And, fortunately, those tools benefit us even we use them in individual, smaller-scale work like you'll be doing in this course.
But, of course, we can't use new tools without having learned about them, and there's no better way to learn about tools than to put them to use. This project will ask you to exercise your existing Python programming skills, but will also require you to use tools and organizational techniques that may be new for you. Your score on this project will largely be determined by your demonstration of how you used those tools and techniques throughout, so this isn't just a matter of doing whatever you prefer and submitting a complete solution; the focus is as much on how you get there as it is on whether you get there.
Setting up your development environment
It is very common in real-world software development jobs to be given, at the outset of a new job, a list of tools that you will be required to use, along with a list of policies and procedures you'll be required to follow. Flexibility is great, and it's nice to be able to choose one's own tools, but, unfortunately, many software tools introduce constraints on how a program can be written, how its components can be arranged, what functions can be called, what documentation can be written and how, what additional software it can be combined with, how it can be "built" to be distributed to end users, and so on. So, like it or not, real-world software development usually requires at least some of the tools to be set in stone and used by all members of a team, even if not all members have the same preferences; this is simply a reality that software developers have to face, because smooth collaboration in a team environment is usually more important than satisfying every individual's whim. (While your work in this course is individual, you'll nonetheless be working with a team of course staff, who won't be able to support everyone's individual tools and bespoke preferences.)
Your first task in this project, then, is to set up the development environment that we'll all be using for our work this quarter.
Developing and testing a Python module incrementally
This project isn't just about setting up tools, though. We'll also be asking you to put those tools to use to enrich your prior skills, by developing and testing a Python module in an incremental fashion. Importantly, you'll also have to demonstrate that you followed that incremental approach by submitting every version of your module as you developed and tested it, step by step. We don't just want to see where you ended up; we want to see how you got there, even if there were missteps along the way.
Don't worry, though; you'll be using the same tools that professional software developers use to achieve these same goals, so you won't need to be making copies of files and arranging your own history as you go. As long as you can take notice that you've reached stable ground periodically, and definitively plant a stake in that stable ground each time with a brief explanation of what's changed and why, the rest of the paper trail will be built for you automatically in a form that will be easily submitted to us within a single file.
A word of warning
Before you start writing any code for this project, you're going to want to read this write-up in its entirety, because how you approach this problem is at least as important as whether you can solve it. The learning objective of this project is to familiarize yourself with the tools and processes you'll be using to begin an assignment, document and test your work incrementally as you proceed with it, and submit it to us in a way that we can accept and grade using automation to assist us. That way, we can continue to rely on your ability to adhere to a similar process throughout the quarter, so that we can focus our grading efforts on providing useful feedback, rather than the secretarial work of rearranging each student's customized submission into something we can work with.
Consequently, correct solutions that do not demonstrate the use of the necessary tools and processes will receive little or no credit. Grades are an attempt to measure learning, and it's those tools and processes that you're meant to be learning in this project.
Getting started
Your first task is to complete the Python Setup for ICS 33 notes, if you haven't already. If you haven't, then do that now, then come back to this afterward.
Each project will begin with a starting point, which will include a combination of provided Python code, with any other necessary tools to use for setup and submission. The starting point is provided in the form of a Git repository, which you'll open and set up in Visual Studio Code, just like you did with the ICS33.Playground repository in the Python Setup for ICS 33 notes.
(We'll discuss Git in some more detail a little later in this write-up.)
Starting a new project
To start a new project, click the link titled To start your project in Visual Studio Code at the top of the page. For convenience, I'll duplicate that link below.
Follow the steps that you followed to set up your ICS33.Playground repository:
Having done those things, you're ready to begin work. To do that, though, you'll need to know what you need to do, so read on!
The problem
After you've created your Visual Studio Code project, you'll see that it contains two files of immediate interest.
src directory, you'll see queens.py, in which you'll find the skeleton of a class named QueensState.tests directory, you'll see test_queens.py, in which you'll find the skeleton of a unit test class named TestQueensState that is meant to contain unit tests for QueensState.Your end goal is twofold: Implementing the methods of QueensState class meeting the requirements specified in its docstrings and type annotations, and implementing unit tests of that class in the TestQueensState class.
You might now want to take a look at those two files, so you have an idea of what lies ahead. But it's important that you not barrel forward toward those goals without having read the rest of this project write-up, because there are requirements about how you do this work, in addition to requirements about whether you complete it. While I'm sure all of you have the skills to complete this task in your own chosen way already, there are learning objectives here that have more to do with tools and techniques — which, for most of you, will be a departure from what you're used to — than the code we're asking you to write, which may not be new territory for you at all.
Working and testing incrementally
In your work on this project, we're requiring you to build small-scale features one at a time, and you are required to write unit tests as you go. If you're not feeling sure what I mean by "small-scale feature" here, it might be worth having a look at the notes accompanying the Test-Driven Development topic that I cover in ICS 32 and ICS H32, which proposes one way to approach this kind of development. In this course, we're not particularly zealous about writing tests before we write code — that's certainly reasonable, but hardly a requirement — but we do at least want to be sure that we're writing tests shortly after writing the code under test, if not before. Whether the tests are written before or after implementing a feature, we want to get a feature nailed down (including its testing) before we move on to the next one; that's mainly where the benefit of this kind of approach arises. And, once we've got it nailed down, we're on demonstrably stable ground, so we should keep a copy of our work at that point in time, for which we'll use a tool called Git.
To give you an example of a small-scale feature you might start with in this project, let's start with this one: "When a QueensState is initially created, its queen_count method will return zero." That's our initial goal and our only goal for now, so we'll focus on that and ignore everything else until later. As it turns out, there's already a unit test for this particular feature provided in test_queens.py, which you'll find in a method named test_queen_count_is_zero_initially. Let's run that test and see it fail, mainly so we know how to run unit tests in Visual Studio Code, which we'll need to do often as we work.
Running unit tests in Visual Studio Code
When you want to run your unit tests within Visual Studio Code, the easiest way to do so is to switch to the Testing view, by clicking the corresponding icon along the left-hand side. (You can find out what each icon is by hovering over it, which will reveal a tooltip that tells you its nam.) When you click the Testing icon, you'll see an area that shows the collection of the unit tests across your entire project. (They may initially be hidden, so you may need to expand the collection, by clicking what looks like a > sign, just to the left of Project: Project 0.)
You should see the one test, named test_queen_count_is_zero_initially (the one and only provided test function), arranged within TestQueensState (the class it's defined in), arranged within test_queens.py (the file that class is defined in), arranged within tests (the directory containing that file). If so, then you're seeing all of the available tests. As you write more and refresh this view (by clicking the circular arrow icon near the top), you'll see your tests listed here, too.
To run your tests, click the Run Tests icon (shaped like a "play" button) near the top. (If you're not sure which icon to click, hover over them until you find the one whose tooltip says Run Tests.) This will Visual Studio Code to run all of the tests and display their results. Because you'll be writing code and tests incrementally, you'll likely find yourself running your tests quite often. It's not uncommon for my develop-test cycle to be no more than a few minutes, or even sometimes less than a minute, depending on the kind of work I'm doing. Frequent actionable feedback about one's progress can be a huge boon to one's productivity, keeping us "in the zone" while leaving behind a lasting record — in the form of our unit tests — of exactly how our program is meant to behave.
Having run the tests, you should see the Test Results view in Visual Studio Code displayed, if it wasn't already. In that tab will be an indication that the test has failed, along with an error message describing that failure. Our next order of business is to make the test succeed, which we can do with a one-line change in queens.py. In the queen_count method, replace the pass statement with return 0 instead. Save your change to queens.py and then re-run the unit tests again, and you should now see that they've passed instead of failing.
(You may find that you need to refresh the tests before re-running them, in at least some cases. This is apparently a long-standing bug in Visual Studio Code's user interface, for which the best remedy at the moment is the workaround of refreshing the tests prior to running them.)
Building our historical record in Git
Now that we've got our first feature nailed down — that's really all there is to our first feature, though they won't all be this easy — we're on stable ground, so we now need to note this in our historical record, since one of the requirements of this project is that you submit a complete record of the changes you made, one feature at a time. To do that, though, we'll need to understand a little bit about what Git is, and how we interact with it in Visual Studio Code.
Git is what's often called a version control or source control system, which means that it's in the business of tracking many versions of our source code (i.e., the code that makes up our program) instead of just one. When you created your new Visual Studio Code project using the provided link, it triggered Visual Studio Code to build a Git repository for it automatically, which simply means that there's an additional directory within your project directory named .git — which you may not see, because a lot of operating systems will hide it from you, unless you turn that feature off — that stores a complete history of every version of your project as it evolved. Visual Studio Code recognizes this Git repository automatically and gives us tools for interacting with it visually.
When we want to update the history of our project, we do so by committing a change to our Git repository, which is to say that we instruct Git to remember which files changed and what changed within them, alongside a commit comment that we write explaining whatever else we want to describe about the update we're making. Notably, this doesn't replace what was in the Git repository previously; it augments it. Our Git repository contains the entire history of changes — every commit we've made from beginning to end — which means it contains enough information to allow us to reconstruct every prior version of our project. As long as we made a commit at a particular point in time, we can get back to that point in time quickly and accurately (and non-destructively!), as well as do other useful things, such as finding out the last time a particular file changed, viewing exactly what we changed in a particular commit, or comparing two commits from different points in history to see how they differ.
(When shared amongst groups of people working together on a project, Git shines even more brightly, allowing us to do things like finding out "Who was the last person to change this line of code and when was it?" or ensuring that changes made by one person don't blindly overwrite changes made by someone else. Since your work in this course will be entirely individual, you won't have occasion to experience those benefits in this course, but when it comes time to work in larger teams, you'll be able to expand your knowledge of Git so that it can support you and your teammates in those efforts, as well.)
Identifying ourselves in our commits
One thing that's tracked for every commit in a Git repository is the author of that commit. When we're committing a change ourselves, we get to decide how our authorship will be identified, in terms of both a name and and email address. This can be a little tricky sometimes, because we may be representing more than one organization — while I always use the same name, I use my UCI email address, my personal email address, or my professional work email address, depending on which kind of work I'm doing.
From the perspective of this quarter's work, you should be identifying yourself in the role you're in: as a student at UCI. So, you'll want to instruct Git that you'd like to use the name and UCI email address that we'll recognize as yours — rather than a completely invented name or off-campus email address.
From the Terminal menu, select New Terminal. At the terminal prompt, you can issue the following commands.
git config user.name "PUT YOUR NAME HERE"
git config user.email "PUT YOUR UCI EMAIL ADDRESS HERE"
Note that this only sets your name and email for this one project. Assuming you won't be doing any work this quarter that requires you to identify yourself in any other way, you might instead want to set this globally, so that every commit in every repository is identified the same way. If so, you can use these commands instead.
git config --global user.name "PUT YOUR NAME HERE"
git config --global user.email "PUT YOUR UCI EMAIL ADDRESS HERE"
There are fancier techniques available, but one of those will likely be the right move for you.
Committing our change
So, how do we commit our change? Along the left-hand side of the Visual Studio Code window, click the Source Control icon, which, when clicked, will display the Source Control view. You should see a list of Changes, which will be any files that have changed since you last made a commit. (So, at the moment, that should be queens.py, since that's the only file you'll have changed.)
Before you can commit it, though, you'll need to stage it, which means that you'll need to select which changed files should be committed. (This allows you to work on multiple things at once, or to commit the things you're surer about without also committing the things that are still in flight.) When you hover over a changed file, you'll see a + icon to the right of it. Hover over queens.py and click its + icon. queens.py will now be listed under a new section named Staged Changes.
Now that you've staged your change, click in the box labeled Message, where you'll write a commit comment describing your change. This is where you'd briefly want to describe what's changed and why. Simple changes require less of a comment than larger ones. For this change, it's simple enough that perhaps a comment like Initially, the number of queens is zero is sufficient. To follow the usual Git convention, you'll want to write your commit comments to have a short title on the first line of text — no more than 50 or so characters — followed by a blank line, followed by any additional explanation you'd like to write.
When you're done writing your commit comment, click the Commit button, and you'll be in business. You should notice afterward that the Graph area listed below will have changed; your commit will now be listed, in addition to the initial commit of the starting point that I provided.
Testing incrementally
While you work, you'll be required to write unit tests. Note, too, that the phrase "while you work" has meaning here; each time you've finished a small feature, it will need to be accompanied at that time by the corresponding unit tests. Ideally, a Git commit that includes a new feature or a fix to existing code would include the applicable changes to the unit tests (i.e., the new or updated tests related to your change), so that each commit is demonstrably stable ground: a finished or fixed feature, along with tests that give you confidence in your assessment that it's finished or fixed. That may be a very different way of working than you're used to, because it requires a level of discipline you might have survived without so far, but working incrementally and testing as you go is a technique that many professional software engineers — me included, as often as I'm able — follow as carefully as possible, because it's one of the many techniques that aid us in tackling substantially larger problems than those we can fit entirely in our heads at any one time.
To reiterate for clarity, following this approach is a requirement and the extent to which you follow this approach constitutes a substantial portion of your grade on this project. While there is certainly leeway in the definition of the word "feature," and we're not going to be able to spend the time negotiating with individual students about exactly how many features are required or exactly which features must be implemented in which order, it is certainly true that students who submit a complete solution with only a small handful of commits, or with tests written only after all features were implemented (or not at all) will find themselves with scores much lower than they would probably expect. Approach the problem incrementally, testing as you go, and you'll be fine from a grade perspective, but you'll also have learned a valuable set of skills that you can use moving forward, ones that I use nearly every day in my professional work.
How many unit tests are required?
You'll need to write as many unit tests as are needed to fully cover its behavior. Your goal for this project is to achieve full coverage, which is to say that at least one test should reach every line of code in queens.py, and that at least one test should exercise every branch in every direction (e.g., an if statement needs to be tested in both the truthy and falsy cases). While that's a more ambitious goal than we'll often be aiming for, the nature of this problem — a class describing a type of immutable object, whose methods have no side effects — is such that we can achieve it with techniques you'll have seen previously, which makes it a great way to learn how to achieve that standard when it's an appropriate one.
Of course, this means that how you choose your tests will have an impact on the number of tests you'll write. Many tests that, when executed, take the same route through your code don't improve the overall coverage measurement at all, which is an indication that having more of them may not be adding any more value than if there were fewer. That's why we're not specifying a number of tests that we're requiring you to write; it doesn't matter how many tests you write if they're all testing the same behavior. It's writing tests with variety that matters, and, fortunately, there's a way to measure that variety.
Measuring code coverage using Visual Studio Code
Code coverage measurement is the practice of running a program in an environment that keeps track of which lines of code were reached, which branches were taken in which directions (e.g., for each if statement, track whether its condition has been both True and False), and so on. Where this technique can be very useful is alongside unit testing. If we measure the level of coverage achieved by running our unit tests, we have a visible indication when there are parts of our code we've not tested at all. While achieving full coverage is not necessarily an indication that every useful test has been written — like any metric, this one can be gamed a bit, so we don't want to turn our brains off and just follow it blindly — the absence of full coverage highlights scenarios that we've certainly not tested.
Visual Studio Code provides the built-in ability to measure code coverage while running tests. The easiest way to do it is to click the Testing icon along the left-hand side of the window, which opens the Testing view. Along the top of the Testing view, you'll find an icon with a tooltip that reads Run Tests with Coverage. Click that.
What you should see is two things:
Further, if you switch back to the Explorer view, you'll also see a coverage indicator next to each of the files in src, along with an overall indicator of coverage for the entire src directory.
Of course, a percentage only tells you so much; it tells you there's something missing, without telling you what it might be. Double-click the queens.py to open it in an editor and you should now see a visual indication along the left-hand side of the file, showing you which lines are covered.
When you're finished, executing all of your unit tests should lead to every meaningful line (i.e., every line that's not blank or only a comment) in queens.py being marked green, and every branch taken in all directions (e.g., the condition in every if statement would need to have tests where that condition was truthy and others where it was falsy). If not, you'll need to evaluate what tests you might need to add — the specific situations you aren't testing already — to achieve that goal.
Importantly, please note that it doesn't matter if the code in test_queens.py is marked green or not. What we're measuring is the extent to which our tests exercise the code being tested, so we only care about the code being tested, which is in queens.py.
Limitations
You can use the Python standard library where appropriate in this project, and you can certainly depend on the code that we've provided, but you will otherwise not be able to use code written by anyone else other than you. Notably, this includes third-party libraries (i.e., those that are not part of Python's standard library), which are strictly off-limits in this course except where specifically allowed. Colloquially, if we have to install something other than Python, Git, and Visual Studio Code in order for your program to work, it's considered off-limits, unless specific permission is given in a particular project. This project offers no such permission.
Since we'll be automatically testing your QueensState class, rather than relying only on your tests to demonstrate its completeness and correctness, do not modify the Position namedtuple or the provided exception classes (DuplicateQueenError and MissingQueenError), since our automated tests will depend specifically on the way that they're written.
Preparing your submission
Because the grading of many of your assignments will be at least partially automated, we'll need to agree on a submission format, so that everyone's submission will be arranged identically. Fortunately, our use of Git makes this choice simple. Git allows a complete repository to be packaged up into a file called a Git bundle, in a format that Git-compatible tools (like Visual Studio Code) can work with easily; so, you'll provide us with a Git bundle as your final submission. In other words, what you'll be submitting to us is essentially your entire Git repository, including all of the commits you made along the way, so that we have a complete history of your work and how it evolved from the starting point to the finished product.
It's important to understand that it's not our goal to judge every one of your commits, so there's no need to feel uncomfortable about submitting a repository that includes in-progress versions of your work, or to spend time curating a parallel repository of "clean" changes. We completely understand that earlier commits will have warts and rough edges, just as the earlier commits in our own repositories in our own work are of varying quality. For the most part, all we'll be grading in detail is your most recent commit on the main branch of your Git repository. But we do want to be able to see an indication of your process, which can sometimes help us to understand the details of your submission, and can allow us to verify that you had a process, which is among the techniques we want you to learn this quarter, but that can't be learned without doing it.
Demonstrating that you've worked incrementally is a substantial part of your grade, but we don't have precise numeric requirements about how you worked incrementally. There's no predetermined number of commits you must have, or a predetermined number of lines of code that can change between commits. The goal here is qualitative rather than quantitative. All you need to do is follow one simple rule: Each time you've reached stable ground, with a small-scale feature working and tested, or with a problem fixed and a test that identified the problem and now demonstrates that it was fixed, it's time to commit your change with a brief explanation of what new feature you added, or what problem you were fixing. That will benefit you as much as it does us — you'd be surprised how much it helps your thought process to stop and write a couple of sentences about what you're doing and why, and how useful it can be when you're able to refer to your own previous commits — and that's all we're asking for here.
Generating a bundle using the prepare_submission.py script
When you're ready to submit your work, you'll run a script we provided — which you'll find in the tools directory — named prepare_submission.py. That script will make an attempt to create a Git bundle from the Git repository in your project directory, which would constitute your entire submission.
The prepare_submission.py script will attempt to verify a few things before it creates a Git bundle, to ensure that it's able to create a bundle, and that you're submitting a complete version of your work.
_GIT_EXECUTABLE_PATH to specify where it is).One way to run prepare_submission.py is to start a Terminal window, then enter these commands.
# If you're running Windows, enter these commands...
cd tools
python -m prepare_submission
# If you're running macOS or Linux, enter these commands...
cd tools
python3 -m prepare_submission
Any output from the script — including any warnings or error messages — will be displayed in that terminal. Assuming there are no warnings or errors, you'll find the file project0.bundle in your project directory, and that's the one and only file you'd submit.
Verifying your bundle before submission
Before you submit your bundle, you'll likely want to verify that it's complete and correct. Note that you bear the risk here, which is to say that we'll be grading what you submit, and you won't later be able to explain that you would have preferred to submit something else. So, now's the time to be sure that what you're submitting is what you want us to grade.
The best way to verify your bundle prior to submitting it is to extract it, so you can most easily see what's in it. The simplest way to do that is to launch a Terminal window, then enter this command.
git clone project0.bundle verify
You should now see a verify directory in Visual Studio Code (amongst the others). Take a look at it — particularly the contents of the src and tests directories, since those are likely the only places you've made any changes. If things look as they should, you're in business; go ahead and submit your work.
If you want to scrutinize your submission in more detail than that, your best bet is to move that verify folder somewhere else (outside of your project directory) and then open it separately in Visual Studio Code.
Deliverables
Submit your project0.bundle file (and no others) to Canvas. There are a few rules to be aware of.
main branch, except to the extent that we'll examine prior commits when evaluating your overall process. We will not negotiate about which commit will be graded, or, for example, grade multiple of your commits and "take the highest score."Can I submit after the deadline?
This project is not included in the late work policy for this course. The objective is not just that you do this one, but that you do it early enough to be of benefit, so it needs to be completed during the initial days of the course and submitted before the due date above. Submissions beyond that deadline will not be considered under any circumstances.
The late work policy for this course — which does not apply to this project, but applies to all of the others — is described in the section titled Late work at this link.
What do I do if Canvas adjusts my filename?
Canvas will sometimes modify your filenames when you submit them (e.g., by adding a numbering scheme like -1 or a long sequence of hexadecimal digits to its name). In general, this is fine; as long as the file you submitted has the correct name prior to submission, we'll be able to obtain it with that same name, even if Canvas adjusts it.