Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Goal: Implement Connect Four using the Model-View-Controller architecture. Instr

ID: 3830268 • Letter: G

Question

Goal: Implement Connect Four using the Model-View-Controller architecture. Instructions Connect Four is a game similar to Tic-Tac-Toe. Two players alternate dropping tokens into a grid (7 columns times 6 rows), where tokens fall as far down into the grid as gravity allows. If there is no space available in a particular column, then a player cannot place a token there. The game ends (a) when a player places four tokens in a row horizontally, vertically, or diagonally, or (b) when the entire grid is filled so that no player can make a move. Use the Model-View-Controller software architecture to implement a version of the Connect Four game where players switch off using the same keyboard/mouse. The GUI must show the following information at all times: The current state of the game board Whose turn it is In the event that the game ends, it must show which player won, or that the game was a draw. Additionally, the GUI must allow users to perform the following actions: Pick which column to place a token in, while also disallowing placement of tokens in columns that are full Reset the game to an empty game board Quit the game and exit the program. In the event that the game ends, the GUI must notify the players which player won or that the game was a draw.

Explanation / Answer

Hi,

In order to implement the MVC software architecture , the fastest approach would be to use the python based UI libraries which can run as app.

Please follow the README to execute the program

The program structure :

connect_four ( dir)

     |

      | --------------> connectfour.kv , __init__.py , main.py

0. __init__.py () make the directory model

1. connectfour.kv ( The ui description)

<ConnectFour>:
player_1_name:player_1_name
player_2_name:player_2_name
games_won_label:games_won
start_game_btn:start_game_btn
game_board:game_board

GridLayout:
    pos:5,root.height-self.height-10
    pos_hint:{}
    cols:2
    padding:0,5,0,5
    spacing:3
    height:60
    cols_minimum:{0:110,1:110}
    row_default_height:30

#!/usr/bin/env python3

import connect_four.main
if __name__ == "__main__":
    connect_four.main.main()

    row_force_default:True
    Label:
      color:0,0,0,1
      text: "Player 1 Name"
      font_size:"12sp"
    TextInput:
      multiline: False
      write_tab: False
      font_size:"14sp"
      id: player_1_name
    Label:
      text: "Player 2 Name"
      color:0,0,0,1
      font_size:"12sp"
    TextInput:
      multiline: False
      write_tab: False
      font_size:"14sp"
      id: player_2_name
    Button:
      text: "Start Game"
      on_press:app.start_game()
      id: start_game_btn
    Button:
      text: "Reset Game"
      on_press:app.restart_game()
    Label:
      text: "Games won:"
      color:0,0,0,1

#!/usr/bin/env python3

import connect_four.main
if __name__ == "__main__":
    connect_four.main.main()

    Label:
      text: "0 v 0"
      color:0,0,0,1
      id:games_won

Widget:
    size:root.width-235,root.height
    pos:235,0
    canvas:
      Color:
        rgb:25/255,71/255,117/255
      Rectangle:
        size:self.size
        pos:self.pos

GameBoard:
    id:game_board

2. main.py

import copy
from kivy.config import Config
Config.set('graphics','resizable',0)
Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.popup import Popup
from kivy.graphics import *
from kivy.properties import NumericProperty,ListProperty,ObjectProperty


def get_first_available(col):
    """
    Returns the index of the first space in a list that is 0,
    returns -1 if the value isn't found as False can evaluate
    to 0
    """
    for i in range(len(col)):
        if col[i] == 0:
            return i
    return False


def get_n_by_n(a, top_x, top_y, n):
    """
    Gets an n by n 2d list from another 2d list
    """
    out = []
    cols = a[top_x:top_x+n]
    for col in cols:
        out.append(col[top_y:top_y+n])

    return out


def rgb_max_1(rgb):
    return tuple([x/255 for x in rgb])


class Player(object):
    def __init__(self, name, col, hover_col, point_score):
        self.name = name
        self.col = col
        self.hover_col = hover_col
        self.point_score = point_score
        self.games_won = 0


class GameBoard(Widget):
    """
    Container widget for the Columns of the board
    """
    def __init__(self, **kwargs):
        super(GameBoard, self).__init__(**kwargs)

        self.columns = [None]*7
        for i in range(7):
            container = Widget()
            layout = RelativeLayout(size=(77, 460), pos=(248+78*i, 20))
            self.columns[i] = Column()
            self.columns[i].col_no = i
            layout.add_widget(self.columns[i])
            container.add_widget(layout)
            self.add_widget(container)


class ConnectFour(Widget):
    board = ListProperty([[0]*6 for _ in range(7)])
    columns = ListProperty(None)
    cur_player = NumericProperty(0)
    players = ListProperty([])
    player_1_name = ObjectProperty(None)
    player_2_name = ObjectProperty(None)
    games_won_label = ObjectProperty(None)
    start_game_btn = ObjectProperty(None)
    game_board = ObjectProperty(None)

    def make_move(self, col_no, col_obj):
        """
        Makes a move on the board. Takes the column number and a
        reference to the column as parameters
        """
        if self.players == []:
            # Players haven't been initialised
            return False

        space_index = get_first_available(self.board[col_no])
        if space_index == False and isinstance(space_index,bool):
            # Column is full up
            return False

        # Set the board element
        self.board[col_no][space_index] = self.players[
                self.cur_player].point_score
      
        # Redraw that column with the new values
        col_obj.redraw(self.board[col_no],self.counter_cols)
      
        if self.check_win():
            self.game_end_popup("{} won".format(self.players[self.cur_player].name))
            return True
        else:
            # Check for draw
            self.draw = True
            for col in self.board:
                if 0 in col:
                    self.draw = False

            if self.draw:
               self.game_end_popup("Draw")


        # Change the current player
        self.cur_player = int(not self.cur_player)

    def make_hover(self, col_no, col_obj):
        """
        Implements a hovered state for the column. Does not affect the
        actual board used for win checking, etc
        """
        if self.players == []:
            # Players haven't been initialised
            return False
        # Copy the game board so the hovered state doesn't affect the win checking
        temp_col = copy.copy(self.board[col_no])
        index = get_first_available(temp_col)

        if index == False and isinstance(index,bool):
            # Move can't be made
            return False
        temp_col[index] = self.players[self.cur_player].point_score*2
        col_obj.redraw(temp_col,self.counter_cols)

    def undo_hover(self, col_no, col_obj):
        """
        Undoes the hovered state of the column, by redrawing it
        """
        if self.players == []:
            # Players haven't been initialised
            return False
        col_obj.redraw(self.board[col_no],self.counter_cols)

    def game_end_popup(self, msg):
        """
        Create popup for the end of the game with a message, New game,
        and Reset game button
        """
        popup_content = BoxLayout(orientation="vertical", size=(250, 200))
        new_game_btn = Button(size_hint=(1, 0.3), text="New Game")
        reset_btn = Button(size_hint=(1, 0.3), text="Reset (New Players)")

        popup_content.add_widget(Label(size_hint=(1, 0.4), text="{}".format(
            msg)))
        popup_content.add_widget(new_game_btn)
        popup_content.add_widget(reset_btn)

        self.popup = Popup(title="Game Finished", size=(250, 200),
                           size_hint=(None, None), content=popup_content, auto_dismiss=False)
        new_game_btn.bind(on_press=self.new_game_handler)
        reset_btn.bind(on_press=self.reset_game_handler)
        self.popup.open()

    def new_game_handler(self, _=None):
        """
        Handle the New game button on the win popup, has *optional*
        parameter as the button instance is by default passed to it
        """
        self.popup.dismiss()

        if self.draw:
            for i in range(2):
                self.players[i].games_won += 1
        else:
            self.players[self.cur_player].games_won += 1
        self.games_won_label.text = "{} v {}".format(self.players[0].games_won, self.players[1].games_won)

        # Reset Board
        self.board = [[0]*6 for x in range(7)]
        # Loop through columns in GameBoard and redraw them
        for col in self.game_board.columns:
            col.redraw([0]*6)

        self.cur_player = 0

    def reset_game_handler(self, _=None):
        """
        Handle the reset game button on the win popup and main
        screen, has *optional* parameter as the button instance
        is passed to it
        """
        if hasattr(self,'popup'):
            self.popup.dismiss()
        if self.players == []:
            return False
        # Reset Board
        self.board = [[0]*6 for x in range(7)]
        for col in self.game_board.columns:
            col.redraw([0]*6)
      
        # Re-enable and clear text inputs
        self.set_inputs_state(False)
        self.player_1_name.text = ""
        self.player_2_name.text = ""
        self.games_won_label.text = "0 v 0"
        self.players = []
        self.cur_player = 0

    def start_game(self):
        """
        Handles the start game button and creates the player objects
        """
        # Create Players
        self.players = [Player(self.player_1_name.text, rgb_max_1((221, 63, 63)), rgb_max_1((221,141,141)), 1),
                        Player(self.player_2_name.text, rgb_max_1((222, 226, 55)), rgb_max_1((224,226,136)), -1)]
        self.counter_cols = {"1": self.players[0].col, "-1": self.players[1].col,
                             "2": self.players[0].hover_col, "-2": self.players[1].hover_col}
        # Disable text inputs and start game button
        self.set_inputs_state(True)

    def set_inputs_state(self, state):
        """
        Sets the disabled property to state for player name entries
        and start game button
        """
        self.player_1_name.disabled = state
        self.player_2_name.disabled = state
        self.start_game_btn = state

    def check_win(self):
        """
        Check for wins by using a 4x4 box and moving that around
        """
        for top_y in range(3):
            for top_x in range(4):
                to_check = get_n_by_n(self.board,top_x,top_y,4)
                row_check = [0]*4
                # Left to right and right to left diagonal check
                diag_check = [0]*2
                # Check columns
                for y, col in enumerate(to_check):
                    # Calculate scores of rows
                    for x, space in enumerate(col):
                        row_check[x] += space
                        if x == y:
                            diag_check[0] += space
                        if x+y == 3:
                            diag_check[1] += space

                    if sum(col) == 4:
                        return self.players[0]
                    elif sum(col) == -4:
                        return self.players[1]

                # Check row_check scores
                for row in row_check:
                    if row == 4:
                        return self.players[0]
                    elif row == -4:
                        return self.players[1]

                for diag in diag_check:
                    if diag == 4:
                        return self.players[0]
                    elif diag == -4:
                        return self.players[1]
        return False


class Column(Widget):
    def __init__(self, **kwargs):
        super(Column, self).__init__(**kwargs)
        Window.bind(mouse_pos=self.on_mouse_pos)
        # Draw Column with zeros, so the spaces will all be white on start
        self.redraw([0]*6, [None])
        self.hovered = False
        self.connectFourGame = None

    def get_game(self):
        """
        Finds the main ConnectFour widget if the property isn't set and returns it.
        Needed for hover states and mouse presses, to access the game board
        """
        if self.connectFourGame == None:
            # Get make move function from connectFourGame
            for child in self.get_root_window().children:
                if isinstance(child, ConnectFour):
                    self.connectFourGame = child
                    break
        return self.connectFourGame

    def on_touch_down(self, touch):
        if self.collide_point(touch.x,touch.y) and touch.button == "left":
            self.get_game().make_move(self.col_no,self)

    def on_mouse_pos(self, *args):
        pos = args[1]
        if self.collide_point(*self.to_widget(*pos)):
            self.hovered = True
            # Hovered over this column
            self.get_game().make_hover(self.col_no,self)
        elif self.hovered:
            # Remove the hovered state as mouse is no longer on this col
            self.hovered = False
            self.get_game().undo_hover(self.col_no,self)

    def redraw(self, col_vals, cols=None):
        self.canvas.clear()
        with self.canvas:
            for i, space in enumerate(col_vals):
                if space == 0:
                    Color(0.9, 0.9, 0.9)
                else:
                    Color(*(cols[str(space)]))
                Ellipse(pos=(0, 78*i),size=(70, 70))


class ConnectFourApp(App):
    def build(self):
        self.title = "Connect Four"
        connectFourGame = ConnectFour()
        self.connectFourGame = connectFourGame
        Window.clearcolor=(0.9,0.9,0.9,1)
        Window.size=(800,500)
        return self.connectFourGame

    def start_game(self):
        self.connectFourGame.start_game()

    def restart_game(self):
        self.connectFourGame.reset_game_handler()


def main():
    ConnectFourApp().run()

=============================================

connect4.py (Calling pogram )

#!/usr/bin/env python3

import connect_four.main
if __name__ == "__main__":
    connect_four.main.main()

========================================================

requirements.txt ( package libraries)

appdirs==1.4.1
Cython==0.25.2
Kivy==1.9.1
Kivy-Garden==0.1.4
packaging==16.8
pyparsing==2.1.10
requests==2.13.0
six==1.10.0

=============================================

+How to run the code base

1. Install a python environment python 3

2.install all the dependent libraries using the command below

$ pip install -r requirements.txt

3. The core logic is in the module "connect_four"

I hope the logic has been undesrtood.

thanks

-shesha

Hire Me For All Your Tutoring Needs
Integrity-first tutoring: clear explanations, guidance, and feedback.
Drop an Email at
drjack9650@gmail.com
Chat Now And Get Quote