diff --git a/decomposition-and-conditionals-homework/.gitignore b/decomposition-and-conditionals-homework/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9dbe7d11c1294e17b42776f8336b4394281d223c
--- /dev/null
+++ b/decomposition-and-conditionals-homework/.gitignore
@@ -0,0 +1,54 @@
+# Mac file finder metadata
+.DS_Store
+# Windows file metadata
+._*
+# Thumbnail image caches
+Thumbs.db
+ethumbs.db
+# MS Office temporary file
+~*
+# Emacs backup file
+*~
+
+# Common
+[Bb]in/
+[Bb]uild/
+[Oo]bj/
+[Oo]ut/
+[Tt]mp/
+[Xx]86/
+[Ii][Aa]32/
+[Xx]64/
+[Xx]86_64/
+[Xx]86-64/
+[Aa]rm
+[Aa]32
+[Tt]32
+[Aa]64
+*.tmp
+*.bak
+*.bk
+*.swp
+
+# Java files
+*.class
+javadoc/
+
+# Maven
+target/
+
+# JetBrains (IntelliJ IDEA, PyCharm, etc) files
+.idea/
+cmake-build-*/
+*.iml
+*.iws
+*.ipr
+
+# Eclipse files
+.settings/
+.project
+.classpath
+.buildpath
+.loadpath
+.factorypath
+local.properties
diff --git a/decomposition-and-conditionals-homework/README.md b/decomposition-and-conditionals-homework/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..81e10a03547da4be7f691a0726b99527562889fd
--- /dev/null
+++ b/decomposition-and-conditionals-homework/README.md
@@ -0,0 +1,57 @@
+# Decomposition and Conditionals Homework
+
+**Collaboration Policy**: No collaboration is permitted on this assignment.
+
+**Notes and References**: This is an open-book, open-note assignment.
+
+## Overview
+
+Proper problem and solution decomposition is an important software engineering
+technique for writing readable and maintainable code. In this assignment you
+will practice decomposition to solve two small computational problems whose
+solutions may not be obvious from their descriptions. You will also practice
+writing code that uses an enumerated type, string concatenation, and exception
+handlers.
+
+## Learning Goals
+
+By completing this assignment, you should be able to:
+
+1.  Decompose a solution to a problem into multiple methods in a Java program,
+
+2.  Use an enumerated type to represent values from a limited space,
+
+3.  Use string concatenation to format text output, and
+
+4.  Use exception handlers to catch and handle input errors.
+
+## Instructions
+
+This assignment is to be completed individually; **no collaboration is
+permitted**.
+
+### Clone the Starter Code and Set Up Eclipse
+
+1.  In your virtual machine, create a new folder in your `soft160-homework`
+    folder named `homework5`.
+
+2.  Change into the `homework5` directory.
+
+3.  Clone
+    [the starter code](https://git.unl.edu/soft-core/soft-160/decomposition-and-conditionals-homework).
+
+4.  Change into the `decomposition-and-conditionals-homework` directory.  Verify that you are in
+    the correct directory by running the command `pwd`.
+
+5.  Remove the Git folder that links this code to the instructors' repository by
+    running the command `chmod -R u+w .git` (which gives you permission to
+    remove the folder without extra confirmation prompts) and then the command
+    `rm -rI .git`.  Answer `yes` when asked to confirm the deletion.
+
+6.  Verify the Git folder has been removed by running `ls -a` and making sure
+    that you see folders with names like `src` and `documentation`, but that `.git`
+    is **not** one of the items listed.
+
+7.  Start the Eclipse IDE.
+
+8.  Import the starter code into Eclipse as a Maven project.
diff --git a/decomposition-and-conditionals-homework/documentation/.gitkeep b/decomposition-and-conditionals-homework/documentation/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/decomposition-and-conditionals-homework/pom.xml b/decomposition-and-conditionals-homework/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fd501b2c7216b387114834beb2899eaccd72fa2a
--- /dev/null
+++ b/decomposition-and-conditionals-homework/pom.xml
@@ -0,0 +1,36 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>edu.unl.cse.soft160.decomposition_and_conditionals</groupId>
+  <artifactId>decomposition_and_conditionals</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>decomposition_and_conditionals</name>
+  <url>http://maven.apache.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperator.java b/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperator.java
new file mode 100755
index 0000000000000000000000000000000000000000..4e0b5cf51d0f9238eb9ba3053a1f030ec1f345af
--- /dev/null
+++ b/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperator.java
@@ -0,0 +1,11 @@
+package edu.unl.cse.soft160.decomposition_and_conditionals;
+
+public class BooleanOperator {
+    private static enum TruthValue {
+        TRUE, FALSE, UNKNOWN,
+    }
+
+    public static void main(String... arguments) {
+        // [write code here]
+    }
+}
diff --git a/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInput.java b/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInput.java
new file mode 100755
index 0000000000000000000000000000000000000000..42892798acc12a1cb1e14a81e4e1c3d065c8f363
--- /dev/null
+++ b/decomposition-and-conditionals-homework/src/main/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInput.java
@@ -0,0 +1,15 @@
+package edu.unl.cse.soft160.decomposition_and_conditionals;
+
+public class ProcessInput {
+    private static enum State {
+        EMPTY, DECIMAL, NUMERIC,
+    }
+    
+    private static enum Classification {
+        FLOAT, INTEGER, NAN,
+    }
+    
+    public static void main(String... arguments) {
+        // [write code here]    
+    }
+}
diff --git a/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperatorTest.java b/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8aadc8e6eba3f8f68db26165d5bbc0330ceb96e
--- /dev/null
+++ b/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/BooleanOperatorTest.java
@@ -0,0 +1,107 @@
+package edu.unl.cse.soft160.decomposition_and_conditionals;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+public class BooleanOperatorTest {
+
+    protected static String assemble(String... lines) {
+        return String.join("\n", lines) + "\n";
+    }
+
+    protected static String runMain(String... inputs) {
+        InputStream in = System.in;
+        PrintStream out = System.out;
+        System.setIn(new ByteArrayInputStream(assemble(inputs).getBytes()));
+        ByteArrayOutputStream collector = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(collector));
+        BooleanOperator.main();
+        System.setIn(in);
+        System.setOut(out);
+        return collector.toString();
+    }
+
+    @Test
+    public void testTrueTrue() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of TRUE and TRUE is FALSE."),
+                runMain("TRUE", "TRUE"));
+    }
+
+    @Test
+    public void testTrueFalse() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of TRUE and FALSE is FALSE."),
+                runMain("TRUE", "FALSE"));
+    }
+
+    @Test
+    public void testFalseTrue() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of FALSE and TRUE is FALSE."),
+                runMain("FALSE", "TRUE"));
+    }
+
+    @Test
+    public void testFalseFalse() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of FALSE and FALSE is TRUE."),
+                runMain("FALSE", "FALSE"));
+    }
+
+    @Test
+    public void testFalseUnknown() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of FALSE and UNKNOWN is UNKNOWN."),
+                runMain("FALSE", "UNKNOWN"));
+    }
+
+    @Test
+    public void testUnknownFalse() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of UNKNOWN and FALSE is UNKNOWN."),
+                runMain("UNKNOWN", "FALSE"));
+    }
+
+    @Test
+    public void testUnknownUnknown() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of UNKNOWN and UNKNOWN is UNKNOWN."),
+                runMain("UNKNOWN", "UNKNOWN"));
+    }
+
+    @Test
+    public void testTrueUnknown() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of TRUE and UNKNOWN is FALSE."),
+                runMain("TRUE", "UNKNOWN"));
+    }
+
+    @Test
+    public void testUnknownTrue() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: The NOR value of UNKNOWN and TRUE is FALSE."),
+                runMain("UNKNOWN", "TRUE"));
+    }
+
+    @Test
+    public void testExceptionInOp1() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: Truth value must be one of TRUE, FALSE, or UNKNOWN."),
+                runMain("FOO", "TRUE"));
+    }
+
+    @Test
+    public void testExceptionInOp2() {
+        assertEquals(assemble(
+                "Enter truth value of operand 1: Enter truth value of operand 2: Truth value must be one of TRUE, FALSE, or UNKNOWN."),
+                runMain("TRUE", "BAR"));
+    }
+
+}
diff --git a/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInputTest.java b/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInputTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..443be183205bc414fa912917d54c0b19cc183697
--- /dev/null
+++ b/decomposition-and-conditionals-homework/src/test/java/edu/unl/cse/soft160/decomposition_and_conditionals/ProcessInputTest.java
@@ -0,0 +1,365 @@
+package edu.unl.cse.soft160.decomposition_and_conditionals;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.junit.Test;
+
+public class ProcessInputTest {
+
+    protected static String assemble(String... lines) {
+        return String.join("\n", lines) + "\n";
+    }
+
+    protected static String runMain(String... inputs) {
+        InputStream in = System.in;
+        PrintStream out = System.out;
+        System.setIn(new ByteArrayInputStream(assemble(inputs).getBytes()));
+        ByteArrayOutputStream collector = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(collector));
+        ProcessInput.main();
+        System.setIn(in);
+        System.setOut(out);
+        return collector.toString();
+    }
+
+    @Test
+    public void testEmptyL() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: NAN"),
+                runMain("empty","L"));
+    }
+
+    @Test
+    public void testEmpty0() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","0"));
+    }
+    
+    @Test
+    public void testEmpty1() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","1"));
+    }
+    
+    @Test
+    public void testEmpty2() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","2"));
+    }
+    
+    @Test
+    public void testEmpty3() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","3"));
+    }
+    
+    @Test
+    public void testEmpty4() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","4"));
+    }
+    
+    @Test
+    public void testEmpty5() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","5"));
+    }
+    
+    @Test
+    public void testEmpty6() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","6"));
+    }
+    
+    @Test
+    public void testEmpty7() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","7"));
+    }
+    
+    @Test
+    public void testEmpty8() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","8"));
+    }
+    
+    @Test
+    public void testEmpty9() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("empty","9"));
+    }
+    
+    @Test
+    public void testEmptyPeriod() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("empty","."));
+    }
+    
+    @Test
+    public void testEmptyF() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: NAN"),
+                runMain("empty","f"));
+    }
+    
+    @Test
+    public void testEmptyD() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: NAN"),
+                runMain("empty","d"));
+    }
+    
+    @Test
+    public void testDecimalL() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: NAN"),
+                runMain("decimal","L"));
+    }
+    
+    @Test
+    public void testDecimal0() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","0"));
+    }
+    
+    @Test
+    public void testDecimal1() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","1"));
+    }
+    
+    @Test
+    public void testDecimal2() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","2"));
+    }
+    
+    @Test
+    public void testDecimal3() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","3"));
+    }
+    
+    @Test
+    public void testDecimal4() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","4"));
+    }
+    
+    @Test
+    public void testDecimal5() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","5"));
+    }
+    
+    @Test
+    public void testDecimal6() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","6"));
+    }
+    
+    @Test
+    public void testDecimal7() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","7"));
+    }
+    
+    @Test
+    public void testDecimal8() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","8"));
+    }
+    
+    @Test
+    public void testDecimal9() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","9"));
+    }
+    
+    @Test
+    public void testDecimalPeriod() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: NAN"),
+                runMain("decimal","."));
+    }
+    
+    @Test
+    public void testDecimalF() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","f"));
+    }
+    
+    @Test
+    public void testDecimalD() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("decimal","d"));
+    }
+    
+    @Test
+    public void testNumericL() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","L"));
+    }
+    
+    @Test
+    public void testNumeric0() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","0"));
+    }
+    
+    @Test
+    public void testNumeric1() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","1"));
+    }
+    
+    @Test
+    public void testNumeric2() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","2"));
+    }
+    
+    @Test
+    public void testNumeric3() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","3"));
+    }
+    
+    @Test
+    public void testNumeric4() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","4"));
+    }
+    
+    @Test
+    public void testNumeric5() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","5"));
+    }
+    
+    @Test
+    public void testNumeric6() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","6"));
+    }
+    
+    @Test
+    public void testNumeric7() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","7"));
+    }
+    
+    @Test
+    public void testNumeric8() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","8"));
+    }
+    
+    @Test
+    public void testNumeric9() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: INTEGER"),
+                runMain("numeric","9"));
+    }
+    
+    @Test
+    public void testNumericPeriod() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("numeric","."));
+    }
+    
+    @Test
+    public void testNumericF() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("numeric","f"));
+    }
+    
+    @Test
+    public void testNumericD() {
+        assertEquals(
+                assemble("Enter the current state: Enter the next character: "
+                        + "Classification: FLOAT"),
+                runMain("numeric","d"));
+    }
+}
diff --git a/sort_three_pairs/.gitignore b/sort_three_pairs/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f35f60a357424aa265b3a6c1933c26af537c57ad
--- /dev/null
+++ b/sort_three_pairs/.gitignore
@@ -0,0 +1,62 @@
+# Mac file finder metadata
+.DS_Store
+# Windows file metadata
+._*
+# Thumbnail image caches
+Thumbs.db
+ethumbs.db
+# MS Office temporary file
+~*
+# Emacs backup file
+*~
+
+# Common
+[Bb]in/
+[Bb]uild/
+[Oo]bj/
+[Oo]ut/
+[Tt]mp/
+[Xx]86/
+[Ii][Aa]32/
+[Xx]64/
+[Xx]86_64/
+[Xx]86-64/
+[Aa]rm
+[Aa]32
+[Tt]32
+[Aa]64
+*.tmp
+*.bak
+*.bk
+*.swp
+
+# Java files
+*.class
+javadoc/
+
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# JetBrains (IntelliJ IDEA, PyCharm, etc) files
+.idea/
+cmake-build-*/
+*.iml
+*.iws
+*.ipr
+
+# Eclipse files
+.settings/
+.project
+.classpath
+.buildpath
+.loadpath
+.factorypath
+local.properties
diff --git a/sort_three_pairs/pom.xml b/sort_three_pairs/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0766594570da21d5d469f241f0345892e29077ae
--- /dev/null
+++ b/sort_three_pairs/pom.xml
@@ -0,0 +1,36 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>edu.unl.cse.soft160.sort_three_pairs</groupId>
+    <artifactId>sort_three_pairs</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0-SNAPSHOT</version>
+    <name>sort_three_pairs</name>
+    <url>http://maven.apache.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/sort_three_pairs/src/main/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairs.java b/sort_three_pairs/src/main/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairs.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e56ae3588ab96dbed7c23a2daddc41b6c223c99
--- /dev/null
+++ b/sort_three_pairs/src/main/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairs.java
@@ -0,0 +1,77 @@
+package edu.unl.cse.soft160.sort_three_pairs;
+
+public class SortThreePairs {
+    public static double readArgument(int position, String... arguments) {
+        return Double.parseDouble(arguments[position]);
+    }
+
+    public static String formatPair(double x, double y) {
+        return "(" + x + ", " + y + ")";
+    }
+
+    public static int getPositionOfMinimum(double x0, double y0, double x1, double y1, double x2, double y2) {
+        int result = 0;
+        double xmin = x0;
+        double ymin = y0;
+        if (x1 < xmin || x1 == xmin && y1 < ymin) {
+            result = result + 1;
+            xmin = x1;
+            ymin = y1;
+        }
+        if (x2 < xmin || x2 == xmin && y2 < ymin) {
+            result = result + 1;
+            xmin = x2;
+            ymin = y2;
+        }
+        return result;
+    }
+
+    public static void main(String... arguments) {
+        if (arguments.length != 6) {
+            System.err.println("Sorts three points by their x coordinates, breaking ties using y coordinates.");
+            System.err.println(
+                    "Usage: java edu.unl.cse.soft160.sort_three_pairs.SortThreePairs [X0] [Y0] [X1] [Y1] [X2] [Y2]");
+            System.exit(1);
+        }
+        double x0 = readArgument(0, arguments);
+        double y0 = readArgument(1, arguments);
+        double x1 = readArgument(2, arguments);
+        double y1 = readArgument(3, arguments);
+        double x2 = readArgument(4, arguments);
+        double y2 = readArgument(5, arguments);
+        double x3 = x0;
+        double y3 = y0;
+        // move the smallest pair to (x0, y0)
+        int firstPositionOfMinimum = getPositionOfMinimum(x0, y0, x1, y1, x2, y2);
+        switch (firstPositionOfMinimum) {
+        case 2:
+            x3 = x1;
+            x1 = x2;
+            x2 = x3;
+            y3 = y1;
+            y1 = y2;
+            y2 = y3;
+        case 1:
+            x3 = x0;
+            x0 = x1;
+            x1 = x3;
+            y3 = y0;
+            y0 = y1;
+            y1 = y3;
+        }
+        // move the second-smallest pair to (x1, y1)
+        int secondPositionOfMinimum = getPositionOfMinimum(Double.MAX_VALUE, Double.MAX_VALUE, x1, y1, x2, y2);
+        switch (secondPositionOfMinimum) {
+        case 2:
+            x3 = x1;
+            x1 = x2;
+            x2 = x3;
+            y3 = y1;
+            y1 = y2;
+            y2 = y3;
+        }
+        System.out.println(formatPair(x1, y1));
+        System.out.println(formatPair(x2, y2));
+        System.out.println(formatPair(x3, y3));
+    }
+}
diff --git a/sort_three_pairs/src/test/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairsTest.java b/sort_three_pairs/src/test/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairsTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c425e59dc97b1aa6d0b6e50e5e1bd57b9532b71
--- /dev/null
+++ b/sort_three_pairs/src/test/java/edu/unl/cse/soft160/sort_three_pairs/SortThreePairsTest.java
@@ -0,0 +1,167 @@
+package edu.unl.cse.soft160.sort_three_pairs;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.io.PrintStream;
+import java.security.Permission;
+import java.io.ByteArrayOutputStream;
+
+import static org.junit.Assert.*;
+
+public class SortThreePairsTest {
+    protected static String assemble(String... lines) {
+        return String.join("\n", lines) + "\n";
+    }
+
+    protected static String runMain(String... arguments) {
+        InputStream in = System.in;
+        PrintStream out = System.out;
+        System.setIn(new ByteArrayInputStream("".getBytes()));
+        ByteArrayOutputStream collector = new ByteArrayOutputStream();
+        System.setOut(new PrintStream(collector));
+        SortThreePairs.main(arguments);
+        System.setIn(in);
+        System.setOut(out);
+        return collector.toString();
+    }
+
+    @SuppressWarnings("serial")
+    protected static class ExitException extends SecurityException {
+        public final int status;
+
+        public ExitException(int status) {
+            super("Exit with status " + status);
+            this.status = status;
+        }
+    }
+
+    private static class TestingSecurityManager extends SecurityManager {
+        @Override
+        public void checkPermission(Permission perm) {
+        }
+
+        @Override
+        public void checkPermission(Permission perm, Object context) {
+        }
+
+        @Override
+        public void checkExit(int status) {
+            super.checkExit(status);
+            throw new ExitException(status);
+        }
+    }
+
+    @Before
+    public void setUp() throws SecurityException {
+        System.setSecurityManager(new TestingSecurityManager());
+    }
+
+    @After
+    public void tearDown() throws SecurityException {
+        System.setSecurityManager(null);
+    }
+
+    protected static String runMainForError(int expectedStatus, String... arguments) {
+        InputStream in = System.in;
+        PrintStream err = System.err;
+        System.setIn(new ByteArrayInputStream("".getBytes()));
+        ByteArrayOutputStream collector = new ByteArrayOutputStream();
+        System.setErr(new PrintStream(collector));
+        boolean exited = false;
+        try {
+            SortThreePairs.main(arguments);
+        } catch (ExitException expected) {
+            assertEquals(expectedStatus, expected.status);
+            exited = true;
+        } finally {
+            System.setIn(in);
+            System.setErr(err);
+        }
+        assertTrue(exited);
+        return collector.toString();
+    }
+
+    @Test
+    public void testZeros() {
+        assertEquals(assemble("(0.0, 0.0)", "(0.0, 0.0)", "(0.0, 0.0)"),
+                runMain("0", "0", "0", "0", "0", "0"));
+    }
+
+    @Test
+    public void testAlreadySorted() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("0", "0", "1", "1", "2", "2"));
+    }
+
+    @Test
+    public void testReversed() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("2", "2", "1", "1", "0", "0"));
+    }
+
+    @Test
+    public void testJumbled() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("1", "1", "2", "2", "0", "0"));
+    }
+
+    @Test
+    public void testTieBreaking() {
+        assertEquals(assemble("(0.0, 1.0)", "(1.0, 2.0)", "(1.0, 3.0)"),
+                runMain("1", "3", "0", "1", "1", "2"));
+    }
+
+    @Test
+    public void testMultipleTieBreaking() {
+        assertEquals(assemble("(0.0, 0.0)", "(0.0, 1.0)", "(0.0, 3.0)"),
+                runMain("0", "1", "0", "0", "0", "3"));
+    }
+
+    @Test
+    public void testDuplicates() {
+        assertEquals(assemble("(1.0, 1.0)", "(1.0, 1.0)", "(1.0, 2.0)"),
+                runMain("1", "1", "1", "2", "1", "1"));
+    }
+
+    @Test
+    public void testPermutation1() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("0", "0", "2", "2", "1", "1"));
+    }
+
+    @Test
+    public void testPermutation2() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("2", "2", "0", "0", "1", "1"));
+    }
+
+    @Test
+    public void testPermutation3() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("2", "2", "1", "1", "0", "0"));
+    }
+
+    @Test
+    public void testPermutation16() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("1", "1", "2", "2", "0", "0"));
+    }
+
+    @Test
+    public void testPermutation20() {
+        assertEquals(assemble("(0.0, 0.0)", "(1.0, 1.0)", "(2.0, 2.0)"),
+                runMain("1", "1", "0", "0", "2", "2"));
+    }
+
+    @Test
+    public void testTooFewArguments() {
+        assertEquals(
+                assemble("Sorts three points by their x coordinates, breaking ties using y coordinates.",
+                        "Usage: java edu.unl.cse.soft160.sort_three_pairs.SortThreePairs [X0] [Y0] [X1] [Y1] [X2] [Y2]"),
+                runMainForError(1, "0", "0", "1", "1", "2", "2", "3"));
+    }
+}
diff --git a/tetris/.gitignore b/tetris/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f35f60a357424aa265b3a6c1933c26af537c57ad
--- /dev/null
+++ b/tetris/.gitignore
@@ -0,0 +1,62 @@
+# Mac file finder metadata
+.DS_Store
+# Windows file metadata
+._*
+# Thumbnail image caches
+Thumbs.db
+ethumbs.db
+# MS Office temporary file
+~*
+# Emacs backup file
+*~
+
+# Common
+[Bb]in/
+[Bb]uild/
+[Oo]bj/
+[Oo]ut/
+[Tt]mp/
+[Xx]86/
+[Ii][Aa]32/
+[Xx]64/
+[Xx]86_64/
+[Xx]86-64/
+[Aa]rm
+[Aa]32
+[Tt]32
+[Aa]64
+*.tmp
+*.bak
+*.bk
+*.swp
+
+# Java files
+*.class
+javadoc/
+
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+
+# JetBrains (IntelliJ IDEA, PyCharm, etc) files
+.idea/
+cmake-build-*/
+*.iml
+*.iws
+*.ipr
+
+# Eclipse files
+.settings/
+.project
+.classpath
+.buildpath
+.loadpath
+.factorypath
+local.properties
diff --git a/tetris/logs/tetris.log b/tetris/logs/tetris.log
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tetris/pom.xml b/tetris/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..47dd09f2e1121ba106b69cdd31adb89affab50de
--- /dev/null
+++ b/tetris/pom.xml
@@ -0,0 +1,36 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>edu.unl.cse.soft160.tetris</groupId>
+  <artifactId>tetris</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>tetris</name>
+  <url>http://maven.apache.org</url>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/tetris/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java b/tetris/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java
new file mode 100644
index 0000000000000000000000000000000000000000..4190fb10183abb1a95f915cf9662b7e39134dfbe
--- /dev/null
+++ b/tetris/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java
@@ -0,0 +1,354 @@
+package edu.unl.cse.soft160.tetris;
+
+import java.util.Collections;
+import java.util.ArrayList;
+
+import java.awt.Font;
+import java.awt.Color;
+import java.awt.Graphics;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+
+public class Tetris extends JFrame {
+    private static final long serialVersionUID = -6851893161385783635L;
+
+    protected static void log(String message) {
+        try {
+            Files.write(Paths.get("logs/tetris.log"), (message + "\n").getBytes(), StandardOpenOption.CREATE,
+                    StandardOpenOption.APPEND);
+        } catch (IOException exception) {
+            exception.printStackTrace();
+        }
+    }
+
+    protected void logFailedMove(String moveName) {
+        log("Attempted move failed: " + moveName + " on shape " + shape + " (\"" + SHAPE_NAMES[shape] + "\")");
+    }
+
+    protected static int mod(int value, int modulus) {
+        int result = value % modulus;
+        return result < 0 ? result + modulus : result;
+    }
+
+    protected static final int DEFAULT_SQUARE_SIZE = 60; // pixels
+
+    protected static final Font TEXT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, DEFAULT_SQUARE_SIZE / 2);
+    protected static final Color TEXT_COLOR = new Color(255, 255, 255, 127);
+
+    protected static final String ROWS_CLEARED = "Rows cleared";
+    protected static final String FINAL_SCORE = "Final score";
+    protected static final String SCORE_SEPARATOR = ": ";
+
+    protected static final int ROTATE_LEFT = KeyEvent.VK_DOWN;
+    protected static final int ROTATE_RIGHT = KeyEvent.VK_UP;
+    protected static final int SHIFT_LEFT = KeyEvent.VK_LEFT;
+    protected static final int SHIFT_RIGHT = KeyEvent.VK_RIGHT;
+    protected static final int DROP = KeyEvent.VK_SPACE;
+    protected static final int PAUSE = KeyEvent.VK_P;
+    protected static final int NEW_GAME = KeyEvent.VK_N;
+
+    protected static final int WIDTH = 7; // squares
+    protected static final int HEIGHT = 11; // squares
+    protected static final int START_Y = 1; // squares from top
+
+    protected static final Color EMPTY = new Color(0, 0, 0);
+    protected static final Color[] COLORS = { new Color(0, 0, 204), new Color(142, 0, 204), new Color(204, 204, 0),
+            new Color(204, 0, 204), new Color(0, 204, 204), new Color(0, 204, 0), new Color(204, 0, 0), };
+
+    protected static final String[] SHAPE_NAMES = { "O", "L", "J", "S", "Z", "T", "I", };
+    protected static final int[] SHAPE_WIDTHS = { 2, 3, 3, 3, 3, 3, 4, };
+    protected static final int[][][][] SHAPES = {
+            { // O
+                    { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, },
+            { // L
+                    { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 2, 1 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 } },
+                    { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 2, -1 }, { 1, 0 }, { 1, 1 } }, },
+            { // J
+                    { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 } }, { { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } },
+                    { { 2, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, },
+            { // S
+                    { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 } }, },
+            { // Z
+                    { { 1, 0 }, { 2, 0 }, { 0, 1 }, { 1, 1 } }, { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, },
+            { // T
+                    { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 1, 1 }, { 2, 1 }, { 1, 2 } },
+                    { { 0, 1 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, },
+            { // I
+                    { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 } }, { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 } }, }, };
+
+    protected Color[][] board;
+    protected int score;
+
+    protected boolean isOccupied(int x, int y) {
+        return x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT || board[x][y] != EMPTY;
+    }
+
+    protected void emptyBoard() {
+        board = new Color[WIDTH][HEIGHT];
+        for (int x = 0; x < WIDTH; ++x) {
+            for (int y = 0; y < HEIGHT; ++y) {
+                board[x][y] = EMPTY;
+            }
+        }
+    }
+
+    protected void clearRows() {
+        int fallTo = HEIGHT - 1;
+        for (int y = HEIGHT; y-- > 0;) {
+            boolean full = true;
+            for (int x = 0; x < WIDTH; ++x) {
+                if (board[x][y] == EMPTY) {
+                    full = false;
+                    break;
+                }
+            }
+            if (full) {
+                ++score;
+            } else {
+                for (int x = 0; x < WIDTH; ++x) {
+                    board[x][fallTo] = board[x][y];
+                }
+                --fallTo;
+            }
+        }
+        for (int x = 0; x < WIDTH; ++x) {
+            for (int y = fallTo; y >= 0; --y) {
+                board[x][y] = EMPTY;
+            }
+        }
+    }
+
+    protected long getDelay() {
+        return 20000 / (score + 50);
+    }
+
+    protected ArrayList<Integer> currentShuffle = new ArrayList<Integer>();
+    protected int currentShuffleIndex;
+
+    protected int nextShape() {
+        if (currentShuffleIndex >= currentShuffle.size()) {
+            currentShuffleIndex = 0;
+            currentShuffle.clear();
+            for (int i = 0; i < SHAPES.length; ++i) {
+                currentShuffle.add(i);
+            }
+            Collections.shuffle(currentShuffle);
+        }
+        return currentShuffle.get(currentShuffleIndex++);
+    }
+
+    protected int shape;
+    protected int orientation;
+    protected int x;
+    protected int y;
+
+    protected void createShape() {
+        shape = nextShape();
+        orientation = 0;
+        x = (WIDTH - SHAPE_WIDTHS[shape]) / 2;
+        y = START_Y;
+    }
+
+    protected int[][] getFallingSquares() {
+        int[][] result = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
+        int[][][] entry = SHAPES[shape];
+        int[][] offsets = entry[mod(orientation, entry.length)];
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 2; ++j) {
+                result[i][j] = (j == 0 ? x : y) + offsets[i][j];
+            }
+        }
+        return result;
+    }
+
+    protected boolean shapeCollides() {
+        for (int[] square : getFallingSquares()) {
+            if (isOccupied(square[0], square[1])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected void drawShape(Color color) {
+        for (int[] square : getFallingSquares()) {
+            board[square[0]][square[1]] = color;
+        }
+    }
+
+    protected void placeShape() {
+        drawShape(COLORS[shape]);
+        JPanel panel = (JPanel)getContentPane();
+        panel.paintImmediately(0, 0, panel.getWidth(), panel.getHeight());
+    }
+
+    protected void unplaceShape() {
+        drawShape(EMPTY);
+    }
+
+    protected boolean playing = false;
+    protected boolean paused = false;
+
+    protected boolean isPlaying() {
+        return playing && !paused;
+    }
+
+    protected void togglePause() {
+        paused = !paused;
+    }
+
+    protected void beginGame() {
+        emptyBoard();
+        createShape();
+        placeShape();
+        score = 0;
+        playing = true;
+        paused = false;
+    }
+
+    protected void endGame() {
+        playing = false;
+    }
+
+    protected boolean maybeAdjustShape(int rotation, int dx, int dy) {
+        if (!isPlaying()) {
+            return true;
+        }
+        unplaceShape();
+        orientation += rotation;
+        x += dx;
+        y += dy;
+        boolean collides = shapeCollides();
+        if (collides) {
+            orientation -= rotation;
+            x -= dx;
+            y -= dy;
+        }
+        placeShape();
+        return collides;
+    }
+
+    protected boolean sinkShape() {
+        if (!isPlaying()) {
+            return true;
+        }
+        if (maybeAdjustShape(0, 0, 1)) {
+            clearRows();
+            createShape();
+            if (shapeCollides()) {
+                endGame();
+            } else {
+                placeShape();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    protected void dropShape() {
+        while (!sinkShape())
+            ;
+    }
+
+    protected void rotateShapeLeft() {
+        maybeAdjustShape(-1, 0, 0);
+    }
+
+    protected void rotateShapeRight() {
+        maybeAdjustShape(1, 0, 0);
+    }
+
+    protected void shiftShapeLeft() {
+        maybeAdjustShape(0, -1, 0);
+    }
+
+    protected void shiftShapeRight() {
+        maybeAdjustShape(0, 1, 0);
+    }
+
+    public Tetris(int width, int height) {
+        super("Tetris");
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        setSize(width, height);
+        setContentPane(new JPanel() {
+            @Override
+            public void paint(Graphics graphics) {
+                super.paint(graphics);
+                int xScale = getWidth() / Tetris.WIDTH;
+                int yScale = getHeight() / Tetris.HEIGHT;
+                for (int x = 0; x < Tetris.WIDTH; ++x) {
+                    for (int y = 0; y < Tetris.HEIGHT; ++y) {
+                        graphics.setColor(board[x][y]);
+                        graphics.fillRect(xScale * x, yScale * y, xScale, yScale);
+                    }
+                }
+                String scoreString = (playing ? ROWS_CLEARED : FINAL_SCORE) + SCORE_SEPARATOR + String.valueOf(score);
+                graphics.setFont(TEXT_FONT);
+                graphics.setColor(TEXT_COLOR);
+                graphics.drawString(scoreString, (getWidth() - graphics.getFontMetrics().stringWidth(scoreString)) / 2,
+                        (getHeight() / 2 - graphics.getFontMetrics().getHeight()) / 2);
+            }
+        });
+        addKeyListener(new KeyListener() {
+            public void keyPressed(KeyEvent event) {
+                switch (event.getKeyCode()) {
+                case ROTATE_LEFT:
+                    rotateShapeLeft();
+                    break;
+                case ROTATE_RIGHT:
+                    rotateShapeRight();
+                    break;
+                case SHIFT_LEFT:
+                    shiftShapeLeft();
+                    break;
+                case SHIFT_RIGHT:
+                    shiftShapeRight();
+                    break;
+                case DROP:
+                    dropShape();
+                    break;
+                case PAUSE:
+                    togglePause();
+                    break;
+                case NEW_GAME:
+                    if (playing) {
+                        endGame();
+                    }
+                    beginGame();
+                    break;
+                }
+            }
+
+            public void keyReleased(KeyEvent e) {
+            }
+
+            public void keyTyped(KeyEvent e) {
+            }
+        });
+        new Thread() {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        Thread.sleep(getDelay());
+                        sinkShape();
+                    } catch (InterruptedException ignored) {
+                    }
+                }
+            }
+        }.start();
+    }
+
+    public static void main(String... arguments) {
+        Tetris tetris = new Tetris(WIDTH * DEFAULT_SQUARE_SIZE, HEIGHT * DEFAULT_SQUARE_SIZE);
+        tetris.setVisible(true);
+        tetris.beginGame();
+    }
+}