Skip to content

Commit

Permalink
Release includes:
Browse files Browse the repository at this point in the history
        Add games inc 2048
        Compiler error exception
        Fix action on negated object; tests
        Fix compile message
        Fix expand rules for actions; rule translate tests
        Unity: scale sprite/text
        Fix UI to show bg for board
        Fix expansion of OR objects in action
        Fix expansion of ambiguous directions in action
        Fix rule after loop is skipped
        Fix Random object should create just one
        Fix Empty pattern start should match everything
        Use equality for comparing atom objects
        Use OrdinalCase for object symbol table
        Just one check for net change in level or movers
        Refactor and find all matches before actioning them
        Fix random prefix to pick one match not one rule
        Update readme
  • Loading branch information
david committed Apr 30, 2018
1 parent ce9c107 commit c5b8b6b
Show file tree
Hide file tree
Showing 49 changed files with 4,779 additions and 1,142 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -248,3 +248,5 @@ ModelManifest.xml
*.dll.meta
/UnityPlayer/Library
/UnityPlayer/Temp
/UnityPlayer/TestPuzzles/Buggy
/UnityPlayer/TestPuzzles/jsonconvert
2 changes: 1 addition & 1 deletion Pegasus.Common/Pegasus.Common.csproj
Expand Up @@ -38,7 +38,7 @@
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
Expand Down
31 changes: 23 additions & 8 deletions PuzzLangLib/Compiler.cs
Expand Up @@ -18,11 +18,20 @@

namespace PuzzLangLib {
public class Compiler {
public TextWriter Out { get; private set; }
// source name to display in messages
public string SourceName { get; private set; }
public bool Success { get { return _parser.ErrorCount == 0; } }
public GameModel Model { get; private set; }
// settings to use for compile and model
public IList<Pair<string, string>> Settings = null;
// output produced during compile
public TextWriter Out { get; private set; }
// result of compile, if successful
public GameModel Model { get; private set; }
// result of compile
public bool Success { get { return _parser.ErrorCount == 0; } }
// explanation, if compile failed
public string Message { get; private set; }
// for testing purposes only
public IList<string> RuleExpansions { get { return _parser.AtomicRules.Select(r=>r.ToString()).ToList(); } }

internal ParseManager _parser;
bool _parseerror = false;
Expand Down Expand Up @@ -52,7 +61,7 @@ public class Compiler {
for (int z = 1; z <= level.Depth; z++) {
var obj = level[x, y, z];
if (obj != 0) {
objs.Add(_parser.SymbolName(obj).ToLower());
objs.Add(_parser.GetSymbolName(obj).ToLower());
}
}
objs.Sort();
Expand Down Expand Up @@ -80,15 +89,21 @@ public class Compiler {
if (e.Data.Contains("cursor")) {
var state = e.Data["cursor"] as Cursor;
var offset = Math.Max(0, state.Location - 30);
var source = program.Substring(offset, state.Location - offset)
+ "(^)" + program.Substring(state.Location);
Out.WriteLine(source.Replace("\r\n", ";").Replace("\n", ";").Shorten(78));
Out.WriteLine($"*** '{SourceName}' at {state.Line},{state.Column}: parse error: {e.Message}");
var source = (program.Substring(offset, state.Location - offset)
+ "(^)" + program.Substring(state.Location))
.Replace("\r", "").Replace("\n", ";");
Message = "{0}\n*** '{1}' at {2},{3}: parse error: {4}".Fmt(source.Shorten(78),
SourceName, state.Line, state.Column, e.Message);
Out.WriteLine(Message);
_parseerror = true;
} else {
Out.WriteLine($"*** '{SourceName}': unexpected exception: {e.ToString()}");
_parseerror = true;
}
} catch (DOLEException e) {
Message = e.Message;
Out.WriteLine(Message);
_parseerror = true;
}
if (_parseerror)
++_parser.ErrorCount;
Expand Down
10 changes: 10 additions & 0 deletions PuzzLangLib/DOLE/Extensions.cs
Expand Up @@ -57,6 +57,16 @@ public static class UtilExtensions {
return arg.Substring(Max(0, arg.Length - count));
}

public static string After(this string arg, string delim) {
var pos = arg.IndexOf(delim);
return (pos == -1) ? arg : arg.Substring(pos + 1);
}

public static string Before(this string arg, string delim) {
var pos = arg.IndexOf(delim);
return (pos == -1) ? "" : arg.Substring(0, pos);
}

// return simple split with trim, excluding empty parts
public static IList<string> SplitTrim(this string target, string delim = ",") {
var parts = target
Expand Down
4 changes: 3 additions & 1 deletion PuzzLangLib/DOLE/Logger.cs
Expand Up @@ -54,6 +54,8 @@ public static class Logger {
static DateTime _timenow = DateTime.Now;

public static void Open(int level) {
Console.OutputEncoding = System.Text.Encoding.UTF8;
//Console.OutputEncoding = System.Text.Encoding.Unicode;
Open(level, Console.Out);
}
public static void Open(int level, string name) {
Expand Down Expand Up @@ -154,7 +156,7 @@ public class AssertException : Exception {

static string Pad(int level, string msg) {
bool timeit = msg.StartsWith(">");
bool padit = msg.Length > 0 && Char.IsUpper(msg[0]);
bool padit = msg.Length > 0 && (Char.IsUpper(msg[0]) || msg[0] == '['); // indent entry and exit

string padding = (timeit) ? String.Format(@"{0:mm\:ss\.fff} {1,3:N0}ms : ",
_timenow - _firsttime, (_timenow - _lasttime).TotalMilliseconds)
Expand Down
25 changes: 16 additions & 9 deletions PuzzLangLib/GameDef.cs
Expand Up @@ -48,29 +48,32 @@ public enum OptionSetting {

// used in win conditions and rule object matching
internal enum MatchOperator {
None, No, Some, All,
None,
No, Some, All,
Any, // alias for Some
}

// used in defining mask and pile objects
internal enum LogicOperator {
None, And, Or, Not,
None,
And, Or, Not,
}

// used in defining a symbol (just objects for now)
internal enum SymbolKind {
None,
Alias, // real object with sprite
Pile, // set of objects occupying single space, for level definition
Mask, // set of objects sharing common property, for rule testing
Mixed, // set of objects of mixed parentage -- usually an error
Text, // text string
Real, // real object with sprite
Aggregate, // set of objects occupying single space, for level definition
Property, // set of objects sharing common property, for rule testing
Mixed, // set of objects of mixed parentage -- usually an error
Text, // text string
}

// directions used in rules and sound triggers
internal enum Direction {
None,
// absolute directions, use for rules, patterns and actions
None, Up, Down, Left, Right, // basic directions
Up, Down, Left, Right, // basic directions
Horizontal, Vertical, Orthogonal, // combos of the above
// note: Orthogonal must be last in above group
// absolute directions, patterns and actions only
Expand Down Expand Up @@ -132,7 +135,7 @@ public class PuzzleObject {
public string Name;
public int Layer; // layer on which to display
public Pair<float,float> Pivot; // fixed point relative to cell
public double Height; // size of image (1.0 = size of cell)
public float Scale; // size of image (1.0 = size of cell)
public int Width; // sprite width in pixels
public IList<int> Sprite; // list of colours for each pixel
public string Text; // string to display over sprite
Expand Down Expand Up @@ -288,6 +291,10 @@ public class GameDef {
return (obj == 0) ? "(--)" : PuzzleObjects[obj - 1].Name;
}

public string ShowName(int obj) {
return (obj == 0) ? "(0)" : "{0} ({1})".Fmt(GetName(obj), obj);
}

//--- sounds
public IList<string> GetSounds() {
return ObjectSounds.Select(o => o.Seed)
Expand Down
6 changes: 2 additions & 4 deletions PuzzLangLib/GameModel.cs
Expand Up @@ -245,14 +245,12 @@ public class GameModel {
// show all objects in level using sorted integers
void ShowLevel(string message, Level level) {
Logger.WriteLine(0, "At {0} level: '{0}'", message, level.Name);
Func<int, char> tochar = n => (char)((n == 0) ? '.' : (n <= 9) ? '0' + n : 'A' + n - 10);

var objs = Enumerable.Range(0, level.Length)
.Select(x => level
.GetObjects(x)
.OrderBy(o => o)
.Select(o => tochar(o))
.Join(""))
.Select(o => o.ToString())
.Join())
.ToList();
var wid = objs.Max(o => o.Length) + 1;
var text = objs.Select(o => o.PadRight(wid - 1)).Join("|");
Expand Down
52 changes: 24 additions & 28 deletions PuzzLangLib/GameState.cs
Expand Up @@ -140,7 +140,7 @@ class GameState {
if (_gamedef.Clickables.Contains(obj)) {
var locator = Locator.Create(cellindex, _gamedef.GetLayer(obj));
_movers.Add(Mover.Create(obj, locator, _input));
Logger.WriteLine(2, "Mover add {0} to {1} at {2}", _input, _gamedef.GetName(obj), locator);
Logger.WriteLine(2, "Mover click {0} to {1} at {2}", _input, _gamedef.ShowName(obj), locator);
}
}
}
Expand All @@ -153,7 +153,7 @@ class GameState {
foreach (var player in _gamedef.Players)
foreach (var locator in FindObject(player)) {
_movers.Add(Mover.Create(player, locator, _input));
Logger.WriteLine(2, "Mover add {0} to {1} at {2}", _input, _gamedef.GetName(player), locator);
Logger.WriteLine(2, "Mover input {0} to {1} at {2}", _input, _gamedef.ShowName(player), locator);
}
}

Expand Down Expand Up @@ -240,36 +240,33 @@ class GameState {
Logger.WriteLine(4, "ApplyRuleGroups {0}", groups.Count);

// work through each rule group or loop group in source code order
for (var groupno = 0; groupno < groups.Count; ++groupno) {
for (var groupno = 0; groupno < groups.Count; ) {
var group = groups[groupno];
var loop = group.Loop; // id for loop, 0 if not

// if not in a group loop just do it
if (loop == 0) {
if (ApplyRuleGroup(group))
_rulestate.HasChanged = true;
++groupno;

// if in a group loop, repeat the whole set until no change
} else {
Logger.WriteLine(3, "Start loop {0}", loop);
var loopchange = false;
var loopstart = groupno;
int gno;
for (var retry = 0; ; ++retry) {
if (retry > 100) throw Error.Fatal("too many rule loops: <{0}>", group);
Logger.WriteLine(3, "Start loop {0} retry={1}", loop, retry);

// do all the rule groups in the loop block
var groupchange = false;
var nextloop = loop;
for (groupno = loopstart; nextloop == loop;) {
group = groups[groupno];
if (ApplyRuleGroup(group))
for (gno = groupno; gno < groups.Count && groups[gno].Loop == group.Loop; ++gno) {
if (ApplyRuleGroup(groups[gno]))
groupchange = true;
nextloop = (++groupno < groups.Count) ? groups[groupno].Loop : 0;
}
loopchange |= groupchange;
if (!groupchange) break;
_rulestate.HasChanged = true;
}
_rulestate.HasChanged |= loopchange;
groupno = gno;
}
}
Logger.WriteLine(4, "[ARGS {0}]", _rulestate.HasChanged);
Expand All @@ -283,25 +280,24 @@ class GameState {
// skip this group -- it failed rigid previously
if (_disabledgrouplookup.Contains(group)) return false;

// rule group is needed to track rigid
_rulestate.RuleGroup = group;
var groupchange = false;

// Apply a single rule chosen at random
// TODO: pick from available matches
if (group.IsRandom) {
var n = _model.Rng.Next(group.Rules.Count);
groupchange = _rulestate.Apply(group.Rules[n]);
// Apply a single match chosen at random
if (group.IsRandom) {
var matches = _rulestate.FindMatches(group);
if (matches.Count > 0) {
var n = _model.Rng.Next(matches.Count);
_rulestate.DoActions(group, new List<RuleMatch> { matches[n] });
groupchange = true;
}

// Apply all the rules in the group
// Find all matches and do all actions for all the rules in the group
// loop until no more changes, return true if there were any
} else {
for (var retry = 0; ; ++retry) {
if (retry > 200) throw Error.Fatal("too many rule group retries: <{0}>", group);
var rulechange = false;
foreach (var rule in group.Rules)
if (_rulestate.Apply(rule))
rulechange = true;
var matches = _rulestate.FindMatches(group);
var rulechange = _rulestate.DoActions(group, matches);
groupchange |= rulechange;
if (!rulechange) break;
}
Expand All @@ -325,7 +321,7 @@ class GameState {
var dups = _movers.Where(m => Destination(m).Equals(newloc));
foreach (var dup in dups.Skip(1).ToArray()) { // avoid modifying collection
if (NoFailRigid(mover)) return;
_model.VerboseLog("Blocked duplicate move {0} to {1}", _gamedef.GetName(mover.ObjectId), mover.Locator.Index);
_model.VerboseLog("Blocked duplicate move {0} to {1}", _gamedef.ShowName(mover.ObjectId), mover.Locator.Index);
_movers.Remove(dup);
}
}
Expand All @@ -344,13 +340,13 @@ class GameState {
foreach (var mover in blocked) {
_level[mover.Locator] = mover.ObjectId;
if (NoFailRigid(mover)) return;
Logger.WriteLine(3, "Blocked move {0} to {1}", _gamedef.GetName(mover.ObjectId), mover.Locator.Index);
Logger.WriteLine(3, "Blocked move {0} to {1}", _gamedef.ShowName(mover.ObjectId), mover.Locator.Index);
_movers.Remove(mover);
}
}
// 4.Insert movers into level at new location, count it
if (_movers.Count > 0)
_model.VerboseLog("Make moves: {0}", _movers.Select(m=>_gamedef.GetName(m.ObjectId)).Join());
_model.VerboseLog("Make moves: {0}", _movers.Select(m=>_gamedef.ShowName(m.ObjectId)).Join());
MoveCounter += _movers.Count;
foreach (var mover in _movers) {
_level[Destination(mover)] = mover.ObjectId;
Expand Down
2 changes: 1 addition & 1 deletion PuzzLangLib/Generator.cs
Expand Up @@ -58,7 +58,7 @@ class RuleCode {
}

public override string ToString() {
return "{{{0}}}".Fmt(Code.Count);
return "#{0}".Fmt(Code.Count);
}
}

Expand Down

0 comments on commit c5b8b6b

Please sign in to comment.