Is there a way I could optimize multiple python for loops? - python

I am trying to make a multiplayer web game, I have a server done in python. Since the game is multiplayer, I need to update each player individually; This requires for loops, at the moment my code uses a lot of for loops, which is causing me some unwanted side affects. The side affects includes slowed speeds. Another issue i have with for loops is they change speeds depending on how many things they are looping through; This is causing problems with the fact that the number of items that the loop needs to loop through changes depending on how many players are connected, thus messing up the client side prediction function I made to mask lag.
This is my main code:
PlayerData = {}
for Player in Players:
if(Player.id > 50):
PlayerData['playerx' + str(Player.id)] = Player.x
PlayerData['playery' + str(Player.id)] = Player.y
PlayerData['playera' + str(Player.id)] = int(Player.angle)
PlayerData['playerstat' + str(Player.id)] = Player.alive
if(Player.id > 50):
if(Player.alive == 0):
Players.remove(Player)
for ID in clientIds:
PlayerData['id'] = str(clientIds[len(clientIds) - 1].id)
PlayerData['players'] = ids
if(Player.id <= 50):
hrIds.append(Player.id)
PlayerData['hx' + str(Player.id)] = Player.x
PlayerData['hy' + str(Player.id)] = Player.y
#PlayerData['ha' + str(Player.id)] = int(Player.angle)
PlayerData['hr'] = hrIds
PlayerJsonData = json.dumps(PlayerData)
await websocket.send(PlayerJsonData)
recivedData = await websocket.receive()
rData = json.loads(recivedData)
for ID in clientIds:
if(rData['id'] == str(ID.id)):
if(ID.IG == 0):
if(rData['playerstat'] == 1):
Players.append(player_classes.Basic(-1300, -1300, ID.ws, ID.id, 1))
ID.IG = 1
for Player in Players:
for Player2 in Players:
if(Player.id > 50):
Player.detect_collide(Player2)
if(rData['id'] == str(Player.id)):
if(rData['direction'] == "up"):
Player.accelerate(rData['direction'])
Player.moveUp(Player2)
if(rData['direction'] == "left"):
Player.accelerate(rData['direction'])
Player.moveLeft(Player2)
if(rData['direction'] == "down"):
Player.accelerate(rData['direction'])
Player.moveDown(Player2)
if(rData['direction'] == "right"):
Player.accelerate(rData['direction'])
Player.moveRight(Player2)
if(rData['direction'] == "none"):
Player.decelerate(rData['direction'])
EDIT: My main looping speed issue happens when I add more classes to Players

You should avoid checking the collision of the same pair of players twice.
Let's take a very simple example with Players list containing 0, 1, 2, 3.
With your code you get a lot of redundant tests:
At loop 0 you test 0 against 1, 2 and 3
At loop 1 you test 1 against 0, 2 and 3 ===> (but 0-1 was already tested)
At loop 2 you test 2 against 0, 1 and 3 ===> (but 0-2 / 1-2 were already tested)
At loop 3 you test 3 against 0, 1 and 2 ===> (but 0-3 / 1-3 / 2-3 were already tested)
So instead of:
for Player in Players:
for Player2 in Players:
Do:
for i, Player in enumerate(Players):
for Player2 in Players[i + 1:]:
If we use the list containing 0, 1, 2, 3 once again, we have the following behavior:
At loop 0 you test 0 against 1, 2, 3
At loop 1 you test 1 against 2, 3
At loop 2 you test 2 against 3
This way, it cuts the number of iterations from N^2 to N(N-1)/2.

Related

error:list index out of range in a for loop

So I was making a program that counts and prints the number of runs scored, the bowls bowled, the bowler and the batsman which is on strike in a cricket match but when i finished my code:
name = input('enter the name of the bowler\n=') # we ask for the input
bat_strike = input('enter the name of the batter on strike\n=')
bat_non_strike = input('enter the name of the batter on the non strike\n=')
bowls = 6 # this is the max number of bowls in a over
bowls_bowled = 0
runs_list = []
bowls_list = []
batter_list = []
batter_list.append(bat_strike)
for j in range(0, bowls):
bowl_type = int(input('enter the number corresponding to the option regarding the bowl bowled right now\n1)wide bowl\n2)simple bowl\n3)no ball\n4)dead bowl\n5)1D\n= '))
if bowl_type == 1:
bowls_list.append('wd')
runs_list.append(1)
batter_list.append(bat_strike)
bowls += 1
elif bowl_type == 2:
run = int(input('enter the number of runs scored on that bowl 1 for 1 run 2 for 2 runs 3 for 3 runs 4 for 4 runs or 0 for a dot ball\n='))
if run == 1 or run == 3:
batter_on_strike = input('enter wether the batter on strike is on non strike? y or n\n=')
if batter_on_strike == 'y':
c = bat_strike
bat_non_strike = bat_strike
bat_non_strike = c
batter_list.append(bat_strike)
runs_list.append(run)
bowls_list.append(1)
bowls_bowled += 1
elif bowl_type == 3:
bowls_list.append('no ball')
runs_list.append(1)
bowls += 1
batter_list.append(bat_strike)
elif bowl_type == 4:
bowls_list.append('dead bowl')
bowls += 1
batter_list.append(bat_strike)
elif bowl_type == 5:
bowls_list.append('1D')
bowls += 1
runs_list.append(1)
bowls_bowled += 1
batter_list.append(bat_strike)
for i in range(0, bowls-1):
print(f'bowler={name} batter on strike={batter_list[i]} bowl bowled={bowls_list[i]} runs scored={runs_list[i]}')
it produces the following error
print(f'bowler={name} batter on strike={batter_list[i]} bowl bowled={bowls_list[i]} runs scored={runs_list[i]}')
IndexError: list index out of range
I want to display the results using a for loop for every bowl but I keep getting this error
anyone has any ideas how can i solve the issue or a better code?
If bowl_type is ever 5, or if it's 2 and the batter on strike or non strike answer isn't y, you don't append anything to batter_list. So if that ever happens, batter_list won't be as long as your other lists.
If, say, all 6 bowls are type 5, then batter_list will be empty, so of course batter_list[4], for example, is going to be an error.
It's not clear what you want to happen in this case, so it's not obvious how you should fix it.
One possibility is to do a batter_list.append(None) in those cases. Or maybe batter_list.append("n/a"), or… whatever you want to show up in the print later.
Another possibility is to replace all of those separate lists with one list of dicts:
bowl_info_list = []
for j in range(0, bowls):
# ...
if bowl_type == 1:
bowl_info_list.append({'bowl': 'wd', 'runs': 1, 'batter': bat_strike})
# ...
for bowl_info in bowl_info_list:
print(f'bowler={name} batter on strike={bowl_info["batter"]} bowl bowled={bowl_info["bowl"]} runs scored={bowl_info["runs"]}')
Or, maybe even better, use a class (maybe a namedtuple or #dataclass) instead of a dict.

Program to Simulate 2 Coins Being Flipped at the Same Time

My assignment is to create a program that simulates 2 coins being tossed at the same time. If both heads, then Group A gets a point; if both tails then Group B gets a point. If the coins are different then the Prof gets a point. The program must take 2 inputs: number of games and number of tosses per game. Here are 2 separate sample runs to illustrate:
How many games? 1
How many coin tosses per game? 100
Game 0:
Group A: 25 (25.0%); Group B: 19 (19.0%); Prof: 56 (56.0%)
Wins: Group A=0 (0.0%); Group B=0 (0.0%); Prof=1 (100.0%)
How many games? 5
How many coin tosses per game? 10
Game 0:
Group A: 3 (30.0%); Group B: 1 (10.0%); Prof: 6 (60.0%)
Game 1:
Group A: 6 (60.0%); Group B: 1 (10.0%); Prof: 3 (30.0%)
Game 2:
Group A: 4 (40.0%); Group B: 1 (10.0%); Prof: 5 (50.0%)
Game 3:
Group A: 4 (40.0%); Group B: 1 (10.0%); Prof: 5 (50.0%)
Game 4:
Group A: 5 (50.0%); Group B: 3 (30.0%); Prof: 2 (20.0%)
Wins: Group A=2 (40.0%); Group B=0 (0.0%); Prof=3 (60.0%)
My code (albeit clunky) works for taking the inputs, simulating coin tosses, and calculating and displaying the number of points per group and the percent. My problem however, is in calculating and storing the number of wins across all of the games played. Here is my code as of now:
import random
def coinFlip():
games = input("How many games? ")
tosses = input("How many coin tosses per game? ")
for i in range(games):
gA = 0
gAW = 0
gB = 0
gBW = 0
prof = 0
profW = 0
for j in range(tosses):
flip1 = random.randint(0, 1)
flip2 = random.randint(0, 1)
if (flip1 == 0 and flip2 == 0):
gA += 1
elif (flip1 == 1 and flip2 == 1):
gB += 1
else:
prof += 1
gAper = ((gA * 1.0) / tosses) * 100
gBper = ((gB * 1.0) / tosses) * 100
profper = ((prof * 1.0) / tosses) * 100
if (gA > gB and gA > prof):
gAW += 1
elif (gB > gA and gB > prof):
gBW += 1
elif ( prof > gA and prof > gB):
profW += 1
gAWper = ((gAW * 1.0) / games) * 100
gBWper = ((gBW * 1.0) / games) * 100
profWper = ((profW * 1.0) / games) * 100
print "Game {}:".format(i)
print " Group A: {} ({}%); Group B: {} ({}%); Prof: {} ({}%)".format(gA, gAper, gB, gBper, prof, profper)
print "Wins: Group A = {} ({}%); Group B = {} ({}%); Prof: {} ({}%)".format(gAW, gAWper, gBW, gBWper, profW, profWper)
I'm thinking I should store the wins in a list, but that's where I'm lost.
The critical problem is that you have reset the long-term counts at the start of every game. Thus, nobody gets to record more than one win. This works great for my Monday-night Frisbee games, but is not effective for your assignment.
Return to your psuedo-code and see where the loops and initializations match up. Here's a code version:
def coinFlip():
# Set-up you do only once per program execution
games = input("How many games? ")
tosses = input("How many coin tosses per game? ")
games_won_A = 0
games_won_B = 0
games_won_prof = 0
for i in range(games):
# Set-up things you do once per game
tosses_won_A = 0
tosses_won_B = 0
tosses_won_prof = 0
for j in range(tosses):
# Things you do every toss
flip1 = random.randint(0, 1)
flip2 = random.randint(0, 1)
...
# Summary things you do every game
# ... such as compute percentages
# Summary things you do at the end of the program execution
# ... such as print the overall totals
Does that get you moving?
BTW< note that this becomes a lot shorter if you put the counters into a list. For instance, counting the winner of each flip becomes a single line:
win_count[flip1 + flip2] += 1
win_count can be a list of three elements, recording the wins for A, prof, and B, in that order.
This probably isn't what the OP was looking for, but in general generating random numbers with numpy can accomplish this quickly and simply.
import numpy as np
# get these from input or wherever
games = 5
tosses = 10
n_coins = 2
experiment = np.random.randint(2,size=(games, tosses, n_coins))
flip_results = experiment.sum(axis=2) # 0 means group A wins, 1 Prof, 2 group B
game_results = np.stack((flip_results == 0, flip_results == 1, flip_results == 2))
game_results = game_results.sum(axis=2)
total_results = game_results.sum(axis=1)
print(game_results, total_results)

How to debug my Python dice game?

So I recently posted my code for a simple dice program I'm having trouble with. It is supposed to randomly generate 5 numbers in an array, then check if there are any matching values, if there are, it adds to MATCH, so once it's done checking, MATCH+1 is how many 'of a kind' there are(match=1 means two of a kind, match=2 means three of a kind etc.)
It randomly generates and then displays the numbers correctly, and the program seems to check without errors except when the last two playerDice elements match, then it throws an out of bounds error, Why is it doing that? Also it never actually displays the last print line with how many of a kind there are, even when it runs error free, Why is that?
Here is the code:
import random
playerDice = [random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6)]
compDice = [random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6),random.randint(1,6)]
match = 0
compmatch = 0
#print player dice
print("You rolled: ",end=" ")
a = 0
while a < len(playerDice):
print(str(playerDice[a]) + ", ",end=" ")
a = a + 1
#player check matches
i = 0
while i < len(playerDice):
j = i + 1
if playerDice[i] == playerDice[j]:
match = match + 1
while playerDice[i] != playerDice[j]:
j = j + 1
if playerDice[i] == playerDice[j]:
match = match + 1
i = i + 1
print("Player has: " + str(match + 1) + " of a kind.")
There's a much easier way to look for matches: sort the dice, and then look for runs of repeated dice. You could look for those runs manually, but the standard library has a function for that: itertools.groupby. Here's a short demo.
import random
from itertools import groupby
# Seed the randomizer while testing so that the results are repeatable.
random.seed(7)
def roll_dice(num):
return [random.randint(1,6) for _ in range(num)]
def find_matches(dice):
matches = []
for k, g in groupby(sorted(dice)):
matchlen = len(list(g))
if matchlen > 1:
matches.append('{} of a kind: {}'.format(matchlen, k))
return matches
for i in range(1, 6):
print('Round', i)
player_dice = roll_dice(5)
#comp_dice = roll_dice(5)
print('You rolled: ', end='')
print(*player_dice, sep=', ')
matches = find_matches(player_dice)
if not matches:
print('No matches')
else:
for row in matches:
print(row)
print()
output
Round 1
You rolled: 3, 2, 4, 6, 1
No matches
Round 2
You rolled: 1, 5, 1, 3, 5
2 of a kind: 1
2 of a kind: 5
Round 3
You rolled: 1, 5, 2, 1, 1
3 of a kind: 1
Round 4
You rolled: 4, 4, 1, 2, 1
2 of a kind: 1
2 of a kind: 4
Round 5
You rolled: 5, 4, 1, 5, 1
2 of a kind: 1
2 of a kind: 5
Here's an alternative version of find_matches that doesn't use groupby. It's probably a good idea to run through this algorithm on paper to see exactly how it works.
def find_matches(dice):
matches = []
dice = sorted(dice)
prev = dice[0]
matchlen = 1
# Add a "dummy" entry so we can detect a group at the end of the list
for d in dice[1:] + [0]:
# Compare this die to the previous one
if d == prev:
# We're inside a run of matching dice
matchlen += 1
else:
# The previous run has ended, so see if it's
# long enough to add to the matches list
if matchlen > 1:
matches.append('{} of a kind: {}'.format(matchlen, prev))
# Reset the match length counter
matchlen = 1
# This die will be the previous die on the next loop iteration
prev = d
return matches

Monty hall simulation returning 50% odds?

from random import randint
numberOfDoors = 3
success = 0
attempts = 0
while True:
try:
doors = [0] * numberOfDoors
doors[randint(0, numberOfDoors - 1)] = 1
chosen = randint(0, numberOfDoors - 1)
while numberOfDoors > 2:
notIn = -1
while notIn == -1:
index = randint(0, numberOfDoors - 1)
if doors[index] == 0 and index != chosen:
notIn = index
if notIn < chosen:
chosen -= 1
del doors[notIn]
numberOfDoors -= 1
# doors is 2, so not chosen (0 or 1) will return the opposite (1 or 0)
success += doors[not chosen]
attempts += 1
if attempts % 1000000 == 0:
print float(success) / float(attempts)
except KeyboardInterrupt:
print float(success) / float(attempts)
break
My results are almost exactly 50% after a few hours of simulation - am I doing something specifically wrong?
Theoretically the door you choose is between 1/3 odds and 2/3 odds, so you should get higher than 50 at the very least.
This answer seems to do the same thing as me (ignoring that he doesn't do anything about monty's choice - I wanted to illustrate the concept).
You're forgetting to reset numberOfDoors (number of doors still closed, right?) back to 3. Since every iteration of the first while True: represents a new game show run, the show starts with all three doors initially closed.
...
while True:
numberOfDoors = 3
try:
doors = [0] * numberOfDoors
doors[randint(0, numberOfDoors - 1)] = 1
...
Next time, try adding print statements to help you debug. In this case, adding print doors right after you assign a car shows that doors has only two elements after the first iteration.
I wrote a Monty Hall simulation problem myself a while ago. Maybe it will help you with your code - in particular the comments may be useful:
from __future__ import division
import random
results = [] # a list containing the results of the simulations, either 'w' or 'l', win or lose
count = 0
while count <200: #number of simulations to perform
l = []
prize = random.randint(1, 3) #choose the prize door
initialchoice = random.randint(1, 3) #make an initial choice (the door the contestant chooses)
exposed = random.randint(1, 3) #choose the exposed door (the door the host chooses)
while exposed == initialchoice or exposed == prize: #make sure exposed is not same as prize or the initial choice
exposed = random.randint(1, 3)
if initialchoice != prize:
results.append('l') #if the initial choice was incorrect, append 'l'
else:
results.append('w') #if the initial choice was correct, append 'w'
count += 1
print 'prize door:', prize, 'initial choice:',initialchoice, 'exposed door:',exposed #print the results of the simulation
print
w = 0
for i in results:
if i == 'w':
w += 1
print w/len(results) #fraction of times sticking with the original door was successful

How would I find the winner of my Python Tic Tac Toe game?

So far, I have a program where 2 players can click to place an X and an O in turns. I'm not sure how to make the program recognize a winner/ draw. If you guys could help me make a function that indicated a win/ draw on the screen in any way, I would love you forever. Thanks.
from graphics import *
import sys
def player_o(win, center):
'''
Parameters:
- win: the window
'''
outline_width = 5
circle = Circle(center, boxsize/2)
circle.setOutline('red')
circle.setWidth(outline_width)
circle.draw(win)
def player_x(win, p1x, p1y):
'''
Parameters:
- win: the window
'''
for i in range(2):
deltaX = (-1) ** i * (boxsize / 2)
deltaY = (boxsize / 2)
line = Line(Point(p1x - deltaX, p1y - deltaY),
Point(p1x + deltaX, p1y + deltaY))
line.setFill('red')
line.setWidth(5)
line.draw(win)
def game():
global win
global boxsize
try:
winsize = int(input("How large would you like the window? (Between 100 and 3000): "))
if winsize < 100 or winsize > 3000:
print("Invalid window size")
quit()
squares = int(input("How many squares per row? (Between 3 and 10):"))
boxsize = winsize/ squares
if squares < 3 or squares > winsize / 10:
print("Invalid number")
quit()
except ValueError:
sys.exit("Not a valid number")
win = GraphWin("Tic Tac Toe", winsize, winsize)
for i in range(squares - 1):
hline = Line(Point(0, (winsize/squares) * (i + 1)), Point(winsize, (winsize/squares) * (i + 1)))
hline.draw(win)
vline = Line(Point((winsize/squares) * (i + 1), 0), Point((winsize/squares) * (i + 1), winsize))
vline.draw(win)
for i in range((squares ** 2) // 2):
print("X, click a square.")
p1mouse = win.getMouse()
p1x = p1mouse.getX()
p1y = p1mouse.getY()
player_x(win, p1x, p1y)
print("O, click a square.")
p2mouse = win.getMouse()
p2x = p2mouse.getX()
p2y = p2mouse.getY()
player_o(win, Point(p2x, p2y))
if squares % 2 == 1:
print("X, click a square.")
p1mouse = win.getMouse()
p1x = p1mouse.getX()
ply = p1mouse.getY()
player_x(win, p1x, p1y)
game()
Keep data and representation of data separated. That's how. Right now you're just drawing things, rather than that you should be generating some representation of the playing field (e.g. a list of the boxes and their state, as in, checked by p1, checked by p2, or unchecked), and then use that to draw when needed. The advantage should be immediately obvious - if you know the state of the game, determining if there's a winner (and who it is) is trivial.
After 3 turns (minimum turns to win) check your 2d array if there is a token next to the last played by adding/substracting one, if found repeat same operation to array indices else break out.
If 2nd control structure is reached break and announce winner.
With each move in the game, a 2D array or a dictionary (with values being lists) should be used. Then, you can just check each way of winning. This way, you can also check if the move is valid or not--- whether or not the spot on the board is taken.
I would also suggest using a numerical or a coordinate system to dictate movement.
The board would look like this:
1 2 3
4 5 6
7 8 9
The numbers are corresponding spots on the board.
For example:
In the initialization:
moves = 0
positions = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9':0}
# '1' - '9' are the spots on the board.
# 0 means the spot is empty, 'X' means the spot is taken by 'X', 'O' means the spot is taken by 'O'. You can use any other naming system, but this is simple.
In the movement code:
while 1 == 1: # just a loop until the input is valid. See the 'break' statement below
new_move = input("X, enter what space to move to: ")
if positions[new_move] == 0: # if that board spot is empty
moves += 1 #moves = moves + 1
positions[new_move] == 'X' # board spot is now occupied by 'X'
# code to show the piece on the board
if moves >= 5: # least possible moves to win is 5
win_check(positions)
break
Alternatively, you can use the movement as a function, and have it recursively call itself until the input is valid:
def move_X():
new_move = input("X, enter what space to move to: ")
if positions[new_move] == 0: # if that board spot is empty
moves += 1 #moves = moves + 1
positions[new_move] == 'X' # board spot is now occupied by 'X'
# code to show the piece on the board
if moves >= 5: # least possible moves to win is 5
win_check(positions)
move_O() # this should be defined similarly to 'move_X' except that it would correlate to 'O'.
else:
move_X()
The the win checking method:
def win_check(positions):
if positions['1'] == 'X' and positions['2'] == 'X' and positions['3'] == 'X':
return "Winner: X"
elif # similar things, checking all of the other ways to win.
You need 1 if statement (in the beginning) and 15 elif statements, as there are 8 ways to win for each player, so 16 checks have to be made.

Categories

Resources