|
| 1 | +require 'math' |
| 2 | +require 'os' |
| 3 | +require 'io' |
| 4 | + |
| 5 | +local GRID_WIDTH <comptime> = 40 |
| 6 | +local GRID_HEIGHT <comptime> = 15 |
| 7 | + |
| 8 | +-- Two-dimensional field of cells. |
| 9 | +local CellField = @record{ |
| 10 | + cells: [GRID_HEIGHT][GRID_WIDTH]boolean, |
| 11 | +} |
| 12 | + |
| 13 | +-- Returns an empty field. |
| 14 | +function CellField.new(): *CellField |
| 15 | + return new(@CellField) |
| 16 | +end |
| 17 | + |
| 18 | +-- Sets the state of the specified cell to the given value.. |
| 19 | +function CellField:set_alive(x: integer, y: integer, b: boolean) |
| 20 | + self.cells[y][x] = b |
| 21 | +end |
| 22 | + |
| 23 | +--[[ |
| 24 | +Reports whether the specified cell is alive. |
| 25 | +If the x or y coordinates are outside the field boundaries they are wrapped. |
| 26 | +]] |
| 27 | +function CellField:is_alive(x: integer, y: integer) |
| 28 | + return self.cells[(y + GRID_HEIGHT) % GRID_HEIGHT][(x + GRID_WIDTH) % GRID_WIDTH] |
| 29 | +end |
| 30 | + |
| 31 | +-- Returns the state of the specified cell at the next time step. |
| 32 | +function CellField:next(x: integer, y: integer): boolean |
| 33 | + -- Count adjacent cells that are alive. |
| 34 | + local alive = 0 |
| 35 | + for i=-1,1 do |
| 36 | + for j=-1,1 do |
| 37 | + if (j ~= 0 or i ~= 0) and self:is_alive(x+i, y+j) then |
| 38 | + alive = alive + 1 |
| 39 | + end |
| 40 | + end |
| 41 | + end |
| 42 | + --[[ |
| 43 | + Returns next state according to the game rules: |
| 44 | + - exactly 3 neighbors: on |
| 45 | + - exactly 2 neighbors: maintain current state |
| 46 | + - otherwise: off |
| 47 | + ]] |
| 48 | + return alive == 3 or (alive == 2 and self:is_alive(x, y)) |
| 49 | +end |
| 50 | + |
| 51 | +-- Stores round state of Conway's Game of Life. |
| 52 | +local GameState = @record{ |
| 53 | + a: *CellField, b: *CellField |
| 54 | +} |
| 55 | + |
| 56 | +-- Returns a new game state with a random initial cells. |
| 57 | +function GameState.new(seed: integer): GameState |
| 58 | + math.randomseed(seed) |
| 59 | + local a = CellField.new() |
| 60 | + for i=1,GRID_WIDTH*GRID_HEIGHT/4 do |
| 61 | + a:set_alive(math.random(0, GRID_WIDTH-1), math.random(0, GRID_HEIGHT-1), true) |
| 62 | + end |
| 63 | + return (@GameState){a=a, b=CellField.new()} |
| 64 | +end |
| 65 | + |
| 66 | +-- Advances the game by one instant, recomputing and updating all cells. |
| 67 | +function GameState:step() |
| 68 | + for y=0,GRID_HEIGHT-1 do |
| 69 | + for x=0,GRID_WIDTH-1 do |
| 70 | + self.b:set_alive(x, y, self.a:next(x, y)) |
| 71 | + end |
| 72 | + end |
| 73 | + self.a, self.b = self.b, self.a -- swap fields |
| 74 | +end |
| 75 | + |
| 76 | +-- Returns the game board as a string. |
| 77 | +function GameState:render() |
| 78 | + io.write("\27c") -- clear screen |
| 79 | + for y=0,GRID_HEIGHT-1 do |
| 80 | + for x=0,GRID_WIDTH-1 do |
| 81 | + io.write(self.a:is_alive(x, y) and 'O' or ' ') -- draw cell |
| 82 | + end |
| 83 | + io.write('\n') |
| 84 | + end |
| 85 | + io.flush() -- flush screen |
| 86 | +end |
| 87 | + |
| 88 | +-- Create new game. |
| 89 | +local l = GameState.new(1) |
| 90 | + |
| 91 | +-- Run game of life for 300 frames. |
| 92 | +for i=1,300 do |
| 93 | + l:step() |
| 94 | + l:render() |
| 95 | + os.sleep(1/24) |
| 96 | +end |
0 commit comments