Skip to main content
Post Made Community Wiki
Source Link

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!

enter image description here

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.