5
$\begingroup$

Here you will find the complete code to apps/tools that may help you solve puzzles and/or create puzzles. They may even turn tedious procedures into interactive simulations!

If you have a piece of fully functioning script that does things users on PSE frequently do, post it as an answer below, and add a link to the list under the appropriate programming language (if the language is not listed you can add it).

Python based tools

$\endgroup$
2
  • $\begingroup$ I really like these puzzles! They are all very innovative ideas. Might I suggest you use the tkinter module for the ones involving dragging? Tkinter has a builtin for dragging that might be helpful. It's decently easy to use :) $\endgroup$
    – 10 Rep
    Commented Dec 13, 2020 at 17:45
  • $\begingroup$ @10Rep Why thank you! :) $\endgroup$ Commented Dec 13, 2020 at 18:06

8 Answers 8

3
$\begingroup$

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.

$\endgroup$
2
$\begingroup$

Arithmetic operations puzzle solver

Solves puzzles of the form:

3 [] 3 [] 8 [] 8 = 24

where the brackets are replaced by arithmetic operations.

How to use

Call the function:

solve(target, numbers)

where

  • target is the final number.
  • numbers is the list of constituent numbers, not necessarily in order. The code doesn't care about the ordering of the numbers - it solves for all combinations in any order.

The results, if any, are printed for you as output to the Python console. Duplicates are automatically removed. If no results are found, the program ends without printing anything.

How to customize

The part of the code where the operations are defined can be changed to include or exclude specific operations, depending on if the puzzle has limitations (i.e. only allows multiplication and addition, for example). To customize this, just comment out the operations you don't want to be tested.

The code

In Python 3:

import operator
import itertools
from fractions import Fraction

operations = dict()
operations['+'] = operator.add
operations['-'] = operator.sub
operations['/'] = operator.truediv
operations['*'] = operator.mul

def solve(target, numbers):
    # List ways to make target from numbers.
    numbers = [Fraction(x) for x in numbers]
    solutions = solve_inner(target, numbers)
    # Remove duplicate solutions.
    solutions = list(dict.fromkeys(solutions))
    # Print the final solutions.
    for solution in solutions:
        print("{0} = {1}".format(target, solution))

def solve_inner(target, numbers):
    if len(numbers) == 1:
        if numbers[0] == target:
            yield str(target)
        return
    # Combine a pair of numbers with an operation, then recurse.
    for a,b in itertools.permutations(numbers, 2):
        for symbol, operation in operations.items():
            try:
                product = operation(a,b)
            except ZeroDivisionError:
                continue
            subnumbers = list(numbers)
            subnumbers.remove(a)
            subnumbers.remove(b)
            subnumbers.append(product)
            for solution in solve_inner(target, subnumbers):
                yield solution.replace(str(product), "({0}{1}{2})".format(a, symbol, b), 1)

If you want to run a quick demo to see how this works, add the following code:

if __name__ == "__main__":
    numbers = [3, 3, 8, 8]
    target = 24
    solutions = solve(target, numbers)

In this demo case, we want to form 24 from 3, 3, 8 and 8. Our output is:

24 = (8/(3-(8/3)))
$\endgroup$
0
2
$\begingroup$

All angles word search

With this program, you can put use any letter grid of any shape.

Here is how it goes:

enter image description here

As shown in the demonstration, you can type in anything into the text box as long as it's in the alphabet or a space. Then the program will highlight all the series of cells in the grid that makes up the word. You can also edit the grid by clicking on the cells during run-time.

Here is the code:

import pygame

pygame.font.init()
wn = pygame.display.set_mode((600, 600))


letters = \ # You can customize this grid with letter of the alphabet and spaces
'''
KJDJCIOSDZ
PGRIWOTAID
VETVALCGLS
OFZESGZASW
SOYRBKOADL
FUWTQOXNGE
CILIWMEPAV
NEZCJRVZNL
GXZAOQMFIG
EUPLIESCGP
HORIZONTAL
'''

class Cell():
    def __init__(self, x, y, s, text='', color=(0, 0, 0), cell=True):
        self.input_box = pygame.Rect(x, y, s, s)
        self.x = x
        self.y = y
        self.s = s
        self.w = s
        self.color_inactive = color
        self.color_active = pygame.Color('purple')
        self.color = self.color_inactive
        self.text = text
        self.active = False
        self.pad = 10
        self.cell = cell
        self.font = pygame.font.Font(None, s)

    def check_status(self, pos):
        if self.input_box.collidepoint(pos):
            self.active = not self.active
        else:
            self.active = False
        self.color = self.color_active if self.active else self.color_inactive

    def type(self, event):
        if self.active:
            if self.cell:
                if event.unicode and event.unicode.lower() in 'abcdefghijklmnopqrstuvwxyz ':
                    self.text = event.unicode
            else:
                if event.key == pygame.K_BACKSPACE:
                    self.text = '' if len(self.text) < 2 else self.text[:-1]
                elif event.unicode and event.unicode.lower() in 'abcdefghijklmnopqrstuvwxyz ':
                    self.text += event.unicode 
            

    def draw(self):
        txt = self.font.render(self.text, True, self.color)
        if not self.cell:
            width = max(self.w, txt.get_width())
            self.input_box.w = width + self.pad * 2
            x = self.x + self.pad
        else:
            x = self.x+(self.s-txt.get_width())//2
        y = self.y+(self.s-txt.get_height())*5//7
        wn.blit(txt, (x, y))
        pygame.draw.rect(wn, self.color, self.input_box, 2)


class Grid():
    def __init__(self, x, y, size, letters, color=(0, 0, 0)):
        rows = len(letters)
        cols = len(letters[0])
        self.grid =  [[Cell(i*size+x, j*size+y, size, letter) for i, letter in enumerate(row)] for j, row in enumerate(letters)]
        self.cells = [cell for row in self.grid for cell in row]
        self.rows = rows
        self.cols = cols

    def check_status(self, pos):
        for cell in self.cells:
            cell.check_status(pos)

    def type(self, event):
        for cell in self.cells:
            cell.type(event)

    def adj(self, c, idx, lstlst, wrd):
        x, y = self.cells.index(c) % self.cols, self.cells.index(c) // self.cols
        x1, x2 = max(0, x - 1), min(x + 2, self.cols + 2)
        y1, y2 = max(0, y - 1), min(y + 2, self.rows + 2)
        adjs = [cell for row in self.grid[y1:y2] for cell in row[x1:x2] if cell != c]
        taillst = lstlst[-1]
        for cell in adjs:
            if len(wrd) > idx:
                if cell.text == wrd[idx] and cell not in taillst:
                    lstlst.append(taillst[:] + [cell])
                    self.adj(cell, idx+1, lstlst, wrd)

    def draw(self):
        for cell in self.cells:
            cell.draw()


grid = Grid(50, 70, 32, list(filter(None, letters.split('\n'))))
word = Cell(50, 20, 32, cell=False)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            grid.check_status(event.pos)
            word.check_status(event.pos)
        elif event.type == pygame.KEYDOWN:
            grid.type(event)
            word.type(event)
            for cell in grid.cells:
                cell.color = (0, 0, 0)
            for cell in grid.cells:
                lstlst = [[cell]]
                grid.adj(cell, 1, lstlst, word.text)
                for lst in lstlst:
                    if ''.join([c.text for c in lst]) == word.text:
                        for c in lst:
                            c.color = (255, 0, 0)

                        
    wn.fill((255, 255, 255))
    grid.draw()
    word.draw()
    pygame.display.flip()

If you scroll down you will see

grid = Grid(50, 70, 32, list(filter(None, letters.split('\n'))))

You can change the size of each cell in the grid by changing the third argument: 32.

50 and 70 are the x, y positions of the entire grid.

$\endgroup$
1
$\begingroup$

Scale simulation real-time

The concept is simple enough. You have a scale that adds up the total weights on each side, and tilts accordingly. On the top corner is a weight. If you drag the weight, a new one appears in its place, giving you infinite weights. You can choose the weight for each weight, and always change it again when needed.

Here is a demonstration:

enter image description here

Here is the entire code:

import pygame
from random import randint as rt


pygame.init()

wn = pygame.display.set_mode((600, 500))
font = pygame.font.SysFont('Arial', 22)

class TextBox():
    def __init__(self, x, y, w, h, color=(0, 0, 0), default=''):
        self.input_box = pygame.Rect(x, y, w, h)
        self.w = w
        self.h = h
        self.color_inactive = color
        self.color_active = (200, 0, 0)
        self.color = self.color_inactive
        self.default = default
        self.text = ''
        self.active = False

    def draw(self):
        if not self.text:
            self.text = '0'
        txt = font.render(self.text, True, self.color)
        width = max(self.w, txt.get_width()+10)
        self.input_box.w = width
        wn.blit(txt, (self.input_box.x+5, self.input_box.y+3))
        pygame.draw.rect(wn, self.color, self.input_box, 2)

    def check_status(self, pos):
        if self.input_box.collidepoint(pos):
            self.active = not self.active
        else:
            self.active = False
        self.color = self.color_active if self.active else self.color_inactive

    def type(self, event):
        if self.active:
            if event.key == pygame.K_RETURN:
                pass
            elif event.key == pygame.K_BACKSPACE:
                if len(self.text) == 1:
                    self.text = '0'
                else:
                    self.text = self.text[:-1]
            else:
                if self.text[0] == '0':
                    self.text = ''
                self.text += event.unicode

objs = []

class Obj():
    def __init__(self, color, x, y, w, h):
        self.w = w
        self.h = h
        self.color = color
        self.rect = pygame.rect.Rect(x, y, w, h)
        self.dragging = False
        self.offset_x = 0
        self.offset_y = 0
        self.offset_x2 = 0
        self.offset_y2 = 0
        self.weight1 = 0
        self.weight2 = 0
        self.txt_box = TextBox(x+10, x+10, w-20, h-20, default='0')
        self.new = True
        objs.append(self)

    def clicked(self, pos):
        return self.rect.collidepoint(pos)

    def offset_click(self, pos):
        self.dragging = True
        self.offset_x = self.rect.x - pos[0]
        self.offset_y = self.rect.y - pos[1]
        self.offset_x2 = self.txt_box.input_box.x - pos[0]
        self.offset_y2 = self.txt_box.input_box.y - pos[1]
        
    def offset_drag(self, pos):
        self.new = False
        self.rect.x = self.offset_x + pos[0]
        self.rect.y = self.offset_y + pos[1]
        self.txt_box.input_box.x = self.offset_x2 + pos[0]
        self.txt_box.input_box.y = self.offset_y2 + pos[1]

    def draw(self):
        pygame.draw.rect(wn, self.color, self.rect)
        self.txt_box.draw()

class Scale():
    def __init__(self, x1, x2, y, w, h):
        self.rect1 = pygame.Rect(x1, y, w, h)
        self.rect2 = pygame.Rect(x2, y, w, h)
        self.w = w
        self.h = h
        self.hand1 = []
        self.hand2 = []
        self.tilted = 0

    def move(self, n):
        for o in self.hand1:
            o.rect.y += n
            o.txt_box.input_box.y += n
        self.rect1.y += n
        for o in self.hand2:
            o.rect.y -= n
            o.txt_box.input_box.y -= n
        self.rect2.y -= n
        
    def tilt(self):
        self.weight1 = sum([int(o.txt_box.text) for o in self.hand1]) if o.txt_box.text else 0
        self.weight2 = sum([int(o.txt_box.text) for o in self.hand2]) if o.txt_box.text else 0
        if self.weight1 > self.weight2:
            if not self.tilted:
                self.move(80)
            elif self.tilted == 'nag':
                self.move(160)
            self.tilted = 'pos'
        elif self.weight1 < self.weight2:
            if not self.tilted:
                self.move(-80)
            elif self.tilted == 'pos':
                self.move(-160)
            self.tilted = 'nag'
        else:
            if self.tilted == 'pos':
                self.move(-80)
            elif self.tilted == 'nag':
                self.move(80)
            self.tilted = False
    def draw(self):
        x1, y1 = self.rect1.x + self.w//2, self.rect1.y + self.h//2
        x2, y2 = self.rect2.x + self.w//2, self.rect2.y + self.h//2
        x, y_u, y_d = (x2-x1)//2 + x1, 240, 400
        pygame.draw.line(wn, (150, 0, 150), (x1, y1), (x2, y2), 20)
        pygame.draw.line(wn, (150, 0, 150), (x, y_u), (x, y_d), 20)
        pygame.draw.rect(wn, (0, 0, 100), self.rect2)
        pygame.draw.rect(wn, (0, 0, 100), self.rect1)
        txt1 = font.render(str(self.weight1), True, (0, 0, 0))
        txt2 = font.render(str(self.weight2), True, (0, 0, 0))
        wn.blit(txt1, (self.rect1.x+30, self.rect1.y-30))
        wn.blit(txt2, (self.rect2.x+30, self.rect2.y-30))

obj = Obj((rt(160, 255), rt(160, 255), rt(160, 255)), 30, 30, 50, 50)
sca = Scale(50, 350, 200, 200, 80)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                for o in objs:
                    o.txt_box.check_status(event.pos)
                    if o.clicked(event.pos):
                        o.offset_click(event.pos)
                        
        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1:
                for o in objs:
                    o.dragging = False
            sca.hand1 = [o for o in objs if sca.rect1.collidepoint(o.rect.x, o.rect.y)]
            sca.hand2 = [o for o in objs if sca.rect2.collidepoint(o.rect.x, o.rect.y)]

        elif event.type == pygame.MOUSEMOTION:
            for o in objs:
                if o.dragging:
                    if o.new:
                        o.new = False
                        obj = Obj((rt(160, 255), rt(160, 255), rt(160, 255)), 30, 30, 50, 50)
                    o.offset_drag(event.pos)

        if event.type == pygame.KEYDOWN:
            if (event.unicode.isdigit() or event.key == pygame.K_BACKSPACE) or event.key == pygame.K_BACKSPACE:
                for o in objs:
                    o.txt_box.type(event)
    sca.tilt()
    wn.fill((255, 255, 255))
    sca.draw()
    for o in objs:
        o.draw()
    pygame.display.flip()

If you scroll down you will see

obj = Obj((rt(160, 255), rt(160, 255), rt(160, 255)), 30, 30, 50, 50)
sca = Scale(50, 350, 200, 200, 80)

There you can change the color, position and size of the scale and weight. Note that the obj is only the first weight, you need to look inside the while loop to see the generator, which is

                    if o.new:
                        o.new = False
                        obj = Obj((rt(160, 255), rt(160, 255), rt(160, 255)), 30, 30, 50, 50)
$\endgroup$
1
$\begingroup$

Rot-anything real-time

With this little app, you can see your text being encoded with rot-13 (any other number) as you type.

Here is how it will run:

enter image description here

Here is the entire code:

import pygame
pygame.font.init()

wn = pygame.display.set_mode((600, 600))
font = pygame.font.Font(None, 32)

class TextBox():
    def __init__(self, x, y, w, h, title='Text Box', color=(0, 0, 0), default=''):
        self.input_box = pygame.Rect(x, y, w, h)
        self.w = w
        self.h = h
        self.color_inactive = color
        self.color_active = pygame.Color('purple')
        self.color = self.color_inactive
        self.default = default
        self.text = ['']
        self.active = False
        self.title = title

    def draw(self):
        title = font.render(self.title, True, self.color)
        wn.blit(title, (self.input_box.x+5, self.input_box.y-self.h))
        txts = [font.render(''.join(t), True, self.color) for t in self.text]
        width = max(self.w, max(txts, key=lambda x:x.get_width()).get_width()+10)
        height = self.h * len(txts)
        self.input_box.w = width
        self.input_box.h = height
        if len(txts) == 1 and txts[0].get_width() == 1:
            wn.blit(font.render(self.default, True, self.color), (self.input_box.x+5, self.input_box.y+5))
        else:
            for i, txt in enumerate(txts):
                wn.blit(txt, (self.input_box.x+5, self.input_box.y+5+i*self.h))
        pygame.draw.rect(wn, self.color, self.input_box, 2)

    def check_status(self, pos):
        if self.input_box.collidepoint(pos):
            self.active = not self.active
        else:
            self.active = False
        self.color = self.color_active if self.active else self.color_inactive

    def type(self, event):
        if self.active:
            if event.key == pygame.K_RETURN:
                self.text.append('')
            elif event.key == pygame.K_BACKSPACE:
                if self.text[-1]:
                    self.text[-1] = self.text[-1][:-1]
                else:
                    if len(self.text) > 1:
                        self.text = self.text[:-1]
            else:
                self.text[-1] += event.unicode

def rot(alp, num):
    while num >= 26:
        num -= 26
    n = ord(alp) + num
    if alp in 'abcdefghijklmnopqrstuvwxyz':
        if n > 122:
            n -= 26
        alp = chr(n)
    elif alp in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        if n > 90:
            n -= 26
        alp = chr(n)
    return alp

box = TextBox(110, 60, 140, 32, 'Input Text')
rot_num = TextBox(10, 60, 50, 32, 'Rot', default='0')
rot_box = TextBox(110, 300, 140, 32, 'Output Text')

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            box.check_status(event.pos)
            rot_num.check_status(event.pos)
        if event.type == pygame.KEYDOWN:
            box.type(event)
            if (event.unicode.isdigit() or event.key == pygame.K_BACKSPACE) and len(rot_num.text[0]) < 6 or event.key == pygame.K_BACKSPACE:
                rot_num.type(event)
#            if event.key == pygame.K_RETURN:
#                print('\n'.join([''.join(row) for row in rot_box.text])+'\n--------------------')

    rt = int(rot_num.text[0]) if rot_num.text[0] else 0
    rot_box.text = [[rot(char, rt) for char in row] for row in box.text]
    wn.fill((255, 255, 200))
    box.draw()
    rot_num.draw()
    rot_box.draw()
    pygame.display.flip()

If you scroll down you will see

box = TextBox(110, 60, 140, 32, 'Input Text')
rot_num = TextBox(10, 60, 50, 32, 'Rot', default='0')
rot_box = TextBox(110, 300, 140, 32, 'Output Text')

There you can choose the position, size, title, color and default text of each text box.

If you want to be able to copy-paste the output text, simply remove the two #s from this part of the code:

#            if event.key == pygame.K_RETURN:
#                print('\n'.join([''.join(row) for row in rot_box.text])+'\n--------------------')

and you will be able to whenever you hit Enter.

I know there's rot13.com, but hey, this is customizable and can be used offline.

$\endgroup$
1
$\begingroup$

Water jugs puzzle simulator

You have a jug generator that gives you as many jugs as you need. Simply drag the jug that's in the top-left corner of the screen, and a new one appears in its place. Drag any jug anywhere. If you want the jugs to align like they're sitting on a level table, right-click on them.

At first, they can each hold 0 units of water. To change each jug's capacity, left click the text box on that jug and type in any positive integer, which will be the number of units of water that jug can hold.

Once the jug's capacity is non-zero, another text box will appear above the first text box. This time it will determine the number of units of water the jug is filled with.

Now you can use the small blue squares found on the top-left corner of each jug to transfer water from one jug to another.

The text box on the top-right corner of the screen is the number of pixels long each unit will be, that is for visuals/convenience.

Here is a demonstration:

enter image description here

Here is the code:

import pygame

pygame.init()

wn = pygame.display.set_mode((600, 600))

class TextBox():
    def __init__(self, x, y, w, h, color_active=(0, 0, 200), color_inactive=(0, 0, 0), default='', pad=10, font=pygame.font.SysFont('Arial', 22)):
        self.pad = pad
        self.input_box = pygame.Rect(x+self.pad, y+self.pad, w, h)
        self.w = w
        self.h = h
        self.font = font
        self.color_inactive = color_inactive
        self.color_active = color_active
        self.color = self.color_inactive
        self.default = default
        self.text = default
        self.active = False

    def draw(self):
        if not self.text:
            self.text = '0'
        txt = self.font.render(self.text, True, self.color)
        width = max(self.w, txt.get_width()+self.pad)
        self.input_box.w = width
        wn.blit(txt, (self.input_box.x+5, self.input_box.y+3))
        pygame.draw.rect(wn, self.color, self.input_box, 2)

    def check_status(self, pos):
        if self.input_box.collidepoint(pos):
            self.active = not self.active
        else:
            self.active = False
        self.color = self.color_active if self.active else self.color_inactive


objs = []

class Obj():
    def __init__(self, color, x, y, w, h, default):
        self.w = w
        self.h = h
        self.color = color
        self.rect = pygame.rect.Rect(x, y, w, h)
        self.dragging = False
        self.pad = 10
        self.txt_box = TextBox(x, y, w-20, 30, pad=10, default='0')
        self.txt_box2 = TextBox(x, y-default, w-20, 30, pad=10, default='0')
        self.new = True
        self.straw = pygame.rect.Rect(x, y, self.pad, self.pad)
        self.line = False
        self.straw_start = None
        self.straw_end = None
        self.snap = False
        self.snap_y = 450
        objs.append(self)

    def clicked(self, pos):
        return self.rect.collidepoint(pos)

    def clicked_straw(self, pos):
        return self.straw.collidepoint(pos)

    def trans(self, o):
        empty = int(o.txt_box.text) - int(o.txt_box2.text)
        full = int(self.txt_box2.text)
        total = full if empty >= full else empty
        self.txt_box2.text = str(int(self.txt_box2.text) - total)
        o.txt_box2.text = str(int(o.txt_box2.text) + total)

    def offset_click(self, pos):
        self.dragging = True
        self.offset_x = self.rect.x - pos[0]
        self.offset_y = self.rect.y - pos[1]
        self.offset_x2 = self.txt_box.input_box.x - pos[0]
        self.offset_y2 = self.txt_box.input_box.y - pos[1]
        self.offset_x3 = self.txt_box2.input_box.x - pos[0]
        self.offset_y3 = self.txt_box2.input_box.y - pos[1]

    def offset_drag(self, pos):
        self.new = False
        self.rect.x = self.straw.x = self.offset_x + pos[0]
        self.rect.y = self.straw.y = self.offset_y + pos[1]
        self.txt_box.input_box.x = self.offset_x2 + pos[0]
        self.txt_box.input_box.y = self.offset_y2 + pos[1]
        self.txt_box2.input_box.x = self.offset_x3 + pos[0]
        self.txt_box2.input_box.y = self.offset_y3 + pos[1]
            
    def draw(self):
        pygame.draw.rect(wn, self.color, self.rect)
        amt1, amt2 = int(self.txt_box.text), int(self.txt_box2.text)
        bt_y = self.txt_box.input_box.bottom
        if amt1:
            w, h = self.rect.w, self.h * amt2
            x, y = self.rect.x, bt_y-h + self.pad - self.h
            pygame.draw.rect(wn, (145, 255, 255), (x, y, w, h))
            self.txt_box2.draw()
        else:
            self.txt_box2.text = '0'
        self.rect.h = self.h * (amt1 + 1)
        self.rect.y = bt_y - self.rect.h + self.pad
        self.txt_box.draw()
        pygame.draw.rect(wn, (0, 55, 255), self.straw)

    def draw_straw(self):
        if self.line:
            pygame.draw.line(wn, (0, 255, 255), self.straw_start, self.straw_end, 5)

num = TextBox(400, 20, 100, 50, color_inactive=(255, 255, 255), color_active=(200, 200, 255), default='50', font=pygame.font.SysFont('Arial', 40))
obj = Obj((255, 255, 255), 30, 30, 70, 50, int(num.default))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                for o in objs:
                    o.txt_box.check_status(event.pos)
                    o.txt_box2.check_status(event.pos)
                    if o.clicked_straw(event.pos):
                        o.line = True
                        o.straw_start = event.pos
                        o.straw_end = event.pos
                    elif o.clicked(event.pos):
                        o.offset_click(event.pos)
                num.check_status(event.pos)
            elif event.button == 3:
                for o in objs:
                    if o.clicked(event.pos) and not o.new:
                        o.snap = not o.snap
                        if o.snap:
                            o.offset_click(event.pos)
                            o.offset_drag((event.pos[0], o.snap_y+event.pos[1]-o.rect.y-int(o.txt_box.text)*o.h-int(num.text)))
                            o.dragging = False

                
        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1:
                for o in objs:
                    if o.line:
                        o2 = [o for o in objs if o.clicked_straw(event.pos)]
                        if o2:
                            o.trans(o2[0])
                    o.line = False
                    o.dragging = False


        elif event.type == pygame.MOUSEMOTION:
            for o in objs:
                if o.line:
                    o.straw_end = event.pos
                if o.dragging:
                    if o.new:
                        o.new = False
                        obj = Obj((255, 255, 255), 30, 30, 70, int(num.text), int(num.default))
                    o.offset_drag(event.pos)

        elif event.type == pygame.KEYDOWN:
            if num.active:
                if event.key == pygame.K_BACKSPACE:
                    num.text = '0'
                elif event.unicode.isdigit():
                    if num.text == '0':
                        num.text = ''
                    num.text += event.unicode
                for o in objs:
                    o.h = int(num.text)
                    o.rect.h = int(num.text)
                    o.rect.y = o.txt_box.input_box.bottom-o.rect.h + o.pad
                    o.straw.y = o.rect.y - o.h * int(o.txt_box.text)
            for o in objs:
                if o.txt_box.active:
                    if event.key == pygame.K_BACKSPACE:
                        o.txt_box.text = '0'
                        o.rect.h = o.h
                    elif event.unicode.isdigit():
                        if o.txt_box.text == '0':
                            o.txt_box.text = ''
                        o.txt_box.text += event.unicode
                        o.rect.h = o.h * (int(o.txt_box.text) + 1)
                    o.rect.y = o.txt_box.input_box.bottom - o.rect.h + o.pad
                    o.straw.y = o.rect.y
                elif o.txt_box2.active:
                    if event.key == pygame.K_BACKSPACE:
                        o.txt_box2.text = '0'
                    elif event.unicode.isdigit():
                        if int(o.txt_box2.text + event.unicode) <= int(o.txt_box.text):
                            if o.txt_box2.text == '0':
                                o.txt_box2.text = '' 
                            o.txt_box2.text += event.unicode

    wn.fill((0, 100, 0))
    num.draw()
    for o in objs:
        o.draw()
    for o in objs:
        o.draw_straw()
    pygame.display.flip()
$\endgroup$
1
$\begingroup$

Words to numbers equation solver

With this program, you can type in equations in the form of

cat + d0g = pets
bird - seed = hunger
you * me = happ9 = h3 * she
etc.
etc.

The program will find all possible values for all the letters used that will satisfy the equation. If a = 3, b = 6, c = 0, then abc = 360. Each letter can only have one number assigned to it, so you will be able to use a maximum of 10 different hidden letters.

Here are some examples of word equations and all of their possible number forms:

bread - yeast = stone

>>> bread - yeast = stone
81390 - 53927 = 27463
81690 - 26954 = 54736
92461 - 34657 = 57804
40389 - 23816 = 16573
70159 - 41528 = 28631
60179 - 31728 = 28451
70359 - 23546 = 46813

he * she = magic

>>> he * she = magic
58 * 358 = 20764
67 * 467 = 31289
73 * 573 = 41829
67 * 367 = 24589
59 * 459 = 27081
52 * 752 = 39104

seed + water = plant

>>> seed + water = plant
6771 + 28079 = 34850
6779 + 28071 = 34850
6885 + 13489 = 20374
6889 + 13485 = 20374
1442 + 69543 = 70985
1443 + 69542 = 70985
5668 + 14769 = 20437
5669 + 14768 = 20437
7331 + 42938 = 50269
7338 + 42931 = 50269

puppy - baby = doggy

>>> puppy - baby = doggy
41440 - 8780 = 32660
51550 - 7670 = 43880
31330 - 6560 = 24770
25220 - 8780 = 16440

mom + dad = baby

>>> mom + dad = baby
878 + 434 = 1312
868 + 545 = 1413
848 + 767 = 1615
474 + 838 = 1312
565 + 848 = 1413
747 + 868 = 1615

site + bobble = better

>>> site + bobble = better
8915 + 242235 = 251150
8917 + 262257 = 271174
7816 + 353346 = 361162
2317 + 868857 = 871174

deusov + puzzle = solved

>>> deusov + puzzle = solved
291703 + 416689 = 708392

The code:

from itertools import count, permutations

OPERATORS = '+-*( )='
VARIABLES = 'abcdefghijklmnopqrstuvwxyz'
NUMERALS = '1234567890'
VALID_CHARACTERS = OPERATORS + VARIABLES + NUMERALS


def is_expression_valid(expression):
    return all(c in VALID_CHARACTERS for c in expression)


def is_expression_consistent(expression, variables):
    for index, value in zip(count(1), variables):
        expression = expression.replace(value, str(index))
    try:
        eval(expression.replace("=", "=="))
    except SyntaxError:
        return False
    return True


def extract_variables(expression):
    variables = {char for char in expression if char in VARIABLES}
    if len(variables) > 10:
        raise ValueError()
    return variables


def get_expression():
    while True:
        expression = input(">>> ")
        if not is_expression_valid(expression):
            print(f"Invalid characters in the expression. Use only from: {VALID_CHARACTERS}")
            continue
        try:
            variables = extract_variables(expression)
        except ValueError:
            print("Expected at most 10 variable/letters!")
            continue
        if not is_expression_consistent(expression, variables):
            print("Invalid syntax!")
            continue
        break
    return expression, variables


def substitute_expression_format(expression):
    return "".join(f"{{{c}}}" if c in VARIABLES else c for c in expression)


def valid_results(expression, variables):
    formatted_expression = substitute_expression_format(expression)
    for permute in permutations(range(10), len(variables)):
        variation = formatted_expression.format(**dict(zip(variables, permute)))
        try:
            if eval(variation.replace("=", "==")):
                yield variation
        except SyntaxError:
            continue


def main():
    while True:
        expression, variables = get_expression()
        for result in valid_results(expression, variables):
            print(result)


if __name__ == "__main__":
    main()

Experimenting with different word combos, I had a great day :)

$\endgroup$
1
$\begingroup$

Queens on a chess board

The below pygame program allows the user to place any number of queens on a chess board, and visualize what arrangement of queens will threaten which squares of the chess board.

Here is how it goes (source of arrangements used in the gif here):

enter image description here

The code:

import pygame

pygame.init()
wn = pygame.display.set_mode((600, 600))

class Chess:
    def __init__(self, x, y, w, h, queens):
        self.x = x
        self.y = y
        self.queens = queens
        self.cells = [[pygame.Rect(x + i * w, y + j * h, w, h) for i in range(8)] for j in range(8)]

    def get_pos(self, queen):
        return (queen.rect.x - self.x) // queen.w, (queen.rect.y - self.y) // queen.h
    
    def diag_pos(self, x, y):
        m = min(x, y)
        x, y = x - m, y - m
        while x < len(self.cells[0]) and y < len(self.cells):
            yield self.cells[y][x]
            x += 1
            y += 1

    def diag_neg(self, x, y):
        m = min(len(self.cells[0]) - 1 - x, y)
        x, y = x + m, y - m
        while x + 1 and y < len(self.cells):
            yield self.cells[y][x]
            x -= 1
            y += 1
            
    def vert(self, x, y):
        for r in self.cells:
            yield r[x]

    def hori(self, x, y):
        for cell in self.cells[y]:
            yield cell
            
    def threatened(self, queen):
        x, y = self.get_pos(queen)
        if -1 < x < len(self.cells[0]) and -1 < y < len(self.cells):
            for func in [self.diag_pos, self.diag_neg, self.vert, self.hori]:
                for cell in func(x, y):
                    yield cell

    def draw(self):
        for i, row in enumerate(self.cells):
            for j, col in enumerate(row):
                pygame.draw.rect(wn, [(255, 255, 255), (0, 0, 0)][abs(i - j) % 2], col)

    def draw_threats(self):
        for queen in self.queens:
            for cell in self.threatened(queen):
                if cell not in self.queens:
                    pygame.draw.rect(wn, (35, 35, 35), cell)

    def draw_queens(self):
        for queen in self.queens:
            queen.draw()


class Queen:
    def __init__(self, x, y, w, h):
        self.rect = pygame.Rect(x, y, w, h)
        self.dragging = False
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def clicked(self, pos):
        return self.rect.collidepoint(pos)

    def drag(self, x, y):
        self.x += x
        self.y += y
        self.rect.x = round(self.x / self.w) * self.w
        self.rect.y = round(self.y / self.h) * self.h

    def draw(self):
        pygame.draw.rect(wn, (255, 0, 0), self.rect)

chess = Chess(100, 100, 50, 50, [Queen(100, 50, 50, 50),
                                 Queen(150, 50, 50, 50),
                                 Queen(200, 50, 50, 50)])

running = True
while running:
    wn.fill((123, 123, 123))
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            for queen in chess.queens:
                if queen.clicked(event.pos):
                    queen.dragging = True
        elif event.type == pygame.MOUSEMOTION:
            for queen in chess.queens:
                if queen.dragging:
                    queen.drag(*event.rel)
        elif event.type == pygame.MOUSEBUTTONUP:
            for queen in chess.queens:
                queen.dragging = False                

    chess.draw()
    chess.draw_threats()
    chess.draw_queens()
    pygame.display.update()

This part of the code allows you to adjust the number of queens on the board:

chess = Chess(100, 100, 50, 50, [Queen(100, 50, 50, 50),
                                 Queen(150, 50, 50, 50),
                                 Queen(200, 50, 50, 50)])

where you can customize each number.

For the Chess class:

  • First argument of Chess is the x coordinate of the top-left corner of the chess board
  • Second argument of Chess is the y coordinate of the top-left corner of the chess board
  • Third argument of Chess is the width of each square of the chess board
  • Forth argument of Chess is the height of each square of the chess board
  • Fifth argument of Chess is the list of queens that will be occupying the chess board

For the Queen class:

  • First argument of Queen is the x coordinate of the top-left corner of the queen
  • Second argument of Queen is the y coordinate of the top-left corner of the queen
  • Third argument of Queen is the width of the queen
  • Forth argument of Queen is the height of the queen
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .