musifter

Automaton class (Smalltalk)

Dec 4th, 2025
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Smalltalk 5.58 KB | Source Code | 0 0
  1. Collection extend [
  2.     incAt: key  [ ^self at: key put: (self at: key ifAbsent: [0]) + 1 ]
  3. ]
  4.  
  5. "
  6. | Class to handle the geometry of the space for our life automaton.
  7. | Must be subclassed to be used. The jobs of the subclass:
  8. |   - mark the initial cells that are alive via setAlive/flip
  9. |   - define the geometry by overriding the neighbours method
  10. "
  11. Object subclass: Space [
  12.     | liveCells currActive nextActive changes |
  13.  
  14.     Space class >> new [
  15.         ^(super new) init
  16.     ]
  17.  
  18.     init [
  19.         liveCells := Set new.
  20.         changes   := 0.
  21.         ^self
  22.     ]
  23.  
  24.     " Geometry definition method: "
  25.     neighbours: cellCoord [
  26.         self subclassResponsibility
  27.     ]
  28.  
  29.     " State change methods: "
  30.     setAlive: cellCoord [
  31.         " Returns if cell is alive after"
  32.         liveCells add: cellCoord.
  33.         changes := changes + 1.
  34.         ^true
  35.     ]
  36.  
  37.     setDead: cellCoord [
  38.         " Returns if cell is alive after"
  39.         liveCells remove: cellCoord ifAbsent: [].
  40.         changes := changes + 1.
  41.         ^false
  42.     ]
  43.  
  44.     flipCell: cellCoord [
  45.         ^(self isAlive: cellCoord) ifTrue:  [ self setDead:  cellCoord ]
  46.                                    ifFalse: [ self setAlive: cellCoord ].
  47.     ]
  48.  
  49.     " Functions to track generation state: "
  50.     initState [
  51.         " Note: this is moved to currActive by first advanceTurn call "
  52.         nextActive := Dictionary new.
  53.  
  54.         " Initialize live counts to nextActive "
  55.         liveCells do: [ :coord | self markActive: coord ].
  56.     ]
  57.  
  58.     advanceTurn [
  59.         changes := 0.
  60.         currActive := nextActive.
  61.         nextActive := Dictionary new.
  62.     ]
  63.  
  64.     markActive: coord [
  65.         " NOTE: Assumes cell is alive, adds one to neighbours for next turn "
  66.         nextActive at: coord ifAbsentPut: [0].
  67.         (self neighbours: coord) do: [:neigh | nextActive incAt: neigh].
  68.     ]
  69.  
  70.     " Access methods: "
  71.     liveCells [ ^liveCells ]
  72.  
  73.     isAlive: cellCoord [
  74.         ^liveCells includes: cellCoord
  75.     ]
  76.  
  77.     changes [
  78.         ^changes
  79.     ]
  80.  
  81.     numActive [
  82.         ^nextActive size
  83.     ]
  84.  
  85.     currActive [
  86.         ^currActive
  87.     ]
  88. ]
  89.  
  90. "
  91. | Class to run a simple cellular automaton.
  92. |   space:    Subclass of above class, defines neighbour geometry
  93. |   dieRule:  Takes # of live neighbours, returns if live cell would die
  94. |   liveRule: Takes # of live neighbours, returns if dead cell would become live
  95. "
  96. Object subclass: Automaton [
  97.     | space dieRule liveRule format |
  98.  
  99.     " Constructors: "
  100.     Automaton class >> space: aSpace dieRule: aBlock1 liveRule: aBlock2 [
  101.         ^(super new) init: aSpace dies: aBlock1 lives: aBlock2 format: #table
  102.     ]
  103.  
  104.     Automaton class >> space: aSpace dieRule: aBlock1 liveRule: aBlock2 format: aSym [
  105.         ^(super new) init: aSpace dies: aBlock1 lives: aBlock2 format: aSym
  106.     ]
  107.  
  108.     init: aSpace dies: aBlock1 lives: aBlock2 format: aSym [
  109.         space    := aSpace.
  110.         dieRule  := aBlock1.
  111.         liveRule := aBlock2.
  112.         format   := aSym.
  113.         ^self
  114.     ]
  115.  
  116.     outputStatus: turn [
  117.         (('Turn: %1 @Live: %2 @Active: %3 @Changes: %4 @Time: %5    ' %
  118.         {
  119.             turn.
  120.             space liveCells size.
  121.             space numActive.
  122.             space changes.
  123.             Time millisecondClock / 1000.0
  124.         }) replaceAll: $@ with: Character tab) display.
  125.  
  126.         (format = #line) ifTrue:  [ stdout cr; flush ]
  127.                          ifFalse: [ stdout nl; flush ].
  128.     ]
  129.  
  130.     statusFormat: formSym [
  131.         " formSym should be one of #none, #line, or #table "
  132.         ^format := formSym.
  133.     ]
  134.  
  135.     " Private method (do not call): "
  136.     runOneTurn: updateNextTurn [
  137.         | notLastTurn |
  138.         space advanceTurn.
  139.  
  140.         space currActive keysAndValuesDo: [ :coord :liveNeigh |
  141.            | alive |
  142.  
  143.             (alive := space isAlive: coord) ifTrue: [
  144.                 " Alive cell case: check if dies "
  145.                 (dieRule value: liveNeigh) ifTrue: [
  146.                     alive := space setDead: coord.
  147.                 ]
  148.             ] ifFalse: [
  149.                 " Dead cell case: check if becomes alive "
  150.                 (liveRule value: liveNeigh) ifTrue: [
  151.                     alive := space setAlive: coord.
  152.                 ]
  153.             ].
  154.  
  155.             (alive and: [updateNextTurn]) ifTrue: [ space markActive: coord ]
  156.         ].
  157.     ]
  158.  
  159.     " Returns a Generator stream for turns of the automaton "
  160.     getTurnGenerator [
  161.         ^Generator on: [ :gen |
  162.            | turn |
  163.             " Initialize active cells (also counts live neighbours): "
  164.             space initState.
  165.  
  166.             turn := 1.
  167.             [
  168.                 self runOneTurn: true.
  169.                 (format ~= #none) ifTrue: [ self outputStatus: turn ].
  170.                 gen yield: space.
  171.                 turn := turn + 1.
  172.             ] repeat
  173.         ]
  174.     ]
  175.  
  176.     " Cover method for generator that runs automaton until no changes "
  177.     runUntilStable [
  178.         | gen |
  179.         gen := self getTurnGenerator.
  180.         [ gen next changes ~= 0 ] whileTrue.
  181.  
  182.         (format = #line) ifTrue: [ stdout nl ].
  183.         ^space liveCells size
  184.     ]
  185.  
  186.     " Cover method for generator that runs automaton a set number of turns "
  187.     runTurns: numTurns [
  188.         | gen |
  189.         gen := self getTurnGenerator.
  190.  
  191.         " Calling runOneTurn with false for the last turn saves a lot of time "
  192.         (numTurns - 1) timesRepeat: [ gen next ].
  193.         self runOneTurn: false.
  194.  
  195.         (format = #line) ifTrue: [ stdout nl ].
  196.         ^space liveCells size
  197.     ]
  198.  
  199.     " Access "
  200.     space  [ ^space ]
  201. ]
Advertisement
Add Comment
Please, Sign In to add comment