Wednesday, March 10, 2010

HTTPResponseHeader

/** HTTP Response Header Class
This class takes a byte array and parses it into a HTTP Response Header

Author:  Daniel J. Tanner
Date:    March 9th, 2010
CMPT 352
*/

import java.io.*;
import java.net.*;
import java.lang.*;

public class HTTPResponseHeader
{
    //Fields
    protected String httpVersion_;
    protected String code_;
    protected String message_;
    protected String date_;
    protected String serverName_;
    protected String contentType_;
    protected long contentLength_;
    protected static final String DEFAULT_CODE = "200";
    protected static final String DEFAULT_MESSAGE = "Ok";
    protected static final String DEFAULT_HTTPVERSION = "HTTP/1.0";
    protected static final String DEFAULT_DATE = "January 1st, 1970";
    protected static final String DEFAULT_SERVERNAME = "So Long, And Thanks For All The Fish!";
    protected static final String DEFAULT_CONTENTTYPE = "matter/dark";
    protected static final long DEFAULT_CONTENTLENGTH = 42;
   
    //Constructors
    public HTTPResponseHeader()
    {
        code_ = DEFAULT_CODE;
        message_ = DEFAULT_MESSAGE;
        httpVersion_ = DEFAULT_HTTPVERSION;
        date_ = DEFAULT_DATE;
        serverName_ = DEFAULT_SERVERNAME;
        contentType_ = DEFAULT_CONTENTTYPE;
        contentLength_ = DEFAULT_CONTENTLENGTH;
    }
   
    public HTTPResponseHeader(String httpVersion, String code, String message, String date, String serverName, String contentType, long contentLength)
    {
        code_ = code;
        message_ = message;
        httpVersion_ = httpVersion;
        date_ = date;
        serverName_ = serverName;
        contentType_ = contentType;
        contentLength_ = contentLength;
    }
   
    //set functions
    public void setCode(String code) {code_ = code;}
    public void setMessage(String message) {message_ = message;}
    public void setHTTPVersion(String httpVersion) {httpVersion_ = httpVersion;}
    public void setDate(String date) {date_ = date;}
    public void setServerName(String serverName) {serverName_ = serverName;}
    public void setContentType(String contentType) {contentType_ = contentType;}
    public void setContentLength(long contentLength) {contentLength_ = contentLength;}
   
    //get functions
    public String getCode() {return code_;}
    public String getMessage() {return message_;}
    public String getHTTPVersion() {return httpVersion_;}
    public String getDate() {return date_;}
    public String getServerName() {return serverName_;}
    public String getContentType() {return contentType_;}
    public long getContentLength() {return contentLength_;}
   
    //print function
    public byte[] getBytes() throws IOException
    {
        String header = httpVersion_ + " " + code_ + " " + message_ + "\r\n"
            + "Date:  " + date_ + "\r\n"
            + "Server:  " + serverName_ + "\r\n"
            + "Content-Type:  " + contentType_ + "\r\n"
            + "Content-Length:  " + contentLength_ + "\r\n";
        return header.getBytes("US-ASCII");
    }
       
}

HTTPRequestHeader

/** HTTP Request Header
This Class represents a HTTP Request Header
It takes a byte array and Parses it, basically
Author: Daniel J. Tanner
Date:  March 9th, 2010
CMPT 352
*/
import java.io.*;
import java.net.*;
import java.lang.*;

public class HTTPRequestHeader
{
    //fields
    protected static final String DEFAULT_METHOD = "GET";
    protected static final String DEFAULT_PATH = "/index.html";
    protected static final String DEFAULT_PROTOCOL = "HTTP/1.0";
    protected static final int CHUNK = 5028;
    protected String method_;
    protected String path_;
    protected String protocol_;
   
    //Constructors
    public HTTPRequestHeader()
    {
        method_ = DEFAULT_METHOD;
        protocol_ = DEFAULT_PROTOCOL;
        path_ = DEFAULT_PATH;
    }
   
    //This one is basically the meat, it will parse the byte stream it receives
    public HTTPRequestHeader(InputStream fromClient)
    {
        BufferedReader input = new BufferedReader(new InputStreamReader(fromClient));
        String requestLine = null;
        String requestParsed = null;
        StringBuffer request = new StringBuffer(CHUNK);
        int i = 0;
        try
        {
            requestParsed = input.readLine();
       
            while((requestLine = input.readLine()) != null)
            {
                if(requestLine.length() == 0) break;
                i += requestLine.length();
                request.append(requestLine);
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
            i = -1;
        }
       
        //Now we parse!
        //String requestParsed = request.toString();
        int index1, index2;
        i = requestParsed.length();
        index1 = requestParsed.indexOf(' ');
       
        //getMethod
        if(index1 != -1)
        {
            method_ = requestParsed.substring(0, index1 - 1);
        }
        index2 = requestParsed.indexOf(' ', index1 + 1);
       
        //getURI
        if(index2 > index1)
        {
            path_ = requestParsed.substring(index1 + 1, index2 - 1);
        }
        index1 = requestParsed.indexOf(' ', index2 + 1);
       
        //getProtocol
        if(index1 > index2)
        {
            protocol_ = requestParsed.substring(index2 + 1, index1 - 1);
        }
       
    }
   
    //Get Functions
    public String getMethod() {return method_;}
    public String getProtocol() {return protocol_;}
    public String getPath() {return path_;}
}

ConfigurationException

public class ConfigurationException extends Exception
{
    public ConfigurationException(String exception) {
        super(exception);
    }
}

Configuration

/**
 * XML parser for configuration parameters.
 *
 * This maps configuration parameters to a HashMap and are retrieved
 * through the following getter methods:
 *
 *     public String getLogFile()
 *     public String getDocumentRoot()
 *     public String getDefaultDocument()
 *     public String getServerName()
 *
 * Usage:
 *     Configuration config = new Configuration();
 *
 *    config.getLogFile();
 *    config.getDocumentRoot();
 *    config.getDefaultDocument();
 *    config.getServerName();
 */

import java.io.*;

import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import java.util.Map;
import java.util.HashMap;

public class Configuration extends DefaultHandler
{
    private Map  map;
    private String configurationFile;

    /**
     * @param File configurationFile - The name of the configuration file
     */
    public Configuration(String configurationFile) throws ConfigurationException {
    this.configurationFile = configurationFile;

    map = new HashMap();

    try {       
            // Use the default (non-validating) parser
            SAXParserFactory factory = SAXParserFactory.newInstance();

            // Parse the input
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse( new File(configurationFile), this);
    }
    catch (javax.xml.parsers.ParserConfigurationException pce) {
        throw new ConfigurationException("javax.xml.parsers.ParserConfigurationException");
    }
    catch (org.xml.sax.SAXException se) {
        throw new ConfigurationException("org.xml.sax.SAXException");
    }
    catch (java.io.IOException ioe) {
        throw new ConfigurationException("java.io.IOException");
    }
    }


    /**
     * We will map each configuration attribute to its value
     *
     * @param namespaceURI the namespace
     * @param lName the local name of the element
     * @param qName the qualified name of the element
     * @param attrs the set of attributess associated with the element
      */
    public void startElement(String namespaceURI,
                             String lName,    
                             String qName,    
                             Attributes attrs)   
    throws SAXException
    {
        String elementName = lName; // element name
        if ("".equals(elementName))
        elementName = qName; // namespaceAware = false

    /**
     * Get the attributes associated with this ELEMENT.
      * Attributes are name/value pairs and are stored by index.
     */
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength(); i++) {
                String aName = attrs.getLocalName(i); // Attr name
                if ("".equals(aName))
            aName = attrs.getQName(i);

        // map the element.attribute to its value
        map.put(elementName+"."+aName,attrs.getValue(i));
            }
        }
    }

    // getter methods for mapped configuration values

    /** Returns the location of the log file */
    public String getLogFile() {
        return map.get("logfile.log");
    }

    /** Returns the location of the document base */
    public String getDocumentRoot() {
        return map.get("context.documentRoot");
    }

    /** Returns the name of the default document */
    public String getDefaultDocument() {
        return map.get("context.defaultDocument");
    }

    /** Returns the name of the server */
    public String getServerName() {
        return map.get("webserver.title");
    }
   
    /** Returns the name of the 400 file */
    public String get400()
    {
        return map.get("context.fourHundredDocument");
    }
   
    /** Returns the name of the 404 file */
    public String get404()
    {
        return map.get("context.fourOhFourDocument");
    }
}

TannerWebServer (not working)

/** Web Server
by Daniel J. Tanner
March 9th, 2010
CMPT 352

This program implements some basic features of the HTTP protocol
**/

import java.io.*;
import java.net.*;
import java.util.*;


public class TannerWebServer
{
    //constants & globals
    public static final int PORT = 2880;
    public static Configuration configurator = null;
    public static WeakHashMap cache = null;
    public static File logFile = null;
   
    public static void main(String[] args) throws java.io.IOException, ConfigurationException
    {
        //begin by initializng the configurator       
        try
        {
            configurator = new Configuration("../conf/config.xml");
        }
       
        catch (ConfigurationException ce)
        {
            System.out.println(ce);
            System.exit(0);
        }
       
        //load the initial files into a hashmap
        cache = new WeakHashMap(20);
        cache.put("/index.html", new File(configurator.getDefaultDocument()));
        cache.put("/400.html", new File(configurator.get400()));
        cache.put("/404.html", new File(configurator.get404()));
       
        //load logfile
        logFile = new File(configurator.getLogFile());
       
        //Create socket
        ServerSocket sock = null;
        try
        {
            //establish socket
            sock = new ServerSocket(PORT);
           
            while(true)
            {
                Socket client = sock.accept();
                //debug testing
                InetAddress ipAddr = client.getInetAddress();
                System.out.print(ipAddr.getHostAddress() + " : ");
                System.out.println(client.getPort());
                Thread worker = new Thread(new TannConnection(client));
               
                worker.start();
            }
        }
        //catch any errors opening sockets
        catch(IOException ioe)
        {
            System.err.println(ioe);
            System.exit(0);
        }
       
        finally
        {
            //close streams
            if(sock != null)
                sock.close();
        }
    }
}

Wednesday, March 3, 2010

Speaking of NIS...

     It would be a little unfair of me to say "I am a loyal customer of brand X, but here's two games of theirs that suck..." without at least mentioning some of the games that made me like the company in the first place, so here's some Amazon links to a buncha games NIS either made or imported over here that I really like:

Disgaea Series:
Obviously, there's the Disgaea series, which probably marks the best series of S-RPGs ever made, period.













As you can see Disgaea and Disgaea 2 have made the rounds to PSP, and #3 was NIS' first PS3 game.












Other SRPGs:
NIS has developed a wide variety of different approaches to the SRPG.  While Disgaea was perhaps their best-known attempt, all of these games have fascinating battle systems, well-developed storylines and characters, great music, and loooooooooaaaaaaads of replay value.













Imports of Gust games:  The Atelier Series
















On a complete sidenote, I have no idea why some of these are showing up on Amazon as being released by KOEI (EU versions maybe?).  Anyway, all of these games were developed by Gust and released here in the US by NIS America.  The atelier series in general focuses on something completely askance from most RPGs:  Item Creation.  In fact, while the more recent games (and besides Annie, the only ones NIS has shipped so far) actually have an interesting battle system to speak of, most of the atelier series have the most basic battle system you can give an RPG and get away with these days.  And mind you, that's a bit refreshing (it's about as simple as Dragon Quest games).  The main point in these games is typically to go around to different areas where you gather some materials for alchemy (oftentimes you have to visit during a particular season for a specific item), and then you return to your shop and mix them together in a number of different ways.  And of course, your item creation is somewhat directed via certain story sequences (which are controlled by certain event triggers) or simply by keeping track of alchemy requests from the local shops.  Personally, my favorite thing about these games is, again, their music.  Very awesome mix of somewhat techno-ish, somewhat metallish music.

Anyways, there's lots of other games NIS has imported, but I can't speak to many of those (since I haven't played them yet).  I should also mention that the Rosenqueen store (www.rosenqueen.com, NIS America's official storefront) offers all of this stuff, often with equivalent or better customer service.  And, one of the other cool little things about NIS America is that Rosenqueen often keeps stock of other small-release niche titles, such as Atlus' Persona games.

Review: Last Rebellion

     Well, this game was unexpectedly short.  I had bought the game with the idea "oh, I'll reward myself with this as a self-incentive to get some schoolwork done."  Y'know, finish an assignment or a bug chunk of a larger assignment, then play for a few hours.  That kind of thing.  Unfortunately, this game was over after 3 days, in about 5 or 6 sessions of ~3 or 4 hours apiece.  So what's my overall impression?  Dissapointment, really.  Nothing pisses me off more than seeing something this half-assed being released by a company that has, through its past products, earned my respect and my loyalty as a customer.  Now, lemme be clear that this game is not developed by NIS, (except perhaps the inclusion of an extra boss), but rather by a company called Hit Maker.  NIS America, however, did the translation and localization here in the states (and NIS America is the company which I'm losing respect for, to a degree).

Let's start with the graphics:  2/10

     This game really should have been released as a PS2 or PSP game.  The graphics are atrocious compared to even the best of 2D games.  The style seems like something out of a Koei game (which is not, by any means, a compliment) with very effeminate male characters.  None of the cut-scenes animate; we are told the story through still pictures, which again, have questionable aesthetics.  The various zones in the game are generally bland and unimaginative, and the enemies are your standard fantasy fare.  The best art, at least aesthetically, in this game are the loading scene images, which were evidently submitted by guest artists.  And let's not forget the loading scenes.  The maps in this game are teeny tiny compared to most games, made even smaller by the use of teleporters left and right.  And yet, despite that this game shouldn't be using even 1/10th of the system resources, there are loading screens for every transition.  It's pretty clear to me that a) they didn't try for anything that you could even call competitive with the graphics engine here; b) they didn't bother to do any optimization, or include any kind of caching techniques that would allow for instant loads of areas you had visited within the last 20-30 seconds; and c) again, the best style comes from those guest artists.  Why didn't they hire THOSE GUYS?  Seriously.

Onto the sound/music:  1/10:

     First things first: NIS America did not include the Japanese vocals.  Now, you may ask, why is this important?  Because NIS has ALWAYS included the original Japanese dubs on their games (at least for their PS2 and PS3 games anyway).  If nothing else, it's a nice touch to be able to hear the voice actors the original creators of a game picked to represent the characters they created; and for NIS America to have forgone the inclusion of such in this case is a sign of laziness.  To make matters worse, the voice actors chosen by NIS America for the localization were so bad I made an effort to interrupt them and just read through the dialogue because I couldn't stand half of them.  The only semi-appreciable performance was by the cat-girl (Sefi I think?) and the mechanic dude.  Everyone else, especially the main characters, was guilty of not reading the lines in context (at the least) if not plain over-acting.

     As for the music and the sound effects, they weren't particularly memorable.  What was memorable was the few sound bugs during boss battles.  On several occasions, the game kept reloading the music track it was supposed to play, so not only did you only get to hear the first half second or so of the song, but it was warped and warbled and the performance of the game was effected.  Kinda sad that the most memorable thing about the music was a bug...

Story & Dialogue: 1/10
     I don't have much to say of the story and the dialogue other than it's almost non-existent.  I think their intention was to show the main male lead as a kid that acts tough in order to hide his own insecurities.  Either that or he's supposed to be brutally honest and nihilistic.  Whoever wrote the dialogue couldn't decide, apparently.  So anyway, the premise of the game is that this kid, named Nine, that acts like a douchebag to everyone he meets, is supposedly the strongest "blade" around; and right after you start the game you are introduced to Aisha, the sealer.  Farcical dialogue ensues and right away we are introduced to Nine's father, the King whose name we can't pronounce (Erlizand or Elrizan or some such), who gets stabbed almost as soon as we meet him by Alfred, (who we are also just meeting) who is apparently related somehow.  We are also introduced to typical cowardly vassal (whose name I also never bothered to remember).  Now, so far Nine has said he hates pretty much everybody, and while he's yapping his mouth off about how he's going to kill Alfred, even though he doesn't apparently care about anything, the King, now in zombie form, stabs Nine in the back.  Next thing we know, Nine and Aisha are talking together in a metaphysical room in some kind of subspace because Aisha has used a forbidden spell to bind his soul to hers.  Clearly, if a guy you've just met treats you like shit, he must be worth literally giving your body to, right?  Anyways this is just the opening scene, and I mention it because the entire game is about as believable.  I don't know, maybe I was a little harsh on the actors earlier.  They really weren't given much to work from.

     I suppose I should also talk a bit about the backstory of the game a bit.  As you might have guessed from the zombifying thing with the King, this game involves lots of stuff coming back from the dead.  Apparently the god of life has given too much life into the world and disrupted the passage of souls out of the world, so you side with the goddess of death to restore balance.  Only, in reality neither of the gods really care about what's going on, and you end up stopping the goddess of death from destroying your world on a whim (again, with farcical dialogue with characters acting and saying stuff that's not really believable).  Anyway, pretty common fare in videogames to imagine a world where people are merely the playthings of gods, and the heresy or indifference of your playable characters is supposed to be wisdom or special insight.  Whatever.  I've seen NES games with more depth in that regard.

Gameplay: 4/10


     The only somewhat interesting bit about this game is the battle system, where everytime you attack a given monster, you have a list of places to aim your attack, and every attack stamps the target.  Now, if you hit the right places in the right order, you get bonus points, which can up your experience points after the battle significantly, as well as rewarding you with rarer items.  The stamps are also required to cast hostile spells on the enemy.  The tutorial in the game actually tells you a few things involved with the gameplay that aren't actually implemented, though.  For one thing, the tutorial says you can decrease an enemy's speed, attack power, or defenses by hitting certain points on their body.  This never happened in any of the fights.  There are red points, where if you hit a particular body part out of sequence you enrage the enemy, and he gets a double or triple damage boost...but otherwise attacking doesn't provide any extra benefits.  So that's the basic idea of the battle system, but let me go into a bit more detail on some specific details that bugged me.

     Status ailments in this game are poorly designed.  First of all, both of your characters share the same body, and while that goes along with the story well enough, it means that when you get stunned, paralyzed, or put to sleep (all of which have the same animation, going back to lazy graphics designers, but I digress), you literally can't do ANYTHING to get yourself out of the status ailment.  Which is funny, because the game gives you spells which cure status effects, but rather than a direct cure, they make you immune to said status ailment for 5 turns.  Now, look, if you get an ailment right off the bat (not unusual when you're fighting opponents that are your level or above), you're likely to take quite a bit of damage during the interim between when you first get stunned and when you can act again.  So you now have to make a decision about whether to get off a healing spell, or to cast a preventative anti-ailment spell.  To add to the problem, you often fight enemy groups made up of several different kinds of monsters, each having typically two status effects they can inflict.  This is poor design, plain and simple.

     I should also mention the magic system in this game.  While it's an interesting idea that you have to stamp your targets before you hit them, you would normally expect this to mean that your magical damage is going to hit all the harder.  Not so, for the most part.  Unless you can find out which branch of spells your opponent is especially weak to, and use the highest rank spell of that school at the highest level on them, even your "sealer" hits harder with her regular weapons against said opponent.  Especially once you've gained some levels, since your magic levels only when you collect "aria papers" which you can constantly use and unuse on any given skill in your library to increase its effectiveness (only out of combat, mind).  You also have to assign your spells to one or the other of your characters to use.  While the obvious is that your sealer has slightly better magical damage and your blade has slightly better physical damage, in the end it's only marginally different.  Granted, you should have to plan ahead a little in any given RPG when deciding equipment and spells and all, but this only ends up being an additional annoyance when you find yourself in the situation where you want to use any number of different spells (say, to restore your health, to remove or prevent status effects, to restore your CP, etc) and have to deal with both spells being equipped to the same person.  To sum up, then, for all the various magic spells you're given (and you DO have to be pretty choosy about which spells you put aria papers on so you can use the higher ranks) I found far more spells than I ever found really useful, and magic damage was, again, only marginally more powerful than physical attacks.

     When you play a RPG, there's another type of gameplay you typically expect to find:  puzzles.  Whether it's the simplest of tasks (IE a fetch quest) to a devious labyrinth with traps to avoid left and right, we expect to come across a few places where our mind is really put to the test.  And what does this game have?  Nothing but lameness!  Some of the few useful spells in the game, which you can only use on the world map are run (which increases your running speed) and true sight (which lets you see otherwise invisible enemies and treasure chests).  And lo and behold the biggest "quest" or "puzzle" in the game is a fetch quest to find holy waters, which are hidden in invisible treasure chests, each located in one of the areas you've visited before.  There are two elements to this which, as someone hoping to be a game designer, I find despicable.  First, here is a game requiring you to backtrack significantly, without really adding any real value to backtracking.  The least they could do is have some trigger activate such that when you do get to the point in the game where you have to backtrack, at least the earliest areas' enemies get buffed up.  It was pathetic and annoying to spend 3-5 minutes per battle with enemies that I was killing in one hit (another gripe, after you outlevel an enemy they stop providing experience of any kind, and killing them in one hit means you don't build up any bonus points so the items you get are always their most common drops).  And I almost forgot to mention the worst part about invisible chests:  the true sight spell only lasts like thirty seconds or so and costs a sizable amount of MP.  There's also another part where you have to run across "ether bridges" for lack of a better word, where your character switching ability actually gets some use since the color of a bridge denotes who can cross it (the other character will just fall through).  This is made annoying though, in that there are points where you go straight from a bridge of blue (your male PC) to a red bridge (the female can walk over it), so you have to time your character switch just so or you fall through the floor.  Rather annoying, if you ask me.  Anyhow so the gameplay has a lot of elements that kinda suck, despite the kinda-sorta innovative battle system (though even the battle system was half-assed in that the game talks about elements that are not implemented... and the big element, performing hits in order, has been done better before like in Legend of Legaia or Legend of Dragoon, to name only two).

Overall Score:  2/10 or 20%

Saturday, February 27, 2010

Review: A Witch's Tale




So I just finished one of NIS America's recent imports for the DS:  a game called A Witch's Tale.  A quick glance through the 'net gives the game an average to above-average rating, which I would have to agree with, for the most part.

Story/Plot: 8/10
The plot is fairly entertaining; it takes a lot of its inspiration from Lewis Carroll's Alice in Wonderland and Through The Looking Glass.  At least it does in the sense that most of the characters get names straight out of the book (though your main character is named Liddell, which is the last name of Alice Pleasance Liddell, who Carroll dedicated his books to).

Basically you start as a witch-in-training who wants to discover the really really powerful magic, like Queen Alice used 1000 years ago... Your search leads you to the vampire Loue's castle where you open a ancient tome to find that, instead of getting any magic, you've released the Eld Witch -- an evil witch who had wrecked havoc on the world of 1000 years ago.  So now you have to visit the six kingdoms and set things right.

The storyline goes from there to be pretty predictable (which isn't necessarily a bad thing) though there is some well written dialogue between Loue (who decides to accompany you on the journey) and Liddell.  And while it's not explicit per se, there is quite a bit of backstory and legend developed for each world and its princess... though, again, you have to go a bit out of your way to get said stories from the locals.

It's also worth mentioning that this game takes a cue from Dragon Quest I, in that you only have one real player-character (and you collect dolls to make up the 2 other members of your party).  Some reviews have chosen to demerit the game for lack of player-characters, but after a game like Cross Edge, Final Fantasy X, or even Chrono Cross, to have only one character to manage is actually somewhat refreshing.

Graphics: 8/10
This is a DS game so you can't really judge too harshly on the quality, per se... I find it's more appropriate to examine it by style, and this game DOES have style.  The character designs are your standard anime-type fare (go see games like Ar-Tonelico, Zwei, Disgaea, Atelier, etc) but they are drawn well, all the same.  While we did get to see the DS put out some cheesy cartoons in, say, the Layton games... all A Witch's Tale has are large standstill images to use for its action images.  These are also well drawn, but a little less imagery and a little more motion might've been a nice touch.  Where this game really shines, though, is in its world designs.  I found the graphics did what graphics are supposed to do: help tell the story.  And they do so beautifully.  The backgrounds in this game are on par with some of the best SNES games, artwise.  Lots of little touches and details that really give each world its own distinct atmosphere.

Control: 2/10
Long story short, the only thing you can use for this game is the touch-pen.  All those buttons on the DS do absolutely nothing.  And while I would give this game credit if the touch pen was used in some innovative fashion (like in some of the minigames in Mario and Luigi: Inside Bowser Story, or even in Castlevania: Portrait of Ruin) in this game all it does is drive menus and move your character.  This is equivalent to Square's FFIII usage, only in that game it was an option instead of using strictly the buttons... In A Witch's Tale you aren't allowed the option of using the buttons.  This is annoying for a number of reasons.  First, despite a great number of advances since early touch screen technology, it still isn't all that accurate (you often touch the screen in one place and there's an action a bit above the cursor or a bit below, despite your best efforts at calibration -_-).  I should add that this inaccuracy is especially annoying when trying to draw runes (though it wasn't THAT bad).  Second, for how many battles there are, I really like how in most RPGs you can pretty much hold the confirm button to just "attack" the enemies (or in some of the better ones, you can save your typical action list on a particular recurring set of enemies and keep reusing that)... only in this game there isn't any such option.  So, control kinda fell flat all around.

Gameplay 8/10
So here's the main bread and butter of any DS game.  Gameplay takes two forms in this game:  solving puzzles on the various world maps, and fighting monsters in a typical turn-based RPG format.  The world exploration and puzzles in this game...while not up to say, Golden Sun level, often tell the story of the particular world you're in... what's gone wrong since the Eld Witch came back.  Most of them are pretty much fetch quests (enter dungeon A to find item x which you must use to gain entrance to dungeon B, where you find item z...and so on) but there are a number of dungeons where you have to navigate some interesting traps and such.  Nothing too out of the ordinary for most RPG players, but fun nevertheless.

The battle system is nothing innovative, but it is different than most RPGs in that it heavily favors the use of magic, and the element system is never laid out explicitly, so you therefore have to keep a mental record of the different types of enemies you come across.  And while there is obviously some color swapping even as soon as the third world, this isn't necessarily a bad thing because in a sense, this is the game giving you the ability to keep a short list of different enemies and their weaknesses.  Once you find a given enemy's weakness you can typically one- or two-shot them when you come across them.  Of course, once you come across enemies that can toss up a reflective barrier, you better hope you've got a companion present with a weaker spell so you don't kill yourself.  Thus the battle system is heavily magic-based (as you'd expect when your main character is a witch) and you'll find yourself rarely, if ever, using pure physical damage.  To balance this, the game does tend to throw more random encounters at you than your average RPG, though there seems to be an internal counter where the game will throw fewer battles at you if you are higher level; essentially it measures how much exp you really need for the next area.

Anyway, that's my review.  I'd say the overall score is probably 7.5/10,or 75% if you prefer.  It's not a great game, but it is a nice reprieve in a certain sense from games where heavier involvement is necessary.

Search Amazon.com for Nintendo DS

Wednesday, January 20, 2010

Should Note a Few Things...

For a number of these programs we're developing, we were actually given the skeleton to build on top of (especially some of the upcoming networking stuff) so...yeah, if it doesn't seem like some of my other code in style, that's why.

ColorModel.java ~ A program to translate between various colorspaces

/** Color translation class
Daniel Tanner
January 14th, 2010
CMPT 360
*/

/**
This program will allow for the user to translate
back and forth between different colorspaces.
*/

//import statements
import java.text.*;
import java.util.*;
import java.io.*;

public class ColorModel {
// unless otherwise stated, all color values are
// given as doubles in the range 0.0 - 1.0

// all methods throw an IllegalArgumentException if an unexpected argument is encountered

/**
* converts 'additive' RGB to 'subtractive' CMY
* @param rgb - color in RGB space
* @return color in CMY space
*/
public static double[] RGBtoCMY (double[] rgb) {
for(int index = 0; index < 3; index++)
rgb[index] = 1.0 - rgb[index];
return rgb;
}

/**
* converts 'subtractive' CMY to 'additive' RGB
* @param cmy - color in CMY space
* @return color in RGB space
*/
public static double[] CMYtoRGB (double[] cmy) {
for(int index = 0; index < 3; index++)
cmy[index] = 1.0 - cmy[index];
return cmy;
}

/**
* converts color from RGB to HSV color space
* hue will be a number between 0..360
* @param rgb - color in RGB space
* @return color in HSV space
*/
public static double[] RGBtoHSV (double[] rgb)
{
double min = rgb[0], max = rgb[0], h = 0, s = 0, v = 0;

//find the max and the min
for(int index = 1; index < 3; index++)
{
if(max < rgb[index])
max = rgb[index];
if(min > rgb[index])
min = rgb[index];
}

//finding h (according to wikipedia)
if(max == min)
h = 0;

if(max == rgb[0]) //if max = r
h = (60 * ((rgb[1] - rgb[2])/(max - min)) + 360) % 360;

if(max == rgb[1]) //if max = g
h = (60 * (rgb[2] - rgb[0])/(max - min)) + 120;

if(max == rgb[2]) //if max = b
h = (60 * (rgb[0] - rgb[1])/(max - min)) + 240;

//determine s
if(max == 0)
s = 0;
else
s = (max - min)/max;
v = max;
//put all variables into array

rgb[0] = h; rgb[1] = s; rgb[2] = v;
return rgb;
}

/**
* converts color from HSV to RGB color space
* hue will be a number between 0..360
* @param hsv - color in HSV space
* @return color in RGB space
*/
public static double[] HSVtoRGB (double[] hsv)
{
//using same variable names as wikipedia's article
double i = (hsv[0] / 60) % 6;
double f = (hsv[0] / 60) - (hsv[0] / 60);
double p = hsv[2] * (1 - hsv[1]);
double q = hsv[2] * (1- f * hsv[1]);
double t = hsv[2] * (1 - (1 - f) * hsv[1]);
double v = hsv[2];

//compute RGB based on i
switch((int)i)
{
case 0:
hsv[0] = v; hsv[1] = t; hsv[2] = p;
break;
case 1:
hsv[0] = q; hsv[1] = v; hsv[2] = p;
break;
case 2:
hsv[0] = p; hsv[1] = v; hsv[2] = t;
break;
case 3:
hsv[0] = p; hsv[1] = q; hsv[2] = v;
break;
case 4:
hsv[0] = t; hsv[1] = p; hsv[2] = v;
break;
case 5:
hsv[0] = v; hsv[1] = p; hsv[2] = q;
break;
}
return hsv;
}

/**
* converts color from RGB to YIQ color space
* @param rgb - color in RGB space
* @return color in YIQ space
*/
public static double[] RGBtoYIQ (double[] rgb)
{
//create Matrices
double [][] toYIQNums = {{.299, .587, .114}, {.596, -.275, -.321}, {.212, -.523, .311}};
Matrix linearTransform = new Matrix(toYIQNums);
Matrix rGBMatrix = new Matrix(rgb, 3);

//run the linear transformation (matrix multiplication)
linearTransform = linearTransform.times(rGBMatrix);
rgb = linearTransform.getRowPackedCopy();

return rgb;
}


/**
* converts color from YIQ to RGB color space
* @param yiq - color in YIQ space
* @return color in RGB space
*/
public static double[] YIQtoRGB (double[] yiq)
{
//create Matrices
double [][] toRGBNums = {{1.0, .956, .621}, {1.0, -.272, -.647}, {1.0, -1.105, 1.702}};
Matrix linearTransform = new Matrix(toRGBNums);
Matrix yIQMatrix = new Matrix(yiq, 3);

//run the linear transformation (matrix multiplication)
linearTransform = linearTransform.times(yIQMatrix);
yiq = linearTransform.getRowPackedCopy();
return yiq;
}

/**
* converts color from RGB to CIE XYZ color space
* @param rgb - color in RGB space
* @return color in CIE XYZ space
*/
public static double[] RGBtoXYZ (double[] rgb)
{
//create Matrices
double [][] toXYZNums = {{.412453, .357580, .180423}, {.212671, .715160, .072169}, {.019334, .119193, .950227}};
Matrix linearTransform = new Matrix(toXYZNums);
Matrix rGBMatrix = new Matrix(rgb, 3);

//run the linear transformation (matrix multiplication)
linearTransform = linearTransform.times(rGBMatrix);
rgb = linearTransform.getRowPackedCopy();
return rgb;
}


/**
* converts color from CIE XYZ to RGB color space
* @param xyz - color in XYZ space
* @return color in RGB space
*/
public static double[] XYZtoRGB (double[] xyz)
{
//Create Matrices
double [][] toRGBNums = {{3.240479, -1.537150, -0.498535}, {-.969256, 1.875992, .041556}, {.055648, -.204043, 1.057311}};
Matrix linearTransform = new Matrix(toRGBNums);
Matrix xYZMatrix = new Matrix(xyz, 3);

//run the linear transformation (matrix multiplication)
linearTransform = linearTransform.times(xYZMatrix);
xyz = linearTransform.getRowPackedCopy();
return xyz;
}

/**
* converts color from RGB to CIE Lab color space
* @param rgb - color in RGB space
* @return color in CIE Lab space
*/
public static double[] RGBtoCIELab (double[] rgb)
{
//start by converting to XYZ colorspace
rgb = RGBtoXYZ(rgb);

//Create variables needed for functions
double ySubN = 100.000;
double xSubN = 95.047;
double zSubN = 108.883;
double uSubN = .2009;
double vSubN = .4610;

/* At this point we use the function f(t)
such that:
f(t) = t^(1/3) t > (6/29)^3
f(t) = (1/3(29/6)^2)t + 4/29 t <= (6/29)^3
for x, y, and z and then plug the resulting number
into a function for L*, a*, and b* as pulled from wikipedia
*/

double functX = rgb[0] / xSubN;
double functY = rgb[1] / ySubN;
double functZ = rgb[2] / zSubN;

if(functX > Math.pow(6.0/29.0, 3.0))
functX = Math.pow(functX, 1.0/3.0);
else
functX = (1.0/3.0)*(Math.pow(29.0 / 6.0, 2.0)*(functX) + 4.0 / 29.0);

if(functY > Math.pow(6.0/29.0, 3.0))
functY = Math.pow(functY, 1.0/3.0);
else
functY = (1.0/3.0)*(Math.pow(29.0 / 6.0, 2.0)*(functY) + 4.0 / 29.0);

if(functZ > Math.pow(6.0/29.0, 3.0))
functZ = Math.pow(functZ, 1.0/3.0);
else
functZ = (1.0/3.0)*(Math.pow(29.0 / 6.0, 2.0)*(functZ) + 4.0 / 29.0);

//using functions from Wikipedia to get L, A, and B
double l = 116.0 * functY - 16;
double a = 500.0 * (functX - functY);
double b = 200.0 * (functY - functZ);
rgb[0] = l; rgb[1] = a; rgb[2] = b;

return rgb;
}


/**
* converts color from CIE Lab to RGB color space
* @param lab - color in CIE Lab space
* @return color in RGB space
*/
public static double[] CIELabtoRGB (double[] lab)
{
//Create variables
double x = 0, y = 0, z = 0;
double ySubN = 100.000;
double xSubN = 95.047;
double zSubN = 108.883;
double uSubN = .2009;
double vSubN = .4610;
double delta = 6.0 / 29.0;

//Create functions pulled from wikipedia (steps 1-6)
double functY = (lab[0] + 16) / 116.0;
double functX = functY + lab[1] / 500.0;
double functZ = functY - lab[2] / 200.0;

if(functY > delta)
y = ySubN * Math.pow(functY, 3.0);
else
y = (functY - 16.0/116.0) * 3.0 * Math.pow(delta, 2.0) * ySubN;

if(functX > delta)
x = xSubN * Math.pow(functX, 3.0);
else
x = (functX - 16.0/116.0) * 3 * Math.pow(delta, 2.0) * xSubN;

if(functZ > delta)
z = zSubN * Math.pow(functZ, 3.0);
else
z = (functZ - 16.0 / 116.0) * 3 * Math.pow(delta, 2.0) * zSubN;

lab[0] = x; lab[1] = y; lab[2] = z;

//end by converting from xyz to rgb
lab = XYZtoRGB(lab);
return lab;
}

/**
* converts color from RGB to CIE Luv color space
* @param rgb - color in RGB space
* @return color in CIE Luv space
*/
public static double[] RGBtoCIELuv (double[] rgb)
{
//start by converting to XYZ
rgb = RGBtoXYZ(rgb);

//Create Variables
double ySubN = 100.000;
double xSubN = 95.047;
double zSubN = 108.883;
double uSubN = .2009;
double vSubN = .4610;
double l = 0, u = 0, v = 0;
double delta = 6.0 / 29.0;

//functions according to wikipedia
double functY = rgb[1] / ySubN;
double uPrime = 4.0 * rgb[0] / (rgb[0] + 15.0 * rgb[1] + 3.0 * rgb[2]); //IE u' = 4x / (x + 15y + 3z)
double vPrime = 9.0 * rgb[1] / (rgb[0] + 15.0 * rgb[1] + 3.0 * rgb[2]);

if(functY > Math.pow(delta, 3.0))
l = 116.0 * Math.pow(functY, 1.0/3.0) - 16.0;
else
l = Math.pow(29.0 / 3.0, 3.0) * functY;

u = 13.0 * l * (uPrime - uSubN);
v = 13.0 * l * (vPrime - vSubN);

//Toss results into the array
rgb[0] = l; rgb[1] = u; rgb[2] = v;

return rgb;
}


/**
* converts color from CIE Luv to RGB color space
* @param luv - color in CIE Luv space
* @return color in RGB space
*/
public static double[] CIELuvtoRGB (double[] luv)
{
//Create Variables
double ySubN = 100.000;
double xSubN = 95.047;
double zSubN = 108.883;
double uSubN = .2009;
double vSubN = .4610;
double x = 0, y = 0, z = 0;

//Create functions as per wikipedia's instructions
double uPrime = luv[1] / 13 * luv[0] + uSubN;
double vPrime = luv[2] / 13 * luv[0] + vSubN;

if(luv[0] > 8.0)
y = ySubN * (9.0 * uPrime / 4.0 * vPrime);
else
y = ySubN * luv[0] * Math.pow(3.0 / 29.0, 3.0);

x = y * (9.0 * uPrime / 4.0 * vPrime);
z = y * ((12.0 - 3.0 * uPrime - 20.0 * vPrime) / 4.0 * vPrime);

//toss the xyz results into the array
luv[0] = x; luv[1] = y; luv[2] = z;

//end by converting from XYZ to RGB
luv = XYZtoRGB(luv);
return luv;
}
}