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

C# WinForms海战游戏Bug:无法放置多段舰船的相邻船段

海战游戏舰船放置Bug排查

问题场景

我用C# Windows Forms开发海战游戏,流程如下:

  • 程序启动后弹出对战模式选择对话框:玩家对战玩家、玩家对战电脑
  • 选择模式后,选择网格尺寸:4x4、8x8、10x10
  • 进入舰船放置界面

以4x4网格为例,需放置1艘尺寸1的舰船和2艘尺寸2的舰船。操作中遇到的问题:

  1. 成功将尺寸1的舰船放在坐标(i=0,j=0,i为列、j为行)
  2. 成功放置尺寸2舰船的第一段在(2,0)
  3. 尝试在(2,1)或(3,0)放置第二段时,均提示“无法在此位置放置船段,请重试”

已花费1小时排查仍未找到Bug根源,相关代码如下:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace oop_template
{
    public partial class ShipPlacementForm : Form
    {
        private int tableSize;
        private List<Ship> ships;
        private List<Ship> placedShips;
        private Button[,] tableButtons;
        private int currentShipIndex;
        private Ship currentShip;
        private bool horizontalPlacement;

        public List<Ship> PlacedShips
        {
            get { return placedShips; }
        }

        public ShipPlacementForm(int tableSize, List<Ship> ships)
        {
            this.tableSize = tableSize;
            this.ships = ships;
            this.placedShips = new List<Ship>();
            CustomInitializeComponent();

            // Initialize placement of the first ship
            this.currentShipIndex = 0;
            SetCurrentShip();
            deleteButton.Enabled = false; // Disable delete until at least one ship is placed
        }

        private void CustomInitializeComponent()
        {
            this.SuspendLayout();

            // Dynamically create buttons for the table fields
            tableButtons = new Button[tableSize, tableSize];
            for (int i = 0; i < tableSize; i++)
            {
                for (int j = 0; j < tableSize; j++)
                {
                    tableButtons[i, j] = new Button();
                    tableButtons[i, j].Size = new Size(30, 30);
                    tableButtons[i, j].Location = new Point(30 * i, 30 * j);
                    tableButtons[i, j].Click += new EventHandler(TableButton_Click);
                    this.Controls.Add(tableButtons[i, j]);
                }
            }

            // Label for displaying the current ship
            this.currentShipLabel = new Label();
            this.currentShipLabel.Location = new Point(30 * tableSize, 30);
            this.currentShipLabel.AutoSize = true;
            this.Controls.Add(this.currentShipLabel);

            // Button for confirmation
            this.confirmButton = new Button();
            this.confirmButton.Text = "Confirm";
            this.confirmButton.Location = new Point(30 * tableSize, 60);
            this.confirmButton.Click += new EventHandler(ConfirmButton_Click);
            this.confirmButton.Enabled = false; // Disabled until all ships are placed
            this.Controls.Add(this.confirmButton);

            // Button for deleting a ship
            this.deleteButton = new Button();
            this.deleteButton.Text = "Delete Ship";
            this.deleteButton.Location = new Point(30 * tableSize, 90);
            this.deleteButton.Click += new EventHandler(DeleteButton_Click);
            this.deleteButton.Enabled = false; // Disabled until at least one ship is placed
            this.Controls.Add(this.deleteButton);

            // Set form size
            this.ClientSize = new System.Drawing.Size(30 * (tableSize + 2), 30 * tableSize);
            this.Name = "ShipPlacementForm";
            this.Text = "Ship Placement";
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        private void TableButton_Click(object sender, EventArgs e)
        {
            Button clickedButton = sender as Button;
            if (clickedButton == null)
                return;

            int x = clickedButton.Location.X / 30;
            int y = clickedButton.Location.Y / 30;

            if (CanPlaceShipPart(x, y))
            {
                PlaceShipPart(x, y);
                currentShip.Positions.Add(new Point(x, y));

                if (currentShip.Positions.Count == 1)
                {
                    // First part of the ship is placed, wait for the second part to determine orientation
                    deleteButton.Enabled = false; // Disable deletion until the entire ship is placed
                }
                else if (currentShip.Positions.Count == 2)
                {
                    // Determine the orientation of the ship based on the first two positions
                    horizontalPlacement = currentShip.Positions[0].Y == currentShip.Positions[1].Y;
                }

                // Check if the ship is complete
                if (currentShip.Positions.Count == currentShip.Size)
                {
                    placedShips.Add(currentShip);
                    currentShipIndex++;

                    if (currentShipIndex < ships.Count)
                    {
                        SetCurrentShip();
                    }
                    else
                    {
                        currentShipLabel.Text = "All ships are placed.";
                        confirmButton.Enabled = true;
                        deleteButton.Enabled = true;
                    }
                }
            }
            else
            {
                MessageBox.Show("You cannot place a ship part on this position. Please try again.");
            }
        }

        private void DeleteButton_Click(object sender, EventArgs e)
        {
            currentShipLabel.Text = "Click on any part of the ship you want to delete.";
            foreach (var ship in placedShips)
            {
                foreach (var position in ship.Positions)
                {
                    tableButtons[position.X, position.Y].Click += DeleteShipClick;
                }
            }
        }

        private void DeleteShipClick(object sender, EventArgs e)
        {
            Button clickedButton = sender as Button;
            if (clickedButton == null)
                return;

            int x = clickedButton.Location.X / 30;
            int y = clickedButton.Location.Y / 30;

            Ship shipToDelete = null;

            foreach (var ship in placedShips)
            {
                if (ship.Positions.Contains(new Point(x, y)))
                {
                    shipToDelete = ship;
                    break;
                }
            }

            if (shipToDelete != null)
            {
                DeleteShip(shipToDelete);
                currentShipLabel.Text = $"Place a ship of size {shipToDelete.Size}";
                currentShip = shipToDelete;
                currentShipIndex = ships.IndexOf(shipToDelete);

                foreach (var position in shipToDelete.Positions)
                {
                    tableButtons[position.X, position.Y].Click -= DeleteShipClick;
                }
            }

            deleteButton.Enabled = placedShips.Count > 0;
        }

        private void DeleteShip(Ship ship)
        {
            foreach (var position in ship.Positions)
            {
                tableButtons[position.X, position.Y].BackColor = default(Color);
                tableButtons[position.X, position.Y].Enabled = true;
            }
            placedShips.Remove(ship);
            ships.Insert(currentShipIndex, ship); // Return the ship to the list for re-placement
        }

        private void ConfirmButton_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        private void SetCurrentShip()
        {
            currentShip = ships[currentShipIndex];
            currentShip.Positions.Clear();
            currentShipLabel.Text = $"Place a ship of size {currentShip.Size}";
            deleteButton.Enabled = placedShips.Count > 0;
        }

        private bool CanPlaceShipPart(int x, int y)
        {
            if (tableButtons[x, y].BackColor == Color.Gray)
                return false; // The field is already taken

            // Check continuity of ship placement
            if (currentShip.Positions.Count > 0)
            {
                var lastPos = currentShip.Positions[currentShip.Positions.Count - 1];

                // Check if the second part of the ship can be placed next to the first one
                bool validHorizontalPos = (x == lastPos.X && Math.Abs(y - lastPos.Y) == 1);
                bool validVerticalPos = (y == lastPos.Y && Math.Abs(x - lastPos.X) == 1);

                if (validHorizontalPos || validVerticalPos)
                {
                    // Check if the adjacent field is taken
                    foreach (var (dx, dy) in new[] { (-1, 0), (1, 0), (0, -1), (0, 1) })
                    {
                        int nx = x + dx;
                        int ny = y + dy;

                        if (nx >= 0 && nx < tableSize && ny >= 0 && ny < tableSize)
                        {
                            if (tableButtons[nx, ny].BackColor == Color.Gray)
                                return false; // The adjacent field is taken
                        }
                    }

                    return true;
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        private void PlaceShipPart(int x, int y)
        {
            tableButtons[x, y].BackColor = Color.Gray; // Color the field gray
            tableButtons[x, y].Enabled = false; // Disable clicking on the same field again
        }

        private Label currentShipLabel;
        private Button confirmButton;
        private Button deleteButton;
    }
}

Bug根源分析

问题出在CanPlaceShipPart方法的相邻区域检查逻辑:
当放置第二艘船的第二段时,代码会检查当前点击位置的四个相邻格子是否有已放置的灰色船段。而你之前在(0,0)放置了尺寸1的船,当尝试在(3,0)放置第二段时,代码会检查(3,0)的所有相邻格,包括左边的(2,0)(当前船的第一段,已标记为灰色),误将同一艘船的已放部分当成“已占用的外部区域”,直接返回false导致无法放置。

修复方案

修改CanPlaceShipPart方法,在检查相邻格子时,排除当前正在放置的船的已存位置:

private bool CanPlaceShipPart(int x, int y)
{
    if (tableButtons[x, y].BackColor == Color.Gray)
        return false; // 当前格子已被占用

    // 检查是否是同一艘船的连续放置
    if (currentShip.Positions.Count > 0)
    {
        var lastPos = currentShip.Positions[currentShip.Positions.Count - 1];

        // 检查是否与上一段相邻
        bool validHorizontalPos = (x == lastPos.X && Math.Abs(y - lastPos.Y) == 1);
        bool validVerticalPos = (y == lastPos.Y && Math.Abs(x - lastPos.X) == 1);

        if (!validHorizontalPos && !validVerticalPos)
        {
            return false; // 不连续,无法放置
        }

        // 检查相邻格子是否被其他船占用(排除当前船已放的位置)
        foreach (var (dx, dy) in new[] { (-1, 0), (1, 0), (0, -1), (0, 1) })
        {
            int nx = x + dx;
            int ny = y + dy;

            if (nx >= 0 && nx < tableSize && ny >= 0 && ny < tableSize)
            {
                // 跳过当前船已放置的位置
                if (currentShip.Positions.Any(p => p.X == nx && p.Y == ny))
                    continue;

                if (tableButtons[nx, ny].BackColor == Color.Gray)
                    return false; // 相邻格子被其他船占用
            }
        }

        return true;
    }
    else
    {
        // 放置第一块时,检查周围是否有其他船
        foreach (var (dx, dy) in new[] { (-1, 0), (1, 0), (0, -1), (0, 1) })
        {
            int nx = x + dx;
            int ny = y + dy;

            if (nx >= 0 && nx < tableSize && ny >= 0 && ny < tableSize)
            {
                if (tableButtons[nx, ny].BackColor == Color.Gray)
                    return false; // 周围有其他船,无法放置
            }
        }
        return true;
    }
}

另外,补充修复一个潜在问题:Ship.Positions.Contains(new Point(x,y))依赖Point的引用比较,可能导致判断错误,建议在DeleteShipClick方法中替换为坐标值比较:

// 替换原判断逻辑
if (ship.Positions.Any(p => p.X == x && p.Y == y))
{
    shipToDelete = ship;
    break;
}

内容的提问来源于stack exchange,提问作者Огњен Стојановић

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 11:37:32