|
@@ -0,0 +1,226 @@
|
|
|
+#!/usr/local/bin/python3
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+
|
|
|
+import torch
|
|
|
+import torch.nn as nn
|
|
|
+import torch.nn.functional as F
|
|
|
+import torch.utils.data as tudata
|
|
|
+import numpy as np
|
|
|
+import sys
|
|
|
+import json
|
|
|
+import os
|
|
|
+
|
|
|
+#将数据输出到一个json文件,便于在tiled进行观察,可以是单层或者多层的,多层的话,层级按照从小到大排列(层级是布局层级,不是视觉层级)
|
|
|
+def output_to_json(slices, suffix="ex"):
|
|
|
+ with open("/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/templates/tm_0000.json", 'r') as f:
|
|
|
+ json_temp = json.load(f)
|
|
|
+ for i in range(0, len(slices)):
|
|
|
+ with open("/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/slices/slice_%d_%s.json" % (i, suffix), "w") as out_f:
|
|
|
+ json_temp["layers"][0]["data"] = slices[i]
|
|
|
+ json.dump(json_temp, out_f)
|
|
|
+
|
|
|
+# 解析一个关卡文件,得到各层的数据,是按照视觉层次划分的
|
|
|
+def get_each_view_slice(jsonLv):
|
|
|
+ jsonData = ""
|
|
|
+ tilesByPos = {}
|
|
|
+ maxZ = 0
|
|
|
+ # 处理各层的tile数据
|
|
|
+ with open(jsonLv, 'r') as f:#, encoding='utf-8'
|
|
|
+ jsonData = json.load(f)
|
|
|
+ # 得到宽高信息
|
|
|
+ w = int(jsonData['width'])
|
|
|
+ h = int(jsonData['height'])
|
|
|
+ for item in jsonData['layers']:
|
|
|
+ name = item['name']
|
|
|
+ if not name.startswith('Tile_'):
|
|
|
+ # stacked
|
|
|
+ continue
|
|
|
+ z = int(name[5:])
|
|
|
+ data = item['data']
|
|
|
+ for i in range(0, len(data)):
|
|
|
+ if data[i] == 0:
|
|
|
+ continue
|
|
|
+ tile_x = (int)(i % w)
|
|
|
+ tile_y = (int)(i / h)
|
|
|
+ tile_z = z
|
|
|
+ # 一些统计信息
|
|
|
+ tilesByPos[(tile_x, tile_y, tile_z)] = [1,0] # [tile类型,zView--视觉层级,默认是0]
|
|
|
+ if tile_z > maxZ:
|
|
|
+ maxZ = tile_z
|
|
|
+ # 根据上面的布局信息,计算每个tile的几个信息:
|
|
|
+ # 1. 每个tile的视觉层级(不同于上面z的信息,那是一个布局信息,相同的z可能出在不同的视觉层级)
|
|
|
+ # 从maxZ开始,逐层向下计算;对于每一个位置,如果该位置有tile,则计算该tile的视觉层级
|
|
|
+ # 对于每一个tile,如果其上方(从该tile的z+1层,一直到maxZ层)有tile,则其视觉层级为上方tile的视觉层级+1
|
|
|
+ slice_by_view_z = [[0]*w*h for i in range(maxZ+1)]
|
|
|
+ for z in range(0, maxZ+1):
|
|
|
+ z = maxZ - z
|
|
|
+ for x in range(0, w+1):
|
|
|
+ for y in range(0, h+1):
|
|
|
+ if (x,y,z) in tilesByPos:
|
|
|
+ tile = tilesByPos[(x, y, z)]
|
|
|
+ adjs = [(x,y),(x,y+1),(x,y-1),(x+1,y),(x+1,y+1),(x+1,y-1),(x-1,y),(x-1,y-1),(x-1,y+1)]
|
|
|
+ # 确定视觉层级
|
|
|
+ zvMax = -1
|
|
|
+ for zup in range(z+1, maxZ+1):
|
|
|
+ for adj in adjs:
|
|
|
+ if (adj[0],adj[1],zup) in tilesByPos:
|
|
|
+ zv = tilesByPos[adj[0],adj[1],zup][1]
|
|
|
+ if zv > zvMax:
|
|
|
+ zvMax = zv
|
|
|
+ zv = zvMax + 1
|
|
|
+ tilesByPos[(x, y, z)][1] = zv
|
|
|
+ # 记录到slice_by_view_z中
|
|
|
+ slice_by_view_z[zv][y*w+x] = tilesByPos[(x, y, z)][0]
|
|
|
+ return slice_by_view_z
|
|
|
+
|
|
|
+# 测试获取视觉层级的数据是否ok
|
|
|
+def test_get_each_view_slice():
|
|
|
+ jsonLv = "/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/templates/tm_0016.json"
|
|
|
+ slice_by_view_z = get_each_view_slice(jsonLv)
|
|
|
+ output_to_json(slice_by_view_z)
|
|
|
+
|
|
|
+##########################################
|
|
|
+# PyTorch RNN
|
|
|
+class TileMatchRNN(nn.Module):
|
|
|
+ def __init__(self, input_size, hidden_size, num_layers, output_size):
|
|
|
+ super(TileMatchRNN, self).__init__()
|
|
|
+ self.hidden_size = hidden_size
|
|
|
+ self.num_layers = num_layers
|
|
|
+ self.rnn = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
|
|
|
+ self.fc = nn.Linear(hidden_size, output_size)
|
|
|
+ # 检查是否有可用的GPU
|
|
|
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
+
|
|
|
+ def forward(self, x):
|
|
|
+ # Add an extra dimension for the batch size if necessary
|
|
|
+ if x.dim() == 2:
|
|
|
+ x = x.unsqueeze(0)
|
|
|
+ out, h = self.rnn(x)
|
|
|
+ out = self.fc(out[:, -1, :])
|
|
|
+ return out, h
|
|
|
+
|
|
|
+# 全连接神经网络
|
|
|
+class TileMatchFullyConnectedNetwork(nn.Module):
|
|
|
+ def __init__(self, input_size, hidden_size, num_layers, output_size):
|
|
|
+ super(TileMatchFullyConnectedNetwork, self).__init__()
|
|
|
+ self.layer1 = nn.Linear(input_size, hidden_size)
|
|
|
+ self.layer2 = nn.Linear(hidden_size, output_size)
|
|
|
+ # 检查是否有可用的GPU
|
|
|
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
+
|
|
|
+ def forward(self, x):
|
|
|
+ x = torch.relu(self.layer1(x))
|
|
|
+ x = self.layer2(x)
|
|
|
+ return x
|
|
|
+
|
|
|
+# 变分自编码器
|
|
|
+class TileMatchVAE(nn.Module):
|
|
|
+ def __init__(self, input_size, hidden_size, latent_size):
|
|
|
+ super(TileMatchVAE, self).__init__()
|
|
|
+ self.encoder = nn.Sequential(
|
|
|
+ nn.Linear(input_size, hidden_size),
|
|
|
+ nn.ReLU(),
|
|
|
+ nn.Linear(hidden_size, latent_size * 2) # We need mean and variance for each latent variable
|
|
|
+ )
|
|
|
+ self.decoder = nn.Sequential(
|
|
|
+ nn.Linear(latent_size, hidden_size),
|
|
|
+ nn.ReLU(),
|
|
|
+ nn.Linear(hidden_size, input_size),
|
|
|
+ nn.Sigmoid() # To get outputs in the range [0, 1]
|
|
|
+ )
|
|
|
+
|
|
|
+ def reparameterize(self, mu, logvar):
|
|
|
+ std = torch.exp(0.5 * logvar)
|
|
|
+ eps = torch.randn_like(std)
|
|
|
+ return mu + eps * std
|
|
|
+
|
|
|
+ def forward(self, x):
|
|
|
+ h = self.encoder(x)
|
|
|
+ mu, logvar = h.chunk(2, dim=1)
|
|
|
+ z = self.reparameterize(mu, logvar)
|
|
|
+ return self.decoder(z), mu, logvar
|
|
|
+
|
|
|
+class TileMatchDataset(tudata.Dataset):
|
|
|
+ def __init__(self, directories):
|
|
|
+ self.data = []
|
|
|
+ for directory in directories:
|
|
|
+ for filename in os.listdir(directory):
|
|
|
+ filepath = os.path.join(directory, filename)
|
|
|
+ if os.path.isfile(filepath) and filepath.endswith('.json'):
|
|
|
+ slices = get_each_view_slice(filepath)
|
|
|
+ if len(slices) > 1:
|
|
|
+ for i in range(0, len(slices)-1):
|
|
|
+ self.data.append([slices[i], slices[i+1]])
|
|
|
+
|
|
|
+ def __len__(self):
|
|
|
+ return len(self.data)
|
|
|
+
|
|
|
+ def __getitem__(self, idx):
|
|
|
+ input_sequence = self.data[idx][0]
|
|
|
+ target_sequence = self.data[idx][1]
|
|
|
+ return torch.tensor(input_sequence, dtype=torch.float32), torch.tensor(target_sequence, dtype=torch.float32)
|
|
|
+
|
|
|
+# 模型的参数
|
|
|
+input_size = 900
|
|
|
+hidden_size = 50
|
|
|
+num_layers = 2
|
|
|
+output_size = 900
|
|
|
+
|
|
|
+def train_NN():
|
|
|
+ # Initialize model, loss function, optimizer
|
|
|
+ # model = TileMatchRNN(input_size, hidden_size, num_layers, output_size)
|
|
|
+ # criterion = nn.BCEWithLogitsLoss() # 适合二分类问题
|
|
|
+ # optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
|
|
|
+
|
|
|
+ model = TileMatchVAE(input_size, 500, 20)
|
|
|
+ criterion = nn.BCEWithLogitsLoss() # 适合二分类问题
|
|
|
+ optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
|
|
|
+
|
|
|
+ # 准备数据集
|
|
|
+ batch_size = 1
|
|
|
+ directories = ["/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/tf_templates",]
|
|
|
+ dataset = TileMatchDataset(directories)
|
|
|
+ train_loader = tudata.DataLoader(dataset, batch_size=batch_size, shuffle=True)
|
|
|
+
|
|
|
+ num_epochs = 10
|
|
|
+ for epoch in range(num_epochs):
|
|
|
+ for data in train_loader: # Assume train_loader provides (input, target) pairs
|
|
|
+ inputs, targets = data
|
|
|
+ # outputs, _ = model(inputs)
|
|
|
+ x_recon, mu, logvar = model(inputs)
|
|
|
+ recon_loss = F.binary_cross_entropy(x_recon, inputs, reduction='sum')
|
|
|
+ kl_div = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
|
|
|
+ loss = recon_loss + kl_div
|
|
|
+
|
|
|
+ optimizer.zero_grad()
|
|
|
+ loss.backward()
|
|
|
+ optimizer.step()
|
|
|
+ return model
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ # test_get_each_view_slice()
|
|
|
+ n = train_NN()
|
|
|
+ torch.save(n, '/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/nn_VAE.model')
|
|
|
+ # n = torch.load('/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/nn_VAE.model')
|
|
|
+ slices = get_each_view_slice("/Users/xulianxin/Documents/develop/game/TileMatch/TileManor.Lv/TileManor/templates/tm_0015.json")
|
|
|
+ n.eval()
|
|
|
+ # 准备输入数据
|
|
|
+ initial_input = torch.tensor(slices[0], dtype=torch.float32).unsqueeze(0) # (1, 1, 900)
|
|
|
+
|
|
|
+ # 初始化隐藏状态
|
|
|
+ h0 = torch.zeros(num_layers, initial_input.size(0), hidden_size)
|
|
|
+
|
|
|
+ # 使用模型进行预测 生成6层
|
|
|
+ slices = []
|
|
|
+ for i in range(1):
|
|
|
+ with torch.no_grad(): # 在评估模式下,不需要计算梯度
|
|
|
+ # output, hn = n(initial_input)
|
|
|
+ output, mu, logvar = n(initial_input)
|
|
|
+ initial_input = output
|
|
|
+ # 应用阈值判断将浮点数转化为0或1
|
|
|
+ threshold = 0.9
|
|
|
+ output_layout = (torch.sigmoid(output) >= threshold).int()
|
|
|
+ # 将 output_layout 转化成一个[]
|
|
|
+ # slices.append([int(d) for d in output_layout[0]])
|
|
|
+ slices.append([int(d) for d in output_layout[0]])
|
|
|
+ output_to_json(slices, "nn_vae")
|