The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Making a MU* in 2 hours...

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
Making a MU* in 2 hours... Posted: Jan 9, 2005 6:36 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: Making a MU* in 2 hours...
Feed Title: Michael Lucas-Smith
Feed URL: http://www.michaellucassmith.com/site.atom
Feed Description: Smalltalk and my misinterpretations of life
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Michael Lucas-Smith

Advertisement

I've got a lot of friends who are really in to online role playing games. I used to be mad about them too - these days they're a random distraction. But the technology behind them is fascinating - imagine an Object Oriented system that auto-persists changes, has its own built in programming language that is reflective and is a multi-user system with security!

That's, at its heart, what a mu* is all about /for the programmer/. For the users - it's a game where you can create to your hearts content. Any way, back to the programmer :) - I wanted to see how easy it would be to make such a game in Smalltalk.

First off, peristence that doesn't get in the way - I used Prevayler - not your average Java-ish prevayler. This Prevayler is the one That Janko Mivsek had me write that is completely transparent to the developer (almost completely, so completely it doesn't work a mention).

This meant that to persist the game objects, alls I had to do was put them in a Dictionary at Prevayler.Repository root. Too easy. Okay so on to the game. One interesting design that most of these games has is that they're prototypical based systems. Each object has a parent and it inherits properties from that parent.

Not only that, but some properties are so named cmd_* so that they are a 'command' that can be executed. So a GameObject for me had a couple of instance variables: name, properties, contents, parent, location, owner

So we probably also want subclasses of that for Player, Room, Exit, etc.. I give a Player a password as well and I give an Exit a destination. Room's have exit's - so if you put an Exit in a room, instead of adding it to contents, it gets added to exits. Not much code to do that..

GameObject>>location: aLocation
location = aLocation ifTrue: [^self].
location ifNotNil: [location removeContent: self].
location := aLocation.
location ifNotNil: [location addContent: self]

GameObject>>addContent: anObject
contents add: anObject

GameObject>>removeContent: anObject
contents remove: anObject

Room>>addContent: anObject
anObject isExit ifTrue: [^exits add: anObject].
super addContent: anObject

Room>>removeContent: anObject
anObject isExit ifTrue: [^exits remove: anObject].
super removeContent: anObject

Right, so now that we have a work, we need to boot strap it. I'll leave that up to the listener - as it involves making a Player, a Room, etc... so lets allow people to connect to our server!

To do this we need to know a little bit about sockets and sockets in VisualWorks. For the server to know that somebody has disconnected, we need to have the server occassionally poll the user for activity. Since we can assume that this is a telnet connection, we can send the telnet commands 'DATA' followed by 'GO AHEAD'. This just two bytes: 255 and 249.

So lets open up the socket:
(server := SocketAccessor newTCPserverAtPort: 8889) listenFor: 1.
Then lets listen for connections:
listener := [[self listen] repeat] newProcess.
listener name: 'Game: Inbound Connections'.
listener resume

If we hold on to the listener and the server like this, we can then stop the server later. We can terminate the listener process and close the server socket. So what does listen do?

GameServer class>>listen
server acceptNonBlock ifNotNil: [:connection | self newConnection: connection].
(Delay forMilliseconds: 250) wait

We put in the delay just to slow down the server. We don't want to get flooded with connections and we don't want to spend 99% of our CPU constantly checking if there's anyone there.

Once we have a new connection, we want to set up the Game and we also want to set up the KeepAlive. The first thing we do with the new connection is to tell it to 'GO AHEAD' so that the telnet negotiation process is terminated.

GameServer class>>newConnection: connection
| socket |
connection nextPutAll: (String with: 255 asCharacter with: 249 asCharacter).
connection upToEnd. "this is what the client has to say about its capabilities."
socket := GameSocket onConnection: connection.
self newSocket: socket

GameServer class>>newSocket: socket
| gameProcess keepAliveProcess |
gameProcess := [socket play] newProcess.
gameProcess name: socket name, ' - Game'.
gameProcess socket: socket.
keepAliveProcess := [gameProcess keepAlive] newProcess.
keepAliveProcess name: socket name, ' - KeepAlive'.
keepAliveProcess socket: socket.
keepAliveProcess resume.
gameProcess resume

Now we have two threads running in the system. One will service the users needs and the other will make sure the game keeps running and knows when the user has disconnected.

Process>>keepAlive
[(Delay forMilliseconds: 250) wait.
self socket nextPutAll: (String with: 255 asCharacter with: 249 asCharacter).
self socket isClosed ifTrue: [self terminate].
(self socket isClosed not and: [self isTerminated]) ifTrue: [Mug.GameServer newSocket: self socket].
self socket isClosed or: [self isTerminated]] whileFalse.

Here, the Process instance we're on is the Game thread. But the thread we're in is the keepAlive thread. Tricky eh? So that means when socket isClosed, we can terminate the game thread by going 'self terminate'. Better yet, if the game is being debugged and the game process ends up dieing, the keep alive can detect this with self isTerminated - and start a new thread to service the user - so the user isn't left with an unresponsive terminal.

From here's it's mostly academic - putting in commands, etc.. but HOW you do it next is interesting. I had two friends connected at this stage and I quickly bootstrapped a chatting program. Very shortly after that I started adding commands - while they were connected. I could get them to try out my commands (free testing!) as I changed them and added new ones. By the end of two hours we had rooms, you could move between them, chat, pose, look, set properties, so on and so forth...

The game is published to Public Store as MultiUserGame (Mug). It's not finished by a long shot - what it needs right now is security so that a game user can write Smalltalk and not circumvent security in (a) the server or (b) the game. This is in the works.. I'll write about that some more later..

Read: Making a MU* in 2 hours...

Topic: What makes a story? Previous Topic   Next Topic Topic: Email is still broken

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use