eC Programming Language
Fork me on GitHub
Language designed in 2004

eC

An Expressive and Intuitive, C Style Object-Oriented Programming Language.

Learn More

Dive in and Create

eC comes bundled with the Ecere SDK, providing a comprehensive API for building apps ranging from games to business, for desktop and mobile platforms.

Windows OSX Linux Android FreeBSD

Sample Code

class HelloApp : Application
{
   void Main()
   {
      PrintLn("Hello, World!!");
   }
}
screenshot 1
import "ecere"

class HelloForm : Window
{
   text = "My First eC Application";
   borderStyle = sizable;
   clientSize = { 304, 162 };
   hasClose = true;

   Label label
   {
      this, position = { 10, 10 }, font = { "Arial", 30 },
      text = "Hello, World!!"
   };
};

HelloForm hello { };
screenshot 2
import "ecere"

class MyApp : GuiApplication
{
   driver = "OpenGL";
};

Camera camera
{
   fixed,
   position = Vector3D { 0, 0, -350 },
   orientation = Euler { 0, 0, 0 },
   fov = 53;
};

Light light
{
   diffuse = lightCoral;
   orientation = Euler { pitch = 10, yaw = 30 };
};

class Hello3D : Window
{
   text = "Hello, 3D";
   background = black;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   clientSize = { 304, 162 };

   Cube cube { };

   bool OnLoadGraphics()
   {
      cube.Create(displaySystem);
      cube.transform.scaling = { 100, 100, 100 };
      cube.transform.orientation = Euler { 50, 30, 50 };
      cube.UpdateTransform();
      return true;
   }

   void OnResize(int w, int h)
   {
      camera.Setup(w, h, null);
      camera.Update();
   }

   void OnRedraw(Surface surface)
   {
      surface.Clear(depthBuffer);
      display.SetLight(0, light);
      display.SetCamera(surface, camera);
      display.DrawObject(cube);
      display.SetCamera(surface, null);
   }
}

Hello3D hello3D {};
screenshot 3
import "ecere"
import "gnosis"
import "view3Dcontroller"

class MyScene : Scene
{
   gis::Map { source = { "/Maps/BlueMarbleNextGen" } };
}
MyScene scene { };

class MainWindow : Window
{
   displayDriver = "OpenGL";
   caption = "GnosIS Sample";
   background = black;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   size = { 640, 480 };

   View3D view { scene = scene };

   controller = View3DController
   {
      this, controlled = view,
      position = { 24.01064, -96.86049, Kilometers { 8000 } };
      orientation = { yaw = 10, pitch = 80, roll = -10 };
   };
}
screenshot 4
import "ecere"

class MyApp : GuiApplication
{
   driver = "OpenGL";
   // driver = "Direct3D";
};

Camera camera
{
   fixed,
   position = Vector3D { 0, 0, -300 },
   orientation = Euler { 0, 0, 0 },
   fov = 70;
};

Light light
{
   //diffuse = white;
   specular = white;
   orientation = Euler { pitch = 10, yaw = 30 };
};

Light light2
{
   diffuse = white;
   //specular = white;
   orientation = Euler { pitch = 20, yaw = -30 };
};

class Test3D : Window
{
   text = "Form1";
   background = black;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   clientSize = { 304, 162 };

   BitmapResource texture { "http://www.ecere.com/images/knot.png", window = this };
   Sphere sphere { };
   Cube cube { };
   Material sphereMat { diffuse = white, ambient = blue, specular = red, power = 8 };
   Material cubeMat { opacity = 1.0f, diffuse = white, ambient = white, flags = { doubleSided = true, translucent = true } };

   bool OnLoadGraphics()
   {
      sphere.Create(displaySystem);
      sphere.mesh.ApplyMaterial(sphereMat);
      sphere.transform.scaling = { 75, 75, 75 };
      sphere.transform.position = { 100, 0, 0 };
      sphere.UpdateTransform();

      cubeMat.baseMap = texture.bitmap;

      cube.Create(displaySystem);
      cube.mesh.ApplyMaterial(cubeMat);
      cube.mesh.ApplyTranslucency(cube);
      cube.transform.scaling = { 100, 100, 100 };
      cube.transform.position = { -100, 0, 0 };
      cube.transform.orientation = Euler { 50, 50 };
      cube.UpdateTransform();
      return true;
   }

   void OnResize(int w, int h)
   {
      camera.Setup(w, h, null);
   }

   void OnRedraw(Surface surface)
   {
      surface.Clear(depthBuffer);
      camera.Update();
      display.SetLight(0, light);
      display.SetLight(1, light2);
      display.fogDensity = 0;
      display.SetCamera(surface, camera);
      display.DrawObject(cube);
      display.DrawObject(sphere);
      display.SetCamera(surface, null);
   }
}

Test3D test3D {};

screenshot 5
#include <GL/gl.h>
import "ecere"

#define GL_MULTISAMPLE_ARB 0x809D

class GLTriangle : Window
{
   text = "Triangle";
   displayDriver = "OpenGL";
   background = activeBorder;
   nativeDecorations = true;
   borderStyle = sizable;
   hasMaximize = true, hasMinimize = true, hasClose = true;
   clientSize = { 304, 162 };

   void OnRedraw(Surface surface)
   {
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glOrtho(-30, 30, -30, 30, -30, 30);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslatef(-15, -15, 0);
      glShadeModel(GL_SMOOTH);
      glEnable(GL_MULTISAMPLE_ARB);

      glBegin(GL_TRIANGLES);
      glColor3f(1, 0, 0);  glVertex2f(0, 0);
      glColor3f(0, 1, 0);  glVertex2f(30, 0);
      glColor3f(0, 0, 1);  glVertex2f(0, 30);
      glEnd();
   }
}

GLTriangle window {};

screenshot 6
import "EDA"
import "genericEditor"

enum MediaType { unknown, tape, dvd, bluRay };

dbtable "Borrowers" Borrower
{
   Borrower id          "ID";
   String   name        "Name";
   String   phoneNumber "Phone Number";
};

dbtable "Movies" Movie
{
   Movie          id             "ID";
   String         name           "Name";
   MediaType      mediaType      "Media Type";
   Date           dateAdded      "Date Added";
   Borrower       borrower       "Borrower";
   Date           dateBorrowed   "Date Borrowed";
};

DataSource ds;
Database db;

class MovieCollectionApp : GuiApplication
{
   MovieCollectionApp()
   {
      SetDefaultIdField("ID");
      SetDefaultNameField("Name");
      ds = DataSource { driver = "SQLite" };
      //ds = DataSource { driver = "Oracle", host = "localhost", port = "1521", user = "test", pass = "test" };

      db = database_open(ds, "collection");
   }
   ~MovieCollectionApp()
   {
      delete db;
      delete ds;
   }
}

class MovieCollectionForm : Window
{
   text = "Movie Collection";
   background = activeBorder;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   hasMenuBar = true;
   size = { 576, 432 };

   Menu fileMenu { menu, "File", f };
   MenuItem exit { fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
   Menu reportsMenu { menu, "Reports", r };
   GenericEditor editor { this, anchor = { 0, 0, 0, 0 }, table = dbtable("Movies"),
      list.text = "List of Movies", editor.text = "Movie Entry Being Edited" };
   Button editBorrowers
   {
      this, caption = "Edit Borrowers", altB, stayOnTop = true, anchor = { right = 40, bottom = 40 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         GenericEditor borrowersEditor
         {
            hasClose = true;
            borderStyle = sizable;
            size = { 640, 300 };
            table = dbtable("Borrowers");
            list.text = "List of Borrowers", editor.text = "Borrower Entry Being Edited";
         };
         borrowersEditor.Modal();
         editor.dataBoxes[3].Refresh();
         return true;
      }
   };
}

MovieCollectionForm mainForm {};

screenshot 7
import "ecere"

// We'll use TCP/IP port 5623 for this sample
define samplePort = 5623;

// We will use this simple structure for our messages
struct SamplePacket
{
   int stringLen;
   // stringLen + 1 bytes are actually used (variable size depending on string)
   char string[1];
};

class SampleService : Service
{
   void OnAccept()
   {
      // When we get an incoming connection to our service, we spawn a SampleSocket (Can only serve one right now)
      if(!servingSocket)
      {
         servingSocket = SampleSocket { this };
         form.UpdateButtonStates();
      }
   }
}

class SampleSocket : Socket
{
   void OnConnect()
   {
      // We want a non blocking Connect() call, so we define an OnConnect() that simply updates the button disabled states
      form.UpdateButtonStates();
   }

   void OnDisconnect(int code)
   {
      // On disconnection we need to null the socket pointers, and update the buttons
      if(connectedSocket == this)
         connectedSocket = null;
      else if(servingSocket == this)
         servingSocket = null;

      form.UpdateButtonStates();
   }

   unsigned int OnReceive(unsigned char * buffer, unsigned int count)
   {
      // We only process the data if we've received enough bytes to make up the message
      // This first if just checks if we have reveived enough bytes for the header
      if(count >= sizeof(SamplePacket))
      {
         SamplePacket * packet = (SamplePacket *) buffer;
         uint size = sizeof(SamplePacket) + packet->stringLen;
         // Here we check if we've actually received the entire message
         if(count >= size)
         {
            // We've received a complete message, so we change the contents of the recvString EditBox
            form.recvString.contents = packet->string;
            // and we return the size of the message we've processed.
            // If more data is already buffered, this method will be called again right away.
            return size;
         }
      }
      // We haven't received enough data to process this message yet: return 0 bytes processed
      // This method will be called again once more data has been received.
      return 0;
   }
}

class SocketSample : Window
{
   text = "Socket Sample";
   background = activeBorder;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   tabCycle = true;
   size = { 416, 176 };
   nativeDecorations = true;

   // Service is missing a property to tell us if it's listening or not already, so we keep track of it in this variable
   bool listening;

   void UpdateButtonStates()
   {
      bool connected = (connectedSocket && connectedSocket.connected) || (servingSocket && servingSocket.connected);

      // The Send button is disabled if we're not connected
      btnSend.disabled = !connected;
      // The Connect button is disabled if we've already defined a connectedSocket
      btnConnect.disabled = connectedSocket != null;
      // The Listen button is disabled if we're already listening
      btnListen.disabled = listening;
   }

   Button btnSend
   {
      this, text = "Send", position = { 344, 64 }, disabled = true;

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         // We build up a SamplePacket here with our message from the sentString EditBox
         String string = sentString.contents;
         int len = strlen(string);
         int size = sizeof(SamplePacket) + len;
         SamplePacket * packet = (SamplePacket *)new byte[size];
         packet->stringLen = len;
         memcpy(packet->string, string, len+1);
         // If we've connected to another server, we use the connectedSocket, otherwise we use the servingSocket (Send back to whom connected to us)
         (connectedSocket ? connectedSocket : servingSocket).Send(packet, size);
         // Make sure to free memory allocated with 'new'
         delete packet;
         return true;
      }
   };
   EditBox serverAddress { this, text = "Server Address", size = { 174, 19 }, position = { 8, 40 }, contents = "localhost" };
   Label lblServerAddress { this, position = { 8, 16 }, labeledWindow = serverAddress };
   Button btnListen
   {
      this, text = "Listen", position = { 144, 104 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         // Start listening here
         if(service.Start())
         {
            listening = true;
            UpdateButtonStates();
         }
         return true;
      }
   };
   EditBox sentString { this, text = "Sent String", size = { 166, 19 }, position = { 224, 40 } };
   Label lblSentString { this, position = { 224, 16 }, labeledWindow = sentString };
   EditBox recvString { this, text = "Received String", size = { 166, 19 }, position = { 224, 104 } };
   Label label1 { this, position = { 224, 80 }, labeledWindow = recvString };
   Button btnConnect
   {
      this, text = "Connect", position = { 8, 72 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         btnConnect.disabled = true;
         // Create a socket and attempt a connection to the address specified in the serverAddress EditBox
         connectedSocket = SampleSocket { };
         // Connect is a blocking call if no OnConnect method is defined in the Socket, a non-blocking call otherwise
         connectedSocket.Connect(serverAddress.contents, samplePort);
         return true;
      }
   };

   void OnDestroy()
   {
      // We need to disconnect the socket and stop the service before destroying the application,
      // otherwise we'll get a crash with the SampleSocket accessing the form that is already destroyed
      if(connectedSocket)
         connectedSocket.Disconnect(0);
      service.Stop();
   }
}

// The form
SocketSample form {};

// The service
SampleService service { port = samplePort };

// We use 2 sockets: one for when we connect to a server, one for the incoming connections to our SampleService
SampleSocket connectedSocket, servingSocket;

screenshot 8
// -- main.ec -----------------------
import "ecere"

import remote "Server"

ChatConnection connection;

class Form1 : Window
{
   caption = $"Server";
   background = formColor;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   clientSize = { 640, 460 };

   EditBox log { this, anchor = { left = 16, top = 56, right = 26, bottom = 81 }, multiLine = true };
   EditBox serverAddress { this, size = { 182, 27 }, anchor = { top = 16, right = 98 }, contents = "localhost" };
   Button btnConnect
   {
      this, caption = $"Connect", anchor = { top = 24, right = 28 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         connection = ChatConnection
         {
            void NotifyMessage(const String msg)
            {
               form1.log.PutS(" < ");
               form1.log.PutS(msg);
               form1.log.PutS("\n");
            }

            void OnDisconnect(int code)
            {
               DCOMClientObject::OnDisconnect(code);
               if(form1)
                  form1.btnSend.disabled = true;
            }
         };
         if(connection.Connect(serverAddress.contents, 1494))
         {
            connection.Join();
            btnSend.disabled = false;
         }
         return true;
      }
   };
   Button btnHost
   {
      this, text = "Host", position = { 32, 16 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         chatService.Start();
         return true;
      }
   };
   EditBox message { this, size = { 510, 43 }, anchor = { left = 16, right = 114, bottom = 17 } };
   Button btnSend
   {
      this, caption = $"Send", isDefault = true, size = { 60, 37 }, anchor = { right = 28, bottom = 23 }, disabled = true;

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         if(serverConnection)
            SendBackMessage(message.contents);
         else
            connection.SendMessage(message.contents);
         log.PutS(" > ");
         log.PutS(message.contents);
         log.PutS("\n");
         message.Clear();
         return true;
      }
   };
}

Form1 form1 {};

DCOMService chatService { port = 1494 };

// -- Server.ec -----------------------
import "main"

ChatConnection serverConnection;

public remote class ChatConnection
{
public:
   ~ChatConnection()
   {
      if(serverConnection == this && form1)
         form1.btnSend.disabled = false;
   }

   void Join()
   {
      serverConnection = this;
      form1.btnSend.disabled = false;
   }

   void SendMessage(const String msg)
   {
      form1.log.PutS(" < ");
      form1.log.PutS(msg);
      form1.log.PutS("\n");
   }

   virtual void NotifyMessage(const String msg);

private:
}

void SendBackMessage(const String msg)
{
   serverConnection.NotifyMessage(msg);
}

screenshot 9
import "ecere"

static char * indexNames[] =
{
   "index.html",
   "index.htm",
   "home.html",
   "home.htm",
   "welcome.html",
   "welcome.htm",
   "default.html",
   "default.htm"
};

#define NUM_INDEX    (sizeof(indexNames) / sizeof(char *))

static void WriteFileName(File f, char * fileName)
{
   byte ch;
   int c;
   for(c = 0; (ch = fileName[c]); c++)
   {
      if(ch <= 32 || ch > 128)
      {
         byte nibble;
         f.Putc('%');
         nibble = (ch & 0xF0) >> 4;
         f.Putc((nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0'));
         nibble = ch & 0x0F;
         f.Putc((nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0'));
      }
      else
         f.Putc(ch);
   }
}

#define CONTENT_PATH   "."

static void CreateDirectoryListing(File f, char * directory)
{
   FileListing listing { directory };

   f.Puts("<HTML><HEAD></HEAD><BODY>\r\n");

   if(directory[0] && directory[1] && (directory[1] != ':' || (directory[2] && directory[3])))
      f.Puts("<A HREF=../>../</A><BR>\r\n");

   while(listing.Find())
   {
      f.Puts("<A HREF=");
      WriteFileName(f, listing.name);
      if(listing.stats.attribs.isDirectory)
         f.Puts("/");
      f.Puts(">");
      f.Puts(listing.name);
      f.Puts("</A><BR>\r\n");
   }

   f.Puts("\r\n</BODY></HTML>\r\n");
}

static char * GetString(char * string, char * what, int count)
{
   int c;
   bool result = true;
   for(c = 0; what[c]; c++)
   {
      if((count && c >= count) || (string[c] != what[c] && tolower(string[c]) != tolower(what[c])))
         return null;
   }
   return string + c;
}

class HTTPClient : Socket
{
   File f;
   bool close;

   #define ishexdigit(x) (isdigit(x) || (x >= 'a' && x<='f') || (x >= 'A' && x <= 'F'))

   uint OnReceive(const byte * buffer, uint count)
   {
      int c;
      for(c = 0; c<count-1; c++)
      {
         if(buffer[c] == '\r' && buffer[c+1] == '\n')
            break;
      }
      if(c<count)
      {
         char * string = (char *)buffer;

         if((string = GetString((char *)buffer, "GET ", count)))
         {
            char reply[1024];
            char path[MAX_LOCATION];
            char addedPath[MAX_LOCATION];
            int d, i;
            FileAttribs attribs;
            int len = 0;

            strcpy(path, CONTENT_PATH);

            for(d = c; d > 0 && string[d] != ' '; d--);
            for(i = 0; i<d; i++)
            {
               if(string[i] == '%' && ishexdigit(string[i+1]) && ishexdigit(string[i+2]))
               {
                  char digits[3];
                  digits[0] = string[i+1];
                  digits[1] = string[i+2];
                  digits[2] = '\0';
                  addedPath[len++] = (byte)strtol(digits, null, 16);
                  i += 2;
               }
               else
                  addedPath[len++] = string[i];
               addedPath[len] = '\0';
            }

            PathCat(path, addedPath+1);

            attribs = FileExists(path);

            if(attribs.isDirectory)
            {
               if(addedPath[len-1] != '/')
               {
                  strcpy(reply, "HTTP/1.1 301 Moved Permantently\r\n");

                  strcat(reply, "Location: ");
                  strcat(reply, addedPath);
                  strcat(reply, "/\r\n");
                  strcat(reply, "Content-Length: 0\r\n\r\n");
               }
               else
               {
                  int i;
                  char indexFile[MAX_LOCATION];
                  for(i = 0; i<NUM_INDEX; i++)
                  {
                     strcpy(indexFile, path);
                     PathCat(indexFile, indexNames[i]);
                     if(FileExists(indexFile).isFile)
                     {
                        f = FileOpen(indexFile, read);
                        break;
                     }
                  }
                  // List contents if we didn't find an index
                  if(i == NUM_INDEX)
                  {
                     f = TempFile {};
                     if(f)
                     {
                        CreateDirectoryListing(f, path);
                        f.Seek(0, start);
                     }
                  }
               }
            }
            else if(attribs.isFile)
               f = FileOpen(path, read);
            else
               strcpy(reply, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n");

            if(f)
            {
               char extension[MAX_EXTENSION];
               uint size = f.GetSize();
               sprintf(reply, "HTTP/1.1 200 OK\r\n");

               GetExtension(addedPath, extension);
               if(attribs.isDirectory || !strcmp(extension, "html") || !strcmp(extension, "htm"))
                  strcat(reply, "Content-Type: text/html\r\n");
               else
                  strcat(reply, "Content-Type: text/plain\r\n");
               sprintf(strchr(reply, 0), "Content-Length: %d\r\n\r\n", size);
            }
            SendString(reply);
         }
         return c+2;
      }
      return count;
   }
}

class HTTPServer : Service
{
   void OnAccept()
   {
      HTTPClient { this };
   }
}

class HTTPApplication : GuiApplication
{
   bool Init()
   {
      httpServer.Start();
      return true;
   }

   bool Cycle(bool idle)
   {
      bool result = true;
      HTTPClient client, next;
      for(client = (HTTPClient)httpServer.firstClient; client; client = next)
      {
         next = (HTTPClient)client.next;
         if(client.f)
         {
            #define PACKETSIZE      65536

            static byte buffer[PACKETSIZE];
            int read = client.f.Read(buffer, 1, PACKETSIZE);

            if(read)
               client.Send(buffer, read);
            if(client.f.Eof())
            {
               delete client.f;
               if(client.close)
                  client.Disconnect(0);
            }

            result = true;
         }
      }
      return result;
   }
}

HTTPServer httpServer { port = 8080 };

Window serverWindow { size = Size { 320, 200 }, text = "ECERE HTTP Server", hasMinimize = true, hasClose = true };

screenshot 10
import "ecere"

define spacing = 20;
define lineWidth = 10;
define mastery = 97;

define noAvailableMove = -100;

enum TTTSquare { _, X, O };

TTTSquare board[3][3];

class TicTacToe : Window
{
   caption = "TicTacToe";
   background = white;
   hasClose = true;
   clientSize = { 400, 400 };

   FontResource tttFont { "Comic Sans MS", 50, bold = true, window = this };

   TTTSquare turn; turn = X;

   TicTacToe()
   {
      RandomSeed((uint)(GetTime() * 1000));
   }

   TTTSquare FindTicTacToe(TTTSquare state[3][3])
   {
      int i;

      // Diagonal '\'
      if(state[0][0] && state[0][0] == state[1][1] && state[1][1] == state[2][2])
         return state[0][0];
      // Diagonal '/'
      if(state[2][0] && state[2][0] == state[1][1] && state[1][1] == state[0][2])
         return state[2][0];

      for(i = 0; i < 3; i++)
      {
         // Horizontal
         if(state[i][0] && state[i][0] == state[i][1] && state[i][1] == state[i][2])
            return state[i][0];
         // Vertical
         if(state[0][i] && state[0][i] == state[1][i] && state[1][i] == state[2][i])
            return state[0][i];
      }
      return 0;
   }

   float BestMove(TTTSquare t, TTTSquare state[3][3], Point bestMove)
   {
      static int level = 0;
      int x, y;
      float bestRating = noAvailableMove;
      int filled = 0;
      bool couldTicTacToe = false;
      Point badMove; /* A player is likely to see the opponent's tic tac toe in his own tic tac toe spot */
      Point moves[9];
      int numMoves = 0;

      level++;
      for(y = 0; y < 3; y++)
         for(x = 0; x < 3; x++)
            if(state[y][x]) filled++;

      for(y = 0; y < 3; y++)
      {
         for(x = 0; x < 3; x++)
         {
            if(!state[y][x])
            {
               float newRating;
               state[y][x] = t;
               if(FindTicTacToe(state))
                  newRating = 1;
               else
               {
                  Point move;
                  newRating = BestMove((t == X) ? O : X, state, move);
                  if(newRating == noAvailableMove)
                     newRating = 0;
                  newRating = -newRating/2;
                  if(newRating <= -0.25f)
                  {
                     badMove = move;
                     couldTicTacToe = true;
                  }
               }

               state[y][x] = 0;
               if(newRating > bestRating)
               {
                  bestRating = newRating;
                  bestMove = { x, y };
                  numMoves = 1;
                  moves[0] = bestMove;
               }
               else if(level == 1 && newRating == bestRating)
                  moves[numMoves++] = { x, y };
            }
         }
      }
      if(GetRandom(0, 60) > mastery || (filled > 4 && filled < 7 && couldTicTacToe && (bestMove.x != badMove.x || bestMove.y != badMove.y)))
      {
         if(level == 2 && GetRandom(0, 25) > mastery)
            bestRating = -0.5f;
         if(level == 4 && GetRandom(0, 100) > mastery)
            bestRating = -0.125f;
      }
      if(level == 1 && numMoves > 1)
         bestMove = moves[GetRandom(0, numMoves-1)];
      level--;
      return bestRating;
   }

   void MovePlayed()
   {
      TTTSquare result = FindTicTacToe(board);
      if(result)
      {
         MessageBox { caption = "Tic Tac Toe!", contents = (result == X ? "You win!" : "Computer wins!") }.Modal();
         turn = 0;
      }
      else if(turn == X)
      {
         // Computer plays
         Point move { };
         turn = O;
         if(BestMove(turn, board, move) != noAvailableMove)
         {
            board[move.y][move.x] = O;
            MovePlayed();
         }
         else
            turn = 0;
      }
      else
         turn = X;
   }

   void DrawPieces(Surface surface)
   {
      int sw = (clientSize.w - 2*spacing) / 3;
      int sh = (clientSize.h - 2*spacing) / 3;
      int x, y;
      int Xw, Xh, Ow, Oh;

      surface.font = tttFont.font;

      surface.TextExtent("X", 1, &Xw, &Xh);
      surface.TextExtent("O", 1, &Ow, &Oh);

      for(y = 0; y < 3; y++)
      {
         for(x = 0; x < 3; x++)
         {
            TTTSquare p = board[y][x];
            if(p == X)
            {
               surface.foreground = green;
               surface.WriteText(spacing + sw * x + sw / 2 - Xw/2, spacing + sh * y + sh / 2 - Xh/2, "X", 1);
            }
            else if(p == O)
            {
               surface.foreground = red;
               surface.WriteText(spacing + sw * x + sw / 2 - Ow/2, spacing + sh * y + sh / 2 - Oh/2, "O", 1);
            }
         }
      }
   }

   void OnRedraw(Surface surface)
   {
      int sw = (clientSize.w - 2*spacing) / 3;
      int sh = (clientSize.h - 2*spacing) / 3;

      surface.background = blue;

      // Vertical lines
      surface.Area(spacing + sw   - lineWidth / 2, spacing, spacing + sw   + lineWidth / 2-1, clientSize.h - spacing - 1);
      surface.Area(spacing + sw*2 - lineWidth / 2, spacing, spacing + sw*2 + lineWidth / 2-1, clientSize.h - spacing - 1);

      // Horizontal lines
      surface.Area(spacing, spacing + sh   - lineWidth / 2, clientSize.w - spacing - 1, spacing + sh   + lineWidth / 2-1);
      surface.Area(spacing, spacing + sh*2 - lineWidth / 2, clientSize.w - spacing - 1, spacing + sh*2 + lineWidth / 2-1);

      DrawPieces(surface);
   }

   bool OnLeftButtonDown(int mx, int my, Modifiers mods)
   {
      if(turn == X && mx >= spacing && mx < clientSize.w - spacing && my >= spacing && my < clientSize.h - spacing)
      {
         int sw = (clientSize.w - 2*spacing) / 3;
         int sh = (clientSize.h - 2*spacing) / 3;
         mx -= spacing;
         my -= spacing;
         if((mx < sw   - lineWidth / 2 || mx > sw   + lineWidth / 2) && /* 1st vertical line */
            (mx < sw*2 - lineWidth / 2 || mx > sw*2 + lineWidth / 2) && /* 2nd vertical line */
            (my < sh   - lineWidth / 2 || my > sh   + lineWidth / 2) && /* 1st horizontal line */
            (my < sh*2 - lineWidth / 2 || my > sh*2 + lineWidth / 2))   /* 2nd horizontal line */
         {
            int x = mx / sw;
            int y = my / sh;
            if(!board[y][x])
            {
               board[y][x] = X;
               Update(null);
               MovePlayed();
            }
         }
      }
      return true;
   }

   Button btnReset
   {
      this, font = { "Arial", 12 }, caption = "Reset", position = { 8, 8 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         memset(board, 0, sizeof(board));
         turn = X;
         Update(null);
         return true;
      }
   };
}

TicTacToe mainForm {};

screenshot 11
import "EcereAudio"

// There are 12 half-tones in an octave, and the frequency doubles in an octave.
define Do = 1.0;
define Do_ = 1.0594630943592952645618252949463; // The root 12 of 2.
define Re = Do_*Do_;
define Re_ = Re*Do_;
define Mi = Re_*Do_;
define Fa = Mi*Do_;
define Fa_ = Fa*Do_;
define Sol = Fa_*Do_;
define Sol_ = Sol*Do_;
define La = Sol_*Do_;
define La_ = La*Do_;
define Si = La_*Do_;

class MainWindow : Window
{
   text = "A keyboard piano";
   background = black;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   size = { 576, 432 };

   Mixer mixer { };
   Sound piano { "piano.wav" };
   Sound xylophone { "xylophone.wav" };
   Sound cello { "cello.wav" };
   Sound tone { "tone.wav" };
   Sound instrument;

   Voice lastVoice;

   instrument = piano;

   bool OnCreate()
   {
      mixer.systemHandle = systemHandle;
      return true;
   }

   void OnDestroy()
   {
      delete mixer;
   }

   bool OnKeyDown(Key key, unichar ch)
   {
      if(instrument == cello && lastVoice)
         lastVoice.volume = 0;

      switch(key)
      {
         case f1: instrument = piano; break;
         case f2: instrument = xylophone; break;
         case f3: instrument = cello; break;
         case f4: instrument = tone; break;

         // The regular octave on the zxcvbn row, sharps above (asdf)
         case z:     lastVoice = mixer.Play(instrument, 1.0, -1, Do); break;
         case x:     lastVoice = mixer.Play(instrument, 1.0, -.8, Re); break;
         case c:     lastVoice = mixer.Play(instrument, 1.0, -.6, Mi); break;
         case v:     lastVoice = mixer.Play(instrument, 1.0, -.4, Fa); break;
         case b:     lastVoice = mixer.Play(instrument, 1.0, -.2, Sol); break;
         case n:     lastVoice = mixer.Play(instrument, 1.0, 0, La); break;
         case m:     lastVoice = mixer.Play(instrument, 1.0, .2, Si); break;
         case comma: lastVoice = mixer.Play(instrument, 1.0, .4, Do*2); break;
         case period:lastVoice = mixer.Play(instrument, 1.0, .6, Re*2); break;
         case slash: lastVoice = mixer.Play(instrument, 1.0, .8, Mi*2); break;
         case s:     lastVoice = mixer.Play(instrument, 1.0, -.9, Do_); break;
         case d:     lastVoice = mixer.Play(instrument, 1.0, -.7, Re_); break;
         case g:     lastVoice = mixer.Play(instrument, 1.0, -.3, Fa_); break;
         case h:     lastVoice = mixer.Play(instrument, 1.0, -.1, Sol_); break;
         case j:     lastVoice = mixer.Play(instrument, 1.0, .1, La_); break;
         case l:     lastVoice = mixer.Play(instrument, 1.0, .5, Do_*2); break;
         case colon: lastVoice = mixer.Play(instrument, 1.0, .7, Re_*2); break;

         // The lower octave on the qwerty row, sharps above (digits)
         case q:     lastVoice = mixer.Play(instrument, 1.0, 0, Do/2); break;
         case w:     lastVoice = mixer.Play(instrument, 1.0, 0, Re/2); break;
         case e:     lastVoice = mixer.Play(instrument, 1.0, 0, Mi/2); break;
         case r:     lastVoice = mixer.Play(instrument, 1.0, 0, Fa/2); break;
         case t:     lastVoice = mixer.Play(instrument, 1.0, 0, Sol/2); break;
         case y:     lastVoice = mixer.Play(instrument, 1.0, 0, La/2); break;
         case u:     lastVoice = mixer.Play(instrument, 1.0, 0, Si/2); break;
         case i:     lastVoice = mixer.Play(instrument, 1.0, 0, Do); break;
         case o:     lastVoice = mixer.Play(instrument, 1.0, 0, Re); break;
         case p:     lastVoice = mixer.Play(instrument, 1.0, 0, Mi); break;
         case k2:    lastVoice = mixer.Play(instrument, 1.0, 0, Do_/2); break;
         case k3:    lastVoice = mixer.Play(instrument, 1.0, 0, Re_/2); break;
         case k5:    lastVoice = mixer.Play(instrument, 1.0, 0, Fa_/2); break;
         case k6:    lastVoice = mixer.Play(instrument, 1.0, 0, Sol_/2); break;
         case k7:    lastVoice = mixer.Play(instrument, 1.0, 0, La_/2); break;
         case k9:    lastVoice = mixer.Play(instrument, 1.0, 0, Do_); break;
         case k0:    lastVoice = mixer.Play(instrument, 1.0, 0, Re_); break;
      }

      return true;
   }
}

MainWindow mainWindow { };


screenshot 12
import "ecere"

class Form1 : Window
{
   background = 0;
   opacity = 0;
   font = { "Arial", 40 };
   size = { 220, 60 };
#if defined(__linux__)     // Alpha blended windows only work in OpenGL on Linux at the moment
   displayDriver = "OpenGL";
#endif
   alphaBlend = true;
   moveable = true;
   stayOnTop = true;
   showInTaskBar = false;

   Timer timer
   {
      userData = this, started = true, delay = 0.2;

      bool DelayExpired()
      {
         Update(null);
         return true;
      }
   };

   bool OnLeftButtonDown(int x, int y, Modifiers mods)
   {
      MenuWindowMove(null, mods);
      return true;
   }
   bool OnKeyDown(Key key, unichar ch)
   {
      if(key == escape) Destroy(0);
      return true;
   }

   void OnRedraw(Surface surface)
   {
      DateTime time;
      surface.Clear(colorBuffer);
      time.GetLocalTime();
      surface.SetForeground({ 128, darkGray });
      surface.WriteTextf(0,0, "%02d:%02d:%02d",
         time.hour, time.minute, time.second);
      surface.SetForeground({ 192, teal });
      surface.WriteTextf(2, 2, "%02d:%02d:%02d",
         time.hour, time.minute, time.second);

   }
}

Form1 form1 { };

screenshot 13
import "HTMLView"

//define homeLocation = "localhost:8080";
//define homeLocation = "www.ecere.com";
define homeLocation = "google.com";

class AddressBar : Window
{
   background = activeBorder;
   tabCycle = true;
   Button back
   {
      this, bevelOver = true, inactive = true, anchor = Anchor { left = 0, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altLeft, bitmap = { "<:ecere>actions/goPrevious.png" };
      disabled = true;

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         ((Explorer)parent).Back();
         return true;
      }
   };
   Button forward
   {
      this, bevelOver = true, inactive = true, anchor = Anchor { left = 24, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = altRight, bitmap = { "<:ecere>actions/goNext.png" };
      disabled = true;

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         ((Explorer)parent).Forward();
         return true;
      }
   };
   Button home
   {
      this, bevelOver = true, inactive = true, anchor = Anchor { left = 52, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = ctrlH, bitmap = { "<:ecere>actions/goHome.png" };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         ((Explorer)parent).Go(homeLocation);
         return true;
      }
   };
   Button refresh
   {
      this, bevelOver = true, inactive = true, anchor = Anchor { left = 76, top = 0, bottom = 0 }, size = Size { 24 }, hotKey = f5, bitmap = { "<:ecere>actions/viewRefresh.png" };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         ((Explorer)parent).Refresh();
         return true;
      }
   };
   Label { this, anchor = Anchor { left = (96+12) }, labeledWindow = address };
   EditBox address
   {
      this, text = "Address:", anchor = Anchor { left = (16+48+96), right = 60, top = 0, bottom = 0 }, hotKey = altD;

      bool NotifyKeyDown(EditBox editBox, Key key, unichar ch)
      {
         if(!go.disabled && (SmartKey)key == enter)
            ((Explorer)parent).Go(editBox.contents);
         return true;
      }

      void NotifyUpdate(EditBox editBox)
      {
         String location = ((Explorer)parent).htmlView.location;
         go.disabled = !strcmp(location ? location : "", editBox.contents);
      }
   };
   Button go
   {
      this, bevelOver = true, inactive = true, text = "Go!", anchor = Anchor { top = 0, right = 0, bottom = 0 }, size = Size { 60 }, hotKey = altG;

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         ((Explorer)parent).Go(address.contents);
         return true;
      }
   };

   bool OnKeyHit(Key key, unichar ch)
   {
      if(key == escape)
         ((Explorer)parent).htmlView.MakeActive();
      return true;
   }
}

class Explorer : Window
{
   FileDialog openFileDialog { caption = "Select a file or enter a URL... " };
   FileDialog saveFileDialog { type = save, caption = "Save to file... " };

   icon = { ":newb.png" };

   tabCycle = true;
   background = activeBorder;
   hasMenuBar = true;
   hasStatusBar = true;
   borderStyle = sizable;
   hasClose = true;
   hasMaximize = true;
   hasMinimize = true;
   text = "Ecere Web Browser";
   size = Size { 800, 600 };
   state = maximized;

   AddressBar addressBar { this, borderStyle = bevel, anchor = Anchor { top = 0, left = 0, right = 0 }, size.h = 26, hotKey = altD };
   HTMLView htmlView { this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true, anchor = Anchor { left = 0, right = 0, top = 26, bottom = 0 }, NotifyPageOpened = PageOpened, OnOpen = OnOpen };

   // File Menu
   menu = Menu {};
   Menu fileMenu { menu, "File", f };
   MenuItem newWindowItem
   {
      fileMenu, "New Window\tCtrl+N", n, ctrlN;

      bool NotifySelect(MenuItem selection, Modifiers mods)
      {
         Explorer { state = normal }.Create();
         return true;
      }
   };
   MenuItem openItem
   {
      fileMenu, "Open...\tCtrl+O", o, ctrlO;

      bool NotifySelect(MenuItem selection, Modifiers mods)
      {
         if(openFileDialog.Modal() == ok)
            Go(openFileDialog.filePath);
         return true;
      }
   };
   // MenuItem closeItem { fileMenu, "Close\tCtrl-F4", c, NotifySelect = MenuFileClose };
   MenuDivider { fileMenu };
   MenuItem saveAsItem
   {
      fileMenu, "Save As...";

      bool NotifySelect(MenuItem selection, Modifiers mods)
      {
         char fileName[MAX_LOCATION];
         GetLastDirectory(htmlView.location, fileName);
         strcpy(fileName, saveFileDialog.currentDirectory);
         PathCat(fileName, fileName);
         saveFileDialog.filePath = fileName;

         if(saveFileDialog.Modal() == ok)
         {
            File f = FileOpen(htmlView.location, read);
            if(f)
            {
               if(!f.CopyTo(saveFileDialog.filePath))
               {
                  String e = PrintString("Error saving to ", saveFileDialog.filePath);
                  MessageBox { contents = e }.Modal();
                  delete e;
               }
            }
         }
         return true;
      }
   };
   MenuDivider { fileMenu };
   MenuItem exitItem { fileMenu, "Exit\tAlt+F4", x, NotifySelect = MenuFileExit };

   bool PageOpened()
   {
      char caption[MAX_LOCATION];

      strcpy(caption, "Ecere Web Browser - ");
      strcat(caption, htmlView.title);

      text = caption;
      addressBar.address.Clear();
      addressBar.address.PutS(htmlView.fileName);
      return true;
   }

   bool OnPostCreate()
   {
      addressBar.MakeActive();
      return true;
   }

   Array<String> history { };
   int historyPos;

   ~Explorer()
   {
      history.Free();
   }

   bool HTMLView::OnOpen(char * href)
   {
      bool result = true;
      char newLocation[MAX_LOCATION];
      Explorer explorer = (Explorer)parent;

      strcpy(newLocation, location ? location : "");
      if(newLocation[strlen(newLocation)-1] != '/')
         PathCat(newLocation, "..");
      if(href[0] == '/' && href[1] == '/')
      {
         strcpy(newLocation, "http:");
         strcat(newLocation, href);
      }
      else
         PathCat(newLocation, href);

      if(explorer.history.count > explorer.historyPos+1)
      {
         int i;
         for(i = explorer.historyPos+1; i < explorer.history.count; i++)
            delete explorer.history[i];
         explorer.history.count = explorer.historyPos+1;
      }
      explorer.historyPos = explorer.history.count-1;
      explorer.addressBar.back.disabled = (explorer.historyPos == 0);
      explorer.addressBar.forward.disabled = (explorer.historyPos >= explorer.history.count-1);

      Open(newLocation, null);

      explorer.history.Add(CopyString(location));
      explorer.historyPos = explorer.history.count-1;

      explorer.addressBar.back.disabled = (explorer.historyPos == 0);
      explorer.addressBar.forward.disabled = (explorer.historyPos >= explorer.history.count-1);
      return result;
   }

   void Go(const String location)
   {
      if(history.count > historyPos+1)
      {
         int i;
         for(i = historyPos+1; i < history.count; i++)
            delete history[i];
         history.count = historyPos+1;
      }
      history.Add(CopyString(location));
      historyPos = history.count-1;
      addressBar.back.disabled = (historyPos == 0);
      addressBar.forward.disabled = (historyPos >= history.count-1);
      htmlView.MakeActive();
      htmlView.location = location;
   }

   void Refresh()
   {
      htmlView.MakeActive();
      if(history.count)
         htmlView.location = history[historyPos];
   }

   bool Forward()
   {
      if(historyPos < history.count-1)
      {
         historyPos++;
         addressBar.back.disabled = (historyPos == 0);
         addressBar.forward.disabled = (historyPos >= history.count-1);
         htmlView.location = history[historyPos];
         return true;
      }
      return false;
   }

   bool Back()
   {
      if(historyPos > 0)
      {
         historyPos--;
         addressBar.back.disabled = (historyPos == 0);
         addressBar.forward.disabled = (historyPos >= history.count-1);
         htmlView.location = history[historyPos];
         return true;
      }
      return false;
   }

   bool OnKeyHit(Key key, unichar ch)
   {
      if(key == ctrlR)
      {
         Refresh();
         return false;
      }
      return true;
   }
}

#ifndef ECERE_MODULE

class BrowserApp : GuiApplication
{
   //driver = "OpenGL";
   Explorer explorerWindow {};

   bool Init()
   {
      app = this;

   #ifndef ECERE_MODULE
      if(argc > 1)
         explorerWindow.Go(argv[1]);
      else
   #endif
         explorerWindow.Go(homeLocation);
      return true;
   }
}

BrowserApp app;

#else

extern GuiApplication app;

#endif


screenshot 14
import "ecere"

class Form1 : Window
{
   text = "Form1";
   background = activeBorder;
   borderStyle = sizable;
   hasMaximize = true;
   hasMinimize = true;
   hasClose = true;
   size = { 640, 480 };

   Button button1
   {
      this, text = "button1", position = { 240, 176 };

      bool NotifyClicked(Button button, int x, int y, Modifiers mods)
      {
         MessageBox { contents = "Hello, world!" }.Modal();
         return true;
      }
   };
}

Form1 form1 {};


screenshot 15
#include <stdio.h>

struct Vector
{
   float x, y;
};

bool FloatToString(char * string, float * value)
{
   char * end;
   *value = (float)strtod(string, &end);
   return end > string;
}

char GetOperation()
{
   char operation = 0;
   PrintLn("Chose an operation to perform: +, -, *, /, m (module/length). q to quit.");
   do
   {
      char input[1024];
      gets(input);
      switch(input[0])
      {
         case '+': case '-': case '*': case '/': case 'm': case 'q':
            operation = input[0];
            break;
         default:
            PrintLn("Invalid Operation");
      }
   } while(!operation);
   return operation;
}

float GetScalar()
{
   float scalar;
   char input[1024];
   gets(input);
   while(!FloatToString(input, &scalar))
   {
      PrintLn("Print enter a valid numeric value");
      gets(input);
   }
   return scalar;
}

Vector GetVector()
{
   Vector vector;
   char input[1024];
   gets(input);
   while(!vector.OnGetDataFromString(input))
   {
      PrintLn("Print enter a valid 2D vector value");
      gets(input);
   }
   return vector;
}

class Lab5VectorApp : Application
{
   void Main()
   {
      while(true)
      {
         Vector vector1, vector2;
         float scalar;
         char operation = GetOperation();
         if(operation == 'q') break;

         PrintLn("Enter the first operand:");
         vector1 = GetVector();

         if(operation != 'm')
         {
            PrintLn("Enter the second operand:");
            if(operation == '+' || operation == '-')
               vector2 = GetVector();
            else
               scalar = GetScalar();
         }
         if(operation == '/' && scalar == 0)
            PrintLn("Cannot divide by 0");
         else
         {
            switch(operation)
            {
               case '+': PrintLn(vector1, " + ", vector2, " = ", Vector { vector1.x + vector2.x, vector1.y + vector2.y }); break;
               case '-': PrintLn(vector1, " - ", vector2, " = ", Vector { vector1.x - vector2.x, vector1.y - vector2.y }); break;
               case '*': PrintLn(vector1, " * ", scalar, " = ", Vector { vector1.x * scalar, vector1.y * scalar }); break;
               case '/': PrintLn(vector1, " / ", scalar, " = ", Vector { vector1.x / scalar, vector1.y / scalar }); break;
               case 'm': PrintLn("|",vector1,"| = ", sqrt(vector1.x * vector1.x + vector1.y * vector1.y)); break;
            }
         }
      }
      system("pause");
   }
}


screenshot 16
#include <stdio.h>

enum KnownColor
{
   black   = 0,
   red     = 0xFF0000,
   green   = 0x00FF00,
   blue    = 0x0000FF,
   yellow  = 0xFFFF00,
   magenta = 0xFF00FF,
   cyan    = 0x00FFFF,
   white   = 0xFFFFFF,
};

char GetOperation()
{
   char operation = 0;
   PrintLn("Chose an operation to perform: +, -. q to quit.");
   do
   {
      char input[1024];
      gets(input);
      switch(input[0])
      {
         case '+': case '-': case 'q':
            operation = input[0];
            break;
         default:
            PrintLn("Invalid Operation");
      }
   } while(!operation);
   return operation;
}

KnownColor GetOperand()
{
   KnownColor operand;
   char input[1024];
   gets(input);
   while(!operand.OnGetDataFromString(input))
   {
      PrintLn("Please enter a known color (black, red, green, blue, yellow, magenta, cyan or white)");
      gets(input);
   }
   return operand;
}

KnownColor ComputeOperation(char operation, KnownColor operand1, KnownColor operand2)
{
   switch(operation)
   {
      case '+': return operand1 + operand2;
      case '-': return (KnownColor)(operand1 - operand2);
   }
   return 0;
}

class Lab5ColorsApp : Application
{
   void Main()
   {
      while(true)
      {
         KnownColor operand1, operand2;
         char operation = GetOperation();
         if(operation == 'q') break;

         PrintLn("Enter the first operand:");
         operand1 = GetOperand();
         PrintLn("Enter the second operand:");
         operand2 = GetOperand();
         {
            KnownColor result = ComputeOperation(operation, operand1, operand2);
            PrintLn(operand1, " ", operation, " ", operand2, " = ", result);
         }
      }
      system("pause");
   }
}


screenshot 17
import "ecere"
#include <stdio.h>

class Spell
{
public:
   int difficulty;
   int damage;
   int manaCost;

   virtual void Backfire(Creature self, Creature opponent)
   {
      self.health -= damage/4;
   }

   virtual void Success(Creature self, Creature opponent)
   {
      if(damage > opponent.health) damage = opponent.health;
      PrintLn(self._class.name, " did ", damage, " damage to ", opponent._class.name, ".");
      opponent.health -= damage;
   }
}

class FireBall : Spell { difficulty = 20, damage = 8; manaCost = 5; };
class Lightning : Spell  { difficulty = 10, damage = 4; manaCost = 3; };
class Healing : Spell
{
   difficulty = 20;
   manaCost = 5;
   void Success(Creature self, Creature opponent)
   {
      self.health += self.maxHealth / 5;
      if(self.health > self.maxHealth) self.health = self.maxHealth;
   }

   void Backfire(Creature self, Creature opponent)
   {
      self.health -= damage/4;
   }
};

class Creature
{
public:
   int xp;
   int health, maxHealth;
   int mana, maxMana;
   int dexterity;
   int magic;
   int strength;
   int gold;
   Array<Spell> spells;
   Array<Equipment> equipment;

   void CastSpell(Spell spell, Creature opponent)
   {
      if(mana >= spell.manaCost)
      {
         int r = GetRandom(0, spell.difficulty);
         mana -= spell.manaCost;
         if(magic >= r)
         {
            PrintLn(_class.name, " cast ", spell._class.name, " successfully.");
            spell.Success(this, opponent);
         }
         else if(r > magic * 2)
         {
            PrintLn(_class.name, "'s ", spell._class.name, " backfired.");
            spell.Backfire(this, opponent);
         }
         else
            PrintLn(_class.name, " unsucessfully cast ", spell._class.name, ".");
      }
   }

   void Attack(Creature opponent)
   {
      Weapon weapon = (Weapon)(equipment ? equipment[EquipmentSlot::rightHand] : null);
      int d, r, o;
      if(!weapon) weapon = bareHand;
      o = GetRandom(0, opponent.dexterity);
      d = GetRandom(0, dexterity - weapon.difficulty);
      if(d > o)
      {
         int d = GetRandom(1, strength);
         int where = GetRandom(0, 100);
         int armor = 0;
         int howBad = 0;
         EquipmentSlot slot;
         int damage;

         if(where < 60)
         {
            slot = body;
            howBad = 2;
         }
         else if(where < 80)
         {
            slot = head;
            howBad = 3;
         }
         else if(where < 95)
         {
            slot = legs;
            howBad = 1;
         }
         else
         {
            slot = feet;
            howBad = 1;
         }
         if(opponent.equipment && opponent.equipment[slot])
            armor = ((Armor)opponent.equipment[slot]).decDamage;
         damage = Max(1, (d * weapon.damage - armor) * howBad / 10);
         if(damage > opponent.health) damage = opponent.health;
         opponent.health -= damage;
         PrintLn(_class.name, " did ", damage, " damage to ", opponent._class.name, ".");
      }
      else
         PrintLn(_class.name, " missed.");
   }
}

Weapon bareHand { difficulty = 3, damage = 1 };

enum EquipmentSlot { leftHand, rightHand, head, feet, body, legs, ring, ring2, ring3, ring4 };

class Equipment
{
public:
   int value;
   EquipmentSlot slot;
   bool twoHands;

   virtual void Show();
}

class Armor : Equipment
{
public:
   int decDamage;

   void Show()
   {
      Print("Damage -", decDamage);
   }
}

class Weapon : Equipment
{
public:
   int difficulty;
   int damage;

   void Show()
   {
      Print("Difficulty: ", difficulty, ", Damage +", damage);
   }
}

class LightShield : Armor { decDamage = 2; slot = leftHand; value = 20; };
class HeavyShield : Armor { decDamage = 5; slot = leftHand; value = 100; };
class Helmet      : Armor { decDamage = 5; slot = head; value = 60; };
class SteelBoots  : Armor { decDamage = 2; slot = feet; value = 40; };
class LightArmor  : Armor { decDamage = 4; slot = body; value = 40; };
class PlateArmor  : Armor { decDamage = 8; slot = body; value = 150; };
class PlateLeggings : Armor { decDamage = 3; slot = legs; value = 50; };

class Dagger      : Weapon { difficulty = 2, damage = 2, value  = 10, slot = rightHand; };
class LongSword   : Weapon { difficulty = 4, damage = 4, value  = 40, slot = rightHand; };
class BattleSword : Weapon { difficulty = 8, damage = 10, value = 100, slot = rightHand, twoHands = true; };

class Slug           : Creature { xp = 10;  maxHealth = 10;  dexterity = 7; strength = 5; gold = 1; }
class GiantRat       : Creature { xp = 30;  maxHealth = 20;  dexterity = 9; strength = 8; gold = 3; }
class GiantSpider    : Creature { xp = 50;  maxHealth = 30;  dexterity = 20; strength = 10; gold = 4; }
class Bat            : Creature { xp = 70;  maxHealth = 10;  dexterity = 40; strength = 5; gold = 6; }
class Goblin         : Creature
{
   xp = 120; maxHealth = 50;  dexterity = 50; strength = 25; gold = 10;
}
class Ghoul          : Creature { xp = 250; maxHealth = 70;  dexterity = 20; strength = 30; gold = 30; }
class DarkKnight     : Creature
{
   xp = 500; maxHealth = 100; dexterity = 50; strength = 50; gold = 50; magic = 30; maxMana = 50;
   spells = { [ FireBall { } ] };
   equipment = { [ HeavyShield { }, LongSword { }, Helmet { }, SteelBoots { }, PlateArmor { }, PlateLeggings { }, null, null, null, null ] };
};

Array<Class> badGuys { [ class(Slug), class(GiantRat), class(GiantSpider), class(Goblin), class(Bat), class(DarkKnight), class(Ghoul) ] };

class EvilSorcerer   : Creature
{
   xp = 1000; maxHealth = 1000; dexterity = 75; strength = 50; gold = 20000; magic = 50; maxMana = 500;
   equipment = { [ HeavyShield { }, LongSword { }, Helmet { }, SteelBoots { }, PlateArmor { }, PlateLeggings { }, null, null, null, null ] };
   spells = { [ FireBall { }, Lightning { }, Healing { } ] };
}

Array<Equipment> shopInventory
{
   [
      Dagger { },
      LongSword { },
      BattleSword { },

      LightShield { },
      HeavyShield { },
      Helmet { },
      SteelBoots { },
      LightArmor { },
      PlateArmor { },
      PlateLeggings { }
   ]
};

enum GameState { realm, shop, fight, training, sorcerer, end };

class Player : Creature
{
public:
   int manaPotions;
   int healthPotions;
   int training;
   spells = { [ FireBall { }, Lightning { }, Healing { } ] };
   equipment = { [ null, null, null, null, null, null, null, null, null, null ] };
}

Player player { xp = 0, maxHealth = 40, health = 40, mana = 20, maxMana = 20, magic = 10, strength = 10, dexterity = 10, gold = 50, training = 2 };
Creature opponent;

class RPGApp : Application
{
   GameState state;
   char command[1024];

   void PrintStatus()
   {
      PrintLn("");
      switch(state)
      {
         case sorcerer:
         case fight:
            PrintLn("You are fighting a ", opponent._class.name);
            PrintLn(opponent._class.name, "'s Health: ", opponent.health, "/",opponent.maxHealth);
            PrintLn("Your Health: ", player.health, "/", player.maxHealth, ", Mana: ", player.mana, "/",player.maxMana);
            PrintLn("[A]ttack");
            PrintLn("[R]un");
            if(player.healthPotions)
               PrintLn("[H]ealth potion");
            if(player.manaPotions)
               PrintLn("[M]ana potion");
            if(player.spells.count)
            {
               int n = 1;
               PrintLn("Cast a spell:");
               for(s : player.spells)
                  PrintLn("   ", n++, ". ", s._class.name);
            }
            break;
         case shop:
         {
            int n = 1;
            PrintLn("Welcome to the village shop! What could we interest you in?");
            PrintLn("   [H]ealth potions (10)  [M]ana potions (15)  Go [B]ack to the realm");
            PrintLn("   You have ", player.gold, " gold coins");
            for(i : shopInventory)
            {
               Print("   ", n++, ". ", i._class.name, ": ");
               i.Show();
               PrintLn(" (", i.value, ")");
            }
            break;
         }
         case realm:
            PrintLn("You are wandering in the realm. What would you like to do?");
            PrintLn("[F]ight bad guys   Visit the [S]hop   [R]est     St[a]ts");
            if(player.xp >= 1000)
               PrintLn("Are you ready to rescue the [P]rincess?");
            if(player.training)
               PrintLn("Do you want to [T]rain? You have ", player.training, " training points.");
            break;
         case training:
            PrintLn("You have ", player.training, " training points. What would you like to improve?");
            PrintLn("[H]ealth");
            PrintLn("[M]ana");
            PrintLn("[S]trength");
            PrintLn("[D]exterity");
            PrintLn("Ma[g]ic");
            PrintLn("   Go [B]ack to the realm");
            break;
      }
   }

   void GetCommand()
   {
      gets(command);
      strlwr(command);
   }
   bool AreYouSure()
   {
      char input[1024];
      gets(input);
      strlwr(command);
      return input[0] == 'y';
   }

   void OpponentAttacks()
   {
      if(opponent.health > 0 && player.health > 0)
      {
         if(opponent.spells && opponent.spells.count && opponent.mana > opponent.maxMana / 5)
         {
            if(GetRandom(0,1) == 1)
            {
               int s = GetRandom(0, opponent.spells.count-1);
               if(opponent.mana >= opponent.spells[s].manaCost)
                  opponent.CastSpell(opponent.spells[s], player);
               else
                  opponent.Attack(player);
            }
            else
               opponent.Attack(player);
         }
         else
            opponent.Attack(player);
      }
      if(player.health <= 0)
      {
         PrintLn("You died :(");
         state = end;
      }
      else if(opponent.health <= 0)
      {
         int trainingPointsBefore = player.xp / 50;
         PrintLn("Congratulations! You won the fight. You gained ", opponent.xp/10, " xp points and ", opponent.gold, " gold.");
         player.gold += opponent.gold;
         player.xp += opponent.xp / 10;
         player.training += player.xp / 50 - trainingPointsBefore;
         delete opponent;
         if(state == sorcerer)
            PrintLn("You saved the princess!! The end.");
         state = realm;
      }
   }

   void FindOpponent()
   {
      while(true)
      {
         int c = GetRandom(0, badGuys.count-1);
         opponent = eInstance_New(badGuys[c]);
         if(opponent.xp < 40 || opponent.xp <= player.xp)
            break;
         delete opponent;
      }
      opponent.health = opponent.maxHealth;
      opponent.mana = opponent.maxMana;
      state = fight;
   }

   void ProcessCommand()
   {
      if(command[0] == 'q')
      {
         PrintLn("Are you sure you want to quit?");
         if(AreYouSure())
            state = end;
      }
      switch(state)
      {
         case shop:
            switch(command[0])
            {
               case 'b': state = realm; break;
               case 'm': case 'h':
               {
                  int price = (command[0] == 'm') ? 15 : 10;
                  if(player.gold < price)
                     PrintLn("You do not have enough gold!");
                  else
                  {
                     (command[0] == 'm') ? player.manaPotions++ : player.healthPotions++;
                     player.gold -= price;
                  }
                  break;
               }
               default:
               {
                  int item = atoi(command);
                  if(item && item <= shopInventory.count)
                  {
                     Equipment eq = shopInventory[item-1];
                     EquipmentSlot slot = eq.slot;
                     int tradeIn;
                     Equipment tradeIn1 = null, tradeIn2 = null;
                     if(slot == ring)
                        while(player.equipment[slot] && slot < ring4)
                           slot++;

                     if(slot == rightHand && eq.twoHands)
                     {
                        if(player.equipment[EquipmentSlot::leftHand])
                           tradeIn1 = player.equipment[EquipmentSlot::leftHand];
                        if(player.equipment[EquipmentSlot::rightHand])
                           tradeIn2 = player.equipment[EquipmentSlot::rightHand];
                     }
                     else if((slot == leftHand || slot == rightHand) && player.equipment[EquipmentSlot::rightHand] && player.equipment[EquipmentSlot::rightHand].twoHands)
                     {
                        if(player.equipment[EquipmentSlot::rightHand])
                           tradeIn1 = player.equipment[EquipmentSlot::rightHand];
                     }
                     else if(player.equipment[slot])
                        tradeIn1 = player.equipment[slot];

                     tradeIn = ((tradeIn1 ? tradeIn1.value : 0) + (tradeIn2 ? tradeIn2.value : 0)) / 2;
                     if(player.gold + tradeIn < eq.value)
                        PrintLn("You do not have enough gold!");
                     else
                     {
                        if(player.equipment[slot])
                           PrintLn("You will need to trade in your ", player.equipment[slot]._class.name, ", for ", tradeIn, ".");

                        PrintLn("Are you sure you want to buy this ", eq._class.name, " for ", eq.value, "?");
                        if(AreYouSure())
                        {
                           if(tradeIn1) shopInventory.Add(tradeIn1);
                           if(tradeIn2) shopInventory.Add(tradeIn2);
                           player.equipment[slot] = eq;
                           player.gold += tradeIn - eq.value;

                           shopInventory.Remove(shopInventory.Find(eq));

                           //shopInventory.TakeOut(eq);
                        }
                     }
                  }
               }
            }
            break;
         case fight:
         case sorcerer:
         {
            bool fightBack = false;
            switch(command[0])
            {
               case 0:
               case 'a':
                  player.Attack(opponent);
                  fightBack = true;
                  break;
               case 'm':
                  if(player.manaPotions)
                  {
                     player.manaPotions--;
                     player.mana += player.maxMana / 5;
                     if(player.mana > player.maxMana) player.mana = player.maxMana;
                  }
                  break;
               case 'h':
                  if(player.healthPotions)
                  {
                     player.healthPotions--;
                     player.health += player.maxHealth / 5;
                     if(player.health > player.maxHealth) player.health = player.maxHealth;
                  }
                  break;
               case 'r':
                  OpponentAttacks();
                  if(player.health > 0)
                     state = realm;
                  break;
               default:
               {
                  int item = atoi(command);
                  if(item && item <= player.spells.count)
                  {
                     if(player.mana >= player.spells[item-1].manaCost)
                     {
                        player.CastSpell(player.spells[item-1], opponent);
                        fightBack = true;
                     }
                     else
                        PrintLn("Not enough mana to cast that spell.");
                  }
               }
            }
            if(fightBack)
               OpponentAttacks();
            break;
         }
         case realm:
            switch(command[0])
            {
               case 'a':
               {
                  EquipmentSlot c;
                  PrintLn("\nYour statistics:");
                  PrintLn("XP: ", player.xp);
                  PrintLn("Health: ", player.health, "/", player.maxHealth);
                  PrintLn("Mana:   ", player.mana, "/", player.maxMana);
                  PrintLn("Strength: ", player.strength, ", Dexterity: ", player.dexterity, ", Magic: ", player.magic);
                  PrintLn("Gold: ", player.gold);
                  PrintLn("Equipment:");
                  for(c = leftHand; c <= ring4; c++)
                  {
                     Equipment eq = player.equipment[c];
                     if(eq)
                     {
                        Print(c, ": ", eq._class.name, ": ");
                        eq.Show();
                        PrintLn("");
                     }
                  }
                  if(player.manaPotions)
                     PrintLn(player.manaPotions, " mana potions");
                  if(player.healthPotions)
                     PrintLn(player.healthPotions, " health potions");
                  if(player.training)
                     PrintLn(player.training, " training points");
                  break;
               }
               case 's': state = shop; break;
               case 'b': state = realm; break;
               case 'f': FindOpponent(); break;
               case 't': state = training; break;
               case 'p':
                  if(player.xp > 1000)
                  {
                     opponent = EvilSorcerer { };
                     state = sorcerer;
                  }
                  break;
               case 0:
               case 'r':
                  if(GetRandom(0, 3) == 0)
                  {
                     PrintLn("Your rest was interrupted!");
                     FindOpponent();
                     OpponentAttacks();
                  }
                  else
                  {
                     player.health += player.maxHealth / 5;
                     if(player.health > player.maxHealth) player.health = player.maxHealth;
                     player.mana += player.maxMana / 5;
                     if(player.mana > player.maxMana) player.mana = player.maxMana;
                  }
                  break;
            }
            break;
         case training:
         {
            bool valid = true;
            switch(command[0])
            {
               case 'h': player.health += player.maxHealth / 5; player.maxHealth += player.maxHealth / 5; break;
               case 'm': player.mana += player.maxMana / 5; player.maxMana += player.maxMana / 5; break;
               case 's': player.strength += player.strength / 5; break;
               case 'd': player.dexterity += player.dexterity / 5; break;
               case 'g': player.magic += player.magic / 5; break;
               case 'b': state = realm;
               default: valid = false;
            }
            if(valid)
            {
               player.training--;
               if(!player.training)
                  state = realm;
            }
            break;
         }
      }
   }

   void Main()
   {
      RandomSeed((uint)(GetTime()*1000));
      PrintLn("Welcome to this great minimalist RPG!");
      PrintLn("You will need to save the princess from an Evil Sorcerer.");
      PrintLn("But first you should wander the realm to fight the sorcerer's minions, ");
      PrintLn("gaining experience and equipment in the process. You will need to reach");
      PrintLn("at least 1000 experience points to search for the sorcerer's hideout.");
      PrintLn("At any time you can [Q]uit.");
      while(state != end)
      {
         PrintStatus();
         GetCommand();
         ProcessCommand();
      }
      system("pause");
   }
}


screenshot 18