Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Limbo: Ristinollan verkkopelitoteutus

Sivun loppuun

jalski [29.11.2009 17:34:02]

#

Kahden pelattava ristinolla client-server peli Infernolle Limbo-ohjelmointikielellä. Pelin verkkoliikenteen oletusporttina toimii portti 2000.

implement RistiNolla;

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

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;


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

PORT : con "2000";
WINBUT : con Hide;
BSZ : con 20;
BSZI : con BSZ + 2;
EMPTY, PLAYER1, PLAYER2 : con iota;

# board[frame][button]
board := array[BSZI] of { * => array[BSZI] of {* => EMPTY} };

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

workerpid : int;

cmd : chan of string;
gamecmd : chan of string;
localcmd : chan of string;
remotecmd : chan of string;



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

   if (xctxt == nil) {
      sys->fprint(sys->fildes(2), "testi: no window context\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();

   wmctl : chan of string;
   (mainwin, wmctl) = tkclient->toplevel(ctxt, nil, "RistiNolla", WINBUT);
   if(mainwin == nil)
   {
      sys->fprint(sys->fildes(2), "RistiNolla: creation of toplevel window failed\n");
      raise "fail:creation of toplevel window failed";
   }

   gamecmd = chan of string;

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

   localcmd = chan of string;
   tk->namechan(mainwin, localcmd, "pcmd");

   remotecmd = chan of string;

   display_board();

   center(mainwin);
   tkclient->onscreen(mainwin, "exact");
   tkclient->startinput(mainwin, "kbd"::"ptr"::nil);


   for(;;) alt {
      s := <- mainwin.ctxt.kbd =>
         tk->keyboard(mainwin, s);

      s := <- mainwin.ctxt.ptr =>
         tk->pointer(mainwin, *s);

      s := <- mainwin.ctxt.ctl or
      s = <- mainwin.wreq or
      s = <- wmctl =>
         case s {
            "exit" =>
               fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE);
               if(fd != nil)
                  sys->fprint(fd, "kill");

               tkclient->wmctl(mainwin, "exit");

            * =>
               tkclient->wmctl(mainwin, s);
         }

      menucmd := <- cmd =>
         case menucmd {
            "host" =>
            reset_board();
            (n, conn) := sys->announce("tcp!*!" + PORT);
            if (n < 0)
            {
               tk->cmd(mainwin,".ft.ls configure -text {Pelin isännöinti epäonnistui}");
               tk->cmd(mainwin, "update");
            }
            else
            {
               spawn listenthread(conn);

               tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state disabled");
               tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state disabled");
               tk->cmd(mainwin,".ft.ls configure -text {Odotetaan toista pelaajaa}");
               tk->cmd(mainwin, "update");
            }

            "join" =>
               reset_board();
               address := "tcp!" + dialog->getstring(ctxt,mainwin.image, "Anna isännän osoite") + "!" + PORT;
               (ok, conn) := sys->dial(address, "");
               if (ok < 0)
               {
                  tk->cmd(mainwin,".ft.ls configure -text {Yhteyden muodostus epäonnistui}");
                  tk->cmd(mainwin, "update");
               }
               else
               {
                  tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state disabled");
                  tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state disabled");
                  tk->cmd(mainwin,".ft.ls configure -text {Vastustajan vuoro}");
                  tk->cmd(mainwin, "update");

                  spawn workerthread(conn);
                  spawn runclient(conn);

               }


         }


   }

}



runserver(conn : Connection)
{

   absorb(localcmd);
   localch := localcmd;
   dummych := chan of string;


   for(;;) alt {

      game := <- gamecmd =>
         case game {
            "close" =>
                tk->cmd(mainwin,".ft.ls configure -text {Toinen pelaaja sulki yhteyden}");
                tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                tk->cmd(mainwin, "update");
                exit;
         }

      # täällä käsitellään tiedot pelilaudan painalluksista
      local := <-localch =>
         localch = dummych;
         (n, tokens) := sys->tokenize(local, " ");
         if(n >= 3)
            case hd tokens {
               "b" =>
                  row := int hd tl tokens;
                  column := int hd tl tl tokens;

                  if(board[row][column] == EMPTY)
                  {
                     message := local + "\r\n";
                     wdfd := sys->open(conn.dir + "/data", Sys->OWRITE);
                     sys->write(wdfd, array of byte message, len array of byte message);

                     board[row][column] = PLAYER1;

                     tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column));

                    if(testrows(row, column, 5, PLAYER1) == 1)
                     {
                        tk->cmd(mainwin,".ft.ls configure -text {Voitit!}");
                        tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                        tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                        tk->cmd(mainwin, "update");

                        fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE);
                        if(fd != nil)
                           sys->fprint(fd, "kill");

                        exit;
                     }

                     tk->cmd(mainwin,".ft.ls configure -text {Vastustajan vuoro}");
                     tk->cmd(mainwin, "update");

                  }
                  else
                     localch = localcmd;

                  * =>
                     localch = localcmd;
         }
         else
            localch = localcmd;


      remote := <-remotecmd =>
         (n, tokens) := sys->tokenize(remote, " ");
         if(n >= 3)
            case hd tokens {
               "b" =>
                  row := int hd tl tokens;
                  column := int hd tl tl tokens;

                  board[row][column] = PLAYER2;

                  tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column));

                  if(testrows(row, column, 5, PLAYER2) == 1)
                  {
                     tk->cmd(mainwin,".ft.ls configure -text {Vastustaja voitti!}");
                     tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                     tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                     tk->cmd(mainwin, "update");

                     fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE);
                     if(fd != nil)
                        sys->fprint(fd, "kill");

                     exit;
                  }


                  tk->cmd(mainwin,".ft.ls configure -text {Oma vuoro}");
                  tk->cmd(mainwin, "update");

                  absorb(localcmd);
                  localch = localcmd;

            }


   }



}




runclient(conn : Connection)
{

   dummych := chan of string;
   localch := dummych;

   for(;;) alt {

      game := <- gamecmd =>
         case game {
            "close" =>
                tk->cmd(mainwin,".ft.ls configure -text {Toinen pelaaja sulki yhteyden}");
                tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                tk->cmd(mainwin, "update");
                exit;
         }

      # täällä käsitellään tiedot pelilaudan painalluksista
      local := <-localch =>
         localch = dummych;
         (n, tokens) := sys->tokenize(local, " ");
         if(n >= 3)
            case hd tokens {
               "b" =>
                  row := int hd tl tokens;
                  column := int hd tl tl tokens;

                  if(board[row][column] == EMPTY)
                  {
                     message := local + "\r\n";
                     sys->write(conn.dfd, array of byte message, len array of byte message);

                     board[row][column] = PLAYER2;

                     tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column));

                     if(testrows(row, column, 5, PLAYER2) == 1)
                     {
                        tk->cmd(mainwin,".ft.ls configure -text {Voitit!}");
                        tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                        tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                        tk->cmd(mainwin, "update");

                        fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE);
                        if(fd != nil)
                           sys->fprint(fd, "kill");

                        exit;
                     }

                     tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {0}", row, column));

                     tk->cmd(mainwin,".ft.ls configure -text {Vastustajan vuoro}");
                     tk->cmd(mainwin, "update");

                  }
                  else
                     localch = localcmd;

                  * =>
                     localch = localcmd;
         }
         else
            localch = localcmd;


      remote := <-remotecmd =>
         (n, tokens) := sys->tokenize(remote, " ");
         if(n >= 3)
            case hd tokens {
               "b" =>
               row := int hd tl tokens;
               column := int hd tl tl tokens;

               board[row][column] = PLAYER1;

               tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column));

               if(testrows(row, column, 5, PLAYER1) == 1)
               {
                  tk->cmd(mainwin,".ft.ls configure -text {Vastustaja voitti!}");
                  tk->cmd(mainwin,".f.menu.gm entryconfigure 0 -state normal");
                  tk->cmd(mainwin,".f.menu.gm entryconfigure 1 -state normal");
                  tk->cmd(mainwin, "update");

                  fd := sys->open("#p/" + string workerpid +"/ctl", sys->OWRITE);
                  if(fd != nil)
                     sys->fprint(fd, "kill");

                  exit;
               }

               tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text {X}", row, column));

               tk->cmd(mainwin,".ft.ls configure -text {Oma vuoro}");
               tk->cmd(mainwin, "update");

               absorb(localcmd);
               localch = localcmd;

         }


   }



}





center(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() / 2 - ir.dy() / 2;

   if (org.y < 0)
   {
      org.y = 0;
   }

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



display_board()
{
   i, j: int;
   pack: string;

   tk->cmd(mainwin, "frame .f");
   tk->cmd(mainwin, "pack .f -fill x");
   tk->cmd(mainwin, "menubutton .f.menu -text Peli -menu .f.menu.gm");
   tk->cmd(mainwin, "menu .f.menu.gm");
   tk->cmd(mainwin, ".f.menu.gm add command -label {isännöi} -command {send cmd host}");
   tk->cmd(mainwin, ".f.menu.gm add command -label {liity} -command {send cmd join}");
   tk->cmd(mainwin, "pack .f.menu -side left");

   for (i = 1; i <= BSZ; i++)
   {
      tk->cmd(mainwin,  sys->sprint("frame .f%d", i));
      pack = "";

      for (j = 1; j <= BSZ; j++)
      {
         pack += sys->sprint(" .f%d.b%d", i, j);
         tk->cmd(mainwin, sys->sprint("button .f%d.b%d -text { } -width 14 -command {send pcmd b %d %d}", i, j, i, j));
      }

      tk->cmd(mainwin, sys->sprint("pack %s -side left", pack));
      tk->cmd(mainwin, sys->sprint("pack .f%d -side top -fill x", i));

   }

   tk->cmd(mainwin, "frame .ft");
   tk->cmd(mainwin, "label .ft.li -text {Tila: }");
   tk->cmd(mainwin, "label .ft.ls -text {Ei yhteydessä}");
   tk->cmd(mainwin, "pack .ft.li .ft.ls -side left -fill x");
   tk->cmd(mainwin, "pack .ft -side bottom -fill x");
   tk->cmd(mainwin, "update");

}





listenthread(conn : Connection)
{

   (ok, c) := sys->listen(conn);
   if (ok < 0)
   {
      sys->fprint(sys->fildes(2), "Server: listen failed\n");
      raise "fail:listen failed";
   }


   tk->cmd(mainwin,".ft.ls configure -text {Oma vuoro}");
   tk->cmd(mainwin, "update");

   spawn runserver(c);
   spawn workerthread(c);

}



workerthread(conn : Connection)
{
   workerpid = sys->pctl(0, nil);

   buf := array [1] of byte;
   rdfd := sys->open(conn.dir + "/data", Sys->OREAD);
   output := "";

   while( (n := sys->read(rdfd, buf, len buf ) ) > 0 )
   {
      output[len output] = int buf[0];

      if(len output >= 2)
      {
         if(output[len output - 2:] == "\r\n")
         {
            remotecmd <- = output[:len output - 2];
            output = "";
         }

      }

   }

   gamecmd <- = "close";


}





absorb(ch : chan of string)
{
   for(;;)
   {
      alt
      {
         <- ch =>
            ;
         * =>
            return;
      }
   }
}



length(row, column, drow, dcolumn, item: int): int
{
   l := 0;

   while(board[row][column] == item)
   {
      row += drow;
      column += dcolumn;

      l++;
   }

   return l;

}



testrows(row, column, items, item: int) : int
{
   vaaka := (length(row, column, 0, -1, item) + length(row, column, 0, 1, item) - 1);
   if(vaaka >= items)
   {
      mark_board(row, column, 0, -1, item);
      mark_board(row, column, 0, 1, item);
      return 1;
   }

   pysty := (length(row, column, -1, 0, item) + length(row, column, 1, 0, item) - 1);
   if(pysty >= items)
   {
      mark_board(row, column, -1, 0, item);
      mark_board(row, column, 1, 0, item);
      return 1;
   }

   vino1 := (length(row, column, -1, -1, item) + length(row, column, 1, 1, item) - 1);
   if(vino1 >= items)
   {
      mark_board(row, column, -1, -1, item);
      mark_board(row, column, 1, 1, item);
      return 1;
   }

   vino2 := (length(row, column, -1, 1, item) + length(row, column, 1, -1, item) - 1);
   if(vino2 >= items)
   {
      mark_board(row, column, -1, 1, item);
      mark_board(row, column, 1, -1, item);
      return 1;
   }

   return 0;

}




mark_board(row, column, drow, dcolumn, item : int)
{
   while(board[row][column] == item)
   {
      tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -bg olive -activebackground  olive", row, column));

      row += drow;
      column += dcolumn;
   }

}


reset_board()
{
   # nollaa board
   for (i := 0; i < BSZI; i++)
      for (j := 0; j < BSZI; j++)
         board[i][j] = EMPTY;

   # tyhjennä board näytöllä
   for (i = 1; i <= BSZ; i++)
      for (j = 1; j <= BSZ; j++)
         tk->cmd(mainwin, sys->sprint(".f%d.b%d configure -text { } -bg #dddddd -activebackground #eeeeee", i,  j));

   tk->cmd(mainwin, "update");

}

trilog [16.12.2009 13:36:20]

#

Mitä hyötyä tuollaisesta koodivinkistä on, jossa on ~600 riviä ja viisi kommenttia? Nekin vähäiset kommentit ovat ihan turhanpäiväisiä.

jalski [16.12.2009 21:29:13]

#

trilog kirjoitti:

Mitä hyötyä tuollaisesta koodivinkistä on, jossa on ~600 riviä ja viisi kommenttia? Nekin vähäiset kommentit ovat ihan turhanpäiväisiä.

Se on koodivinkki eikä mikään helv*tin tutoriaali.

Tuosta n. 600 rivistä tarvitsee oivaltaa vain miten kanavat toimivat ja lopun ymmärtäminen ei ole enää kovinkaan vaikeaa.

Ohjelmahan koostuu vain muutamasta säikeestä ja homma pysyy kasassa kanavien avulla.

tsuriga [03.01.2010 14:55:10]

#

Siinä tapauksessa olis ollu varmaan hyvä kirjotella jotain niistä kanavista. Koodivinkkien ohjeissa kyllä mainitaan kommentointivaatimukset.

jalski [03.01.2010 19:38:50]

#

tsuriga kirjoitti:

Siinä tapauksessa olis ollu varmaan hyvä kirjotella jotain niistä kanavista. Koodivinkkien ohjeissa kyllä mainitaan kommentointivaatimukset.

Innostuin kirjoittamaan tuon Infernon ristinolla toteutuksen Limbolla Macron pähkäillessä Python toteutuksen kanssa. Postitin sen koodivinkkeihin, koska mielestäni siitä tuli ihan hyvä, lyhyt ja yksinkertainen toteutus.

Laitanpa haasteen: kirjoita omaa esimerkkiäni yksinkertaisempi graafinen kahden pelattava verkkopelitoteutus.

Ohjelman ulkoasuksi riittänee omaani vastaava: http://www.tip9ug.jp/who/jalih/rn3.jpg

Vastailen muuten ihan mielelläni, jos jollain on esimerkistäni jotain asiallista kommentoitavaa tai kysymyksiä.

vehkis91 [16.03.2010 20:32:12]

#

Ei millään pahalla, mutta mun mielestä limbo näyttää ihan saakelin sekaiselta verrattuna esim, pythoniin, c++, java , vb.net...

Ihan hieno esimerkki kyllä toi. :):)

Macro [13.07.2010 17:05:40]

#

tsuriga kirjoitti:

Koodivinkkien ohjeissa kyllä mainitaan kommentointivaatimukset.

Kun se kerran on hyväksytty tänne, niin se on ihan tarpeeksi hyvä.

Kray [20.07.2010 13:33:59]

#

Macro kirjoitti:

Kun se kerran on hyväksytty tänne, niin se on ihan tarpeeksi hyvä.

Sitä vaan ei hyväksytty.

ErroR++ [19.09.2011 18:00:42]

#

Onko tosta exefilua?

jalski [25.11.2011 16:56:25]

#

ErroR++ kirjoitti:

Onko tosta exefilua?

Tuon ajamiseen tarvitaan joka tapauksessa Infernon virtuaalikone dis, joten linkki valmiiksi käännettyyn ajettavaan tiedostoon säästäisi aikaa ja vaivaa korkeintaan joitain sekuntteja.

Ohjeet Infernon ajoon Windows:in päällä sekä limbolla kirjoitetun ohjelman kääntämiseen:

Lataa valmis Infernon Windows paketti: http://www.vitanuova.com/dist/4e/inferno.zip

Tuo kannattanee purkaa ihan C-aseman juureen, jonka jälkeen valmis Infernon asennus löytyy hakemistosta: C:\Inferno\

Itse yleensä käynnistän Infernon komennolla: C:\Inferno\Nt\386\bin\emu -pmain=256M -pheap=256M -pimage=256 -g1280x1024 wm/wm wm/logon -u inferno

Tuo rimpsu käynnistää Infernon inferno käyttäjänä ilman JIT-tukea.


Limbolla kirjoitetun ohjelman voi kääntää yksinkertaisesti komennolla: limbo -g ohjelma.b


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta