Transforming a game to an object-oriented version - python

I have programmed a simple console game that allows me to move my player inside a small level with walls and blank spaces. It's all done using only few simple functions.
I'm rather new to Python but I'd like to learn OOP next, how would I continue from here on if I wanted to make this game object-oriented?
I understand classes and objects quite well, but bear with me if I don't understand all the answers.
Here's the current game:
LEVEL = [
'xxxxxx',
'x x',
'x i x',
'x x',
'x x',
'xxxxxx'
]
def get_block(x, y):
"""Gets a block at the given coordinates."""
try:
return LEVEL[y][x]
except IndexError:
return None
def set_block(x, y, block):
"""Sets a block at the given coordinates."""
try:
LEVEL[y] = LEVEL[y][:x] + block + LEVEL[y][x + 1:]
except IndexError:
pass
def get_player_position():
"""Gets player's position."""
for y, row in enumerate(LEVEL):
for x, column in enumerate(row):
if column == 'i':
return x, y
def set_player_position(x, y):
"""Sets player's position."""
block = get_block(x, y)
if block == ' ':
px, py = get_player_position()
set_block(px, py, ' ')
set_block(x, y, 'i')
def main():
"""Entry point for the program."""
cmd = ''
while cmd.lower() not in ('quit', 'q'):
print('\n' * 30)
for row in LEVEL:
print(row)
cmd = input('Command: ').lower()
px, py = get_player_position()
if cmd == 'w':
set_player_position(px, py - 1)
elif cmd == 's':
set_player_position(px, py + 1)
elif cmd == 'a':
set_player_position(px - 1, py)
elif cmd == 'd':
set_player_position(px + 1, py)
print('Bye.')
if __name__ == '__main__':
main()

You're question is pretty open-ended, so it's difficult to give an all-encompassing answer — so what I've done instead is identified one data-structure in your existing code and turned it an class.
Functions which used to operate on global data-datastructure, are now all public methods of instances of the class which is the only ones that's allowed make changes to the data held inside it in a private attribute named _field.
Doing this sort of thing is an essential first step in writing object-oriented software.
Hope you find the example somewhat enlightening.
class PlayingField(object):
# Class constants
PLAYER = 'i'
EMPTY = ' '
EDGE = 'x'
DEFAULT_SIZE = 6
def __init__(self, size=DEFAULT_SIZE):
X, EMPTY = self.EDGE, self.EMPTY
self._size = size
# build playing field
self._field = [size*X] + (size-2)*[X + (size-2)*EMPTY + X] + [size*X]
self._set_block(2, 2, self.PLAYER) # Initialize player's position.
def display(self):
print(30*'\n')
for row in self._field:
print(row)
def get_player_position(self):
"""Gets player's position."""
for y, row in enumerate(self._field):
for x, column in enumerate(row):
if column == self.PLAYER:
return x, y
else:
raise ValueError("Couldn't determine player's location on field")
def set_player_position(self, x, y):
"""Sets player's position."""
block = self._get_block(x, y)
if block == self.EMPTY:
px, py = self.get_player_position()
self._set_block(px, py, self.EMPTY)
self._set_block(x, y, self.PLAYER)
# Private methods
def _get_block(self, x, y):
"""Gets a block at the given coordinates."""
try:
return self._field[y][x]
except IndexError:
return None
def _set_block(self, x, y, block):
"""Sets a block at the given coordinates."""
try:
self._field[y] = self._field[y][:x] + block + self._field[y][x + 1:]
except IndexError:
pass
def main():
"""Entry point for the program."""
field = PlayingField()
cmd = ''
while cmd.lower() not in ('quit', 'q'):
field.display()
cmd = input('Command: ').lower()
px, py = field.get_player_position()
if cmd == 'w':
field.set_player_position(px, py - 1)
elif cmd == 's':
field.set_player_position(px, py + 1)
elif cmd == 'a':
field.set_player_position(px - 1, py)
elif cmd == 'd':
field.set_player_position(px + 1, py)
print('Bye.')
if __name__ == '__main__':
main()

Related

Minimax method in an AI TicTacToe game

The following code describes an AI TicTacToe game(the main file is game.py)
**player.py:**
import math
import random
class Player():
def __init__(self, letter):
self.letter = letter
def get_move(self, game):
pass
class HumanPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
valid_square = False
val = None
while not valid_square:
square = input(self.letter + '\'s turn. Input move (0-9): ')
try:
val = int(square)
if val not in game.available_moves():
raise ValueError
valid_square = True
except ValueError:
print('Invalid square. Try again.')
return val
class RandomComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
square = random.choice(game.available_moves())
return square
class SmartComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
if len(game.available_moves()) == 9:
square = random.choice(game.available_moves())
else:
square = self.minimax(game, self.letter)['position']
return square
def minimax(self, state, player):
max_player = self.letter # yourself
other_player = 'O' if player == 'X' else 'X'
# first we want to check if the previous move is a winner
if state.current_winner == other_player:
return {'position': None, 'score': 1 * (state.num_empty_squares() + 1) if other_player == max_player else -1 * (
state.num_empty_squares() + 1)}
elif not state.empty_squares():
return {'position': None, 'score': 0}
if player == max_player:
best = {'position': None, 'score': -math.inf} # each score should maximize
else:
best = {'position': None, 'score': math.inf} # each score should minimize
for possible_move in state.available_moves():
state.make_move(possible_move, player)
sim_score = self.minimax(state, other_player) # simulate a game after making that move
# undo move
state.board[possible_move] = ' '
state.current_winner = None
sim_score['position'] = possible_move # this represents the move optimal next move
if player == max_player: # X is max player
if sim_score['score'] > best['score']:
best = sim_score
else:
if sim_score['score'] < best['score']:
best = sim_score
return best
**game.py:**
import math
import time
from player import HumanPlayer, RandomComputerPlayer, SmartComputerPlayer
class TicTacToe():
def __init__(self):
self.board = self.make_board()
self.current_winner = None
#staticmethod
def make_board():
return [' ' for _ in range(9)]
def print_board(self):
for row in [self.board[i*3:(i+1) * 3] for i in range(3)]:
print('| ' + ' | '.join(row) + ' |')
#staticmethod
def print_board_nums():
# 0 | 1 | 2
number_board = [[str(i) for i in range(j*3, (j+1)*3)] for j in range(3)]
for row in number_board:
print('| ' + ' | '.join(row) + ' |')
def make_move(self, square, letter):
if self.board[square] == ' ':
self.board[square] = letter
if self.winner(square, letter):
self.current_winner = letter
return True
return False
def winner(self, square, letter):
# check the row
row_ind = math.floor(square / 3)
row = self.board[row_ind*3:(row_ind+1)*3]
# print('row', row)
if all([s == letter for s in row]):
return True
col_ind = square % 3
column = [self.board[col_ind+i*3] for i in range(3)]
# print('col', column)
if all([s == letter for s in column]):
return True
if square % 2 == 0:
diagonal1 = [self.board[i] for i in [0, 4, 8]]
# print('diag1', diagonal1)
if all([s == letter for s in diagonal1]):
return True
diagonal2 = [self.board[i] for i in [2, 4, 6]]
# print('diag2', diagonal2)
if all([s == letter for s in diagonal2]):
return True
return False
def empty_squares(self):
return ' ' in self.board
def num_empty_squares(self):
return self.board.count(' ')
def available_moves(self):
return [i for i, x in enumerate(self.board) if x == " "]
def play(game, x_player, o_player, print_game=True):
if print_game:
game.print_board_nums()
letter = 'X'
while game.empty_squares():
if letter == 'O':
square = o_player.get_move(game)
else:
square = x_player.get_move(game)
if game.make_move(square, letter):
if print_game:
print(letter + ' makes a move to square {}'.format(square))
game.print_board()
print('')
if game.current_winner:
if print_game:
print(letter + ' wins!')
return letter # ends the loop and exits the game
letter = 'O' if letter == 'X' else 'X' # switches player
time.sleep(.8)
if print_game:
print('It\'s a tie!')
if __name__ == '__main__':
x_player = SmartComputerPlayer('X')
o_player = HumanPlayer('O')
t = TicTacToe()
play(t, x_player, o_player, print_game=True)
As you can see, the programmer used an minimx algorithmus to minimize the possibility to lose and to maximize the possibility to win. Now after several days of trying to understand how this minimax method works, I can't help but ask you guys to explain this to me.
1.what is the reasoning behind adding this code to player.py and how does the method return a score that will be greater than the initial value of negative infinity?:
if player == max_player:
best = {'position': None, 'score': -math.inf}
else:
best = {'position': None, 'score': math.inf}
2.in our simscore variabel we added the otherplayer parameters into our minimax function. Why did we do that? And doesnt we need to add the variable max_player as a parameter to minimax() to simulate a game?
3. How does recursion in this specific case work?
1.what is the reasoning behind adding this code to player.py and how does the method return a score that will be greater than the initial
value of negative infinity?:
if state.current_winner == other_player:
return {
'position': None,
'score': 1 * (state.num_empty_squares() + 1) if other_player == max_player else -1 * (state.num_empty_squares() + 1)}
or I think alternately:
if state.current_winner == other_player:
position = None
score = state.num_empty_squares() + 1
if other_player == max_player:
score = -score
return {'position': None, 'score': score}
If the player that moved last at this move in the tree of all moves "won" then that is ultimately good for that player and bad for the other player. Wins with more empty spaces are better.
2.in our simscore variabel we added the otherplayer parameters into our minimax function. Why did we do that? And doesnt we need to add
the variable max_player as a parameter to minimax() to simulate a
game?
minimax() is called recursively simulating a game. When called it switches players via other_player = 'O' if player == 'X' else 'X' when we pass the "current" other_player into the next move, minimax() can determine who moves in this call.
How does recursion in this specific case work?
Each call of minimax() allows for a simulated move by one of the two players. inside this method, the current moving player is other_player. A move is selected based on the current game state and minimax() is called to simulate the next player moving.

Why do I get AttributeError: 'NoneType' object has no attribute 'path' in my maze path finding program?

I am trying to create a Python program using Jupiter Notebook and the A* algorithm, which finds the shortest path out of a hashtag maze. I keep getting the error that the 'NoneType' object has no attribute 'path'. Does anyone have any ideas as to why this could be occurring? I did override the result method, so I am not sure whether that may be causing the issue, but I am uncertain how I could fix this.
The error is-
AttributeError Traceback (most recent call last)
Input In [26], in <cell line: 57>()
88 problem = MazeSolver(MAP)
90 result = astar(problem, graph_search=True)
---> 92 path = [x[1] for x in result.path()]
94 print()
95 for y in range(len(MAP)):
AttributeError: 'NoneType' object has no attribute 'path'
import math
from simpleai.search import SearchProblem, astar
class MazeSolver(SearchProblem):
def __init__(self, board):
self.board = board
self.goal = (0,0)
for y in range(len(self.board)):
for x in range(len(self.board[y])):
if self.board[y][x].lower() == "o":
self.initial = (x, y)
elif self.board[y][x].lower() == "x":
self.goal = (x, y)
super(MazeSolver, self).__init__(initial_state = self.initial)
def actions(self, state):
actions = []
for action in COSTS.keys():
newx, newy = self.result(state, actions)
if self.board[newy][newx] != "#":
actions.append(action)
return actions
def result(self, state, action):
x, y = state
if action.count("up"):
y -= 1
if action.count("down"):
y += 1
if action.count("left"):
x -= 1
if action.count("right"):
x += 1
new_state = (x, y)
return new_state
def is_goal(self, state):
return state == self.goal
def cost(self, state, action, state2):
return COSTS[action]
def heuristic(self, state):
x, y = state
gx, gy = self.goal
return math.sqrt((x - gx) ** 2 + (y - gy) ** 2)
if __name__ == "__main__":
MAP = """
#############################
# # # #
# #### ######## # #
# o # # # #
# ### ##### ###### #
# # ### # #
# # # # # # ###
# ##### # # # x #
# # # #
#############################
"""
print(MAP)
MAP = [list(x) for x in MAP.split("\n") if x]
cost_regular = 1.0
cost_diagonal = 1.7
COSTS = {
"up": cost_regular,
"down": cost_regular,
"left": cost_regular,
"right": cost_regular,
"up left": cost_diagonal,
"up right": cost_diagonal,
"down left": cost_diagonal,
"down right": cost_diagonal
}
problem = MazeSolver(MAP)
result = astar(problem, graph_search=True)
path = [x[1] for x in result.path()]
print()
for y in range(len(MAP)):
for x in range(len(MAP[y])):
if (x, y) == problem.initial:
print('o', end='')
elif(x, y) == problem.goal:
print('x', end='')
elif (x, y) in path:
print('.', end='')
else:
print(MAP[y][x], end='')
print()

One part of my code is faster than the other one, but they're almost the same

When I put the snake to start going to the "►" direction, it goes faster than when I do it for "◄" and the same result for "▲" and "▼".
Actually, it literally teleports. Even if I see that it's doing the right way correctly by going from key to key.
Maybe it's doing the process without showing it, until the end.
Do you see any reasons why does that happen? Because I really don't see why.
import numpy as np
from random import randint
import keyboard
from time import sleep
import os
#Get to work
game_area = {}
#Random snake and apple start positions
apple = randint(0, 540)
snake_pos = randint(0, 540)
#Define positions
for x in np.arange(0,540,1):
game_area[x] = '.'
#Insert Apple
game_area[apple] = 'Ó'
#Limit areas
game_area[0], game_area[59], game_area[60], game_area[119],game_area[120], game_area[179],game_area[180], game_area[239],game_area[240],game_area[299],game_area[300],game_area[359], game_area[360],game_area[419],game_area[420],game_area[479],game_area[480], game_area[539] = '╔','╗','║','║','║','║','║','║','║','║','║','║','║','║','║','║','╚','╝'
for x in range(1,59):
game_area[x] = '═'
for x in range(481,539):
game_area[x] = '═'
#Snake Class
class Snake:
def __init__(self, length, position):
self.length = length
self.position = position
def printar(self):
print(self.length,self.position)
snake = Snake(1,snake_pos)
game_area[snake.position] = '►'
#Functions
def prepare_game():
#Create game area
for number, instance in zip(list(game_area.keys()), list(game_area.values())):
if number not in [60,120,180,240,300,360,420,480]:
print(instance, end='')
else:
print('\n' + instance, end='')
print('')
def start_game():
global game_area
game_over = False
while True:
prepare_game()
if game_over == False:
#Keyboard Detect / Automatic Move
if '◄' in list(game_area.values()):
for x,y in game_area.items():
if y == '◄':
if game_area[x - 1] != '║':
game_area[x - 1] = y
game_area[x] = ' '
else:
game_over = True
break
if '►' in list(game_area.values()):
for x,y in game_area.items():
if y == '►':
if game_area[x + 1] != '║':
game_area[x + 1] = y
game_area[x] = ' '
else:
game_over = True
break
if '▲' in list(game_area.values()):
for x,y in game_area.items():
if y == '▲':
if game_area[x - 60] != '═':
game_area[x - 60] = y
game_area[x] = ' '
else:
game_over = True
break
if '▼' in list(game_area.values()):
for x,y in game_area.items():
if y == '▼':
if game_area[x + 60] != '═':
game_area[x + 60] = y
game_area[x] = ' '
else:
game_over = True
break
if keyboard.is_pressed('up arrow'):
pass
if keyboard.is_pressed('left arrow'):
pass
if keyboard.is_pressed('right arrow'):
pass
if keyboard.is_pressed('down arrow'):
pass
if keyboard.is_pressed('space'):
break
#End
sleep(1)
os.system("cls")
continue
else:
break
start_game()

Moving a character around within a text file using Python

In order to implement a 'maze game' I need to move a character 'X' around a pre-built maze that exists as a .txt file. I have been trying to achieve this via various read/write/append methods but thus far have had no luck. The best I've been able to do is to read and print the coordinates of the 'X' in the text, but not manipulate it in any way. Any help in sorting out a means by which to move it would be greatly appreciated.
class game():
def __init__(self):
self.score = 0
self.exit = False
self.board = []
filepath = 'maze.txt'
with open(filepath,'r') as fp:
line = fp.readline()
while(line):
self.board.append(list(line))
line = fp.readline()
def show_board (self):
CSI="\x1b["
print ("----------")
print(CSI+"38;40m" + 'score:'+ str(self.score) + CSI + "0m")
for i in range(len(self.board)):
for j in range(len(self.board[i])):
if self.board[i][j] == "X":
CSI="\x1b["
print(CSI+"33;91m" + ''.join(self.board[i][j])+ CSI + "0m", end='', flush=True)
elif self.board[i][j] == "*":
CSI="\x1b["
print(CSI+"36;40m" + ''.join(self.board[i][j])+ CSI + "0m", end='', flush=True)
elif self.board[i][j] == "#":
CSI="\x1b["
print(CSI+"32;40m" + ''.join(self.board[i][j])+ CSI + "0m", end='', flush=True)
else:
CSI="\x1B["
print(CSI+"31;40m" + ''.join(self.board[i][j])+ CSI + "0m", end='', flush=True)
def detect_start_point(self):
for x in range(len(self.board)):
for y in range(len(self.board[x])):
if self.board[x][y] == 'X':
self.i,self.j = x,y
def move_player(self, move):
return
#you may remove return (if it is not required)
board = game()
board.detect_start_point()
board.show_board()
while(True): # you may need to change the condition
# get the selected action from the user here
move=input()
# implement
board.move_player(move)
board.show_board()
In theory I should only need to finish the:
def move_player(self, move):
return
#you may remove return (if it is not required)
But my attempts to produce even basic movement have failed.
For anyone interested in the particulars, the text file (referred to in the code as 'maze.txt') contains:
##############################
# X * # #
# ############ # #
# * # # #
# # * # #
# ###### #
# #
########## # #
#* # #
# # ##############
# # * # * #
# # #
# * #
##############################
Where the * are items to collect, and # is a gate to finish the level. I have no issue coding the conditions for those however.
You don't have to change in file but self.board.
First: you should remeber that self.board keeps data in [row][column] which means [y][x] instead of [x][y]
First you should check if new position is empty ie.
# move down (y+1)
if self.board[self.y+1][self.x] in (' ', "#', '*'):
Next you have to remove player from old place, move its x,y and put in new place
self.board[self.y][self.x] = ' ' # clear old place
self.y += 1
self.board[self.y][self.x] = 'X' # put in new place
Before move it could be good to keep old value in some variable - to use it later to receognize if player is standing on # or *
self.current = self.board[self.y][self.x]
I used io.StringIO to simulate file in memory so I could keep code and data in one file for test.
Only one direction: d moves down
text = """##############################
# X * # #
# ############ # #
# * # # #
# # * # #
# ###### #
# #
########## # #
#* # #
# # ##############
# # * # * #
# # #
# * #
##############################"""
import io
# --- constants ---
CSI = "\x1b["
C0 = "\x1b[0m" # color 0 (zero)
CX = "\x1b[33;91m" # color X (player)
CS = "\x1b[36;40m" # color * (star)
CA = "\x1b[32;40m" # color # (at)
C_ = "\x1b[31;40m" # color _ (floor)
# ---
class Game():
def __init__(self):
self.score = 0
self.exit = False
self.board = []
filepath = 'maze.txt'
#with open(filepath,'r') as fp:
with io.StringIO(text) as fp:
for line in fp:
self.board.append(list(line))
def show_board (self):
print ("----------")
print(CSI+"38;40m" + 'score:'+ str(self.score) + C0)
for row in self.board:
for item in row:
if item == "X":
print(CX + item + C0, end='', flush=True)
elif item == "*":
print(CS + item + C0, end='', flush=True)
elif item == "#":
print(CA + item + C0, end='', flush=True)
else:
print(C_ + item + C0, end='', flush=True)
def detect_start_point(self):
for y, row in enumerate(self.board):
for x, item in enumerate(row):
if item == 'X':
self.x, self.y = x, y
def move_player(self, move):
if move == 'd': # down
#if self.board[self.y+1][self.x] in (' ', "#', '*'):
if self.board[self.y+1][self.x] == ' ':
self.board[self.y][self.x] = ' ' # clear old place
self.y += 1
self.board[self.y][self.x] = 'X' # put in new place
return
#you may remove return (if it is not required)
board = Game()
board.detect_start_point()
board.show_board()
while(True): # you may need to change the condition
# get the selected action from the user here
move = input()
# implement
board.move_player(move)
board.show_board()
EDIT: version more univeral. Player can move u, d, l, r (up/down/left/right) but code is shorter.
text = """##############################
# X * # #
# ############ # #
# * # # #
# # * # #
# ###### #
# #
########## # #
#* # #
# # ##############
# # * # * #
# # #
# * #
##############################"""
import io
# --- constants ---
CSI = "\x1b["
C0 = "\x1b[0m" # color 0 (zero)
CX = "\x1b[33;91m" # color X (player)
CS = "\x1b[36;40m" # color * (star)
CA = "\x1b[32;40m" # color # (at)
C_ = "\x1b[31;40m" # color _ (floor)
# ---
class Game():
def __init__(self):
self.score = 0
self.exit = False
self.board = []
filepath = 'maze.txt'
#with open(filepath,'r') as fp:
with io.StringIO(text) as fp:
for line in fp:
self.board.append(list(line))
def show_board (self):
print ("----------")
print(CSI+"38;40m" + 'score:'+ str(self.score) + C0)
for row in self.board:
for item in row:
if item == "X":
print(CX + item + C0, end='', flush=True)
elif item == "*":
print(CS + item + C0, end='', flush=True)
elif item == "#":
print(CA + item + C0, end='', flush=True)
else:
print(C_ + item + C0, end='', flush=True)
def detect_start_point(self):
for y, row in enumerate(self.board):
for x, item in enumerate(row):
if item == 'X':
self.x, self.y = x, y
def move_player(self, move):
moved = False
if move == 'r': # right
new_x = self.x + 1
new_y = self.y
moved = True
if move == 'l': # left
new_x = self.x - 1
new_y = self.y
moved = True
if move == 'd': # down
new_x = self.x
new_y = self.y + 1
moved = True
if move == 'u': # up
new_x = self.x
new_y = self.y - 1
moved = True
if moved:
if self.board[new_y][new_x] in (' ', '#', '*'):
self.current = self.board[new_y][new_x]
self.board[self.y][self.x] = ' ' # clear old place
self.x = new_x
self.y = new_y
self.board[self.y][self.x] = 'X' # put in new place
if self.current == '*':
self.score += 10
self.current = ' ' # no more gold in current place
return
#you may remove return (if it is not required)
board = Game()
board.detect_start_point()
board.show_board()
while(True): # you may need to change the condition
# get the selected action from the user here
move = input()
# implement
board.move_player(move)
board.show_board()

In over my head: Debugging and many errors

I am trying to write a program for connect 4 but am having a lot of trouble getting past the directions. Everything under the comment, "#everything works up to here" works but then it all explodes and I have no idea even where to start to fix it.
#connect 4
import random
#define global variables
X = "X"
O = "O"
EMPTY = "_"
TIE = "TIE"
NUM_ROWS = 6
NUM_COLS = 8
def display_instruct():
"""Display game instructions."""
print(
"""
Welcome to the second greatest intellectual challenge of all time: Connect4.
This will be a showdown between your human brain and my silicon processor.
You will make your move known by entering a column number, 1 - 7. Your move
(if that column isn't already filled) will move to the lowest available position.
Prepare yourself, human. May the Schwartz be with you! \n
"""
)
def ask_yes_no(question):
"""Ask a yes or no question."""
response = None
while response not in ("y", "n"):
response = input(question).lower()
return response
def ask_number(question,low,high):
"""Ask for a number within range."""
#using range in Python sense-i.e., to ask for
#a number between 1 and 7, call ask_number with low=1, high=8
low=1
high=NUM_COLS
response = None
while response not in range (low,high):
response=int(input(question))
return response
def pieces():
"""Determine if player or computer goes first."""
go_first = ask_yes_no("Do you require the first move? (y/n): ")
if go_first == "y":
print("\nThen take the first move. You will need it.")
human = X
computer = O
else:
print("\nYour bravery will be your undoing... I will go first.")
computer = X
human = O
return computer, human
def new_board():
board = []
for x in range (NUM_COLS):
board.append([" "]*NUM_ROWS)
return board
def display_board(board):
"""Display game board on screen."""
for r in range(NUM_ROWS):
print_row(board,r)
print("\n")
def print_row(board, num):
"""Print specified row from current board"""
this_row = board[num]
print("\n\t| ", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num], "|", this_row[num],"|")
print("\t", "|---|---|---|---|---|---|---|")
# everything works up to here!
def legal_moves(board):
"""Create list of column numbers where a player can drop piece"""
legal=True
while not legal:
col = input("What column would you like to move into (1-7)?")
for row in range (6,0,1):
if (1 <= row <= 6) and (1 <= col <= 7) and (board[row][col]==" "):
board[row][col] = turn
legal = True
else:
print("Sorry, that is not a legal move.")
def human_move(board,human):
"""Get human move"""
try:
legals = legal_moves(board)
move = None
while move not in legals:
move = ask_number("Which column will you move to? (1-7):", 1, NUM_COLS)
if move not in legals:
print("\nThat column is already full, nerdling. Choose another.\n")
print("Human moving to column", move)
return move #return the column number chosen by user
except NameError:
print ("Only numbers are allowed.")
except IndexError:
print ("You can only select colums from 1-7.")
def get_move_row(turn,move):
for m in (NUM_COLS):
place_piece(turn,move)
display_board()
def computer_move ():
move= random.choice(legal)
return move
def place_piece(turn,move):
if this_row[m[move]]==" ":
this_row.append[m[move]]=turn
def winner(board):
# Check rows for winner
for row in range(6):
for col in range(3):
if (board[row][col] == board[row][col + 1] == board[row][col + 2] == board[row][col + 3]) and (board[row][col] != " "):
return [row][col]
# Check columns for winner
for col in range(6):
for row in range(3):
if (board[row][col] == board[row + 1][col] == board[row + 2][col] ==board[row + 3][col]) and (board[row][col] != " "):
return [row][col]
# Check diagonal (top-left to bottom-right) for winner
for row in range(3):
for col in range (4):
if (board[row][col] == board[row + 1][col + 1] == board[row + 2][col + 2] == board[row + 3][col + 3]) and (board[row][col] != " "):
return true
# Check diagonal (bottom-left to top-right) for winner
for row in range (5,2,-1):
for col in range (3):
if (board[row][col] == board[row - 1][col + 1] == board[row - 2][col + 2] == board[row - 3][col + 3]) and (board[row][col] != " "):
return [row][col]
# No winner
return False
def main():
display_instruct()
computer,human = pieces()
turn = X
board = new_board()
while not winner(board) and (" " not in board):
display_board(board)
if turn == human:
human_move(board,human)
get_move_row()
place_piece()
else:
computer_move(board,computer)
place_piece()
display_board(board)
turn = next_turn()
the_winner = winner(board)
congrat_winner(the_winner, computer, human)
#start the program
main ()
input ("\nPress the enter key to quit.")
For fun, here's an object-oriented refactorization. It's a bit long, but well documented and should be easy to understand.
I started with your code and split it into Board, Player, and Game classes, then derived Computer and Human classes from Player.
Board knows the shape and size of the rack, what moves are legal, and recognizes when wins and ties occur
Player has a name and knows how to choose (or prompt for) a legal move
Game has a Board and two Players and controls turn-taking and output
I'm not 100% happy with it - Board has a .board that is a list of list of string, but Game has a .board that is a Board; a bit of judicious renaming would be a good idea - but for an hour's work it's pretty solid.
Hope you find this educational:
# Connect-4
from itertools import cycle, groupby
from random import choice
from textwrap import dedent
import sys
# version compatibility shims
if sys.hexversion < 0x3000000:
# Python 2.x
inp = raw_input
rng = xrange
else:
# Python 3.x
inp = input
rng = range
def get_yn(prompt, default=None, truthy={"y", "yes"}, falsy={"n", "no"}):
"""
Prompt for yes-or-no input
Return default if answer is blank and default is set
Return True if answer is in truthy
Return False if answer is in falsy
"""
while True:
yn = inp(prompt).strip().lower()
if not yn and default is not None:
return default
elif yn in truthy:
return True
elif yn in falsy:
return False
def get_int(prompt, lo=None, hi=None):
"""
Prompt for integer input
If lo is set, result must be >= lo
If hi is set, result must be <= hi
"""
while True:
try:
value = int(inp(prompt))
if (lo is None or lo <= value) and (hi is None or value <= hi):
return value
except ValueError:
pass
def four_in_a_row(tokens):
"""
If there are four identical tokens in a row, return True
"""
for val,iterable in groupby(tokens):
if sum(1 for i in iterable) >= 4:
return True
return False
class Board:
class BoardWon (BaseException): pass
class BoardTied(BaseException): pass
EMPTY = " . "
HOR = "---"
P1 = " X "
P2 = " O "
VER = "|"
def __init__(self, width=8, height=6):
self.width = width
self.height = height
self.board = [[Board.EMPTY] * width for h in rng(height)]
self.tokens = cycle([Board.P1, Board.P2])
self.rowfmt = Board.VER + Board.VER.join("{}" for col in rng(width)) + Board.VER
self.rule = Board.VER + Board.VER.join(Board.HOR for col in rng(width)) + Board.VER
def __str__(self):
lines = []
for row in self.board:
lines.append(self.rowfmt.format(*row))
lines.append(self.rule)
lines.append(self.rowfmt.format(*("{:^3d}".format(i) for i in rng(1, self.width+1))))
lines.append("")
return "\n".join(lines)
def is_board_full(self):
return not any(cell == Board.EMPTY for cell in self.board[0])
def is_win_through(self, row, col):
"""
Check for any winning sequences which pass through self.board[row][col]
(This is called every time a move is made;
thus any win must involve the last move,
and it is faster to check just a few cells
instead of the entire board each time)
"""
# check vertical
down = min(3, row)
up = min(3, self.height - row - 1)
tokens = [self.board[r][col] for r in rng(row - down, row + up + 1)]
if four_in_a_row(tokens):
return True
# check horizontal
left = min(3, col)
right = min(3, self.width - col - 1)
tokens = [self.board[row][c] for c in rng(col - left, col + right + 1)]
if four_in_a_row(tokens):
return True
# check upward diagonal
down = left = min(3, row, col)
up = right = min(3, self.height - row - 1, self.width - col - 1)
tokens = [self.board[r][c] for r,c in zip(rng(row - down, row + up + 1), rng(col - left, col + right + 1))]
if four_in_a_row(tokens):
return True
# check downward diagonal
down = right = min(3, row, self.width - col - 1)
up = left = min(3, self.height - row - 1, col)
tokens = [self.board[r][c] for r,c in zip(rng(row - down, row + up + 1), rng(col + right, col - left - 1, -1))]
if four_in_a_row(tokens):
return True
# none of the above
return False
def legal_moves(self):
"""
Return a list of columns which are not full
"""
return [col for col,val in enumerate(self.board[0], 1) if val == Board.EMPTY]
def do_move(self, column):
token = next(self.tokens)
col = column - 1
# column is full?
if self.board[0][col] != Board.EMPTY:
next(self.move) # reset player token
raise ValueError
# find lowest empty cell (guaranteed to find one)
for row in rng(self.height-1, -1, -1): # go from bottom to top
if self.board[row][col] == Board.EMPTY: # find first empty cell
# take cell
self.board[row][col] = token
# did that result in a win?
if self.is_win_through(row, col):
raise Board.BoardWon
# if not, did it result in a full board?
if self.is_board_full():
raise Board.BoardTied
# done
break
class Player:
def __init__(self, name):
self.name = name
def get_move(self, board):
"""
Given the current board state, return the row to which you want to add a token
"""
# you should derive from this class instead of using it directly
raise NotImplemented
class Computer(Player):
def get_move(self, board):
return choice(board.legal_moves())
class Human(Player):
def get_move(self, board):
legal_moves = board.legal_moves()
while True:
move = get_int("Which column? (1-{}) ".format(board.width), lo=1, hi=board.width)
if move in legal_moves:
return move
else:
print("Please pick a column that is not already full!")
class Game:
welcome = dedent("""
Welcome to the second greatest intellectual challenge of all time: Connect4.
This will be a showdown between your human brain and my silicon processor.
You will make your move known by entering a column number, 1 - 7. Your move
(if that column isn't already filled) will move to the lowest available position.
Prepare yourself, human. May the Schwartz be with you!
""")
def __init__(self):
print(Game.welcome)
# set up new board
self.board = Board()
# set up players
self.players = cycle([Human("Dave"), Computer("HAL")])
# who moves first?
if get_yn("Do you want the first move? (Y/n) ", True):
print("You will need it...\n")
# default order is correct
else:
print("Your rashness will be your downfall...\n")
next(self.players)
def play(self):
for player in self.players:
print(self.board)
while True:
col = player.get_move(self.board) # get desired column
try:
print("{} picked Column {}".format(player.name, col))
self.board.do_move(col) # make the move
break
except ValueError:
print("Bad column choice - you can't move there")
# try again
except Board.BoardWon:
print("{} won the game!".format(player.name))
return
except Board.BoardTied:
print("The game ended in a stalemate")
return
def main():
while True:
Game().play()
if not get_yn("Do you want to play again? (Y/n) ", True):
break
if __name__=="__main__":
main()

Categories

Resources