CS 1101.py Computer Science I
Spring 2016

Computer Science Department
The Morrissey College of Arts and Sciences
Boston College

About Staff Textbook Grading Schedule Canvas
Piazza Library Resources Labs GitHub Problem Sets
Problem Set 10: Flow

Assigned: Wednesday April 27, 2016
Due: Friday May 6, 2016
Points: 18 Points up to 24 Points

Thanks to Prof. Bill Ames for suggesting this problem.

This is a pair project. You can work with one partner if you would like to. If you would like an assist in finding a partner you can ask a course staffer or you can use the Piazza partner finding tool.

The game of Flow involves working with a 2D grid of cells. Some cells are initially empty while others contain colored circles.

       

The object of the game is to connect all of the like-colored circles leaving no empty cells. The connections formed between two like-colored circles are made up of sequences of "line pieces" of the matching color.

This problem set involves writing a Python program that "solves" a given flow grid. This is a pair problem set: you may work in pairs. Contact your TA if you need help finding a partner.

This problem set has plenty of room for extra credit. See below.

Overview

This problem is an example of a search problem: your progam will implement an algorithm that searches for an ordering of pieces in the open cells that solves the problem.

Pieces

There are 7 piece shapes:

                       

The images of the pieces that you see above are just gif images. These images are provided and managed by the harness code. In solving this problem, your code can manipulate pieces using the functions provided in the harness code. These are described below.

Of the 7 pieces, only the first 6 are playable by the program --- the circular end-pieces are fixed at the outset.

Since pieces come in 9 different colors, there are a total of 7 x 9 = 63 different pieces and 6 x 9 = 54 different playable pieces. To get an idea of the stupendous size of the search space, since there are 6 x 6 = 36 cells, there are 6336 possible configurations of pieces on the board. This number is reasonbly close to (in fact, larger than) 6432 = (26)32 = 2192. Now that's clearly a big number but it's hard to get intuition about it so here's what it is:

2192 = 6277101735386680763835789423207666416102355444464034512896

So any program that is going to sort through all sequences of 36 items, each of which can hold 63 values, well, it's going to take a while.

Fortunately, there are many factors working in our favor that cut down the search space to something actually manageable. First, there are only 54 playable pieces and there will be somewhat fewer than 36 slots --- some of them will be occupied by circles. This may reduce it to something on the order of 5430, still intractable. But there are significant physical and color constraints that greatly restrict the search space. For example, lets say there is an orange corner in the upper left cell:

Then only 3 of the 63 pieces would fit in the cell to the right of it:

       

Just this one constraint eliminates 60 * 6334 cases.

And moreover, our Flow program isn't trying to inspect all solutions, it should quit when it finds the first sequence of pieces that solves the problem. With these constraints, our search space will be down to a manageable few 10s of thousands of configurations to be tried.

Backtracking

The Flow problem can be solved using a relatively simple version of the well-known backtracking algorithm. The application of the algorithm here is not unlike the one that we discussed in solving the 8 Queens problem. (The code solving the 8 Queens problem is posted on the Schedule page.) Like the 8 Queens problem, we're concerned with placing pieces in a 2D grid. In that problem there was only 1 piece, a queen, and we needed only one queen per row. In this problem there are many pieces and every cell needs to be filled.

As in the 8 Queens problem, it's natural to represent the 2D grid of cells using a 2D list. Lets call it a board. Then one way to use backtracking to solve the board is to use a recursive function housing a pair of nested for-loops. If the loop indices are row and col, then the backtracking algorithm can proceed by finding the first open cell, trying all pieces in the cell that are compatible with the neighboring cells, then recursively trying to solve the remaining (smaller) puzzle. The recursion will introduce new copies of the loop indices, and the various copies of row, col serve as backtrack points, as they did in the 8 Queens problem.

Getting Started

You and your teammate are invited to implement your Flow program by starting with the following harness code 10.ps.harness.zip. Download this code and look it over.

The Harness Code

The harness code provides functions for working with pieces and provides support code implementing the graphical user interface. In writing your code, you can focus on the 2D board and the pieces it contains. You can think of the colorful pictures above as visualizations of the board.

The following code is provided as part of the harness code.

Basic Piece-Centric Functions

These functions provide operations on pieces, shapes and colors.

  • shapeOf : piece -> shape

  • colorOf : piece -> color

  • isEmpty : piece -> boolean

  • isEndPiece : piece -> boolean

  • sameColor : piece * piece -> boolean

  • pieceName : piece -> string

  • goesUp : piece -> boolean

    One of 4 functions providing information about the geometry of a piece. You will need to implement the three companion functions of goesUp as listed below.

Pieces and the Board

In addition to the above basic piece-centric code, functions will be required that check properties of pieces that have been tentatively placed on the board.

  • isAllowedAbove : piece * piece -> boolean

    The isAllowedAbove function determines whether or not a piece is compatible with the one above it. (See the code.) This is one part of four, the other three need to be written.

  • endPieceEntryOK : board * row * col -> boolean

    The endPieceEntryOK function determines whether or not the end piece at row col is compatible with the cells around it. (See the code.)

Utility Functions

These routines assist with piece placement and the rendering of the board in the graphical user interface.

  • placePiece : piece * board * row * col -> void

    The placePiece function puts the piece in board[row][col] and does the background work required to ensure that the GUI is consistent with the board.

  • show : board -> void

    The show function renders the board using the GUI.

Your Task

If you are using the harness code, you need to complete the following.

Basic Piece-Centric Functions

Implement the 3 companion functions of goesUp that relate to the geometry of pieces.

  • goesDown : piece -> boolean

    See the goesUp function in the harness code.

  • goesLeft : piece -> boolean

  • goesRight : piece -> boolean

Pieces and the Board

You will need to implement functions that help determine whether or not a piece that has been tentatively placed on the board is compatible with it's neighbors.
  • isAllowedLeft : piece * piece -> boolean

    The isAllowedLeft function determines whether or not the current piece is consistent with the one to its left. See the isAllowedAbove function in the harness code.

  • isAllowedRight : piece * piece -> boolean

    Likewise.

  • isAllowedBelow : piece * piece -> boolean

    Likewise.

  • checkCell : board * row * col -> boolean

    Is the current piece consistent with all of the neighbors?

  • endPieceEntriesOK : board -> boolean

    The endPieceEntriesOK function determines whether or not all of the end pieces are consistent with all of their neighbors.

Control

Referring to the GUI figures above, when the Solve button is clicked, the solve function is called. This is the main entry point to your code.

  • solve : board -> boolean

    Given the call solve(board), the solve function will be provided with a 2D board populated with pieces. The solve function should index through the board to find the first empty cell. If no empty cell is found, the board is solved. If an empty cell is found at row, col, the solve function should ask the tryAllPieces function to try all of the playable pieces in that cell. If so and the end pieces are OK then return True.

  • tryAllPieces : board * row * col -> boolean

    Given the call tryAllPieces(board, row, col), the tryAllPieces function should index through the playable pieces which are available in the global variable playablePieces. If a given piece is consistent with its neighbors and the end pieces are too then tryAllPieces should recursively try to solve the rest of the board.

Our Algorithm is Broken!

The algorithm that we are using has a logical error in it that allows for "solutions" like the following:

We don't expect you to solve this problem.

Extra Credit

Here are a few ideas.
  1. (1 Point) Count the number of pieces tried and print the result when the board is solved.

  2. (2 Points) Our simple solution tries playable pieces of colors that don't match the colors of circles on the board. They cannot possibly work. Implement the code so that it doesn't try pieces that cannot appear in a solution.

  3. (LOTS of Points) Solve the problem cited above relating to lines that do not end at circles.

  4. (4 Points) Add a scale widget to the UI, aka a slider. When the slider is all the way to the left, the program tries and shows pieces very slowly. When the slider moves to the right, it speeds up.

  5. (Lots of Points) Don't use the harness code, do it from scratch! If you want to do this, feel free to use the following gif images of all 63 pieces.

  6. (Middling Points) Develop an algorithm that solves the board more efficiently that the naive backtracking algorithm.

Created on 01-19-2016 23:10.