Project General Description

For our first project, we're going to be implementing the game Mancala. Mancala has been discovered in the excavated ruins of a Roman bathhouse built in the 2nd or 3rd century AD.

In the 21st century, however, we have Python so we'll write a little version of the game ourselves.

The game is played with 'stones' which can be glass bubbles or stones, and there are generally two rows of 6 circular "cups" or pits, and then there's a double length pit on both sides, which we will also call a "mancala."

Rules of the Game

1. The board is formed by the two long pits or cups, and 12 cups divided into two rows, like in the picture.

2. To set up the game, place four stones in each of the 12 regular cups.

3. Players alternate by taking turns.

4. A turn consists of selecting a non-empty non-mancala cup and taking all of the stones from it, and then starting in the next cup, place a stone, and then advance to the next cup and place another stone, repeating this until all of the stones have been moved.

  • The direction of play will be clockwise.
  • Mancalas count as a put so you must place a stone in there as you pass that space.
  • If the last stone is placed into a mancala, then that player gets another turn.
  • A player cannot select a mancala to move stones out.

5. The game ends when one of the rows of regular cups is empty, so when either the "top" row or the "bottom" row is empty, the game is over.

6. The winner when the game ends is the player with the most stones in their mancala.

7. The board is numbered in the following order. Cups 0 and 7 are mancalas whereas the rest are "regular."

8. Player 1's mancala is at position 7, and player 2's mancala is at position 0.

0 1 2 3 4 5 6 7
13 12 11 10 9 8

Implementation Details

This is a specification for how the project should be implemented.

1. Get the names of players.

2. Begin the cycle of turns by alternating back and forth from player 1 to player 2 and back until the game is over.

3. When a player takes a turn, move the stones for them, starting in the next cup and placing a stone in each cup until the stones run out.

4. If a user enters an invalid cup number, make them try again.

5. If a turn ends in a mancala (the last stone is placed there), then that player gets a second turn.

6. When one of the two rows (positions 1 to 6 or positions 8 to 13) are empty, then the game ends.

7. Tell the players who has won based on the number of stones in each of the mancalas.

8. After each move, reprint the board and ask for the next user input.

Design Overview

Your project should include at least the functions:

1.) This function should be the entry point for your program. Your if __name__ == "__main__": should be exactly one line, which calls this function, unless you want to create the players before you run the game.

def run_game():
  • You can change the parameters of the function.
  • This function should run the game, set up the board, alternate between players, and determine the winner at the end. You should create helper functions in order to distribute all of this functionality. This function shouldn't be a 100 line monolithic monstrosity.

2.) The function get_player should be something like:

def get_player():
  • You are permitted to modify the arguments of the function.
  • This function should get the player name or names, depending on how you decide to implement it.

3.) You should create a function:

def take_turn(player, game_cups):
  • You can modify the function's signature (i.e. change parameters depending on your implementation.
  • This function should print the board, ask for the new move, verify that it is a legal move, and then make it.
  • There should be a way for you to signal from this function back to run_game that you have landed on a mancala and that player should get a new turn.

4.)You must create at least 2 more functions to help you finish the project. You can choose what they do, and what exactly their signatures should be. If you ask the question "but what if I don't need 2 more functions?" then you definitely have too much functionality going on in any single function. I created about 5 helper functions in my implementation.

I have provided a starter file on the GL server at: /afs/umbc.edu/users/e/r/eric8/pub/cs201/spring21/mancala_starter.py

You should copy it to your directory using the command: cp /afs/umbc.edu/users/e/r/eric8/pub/cs201/spring21/mancala_starter.py mancala.py

There are three functions in the file as well as some constants.

draw_board(top_cups, bottom_cups, mancala_a, mancala_b):

The draw board function is the function you should be calling. You should send data to this function in the following way:

Top_cups and bottom_cups should be a 2-d list of strings. The outer list for both of these variables should be length 6, because that's the number of cups.

The inner list should be a list of strings which is of length BLOCK_HEIGHT, because that's the number of lines.

You can use slices like this: my_string[0: BLOCK_WIDTH] to chop off any strings you print, and str.rjust(BLOCK_WIDTH) to give extra space. The draw board function will try to do this for you, but if you end up with IndexErrors, then you should use these methods to force the string to be the right length.

Mancala_a and mancala_b are lists of strings of length 2 * BLOCK_HEIGHT + 1 because there is the midline asterisk which takes up another space. The strings should all be of length BLOCK_WIDTH, but my code tries to help you out as well.

I am providing minimal documentation, i.e. nearly nothing about the following two functions. The reason for this is that you don't need to call these functions, and shouldn't modify them. You can read them of course and though they are documented, they are not terribly complex, but just call draw_board with the right data.

draw_mancala(fore_or_aft, mancala_data, the_board):

This function draws out a mancala, but you should not call this function directly. It's a helper function for my draw_board function.

draw_block(the_board, pos_x, pos_y, block_data):

Similarly, this is also a function you shouldn't have to call. It's another helper function for the draw_board function.

Coding Standards

1. At least one inline comment per function explaining something about your code.

2. Constants above your function definitions, outside of the "if __name__ == '__main__':" block.

  • A magic value is a string which is outside of a print or input statement, but is used to check a variable, so for instance:
    • print(first_creature_name, 'has died in the fight. ') does not involve magic values.
    • However, if my_string == 'EXIT': exit is a magic value since it's being used to compare against variables within your code, so it should be: EXIT_STRING = 'EXIT' if my_string == EXIT_STRING:
  • A number is a magic value when it is not 0, 1, and if it is not 2 being used to test parity (even/odd).
  • A number is magic if it is a position in an array, like my_array[23], where we know that at the 23rd position, there is some special data. Instead it should be USERNAME_INDEX = 23 my_array[USERNAME_INDEX]
  • Constants in mathematical formulas can either be made into official constants or kept in a formula.

3. Previously checked coding standards involving:

  • snake_case_variable_names
  • CAPITAL_SNAKE_CASE_CONSTANT_NAMES
  • Use of whitespace (2 before and after a function, 1 for readability.)

Allowed Built-ins/Methods/etc

  • Declaring and assigning variables, ints, floats, bools, strings, lists, dicts.
  • Using +, -, *, /, //, %, **; +=, -=, *=, /=, //=, %=, **= where appropriate
  • Comparisons ==, <=, >=, >, <, !=, in
  • Logical and, or, not
  • if/elif/else, nested if statements
  • Casting int(x), str(x), float(x), (technically bool(x))
  • For loops, both for i and for each type.
  • While loops
    • sentinel values, boolean flags to terminate while loops
  • Lists, list(), indexing, i.e. my_list[i] or my_list[3]
    • 2d-lists if you want them/need them my_2d[i][j]
    • Append, remove
    • list slicing
  • If you have read this section, then you know the secret word is: createous.
  • String operations, concatenation +, +=, split(), strip(), join(), upper(), lower(), isupper(), islower()
    • string slicing
  • Print, with string formatting, with end= or sep=:
    • '{}'.format(var), '%d' % some_int, f-strings
    • Really the point is that we don't care how you format strings in Python
    • Ord, chr, but you won't need them this time.
  • Input, again with string formatting in the prompt, casting the returned value.
  • Dictionaries
    • creation using dict(), or {}, copying using dict(other_dict)
    • .get(value, not_found_value) method
    • accessing, inserting elements, removing elements.
    • Deleting elements from dictionaries using del.
  • Using the functions provided to you in the starter code.
  • Using import with libraries and specific functions as allowed by the project/homework.
  • You may define your own new functions with new parameters
    • Single return values (you must return a single float, bool, string, int, list or dictionary, or None/not return).

Forbidden Built-ins/Methods/etc

This is not a complete listing, but it includes:

  • break, continue
  • methods outside those permitted within allowed types
    • for instance str.endswith
    • list.index, list.count, etc.
  • Keywords you definitely don't need: await, as, assert, async, class, except, finally, global, lambda, nonlocal, raise, try, yield
  • The is keyword is forbidden, not because it's necessarily bad, but because it doesn't behave as you might expect (it's not the same as ==).
  • built in functions: any, all, breakpoint, callable, classmethod, compile, exec, delattr, divmod, enumerate, filter, map, max, min, isinstance, issubclass, iter, locals, oct, next, memoryview, property, repr, reversed, round, set, setattr, sorted, staticmethod, sum, super, type, vars, zip
  • If you have read this section, then you know the secret word is: serendipity.
  • exit() or quit()
  • If something is not on the allowed list, not on this list, then it is probably forbidden.
  • The forbidden list can always be overridden by a particular problem, so if a problem allows something on this list, then it is allowed for that problem.

Starter Code

BLOCK_WIDTH = 6
BLOCK_HEIGHT = 5
BLOCK_SEP = "*"
SPACE = ' '

def draw_board(top_cups, bottom_cups, mancala_a, mancala_b):
"""
draw_board is the function that you should call in order to draw the board.
top_cups and bottom_cups are 2d lists of strings. Each string should be
length BLOCK_WIDTH and each list should be of length BLOCK_HEIGHT.
mancala_a and mancala_b should be 2d lists of strings. Each string should be
BLOCK_WIDTH in length, and each list should be 2 * BLOCK_HEIGHT + 1
:param top_cups: This should be a list of strings that represents cups 1 to 6
(Each list should be at least BLOCK_HEIGHT in length, since each string in the list is
a line.)
:param bottom_cups: This should be a list of strings that represents cups 8 to 13
(Each list should be at least BLOCK_HEIGHT in length, since each string in the list is
a line.)
:param mancala_a: This should be a list of 2 * BLOCK_HEIGHT + 1 in length which
represents the mancala at position 7.
:param mancala_b: This should be a list of 2 * BLOCK_HEIGHT + 1 in length which
represents the mancala at position 0.
"""
board = [[SPACE for _ in range((BLOCK_WIDTH + 1) * (len(top_cups) + 2) + 1)] for _
in range(BLOCK_HEIGHT * 2 + 3)]
for p in range(len(board)):
board[p][0] = BLOCK_SEP
board[p][len(board[0]) - 1] = BLOCK_SEP
for q in range(len(board[0])):
board[0][q] = BLOCK_SEP
board[len(board) - 1][q] = BLOCK_SEP
# draw midline
for p in range(BLOCK_WIDTH + 1, (BLOCK_WIDTH + 1) * (len(top_cups) + 1) + 1):
board[BLOCK_HEIGHT + 1][p] = BLOCK_SEP
for i in range(len(top_cups)):
for p in range(len(board)):
board[p][(1 + i) * (1 + BLOCK_WIDTH)] = BLOCK_SEP
for p in range(len(board)):
board[p][1 + BLOCK_WIDTH] = BLOCK_SEP
board[p][len(board[0]) - BLOCK_WIDTH - 2] = BLOCK_SEP
for i in range(len(top_cups)):
draw_block(board, i, 0, top_cups[i])
draw_block(board, i, 1, bottom_cups[i])
draw_mancala(0, mancala_a, board)
draw_mancala(1, mancala_b, board)
print('\n'.join([''.join(board[i]) for i in range(len(board))]))

def draw_mancala(fore_or_aft, mancala_data, the_board):
"""
Draw_mancala is a helper function for the draw_board function.
:param fore_or_aft: front or back (0, or 1)
:param mancala_data: a list of strings of length 2 * BLOCK_HEIGHT + 1 each string
of length BLOCK_WIDTH
:param the_board: a 2d-list of characters which we are creating to print the
board.
"""
if fore_or_aft == 0:
for i in range(len(mancala_data)):
data = mancala_data[i][0: BLOCK_WIDTH].rjust(BLOCK_WIDTH)
for j in range(len(mancala_data[0])):
the_board[1 + i][1 + j] = data[j]
else:
for i in range(len(mancala_data)):
data = mancala_data[i][0: BLOCK_WIDTH].rjust(BLOCK_WIDTH)
for j in range(len(mancala_data[0])):
the_board[1 + i][len(the_board[0]) - BLOCK_WIDTH - 1 + j] = data[j]

def draw_mancala(fore_or_aft, mancala_data, the_board):
"""
Draw_mancala is a helper function for the draw_board function.
:param fore_or_aft: front or back (0, or 1)
:param mancala_data: a list of strings of length 2 * BLOCK_HEIGHT + 1 each string
of length BLOCK_WIDTH
:param the_board: a 2d-list of characters which we are creating to print the
board.
"""
if fore_or_aft == 0:
for i in range(len(mancala_data)):
data = mancala_data[i][0: BLOCK_WIDTH].rjust(BLOCK_WIDTH)
for j in range(len(mancala_data[0])):
the_board[1 + i][1 + j] = data[j]
else:
for i in range(len(mancala_data)):
data = mancala_data[i][0: BLOCK_WIDTH].rjust(BLOCK_WIDTH)
for j in range(len(mancala_data[0])):
the_board[1 + i][len(the_board[0]) - BLOCK_WIDTH - 1 + j] = data[j]

def draw_block(the_board, pos_x, pos_y, block_data):
"""
Draw block is a helper function for the draw_board function.
:param the_board: the board is the 2d grid of characters we're filling in
:param pos_x: which cup it is
:param pos_y: upper or lower
:param block_data: the list of strings to put into the block.
"""
for i in range(BLOCK_HEIGHT):
data = block_data[i][0:BLOCK_WIDTH].rjust(BLOCK_WIDTH)
for j in range(BLOCK_WIDTH):
the_board[1 + pos_y * (BLOCK_HEIGHT + 1) + i][1 + (pos_x + 1) *
(BLOCK_WIDTH + 1) + j] = data[j]

def get_player():
pass

def take_turn(player, cups):
pass

def run_game():
pass

if __name__ == "__main__":
run_game()
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.