River crossing simulator
Take your typical river crossing problem
Yellow won't go with Black
Red will only go alone
Pink will only go if Yellow is going
Blue won't go with Green, and will only go if Yellow is going
Green will go without any conditions
Black won't go alone and won't go with any of Yellow and Blue.
With the tool you can see the puzzle in action!

Also, you won't accidentally break any of the conditions, as it won't let you. It will count the number of trips too.
Here is the code:
import pygame
from random import randint, choice
pygame.init()
wn = pygame.display.set_mode((600, 600))
font = pygame.font.SysFont('Arial', 30)
people = []
class Person():
def __init__(self, name, x, y, color, side='UP', w=20, h=20, hate=[], like=[], alone=None):
self.name = name
self.color = color
self.x = x
self.y = y
self.w = w
self.h = h
self.alone = alone
self.hate = hate
self.like = like
self.side = side
self.obj = pygame.Rect(x, y, w, h)
people.append(self)
conditions = []
if alone == False:
conditions.append("won't go alone")
elif alone == True:
conditions.append("will only go alone")
if len(hate) > 1:
conditions.append(f"won't go with any of: {', '.join(hate)}")
elif len(hate) == 1:
conditions.append(f"won't go with {hate[0]}")
if len(like) > 1:
conditions.append(f"will only go if any of: {', '.join(like)} are going")
elif len(like) == 1:
conditions.append(f"will only go if {like[0]} is going")
if not conditions:
conditions = ['will go without any conditions']
self.conditions = conditions
def draw(self):
self.obj = pygame.Rect(self.x, self.y, self.w, self.h)
pygame.draw.rect(wn, self.color, self.obj)
def get_on_boat(self, boat):
self.x = randint(boat.x, boat.x+boat.w-self.w)
self.y = randint(boat.y, boat.y+boat.h-self.h)
self.side = boat.side
def write_conditions(self, x, y):
text = font.render(f"{self.name} {'; '.join(self.conditions)}.", True, (0, 0, 0))
wn.blit(text, (x, y))
def get_off_boat(self, boat):
self.x += choice([-1, 1]) * randint(50, 200)
def on_boat(self, boat):
return boat.x+boat.w-self.w >= self.x >= boat.x and boat.y+boat.h-self.h >= self.y >= boat.y
class Boat():
def __init__(self, room, x=280, y=200, color=(140, 140, 140), w=40, h=60, side='UP'):
self.room = room
self.x = x
self.y = y
self.w = w
self.h = h
self.trips = 0
self.color = color
self.obj = pygame.Rect(x, y, w, h)
self.side = side
self.aboard = []
def go(self):
hated = [name for person in self.aboard for name in person.hate]
liked = [name for person in self.aboard for name in person.like]
no = False
if any(person.alone == True for person in self.aboard) and len(self.aboard) > 1:
no = True
elif any(person.alone == False for person in self.aboard) and len(self.aboard) == 1:
no = True
elif any(person.name == name for person in self.aboard for name in hated):
no = True
elif liked:
if any(person.name == name for person in self.aboard for name in liked):
no = False
else:
no = True
else:
no = False
if not no:
if self.side == 'UP':
self.y += 300
self.side = 'DOWN'
else:
self.y -= 300
self.side = 'UP'
self.obj.y = self.y
self.trips += 1
def draw(self):
pygame.draw.rect(wn, self.color, self.obj)
def display_moves(num):
text = font.render(f'{num} trips', True, (0, 0, 0))
wn.blit(text, (20, 530))
boat = Boat(2)
Yel = Person('Yel', boat.x, boat.y+300, (255, 255, 0), side='DOWN', hate=['Bla'])
Red = Person('Red', boat.x, boat.y+300, (255, 0, 0), side='DOWN', alone=True)
Pin = Person('Pin', boat.x, boat.y, (255, 0, 255), like=['Yel'])
Blu = Person('Blu', boat.x, boat.y, (0, 0, 255), like=['Yel'], hate=['Gre'])
Gre = Person('Gre', boat.x, boat.y, (0, 255, 0))
Bla = Person('Bla', boat.x, boat.y, (0, 0, 0), alone=False, hate=['Yel', 'Blu'])
for person in people:
person.get_off_boat(boat)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
for person in people:
if person.obj.collidepoint(pygame.mouse.get_pos()):
if person in boat.aboard:
person.get_off_boat(boat)
boat.aboard.remove(person)
boat.room += 1
else:
if boat.room and person.side == boat.side:
person.get_on_boat(boat)
boat.aboard.append(person)
boat.room -= 1
if not any(person.obj.collidepoint(pygame.mouse.get_pos()) for person in people):
if boat.obj.collidepoint(pygame.mouse.get_pos()):
if boat.aboard and boat.room >= 0:
boat.go()
for person in boat.aboard:
person.get_on_boat(boat)
wn.fill((255, 255, 255))
pygame.draw.rect(wn, (100, 200, 255), (0, 290, 600, 180))
boat.draw()
for i, person in enumerate(people):
person.draw()
person.write_conditions(20, i*30)
display_moves(boat.trips)
pygame.display.update()
If you scroll down the code, you will see
boat = Boat(2)
Yel = Person('Yel', boat.x, boat.y+300, (255, 255, 0), side='DOWN', hate=['Bla'])
Red = Person('Red', boat.x, boat.y+300, (255, 0, 0), side='DOWN', alone=True)
Pin = Person('Pin', boat.x, boat.y, (255, 0, 255), like=['Yel'])
Blu = Person('Blu', boat.x, boat.y, (0, 0, 255), like=['Yel'], hate=['Gre'])
Gre = Person('Gre', boat.x, boat.y, (0, 255, 0))
Bla = Person('Bla', boat.x, boat.y, (0, 0, 0), alone=False, hate=['Yel', 'Blu'])
There you will have plenty of control.
Boat(2)
means the boat can only hold 2 colors, you can change that to whatever you like.
side='DOWN'
and side='UP'
are self-explanatory (which side of the river each color starts at).
like=[...]
and hate=[..]
determines which color must and mustn't be in the boat with the color when crossing.
alone=True
and alone=False
determines if the color must go alone or must go with another.
- If you omit all these arguments it will mean that the color doesn't have any conditions.
Whenever you change/add/remove a requirement, the program's text and choosing when to lock the boat will automatically adapt.
With such customizable rules, you can use this program to help you solve other river crossing problems on the site. You can even use this program to come up with your own river crossing problems more easily.