Created an endless loop, but can't tell why it is endless - python

So I am creating a card game in python using classes. I got it all set up to the point where it should work. But when I ran it it just simply never stops running. I am not really sure how to make this code minimal and reproduceable, because I do not know where the issue is at.
Here is the code i have written.
It has to be in the play_uno() class object.
""" UNO Simulator """
import random
class card():
def __init__(self, value, color, wild):
'''
An UNO deck consists of 108 cards, of which there are 76 Number cards,
24 Action cards and 8 Wild cards. UNO cards have four color "suits",
which are red, yellow, blue and green.
'''
self.card_nums = ['0','1','2','3','4','5','6','7','8','9']
self.card_colors = ['red','blue','green','yellow']
self.spec_cards = ['draw2','reverse','skip']
self.wild_cards = ['wild','wild_draw_4']
if wild == False:
self.value = self.card_nums[value]
self.color = self.card_colors[color]
elif wild == 'special':
self.value = self.spec_cards[value]
self.color = self.card_colors[color]
elif wild == True:
self.value = self.wild_cards[value]
self.color = None
class generate_deck():
def __init__(self):
self.number_cards = self.get_num_cards()
self.special_cards = self.get_special_cards()
self.wild_cards = self.get_wild_cards()
self.cards = self.number_cards + self.special_cards + self.wild_cards
random.shuffle(self.cards)
def get_num_cards(self):
# only one zero per color
with_zeroes = [card(i,j,False) for i in range(10) for j in range(4)]
no_zeroes = [card(i,j,False) for i in range(1,10) for j in range(4)]
return no_zeroes + with_zeroes
def get_wild_cards(self):
wild_draw4s = [card(i,None,True) for i in range(2) for x in range(2)]
wilds = [card(i,None,True) for i in range(2) for x in range(2)]
return wilds + wild_draw4s
def get_special_cards(self):
return [card(i,j,'special') for i in range(3) for j in range(4) for x in range(2)]
class player():
def __init__(self, name):
self.wins = 0
self.name = name
self.cheater = False
self.cards = ''
self.turn = 0
self.uno = 0
class play_uno():
def __init__(self, num_players = 3, num_cheaters = 0, cards_per_player = 5):
# get started
self.rules = 'default'
self.status = 'ongoing'
self.deck = generate_deck().cards
self.played_cards = []
self.dro = 0
self.direction = 0
self.top_card = self.deck.pop() # random card as first card to play on
self.tot_turns = 0
# generate players, make cheaters later
self.players = [player('player' + str(i)) for i in range(num_players + num_cheaters)]
# give each player 7 cards to start
for _player_ in self.players:
_player_.cards = [self.draw_card() for i in range(cards_per_player)]
# start playing turns in order
# do not know how to reverse yet
"""
Right now it is endless for some reason.
"""
while self.status == 'ongoing':
for _player in self.players:
self.turn(_player)
def draw_card(self):
# draws random card from deck instead of card on top
if len(self.deck) == 0:
self.re_shuffle()
self.dro += 1
return self.deck.pop()
def re_shuffle(self):
self.deck = self.played_cards
random.shuffle(self.deck)
self.played_cards = []
return self.deck, self.played_cards
def play_card(self, player_cards, _card):
self.top_card = _card
return player_cards.remove(_card), self.played_cards.append(_card), self.top_card
def game_over(self, winner):
winner.wins += 1
self.game_status = 'over'
def turn(self, _player):
played = False
# check if someone played wild card last turn
if self.top_card.value in card(1,2,None).wild_cards:
self.top_card.color = random.choice(card.card_colors)
if self.top_card.value == 'wild_draw_4':
_player.cards += [self.draw_card() for i in range(4)]
self.tot_turns += 1
return _player
# check for special cards
elif self.top_card.value in card(1,2,None).spec_cards:
if self.top_card.value == 'draw2':
_player.cards += [self.draw_card() for i in range(4)]
self.tot_turns += 1
return _player
# for now we are treating reverse cards like skips
elif self.top_card.value == 'reverse' or self.top_card.value == 'skip':
played = True
self.tot_turns += 1
return _player
# If its a normal card, or regular wild
if played == False:
for _card in _player.cards:
if _card.color == self.top_card.color:
self.play_card(_player.cards, _card)
played = True
break
elif _card.value == self.top_card.value:
self.play_card(_player.cards, _card)
played = True
break
# if the player cannot play at all
# rn they just move on if they have to draw,
# cant play the card they just drew.
if played == False:
_player.cards += [self.draw_card()]
played = True
self.tot_turns += 1
# check if the player won or not
if len(_player.cards) == 0:
self.game_over(_player)
elif len(_player.cards) == 1:
_player.uno += 1
return _player.cards

In the function turn in the play_uno class you are checking for certain wild/special cards. If the value is reverse, for example, the function hits a return _player line which ends the execution of the function and the player is unable to play another card.
Move the return statements to the end of the function if you want to ensure the rest of the code is run.

I did not run the code, but I think you should test a player's number of cards before drawing again. Like this:
if len(_player.cards) == 0:
self.game_over(_player)
elif len(_player.cards) == 1:
_player.uno += 1
if played == False:
_player.cards += [self.draw_card()]
played = True
self.tot_turns += 1
Or are the rules of this game different? I sincerely don't remember them anymore.

The while loop at the end of play_uno's __init__ checks for status, which never changes.
The loop calls turn on each of players every iteration. You must change status somewhere or you must put an if ...: break in the while loop (e.g. if not _player.cards).
EDIT: It appears that you meant self.status instead of self.game_status, in game_over. Try changing game_status to status there.

Related

Implementing an aima-python agent

I am using aima-python to implement a covid cleaning agent, this agent will go to each square and spray. If there is a an object present in the cell it will still spray such as table, chair etc. However, if there is a person in the next cell the robot cannot cross paths with the cell and must avoid entering a cell with a human. I have implemented most of the code and I think the code does as described above, I am however getting a 'TypeError: 'chair' object is not callable'. I can't see why I would be getting this, this is the same for all object present in my environment. I would really appreciate some assistance with this issue.(This program runs on jupyter notebook)
from agents import *
from random import choice
class DisinfectingRobot(Agent):
location = [0,1]
direction = Direction("down")
def moveforward(self, success=True):
'''moveforward possible only if success (i.e. valid destination location)'''
if not success:
return
if self.direction.direction == Direction.R:
self.location[0] += 1
elif self.direction.direction == Direction.L:
self.location[0] -= 1
elif self.direction.direction == Direction.D:
self.location[1] += 1
elif self.direction.direction == Direction.U:
self.location[1] -= 1
def turn(self, d):
if isinstance(thing,person):
self.direction = self.direction + d
return True
return False
def spray(self, thing):
'''returns True upon success or False otherwise'''
if isinstance(thing, Food):
return True
return False
def program(percepts):
'''Returns an action based on it's percepts'''
for p in percepts:
if isinstance(p, chair):
return 'spray'
elif isinstance(p, table):
return 'spray'
elif isinstance(p,person):
turn = False
choice = random.choice((1,2))
else:
choice = random.choice((1,2,3,4))
if isinstance(p,Bump): # then check if you are at an edge and have to turn
turn = False
choice = random.choice((1,2));
else:
choice = random.choice((1,2,3,4)) # 1-right, 2-left, others-forward
if choice == 1:
return 'turnright'
elif choice == 2:
return 'turnleft'
else:
return 'moveforward'
class chair(Thing):
pass
class table(Thing):
pass
class person(Thing):
pass
class lab2D(GraphicEnvironment):
def percept(self, agent):
'''return a list of things that are in our agent's location'''
things = self.list_things_at(agent.location)
loc = copy.deepcopy(agent.location) # find out the target location
#Check if agent is about to bump into a wall
if agent.direction.direction == Direction.R:
loc[0] += 1
elif agent.direction.direction == Direction.L:
loc[0] -= 1
elif agent.direction.direction == Direction.D:
loc[1] += 1
elif agent.direction.direction == Direction.U:
loc[1] -= 1
if not self.is_inbounds(loc):
things.append(Bump())
return things
def execute_action(self, agent, action):
'''changes the state of the environment based on what the agent does.'''
if action == 'turnright':
print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))
agent.turn(Direction.R)
elif action == 'turnleft':
print('{} decided to {} at location: {}'.format(str(agent)[1:-1], action, agent.location))
agent.turn(Direction.L)
elif action == 'moveforward':
print('{} decided to move {}wards at location: {}'.format(str(agent)[1:-1], agent.direction.direction, agent.location))
agent.moveforward()
elif action == "spray":
items = self.list_things_at(agent.location, tclass=chair)
if len(items) != 0:
if agent.spray(items[0]):
print('{} sprayed {} at location: {}'
.format(str(agent)[1:-1], str(items[0])[1:-1], agent.location))
elif action == "turn":
items = self.list_things_at(agent.location, tclass=person)
if len(items) != 0:
if agent.turn(items[0]):
print('{} turned because {} at location: {}'
.format(str(agent)[1:-1], str(items[0])[1:-1], agent.location))
lab = lab2D(5,5, color={'DisinfectingRobot': (128,128,128), 'chair': (153, 76, 0),
'table': (230, 115, 40), 'person': (51,25,0)})
robot = DisinfectingRobot(program)
lab.add_thing(robot, [0,0])
chair = chair()
table = table()
person = person()
lab.add_thing(robot, [0,0])
lab.add_thing(table, [1,2])
lab.add_thing(chair, [0,1])
lab.add_thing(person,[5,1])
moretable = table()
morechair = chair()
moreperson = person
lab.add_thing(moretable, [2,4])
lab.add_thing(morechair, [4,3])
lab.add_thing(moreperson,[3,3])
print("robot started at [0,0], facing down. Time to disinfect!")
lab.run(100)

Mancala AI completes the game and wins in one round instead of playing one turn

I'm working on a Mancala game where players get to play against an AI. the code is complete, the Mancala game functionality is found within the Mancala_helpers, the AI algorithm is a MinMax tree and is found in the MinMax file, and finally the game itself. everything runs fine except for when the AI plays, if the AI starts the game immediately ends, it moves all the rocks from its pits in one round. and if I start I can only play one move before it does the same. I cannot understand what's happening, at first, I thought maybe I had a problem within the function of mancala helpers where they did not switch turns properly and the AI kept playing. but I ran multiple tests and that part is working fine. I cant identify the issue, help, please. if anyone also has suggestions for a better evaluation function then that would be great. thanks
--------------------------Mancala helpers--------------
# TODO: implement pad(num)
# Return a string representation of num that is always two characters wide.
# If num does not already have two digits, a leading "0" is inserted in front.
# This is called "padding". For example, pad(12) is "12", and pad(1) is "01".
# You can assume num is either one or two digits long.
def pad(num: int) -> str:
x = str(num)
if len(x) > 1:
return x
else:
return "0"+x
# TODO: implement pad_all(nums)
# Return a new list whose elements are padded versions of the elements in nums.
# For example, pad_all([12, 1]) should return ["12", "01"].
# Your code should create a new list, and not modify the original list.
# You can assume each element of nums is an int with one or two digits.
def pad_all(nums: list) -> list:
x = []
for i in nums:
x.append(pad(i))
return x
# TODO: implement initial_state()
# Return a (player, board) tuple representing the initial game state
# The initial player is player 0.
# board is list of ints representing the initial mancala board at the start of the game.
# The list element at index p should be the number of gems at position p.
def initial_state() -> tuple:
return (0, [4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0])
# TODO: implement game_over(state)
# Return True if the game is over, and False otherwise.
# The game is over once all pits are empty.
# Your code should not modify the board list.
# The built-in functions "any" and "all" may be useful:
# https://docs.python.org/3/library/functions.html#all
def game_over(state: tuple) -> bool:
lst = state[1]
if (lst[0] == lst[1] == lst[2] == lst[3] == lst[4] == lst[5] == 0) or (lst[7] == lst[8] == lst[9] == lst[10] == lst[11] == lst[12] == 0):
return True
else:
return False
# TODO: implement valid_actions(state)
# state is a (player, board) tuple
# Return a list of all positions on the board where the current player can pick up gems.
# A position is a valid move if it is one of the player's pits and has 1 or more gems in it.
# For example, if all of player's pits are empty, you should return [].
# The positions in the returned list should be ordered from lowest to highest.
# Your code should not modify the board list.
def valid_actions(state: tuple) -> list:
actions = []
lst = state[1]
player = state[0]
if player == 0:
for i in range(6):
if lst[i] > 0:
actions.append(i)
return actions
else:
for i in range(6):
if lst[i+7] >0: actions.append(i+7)
return actions
# TODO: implement mancala_of(player)
# Return the numeric position of the given player's mancala.
# Player 0's mancala is on the right and player 1's mancala is on the left.
# You can assume player is either 0 or 1.
def mancala_of(player: int) -> int:
if player ==0: return 6
elif player==1: return 13
# TODO: implement pits_of(player)
# Return a list of numeric positions corresponding to the given player's pits.
# The positions in the list should be ordered from lowest to highest.
# Player 0's pits are on the bottom and player 1's pits are on the top.
# You can assume player is either 0 or 1.
def pits_of(player: int) -> list:
if player ==0:
return [0,1,2,3,4,5]
elif player==1:
return [7,8,9,10,11,12]
# TODO: implement player_who_can_do(move)
# Return the player (either 0 or 1) who is allowed to perform the given move.
# The move is allowed if it is the position of one of the player's pits.
# For example, position 2 is one of player 0's pits.
# So player_who_can_do(2) should return 0.
# You can assume that move is a valid position for one of the players.
def player_who_can_do(move: int) -> int:
if move in [0,1,2,3,4,5] : return 0
elif move in [7,8,9,10,11,12]: return 1
# TODO: implement opposite_from(position)
# Return the position of the pit that is opposite from the given position.
# Check the pdf instructions for the definition of "opposite".
def opposite_from(position: int) -> int:
d_p_1 = {}
d_p_1[0]=12
d_p_1[1]=11
d_p_1[2]=10
d_p_1[3]=9
d_p_1[4]=8
d_p_1[5]=7
d_p_1[7]=5
d_p_1[8]=4
d_p_1[9]=3
d_p_1[10]=2
d_p_1[11]=1
d_p_1[12]=0
return d_p_1[position]
# TODO: implement play_turn(move, board)
# Return the new game state after the given move is performed on the given board.
# The return value should be a tuple (new_player, new_board).
# new_player should be the player (0 or 1) whose turn it is after the move.
# new_board should be a list representing the new board state after the move.
#
# Parameters:
# board is a list representing the current state of the game board before the turn is taken.
# move is an int representing the position where the current player picks up gems.
# You can assume that move is a valid move for the current player who is taking their turn.
# Check the pdf instructions for the detailed rules of taking a turn.
#
# It may be helpful to use several of the functions you implemented above.
# You will also need control flow such as loops and if-statements.
# Lastly, the % (modulo) operator may be useful:
# (x % y) returns the remainder of x / y
# from: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
def play_turn(move: int, board: list) -> tuple:
player = player_who_can_do(move)
new_board = board
gems = new_board[move]
new_board[move] = 0
hasht = {}
hasht[0] =1
hasht[1] = 0
if player ==0:
x =0
offset = 1
gems_counter = gems
for i in range(gems):
if i + move + offset == 13: offset += 1
elif (i+move+offset) - 14 == 13: offset += 1
if i + move +offset > 13:
gem_position = (i+move+offset) - 14
else:
gem_position = i + move + offset
new_board[gem_position] += 1
gems_counter -= 1
if gems_counter ==0 and gem_position==6: x = 1
if gems_counter==0 and gem_position in pits_of(0) and new_board[gem_position] == 1 and new_board[opposite_from(gem_position)] > 0:
gems_from_myside = new_board[gem_position]
gems_from_opside = new_board[opposite_from(gem_position)]
new_board[6] = gems_from_myside+gems_from_opside
new_board[gem_position] = 0
new_board[opposite_from(gem_position)] = 0
return (hasht[x],new_board)
if player ==1:
x_2 = 1
offset = 1
gems_counter2 = gems
for i in range(gems):
if i + move + offset == 6: offset += 1
elif (i+move+offset) - 14 == 6: offset += 1
if i + move +offset > 13:
gem_position = (i+move+offset) - 14
else:
gem_position = i + move + offset
new_board[gem_position] += 1
gems_counter2 -= 1
if gems_counter2 == 0 and gem_position == 13: x_2 = 0
if gems_counter2==0 and gem_position in pits_of(1) and new_board[gem_position] == 1 and new_board[opposite_from(gem_position)] > 0:
gems_from_myside = new_board[gem_position]
gems_from_opside = new_board[opposite_from(gem_position)]
new_board[13] = gems_from_myside+gems_from_opside
new_board[gem_position] = 0
new_board[opposite_from(gem_position)] = 0
return (hasht[x_2],new_board)
# TODO: implement clear_pits(board)
# Return a new list representing the game state after clearing the pits from the board.
# When clearing pits, any gems in a player's pits get moved to that player's mancala.
# Check the pdf instructions for more detail about clearing pits.
def clear_pits(board: list) -> list:
length = len(board)
middle_index = length // 2
first_half = board[:middle_index]
second_half = board[middle_index:]
for i in range(6):
first_half[6] += first_half[i]
first_half[i]=0
second_half[6] += second_half[i]
second_half[i] = 0
return (first_half+second_half)
# This one is done for you.
# Plays a turn and clears pits if needed.
def perform_action(action, state):
player, board = state
new_player, new_board = play_turn(action, board)
if 0 in [len(valid_actions((0, new_board))), len(valid_actions((1, new_board)))]:
new_board = clear_pits(new_board)
return new_player, new_board
# TODO: implement score_in(state)
# state is a (player, board) tuple
# Return the score in the given state.
# The score is the number of gems in player 0's mancala, minus the number of gems in player 1's mancala.
def score_in(state: tuple) -> int:
lst = state[1]
return lst[6] - lst[13]
# TODO: implement is_tied(board)
# Return True if the game is tied in the given board state, False otherwise.
# A game is tied if both players have the same number of gems in their mancalas.
# You can assume all pits have already been cleared on the given board.
def is_tied(board: list) -> bool:
if board[mancala_of(0)] - board[mancala_of(1)] == 0: return True
else: return False
# TODO: implement winner_of(board)
# Return the winning player (either 0 or 1) in the given board state.
# The winner is the player with more gems in their mancala.
# You can assume it is not a tied game, and all pits have already been cleared.
def winner_of(board: list) -> int:
if board[mancala_of(0)] > board[mancala_of(1)]: return 0
elif board[mancala_of(0)] < board[mancala_of(1)]: return 1
# TODO: implement string_of(board)
def string_of(board: list) -> str:
new_board = pad_all(board)
return '\n {} {} {} {} {} {}\n {} {}\n {} {} {} {} {} {}\n'.format(new_board[12],new_board[11],new_board[10],new_board[9],new_board[8],new_board[7],new_board[13],new_board[6],new_board[0],new_board[1],new_board[2],new_board[3],new_board[4],new_board[5])
-----------------------MinMax AI-------------------------------------------------------------
from os import stat
import numpy as np
from mancala_helpers import *
# A simple evaluation function that simply uses the current score.
def simple_evaluate(state):
return score_in(state)
# TODO
# Implement a better evaluation function that outperforms the simple one.
def better_evaluate(state):
#lst = state[1]
#return score_in(state)/2
return None
# depth-limited minimax as covered in lecture
def minimax(state, max_depth, evaluate):
# returns chosen child state, utility
# base cases
if game_over(state): return None, score_in(state)
if max_depth == 0: return None, evaluate(state)
# recursive case
children = [perform_action(action, state) for action in valid_actions(state)]
results = [minimax(child, max_depth-1, evaluate) for child in children]
_, utilities = zip(*results)
player, board = state
if player == 0: action = np.argmax(utilities)
if player == 1: action = np.argmin(utilities)
return children[action], utilities[action]
# runs a competitive game between two AIs:
# better_evaluation (as player 0) vs simple_evaluation (as player 1)
def compete(max_depth, verbose=True):
state = initial_state()
while not game_over(state):
player, board = state
if verbose: print(string_of(board))
if verbose: print("--- %s's turn --->" % ["Better","Simple"][player])
state, _ = minimax(state, max_depth, [better_evaluate, simple_evaluate][player])
score = score_in(state)
player, board = state
if verbose:
print(string_of(board))
print("Final score: %d" % score)
return score
if __name__ == "__main__":
score = compete(max_depth=4, verbose=True)
----------------------------------------playing the game---------------------------
from os import stat
from mancala_helpers import *
from mancala_minimax import minimax, simple_evaluate
def get_user_action(state):
actions = list(map(str, valid_actions(state)))
player, board = state
prompt = "Player %d, choose an action (%s): " % (player, ",".join(actions))
while True:
action = input(prompt)
if action in actions: return int(action)
print("Invalid action, try again.")
if __name__ == "__main__":
max_depth = 1
state = initial_state()
while not game_over(state):
player, board = state
print(string_of(board))
if player == 0:
action = get_user_action(state)
state = perform_action(action, state)
else:
print("--- AI's turn --->")
#print(string_of(board))
print(state)
print(max_depth)
state, _ = minimax(state, max_depth, simple_evaluate)
#print(string_of(board))
player, board = state
print(string_of(board))
if is_tied(board):
print("Game over, it is tied.")
else:
winner = winner_of(board)
print("Game over, player %d wins." % winner)
the entire problem was in the mancala helper file. in the function play_turn() the 2nd line new_board = board. this was causing the issue because the original state should be immutable to work properly. any changes on new_board were also affecting board. the following new_board = copy.deepcopy(board) fixed everything. the function copy.deepcopy() creates a completely new copy, any changes applied to one of them does not affect the other.

Creating the war(card) game using OOP in Python

I am trying to create the war card game where 2 players draw a card each on the table, and the player whose card has max value gets both the cards, if the card values are equal(called war condition) each player draws 5 cards, and the value of the last card among these 5 cards are compared and works similarly as above. A player wins when either the other player runs out of cards or if in war condition the other player has less than 5 cards.
My questions:
When I am running the game logic sometimes it runs but sometimes it gets stuck in an infinite loop. Why?
I can notice that when the total number of cards for both players at all times should be 52, but here it is decreasing. Why? (I can see that the cards are getting lost every time the game goes into war condition, but I am not able to understand why that is happening, since I am adding all the 10 cards to the player whose card has a greater value.)
I have tried another method where I assume that players are always at war and then approach it, which works. But I want to understand why, if I break it into steps which I am trying to do here, is it not working?
Code:
import random
suits = ['Hearts','Clubs','Spades','Diamonds']
ranks = ['Two','Three','Four','Five','Six','Seven','Eight','Nine','Ten','Jack','Queen','King','Ace']
values = {'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5, 'Six' : 6 , 'Seven' :7, 'Eight': 8,'Nine':9,
'Ten':10,'Jack':11, 'Queen': 12,'King': 13,'Ace':14 }
classes:
class Card():
def __init__(self,suit,rank):
self.suit = suit
self.rank = rank
self.value = values[rank]
#string method
def __str__(self):
return self.rank + ' of ' + self.suit
class Deck():
def __init__(self):
self.all_cards = [] #list of objects
for suit in suits:
for rank in ranks:
#create card object
created_card = Card(suit,rank)
self.all_cards.append(created_card)
def shuffle(self): #method to shuffle the card
random.shuffle(self.all_cards)
def deal_one(self):
return self.all_cards.pop() #we want the last card from deck
class Player():
def __init__(self,name):
self.name = name
self.all_cards = []
def remove_one(self):
return self.all_cards.pop(0) #to remove card from beginning of the list
def add_cards(self,new_cards):
if type(new_cards) == type([]):
self.all_cards.extend(new_cards)
else:
self.all_cards.append(new_cards)
def __str__(self):
return f'Player {self.name} has {len(self.all_cards)} cards.'
And below is the final game logic:
#create 2 new instances of the player class
player1_name = input('Enter the name of Player1: ')
player2_name = input('Enter the name of Player2: ')
player1 = Player(player1_name)
player2 = Player(player2_name)
newdeck = Deck()
newdeck.shuffle()
#splitting the deck among the two players - alternate card from deck goes to each player respectively
for i in range(0,len(newdeck.all_cards)-1,2):
player1.add_cards(newdeck.all_cards[i])
player2.add_cards(newdeck.all_cards[i+1])
#for x in range(26):
# player1.add_cards(newdeck.deal_one())
#player2.add_cards(newdeck.deal_one())
print(player1)
print(player2)
game_status = True
round_num = 0
while game_status == True:
round_num +=1
print(f"Round {round_num}")
if len(player1.all_cards) == 0:
print('Player 1 out of cards'+ player2.name + 'Wins!')
game_status = False
break
if len(player2.all_cards) == 0:
print('Player 2 out of cards' + player1.name + 'Wins!')
game_status = False
break
else:
player_cards = []
player1_card = player1.remove_one()
player2_card = player2.remove_one()
player_cards.append(player1_card)
player_cards.append(player2_card)
print('player1_card_value: ',player1_card.value)
print('')
print('player2_card_value: ',player2_card.value)
print('')
print(player1)
print('')
print(player2)
at_war = True
if player1_card.value == player2_card.value:
while at_war == True:
player1_list = []
player2_list = []
card_list = []
if len(player1.all_cards) < 5:
print('Player 2 won')
game_status = False
at_war = False
break
elif len(player2.all_cards) <5:
print('Player 1 won')
game_status = False
at_war = False
break
if len(player1.all_cards) >= 5 and len(player2.all_cards) >= 5:
for i in range(5):
player1_list.append(player1.remove_one())
player2_list.append(player2.remove_one())
card_list.extend(player1_list)
card_list.extend(player2_list)
print("CARD LIST LEN", len(card_list))
if player1_list[0].value > player2_list[0].value:
player1.add_cards(card_list)
at_war = False
break
elif player1_list[0].value < player2_list[0].value:
player2.add_cards(card_list)
#player2.add_cards(player1_list)
at_war = False
break
else:
at_war = True
elif player1_card.value > player2_card.value:
player1.add_cards(player_cards)
#player1.add_cards(player2_cards)
#print('p1>p2', player1)
elif player2_card.value > player1_card.value:
player2.add_cards(player_cards)
print(player1)
print(player2)
if len(player1.all_cards) == 0:
print('Player 2 won')
elif len(player2.all_cards) == 0:
print('Player 1 won')
Output: When it was stuck in an infinite loop:(You can see the total number of cards is now 34 instead of 52.)

Micro:bit classes instead of global variables - memory allocation error in micropython

I've just created my first little truth or dare/spin the bottle game for a senior/high school coding club on the Micro:bit. I would like to introduce using oop/classes/objects instead of (dreaded) global vars. The game works great on emulators such as https://create.withcode.uk/, but on the Micro:bit itself I encounter memory allocation errors as soon as I try to put pretty much anything into classes. Is the microbit's 16KB of RAM not enough? Or am I declaring classes incorrectly?
Coming from front-end and a bit of PHP/SQL, I'm a Python/memory knowledge noob so any help is appreciated.
If I use global vars in each of the functions it works without issue.
Here is the code:
from microbit import *
import random
#timer current function
#Global variables
class game:
gsTime = 3000
timerPrev = 0
numOfPlayers = 0
maxPlayers = 8
stage = 'start'
minSpinTime = 3000
class player:
#Which player is currently selected
selected = 0
#Player position display
def pos1(): display.set_pixel(2, 4, 9)
def pos2(): display.set_pixel(2, 0, 9)
def pos3(): display.set_pixel(0, 2, 9)
def pos4(): display.set_pixel(4, 2, 9)
def pos5(): display.set_pixel(0, 4, 9)
def pos6(): display.set_pixel(4, 0, 9)
def pos7(): display.set_pixel(0, 0, 9)
def pos8(): display.set_pixel(4, 4, 9)
#Array of all player positions
positions = [
[pos1, 1, Image.ARROW_S],
[pos2, 5, Image.ARROW_N],
[pos3, 3, Image.ARROW_W],
[pos4, 7, Image.ARROW_E],
[pos5, 2, Image.ARROW_SW],
[pos6, 6, Image.ARROW_NE],
[pos7, 4, Image.ARROW_NW],
[pos8, 8, Image.ARROW_SE]
]
positionsOrdered = []
class buttons:
pressed = False
class spinner:
completeSpins = 0
isCompleteSpin = False
rdTime = 0
stage = 'start'
stageStarted = False
gameCall = game()
playerCall = player()
buttonsCall = buttons()
spinnerCall = spinner()
#Return a random range of numbers
def rdRange(minMult,maxMult,base):
return random.randint(base*minMult, base*maxMult)
#return sort key of list
def getKey(item):
return item[1]
#Timer function
def timer(timeElapsed, onCompleteFunc):
if running_time() - gameCall.timerPrev >= timeElapsed:
onCompleteFunc()
#Position Players Start is true
def positionPlayersStartTrue():
game.stage = 'positionPlayers'
def selectNumOfPlayers(gteOrLte):
game.timerPrev = running_time()
if gteOrLte == 'gte':
if gameCall.numOfPlayers >= gameCall.maxPlayers:
game.numOfPlayers = 1
else:
game.numOfPlayers += 1
else:
if gameCall.numOfPlayers <= 1:
game.numOfPlayers = maxPlayers
else:
game.numOfPlayers -= 1
display.show(str(gameCall.numOfPlayers)) #Have to convert int to string before passing to display.show func
buttons.pressed = True
#Ask for number of players up to maxPlayers.
def setPlayerNum():
#If B is pressed increment by 1 up the max players and cycle back to 1
if button_b.was_pressed():
selectNumOfPlayers('gte')
#If A is pressed decrement by 1 down to 1 and then cycle back to maxPlayers var
elif button_a.was_pressed():
selectNumOfPlayers('lte')
elif buttonsCall.pressed == False:
#Ask how many players
display.show('#?')
else:
timer(gameCall.gsTime, positionPlayersStartTrue)
#display the position of players
def positionPlayers():
buttons.pressed = False
display.clear()
for i in range(gameCall.numOfPlayers):
el = player.positions[i]
player.positionsOrdered.append(el)
el[0]()
player.positionsOrdered.sort(key=getKey)
while buttonsCall.pressed == False:
startSpin()
#start the spin - useful for starting the spin after one spin was complete too
def startSpin():
if button_a.was_pressed() or button_b.was_pressed():
buttons.pressed = True
game.stage = 'spin'
#Spin start
def spin():
if spinnerCall.stage == 'start' and spinnerCall.stageStarted == False:
game.timerPrev = running_time()
spinner.rdTime = rdRange(200, 700, gameCall.numOfPlayers)
spinner.stageStarted = True
print(spinner.rdTime)
for i in range(gameCall.numOfPlayers):
display.clear()
el = player.positionsOrdered[i]
el[0]()
if i + 1 == gameCall.numOfPlayers:
spinner.completeSpins += 1
spinner.isCompleteSpin = True
else:
spinner.isCompleteSpin = False
if spinnerCall.stage == 'start':
if (running_time() - gameCall.timerPrev >= (gameCall.minSpinTime + spinnerCall.rdTime)) and (spinnerCall.isCompleteSpin == True):
spinner.stage = 'slow'
spinner.stageStarted = False
sleep(200)
#Slower spin to emulate spinner slowing down as it comes near to stopping. Should probably use some clever-er maths here instead.
elif spinner.stage == 'slow':
if spinnerCall.stageStarted == False:
game.timerPrev = running_time()
spinner.rdTime = rdRange(500, 900, gameCall.numOfPlayers)
spinner.stageStarted = True
print(spinnerCall.rdTime)
if running_time() - gameCall.timerPrev >= spinnerCall.rdTime:
spinner.stage = 'stop'
spinner.stageStarted = False
sleep(400)
elif spinner.stage == 'stop':
player.selected = i
game.stage = 'selectedPlayer'
# reset spinner stage for next spin
spinner.stage = 'start'
break
#Player has been selected
def selectedPlayer():
el = playerCall.positionsOrdered[playerCall.selected]
sleep(200)
display.show(el[2])
sleep(200)
display.clear()
while True:
#CALL FUNCTIONS
if gameCall.stage == 'start':
setPlayerNum()
elif gameCall.stage == 'positionPlayers' and buttonsCall.pressed == True:
positionPlayers()
elif gameCall.stage == 'spin':
spin()
elif gameCall.stage == 'selectedPlayer':
#print('this one is selected ', playerCall.selected)
selectedPlayer()
#start spin again if button is pressed
startSpin()
Your code is too big for microbit. Microbit is limited by 16KB of RAM. To decrease size of your code you can:
minify it directly from Mu editor or use any other minifier lib
Shrink variable names
Delete comments

python - 'NoneType' object has no attribute

I'm new with Python/programming and trying to solve little problems to get a hang of it.
I've been struggling with the error below and not too sure how i got it. I understand it is saying the file type is None which is why it's throwing the error. However, I don't get how it is None in the first place? Just wondering if I could get some guidance or hint on the issue? Thank you very much, sorry if the code is messy
line 138, in move
self.ecoList[self.temP].nP = tempAni.p AttributeError: 'NoneType' object has no attribute 'nP'
from random import randint
class Bears:
def __init__(self):
self.p = 0
self.dp = 0
self.nP = 0
self.check = True
def run(self):
self.dp = randint(-1, 1)
self.nP = self.dp + self.p
self.check = False
def __str__(self):
return "Bear_"
def __repr__(self):
return self.__str__()
class Fish:
def __init__(self):
self.p = 0
self.dp = 0
self.nP = 0
self.check = True
def run(self):
self.dp = randint(-1, 1)
self.nP = self.dp + self.p
self.check = False
def __str__(self):
return "Fish|"
def __repr__(self):
return self.__str__()
class EcoSystem:
def __init__(self):
self.ecoList = [None] * 10
self.bearList = []
self.fishList = []
for i in range(2):
self.bearList.append(Bears())
#Adding bear to ecoList
while True:
index = randint(0, 9)
if self.ecoList[index] is None:
self.ecoList[index] = self.bearList[i]
self.ecoList[index].p = index
break
else:
continue
for i in range(2):
self.fishList.append(Fish())
#Adding fish to ecoList
while True:
index = randint(0, 9)
if self.ecoList[index] is None:
self.ecoList[index] = self.fishList[i]
self.ecoList[index].p = index
break
else:
continue
self.move()
def move(self):
#Print out the current eco system
print(*self.ecoList, sep='\n')
anwser = True
while anwser:
#populate next move new position for each object
for i in range(len(self.ecoList)):
if self.ecoList[i] is None:
continue
else:
self.ecoList[i].run()
#run for loop to test next position of the object
for i in range (len(self.ecoList)):
#if [i] item is None skip to next loop
if self.ecoList[i] is None:
continue
elif self.ecoList[i].check == True:
continue
#else check if it is going to move then check adjacent slot is going to be taken
else:
tempAni = None #temp animal to compare with item in loop
#call out new position from item i
newP = self.ecoList[i].nP
#call out direction:
newDP = self.ecoList[i].dp
#do nothing, skip to next slot if it is not going to move
if newDP == 0:
self.ecoList[i].check = True
continue
elif newDP != 0:#test if new position is going to be out of bound
if newP < 0 or newP > (len(self.ecoList)-1):
#set new position back to current
self.ecoList[i].nP = self.ecoList[i].p
self.ecoList[i].dp = 0
self.ecoList[i].check = True
else:
#test if new position is going to be collided
if self.ecoList[newP] is not None:
if self.ecoList[newP].nP == self.ecoList[i].nP:
print("////////////////")
tempAni = self.ecoList[newP]
#test if the next next new position is not None or out of bound
#Assumption - prioritize the closet animal going to move
elif (newP+newDP) > 0 and (newP+newDP) < (len(self.ecoList)-1):
if self.ecoList[newP+newDP] is not None:
#test if this is going to be collided
if self.ecoList[newP+newDP].nP == self.ecoList[i].nP:
print("\\\\\\\\\\\\\\")
tempAni = self.ecoList[newP+newDP]
#if tempAni is not none compare the type
if tempAni is not None:
print ("####")
print (self.ecoList[i].p)
print (self.ecoList[i])
print("-----------")
print (tempAni.p)
print(tempAni)
print ("####")
#test if they are the same type
self.temP = tempAni.p
if tempAni.__class__.__name__ == self.ecoList[i].__class__.__name__:
#if they are, change new position to current position
self.ecoList[i].nP = self.ecoList[i].p
self.ecoList[i].check = True
print("?????")
print(self.temP)
print(tempAni)
print(tempAni.dp)
print(self.ecoList[i])
print(self.ecoList[i].dp)
self.ecoList[self.temP].nP = tempAni.p
self.ecoList[self.temP].check = True
#create new animal of the same type and put it to a random place on the list
#Assumption - if the list is full add do nothing
#Determine tempAni type to create new bear or fish
if isinstance(tempAni, Bears):
#create new bear
newAni = Bears()
else:
#creaete new fish
newAni = Fish()
#while loop if the list is still have available spot add new animal to random spot, otherwise do nothing
while None in self.ecoList:
index = randint(0, 9)
if self.ecoList[index] is None:
self.ecoList.insert(index, newAni)
self.ecoList[index].p = index
print ("*****")
print (self.ecoList[index].p)
print (self.ecoList[index])
print ("*****")
break
#if they are not the same type, kill the fish
else:
#determine if tempAni is the fish or bear
if isinstance(tempAni, Bears):
#if it is bears kill the fish in i
self.ecoList[i].p = -1
self.ecoList[i].check = True
self.ecoList[self.temP].check = True
elif isinstance(tempAni, Fish):
#if it is fish kill it
self.ecoList[self.temP].p = -1
self.ecoList[i].check = True
self.ecoList[self.temP].check = True
#Apply the change after all the checks are finished
#Remove all the fish got killed and apply the moves
for i in range (len(self.ecoList)):
if self.ecoList[i] is not None:
if self.ecoList[i].p == -1:
self.ecoList[i] = None
elif self.ecoList[i].check == False:
self.ecoList[i].check = True
newP = self.ecoList[i].nP
if newP != i:
self.ecoList[newP] = self.ecoList[i]
self.ecoList[newP].p = newP
self.ecoList[i] = None
#Print out the current eco system
print ("---------------------------------------")
for i in range (len(self.ecoList)):
print(self.ecoList[i])
print(i)
#Ask if user want to continue playing
test = True
while test == True:
strAns = input ('Enter y/n to continue or not: ')
if strAns.lower() == "n":
anwser = False
test = False
break
elif strAns.lower() == "y":
test = False
break
def main():
EcoSystem()
main()
The error means that self.ecoList[self.temP] is None, although your code does not expect it to be None. So, this is the offending line:
self.ecoList[self.temP].nP = tempAni.p
Your code actually wants to assign tempAni.p to None.nP. None does not have such an attribute, and that is why you get the error. The line where the code throws the exception is just an indication that something is wrong in your code, somewhere. Finding this bug is your task now.
You need to breathe calmly and step-by-step figure out where your code is wrong. This might be anywhere and nobody here on SO will find this for you. Add print and/or assert statements to your code, and narrow down the issue. This is debugging work, and you have to go through it!

Categories