Warning: Outdated Content
This MP is from a previous version of CS 125. Click here to access the latest version.
MP1: Area Mode
In the first Machine Project checkpoint, MP0, you started making a cool Android app 1. This checkpoint and all subsequent ones build on that codebase, adding new features and improving the structure of the code.
In this second checkpoint (MP1), you will use your new object-oriented programming skills to package up some game logic into a class that will help you implement a new mode of the game. You will take advantage of Android Studio’s UI designer to create an interface that allows the user to select the game mode.
In area mode, the objectives that can be captured are rectangular cells on an evenly spaced grid rather than specific targets. Entering a cell captures it—if possible. If at least one capture has already been made, subsequent cells can only be captured if they share a side with the most recently captured cell.
Again, deadlines for each checkpoint vary by your deadline group. MP1 is due at:
-
8 PM on Sunday 10/6/2019 for the Blue Group: all labs starting at 2 PM or earlier
-
8 PM on Monday 10/7/2019 for the Orange Group: all labs starting at 3 PM or later
Since MP1 is another multi-week checkpoint, 10% of your grade on it is for submitting code that earns at least 40 points by Sunday 9/29/2019 (Blue) or Monday 9/30/2019 (Orange). Late submissions will be subject to the MP late submission policy.
1. Learning Objectives
This is the first checkpoint that makes use of object-oriented programming. In addition to the imperative skills you practiced on MP0, you will:
-
implement a class to bundle state and behavior,
-
obtain and use instances of existing classes,
-
work with data in multidimensional arrays, and
-
design a user interface
2. Assignment Structure
You’ll be working on the same repository for the entirety of the Machine
Project.
MP0 focused only on the Java code files, but Android projects have another major
part: resources.
These are found in the res
folder, which is in the main
folder that also
holds java
.
The resources you’ll work with most are layouts, which are stored in the
layout
subfolder of res
.
They represent the screens and controls the user sees when using your app.
The Android section later in this document will tell you more about how to work
on the user interface.
2.1. Obtaining MP1
You already have your GitHub repository ready to go; all that’s missing is the test suite for Checkpoint 1.
To do this we are going to provide you with a patch: a single file that contains multiple changes to your project. The patch for MP1 adds two files: the test suite itself and an Android Studio run configuration that will allow you to run the test suites easily.
To apply the patch and get started on MP1, follow the following instructions:
-
First, commit your work! This makes sure that you can easily undo any problems that the patch may cause. It should apply cleanly, but you never know.
-
Next, download the patch file. Remember where you put it since you’ll need it in the next step.
-
Open your MP in Android Studio and then select "VCS | Apply Patch". Select the patch you saved in the previous step. Android Studio will allow you to review the changes that the patch will make to your project. You’ll see two new files:
Checkpoint1Test.java
(the test suite) andTest_Checkpoint_1.xml
(the Android Studio run configuration). -
When you’re ready select OK to apply the patch. If the patch is successfully applied, you’re ready to get started on MP1! You should have a new "Test Checkpoint 1" test menu item available—if not, try repeating the magic "Sync Project with Gradle Files" from the File menu.
-
If the patch does not apply cleanly, please get help right away! Post on the forum, come down to office hours, or get help in lab next week. MP1 is a challenge and we want you to get started right away.
You also need to configure the autograder so that it knows that you’re working
on MP1.
Open the grade.yaml
file in the root of the project, change the checkpoint
setting from 0
to 1
, and do File | Sync Project with Gradle Files.
Running the Grade task should then produce a grade report for Checkpoint 1
rather than Checkpoint 0.
2.2. Late MP0 Submissions
If you didn’t finish the TargetVisitChecker
functions in MP0, you can still do
MP1.
Changing the useProvided
setting in grade.yaml
from false
to true
(after
updating checkpoint
to 1
) and doing a Gradle sync will make the app and
grader use our correct TargetVisitChecker
implementation.
That is, calling TargetVisitChecker
functions from GameActivity
will produce
the result from our solution, not your code.
If you’re happy with your TargetVisitChecker
implementation, leave
useProvided
set to false
to continue using your MP0 code.
5% of the MP1 grade requires a working target mode game, so even if you use our
provided TargetVisitChecker
functions, you will still need to implement the
GameActivity
logic of MP0 yourself.
If you have not done this, please do so now.
Come to office hours if you need help.
If you want to go back later and work on a late MP0 submission for half credit,
change the checkpoint
setting in grade.yaml
back to 0
.
That tells our grader to do an MP0 grading run.
It’s OK if you’ve started working on MP1 or even a later checkpoint when you
regrade MP0.
The test suites are forward-compatible.
3. Android
MP1 continues introducing you to Android development. We’ll introduce a couple new ideas and tools below. There’s a bit more work to do than on MP0, but the MP1 tasks should be more straightforward, especially since you’ve started finding your way around the codebase.
3.1. UI Designer
Most Android resources are XML files. While it is possible 2 to edit these directly, Android Studio provides a graphical interface that makes it much easier to design your UI layouts. When you open a layout XML file, Android Studio shows a preview of the layout in the main view. (If you instead see a bunch of text, switch to the Design tab in the lower left.)
Every piece of user interface offered by Android has a type of view, also
called a control or UI element.
For example, a Button
view represents a button the user can push to do
something.
A TextView
is a plain label that displays some text.
An EditText
is a box that the user can type text into.
To the upper right of the UI preview, you’ll find the Palette pane, which shows
many kinds of views available to you.
Android organizes views into containers nested inside each other.
For example, a row of buttons might be represented by a LinearLayout
containing several Button
s.
That could then be nested inside other containers to build a more complex
screen.
To the lower left of the preview, you’ll see a Component Tree pane.
This shows the hierarchy of all controls in the layout.
To add a view, drag your desired kind of view from the Palette pane to where you
want it to be in the Component Tree.
To delete a view and any views inside it, right-click it in the Component Tree
and choose Delete.
When you select a view in the preview or Component Tree, the Attributes pane on
the right shows many attributes (properties) that you can set for that view.
For controls that have text, the text
attribute sets the text initially
displayed by the view.
The id
attribute at the top sets the view’s ID that can be used in code to
manipulate the view.
Some attributes appear twice in the Attributes pane, once with a wrench icon and once without. The value of the setting with the wrench is only visible in the preview, not during testing or in the app. You almost always want to use the setting without the wrench.
3.2. Manipulating UI from Java
Usually at least part of a user interface is dynamic, changing at runtime when the user does something or when data is fetched. To make this happen, your Java code needs to be able to manipulate the UI elements.
Android exposes views to Java as objects with functions that allow you to get
information about the view or modify its attributes.
If a view has an ID set in the Attributes pane of the UI designer, you can get
an object representing it from the findViewById
function.
For example, this code gets a TextView
object corresponding to the TextView
with ID greeting
:
TextView label = findViewById(R.id.greeting);
The variable type (e.g. TextView
) must match the type of view from the UI designer.
The variable name (e.g. label
) can be anything of your choosing.
The field name after R.id.
is the view ID from the UI designer.
Since the view classes are defined in Android rather than your project, they have to be imported before you can use them from your code. Android Studio can help with this: if you tab-complete the class name as you’re typing it, the needed import statement will be automatically added to the top of the file.
Once you have a view object, you can use dot notation to call its functions and
do something with it.
For example, all views have a setVisibility
method to change whether the view
can be seen.
If the greeting label was invisible or gone, this code would make it visible
again:
label.setVisibility(View.VISIBLE);
// or pass View.INVISIBLE to make it invisible
// or View.GONE to make it gone (invisible and taking up no space)
Views that display text have a setText
method to change the text:
label.setText("Hello!");
Member functions can also be used to set handlers—code that will be run
when something happens to the view, like a button being clicked.
The syntax for handlers is a little messy, but Android Studio’s tab completion
can help you with it, as can the examples in our starter code or in lab.
For example, this attaches a click handler to a Button
variable named
goodbye
:
goodbye.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
// Change the label's text
label.setText("Goodbye.");
}
});
Or more concisely:
goodbye.setOnClickListener(v -> {
// Change the label's text
label.setText("Goodbye.");
});
3.3. Multiple Activities
Most apps have multiple different screens that are shown at different times.
To switch to a different activity, code has to launch the new activity using an Intent
.
An intent specifies what is to be done and provides any extra information needed to do
that—kind of like calling a function.
To create an intent to launch an activity, you need to specify the current context 3 and the new activity:
Intent intent = new Intent(this, OtherActivity.class);
// Don't worry about what the .class part means
Once you have an intent, you can pass it to the startActivity
function to act on it:
startActivity(intent);
By default, when a new activity is launched, the user can return to the old one
by using the back button on the device.
To make the old activity completely finish and no longer be available, you can
call finish()
after the startActivity
call.
Additional data that needs to be passed to the new activity can be placed in extras.
Intent extras are identified by a string name and can have various kinds of values.
Each intent can have many extras.
To add an extra, use a putExtra
instance method of the intent:
intent.putExtra("name", "Jane Smith");
intent.putExtra("gpa", 4.0);
To access this data from the new activity, use the getIntent
function to get
the Intent
that was used to launch the activity.
To extract extras from the intent, call the get<Type>Extra
functions, like
getStringExtra
or getDoubleExtra
.
The functions to get extras of primitive types require you to specify a default
value that will be returned in case that extra wasn’t present.
For example:
Intent intent = getIntent();
String name = intent.getStringExtra("name");
double gpa = intent.getDoubleExtra("gpa", 0.0); // 0.0 is the default
If you’d like more information, feel free to refer to Android’s
official Intent
documentation.
4. Your Goal
When you’re done with MP1, your Campus Snake 125 app will support target mode and the new area mode. In area mode, the map will show a grid of cells and highlight captured cells with green rectangles. There will be a user interface to select the game mode and set game configuration (proximity threshold for target mode, area and cell size for area mode).
MP1 is a step up from MP0, and may seem overwhelming at first. This is normal! As we always recommend: start early, take it one step at a time, get help when you need it, and you’ll be able to build amazing things.
4.1. AreaDivider
Class
You may notice after acquiring the Checkpoint 1 test suite that the project can
no longer compile.
This is because the test code refers to an AreaDivider
class that you need to
create.
So the first order of business is to define that class and the needed functions
on it.
To add a new class file in the Project view, right-click the package folder
(edu.illinois.cs.cs125.fall2019.mp
) that contains all the existing files
you’ve been working on and choose New | Java Class.
Enter the class name, AreaDivider
in this case, in the Name box and press OK.
If prompted to add the file to Git, press Add.
You must create the new file in our package in main
source set,
the one containing all the other files you’ve been working on.
If you incorrectly create it in the test
part of the project, the class may
appear to work locally but will not be usable from GameActivity
or during
grading.
To see what you need to add to this class, refer to our official Javadoc. You may find our coordinate system figure helpful.
If you prefer to work on other parts of the checkpoint before implementing these functions, you can—you just need the class and its members declared so that the test suite can compile.
4.2. Create Game Button
When the app is done, the main screen represented by MainActivity
will have several options.
We’ll start on it in this checkpoint by adding a Create Game button that takes the user to
the game setup screen, NewGameActivity
.
Open the activity_main.xml
layout resource file.
Add a Button
with ID createGame
and text "Create Game".
Then add code to the MainActivity
Java file to make pushing the button launch
NewGameActivity
.
For an example of how to attach a function as a button click handler, see the
provided NewGameActivity
code.
For an example of how to switch to a new activity, see the existing code in
MainActivity
: you need to make it switch to NewGameActivity
rather than
GameActivity
in the button click handler function.
MainActivity
should no longer immediately launch GameActivity
.
4.3. Game Setup UI
The game configuration screen will allow the user to select their desired game mode
(area or target) and set other parameters like the cell size or proximity threshold.
This screen’s layout is activity_new_game.xml
and its Java class is NewGameActivity
.
Our starter version of the layout contains a RadioGroup
with id
gameModeGroup
.
Add two RadioButton
s (from the Buttons section of the Palette) to inside
this group.
One should have ID targetModeOption
and the other should have ID
areaModeOption
.
The user will use these to pick the game mode.
Some settings only make sense for one game mode, so they shouldn’t be shown all
the time.
For example, the user shouldn’t see a setting for proximity threshold when
setting up an area mode game.
To allow showing and hiding the different game-mode-specific settings as a unit,
we’ll organize the views into containers.
Add some kind of container 4 inside the
existing LinearLayout
and give it the ID areaSettings
.
Move the areaSizeMap
control inside this new container.
Also add to this container a numeric EditText
control with ID cellSize
: this
will allow the user to set the cell size.
For target mode settings, add another container with ID targetSettings
to the
provided LinearLayout
.
Inside that new container, add another numeric text box with ID
proximityThreshold
: the user will use this to set a custom proximity
threshold.
Also feel free to add any labels or extra instructions you like.
To make the radio buttons change the containers' visibility, we need to add code
to NewGameActivity
.
In onCreate
, attach a handler that will be run when the selected radio button
in the RadioGroup
is changed:
// Suppose modeGroup is a RadioGroup variable (maybe an instance variable?)
modeGroup = findViewById(R.id.gameModeGroup);
modeGroup.setOnCheckedChangeListener((unused, checkedId) -> {
// checkedId is the R.id constant of the currently checked RadioButton
// Your code here: make only the selected mode's settings group visible
});
4.4. Game Setup Intent
Once the user has configured the game as they like by selecting the mode,
entering a cell size or proximity threshold, and panning/zooming the area map if in area mode,
they will press the Create Game button to start the game.
We attached a handler to that button that calls the createGameClicked
function in NewGameActivity
.
You need to fill out that function to populate an Intent
with the game setup
and start the game activity.
Specifically, you need to add several extras to the Intent
we initialized for
you.
The string extra named mode
specifies the game mode, either "target" or
"area".
If the game is target mode, there should be an integer extra named
proximityThreshold
specifying the proximity threshold.
If the game is area mode, there should be an integer extra named cellSize
specifying the cell size and four double extras—areaNorth
, areaEast
,
areaSouth
, areaWest
—specifying the boundaries of the selected area.
To get all of this information, you will need to call functions of the view
objects.
RadioGroup
s have a
getCheckedRadioButtonId
function that returns the R.id
constant of the selected option.
Interestingly, the getText
method of text-containing views doesn’t return just
a string, but the result’s toString
method will get just the text.
To get the integer represented by a string of text, use Java’s
Integer.parseInt
function:
// Suppose textBox is an EditText variable
String text = textBox.getText().toString();
int number = Integer.parseInt(text); // This will crash if text isn't a number
Finally, Google Maps controls like areaMap
have a function to get the visible
region (for the area boundaries) as a
LatLngBounds
object:
LatLngBounds bounds = areaMap.getProjection().getVisibleRegion().latLngBounds;
If the user didn’t finish setting up the game before pressing Create Game—that is, no radio button is selected or a needed numeric text box is empty—no intent should be launched.
4.5. Area Mode Gameplay
Now that you have the user’s game setup stored in intents and an AreaDivider
class to help with
area division and grid drawing, you can add logic to GameActivity
to make area mode games work.
First, GameActivity
needs to know the game configuration.
Add logic to onCreate
to get the intent and record the needed information in
instance variables of your design.
You will probably want to wrap our provided target mode variable setup in an if
statement, then use the other (area mode) branch to create an AreaDivider
instance to manage cell boundaries and a boolean[][]
to store whether each
cell has been visited.
Update setUpMap
to check the game mode and render the grid if the game is area mode.
This should be very easy because all the work is done by the AreaDivider
object.
If the game is target mode, markers should still be placed at target positions like in MP0.
Similarly, add a branch to updateLocation
with area mode gameplay logic:
detect cell capture and show the user’s progress on the map.
Initially any cell in the area can be captured.
Subsequent captures are only possible of the cell the user is currently in is
uncaptured and shares one side with the most recently captured cell
5.
When a cell is captured, it should be filled with a green polygon
6.
To add a polygon to a Google Maps control, pass a
PolygonOptions
instance to the map’s addPolygon
method.
As you read the PolygonOptions
method summary, look for two methods that
you’ll need: one to add vertices to the polygon and one to set the polygon’s
fill color.
To make the custom proximity threshold take effect, tweak your MP0 target mode logic in
updateLocation
to use your proximity threshold variable instead of a constant.
5. Grading
MP1 is worth 100 points total, broken down as follows:
-
20 points for the area division calculation features of
AreaDivider
-
10 points for the
renderGrid
function ofAreaDivider
-
5 points for the Create Game button in
MainActivity
-
10 points for setting up the user interface in
NewGameActivity
and making the radio buttons work -
10 points for making the Create Game button in
NewGameActivity
start a correctly configuredIntent
-
20 points for making area mode work by updating
GameActivity
-
5 points for making target mode respect the user’s proximity threshold setting
-
10 points for having no
checkstyle
violations -
10 points for submitting code that earns at least 40 points by 8 PM on your early deadline day
5.1. Test Cases
Just like on MP0, we have provided a test suite that exhaustively
tests your code.
You should not modify the test suite, but feel free to examine it to see
what it is doing with your code, especially when you’re debugging test failures.
Checkpoint1Test
is stored in the same folder as Checkpoint0Test
, under the test
part
of the src
folder hierarchy.
To run Checkpoint 1 tests, change the run configurations dropdown to Test
Checkpoint 1 and press the green run button.
You can also run a specific test function using the button in the left margin
when looking at the test suite code.
After updating grade.yaml
, the Grade run configuration that you used in MP0
will grade MP1 instead.
5.2. Submitting Your Work
Follow the instructions from the submitting portion of the CS 125 workflow instructions.
5.3. Style Points
Like in MP0, 10% of your MP1 score is from successful checkstyle
validation.
One thing checked by checkstyle
is the presence of Javadoc documentation on each function
and function parameter.
Android Studio can help with this: once you’ve written a function signature,
typing /**
(the start of a Javadoc comment) right above the function and
pressing Enter will insert any necessary @param
and @return
tags for you to
fill out.
checkstyle
also wants all function parameters to be declared final
(like we
did in MP0), which means you cannot reassign them inside the function.
6. Cliffhanger
After completing MP1 you may be thinking that it would be nice to bundle all the target mode
logic together in one place and all the area mode logic together in another
rather than having all those if statements throughout GameActivity
.
Later in lecture you’ll learn about a concept called polymorphism that will allow us to do this.
Now that we can create customized games, we’ll want some way to share or join games with other people and see ongoing games' configuration. We’ll start on that in the next checkpoint by connecting the app to a server.
7. Cheating
All submissions on all CS 125 assignments will be checked for plagiarism. You may not submit work done by anyone else, nor may you share your assignment code with others. Please review the cheating policies from the syllabus.