Placing ships in tkinter-based Battleship game - python

I'm making a Battleship game using Python and Tkinter for GUI but I'm having some difficulties when it comes to the placement of the ships.
I'm using the random library (as rd) but there are 2 problems in my code:
The code only places one squared "ships" instead of varying from 1-5 depending on the length of each ship.
I can't prevent the ships from accumulating (being on top of each other).
Here is the function:
def place_boats(self):
possible_coordinates = [0,1,2,3,4,5,6,7]
possible_directions = ["horizontal","vertical"]
direction = rd.choice(possible_directions)
for i in range(5):
if direction == "horizontal":
row = 0
column = rd.choice(possible_coordinates)
else:
row = rd.choice(possible_coordinates)
size = 1
# self.board_status is an array composed of zeros and ones represent the ships
for j in range(size):
self.board_status[row+i][column] = 1
size += 1

Related

Average time to hit a given line on 2D random walk on a unit grid

I am trying to simulate the following problem:
Given a 2D random walk (in a lattice grid) starting from the origin what is the average waiting time to hit the line y=1-x
import numpy as np
from tqdm import tqdm
N=5*10**3
results=[]
for _ in tqdm(range(N)):
current = [0,0]
step=0
while (current[1]+current[0] != 1):
step += 1
a = np.random.randint(0,4)
if (a==0):
current[0] += 1
elif (a==1):
current[0] -= 1
elif (a==2):
current[1] += 1
elif (a==3):
current[1] -= 1
results.append(step)
This code is slow even for N<10**4 I am not sure how to optimize it or change it to properly simulate the problem.
Instead of simulating a bunch of random walks sequentially, lets try simulating multiple paths at the same time and tracking the probabilities of those happening, for instance we start at position 0 with probability 1:
states = {0+0j: 1}
and the possible moves along with their associated probabilities would be something like this:
moves = {1+0j: 0.25, 0+1j: 0.25, -1+0j: 0.25, 0-1j: 0.25}
# moves = {1: 0.5, -1:0.5} # this would basically be equivelent
With this construct we can update to new states by going over the combination of each state and each move and update probabilities accordingly
def simulate_one_step(current_states):
newStates = {}
for cur_pos, prob_of_being_here in current_states.items():
for movement_dist,prob_of_moving_this_way in moves.items():
newStates.setdefault(cur_pos+movement_dist, 0)
newStates[cur_pos+movement_dist] += prob_of_being_here*prob_of_moving_this_way
return newStates
Then we just iterate this popping out all winning states at each step:
for stepIdx in range(1, 100):
states = simulate_one_step(states)
winning_chances = 0
# use set(keys) to make copy so we can delete cases out of states as we go.
for pos, prob in set(states.items()):
# if y = 1-x
if pos.imag == 1 - pos.real:
winning_chances += prob
# we no longer consider this a state that propogated because the path stops here.
del states[pos]
print(f"probability of winning after {stepIdx} moves is: {winning_chances}")
you would also be able to look at states for an idea of the distribution of possible positions, although totalling it in terms of distance from the line simplifies the data. Anyway, the final step would be to average the steps taken by the probability of taking that many steps and see if it converges:
total_average_num_moves += stepIdx * winning_chances
But we might be able to gather more insight by using symbolic variables! (note I'm simplifying this to a 1D problem which I describe how at the bottom)
import sympy
x = sympy.Symbol("x") # will sub in 1/2 later
moves = {
1: x, # assume x is the chances for us to move towards the target
-1: 1-x # and therefore 1-x is the chance of moving away
}
This with the exact code as written above gives us this sequence:
probability of winning after 1 moves is: x
probability of winning after 2 moves is: 0
probability of winning after 3 moves is: x**2*(1 - x)
probability of winning after 4 moves is: 0
probability of winning after 5 moves is: 2*x**3*(1 - x)**2
probability of winning after 6 moves is: 0
probability of winning after 7 moves is: 5*x**4*(1 - x)**3
probability of winning after 8 moves is: 0
probability of winning after 9 moves is: 14*x**5*(1 - x)**4
probability of winning after 10 moves is: 0
probability of winning after 11 moves is: 42*x**6*(1 - x)**5
probability of winning after 12 moves is: 0
probability of winning after 13 moves is: 132*x**7*(1 - x)**6
And if we ask the OEIS what the sequence 1,2,5,14,42,132... means it tells us those are Catalan numbers with the formula of (2n)!/(n!(n+1)!) so we can write a function for the non-zero terms in that series as:
f(n,x) = (2n)! / (n! * (n+1)!) * x^(n+1) * (1-x)^n
or in actual code:
import math
def probability_of_winning_after_2n_plus_1_steps(n, prob_of_moving_forward = 0.5):
return (math.factorial(2*n)/math.factorial(n)/math.factorial(n+1)
* prob_of_moving_forward**(n+1) * (1-prob_of_moving_forward)**n)
which now gives us a relatively instant way of calculating relevant parameters for any length, or more usefully ask wolfram alpha what the average would be (it diverges)
Note that we can simplify this to a 1D problem by considering y-x as one variable: "we start at y-x = 0 and move such that y-x either increases or decreases by 1 each move with equal chance and we are interested when y-x = 1. This means we can consider the 1D case by subbing in z=y-x.
Vectorisation would result in much faster code, approximately ~90K times faster. Here is the function that would return step to hit y=1-x line starting from (0,0) and trajectory generation on the 2D grid with unit steps .
import numpy as np
def _random_walk_2D(sim_steps):
""" Walk on 2D unit steps
return x_sim, y_sim, trajectory, number_of_steps_first_hit to y=1-x """
random_moves_x = np.insert(np.random.choice([1,0,-1], sim_steps), 0, 0)
random_moves_y = np.insert(np.random.choice([1,0,-1], sim_steps), 0, 0)
x_sim = np.cumsum(random_moves_x)
y_sim = np.cumsum(random_moves_y)
trajectory = np.array((x_sim,y_sim)).T
y_hat = 1-x_sim # checking if hit y=1-x
y_hit = y_hat-y_sim
hit_steps = np.where(y_hit == 0)
number_of_steps_first_hit = -1
if hit_steps[0].shape[0] > 0:
number_of_steps_first_hit = hit_steps[0][0]
return x_sim, y_sim, trajectory, number_of_steps_first_hit
if number_of_steps_first_hit is -1 it means trajectory does not hit the line.
A longer simulation and repeating might give the average behaviour, but the following one tells if it does not escape to Infiniti it hits line on average ~84 steps.
sim_steps= 5*10**3 # 5K steps
#Repeat
nrepeat = 40000
hit_step = [_random_walk_2D(sim_steps)[3] for _ in range(nrepeat)]
hit_step = [h for h in hit_step if h > -1]
np.mean(hit_step) # ~84 step
Much longer sim_steps will change the result though.
PS:
Good exercise, hope that this wasn't a homework, if it was homework, please cite this answer if it is used.
Edit
As discussed in the comments current _random_walk_2D works for 8-directions. To restrict it to cardinal direction we could do the following filtering:
cardinal_x_y = [(t[0], t[1]) for t in zip(random_moves_x, random_moves_y)
if np.abs(t[0]) != np.abs(t[1])]
random_moves_x = [t[0] for t in cardinal_x_y]
random_moves_y = [t[1] for t in cardinal_x_y]
though this would slow it down the function a bit but still will be super fast compare to for loop solutions.

Simulating the transition of packages in a distribution line

I am new to python and am trying to run a simulation of a warehouse logistics. The problem is composed of four main agents:
a shed, trucks, motorcycles and a distribution line. The truck enters the shed in one side with a specified amount of boxes, it goes to center of the shed, stops and start unloading the boxes to the distribution line, the distribution line moves the boxes to the other side of the shed where motorcycles pickup one box each.
The objective is to vary the size of the shed and distribution line to find the shape that can deliver more boxes in fixed amount of time (or compute the time taken to distribute a fixed amount of boxes, as in my code for now)
The distribution line is a rectangle, a grid with variable amount of rows and columns, depending on the size of the shed, let's say each cell has 0,50m on each side.
In the code I simulated the truck passing through the shed, and the amount of trucks passing as iterations, the problems is:
how to simulate the boxes moving through the grid (distribution line) from one side to the other, maybe accumulating in the stock until a bike arrives, and have the motorcycles "grab" them and go out after the boxes arrive?
I tried to count the boxes with "+= 1" function but I don't know why it's not working (would not be very realistic as well)
This is the main code:
import time
from Vehicles import Truck, Motorbike
bike1 = Motorbike(10, 1)
truck1 = Truck(10, int(input("Enter how many loads the truck has: ")))
num_iterations = int(input("Enter number of iterations: "))
start = time.time()
shed_width = 4
shed_length = 12
truck_path = int(shed_length * truck1.truck_speed/2)
for n in range(num_iterations):
truck_middle = False
while truck_middle is not True:
for i in range(truck_path):
x = 100/truck_path
if i == truck_path/2:
truck_middle = True
else:
#the bar here is to just have some visual feedback while the code runs
print("\r[%-60s] %d%%" % ('=' * i, x * i), end='')
time.sleep(0.1)
print("\ntruck is in the middle")
truck_middle = True
# while truck_middle is True:
# box = 0
# if box < truck1.truck_load:
# box += 1
# else:
# truck_middle = False
print("This was iteration: " + str(n+1))
time.sleep(0.01)
end = time.time()
print("\nDone! \nThe simulation took " + str(end - start) + " seconds to complete!")
I also created a class in a file called "Vehicles" for the truck and the motorcycles, where I can define their speed and the load they can carry:
class Truck:
def __init__(self, truck_speed, truck_load):
self.truck_speed = truck_speed
self.truck_load = truck_load
class Motorbike:
def __init__(self, motorbike_speed, motorbike_load):
self.motorbike_speed = motorbike_speed
self.motorbike_load = motorbike_load
I am open to code suggestions, indications of libraries and other resources I can search and study, any help will be much appreciated! thanks!
box = 0
while truck_middle == True:
if box < truck1.truck_load:
box += 1
else:
truck_middle = False
In your way, box will always be 1 and truck_middle is always True, and it goes in a dead loop

Python Conway's Game of Life not behaving properly for glider gun design

I'm trying to replicate Conway's Game of Life in Python.
The rules of this simulation and a properly working version of it can be found here: https://bitstorm.org/gameoflife/
In my version, when I randomly assign cells to being alive at the start, it appears to behave properly, with the classic amourphous blobs of cells expanding over the screen.
However, when I replicate the "glider gun" arrangement (which can also be seen on the linked website), the cells are not updating properly: the structures decay slightly and then the movement of the cells remains stagnant.
This leads me to believe I have a logical error in my code - any help would be appreciated!
This is the section of my code within my Cell class that updates
its survival based on its neighbors (the eight cells surrounding it):
def update(self, neighbors):
numAliveNeighbors = 0
for neighbor in neighbors:
if neighbor.isAlive:
numAliveNeighbors+=1
if numAliveNeighbors <= 1 or numAliveNeighbors >=4:
self.isAlive = False
elif not self.isAlive and numAliveNeighbors is 3:
self.isAlive = True
This is the section of my code that finds all of the neighbors of every cell and calls the update method on them:
for row in range(len(cells)):
for column in range(len(cells[row])):
neighbors = []
for tempRow in range (row-1, row + 2):
for tempColumn in range (column-1, column + 2):
if tempRow >= 0 and tempRow < len(cells):
if tempColumn >= 0 and tempColumn < len(cells[tempRow]):
if not(tempRow is row and tempColumn is column):
neighbors.append(cells[tempRow][tempColumn])
cells[row][column].update(neighbors)
In-Place Updates
You're doing in-place updates; that is, you update each cell individually. What you need to do is create a cloned grid and each time you update a cell on an iteration, update the cloned grid, then set the game board to the cloned grid. Otherwise, cells are going to be updated based on the current iteration, which doesn't make sense.
What you could do is something like this:
def isAlive(alive, neighbours):
return (alive and 2 <= neighbours <= 3) or (not alive and neighbours == 3)
def update(cells):
grid = [[0] * len(row) for row in cells]
for row in range(len(cells)):
for col in range(len(cells[row])):
neighbours = 0
for tr in range(row - 1, row + 2):
for tc in range(col - 1, col + 2):
if (tr != row or tr != col) and cells[tr][tc]:
neighbours += 1
grid[row][col] = isAlive(cells[row][col], neighbours)
return grid
Then you can call cells = update(cells) in a loop.

Spawning objects in groups when the first object of the group was spawned randomly Python

I'm currently doing a project, and in the code I have, I'm trying to get trees .*. and mountains .^. to spawn in groups around the first tree or mountain which is spawned randomly, however, I can't figure out how to get the trees and mountains to spawn in groups around a single randomly generated point. Any help?
grid = []
def draw_board():
row = 0
for i in range(0,625):
if grid[i] == 1:
print("..."),
elif grid[i] == 2:
print("..."),
elif grid[i] == 3:
print(".*."),
elif grid[i] == 4:
print(".^."),
elif grid[i] == 5:
print("[T]"),
else:
print("ERR"),
row = row + 1
if row == 25:
print ("\n")
row = 0
return
There's a number of ways you can do it.
Firstly, you can just simulate the groups directly, i.e. pick a range on the grid and fill it with a specific figure.
def generate_grid(size):
grid = [0] * size
right = 0
while right < size:
left = right
repeat = min(random.randint(1, 5), size - right) # *
right = left + repeat
grid[left:right] = [random.choice(figures)] * repeat
return grid
Note that the group size need not to be uniformly distributed, you can use any convenient distribution, e.g. Poisson.
Secondly, you can use a Markov Chain. In this case group lengths will implicitly follow a Geometric distribution. Here's the code:
def transition_matrix(A):
"""Ensures that each row of transition matrix sums to 1."""
copy = []
for i, row in enumerate(A):
total = sum(row)
copy.append([item / total for item in row])
return copy
def generate_grid(size):
# Transition matrix ``A`` defines the probability of
# changing from figure i to figure j for each pair
# of figures i and j. The grouping effect can be
# obtained by setting diagonal entries A[i][i] to
# larger values.
#
# You need to specify this manually.
A = transition_matrix([[5, 1],
[1, 5]]) # Assuming 2 figures.
grid = [random.choice(figures)]
for i in range(1, size):
current = grid[-1]
next = choice(figures, A[current])
grid.append(next)
return grid
Where the choice function is explained in this StackOverflow answer.

How to deal with very big Bitboards

I'm working on a 2-player board game (e.g. connect 4), with parametric board size h, w. I want to check for winning condition using hw-sized bitboards.
In game like chess, where board size is fixed, bitboards are usually represented with some sort of 64-bit integer. When h and w are not constant and maybe very big (let's suppose 30*30) are bitboards a good idea? If so, are the any data types in C/C++ to deal with big bitboards keeping their performances?
Since I'm currently working on python a solution in this language is appreciated too! :)
Thanks in advance
I wrote this code while ago just to play around with the game concept. There is no intelligence behaviour involve. just random moves to demonstrate the game. I guess this is not important for you since you are only looking for a fast check of winning conditions. This implementation is fast since I did my best to avoid for loops and use only built-in python/numpy functions (with some tricks).
import numpy as np
row_size = 6
col_size = 7
symbols = {1:'A', -1:'B', 0:' '}
def was_winning_move(S, P, current_row_idx,current_col_idx):
#****** Column Win ******
current_col = S[:,current_col_idx]
P_idx= np.where(current_col== P)[0]
#if the difference between indexes are one, that means they are consecutive.
#we need at least 4 consecutive index. So 3 Ture value
is_idx_consecutive = sum(np.diff(P_idx)==1)>=3
if is_idx_consecutive:
return True
#****** Column Win ******
current_row = S[current_row_idx,:]
P_idx= np.where(current_row== P)[0]
is_idx_consecutive = sum(np.diff(P_idx)==1)>=3
if is_idx_consecutive:
return True
#****** Diag Win ******
offeset_from_diag = current_col_idx - current_row_idx
current_diag = S.diagonal(offeset_from_diag)
P_idx= np.where(current_diag== P)[0]
is_idx_consecutive = sum(np.diff(P_idx)==1)>=3
if is_idx_consecutive:
return True
#****** off-Diag Win ******
#here 1) reverse rows, 2)find new index, 3)find offest and proceed as diag
reversed_rows = S[::-1,:] #1
new_row_idx = row_size - 1 - current_row_idx #2
offeset_from_diag = current_col_idx - new_row_idx #3
current_off_diag = reversed_rows.diagonal(offeset_from_diag)
P_idx= np.where(current_off_diag== P)[0]
is_idx_consecutive = sum(np.diff(P_idx)==1)>=3
if is_idx_consecutive:
return True
return False
def move_at_random(S,P):
selected_col_idx = np.random.permutation(range(col_size))[0]
#print selected_col_idx
#we should fill in matrix from bottom to top. So find the last filled row in col and fill the upper row
last_filled_row = np.where(S[:,selected_col_idx] != 0)[0]
#it is possible that there is no filled array. like the begining of the game
#in this case we start with last row e.g row : -1
if last_filled_row.size != 0:
current_row_idx = last_filled_row[0] - 1
else:
current_row_idx = -1
#print 'col[{0}], row[{1}]'.format(selected_col,current_row)
S[current_row_idx, selected_col_idx] = P
return (S,current_row_idx,selected_col_idx)
def move_still_possible(S):
return not (S[S==0].size == 0)
def print_game_state(S):
B = np.copy(S).astype(object)
for n in [-1, 0, 1]:
B[B==n] = symbols[n]
print B
def play_game():
#initiate game state
game_state = np.zeros((6,7),dtype=int)
player = 1
mvcntr = 1
no_winner_yet = True
while no_winner_yet and move_still_possible(game_state):
#get player symbol
name = symbols[player]
game_state, current_row, current_col = move_at_random(game_state, player)
#print '******',player,(current_row, current_col)
#print current game state
print_game_state(game_state)
#check if the move was a winning move
if was_winning_move(game_state,player,current_row, current_col):
print 'player %s wins after %d moves' % (name, mvcntr)
no_winner_yet = False
# switch player and increase move counter
player *= -1
mvcntr += 1
if no_winner_yet:
print 'game ended in a draw'
player = 0
return game_state,player,mvcntr
if __name__ == '__main__':
S, P, mvcntr = play_game()
let me know if you have any question
UPDATE: Explanation:
At each move, look at column, row, diagonal and secondary diagonal that goes through the current cell and find consecutive cells with the current symbol. avoid scanning the whole board.
extracting cells in each direction:
column:
current_col = S[:,current_col_idx]
row:
current_row = S[current_row_idx,:]
Diagonal:
Find the offset of the desired diagonal from the
main diagonal:
diag_offset = current_col_idx - current_row_idx
current_diag = S.diagonal(offset)
off-diagonal:
Reverse the rows of matrix:
S_reversed_rows = S[::-1,:]
Find the row index in the new matrix
new_row_idx = row_size - 1 - current_row_idx
current_offdiag = S.diagonal(offset)

Categories