Path: news.cs.au.dk!news.net.uni-c.dk!howland.erols.net!news-feed.ifi.uio.no!ifi.uio.no!not-for-mail From: Alf-Ivar Holm Newsgroups: comp.lang.beta Subject: tron - X11 multi-player game (Long) Date: 27 Mar 2000 20:17:43 +0200 Organization: The Department of Informatics, University of Oslo, Norway Lines: 601 Message-ID: NNTP-Posting-Host: naglfar.ifi.uio.no Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: maud.ifi.uio.no 954181065 26529 129.240.64.54 (27 Mar 2000 18:17:45 GMT) X-Complaints-To: abuse@ifi.uio.no NNTP-Posting-Date: 27 Mar 2000 18:17:45 GMT User-Agent: Gnus/5.0803 (Gnus v5.8.3) Emacs/20.6 Xref: news.cs.au.dk comp.lang.beta:12279 --=-=-= tron was written in C for X10. When winter crept up in '95 I grew bored and decided to rewrite it, in Beta. Having started to learn Beta, I was also anxious to try to do something else than the usual stuff. Well, it was shockingly easy to make something that worked, and it was more than fast enough too! The only thing that took some time, was for me to add a GPL statement and post it here ... Included are one Beta source file, along with a xbm-file, that is needed to make a grey background. (There are functionality in X for doing it on the fly, but at the time of writing, the mapping to that function was not implemented in the XtEnv library.) It is based on AwEnv, so it ought to run on most X11-variants. Compilation should be quite straightforward with: beta --noList --noWarnQua tron.bet (Note: If you've got a newer compiler you might need to remove the "current/" part of the origin and include statements.) To play the game, start "tron" and select up to 8 displays or robots (machine players) to play with. Default is your display. Key layout: Left arrow key Turn left Right arrow key Turn right Space Jump Holding 's' down Speedup If you would like to change the screen size, the speed, and some other stuff, see the constants in the start of the program - or write your own add-ons to be able to specify this from command line. I have included some odd stuff that has come up on this list during the last years, among others, the side effect that makes it possible to use enumerations in Beta. (BTW, who wrote that? I've forgot to include the authors name.) Further on, for those that investigates the code: Yes, its compact. That is because I was fighting with myself to keep the code small, and linesize was my measure ... It turned out to be basically 500 lines, which I think is not that much. Affi --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename=grey.xbm Content-Description: Background image #define grey_width 2 #define grey_height 2 static char grey_bits[] = { 0x01, 0x02}; --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename=tron.bet Content-Description: Beta source code for tron origin '~beta/Xt/current/awenv'; include '~beta/Xt/current/athena/prompts'; include '~beta/sysutils/current/envstring'; include '~beta/containers/current/seqContainers'; include '~beta/containers/current/list'; --- LIB: attributes --- (* tron - X11 version of the classic X10 multiplayer game * Copyright (C) 1995-2000 Alf-Ivar Holm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be funny, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * *) cyclicList: list (# (* Add operations next and prev to traverse the list like a cyclic list *) theCellType::< (# prev: (# position: ^theCellType do pred[]->position[]; (if position[]//none then last->position[] if); exit (position[]) #); next: (# position: ^theCellType do succ[]->position[]; (if position[]//none then head->position[] if); exit (position[]) #); #); #); AutoInit: (# (* Run init automagically *) dummy: [(# do init exit 0 #)] @integer; init:< Object; do inner #); Enumeration: AutoInit (# (* Enumeration *) nextValue: @integer; value: AutoInit (# v:@integer; init::(# do nextValue->v; nextValue+1->nextValue #); exit v #); #); --- PROGRAM: descriptor --- awEnv (# (* User modifiable constants(!) - here, in the source code :-/ *) H: (# exit 250 #); (* Matrix height *) W: (# exit 250 #); (* Matrix width *) ts: (# exit 3 #); (* Size of Tron points (in pixels) *) cd: (# exit 6 #); (* Countdown (seconds) to start *) dTime: (# exit 10 #); (* Ms between movement, initial speed*) ddTime: @integer; (* Dynamic dTime *) rTime: (# exit ddTime*10+10 #); (* Ms between robot checks *) rlook: (# exit 10 #); (* How far robot looks for obstacles *) jumpLen: (# exit 10 #); (* Jump length *) jumpKey: (# exit ' ' #); (* Player keys *) speedKey: (# exit 's' #); (* Internal constants *) types: @Enumeration(# human, machine: @value; #); (* Player types *) status: @Enumeration(# running, watching: @value; #); (* Player status *) (* human: (# exit 0 #); {* Player types *} * machine: (# exit 1 #); * running: (# exit 0 #); {* Player status *} * watching: (# exit 1 #); * *) cdTime: (# exit 1000 #); (* Countdown unit (1000ms=1second) *) main: @Box (# (* Main status widget *) sl: @statusList; (* Status list in control window *) commandBox: @Box (# (* Box containing buttons *) checkButtons: (# (* Set button sesitivity *) do (if not(bikeQ.empty) then true->startButton.sensitive if); (if startPosL.size=bikeQ.size then false->displayButton.sensitive; false->robotButton.sensitive; if); #); sp: ^startPosL.theCellType; (* Start position *) displayButton: @Command (# createAndOpen: (# (* Create display and bike *) ndi: ^displayItem; (* New displayItem *) nb: ^bike; (* New bike *) dispName: ^text; (* Display name *) enter dispName[] do &displayItem[]->ndi[]; (* Create display *) openAdd: (# (* Open display and add it to queue *) dnLen: @integer; do dispName[]->ndi.xd.open (# (* Open display - leave if display error*) DisplayError:: (# do msg[]->screen.putline; true->continue; leave openAdd #); #); dispName[]->ndi.dn[]; (* Set display name *) ndi[]->dispQ.insert; (* Add display to queue *) &bike[]->nb[]->bikeQ.insertBack; (* Add bike to queue *) nb[]->ndi.db[]; ndi[]->nb.di[]; (* Swap bike/display ptrs *) ndi.slid.sl[]->statusListQ.insert; (* Add sl to queue *) types.human->nb.type; (* Set player type *) dispName.reset; (* Skip everything after first '.' *) dispName.scan (# while:: (# do ('.'<>ch)->value #); do dnLen+1->dnLen #); dnLen->dispName.lgth; dispName.reset; dispName[]->nb.bname[]; (* Set bike name *) nb[]->sl.addLine; (* Update statusList *) sp.elm->nb.init; sp.succ[]->sp[]; (* Set position & colour *) (ndi.dn[], ndi.xd.shell)->ndi.init; ndi.init2; (* Popup *) checkButtons; #); (* openAdd *) #); (* createAndOpen *) callback:: (# h, v: @integer; do (x+10,y+10)->translatecoords->(h,v); (h, v, 0, 'Display', 'OK', 'Cancel', '$(DISPLAY)'->expandEnvVar) -> PromptForString(# ok:: (# do value[]->createAndOpen #)#); #); #); (* displayButton *) robotButton: @Command (# callback:: (# nb: ^bike; (* New bike *) do &bike[]->nb[]->bikeQ.insertBack; (* Add bike to queue *) types.machine->nb.type; (* Set player type *) 'Robot'->nb.bname[]; (* Set bike name *) nb[]->sl.addLine; (* Update statusList *) sp.elm->nb.init; sp.succ[]->sp[]; (* Set position & colour *) checkButtons; #); #); (* robotButton *) startButton: @Command (# (* Reset matrix, bikes, and displays, then start game *) callback:: (# sp: ^startPosL.theCellType; (* Start position *) s: ^stringArray; (* Main status list *) do (false, false, false, true)->(displayButton.sensitive, robotButton.sensitive, startButton.sensitive, fastButton.sensitive); matrix.init; (* Empty matrix *) startPosL.head->sp[]; (* Reset start positions pointer *) bikeQ.scan (# (* Init bikes *) do sp.elm->current.init; sp.succ[]->sp[]; (* Set pos & colour *) (current.sli, 'Init')->main.sl.changeText;(* Reset sl *) (current.sli+1, current.score2text)->main.sl.changeText; #); dispQ.scan (# do current.area.clear #); (* Reset playing area *) bikeQ.size->bikeQ.active; (* Reset active counter *) main.sl.getStrings->s[]; (* Copy statusList from main to all * other displayItems *) statusListQ.scan(# do (s[], true)->current.change #); dTime->ddTime; (* Init ddTime - ugly *) cdTime->cdt.start; (* Set timer for countdown *) #); #); (* startButton *) fastButton: @Command(# callback:: (# do 0->ddTime #) #); (* Speedup *) quitButton: @Command(# callback:: (# do stop #) #); (* Quit game *) init:: (# (* Set labels, X stuff and start postion pointer*) do displayButton.init; 'Add display'->displayButton.label; robotButton.init; 'Add robot'->robotButton.label; startButton.init; 'Start'->startButton.label; fastButton.init; 'Fast'->fastButton.label; quitButton.init; 'Quit'->quitButton.label; false->vertical; false->startButton.sensitive; false->fastButton.sensitive; startPosL.head->sp[]; #); #); (* commandBox *) init:: (# do 1->toplevel.allowShellResize; (* Make main window resizable *) commandBox.init; ('sl', main)->sl.init; sl[]->statusListQ.insert; #); #); (* main *) statusListQ: @queue(# element:: statusList #); (* statusList queue *) fontList: listWidget (# (* Abstract list setting fonts *) font: IntegerResource(# resourceName:: XtNFont #); (* X font - Works!*) init::< (# do '*courier-medium-r-*--12-*-iso8859-1'->textToFont->font; inner #); #); (* fontList *) statusList: fontList (# (* List of all players and their status and score *) addLine: (# b: ^bike; sa: ^stringArray; enter (b[]) do getStrings->sa[]; b.bname[]->sa.addText; 'Init'->sa.addText; '0/0'->sa.addText; (sa[], true)->change; (numberStrings-1)->b.sli; #); changeText: (# i: @integer; t: ^text; sa: ^stringArray; enter (i, t[]) do getStrings->sa[]; (i, t[])->sa.changeText; (sa[], true)->change; #); init:: (# sa: ^stringArray; do (3, 20, true)->(defaultColumns, columnSpacing, forceColumns); &stringArray[]->sa[]; 'Name'->sa.addText; 'Status'->sa.addText; 'Score/Total'->sa.addText; (sa[], true)->change; #); #); (* statusList *) dispQ: @queue (# element:: displayItem; (* Display Queue *) point: (# (* Draw a "point" of size ts on all displays *) x, y, c: @integer; (* Position and colour *) enter (x, y, c) do scan(# do (x, y, c)->current.area.fill #) #); #); displayItem: box (# (* Drawing area and labels *) dn: ^text; (* Display Name *) xd: @this(xtEnv).display; (* The X display (Not core.display!)*) db: ^bike; (* Bike *) eventHandler:: (# kp: @keyPress (# (* Handle key press *) do event.translate (# left:: (# do db.turnLeft #); right:: (# do db.turnRight #); do (if kpch //jumpKey then db.jump //speedKey then db.speedUp if); #); #); (* Handle key release *) kr: @keyRelease (# do event.translate (# do (if kpch//speedKey then db.speedDown if) #); #); (* Init keyboard if human player *) init:: (# do (if db.type=types.human then kp.enable; kr.enable if) #); #); drawPoint: core (# (* Widget used for drawing points *) gc: @integer; (* Graphics Context *) fill: (# (* Fill point *) x, y, c: @integer; enter (x, y, c) do (display, gc, c)->XSetForeground; (display, window, gc, (x-1)*ts, (y-1)*ts, ts, ts)->XFillRectangle #); init::< (# do screenResource->XDefaultGCOfScreen->gc; (* '/local/pub/images/x11-bitmap/bgrnd.xbm' * *) 'grey.xbm'->BitmapFileToPixmap->backgroundPixmap; (* Background *) inner; #); #); slid: @Box (# (* Status list, idLine, name and help text *) sl: @statusList; (* idn: @label(# init:: (# do 'You: '->label #)#); * *) idLine: @drawPoint (# fillLine: (# do (for i: 16 repeat (5, 2+i, db.colour)->fill for)#); eventHandler:: (# ex: @exposure(# do fillLine #); init:: (# do ex.enable #)#); init:: (# do (20*ts, 9*ts)->(height, width)#); #); help: @fontList (# (* Help text *) init:: (# t: ^text; sa: @stringArray; do (2, 20, true)->(defaultColumns, columnSpacing, forceColumns); 'Key: '->sa.addText; 'Action: '->sa.addText; 'Left arrow'->sa.addText; 'Turn left'->sa.addText; 'Right arrow'->sa.addText; 'Turn right'->sa.addText; ''''->t[]; jumpKey->t.put; ''''->t.append; t[]->sa.addText; 'Jump'->sa.addText; ''''->t[]; speedKey->t.put; '''-down'->t.append; t[]->sa.addText; 'Speedup'->sa.addText; (sa[], true)->change; #); #); init:: (# do true->vertical; ('statusList', slid)->sl.init; (* idn.init; *) ('slid', slid)->idLine.init; help.init #); #); area: @drawPoint (# (* Playing area *) eventHandler:: (# ex: @exposure(# do matrix.redraw #); init:: (# do ex.enable #); #); clear: (# do (display,window,0,0,width,height,true) ->XClearArea #); init:: (# do (h*ts, w*ts)->(height, width); #); #); (* area *) init:: (# (* Create id line, status list and playing area *) do false->xd.shell.allowShellResize; false->vertical; slid.init; area.init; #); init2: (# do xd.shell.realize; slid.idLine.fillLine #); #); (* displayItem *) bikeQ: @deque (# active: @integer; (* Number of active (running) bikes (Trons) *) element:: bike; #); (* bikeQ *) bike: (# (* A bike *) bx, by, colour, state, (* Position, colour, status code, *) jumpCount, type, score, (* jump count, player type, score, *) total: @integer; (* and total score *) bname: ^text; (* Bike name *) speeding: @boolean; (* True when speeding *) di: ^displayItem; (* Display (none if type=machine) *) dir: ^dirCL.theCellType; (* Tron direction (E, W, N, S) *) sli: @integer; (* Corresponding item in statusList *) end: (# (* Abstract superpattern - update score and message, set state *) message: ^text; do inner; updateScore(# do (sli, message[])->current.changeText #); status.watching->state; (* Change state *) #); win: end(# do (score+500->score)+total->total; 'Winner'->message[] #); score2text: (# t: ^text; (* Make a text like "score/hiscore" *) do ''->t[]; score->t.putInt; '/'->t.putText; total->t.putInt exit (t[]) #); updateScore: statusListQ.scan (* Update score in statusList *) (# do (sli+1,score2text)->current.changeText; inner #); move: (# (* Move bike, check for crash, set & draw point *) crashed: (# (* Check wheter bike crashed or not *) wreck: @boolean; crash: end (# (* Update score and total and set message *) do (score-(bikeQ.active-1->bikeQ.active)*100->score)+total->total; 'Crashed'->message[]; true->wreck; #); do false->wreck; (if (bx, by)->matrix.checkWall then crash else (if (bx, by)->matrix.checkPoint then crash if) if); exit (wreck) #); point2: @boolean; do false->point2; draw: (# (* Draw two points if speeding *) do (bx, by)->dir.elm->(bx, by); (* Calculate new position *) (if jumpCount=0 then (* Not jumping *) (if crashed then leave draw (* Do not draw if crashed *) else (* Update matrix, create point, increase score *) (bx, by, colour)->matrix.set; (bx, by, colour)->dispQ.point; (if ((score+1->score) mod 100)=0 then updateScore if); if); else (if not(point2) then jumpCount-1->jumpCount if); if); (if not(point2) and speeding then true->point2; restart draw if); #); #); (* move *) rt: @Timer (# (* Robot timer, run robot until it crashes *) timeout:: (# do (if state=status.running then robot; rTime->start; if); #); #); robot: (# (* Check whether there is an obstacle in front, * if so, try to find most promissing path *) checkAll: (# thingDet:< (# do inner #); (* Wall or point detected *) rx, ry, dist: @integer; (* Position and distance *) do (bx, by)->(rx, ry); cp: (for i: rlook repeat i->dist; (rx, ry)->dir.elm->(rx, ry); (if (rx, ry)->matrix.checkWall then thingDet; leave cp if); (if (rx, ry)->matrix.checkPoint then thingDet; leave cp if) for); exit (dist) #); rollOver: (# ci, li, ri: @integer; (* Current, left and right index *) enter (ci) do turnLeft; checkAll->li; (* Look to the left *) turnRight; turnRight; checkAll->ri; (* Look to the right *) turnLeft; (if true (* Now decide *) //(ciri) then turnLeft //(ci=li) then turnRight if); #); do checkAll(# thingDet:: (# do dist->rollOver #); #); #); (* robot *) (* Actions *) turnLeft: (# do dir.prev->dir[]; #); turnRight: (# do dir.next->dir[]; #); jump: (# do (if jumpCount=0 then jumpLen->jumpCount if)#); speedUp: (# do true->speeding #); speedDown: (# do false->speeding #); init: (# d: ##direction; enter (bx, by, d##, colour) (* Start pos, direction and colour *) do (* Set start direction *) dirCL.iterate(# do (if current.elm##//d## then current[]->dir[] if)#); status.running->state; (* Kick start bike *) 0->jumpCount; (* Start on ground *) false->speeding; (* Start slow *) 0->score; (* Reset score *) #); #); (* bike *) cdt: @Timer (# (* Countdown timer - not very fancy *) cs: @integer; (* Count seconds *) timeOut:: (# (* Countdown, then start robots and drawing *) bikeQStatuses: bikeQ.scan (# (* Scan all bikes and statuses *) t: ^text; b: ^bike; do inner; current[]->b[]; statusListQ.scan(# do (b.sli, t[])->current.changeText #) #); do (if (cs+1->cs)t[]; (cd-cs)->t.putInt; #); bikeQ.scan(# do current.move #); (* Start rolling *) cdTime/cs->start; (* Restart (a bit faster) *) else (* Set labels and start robots *) 0->cs; (* Reset counter *) bikeQStatuses (# do (if current.type=types.machine then rTime->current.rt.start if); 'Running'->t[]; #); dTime->dtt.start; (* Start drawing *) if); #); (* timeOut *) #); (* cdt *) dtt: @Timer (# (* Draw Tron timer *) timeOut:: (# (* Keep on until no player is running *) winner: ^bike; do (if bikeQ.active>1 then (* More than one player running *) bikeQ.scan(# where:: (# do (current.state=status.running)->value #); do current.move #); (* Move each running player *) ddTime->start; (* Restart Timer *) else (* One player left - the winner *) bikeQ.find (# predicate:: (# do (current.state=status.running)->value #) #) ->winner[]; (if winner[]<>none then winner.win if); true->main.commandBox.startButton.sensitive; false->main.commandBox.fastButton.sensitive; if); #); (* timeOut *) #); (* dtt *) matrix: @ (# (* Keep track of all points used *) sdw: [W] @sdhObj; (* 2D-boolean matrix - 1st dim. *) sdhObj: (# sdh: [H]@integer #); (* - 2nd dim. *) (* Functions *) check: (# x, y: @integer; enter (x, y) #); (* I'm lazy! *) checkWall: check(# exit ((x<1) or (x>W) or (y<1) or (y>H))#); checkPoint: check(# exit (sdw[x].sdh[y] >= 0) #); set: (# x, y, c: @integer; enter (x, y, c) do c->sdw[x].sdh[y]; #); unset: (# x, y: @integer; enter (x, y) do -1->sdw[x].sdh[y]; #); redraw: (# (* Redraw all points *) do (for x: W repeat (for y: H repeat (if (x,y)->checkPoint then (x,y,sdw[x].sdh[y])->dispQ.point if) for); for); #); init: (# do (for i: W repeat (for j: H repeat (i, j)->unset for) for) #); #); (* matrix *) (* Start positions *) startPos: (# x,y,c: @integer; d: ##direction do inner exit (x,y,d##,c) #); LeftUp: startPos(# do (5, 5, South##, 0)->(x, y, d##, c) #); RightDown: startPos(# do (W-5, H-5, North##, 1)->(x, y, d##, c) #); LeftDown: startPos(# do (5, H-5, North##, 2)->(x, y, d##, c) #); RightUp: startPos(# do (W-5, 5, South##, 3)->(x, y, d##, c) #); MiddelUp: startPos(# do (W/2, 5, South##, 4)->(x, y, d##, c) #); MiddelDown: startPos(# do (W/2, H-5, North##, 5)->(x, y, d##, c) #); LeftMiddel: startPos(# do (5, H/2, East##, 6)->(x, y, d##, c) #); RightMiddel: startPos(# do (W-5, H/2, West##, 7)->(x, y, d##, c) #); startPosL: @list (# element:: startPos; init:: (# do (&LeftUp[], &RightDown[], &LeftDown[], &RightUp[], &MiddelUp[], &MiddelDown[], &LeftMiddel[], &RightMiddel[]) -> (append, append, append, append, append, append, append, append) #); #); direction: (# x,y: @integer; enter (x, y) do inner exit (x, y) #); East: direction(# do x+1->x #); South: direction(# do y+1->y #); West: direction(# do x-1->x #); North: direction(# do y-1->y #); dirCL: @cyclicList (# element:: direction; init:: (# do (&East[], &South[], &West[], &North[]) ->(append, append, append, append) #); #); do dirCL.init; startPosL.init; bikeQ.init; (* Init 'global' lists and queues *) dispQ.init; statusListQ.init; matrix.init; main.init; (* Init matrix and open main window *) #) --=-=-=--