Last active
August 23, 2024 19:41
-
-
Save LieBtrau/88924083a531d99251664d0616129b31 to your computer and use it in GitHub Desktop.
Songka game Killjoy: finds a sequence of moves so that the player that goes first always wins.
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
#!/usr/bin/env python3 | |
# Songka game solver | |
# [Game Rules](https://www.wikihow.com/Play-Congkak) | |
# [Songka_Plus on Android](https://play.google.com/store/apps/details?id=net.azurewebsites.sungka.twa&hl=en_US) | |
# This is pretty much a solved game. The player that goes first will always win if he plays optimally. | |
# There are several sequences to achieve this. The following is one of them: | |
# Solution found: 41 turns, 93 seeds in the storehouse | |
# 0 1 4 1 0 4 | |
# 1 1 2 3 2 0 | |
# 1 2 0 1 6 1 | |
# 6 5 5 0 6 6 | |
# 3 0 6 1 4 6 | |
# 6 0 6 1 6 5 | |
# 6 2 6 4 6 | |
# Where 0 is the first house on the player's side (farthest from player's store house), 1 is the second house on the player's side, etc. | |
# As the opponent is only left with 5 pieces at the end of the game, all opponent's houses are burnt. Game over. | |
import copy, sys | |
opponent_storehouse = 7 | |
player_storehouse = 15 | |
maximum_score = 0 | |
minimum_turns = 100 | |
turns = [0 for i in range(100)] | |
def play(): | |
# Play the game | |
# Board is arranged in clockwise direction as follows: | |
# board[0] to board[6] = houses on opponent side | |
# board[7] = opponent's storehouse | |
# board[8] to board[14] = houses on player side | |
# board[15] = player's storehouse | |
# board[0] is opposite to board[14], board[1] is opposite to board[13], etc. | |
board = [0 for i in range(16)] | |
# Prepare the board, fill the houses with 7 seeds each. The storehouses are empty. | |
for i in range(16): | |
if i != opponent_storehouse and i != 15: | |
board[i] = 7 | |
play_opponent(0, board) | |
def string_board(board): | |
# Convert the board to a string | |
# board = current board | |
s = "\t" | |
for i in range(8): | |
s += str(board[i]) + "\t" | |
s += "\n" + str(board[player_storehouse]) + "\t" | |
for i in range(14, 7, -1): | |
s += str(board[i]) + "\t" | |
s += "\n" | |
return s | |
def play_opponent(level, board): | |
# Find the solution by playing the opponent's turn | |
for position in range(7): | |
global turns | |
turns[level] = position | |
board_copy = copy.deepcopy(board) | |
out_board, playing = play_turn(0, board_copy, position) | |
# if(playing): | |
# for j in range(level): | |
# print(" ", end="") | |
# print(level, position, out_board[opponent_storehouse], playing) | |
# print(string_board(out_board)) | |
if(playing): | |
# The opponent gets another turn | |
# Check if opponent houses are all empty | |
empty = True | |
for i in range(7): | |
if out_board[i] > 0: | |
empty = False | |
break | |
if empty: | |
# Game over | |
global minimum_turns | |
if(level + 1 < minimum_turns): | |
minimum_turns = level + 1 | |
global maximum_score | |
maximum_score = out_board[opponent_storehouse] | |
print("New minimum turns: ", minimum_turns, "New maximum score: ", maximum_score) | |
print("Solution found: ", level+1, "turns, ", out_board[opponent_storehouse], "seeds in the storehouse") | |
for i in range(level+1): | |
print(turns[i], end=" ") | |
if((i+1)%6 ==0): | |
print() | |
print() | |
else: | |
play_opponent(level + 1, out_board) | |
def play_turn(player, board, house): | |
# Play one turn | |
# player = 0 for opponent, 1 for player | |
# board = current board | |
# house = house number | |
# Return board after the turn, and True if the player gets another turn, False otherwise | |
# Check for invalid house | |
if(player == 0 and (house < 0 or house > 6)): | |
print("Invalid house number") | |
return board, False | |
if(player == 1 and (house < 8 or house > 14)): | |
print("Invalid house number") | |
return board, False | |
# Distribute the seeds | |
while(board[house] > 0): | |
board, house = distribute_seeds(player, house, board) | |
# print(string_board(board)) | |
if(player == 0 and house == opponent_storehouse): | |
# The last seed is placed in the storehouse | |
return board, True | |
if(player == 1 and house == player_storehouse): | |
# The last seed is placed in the storehouse | |
return board, True | |
if(board[house] == 1): | |
# The last seed is placed in an empty house | |
# If the player ends in an empty house on his side, he captures the seeds in the opposite house if there are seeds in it. | |
if(((player == 0 and house < 7)) or (player == 1 and house > 7)) and board[14 - house] > 0: | |
# Capture the seeds | |
board[opponent_storehouse if player==0 else player_storehouse] += board[14 - house] + 1 | |
board[14 - house] = 0 | |
board[house] = 0 | |
return board, False | |
return board, False | |
def distribute_seeds(player, house, board): | |
# Distribute seeds from a house | |
# player = 0 for opponent, 1 for player | |
# house = house number | |
# board = current board | |
# Return the new board and the house number where the last seed is placed | |
house = int(house) | |
if(house == opponent_storehouse or house == player_storehouse): | |
print("The house is a storehouse, nothing to distribute") | |
return board, -1 | |
if(house < 0 or house > 15): | |
print("Invalid house number") | |
return board, -1 | |
# Get the number of seeds in the house | |
seeds = board[house] | |
if(seeds == 0): | |
# The house is empty, nothing to distribute | |
return board, house | |
# Empty the house | |
board[house] = 0 | |
# Distribute the seeds | |
for i in range(seeds): | |
house = (house + 1) % 16 | |
if(player == 0 and house == player_storehouse): | |
# Skip player's storehouse | |
house = 0 | |
if(player == 1 and house == opponent_storehouse): | |
# Skip opponent's storehouse | |
house = 8 | |
board[house] += 1 | |
return board, house | |
def main(argv): | |
# Find the solution | |
play() | |
if __name__ == '__main__': | |
main(sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment