Why are the values being made into "None" instead of "LIVE"? - python

This is Game of Life program. When I tested it, my surrounding(row,col) function returned 0 even if the configuration file indicated that 8 squares would be made "LIVE." Just ran a test by printing the board after opening the configuration file, and it turns out instead of making the indicated squares say 'LIVE,' the ones that are 'LIVE' say 'None' so no 'LIVE' values are being counted.
[[None, None, None, 0, 0, 0, 0], [None, 0, None, 0, 0, 0, 0], [None, None, None, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]] is what I get when I print board. Can't see what I'm missing here?
LIVE = 1
DEAD = 0
def board(canvas, width, height, n):
for row in range(n+1):
for col in range(n+1):
canvas.create_rectangle(row*height/n,col*width/n,(row+1)*height/n,(col+1)*width/n,width=1,fill='black',outline='green')
n = int(raw_input("Enter the dimensions of the board: "))
width = n*25
height = n*25
from Tkinter import *
import math
window=Tk()
window.title('Game of Life')
canvas=Canvas(window,width=width,height=height,highlightthickness=0)
canvas.grid(row=0,column=0,columnspan=5)
board = [[DEAD for row in range(n)] for col in range(n)]
rect = [[None for row in range(n)] for col in range(n)]
for row in range(n):
for col in range(n):
rect[row][col] = canvas.create_rectangle(row*height/n,col*width/n,(row+1)*height/n,(col+1)*width/n,width=1,fill='black',outline='green')
#canvas.itemconfigure(rect[2][3], fill='red') #rect[2][3] is rectangle ID
#print rect
f = open('filename','r') #filename is whatever configuration file is chosen that gives the step() function to work off of for the first time
for line in f:
parsed = line.split()
print parsed
if len(parsed)>1:
row = int(parsed[0].strip())
col = int(parsed[1].strip())
board[row][col] = LIVE
board[row][col] = canvas.itemconfigure(rlist[row][col], fill='red')
def surrounding(row,col):
count = 0
if board[(row-1) % n][(col-1) % n] == LIVE:
count += 1
if board[(row-1) % n][col % n] == LIVE:
count += 1
if board[(row-1) % n][(col+1) % n] == LIVE:
count += 1
if board[row % n][(col-1) % n] == LIVE:
count += 1
if board[row % n][(col+1) % n] == LIVE:
count += 1
if board[(row+1) % n][(col-1) % n] == LIVE:
count +=1
if board[(row+1) % n ][col % n] == LIVE:
count += 1
if board[(row+1) % n][(col+1) % n] == LIVE:
count += 1
print count
return count
surrounding(1,1)

You're assigning to the items of your board nested list twice:
board[row][col] = LIVE
board[row][col] = canvas.itemconfigure(rlist[row][col], fill='red')
The first assigns 1 to the appropriate value, the second replaces the 1 with None, since that's the return value of canvas.itemconfigure when called with those arguments. I suspect (without testing it) that you should simply remove the assignment from the second statement:
board[row][col] = LIVE
canvas.itemconfigure(rlist[row][col], fill='red')
This might still have issues (such as rlist needing to be rect, perhaps?), but the issue with None values should be resolved.

Related

Trying to figure out horizontal wins for a connect 4 game in python

ive been trying to make a connect four game. As I was following a tutorial it abruptly ended and I wanting to finish it.
Right now im making win functions so the game can see if 4 peices are next to eachother horizontally. Right now i have this and it does give me a win if we put 4 peices in the first four colums. But if we do it on the last four the game continues which it should give the player a win.
(Im new to stack overflow sorry for the bad wording. You should try it yourself on your own IDE)
This snippet is running in html. It is a python program!
# Connect Four
# Creates 2d board list
numRows = 6
numCols = 7
board = [[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0], ]
### Prints board in 0's for empty boxs, and 1s, and 2s for players
def printBoard():
for rows in range(0, numRows):
for cols in range(0, numCols):
print(board[rows][cols], end=' ')
print(" ")
# Goes through all rows and checks if the player can place it there, if not it writes one above it
def fillIn(col, player):
col = col - 1
for rows in range(numRows - 1, -1, -1):
if board[rows][col] == 0:
print("Legal Move")
board[rows][col] = player
break
def win():
if horizWin() or vertWin() or diagWin():
return True
return False
def horizWin():
for row in board:
for i in range(len(row) - 4):
window = row[i:i + 4]
# check if all elements in the row are 1s or 2s
if window.count(window[0]) == len(window) and window[0] in [1, 2]:
print("You Win!")
return True
def vertWin():
for col in board:
()
def diagWin():
pass
player = 1
while not win():
col = int(input("Please Select A Colum 1-7: "))
if col > 7:
print("That colum is over 7!")
fillIn(col, player)
printBoard()
if player == 1:
player = 2
else:
player = 1
Cool project!
Python's range excludes the upper bound, meaning range(n) produces [0, 1, 2, ..., n-1], and range(len(row) - 4), like you have, will produce [0, 1, 2, ..., len(row)-5]. If you want the last 4 elements of a row ([row[len(row)-4], row[len(row)-3], row[len(row)-2], row[len(row)-1]]), you need to start on i = len(row)-4. Currently your code doesn't do this, because it has the line for i in range(len(row) - 4):. Try changing that to for i in range(len(row) - 3):.

Max interval intersection point

I am trying to implement the logic in python. Given a set of intervals, find the interval which has the maximum number of intersections. If input (1,6) (2,3) (4,11), then (1,6) should be returned. This has been answered in below but I have been unable to implement it in python.
given-a-set-of-intervals-find-the-interval-which-has-the-maximum-number-of-inte.
So far I am using the below logic. Any help will be greatly appreciated.
def interval_intersection(intervals):
if len(intervals) ==1:
return intervals
intervals.sort(key=lambda x: x[0])
res =[intervals[0]]
for i in range(1,len(intervals)):
if intervals[i][0] > res[-1][1]:
res.append(intervals[i])
else:
res[-1] = [min(res[-1][0],intervals[i][0]),max(res[-1][1],intervals[i][1])]
return res
Examples:
[[1,5],[5,10],[5,5]]
ans should be [5,5]
In case of tie [5,5] the interval with least number of elements . Here [5,5] has only 1 element in the range ie 5 hence the ans
[[1,2],[3,5]]
no intersection return -1
This is a fairly straightforward implementation of David Eisenstat's algorithm. The only subtleties are:
I assume that all intervals are closed on both ends, which means that sorting events should put starts before ends if they're simultaneous. If you want intervals that are fully open, or open on the right side, this order needs to be reversed.
The returned interval has the most intersections, with ties broken first by smallest length, then by earliest start.
def interval_solve(intervals: Sequence[Sequence[int]]) -> Union[Sequence[int], int]:
start_type = -1 # Assumes all intervals are closed
end_type = 1
events = [(s, start_type, i) for i, (s, e) in enumerate(intervals)]
events.extend((e, end_type, i) for i, (s, e) in enumerate(intervals))
events.sort()
inter_count: Dict[int, int] = {}
start_count = 0
stop_count = 0
for event_time, event_type, event_id in events:
if event_type == start_type:
start_count += 1
inter_count[event_id] = -(stop_count + 1)
else:
stop_count += 1
inter_count[event_id] += start_count
# Find max by most intersections, then by shortest interval, then by earliest start
answer = max(range(len(intervals)),
key=lambda j: (inter_count[j], intervals[j][0] - intervals[j][1]))
if inter_count[answer] == 0:
return -1
return intervals[answer]
The actual idea is pretty simple, we sort the intervals and store some of them with an index and a boolean key for indicating the start or end events.
Then, we just traverse it while counting the end events before an index and the start events. For any index i, interval overlap count is simply, number of start events before - number of end events before (-1).
Finally, we can just check which one has the minimum length in case of multiple solutions.
# https://stackoverflow.com/questions/69426852/max-interval-intersection-point
def max_interval_count(intervals):
interval_sorted = []
for idx, interval in enumerate(intervals):
s, e = interval
interval_sorted.append([s, idx, 0]) # 0 for start
interval_sorted.append([e, idx, 1]) # 1 for end
interval_sorted.sort(key = lambda x: x[0])
print(interval_sorted)
number_of_starts = 0
number_of_ends = 0
overlap_count = {}
for event in interval_sorted:
_, idx, start_end = event
if start_end == 0: # start event
# subtract all the ending before it
overlap_count[idx] = - (number_of_ends)
number_of_starts += 1
else: # end event
overlap_count[idx] += (number_of_starts - 1) # -1 as we should not include the start from the same interval
number_of_ends += 1
print(overlap_count)
ans_idx = -1
max_over_count = 0
min_len_interval = 99999999999
for idx, overl_cnt in overlap_count.items():
if overl_cnt > max_over_count:
ans_idx = idx
max_over_count = overl_cnt
elif overl_cnt == max_over_count and overl_cnt > 0 and (intervals[idx][1] - intervals[idx][0] + 1) < min_len_interval:
min_len_interval = (intervals[idx][1] - intervals[idx][0] + 1)
ans_idx = idx
if ans_idx == -1:
return ans_idx
return intervals[ans_idx]
if __name__ == "__main__":
test_1 = [[1,5],[5,10],[5,5]]
test_2 = [[1,2],[3,5]]
test_3 = [(1,6), (2,3), (4,11)]
ans = max_interval_count(test_1)
print(ans)
print("---------")
ans = max_interval_count(test_2)
print(ans)
print("---------")
ans = max_interval_count(test_3)
print(ans)
print("---------")
[[1, 0, 0], [5, 0, 1], [5, 1, 0], [5, 2, 0], [5, 2, 1], [10, 1, 1]]
{0: 0, 1: 1, 2: 1}
[5, 5]
---------
[[1, 0, 0], [2, 0, 1], [3, 1, 0], [5, 1, 1]]
{0: 0, 1: 0}
-1
---------
[[1, 0, 0], [2, 1, 0], [3, 1, 1], [4, 2, 0], [6, 0, 1], [11, 2, 1]]
{0: 2, 1: 1, 2: 1}
(1, 6)
---------

TypeError: int() argument must be a string, a bytes-like object or a number, not 'method'

I am working on a python sudoku solver but I am not sure why I get this error: TypeError: int() argument must be a string, a bytes-like object or a number, not 'method'
The error occurs when the solve function calls the possible function. Maybe I'm just not thinking clearly as I have been doing this most of the day but here is the code:
import copy
class Sudoku:
def __init__(self, board, cells):
self.board = board
self.cells = cells
# Creates a board
def newboard(self):
values = self.cells.split(" ")
val_counter = 0
if len(values) != 81:
print("Error: Not enough values.")
exit()
else:
for i in range(9):
for j in range(9):
self.board[i][j] = values[val_counter]
val_counter += 1
# Returns row
def findValidRow(self):
for i in range(9):
for j in range(9):
if int(self.board[i][j]) == 0:
return int(i)
return True
# Returns col
def findValidCol(self):
for i in range(9):
for j in range(9):
if int(self.board[i][j]) == 0:
return int(j)
return True
def possible(self, row, col, val):
# Check row for value
for i in range(9):
if self.board[int(row)][i] == val:
return False
# Checks col for value
for j in range(9):
if self.board[j][col]:
return False
# Checks square for value
coordX, coordY = 3 * (row // 3), 3 * (col // 3)
for x in range(coordX, coordX + 3):
for y in range(coordY, coordY + 3):
if coordX == row and coordY == col:
continue
if self.board == val:
return False
return True
# Solves the board
def solve(self):
# Checks if cells are all solved
if self.findValidCol == True:
print(self.board)
return True
# Finds first cell to fill
row = copy.deepcopy(self.findValidRow)
col = copy.deepcopy(self.findValidCol)
for i in range(1, 10):
if self.possible(row, col, i):
self.board[row][col] = i
# Updates values to find new cell to fill
row = copy.deepcopy(self.findValidRow)
col = copy.deepcopy(self.findValidCol)
if self.solve():
return True
# Backtracks
self.board[row][col] = 0
return False
# Get cell values and call solve function
get_cells = input("Enter cell values seperated by 1 space. Enter 0 for empty cells: ")
b = Sudoku([[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0]], get_cells)
b.newboard()
b.solve()
Change this :
row = copy.deepcopy(self.findValidRow)
col = copy.deepcopy(self.findValidCol)
to :
row = self.findValidRow()
col = self.findValidCol()
You dont need to deep copy int, and you need to call the methods not to pass them
Other thing you should pay attention, your methods may return True if it wont find, its not best practice, to return different primitives types, if you dont find, maybe consider to return -1
def solve(self):
# Checks if cells are all solved
if self.findValidCol() == True:
print(self.board)
return True
# Finds first cell to fill
row = self.findValidRow()
col = self.findValidCol()
for i in range(1, 10):
if self.possible(row, col, i):
self.board[row][col] = i
# Updates values to find new cell to fill
row = self.findValidRow()
col = self.findValidCol()
if self.solve():
return True
# Backtracks
self.board[row][col] = 0
return False
Your solve method needs to be modified, add parenthesis into the deep copy part, also the deep copy won't be necessary
and the check if true part (if self.findValidCol() == True) because the findValidRow and Col are methods
I assume your mistake is here:
row = copy.deepcopy(self.findValidRow)
col = copy.deepcopy(self.findValidCol)
You are copying the method references. What you really want is to copy the result of the method like
row = copy.deepcopy(self.findValidRow())
col = copy.deepcopy(self.findValidCol())
Edit:
Ignore this answer, copy is unnecessary as int is passed to a function by value
Instead look at the other answer and check your code for more mistakes where you forget to call methods

2D List not calculating properly

I'm trying to do a game of life. There's this weird bug I can't really fix because I don't really know where the problem is? I'm guessing it's in the loop? I don't really know. I tried to debug it using the if total > 0 print(total), and the total is only 2 when it should've been 3. I'm sorry if i'm explaining it confusing because i'm also confused.
def test():
board = [[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]]
#Tracking the neighbor, it shows that there is 3 alive neighbors in
#here.
print(board[2][1])
print(board[2-1][1+1])
print(board[2][1+1])
print(board[2+1][1+1])
return board
def update(grid, N):
newGrid = grid.copy()
for i in range(N):
if i == 0 or i == 4:
continue
for j in range(N):
if j == 0 or j == 4:
continue
total = 0
total = total + grid[i][j-1] #
total = total + grid[i][j+1] #
total = total + grid[i-1][j] #
total = total + grid[i+1][j] #
total = total + grid[i-1][j-1] #
total = total + grid[i-1][j+1] #
total = total + grid[i+1][j-1] #
total = total + grid[i+1][j+1] #
# In here it only states that there's only 2 alive neighbors
# when there should've been 3
if total > 0:
print(total)
# apply Conway's rules
if grid[i][j] == 1:
if (total < 2) or (total > 3):
newGrid[i][j] = 0
elif total == 3:
newGrid[i][j] = 1
else:
if total == 3:
newGrid[i][j] = 1
grid[:] = newGrid[:]
return(grid)
f = 0
zboard = test()
while f <= 3:
print("Generation: " + str(f))
gen = update(zboard, 5)
for i in gen:
print(i)
f += 1
You need to use deepcopy.
When you do newGrid = grid.copy(), since you have a 2D-list, the sublists in newGrid will not be independent from the sublists from grid. Your problem was that grid was updated each time you updated newGrid.
You need to replace this:
newGrid = grid.copy()
by this :
import copy
newGrid = copy.deepcopy(grid)
Here is an example to show you what was happening. cop_1 is a dependent copy whereas cop_2 is an independent one :
board = [[0, 0],
[0, 0]]
cop_1 = board.copy()
import copy
cop_2 = copy.deepcopy(board)
board[0][0] = 3 # change a value of board
print("cop_1[0][0] =", cop_1[0][0])
# cop_1[0][0] = 3
print("cop_2[0][0] =", cop_2[0][0])
# cop_2[0][0] = 0
If you run the code like you posted, you get a mistake because you didn't indent the lines after your function def test(): until return board

Not printing a value make's it behave weird [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 6 years ago.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Improve this question
To start, sorry for the not-so-descriptive tittle, I really don't know how to call this problem, so let's start...
I have to designe an AI for a 4x4x4-4 in line game and we're using Monte Carlo method to do this AI, so basically I have to make a simulation of the posible moves of the computer n-times and determine which next move is the best posible, our professor gave us some base code to play a 4x4x4-4 in line game and we had to designe the AI so here is my method to simulate the AI:
EDIT1: Added full program.
from __future__ import print_function
import random
from copy import deepcopy
import time
board = [
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
]
p1_token = 1
p2_token = -1
draw_token = 0
pool = ThreadPool(processes=1)
def slice_winner(state_slice):
slice_size = len(state_slice)
sums = [sum(row) for row in state_slice]
sums.extend([sum([row[i] for row in state_slice]) for i in range(slice_size)])
if (p1_token * slice_size) in sums:
return p1_token
elif (p2_token * slice_size) in sums:
return p2_token
return 0
def winner(state):
for state_slice in state:
winner_in_slice = slice_winner(state_slice)
if winner_in_slice != draw_token:
return winner_in_slice
state_size = len(state)
for i in range(state_size):
state_slice = []
for j in range(state_size):
state_slice.append([state[j][i][k] for k in range(state_size)])
winner_in_slice = slice_winner(state_slice)
if winner_in_slice != draw_token:
return winner_in_slice
diagonals = [0, 0, 0, 0]
for i in range(state_size):
diagonals[0] += state[i][i][i]
diagonals[1] += state[state_size - 1 - i][i][i]
diagonals[2] += state[i][state_size - 1 - i][i]
diagonals[3] += state[state_size - 1 - i][state_size - 1 - i][i]
if (p1_token * state_size) in diagonals:
return p1_token
elif (p2_token * state_size) in diagonals:
return p2_token
return draw_token
def str_token(cell):
if cell == p1_token:
return "X"
elif cell == p2_token:
return "O"
return "."
def draw_board(state):
result = ""
state_size = len(state)
for y in range(state_size):
for z in range(state_size):
for x in range(state_size):
result += str_token(state[x][y][z]) + " "
result += "\t"
result += "\n"
return result
def isInVector(vector, x, y, z):
n = 0
while (n < len(vector)):
if (vector[n][0] == x and vector[n][1] == y and vector[n][2] == z):
return True
n += 1
return False
def getInVector(vector, x, y, z):
n = 0
while (n < len(vector)):
if (vector[n][0] == x and vector[n][1] == y and vector[n][2] == z):
return n
n += 1
return -1
def getBestPlay(vector):
max_value = -100000
index_of_max = -1
for i in range(len(vector)):
if (vector[i][3] > max_value):
max_value = vector[i][3]
index_of_max = i
return [vector[index_of_max][0], vector[index_of_max][1], vector[index_of_max][2]]
def AISim(main_state, p1x, p1y, p1z, maxIt):
n = 0 # Number of simulations
p2Moves = [] # A vector to hold player 2 moves.
while (n < maxIt): # While 1
x = p1x
y = p1y
z = p1z
player_turn = False # False because simulation will always start with player 2 turn
moves = 0
first_move = [0, 0, 0]
new_state = deepcopy(main_state)
while winner(new_state) == draw_token: # While 2
temp = new_state[x][y][z]
while temp != draw_token: # While 3
x = random.randint(0, 3)
y = random.randint(0, 3)
z = random.randint(0, 3)
temp = new_state[x][y][z]
# THIS IS THE PROBLEEEEEM!!!!
print (temp)
# END while 3
if (moves == 0):
first_move = [x, y, z]
if (not isInVector(p2Moves, x, y, z)):
p2Moves.append([x, y, z, 0])
# END if
# END if
new_state[x][y][z] = (1 if player_turn else -1)
player_turn = not player_turn
moves += 1
# END while 2
if (winner(new_state) == 1):
temPos = getInVector(p2Moves, first_move[0], first_move[1], first_move[2])
p2Moves[temPos][3] -= 1
else:
temPos = getInVector(p2Moves, first_move[0], first_move[1], first_move[2])
p2Moves[temPos][3] += 1
# END if-else
n += 1
# END while 1
return getBestPlay(p2Moves)
# --------------------------------------------------------------------------------------------------------
# ----------------------------------------- MAIN PROGRAM -------------------------------------------------
# --------------------------------------------------------------------------------------------------------
AIMove = [0, 0, 0]
player_1_turn = True
while winner(board) == draw_token:
# Print board state
print ("")
print ("Board:")
print (draw_board(board))
print ("")
# Print
print ("Player %s turn:" % (1 if player_1_turn else 2))
if (player_1_turn):
# Get input
x = int(raw_input("x: "))
y = int(raw_input("y: "))
z = int(raw_input("z: "))
else:
# Player 2 turn
start = time.time()
AIMove = AISim(board, x, y, z, 500)
end = time.time()
print ("Thinking time: %0.4f seconds" % (end - start))
x = AIMove[0]
print ("x:",x)
y = AIMove[1]
print ("y:",y)
z = AIMove[2]
print ("z:",z)
if board[x][y][z] == draw_token:
board[x][y][z] = 1 if player_1_turn else -1
player_1_turn = not player_1_turn
else:
print ("")
print ("ERROR: occupied position, please retry in a new position")
print ("")
print ("Player %s is the winner!" % (1 if winner(board) == 1 else 2))
And the code works I've test it many times and all, the problem is in the "While 3", for an unknown reason SOMETIMES the code may get stuck in a somewhat infinite loop regardless of the state of the board, like if there were no posible (x, y, z) coordinates that make the loop end, but I know there're! Because for the same unknown reason if I place a print (x, y, z) inside the loop, the AI work perfectly 100% of the times, but wait, there's more! As you might notice by printing (x, y, z) inside that loop make's the final output horrible, so I try print(x, y, z, end='\r) (I'm importing from __future__ import print_function) and it didn't work, the same weird behavior again, I've try so many things but the only solution seems to be printing the values I've been trying for hours and I need help, please!
You can test the program here.
My list of fail trys:
Save (x, y, z) in another array. It worked if I printed the vector...
Writing in an eternal file the coordinates, the coordinates were saved correctly, but it didn't fix it.
Assigning the random values generated to other variables and then those variables to the x, y and z.
Printing only 1 or 2 values, like x and z.
Swap x, y and z values.
EDIT2: Added a link to c9 so you can test it.
EDIT3: If you run it in your PC you might want to change the number of simulations, because printing over 1000 simulations might feel like it is infinite, but it isn't. Try printing like 100 or 300 simulations it might take a while but you'll see it working, then try like 1000 without printing(time should be the same since printing takes so much time) and you'll see it doesn't work.
EDIT4: Removed threading code.
UPDATE: I don't need this anymore for an assignment, I solve it in other way not using this code, but I really want to know what is happening and why does the print make's the program work or not.
P.S. Sorry again if it's somehow unclear what is happening, but I don't really know how to explain it better than this.

Categories

Resources