Skip to content

Instantly share code, notes, and snippets.

@vmarquez
Created September 12, 2012 00:05
Show Gist options
  • Save vmarquez/3703153 to your computer and use it in GitHub Desktop.
Save vmarquez/3703153 to your computer and use it in GitHub Desktop.
Simple multiplayer, concurrent 'game' written in scala trying to minimize mutability
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import akka.actor.Actor
import akka.dispatch.Future
import akka.actor.ActorSystem
import akka.actor._
import scala.collection.JavaConversions._
import scala.collection.JavaConversions
import akka.pattern._
import akka.util.Timeout
import akka.util.duration._
import akka.dispatch.ExecutionContext
import java.util.concurrent.Executors
object BombGame {
val system = ActorSystem("BombGame")
def main(args:Array[String]) {
val l = new CountDownLatch(3)
val u1 = createUser(1)
val u2 = createUser(2)
val u3 = createUser(3)
for (user <- u1.move(4);
user <- user.move(5);
user <- user.throwBomb(6);
user <- user.throwBomb(4)) {
l.countDown()
}
for (user <- u2.move(9);
user <- user.move(6);
user <- user.throwBomb(7);
user <- user.throwBomb(4)) {
l.countDown()
}
for (user <- u3.move(7);
user <- user.move(4);
user <- user.throwBomb(5);
user <- user.throwBomb(5)) {
l.countDown()
}
//print out who is wounded the most?
l.await()
GlobalState.getAll().foreach(user => println("Id:"+user.id + " Position:"+user.position + " damage:"+user.damage + " hitCount:"+ user.killCount))
}
def createUser(i:Int): User = {
val user = User(id=i, position=getRandom(),actor= system.actorOf(Props[UserActor], "useractor"+i))
GlobalState.putUser(user)
user
}
def getRandom() = Math.abs(scala.util.Random.nextInt() % 9)
}
//we need a way to share state with other threads, but we are using mutabiltiy here...
//feels like there's got to be a more structured way to do this so I won't forget to update it...
//not to mention race conditions for trying to update two users at once transactionally
object GlobalState {
val userMap = new ConcurrentHashMap[Int,User]()
def getUser(id:Int) = Option(userMap(id))
def putUser(u:User): User = {
userMap.put(u.id, u)
u
}
def getAll() = userMap.values.toList
}
abstract class Action
case class Move(user:User, position:Int) extends Action
case class Look(user:User, position:Int) extends Action
case class ThrowBomb(user:User, position:Int) extends Action
case class Shot(user:User, position:Int) extends Action
case class User(id:Int, position:Int, damage:Int = 0, killCount:Int = 0, actor:ActorRef) {
implicit val timeout = Timeout(5 seconds)
def move(i:Int) = ask(actor, Move(this,i)).mapTo[User]
def look(i:Int) = ask(actor, Look(this,i)).mapTo[(User,Seq[User])]
def throwBomb(i:Int) = ask(actor, ThrowBomb(this,i)).mapTo[User]
def shot(i:Int) = ask(actor, Shot(this, i)).mapTo[(Boolean,User)]
}
class UserActor extends Actor {
implicit val ec = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool) //not sure if I should be using the EC from the ActorSystem...
def updatedUser(u:User) =
GlobalState.getUser(u.id).getOrElse(u)
def receive = {
case Move(u,pos) =>
val user = updatedUser(u).copy(position=pos)
sender ! GlobalState.putUser(user)
case Look(u, pos) =>
val user = updatedUser(u)
sender ! (user,GlobalState.getAll().filter(user => user.position == pos ))
case ThrowBomb(u,pos) =>
val users= GlobalState.getAll()
val responseFuture = Future.sequence(users.map(_.shot(pos)))
val s = sender //uh, ths is weird
for (responses <- responseFuture) {
val user = updatedUser(u)
val kills = responses.filter(t => t._1).size
s ! GlobalState.putUser(user.copy(killCount = user.killCount+kills))
}
case Shot(u, position) =>
val user = updatedUser(u)
val t =
if (user.position == position)
(true,GlobalState.putUser(user.copy(damage=user.damage+1)))
else
(false,user)
sender ! t
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment