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"); }
Mitä hyötyä tuollaisesta koodivinkistä on, jossa on ~600 riviä ja viisi kommenttia? Nekin vähäiset kommentit ovat ihan turhanpäiväisiä.
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.
Siinä tapauksessa olis ollu varmaan hyvä kirjotella jotain niistä kanavista. Koodivinkkien ohjeissa kyllä mainitaan kommentointivaatimukset.
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ä.
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. :):)
tsuriga kirjoitti:
Koodivinkkien ohjeissa kyllä mainitaan kommentointivaatimukset.
Kun se kerran on hyväksytty tänne, niin se on ihan tarpeeksi hyvä.
Macro kirjoitti:
Kun se kerran on hyväksytty tänne, niin se on ihan tarpeeksi hyvä.
Sitä vaan ei hyväksytty.
Onko tosta exefilua?
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
Aihe on jo aika vanha, joten et voi enää vastata siihen.