Civ3 Show-And-Tell

After thinking about and researching APIs and RESTful APIs I think I see how this app will look from where it nearly is now and into the future as features get added:

In: Civ3 save game file(s)
Out: REST API of various info from the game

Then the UI(s) is (are) fungible.

So for now I have threebits of information for each file:
  • Save game file itself: /sav/<id>/savefile
  • Original filename: /sav/<id>/savefilename
  • svg map: /sav/<id>/map/svg

And as I am able to extract more info from the file I can add a new return value in the API. And when I learn to associate related sav files by same-map and/or same-game I can have /game/<id> and /map/<id> API trees.

One of the best parts about this architecture for today is that I can make it work now with static files and folders and refactor the back end without affecting the front end as I modify how the game data is parsed and stored.

Another random thought: since the SVGs are sourced from the same API, I can now allow themable maps by requesting a different tile definition SVG file.
 
Thank you for your work, Puppeteer :D

Did you upload the "End turn 139 End" save? I was confused at first about the 0x##'s on the map...I thought I accidentally left debugging on, but I see now that if the program doesn't recognized the resource code it writes the resource code out in hex. I guess that is a modded game?

I'm thinking that in the future I'll be able to handle modded games with alternate resources. There's a lot of other work to do first, though.

Speaking of which, I'm really itching to try some new and different things that won't have visible results for a while, but I really wanted to get this web app more usable before I go down myriad rabbit holes or get distracted by real life. So I did.

Trying to make a static site representation of a REST API was becoming a little more than I wanted to do, and I thought of hacky idea to make uploading and having pan&zoom working. Basically the conversion creates a folder with the save file name then uses the names civ3game.sav and civmap.svg under that folder for the files. Then I have a cloned viewer in each folder.

So, here is the new web app. It's not perfect, but it's usable:

http://civ3.bigmoneyjim.com/
Spoiler :


Edit: I also fixed the duplicate name problem. I had already started dividing the maps into by-month folders so the scrolling won't be out of control in a few months, so I used the same technique to prepend the day of month, hour, minutes and seconds to the filename. Since the converter currently runs less often than once a second--and the uploader already avoids upload name collisions--there won't be a same-name-overwrite problem.
 
Holy #%@. I just realized that MongoDB may be able to do some of the data extraction itself. That's kind of what it does, supposedly. Ok, I'm getting excited about the possibilities. Or it may just be that every problem is looking like a nail now that I'm getting a new hammer.

Ok, MongoDB doesn't magically turn binary data into fields on its own. I still need to parse the save file and format the data as a "document".

So where my current Python parser reads the save and outputs SVG, the next flow is that the script will be split in two with one part reading the save and stuffing all data into MongoDB, and the other part picking the data out of the DB and doing things like preventing providing spoiler info.

Edit: This idea should have been very obvious, but I don't think I thought of it until now, and I'll post it here in case I forget it: I can start mostly-identical games with the same seed and settings except one and then compare the saves to find out where each of the game options are stored. (Map settings, difficulty, options, etc..) I can also make minor changes in the same turn and compare saves to find items, such as give 20g to another civ, gift a tech, start a resource or gpt trade, RoP, embargo, trade maps, perform various diplomacy and espionage actions, meet a civ, trade for a tech, create a military alliance, break a trade route, etc.. With a DB backend I'm now interested in extracting any and all info I can, and then I can later decide whether or not it needs to be pulled back out for a map or report or whatnot. I think terrain info is more or less complete, and I don't expect trouble figuring out cities, buildings, citizens and buildings info, but I don't know where the rules are yet. One of the things I'm itching to find now is where the civ colors are. I think there are two colors per civ per game. Maybe I can create identical games except change one civ...that would change a lot of stuff, but it would narrow down where I need to look.

Edit 2: More ideas are coming to me: set a rally point, a continental rally point, automate units (all types, all automation commands). I think part of the reason I didn't have this idea before is that I was only interested in map-display info. Now that I'm putting things in a DB I want all the info I can get. ...Trade a tech or from someone to advance one of us an age. Do things I know affect AI attitude or my reputation like break a deal, raze a city, build a pirate ship (well, guess I can't do that mid-turn), demand tribute, plop a city so that it takes away some of another civ's territory.... This stuff won't go on a map, but if it's in the DB it could be on a report somewhere. Oh, start a GA, start an age of science, initiate WLTK day (another one that can't be done mid-turn). Oh, whip citizens and do other things to make them happy or unhappy.

Edit 3: Also create a situation where I can disconnect an AI's source of a resource and then see if/where it's stored as to what resources the AI can trade. I am guessing that the trade route availabilities are calculated in-memory but that the resource-per-AI is in the save file, although it doesn't necessarily have to be.
 
I've created a couple of MongoDB databases but haven't put anything in them yet. I've been reading up on it and thinking about how I want to store the game data and what I want to do with it. At first MongoDB seemed like a natural fit because it's conceptually a JSON store, and I pretty much started this project thinking of JSON as the intermediate data format. It's also attractive because I don't have to have a rigid predefined schema.

But I'm thinking about how I want to use the data, and I'm realizing that it's mostly relational. The tiles point to unit IDs and city IDs, the units and cities refer to tiles. Civs, units, tiles and such are all repetitions of the same schema. I want summary reports and want to try to associate different saves of the same game for replay and progress analysis. I want to make use of relationships between the data. If I do this in MongoDB I'm coding all the logic by hand. But the queries lend themselves to SQL select statements, and I'm now realizing a lot of the logic I'll need--spoiler info identification and map segmentation, for examples--would fit better in stored procedures in a SQL DB than in app code.

And my schema *is* rigid and predefined. Or it will be. The only reason it changes is because I'll find out more info I can extract from the game file or I'll decide the info is better structured a different way. And the schema changes happen because of code changes, not because of data input.

So yeah, MongoDB is the wrong tool for this app. I'll want a SQL database for this.
 
One of the things I'm itching to find now is where the civ colors are. I think there are two colors per civ per game. Maybe I can create identical games except change one civ...that would change a lot of stuff, but it would narrow down where I need to look.

Civ colors are stored in the BIQ section, which should also be in the SAV file (except possible for default-rules games, in which case it would be in conquests.biq?). In particular, you can see this Apolyton thread, section RACE (post #3), and default color/unique color (which isn't always actually unique). Unfortunately the links in post #1 were broken when Apolyton updated from VBulletin 2 to VBulletin 4, but search in the browser still works.

Edit 2: More ideas are coming to me: set a rally point, a continental rally point, automate units (all types, all automation commands). I think part of the reason I didn't have this idea before is that I was only interested in map-display info. Now that I'm putting things in a DB I want all the info I can get. ...Trade a tech or from someone to advance one of us an age. Do things I know affect AI attitude or my reputation like break a deal, raze a city, build a pirate ship (well, guess I can't do that mid-turn), demand tribute, plop a city so that it takes away some of another civ's territory.... This stuff won't go on a map, but if it's in the DB it could be on a report somewhere. Oh, start a GA, start an age of science, initiate WLTK day (another one that can't be done mid-turn). Oh, whip citizens and do other things to make them happy or unhappy.

Edit 3: Also create a situation where I can disconnect an AI's source of a resource and then see if/where it's stored as to what resources the AI can trade. I am guessing that the trade route availabilities are calculated in-memory but that the resource-per-AI is in the save file, although it doesn't necessarily have to be.

Yeah, there's a lot of things that potentially could be shown beyond what's actually shown in-game. All those things are likely something you could identify somehow. You probably are right that resource-availability is something dynamically calculated in memory (as evidenced by it changing whenever a tile improvement is completed/destroyed), but that too could be figured out even from the SAV with the correct algorithm.

I've created a couple of MongoDB databases but haven't put anything in them yet. I've been reading up on it and thinking about how I want to store the game data and what I want to do with it. At first MongoDB seemed like a natural fit because it's conceptually a JSON store, and I pretty much started this project thinking of JSON as the intermediate data format. It's also attractive because I don't have to have a rigid predefined schema.

But I'm thinking about how I want to use the data, and I'm realizing that it's mostly relational. The tiles point to unit IDs and city IDs, the units and cities refer to tiles. Civs, units, tiles and such are all repetitions of the same schema. I want summary reports and want to try to associate different saves of the same game for replay and progress analysis. I want to make use of relationships between the data. If I do this in MongoDB I'm coding all the logic by hand. But the queries lend themselves to SQL select statements, and I'm now realizing a lot of the logic I'll need--spoiler info identification and map segmentation, for examples--would fit better in stored procedures in a SQL DB than in app code.

And my schema *is* rigid and predefined. Or it will be. The only reason it changes is because I'll find out more info I can extract from the game file or I'll decide the info is better structured a different way. And the schema changes happen because of code changes, not because of data input.

So yeah, MongoDB is the wrong tool for this app. I'll want a SQL database for this.

This makes sense to me. The relationship between different parts of the BIQ is already determined, and fairly relational (said as someone who is not a database expert). Although me editor is in Java, how my classes are structured, from the BIQ format standpoint, could easily be mapped to SQL with foreign-key relationships. I don't think the SAV would be significantly different.
 
I recall seeing the Apolyton discussion with the RACE mention, but I didn't see RACE anywhere in my save file. I'll look over it again.

I've been working on this today. (All year!) I created a "develop" branch in the git repo and tagged the current master head as 0.1 since that is more or less usable and I'm breaking more than I'm adding currently in my current activities.

I'm basically going back to square one, except this time I have lessons learned along the way. I can use that hexdump function to store & print each section in hex dump format, and it's easier to look at it that way than try to manually pick it out from the whole file hex dump. It gives me something to analyze before I know anything about the section. I also have the seek function so I can just skip parts I don't want to deal with.

So I'm hex-dump-collecting the first parts of the file including the first part of the BIC, and I'm also getting CNSL, the part before WRLD/TILE/CONT. And I should probably start this now: Since I have sectional hex dumps and the ability to parse arbitrary files I can start comparing sections between different saves.

I'm getting stuck in the same place, too. I can make nice with parsing the BIC section until I hit that second instance of GAME. I now know that the "sections" and "headers" aren't really that...they are a handy artifact of how the developers used class inheritance and seem to have much of the data in descendants of a class that has a 4-byte string followed by an integer data-length. (I keep thinking I need to change my terminology away from "sections" and "headers", but I haven't settled on that yet.) The BIC classes appear to be slightly different and then radically different starting with the second instance of the string GAME. But I want to get to the rest of the data, so I'm going to take advantage of my tools and skip to CNSL for now so I can get to LEAD, CITY and so forth.

(I am assuming that the second "GAME" is part of the BIC, and that game-specific data starts at CNSL, but that's not necessarily true. I really need to compare against other saves and against conquests.biq.)
 
Grrrrr, LEAD is giving me a problem, too. :twitch:

I think I need to change strategies. I think I should use the Horspool search to burn through saves and log the count of and distance between specific headers or sequences of headers. If they're the same size in all saves then I can quit looking for length and count values.

Hmm, I can also use the strings command and sort and count occurrences of strings between saves. I notice in my 4-civ test save there are 32 LEAD sections, so I'll hazard a guess that there will be 32 of them in all Conquests saves. I should then be able to see which other header counts vary between saves.
 
Ok that's weird. The forum is turning my pasted code below into all-lower-case. Some sort of anti-shouting feature? Ah yes, now that I have some normal sentences up here it will let me paste and preview all-caps below.

Code:
     20      32 CULT
     20      32 LEAD
     21       1 CNSL
     21       1 FAXX
     21       1 HIST
     21       1 PEER
     21       1 RPLS
     21       1 TUTR
     21       3 WRLD
     21      32 PALV
     21      64 ESPN

I ran 21 saves through a couple of scripts of mine (iterate and mystrings) and then compared the 21 resulting files.

In the above table, the first column is how many files matched the results on the right. The second column is how many times the value on the right appears in the file. (It is a uniq -c applied to a number of uniq -c results files.)

One file had 33 CULTs, and another had 36 LEADs. I'm pretty confident I'll find that those aren't an unusual number of data structures, but an occurrence of a matching string elsewhere in the game file. I think the above table is a good indicator of which section counts don't vary; all the others do vary. Now I can start looking at whether or not their sizes vary.

(This script omits everything before CNSL because some of the custom BICs had LEAD and other sections in there that were confusing the counts. The order shown is sorted by col1 then col2 then col3, it's not the order in which they appear.)
 
I really want to get to LEAD and beyond, but I've spent some time looking at the embedded BIQ. I previously thought that CNSL was the demarc between BIC info and the game-specific info, but now I think the second GAME is the demarc.

Based on an error message some modders have with BLDG and CNSL, I thought that CNSL was a parent of BLDG, but now I see that BLDG is in the BIC and doesn't exist in the game save unless there's a custom BIC included. So I guess the modders getting that error are getting a BLDG error reading CNSL (presumably due to a formatting error) and not a CNSL error reading BLDG.

Also, now that I'm comparing games side-by-side including some modded games, I see that all the custom BIC info is before the GAME sections.

I suppose even the first GAME could be the end of the biq, but I see one GAME in the default conquests.biq.
 
Did you upload the "End turn 139 End" save? I was confused at first about the 0x##'s on the map...I thought I accidentally left debugging on, but I see now that if the program doesn't recognized the resource code it writes the resource code out in hex. I guess that is a modded game?

It wasn't me, i am still trying to get civIII to run in linux... :D
 
It wasn't me, i am still trying to get civIII to run in linux... :D

Oh yeah. I had SMAC running in Linux many, many years ago, but I don't think I ever tried to run Civ in Linux, or at least I never did so successfully that I can recall.
 
Well, I've spent a lot of time and don't have much to show for it, but I am reading in and storing the raw data of more of the file than I was before. From the beginning to the end of the CONTs I am storing everything except the second instance of GAME which I still haven't figured out.

There is also some data after CONT and before LEAD that appears to be an array of integers. In most saves it is 26 integers, but it is more in one of the modded saves. What is there 26 of in unmodded games that there might be more of in modded games? I'm guessing tradable resources, but I haven't counted them yet and can't figure out why an integer array of values about them would be of any use.

All my attempts at reading the file involve reading it beginning-to-end, because I am convinced that is how the game writes it and reads it in. It is a serialized dump of data structures, mostly C++ objects. For various reasons I also don't want to seek back and forth, mainly so I can read in from an upload, download or pipe. But with GAME, the integer array and LEAD I don't yet know to determine their length before reading them in, but I do know what is after they end--DATE, LEAD and CULT, respectively--so as a stopgap measure I could find the beginning of the next section and then stuff what's in between into the unknown-length section. This would be easier if I could seek back and forth, but I think I can modify the Horspool search algorithm to capture and return all the data it skips when it's finding a match. So I think that's my next step.

And once I have the unknown-size data structures captured I can better compare them across different saves to figure them out.

But for now I need to get my butt of this computer chair...and set it on the couch to watch footall. :D
 
There is also some data after CONT and before LEAD that appears to be an array of integers. In most saves it is 26 integers, but it is more in one of the modded saves. What is there 26 of in unmodded games that there might be more of in modded games? I'm guessing tradable resources, but I haven't counted them yet and can't figure out why an integer array of values about them would be of any use.

There are 26 resources including bonus (non-tradable) resources in an unmodded game. And in a modded game someone uploaded--an Escape From Zombie Island 2 game--there are 55 integers in the array and it looks like 54 resources in the BIQ. I'm guessing these are the counts of how many of each resource there is on the map. I'll have to verify that later, but there seem to be about the same number of horses and iron that there are civs in the game, and lots of fish and whales on larger maps. I think. And the placement of these values right after the map data feels about right. I'm not sure why they would need to store counts of resources; maybe the AI uses it to asses how strategically valuable/rare a resource is?
 
I am now either reading in or skipping info up to RPLS which appears to be a the start of the replay timeline info which seems to be the end of the file. I'm skipping the non-BIC GAME, LEADs, BINFs and most of the CITY sections. I can read in some cities, but there are a lot of more-or-less empty CITY sections.

I haven't started modifying the Horspool search yet, but I did start looking into Antal1987's GitHub project, and the header files in there may be what I need to complete my puzzle. And I hadn't even thought about airfields, victory points, radar towers or outposts, all of which apparently have their own sections if they exist. I knew of CLNY but didn't think about having to parse it yet. But with what I have and his header files I think I can work out what I need to read everything in. Except replay info....I don't think I saw that in his files yet. And the layout of the data wasn't immediately clear to me last night, but then I was really tired by the time I got to it.
 
I haven't touched Civ3 in quite a while, but today I thought of this project again while looking into d3.js . I have several potential productive uses for d3 and kick its tires every now and then. I was reading a tutorial about it and realized it might fit well with this project.

I've gone back and forth more than once on deciding how to store and display the map data in this thread. d3 is a declarative syntax and should allow me to build the map more easily and dynamically. Maybe.

Also, my web app working again. Unfortunately the way I set it up, every time it reboots I have to go reconfigure something. Maybe I'll fix that, too.
 
I haven't touched Civ3 in quite a while, but today I thought of this project again while looking into d3.js . I have several potential productive uses for d3 and kick its tires every now and then. I was reading a tutorial about it and realized it might fit well with this project.

I've gone back and forth more than once on deciding how to store and display the map data in this thread. d3 is a declarative syntax and should allow me to build the map more easily and dynamically. Maybe.

Ok, d3 is awesome! There's a bit of a learning curve, but it's very much like jQuery, except that with jQuery you select DOM objects to manipulate, but with d3 you select data and create DOM objects based on data.

Seriously, inside the spoiler is the html page and all the d3 code needed to draw the map tiles filled with basic terrain color based on map data in JSON format:

Spoiler :
Code:
<!DOCTYPE HTML>
<html>
<head>
  <title>Civ3 Show-and-Tell JSON/d3/SVG Map</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script>

mapstyle = {
    'baseTerrain': {
        // desert
        0x00: 'cornsilk',
        // plains
        0x01: 'sandybrown',
        // grassland
        0x02: 'green',
        // tundra
        0x03: 'white',
        // coast
        0x0b: 'blue',
        // sea
        0x0c: 'mediumblue',
        // ocean
        0x0d: 'darkblue'
    }
}
// load map json file
d3.json("civmap.json", function(error, data){
    if (error) return console.warn(error);
    else
    // Just making sure I have the data
    console.log(data.tile.length); 
    console.log(data.width); 
    console.log(data.height); 

    // Add svg element and assign to var
    var svg = d3.select('body').append('svg')
       .attr('style', 'background: gray')
       .attr('width', data.width * 64 + 64)
       .attr('height', data.height * 32 + 32);

    var tiles = svg.selectAll('polygon')
        .data(data.tile);

    // Adds tiles to the DOM
    tiles.enter().append('polygon')
       .attr('points', '0,32 64,0 128,32 64,64');

    // Set x transforms for tiles
    tiles.attr('transform', function(d, i) {
       // tile-width * map width, aslo add half-tile offset to the right for odd rows
       x = 128 * (i % (data.width / 2)) + 64 * ( Math.floor(i / (data.width /2)) % 2);
       y = 32 * Math.floor(i / (data.width / 2));
       return 'translate(' + x.toString() + ',' + y.toString() + ')'
    });

    // Assign fill color based on terrain
    tiles.attr('style', function(d, i) {
       myFill = mapstyle.baseTerrain[data.tile[i]['info']['terrain'] & 0x0F] 
       if (myFill)
         return 'fill: ' + myFill
       else {
         console.warn('Unstyled tile terrain ' + data.tile[i].info.terrain.toString())
         return 'fill: red'
       }
    });
});
</script>
</head>
<body>
  <h1>Civ3 Show-and-Tell JSON/d3/SVG Map</h1>
</body>
</html>
 

Attachments

  • d3-jsonmap1.PNG
    d3-jsonmap1.PNG
    59.5 KB · Views: 146
  • d3-jsonmap2.PNG
    d3-jsonmap2.PNG
    108.1 KB · Views: 157
Not much happening of interest, but due to the way I'm doing the map now, it's easy again to re-style the map with an external CSS file. Here's my first attempt at making the base terrain closer in color to in-game.
 

Attachments

  • Screenshot 2015-07-23 at 2.48.38 PM.png
    Screenshot 2015-07-23 at 2.48.38 PM.png
    28.2 KB · Views: 160
I got fog-of-war and rivers completed (screenshot attached) via d3.js.

My current interest and drive is to learn d3. I've looked at it a few times in the past when looking for charting libraries, but this is the first time I've actually done anything with it. It's perfect for this project, and it's really not a charting library, but it's a powerful data-to-DOM library that has a lot of potential use for me aside from this project.

But now that I see I can do this, I see how the architecture of C3SAT will be. I've changed the planned architecture several times in this thread, but this is really how I originally wanted it, more or less:
  • Save file data is converted to JSON - For now I'm using the Python program I developed and am dumping the internal structure to JSON, and that's working fine. But with Antal1987's work last year I may have the info I need to load the data more effectively either in C++ or a library that understands the header files. If I get back around to improving the data import I may look at trying to leverage Antal1987's work.
  • JSON is stored. For now as a file, but soon in MongoDB or similar. - I know I recently-in-post-count swore off of NoSQL for this, but it's a natural fit for JSON data, and d3 will handle any data manipulation. It will even work great for stats tables if I ever get the unit and city data converted to JSON.
  • A simple HTML page loads the Javascript d3 library and CSS style sheet
  • d3 builds the map and the CSS sheet styles it - I like the CSS option because it gives other people the ability to easily alter the look of the map. It also gives me the ability to have multiple style sheets available. d3 takes a bit of learning, but it's amazing how little code is needed to build the map. All I have left to do to catch up to my previous Python-builds-svg effort is to add the overlay terrain and non-spoiler resources, and that should happen pretty quickly (time permitting). And then I can quickly go beyond...perhaps highlighting territory, perhaps mini maps, perhaps mapping to a globe...it's all possible with the data I already can extract from the save.

Edit: In the Python-to-SVG map maker I used similar logic for the flood plains and rivers with flood plains just being a wide band of green by the river. In the d3.js version the rivers are decorated lines, and I can't treat the FPs that way. So I gave them a "floodplain" class and pushed the desert color a bit green. I'll probably keep the class-based filled-polygon flood plains, but maybe we can come up with a better way to fill them. In the attached screenshot there are some flood plains in the NW and the NE.
 

Attachments

  • jsonmap-rivers-fow.PNG
    jsonmap-rivers-fow.PNG
    77.6 KB · Views: 159
Looks fairly good, particularly with the more Civ3-like colors. I'm mildly interested in d3 myself, as having worked with client-side charting at my previous job, I occasionally came across situations where it would have been nice to have d3's more powerful options. It never got to the point that we actually used d3 in my area, but every 2-3 months there would be a demo or proof of concept with it in another area.

MongoDB is an area where I have learned a lot in the past few months, much more than previously. I haven't really thought about it much in relation to Civ3 save/scenarios (though a bit in relation to how I might store info about the downloads database), but I'll be curious how it works for you in practice here. One of the aspects I like about it is that it encourages structuring the data to be conducive to the most common access patterns, thus giving good performance in the common use cases. I don't know if that would make much difference here, but I originally started learning about it because it looked like it would be a good fit for reporting on data more efficiently than in SQL at my previous position.

At any rate, I'm glad to see the project lives on. This definitely would've been cool to have back when I was in S&T a lot... and come to think of it, now that I'm doing S&T a bit again, I might be able to make use of it for that now as well.

Edit: Does the old one with file-uploading still work? I uploaded a file (via http://civ3.bigmoneyjim.com/), but I don't see a July 2015 folder nor the file (Quintillus of the Intelligent, 1745 AD.sav). Maybe it got tripped up by the lack of a 2015-07 folder? Figured I'd upload it and see how it'd work for linking to in my story, even if it's the old version currently.
 
Ah, the file uploaded, but there was yet another Docker container I needed to have running. It's running now, and the map is there there: http://civ3.bigmoneyjim.com/hack-a-...5-Quintillus of the Intelligent, 1745 AD.SAV/ . "July 2015" still isn't on the front page...I'll thought I had that automatically updating, but clearly not.

I'm really liking d3 so far, and I'm pretty sure I still have tons to learn about it. Right now it's keeping me from imperative looping, but I think if I were better I could bind the data in such a way that the code could look even simpler. For example, in almost every creation collection I create elements for every tile and then go back and remove the elements that are either spoiler or empty. I'm pretty sure there's a binding or filter where I could only create the elements I need and not have to undo anything. But what I have beats the heck out of the imperative SVG building I was doing in Python.

For this project I think Mongo will just be a convenient data store. I expect to pull the entire document every time and then have d3 selectively display it. But I suppose depending on the situation I might fetch only map-related data or only histogram-related data, for example. And in this case the JSON/BSON itself is a selection of interesting data from the save file. Maybe I'll get some time to mess with it this weekend.

Oh, I have the JSON/d3 maps almost to the point the old Python-generated SVG maps are. I recolored the hills and marshes, and I'm using Unicode codepoints for cows, elephants and a few other things with different results in different browsers, but I'm close to "even" with the previous progress.
 

Attachments

  • Screenshot 2015-07-24 at 6.25.02 PM.png
    Screenshot 2015-07-24 at 6.25.02 PM.png
    84.4 KB · Views: 146
  • Screenshot 2015-07-24 at 6.26.26 PM.png
    Screenshot 2015-07-24 at 6.26.26 PM.png
    236.6 KB · Views: 138
  • Screenshot 2015-07-24 at 6.28.40 PM.png
    Screenshot 2015-07-24 at 6.28.40 PM.png
    280.3 KB · Views: 148
Top Bottom