Posterous theme by Cory Watilo

Dead Space 2 Mini-Strategy Guide

I freaking love this game. Here are some observations I've made so far.

General Strategy

There are two important considerations in this game: mechanics and efficiency. Mechanics are the basics that are shared across most shooters. Things like aiming, positional awareness, pathfinding, and skill usage fall under mechanics. This game has really interesting mechanics like dismemberment and stasis, but for the most part if you are good at most shooters you will figure these aspects of the game out pretty quickly.

The other main consideration is efficiency. This is unlike most shooters because, if you tune the difficulty properly, you will find that resource management is a serious challenge in this game. By resource management, I mean "do I have enough bullets to kill all the zombies?" Making decisions that lead to the answer being yes is the other really interesting thing about dead space.

Mechanics

Let's get this part out of the way.

Stay out of aim mode until you are ready to shoot. The camera turns more quickly in normal mode, so you can react to surprise attacks better in this mode.

Once you have killed an enemy, if you have the "APM," try to pick up one of their claws to use against another enemy. It seems to do about as much damage as an upgraded plasma cutter shot.

Melee might as well not exist for all the good it does. However, you do have to hit an enemy once more after it is dead to get its loot. The stomp is the best way to get this done most of the time. Be careful that the enemy is actually dead (use tk to pick off one of its claws, for example).

Stasis recharges automatically in this game, although the cooldown is long. That said, if you are engaging an enemy and you have full stasis, you should use it if you think there is any chance you will miss or waste any shots. It's always wise to trade an unlimited resource for a limited one.

As in Dead Space 1, backing away from enemies in a large room is a bad idea. You know what is in front of you, but you don't know what is behind you. If there are too many enemies in front, use stasis to slow them down and dismember them while slowly advancing. If you must fall back, make sure to turn and run, not backpedal.

Upgrades

Most weapons have a few low-hanging fruit upgrade options. Focus on damage nodes. If you play carefully, reload speed, fire rate, and magazine size will almost never matter. Except for damage, all other properties tend to decrease your efficiency: they encourage firing faster, not better. On the other hand, damage tends to increase your efficiency, since it will take fewer rounds to cut off that necro's limb with more damage.

The special node for the plasma cutter does not seem to be worth the power modules it takes to get there. Stop at the last damage node.

You can boost the line gun's damage by 40% with just three nodes. These nodes are more efficient than any of the other bread-and-butter weapons that I've played with. Make sure to get these early.

The line gun's "width" attribute is like damage. More width makes the weapon more efficient, although only in a certain limited set of circumstances. This game seems to have fewer situations where there are tons of enemies running at you in a narrow corridor compared to Dead Space 1.

Maximally upgrade stasis as soon as possible.

Consider base damage when deciding what to upgrade. The plasma cutter has 10 base damage. Each upgrade grants 20% more damage of base damage. The line gun also has 20% upgrades, but base damage is 15. That said, since the line gun often severs two limbs (both legs), more damage might just be overkill.

Also be sure to consider how much bonus damage you are getting per node spent.

Unless you are playing on hardcore, wait until later to invest in HP. More HP generally speaking does not increase your efficiency, because it takes the same number of health packs to recharge it.

Always carry an extra power node. So far as I can tell, the node-locked rooms contain strictly more than ten thousand credits worth of equipment. At worst, you can sell the entire contents and come out with more than a power node's worth of credits.

Equipment

Each suit gives you armor, slots, and a special power. You keep all of these except the special power if you revert suits. If you want to min-max, switch to the vintage suit whenever you are shopping. The security suit is better when you are out and about. Don't know about the later suits as I have not gotten there yet.

Don't buy weapons you don't plan to use. The game seems to prioritize the weapons you are carrying when dropping ammunition.

Limit the number of concurrent bash jobs

If you want to limit the number of concurrent bash jobs, you can use code like this:

wait_for_jobs() {
  max_jobs=$1
  while [[ $(jobs -p | wc -l) -gt $max_jobs ]]; do
    sleep 1
  done
}

Inside your bash loop, call the wait_for_jobs function

for x in $(some command producing lots of x); do
  wait_for_jobs 100 # Limits number of jobs to 100 concurrently.
  some_long_running_command &
done

Useful if you have a loop that would otherwise kick off hundreds of thousands of jobs (which would cripple your computer, or more likely cause it to lock up).

FUSE python bindings reviewed

I'm trying to get a userspace filesystem set up for a personal project I'm working on. Ideally it will work on Mac and Windows, with Linux as a minor bonus. I'm writing in Python. I'm developing the first cut on Mac OS X.

First I tried fuse-python. Installation required modifying system header files -_- (comment out the include for osreldate.h in pyports.h). After installation, the examples did not work. access(2) is not implemented in hello.py, which makes Mac OS X choke. Even after implementing that, I was unable to get things to work. First, I got generic I/O errors. I tried adjusting the stat and statvfs results returned by the program, and although this changed the error message ("device not configured"), it wasn't enlightening. There is almost no discussion of the library online and the documentation is not even a quarter baked.

Attempt two used fusepy. Installation is simple (just copy the python file into your source tree, assuming you have the actual fuse c libraries installed). Licensing is less restrictive. And the example file systems work on Mac OS X without modification. Documentation seems slightly better too. Best of all, it's going to provide me some good example code for writing a wrapper around Dokan, Windows' version of FUSE.

If you're thinking about doing a userspace filesystem that needs to work on Mac OS X and Linux, I recommend fusepy.

Fixing Eclipse Scala plugin slowness

The Eclipse Scala plugin was really slow for me on my new mac. At first, I thought it was just because this mac is slower than my old one. However, reading a few internet posts convinced me to have another look.

When I turned on the heap size viewer in Eclipse, I saw that the memory usage was running up against the max heap size. As a quick fix, I tried adjusting the value of the following two flags in Eclipse.ini:

-Xms256m
-Xmx1028m

Increasing respectively the min and max heap sizes. It worked like a charm and now Eclipse is thrumming along without all the hiccups it was experiencing before. Basically what was happening was transient objects were being garbage collected a lot and it was using lots of CPU.

Requirements for languages I will consider using in hobby projects (part 1)

Requirements
  • Have a read-evaluate-print loop. (Eliminates C++, C, various other languages.)
  • Provide a way to use different comparators in basic sort functions. (Eliminates Go.)
  • Have an sane interface to SQL systems like SQLite. (Sort of eliminates Go and Lisp.)
  • Have a way to split strings by a delimiter in the standard library. (Eliminites Common Lisp.)
  • Have regular expressions. (Eliminates Common Lisp.)
Non-Requirements
  • Fast.
This may be part of an ongoing series.

Negamax in Scala (untested)

Here I have untested negamax code for my mini-game that I'm working on in Scala. It's not too bad compared to some of what I produced earlier. It's not very generic, but what do you want from a hobby project?

A few main things that could be improved:

  • The user should be able to provide a move generator.
  • Moves should be a generic object type rather than the implementation dependent ones I have now

Specific to my case, the MoveGenerator should try to generate moves in an optimal order. Another issue is that some trees will never be explored in the current implementation. For example, any case where one piece needs to move where another is already standing.

class Negamax(runningTeam:Team) {

/**

* Runs the negamax algorithm on world, returning the best score, the updated location of the piece we moved

* to get to it, and optionally the piece we attacked.

*/

def negamax(world:Map, depth:Int, alpha:Double, beta:Double, t:Team):Tuple3[Double,Piece,Option[Piece]] = {

// Decide if this node is terminal and return its heuristic if so.

val color:Int = teamColor(t)

val terminal = depth == 0 || (world.position.find(p => p.team == t) match {

case Some(p) => false

case None => true

})

if (terminal) {

return (color * Heuristic.evaluate(world, runningTeam), null, null)

}

var bestChild:Tuple3[Double,Piece,Option[Piece]] = null

for (move <- MoveGenerator.moves(world, t)) {

val child = negamax(move._1, depth, alpha, beta, t)

if (child._1 > bestChild._1) {

// If the child's score is better than the best child so far, replace the best child.

bestChild = (child._1, move._2, move._3)

}

if (bestChild._1 >= beta) {

// If the best child's score fails beta, return immediately.

return bestChild

}

}

if (bestChild != null) {

return bestChild

} else {

// No children means no more moves for our team. Switch teams and eval again.

val enemyChild = negamax(world.resetTeam(t.other), depth - 1, -beta, -alpha,  t.other)

// Must invert the score when switching teams. 

return (-enemyChild._1, enemyChild._2, enemyChild._3)

}

}

def teamColor(t:Team):Int = if (t == runningTeam) 1 else -1

}

a-star search in Scala, part two

A less horrific version of what I posted yesterday. Improvements in this version:

- Does not depend on internal structures in my program.
- Requires little additional code in yours.

This language is still terrifying.

/**

 * Can search for a shortest path between two nodes of type T that are subtypes of Searchable[T].

 * Searchable[T] must provide several functions that are required for a-star.

 */


import scala.collection.immutable.TreeSet

import scala.collection.SortedSet

import scala.collection.mutable.HashSet


trait Searchable[T] {

/** The cost of this particular searchable node. */

def cost: Int

/** The heuristic distance between this searchable and another.

*  Must be admissible in the sense of a* search.

*/

def heuristic(other: T): Int


/** Return all the adjacent searchable nodes. */

def adjacent: List[T]

}


class AStarSearch[T <: Searchable[T]] {

class Node(o: T, from: Node, dest: Node) {

val searchable = o

/** Actual cost up to this point. */

val actual: Int = if (from == null) 0 else from.actual + searchable.cost

/** Estimate of the cost from here to the goal. */

val estimate = if (dest == null) 0 else actual + searchable.heuristic(dest.searchable)

/** Return a list of nodes that are adjacent to the current node.*/

def adjacent = searchable.adjacent.map(s => new Node(s, this, dest))

override def hashCode = o.hashCode

override def equals(other: Any) = searchable.equals(other.asInstanceOf[Node].searchable)

override def toString() = searchable.toString

/** Get the path from the start to this node. */

def toPath = toPathHelp.reverse

/** Yields a list of the path up to this point, in reverse. */

def toPathHelp: List[T] = if (from != null) searchable :: from.toPathHelp else List(searchable)

}

class EditablePriorityQueue {

var open = new TreeSet[Node]()(Ordering.by((n: Node) => (n.estimate, n.searchable.hashCode)))

val openSet = new HashSet[Node]

def push(n: Node) = {

openSet.add(n)

open = open + n

}

def pop(): Node = {

val n = open.head

remove(n)

return n

}

def nonEmpty() = open.nonEmpty

def remove(n: Node) = {

openSet.removeEntry(n)

open = open - n

}

def update(n: Node): Boolean = {

val incumbent = openSet.findEntry(n).getOrElse(null)

if (incumbent == null) return false

if (incumbent.actual > n.actual) {

remove(incumbent)

push(n)

}

return true

}

}


def search(from: T, to: T): List[T] = {

val closed = new HashSet[Node]

val open = new EditablePriorityQueue()

open.push(new Node(from, null, new Node(to, null, null)))

while (open.nonEmpty) {

val elem = open.pop()

if (elem.searchable == to) {

// If we've reached the goal node . . .

return elem.toPath

}

closed.add(elem)

// Iterate over the neighbors that are not in the closed set.

for (n <- elem.adjacent.filter(n => !closed.contains(n))) {

// Replace the incumbent if n is better.

if (!open.update(n)) {

// If no incumbent exists, simply add n.

open.push(n)

}

}

}

return null

}

}