Kirjautuminen

Haku

Tehtävät

Joulukalenteri 2009: Limbo

Kirjoittaja: jalski (16.12.2009)

Limbo on Inferno käyttöjärjestelmän ohjelmointikieli. Sen suunnittelivat Bell Labs:illa Sean Dorward, Phil Winterbottom ja Rob Pike. Limbo soveltuu hyvin hajautettujen järjestelmien ja CSP:tä (Communicating Sequential Processes) hyödyntävien ohjelmien kehittämiseen.

Limbo kääntäjä tuottaa laitteistoriippumatonta tavukoodia. Tavukoodin tulkkaa Infernon Dis virtuaalikone, tai se käännetään juuri ennen ajoa (JIT) suorituskyvyn parantamiseksi.

Limbon ominaisuuksia ovat muun muassa: vahva tyyppitarkastus (käännös -ja ajonaikainen), roskienkeruu, kanavat säikeiden synkronointiin ja kommunikointiin säikeiden välillä, dynaaminen moduulien lataaminen ja yksinkertainen abstrakti tietotyyppi.

Infernoa voi ajaa natiivina käyttöjärjestelmänä tai emu:lla useimpien suosittujen käyttöjärjestelmien päällä.

Inferno sisältää kaikki tarvittavat työkalut Limbo-ohjelmointiin, kuten kääntäjän, debuggerin ja editorin.

Virheen etsiminen limbo-ohjelmasta on helppoa: Vikatilanteen sattuessa ohjelma jää broken-tilaan, jolloin Infernon task mananagerista voidaan valita kyseinen ohjelma ja käynnistää debuggeri. Debuggeri näyttää mihin kohtaan ohjelma on pysähtynyt ja yleensä muuttujien, taulukoiden ja muiden tietotyyppien sisältöjä tarkastelemalla selviää syykin varsin nopeasti.

Esimerkki: Tekstin tulostus

Seuraava ohjelma tulostaa rivin tekstiä:

implement Hello;

include "sys.m";
    sys: Sys;

include "draw.m";


Hello : module
{
   init : fn(nil : ref Draw->Context, argv : list of string);
};



init(nil: ref Draw->Context, nil: list of string)
{
   sys = load Sys Sys->PATH;
   sys->print("Hyvää joulua!\n");
}

Esimerkki: Fibonaccin luvut

Seuraava ohjelma tulostaa Fibonaccin lukuja:

implement Fibonacci;

include "sys.m";
   sys : Sys;

include "draw.m";

MAX : con 100;

Fibonacci : module
{
   init : fn(nil : ref Draw->Context, nil : list of string);
};

init(nil : ref Draw->Context, nil : list of string)
{
   sys = load Sys Sys->PATH;

   fibonacci(0, 1);
}


fibonacci(a, b : int)
{

   if (a + b < MAX)
   {
      sys->print("%-3d\n", a + b);
      fibonacci(b, a + b);
   }

}

Esimerkki: Käyttöliittymä ja tiedosto-operaatiot

Seuraava ohjelma toteuttaa yksinkertaisen graafisen puhelinmuistion käyttäen apuna Infernon mukana tulevaa rawdbfs:sää.

Tuettuna ovat yleisimmät operaatiot: lajittelu, lisäys, poisto, muokkaus ja etsiminen.

#Simppeli puhelinmuistio
#
#Jokusen hommelin voisi tehdä nätimminkin, mutta toisaalta ..
#
# Käyttö: Ohjelma olettaa, että tietokanta löytyy hakemistosta: "/mnt/puhdb"
#
# Tietokanta luodaan tekemällä uusi tiedosto johonkin:
# esim. komento: "touch $home/puhdb"
# Luo "puhdb" nimisen tiedoston käyttäjän kotihakemistoon.
#
# Ennen ohjelman käynnistämistä rawdbfs pitää olla käynnissä:
# esim.: "rawdbfs $home/puhdb /mnt/puhdb"
#
#
implement Puhelintiedot;

include "sys.m";
   sys: Sys;
   Dir: import sys;

include "bufio.m";
   bufio: Bufio;
   Iobuf: import bufio;

include "readdir.m";

include "string.m";
   str: String;

include "draw.m";
   draw: Draw;
   Screen, Display, Image, Context, Point, Rect: import draw;

include "tk.m";
   tk: Tk;
   Toplevel: import tk;

include "tkclient.m";
   tkclient: Tkclient;
   Hide: import tkclient;

include "dialog.m";
   dialog: Dialog;


Puhelintiedot : module
{
   init: fn(ctxt: ref Draw->Context, argv: list of string);
};

# Ikkunan yläpalkin napit
WINBUT : con Hide;

DATABASEDIR: con "/mnt/puhdb";

ctxt: ref Draw->Context;
main : ref Tk->Toplevel;


Henkilo : adt
{
   sukunimi : string;
   etunimi : string;
   puhelin : string;
};


Entry : adt
{
   file : int;
   tiedot : Henkilo;
};

entries : array of Entry;


puh_cfg := array[] of {
   "frame .fl",
   "scrollbar .fl.scroll -command {.fl.l yview}",
   "listbox .fl.l -width 50w -height 50w -yscrollcommand {.fl.scroll set}",
   "frame .b0 -borderwidth 1 -relief raised",
   "frame .b",
   "button .b.uusi -text Lisää -command {send cmd uusi}",
   "button .b.muokkaa -text Muokkaa -command {send cmd muokkaa}",
   "button .b.poista -text Poista -command {send cmd poista}",
   "button .b.etsi -text Etsi -command {send cmd etsi}",
   "menubutton .b0.valikko -text Ohjelma -menu .b0.valikko.menu",
   "menu .b0.valikko.menu",
   ".b0.valikko.menu add command -label Lopeta -command {send cmd exit}",
   "pack .b0.valikko -side left -padx 1 -pady 1",
   "pack .b0 -fill x",
   "pack .b.uusi .b.muokkaa .b.poista .b.etsi -side left -padx 5 -pady 4",
   "pack .b -fill x",
   "pack .fl.scroll -side left -fill y",
   "pack .fl.l -fill both -expand 1",
   "pack .fl -fill both -expand 1",
   "pack propagate . 0"
};

uusi_cfg := array[] of {
   "frame .b",
   "button .b.ok -text {Ok} -command {send cmd ok}",
   "button .b.can -text {Peruuta} -command {send cmd can}",
   "pack .b.ok .b.can -side left -fill x -padx 10 -pady 10 -expand 1",
   "frame .l",
   "label .l.sukunimi -text {Sukunimi :} -anchor w",
   "label .l.etunimi -text {Etunimi  :} -anchor w",
   "label .l.puhelin -text {puh.     :} -anchor w",
   "pack .l.sukunimi .l.etunimi .l.puhelin -fill both -expand 1",
   "frame .e",
   "entry .e.sukunimi",
   "entry .e.etunimi",
   "entry .e.puhelin",
   "pack .e.sukunimi .e.etunimi .e.puhelin -fill x",
   "frame .f -borderwidth 2 -relief raised",
   "pack .l .e -fill both -expand 1 -side left -in .f",
   "pack .f",
   "pack .b -fill x -expand 1",
   "bind .e.sukunimi <Key-\n> {send cmd ok}",
   "bind .e.etunimi <Key-\n> {send cmd ok}",
   "bind .e.puhelin <Key-\n> {send cmd ok}",
   "focus .e.sukunimi"
};


etsi_cfg := array[] of {
   "frame .b",
   "button .b.ok -text {Ok} -command {send cmd ok}",
   "button .b.can -text {Peruuta} -command {send cmd can}",
   "pack .b.ok .b.can -side left -fill x -padx 10 -pady 10 -expand 1",
   "frame .l",
   "label .l.sukunimi -text {Sukunimi:} -anchor w",
   "pack .l.sukunimi -fill both -expand 1",
   "frame .e",
   "entry .e.sukunimi",
   "pack .e.sukunimi -fill x",
   "frame .f -borderwidth 2 -relief raised",
   "pack .l .e -fill both -expand 1 -side left -in .f",
   "pack .f",
   "pack .b -fill x -expand 1",
   "bind .e.sukunimi <Key-\n> {send cmd ok}",
   "focus .e.sukunimi"
};




init(xctxt: ref Draw->Context, nil: list of string)
{
   sys  = load Sys  Sys->PATH;
   bufio = load Bufio Bufio->PATH;
   str = load String String->PATH;
   if (xctxt == nil) {
      sys->fprint(sys->fildes(2), "puhelintiedot: ei ikkuna kontekstia\n");
      raise "fail:bad context";
   }

   ctxt = xctxt;

   draw = load Draw Draw->PATH;
   tk   = load Tk   Tk->PATH;
   tkclient= load Tkclient Tkclient->PATH;
   dialog = load Dialog Dialog->PATH;

   sys->pctl(Sys->NEWPGRP, nil);

   tkclient->init();
   dialog->init();


   (t, wmctl) := tkclient->toplevel(ctxt, nil, "Puhelintiedot", WINBUT);
   if(t == nil)
      return;

   main = t;

   cmd := chan of string;
   tk->namechan(main, cmd, "cmd");

   for (c:=0; c<len puh_cfg; c++)
      tk->cmd(main, puh_cfg[c]);

   error : string;
   # pitäisiköhän virhetiedolla tehdäkin jotain? ... NÄÄH ...
   (entries, error) = lataa();

   entries = lajittele(entries);

   tk->cmd(main, ".fl.l delete 0 end");

   for(i := 0 ; i < len entries; i++)
   {
         tietue := sys->sprint("%-15.15s %-15.15s %-15.15s",
               entries[i].tiedot.sukunimi,
               entries[i].tiedot.etunimi,
               entries[i].tiedot.puhelin);

         tk->cmd(main, ".fl.l insert end '" +tietue);
   }

   tk->cmd(main, ".fl.l see end;update");

   keskitys(main);
   tkclient->onscreen(main, "exact");
   tkclient->startinput(main, "kbd"::"ptr"::nil);

   for(;;) alt {
      s := <-main.ctxt.kbd =>
         tk->keyboard(main, s);
      s := <-main.ctxt.ptr =>
         tk->pointer(main, *s);
      s := <-main.ctxt.ctl or
      s = <-main.wreq =>
         tkclient->wmctl(main, s);

      menu := <-wmctl =>
         case menu {
            "exit" =>
               return;

            * =>
               tkclient->wmctl(main, menu);
         }

      bcmd := <-cmd =>
         case bcmd {
            "exit" =>
               return;

            "poista" =>
               sel := tk->cmd(main, ".fl.l curselection");
               if(sel == "")
                  break;

               fno := entries[int sel].file;
               f := DATABASEDIR + "/" + string fno;
               sys->remove(f);
               entries[int sel:] = entries[int sel +1:];
               entries = entries[0:len entries - 1];

               tk->cmd(main, ".fl.l delete "+sel);
               tk->cmd(main, ".fl.l see end;update");


            "uusi" =>
               ch := chan of (string, string, string, string);
               spawn uusi(ch);
               (sukunimi, etunimi, puhelin, err) := <- ch;

               if ( err == "" )
               {
                  fd := sys->open(DATABASEDIR + "/new", Sys->OWRITE);

                  if (fd == nil)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        sys->sprint("Ei voi avata: %r"),
                        0, "Jatka"::nil);
                     break;
                  }

                  (ok, info) := sys->fstat(fd);

                  if (ok == -1)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        sys->sprint("Ei voi suorittaa: stat new: %r"),
                        0, "Jatka"::nil);
                     break;
                  }

                  if (!isnum(info.name))
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        "Uusi dbfs tietue ei ole numeerinen",
                        0, "Jatka"::nil);
                     break;
                  }


                  b := array of byte (sukunimi +";" + etunimi + ";" +puhelin +"\n");

                  if (len b > Sys->ATOMICIO)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        "Tietue liian pitkä",
                        0, "Jatka"::nil);
                     break;
                  }

                  if (sys->write(fd, b, len b) != len b)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        sys->sprint("Ei voi kirjoittaa tietuetta: %r"),
                        0, "Jatka"::nil);
                     break;
                  }

                  e := entries;

                  i = len e;

                  ne := array[len e + 1] of Entry;

                  (ne[0:],  ne[i], ne[i+1:]) = (e[0:i], (int info.name, (sukunimi, etunimi, puhelin)), e[i:]);
                  entries = ne;

                  entries = lajittele(entries);

                  tk->cmd(main, ".fl.l delete 0 end");

                  for(i = 0 ; i < len entries; i++)
                  {
                     tietue := sys->sprint("%-15.15s %-15.15s %-15.15s",
                        entries[i].tiedot.sukunimi,
                        entries[i].tiedot.etunimi,
                        entries[i].tiedot.puhelin);

                     tk->cmd(main, ".fl.l insert end '" +tietue);
                  }

               tk->cmd(main, ".fl.l see end;update");
            }

            "muokkaa" =>
               sel := tk->cmd(main, ".fl.l curselection");
               if(sel == "")
                  break;

               ch := chan of (string, string, string, string);
               spawn muokkaa(ch, entries[int sel].tiedot);
               (sukunimi, etunimi, puhelin, err) := <- ch;

               if (err == "" )
               {
                  entries[int sel].tiedot = (sukunimi, etunimi, puhelin);

                  fno := entries[int sel].file;
                  fd := sys->open(DATABASEDIR +"/" +string fno, Sys->OWRITE);

                  if (fd == nil)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        sys->sprint("Ei voi avata %d: %r", fno),
                        0, "Jatka"::nil);
                     break;
                  }

                  b := array of byte (sukunimi +";" + etunimi + ";" +puhelin +"\n");

                  if (len b > Sys->ATOMICIO)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        "Tietue liian pitk€",
                        0, "Jatka"::nil);
                     break;
                  }

                  if (sys->write(fd, b, len b) != len b)
                  {
                     dialog->prompt(ctxt, t.image, "error -fg red", "Error",
                        sys->sprint("Ei voi kirjoittaa tietuetta: %r"),
                        0, "Jatka"::nil);
                     break;
                  }

                  entries = lajittele(entries);


                  tk->cmd(main, ".fl.l delete 0 end");

                  for(i = 0 ; i < len entries; i++)
                  {
                     tietue := sys->sprint("%-15.15s %-15.15s %-15.15s",
                        entries[i].tiedot.sukunimi,
                        entries[i].tiedot.etunimi,
                        entries[i].tiedot.puhelin);

                     tk->cmd(main, ".fl.l insert end '" +tietue);
                  }

                  tk->cmd(main, ".fl.l see end;update");
            }

            "etsi" =>
               ch := chan of (string, string);
               spawn etsi(ch);
               (sukunimi, err) := <- ch;

               sukunimi = str->toupper(sukunimi);

               if ( err == "" )
               {
                  index := vertaa(sukunimi, entries);
                  if(index != nil)
                  {
                     tk->cmd(main, ".fl.l selection clear  0 end");
                     tk->cmd(main, ".fl.l selection set " +index);
                     tk->cmd(main, ".fl.l see " +index +";update");
                  }

               }

         }

   }

}



lajittele(tiedot : array of Entry): (array of Entry)
{
   count := len tiedot;

   if(count < 1)
      return tiedot;

   for(i:=1; i < count ; i++)
   {
      v := tiedot[i];
      j := i;
      while( ( j > 0 ) && ( (tiedot[j-1].tiedot.sukunimi +tiedot[j-1].tiedot.etunimi) > (v.tiedot.sukunimi + v.tiedot.etunimi) ))
      {
         tiedot[j] = tiedot[j -1];
         j --;
      }
      tiedot[j] = v;
   }

   return tiedot;

}


lataa() : (array of Entry, string)
{
   (rc, nil) := sys->stat(DATABASEDIR);

   if (rc == -1)
      return (nil, sys->sprint("ei löydä %s: %r", DATABASEDIR));

   (rc, nil) = sys->stat(DATABASEDIR + "/new");

   if (rc == -1)
      return (nil, "ei dbfs mountattuna : " + DATABASEDIR);

   readdir := load Readdir Readdir->PATH;

   if (readdir == nil)
      return (nil, sys->sprint("ei voi ladata %s: %r", Readdir->PATH));


   entryt := entries;

   (de, nil) := readdir->init(DATABASEDIR, Readdir->NONE);

   if (de == nil)
      return (nil, "ei  voinut lukea tietokanta hakemistoa");


   buf := array[Sys->ATOMICIO] of byte;

   entryt = array[len de] of Entry;
   ne := 0;
   for (i := 0; i < len de; i++)
   {
      if (!isnum(de[i].name))
         continue;

      f := DATABASEDIR + "/" + de[i].name;
      fd := sys->open(f, Sys->OREAD);
      if (fd == nil)
      {
         sys->fprint(sys->fildes(2), "puhelintiedot: ei voi avata %s: %r\n", f);
         return (nil, "error opening");
      }
      else
      {
         n := sys->read(fd, buf, len buf);
         if (n == -1)
         {
            sys->fprint(sys->fildes(2), "puhelintiedot: virhe lukiessa %s: %r\n", f);
            return (nil, "error reading");
         }
         else
         {
            (ok, e, err) := parsehenkilo(string buf[0:n]);
            if (ok == -1)
            {
               sys->fprint(sys->fildes(2), "puhelintiedot: virhe tietueessa %s: %s\n", f, err);
               return (nil, err);
            }
            else
               entryt[ne++] = (int de[i].name, e);

          }

      }

   }

   entryt = entryt[0:ne];

   return (entryt,"");
}



parsehenkilo(body: string): (int, Henkilo, string)
{
   hlo : Henkilo;

   for (i := 0; i < len body; i++)
      if (body[i] == '\n')
         break;

   if (i == len body)
      return (-1, ("","",""), "väärä headeri (ei newlinea");

   (num, tokens) := sys->tokenize(body[0:i], ";");


   if(num != 3)
      return (-1, ("","",""), "virheellinen data (väärä määrä dataa)");


   hlo.sukunimi = hd tokens;
   tokens = tl tokens;
   hlo.etunimi = hd tokens;
   tokens = tl tokens;
   hlo.puhelin = hd tokens;

   return (0, hlo, "");

}




isnum(s: string): int
{
   for (i := 0; i < len s; i++)
      if (s[i] < '0' || s[i] > '9')
         return 0;
   return 1;
}



vertaa(sukunimi : string, entryt : array of Entry) : string
{
   count  := len entryt;

   if ( count < 1 )
      return nil;


   for( i := 0; i < count; i++)
   {
      if(sukunimi == entryt[i].tiedot.sukunimi)
         return (string i);
   }

   return nil;
}






uusi(ch : chan of (string, string, string, string))
{
   (t, conctl) := tkclient->toplevel(ctxt, nil, "Uusi", 0);

   cmd := chan of string;
   tk->namechan(t, cmd, "cmd");

   for (c:=0; c<len uusi_cfg; c++)
      tk->cmd(t, uusi_cfg[c]);

   tk->cmd(t, "update");
   keskitys(t);
   tkclient->onscreen(t, "exact");
   tkclient->startinput(t, "kbd"::"ptr"::nil);

   for(;;) alt {
      s := <-t.ctxt.kbd =>
         tk->keyboard(t, s);
      s := <-t.ctxt.ptr =>
         tk->pointer(t, *s);
      s := <-t.ctxt.ctl or
      s = <-t.wreq or
      s = <-conctl =>
         if(s == "exit")
         {
            ch <- = ( nil, nil, nil, "Ei Syötettä" );
            exit;
         }
         tkclient->wmctl(t, s);
      s := <-cmd =>
         if(s == "can")
         {
            ch <- = ( nil, nil, nil, "Ei Syötettä" );
            exit;
         }

         sukunimi := tk->cmd(t, ".e.sukunimi get");
         if(sukunimi == "")
         {
            tk->cmd(t, "focus .e.sukunimi;update");
            break;
         }

         etunimi := tk->cmd(t, ".e.etunimi get");
         if(etunimi == "")
         {
            tk->cmd(t, "focus .e.etunimi;update");
            break;
         }

         puh := tk->cmd(t, ".e.puhelin get");

         parse := isnum(puh);

         if(!parse) puh = "";

         if(puh == "")
         {
            tk->cmd(t, ".e.puhelin delete 0 end;update");
            tk->cmd(t, "focus .e.puhelin;update");
            break;
         }

         sukunimi = str->toupper(sukunimi);
         etunimi = str->toupper(etunimi);

         ch <- = (sukunimi, etunimi, puh, "");
         exit;
      }

}


muokkaa(ch : chan of (string, string, string, string), hlo : Henkilo)
{
   (t, conctl) := tkclient->toplevel(ctxt, nil,
            "Muokkaa", 0);

   cmd := chan of string;
   tk->namechan(t, cmd, "cmd");

   for (c:=0; c<len uusi_cfg; c++)
      tk->cmd(t, uusi_cfg[c]);


   tk->cmd(t, ".e.sukunimi insert 0 "+hlo.sukunimi);
   tk->cmd(t, ".e.etunimi insert 0 "+hlo.etunimi);
   tk->cmd(t, ".e.puhelin insert 0 "+hlo.puhelin);


   tk->cmd(t, "update");
   keskitys(t);
   tkclient->onscreen(t, "exact");
   tkclient->startinput(t, "kbd"::"ptr"::nil);

   for(;;) alt {
      s := <-t.ctxt.kbd =>
         tk->keyboard(t, s);
      s := <-t.ctxt.ptr =>
         tk->pointer(t, *s);
      s := <-t.ctxt.ctl or
      s = <-t.wreq or
      s = <-conctl =>
         if(s == "exit")
         {
            ch <- = ( nil, nil, nil, "Ei Syötettä" );
            exit;
         }

         tkclient->wmctl(t, s);

      s := <-cmd =>
         if(s == "can")
         {
            ch <- = ( nil, nil, nil, "Ei Syötettä" );
            return;
         }

         sukunimi := tk->cmd(t, ".e.sukunimi get");
         if(sukunimi == "")
         {
            tk->cmd(t, "focus .e.sukunimi;update");
            break;
         }

         etunimi := tk->cmd(t, ".e.etunimi get");
         if(etunimi == "")
         {
            tk->cmd(t, "focus .e.etunimi;update");
            break;
         }

         puh := tk->cmd(t, ".e.puhelin get");

         parse := isnum(puh);
         if(!parse) puh = "";

         if(puh == "")
         {
            tk->cmd(t, ".e.puhelin delete 0 end;update");
            tk->cmd(t, "focus .e.puhelin;update");
            break;
         }


      sukunimi = str->toupper(sukunimi);
      etunimi = str->toupper(etunimi);

      ch <- = (sukunimi, etunimi, puh, "");
      exit;
   }


}



etsi(ch : chan of (string, string))
{
   (t, conctl) := tkclient->toplevel(ctxt, nil,
            "Etsi", 0);

   cmd := chan of string;
   tk->namechan(t, cmd, "cmd");

   for (c:=0; c<len etsi_cfg; c++)
      tk->cmd(t, etsi_cfg[c]);


   tk->cmd(t, "update");
   keskitys(t);
   tkclient->onscreen(t, "exact");
   tkclient->startinput(t, "kbd"::"ptr"::nil);

   for(;;) alt {
      s := <-t.ctxt.kbd =>
         tk->keyboard(t, s);
      s := <-t.ctxt.ptr =>
         tk->pointer(t, *s);
      s := <-t.ctxt.ctl or
      s = <-t.wreq or
      s = <-conctl =>
         if(s == "exit")
         {
            ch <- = ( nil, "Ei Syötettä" );
            exit;
         }

         tkclient->wmctl(t, s);

      s := <-cmd =>
         if(s == "can")
         {
            ch <- = ( nil, "Ei Syötettä" );
            exit;
         }

         sukunimi := tk->cmd(t, ".e.sukunimi get");
         if(sukunimi == "")
         {
            dialog->prompt(ctxt, t.image, "error -fg red", "Haettava sukunimi",
               "Sinun täytyy syöttää¤ haettava sukunimi",
               0, "Jatka"::nil);
            tk->cmd(t, "focus .e.sukunimi");
            break;
         }

         ch <- = (sukunimi, "");
         exit;
   }

}

#melkein keskelle ..
keskitys(t: ref Tk->Toplevel)
{
   org: Point;
   ir := tk->rect(t, ".", Tk->Border|Tk->Required);
   org.x = t.screenr.dx() / 2 - ir.dx() / 2;
   org.y = t.screenr.dy() / 3 - ir.dy() / 2;

   if (org.y < 0)
      org.y = 0;
   tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y);
}

Esimerkki: kahden pelattava graafinen Ristinolla verkkopeli

Pitäisi löytyä Ohjelmointiputkan koodivinkeistä:

Lisään koodivinkkeihin usean käyttäjän monisäikeisen chat-serverin, kunhan kerkiän.

Hauska tietää

Inferno ja suuri osa sen mukana tulevista ohjelmista on saanut inspiraation "helvetillisiin" nimiinsä Dante Alighieri:n teoksen Divine Comedy mukaan.

Linkkejä

Tietoa sivustosta