4
\$\begingroup\$

I attempted making a "Mancala" clone that is played via a simple terminal interface. The overall game is functional in that the user may play against themselves, play against a randomized opponent, or watch the board as two random opponents play against one another.

This was an attempt to leverage my understanding of classes and objects but I feel that I may have overused the Board class. Areas like the make_move function are seemingly bloated but the end result was the simplest and most effective way I could perceive of doing it.

In the future I'd like to add error handling and incorrect user input. I am trying to construct this to be extendable into something like Kivy so I can make a GUI for it next. My hope was that with each portion of the game being an object it should be a simple matter to translate it into objects to display via Kivy. Am I at all along the right train of thought?

Note: The code is commented throughout, usually line-by-line for the more convoluted areas.

import random

class Cell():
    '''
    The object that will hold information about each cell's position
    and it's contents as well as who's side of the board it's on.
    '''
    def __init__(self,data):
        '''
        Takes in a simple list item that's generated by the board.
        Has three items, [side,position,contents]
        '''
        self.side = data[0]
        self.position = data[1]
        self.contents = data[2]

    def take(self):
        '''
        Simulates a player picking up a pile of pieces from a spot on the
        board, leaving none behind.
        '''
        pieces = self.contents
        self.contents = 0
        return pieces

class Board():
    '''
    The class that will serve as the main working area for the game
    '''
    def __init__(self):
        '''
        Starts with empty values
        '''
        self.cells = {}
        self.player = 0
        self.game_state = 0
        self.want_board = 0
        self.self_play = 0

    def setup(self):
        '''
        Provide a brief explanation of the game and allow some settings
        to be selected such as showing the board each turn and whether
        automatic play is enabled or not.
        '''
        print("Welcome to Mancala")
        print("A simple board game where players move stones from")
        print("one cell to another in consecutive order attempting")
        print("to accumulate as many pieces as possible in their")
        print("respective 'end' cells.")
        self.want_board = int(input("Would you like the full board to be displayed each turn?(0\\1)\n"))
        self.self_play = int(input("Would you like to control both sides?(0\\1)\n"))
        if self.self_play == 3 and self.want_board != 1:
            self.want_board = 1
            print("Forcing board to see progress.")
    def build_board(self):
        '''
        This will make data list items to pass into the cell class
        Depending on which cell it is, it will be given different values
        '''
        for x in range(0,14):
            data = [0,x,4]
            if x%7 == 0:
                if x > 6:
                    data[0] = 1
                data[2] = 0
            if x > 7:
                data[0] = 1
            self.cells[x] = Cell(data)

    def show_board(self):
        '''
        Prints the current board information as a crude table
        '''
        print("___________________")
        for cell in self.cells:
            side = self.cells[cell].side
            contents = self.cells[cell].contents
            position = self.cells[cell].position
            print("s{} | p{} | c{}".format(side,position,contents))
        print("___________________")

    def change_player(self):
        '''
        Switches the current player
        '''
        if self.player == 1:
            self.player = 0
        else:
            self.player = 1

    def make_move(self):
        '''
        A ... bloated... function that handles all the game play
        functionality
        '''
        options = []
        for cell in game.cells:
            if self.player == self.cells[cell].side:
                #if the cell is on that player's side
                if self.cells[cell].contents < 1:
                    #and it's empty, skip it
                    continue
                if cell%7 == 0:
                    #and it's an end zone cell, skip it
                    continue
                #otherwise add it to the options
                options.append(cell)
        #if there are no options
        if len(options) < 1:
            #The game is over, this is the "win" condition
            print("Player {} Cleared.".format(self.player))
            print("P0:{} , P1:{}".format(self.cells[7].contents,self.cells[0].contents))
            exit()
        if self.self_play == 1:
            #if the user wants to play against themselves, they will
            #take the turn each time
            for opt in options:
                print("{} : {}".format(opt,self.cells[opt].contents))
            choice = int(input("Which cell?\n"))
        if self.self_play != 1:
            #If they don't want to play against themselves
            #and it's player 0's turn
            if self.player == 0:
                #And they hit option 3 
                if self.self_play == 3:
                    #Both sides will play automatically
                    choice = random.choice(options)
                #If they picked to play against an automatic opponent
                if self.self_play == 1:
                    #They get to play their turn
                    for opt in options:
                        print("{} : {}".format(opt,self.cells[opt].contents))
                    choice = int(input("Which cell?\n"))
            else:
                #Automate the turn
                choice = random.choice(options)
        #Takes the pieces from that cell
        pieces = self.cells[choice].take()
        #While there are pieces left to distribute
        while pieces >= 1:
            #To move to the next space, increment the choice value
            choice += 1
            if choice >= 14:
                #makes the board 'loop' since there are only 14 spaces
                #and we're counting from 0
                choice = 0
            #If the target cell is empty
            if self.cells[choice].contents == 0:
                #and if it's the last piece in the player's hand
                #and it's not an end cell
                if pieces == 1 and choice%7 != 0:
                    #Add the piece to the cell and take another turn
                    self.cells[choice].contents = self.cells[choice].contents + 1
                    print("Additional Turn")
                    return
            #if it's an end cell and it's the player's side
            if choice%7 == 0 and self.player == self.cells[choice].side:
                #And if it's the last piece
                if pieces == 1:
                    #Add the piece and take another turn
                    self.cells[choice].contents = self.cells[choice].contents + 1
                    print("Additional Turn")
                    return
                #If it's not the last piece, just add it like normal
                self.cells[choice].contents = self.cells[choice].contents + 1
                continue
            #If it's an end cell and it's not the players, skip it
            if choice%7 == 0 and self.player != self.cells[choice].side:
                continue
            #If none of those trigger, then just add a piece to the cell
            #remove a piece from 'pieces' and continue the loop
            self.cells[choice].contents = self.cells[choice].contents + 1
            pieces -= 1
        #once the loop is over, change players
        self.change_player()

game = Board()
game.game_state = 1
game.build_board()
game.setup()
while game.game_state != 0:
    if game.want_board == 1:
        game.show_board()
    game.make_move()
\$\endgroup\$
2
\$\begingroup\$

typo: it's (Think of another possessive pronoun - no one ever spells it "hi's".) Also: whose

    self.side = data[0]
    self.position = data[1]
    self.contents = data[2]

This is perfectly nice, you might phrase it as self.side, self.position, self.contents = data. Also "state" might be slightly more descriptive than "data".

    for x in range(0,14):

This is more simply expressed as range(14)

        data = [0,x,4]
        if x%7 == 0:
            if x > 6:
                data[0] = 1
            data[2] = 0

Consider using named tuple, with ('side', 'position', 'contents').

        contents = self.cells[cell].contents
        position = self.cells[cell].position

Please be consistent, and assign position first.

    if self.player == 1:
        self.player = 0
    else:
        self.player = 1

Please phrase this as self.player = 1 - self.player

            if cell%7 == 0:
                #and it's an end zone cell, skip it

That's the 2nd time we've seen this modulo expression. Please replace it with a helper function, a predicate named is_end_zone(cell). That will let you delete the now redundant comment.

    if self.self_play != 1:

This code section is not so bad. But to reduce bloat, you might pass in a pair of Player objects, and ask the object for its move. It will prompt or roll a random number, as appropriate.

                self.cells[choice].contents = self.cells[choice].contents + 1

Please phrase it this way: self.cells[choice].contents += 1`

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.