对于一个非常非常简单的C#引擎,我有一些现有的RogueLike代码。这是故意天真的,因为我试图尽可能简单地做到最低限度。它所做的就是使用箭头键和System.Console在硬编码地图周围移动@符号:
//define the map
var map = new List<string>{
" ",
" ",
" ",
" ",
" ############################### ",
" # # ",
" # ###### # ",
" # # # # ",
" #### #### # # # ",
" # # # # # # ",
" # # # # # # ",
" #### #### ###### # ",
" # = # ",
" # = # ",
" ############################### ",
" ",
" ",
" ",
" ",
" "
};
//set initial player position on the map
var playerX = 8;
var playerY = 6;
//clear the console
Console.Clear();
//send each row of the map to the Console
map.ForEach( Console.WriteLine );
//create an empty ConsoleKeyInfo for storing the last key pressed
var keyInfo = new ConsoleKeyInfo( );
//keep processing key presses until the player wants to quit
while ( keyInfo.Key != ConsoleKey.Q ) {
//store the player's current location
var oldX = playerX;
var oldY = playerY;
//change the player's location if they pressed an arrow key
switch ( keyInfo.Key ) {
case ConsoleKey.UpArrow:
playerY--;
break;
case ConsoleKey.DownArrow:
playerY++;
break;
case ConsoleKey.LeftArrow:
playerX--;
break;
case ConsoleKey.RightArrow:
playerX++;
break;
}
//check if the square that the player is trying to move to is empty
if( map[ playerY ][ playerX ] == ' ' ) {
//ok it was empty, clear the square they were standing on before
Console.SetCursorPosition( oldX, oldY );
Console.Write( ' ' );
//now draw them at the new square
Console.SetCursorPosition( playerX, playerY );
Console.Write( '@' );
} else {
//they can't move there, change their location back to the old location
playerX = oldX;
playerY = oldY;
}
//wait for them to press a key and store it in keyInfo
keyInfo = Console.ReadKey( true );
}我在F#中玩这个游戏,最初我试图用函数概念来编写它,但结果发现我有点过头了,所以我做了一个很直接的端口--它实际上不是一个F#程序(尽管它编译和运行)--它是一个用F#语法编写的过程程序:
open System
//define the map
let map = [ " ";
" ";
" ";
" ";
" ############################### ";
" # # ";
" # ###### # ";
" # # # # ";
" #### #### # # # ";
" # # # # # # ";
" # # # # # # ";
" #### #### ###### # ";
" # = # ";
" # = # ";
" ############################### ";
" ";
" ";
" ";
" ";
" " ]
//set initial player position on the map
let mutable playerX = 8
let mutable playerY = 6
//clear the console
Console.Clear()
//send each row of the map to the Console
map |> Seq.iter (printfn "%s")
//create an empty ConsoleKeyInfo for storing the last key pressed
let mutable keyInfo = ConsoleKeyInfo()
//keep processing key presses until the player wants to quit
while not ( keyInfo.Key = ConsoleKey.Q ) do
//store the player's current location
let mutable oldX = playerX
let mutable oldY = playerY
//change the player's location if they pressed an arrow key
if keyInfo.Key = ConsoleKey.UpArrow then
playerY <- playerY - 1
else if keyInfo.Key = ConsoleKey.DownArrow then
playerY <- playerY + 1
else if keyInfo.Key = ConsoleKey.LeftArrow then
playerX <- playerX - 1
else if keyInfo.Key = ConsoleKey.RightArrow then
playerX <- playerX + 1
//check if the square that the player is trying to move to is empty
if map.Item( playerY ).Chars( playerX ) = ' ' then
//ok it was empty, clear the square they were standing on
Console.SetCursorPosition( oldX, oldY )
Console.Write( ' ' )
//now draw them at the new square
Console.SetCursorPosition( playerX, playerY )
Console.Write( '@' )
else
//they can't move there, change their location back to the old location
playerX <- oldX
playerY <- oldY
//wait for them to press a key and store it in keyInfo
keyInfo <- Console.ReadKey( true )所以我的问题是,我需要学些什么才能更有功能地重写它,你能给我一些提示,一个模糊的概述,诸如此类的事情吗?
我宁愿往正确的方向推进,而不只是看到一些代码,但是如果这是你向我解释的最简单的方法,那么就好了,但在这种情况下,你能解释一下“为什么”,而不是“如何”吗?
发布于 2010-12-21 17:38:41
游戏编程一般会测试你管理复杂性的能力。我发现函数式编程鼓励您将解决的问题分解成更小的部分。
您要做的第一件事是通过分离所有不同的关注点,将脚本转换为一组函数。我知道这听起来很傻,但是这样做会使代码更有功能(双关意)。你主要关心的是国家管理。我使用一个记录来管理位置状态,使用一个元组来管理运行状态。随着代码变得更高级,您将需要对象来干净地管理状态。
尝试加入更多的这个游戏,并继续打破功能,因为他们的成长。最终,您将需要对象来管理所有函数。
在游戏编程注意事项中,不要将状态更改为其他状态,如果测试失败,则将其更改回。你想要最小的状态变化。因此,例如,在下面,我计算newPosition,然后只在这个未来的位置通过时更改playerPosition。
open System
// use a third party vector class for 2D and 3D positions
// or write your own for pratice
type Pos = {x: int; y: int}
with
static member (+) (a, b) =
{x = a.x + b.x; y = a.y + b.y}
let drawBoard map =
//clear the console
Console.Clear()
//send each row of the map to the Console
map |> List.iter (printfn "%s")
let movePlayer (keyInfo : ConsoleKeyInfo) =
match keyInfo.Key with
| ConsoleKey.UpArrow -> {x = 0; y = -1}
| ConsoleKey.DownArrow -> {x = 0; y = 1}
| ConsoleKey.LeftArrow -> {x = -1; y = 0}
| ConsoleKey.RightArrow -> {x = 1; y = 0}
| _ -> {x = 0; y = 0}
let validPosition (map:string list) position =
map.Item(position.y).Chars(position.x) = ' '
//clear the square player was standing on
let clearPlayer position =
Console.SetCursorPosition(position.x, position.y)
Console.Write( ' ' )
//draw the square player is standing on
let drawPlayer position =
Console.SetCursorPosition(position.x, position.y)
Console.Write( '@' )
let takeTurn map playerPosition =
let keyInfo = Console.ReadKey true
// check to see if player wants to keep playing
let keepPlaying = keyInfo.Key <> ConsoleKey.Q
// get player movement from user input
let movement = movePlayer keyInfo
// calculate the players new position
let newPosition = playerPosition + movement
// check for valid move
let validMove = newPosition |> validPosition map
// update drawing if move was valid
if validMove then
clearPlayer playerPosition
drawPlayer newPosition
// return state
if validMove then
keepPlaying, newPosition
else
keepPlaying, playerPosition
// main game loop
let rec gameRun map playerPosition =
let keepPlaying, newPosition = playerPosition |> takeTurn map
if keepPlaying then
gameRun map newPosition
// setup game
let startGame map playerPosition =
drawBoard map
drawPlayer playerPosition
gameRun map playerPosition
//define the map
let map = [ " ";
" ";
" ";
" ";
" ############################### ";
" # # ";
" # ###### # ";
" # # # # ";
" #### #### # # # ";
" # # # # # # ";
" # # # # # # ";
" #### #### ###### # ";
" # = # ";
" # = # ";
" ############################### ";
" ";
" ";
" ";
" ";
" " ]
//initial player position on the map
let playerPosition = {x = 8; y = 6}
startGame map playerPosition发布于 2010-12-21 12:55:37
这是一个不错的小游戏:-)。在函数式编程中,您可能希望避免使用可变状态(正如其他人所指出的那样),您还希望将游戏的核心编写为没有任何副作用的函数(例如从控制台读取和写入)。
游戏的关键部分是控制位置的功能。您可以重构代码,使其具有类型签名的函数:
val getNextPosition : (int * int) -> ConsoleKey -> option<int * int>如果游戏退出,该函数将返回None。否则,它将返回Some(posX, posY),其中posX和posY是@符号的新位置。通过进行更改,您可以得到一个很好的函数核心,并且函数getNextPosition也很容易测试(因为它总是对相同的输入返回相同的结果)。
要使用该函数,最好的选择是使用递归编写循环。主要功能的结构如下所示:
let rec playing pos =
match getNextPosition pos (Console.ReadKey()) with
| None -> () // Quit the game
| Some(newPos) ->
// This function redraws the screen (this is a side-effect,
// but it is localized to a single function)
redrawScreen pos newPos
playing newPos发布于 2010-12-21 03:32:22
作为一个游戏,使用控制台,这里有状态和副作用,这是固有的。但你要做的关键是消除那些变项。使用递归循环而不是while循环将有助于您这样做,因为这样您就可以将状态作为参数传递给每个递归调用。除此之外,我在这里看到的利用F#特性的主要方法是使用模式匹配,而不是if/ see语句和开关,尽管这主要是一种美学上的改进。
https://stackoverflow.com/questions/4495993
复制相似问题