Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - LostTrainDude

#21
Quote from: eri0o on Fri 17/08/2018 16:10:20
Hey, you forgot to show the function GetDistance.

You're right!
Here it is:

Code: ags
int GetDistance(int x1, int y1, int x2, int y2)
{   
	float x1f = IntToFloat(x1);
	float x2f = IntToFloat(x2);
	float y1f = IntToFloat(y1);
	float y2f = IntToFloat(y2);
	
	float a = Maths.RaiseToPower((x2f - x1f), 2.0);
	float b = Maths.RaiseToPower((y2f - y1f), 2.0);
	
	float d = Maths.Sqrt(a + b);
	int dist = FloatToInt(d);
	
	return dist;
}
#22
In my space trading game prototype I have also NPCs who run errands in such way:

  • A new random mission is assigned to them, from the system they are currently on, to a random system (different from the one they are on)
  • They are "flagged" as being on mission
  • They travel from ORIGIN to TARGET
  • Upon arrival on TARGET they are "flagged" as being NOT on mission
  • A new random mission is assigned to them
  • Go to point 2

The problem I have is that this "routine" does not seem to happen "endlessly" as it should. It randomly lasts an average 3 to - maybe - 5 cycles, then the NPC will get a new mission, with new coords, and just sit there, although they are flagged as being "on mission".

I have double checked and the coords are always within the bounds and "real", so I'm not sure what I'm getting wrong. Maybe someone here can provide some feedback?

Here comes the code in the order in which it is compiled:
Code: ags
// Movement uses a pretty basic algorithm.
// Manhattan distance to calculate the distance between the NPC and its target

function Person::Move(int toX, int toY)
{
   int x1 = this.X;
   int y1 = this.Y;
   
   int x2 = toX;
   int y2 = toY;
   
   float tx = IntToFloat(x2 - x1);
   float ty = IntToFloat(y2 - y1);
   
   int dist = GetDistance(this.X, this.Y, x2, y2);
   
   if (dist > 0)
   {
      tx = tx/IntToFloat(dist);
      ty = ty/IntToFloat(dist);
      this.X += FloatToInt(Maths.Ceil(tx));
      this.Y += FloatToInt(Maths.Ceil(ty));
   }
}

Code: ags
/// Move People who are flagged as being on mission towards their destination

function MovePerson(int who)
{
   // The ID of the mission an NPC is on (always 0 in this test)
   int mission_id = People[who].MissionID;
   
   // The ID of the target planet
   int target_planet = Missions[mission_id].TargetPlanet;
   
   // The current X/Y coords of the NPC
   int pX = People[who].X;
   int pY = People[who].Y;
   
   // The X/Y coords of the target planet
   int toX = planetarySystem[target_planet].X;
   int toY = planetarySystem[target_planet].Y;

   // If the NPC is on the target planet
   if (pX == toX && pY == toY)
   {
      // Then is not on mission anymore
      People[who].IsOnMission = false;
      
      // Former target planet becomes new starting position
      People[who].Where = Missions[mission_id].TargetPlanet;
   }     
   else
   {
      // Move towards the target planet
      People[who].Move(toX, toY);
   }
}

Code: ags
// This is the main function who controls the routine

function NextTurn(int turns)
{
   for (int t = 0; t < turns; t++)
   {
      // If NPC (always 0 in this test) is flagged as being on mission
      if (People[0].IsOnMission)
      {
         // Then move NPC (always 0 in this test)
         MovePerson(0);
      }
      else
      {
         // Check that this code is actually being executed
         Display("New Mission!");
         int person = 0; // Always 0 in this test
        
         // Select a random type of mission
         int type = Random(MISSION_TYPES-1);
         
         // Select the starting planet to be the same as the one the NPC is sitting on
         int startPlanet = People[person].Where;
         
         // Select a random target planet
         int targetPlanet = Random(TOTAL_SYSTEMS-1);
         
         // Repeat if start and target planets are the same
         if ((targetPlanet == startPlanet))
         {
            do
            {
               targetPlanet = Random(TOTAL_SYSTEMS-1);
            } while ((targetPlanet == startPlanet));
         }
         
         Missions[0].AssignedTo = 0; // The ID of the NPC to which this mission is assigned
         Missions[0].ID = 0; // The ID of the mission
         Missions[0].OriginPlanet = startPlanet;
         Missions[0].TargetPlanet = targetPlanet;
         Missions[0].Type = type;
         
         People[0].MissionID = 0; // The same as Mission[0].ID
         People[0].IsOnMission = true;
      }
      // Draw the pixels on the screen!
      map.DrawPixelPeople();
   }
}


Code: ags
// This is the debug function I'm using to test the whole thing

function on_key_press(eKeyCode keycode) 
{
   if (keycode == eKey1)
   {
      for (int i = 0; i < 1500; i++)
      {
         NextTurn(1);
         Wait(1); // I want to see it moving on the screen!
      }
   }
}


Thanks in advance and I hope I have made everything clear!

EDIT:

The issue is apparently solved by changing Math.Ceil() to Math.Round() in the Person::Move() function.
vga256 suggested me to go deeper with the debug and I just did that. I'm not sure why the calculation caused the problem, but now it seems to be working as intended!
#23
Congrats Kastchey! A very nice little game with an incredibly spot on eerie atmosphere!
Always a big fan of your work :)
#24
So many great items! Drat! I can only choose three!
Ok, I can do this:

1st> 21
2nd> 28
3rd> 16

Great entries, everyone!
#25
V1.2 UPDATE!
After quite some time here's an update!

Translations
The game is now available in Spanish, thanks to Maximiliano Vaccaro (LinkedIn, Twitter) and Italian (you know, being Italian it was about time (roll))

You can now switch languages either in-game or through the winsetup.exe file


New GUIs
Another "it was about time" feature: GUIs. We finally got rid of the default AGS GUIs for the Load\Save\Quit game GUIs. Now at least, they match the mood of the game :)


Dialogue Skipping
Over the years, we have often received feedback about dialogue lines disappearing too quickly. So, now you can read conversations at your own pace, skipping them by hitting a key or a mouse button (which also makes the game a little more accessible).




Download Links
itch.io | AGS Database Entry | Indiexpo | Fireflower Games (as soon as it will be updated there as well)
#26
Thanks a lot for this, Snarky :)

Although I think I will spend some time figuring out how to use it for my own purposes, I think I understand the concept behind it.
Those links you included in the comments are very useful!
#27
I have been struggling for about a week to convert this pseudocode (and its C++ implementation) in AGS.

After quite some time I was able to shrink down the script to just integers and bools, which made everything more easy to manage and edit. I made some progress but I'm not there yet.

I am using a grid of nodes, 8px distant from each other. Each Node has an x and a y that can be used to calculate their node number within a global array of Nodes.
Code: ags
#define NCOLS 8
#define NROWS 8

int GetNodeAtRowCol(int row, int col)
{
   return row*NCOLS+col;
}

int GetNodeAtYX(int y, int x)
{
   return GetNodeAtRowCol((y - 8) / 8 , (x - 8) / 8);
}

I started with a 24x24 grid, but 576 nodes were too much for the search algorithm, who started running those pesky loops running "150001 times" and more. So, for testing purposes, I scaled down, up to 8x8, so 64 nodes. Things went better but as soon as I started moving from a less complex algorithm, such as the Breadth First search, towards the A* I started getting those again.

I tried my best to make the code as simple as possible, splitting much of it into separate functions, also having to write my own versions of push(), pop(), insert(), etc. Still, it returns such "infinite loop" errors: 90% of the time it won't work from the very start, 10% of the time it would crash after a few moments.

Of course, I do not exclude that I may have made some mess.
Can you make sense of it? I tried my best to comment it and I hope it is understandable.

Most of all I'd like to know if it's me just pushing this too far or if there is something wrong with what I have coded.

I have marked with an asterisk (*) the lines that trigger the error, but I suppose it was quite obvious.

Code: ags
// BigBang.ash
struct Node
{
   int x, y;
   int neighbours[8];  
   bool isFree;
   int cost;
}

(cost at the moment is either 1 or 2, depending on whether there is a planet on there or not)

Code: ags
// Pathfinding.asc
int frontier[TOTAL_NODES];
int frontier_priority[TOTAL_NODES];
int came_from[TOTAL_NODES];
int cost_so_far[TOTAL_NODES];

int heuristic(int node_a, int node_b)
{   
   float n1x = IntToFloat(g_Nodes[node_a].x);
   float n1y = IntToFloat(g_Nodes[node_a].y);
   float n2x = IntToFloat(g_Nodes[node_b].x);
   float n2y = IntToFloat(g_Nodes[node_b].y);
   
   return FloatToInt(Maths.Ceil(Maths.Abs(n1x - n2x) + Maths.Abs(n1y - n2y)));
}

// Initialize all of the arrays for the search function
function InitializeArrays()
{
   for (int i = 0; i < TOTAL_NODES; i++)
   {
      frontier[i] = -1;
      came_from[i] = -1;
      cost_so_far[i] = -1;
      frontier_priority[i] = -1;
   }   
}

// Check if there is at least one valid element in the array
bool IsFrontierEmpty()
{
   for (int i = 0; i < TOTAL_NODES; i++)
   {
      if (frontier[i] != -1)
      {
         return false;
      }
   }
   return true;
}

// "Remove" the first valid element in the array and return its value
int Frontier_GetFirstNode()
{
   int i, ret;
   for (i = 0; i < TOTAL_NODES; i++)
   {
      // if something different than -1 is encountered
      if (frontier[i] != -1)
      {
         // break the loop
         break;
      }
   }
   // store the value at the position
   ret = frontier[i];
   
   // "nullify" the element
   frontier[i] = -1;
   frontier_priority[i] = -1;
   
   // return the stored value;
   return ret;
}

// Insert 
function Frontier_Insert(int node, int priority)
{
   int cur;
   
   // From the back of the array, copy each element to the following index, until desired position is reached
   for (cur = TOTAL_NODES-2; cur >= 0; cur--)
   {
      frontier[cur+1] = frontier[cur];
      frontier_priority[cur+1] = frontier_priority[cur];
   }
      
   // Fill the element at the desired position with the desired node's values
   frontier[0] = node;
   frontier_priority[0] = priority;
} 

// Insert an element at the front of the array
function CameFrom_Insert(int node)
{
   int cur;
   
   // Copy each element to the slot next to them, until desired position is reached
   for (cur = TOTAL_NODES-2; cur >= 0; cur--)
   {
      came_from[cur+1] = came_from[cur];
   }
      
   // Fill the element at the desired position with the desired node's values
   came_from[0] = node;   
}

// Return the first valid element from the back of the array
int CostSoFar_GetLastNode()
{
   for (int j = TOTAL_NODES-1; j > 0; j--)
   {
      if (cost_so_far[j] != -1)
      {
         return cost_so_far[j];
      }
   }
}

// Check if the given node is already included in the array
bool CostSoFar_CheckNode(int node)
{
   for (int i = 0; i < TOTAL_NODES; i++)
   {
      if (cost_so_far[i] == node)
      {
         return true;
      }
   }
   return false;
}

// Foresee the cost of the next step in the search algorithm
int CostSoFar_NextStepCost(int node)
{
   for (int i = 0; i < TOTAL_NODES; i++)
   {
      if (g_Nodes[i] == g_Nodes[node])
      {
         return g_Nodes[i].cost;
      }
   }
}

// Insert an element after the last valid one in the array
function CostSoFar_InsertToBack(int cost)
{
   for (int i = TOTAL_NODES-1; i > 0; i--)
   {
      if (cost_so_far[i] != -1)
      {
         cost_so_far[i+1] = cost;
      }
   }
}

String a_star_search(int nStart, int nGoal)
{
   // Initialize all arrays elements to -1
   InitializeArrays();
   
   // INIT start_location
   int start_location = nStart;
   int start_location_nbs[8];
   for (int i = 0; i < 8; i++) start_location_nbs[i] = g_Nodes[nStart].neighbours[i];
   
   // Add start_location to frontier
   frontier[0] = nStart;
   frontier_priority[0] = 0;
   
   // Add start_location to frontier
   came_from[0] = nStart;
   
   // Add current cost of 0;
   cost_so_far[0] = 0;
   
   // As long as there is something different than -1 in frontier[]
   while (!IsFrontierEmpty())
   {
      // Get the first element in frontier[]
      int f_node = Frontier_GetFirstNode();
      
      // INIT current_location
      int current_location = f_node;
      int current_location_nbs[8]; // neighbours
      for (int i = 0; i < 8; i++)
      {
         current_location_nbs[i] = g_Nodes[f_node].neighbours[i];
      }
      
      // Stop searching once arrived to the destination
      if (f_node == nGoal)
      {
         break;
      }
     
      
      // Scan the neighbours to see if they have been visited
      for (int i = 0; i < 8; i++)
      {
         // Store the node
         int neighbour_node = current_location_nbs[i];
         
         // If the node is within the bounds
         if (neighbour_node != -1)
         {
            // Calculate: last cost + the cost from the last location to the current one
            int new_cost = CostSoFar_GetLastNode() + (CostSoFar_GetLastNode()+g_Nodes[neighbour_node].cost);
            
            // Test the next iteration in advance to check whether it's valid or not
            if (i+1 < 8 && current_location_nbs[i+1] != -1)
            {
               // If it's valid calculate the cost of the next location
               int next_cost = CostSoFar_NextStepCost(current_location_nbs[i+1]);
               
               // If we didn't visit this node yet OR if it is less expensive
               if (!CostSoFar_CheckNode(current_location_nbs[i+1]) || new_cost < next_cost)
               {
                  // Add the cost to the cost_so_far array
                  CostSoFar_InsertToBack(new_cost);
                  
                  // Establish the priority
                  int priority = new_cost + heuristic(nGoal, current_location_nbs[i+1]);
                  
                  // Insert the next neighbour at the top of the frontier array;
                  Frontier_Insert(current_location_nbs[i+1], new_cost); // *
                  
                  // Insert the next neighbour at the top of the came_from array;
                  CameFrom_Insert(current_location_nbs[i+1]); // *
               }
            }
         }
      }
   }
   
   
   // Store the path into a string, separating values with comma
   String s = "";
   for (int i = TOTAL_NODES-1; i > 0; i--)
   {
      if (came_from[i] != -1)
      {
         String ss = String.Format("%d,", came_from[i]);
         s = s.Append(ss);
      }
   }
   return s;
}

export a_star_search;


It is also worth noticing that the frontier_priority array, theoretically to be used "side by side" with the frontier one, is still not used in any kind of sorting (which at this stage I'm not really sure how to tackle).

Thanks a lot in advance to anyone who spends some time on this. Also thanks again to Crimson Wizard, Gurok* and vga256 who have helped a lot already.
I wonder if this can be turned into a module, once (and if) it will be figured out. :)

*Gurok: if you're reading this, the last code you shared with me helped me a great bunch, although I was not able to figure out the circular queue, yet :(
#28
Quote from: fernewelten on Sun 24/06/2018 14:25:12
If it's a stack in the classic sense, then you "push" something on top, and you "pop" something from the top. That means, at any point of time, the elements a[0], a[1] ... a[mx] for some mx are valid, the elements a[n+1], a[n+2] ... are invalid (free). So you don't need to keep track of "valid" for each element, but you manage an int mx as the fill level. It increments at each push and decrements at each pop.

I am trying to reproduce what queue::push() and queue::pop() do which, I suppose, is to add\remove elements to\from the back of the queue.
I think I have more or less grasped the idea of how to do that, but let's not put the cart before the horse (laugh)
#29
Thanks a lot, Gurok. I'll give it a spin and see where does it take me to :)
#30
Ohh, I see! Thanks Gurok! This means I should assign a new PathNode every time?

I tried that but apparently I'm getting another kind of error and that is: it only works once.
The first time it turns the first found valid element to false, the second time all of them.

But now I guess it's maybe something missing in the Pop() function.

(New code in the game_start() function)
Code: ags
for (int c = 0; c < 4; c++)
{
    PathNode* pn = new PathNode;
    pn.valid = true;
    Push("frontier", pn);
}
#31
As I am trying to work my way around missing equivalents of functions such as push(), pop() and empty() for arrays, I created my own Pop() function.

The idea is that: iterating through an array of already initalized managed structs, as soon as the loop finds an element that has its valid variable value set to true, it should:
  • switch it to false;
  • break out of the loop and exit the function.
Problem is: it exits, but for some reason doesn't stop at the first element it finds.
I know it exits because I tried creating breakpoints in the loop and debugging it step by step and apparently the loop breaks\returns when it should.
Still, the results are still not what I think I coded them to be (unless I made some silly mistake, and that could be possible).

This is the code I'm currently using:

Code: ags
managed struct PathNode
{
   bool valid;
};

PathNode* frontier[TOTAL_NODES];

PathNode* current;

function Pop(String array)
{
   for (int n = 0; n < TOTAL_NODES; n++)
   {
      if (array == "frontier")
      {
         if (frontier[n].valid)
         {
            frontier[n].valid = false;
            return; // also tried using break
         }
      }
   }
}

function Push(String array, PathNode* to_add)
{
   for (int n = TOTAL_NODES-1; n >= 0; n--)
   {
      if (array == "frontier")
      {
         if (frontier[n].valid)
         {
            frontier[n+1].valid = true;
            frontier[n+1] = to_add;
         }
         else if (n == 0)
         {
            frontier[n].valid = true;
            frontier[n] = to_add;
         }
      }
   }
}

function game_start()
{
   current = new PathNode;
   current.valid = true;
   
   for (int n = 0; n < TOTAL_NODES; n++)
   {
      frontier[n] = new PathNode;
      frontier[n].valid = false;
   }
   
   if (Empty("frontier")) Display("Frontier is EMPTY");
   else Display("Frontier is NOT EMPTY");
   
   Push("frontier", current);
   Push("frontier", current);
   Push("frontier", current);
   
   for (int n = 0; n < 5; n++)
   {
      Display("frontier\[%d]: %d", n, frontier[n].valid);
   }
   
   // Outputs:
   // Frontier[0]: 1
   // Frontier[1]: 1
   // Frontier[2]: 1
   // Frontier[3]: 0
   // Frontier[4]: 0
   
   
   Pop("frontier");
   
   for (int n = 0; n < 5; n++)
   {
      Display("frontier\[%d]: %d", n, frontier[n].valid);
   }
   
   // Outputs:
   // Frontier[0]: 0 
   // Frontier[1]: 0 -- SHOULD BE 1
   // Frontier[2]: 0 -- SHOULD BE 1
   // Frontier[3]: 0 
   // Frontier[4]: 0
}


I also tried writing the loop like this, but it still produces the same results:

Code: ags

function Pop(String array)
{
   bool found = false;
   for (int n = 0; n < TOTAL_NODES; n++)
   {
      if (!found)
      {
         if (array == "frontier")
         {
            if (frontier[n].valid)
            {
               frontier[n].valid = false;
               found = true;
            }
         }
      }
   }
}


I even tried not doing everything altogether in the game_start()

Code: ags
function on_key_press(eKeyCode keycode) 
{ 
   if (keycode == eKeyU)
   {
      Pop("frontier");
      for (int n = 0; n < 5; n++)
      {
         Display("frontier\[%d]: %d", n, frontier[n].valid);
      }
   }
}


Thanks in advance to anyone who can shed some light.
#32
Looking forward to see more of this :) Keep us posted and good luck with the development!
#33
Working further with the code you provided I may have found what I am looking for (or at least what I am ok with!)

This is how I changed my script:
Code: ags
// Time.asc

int DateToRaw(int y, int m, int d)
{
   return d + m * 100 + y * 10000; // Gives YYYYMMDD
}

Date* RawToDate(int rawDate)
{
   Date* d = new Date;
   d.Day = rawDate % 100;
   d.Month = (rawDate / 100) % 100;
   d.Year = rawDate / 10000;
   
   return d;
}

int Date::RawDate()
{
   return DateToRaw(this.Year, this.Month, this.Day);
}

bool Date::IsLeap()
{
   return this.Year%4 == 0 && (this.Year%100 != 0 || this.Year%400 == 0);
}

function Date::Add(int days)
{
   int newRawDate = this.RawDate() + days;
   
   for (int i = this.RawDate(); i <= newRawDate; i++)
   {
      this.Day++;
      
      if (this.Day > 28 && this.Month == 2)
      {
         if (!this.IsLeap())
         {
            this.Day = 1;
            this.Month++;
         }
         else
         {
            if (this.Day > 29)
            {
               this.Day = 1;
               this.Month++;
            }
         }
      }
      else if (this.Day > 30 && (this.Month == 11 || this.Month == 4 || this.Month == 6 || this.Month == 9))
      {
         this.Day = 1;
         this.Month++;
      }
      else if (this.Day > 31 && (this.Month == 1 || this.Month == 3 || this.Month == 5 || this.Month == 7 || this.Month == 8 || this.Month == 10 || this.Month == 12))
      {
         this.Day = 1;
         this.Month++;
         if (this.Month == 13)
         {
            this.Month = 1;
            this.Year++;
         }
      }
   }
}


And just to provide the context:
Code: ags
// Missions.asc

// some other code

function CreateMission(int ID, MissionType type)
{
   // some other code
   
   Missions[ID].RawStartDate = CurrentDate.RawDate();
   Date* start = RawToDate(Missions[ID].RawStartDate);
   Date* due = start;
   due.Add(30);
   
   Missions[ID].RawDueDate = DateToRaw(due.Year, due.Month, due.Day);
   
   // some other code
}

String MissionText(int ID)
{
   String missionType;
   
   // some other code
   String p = planetarySystem[Missions[ID].TargetPlanet].name;
   
   Date* start = RawToDate(Missions[ID].RawStartDate);
   Date* due = RawToDate(Missions[ID].RawDueDate);
   
   String s;
   s = String.Format("%s is sent over to %s for %s, commissioned by The %s. Start: %.2d-%.2d-%.2d. Due: %.2d-%.2d-%.2d", People[Missions[ID].AssignedTo].name, p, missionType, Missions[ID].IssuingFaction, start.Day, start.Month, start.Year, due.Day, due.Month, due.Year);
   return s;
}


The output being this, where the due date is basically the start date + 30 days:


EDIT: the result is 31 days actually, but hmm, yeah it probably is something I messed up in the for loop in the Date::Add() function.
EDIT 2: Yeah I tweaked the for loop to be
Code: ags
for (int i = this.RawDate(); i < newRawDate; i++) // < instead of <=
and it works :)

It seems to be working, but I will test it more and see if I can use it as I intended to before confirming!

In the meantime thank you all! :) And feel free to ask for details or provide further feedback, if you want!
#34
Quote from: selmiak on Thu 14/06/2018 18:11:55
then start at 3000-01-01 or whatever is closest to the ingame start date and add the numbers, or rather start at 0001-01-01 for easier calculating and calculate this internal and just add 3000 or so years every time you put out a date for the player to see.

That was one of my ideas as well but I honestly don't know how to twist the DateToRaw() function in order to have it start from year 3000. And, continuing on to Snarky's suggestion...

Quote from: Snarky on Thu 14/06/2018 13:30:57
What do you need the int representation for, anyway?

The reason I think I need these is that I would like to do date calculations "at leisure".
I would like to add dateB to dateA, as well as be able to know how many days\months\years of distance happen between the two.

For instance let's say that I want to set up a random Mission constructor that sets a due date that is 2 months from whatever the game's current date is, and stores both (e.g. startDate, dueDate).
Another example could be: to finish my current mission I have to travel from Planet X to Planet Y and to do so I need Z amount of days. Will I manage to be there before time is up?

Anyway, I like how your compact your code is :) I am not sure I can use it for all of the above purposes, though!

Another solution vga256 and me tried was to use Julian/Gregorian conversions and that seemed to be quite a good solution but unfortunately AGS doesn't really work well with float :(

Code: ags

float GtoJ(int year, int month, int day)
{
   float I = IntToFloat(year);
   float J = IntToFloat(month);
   float K = IntToFloat(day);
   
   return K-32075.0+1461.0*(I+4800.0+(J-14.0)/12.0)/4.0+367.0*(J-2.0-(J-14.0)/12.0*12.0)/12.0-3.0*((I+4900.0+(J-14.0)/12.0)/100.0)/4.0;
}


3200-01-31 in Julian date should be 2889865.500000, but what I get is this:



#35
Quote from: Snarky on Wed 13/06/2018 20:20:04
Without delving into the code (and I see that you've got the calculation from Stackoverflow, which would require further study), I'm guessing it's very likely to do with starting the month and day counts at 1 instead of 0. It messes with any calculations that use division or modulo.

I guess that part of the issue is that, starting from 0001-01-01 generates an incredibly high (and likely useless) number (1168441 days from 0001-01-01 to 3200-01-31). Because of this I can't run just the iteration because of the iteration limit for the for loop.

Quote from: tzachs on Thu 14/06/2018 00:38:51
Well, the hacky solution (without understanding the code, just based on the behavior you're describing) would be to move the "reduce one year" line to the end of the function, just before the return.

Note, though, that dealing with time is incredibly complex with like a billion of edge cases, so no matter what, test a lot.
Some reading material to appreciate the complexity:
https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time
https://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe-about-time
The hacky solution works momentarily, but 3201 is not a leap year, while 3200 is, so you end up with this issue :( :



(OT: That website is amazing and not only for the falsehoods about time! Thanks for sharing!)
#36
Hi all!

I am tinkering with this idea of creating some sort of space trading sim in AGS and, after managing to scatter some "VIPs" in the universe, I wondered if I could have them run their own errands\missions.
These errands\missions, along with other features, are all going to have a start date and a due date. This led me to implement an internal clock and also a "calendar", in order to make date differences calculations.

I treated dates as managed structs as defined here:
Code: ags
// Time.ash

managed struct Date
{
   int Hour;
   int Minute;
   int Second;
   
   int Year;
   int Month;
   int Day;
   
   import bool IsLeap();
   import function Add(int days);
};

import int DateToRaw(int y, int m, int d); // converts years, months, days to days
import Date* RawToDate(int rawDate); // converts days to years, months, days

import function Clock(int speed); // speed = 40*x, where 40 is 1 second in game loops

import Date* StartDate; // set to 01-01-3200
import Date* CurrentDate; // updated by the clock


Code: ags
// Time.asc

Date* StartDate;
Date* CurrentDate;

int tick; // Game loops

// I know it is kind of a duplicate but I was testing different solutions
bool leapyear(int year)
{
   if (year %   4 != 0) return false;
   if (year % 100 != 0) return true;
   if (year % 400 != 0) return false;
   return true;
   
   //Alternative one-liner
   /* return year%4 == 0 && (year%100 != 0 || year%400 == 0); */
}

// I know it is kind of a duplicate but I was testing different solutions
bool Date::IsLeap()
{
   return leapyear(this.Year);
}

// This increases Date of x number of days, and keeps track of months and years
// It is not used in this example here but it works perfectly fine
function Date::Add(int days)
{ 
   for (int i = 0; i < days; i++)
   {
      this.Day++;
      
      if (this.Day > 28 && this.Month == 2)
      {
         if (!this.IsLeap())
         {
            this.Day = 1;
            this.Month++;
         }
         else
         {
            if (this.Day > 29)
            {
               this.Day = 1;
               this.Month++;
            }
         }
      }
      else if (this.Day > 30 && (this.Month == 11 || this.Month == 4 || this.Month == 6 || this.Month == 9))
      {
         this.Day = 1;
         this.Month++;
      }
      else if (this.Day > 31 && (this.Month == 1 || this.Month == 3 || this.Month == 5 || this.Month == 7 || this.Month == 8 || this.Month == 10 || this.Month == 12))
      {
         this.Day = 1;
         this.Month++;
         if (this.Month == 13)
         {
            this.Month = 1;
            this.Year++;
         }
      }
   }
}

// Convert YYYY-MM-DD to days
// Found on stackoverflow, here https://stackoverflow.com/a/14224238
int DateToRaw(int y, int m, int d)
{
   /* Rata Die day one is 0001-01-01 */
   if (m < 3)
   {
      y--;
      m += 12;
   }
   return 365*y + y/4 - y/100 + y/400 + (153*m - 457)/5 + d - 306;
}

// Convert days to YYYY-MM-DD
// includes code provided by vga256 and most of the code from the Date.Add() function
Date* RawToDate(int rawDate)
{
   // Initialize a new Date object
   Date* d = new Date;
   d.Year = 1;
   d.Month = 1;
   d.Day = 1;
   d.Second = 0;
   d.Minute = 0;
   d.Hour = 0;
   
   int rawdays = rawDate;
   int howmanyleapyears;
   
   // To make it start at 3200 subtract 1
   d.Year = rawdays / 365;
   d.Year--; // *
   
   // How many leap years from year 0001 to rawDate?
   //
   // PLEASE NOTE: I do this to keep the number of iterations low
   // in order to prevent the loop from hanging
   for (int i = 1; i < d.Year; i++)
   {
      if (leapyear(i)) howmanyleapyears++;
   }
   
   // If the current year is a leap year then calculate the
   // remainder out of 366 days
   if (d.IsLeap()) rawdays = (rawdays - howmanyleapyears) % 366;
   
   // Otherwise calculate the remainder out of 365 days
   else rawdays = (rawdays - howmanyleapyears) % 365;

   // Same as the one in the Clock() function
   for (int i = 0; i < rawdays; i++)
   {
      d.Day++;
      
      if (d.Day > 28 && d.Month == 2)
      {
         if (!d.IsLeap())
         {
            d.Day = 1;
            d.Month++;
         }
         else 
         {
            if (d.Day > 29)
            {
               d.Day = 1;
               d.Month++;
            }
         }
      }
      else if (d.Day > 30 && (d.Month == 11 || d.Month == 4 || d.Month == 6 || d.Month == 9))
      {
         d.Day = 1;
         d.Month++;
      }
      else if (d.Day > 31 && (d.Month == 1 || d.Month == 3 || d.Month == 5 || d.Month == 7 || d.Month == 8 || d.Month == 10 || d.Month == 12))
      {
         d.Day = 1;
         d.Month++;
         if (d.Month == 13)
         {
            d.Month = 1;
            d.Year++;
         }
      }
   }
   return d;
}

// Tick tock!
// The clock works perfectly fine
function Clock(int speed)
{
   tick -= 1*speed;
   if (tick <= 0)
   {
      CurrentDate.Second++;
      if (CurrentDate.Second == 60)
      {
         CurrentDate.Second = 0;
         CurrentDate.Minute++;
         if (CurrentDate.Minute == 60)
         {
            CurrentDate.Minute = 0;
            CurrentDate.Hour++;
            if (CurrentDate.Hour == 24)
            {
               CurrentDate.Hour = 0;
               CurrentDate.Day++;
               if (CurrentDate.Day > 28 && CurrentDate.Month == 2)
               {
                  if (!CurrentDate.IsLeap())
                  {
                     CurrentDate.Day = 1;
                     CurrentDate.Month++;
                  }
                  else 
                  {
                     if (CurrentDate.Day > 29)
                     {
                        CurrentDate.Day = 1;
                        CurrentDate.Month++;
                     }
                  }
               }
               if (CurrentDate.Day > 30 && (CurrentDate.Month == 11 || CurrentDate.Month == 4 || CurrentDate.Month == 6 || CurrentDate.Month == 9))
               {
                  CurrentDate.Day = 1;
                  CurrentDate.Month++;
               }
               else if (CurrentDate.Day > 31 && (CurrentDate.Month == 1 || CurrentDate.Month == 3 || CurrentDate.Month == 5 || CurrentDate.Month == 7 || CurrentDate.Month == 8 || CurrentDate.Month == 10 || CurrentDate.Month == 12))
               {
                  CurrentDate.Day = 1;
                  CurrentDate.Month++;
                  if (CurrentDate.Month == 13)
                  {
                     CurrentDate.Month = 1;
                     CurrentDate.Year++;
                  }
               }                      
            }
         }
      }
      tick = 40;
   }  
}

function repeatedly_execute()
{
   // Runs and displays the clock on screen in a certain room
   if (player.Room == 2)
   {
      Clock(1);
      String s = String.Format("%.2d-%.2d-%.2d \\ %.2d:%.2d:%.2d", CurrentDate.Day, CurrentDate.Month, CurrentDate.Year, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second);
      lblCurrentTime.Text = s;
   }
}

function game_start()
{
   StartDate = new Date;
   CurrentDate = new Date;
   
   tick = 40;
   
   StartDate.Year = 3200;
   CurrentDate.Year = StartDate.Year;
   
   StartDate.Month = 1;
   CurrentDate.Month = StartDate.Month;
   
   StartDate.Day = 31;
   CurrentDate.Day = StartDate.Day;
   
   StartDate.Second = 50;
   CurrentDate.Second = StartDate.Second;
   
   StartDate.Minute = 59;
   CurrentDate.Minute = StartDate.Minute;
   
   StartDate.Hour = 23;
   CurrentDate.Hour = StartDate.Hour;
   String s = String.Format("%.2d-%.2d-%.2d \\ %.2d:%.2d:%.2d", CurrentDate.Day, CurrentDate.Month, CurrentDate.Year, CurrentDate.Hour, CurrentDate.Minute, CurrentDate.Second);
   lblCurrentTime.Text = s;   
}

export CurrentDate;
export StartDate;


Last but not least, I have bound to a key this block of code, which calls the above function and displays the returned string:
Code: ags
String s;
int a = DateToRaw(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day); // = 1168441 
Date* b;
b = RawToDate(a);
s = String.Format("%.2d-%.2d-%.2d", b.Day, b.Month, b.Year);
return s;


Now, given all of this, the problem I'm having (actually me and vga256 are having, since he is helping me a great deal to crack this) is that, when I call this function:

1) If I don't subtract 1 from d.Year (check line 99 of the Time.asc): Months and Days show correctly, but the year is off.


2) If I subtract 1 from d.Year: the year is fine but Months and Days are quite off.


Does anyone have an idea why this is happening?

To give you an idea of what my ideal outcome would be, at the end of the day, here is a mockup:


Thanks a million in advance!
#37
Congrats to all the winners and nominees!
And of course huge thanks to all who was involved in the client and ceremony itself :)
#38
Thanks for making this possible again, this year, folks! :D
#39
AGS Games in Production / Re: Feria d'Arles
Fri 16/02/2018 00:10:10
Each image is dripping awesomeness :D
Looking forward to it!
#40
FOR YOUR CONSIDERATION

THE FICKLE HANDS OF FATE
A short single-click point and click adventure game


"Fate is finalizing her last recording in the steeple of an abandoned church in a village she is about to leave. Then, off to another city and another recording: her search is not over yet."

Made for MAGS May 2017, this game also represents an experiment I wanted to make to strip down the gameplay to a single-click, without compromising storytelling. The result is some sort of visual stream of consciousness, if you will.

I hope you enjoy playing this game, no matter what! :)

Download
AGS Games Database / http://www.adventuregamestudio.co.uk/site/games/game/2159/
itch.io / https://losttraindude.itch.io/the-fickle-hands-of-fate

Screenshots



Possible Awards
Best Freeware Game Created with AGS
Best Short Game
Best Gameplay
Best Writing
Best Music and Sound
SMF spam blocked by CleanTalk