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
Related Questions
drjack9650@gmail.com
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.