Dr. Eureka is a game where players take on the role of apprentice scientists. They race to be the first to realize the correct combination of the colored balls in the test tubes, in the order prescribed on the current challenge card. In order to achieve this, players must transfer the balls from one tube to the other, without touching them with their hands or letting them fall from the tubes.
When all players are ready to get started, the top challenge card is turned face up. All at the same time, players race to complete the challenge by transferring their balls from one tube to another, trying to arrange their balls in the order shown on the current challenge card. Players must obey the following rules:
a combination can be completed with all tubes right side up (A)
a combination can be completed with one of the tubes flipped upside down (B)
The order of the test tubes (from left to right) doesn't matter in realizing a combination. Only the order of the colored balls in the test tubes (from top to bottom) matters. Players are allowed to swap the position of their tubes if that may help them.
If a player touches or drops one of his balls, or incorrectly announces that he has completed the challenge, he is immediately removed from the rest of the round (until the current challenge card is completed).
The game is played with a fixed number of test tubes. These tubes have a maximal capacity, which indicates the maximum number of balls that fit in a test tube.
The first player who arranges his balls in the correct combination, calls out "Eureka!". If all the players agree that his combination is correct, he scores the current challenge card as 1 point.
Players leave their balls where they were at the end of the previous challenge. They are not returned to a particular starting position. However, all tubes must be turned right side up before beginning a new round. When all players are ready to start again, a new round begins by drawing the next challenge card face up. The first player to 5 points (5 challenge cards) wins the game.
Define a class Eureka that can be used to simulate how the players of Dr. Eureka can arrange the colored balls into their test tubes. To refer to the individual test tubes, the tubes are numbered from left to right, starting from 0.
When creating a simulation (Eureka), a string (str) must be passed that describes the configuration of the colored balls in the different test tubes. Each ball is represented as an uppercase letter that indicates the color of the ball (the same letters represent balls of the same color). Each test tube is described by the sequence of balls it contains, listed from bottom to top. The successive test tubes are separated by commas (,). For example, PG,GR,PR describes the configuration of three test tubes that can be seen in the first image of the introduction, where the uppercase letter P represents a purple ball, G a green ball and R a red ball.
The maximum capacity (int) of the test tubes can be passed to the optional parameter maximum (default value: 4). If one of the test tubes in the given configuration exceeds the maximum capacity, an AssertionError must be raised with the message invalid configuration.
If a test tube is flipped upside down, its number (int) can be passed to the optional parameter flipped. All test tubes are turned right side up by default. If an invalid number is passed, an AssertionError must be raised with the message invalid configuration.
If a simulation (Eureka) is passed to the built-in function repr, a string representation (str) must be returned that reads as a Python expression to create a new simulation with the same configuration as the object passed to the function repr. The maximum capacity of the test tubes must always be passed explicitly to the parameter maximum. Only if one of the test tubes is flipped upside down, its number must be passed to the parameter flipped.
If a simulation (Eureka) is passed to the built-in function str, a string representation (str) of the test tube configuration must be returned. The format of this representation can be derived from the examples below. Note that the top (= =) and bottom (===) of each test tube indicate whether the tube is upright or upside down. All tubes are separated from each other by a single space in the string representation.
In addition, the class Eureka must support at least the following methods:
A method flip that takes the number (int) of a test tube. The method must flip the given tube upside down, with the condition that at most one tube may be flipped at each point in time. It is therefore only allowed to flip a tube if all tubes are upright, or to flip the tube that was upside down back right side up. The method must return a reference to the object on which it was called. If an invalid test tube number is passed to the method or if the given test tube cannot be flipped, an AssertionError must be raised with the message invalid move.
A method transfer that takes the numbers (int) of two different test tubes: i) the source tube and ii) the destination tube for transferring balls. The method also has a third optional parameter that may take the number of balls (int) that must be transferred. It is not allowed to transfer balls if any of the tubes is flipped upside down, and the maximum capacity of the tubes cannot be exceeded in transferring balls. The method must return a reference to the object on which it was called. If an invalid test tube number is passed to the method, if there are less balls in the tube then must be transferred, or if the transfer is not allowed, an AssertionError must be raised with the message invalid move. The configuration of the colored balls may not be modified in that case.
Two simulations
>>> game = Eureka('RPG,G,PR')
>>> game
Eureka('RPG,G,PR', maximum=4)
>>> print(game)
= = = = = =
| | | | | |
|G| | | | |
|P| | | |R|
|R| |G| |P|
=== === ===
>>> game.transfer(0, 1, 1)
Eureka('RP,GG,PR', maximum=4)
>>> print(game)
= = = = = =
| | | | | |
| | | | | |
|P| |G| |R|
|R| |G| |P|
=== === ===
>>> game.transfer(1, 2, 2)
Eureka('RP,,PRGG', maximum=4)
>>> print(game)
= = = = = =
| | | | |G|
| | | | |G|
|P| | | |R|
|R| | | |P|
=== === ===
>>> game.transfer(0, 1, 1)
Eureka('R,P,PRGG', maximum=4)
>>> print(game)
= = = = = =
| | | | |G|
| | | | |G|
| | | | |R|
|R| |P| |P|
=== === ===
>>> game.transfer(2, 1, 1).transfer(2, 0, 1)
Eureka('RG,PG,PR', maximum=4)
>>> print(game)
= = = = = =
| | | | | |
| | | | | |
|G| |G| |R|
|R| |P| |P|
=== === ===
>>> game.flip(2)
Eureka('RG,PG,RP', maximum=4, flipped=2)
>>> print(game)
= = = = ===
| | | | | |
| | | | | |
|G| |G| |P|
|R| |P| |R|
=== === = =
>>> game == Eureka('RP,RG,PG')
True
>>> game == Eureka('PR,RG,PG')
False
>>> game.flip(100) # wrong tube index
Traceback (most recent call last):
AssertionError: invalid move
>>> game.flip(0) # another tube is already flipped over
Traceback (most recent call last):
AssertionError: invalid move
>>> game.transfer(0, 1) # a tube is flipped over
Traceback (most recent call last):
AssertionError: invalid move
>>> game.flip(2)
Eureka('RG,PG,PR', maximum=4)
>>> print(game)
= = = = = =
| | | | | |
| | | | | |
|G| |G| |R|
|R| |P| |P|
=== === ===
>>> game.transfer(0, 100) # wrong tube index
Traceback (most recent call last):
AssertionError: invalid move
>>> game.transfer(100, 0) # wrong tube index
Traceback (most recent call last):
AssertionError: invalid move
>>> game.transfer(0, 0) # tube cannot be transferred into itself
Traceback (most recent call last):
AssertionError: invalid move
>>> game.transfer(0, 1)
Eureka(',PGGR,PR', maximum=4)
>>> print(game)
= = = = = =
| | |R| | |
| | |G| | |
| | |G| |R|
| | |P| |P|
=== === ===
>>> game.transfer(2, 1) # capacity of tube 1 would be exceeded
Traceback (most recent call last):
AssertionError: invalid move
>>> game.transfer(0, 1)
Eureka(',PGGR,PR', maximum=4)
>>> print(game)
= = = = = =
| | |R| | |
| | |G| | |
| | |G| |R|
| | |P| |P|
=== === ===