570 lines
22 KiB
Org Mode
570 lines
22 KiB
Org Mode
:PROPERTIES:
|
|
:ID: e7ae9b78-7d75-426f-bd19-7f986bc3c6d0
|
|
:END:
|
|
#+TITLE: Group B Pace Notes
|
|
#+filetags: :Project:Icebox:
|
|
|
|
#+ARCOLOGY_KEY: garden/dumb-ideas/group-b
|
|
#+ARROYO_DIRENV_DIR: ~/org/group-b
|
|
|
|
#+AUTO_TANGLE: t
|
|
|
|
I've been thinking about a rally card-game, either a 52-card deck or a custom art deck, called =Group B= or =pacenotes= or something, two players, a co-driver will draw a hand of cards and play them with a driver, who will react to the pacenotes, managing speed, turbo, reaction energy and try to get to the end of a stage.
|
|
|
|
or maybe it's a roguelite deckbuilder... Let's try that:
|
|
|
|
* Prototype Game Loop
|
|
|
|
I haven't actually beat this yet. That doesn't mean you can't? In theory? I don't know what happens when you run out of stage cards is what I am saying 😇
|
|
|
|
Some notes:
|
|
- the longer the straight is, the more you'll accelerate. Probably worth making the longest straight 50 meters or so or injecting a braking-zone card between long straight and hard turns to make sure the player has the right resources
|
|
- card values are all fucked up, this is just a first playtest..
|
|
|
|
#+begin_src python :tangle ~/org/group-b/prototype.py
|
|
from group_b.action_deck import ActionDeck, ActionCard
|
|
from group_b.player import PlayerState
|
|
|
|
import os
|
|
|
|
p = PlayerState()
|
|
|
|
os.system('cls' if os.name=='nt' else 'clear')
|
|
print("Stage start. Ready?")
|
|
while len(p._pace_note_queue) > 0:
|
|
print(p.resources())
|
|
print()
|
|
print(', '.join([str(c) for c in p._pace_note_queue]))
|
|
print("you can:", [f"{idx}: {str(c)}" for idx, c in enumerate(p.hand())])
|
|
choice = int(input("action index: "))
|
|
|
|
os.system('cls' if os.name=='nt' else 'clear')
|
|
|
|
card = p.hand()[choice]
|
|
p.perform_action(card)
|
|
p.draw()
|
|
|
|
p.check_damage()
|
|
|
|
print("You've beat the stage!")
|
|
#+end_src
|
|
|
|
Once it's possible to complete a stage the next logical step would be to add (speed*distance) timing to the stage so that you can see how fast you progress through and race against known seeds?
|
|
|
|
* Dev Environment Setup and running the prototype
|
|
|
|
Naturally I'm using Nix for this because i have the brain worms.
|
|
|
|
#+begin_src nix :tangle ~/org/group-b/shell.nix :mkdirp yes
|
|
{ pkgs ? import <nixpkgs> {}, ...}:
|
|
|
|
with pkgs;
|
|
let
|
|
myPython = python3.withPackages( pp: with pp; []);
|
|
in
|
|
mkShell {
|
|
packages = [
|
|
myPython
|
|
|
|
isort
|
|
black
|
|
];
|
|
}
|
|
#+end_src
|
|
|
|
This is just a python thing. It only uses what's in =stdlib=. Clone this repo, set up a python or use the system one, and run =python prototype.py=. [[roam:Enjoy Your Gaming]].
|
|
|
|
* Player Actor
|
|
:LOGBOOK:
|
|
CLOCK: [2023-04-07 Fri 14:28]
|
|
:END:
|
|
|
|
Okay the logic of the loop and the deck management happens in this =PlayerState= class. It has an [[(ActionDeck)]] and a [[(StageDeck)]] where the cards are managed and it tracks the hand of the player. A player will draw four cards to start with based on the =START_HAND= const-ish and will play one per turn by calling =perform_action= and passing the card in.
|
|
|
|
There is, frankly, too much card logic in the =perform_action= function, it will be some work and diagramming to figure out how to design a state machine that'll behave better. It really does feel to me like an Elixir pattern matching model + a data struct will go a long way here? unsure.
|
|
|
|
But basically action cards are used to progress through stage cards until the stage card deck is empty.
|
|
|
|
A player can see =NOTE_LOOKAHEAD= cards in to the future. there is a difficulty lever here: having more future steps lets you plan better, but in "hardcore mode" you will only see the most-future prediction so having more steps actually could be harder since you have to internalize what the coming track looks like
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/player.py :noweb yes
|
|
from .action_deck import ActionDeck, ActionCard
|
|
from .stage_deck import StageDeck, StageDeckEmpty, StraightCard
|
|
|
|
class PlayerState():
|
|
START_HAND = 4
|
|
NOTE_LOOKAHEAD = 3
|
|
MAX_HAND_SIZE = 5
|
|
|
|
def __init__(self):
|
|
self._action_deck = ActionDeck()
|
|
self._action_deck.shuffle()
|
|
|
|
self._stage = StageDeck()
|
|
|
|
self._resources = PlayerResources()
|
|
self._hand = [self._action_deck.draw() for _ in range(PlayerState.START_HAND)]
|
|
self._pace_note_queue = [self._stage.draw() for _ in range(PlayerState.NOTE_LOOKAHEAD)]
|
|
|
|
|
|
def hand(self):
|
|
return self._hand
|
|
|
|
def resources(self):
|
|
return self._resources
|
|
|
|
def draw(self):
|
|
assert(len(self._hand) <= self.MAX_HAND_SIZE)
|
|
new_action = self._action_deck.draw()
|
|
self._hand.append(new_action)
|
|
return new_action
|
|
|
|
def perform_action(self, action: ActionCard):
|
|
# no cheating!
|
|
assert(action in self.hand())
|
|
|
|
current_stage_note = self._pace_note_queue.pop(0)
|
|
self.apply_road_friction()
|
|
|
|
for modifier, value in action.modifiers.items():
|
|
if type(current_stage_note) == StraightCard:
|
|
# TKTKTK partial value or the ability to play two cards
|
|
# on long straight? hard to balance 70 meter straight in
|
|
# to heavy zone
|
|
if modifier != "focus":
|
|
value = current_stage_note.modifier * value
|
|
|
|
value += value * self._resources.momentum
|
|
self.maybe_modify_resource(modifier, value)
|
|
|
|
if not current_stage_note.can_pass_with(self, action):
|
|
# maybe lol
|
|
damage = int(self._resources.momentum + self._resources.speed)
|
|
print(f"{self._resources.speed} is too fast for a {current_stage_note}! you've taken {damage*5} car and {damage} tire damage")
|
|
self._resources.car_health -= damage * 5
|
|
self._resources.tire_health -= damage
|
|
|
|
current_stage_note.discard()
|
|
action.discard()
|
|
self.hand().remove(action)
|
|
try:
|
|
self._pace_note_queue.append(self._stage.draw())
|
|
except StageDeckEmpty:
|
|
pass
|
|
|
|
def check_damage(self):
|
|
if self._resources.car_health <= 0:
|
|
raise Exception("you've crashed")
|
|
|
|
def apply_road_friction(self):
|
|
rf = self._resources.road_friction
|
|
self._resources.speed -= self._resources.momentum * rf
|
|
self._resources.momentum -= self._resources.momentum * rf
|
|
|
|
|
|
# TKTKTK this will spend resources until one cannot be spent , need to check all before spendign any
|
|
def maybe_modify_resource(self, modifier, value):
|
|
if modifier == "focus" and self._resources[modifier] + value <= 0:
|
|
print(f"{modifier} {value} {self._resources[modifier]}")
|
|
raise Exception("you've lost focus and crashed!")
|
|
|
|
if modifier == "speed" and self._resources[modifier] + value >= self._resources.top_speed:
|
|
self._resources.speed = self._resources.top_speed
|
|
elif self._resources[modifier] + value >= 0:
|
|
self._resources[modifier] += value
|
|
else:
|
|
self._resources[modifier] = 0
|
|
#+end_src
|
|
|
|
A player has a few visible and invisible resources. The =speed=, =focus= and =momentum= are modified by the cards the player is able to play from the hand. The others are affected by the stage cards, and used to modify the others; for example =road_friction= will be set to a higher value on gravel stages causing player to lose more speed when coasting etc. This =DictyDataClass= is a useful little thing that lets me define a =dataclass= but still access it as though it is they are dictionaries.
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/player.py
|
|
from dataclasses import dataclass
|
|
class DictyDataClass():
|
|
def __getitem__(self, item):
|
|
return getattr(self, item)
|
|
|
|
def __setitem__(self, item, val):
|
|
return setattr(self, item, val)
|
|
|
|
@dataclass
|
|
class PlayerResources(DictyDataClass):
|
|
speed: int = 0
|
|
focus: int = 10
|
|
momentum: int = 0
|
|
car_health: int = 100
|
|
tire_health: int = 100
|
|
|
|
# only relevant in meta-progression
|
|
soft_tires: int = 4
|
|
hard_tires: int = 4
|
|
|
|
# hidden values attached to the car, stage, etc
|
|
tire_wear: int = 1
|
|
road_friction: float = 0.1
|
|
top_speed: int = 15
|
|
|
|
def __str__(self):
|
|
return "".join([
|
|
"speed: ", str(self.speed), "\n",
|
|
"focus: ", str(self.focus), "\n",
|
|
"momentum: ", str(self.momentum), "\n",
|
|
"car: ", str(self.car_health), "\n",
|
|
"tire: ", str(self.tire_health), "\n",
|
|
])
|
|
#+end_src
|
|
|
|
|
|
* Deck Abstractions
|
|
|
|
The =Card= and =Deck= exist to make some common interfaces for the different types of cards.
|
|
|
|
** Base classes
|
|
|
|
A Card is pretty simple, basically it just exists to be extended by ActionCard and StageCard to embed metadata about how the card behaves, and to integrate with the Deck class below.
|
|
|
|
Higher-level classes should be able to load modifiers and actions from files on disk. =discard= is a silly little helper that I hope I don't regret adding since the =self.deck= makes this all feel kind of spaghetti-ish.
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/deck.py :mkdirp yes
|
|
class Card():
|
|
def __init__(self, name, deck):
|
|
self.name = name
|
|
self.deck = deck
|
|
|
|
def discard(self):
|
|
self.deck.discard(self)
|
|
return None
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
#+end_src
|
|
|
|
The deck base class is pretty simple, it really only exists to manage two lists of cards, a draw pile and a discard pile. A really simple [[https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle][Fisher-Yates shuffle]] is used to shuffle the cards in =shuffle='s [[(fisher-yates)]] below.
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/deck.py :mkdirp yes
|
|
from random import randint
|
|
from typing import List
|
|
|
|
class Deck():
|
|
def __init__(self):
|
|
self._cards = self.load_cards()
|
|
self._discard = []
|
|
|
|
def load_cards(self) -> List[Card]:
|
|
raise NotImplemented()
|
|
|
|
def shuffle(self):
|
|
self._cards.extend(self._discard)
|
|
self._discard = []
|
|
|
|
# (ref:fisher-yates)
|
|
for old_slot, card in enumerate(reversed(self._cards)):
|
|
new_slot = randint(0, len(self._cards)-1)
|
|
self._cards[old_slot], self._cards[new_slot] = (
|
|
self._cards[new_slot], self._cards[old_slot]
|
|
)
|
|
|
|
def draw(self) -> Card:
|
|
if len(self._cards) == 0:
|
|
print("shuffle...")
|
|
self.shuffle()
|
|
|
|
return self._cards.pop()
|
|
|
|
def discard(self, card: Card):
|
|
self._discard.append(card)
|
|
|
|
def depth(self) -> int:
|
|
return len(self._cards)
|
|
#+end_src
|
|
|
|
** Action Deck
|
|
|
|
An =ActionDeck='s would be defined by the contents of the car that the player is working with. This would be used to abstract the sort of differences between an Audi Quattro, Rally1 and Mini Cooper, the spread and strength of the various cards. Players could also be granted specialty action cards in the service park or as part of meta-progression. By default a player has 10 cards spread between "go faster" and "slow down". I'll want to make this load from JSON or YAML files soon so that I can use Babel to generate them.... 😈
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/action_deck.py
|
|
from .deck import Deck, Card
|
|
|
|
class ActionDeck(Deck):
|
|
def load_cards(self):
|
|
cards = [ActionCard(name="accelerate", deck=self)] * 3
|
|
cards.extend([ActionCard(name="gun it!", deck=self)] * 2)
|
|
cards.extend([ActionCard(name="brake", deck=self)] * 5)
|
|
cards.extend([ActionCard(name="coast", deck=self)] * 2)
|
|
cards.extend([ActionCard(name="find flow", deck=self)] * 2)
|
|
|
|
return cards
|
|
|
|
class ActionCard(Card):
|
|
def __init__(self, name: str, deck: ActionDeck):
|
|
super().__init__(name, deck)
|
|
self.modifiers = ActionCard.load_modifiers(name)
|
|
|
|
|
|
@classmethod
|
|
def load_modifiers(cls, name):
|
|
if name == "accelerate":
|
|
return dict(
|
|
speed=0.5,
|
|
focus=-1,
|
|
momentum=0.25,
|
|
tire_wear=-0.25,
|
|
car_health=0,
|
|
)
|
|
elif name == "brake":
|
|
return dict(
|
|
speed=-1,
|
|
focus=0,
|
|
momentum=-1,
|
|
tire_wear=0,
|
|
car_health=0,
|
|
)
|
|
elif name == "coast":
|
|
return dict(
|
|
speed=0,
|
|
focus=1,
|
|
momentum=-0.25,
|
|
tire_wear=0,
|
|
car_health=0,
|
|
)
|
|
elif name == "gun it!":
|
|
return dict(
|
|
speed=1,
|
|
focus=-2,
|
|
momentum=0.5,
|
|
tire_wear=-0.5,
|
|
car_health=-0.05,
|
|
)
|
|
elif name == "find flow":
|
|
return dict(
|
|
speed=0,
|
|
focus=4,
|
|
momentum=0,
|
|
tire_wear=-0.5,
|
|
car_health=-0.05,
|
|
)
|
|
#+end_src
|
|
|
|
speed should not be set by the cards but calculated from momentum... The user is managing the momentum and focus to get the most speed with the least damage/wear.
|
|
|
|
** Stage Deck
|
|
|
|
The Stage Deck is what defines a rally stage. This version of it is procedurally generated where there are 2x as many turns as straights [[(StageCardWeight)]], and the straights' lengths are weighted toward being shorter [[(StraightCardWeight)]]. You could imagine a =StageDeck= class that pulls its list of cards from a rough take on famous stages and could be paired with art of them.
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/stage_deck.py
|
|
from .deck import Deck, Card
|
|
from .action_deck import ActionCard
|
|
# from .player import PlayerState
|
|
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
from random import randint, choice, choices
|
|
|
|
class StageDeck(Deck):
|
|
DEFAULT_DECK_SIZE=25
|
|
|
|
def load_cards(self):
|
|
cards = list()
|
|
cards.extend(StageCard.generate(self, cnt=self.DEFAULT_DECK_SIZE))
|
|
|
|
return cards
|
|
|
|
def draw(self) -> Card:
|
|
if len(self._cards) == 0:
|
|
raise StageDeckEmpty()
|
|
return self._cards.pop()
|
|
|
|
@dataclass
|
|
class StageCard(Card):
|
|
# turn w/ numeric modifier (max speed you can take the turn)
|
|
# straight w/ distance modifier (would it scale the accelerate cards?)
|
|
# hazards (jump, gate, narrow, don't cut, over crest, unseen)
|
|
|
|
def __init__(self, name: str, deck: StageDeck):
|
|
super().__init__(name, deck)
|
|
|
|
def can_pass_with(self, player, action: ActionCard) -> bool:
|
|
raise NotImplemented()
|
|
|
|
@classmethod
|
|
def generate(cls, deck: StageDeck, cnt=1):
|
|
weights = [10, 5] # (ref:StageCardWeight)
|
|
cls_list = choices([TurnCard, StraightCard], weights=weights, k=cnt)
|
|
return [real_cls.generate(deck) for real_cls in cls_list]
|
|
|
|
class StageDeckEmpty(BaseException):
|
|
pass
|
|
#+end_src
|
|
=Card= sub-classes are expected to implement =can_pass_with= which take a =PlayerState= and an =ActionCard= to evaluate whether it is safe for a Player to progress through. For example, the =TurnCard= compares the speed, health, etc of the user to ensure they aren't taken a corner too quickly.
|
|
|
|
The shape of the stages being procedurally generated, the names of the =StageCard='s are inspired by actual pace notes; eventually these will have hazard modifiers like "long", "narrow", "over crest" but for now this works out. You end up with some fun things like reading out "flat out right into hairpin left".
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/stage_deck.py
|
|
class CardShape(str, Enum):
|
|
RIGHT = "right"
|
|
LEFT = "left"
|
|
ESSES = "esses"
|
|
|
|
class TurnCard(StageCard):
|
|
shape: CardShape
|
|
modifier: int
|
|
|
|
def __str__(self):
|
|
if self.modifier == 1:
|
|
return f"sharp hairpin {self.shape}"
|
|
|
|
if self.modifier == 2:
|
|
return f"hairpin {self.shape}"
|
|
|
|
if self.modifier == 8:
|
|
return f"fast {self.shape}"
|
|
|
|
if self.modifier == 9:
|
|
return f"flat out {self.shape}"
|
|
|
|
return f"{self.shape}-{self.modifier}"
|
|
|
|
def can_pass_with(self, player, action: ActionCard) -> bool:
|
|
# TKTKTK use certain action cards as pass?
|
|
if int(player._resources.speed) > self.modifier:
|
|
return False
|
|
if player._resources.focus <= 0:
|
|
return False
|
|
if player._resources.tire_health <= 0:
|
|
return False
|
|
if player._resources.car_health <= 0:
|
|
return False
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def generate(cls, deck: StageDeck):
|
|
mod = randint(1,9)
|
|
shape = choice(list(CardShape))
|
|
card = cls("", deck)
|
|
card.modifier=mod
|
|
card.shape=shape
|
|
|
|
return card
|
|
#+end_src
|
|
|
|
Well, the naming mostly works out. For straight cards, if there are multiple back-to-back it can be a little weird. "into and 100" could/should/coalesce or be displayed to the player in a better fashion than it is right now.
|
|
|
|
#+begin_src python :tangle ~/org/group-b/group_b/stage_deck.py
|
|
class StraightCard(StageCard):
|
|
# decameters lol
|
|
modifier: int
|
|
LENGTH_WEIGHTS = [10, 10, 7, 5, 5, 2, 2, 2, 1, 1]
|
|
|
|
def __str__(self):
|
|
if self.modifier == 1:
|
|
return f"into"
|
|
|
|
if self.modifier == 2:
|
|
return f"and"
|
|
|
|
return f"{self.modifier*10}"
|
|
|
|
@classmethod
|
|
def generate(cls, deck: StageDeck):
|
|
mod = randint(1,10)
|
|
shape = choices(range(10), weights=cls.LENGTH_WEIGHTS, k=1)[0] # (ref:StraightCardWeights)
|
|
card = cls("", deck)
|
|
card.modifier=mod
|
|
|
|
return card
|
|
|
|
def can_pass_with(self, player, action):
|
|
return True
|
|
#+end_src
|
|
|
|
* Late Night Notes
|
|
|
|
But maybe it's a single-player deckbuilding roguelite: [[id:20230406T231710.508310][this game Death Roads: Tournament has me thinking about "roguelite deckbuilder" style take on stage Rally Racing]]
|
|
|
|
there's a realism version of this where you're trying to model an actual rally weekend, and there is a gonzo version of this where you're trying to build god's own rally team. prototype is just about getting to the point where I can play with systems and not about the energy of the game.
|
|
|
|
- the goal is to compete in stage rallies, 10-15 levels for each rally with a season-long game-loop progression, and some sort of meta-progression built on top of that
|
|
- meta-progression: upgrading your team, car, training mechanics, moving to a new car class?
|
|
- in-rally-progression: getting more fine-tuned setup of the car, making tire choice and making sure you don't over-extend yourself, deck restructuring, random events between stages and on stages, etc
|
|
- players manage a handful of competing resources like focus, speed, momentum, car health, tire wear to have the shortest time through each stage and survive the rally
|
|
- manage a deck of cards containing actions that they use to transit the stage safely. simple cards like accelerate/break/turn in, specialized cards like cut the apex, flash of insight, etc
|
|
|
|
game loop is basically:
|
|
|
|
you start at time control, your codriver reads out the next 2-3 pace notes and you're off; this can be abstracted by having a stage deck and an action deck; the stage deck can have negative effects mixed in like gravel on the stage, surprise ice, spectator on stage, etc. in easy modes the pace notes would be listed on the side but in harder difficulties you'd have to remember them yourself as you move through the stage. pace notes are similar to the real notes where
|
|
|
|
so basically imagine you are going to high speed through some left-6, right-6, 30 meters straight, so you build up a lot of speed and momentum but you start to drain focus because you are going so fast on a narrow road. now the co-driver reads a "right hairpin" - the way this would work is that you would see "right hairpin" when you are progressing through the right-6 so you would say "oh i have three cards, a speed up card, a late-braking card, and 'do it like takumi' special card that lets me turn with less momentum loss than otherwise normal brake and turn cards would let me" so i would play late braking so that i go through the 30 meter straight without scrubbing too much speed. at this point, i am coming up to the hairpin and my codriver reads "in to left 4" signaling a quick medium-speed left hander, so i then play 'do it like takumi' to get through the hairpin with enough momentum to accelerate in to the left-4 with the proper amount of speed.
|
|
|
|
the part of that deckbuilding game that i linked to in the post above is that your deck's contents is determined by the parts you have attached to the car. in my system that could be as simple as fitting hard vs soft compound tires; conserve softs for later rounds but have cards that cost more focus to use or less grip (is grip a modifier?), fit a different turbocharger to have different speed/momentum tradeoffs, or you could get a whole car upgrade from a mini to an esky to a lancia delta etc that would have different stats and card spread entirely
|
|
|
|
** NEXT how many game systems/resources are reasonable to balance?
|
|
|
|
there's like 5 or 6 in the stage and another couple in the rally progression...
|
|
|
|
** NEXT when do draws happen?
|
|
|
|
** NEXT how do we handle difficulty curve?
|
|
|
|
** NEXT there is a whole motorsport manager that could be built as the meta-progression lol -- is this my racing dynasty simulator too?
|
|
|
|
** NEXT consider bolstering/replacing the focus mechanic with an actual time pressure placed on the player
|
|
|
|
if you only had a few seconds to play each hand the stress of rallying could actually be represented pretty well here, heh. maybe a focus bonus/buff that would come in to play if you are playing against the clock
|
|
|
|
** NEXT consider whether this idea could extend to rally raid events like dakar or baja 1000
|
|
|
|
multiple classes like unsupported motorcycles to trucks
|
|
|
|
maintenance and parts management
|
|
|
|
fuel water etc resources
|
|
|
|
* Original Spec :noexport:
|
|
|
|
Originally Spec'd: [2020-11-11 Wed 21:25]
|
|
|
|
** Some Ideas to throw at the wall
|
|
|
|
- you're managing two resources, attention and speed.
|
|
- Every turn you are accelerating and gain one speed, by default
|
|
|
|
*** turn flow
|
|
- co-driver draws and reads a card, a pace-note
|
|
- co-driver places it face down
|
|
- if there are three cards face down, co-driver reveals the last pace-note
|
|
- driver makes an action
|
|
- resolve
|
|
|
|
*** Actions
|
|
|
|
**** accelerate
|
|
|
|
gain 1 speed, 0 focus
|
|
|
|
**** spot the apex
|
|
|
|
gain 1 focus, 0 speed
|
|
|
|
**** a flash of insight
|
|
|
|
look at the face down cards, lose 1 focus
|
|
|
|
*** resolution
|
|
|
|
- each corner has a cost associated with it, like "left 3" "right 4 narrow"
|
|
- if the car is slower than the corner cost, resolves cleanly
|
|
- if the car is faster than the corner cost, driver can spend 2 focus for 2 speed
|
|
|
|
*** what's going on here
|
|
|
|
this isnt' much of a game. single-player at best. what is the skill? there isn't a strategy here except for memory.
|
|
|
|
try to implement a crappy version of this in python or something.
|
|
|
|
* NEXT [#C] Plan Project tasks
|
|
:PROPERTIES:
|
|
:ID: e335d6d7-41b7-48c0-a3fc-95b534841a77
|
|
:END:
|