Sample evolving species: evolver.cl |
Code status: as of Feb. 26th, the dispatcher is complete except for the game save/load features (and any bug reports that come in). A set of sample species is included in the repository along with the latest version of the controller. |
As a project option for the course, you may create an entry for the World of Foo 485 tournament.
World of Foo is a game in which each competitor designs a critter species. A population of each species is generated, and the individual critters can begin cooperating and competing with one another to see which population can amass the most wealth over the course of the game. In the beginning your critters will have no idea what other kinds of critters exist or what they are like, so as the game progresses they will need to learn which species to avoid, which they can cooperate with, and which they can take advantage of. Keep in mind that each individual critter may be created differently than others in its species, and will have different experiences than others in its species, so will learn differently as the game progresses.
You will create a lisp let-over-lambda style dispatcher that represents an individual of your species: determining what their starting stats are, what actions they take as the game progresses, how they interact with the other critters they encounter, what they choose to remember as the game progresses and how their behaviour adapts, plus some administrative tools to allow your creating to interact with the game controller.
Specific details on the dispatcher, along with the required code template, are provided
further below.
(Note: details may evolve somewhat over the next couple of weeks, as the controller is only partially
complete at the moment. There is a list of anticipated controller revisions provided in the controller.cl file
included in the git repository.)
Project marking criteria
For your species to do well, they will need to learn what other species they can work with, which ones to avoid, and which ones can be exploited, but with the proviso that some species might be programmed to change their behaviour over time (maybe start nice then turn aggressive when no one expects it?) and some species might be highly unpredictable ... who knows what your 485 classmates might do!
However, that alone doesn't necessarily capture the metaprogramming component of the project. How you incorporate metaprogramming into your critter handler is entirely up to you - do you use some form of closure when each critter is first created to generate custom handlers for its initiator/responder actions? Do you actually modify the code for those actions over time? Do you generate/revise custom handlers for dealing with specific species as time goes on? In the event of a game save/load, how do you ensure the code you generated is part of your save/load data?
Critter overview
Each critter has five permanent stats: Attack, Defense, Speed, Skill, and Max-health. These stats are determined when the critter is first created, and remain constant for the duration of the game, and each is in the range 0-5. (In fact, the maximum number of points per stat can be set at the start of a game, see the dispatcher information below for details.)
When they are first created, each individual critter is given between 12 and 15 points (randomly determined) they can then distribute amongst their permanent stats (within the permitted 0-5 range). (Again, this amount is customizable per game, see the dispatcher for details.)
Each critter also has three variable stats: Food, Wealth, and Health. When a critter is first created, its food and wealth are 0, it's health starts at its max-health.
Actions overview
Each turn, all the critters in the game will be randomly paired up for an encounter with another critter, and one critter from each pair will be randomly chosen as the initiator of the encounter.
Initiator actions
The initiator is told what species of critter they have encountered, but nothing else about the critter.
The initiator must then pick one of the following actions:
Responses
The other critter is told what species of critter they have encountered, and what action the
initiator is attempting. The critter can then choose a response as follows:
Results
The results of the actions are determined as follows:
All code will be run in common lisp using gcl on otter (or one of the cubs/pups).
The repository of skeleton code is available through the usual git process, using csci485 and WoFoo. The repository contains the latest version of the central controller and a couple of basic critters to test your own species in simulation. It will (later) also include a reporting tool to analyze the data in the saved game files and provide information on the different species encounter histories and results.
**Note** (especially in the first few weeks) the controller will be continually revised/updated. It's only partially functional at the moment, a list of anticipated changes is provided near the top of controller.cl. |
The central controller
The central controller will load all the entries, generate the starting population, keep track of the permanent stats and current wealth, health, and food for each critter (to prevent accidental or deliberate stats errors) and run the game turns.
Each turn will follow the sequence below:
In the event of a crash or shutdown, when the game is restarted it will be from the most recent saved state.
The dispatcher
Because all competitors will be providing code run by/interacting with the central controller, there are some strict rules around the code. If your code violates any of the rules below you will be immediately eliminated from the competition.
If your code gives an invalid choice/response at any point, the controller will randomly pick a valid choice instead.
*Note: you can safely assume the random number generator has already been seeded, i.e. do not make your own call to (make-random-state).
; Dispatcher template ; critter dispatcher template ; initialize a critter given parameters specifing ; - an ID for it, ; - the total number of Points available to be spent on permanent stats, ; - and the maximum number of points that can be spent on any one stat, ; return the lambda function that acts as a dispatcher for interaction ; with the central controller (defun SPECIESNAME (critID Points StatMax) (let ( ; any local data you want to track for your critter, ; e.g. your species name, this specific critter's id, ; the latest stats for your critter, ; the types of species they've encountered so far ; and how those encounters went, ; ...? (labels ( ; any optional local methods also can be included, ; below we show the set of required methods (allocate () ; allocate based on the Points and StatsMax values supplied, ; returns the critters starting allocation of stats as a list ; in this order: (attack defense max-health skill speed) ; allocate will only be called once in the lifetime of the critter, ; and may use any algorithm desired (not all your critters ; have to start with the exact same stats) (let ((aveStat (floor (/ Points 5))) ; calc an average stat value, round down (leftOver (- Points (* 4 aveStat)))) ; calc what's left over after the first 4 stats (list aveStat aveStat aveStat aveStat leftOver)) ; (initiate (species) ; given the symbol for the species encounted, e.g. 'COW, ; return the action your critter wishes to initiate, ; in one of the following list formats: ; ('ATTACK) ; ('FLEE) ; ('TRADEWEALTH coinsOffered foodExpected) ; ('TRADEFOOD foodOffered coinsExpected) ; ('SHARE) ; ('COLLECT) ; ('HARVEST) ; ('EAT) '(EAT)) (respond (species initiatorAction) ; given the species encounted and their chosen action (in the form above) ; return the action your critter wishes to respond with, ; in one of the following list formats: ; ('ATTACK) ; ('FLEE) ; ('ACCEPT) ; works for offers to trade or share ; ('COLLECT) ; ('HARVEST) ; ('EAT) '(EAT)) (update (turnResults) ; this gives you an opportunity to update your internal information ; based on your stats after resolving the initiate/respond actions ; you and the other critter chose ; turnResults is a list of your stats after resolving the actions, ; in the following order ; (attack defense max-health skill speed food health wealth) ) (save () ; return a list of data you would like stored as part of the save game process ; (so you can recreate the current state using the load function below ; after a system restart) nil) (load (savedData) ; restore your critter state based on the most recent saved data ) ; end of local methods ) ; begin the dispatcher, responds to requests from the controller ; nothing below here should be altered as the controller will ; never interact with the dispatcher in any other way (lambda (request &optional (options nil) (extra nil)) (case request ('ALLOCATE (allocate)) ('INITIATE (initiate options)) ; passing species ('RESPOND (respond options extra)) ; passing species and action ('UPDATE (update options)) ; passing updated stats ('SAVE (save)) ('LOAD (load options)) ; passing most recent saved data (otherwise (format t "~%***DANGER DANGER: The controller is issuing bad requests! ~A ~A ~A~%~%" request options extra)) )))))) |
; cows are mostly just food focused ; initialize a cow given parameters specifing ; - an ID for it, ; - the total number of Points available to be spent on permanent stats, ; - and the maximum number of points that can be spent on any one stat, ; return the lambda function that acts as a dispatcher for interaction ; with the central controller (defun cow (critID Points StatMax) (let ( ; keep track of this cow's latest stats, ; initialize them just before the dispatcher (attack 1) (defense 1) (maxhealth 1) (skill 1) (speed 1) (health 1) (wealth 0) (food 0) ; keep track of this cow's id (id critID) ; misc scratch var for local calculations (scratch nil) ) (labels ( ; no optional methods needed here, the cow is pretty basic ; below are the required methods (allocate () ; the permanent stats were initialized earlier (just before the dispatcher) ; so just return them in the order expected (list attack defense maxhealth skill speed)) (initiate (species) (cond ((and (= health 0) (= food 0)) '(SHARE)) ; ask to share if we're desparate ((and (< health (- maxhealth 2)) (> food 0)) '(EAT)) ; don't wanna be hungry! ((and (> health 1) (= 0 (random 3))) '(FLEE)) ; get spooked and run away sometimes (t '(HARVEST)))) ; build up our food supplies (respond (species initiatorAction) (cond ((and (< health 2) (> food 0)) '(EAT)) ; neeeeed fooood! ((equalp initiatorAction '(SHARE)) '(ACCEPT)) ; sure, we'll share ((and (> health 1 ) (equalp initiatorAction '(ATTACK))) '(FLEE)) ; run away! (t '(HARVEST)))) ; always game to gather more food (update (turnResults) ; make note of the cow's most up-to-date stats (setf attack (nth 0 turnResults)) (setf defense (nth 1 turnResults)) (setf maxhealth (nth 2 turnResults)) (setf skill (nth 3 turnResults)) (setf speed (nth 4 turnResults)) (setf food (nth 5 turnResults)) (setf health (nth 6 turnResults)) (setf wealth (nth 7 turnResults))) (save () ; save all the data from the let variables except the scratch (list attack defense maxhealth skill speed food health wealth id)) (load (savedData) ; restore all the cow's data from the recent save, ; (all but the id uses the same format as update) (update savedData) (setf id (nth 8 savedData))) ; end of local methods ) ; initialize the cow stats from the available Points (setf defense (floor (/ Points 3))) (setf maxhealth (floor (/ Points 4))) (setf health maxhealth) (setf skill 2) (setf scratch (- Points (+ defense maxhealth skill))) (setf attack (ceiling (/ scratch 3))) (setf speed (- scratch attack)) (setf wealth 0) (setf food 0) ; begin the dispatcher, responds to requests from the controller (lambda (request &optional (options nil) (extra nil)) (cond ((equalp 'ALLOCATE request) (allocate)) ((equalp 'INITIATE request) (initiate options)) ; passing species ((equalp 'RESPOND request) (respond options extra)) ; passing species and action ((equalp 'UPDATE request) (update options)) ; passing updated stats ((equalp 'SAVE request) (save)) ((equalp 'LOAD request) (load options)) ; passing most recent saved data (t (format t "~%***DANGER DANGER: The controller is issuing bad requests! ~A ~A ~A~%~%" request options extra)) ))))) |