-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from Miserlou/rich/fflogic
Adds Zebra/Murdle/Einstein/Grid Style Puzzles
- Loading branch information
Showing
21 changed files
with
2,432 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
via https://github.com/nouhadziri/faith-and-fate | ||
|
||
@article{dziri2023faith, | ||
title={Faith and Fate: Limits of Transformers on Compositionality}, | ||
author={Dziri, Nouha and Lu, Ximing and Sclar, Melanie and Li, Xiang Lorraine and Jian, Liwei and Lin, Bill Yuchen and West, Peter and Bhagavatula, Chandra and Bras, Ronan Le and Hwang, Jena D and others}, | ||
journal={arXiv preprint arXiv:2305.18654}, | ||
year={2023} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
""" | ||
clues.py | ||
These are all the clue types that a puzzle can have. Things like "the tea drinker lives in the | ||
green house" and "the cat owner lives left of the person who likes grilled cheese." | ||
There's a Clue ABC that requires you implement an `as_cnf` method, to convert the clue to an | ||
and-of-ors (probably using things defined in `sat_utils`), and a human-readable __repr__ that | ||
can be used in a puzzle description. | ||
""" | ||
|
||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass, field | ||
from functools import wraps | ||
from itertools import product | ||
from typing import Iterable, List, Tuple | ||
|
||
from reasoning_gym.logic.contrib.logic_puzzle.literals import Literal | ||
from reasoning_gym.logic.contrib.logic_puzzle.sat_utils import from_dnf, neg | ||
|
||
|
||
def _capitalize_first(repr_func): | ||
""" | ||
Decorator for a __repr__ function that capitalizes the first letter without chagning the rest | ||
(in contrast to str.capitalize(), which capitalizes the first letter and makes the rest lower) | ||
""" | ||
|
||
@wraps(repr_func) | ||
def wrapper(*args, **kwargs): | ||
output = repr_func(*args, **kwargs) | ||
return output[0].upper() + output[1:] | ||
|
||
return wrapper | ||
|
||
|
||
class Clue(ABC): | ||
"""Base class for the types of clues that we allow.""" | ||
|
||
@abstractmethod | ||
def as_cnf(self) -> Iterable[Tuple[str]]: ... | ||
|
||
@abstractmethod | ||
def __repr__(self) -> str: ... | ||
|
||
|
||
def comb(value: Literal, house: int) -> str: | ||
"""Format how a value is shown at a given house""" | ||
|
||
return f"{value} {house}" | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class found_at(Clue): | ||
""" | ||
A literal is known to be at a specific house | ||
Examples: | ||
- the tea drinker lives in the middle house | ||
- the fourth house is red | ||
""" | ||
|
||
value: Literal | ||
house: int | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return [(comb(self.value, self.house),)] | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
houses = [None, "first", "second", "third", "fourth", "fifth", "sixth", "seventh"] | ||
return f"{self.value.value} is in the {houses[self.house]} house." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class not_at(Clue): | ||
""" | ||
Two values are known *not* to be at the same house | ||
Examples: | ||
- the musician does not drink tea | ||
- the red house does not contain a cat | ||
""" | ||
|
||
value: Literal | ||
house: int | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return [(neg(comb(self.value, self.house)),)] | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
houses = [None, "first", "second", "third", "fourth", "fifth", "sixth", "seventh"] | ||
return f"{self.value.value} is not in the {houses[self.house]} house." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class same_house(Clue): | ||
""" | ||
Two values are known to be at the same house | ||
Examples: | ||
- the musician drinks tea | ||
- the red house contains a cat | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf((comb(self.value1, i), comb(self.value2, i)) for i in self.houses) | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
return f"{self.value1.value} is {self.value2.value}." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class consecutive(Clue): | ||
""" | ||
The first value is directly to the left of the second value | ||
Examples: | ||
- the green house is directly to the left of the white house | ||
(green in 1, white in 2 OR green in 2, white in 3 OR etc.) | ||
- the house with the kittens is directly to the right of the tea drinker's home | ||
(kittens in 2, tea in 1 OR kittens in 3, tea in 2 OR etc.) | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf((comb(self.value1, i), comb(self.value2, j)) for i, j in zip(self.houses, self.houses[1:])) | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
return f"{self.value1.value} is directly left of {self.value2.value}." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class beside(Clue): | ||
""" | ||
The two values occur side-by-side (either left or right) | ||
Examples: | ||
- the coffee drinker is (left or right) of the tea drinker | ||
- the cat owner is (left or right) of the green house | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf( | ||
[(comb(self.value1, i), comb(self.value2, j)) for i, j in zip(self.houses, self.houses[1:])] | ||
+ [(comb(self.value2, i), comb(self.value1, j)) for i, j in zip(self.houses, self.houses[1:])] | ||
) | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
return f"{self.value1.value} and {self.value2.value} are next to each other." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class left_of(Clue): | ||
""" | ||
The first value is somewhere to the left of the second value | ||
Examples: | ||
- the tea drinker is in house 1 and the musician in 2, 3, 4, or 5; | ||
OR the tea drinker in 2, and musician in 3, 4, or 5; | ||
OR the tea drinker in 3, musician in 4, 5; OR tea 4, musician 5. | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf( | ||
(comb(self.value1, i), comb(self.value2, j)) for i, j in product(self.houses, self.houses) if i < j | ||
) | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
return f"{self.value1.value} is somewhere to the left of {self.value2.value}." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class right_of(Clue): | ||
""" | ||
The first value is somewhere to the right of the second value. | ||
Examples: | ||
- the coffee drinker is in house 5 and the artist in 1, 2, 3, 4; | ||
OR the coffee drinker in 4, and artist in 1, 2, or 3; | ||
OR the coffee drinker in 3, artist in 1, 2; OR coffee 2, artist 1. | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return sat_utils.from_dnf( | ||
(comb(self.value1, i), comb(self.value2, j)) for i, j in product(self.houses, self.houses) if i > j | ||
) | ||
|
||
@_capitalize_first | ||
def __repr__(self) -> str: | ||
return f"{self.value1.value} is somewhere to the right of {self.value2.value}." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class one_between(Clue): | ||
""" | ||
The values are separated by one house | ||
Examples (if 5 houses): | ||
- the cat is in house 1 and tea drinker in house 3; OR cat 2, tea 4; | ||
OR cat 4 house 5 | ||
- the green house is #1 and the musician in house 3; or green house 2, musician 4; | ||
OR green house 3, musician 5. | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf( | ||
[(comb(self.value1, i), comb(self.value2, j)) for i, j in zip(self.houses, self.houses[2:])] | ||
+ [(comb(self.value2, i), comb(self.value1, j)) for i, j in zip(self.houses, self.houses[2:])] | ||
) | ||
|
||
def __repr__(self) -> str: | ||
return f"There is one house between {self.value1.value} and {self.value2.value}." | ||
|
||
|
||
@dataclass(eq=True, frozen=True) | ||
class two_between(Clue): | ||
""" | ||
The values are separated by two houses | ||
Examples (if 5 houses): | ||
- the cat is in house 1 and artist in house 4; or cat 2, artist 5 | ||
- the dog is in house 1 and red house is #4; or dog 2, red house 5 | ||
""" | ||
|
||
value1: Literal | ||
value2: Literal | ||
houses: Tuple[int, ...] = field(default_factory=lambda: (1, 2, 3, 4, 5)) | ||
|
||
def as_cnf(self) -> List[Tuple[str]]: | ||
return from_dnf( | ||
[(comb(self.value1, i), comb(self.value2, j)) for i, j in zip(self.houses, self.houses[3:])] | ||
+ [(comb(self.value2, i), comb(self.value1, j)) for i, j in zip(self.houses, self.houses[3:])] | ||
) | ||
|
||
def __repr__(self) -> str: | ||
return f"There are two houses between {self.value1.value} and {self.value2.value}." |
Oops, something went wrong.