You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Visual Studio开发ASCII蛇形游戏.exe运行时控制台垂直间距异常求助

问题:ASCII蛇形游戏控制台垂直间距异常
  • 在Visual Studio中按F5运行/构建时显示完全正常
  • 直接运行生成的.exe文件或其桌面快捷方式时,控制台字符垂直间距过大,部分行超出屏幕范围;经测试确认是预期行之间多输出了一行空行
  • 已尝试设置控制台缓冲区和窗口尺寸,无效果;程序在朋友的电脑上运行正常

异常显示(拉伸状态)

异常显示(拉伸状态)

水平方向'x'间距与正常版本一致,但垂直方向行之间多出空行,导致整体显示被拉长

Visual Studio中正常显示

Visual Studio中正常显示

仅在Visual Studio内部运行时显示符合预期

游戏代码

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Snake_Game
{
    public static class Program
    {
        public static ScreenCompiler screen;
        public static List<SnakePart> parts = new List<SnakePart>();
        public static Apple[,] food;
        public static int partNum;

        public static void UpdateFood()
        {
            for (int x = 0; x < screen.screenx; x++)
            {
                for (int y = 0; y < screen.screeny; y++)
                {
                    if (food[x, y] != null)
                    {
                        food[x, y].Update();
                    }
                }
            }
        }

        static void Main()
        {
            Console.WriteLine("Hit enter to start.");
            Console.ReadLine();
            Console.WriteLine("Use W, A, S, and D to control the snake.");
            Thread.Sleep(1000);
            Console.Clear();
            Console.SetWindowSize(120, 30);
            Console.SetBufferSize(120, 30);
            Console.SetCursorPosition(0, 0);
            Console.Clear();
            Thread.Sleep(1000);
            Console.CursorVisible = false;
            Console.Beep(600, 60);
            Console.Beep(900, 60);
            Console.Beep(1200, 100);
            screen = new ScreenCompiler(120, Console.LargestWindowHeight);
            food = new Apple[screen.screenx, screen.screeny];
            parts.Add(new SnakePart(20, 5, 0, 0));
            parts.Add(new SnakePart(19, 5, 1, 0));
            parts.Add(new SnakePart(18, 5, 2, 0));
            partNum = 3;
            int tick = 20;
            Random rng = new Random();

            while (true)
            {
                ConsoleKeyInfo key = new ConsoleKeyInfo('\0', ConsoleKey.None, false, false, false);
                if (Console.KeyAvailable)
                {
                    key = Console.ReadKey();
                }

                if (key.Key == ConsoleKey.W)
                {
                    if (parts[0].dir != 3 && parts[0].dir != 1) { parts[0].dir = 1; Console.Beep(600, 20); }
                }
                if (key.Key == ConsoleKey.A)
                {
                    if (parts[0].dir != 0 && parts[0].dir != 2) { parts[0].dir = 2; Console.Beep(600, 20); }
                }
                if (key.Key == ConsoleKey.S)
                {
                    if (parts[0].dir != 1 && parts[0].dir != 3) { parts[0].dir = 3; Console.Beep(600, 20); }
                }
                if (key.Key == ConsoleKey.D)
                {
                    if (parts[0].dir != 2 && parts[0].dir != 0) { parts[0].dir = 0; Console.Beep(600, 20); }
                }

                int boost = partNum;
                if (boost > 50)
                {
                    boost = 50;
                }
                Thread.Sleep(80 - boost);

                Console.SetCursorPosition(0, 0);
                screen = new ScreenCompiler(Console.WindowWidth, Console.WindowHeight);
                string score = "Score: " + partNum;
                for (int i = 0; i < score.Length; i++)
                {
                    screen.lines[0][i] = score[i];
                }

                tick--;
                if (tick <= 0)
                {
                    tick = 20;
                    int x = rng.Next(1, screen.screenx);
                    int y = rng.Next(1, screen.screeny);
                    food[x, y] = new Apple(x, y);
                    new Thread(() =>
                    {
                        Console.Beep(1000, 20);
                    }).Start();
                }

                for (int i = parts.Count - 1; i > -1; i--)
                {
                    parts[i].Update();
                }

                for (int i = screen.screeny - 1; i > -1; i--)
                {
                    Console.WriteLine(screen.lines[i]);
                }
            }
        }
    }

    public class ScreenCompiler
    {
        public StringBuilder[] lines;
        public int screenx;
        public int screeny;

        public ScreenCompiler(int screenx, int screeny)
        {
            this.screenx = screenx;
            this.screeny = screeny;
            lines = new StringBuilder[screeny];
            StringBuilder def = new StringBuilder();
            for (int i = 0; i < screenx; i++)
            {
                def.Append(' ');
            }
            for (int i = 0; i < screeny; i++)
            {
                lines[i] = new StringBuilder(def.ToString());
            }
        }
    }

    public class SnakePart
    {
        public int count;
        public int x;
        public int y;
        public int? dir;

        public SnakePart(int x, int y, int count, int? dir = null)
        {
            if (count == 0 && dir == null)
            {
                throw new ArgumentException("dir is required when part count is 0");
            }
            this.x = x;
            this.y = y;
            this.count = count;
            this.dir = dir;
        }

        public void Update()
        {
            if (count == 0)
            {
                if (dir == 0)
                {
                    x += 1;
                    Program.UpdateFood();
                    x += 1;
                    Program.UpdateFood();
                }
                else if (dir == 1)
                {
                    y += 1;
                    Program.UpdateFood();
                }
                else if (dir == 2)
                {
                    x -= 1;
                    Program.UpdateFood();
                    x -= 1;
                    Program.UpdateFood();
                }
                else
                {
                    y -= 1;
                    Program.UpdateFood();
                }

                try
                {
                    if (Program.screen.lines[y][x] == 'x')
                    {
                        Console.WriteLine("Game Over!");
                        Console.Beep(900, 100);
                        Console.Beep(700, 100);
                        Console.Beep(500, 150);
                        Environment.Exit(0);
                    }
                    Program.screen.lines[y][x] = 'c';
                }
                catch
                {
                    Console.WriteLine("Game Over!");
                    Console.Beep(900, 100);
                    Console.Beep(700, 100);
                    Console.Beep(500, 150);
                    Environment.Exit(0);
                }
            }
            else
            {
                SnakePart s = Program.parts[count - 1];
                if (s.x > x && s.x - x != 0)
                {
                    dir = 0;
                }
                else if (s.x < x && s.x - x != 0)
                {
                    dir = 2;
                }
                else if (s.y > y && s.y - y != 0)
                {
                    dir = 1;
                }
                else
                {
                    dir = 3;
                }

                x = s.x;
                y = s.y;

                try
                {
                    Program.screen.lines[y][x] = 'x';
                }
                catch
                {
                    Console.WriteLine("Game Over!");
                    Console.Beep(900, 100);
                    Console.Beep(700, 100);
                    Console.Beep(500, 150);
                    Environment.Exit(0);
                }
            }
        }
    }

    public class Apple
    {
        public int x;
        public int y;

        public Apple(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        public void Update()
        {
            Program.screen.lines[y][x] = 'a';
            if (Math.Abs(Program.parts[0].x - x) <= 2 && Math.Abs(Program.parts[0].y - y) <= 1)
            {
                int newx = 0;
                int newy = 0;
                if (Program.parts[Program.parts.Count - 1].dir == 0)
                {
                    newx = -1;
                }
                else if (Program.parts[Program.parts.Count - 1].dir == 1)
                {
                    newy = -1;
                }
                else if (Program.parts[Program.parts.Count - 1].dir == 2)
                {
                    newx = 1;
                }
                else if (Program.parts[Program.parts.Count - 1].dir == 3)
                {
                    newy = 1;
                }

                Program.parts.Add(new SnakePart(Program.parts[Program.parts.Count - 1].x + newx, Program.parts[Program.parts.Count - 1].y + newy, Program.partNum));
                Program.partNum++;
                Console.Beep(800, 200);
                Program.food[x, y] = null;
            }
        }
    }
}

解决方案

1. 修复渲染时的换行重复问题

问题核心是Console.WriteLine会自动添加换行符,加上控制台环境差异,导致每行之间多出空行。把渲染循环代码替换为:

// 替换原有的Console.WriteLine循环
Console.Write(string.Join(Environment.NewLine, screen.lines));

或者手动控制换行:

for (int i = screen.screeny - 1; i > -1; i--)
{
    Console.Write(screen.lines[i]);
    if (i > 0) Console.Write(Environment.NewLine);
}

2. 统一窗口与屏幕尺寸初始化

你初始设置了窗口大小为120x30,但随后用Console.LargestWindowHeight初始化ScreenCompiler,导致屏幕行数远大于窗口高度,加剧显示混乱。修改初始化代码:

// 替换原screen初始化代码,和窗口尺寸保持一致
screen = new ScreenCompiler(120, 30);

同时锁定窗口大小,避免用户拉伸:

Console.SetWindowSize(120, 30);
Console.SetBufferSize(120, 30);
// 强制锁定窗口尺寸
Console.WindowWidth = 120;
Console.WindowHeight = 30;

3. 调整控制台属性设置

右键.exe快捷方式 → 属性:

  • 切换到「选项」标签,取消勾选「快速编辑模式」
  • 切换到「字体」标签,选择Consolas或Lucida Console等宽字体,调整字号确保垂直间距正常

内容的提问来源于stack exchange,提问作者CJTheGoldfish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 05:53:09