supervisor-simulator/scripts/Lab.cs
2024-12-01 23:47:55 +08:00

239 lines
6.0 KiB
C#

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
public partial class Lab : Node2D
{
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker");
Player.Timeline.Attach(ticker);
Player.Timeline.Subscribe(DateOnly.Parse("2024/11/20"), Guid.NewGuid());
Player.Timeline.OnEventTriggered += (d, e) => GD.Print($"Timeline event triggered: {d}, {e}");
var label = GetNode<Label>("BottomBar/HBoxContainer/Date");
label.Text = Player.Timeline.InternalDate.ToLongDateString();
Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString();
var table = GetNode<Table>("Table");
Player.Timeline.OnDayChanged += d => table.RandomChangeTheme();
MoneyLabel = GetNode<Label>("BottomBar/HBoxContainer/Money");
tileMap = GetNode<TileMapLayer>("TestMap");
UpdateMap();
}
private Label MoneyLabel = null;
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
switch(GD.RandRange(0,2)) {
case 0:
Player.Budget.Operational++;
break;
case 1:
Player.Budget.Facility += 10;
break;
case 2:
Player.Budget.Labor += 100;
break;
}
MoneyLabel.Text = Player.Budget.Total.ToString("N0");
}
private static readonly Vector2I tableTileCoord = new(0, 6);
private static readonly Vector2I waterTileCoord = new(6, 9);
private static readonly Vector2I doorTileCoord = new(9, 3);
private static readonly Vector2I axeTileCoord = new(10, 9);
private static readonly Vector2I blockTileCoord = new(4, 3);
public enum MapNodeType
{
Table,
Water,
Door,
Axe,
Block,
Road,
}
private const int MAP_WIDTH = 24;
private const int MAP_HEIGHT = 13;
private readonly MapNodeType[,] blocks = new MapNodeType[MAP_WIDTH, MAP_HEIGHT];
private TileMapLayer tileMap;
public void UpdateMap()
{
for (int i = 0; i < MAP_WIDTH; i++) {
for (int j = 0; j < MAP_HEIGHT; j++) {
var tile = tileMap.GetCellAtlasCoords(new Vector2I(i,j));
if (tile == tableTileCoord) {
blocks[i,j] = MapNodeType.Table;
} else if (tile == waterTileCoord) {
blocks[i,j] = MapNodeType.Water;
} else if (tile == doorTileCoord) {
blocks[i,j] = MapNodeType.Door;
} else if (tile == axeTileCoord) {
blocks[i,j] = MapNodeType.Axe;
} else if (tile == blockTileCoord) {
blocks[i,j] = MapNodeType.Block;
} else {
blocks[i,j] = MapNodeType.Road;
}
}
}
}
private static bool IsValidPosition(Vector2I pos)
{
int x = pos.X;
int y = pos.Y;
if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) {
return false;
}
return true;
}
private List<Vector2I> GetNeighbors(Vector2I pos) {
int x = pos.X;
int y = pos.Y;
var neighbor = new List<Vector2I>();
if (!IsValidPosition(pos)) {
return neighbor;
}
if (IsValidPosition(new Vector2I(x-1,y)) && blocks[x-1,y] == MapNodeType.Road) {
neighbor.Add(new Vector2I(x-1,y));
}
if (IsValidPosition(new Vector2I(x+1,y)) && blocks[x+1,y] == MapNodeType.Road) {
neighbor.Add(new Vector2I(x+1,y));
}
if (IsValidPosition(new Vector2I(x,y-1)) && blocks[x,y-1] == MapNodeType.Road) {
neighbor.Add(new Vector2I(x,y-1));
}
if (IsValidPosition(new Vector2I(x,y+1)) && blocks[x,y+1] == MapNodeType.Road) {
neighbor.Add(new Vector2I(x,y+1));
}
return neighbor;
}
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
{
for (int j = 0; j < MAP_HEIGHT; j++) {
string t = "";
for (int i = 0; i < MAP_WIDTH; i++) {
t += $"{blocks[i,j]} ";
}
GD.Print(t);
}
var path = new List<Vector2I>();
if (tileMap.GetCellSourceId(start) == -1 || tileMap.GetCellSourceId(end) == -1) {
return path;
}
if (start == end) {
return path;
}
GD.Print(start);
GD.Print(end);
static int vec2idx(Vector2I v) => v.Y * MAP_WIDTH + v.X;
static Vector2I idx2vec2(int i) => new(i % MAP_WIDTH, i / MAP_WIDTH);
HashSet<int> entriesIdx = new();
foreach (var e in GetNeighbors(end)) {
entriesIdx.Add(vec2idx(e));
}
if (entriesIdx.Count == 0) {
return path;
}
// Use Dijkstra algorithm to find the shortest path
int[] dist = new int[MAP_WIDTH * MAP_HEIGHT];
int[] prev = new int[MAP_WIDTH * MAP_HEIGHT];
bool[] visited = new bool[MAP_WIDTH * MAP_HEIGHT];
for (int i = 0; i < dist.Length; i++) {
dist[i] = int.MaxValue;
prev[i] = -1;
visited[i] = false;
}
dist[vec2idx(start)] = 0;
int answer = -1;
for (int i = 0; i < dist.Length; i++) {
int min_distance = int.MaxValue;
int u = -1;
for (int j = 0; j < dist.Length; j++) {
if (!visited[j] && dist[j] <= min_distance) {
min_distance = dist[j];
u = j;
}
}
GD.Print($"u={u}");
if (u == -1) {
return path;
}
if (entriesIdx.Contains(u)){
answer = u;
break;
}
visited[u] = true;
var neighbors = GetNeighbors(idx2vec2(u));
int alt = dist[u] + 1;
GD.Print($"Alt={alt}");
foreach (var neighbor in neighbors) {
int idx = vec2idx(neighbor);
if (visited[idx]) continue;
GD.Print($"Neighbor: {neighbor}, id={idx}, dist={dist[idx]}");
if (alt < dist[idx]) {
dist[idx] = alt;
prev[idx] = u;
GD.Print($"Prev({idx}) = {u}");
}
}
}
while (answer != vec2idx(start)) {
path.Add(idx2vec2(answer));
answer = prev[answer];
}
path.Reverse();
return path;
}
public Vector2I GetTypedBlock(MapNodeType nType, uint idx) {
Vector2I tileCoord;
switch(nType) {
case MapNodeType.Table:
tileCoord = tableTileCoord;
break;
case MapNodeType.Water:
tileCoord = waterTileCoord;
break;
case MapNodeType.Door:
tileCoord = doorTileCoord;
break;
case MapNodeType.Axe:
tileCoord = axeTileCoord;
break;
default:
return Vector2I.Zero;
}
var candidates = tileMap.GetUsedCellsById(0, tileCoord, -1);
idx %= (uint)candidates.Count;
return candidates[(int)idx];
}
public Vector2I Point2Coord(Vector2 pos) {
return tileMap.LocalToMap(tileMap.ToLocal(pos));
}
}