001package ball.game.life;
002/*-
003 * ##########################################################################
004 * Game Applications and Utilities
005 * %%
006 * Copyright (C) 2010 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.beans.ConstructorProperties;
022import java.math.BigInteger;
023import lombok.ToString;
024
025/**
026 * Life {@link Automata}.
027 *
028 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
029 */
030@ToString
031public class Automata {
032    private final int height;
033    private final int width;
034
035    /**
036     * @param   height          The extent of the y-axis.
037     * @param   width           The extent of the x-axis.
038     */
039    @ConstructorProperties({ "height", "width" })
040    public Automata(int height, int width) {
041        if (height > 0) {
042            this.height = height;
043        } else {
044            throw new IllegalArgumentException("height=" + height);
045        }
046
047        if (width > 0) {
048            this.width = width;
049        } else {
050            throw new IllegalArgumentException("width=" + width);
051        }
052    }
053
054    /**
055     * Method to get the height of the board.
056     *
057     * @return  The height.
058     */
059    public int getHeight() { return height; }
060
061    /**
062     * Method to get the width of the board.
063     *
064     * @return  The width.
065     */
066    public int getWidth() { return width; }
067
068    /**
069     * Given the current {@code state}, calculate the next {@code state}.
070     *
071     * @param   current         The current {@code state}.
072     *
073     * @return  The next {@code state}.
074     */
075    public BigInteger next(BigInteger current) {
076        var next = BigInteger.ZERO;
077
078        for (var y = 0; y < height; y += 1) {
079            for (var x = 0; x < width; x += 1) {
080                var count = 0;
081
082                count += get(current, y - 1, x - 1) ? 1 : 0;
083                count += get(current, y - 1, x)     ? 1 : 0;
084                count += get(current, y - 1, x + 1) ? 1 : 0;
085                count += get(current, y,     x - 1) ? 1 : 0;
086                count += get(current, y,     x + 1) ? 1 : 0;
087                count += get(current, y + 1, x - 1) ? 1 : 0;
088                count += get(current, y + 1, x)     ? 1 : 0;
089                count += get(current, y + 1, x + 1) ? 1 : 0;
090
091                if (get(current, y, x)) {
092                    switch (count) {
093                    case 2:
094                    case 3:
095                        next = next.setBit(y * width + x);
096                        break;
097
098                    default:
099                        next = next.clearBit(y * width + x);
100                        break;
101                    }
102                } else {
103                    switch (count) {
104                    case 3:
105                        next = next.setBit(y * width + x);
106                        break;
107
108                    default:
109                        next = next.clearBit(y * width + x);
110                        break;
111                    }
112                }
113            }
114        }
115
116        return next;
117    }
118
119    /**
120     * Method to get the value of a cell at a specified {@code (y, x)}
121     * coordinate fron the argument {@code state}.
122     *
123     * @param   state           The {@link Automata} {@code state}.
124     * @param   y               The {@code y} coordinate.
125     * @param   x               The {@code x} coordinate.
126     *
127     * @return  {@code true} if the {@code (y, x)} coordinate is valid and
128     *          if the specified cell is "alive;" {@code false} otherwise.
129     */
130    public boolean get(BigInteger state, int y, int x) {
131        return ((0 <= y && y < height && 0 <= x && x < width) && state.testBit(y * width + x));
132    }
133}