Skip to content

Instantly share code, notes, and snippets.

@vmarquez
Created October 5, 2012 00:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vmarquez/3837265 to your computer and use it in GitHub Desktop.
Save vmarquez/3837265 to your computer and use it in GitHub Desktop.
This is a simple 'mulit-player, concurrent' game that attempts to reduce the number of mutable variables to a minimum.
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import akka.dispatch.Future
import akka.dispatch.Promise
import scala.collection.JavaConversions._
import scala.collection.JavaConversions
import akka.dispatch.ExecutionContext
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicReference
object BombGameNoActorsOrVars {
def main(args:Array[String]) {
val l = new CountDownLatch(3)
implicit val stateMap = new StateMap[Int,User]()
val u1 = createUser(1)
val u2 = createUser(2)
val u3 = createUser(3)
for (user <- u1.move(4);
_ = Thread.sleep(4000); //shows that the other two users can proceed while this one is waiting
user <- user.move(5);
user <- user.throwBomb(6);
user <- user.throwBomb(4)) {
println("finished user1")
l.countDown()
}
for (user <- u2.move(9);
user <- user.move(6);
user <- user.throwBomb(6);
user <- user.throwBomb(6)) {
println("finished user 2")
l.countDown()
}
for (user <- u3.move(7);
user <- user.move(4);
user <- user.throwBomb(0);
user <- user.throwBomb(0)) {
println("finihsed user 3")
l.countDown()
}
l.await()
println("show the stats of the game")
stateMap.getAll().foreach(user => println("Id:"+user.id + " Position:"+user.position + " damage:"+user.damage + " hitCount:"+ user.killCount))
}
def createUser(i:Int)(implicit userMap: StateMap[Int,User]): User = {
val user = User(id=i, position=getRandom())
userMap.put(i,user)
user
}
def getRandom() = Math.abs(scala.util.Random.nextInt() % 9)
}
case class User(id: Int, position: Int, damage: Int = 0, killCount: Int = 0)(implicit userMap: StateMap[Int,User]) {
implicit val ec = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool)
implicit val getRef = ()=>userMap.get(id)
def move(i: Int) = wrapPromise(p => {
p.success(user => user.copy(position=i))
})
def throwBomb(pos:Int): Future[User] = wrapPromise(p => {
val users = userMap.getAll()
val responseFuture = Future.sequence(users.filter(u => u.position == pos).map(_.shot(pos)))
for (responses <- responseFuture)
p.success(u => u.copy(killCount = u.killCount+responses.size))
})
def shot(i:Int): Future[User] = wrapPromise(p => {
p.success(u => u.copy(damage = u.damage+1))
})
def wrapPromise(f: SynchronizedPromise[User]=>Unit): Future[User] = {
val promise = SynchronizedPromise[User]()
f(promise)
promise.future
}
}
//we need a way to share state with other threads, but we are using mutabiltiy here...
//kind of weird I just have a mutalbe map floating around in an object. Better ways to do this?
class StateMap[K,V] {
private val userMap = new ConcurrentHashMap[K,AtomicReference[V]]()
def put(k: K,v: V) = userMap.put(k,new AtomicReference(v))
def get(k:K) = userMap.get(k)
def getAll() = userMap.values().map(_.get)
}
class SynchronizedPromise[T](getRef: ()=>AtomicReference[T]) {
implicit val ec = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool)
private val innerPromise = Promise[T]()
def map[A](f: T=>A) = innerPromise.map(f)
def flatMap[A](f: T=>Future[A]) = innerPromise.flatMap(f)
def success(f: T=>T) {
val ref = getRef()
var success = false
do {
val originalval = ref.get
success = ref.compareAndSet(originalval, f(originalval))
} while(!success)
innerPromise.success(ref.get())
}
def future = innerPromise.future
}
object SynchronizedPromise {
def apply[T]()(implicit getRef:()=>AtomicReference[T]) =
new SynchronizedPromise[T](getRef)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment