There are a few changes you can make here to make this a little easier to add to. A couple major items to look at:
Player
object's responsibility to check for legal plays? This is likely a better fit for the Board
object. In fact, it is hard to imagine the role a Player
object fulfils in this structure, since you are the player and you are handling all the I/O in your controller/event loop, which is currently just in the main.Board
object likely doesn't need to understand the Player
object, it only needs to provide its state and methods to update its state. Later, then you add a visual component to this, the visual component, maybe called View
, will request the game state from the Board
and draw it to screen, providing visually rich user input controls, and then forwarding those inputs to your Board
for updates.Board
object into your constructor for your Player
and then save it to its internal state to reference whenever you need to make an update.Player
object for every input. I recommend moving the Player
object initialization outside of the loop and referencing the same object over and over. This does require reworking the Player
class a little to accept inputs from a function call instead of at initialization.To address just the question as asked, I recommend changes like the following:
main.py
from Board import *
from Player import *
if __name__ == "__main__":
b = Board()
p = Player(b) # Edited
while True:
old_x_input = int(input("x: "))
old_y_input = int(input("y: "))
new_x_input = int(input("move to newX: "))
new_y_input = int(input("move to newY: "))
# print(b.move()) # Edited
p.move(old_x_input, old_y_input, new_x_input, new_y_input) # Added
print(b.board_flaeche) # Edited
Player.py
# from numpy import np
# import main # Edited
from Board import *
# from x, y to newX, newY
class Player:
def __init__(self, board): # Edited
self.board = board # Added
# Edited
# self.old_x = old_x
# self.old_y = old_y
# self.new_x = new_x
# self.new_y = new_y
#
# def getOldX(self):
# return self.old_x
#
# def getOldY(self):
# return self.old_y
#
# def getNewX(self):
# return self.new_x
#
# def getNewY(self):
# return self.new_y
# switching positions - not finished yet
def move(self, old_x, old_y, new_x, new_y): # Edited
# Board.board_flaeche[x][y] = Board.board_flaeche[newX][newY]
self.legal_length(old_x, old_y, new_x, new_y) # Added
# Still need to add something here, such as self.board.move(old_x, old_y, new_x, new_y)
# ToDo:
# Task: Write a method legal_length where old_x, old_y, new_x, new_y
# must be checked if they are within the self.board_area[i][j] range
def legal_length(self, old_x, old_y, new_x, new_y): # Edited
# ToDo Problem:
# how do I get the attributes from another class in Python without creating an instance each time
pass
# Added section
if old_x >= self.board.x or old_x < 0:
raise ValueError(f'Provided old x is outside of range! {self.board.x} > {old_x} >= 0')
if old_y >= self.board.y or old_y < 0:
raise ValueError(f'Provided old y is outside of range! {self.board.y} > {old_y} >= 0')
if new_x >= self.board.x or new_x < 0:
raise ValueError(f'Provided new x is outside of range! {self.board.x} > {new_x} >= 0')
if new_y >= self.board.y or new_y < 0:
raise ValueError(f'Provided new y is outside of range! {self.board.y} > {new_y} >= 0')
# / Added section
def setPosition(self, new_x, new_y):
pass
Board.py
import numpy as np
from Player import *
class Board:
def __init__(self, x=None, y=None):
if x is None:
x = int(input("Enter the width of the board: "))
if y is None:
y = int(input("Enter the height of the board: "))
self.x = x
self.y = y
self.board_flaeche = np.zeros((x, y), dtype=str)
fruits_liste = ["Y", "B", "P"]
for i in range(x):
for j in range(y):
self.board_flaeche[i][j] = np.random.choice(fruits_liste)
print(self.board_flaeche)
# do I need it??
# self.player = Player(0, 0, 0, 0) # Edited
# Recommend this function have the following signature:
# def move(self, old_x, old_y, new_x, new_y):
def move(self):
old_x = self.player.getOldX()
old_y = self.player.getOldY()
new_x = self.player.getNewX()
new_y = self.player.getNewY()
# Update position of the player instance
# self.player.setPosition(new_x, new_y)
But overall, to address some of my other comments, I would recommend a larger rework to better divide the responsibilities of each class to look something like this:
import numpy as np
class Board:
fruit_lists = ['Y', 'B', 'P']
def __init__(self, x_size: int = None, y_size: int = None):
if x_size is None:
x_size = int(input("Enter the width of the board: "))
if y_size is None:
y_size = int(input("Enter the height of the board: "))
self.x_size: int = x_size
self.y_size: int = y_size
rng = np.random.default_rng()
self.board_flaeche = rng.choice(Board.fruit_lists, size=(self.x_size, self.y_size))
def get_state(self):
return self.board_flaeche
def print_board(self):
board_str = ''
for row in self.board_flaeche:
for col in row:
board_str += f' {col} '
board_str += '\n'
print(board_str)
def validate_move(self, old_x: int, old_y: int, new_x: int, new_y: int):
if old_x >= self.x_size or old_x < 0:
raise ValueError(f'Provided old x is outside of range! {self.x_size} > {old_x} >= 0')
if old_y >= self.y_size or old_y < 0:
raise ValueError(f'Provided old y is outside of range! {self.y_size} > {old_y} >= 0')
if new_x >= self.x_size or new_x < 0:
raise ValueError(f'Provided new x is outside of range! {self.x_size} > {new_x} >= 0')
if new_y >= self.y_size or new_y < 0:
raise ValueError(f'Provided new y is outside of range! {self.y_size} > {new_y} >= 0')
def move(self, old_x: int, old_y: int, new_x: int, new_y: int):
self.validate_move(old_x, old_y, new_x, new_y)
# Valid input, now swap them
source_fruit = self.board_flaeche[old_x, old_y]
target_fruit = self.board_flaeche[new_x, new_y]
self.board_flaeche[old_x, old_y] = target_fruit
self.board_flaeche[new_x, new_y] = source_fruit
class Player:
def __init__(self):
pass
def get_input(self):
old_x_input = int(input("x: "))
old_y_input = int(input("y: "))
new_x_input = int(input("move to newX: "))
new_y_input = int(input("move to newY: "))
return old_x_input, old_y_input, new_x_input, new_y_input
class Game:
def __init__(self):
self.board = None
self.player = None
def start_new_game(self, x_size: int = None, y_size: int = None):
self.board = Board(x_size, y_size)
self.player = Player()
while True:
self.board.print_board()
old_x, old_y, new_x, new_y = self.player.get_input()
try:
self.board.move(old_x, old_y, new_x, new_y)
except ValueError:
print('Bad move requested! Try again')
if __name__ == '__main__':
game = Game()
game.start_new_game()
With this rework, each class is only responsible for one thing. The Board
is only responsible for keeping track of the board, making updates to it, printing itself to console etc. The Player
is only responsible for handling player interactions. The Game
is only responsible for setting up and running the event loop. Now as you add functionality it'll be much easier to stay organized, as each object is more independent, and once you add a graphical component to it, you'll only need to update the Game
class without really worrying about what's in the other classes.