首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java生命游戏

Java生命游戏
EN

Stack Overflow用户
提问于 2017-03-16 13:56:45
回答 1查看 2.9K关注 0票数 0

我有一个生命游戏的启动和运行版本,但有一件事我不知道如何包装网格或董事会,我猜这一定与邻居计数和网格有关,我需要一种方法来指示数组包装。

规则: 生命游戏的宇宙是由方形细胞组成的无限二维正交网格, 它们中的每一种都处于两种可能的状态之一,无论是活的还是死的。 每个细胞都与它的八个邻居相互作用,这些相邻的细胞是直接水平的, 垂直的或对角相邻的。在时间的每一步,都会发生以下过渡: 1.少于两个活的邻居的活细胞死亡,似乎是由于人口不足造成的。 2.任何有三个以上活邻居的活牢房都会因过度拥挤而死亡。 3.任何有两个或三个活邻居的活牢房都能活到下一代。 4.任何有三个活的邻居的死细胞都会变成活的细胞。 初始模式构成系统的种子。第一代是通过对种子中的每个细胞同时应用上述规则而产生的--出生和死亡同时发生。

下面是一些与网格/板相关的代码:称为CellsGrid单元;

代码语言:javascript
复制
  GameOfLife2(int nbRow, int nbCol) {

            super(" New GameOfLife");

            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);



            // create the labels (2 more on each size) these wont be shown

            // but will be used in calculating the cells alive around

            Cells = new CellsGrid[nbRow+2][nbCol+2];

            for(int r = 0; r < nbRow+2; r++) {

                for(int c = 0; c < nbCol+2; c++) {

                    Cells[r][c] = new CellsGrid();

                }

            }

for(int r = 1; r < nbRow+1; r++) {

            for(int c = 1; c < nbCol+1; c++) {

                panel.add(Cells[r][c]);

                Cells[r][c].addNeighbour(Cells[r-1][c]);    // North

                Cells[r][c].addNeighbour(Cells[r+1][c]);    // South

                Cells[r][c].addNeighbour(Cells[r][c-1]);    // West

                Cells[r][c].addNeighbour(Cells[r][c+1]);    // East

                Cells[r][c].addNeighbour(Cells[r-1][c-1]);  // North West

                Cells[r][c].addNeighbour(Cells[r-1][c+1]);  // North East

                Cells[r][c].addNeighbour(Cells[r+1][c-1]);  // South West

                Cells[r][c].addNeighbour(Cells[r+1][c+1]);  // South East

            }


        }

 if(!gameRunning)

            return;

        ++generation;

        CellsIteration.setText("Generation: " + generation);

        for(int r = 0; r < Cells.length; r++) {

            for(int c = 0; c < Cells[r].length; c++) {

                Cells[r][c].checkState();

            }

        }

        for(int r = 0; r < Cells.length; r++) {

            for(int c = 0; c < Cells[r].length; c++) {

                Cells[r][c].updateState();

            }

        }

    }



void checkState() {



  // number alive around

    int NumNeighbours = 0; // number alive neighbours

    // see the state of my neighbour

    for(int i = 0; i < numNeighbours; i++)

        NumNeighbours += neighbour[i].state;

    // newState

    if(state == 1) {                // if alive

        if(NumNeighbours < 2)              // 1.Any live cell with fewer than two live neighbours dies

            newState = 0;

        if(NumNeighbours > 3)              // 2.Any live cell with more than three live neighbours dies

            newState = 0;

    }

    else {

        if(NumNeighbours == 3)            // 4.Any dead cell with exactly three live neighbours becomes a live cell

            newState = 1;

    }

}

完整代码:

代码语言:javascript
复制
package com.ggl.life;
import java.awt.*;

import java.awt.event.*;
import java.util.Random;

import javax.swing.*;


public class GameOfLife2 extends JFrame implements ActionListener {

    /**
     * 
     */
    public static Random random  = new Random();

    private static final long serialVersionUID = 1L;

    static final Color[] color = {Color.YELLOW, Color.BLACK};

    // size in pixel of every label

    static final int size = 15;

    static final Dimension dim = new Dimension(size, size);

    static final int GenDelay = 200;

    // the cells labels
    private CellsGrid[][] Cells;

    // timer that fires the next generation

    private Timer timer;

    // generation counter

    private int generation = 0;

    private JLabel CellsIteration = new JLabel("Generation: 0");

    // the 3 buttons

    private JButton clearBtn = new JButton("Clear"),

                    PauseBtn = new JButton("Pause"),

                    StartBtn = new JButton("Start");

    // the slider for the speed


    // state of the game (running or pause)

    private boolean gameRunning = false;

    // if the mouse is down or not

    private boolean mouseDown = false;



    GameOfLife2(int nbRow, int nbCol) {

        super(" New GameOfLife");

        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);



        // create the labels (2 more on each size) these wont be shown

        // but will be used in calculating the cells alive around

        Cells = new CellsGrid[nbRow+2][nbCol+2];

        for(int r = 0; r < nbRow+2; r++) {

            for(int c = 0; c < nbCol+2; c++) {

                Cells[r][c] = new CellsGrid();

            }


        }


        // panel in the center with the labels

        JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));

        panel.setBackground(Color.BLACK);

        panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));



        // add each label (not the one on the border) to the panel and add to each of them its neighbours

        for(int r = 1; r < nbRow+1; r++) {

            for(int c = 1; c < nbCol+1; c++) {

                panel.add(Cells[r][c]);

                Cells[r][c].addNeighbour(Cells[r-1][c]);
                //Cells[r][c].addNeighbour(getCellSafe(r-1, c)); // North

                Cells[r][c].addNeighbour(Cells[r+1][c]);    // South
              //Cells[r][c].addNeighbour(getCellSafe(r+1, c));

                Cells[r][c].addNeighbour(Cells[r][c-1]);    // West
              //Cells[r][c].addNeighbour(getCellSafe(r, c-1));

                Cells[r][c].addNeighbour(Cells[r][c+1]);    // East
              //Cells[r][c].addNeighbour(getCellSafe(r, c+1));

                Cells[r][c].addNeighbour(Cells[r-1][c-1]);  // North West
              //Cells[r][c].addNeighbour(getCellSafe(r-1, c-1));

                Cells[r][c].addNeighbour(Cells[r-1][c+1]);  // North East
              //Cells[r][c].addNeighbour(getCellSafe(r-1, c+1));

                Cells[r][c].addNeighbour(Cells[r+1][c-1]);  // South West
              //Cells[r][c].addNeighbour(getCellSafe(r+1, c-1));

               Cells[r][c].addNeighbour(Cells[r+1][c+1]);  // South East
              //Cells[r][c].addNeighbour(getCellSafe(r+1, +c));

            }


        }



        // now the panel can be added

        add(panel, BorderLayout.CENTER);



        // the bottom panel with the buttons the generation label and the slider

        // this panel is formed grid panels

        panel = new JPanel(new GridLayout(1,3));

       // another panel for the 3 buttons

        JPanel buttonPanel = new JPanel(new GridLayout(1,3));

        clearBtn.addActionListener(this);

        buttonPanel.add(clearBtn);

        PauseBtn.addActionListener(this);

        PauseBtn.setEnabled(false);           // game is pause the pause button is disabled

        buttonPanel.add(PauseBtn);

        StartBtn.addActionListener(this);

        buttonPanel.add(StartBtn);

        // add the 3 buttons to the panel

        panel.add(buttonPanel);

        // the generation label

        CellsIteration.setHorizontalAlignment(SwingConstants.CENTER);

        panel.add(CellsIteration);


        // in the JFrame

        add(panel, BorderLayout.NORTH);

        // put the frame on

        setLocation(20, 20);

        pack(); // adjust to the window size
        setVisible(true);

        // start the thread that run the cycles of life

        timer = new Timer(GenDelay , this);

    }

    private CellsGrid getCellSafe(int r0, int c0) {
        int r = r0  % Cells.length; // Cells.length is effectively nbRow
        if (r < 0) r += Cells.length; // deal with how % works for negatives
        int c = c0  % Cells[0].length; // Cells[0].length is effectively nbCol
        if (c < 0) c += Cells[0].length; // deal with how % works for negatives
        return Cells[r][c];
   }

//end of game of life

    // called by the Timer and the JButtons

    public synchronized void actionPerformed(ActionEvent e) {

        // test the JButtons first

        Object o = e.getSource();

        // the clear button

        if(o == clearBtn) {

            timer.stop();                   // stop timer

            gameRunning = false;            // flag gamme not running

            PauseBtn.setEnabled(false);       // disable pause button

            StartBtn.setEnabled(true);           // enable go button

            // clear all cells

            for(int r = 1; r < Cells.length ; r++) {

                for(int c = 1; c < Cells[r].length ; c++) {

                    Cells[r][c].clear();

                }

            }

            // reset generation number and its label

            generation = 0;

            CellsIteration.setText("Generation: 0");

            return;

        }

        // the pause button

        if(o == PauseBtn) {

            timer.stop();                   // stop timer

            gameRunning = false;            // flag not running

            PauseBtn.setEnabled(false);       // disable myself

            StartBtn.setEnabled(true);           // enable go button

            return;

        }

        // the go button

        if(o == StartBtn) {

            PauseBtn.setEnabled(true);                // enable pause button

            StartBtn.setEnabled(false);                  // disable myself

            gameRunning = true;                     // flag game is running

            timer.setDelay(GenDelay);

            timer.start();

           return;

        }

        // not a JButton so it is the timer

        // set the delay for the next time

        timer.setDelay(GenDelay);

        // if the game is not running wait for next time

        if(!gameRunning)

            return;

        ++generation;

        CellsIteration.setText("Generation: " + generation);

        for(int r = 0; r < Cells.length; r++) {

            for(int c = 0; c < Cells[r].length; c++) {

                Cells[r][c].checkState();

            }

        }

        for(int r = 0; r < Cells.length; r++) {

            for(int c = 0; c < Cells[r].length; c++) {

                Cells[r][c].updateState();

            }

        }

    }
    //end of action


    // to start the whole thing as a Java application

    public static void main(String[] arg) {

      SwingUtilities.invokeLater(new Runnable() {

            public void run() {

                new GameOfLife2(50, 50);

            }

        });

    }



    // A class that extends JLabel but also check for the neigbour

    // when asked to do so

    class CellsGrid extends JLabel implements MouseListener {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        private int state, newState;

        private int numNeighbours;

        private CellsGrid[] neighbour = new CellsGrid[8]; // array of total neighbours with possibility of 8




        CellsGrid() {

            state = newState = 0;           // Dead

            setOpaque(true);                // so color will be showed

            setBackground(color[0]);        //set colour of dead cell

            addMouseListener(this);         // to select new LIVE cells

            this.setPreferredSize(dim);     //set size a new cells

        }

        // to add a neighbour

        void addNeighbour(CellsGrid n) {

            neighbour[numNeighbours++] = n;

        }

        // to see if I should live or not

        void checkState() {

            // number alive around

            int NumNeighbours = 0; // number alive neighbours

            // see the state of my neighbour

            for(int i = 0; i < numNeighbours; i++)

                NumNeighbours += neighbour[i].state;

            // newState

            if(state == 1) {                // if alive

                if(NumNeighbours < 2)              // 1.Any live cell with fewer than two live neighbours dies

                    newState = 0;

                if(NumNeighbours > 3)              // 2.Any live cell with more than three live neighbours dies

                    newState = 0;

            }

            else {

                if(NumNeighbours == 3)            // 4.Any dead cell with exactly three live neighbours becomes a live cell

                    newState = 1;

            }

        }

        // after the run switch the state to new state

        void updateState() {

            if(state != newState) {     // do the test to avoid re-setting same color for nothing  

                state = newState;

                setBackground(color[state]);

            }

        }



        // called when the game is reset/clear

        void clear() {

            if(state == 1 || newState == 1) {

               state = newState = 0;

                setBackground(color[state]);

            }

        }

        @Override

        public void mouseClicked(MouseEvent arg0) {

        }

        // if the mouse enter a cell and it is down we make the cell alive

        public void mouseEntered(MouseEvent arg0) {

            if(mouseDown) {

                state = newState = 1;

                setBackground(color[1]);               

            }

        }

        @Override

        public void mouseExited(MouseEvent arg0) {

        }

        // if the mouse is pressed on a cell you register the fact that it is down

        // and make that cell alive

        public void mousePressed(MouseEvent arg0) {

            mouseDown = true;

            state = newState = 1;

            setBackground(color[1]);

        }

        // turn off the fact that the cell is down

        public void mouseReleased(MouseEvent arg0) {

            mouseDown = false;

        }      

    }

}
EN

回答 1

Stack Overflow用户

发布于 2017-03-16 16:57:40

作为UnholySheep,您需要学习%操作符。%是计算单元除法提醒的方法。请参阅操作

在这里应用它的最简单的方法是这样的。首先介绍getCellSafe方法

代码语言:javascript
复制
 private CellsGrid getCellSafe(int r0, int c0) {
      int r = r0  % Cells.length; // Cells.length is effectively nbRow
      if (r < 0) r += Cells.length; // deal with how % works for negatives
      int c = c0  % Cells[0].length; // Cells[0].length is effectively nbCol
      if (c < 0) c += Cells[0].length; // deal with how % works for negatives
      return Cells[r][c];
 }

然后使用此方法代替直接访问,例如

代码语言:javascript
复制
     Cells[r][c].addNeighbour(getCellSafe(r-1, c);    // North

同时,在你的+ 2nbRow中去除你的nbCol

更新:L型的修补程序

“L- for”的错误出现在“东南”行的副本中,您在其中将c+1转换为+c

代码语言:javascript
复制
          Cells[r][c].addNeighbour(Cells[r+1][c+1]);  // South East
          //Cells[r][c].addNeighbour(getCellSafe(r+1, +c));

更大的改进

一般来说,您的代码非常糟糕。有许多逻辑重复(请参阅干原理)和其他坏代码,如巨大的ActionListener。以下是我试图把它清理一下的尝试:

代码语言:javascript
复制
public class GameOfLife2 extends JFrame
{
    /**
     *
     */
    public static Random random = new Random();

    static final Color[] color = {Color.YELLOW, Color.BLACK};

    // size in pixel of every label
    static final int cellSize = 15;

    static final Dimension cellDim = new Dimension(cellSize, cellSize);

    static final int GenDelay = 200;

    // the cells labels
    private CellsGrid[][] Cells;

    // that fires the next generation

    private Timer timer;

    // generation counter

    private int generation = 0;

    private JLabel CellsIteration = new JLabel("Generation: 0");

    // the 3 buttons

    private JButton clearBtn = new JButton("Clear");

    private JButton PauseBtn = new JButton("Pause");

    private JButton StartBtn = new JButton("Start");

    private JButton StepBtn = new JButton("Step");

    // the slider for the speed


    // state of the game (running or pause)

    private boolean gameRunning = false;


    GameOfLife2(int nbRow, int nbCol)
    {

        super("New GameOfLife");

        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        Cells = new CellsGrid[nbRow][nbCol];

        for (int r = 0; r < nbRow; r++)
        {
            for (int c = 0; c < nbCol; c++)
            {
                Cells[r][c] = new CellsGrid();
            }
        }


        // panel in the center with the labels

        JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));
        panel.setBackground(Color.BLACK);
        panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));


        // add each label (not the one on the border) to the panel and add to each of them its neighbours

        for (int r = 0; r < nbRow; r++)
        {
            for (int c = 0; c < nbCol; c++)
            {
                CellsGrid curCell = Cells[r][c];
                panel.add(curCell);

                for (int dr = -1; dr <= 1; dr++)
                {
                    for (int dc = -1; dc <= 1; dc++)
                    {
                        CellsGrid neighbor = getCellSafe(r + dr, c + dc);
                        if (neighbor != curCell)
                            curCell.addNeighbour(neighbor);
                    }
                }
            }
        }


        // now the panel can be added

        add(panel, BorderLayout.CENTER);

        // the bottom panel with the buttons the generation label and the slider

        Box headerPanel = Box.createHorizontalBox();
        Box buttonPanel = Box.createHorizontalBox();

        // old pre-Java 8 syntax
        //clearBtn.addActionListener(new ActionListener()
        //{
        //  @Override
        //  public void actionPerformed(ActionEvent e)
        //  {
        //      clearGame();
        //  }
        //});
        clearBtn.addActionListener((e) -> clearGame()); // holy Java 8 lambda syntax
        buttonPanel.add(clearBtn);

        PauseBtn.addActionListener((e) -> stopGame());
        buttonPanel.add(PauseBtn);

        StartBtn.addActionListener((e) -> startGame());
        buttonPanel.add(StartBtn);

        StepBtn.addActionListener((e) -> stepToNextGeneration());
        buttonPanel.add(StepBtn);

        // the generation label
        CellsIteration.setHorizontalAlignment(SwingConstants.CENTER);

        headerPanel.add(Box.createHorizontalStrut(10));
        headerPanel.add(buttonPanel);
        headerPanel.add(Box.createHorizontalStrut(10));
        headerPanel.add(Box.createHorizontalGlue());
        headerPanel.add(Box.createHorizontalStrut(10));
        headerPanel.add(CellsIteration);
        headerPanel.add(Box.createHorizontalGlue()); // if you want "generation" label closer to center
        headerPanel.add(Box.createHorizontalStrut(10));


        // in the JFrame
        add(headerPanel, BorderLayout.NORTH);

        // put the frame on
        setLocation(20, 20);

        gameRunning = false;
        generation = 0;
        updateGenerationTitle();
        updateButtonsState();


        pack(); // adjust to the window size
        setMinimumSize(new Dimension(600, 500));
        setVisible(true);

        // start the thread that run the cycles of life
        timer = new Timer(GenDelay, (e) -> onTimerStep());
    }

    private CellsGrid getCellSafe(int r0, int c0)
    {
        int r = r0 % Cells.length; // Cells.length is effectively nbRow
        if (r < 0) r += Cells.length; // deal with how % works for negatives
        int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
        if (c < 0) c += Cells[0].length; // deal with how % works for negatives
        return Cells[r][c];
    }


    private void updateButtonsState()
    {
        PauseBtn.setEnabled(gameRunning);
        StartBtn.setEnabled(!gameRunning);
        StepBtn.setEnabled(!gameRunning);
    }

    private void updateGenerationTitle()
    {
        CellsIteration.setText("Generation: " + generation);
    }

    private void startGame()
    {
        gameRunning = true;                     // flag game is running
        updateButtonsState();
        timer.setDelay(GenDelay);
        timer.start();
    }

    private void stopGame()
    {
        timer.stop();                   // stop timer
        gameRunning = false;            // flag not running
        updateButtonsState();
    }

    private void clearGame()
    {
        stopGame();
        // clear all cells

        for (int r = 0; r < Cells.length; r++)
        {
            for (int c = 0; c < Cells[r].length; c++)
            {
                Cells[r][c].clear();
            }
        }

        // reset generation number and its label

        generation = 0;
        updateGenerationTitle();
    }

    private void stepToNextGeneration()
    {
        ++generation;
        updateGenerationTitle();

        for (int r = 0; r < Cells.length; r++)
        {
            for (int c = 0; c < Cells[r].length; c++)
            {
                Cells[r][c].checkState();
            }
        }

        for (int r = 0; r < Cells.length; r++)
        {
            for (int c = 0; c < Cells[r].length; c++)
            {
                Cells[r][c].updateState();
            }
        }
    }

    private void onTimerStep()
    {
        if (gameRunning)
            stepToNextGeneration();
    }

    // to start the whole thing as a Java application
    public static void main(String[] arg)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new GameOfLife2(50, 50);
            }
        });
    }


    // A class that extends JLabel but also check for the neigbour

    // when asked to do so

    static class CellsGrid extends JLabel implements MouseListener
    {
        private boolean alive = false;
        private boolean newAlive = false;

        private final ArrayList<CellsGrid> neighbours = new ArrayList<>(8); // array of total neighbours with possibility of 8


        CellsGrid()
        {
            setOpaque(true);                // so color will be showed
            addMouseListener(this);         // to select new LIVE cells
            setPreferredSize(cellDim);     //set size a new cells

            updateColor();
        }

        // to add a neighbour

        void addNeighbour(CellsGrid n)
        {
            neighbours.add(n);
        }

        // to see if I should live or not

        void checkState()
        {
            // number alive around
            int NumNeighbours = 0; // number alive neighbours

            // see the state of my neighbour
            for (CellsGrid neighbour : neighbours)
                NumNeighbours += neighbour.alive ? 1 : 0;

            // 1. Any live cell with fewer than two live neighbours dies
            // 2. Any live cell with two or three live neighbours lives on to the next generation.
            // 3. Any live cell with more than three live neighbours dies
            // 4. Any dead cell with exactly three live neighbours becomes a live cell
            if (alive)
            {
                newAlive = (NumNeighbours == 2) || (NumNeighbours == 3);
            }
            else
            {
                newAlive = (NumNeighbours == 3);
            }
        }

        // after the run switch the state to new state
        void updateState()
        {
            alive = newAlive;
            updateColor();
        }


        // called when the game is reset/clear
        void clear()
        {
            alive = newAlive = false;
            updateColor();
        }

        private void updateColor()
        {
            setBackground(color[alive ? 1 : 0]);
        }


        @Override
        public void mouseClicked(MouseEvent arg0)
        {
        }

        // if the mouse enter a cell and it is down we make the cell alive
        @Override
        public void mouseEntered(MouseEvent e)
        {
            boolean mouseDown = (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0;
            if (mouseDown)
            {
                alive = newAlive = true;
                updateColor();
            }
        }

        @Override
        public void mouseExited(MouseEvent e)
        {
        }

        @Override
        public void mousePressed(MouseEvent arg0)
        {
            //              state = newState = true;
            alive = newAlive = !newAlive; //invert state is more useful, so you can clear something
            updateColor();
        }

        @Override
        public void mouseReleased(MouseEvent arg0)
        {
        }
    }
}

这段代码似乎可以工作,包括滑翔机对角飞行,环绕着这个领域。

一些注意事项:

  • 对于每件事,我将您的单个ActionListener拆分为独立的,并使用Java 8语法使它们在注册时变得简短和漂亮。
  • 我介绍了几种updateXyzstepToNextGeneration方法,以大幅度减少代码重复。
  • MouseEvent已经提供了按钮是否关闭的信息。
  • 我添加了调试代码所需的“步骤”按钮。
  • 除了GridLayout之外,还有其他布局
  • 在UI (JComponent)类中定义JComponent没有多大意义,因为它们实际上是不可序列化的(尽管它们确实实现了Serializable接口)。另见Swing组件和序列化

具有更多注释答案的更新

如果我想要有一些预先创建的初始状态,我已经创建了一个简单的类(更像structute) Point,只有两个字段rowcolumn,并且有一个List或它们的数组,然后进行clear并对集合进行迭代以使列表中的那些单元活下来。如果我想更进一步的话,我可能会再添加一个包装类(structure) SavedGame或其他东西来保存widthheight以及一个Point的集合;并使整个UI动态化,而不是在构造函数中构建一次;然后添加从某个文件保存/读取此类配置的能力。

至于随机,有一个java.util.Random类具有有用的int nextInt(int n)方法。所以我会问用户应该有多少个随机单元被激活,然后运行一个循环直到那个数,并使用随机生成的新活单元生成行和列。注意,如果您想填充大量的单元格,您可能需要检查您选择的下一个随机单元是否已经激活,如果是,则再一次“滚动骰子”。如果您想要填充更多的单元格,则需要将算法更改为更复杂或填充90%的单元格可能要花费很长时间。一种方法可能是在范围内生成单细胞指数(0;行*已经活跃的单元格的高度计数),而不是独立的行和列,然后只从左上角到右下角,只计算死细胞并使活的相应单元格存活。这样,您就可以确保每个新的活着的单元格都需要相同的时间来生成。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42836149

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档