# author: Vahid Ghafourian
# Date: 2023/09/06

from src.components.game import Game
from src.tools.read_config import read_config
from src.turn_controllers.change_turn import change_turn
import os
import argparse
from src.components.client_game import ClientGame
from src.blueprints.login import reset_global_player_id


def start_p0(client_game, name="<name of player one>"):
    from player0.initialize import initializer as initializer_p0
    from player0.main import initializer, turn
    initializer_p0(client_game, name, initializer, turn)


def start_p1(client_game, name="<name of player two>"):
    from player1.initialize import initializer as initializer_p1
    from player1.main import initializer, turn
    initializer_p1(client_game, name, initializer, turn)


def start_p2(client_game, name="<name of player three>"):
    from player2.initialize import initializer as initializer_p2
    from player2.main import initializer, turn
    initializer_p2(client_game, name, initializer, turn)


def main(selected_map):
    # define argument parser
    parser = argparse.ArgumentParser(description='choose map to play on')
    parser.add_argument('-m', '--map', type=str, help='choose map to play on')
    args = parser.parse_args()

    # read map file
    main_game = Game()
    # ask player to choose map from the list of maps
    maps = os.listdir('maps')

    ## get the selected map from the player
    selected_map = str(maps.index(args.map)) if args.map != None else str(selected_map)

    while selected_map.isdigit() == False or int(selected_map) >= len(maps) or int(selected_map) < 0:
        ## show the list of maps from the maps folder
        print("Choose a map from the list of maps:")
        for i, map in enumerate(maps):
            print(i, '-', map)
        selected_map = input("Enter the number of the map you want to choose: ")

    ## read the selected map
    main_game.read_map('maps/' + maps[int(selected_map)])

    # read config
    main_game.config = read_config()

    # set the debug variable to True or False to see the debug messages and generate debug logs
    main_game.debug = main_game.config['debug']

    # Build Clients
    client_game = ClientGame(main_game)

    # you can change order of start players for change order on playing
    start_p0(client_game)
    start_p1(client_game)
    start_p2(client_game)

    # Run the server
    change_turn(main_game, client_game)

    reset_global_player_id()
    del main_game, client_game


if __name__ == '__main__':
    main(None)

main.py
VahidGhafourian/AICup2023-No-Flask
from src.components.client_game import ClientGame

def initializer(game: ClientGame, name, initialize, turn):
    output = game.blueprints.login(game.main_game, name, initialize, turn)
    if 'error' in output:
        raise Exception("error: " + output['error'])
    print(game.blueprints.ready(game.main_game, output['player_id']))
player0/initialize.py
VahidGhafourian/AICup2023-No-Flask
import random
from src.components.client_game import ClientGame

flag = False


def initializer(game: ClientGame):
    print(game.get_player_id())
    strategic_nodes = game.get_strategic_nodes()['strategic_nodes']
    score = game.get_strategic_nodes()['score']
    strategic_nodes = list(zip(strategic_nodes, score))
    strategic_nodes.sort(key=lambda x: x[1], reverse=True)
    strategic_nodes, score = list(zip(*strategic_nodes))
    print(game.get_turn_number())

    owner = game.get_owners()
    for i in strategic_nodes:
        if owner[str(i)] == -1:
            print(game.put_one_troop(i), "-- putting one troop on", i)
            return
    adj = game.get_adj()
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == -1:
                print(game.put_one_troop(j), "-- putting one troop on neighbor of strategic node", j)
                return
    my_id = game.get_player_id()['player_id']
    nodes = []
    nodes.extend([i for i in strategic_nodes if owner[str(i)] == my_id])
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == my_id:
                nodes.append(j)
    nodes = list(set(nodes))
    node = random.choice(nodes)
    game.put_one_troop(node)
    print("3-  putting one troop on", node)


def turn(game: ClientGame):
    global flag
    print(game.get_number_of_troops_to_put())
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == -1 and game.get_number_of_troops_to_put()['number_of_troops'] > 1:
            print(game.put_troop(int(i), 1))

    list_of_my_nodes = []
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            list_of_my_nodes.append(i)

    print(game.put_troop(random.choice(list_of_my_nodes), game.get_number_of_troops_to_put()['number_of_troops']))
    print(game.get_number_of_troops_to_put())

    print(game.next_state())

    # find the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    # find a neighbor of that node that I don't own
    adj = game.get_adj()
    for i in adj[max_node]:
        if owner[str(i)] != game.get_player_id()['player_id'] and owner[str(i)] != -1:
            print(game.attack(int(max_node), int(i), 1, 0.5))
            break
    
    print(game.next_state())
    print(game.get_state())
    # get the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    print(game.get_reachable(int(max_node)))
    x = game.get_reachable(int(max_node))['reachable']
    x.remove(int(max_node))
    if len(x) > 0:
        destination = random.choice(x)
        print(game.move_troop(int(max_node), int(destination), 1))
    
    print(game.next_state())

    if flag == False:
        max_troops = 0
        max_node = -1
        owner = game.get_owners()
        for i in owner.keys():
            if owner[str(i)] == game.get_player_id()['player_id']:
                if game.get_number_of_troops()[i] > max_troops:
                    max_troops = game.get_number_of_troops()[i]
                    max_node = i

        print(game.get_number_of_troops()[max_node])
        print(game.fort(int(max_node), 3))
        print(game.get_number_of_fort_troops())
        flag = True
    

player0/main.py
VahidGhafourian/AICup2023-No-Flask
from src.components.client_game import ClientGame

def initializer(game: ClientGame, name, initialize, turn):
    output = game.blueprints.login(game.main_game, name,  initialize, turn)
    if 'error' in output:
        raise Exception("error: " + output['error'])
    print(game.blueprints.ready(game.main_game, output['player_id']))
player1/initialize.py
VahidGhafourian/AICup2023-No-Flask
import random
from src.components.client_game import ClientGame

flag = False


def initializer(game: ClientGame):
    print(game.get_player_id())
    strategic_nodes = game.get_strategic_nodes()['strategic_nodes']
    score = game.get_strategic_nodes()['score']
    strategic_nodes = list(zip(strategic_nodes, score))
    strategic_nodes.sort(key=lambda x: x[1], reverse=True)
    strategic_nodes, score = list(zip(*strategic_nodes))
    print(game.get_turn_number())

    owner = game.get_owners()
    for i in strategic_nodes:
        if owner[str(i)] == -1:
            print(game.put_one_troop(i), "-- putting one troop on", i)
            return
    adj = game.get_adj()
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == -1:
                print(game.put_one_troop(j), "-- putting one troop on neighbor of strategic node", j)
                return
    my_id = game.get_player_id()['player_id']
    nodes = []
    nodes.extend([i for i in strategic_nodes if owner[str(i)] == my_id])
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == my_id:
                nodes.append(j)
    nodes = list(set(nodes))
    node = random.choice(nodes)
    game.put_one_troop(node)
    print("3-  putting one troop on", node)


def turn(game: ClientGame):
    global flag
    print(game.get_number_of_troops_to_put())
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == -1 and game.get_number_of_troops_to_put()['number_of_troops'] > 1:
            print(game.put_troop(int(i), 1))

    list_of_my_nodes = []
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            list_of_my_nodes.append(i)

    print(game.put_troop(random.choice(list_of_my_nodes), game.get_number_of_troops_to_put()['number_of_troops']))
    print(game.get_number_of_troops_to_put())

    print(game.next_state())

    # find the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    # find a neighbor of that node that I don't own
    adj = game.get_adj()
    for i in adj[max_node]:
        if owner[str(i)] != game.get_player_id()['player_id'] and owner[str(i)] != -1:
            print(game.attack(int(max_node), int(i), 1, 0.5))
            break

    print(game.next_state())
    print(game.get_state())
    # get the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    print(game.get_reachable(int(max_node)))
    x = game.get_reachable(int(max_node))['reachable']
    x.remove(int(max_node))
    if len(x) > 0:
        destination = random.choice(x)
        print(game.move_troop(int(max_node), int(destination), 1))

    print(game.next_state())

    if flag == False:
        max_troops = 0
        max_node = -1
        owner = game.get_owners()
        print(owner)
        for i in owner.keys():
            if owner[str(i)] == game.get_player_id()['player_id']:
                print('yes')
                if game.get_number_of_troops()[i] > max_troops:
                    max_troops = game.get_number_of_troops()[i]
                    max_node = i

        print(game.get_number_of_troops()[max_node])
        print(game.fort(int(max_node), 3))
        print(game.get_number_of_fort_troops())
        flag = True

    

player1/main.py
VahidGhafourian/AICup2023-No-Flask
from src.components.client_game import ClientGame

def initializer(game: ClientGame, name, initialize, turn):
    output = game.blueprints.login(game.main_game, name,  initialize, turn)
    if 'error' in output:
        raise Exception("error: " + output['error'])
    print(game.blueprints.ready(game.main_game, output['player_id']))
player2/initialize.py
VahidGhafourian/AICup2023-No-Flask
import random
from src.components.client_game import ClientGame

flag = False


def initializer(game: ClientGame):
    print(game.get_player_id())
    strategic_nodes = game.get_strategic_nodes()['strategic_nodes']
    score = game.get_strategic_nodes()['score']
    strategic_nodes = list(zip(strategic_nodes, score))
    strategic_nodes.sort(key=lambda x: x[1], reverse=True)
    strategic_nodes, score = list(zip(*strategic_nodes))
    print(game.get_turn_number())
    
    owner = game.get_owners()
    for i in strategic_nodes:
        if owner[str(i)] == -1:
            print(game.put_one_troop(i), "-- putting one troop on", i)
            return
    adj = game.get_adj()
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == -1:
                print(game.put_one_troop(j), "-- putting one troop on neighbor of strategic node", j)
                return
    my_id = game.get_player_id()['player_id']
    nodes = []
    nodes.extend([i for i in strategic_nodes if owner[str(i)] == my_id])
    for i in strategic_nodes:
        for j in adj[str(i)]:
            if owner[str(j)] == my_id:
                nodes.append(j)
    nodes = list(set(nodes))
    node = random.choice(nodes)
    game.put_one_troop(node)
    print("3-  putting one troop on", node)


def turn(game: ClientGame):
    global flag
    print(game.get_number_of_troops_to_put())
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == -1 and game.get_number_of_troops_to_put()['number_of_troops'] > 1:
            print(game.put_troop(int(i), 1))

    list_of_my_nodes = []
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            list_of_my_nodes.append(i)

    print(game.put_troop(random.choice(list_of_my_nodes), game.get_number_of_troops_to_put()['number_of_troops']))
    print(game.get_number_of_troops_to_put())

    print(game.next_state())

    # find the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    # find a neighbor of that node that I don't own
    adj = game.get_adj()
    for i in adj[max_node]:
        if owner[str(i)] != game.get_player_id()['player_id'] and owner[str(i)] != -1:
            print(game.attack(int(max_node), int(i), 1, 0.5))
            break

    print(game.next_state())
    print(game.get_state())
    # get the node with the most troops that I own
    max_troops = 0
    max_node = -1
    owner = game.get_owners()
    for i in owner.keys():
        if owner[str(i)] == game.get_player_id()['player_id']:
            if game.get_number_of_troops()[i] > max_troops:
                max_troops = game.get_number_of_troops()[i]
                max_node = i
    print(game.get_reachable(int(max_node)))
    x = game.get_reachable(int(max_node))['reachable']
    x.remove(int(max_node))
    if len(x) > 0:
        destination = random.choice(x)
        print(game.move_troop(int(max_node), int(destination), 1))

    print(game.next_state())

    if flag == False:
        max_troops = 0
        max_node = -1
        owner = game.get_owners()
        for i in owner.keys():
            if owner[str(i)] == game.get_player_id()['player_id']:
                if game.get_number_of_troops()[i] > max_troops:
                    max_troops = game.get_number_of_troops()[i]
                    max_node = i

        print(game.get_number_of_troops()[max_node])
        print(game.fort(int(max_node), 3))
        print(game.get_number_of_fort_troops())
        flag = True

    

player2/main.py
VahidGhafourian/AICup2023-No-Flask
# import blueprints
from src.blueprints.index import index
from src.blueprints.login import login
from src.blueprints.ready import ready
from src.blueprints.get_owners import get_owners
from src.blueprints.get_troops_count import get_troops_count
from src.blueprints.get_state import get_state
from src.blueprints.get_turn_number import get_turn_number
from src.blueprints.get_adj import get_adj
from src.blueprints.next_state import next_state
from src.blueprints.put_one_troop import put_one_troop
from src.blueprints.put_troop import put_troop
from src.blueprints.get_player_id import get_player_id
from src.blueprints.attack import attack
from src.blueprints.move_troop import move_troop
from src.blueprints.get_strategic_nodes import get_strategic_nodes
from src.blueprints.get_number_of_troops_to_put import get_number_of_troops_to_put
from src.blueprints.get_reachable import get_reachable
from src.blueprints.get_number_of_fort_troops import get_number_of_fort_troops
from src.blueprints.fort import fort


class BluePrints:
    def __init__(self):
        self.index = index
        self.login = login
        self.ready = ready
        self.get_owners = get_owners
        self.get_troops_count = get_troops_count
        self.get_state = get_state
        self.get_turn_number = get_turn_number
        self.get_adj = get_adj
        self.next_state = next_state
        self.put_one_troop = put_one_troop
        self.put_troop = put_troop
        self.get_player_id = get_player_id
        self.attack = attack
        self.move_troop = move_troop
        self.get_strategic_nodes = get_strategic_nodes
        self.get_number_of_troops_to_put = get_number_of_troops_to_put
        self.get_reachable = get_reachable
        self.get_number_of_fort_troops = get_number_of_fort_troops
        self.fort = fort

src/blueprints/BluePrints.py
VahidGhafourian/AICup2023-No-Flask
import random


def attack(attacking_id: int, target_id: int, fraction: float, move_fraction: float, main_game, player_id):
    # this API used to attack a node from another node 

    # the body of the request should be like this
    ## attacking_id : the id of the node that will attack
    ## target_id : the id of the node that will be attacked
    ## fraction: the attack continues until the number of troops in the attacking node is fraction of the number of troops in the target node or the attacking node has only one troop or the target node has no troops
    ## move_fraction: the fraction of troops of attacking_id that will move to the target node after a successful attack

    # check if the game is in the turn state
    if main_game.game_state != 2:
        return {'error': 'The game is not in the turn state'}

    # check if the game is in the attack state
    if main_game.state != 2:
        return {'error': 'The game is not in the attack state'}

    # check if the attacking_id is valid
    if attacking_id not in main_game.nodes.keys():
        return {'error': 'attacking_id is not valid'}

    # check if the attacking_id has a owner
    if main_game.nodes[attacking_id].owner is None:
        return {'error': 'attacking_id does not have any owner'}

    # check if the attacking_id is owned by the player
    if main_game.nodes[attacking_id].owner.id != player_id:
        return {'error': 'attacking_id is not owned by the player'}

    # check if the target_id is valid
    if target_id not in main_game.nodes.keys():
        return {'error': 'target_id is not valid'}

    # check if the target_id has a owner
    if main_game.nodes[target_id].owner is None:
        return {'error': 'target_id does not have any owner'}

    # check if the target_id is not owned by the player
    if main_game.nodes[target_id].owner.id == player_id:
        return {'error': 'target_id is owned by the player'}

    # check if move fraction is between 0 and 1
    if move_fraction <= 0 or move_fraction >= 1:
        return {'error': 'move_fraction should be between 0 and 1'}

    # check if the player has at least 2 troops in the attacking node
    if main_game.nodes[attacking_id].number_of_troops < 2:
        return {'error': 'attacking node does not have enough troops'}

    # check if the fraction is a positive number
    if fraction < 0:
        return {'error': 'fraction should be positive'}

    # check if the attacker_id and target_id are connected
    if main_game.nodes[attacking_id] not in main_game.nodes[target_id].adj_main_map:
        return {'error': 'attacking_id and target_id are not connected'}

    attacker_troops = main_game.nodes[attacking_id].number_of_troops  # number of troops in the attacking node
    target_troops = main_game.nodes[target_id].number_of_troops + main_game.nodes[
        target_id].number_of_fort_troops  # number of troops in the target node

    # save the number of fort troops in the target node    
    fort_troops = main_game.nodes[target_id].number_of_fort_troops
    normal_troops = main_game.nodes[target_id].number_of_troops

    while attacker_troops > 1 and target_troops > 0 and attacker_troops / target_troops > fraction:
        if attacker_troops > 3:
            attacker_dice = 3
        else:
            attacker_dice = attacker_troops - 1

        if target_troops >= 2:
            target_dice = 2
        else:
            target_dice = target_troops

        attacker_dice_list = []
        target_dice_list = []

        for _ in range(attacker_dice):
            attacker_dice_list.append(random.randint(1, 6))
        for _ in range(target_dice):
            target_dice_list.append(random.randint(1, 6))

        attacker_dice_list.sort(reverse=True)
        target_dice_list.sort(reverse=True)
        if main_game.config['debug_dice']:
            main_game.print(f'attacker troops: {attacker_troops} target troops: {target_troops}')
            main_game.print(f"attacker dice: {attacker_dice_list}" + f" target dice: {target_dice_list}")

        for i in range(min(attacker_dice, target_dice)):
            if attacker_dice_list[i] > target_dice_list[i]:
                target_troops -= 1
            else:
                attacker_troops -= 1
        if main_game.config['debug_dice']:
            main_game.print(f"new attacker troops: {attacker_troops}" + f" new target troops: {target_troops}")
            main_game.print(f'_________________________________________________________')

    # check if the attacker won
    if target_troops <= 0:
        move_troops = int(attacker_troops * move_fraction)
        if move_troops == 0:
            move_troops = 1

        while attacker_troops - move_troops < 1:
            move_troops -= 1
        if move_troops <= 0 or (attacker_troops - move_troops) < 1:
            return {'error': 'its not possible that this error happens but Im just checking'}

        main_game.nodes[attacking_id].number_of_troops = attacker_troops - move_troops
        main_game.nodes[target_id].number_of_troops = move_troops
        main_game.nodes[target_id].number_of_fort_troops = 0

        main_game.remove_node_from_player(target_id, main_game.nodes[target_id].owner.id)
        main_game.add_node_to_player(target_id, player_id)
        if main_game.has_won_troop == False:
            main_game.player_turn.number_of_troops_to_place += main_game.config[
                'number_of_troops_after_successful_attack']
            main_game.has_won_troop = True

    else:
        if fort_troops > 0:
            if target_troops <= normal_troops:
                main_game.nodes[target_id].number_of_fort_troops = 0
                main_game.nodes[target_id].number_of_troops = target_troops
            else:
                main_game.nodes[target_id].number_of_fort_troops = target_troops - normal_troops
        else:
            main_game.nodes[target_id].number_of_troops = target_troops

        main_game.nodes[attacking_id].number_of_troops = attacker_troops

    log = {
        "attacker": attacking_id,
        "target": target_id,
        "new_troop_count_attacker": main_game.nodes[attacking_id].number_of_troops,
        "new_troop_count_target": main_game.nodes[target_id].number_of_troops,
        "new_target_owner": main_game.nodes[target_id].owner.id,
        "new_fort_troop": main_game.nodes[target_id].number_of_fort_troops
    }
    main_game.log_attack.append(log)
    if main_game.debug:
        main_game.print(
            f"player {player_id} attacked node {target_id} from node {attacking_id} with fraction {fraction}. successful: {target_troops <= 0}")

    if target_troops <= 0:
        return {'message': 'attack successful', 'won': 1}
    else:
        return {'message': 'attack successful', 'won': 0}

src/blueprints/attack.py
VahidGhafourian/AICup2023-No-Flask
def fort(node_id: int, troop_count: int, main_game, player_id):
    # this API used to apply the fortification ability of the player

    # the body of the request should be like this
    ## node_id : the id of the node that will be fortified
    ## troop_count : the number of troops that will be fortified

    # check if the ClientGame is in the turn state
    if main_game.game_state != 2:
        return {'error': 'The game is not in the turn state'}

    # check if the turn is in the fort state
    if main_game.state != 4:
        return {'error': 'The game is not in the fort state'}

    # check if the node_id is valid
    if node_id not in main_game.nodes.keys():
        return {'error': 'node_id is not valid'}

    # check the ownership status of the node
    # check if the node has an owner
    if main_game.nodes[node_id].owner is None:
        return {'error': 'This node has no owner'}

    # check if the node is owned by the player
    if main_game.nodes[node_id].owner.id != player_id:
        return {'error': 'This node is already owned by another player'}

    # check if the troop_count is valid
    if troop_count >= main_game.nodes[node_id].number_of_troops:
        return {'error': 'there is not enough troops in the node'}

    # check if the player hasn't used the fortification ability in the game
    if main_game.player_turn.use_fort:
        return {'error': 'you have already used the fortification ability in the game'}

    # start the fortification ability
    main_game.player_turn.use_fort = True

    # fortify the node
    main_game.nodes[node_id].number_of_troops -= troop_count
    main_game.nodes[node_id].number_of_fort_troops += main_game.config['fort_coef'] * troop_count

    if main_game.debug:
        main_game.print(f"player {player_id} fortified node {node_id} with {troop_count} troops")

    return {'success': 'the fortification ability is applied successfully'}

src/blueprints/fort.py
VahidGhafourian/AICup2023-No-Flask
def get_adj(main_game):
    # this API used to the list of the adjacent nodes of each node
    output_dict = {}
    for node in main_game.nodes.values():
        output_dict[str(node.id)] = [i.id for i in node.adj_main_map]
    return output_dict

src/blueprints/get_adj.py
VahidGhafourian/AICup2023-No-Flask
def get_number_of_fort_troops(main_game):
    # this API used to get the number of fort troops on each node
    output_dict = {}
    for node in main_game.nodes.values():
        output_dict[str(node.id)] = node.number_of_fort_troops
    return output_dict

src/blueprints/get_number_of_fort_troops.py
VahidGhafourian/AICup2023-No-Flask
def get_number_of_troops_to_put(main_game):
    # return the number of troops that the player can put on the map
    output_dict = {"number_of_troops": main_game.player_turn.number_of_troops_to_place}
    return output_dict

src/blueprints/get_number_of_troops_to_put.py
VahidGhafourian/AICup2023-No-Flask
def get_owners(main_game):
    output_dict = {}
    for node in main_game.nodes.values():
        if node.owner != None:
            output_dict[str(node.id)] = node.owner.id
        else:
            output_dict[str(node.id)] = -1
    return output_dict

src/blueprints/get_owners.py
VahidGhafourian/AICup2023-No-Flask
def get_player_id(main_game):
    output_dict = {'player_id': int(main_game.player_turn.id)}
    return output_dict

src/blueprints/get_player_id.py
VahidGhafourian/AICup2023-No-Flask
from src.tools.find_reachable import find_reachable


def get_reachable(node_id: int, main_game):
    # this API used to find all the nodes that the owner can move it's troops from node_id to them    
    # body of the request should be like this:
    ## node_id: the id of the node that the player wants to move his troops from it

    # check if the node_id is valid
    if node_id not in main_game.nodes.keys():
        return {'error': 'node_id is not valid'}

    output_dict = {"reachable": find_reachable(node_id, main_game)}

    return output_dict

src/blueprints/get_reachable.py
VahidGhafourian/AICup2023-No-Flask
def get_state(main_game):
    output_dict = {'state': main_game.state}
    return output_dict

src/blueprints/get_state.py
VahidGhafourian/AICup2023-No-Flask
def get_strategic_nodes(main_game):
    output_dict = {'strategic_nodes': [i.id for i in main_game.nodes.values() if i.is_strategic],
                   'score': [i.score_of_strategic for i in main_game.nodes.values() if i.is_strategic]}
    return output_dict

src/blueprints/get_strategic_nodes.py
VahidGhafourian/AICup2023-No-Flask
def get_troops_count(main_game):
    output_dict = {}
    for node in main_game.nodes.values():
        output_dict[str(node.id)] = node.number_of_troops
    return output_dict

src/blueprints/get_troops_count.py
VahidGhafourian/AICup2023-No-Flask
def get_turn_number(main_game):
    output_dict = {'turn_number': main_game.turn_number}
    return output_dict

src/blueprints/get_turn_number.py
VahidGhafourian/AICup2023-No-Flask
def index():
    # this API used to check if the server is running
    return {"message": "Welcome, server is running"}

src/blueprints/index.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi 
# Date: 2023/8/16

# Description: This file defines a blueprint for the login API 
# each player should send a request to this API to get a token and player_id 
# it also gets a port number to run a server
# player also should send a token/password to this API so server is going to use it to authenticate that the request comes from server 

# initialize the player_id
player_id = 0


def login(main_game, name, initializer, turn):
    global player_id
    print(player_id)
    # make sure there is no more than number_of_players players
    if player_id >= main_game.config['number_of_players']:
        output_dict = {'error': 'game players is full'}
        return output_dict

    # create the output dictionary
    output_dict = {'player_id': player_id,
                   'message': 'login successful'}

    # initialize the player
    main_game.add_player(player_id, name, initializer, turn)
    main_game.players[player_id].number_of_troops_to_place = main_game.config['initial_troop']
    player_id += 1
    return output_dict

def reset_global_player_id():
    global player_id
    player_id = 0
src/blueprints/login.py
VahidGhafourian/AICup2023-No-Flask
from src.tools.find_path import find_path


def move_troop(source: int, destination: int, troop_count: int, main_game, player_id):
    # this API used to move troops from source to destination

    # the body of the request should be like this
    ## source: the source node id
    ## destination: the destination node id
    ## troop_count: the number of troops to move

    # check if the move troop happened in the current turn
    if main_game.move_troop_done:
        return {'error': 'move troop already happened in the current turn'}

    # check if the game is in the turn state
    if main_game.game_state != 2:
        return {'error': 'The game is not in the turn state'}

    # check if the game is in the move troop state
    if main_game.state != 3:
        return {'error': 'The game is not in the move troop state'}

    # check if the source is valid
    if source not in main_game.nodes.keys():
        return {'error': 'source is not valid'}

    # check if the source has a owner
    if main_game.nodes[source].owner == None:
        return {'error': 'source does not have any owner'}

    # check if the source is owned by the player
    if main_game.nodes[source].owner.id != player_id:
        return {'error': 'source is not owned by the player'}

    # check if the destination is valid
    if destination not in main_game.nodes.keys():
        return {'error': 'destination is not valid'}

    # check if the destination has a owner
    if main_game.nodes[destination].owner == None:
        return {'error': 'destination does not have any owner'}

    if main_game.nodes[destination].owner.id != player_id:
        return {'error': 'destination is not owned by the player'}

    # check if the player has at least 2 troops in the source node
    if main_game.nodes[source].number_of_troops <= troop_count:
        return {'error': 'source node does not have enough troops'}

    # check if there is a path between source and destination
    res, path = find_path(source, destination, main_game, player_id)
    if not res:
        return {'error': 'there is no path between source and destination'}

    # check if the number of troops is positive
    if troop_count <= 0:
        return {'error': 'troop_count should be positive'}

    # check if the source and destination isn't same
    if source == destination:
        return {'error': 'source and destination should be different'}

    main_game.nodes[source].number_of_troops -= troop_count
    main_game.nodes[destination].number_of_troops += troop_count

    main_game.move_troop_done = True

    main_game.log_fortify = {"number_of_troops": troop_count,
                             "path": path}

    if main_game.debug:
        main_game.print("player " + str(player_id) + " moved " + str(troop_count) + " troops from node " + str(
            source) + " to node " + str(destination))

    return {'message': 'troops moved successfully'}

src/blueprints/move_troop.py
VahidGhafourian/AICup2023-No-Flask
def next_state(main_game):
    ''' 
    This function is used to change the state of the game to the next state 
    1: put troop state
    2: attack state
    3: move troop state
    4: fortification state
    '''
    if main_game.game_state != 2:
        output_dict = {'error': 'The game is not in the turn state'}
        return output_dict

    if main_game.state >= 4:
        output_dict = {'error': 'you already finished the turn'}
        return output_dict

    main_game.state += 1
    if main_game.debug:
        main_game.print("******* state changed to: " + str(main_game.state) + " *******")

    output_dict = {'game_state': main_game.state, 'message': 'success'}
    return output_dict

src/blueprints/next_state.py
VahidGhafourian/AICup2023-No-Flask
def put_one_troop(node_id: int, main_game, player_id):
    # this API is used to put one troop on the map in the initial troop state of the game

    # body of the request should be like this:
    ## node_id: the id of the node that the player wants to put the troop on it

    # check if the player just put one Troop in a init turn
    if main_game.state != 1:
        return {'error': 'You can not put more than one troop in a turn'}
    # check if the game is in the initial troop putting state
    if main_game.game_state != 1:
        return {'error': 'The game is not in the initial troop putting state'}

    # check if the player has enough troops to put
    if main_game.player_turn.number_of_troops_to_place <= 0:
        return {'error': 'You have no more initial troops to put'}

    # check if the node_id is valid
    if node_id not in main_game.nodes.keys():
        return {'error': 'node_id is not valid'}

    # check the ownership status of the node
    if main_game.nodes[node_id].owner is None:
        # if the node is not owned by any player, add it to the player
        main_game.add_node_to_player(node_id, player_id)

    elif main_game.nodes[node_id].owner.id != player_id:
        return {'error': 'This node is already owned by another player'}

    # add one troop to the node and subtract one from the player
    main_game.nodes[node_id].number_of_troops += 1
    main_game.player_turn.number_of_troops_to_place -= 1

    # add the node id and player id to the log variable of the game
    main_game.log_initialize.append([player_id, node_id])

    # change the state to 2 so player just can put one troop in a turn
    main_game.state = 4
    if main_game.debug:
        main_game.print(f"player {player_id} put one troop on node {node_id}")

    return {'message': 'troop added successfully'}

src/blueprints/put_one_troop.py
VahidGhafourian/AICup2023-No-Flask
def put_troop(node_id: int, number_of_troops: int, main_game, player_id):
    # this API used to put troops in the map in the put troop state

    # body of the request should be like this:
    ## node_id: the id of the node that the player wants to put the troop on it
    ## number_of_troops: the number of troops that the player wants to put on the node

    # check if the game is in the turn state
    if main_game.game_state != 2:
        return {'error': 'The game is not in the turn state'}

    # check if the turn in the put troop state
    if main_game.state != 1:
        return {'error': 'The game is not in the troop putting state'}

    # check if the node_id is valid
    if node_id not in main_game.nodes.keys():
        return {'error': 'node_id is not valid'}

    # check if the player has enough troops to place
    if main_game.player_turn.number_of_troops_to_place < number_of_troops:
        return {'error': 'You do not have enough troops to place'}

    # check if the node is not owned by anyone
    if main_game.nodes[node_id].owner is None:
        main_game.add_node_to_player(node_id, player_id)

    # check if the node is not owned by another player
    elif main_game.nodes[node_id].owner.id != player_id:
        return {'error': 'This node is already owned by another player'}

    # check if the number_of_troops is positive
    if number_of_troops <= 0:
        return {'error': 'number_of_troops should be positive'}

    # add one troop to the node and subtract one from the player
    main_game.nodes[node_id].number_of_troops += number_of_troops
    main_game.player_turn.number_of_troops_to_place -= number_of_troops

    # add the node id and player id to the log variable of the game
    main_game.log_put_troop.append([node_id, number_of_troops])

    if main_game.debug:
        main_game.print(
            "player " + str(player_id) + " put " + str(number_of_troops) + " troops on node " + str(node_id))

    return {'message': 'troop added successfully'}

src/blueprints/put_troop.py
VahidGhafourian/AICup2023-No-Flask
"""
in this API client shows that it's ready to start the game 
that means it has a server on the port that it said in the login API
"""


def ready(main_game, player_id):
    main_game.players[player_id].is_ready = True
    output_dict = {"message": "every thing is ok, you should wait for other players to be ready"}
    main_game.check_all_players_ready()
    return output_dict

src/blueprints/ready.py
VahidGhafourian/AICup2023-No-Flask
import requests
from src.blueprints import BluePrints


class ClientGame:
    def __init__(self, main_game) -> None:
        self.main_game = main_game
        self.blueprints = BluePrints

    def output_handler(self, output):
        """
            handles the output of the server
        """
        if 'error' in output:
            raise Exception("player_id: " + str(self.get_player_id()['player_id']) + " error: " + output['error'])
        return output
    
    def __check_int(self, n):
        return n if type(n) == int else int(n)

    def __check_float(self, n):
        return n if type(n) == float else float(n)

    def get_owners(self):
        """
            returns a dictionary of node_id: owner_id
            node_id: str
            owner_id: int
        """

        return self.output_handler(self.blueprints.get_owners(self.main_game))

    def get_number_of_troops(self):
        """
            returns a dictionary of node_id: number_of_troops
            node_id: str
            number_of_troops: int
        """
        return self.output_handler(self.blueprints.get_troops_count(self.main_game))

    def get_state(self):
        """
            returns a dictionary containing the state of the game
            1: put_troop
            2: attack
            3: move_troop
            4: fort
            {'state': number_of_state}
        """
        return self.output_handler(self.blueprints.get_state(self.main_game))

    def get_turn_number(self):
        """
            returns a dictionary containing the turn number
            {'turn_number': number_of_turn}
        """
        return self.output_handler(self.blueprints.get_turn_number(self.main_game))

    def get_adj(self):
        """
            return the adjacent nodes of each node
            returns a dictionary of node_id: [adjacent_nodes]
            node_id: str
            adjacent_nodes: list of int
        """
        return self.blueprints.get_adj(self.main_game)

    def next_state(self):
        """
            changes the state of the turn to the next state
        """
        return self.output_handler(self.blueprints.next_state(self.main_game))

    def put_one_troop(self, node_id):
        """
            puts one troop in the node with the given id
            this function can only be used in the put_troop state in the initialize function
        """
        node_id = self.__check_int(node_id)
        return self.output_handler(self.blueprints.put_one_troop(node_id, self.main_game, self.get_player_id()['player_id']))

    def put_troop(self, node_id, num):
        """
            puts num troops in the node with the given id
            this function can only be used in the put_troop state in the turn function
        """
        node_id = self.__check_int(node_id)
        num = self.__check_int(num)
        return self.output_handler(self.blueprints.put_troop(node_id, num, self.main_game, self.get_player_id()['player_id']))

    def get_player_id(self):
        """
            returns the id of the player
        """
        return self.output_handler(self.blueprints.get_player_id(self.main_game))

    def attack(self, attacking_id, target_id, fraction, move_fraction):
        """
            attacks the target node with the given fraction of troops
        """
        attacking_id = self.__check_int(attacking_id)
        target_id = self.__check_int(target_id)
        fraction = self.__check_float(fraction)
        move_fraction = self.__check_float(move_fraction)
        return self.output_handler(self.blueprints.attack(attacking_id, target_id, fraction, move_fraction, self.main_game,
                                      self.get_player_id()['player_id']))

    def move_troop(self, source, destination, troop_count):
        """
            moves the given number of troops from the source node to the destination node
        """
        source = self.__check_int(source)
        destination = self.__check_int(destination)
        troop_count = self.__check_int(troop_count)
        return self.output_handler(self.blueprints.move_troop(source, destination, troop_count, self.main_game,
                                          self.get_player_id()['player_id']))

    def get_strategic_nodes(self):
        """
            returns a list of strategic nodes and their score
            {"strategic_nodes": [node_id, ...], "score": [score, ...]}
        """
        return self.output_handler(self.blueprints.get_strategic_nodes(self.main_game))

    def get_number_of_troops_to_put(self):
        """
            returns the number of troops that the player can put in the put_troop state
            {"number_of_troops": number_of_troops}
        """
        return self.output_handler(self.blueprints.get_number_of_troops_to_put(self.main_game))

    def get_reachable(self, node_id):
        """
            returns a dictionary of "reachable" key and a list of reachable nodes
            {"reachable": [node_id, ...]}
        """
        node_id = self.__check_int(node_id)
        return self.output_handler(self.blueprints.get_reachable(node_id, self.main_game))

    def get_number_of_fort_troops(self):
        """
            returns the number of troops that used to defend the node
            {node_id: number_of_troops, ...}
            node_id: str
            number_of_troops: int
        """
        return self.output_handler(self.blueprints.get_number_of_fort_troops(self.main_game))

    def fort(self, node_id, troop_count):
        """
            fortifies the node with the given number of troops
        """
        node_id = self.__check_int(node_id)
        troop_count = self.__check_int(troop_count)
        return self.output_handler(self.blueprints.fort(node_id, troop_count, self.main_game, self.get_player_id()['player_id']))

src/components/client_game.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi

# this is the main component of the game 
# it includes all the players and nodes of the game and all the information about the game

# turn state:
## 1: add troops
## 2: attack
## 3: move troops

# game state:
## 1: still need to initialize the troops (initial state)
## 2: the game started (turns state)


from src.components.node import Node
from src.components.player import Player
from src.turn_controllers.change_turn import change_turn
import json
from src.tools.calculate_number_of_troops import calculate_number_of_troops


class Game:
    def __init__(self) -> None:
        self.debug_logs = ""
        self.debug = False
        self.players = {} # player_id: player object, includes all players in the game

        self.nodes = {} # node_id: node object, includes all nodes of the map

        self.turn_number = 0 # each turn is a round for a player to play the game (it starts from 1)
        self.state = 1 # that could be 'add troops': 1, 'attack': 2, 'move troops': 3
        self.player_turn = None # Player object: the player who is playing this turn
        self.game_started = False # True if the game already started
        self.game_state = 1 # 1: still need to initialize the troops, 2: the game started
        self.config = None # the config dictionary
        self.finish_func = None # the function that will be called when the game is finished

        # the following variables are used to for log file
        self.log_initialize = [] # the log of the initialize phase 
        self.log_node_owner = [] # the log of the node owner at the beginning of each turn
        self.log_troop_count = [] # the log of the number of troops at the beginning of each turn
        self.log_put_troop = [] # the log of the number of troops that are put on the map at each turn
        self.log_attack = [] # the log of the attacks at each turn
        self.log_fortify = {} # the log of the move_troop at each turn
        self.log_fort = [] # the log of the fortify at each turn
        self.has_won_troop = False
        self.move_troop_done = False # a boolean that shows if the move_troop is done or not in the current turn
        # the main log file that will saved at the end of the game
        self.log = {"initialize": self.log_initialize, "turns": {}} 

    def update_game_state(self) -> None:
        # update the game state
        # this update will happen at the beginning of each turn

        # check if the the game is already in the turns state
        if self.game_state == 2:
            return
        
        # check if the players had enough turns to put all their initial troops
        if self.turn_number > int(self.config["number_of_players"]) * int(self.config["initial_troop"]):
            self.game_state = 2

    def add_player(self, player_id: int, name: str, initializer, turn) -> None:
        # add a player to the game if it doesn't exist
        # this function will generate a new player object and add it to the players dictionary
        if player_id not in self.players:
            self.players[player_id] = Player(player_id, name, initializer, turn)

    def read_map(self, map_file: str) -> None:
        # read the map from the json file and create the nodes and initialize them  

        # open the json file and load it into a dictionary
        with open(map_file,'r') as json_file:   
            json_py=json.load(json_file)        

        # create the nodes and add them to the nodes dictionary
        for id in range(json_py["number_of_nodes"]):
            node=Node(id)        
            self.nodes[id] = node

        # add edges to the nodes
        for edge in json_py["list_of_edges"]:  
            self.nodes[edge[0]].adj_main_map.append(self.nodes[edge[1]])
            self.nodes[edge[1]].adj_main_map.append(self.nodes[edge[0]])
        
        # add the strategic nodes to the nodes
        for id in json_py["strategic_nodes"]:
            self.nodes[id].is_strategic = True
        
        for i in range(len(json_py["scores_of_strategic_nodes"])):
            id = json_py["strategic_nodes"][i]
            score = json_py["scores_of_strategic_nodes"][i]
            self.nodes[id].score_of_strategic = score

    def check_all_players_ready(self) -> None:
        # this function will check if all players are ready to start the game
        # this function will be called after each player sends a ready request
        # this function also will start the game with a new thread if all players are ready

        # check if the game started before or not
        if self.game_started == True:
            return
        
        # check if all players were logged in
        if len(self.players) != self.config['number_of_players']:
            return
         
         # check if all players are ready
        for player in self.players.values():
            if not player.is_ready:
                return

        self.game_started = True

    def start_turn(self):
        # this function will be called at the beginning of each turn
        # it will initialize the turn variables in the game object

        # increase the turn number
        self.turn_number += 1

        # reset the move_troop_done variable
        self.move_troop_done = False

        # calculate the player who should play this turn
        player_id = self.turn_number % len(self.players)

        # check if the game needs to change state from initialize to turns
        self.update_game_state()

        # initialize the turn state to put troops for the player who should play this turn
        self.state = 1
        # initialize the player who should play this turn
        self.player_turn = self.players[player_id]
        self.has_won_troop = False
        # check if the game is in the turns state
        if self.game_state == 2:
            # calculate the number of troops that the player get at the beginning of this turn
            self.player_turn.number_of_troops_to_place += calculate_number_of_troops(player_id, self)
            # initialize the log variables
            ## the log of the node owner at the beginning of each turn
            self.log_node_owner = [i.owner.id if i.owner is not None else -1 for i in self.nodes.values()]
            ## the log of the number of troops at the beginning of each turn
            self.log_troop_count = [i.number_of_troops for i in self.nodes.values()]
            ## clear the log of the number of troops that are put on the map at each turn
            self.log_put_troop = []
            ## clear the log of the attacks at each turn
            self.log_attack = []
            ## clear the log of the move_troop at each turn
            self.log_fortify = {}

        return player_id

    def end_turn(self):
        # this function will be called at the end of each turn
        # it will save all the log variables in the main log variable

        # check if the game is in the turns state
        if self.game_state == 2:
            self.log['turns']['turn'+str(self.turn_number)] = {
                "nodes_owner": self.log_node_owner,
                "troop_count": self.log_troop_count,
                "add_troop": self.log_put_troop,
                "attack": self.log_attack,
                "fortify": self.log_fortify,
                "fort": [i.number_of_fort_troops for i in self.nodes.values()]
            }

    def print(self, text):
        # this function will print the text in the a log
        self.debug_logs += text + "\n"
    
    def add_node_to_player(self, node_id, player_id):
        self.players[player_id].nodes.append(self.nodes[node_id])
        self.nodes[node_id].owner = self.players[player_id]

    def remove_node_from_player(self, node_id, player_id):
        self.players[player_id].nodes.remove(self.nodes[node_id])
        self.nodes[node_id].owner = None
src/components/game.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi

# this is the most basic component of the Map
# each region is a node, and each node has a list of adjacent nodes in the main map
# each node has an owner, which is a player object.


class Node:
    def __init__(self, id) -> None:
        self.id = id # each node has an id that is unique in the game
        self.owner = None # Player object that owns this node
        self.number_of_troops = 0 # number of troops that are placed on this node
        self.number_of_fort_troops = 0 # number of fort troops that are placed on this node
        self.adj_main_map = [] # list of Node objects that are adjacent to this node in the main map
        self.is_strategic = False # if this node is strategic or not
        self.score_of_strategic = 0 # the score of this node if it's strategic
src/components/node.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi

# this is one of the most basic components of the Risk game.

class Player:
    def __init__(self, id, name, initializer, turn) -> None:
        self.nodes = []  # list of Node objects that owned by this player
        self.id = id  # each player has an id that is unique in the game
        self.name = name  # each player has a name
        self.number_of_troops_to_place = 0  # number of troops that the player have but not placed on the map
        self.is_ready = False  # a boolean that shows if the player is ready to get requests and play the game or not
        self.use_fort = False  # a boolean that shows if the player used fortify or not
        self.turn = turn  # turn function
        self.initializer = initializer  # initializer function

src/components/player.py
VahidGhafourian/AICup2023-No-Flask
def calculate_number_of_troops(player_id, main_game):
    # calculate the number of troops to give a player at the beginning of a turn
    number_of_nodes = len(main_game.player_turn.nodes)
    score_of_strategic_nodes = sum([i.score_of_strategic for i in main_game.player_turn.nodes if i.is_strategic])
    return max(number_of_nodes // 4 + score_of_strategic_nodes, main_game.config['minimum_troops_per_turn'])

src/tools/calculate_number_of_troops.py
VahidGhafourian/AICup2023-No-Flask
mark = []

def DFS(u, v, main_game, player_id):
    global mark
    mark[u] = 1
    if u == v:
        return True, [v]
    path = []
    for node in main_game.nodes[u].adj_main_map:
        if mark[node.id] == 0 and node.owner != None and node.owner.id == player_id:
            res, path = DFS(node.id, v, main_game, player_id)
            if res:
                path = [u] + path
                return True, path
    return False, []
                


def find_path(u, v, main_game, player_id):
    global mark
    # find a path from node u to node v that all the nodes in the path are owned by the player
    # return the path as a list of nodes
    # if there is no path return None
    mark = [0 for i in range(len(main_game.nodes))]
    return DFS(u, v, main_game, player_id)
    

    


src/tools/find_path.py
VahidGhafourian/AICup2023-No-Flask
ans = []
mark = []

def DFS(node_id, main_game, player_id):
    global ans
    global mark
    mark[node_id] = 1
    ans.append(node_id)
    for node in main_game.nodes[node_id].adj_main_map:
        if mark[node.id] == 0 and node.owner != None and node.owner.id == player_id:
            DFS(node.id, main_game, player_id)


def find_reachable(node_id, main_game):
    global ans
    global mark

    mark = [0 for i in range(len(main_game.nodes))]
    ans.clear()
    if main_game.nodes[node_id].owner == None:
        return ans
    DFS(node_id, main_game, main_game.nodes[node_id].owner.id)
    return ans 
src/tools/find_reachable.py
VahidGhafourian/AICup2023-No-Flask
'''
read config file and return a dictionary
'''

import json

def read_config():
    with open('config.json', 'r') as f:
        config = json.load(f)
    return config
src/tools/read_config.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi
# Date: 2023/8/16

# Description: This file is used to manage the turns of the game
# it will be called after all the players requested for ready


import time
from src.turn_controllers.check_finish import check_finish
import datetime


def change_turn(main_game, client_game):
    while True:
        # increase the turn number and initialize the turn
        player_id = main_game.start_turn()

        # add the turn number to the logs 
        if main_game.debug:
            main_game.print("----------------------------- start turn: " + str(
                main_game.turn_number) + "----------------------------")
            main_game.print(
                "player: " + str(player_id) + ' -- start time ' + datetime.datetime.now().strftime("%H:%M:%S"))
            # print the owner and number of troops of each node at the beginning of the turn
            for i in main_game.nodes.values():
                main_game.print(
                    f"node {i.id}: owner: {i.owner.id if i.owner is not None else -1}, number of troops: {i.number_of_troops} , number of fort troops: {i.number_of_fort_troops}")

        # show number of troops that the player did not put on the map
        if main_game.debug:
            main_game.print(
                f"player {player_id} has {main_game.player_turn.number_of_troops_to_place} troops to put on the map")

        print("Turn Number:", main_game.turn_number, ' =' * 20)
        # wait for the player to play
        if main_game.game_state == 2:
            try:
                main_game.players[player_id].turn(client_game)
            except Exception as e:
                raise Exception('Wrong player id:' + str(player_id))
        elif main_game.game_state == 1:
            try:
                main_game.players[player_id].initializer(client_game)
            except Exception as e:
                raise Exception('Wrong player id:' + str(player_id))

        # end the turn to add the logs for client
        main_game.end_turn()

        if main_game.debug:
            main_game.print("end turn: " + datetime.datetime.now().strftime("%H:%M:%S"))
        # check if the game is finished
        if check_finish(main_game):
            break

src/turn_controllers/change_turn.py
VahidGhafourian/AICup2023-No-Flask
# author: Mohamad Mahdi Reisi
# Date: 2023/8/16

import json
import datetime
import os

def calculate_score(main_game):
    # (number of nodes * 1000) + (number of troops)+ (3000/score of strategic nodes)
    scores = []
    for i in main_game.players.values():
        number_of_nodes = len(i.nodes)
        number_of_troops = 0
        strategic_score = 0
        for j in i.nodes:
            number_of_troops += j.number_of_troops
            if j.is_strategic:
                strategic_score += 3000//j.score_of_strategic
    
        scores.append((number_of_nodes * 1000) + number_of_troops + strategic_score)
    return scores 

def check_finish(main_game) -> bool:

    # check if the game pass the minimum number of turns
    if main_game.turn_number < int(main_game.config["minimum_number_of_turns"]):
        return False

    # find the number of strategic nodes for each player    
    players_strategic_nodes_count = []
    for player in main_game.players.values():
        number_of_strategic_nodes = 0
        for node in player.nodes:
            if node.is_strategic:
                number_of_strategic_nodes += 1
        players_strategic_nodes_count.append(number_of_strategic_nodes)
    
    # check if there is a player with enough strategic nodes to win    
    for i in range(len(players_strategic_nodes_count)):
        if players_strategic_nodes_count[i] >= int(main_game.config["number_of_strategic_nodes_to_win"]):
            if main_game.debug:
                main_game.print("player won because of having enough strategic nodes")
            scores = calculate_score(main_game)
            scores[i] += sum(scores)
            game_finished(main_game, scores)
            print("player", "{{", main_game.players[i].name, "}}",
                  "won because of having enough strategic nodes. scores:", scores)
            return True
    # check if the game is finished
    if main_game.turn_number >= int(main_game.config["number_of_turns"]):
        if main_game.debug:
            main_game.print("game finished because of number of turns")
        scores = calculate_score(main_game)
        print("game finished because of number of turns. and player",
              "{{", main_game.players[scores.index(max(scores))].name, "}}", "wins. scores: ",  scores)
        game_finished(main_game, scores)
        return True
    return False

def game_finished(main_game, score):
    # finish the game
    # make log folder if it does not exist
    if not os.path.exists("log"):
        os.makedirs("log")

    # add score the the log file 
    main_game.log["score"] = score
    
    # generate and save the main_game.log file into a json file in the log folder
    with open("log/" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".json", "w") as log_file:
        json.dump(main_game.log, log_file)
    
    # generate and save an export from main_game object and save in the result log folder 
    # generate export 
    export = dict()
    export['node_owners'] = [i.owner.id if i.owner != None else -1 for i in main_game.nodes.values()]
    export['troop_count'] = [i.number_of_troops for i in main_game.nodes.values()]
    export['turn_number'] = main_game.turn_number
    export['score'] = score
    # add the number of strategic nodes for each player
    for player in main_game.players.values():
        export['player'+str(player.id)+" strategic nodes"] = len([i for i in player.nodes if i.is_strategic]) 

    # make result_log folder if it does not exist
    if not os.path.exists("result_log"):
        os.makedirs("result_log")

    # save the export in the result log folder
    with open("result_log/" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".json", "w") as result_log_file: 
        json.dump(export, result_log_file)


    # write debug_logs in the text file in the debug_log folder
    if main_game.debug:
        # make debug_log folder if it does not exist
        if not os.path.exists("debug_log"):
            os.makedirs("debug_log")
        with open("debug_log/" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ".txt", "w") as debug_log_file:
            debug_log_file.write(main_game.debug_logs)

src/turn_controllers/check_finish.py
VahidGhafourian/AICup2023-No-Flask