21
5
Your task make a bot that plays Atomas, with the highest score.
How the game works:
The gameboard starts with a ring of 6 "atoms", with numbers ranging from 1
to 3
. You can "play" an atom between two atoms, or on another atom, depending on the atom itself.
You can either have a normal atom, or a special atom.
The normal atom:
You can play a normal atom between any two available atoms on the board.
You start off with atoms in the range 1 to 3
, but the range increases by 1 once every 40 moves (so after 40 moves, the range becomes 2 to 4
).
If there are atoms on the board that are lower than the range, it has a 1 / no. of atoms of that number on the board
chance of spawning.
Let's say you have a 2
to play, and the board looks like this:
1 1 2 1
Let's place the 2
to the right of the 1
.
The board now becomes:
1 1 2 1 2
Note: the board wraps around, so the 1
on the far left is actually next to the 2
on the far right. This will be important later.
There are 4 types of "special" atoms, and they are:
The +
atom:
This atom is played between two atoms. It has a 1 in 5 chance of spawning.
If the atoms on both sides of the +
atom are the same, fusion occurs. Here's how it works:
The two atoms fuse together to create an atom one higher.
(So, two 3 atoms fuse together to form one 4 atom.)
While the atoms on both sides of the fused atom are equal:
If the atoms on the side >= the fused atom:
The new fused atom = the old fused atom's value + 2.
If the atoms on the side < the fused atom:
The new fused atom = the old fused atom's value + 1.
Example:
1 1 3 2 2 3 (the 1 on the left-hand side "wraps back"
to the 3 on the right-hand side)
Let's use the + on the two 2's in the middle.
-> 1 1 3 3 3 (the two 2's fused together to make a 3)
-> 1 1 5 (the two 3's fused with the 3, and because 3 >= 3,
the new fused atom = 3 + 2 = 5)
-> 6 (the two 1's fused with the 5, since the board wraps,
and because 1 < 5, the new fused atom = 5 + 1 = 6)
Because the atoms on the sides of the 6 don't exist, fusion stops,
and the board is now [6].
If the atoms on both sides of the +
atom are different, then the +
stays on the board.
Example:
1 3 2 3 1 1
Let's use the + on the 2 and 3 in the middle.
-> 1 3 2 + 3 1 1 (2 != 3, so the + stays on the board)
The -
atom:
This atom is played on another atom. It has a 1 in 10 chance of spawning.
The -
atom removes an atom from the board, and gives you a choice to either:
- play the removed atom next round, or
- turn it into a + atom to play next round.
Example:
1 3 2 3 1 1
Let's use the - on the left-hand 2.
-> 1 3 3 1 1 (the 2 is now removed from the board)
Let's turn it into a +, and place it in between the 3's.
-> 1 4 1 1 (the two 3's fused together to make a 4)
-> 5 1 (the two 1's fused with the 4, and because 1 < 4,
the new fused atom = 4 + 1 = 5)
The black +
atom (B
):
This atom is played between 2 atoms. It has a 1 in 80 chance of spawning, and only spawns once your score > 750.
This atom is basically the same as the +
atom, except that it fuses any two atoms together, even +
's. From then on, it follows the +
rule (it only fuses atoms together if the atoms on both sides of the fused atom are equal).
The fused atom as a result of the black +
is equal to:
- the higher number atom in the fusion + 3
4
if the two fused atoms are+
's
Example:
1 3 2 1 3 1
Let's use the black + on the 2 and 1 in the middle.
-> 1 3 5 3 1 (the 2 and 1 fused together to make a 2 + 3 = 5)
-> 1 6 1 (+ rule)
-> 7 (+ rule)
Another example:
2 + + 2
Let's use the black + on the two +'s.
-> 2 4 2 (the two +'s fused together to make a 4)
-> 5 (+ rule)
The clone atom (C
):
This atom is played on another atom. It has a 1 in 60 chance of spawning, and only spawns once your score > 1500.
The clone atom allows you to choose an atom, and play it next round.
Example:
1 1 2 1
Let's use the clone on the 2, and place it to the right of the 1.
-> 1 1 2 1 2
Here is my build of the game, in Python 2:
import random
import subprocess
logs='atoms.log'
atom_range = [1, 3]
board = []
score = 0
move_number = 0
carry_over = " "
previous_moves = []
specials = ["+", "-", "B", "C"]
def plus_process(user_input):
global board, score, previous_moves, matches
previous_moves = []
matches = 0
def score_calc(atom):
global score, matches
if matches == 0:
score += int(round((1.5 * atom) + 1.25, 0))
else:
if atom < final_atom:
outer = final_atom - 1
else:
outer = atom
score += ((-final_atom + outer + 3) * matches) - final_atom + (3 * outer) + 3
matches += 1
if len(board) < 1 or user_input == "":
board.append("+")
return None
board_start = board[:int(user_input) + 1]
board_end = board[int(user_input) + 1:]
final_atom = 0
while len(board_start) > 0 and len(board_end) > 0:
if board_start[-1] == board_end[0] and board_end[0] != "+":
if final_atom == 0:
final_atom = board_end[0] + 1
elif board_end[0] >= final_atom:
final_atom += 2
else:
final_atom += 1
score_calc(board_end[0])
board_start = board_start[:-1]
board_end = board_end[1:]
else:
break
if len(board_start) == 0:
while len(board_end) > 1:
if board_end[0] == board_end[-1] and board_end[0] != "+":
if final_atom == 0:
final_atom = board_end[0]
elif board_end[0] >= final_atom:
final_atom += 2
else:
final_atom += 1
score_calc(board_end[0])
board_end = board_end[1:-1]
else:
break
if len(board_end) == 0:
while len(board_start) > 1:
if board_start[0] == board_start[-1] and board_start[0] != "+":
if board_start[0] >= final_atom:
final_atom += 2
else:
final_atom += 1
score_calc(board_start[0])
board_start = board_start[1:-1]
else:
break
if matches == 0:
board = board_start + ["+"] + board_end
else:
board = board_start + [final_atom] + board_end
for a in range(len(board) - 1):
if board[a] == "+":
if board[(a + 1) % len(board)] == board[a - 1]:
board = board[:a - 1] + board[a:]
plus_process(a)
break
def minus_process(user_input, minus_check):
global carry_over, board
carry_atom = board[int(user_input)]
if user_input == len(board) - 1:
board = board[:-1]
else:
board = board[:int(user_input)] + board[int(user_input) + 1:]
if minus_check == "y":
carry_over = "+"
elif minus_check == "n":
carry_over = str(carry_atom)
def black_plus_process(user_input):
global board
if board[int(user_input)] == "+":
if board[int(user_input) + 1] == "+":
inter_atom = 4
else:
inter_atom = board[int(user_input) + 1] + 2
else:
if board[int(user_input)] + 1 == "+":
inter_atom = board[int(user_input)] + 2
else:
inter_list = [board[int(user_input)], board[int(user_input) + 1]]
inter_atom = (inter_list.sort())[1] + 2
board = board[int(user_input) - 1:] + [inter_atom] * 2 + board[int(user_input) + 1:]
plus_process(int(user_input) - 1)
def clone_process(user_input):
global carry_over
carry_over = str(board[int(user_input)])
def regular_process(atom,user_input):
global board
if user_input == "":
board.append(random.randint(atom_range[0], atom_range[1]))
else:
board = board[:int(user_input) + 1] + [int(atom)] + board[int(user_input) + 1:]
def gen_specials():
special = random.randint(1, 240)
if special <= 48:
return "+"
elif special <= 60 and len(board) > 0:
return "-"
elif special <= 64 and len(board) > 0 and score >= 750:
return "B"
elif special <= 67 and len(board) > 0 and score >= 1500:
return "C"
else:
small_atoms = []
for atom in board:
if atom not in specials and atom < atom_range[0]:
small_atoms.append(atom)
small_atom_check = random.randint(1, len(board))
if small_atom_check <= len(small_atoms):
return str(small_atoms[small_atom_check - 1])
else:
return str(random.randint(atom_range[0], atom_range[1]))
def specials_call(atom, user_input):
specials_dict = {
"+": plus_process,
"-": minus_process,
"B": black_plus_process,
"C": clone_process
}
if atom in specials_dict.keys():
if atom == "-":
minus_process(user_input[0], user_input[1])
else:
specials_dict[atom](user_input[0])
else:
regular_process(atom,user_input[0])
def init():
global board, score, move_number, carry_over, previous_moves
board = []
score = 0
for _ in range(6):
board.append(random.randint(1, 3))
while len(board) <= 18:
move_number += 1
if move_number % 40 == 0:
atom_range[0] += 1
atom_range[1] += 1
if carry_over != " ":
special_atom = carry_over
carry_over = " "
elif len(previous_moves) >= 5:
special_atom = "+"
else:
special_atom = gen_specials()
previous_moves.append(special_atom)
bot_command = "python yourBot.py"
bot = subprocess.Popen(bot_command.split(),
stdout = subprocess.PIPE,
stdin = subprocess.PIPE)
to_send="/".join([
# str(score),
# str(move_number),
str(special_atom),
" ".join([str(x) for x in board])
])
bot.stdin.write(to_send)
with open(logs, 'a') as f:f.write(to_send+'\n')
bot.stdin.close()
all_user_input = bot.stdout.readline().strip("\n").split(" ")
specials_call(special_atom, all_user_input)
print("Game over! Your score is " + str(score))
if __name__ == "__main__":
for a in range(20):
with open(logs, 'a') as f:f.write('round '+str(a)+'-'*50+'\n')
init()
How the bot thing works:
Input
- Your bot will get 2 inputs: the atom that is currently in play, and the state of the board.
- The atom will be like so:
+
for a+
atom-
for a-
atomB
for a Black+
atomC
for a Clone atom{atom}
for a normal atom
- The state of the board will be like so:
atom 0 atom 1 atom 2... atom n
, with the atoms separated by spaces (atom n
wraps back toatom 1
, to simulate a "ring" gameboard)
- These two will be separated by a
/
.
Example inputs:
1/1 2 2 3 (the atom in play is 1, and the board is [1 2 2 3])
+/1 (the atom in play is +, and the board is [1] on its own)
Output
You will output a string, depending on what the atom in play is.
If the atom is meant to be played between two atoms:
Output the gap you want to play the atom in. The gaps are like in between each atom, like so:
atom 0, GAP 0, atom 1, GAP 1, atom 2, GAP 2... atom n, GAP N
(
gap n
indicates you want to place the atom betweenatom 1
and atomn
) So output2
if you want to play the atom ongap 2
.
- If the atom is meant to be played on an atom:
- Output the atom you want to play it on, so
2
if you want to play the atom onatom 2
.
- Output the atom you want to play it on, so
- If the atom is a
-
:- Output the atom you want to play it on, followed by a space, followed by a
y/n
choice of turning the atom into a+
later, so2, "y"
if you want to play the atom onatom 2
, and you want to turn it into a+
. Note: this requires 2 inputs, instead of 1.
- Output the atom you want to play it on, followed by a space, followed by a
Example outputs:
(Atom in play is a +)
2 (you want to play the + in gap 2 - between atom 2 and 3)
(Atom in play is a -)
3 y (you want to play the - on atom 3, and you want to change it to a +)
2 n (you want to play the - on atom 2, and you don't want to change it)
- To make the bot work, you have to go to the
Popen
bit (at around the end of the code), and replace it with whatever makes your program run as a Pythonic list (so if your program isderp.java
, replace["python", "bot.py"]
with["java", "derp.java"]
).
Answer-specific Specs:
- Place the entire code of your bot into the answer. If it doesn't fit, it doesn't count.
- Each user is allowed to have more than 1 bot, however, they should all be in separate answer posts.
- Also, give your bot a name.
Scoring:
- The bot with the highest score wins.
- Your bot will be tested for 20 games, and the final score is the average of the 20 games.
- The tie-breaker will be the time of the upload of the answer.
So your answer will be formatted like this:
{language}, {bot name} Score: {score}
Good luck!
How does the generated
+
for a-
atom work ? If you chosey
will you be guaranteed to get a+
on the next move ? – Ton Hospel – 2016-10-11T10:01:24.860@TonHospel Yes, that's right. – clismique – 2016-10-11T10:01:49.997
4I suggest changing your bot driver so it can handle any standalone program that takes input on STDIN and gives a result on STDOUT. That should give language independence and most languages used on this site can easily do that. Of course this means defining a strict I/O format, e.g.
input_atom\natom0 atom1 .... atomn\n
for STDIN – Ton Hospel – 2016-10-11T10:08:03.333@TonHospel Is it okay if I'm asking the bot for an optional input (So, the bot could experience 1 or 2 inputs per turn, depending on the atom)? – clismique – 2016-10-11T10:49:39.317
Sure, you can make up any protocol you like, but I would avoid it. As soon as you go beyond the trivial
input then output
you start to have to worry about buffering and deadlock in the communication protocol. Nor do I see the need. Is it for the for the-
y
case ? Simply call the bot twice then – Ton Hospel – 2016-10-11T11:05:47.573To be clear, I'm suggesting the standalone bot gets called on every turn, NOT that it is kept running while input and output keep happening. I don't see a need to keep history or even current score in the bot. However the bot should get the move number as input too (to know the placing atom range and when it will change) – Ton Hospel – 2016-10-11T13:48:59.890
Scoring
+
. The text saysYou earn 2 * matched atom points for every atom you match
, but the code saysscore += (temp_list_end[0] * 2) + 1
which is 1 more. Which is it ? – Ton Hospel – 2016-10-11T14:32:24.1531The code seems to be able to put
+
in the element list, but this is nowhere found in the textual description – Ton Hospel – 2016-10-11T16:25:19.257I love this game! I have it on my phone, and it can be addicting. I gotta say though, it took a while to figure it out completely without a tutorial. Does your implementation use the same scoring method that the game does? – mbomb007 – 2016-10-11T18:10:58.853
Let us continue this discussion in chat.
– mbomb007 – 2016-10-11T18:33:35.090Could the code include built-in graphics, or does it have to have an ASCII output? – JungHwan Min – 2016-10-11T23:47:02.677
@JHM It has to be ASCII, I'm afraid. – clismique – 2016-10-12T08:19:43.390
1Ah, I see you made the program able to call external bots. However, you also need to pass the current move number and score on STDIN otherwise the bot cannot predict the chances of each atom happening in the future – Ton Hospel – 2016-10-18T14:15:56.513
@Qwerp-Derp You're using
– mbomb007 – 2016-10-24T18:46:46.753750
and1500
as the score requirement for dark pluses and neutrinos, but you don't use Atomas's method of Scoring. It looks like getting to that score may be more difficult than you wanted using your controller. I'm not sure.1Idk if people will spend time creating a solution if the controller isn't improved. I like the question, but not the implementation. – mbomb007 – 2016-11-02T16:14:22.660
@TonHospel Included. – clismique – 2016-11-03T01:50:30.777
@mbomb007 I fixed the implementation a bit - there shouldn't be any bugs anymore. If there's anything else I can fix, please tell me. – clismique – 2016-11-03T01:51:14.193
IMHO, the scoring should be changed. http://atomas.wikia.com/wiki/Score
– mbomb007 – 2016-11-03T13:48:52.447@mbomb007 Fixed... – clismique – 2016-11-03T22:47:36.403
The challenge is good, it's just that the work required to create a good answer is pretty high, at least according to my standards. My highscore IRL is
Np 93; 129K
. – mbomb007 – 2016-12-09T22:41:38.450Why the
atom_range
is not initialized at theinit()
function? – mdahmoune – 2017-07-08T11:51:24.650@mdahmoune It's been half a year since I wrote that code. From what I recall,
atom_range
is global, and it would be a pain to initialise it atinit()
(causeglobal
hell). – clismique – 2017-07-08T11:58:32.923@mdahmoune 1. Yes,
atom_range
is supposed to grow over time, spawning elements can change. And for the bot thing, I agree that more variables will help, but the bot honestly doesn't need those elements. Sorry for replying so late. – clismique – 2017-07-08T12:32:47.250for
atom_range
what I try to say, is that when we execute theinit()
function the second, third,... time, theatom_range
is not reset to[1, 3]
– mdahmoune – 2017-07-08T12:35:00.520@mdahmoune Yeah, good point. I'm on mobile now, though, will fix up the code in the question tomorrow morning. In the meantime, you can edit the controller. – clismique – 2017-07-08T12:38:34.460
I believe there is a problem with
regular_process
because it does not add theatom
parameter when callingspecials_call(atom, user_input)
, in place it add to the board a random atom. – mdahmoune – 2017-07-08T22:59:09.960I built a draft bot that achieve 1500 as score, but the controller crashes just after... I think it du to the "C" atom... – mdahmoune – 2017-07-08T23:04:31.377
@mdahmoune Can you give me a GitHub Gist to your code? I might have a look at the issue. – clismique – 2017-07-08T23:13:38.250
Yes of course, just give me the time to clean it.. – mdahmoune – 2017-07-08T23:16:33.107
I think also that your controller does not handle the case where the board is like ... 1 + 2 ... and the bot put 1 between + and 2, the controller does not merge the two 1... – mdahmoune – 2017-07-08T23:29:40.530
Real behavior controller eg showing the above problem:|1/1 3 3 2 2 2|3/1 3 3 2 2 2 12|3/1 3 3 2 2 2 12 11|+/1 3 3 2 2 2 12 11 13|2/1 4 2 2 2 12 11 13|12/1 4 2 2 2 12 11 13 11|13/1 4 2 2 2 12 11 13 11 13|+/1 4 2 2 2 12 11 13 11 13 11|+/1 4 3 2 12 11 13 11 13 11|1/1 + 4 3 2 12 11 13 11 13 11|11/1 + 11 4 3 2 12 11 13 11 13 11|+/1 12 + 11 4 3 2 12 11 13 11 13 11|+/1 + 12 + 11 4 3 2 12 11 13 11 13 11|1/1 + + 12 + 11 4 3 2 12 11 13 11 13 11|11/1 + 12 + 12 + 11 4 3 2 12 11 13 11 13 11|4/1 + 12 + 12 + 11 4 3 2 12 11 13 11 13 11 13|12/1 + 12 + 12 + 11 4 3 2 12 11 13 11 13 11 13 12 – mdahmoune – 2017-07-09T08:53:46.290
sometime when the draft bot achieves 750 point, the controller crashes, I think we need to cast
user_input
intoint
in function namedclone_process()
. – mdahmoune – 2017-07-09T09:07:16.133I made a version of Atomas you can play via text input. It'll take you forever to finish a game, though. https://ideone.com/U7ZUnD
– mbomb007 – 2017-11-29T21:40:10.767