import torch
import torch.nn as nn
import torch.nn.functional as F




class CNNActorCritic(nn.Module):
    def __init__(self, input_shape, num_actions):
        super(CNNActorCritic, self).__init__()

        # Initialize conv layer weight orthogonally with relu gain and bias 0
        init_ = lambda m: self.init(m , nn.init.orthogonal_, lambda x : nn.init.constant_(x, 0), nn.init.calculate_gain('relu'))

        self.conv1 = init_(nn.Conv2d(input_shape[0], 32, 8, stride=4))
        self.conv2 = init_(nn.Conv2d(32, 64, 4, stride=2))
        self.conv3 = init_(nn.Conv2d(64, 32, 3, stride=1))

        # extract image features
        self.fc1 = init_(nn.Linear(self.feature_size(input_shape), 512)) 
        
        # Initialize actor&critic differently
        init_critic = lambda m : self.init(m, nn.init.orthogonal_, lambda x:nn.init.constant_(x, 0))
        init_actor = lambda m : self.init(m, nn.init.orthogonal_, lambda x : nn.init.constant_(x, 0), gain=0.01)

        self.critic_value = init_critic(nn.Linear(512, 1))
        self.actor_features = init_actor(nn.Linear(512, num_actions))

        

        self.train()

    def forward(self, x):
        x = F.relu(self.conv1(x/255.0)) # Remember to Normalize the inputs
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = x.view(x.size(0), -1)  # (batch_size, feature_size)
        x = F.relu(self.fc1(x))

        value = self.critic_value(x)
        logits = self.actor_features(x)

        return value, logits


    def init(self, module, weight_init, bias_init, gain=1):
        weight_init(module.weight.data, gain=gain)
        bias_init(module.bias.data)
        return module    

    def feature_size(self, input_shape):
        return self.conv3(self.conv2(self.conv1(torch.zeros(1, *input_shape)))).view(1,-1).size(1)

        



import torch
import torch.nn as nn
import torch.nn.functional as F




class CNNActorCritic(nn.Module):
    def __init__(self, input_shape, num_actions):
        super(CNNActorCritic, self).__init__()

        # Initialize conv layer weight orthogonally with relu gain and bias 0
        init_ = lambda m: self.init(m , nn.init.orthogonal_, lambda x : nn.init.constant_(x, 0), nn.init.calculate_gain('relu'))

        self.conv1 = init_(nn.Conv2d(input_shape[0], 32, 8, stride=4))
        self.conv2 = init_(nn.Conv2d(32, 64, 4, stride=2))
        self.conv3 = init_(nn.Conv2d(64, 32, 3, stride=1))

        # extract image features
        self.fc1 = init_(nn.Linear(self.feature_size(input_shape), 512)) 
        
        # Initialize actor&critic differently
        init_critic = lambda m : self.init(m, nn.init.orthogonal_, lambda x:nn.init.constant_(x, 0))
        init_actor = lambda m : self.init(m, nn.init.orthogonal_, lambda x : nn.init.constant_(x, 0), gain=0.01)

        self.critic_value = init_critic(nn.Linear(512, 1))
        self.actor_features = init_actor(nn.Linear(512, num_actions))

        

        self.train()

    def forward(self, x):
        x = F.relu(self.conv1(x/255.0)) # Remember to Normalize the inputs
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = x.view(x.size(0), -1)  # (batch_size, feature_size)
        x = F.relu(self.fc1(x))

        value = self.critic_value(x)
        logits = self.actor_features(x)

        return value, logits


    def init(self, module, weight_init, bias_init, gain=1):
        weight_init(module.weight.data, gain=gain)
        bias_init(module.bias.data)
        return module    

    def feature_size(self, input_shape):
        return self.conv3(self.conv2(self.conv1(torch.zeros(1, *input_shape)))).view(1,-1).size(1)

        



# Initialize conv layer weight orthogonally with relu gain and bias 0 # extract image features # Initialize actor&critic differently # Remember to Normalize the inputs # (batch_size, feature_size)