I know many people who started programming because they wanted to write a game of their own. I myself had never done game programming but after I ran into articles about programming a roguelike in Haskell I decided to give it a try using F#.
First I wanted to get hero to move around on map.
These are the types I came up with:
namespace SharpRogue
module Types =
type Coordinate = { x:int; y:int; }
type Hero = {
currentPosition : Coordinate;
oldPosition : Coordinate;
}
type MapTile = {
coordinate : Coordinate;
tile : char;
}
type World = {
tiles : MapTile list
hero : Hero
}
type Input =
Up
| Down
| Left
| Right
| Open
| Exit
Game World consists of MapTiles and Hero. MapTile has a coordinate in the world and a char that symbolises content of the tile, for example symbol for wall is '#'. Hero has data of it's location(currentPosition) and location on previous turn(oldPosition). Input is a discriminated union of the possible inputs from the player.
// Graphics.fs
let hideCursor() = System.Console.SetCursorPosition(0,0)
let drawHero (hero:Hero, world:MapTile list) =
System.Console.
SetCursorPosition(hero.currentPosition.x, hero.currentPosition.y)
System.Console.Write '@'
let found = List.find (Utils.findTile hero.oldPosition) world
System.Console.
SetCursorPosition(hero.oldPosition.x, hero.oldPosition.y)
found.tile |> System.Console.Write
hideCursor()
let drawTile (tile:MapTile) =
System.Console.
SetCursorPosition(tile.coordinate.x, tile.coordinate.y)
System.Console.Write tile.tile
let drawWorld world =
System.Console.Clear()
List.map (fun x -> drawTile(x)) world |> ignore
let drawOpenDoor coordinate =
System.Console.SetCursorPosition(coordinate.x, coordinate.y)
System.Console.Write '-'
hideCursor()
Since my roguelike is basically a console app the "graphics" module deals with writing out chars on the correct coordinate in the console. HideCursor-function is used to set cursor on the upper left corner of screen. Otherwise it will stay where a character has been written last. drawHero draws the @-character representing hero in currentPosition of hero record. Original tile from world record will replace @-character in oldPosition of hero. Originally I reprinted whole map after every move, but that caused screen to flicker.
The game logic is located in the Program.fs-file. Starting at the main function:
[<EntryPoint>]
let main argv =
generateCoordinates |> drawWorld
let world = {
hero = {
oldPosition = {x = 1; y = 1;};
currentPosition = {x = 1; y = 1;};
};
tiles = generateCoordinates
}
gameLoop world
0 // return an integer exit code
GenerateCoordinates breaks level map into coordinates and tiles. World initialized with hero starting from the top left corner.
GameLoop is a recursive function that draws hero, gets player input and calls gameLoop again with new state of the world.
let rec gameLoop world =
drawHero (world.hero, world.tiles)
let input = getInput()
match input with
| Exit -> ()
| Open -> openDoor world.hero world |> gameLoop
| _ -> {world with hero = (move input world);} |> gameLoop
Move returns hero with updated coordinates. Movement is allowed if the wanted location is not a wall(#) or a closed door(+). OpenDoor replaces closed door(+) located next to the hero in the given direction with an open door(-).
let move direction world =
let hero = world.hero
let newCoordinates = getNextCoordinate hero direction
let found = List.find (Utils.findTile newCoordinates) world.tiles
match found.tile with
| '#' -> hero
| '+' -> hero
| _ -> {hero with currentPosition = newCoordinates;
oldPosition = hero.currentPosition}
let openDoor hero world =
let direction = getInput()
let coordinate = getNextCoordinate hero direction
let tile = List.find (Utils.findTile coordinate) world.tiles
if tile.tile = '+' then drawOpenDoor tile.coordinate
let newTiles = List.map
(fun x ->
if x.coordinate = coordinate then { x with tile = '-'}
else x) world.tiles
{world with hero = hero; tiles = newTiles}
Here is the amazing result!
Not quite Nethack yet, but we'll see what features future brings. The codes for SharpRogue can be found on GitHub.
Software professional with a passion for quality. Likes TDD and working in agile teams. Has worked with wide-range of technologies, backend and frontend, from C++ to Javascript. Currently very interested in functional programming.