Python: List not searching diagonally - python

I am implementing a Python version of the game Othello / Reversi. However, my algorithm seems to be having trouble when searching in the southwest direction.
Here are some important functions to understand how my current code works:
def _new_game_board(self)->[[str]]:
board = []
for row in range(self.rows):
board.append([])
for col in range(self.columns):
board[-1].append(0)
return board
def _is_valid_position(self, turn:list)->bool:
'''return true if the turn is a valid row and column'''
row = int(turn[0]) - 1
column = int(turn[1]) - 1
if row >= 0:
if row < self.rows:
if column >= 0:
if column < self.columns:
return True
else:
return False
def _is_on_board(self, row:int, col:int)->bool:
'''returns true is coordinate is on the board'''
if row >=0:
if row < self.rows:
if col >=0:
if col < self.columns:
return True
def _searchNorthEast(self)->None:
'''Search the board NorthEast'''
print("NorthEast")
row = self.move_row
column = self.move_column
should_be_flipped = list()
row += 1
column -= 1
if self._is_on_board(row, column):
print("column searching NorthEast on board")
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
while True:
row += 1
column -= 1
if self._is_on_board(row, column):
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
continue
elif self.board[row][column] == self.turn:
self._to_be_flipped.extend(should_be_flipped)
break
else:
break
else:
self._to_be_flipped.extend(should_be_flipped)
else:
pass
def _searchSouthWest(self)->None:
'''Search the board SouthWest'''
print("in SouthWest")
row = self.move_row
column = self.move_column
should_be_flipped = list()
row -= 1
column += 1
if self._is_on_board(row, column):
print("column searching SouthWest on board")
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
while True:
row -= 1
column += 1
if self._is_on_board(row, column):
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
continue
elif self.board[row][column] == self.turn:
self._to_be_flipped.extend(should_be_flipped)
break
else:
break
else:
self._to_be_flipped.extend(should_be_flipped)
else:
pass
def _move_is_valid(self, turn:list)->bool:
'''Verify move is valid'''
self._to_be_flipped = list()
self._opponent = self._get_opposite(self.turn)
if self._is_valid_position(turn):
self.move_row = int(turn[0]) - 1
self.move_column = int(turn[1]) - 1
self._searchRight()
self._searchLeft()
self._searchUp()
self._searchDown()
self._searchNorthWest()
self._searchNorthEast
self._searchSouthEast()
self._searchSouthWest()
if len(self._to_be_flipped) > 0:
return True
else:
return False
Now let's say the current board looks like the following:
. . . .
W W W .
. B B .
. B . .
Turn: B
and the player makes a move to row 1 column 4, it says invalid because it does not detect the white piece in row 2 column 3 to be flipped. All my other functions are written the same way. I can get it to work in every other direction except in this case.
Any ideas why it is not detecting the piece in this diagonal direction?

Don't Repeat Yourself. The _search* methods are extremely redundant which makes it difficult to see that the signs in
row -= 1
column += 1
are correct. Since you've given only two directions (NE, SW) and no documentation of the board orientation, I cannot tell if the signs agree with the board layout or even agree with themselves.
The _search* methods are also too long and should be divided into multiple functions, but that's a secondary concern.

I agree with msw about not repeating stuff. It is tempting to go ahead and do what you can once you see it, but generalizing will save you the headaches of debugging.
Here is some pseudocode that should give the general idea. I might not be able to finagle working with your code, but hopefully this shows how to reduce repetitive code. Note it doesn't matter if -1 is up or down. The board class is simply a 2x2 array of (open square/player 1's piece/player 2's piece,) along with whose turn it is to move.
# x_delta and y_delta are -1/0/1 each based on which of the up to 8 adjacent squares you are checking. Player_num is the player number.
def search_valid(x_candidate, y_candidate, x_delta, y_delta, board, player_num):
y_near = y_candidate + y_delta
x_near = x_candidate + x_delta
if x_near < 0 or x_near >= board_width:
return False
if y_near < 0 or y_near >= board_width:
return False # let's make sure we don't go off the board and throw an exception
if board.pieces[x_candidate+x_delta][y_candidate+y_delta] == 0:
return False #empty square
if board.pieces[x_candidate+x_delta][y_candidate+y_delta] == player_num:
return False #same color piece
return True #if you wanted to detect how many flips, you could use a while loop
Now a succinct function can loop this search_valid to see whether a move is legal or not e.g.
def check_valid_move(x_candidate, y_candidate, board, player_num):
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if not x and not y:
continue # this is not a move. Maybe we don't strictly need this, since board.pieces[x_candidate+x_delta][y_candidate+y_delta] == player_num anyway, but it seems like good form.
if search_valid(x_candidate, y_candidate, dx, dy, board, player_num):
return True
return False
A similar function could actually flip all the opposing pieces, but that's a bit trickier. You'd need a while function inside the for loops. But you would not have to rewrite code for each direction.

Related

What is the best way to generate random ships in an array in Python?

Recently, I've been working on a battleship game for my CS2 class. The focus of this project is to create a board game using arrays, and I decided to create a battleship. I have most of the code, but I cannot figure out how to get ships to get randomly generated on a 10x10 array without the ships being...
A. The wrong length
B. Looping around the array
Below is the function I currently have.
def createShips(board):
shipLen = [5,4,3,3,2]
shipAvalible = 5
directionposibilities = ["vertical", "horizontal"]
j = 0
for i in range(shipAvalible):
boatMade = False
#REGULAR VAR STATMENTS
direction = random.choice(directionposibilities)
col = randint(0,9)
row = randint(0,9)
while boatMade == False:
if direction == "vertical":
buildCount = 0
if col + int(shipLen[i]) <= 11:
colission = False
for i in range(0, int(shipLen[i])):
buildCount += 1
if board[int(row-i)][int(col)-1] == "X":
if colission:
pass
else:
colission = True
if colission:
col = randint(0,9)
row = randint(0,9)
else:
for j in range(buildCount):
board[int(row-j)][int(col)-1] = "X"
boatMade = True
else:
col = randint(0,9)
row = randint(0,9)
if direction == "horizontal":
if col + int(shipLen[i]) <= 10:
colission = False
buildCount = 0
for i in range(0, int(shipLen[i])):
buildCount += 1
if board[int(row)][int(col)+i-1] == "X":
if colission:
pass
else:
colission = True
if colission:
col = randint(0,9)
row = randint(0,9)
else:
for j in range(buildCount):
board[int(row)][int(col)+j-1] = "X"
boatMade = True
else:
col = randint(0,9)
row = randint(0,9)
shipAvalible = shipAvalible - 1
return(board)
board = [["■"] * 10 for x in range(10)]
print(createShips(board))
If you have any idea why this may not work please let me know!
P.S. I am using another function that prints the array nicely, if you would like that for convenience, it is seen below:
def showBoard(board):
print(" A B C D E F G H I J")
print(" ------------------------------")
rownumb = 1
for r in board:
if rownumb == 10:
space = ""
else:
space = " "
print("%d|%s|" % (rownumb, space + "|".join(r)))
rownumb += 1
Edit: #Eumel was a bit faster and said it better here
First of all, you have a inner and outer loop with the variable i, which might not actually interfere but it makes it harder to read the code. Consider renaming one of the variables.
When you create a vertical ship, you check the col rather than the row for index error, and does so against a value of 11 instead of your board size 10.
In the vertical case you build the ship "backwards" from the selected position, even though you checked that your board could fit the ship in the positive direction (And you place it in a column to the left). These negative values makes it such that your ships can wrap around.
In the horizontal case you are indexing a bit strange again where board[int(row)][int(col)+i-1] this gives that when i=0 you place your ship an index further to the left than intended, again causing the ship to wrap when col=0.
Further more, you could remove a few redundant if statements and duplicate lines of code, by moving:
direction = choice(directionposibilities)
col = randint(0, 9)
row = randint(0, 9)
Inside the while loop.
Ok lets go over this:
length check
if col + int(shipLen[i]) <= 9: since arrays start at 0 in python the maximum index for a 10x10 board is 9, therefore you need to chek against 9 instead of 11
loop variables
for k in range(0, int(shipLen[i])): using the same variable name for the outer and inner loop (while working in this specific case) is really bad form
collision check
if board[row+k][col] == "X": you want to build the ships "forwards" so you use row+k, you can also forego int conversions when you only use integers to begin with
collsion check 2
if colission: pass else: colission = True This whole block can be shortened to collision = True break since you dont need to keep checking for multiple collisions
buildcount
for j in range(buildCount): unless you get a collision (then you wouldnt be in this branch) buildCount is always the same as shipLen[i] so it can be completly removed from your code
boat building
board[row+j][col] = "X": same as before we build forwards
honorable mentions
shipAvailable = len(shipLen) you could also remove this part completly and iterate over your ships directly in your out for loop
Instead of radoomising your direction row and col on every break condition you could randomize it once at the start of your while loop
j = 0 unlike C you dont have to define your variables before using them in a loop, this statement does nothing

Problem detecting collisions involving rotated pieces in game

I'm trying to create Tetris for an university project. But I'm having a quite difficult time on doing the collisions. Actually, they work, until I try to rotate a piece. After I rotate a piece the collision always returns True.
This is the code:
def constraint(self):
if self.shape_y + len(self.shape) == configuration.config['rows']:
return True
for shape_row, row in enumerate(self.shape):
column_index = -1
for x in range(self.shape_x, self.shape_x + len(self.shape[0])):
column_index += 1
if self.shape[shape_row][column_index] != 0:
if shape_row+1 < len(self.shape):
if self.shape[shape_row+1][column_index] == 0:
if self.board.board[self.shape_y + 1][x] != 0:
return True
else:
if self.board.board[self.shape_y + len(self.shape)][x] != 0:
print("qui")
return True
return False
shape_y is the row where the shape is located. len(self.shape) returns how many rows is the shape because it's coded like a matrix:
Example:
[[0, 1, 0],
[1, 1, 1]],
Is the piece with one block on top and three under.
Shape is the matrix that represent the piece.
Shape_x is the column where the shape is located.
The board is a matrix like this:
self.board = np.array([[0 for _ in range(configuration.config["cols"])]
for _ in range(configuration.config['rows'])])
where 0 is free, others numbers are block that are not free.
Here a screenshot that show the problem:
The blue and the green pieces get stuck like a collision occurred but are in "mid air" and nothing really occurred.
EDIT1:
This is the code for the rotation
def rotate(self):
self.board.remove_piece(self)
self.shape = np.rot90(self.shape)
self.board.add_piece(self)
Where self.board.remove_piece(self) and self.board.add_piece(self) just remove and add the values inside the board so that i can draw it again. So, basically, the rotation code is just self.shape = np.rot90(self.shape)
I should have actually fixed the problem, the error was inside an index to check on the board.
def constraint(self):
if self.shape_y + len(self.shape) == configuration.config['rows']:
return True
for shape_row, row in enumerate(self.shape):
column_index = -1
for x in range(self.shape_x, self.shape_x + len(self.shape[0])):
column_index += 1
if self.shape[shape_row][column_index] != 0:
if shape_row+1 < len(self.shape):
if self.shape[shape_row+1][column_index] == 0:
if self.board.board[self.shape_y + shape_row + 1][x] != 0:
return True
else:
if self.board.board[self.shape_y + len(self.shape)][x] != 0:
return True
return False

(Traversal) How to solve this question in python?

Write a function traverse() that takes in a list tb of n strings each containing n lower case characters
(a-z).
tb represents a square table with n rows and n columns. The function returns a string st generated by the procedure below that traverses the grid starting from the top left cell and ending at the bottom right cell.
At every step, the procedure moves either horizontally to the right or vertically down, depending on which of the two cells has a \smaller" letter (i.e., a letter that appears earlier in the alphabetical order).
The letter in the visited cell is then added to st. In case of ties, either direction can be taken.
When the right or bottom edges of the table are reached, there is obviously only a unique next cell to move to. As an example, traverse(["veus", "oxde", "oxlx", "hwuj"]) returns "veudexj"
so the table would look like this:
v o o h
e x x w
u d l u
s e x j
I am new in python and I wrote this code ... but it only prints "veuexj" I would say the problem is in this line if new_list[a - 1][b - 1] == new_list[a - 1][-2]: which force the parameter to skip the 'd' character. #And I don't know how to solve it.
def traverse(tb_list):
new_list = tb_list.copy()
st = new_list[0][0]
parameter = ''
handler = 1
for a in range(1, len(new_list)):
for b in range(handler, len(new_list[a])):
if new_list[a - 1][b - 1] == new_list[a - 1][-2]:
parameter = new_list[a][b]
elif new_list[a - 1][b - 1] > min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
elif new_list[a - 1][b - 1] < min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
st += parameter
handler = b
return st
print(traverse(["veus", "oxde", "oxlx", "hwuj"]))
You can try something like this (explanation added as comments):
def traverse(tb_list):
lst = tb_list.copy() #Takes a copy of tb_list
lst = list(zip(*[list(elem) for elem in lst])) #Transposes the list
final = lst[0][0] #Sets final as the first element of the list
index = [0,0] #Sets index to 0,0
while True:
x = index[0] #The x coordinate is the first element of the list
y = index[1] #The y coordinate is the second element of the list
if x == len(lst) - 1: #Checks if the program has reached the right most point of the table
if y == len(list(zip(*lst))) - 1: #Checks if program has reached the bottommost point of the table
return final #If both the conditions are True, it returns final (the final string)
else:
final += lst[x][y+1] #If the program has reached the right most corner, but not the bottommost, then the program moves one step down
index = [x, y+1] #Sets the index to the new coordinates
elif y == len(list(zip(*lst))) - 1: #Does the same thing in the previous if condition button in an opposite way (checks if program has reached bottommost corner first, rightmost corner next)
if x == len(lst) - 1:
return final
else:
final += lst[x + 1][y] #If the program has reached the bottommost corner, but not the rightmost, then the program moves one step right
index = [x + 1, y]
else: #If both conditions are false (rightmost and bottommost)
if lst[x+1][y] < lst[x][y+1]: #Checks if right value is lesser than the bottom value
final += lst[x+1][y] #If True, then it moves one step right and adds the alphabet at that index to final
index = [x+1,y] #Sets the index to the new coords
else: #If the previous if condition is False (bottom val > right val)
final += lst[x][y+1] #Moves one step down and adds the alphabet at that index to final
index = [x,y+1] #Sets the index to the new coords
lst = ["veus", "oxde", "oxlx", "hwuj"]
print(traverse(lst))
Output:
veudexj
I have added the explanation as comments, so take your time to go through it. If you are still not clear with any part of the code, feel free to ask me. Any suggestions to optimize/shorten my code are most welcome.

Python Wrong Input when working with Lists

I am working with this problem (https://open.kattis.com/problems/whowantstoliveforever). As the problem states, my program has to determine if the universe lives or dies based on the input 0s and 1s.
to determine the next value of the i-th bit, look at the current value of the bits at positions i−1 and i+1 (if they exist; otherwise assume them to be 0). If you see exactly one 1, then the next value of the i-th bit is 1, otherwise it is 0. All the bits change at once, so the new values in the next state depend only on the values in the previous state. We consider the universe dead if it contains only zeros.
My current solution works on the example input file, however it fails when submitting it to Kattis. (Wrong Answer)
Below is my code.
import sys
def get_bit(bits, i):
if 0 <= i < len(bits):
return int(bits[i])
else:
return 0
def get_new_state(old_state):
new_state = []
for index in range(len(old_state)):
if (get_bit(old_state, index-1) == 0 and get_bit(old_state, index+1) == 0) or (get_bit(old_state, index-1) == 1 and get_bit(old_state, index+1) == 1):
new_state.append(0)
elif(get_bit(old_state, index-1) == 0 and get_bit(old_state, index+1) == 1) or (get_bit(old_state, index-1) == 1 and get_bit(old_state, index+1) == 0):
new_state.append(1)
return new_state
def is_dead(state):
if len(set(state)) == 1:
return True
else:
return False
def foresee_fate(state):
seen = []
while True:
if is_dead(state):
return False
if state in seen:
return True
seen.append(state)
state = get_new_state(state)
def print_result(boolean):
print("LIVES" if boolean else "DIES")
num_cases = int(sys.stdin.readline().strip())
for i in range(num_cases):
cur_state = []
case = sys.stdin.readline().strip()
for char in case:
cur_state.append(char)
print_result(foresee_fate(cur_state))
Please let me know what I can do to improve this program.
As suggested, I have added the following to my code and IT works now. However, I ran into a new problem. now I'm getting "Time Limit Exceeded" > 3.00 s.
def is_dead(state):
if set(state).pop() == 1:
return False
elif len(set(state)) == 1:
return True
else:
return False
Please let me know if there is any suggestion on getting around this problem

Small change make script hang - don't understand why

I've made a word searching game.It will print out a grid containing some words.
The code goes like this:
import random
DirectionList = []
# Create a grid filled with "." representing a blank
def createGrid():
grid=[]
for row in range(15):
grid.append([])
for column in range(50):
grid[row].append(".")
return grid
# Print the grid to the screen
def printGrid(grid):
for row in range(len(grid)):
for column in range(len(grid[row])):
print(grid[row][column],end="")
print()
# Try to place the word. Return True if successful
# False if it failed and we need to try again.
def tryToPlaceWord(grid,word):
# Figure out the direction of the work.
# Change the 8 to a 7 if you don't want backwards
# words.
status_check = []
direction=random.randrange(0,8)
DirectionList.append(direction)
if( direction == 0 ):
x_change=-1
y_change=-1
if( direction == 1 ):
x_change=0
y_change=1
if( direction == 2 ):
x_change=1
y_change=-1
if( direction == 3 ):
x_change=1
y_change=0
if( direction == 4 ):
x_change=1
y_change=1
if( direction == 5 ):
x_change=0
y_change=1
if( direction == 6 ):
x_change=-1
y_change=1
if( direction == 7 ):
x_change=-1
y_change=0
# Find the length and height of the grid
height=len(grid)
width=len(grid[0])
# Create a random start point
column=random.randrange(width)
row=random.randrange(height)
# Check to make sure the word won't run off the edge of the grid.
# If it does, return False. We failed.
if( x_change < 0 and column < len(word) ):
status_check.append(False)
status_check.append("None")
return status_check
if( x_change > 0 and column > width-len(word) ):
status_check.append(False)
status_check.append("None")
return status_check
if( y_change < 0 and row < len(word) ):
status_check.append(False)
status_check.append("None")
return status_check
if( y_change > 0 and row > height-len(word) ):
status_check.append(False)
status_check.append("None")
return status_check
# Now check to make sure there isn't another letter in our way
current_column=column
current_row=row
for letter in word:
# Make sure it is blank, or already the correct letter.
if grid[current_row][current_column]==letter or grid[current_row][current_column]=='.':
current_row += y_change
current_column += x_change
else:
# Oh! A different letter is already here. Fail.
status_check.append(False)
status_check.append("None")
return status_check
# Everything is good so far, actually place the letters.
current_column=column
current_row=row
for letter in word:
grid[current_row][current_column]=letter
current_row += y_change
current_column += x_change
if 7 in DirectionList:
status_check.append(True)
status_check.append("True")
return status_check
else:
status_check.append(True)
status_check.append("None")
return status_check
# This just calls tryToPlaceWord until we succeed. It could
# repeat forever if there is no possible place to put the word.
def placeWord(grid,words):
for word in words:
succeed=False
while not(succeed):
status_check=tryToPlaceWord(grid,word)
succeed=status_check[0]
backward = status_check[1]
return backward
# Create an empty grid
grid = createGrid()
# A list of words
words = ["pandabear","fish","snake","porcupine","dog","cat","tiger","bird","alligator","ant","camel","dolphin"]
# Place some words
placeWord(grid,words)
backward = placeWord(grid,words)
print("Current status:\n"
"\tGenerating a word searching diagram...\n"
"\tWords :",len(words),"\n"
"\tBackword :",backward)
# Print it out
printGrid(grid)
Then, I want to make the code print out random letters instead of dots(.) So I made some little changes.
import random
import string
DirectionList = []
# Create a grid filled random letters representing a blank
def createGrid():
grid=[]
for row in range(15):
grid.append([])
for column in range(50):
grid[row].append(random.choice(string.ascii_uppercase))
return grid
But python stops functioning, it prints out nothing for a long time. I thought maybe python needed a lot of time to generate 15 x 50 random letters, so I changed the code to grid[row].append("a"), but python is still not functioning.
What had gone wrong?
Edit:
Changed the code to
for letter in word:
# Make sure it is blank, or already the correct letter.
if grid[current_row][current_column]==letter or grid[current_row][current_column]==' ':
current_row += y_change
current_column += x_change
But the code is still hanging...
You use '.' also to check that a cell is free in your grid (line 87), so if you fill cells with random letters, they won't be free anymore. You could instead fill empty spaces with random letters after putting in the words...

Categories

Resources