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
Related
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
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
I wasn't quite sure how to word the title, sorry if it doesn't make sense/is misleading.
Note - a "boat" is 3 O's next to each other in an array, so
_|O|_ _|_|_
_|O|_ O|O|O
|O| | |
are boats.
So I have an a list of lists (n x n) (working with lists) in which I generate n boats at random spaces. I don't want the boats to be next to each other, touch by corners, or be on top of each other.
A tried checking if a boat would end up on top of another boat in the vein of this:
if board[y - 2][x] == 'O' or board[y + 2][x] == 'O' ...
and so on, which ended up being unsurprisingly long.
I also got index out of range errors, since I was sometimes checking for coordinates not in the field.
So, is there a way I can check for boats in every direction without going out of index range?
Better yet, any ideas on how to make the boats not generate next to each other?
The code for boat generation is here:
from random import *
side = int(input())
game_state = []
def generate_initial_state():
for i in range(side):
game_state.append([])
for j in range(side):
game_state[i].append('.')
for i in range(side):
# Generate boat origin on random coordinates within the game board,
# if there's a boat already, generate new ones
y_cor = randint(0, side-1)
x_cor = randint(0, side-1)
while game_state[y_cor][x_cor] == 'O':
y_cor = randint(0, side - 1)
x_cor = randint(0, side - 1)
# Direct chooses if the boat will be generated up, down, or sideways
direct = randint(1, 4)
cycle = 0
while cycle < 3:
# Generates a boat going from origin in one direction,
# if the boat would end outside the board, chooses a different direction
if direct == 1:
if y_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor + cycle][x_cor] = 'O'
cycle += 1
elif direct == 2:
if x_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor + cycle] = 'O'
cycle += 1
elif direct == 3:
if y_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor - cycle][x_cor] = 'O'
cycle += 1
elif direct == 4:
if x_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor - cycle] = 'O'
cycle += 1
for i in range(side):
print(*game_state[i])
First I would only use two directions (horizontal and vertical), which shouldn't change the probabilities (with your model, a boat can be generated in two ways).
This allows index overflow to only occur by exceeding the allowable indices, which provokes an IndexError that can be intercepted (using a negative index doesn't and that could mess up your generator).
Secondly, using a flag could help you do the trick.
I added a few other modifications :
EDIT : I just realized that my code was rejecting perfectly valid boats if they were on the border, so here is a version that doesn't.
Update : some explanations
We use a boolean flag boat_built to track the suitability of a randomly chosen position of the boat: once all tests are made, this variable decides if the choice was suitable (True) or if obstructions were encountered while tests were carried out (False).
Using
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
we update the flag for every test : if boat_built was False before the test, it will remain False regardless of the test's result (False & a = False): this is intended, because it means that an obstruction has already been encountered and the boat is invalid.
On the other hand, if boat_built was True before the test, it will contain the result of the test afterwards (True & a = a): this is also intended, since failing the new test means we now have found an obstruction.
Note that all 15 tests are carried out for every boat, even if an obstruction is encountered early on.
from random import *
side = int(input())
game_state = [['.' for i in range(side)] for j in range(side)]
l_dir = [(1, 0), (0, 1)]
def generate_initial_state():
for i in range(side):
boat_built = False
while not boat_built:
boat_built = True
y_cor = randrange(side)
x_cor = randrange(side)
dx, dy = l_dir[randrange(2)]
try:
# check that the three required cells are empty
for k in range(3):
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# if any is out of range, choice is invalid
boat_built = False
for k in range(5):
for l in [-1, 1]:
try:
# check if neighbours on the long sides are empty
boat_built &= (game_state[x_cor + (k-1)*dx + l*dy][y_cor + l*dx + (k-1)*dy] != "0")
except IndexError:
# if we're out of range, no obstruction
pass
for k in [-1, 3]:
try:
# check if neighbours on the short sides are empty
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# again, if we're out of range, no obstruction
pass
# if we reach this point, a valid position has been found
for k in range(3):
game_state[x_cor + k*dx][y_cor + k*dy] = "0"
generate_initial_state()
for i in range(side):
print(*game_state[i])
Your code has, as you complain, too much tedious repeated logic to inspect a point's neighbors. You could turn four tests into one with something like this:
offset = [
(0, -1),
(-1, 0), (1, 0),
(0, 1),
]
for xoff, yoff in offset:
if game_state[x + xoff * cycle][y + yoff * cycle] == 'O':
report_collision(x, y)
Additionally, you could mark the grid with both 'O' for "boat" and 'o' for "bordering a boat", to simplify detecting adjacent boats.
You can try the following class and see if it solves your problem:
#! /usr/bin/env python3
import collections
import enum
import random
def main():
board = Board(10, 10)
print(board)
board.place_boats([2, 3, 3, 4, 5])
print('\n' + '=' * 21 + '\n')
print(board)
Point = collections.namedtuple('Point', 'x, y')
# noinspection PyArgumentList
Orientation = enum.Enum('Orientation', 'HORIZONTAL, VERTICAL')
class Board:
def __init__(self, width, height):
self.__width = width
self.__height = height
self.__matrix = [[False] * height for _ in range(width)]
self.__available = {Point(x, y)
for x in range(width)
for y in range(height)}
def __str__(self):
width = self.__width * 2 + 1
height = self.__height * 2 + 1
grid = [[' '] * width for _ in range(height)]
for yo, xo, character in (0, 1, '|'), (1, 0, '-'), (1, 1, '+'):
for y in range(yo, height, 2):
for x in range(xo, width, 2):
grid[y][x] = character
for x, column in enumerate(self.__matrix):
for y, cell in enumerate(column):
if cell:
grid[y << 1][x << 1] = '#'
return '\n'.join(''.join(row) for row in grid)
# noinspection PyAssignmentToLoopOrWithParameter
def place_boats(self, sizes, patience=10):
matrix_backup = [column.copy() for column in self.__matrix]
available_backup = self.__available.copy()
for _ in range(patience):
# try to place all the boats
for size in sizes:
for _ in range(patience):
# try to place boat of current size
point = random.choice(tuple(self.__available))
method = random.choice(tuple(Orientation))
try:
# try to place a boat; does not mangle the matrix
self.make_boat(point, size, method)
except RuntimeError:
pass
else:
# break out of inner patience loop; go to next size
break # on success
else:
# break to outer patience loop; start from beginning
self.__matrix = [column.copy() for column in matrix_backup]
self.__available = available_backup.copy()
break # on failure
else:
# break out of outer patience loop; all sizes were placed
break # on success
else:
raise RuntimeError('could not place the requested boats')
def make_boat(self, point, size, method):
backup = [column.copy() for column in self.__matrix]
unusable = set()
for offset in range(size):
if method is Orientation.HORIZONTAL:
block = self.mark_cell(point, x_offset=offset)
elif method is Orientation.VERTICAL:
block = self.mark_cell(point, y_offset=offset)
else:
raise ValueError('method was not understood')
if block:
unusable.update(block)
else:
self.__matrix = backup
raise RuntimeError('cannot place boat')
self.__available -= unusable
def mark_cell(self, point, *, x_offset=0, y_offset=0):
target = Point(point.x + x_offset, point.y + y_offset)
if target in self.__available and \
0 <= target.x < self.__width and \
0 <= target.y < self.__height:
self.__matrix[target.x][target.y] = True
return {Point(target.x + xo, target.y + yo)
for xo in range(-1, 2)
for yo in range(-1, 2)}
if __name__ == '__main__':
main()
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.
There is a robot at the top-left corner of an N*M grid. The robot can move up, down, left and right, but cannot visit the same cell more than once in each traversal. How do I find the total number of ways the robot can reach the bottom-right corner?
(the robot does not need to visit every cell for a path to be valid)
I think there is a recursive solution to this but I can't get it somehow.
Here's what I've got so far:
def initialize(row, cols):
grid = [ [ 0 for c in range(cols) ] for r in range(rows) ]
pos_r, pos_c = 0, 0
grid[pos_r][pos_c] = 1 # set start cell as visited
dst_x, dst_y = len(grid)-1, len(grid[0])-1 # coords of bottom-right corner
print move_robot(grid, dst_x, dst_y, pos_r, pos_c)
def move_robot(grid, dst_x, dst_y, pos_r, pos_c, prev_r=None, prev_c=None):
num_ways = 0
if reached_dst(dst_x, dst_y, pos_r, pos_c):
undo_move(grid, pos_r, pos_c)
undo_move(grid, prev_r, prev_c)
return 1
else:
moves = get_moves(grid, pos_r, pos_c)
if len(moves) == 0:
undo_move(grid, prev_r, prev_c)
return 0
for move in moves:
prev_r = pos_r
prev_c = pos_c
pos_r = move[0]
pos_c = move[1]
update_grid(grid, pos_r, pos_c)
num_ways += move_robot(grid, dst_x, dst_y, pos_r, pos_c, prev_r, prev_c)
return num_ways
if __name__ == '__main__':
initialize(4, 4)
I left out some function definitions for brevity. Get_moves retrieves the all legal moves, checking whether each move would still be on the board and whether the cell has already been visited. Update_grid sets the specified cell to '1', which means visited. Undo_move does the opposite, setting the specified cell to '0'.
I get the right answer for the simplest possible case (2*2 grid), but for larger grids the output is always too low. What's wrong with my code, and is there a simpler way of doing this?
The recursion is pretty straight forward but one should be careful to create copies of the matrix while recursing in order to receive good results:
from copy import copy, deepcopy
def calc(i, j, mat):
if i < 0 or j < 0 or i >= len(mat) or j >= len(mat[0]):
return 0 # out of borders
elif mat[i][j] == 1:
return 0 # this cell has already been visited
elif i == len(mat)-1 and j == len(mat[0])-1:
return 1 # reached destination (last cell)
else:
mat[i][j] = 1 # mark as visited
# create copies of the matrix for the recursion calls
m1 = deepcopy(mat)
m2 = deepcopy(mat)
m3 = deepcopy(mat)
m4 = deepcopy(mat)
# return the sum of results of the calls to the cells:
# down + up + right + left
return calc(i+1, j, m1) + calc(i-1, j, m2) + calc(i, j+1, m3) + calc(i, j-1, m4)
def do_the_robot_thing(m, n):
# an un-visited cell will be marked with "0"
mat = [[0]*n for x in xrange(m)]
return calc(0, 0, mat)
print(do_the_robot_thing(3, 3))
OUTPUT:
12