Skip to content

Project 1: FlexDoku

sudoku board
credit: printable-sudokus

Introduction

FlexDoku is a new grid-based game where you place letters in such a way that no letter repeats in any row, column, or sub-block. In other words, Sudoku, but with letters and a flexible grid. Sounds simple, right? Behind the scenes, a smart system of rules and logic is quietly working to make sure everything flows smoothly and stays fun. Let's dive into the functions that make FlexDoku work and see how you can approach them logically.


Part 1: utilities.py

Board Operations and Utility Functions


In this first part, we’ll focus on understanding how the game board works. How do we set it up, display it, and access its values? Let’s explore.

check_valid_size(size)

Before we can start playing, we need to ensure that the board size is valid. This function checks if the provided board size is a perfect square. Why? Because FlexDoku boards must always be square (e.g., 4x4, 9x9). If the size isn’t valid, the game won’t proceed.

Tip: Think about how you would check if a number is a perfect square in math! 👩‍🏫


initialize_board(size)

Alright, let’s get that board ready! This function sets up a clean, empty board of a given size. The board is just a grid where every cell starts out empty. By empty, we mean None values. You'll soon fill these cells with letters!

Visualize it: Imagine a blank crossword puzzle where you haven’t written any letters yet. That’s what this function creates.


display_board(board)

Once the board is set up, we’ll need to display it in a human-friendly way. This function takes the raw data (stored in a 1D list) and prints it out as a 2D grid. It’s like unfolding a scroll and seeing all the cells laid out in front of you!

Hint: It uses the size of the board to know how to print the rows and columns.


map_index_to_2d(index, size)

When playing FlexDoku, the board is internally represented as a 1D list (think of it as a single row of cells). However, visually and for game logic, we treat it as a 2D grid (rows and columns). This function helps us map a 1D index to its 2D coordinates (row and column). You’ll need this when determining where to place your letters.

Hint: If you’re placing a letter at position 5 on a 4x4 board, can you figure out which row and column that corresponds to? This function helps you find out! Answer: Row 1, Column 1. So the function would return (1, 1).


map_2d_to_index(position, size)

This is the reverse of the previous function. It converts a 2D position (row and column) back into a 1D index, so the program knows exactly where in the internal list to update. Imagine pointing to a cell on a grid—this function converts your point into an index the program understands.

Example: For a 4x4 board, if you are at position (1, 2), meaning row 1 and column 2, then the corresponding 1D index would be 6. This means the program knows that the 7th element in the internal list is where your letter should go.


get_column_vals(board, index)

Ever wondered what letters are already placed in a column? This function returns all the values from the column you’re interested in. This is crucial for making sure no letter is repeated within that column (one of the game’s rules).

Think about it: If you’re filling out a column in Sudoku, you’d check to see what numbers are already there. Same concept, just with letters!


get_row_vals(board, index)

Similarly, this function returns all the values from a specific row. You’ll need this to check whether any letter has been repeated in that row (which, of course, is not allowed!).

Imagine: You’re filling out a row in a crossword puzzle and need to know what letters are already there. This function gets that for you.


Part 2: game_logic.py

Validation and Game Logic


Now that we have the board mechanics covered, let’s get into the logic that makes FlexDoku challenging and fun! These functions are responsible for validating the player’s moves, checking game rules, and ensuring everything stays on track.

get_valid_items(size)

This function generates the set of letters that can be used in the game, based on the size of the board. The size determines how many unique letters are needed. For example, if the board is 4x4, the function will return the first 4 letters of the alphabet: ‘a’, ‘b’, ‘c’, and ‘d’. For a 9x9 board, it will return the first 9 letters: ‘a’ through ‘i’. This way, the game always has the correct number of valid items for the board you're playing on!


check_spot_status(board, index)

Before placing a letter, you need to know if the spot is free, right? This function checks whether the spot at the given index is empty (i.e., None) and ready to be filled.

Friendly reminder: Always check if a spot is empty before making a move!


has_empty_space(board)

This one’s simple but important: It checks if there are any empty spaces left on the board. The game isn’t over until all spaces are filled!

Hint: The game checks for this every time you make a move to see if the game is complete.


check_line(line)

This function plays a key role in ensuring that no duplicate letters appear in any row or column. It checks whether all the letters in the given row or column are unique, while allowing repeated empty spaces (represented by None). If a letter is repeated, the function will flag the line as invalid. Think of it like scanning a row in Sudoku to ensure that no number appears more than once, except we’re working with letters instead.


check_valid_letter(letter, items)

This function verifies if the letter you’re trying to place is allowed in the game. The set of valid letters is determined by the size of the board. For example, on a 4x4 board, only the first four letters of the alphabet (a, b, c, d) are valid. The function ensures that the letter you want to place is part of this set. If the letter isn’t in the valid set, the move will be rejected to keep the game rules intact.


check_valid_move(letter, index, board)

This is one of the core functions of the game! It checks if placing a specific letter at a given position on the board is a valid move. To do this, it evaluates three key areas:

  • Row: It ensures that the letter isn’t already used in the same row.
  • Column: It checks that the letter isn’t repeated in the same column.
  • Block: It verifies that the letter doesn’t violate the no-duplicate rule within its smaller sub-block (like Sudoku’s 3x3 grid).

If all these conditions are met, the move is considered valid, and you can place the letter on the board. Otherwise, it prompts you to try again.

Thought: Imagine you're playing a crossword puzzle. Before placing a letter, you’d check the row, column, and surrounding area to ensure there’s no conflict. This function does all that work for you, ensuring you follow the rules with each move!


get_block_vals(board, index)

In FlexDoku, it’s not just the rows and columns that need to be free of duplicates—each block (a smaller section of the board) must also follow the no-repetition rule. This function finds the block that the given index falls within, and it returns the set of values in that block, just like get_row_vals(), and get_column_vals() do for rows and columns.

Blocks divide the board into smaller grids, like in Sudoku. For example, on a 4x4 board, blocks are 2x2; on a 9x9 board, blocks are 3x3. This function returns a list of the values in a block.


check_completion_status(board)

Are we done yet? This function checks whether the game is complete. The game is only finished when the board has no empty spaces, and all rows, columns, and blocks are valid with no repeated letters.

Once this function returns true, the game is won. 🎉


Ready to Get Started?


You’ve now explored the key building blocks of FlexDoku! It’s time to dive in and put your understanding into action. Remember to follow the rules and test each function step by step to ensure everything works smoothly. Once you’re ready, you can use the play_game() function to test your entire game logic in action.

To begin, download the provided modules:

Inside, you'll find the function templates and detailed docstrings to guide you. Your task is to complete the functions based on the instructions above. Take your time, but don’t wait too long—it’s always best to get started early so you have plenty of time to test and fine-tune your code!

The project is divided into two parts. You will first complete Part 1 and submit it, and then move on to Part 2. The parts are broken down into sections to help guide you through the assignment.


Part 1: FlexDoku Preparation and Utilities

a) Readiness Quiz (10 points)

Before you begin coding, carefully read through this document and review the starter code provided. Make sure you fully understand the expectations for the project. Then, head over to Canvas and complete the Readiness Quiz (10 points). The quiz is a straightforward test of your understanding, and it’s graded on an all-or-nothing basis: you’ll receive full credit only if all questions are answered correctly.

Tip

Revisit the project instructions as often as needed to ensure you are fully prepared before taking the quiz.


b) utilities.py (30 points)

Now, it’s time to dive into writing code! You will implement the functions in utilities.py. This file includes utility functions that handle the board's initialization, display, and other basic operations essential for FlexDoku.

  • Testing: Ensure that you rigorously test your utilities.py file for both correctness and style. Your code should be both functional and clean before submitting.

  • Submission: Once you’re confident in your solution, submit utilities.py to Gradescope by the deadline for your section.

Reminder

Make sure to follow the style guide and run flake8 to eliminate any warnings before submission.


Part 2: FlexDoku Game Logic

After completing and submitting Part 1, it’s time to move on to the more challenging Part 2, where you'll implement the game logic for FlexDoku.

game_logic.py (60 points)

In this section, you’ll be writing the full logic that powers the FlexDoku game in game_logic.py. This file should handle user inputs, game validation, and the overall flow of the game.

Before you submit to Gradescope, complete the following checklist:

  1. Test Your Code: Ensure that your game logic functions correctly by testing various scenarios.

  2. Integrate utilities.py: Make sure your game_logic.py interacts seamlessly with utilities.py. Both files should work together as expected.

  3. Code Style: Review the course style guide and ensure your code meets all the requirements.

  4. Run flake8: Fix any style warnings or errors that come up to ensure your code meets the standard.

  5. Comments: Add or update comments in your code to make it clear and easy to understand.

Reminder

When you submit your final work for Part 2, submit only game_logic.py. Do not include utilities.py in this submission.


Happy coding, and good luck with your first CS 149 project!