LẬP TRÌNH GAME “PLAN TANK”

Lập trình game “Plan Tank”

Trước tiên, Tôi xin giới thiệu nội dung game “Plan Tank”. Trên gameboard có hình dạng lục giác, người chơi cần phải đưa chiếc xe tank vượt qua các chướng ngại vật và di chuyển được từ điểm màu xanh đến điểm màu đỏ bằng cách chọn function tiến lên, quay trái, quay phải, v.v…

alt

1. Implement class quản lý status chung của game

Class Game quản lý level, gameboard, các bước di chuyển, xác định xem bước di chuyển có hợp lệ hay không, xác định điều kiện người chơi chiến thắng.

namespace PlanTankGameModel
{
    public interface IGame
    {
        IList<IList<State>> Board { get; set; }
        int RowCount { get; set; }
        int ColumnCount { get; set; }
        IList<ISpace> Moves { get; set; }
        IList<ISpace> MoveStack { get; set; }

        State CurrentPlayer { get; }
        State Winner { get; }
        IScore GetScore();

        State GetSpaceState(int row, int column);
        bool IsValidMove(ISpace move);
        bool IsValidMove(int row, int column);
        bool IsPassValid();
        bool IsGameOver();

        [Windows.Foundation.Metadata.DefaultOverload()]
        IAsyncOperation<IList<ISpace>> MoveAsync(ISpace move);
        IAsyncOperation<IList<ISpace>> MoveAsync(int row, int column);
        IAsyncAction MoveAsync(string moves);
        IAsyncAction AiMoveAsync(int searchDepth);
        IAsyncOperation<ISpace> GetBestMoveAsync(int searchDepth);

        IAsyncOperation<IList<ISpace>> PassAsync();
        IAsyncOperation<IList<ISpace>> RedoAsync();
        IAsyncOperation<IList<ISpace>> UndoAsync();

        string ToString();
        void LoadSerializedBoardState(string state);
    }
}

2. Class Level quản lý thông tin về map của từng level

Class level quản lý thông tin về map, tập hợp các ô di chuyển trong map(space), tọa độ bắt đầu và kết thúc của game, số command được sử dụng để điều khiển tank.

namespace PlanTankGameState
{
    public class LevelInfo
    {
        public int Id { get; set; }

        public string Title { get; set; }

        public string Description{ get; set; }

        public int[] SubLengths { get; set; }

        public Space[] Colors { get; set; }

        public string[] Items { get; set; }

        public int BeginRow { get; set; }

        public int BeginCol { get; set; }

        public int TankDir { get; set; }

        public int Solutions { get; set; }

        public bool Featured { get; set; }

        public int AllowedCommands { get; set; }
    }
}

**3. Implement hexagon game board ** Tuy các ô trong game có hình dạng lục giác nhưng tôi vẫn sẽ là đưa bản đồ trò chơi như là một mảng 2 chiều của các đối tượng Space. Hãy xem xét các minh họa dưới đây:

alt

Tôi vẫn cần tạo bản đồ với các loại hình nền đất khác nhau

alt

Source code tạo game board

public class Hexagone {

        private static const double  SIN_60 = Math.sin(Math.PI / 3);

        private static const double  COS_60 = Math.cos(Math.PI / 3);

        private static Líst<double> _gCommands = new Líst<double>(7, true);

        private static Líst<double> _gCoords = new Líst<double>(14, true);

        private static double _lastSide;

        private static double _lastHSpace;

        private static double _lastHeight;

        private static void _staticInit() {
            _gCommands.Adđ(GraphicsPathCommand.MOVE_TO);
        }

        private static void _calculatePoints(double side) {

            double height = side * SIN_60 * 2;
            double hSpace = side * COS_60;

            _gCoords[0] = _gCoords[12] = 0;
            _gCoords[2] = _gCoords[10] = hSpace;
            _gCoords[4] = _gCoords[8] = hSpace + side;
            _gCoords[6] = side + hSpace * 2;

            _gCoords[1] = _gCoords[7] = _gCoords[13] = height / 2;
            _gCoords[3] = _gCoords[5] = height;
            _gCoords[9] = _gCoords[11] = 0;

            _lastSide = side;
            _lastHSpace = hSpace;
            _lastHeight = height;

        }

        private double _side;

        private string _text;

        private int _charCode;

        private bool _activated = false;

        /**
         * Creates a new hexagone object.
         */
        public void Hexagone (double side, int fill, int letter, Color textColour) {
            _init(side, fill, letter, textColour);
        }

        /*
         * Initialises the Honeycomb object.
         *
         */
        private void _init(double side, int fill, int letter, Color textColour) {

            mouseChildren = false;
            buttonMode = true;
            useHandCursor = false;

            graphics.beginFill(fill);
            graphics.lineStyle(3, 0x000000);

            if ( side != _lastSide )
                _calculatePoints(side);

            _side = side;
            _charCode = letter;

            graphics.drawPath(_gCommands, _gCoords);

            _text = new TextField();
            _text.autoSize = TextFieldAutoSize.CENTER;
            _text.defaultTextFormat = new TextFormat('_sans', side * 1.2, textColour, true);
            _text.text = String.fromCharCode(letter);
            _text.x = (side + _lastHSpace * 2 - _text.width) / 2;
            _text.y = (_lastHeight - _text.height) / 2;

            addChild(_text);

        }

        public int GetCharCode() {
            return _charCode;
        }

        public void activate(Color backColour, Color textColour){

            if ( _side != _lastSide )
                _calculatePoints(_side);

            graphics.beginFill(backColour);
            graphics.drawPath(_gCommands, _gCoords);

            TextFormattextFormat = _text.getTextFormat();
            textFormat.color = textColour;
            _text.setTextFormat(textFormat);

            _activated = true;

        }

    }

4. Check lời giải đưa ra có thỏa mãn bài toán không

/// <summary>
/// Summary description for SolutionVerifier
/// </summary>
public class SolutionVerifier
{
    private class TankMoveStackEntry
    {
        public int Function { get; set; }
        public int Instruction { get; set; }
    }

    public bool IsSolution(Level levelInfo, string program)
    {
        return IsSolution(levelInfo, ParseSolution(program));
    }

    public bool IsSolution(Level levelInfo, string[][] commands)
    {
        // Check assumption required for this code to work.
        if (TankService.MAX_SUBS > 9)
            throw new InvalidOperationException("There cannot be more than 9 functions.");

        // Initialize the state before running the program.
        int commandsExecuted = 0;
        int fp = 0; // Current function pointer.
        int ip = 0; // Current instruction pointer.
        Stack<TankMoveStackEntry> stack = new Stack<RobozzleStackEntry>();
        bool[,] starsHit = new bool[TankService.ROWS, TankService.COLS];
        int row = levelInfo.RobotRow;
        int column = levelInfo.RobotCol;
        int direction = levelInfo.RobotDir;
        string[] colorsString, items;
        TankService.EntityToObj(levelInfo.Board, out colorsString, out items);
        char[][] colors = colorsString.Select(x => x.ToCharArray()).ToArray();
        int starsRemaining = items.Select(x => x.ToCharArray()
                                                .Count(c => c == TankService.ITEM_STAR))
                                  .Sum();
        bool[] functionMask = new bool[commands.Length];

        long steps = 0;

        // Run the program.
        while (commandsExecuted < TankService.MAX_COMMANDS)
        {
            steps++;
            // If we have reached the end of the function, return.
            if (ip == commands[fp].Length)
            {
                if (stack.Count == 0)
                    return false;

                functionMask[fp] = false;
                RobozzleStackEntry stackEntry = stack.Pop();
                fp = stackEntry.Function;
                ip = stackEntry.Instruction;
                continue;
            }

            string command = commands[fp][ip];

            // Check the condition on the command.
            char condition = command[0];
            if (condition != TankService.COLOR_NONE && condition != colors[row][column])
            {
                // Condition not satisfied. Skip this command.
                ++ip;
                continue;
            }

            // Execute the command.
            char commandType = command[1];
            if (commandType >= '1' && commandType <= '0' + TankService.MAX_SUBS)
            {
                int nextFunction = commandType - '1';

                // Detect an infinite loop.
                if (functionMask[nextFunction])
                    return false;
                functionMask[nextFunction] = true;

                // Push the return address.
                stack.Push(new TankService{ Function = fp, Instruction = ip + 1 });
                // Jump to the beginning of the next function.
                fp = nextFunction;
                ip = 0;
            }
            else
            {
                switch (commandType)
                {
                    case 'F':
                        // Move forward.
                        switch (direction)
                        {
                            case TankService.DIRECTION_RIGHT: column++; break;
                            case TankService.DIRECTION_DOWN: row++; break;
                            case TankService.DIRECTION_LEFT: column--; break;
                            case TankService.DIRECTION_UP: row--; break;
                        }

                        // Check if the robot fell off the board.
                        if (row < 0 || row >= TankService.ROWS) returnfalse;
                        if (column < 0 || column >= TankService.COLS) returnfalse;
                        char item = items[row][column];
                        if (item == TankService.ITEM_HOLE) returnfalse;
                        // Check if we hit a star we haven't hit before.
                        if (item == TankService.ITEM_STAR && !starsHit[row, column])
                        {
                            starsHit[row, column] = true;
                            starsRemaining--;
                            if (starsRemaining == 0)
                                return true;
                        }

                        break;

                    case 'R': direction = (direction + 1) % 4; break;
                    case 'L': direction = (direction + 3) % 4; break;
                    case 'r': colors[row][column] = TankService.COLOR_RED; break;
                    case 'g': colors[row][column] = TankService.COLOR_GREEN; break;
                    case 'b': colors[row][column] = TankService.COLOR_BLUE; break;
                    default:
                        throw new InvalidOperationException("Unknown Robozzle command.");
                }

                commandsExecuted++;
                functionMask = new bool[commands.Length];

                ++ip;
            }
        }

        return false;
    }

    private static string[][] ParseSolution(string solution)
    {
        string[] functions = solution.Split('|').Take(TankService.MAX_SUBS).ToArray();
        string[][] commands = new string[functions.Length][];
        for (int i = 0; i < commands.Length; ++i)
        {
            string f = functions[i];
            commands[i] = new string[f.Length / 2];
            for (int j = 0; j < f.Length / 2; j++)
                commands[i][j] = f[j * 2].ToString().ToUpper() + f[j * 2 + 1];

        }
        return commands;
    }
}

5. Tổng kết

Tháng này tôi đã implement các phương thức quản lý game, quản lý level, gameboard, cách thức tank di chuyển. Trong kì tiếp theo tôi sẽ tiếp tục implement phương thức ăn item, nhả đạn, một số đối tượng computer tham gia vào game.