On this page:
Instructions
Problem 1: Wordle
5.1 Dordle
A note about randomness
Submission Instructions
Problem 2: Visitors
8.3

Assignment 5: Building a game; Visitors

Goals: To build a non-trivial Big-bang game from scratch, and practice with visitors

Instructions

Be very, very careful with naming! Again, the solution files expect your submissions to be named a certain way, so that they can define their own Examples class with which to test your code. Therefore, whenever the assignment specifies:
  • the names of classes,

  • the names and types of the fields within classes,

  • the names, types and order of the arguments to the constructor,

  • the names, types and order of arguments to methods, or

  • filenames,

...be sure that your submission uses exactly those names.

Make sure you follow the style guidelines that Bottlenose enforces. For now the most important ones are: using spaces instead of tabs, indenting by 2 characters, following the naming conventions (data type names start with a capital letter, names of fields and methods start with a lower case letter), and having spaces before curly braces.

You will submit this assignment by the deadline using the Bottlenose submission system. You may submit as many times as you wish. Be aware of the fact that close to the deadline the Bottlenose system may slow down to handle many submissions - so try to finish early.

There will be a separate submission for each problem - it makes it easier to grade each problem, and to provide you with the feedback for each problem you work on.

The submissions will be organized as follows:

Due Dates:
  • Problem 1, initial examples: Friday, Feb 18th, at 9:00pm

  • Problem 1 peer review: Saturday, Feb 19th, at 9:00pm

  • Problem 2, final game implementation: Friday Feb 25th, at 9:00pm

  • Problem 3, arithmetic visitors: Friday Feb 25th, at 9:00pm

Problem 1: Wordle

Related files:
  Starter.java  

Note: Read through this whole problem to see which parts are due when. You will submit this project in three stages over two weeks, so you should work steadily at it, but there’s no need to rush.

Implement the game Wordle. Wordle is a logic puzzle, where the player’s task is to guess a secret word that’s been generated by the game, using only words in an allowed dictionary of words. You are given two forms of feedback on your guess: the letters that are correct and in the correct place, and the letters that are correct but in the incorrect place.

For example, suppose the secret code is image

Some design suggestions:
  • You are encouraged to use generic types in this assignment, if you feel comfortable with them. They are not required, but they may be helpful. You will likely find it very helpful to implement a bunch of the common list-abstraction functions from Fundies 1 as methods on your lists here. You may find it convenient to use the following helper class (you may access its fields directly, like we do with Posns):

    class Pair<L, R> {
    L left;
    R right;
    Pair(L left, R right) { this.left = left; this.right = right; }
    }
  • Use the starter file given above for dictionaries of 4- and 5-letter words. Complete the design of the helper method in that file that takes in a String containing a bunch of words, an int indicating the word length, and an int indicating the separation length, and splits the string into a list of individual strings. For example, given the string "HELLO,WORLD/SPACE", a word length of 5 and a separation length of 1, it should produce a list containing "HELLO", "WORLD" and "SPACE" the separators themselves don’t matter, so long as they’re all the same length. You will likely want to use the two .substring methods on String for this.

  • Your game should be parameterized by the dictionary of words available, and by the number of guesses allowed before the game is over. That is, your game should at least have a constructor (assuming you’re using generic lists)

    import javalib.funworld.*;
     
    class YourWorld extends World {
    YourWorld(IList<String> dictionary, int numGuesses) { ... }
    }

    Your constructor should ensure that the dictionary is not empty, and that every word in the list has the same length.

    This implies that you cannot hardcode a guess as a class with five fields (letter1 through letter5, say), because you cannot know in advance how many letters there will be. You can assume that every word in the dictionary uses only the letters A through Z.

  • Therefore, you should represent the secret code, and each guess, as an IList<String>, where each string in the list is one letter long. How you choose to represent the set of guesses is up to you.

  • You will need to check whether two lists of strings are the same, meaning they have the same strings in the same order.

  • You will need to compute which items in a IList<String> are the same (and in the same position) as items in a second IList<String>, and somehow produce a list of results.

  • (Tricky!) You will also need to compute which letters are correct but in the wrong place. Note that as phrased here, this is not quite detailed enough a purpose statement for use in your game. (Why?) Test this one thoroughly...

  • (Hint) You may want to produce a modified list of letters, such that all the exactly-correct ones have been replaced by "_" (which is deliberately not a valid letter). You may also want to design a method that removes the first time a value appears in a list from a list of values.

  • I strongly recommend you try to solve the methods above using double-dispatch rather than via casting or via getter methods like getFirst or getRest. It’s a puzzle, but it’s worth the practice – if you completed all of Lab 4, you should have a big head-start on this problem.

  • Your graphics do not need to worry about pinholes in this assignment at all. You also do not need to render the on-screen keyboard the way Wordle does (since we’re not playing this game on smartphones).

  • I strongly recommend that you design your makeScene method to call a helper that returns a WorldImage, and do most of the interesting image composition in that helper.

  • You should let the user guess a word by typing in letters, using the onKey handler of Big Bang. You should let them backspace to delete the last-entered letter, press enter to commit to a guess, and prevent them from entering invalid words (i.e. that aren’t in the dictionary) as guesses. Note that the arguments you receive in onKey are always lowercase.

5.1 Dordle

Once you’ve implemented Wordle, you should implement Dordle. Dordle is like Wordle, but doubled: there are two secret words to guess, and entering a guess will guess it in both games simultaneously.

For what it’s worth: my implementation of Dordle is only about 20 extra lines of code, after my implementation of Wordle is complete.

A note about randomness

There are two ways to generate random numbers in Java. The easiest is to use Math.random(), which generates a double between 0 (inclusive) and 1 (exclusive). You can multiply this number by some integer to make it bigger, then coerce to an int to produce a random integer in the range you wish. However, this is not easily testable: you’ll get different random values every time.

The better way to generate random numbers is: First, import java.util.Random at the top of your file. Next, create a new Random() object, and use its nextInt(int maxVal) method, which will give you a random integer between zero (inclusive) and maxVal (exclusive).

This is known as a "pseudorandom number generator", since the numbers aren’t really random if they can be reliably repeated...

The reason this is better is because there is a second constructor, new Random(int initialSeed), which has the property that every time you create a Random with the same initial seed, it will generate the same "random" numbers in the same order every time your program runs. You should therefore design your world classes with two constructors:

Submission Instructions

Problem 2: Visitors

In a new file Visitors.java, implement the following class diagram:
      +-------------------+
      | IArith            |
      +-------------------+
      +-------------------+
          /_\      /_\
           |        |--------------------------------------------------------------
           |        |                                                             |
+-------------+  +------------------------------+           +-----------------------------------------+
| Const       |  | UnaryFormula                 |           |              BinaryFormula              |
+-------------+  +------------------------------+           +-----------------------------------------+
| double num  |  | Function<Double,Double> func |           | BiFunction<Double, Double, Double> func |
+-------------+  | String name                  |           | String name                             |
                 | IArith child                 |           | IArith left                             |
                 +------------------------------+           | IArith right                            |
                                                            +-----------------------------------------+

Specifically, the above represents an arithmetic expression. The Function and BiFunction in UnaryFormula and BinaryFormula respectively denote the arithmetic operation to be applied to their respective operands (child and left,right). In class, we defined these interfaces as IFunc and IFunc2, but they’re actually predefined for you in Java. To use them, you will need to write import java.util.function.*; at the top of your file among the other import statements. As in class, these interfaces require that you implement a method named apply, whose parameters are (in this problem) all Doubles.

You must support 4 binary formulas (named "plus", "minus", "mul" and "div" representing addition, subtraction, multiplication and division respectively), and 2 unary formulas (named "neg" and "sqr" representing negation and squaring respectively).