Who is Bioviva and what did I do here?
An excellent question! but if you've read the website's name, I'm sure you can guess. Bioviva is a game publisher and editor, who specialises in educational and environmentally friendly board games. for the sake of the word count here is their description from their press document:
Forte de 27 ans d’engagements et de cohérence, Bioviva s’efforce d’apporter le meilleur rapport qualité-prix-éthique dans l’univers du jeu-jouet avec ses jeux fabriqués en France, produits de manière responsable et dont les contenus ludiques et éducatifs sensibilisent petits et grands à l'importance de préserver notre belle planète.
I got it translated by my good friend DeepL so you do not have to:
Backed by 27 years of commitment and consistency, Bioviva strives to offer the best value for money and ethics in the world of toys, with games made in France, produced responsibly and with fun and educational content to raise awareness among young and old of the importance of preserving our beautiful planet.
Bioviva is based in Montpellier, France!
exactly here:
It was a bit annoying getting the plane every morning from Dublin, and also kind of against the point of the company, so I moved to Montpellier just so I could work here! (definitely not cause my girlfriend lives there)
What do I do at Bioviva I don't hear you ask? I'm doing an internship there! Making coffee for people, taking notes, shining Igor's shoes, using the printer, mopping the floor and sitting in the back of meetings twiddling my thumbs. Sarcasm aside, my internship specialises in Game Design. I've been creating, refining, testing and designing many different games and ideas throughout my 20 weeks of working here. I was fortunate to have creative freedom in my projects, due to the fact that Igor Davin, my industrial supervisor, treated me more like a colleague than an intern, which allowed me to make mistakes and learn from them, he was great at bouncing ideas on and taught me a great deal when it comes to the design and development process of a game.
Seeing as I have to write a certain amount of words, let's go into detail on what my objectives and goals were in this internship. To no one's surprise, my goal was to make money design and develop a game that would be commercially released. In the end, I did exactly that! but I also learned many things on the side that I didn't expect such as narrative design, printing processes, different coffee types, the impact games can have around the world, new software, playtesting, M&M's are made with bugs, iterative design, prototype creation and much much more.
Here is the list of names of the projects that I had an impact on while working here :) In order of importance of course
Gamme Protect (I made the little stand :) )
Dino Picnic (translated some things)
A la folie (translated some things)
Project Cata (I started working on it at the end of my internship)
Faites vos Oeufs
Defis Nature Escape: Aventures Polaires
I could only get 2 images as none of the other projects have released so I don't have the finished product images.
PICTURE TIME WOOOOOO
At the start of each week (Monday) we would have a team meeting! At this weekly festive, each project would be reviewed and the team would be updated on its progress, this was cool as we got to see what each other did and if they were actually working. On top of this, every last Thursday of the month we had to do the same thing but for the entire office, not just the dev team, what was that? Who's in the dev team?! [enter epic superhero into music]
Now who did I work with on these projects?! I'll tell you! I was part of the " Pôle Dev " which according to DeepL translates to "Dev Division", an epic translation to be honest. Here is the team:
Michaël : Directeur du développement et de la production
Igor : Chargé de projet développement / game design
Raphaël : Chargé de projet développement enfance et environnement
Honorine : Chargée de projet / rédactrice
Adeline : Chargée de projets / rédactrice
Corinne : Infographiste
Andrea : Infographiste
Margaux : Infographiste
Emeline : Responsable de projets Gamme éducative
which again according to DeepL translated to:
Michaël: Development and Production Director
Igor: Development / game design project manager
Raphaël: Child development and environment project manager
Honorine: Project manager/editor
Adeline: Project manager / writer
Corinne: Graphic designer
Andrea: Graphic designer
Margaux: Graphic designer
Emeline: Project manager Educational range
It was almost there, I mean I wouldn't say Raphaël (Raph for short cause I can't do the weird e without ctrl C + V) is doing child Development. It's meant to say he is the project manager for children's games and environmental games, the rest are accurate enough tho, but as with any small team, people tend to do more than what their labels say they do.
Me (handsome lad in the front left) and the crew
So that is my overview of what is Bioviva, what my role was in the company and who I worked with! time to go into my Work Undertaken! [insert epic transition]
Let us get to the nitty gritty of what I did while I was there! and where better to start than on the most exciting and fulfilling projects! Translation! Yeah they took full advantage of the fact that I am bilingual and put me on translation duties (I'm looking at you Raph!)
It was, in reality, good practice as it showed me nice and efficient ways to write rule books, and made me think of how to name elements, as some elements had to be referenced more than once throughout the booklet. so here are the translated booklets (not the most up-to-date versions as I wasn't bothered couldn't find them)
The Dino picnic is not fully translated due to the fact that these booklets were meant for our clients (the shops not individual people) so they cared more about how to play and not the educational facts about the dinos :(
As of writing this (yes I'm still working as I write this), I got another translation job! hilariously I originally thought was a project on gothic history, but just ended up being about a project called GoTique, a game about ticks lol. I translated all the cards that were going to be in the game.
It's low res on purpose as its info is about a non-released project, but I translated each line, which took longer than expected due to the number of technical terms I didn't know even existed in French, or English as a matter of fact.
Other than these transition jobs, I did some minor checks and translations for Raph, which was usually a pleasant break from my usual workflow.
Let's talk about " Gamme Protect " and what I did for it! first off what is it? It's a game! a board if you can believe it! it's a new branch of the Defis Nature line (Nature challenge for those who speak English). the twist in this new game is that when you win a round, you get to protect your animal, the player who protects the most animals wins! That is of course simplified, I'm hardly gonna write out the 4 pages of rules, so please don't contact me if you wish to know all the rules, go buy it.
But what did I do for it?!?! Because it's not Igors' game project, It's Honorine and Emiline's baby, well put on your seatbelt, sit back, and relax as I delve into this rich and long story!
One Thursday morning, while the fog lifted from town, the mildew dripped from the windows and the coffee dripped into the pot, I, Jamie, started my morning like most others, with a tired drowsy face, blasting metal music from my HyperX cloud II headphones (not sponsored). as I sat down at a desk with a broken chair, I was going to start my daily doodles. when someone, suddenly, without warning, asked me for my opinion, a rare event for an intern! so after spilling my coffee everywhere and mopping it up, they asked me to see if I could come up with a way to make the card stand for the game! A riveting assignment!
Jokes aside, I was working on some Faites vos Oeufs (FOV) prototypes, and was trying to make a 3D element for that, and seeing as I was already cutting out and testing stands, it didn't cost me much to prototype for that project too. I presented a couple of ideas to Honorine and Emiline, and in the end, they decided that the following design was the best option for the game:
Brings tears to my eyes every time I see this, the amount of blood sweat and tears I poured into it is unbelievable (papercuts, I don't have a high pain threshold)
As I explain later, (foreshadowing) we had to make dozens of prototypes for our upcoming games, so here are some pictures of our cutting-out station, and me in this not-staged scenario cutting out elements for this game.
I also held a mini playtest session for this game with a small group of people, I took notes and tested the first version of the instruction booklet, it changed a lot since here is the email sent to Honorine reporting back after the test:
Bonjour Honorine !
J'espère que tout va bien!
Le playtest d'hier s'est super bien passé ! j'ai laissé le questionnaire sur ton bureau, j'espère qu'il te aidera.
Le principal problème était le livre de règles, dont vous étiez déjà au courant, mais assurez-vous de préciser quand le jeu se termine, car ils n'ont pas pu le trouver dans le livre et ont supposé que le jeu se terminait lorsqu'ils n'avaient plus de cartes.
En dehors de cela, ils n'ont eu aucun problème à jouer à partir du livre de règles.
Les cartes spéciales allaient bien jusqu'à ce qu'ils jouent celles qui permettent d'affecter les autres joueurs (prendre la carte d'un autre joueur et la placer où l'on veut). Ils ont payé la carte comme n'importe quelle autre carte de protection, l'ont révélée à la fin, puis se sont rendu compte qu'elle aurait dû prendre effet avant.
Ce que je suggère, c'est une étiquette claire sur la carte qui indique quand la carte doit être jouée, par exemple :
jouer pendant la phase 1
jouer en phase 2
où la phase 1 est avant que les joueurs ne se mettent d'accord pour commencer à placer les cartes, de cette façon ils peuvent placer les cartes spéciales et jouer leurs effets immédiatement.
et la phase 2 est celle où les joueurs commencent à placer les cartes, ce qui apporte un peu de calme avant la tempête.
A part cela, un autre problème était que certains joueurs ne voulaient pas être les premiers à placer des cartes, mais ils ont commencé le compte à rebours quand les gens ont cessé de placer des cartes, ce qui signifie qu'à la fin, ils ont dû prendre une décision rapide,
Il est possible que l'ajout d'une incitation à jouer en premier aide à résoudre ce problème, mais je n'ai pas de bonnes idées à ce sujet.
Mais après avoir terminé leur premier tour, ils étaient heureux de recommencer, et à la fin du jeux , ils étaient prêts à re-jouer!
Je me suis concentré sur certains des problèmes mentionnés dans cet e-mail, mais ils n'ont duré qu'une petite partie du jeu, ils ont eu beaucoup de plaisir à jouer et voulaient rejouer à la fin, à part les règles que j'ai mentionnées, tout était clair, et ils auraient pu avoir un jeu amusant même sans la clarification des règles.
Il ne s'agit que de suggestions, et il est urgent de les modifier maintenant. Revenez sur cet email après d'autres playtests pour voir si les résultats s'alignent, car il s'agit d'un petit groupe de personnes.
kind regards
Jamie Clarke
Hello Honorine!
I hope everything's going well!
Yesterday's playtest went really well! I've left the questionnaire on your desk, I hope it helps.
The main problem was the rule book, which you already knew about, but make sure you specify when the game ends, as they couldn't find it in the book and assumed the game ended when they ran out of cards.
Other than that, they had no problem playing from the rulebook.
The special cards were fine until they played the ones that allow you to affect other players (take another player's card and place it wherever you want). They played the card like any other protection card, revealed it at the end, and then realised it should have taken effect before.
What I suggest is a clear label on the card indicating when the card should be played, for example:
play during phase 1
play in phase 2
where phase 1 is before the players agree to start placing cards, so they can place special cards and play their effects immediately.
and phase 2 is when the players start placing cards, which provides a bit of calm before the storm.
Apart from that, another problem was that some players didn't want to be the first to place cards, but they started the countdown when people stopped placing cards, which meant that in the end, they had to make a quick decision,
It's possible that adding an incentive to play first would help solve this problem, but I don't have any good ideas about it.
But after finishing their first round, they were happy to start again, and by the end of the game, they were ready to play again!
I concentrated on some of the problems mentioned in this e-mail, but they only lasted for a small part of the game, they had a lot of fun playing and wanted to play again at the end, apart from the rules I mentioned, everything was clear, and they could have had a fun game even without the clarification of the rules.
These are just suggestions, and they urgently need to be changed now. Come back to this email after more playtests to see if the results line up, as this is a small group of people.
kind regards
Jamie Clarke
I had fun interacting with this project, and although I didn't do much with it I can see how even I, an underpaid intern, had some lasting effect on the games development.
Next up on my lengthy list of accomplishments, we have Project Cata! It is a nice project which is still (as of writing this) in its very early stages. It's a project with the theme of catacombs, and it was a brand new project! that meant that we needed to come up with an idea for the game, so for a good month I worked through some ideas, Raph and I play-tested some games and bounced ideas off of one another. My main issue with going forward with this project was getting prototypes printed, For every idea I came up with I would usually try to refine it on paper, but this led to variants and changes that in the end were worth nothing as I never tested it. So in January, I decided to take all my ideas and boil them down to the main gameplay loop, what the player was gonna do at every turn, and try to make sure that that was fun. I came up with a simple game that was easy to play and barebones, but what I thought was fun. I added a goal: Escape, an obstacle: The Darkness, and built a little prototype where the player would try to find the exit, then the key, all whilst trying to avoid the darkness and work together to escape, simple but fun! below are all my notes on the game, with ideas, variations, mechanics and goals. But the final prototype that was presented to the big boss man was different to all these notes.
And here is a picture of the prototype that was presented to the big boss during my last week of internship:
One pile of cards, one pile of tokens, one die and tiles in a grid-like pattern, simple and easy to read.
The project got greenlit to be presented to the clients, if they have the budget the project will go ahead and Ill be able to say I'm the Author of a game! pretty cool if you ask me!
Faites vos Oeufs (FOV for short as I'm not typing that every time) is a game that teaches kids how to gamble! or more accurately it's a game where you play as birds competing for resources to build your nest, you try to bluff and outplay your opponents by placing values on the resources, and at the end of a round, the values are counted and the player with the highest value on a resource wins the resource! The game is made by Ludovic Lepine, a game designer from Paris, who pitched the game to Igor in 2023, the game was handed to us to be edited, developed and released.
This was actually really cool (unironically) and interesting to see, as I got to learn how it worked as a publisher to take on someone's project. I was present in talks about his contract, which was a great insight as in the future I would like to come back to Bioviva as a potential author, and how much cost was involved in producing and developing a game. I learned how little of the final price actually goes back into Bioviva's pockets, which ain't much.
Other than that I didn't do much else, except for the following:
Create a unity version to quickly test metas and stress test strategies.
Help balance and simplify the mechanics.
Go to a school to playtest and gather feedback on the game.
Physically create and design parts of the game.
Pitch a new idea to make the game look more interesting and help it stand out in a shop.
oh, what is it that I still don't hear cause you reading this in silence? This is a report so I need to explain what I did and offer proof? fffffffiiiiiiiiinnnnnnnnneeeeeeeee, 'll put in some effort for you <3
the second I thought of the idea I knew I had to do it, as part of my degree as a Game Designer is working with game engines, mostly with Unity :) what is this revolutionary great idea I had, well it was to put the board game... into... a... video game!!!!! that's right, how original! But why did I do this?!!? simply so we could easily test to see if there were winning strategies that we couldn't test due to one game lasting around 15 mins, with this we would be able to test hundreds of games in 15 mins. we could test if betting only on high-value tiles was a game-breaking strategy, or if avoiding social interaction would make the game chaotic. we could do whatever we wanted, test it hundreds of times, and see the result!
So after getting Igor's blessing, I started working on it! I implemented stunning graphics with ray tracing capabilities, complex AI built with machine learning algorithms, online support for millions of players, and.... nah I'm joking, I wasn't getting paid enough for that, I made a simulation that was easy to run and quick, the only graphics was a counter, showing how many games had been played. here is a video of what this looked like (it's my website I can do what I want):
So how does it work :) honestly, I couldn't tell you, but thankfully I wrote comments on all my lines of code :) so here are all my scripts for your viewing pleasure:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
using Random = UnityEngine.Random;
public class GameManager : MonoBehaviour
{
[Header("step by step means that \nthe game is played one step at a time \n"+
"using the space bar")]
public bool stepByStep;
[Header("random games means that \nthe game is played with a random amount of people \n"+
"with different types of people")]
public bool RandomGames;
[Header("**FILL IN AT EACH PLAYTEST** \nthe ID for the playtest session")]
public int playtestSession = 0;
[Header("the parameters changed and player settings")]
public string descriptionOfGame = null;
#region Tiles
[Header("Tiles\nyou can state the amount of each tile\n" +
"there will be in the deck")]
public Tile grass;
public int numberOfGrass;
public Tile woodChips;
public int numberOfWoodChips;
public Tile hay;
public int numberOfHay;
public Tile soil;
public int numberOfSoil;
public Tile feather;
public int numberOfFeathers;
public Tile twigs;
public int numberOfTwigs;
#endregion
[Header("the objectives cards put into a list")]
public List<Objectives> objectives;
[Header("Game settings \nthe amount of tiles dealt in a round")]
public int dealAmount = 5;
public Vector2 minMaxPeople = new Vector2(0,6);
[Header("if true then the player who places a \ncuckoo will receive nothing. \n" +
"The player who the cuckoo egg affects\nwill receive the cuckoo \n" +
"egg into their nest (0) and loose a \nrandom egg they had bet on the tile")]
public bool newCuckooEggRules = false;
[FormerlySerializedAs("evemNewerCuckooEggRules")]
[Header("if true then the player who places a \ncuckoo will receive nothing. \n" +
"The player who the cuckoo egg affects\nwill receive the cuckoo \n" +
"egg into their nest (0) and loose a \nrandom egg they had bet on the tile")]
public bool evenNewerCuckooEggRules = false;
[Header("if true then if a tile has bets on it \nand is a draw, then the tile will\n" +
"stay on the table (dealtCards[]) and \nbe used in the next round.\n" +
"Note: if a tile has no bets it will be \ndiscarded into usedTiles[]")]
public bool ifCardIsDrawThenLeaveIt = false;
[Header("**THE FOLLOWING IS FOR VIEWING ONLY**\nthe deck is the tiles that \n" +
"can be used and dealt"), SerializeField]
private List<Tile> deck = new();
[Header("vector3 list of the (player, tile, egg value)"),SerializeField]
private List<Vector3> bets;
[Header("the current Tiles that are dealt on the \"table\"")]
public List<Tile> dealtTiles;
[FormerlySerializedAs("usedCards")] [Header("the tiles that have been discarded \nAKA cards that have not been won in rounds"),SerializeField]
private List<Tile> usedTiles;
[Header("the list of players in the game")]
public PlayerController[] players;
[Header("the players whose turn it is")]
public int playersTurn = 0;
[Header("the player who won the game")]
public int winner = -1;
[Header("the ID of the current game")]
public int gameNumber = 0;
[Header("the current round")]
public int rounds = 0;
#region private vars
private bool allBetsPlaced;
private bool gameOver = false;
private UGS_Analytics analytics;
private int cardsUsed = 0;
private bool draw = false;
private bool inSection = false;
private const float Tolerance = 0.000000001f;
private bool section1 = false;
private bool section2 = false;
private bool section3 = false;
private bool section4 = false;
private bool section5 = false;
private bool section6 = false;
#endregion
// Start is called before the first frame update
private void Start()
{
// declare variable
Tile tile;
// repeat amount of times there is meant to be the tile in the deck
for(int i = 0; i < numberOfGrass; i++)
{
//create the tile in the scene
tile = Instantiate(grass);
//add the tile to the deck
deck.Add(tile);
}
// repeat for other tiles
for(int i = 0; i < numberOfWoodChips; i++){
tile = Instantiate(woodChips);
deck.Add(tile);
}
for(int i = 0; i < numberOfHay; i++){
tile = Instantiate(hay);
deck.Add(tile);
}
for(int i = 0; i < numberOfTwigs; i++){
tile = Instantiate(twigs);
deck.Add(tile);
}
for(int i = 0; i < numberOfSoil; i++){
tile = Instantiate(soil);
deck.Add(tile);
}
for(int i = 0; i < numberOfFeathers; i++){
tile = Instantiate(feather);
deck.Add(tile);
}
//get all the players in the scene
players = FindObjectsOfType<PlayerController>();
//change the order of players every game
ShufflePlayers();
if (RandomGames)
{
List<PlayerController> newPlayer = players.ToList();
int RPC = Random.Range(Convert.ToInt32(minMaxPeople.x), Convert.ToInt32(minMaxPeople.y)+1);
while (newPlayer.Count != RPC)
{
int PTR = Random.Range(0, newPlayer.Count-1);
newPlayer[PTR].gameObject.SetActive(false);
newPlayer.RemoveAt(PTR);
}
players = newPlayer.ToArray();
dealAmount = players.Length + 1;
}
//shuffle the objectives
ShuffleObjectives();
// declare a variable to keep count
int j = 0;
//for each player
foreach(PlayerController player in players){
//give the player a number
player.playerNumber = j;
//sync the play session var in player
player.playtestSession = playtestSession;
//give the player their objective
DealObjective(player);
//increase integer
j++;
//tell player its a new game and get set up
player.NewGame();
}
// set up analytics var
analytics = FindObjectOfType<UGS_Analytics>();
//shuffle the tiles
ShuffleTiles();
//tell the game it can proceed with section one
section1 = true;
}
// Update is called once per frame
private void Update()
{
//only execute if space bar pressed and stepByStep is true
if(Input.GetKeyDown(KeyCode.Space) && stepByStep){
Play();
}
}
// Update is called once every 0.02 seconds no matter the FPS
private void FixedUpdate()
{
//make sure that the game isn't already running code and its not meant to be step by step
if (!inSection && !stepByStep)
{
//make the game run and play
Play();
}
}
/*
Play()
Function used when we want the game to advance
the function is split into six section,
section one
deals the tiles on the "table"
and clears any previous bets
section two
gets the players bets and places them on tiles
section three
distributes tiles to the winners
this also executes cuckoo egg effects
and places the tiles and eggs into the respective nests
section four
checks if the game is over (if a player has over 6 tiles)
if its not then it resets the game and gets ready to start from section one
section five
if the game is over it counts the scores of each player and determines a winner
then it sends all the data to an export file to be analyzed later
section six
reloads the scene (inefficient way of setting all the values back to default)
each section will print a line so that if bugs occur we know which section it is in
*/
private void Play(){
//check which section we are in and execute the appropriate code
if(section1 && !gameOver)
{
//print visual message for keeping track of section
print("dealing tiles");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//increase the count for amount of rounds in the game
rounds++;
//deal the amount of tiles specified
DealTiles(dealAmount);
//no bets have been placed yet for round, so set to false
allBetsPlaced = false;
//next section and section finished
section1 = false;
section2 = true;
inSection = false;
}
else if(section2 && !gameOver)
{
//print visual message for keeping track of section
print("getting player bets");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//do one turn for each player
//each player will choose a card and an egg to bet
GetPlayerBets();
//if all the players have placed their eggs
if (allBetsPlaced)
{
//proceed to next section
section2 = false;
section3 = true;
}
//section finished
inSection = false;
}
else if(section3 && !gameOver)
{
//print visual message for keeping track of section
print("sorting winners");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//distribute winning tiles to everyone who won the bets
DistributeTiles();
//next section and section finished
section3 = false;
section4 = true;
inSection = false;
}
else if (section4 && !gameOver)
{
//print visual message for keeping track of section
print("checking game status");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//check if game should be over or if we continue onto a new round
CheckGameStatus();
//if game not over
if (!gameOver)
{
//start a new round / it sets section1 to true
Restart();
//print visual message for keeping track of section
print("starting new round");
}
//next section and section finished
section4 = false;
inSection = false;
}
else if (section5 && gameOver)
{
//print visual message for keeping track of section
print("counting player scores");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//count the score of each player and assign the winner
CountScore();
//get the game id
gameNumber = FindObjectOfType<ShowText>().game;
//send game data to UGS_Analytics
analytics.GameComplete(winner: players[winner].describer,
winnerScore: players[winner].score,
rounds: rounds,
cardsLeft: deck.Count,
cardsUsed: cardsUsed,
cardsNotPickedUp: usedTiles.Count,
playerNumber: players.Length,
playtestSession: playtestSession,
draw: draw,
gameNumber: gameNumber,
describer: descriptionOfGame
);
// send all the players data to UGS_Analytics
foreach (PlayerController player in players)
{
//send individuals player data
player.SendData();
}
//next section and section finished
section5 = false;
section6 = true;
inSection = false;
}
else if (section6 && gameOver)
{
//print visual message for keeping track of section
print("starting new game");
//set section to true to make sure game doesnt execute code multiple times
inSection = true;
//load new scene
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
//section finished / code is here in case code executes in background of loading a new scene
section6 = false;
inSection = false;
}
}
/*
CountScore()
Function for counting the score of each player and deciding a winner
EXAMPLE:
winner = 0;
CountScore();
winner = 3;
*/
private void CountScore()
{
//set the current winner to player 0
winner = 0;
//repeat for each player in game
foreach (PlayerController player in players)
{
//count the players score
player.CountScore();
//check if score is higher than the current winners score
if (player.score > players[winner].score)
{
//sets a new winner
winner = player.playerNumber;
//sets the draw var
draw = false;
}
//checks if the scores are the same and make sure its not comparing the same player
else if (player.score == players[winner].score && winner != player.playerNumber)
{
//sets the draw var
draw = true;
}
}
}
/*
CheckGameStatus()
Function for checking if the game is over or not
EXAMPLE:
gameOver = false;
CheckGameStatus();
gameOver = true;
*/
private void CheckGameStatus()
{
//loops through each player
foreach (PlayerController player in players)
{
//checks if any player has more that 6 tiles
if (player.tiles.Count() >= 6)
{
//set gameOver to true and allow the game to go to section 5
gameOver = true;
section5 = true;
}
}
}
/*
Restart()
Function for resetting round values
resets bets and moves the left over dealtTiles[] into the usedTiles[]
it also send a message to each player for them to take a new hand
EXAMPLE:
dealtTiles[soil]
usedTiles[];
player.hand[]
Restart();
dealtTiles[]
usedTiles[soil];
player.hand[1,2,3,4,5]
*/
private void Restart()
{
//check if the tiles that had bets and drew stay in play
//note: the tiles that have won will have already been removed from dealTiles[]
if (!ifCardIsDrawThenLeaveIt)
{
//move left over tiles to usedTiles[]
usedTiles.AddRange(dealtTiles);
//clear the dealTiles[]
dealtTiles.Clear();
}
else
{
//for each tile in dealTiles[]
for(int i = 0; i < dealtTiles.Count; i++)
{
//if there are no bets on the tile
if (dealtTiles[i].bets.Count <= 0)
{
//move it over to usedTiles[]
usedTiles.Add(dealtTiles[i]);
dealtTiles.RemoveAt(i);
//remove index as there are now less items in the list
i--;
}
//if there are bets on the tile
else
{
//remove the bets
dealtTiles[i].bets.Clear();
}
}
}
//clear all bets recorded on the GameManager
bets.Clear();
//go through each player
foreach (PlayerController player in players)
{
//tell them its a new round so the shuffle their eggs and take a new hand
player.NewRound();
}
//set section1 to true for round to reset
section1 = true;
}
/*
DistributeTiles()
Function for distributing the dealtTiles[] to the winners
of each respective tile. also it execute cuckoo eggs that are in play
EXAMPLE:
player[0].tiles[]
player[0].eggsInNest[]
player[1].gotObj5 = false
DistributeTiles()
player[0].tiles[soil, hay]
player[0].eggsInNest[cuckooEgg, 7]
player[1].gotObj5 = true
*/
private void DistributeTiles(){
//go through each tile that was dealt
for(int i = 0; i < dealtTiles.Count; i++)
{
//get the tile that may be distributed
Tile tile = dealtTiles[i];
//get the winner of the tile
int winnerOfTile = tile.Winner();
//if there was a winner / it wasn't a draw or no one bet on it
if (winnerOfTile != -1)
{
//give the tile to the winner
players[winnerOfTile].ReceiveTile(tile);
//remove the tile from the dealtTiles[]
dealtTiles.Remove(tile);
//change index as number of tiles in list was reduced
i--;
}
}
}
/*
GetPlayerBets()
Function for getting all the players bets they are placing for the round
bets come in a Vector3 (player number, tile their betting on, value of egg being bet)
the function is codded so that every round the next player starts
round 1 = player 0 starts, round 2 = player 1 starts...
it also checks if all the players have placed their hands,
aka there are no more eggs to place
EXAMPLE:
bets[];
Play();
bets[(0,1,2),
(1,2,3),
(2,3,4)]
*/
private void GetPlayerBets(){
//repeat for the amount of times there are players
for(int i = 0; i < players.Length; i++){
//retrieve bet off the player
Vector3 bet = players[playersTurn].Play();
//if the player has a valid bet
if (Math.Abs(bet.z - (-5)) > Tolerance)
{
//add the bet to the bet list
bets.Add(bet);
//add the bet to the tile
TileManager(bet[0], bet[1], bet[2]);
}
//increment the count for who's players turn it is
playersTurn++;
//if its the last player in the list then make sure the next player
//is the first in the list
if (playersTurn >= players.Length)
playersTurn = 0;
}
//increment the count for who's players turn it is for next round
playersTurn++;
//if its the last player in the list then make sure the next player
//is the first in the list
if(playersTurn >= players.Length)
playersTurn = 0;
//go through each player in list again
foreach (PlayerController player in players)
{
//check if the have any eggs left in their hand
if(player.hand.Count == 0)
//assume all bets have been placed
allBetsPlaced = true;
else
{
//set to false if they still have a hand (round continues)
allBetsPlaced = false;
//break out of loop, there are still eggs in play
break;
}
}
}
/*
TileManager(float player, float value, float tile)
Function for assigning a bet onto a specific tile, it inserts the bet as a
Vector 2
player is the player who made the bet
value is the value of the egg placed on the tile
tile is the index of which tile the bet was placed on
EXAMPLE:
tile[2].bets[];
TileManager(0,1,2);
tile[2].bets[(0,1)];
*/
private void TileManager(float player, float value, float tile){
//select the tile which was bet on, and inject a vector2 with the player and value
dealtTiles[Convert.ToInt32(tile)].bets.Add(new Vector2(player, value));
}
/*
ShuffleTiles()
Function for shuffling the deck[] of tiles in the game
EXAMPLE:
deck[soil,hay,twig];
ShuffleTiles();
deck[twig,soil,hay];
*/
private void ShuffleTiles(){
//create a new list that gives a new guid to the tiles and
//orders them based on this new guid, basically making it random
var shuffledTiles = deck.OrderBy(a => Guid.NewGuid()).ToList();
//replace the egg list with this new randomised order of eggs
deck = shuffledTiles;
}
/*
ShuffleTiles()
Function for shuffling the objectives[] in the game
EXAMPLE:
objectives[obj1,obj2,obj3];
ShuffleObjectives();
objectives[obj2,obj3,obj1];
*/
private void ShuffleObjectives(){
var shuffledCards = objectives.OrderBy(a => Guid.NewGuid()).ToList();
objectives = shuffledCards;
}
private void ShufflePlayers(){
List<PlayerController> shuffledPlayers = players.OrderBy(a => Guid.NewGuid()).ToList();
players = shuffledPlayers.ToArray();
}
/*
DealTiles(int amount)
Function dealing the tiles from the deck[] onto the "table" for the players
to bet on for the round
amount is the amount of tiles to be dealt onto the table
EXAMPLE:
dealtTiles[];
DealTiles(3);
objectives[obj2,obj3,obj1];
*/
private void DealTiles(int amount){
//while there are less cards on the table than required
while (dealtTiles.Count < amount)
{
//check if the deck is empty
if(deck.Count <= 0)
{
//if used tiles also empty then we have run out of cards so break
if (usedTiles.Count == 0)
break;
//put the usedTiles into the deck
deck = usedTiles;
//shuffle the deck
ShuffleTiles();
//delete the usedTiles
usedTiles.Clear();
}
//increase the integer keeping track of cards used in game
cardsUsed++;
//add the top card of deck to dealtTiles
dealtTiles.Add(deck[0]);
//remove the top card from the deck
deck.RemoveAt(0);
}
}
/*
DealObjectives(PlayerController player)
Function dealing the objectives to each player
player is the player who will receive the objective
EXAMPLE:
player[0].objective = null;
DealObjectives(0);
player[0].objective = obj1;
*/
private void DealObjective(PlayerController player){
// assign the first objective to the player
player.objective = Instantiate(objectives[0]);
// remove the objective from the objectives deck
objectives.RemoveAt(0);
}
}
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Random = UnityEngine.Random;
public class Tile : MonoBehaviour
{
[Header("name of the tile")]
public string nameOfTile;
[Header("how many points is the tile worth")]
public int points;
[Header("the bets placed on the tile")]
public List<Vector2> bets;
#region private vars
private int numberOfPlayers;
private GameManager gm;
private const float Tolerance = 0.000000001f;
#endregion
// Start is called before the first frame update
private void Start()
{
//assign the game manager
gm = FindObjectOfType<GameManager>();
//get the amount of players in the game
numberOfPlayers = gm.players.Length;
}
/*
Winner()
Function used when we need to know who won the current tile
returns the player number that has the highest value on it
it also executes the cuckoo egg effects on the tile
Returns:
int winner of the tile
EXAMPLE:
player[0].gotObj5 = false;
player[1].eggsInNest[];
Winner();
return 1;
player[0].gotObj5 = true;
player[1].eggsInNest[cuckoo egg];
*/
public int Winner()
{
//assume the winners score is -5
float winnerScore = -5;
//assume the winner is -1 (this means no one bet)
int winner = -1;
//bool if their is a draw
bool draw = false;
//go through each player in the game
for(int currentPlayer = 0; currentPlayer < numberOfPlayers; currentPlayer++)
{
//set the score to 0
float currentPlayerScore = 0;
//for each bet on the tile
foreach (Vector2 bet in bets)
{
//check if the bet is placed by the current player being checked
if (Math.Abs(bet.x - currentPlayer) < Tolerance)
{
//make sure its not the cuckoo egg being checked
if(Math.Abs(bet.y - 13) > Tolerance)
//add the bet to the current score
currentPlayerScore += bet.y;
}
}
// if the current player has a higher score than the previous winning score
if (currentPlayerScore > winnerScore)
{
//change the winning score
winnerScore = currentPlayerScore;
//assign a new winner
winner = currentPlayer;
//set draw to false
draw = false;
}
//if the scores are the same
else if (Math.Abs(currentPlayerScore - winnerScore) < Tolerance)
{
//set draw to true
draw = true;
}
}
//if there is no draw
if (!draw)
{
//check and execute the cuckoo eggs
GiveCuckooEgg(winner);
//return the winner of the tile
return winner;
}
//return -1 if its a draw
return -1;
}
/*
GiveCuckooEgg(int target)
Function used for when we need to find and execute the cuckoo eggs on a tile
the function will find who placed the cuckoo egg and check if they did their objective
and if give the target the penalty of receiving a cuckoo egg
target is the player who won the tile and therefore will be affected by the
effects of the cuckoo eggs
EXAMPLE:
** newCuckooEggRules **
player[0].eggsInNest[];
player[1].gotObj5 = false;
GiveCuckooEgg(0);
player[0].eggsInNest[cuckoo egg];
player[1].gotObj5 = true;
----------------------
** !newCuckooEggRules **
player[0].eggsInNest[];
player[1].eggsInNest[];
GiveCuckooEgg(0);
player[0].eggsInNest[];
player[1].eggsInNest[7];
*/
void GiveCuckooEgg(int target)
{
//go through each player and reset the gotEggStolenByCuckoo, so they can place down eggsInNest
//if they dont get affected by the cuckoo egg
foreach (PlayerController player in gm.players)
{
player.gotEggStolenByCuckoo = false;
}
//get a list to store the target bets
List<int> targetOptions = new();
//for each bet on the tile
foreach (Vector2 bet in bets)
{
//check if the bet was made by the target and its not a cuckoo egg
if (Convert.ToInt32(bet.x) == target && Convert.ToInt32(bet.y) != 13)
{
//add the bet to target list
targetOptions.Add(Convert.ToInt32(bet.y));
}
}
//if there are no bets to be targeted
if (targetOptions.Count <= 0)
return;
//sort by smallest to highest
targetOptions.Sort();
//invert to highest value is first
targetOptions.Reverse();
//make new list to see which player has cuckoo
List<int> playersWithCuckoos = new();
//for each bet on tile
foreach (Vector2 bet in bets)
{
//check if the bet was not made by the target and it is a cuckoo egg
if (Convert.ToInt32(bet.x) != target && Convert.ToInt32(bet.y) == 13)
{
//add player to the cuckoo list
playersWithCuckoos.Add(Convert.ToInt32(bet.x));
print(bet);
}
}
//if their is only one cuckoo egg (more than one and it cancels itself) and if we are playing with new rules
if(playersWithCuckoos.Count == 1 && gm.newCuckooEggRules)
{
//egg being used is a random egg
int egg = targetOptions[Random.Range(0, targetOptions.Count)];
//check player that used cuckoo if it was their target
gm.players[playersWithCuckoos[0]].ReceiveEgg(14, target);
//remove the egg from the targets game so they dont use it again
gm.players[target].eggsUsed.Remove(egg);
//give the target the cuckoo egg (value 0 once in nest)
gm.players[target].ReceiveEgg(13, playersWithCuckoos[0]);
//remove the egg form the options
targetOptions.Remove(egg);
print($"success! egg taken is {egg} from player {target}, player {playersWithCuckoos[0]} gave the cuckoo egg");
}
else if (playersWithCuckoos.Count == 1 && gm.evenNewerCuckooEggRules)
{
//check player that used cuckoo if it was their target
gm.players[playersWithCuckoos[0]].ReceiveEgg(14, target);
//give the target the cuckoo egg (value 0 once in nest)
gm.players[target].ReceiveEgg(13, playersWithCuckoos[0]);
print($"success! cuckoo given to player {target}, player {playersWithCuckoos[0]} gave the cuckoo egg");
}
else if (!gm.newCuckooEggRules && !gm.evenNewerCuckooEggRules)
{
foreach (int player in playersWithCuckoos)
{
//check if there are any eggs to steal left
if(targetOptions.Count == 0) return;
//egg being used is the highest value
int egg = targetOptions[0];
//the player who placed the cuckoo egg places the egg in their nest and
//checks if it was their target
gm.players[player].ReceiveEgg(egg, target);
//remove the egg from the targets game so they dont use it again
gm.players[target].eggsUsed.Remove(egg);
//give the target the cuckoo egg (value 0 once in nest)
gm.players[target].ReceiveEgg(13, player);
//remove the egg form the options
targetOptions.Remove(egg);
}
}
}
}
using System.Collections.Generic;
using UnityEngine;
public class UGS_Analytics : MonoBehaviour
{
[Header("records data of the games to \nF:\\Game Design\\Unity projects\\faites vos oeufs\\Assets\\StreamingAssets")]
public bool recordDataCsv = true;
//private vars
private CSVExport csv;
// play on Awake
private void Awake()
{
//assign CSV script
csv = GetComponent<CSVExport>();
}
/*
Send(string eventName, Dictionary<string, object> parameters)
Function used when we must send data to the CSV file
eventName is the name of the type of data being sent
parameters is the data that is being sent to the CSV
EXAMPLE:
CSVFile = "";
Send(event1, ({score,1},player,2));
CSVFile = "event1
score, 1
player, 2";
*/
void Send(string eventName, Dictionary<string, object> parameters)
{
//check if we are recording data
if (recordDataCsv)
{
//record data to CSV
csv.Record(eventName, parameters);
}
}
/*
GameComplete(string winner,
int winnerScore,
int rounds,
int cardsLeft,
int cardsUsed,
int cardsNotPickedUp,
int playerNumber,
int playtestSession,
bool draw,
int gameNumber,
string describer
)
Function used when we want to sort the data from the games data into a CSV format
we put the data into a dictionary to be sent off
then it send the dictionary to the Send() function
string winner is the winner of the game
int winnerScore is the score the winner got
int rounds is the amount of rounds played
int cardsLeft is the amount of tiles left in the deck
int cardsUsed is the amount of tiles dealt in the game
int cardsNotPickedUp are tiles that where left in the usedTiles pile
int playerNumber is the amount of players in the game
int playtestSession is the id of the playtest session
bool draw is the game a draw
int gameNumber is the number id of the game
string describer the description of the game
EXAMPLE:
GameComplete(0,1,2,3)
Send("GameCompleted", ({score,0},{winnerScore,1},{rounds,2},{cardsLeft,3})));
*/
public void GameComplete(string winner,
int winnerScore,
int rounds,
int cardsLeft,
int cardsUsed,
int cardsNotPickedUp,
int playerNumber,
int playtestSession,
bool draw,
int gameNumber,
string describer = "unknown player profiles and details"
)
{
//convert data into dictionary
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "Winner", winner },
{ "winnerScore", winnerScore},
{ "rounds", rounds},
{ "cardsLeft", cardsLeft },
{ "cardsUsed", cardsUsed},
{ "cardsNotPickedUp", cardsNotPickedUp},
{ "playerNumber", playerNumber},
{ "playtestSession", playtestSession},
{ "draw", draw},
{ "describer", describer},
{ "gameNumber", gameNumber}
};
//send off the data
Send("GameCompleted", parameters);
}
/*
GameComplete(string winner,
int winnerScore,
int rounds,
int cardsLeft,
int cardsUsed,
int cardsNotPickedUp,
int playerNumber,
int playtestSession,
bool draw,
int gameNumber,
string describer
)
Function used when we want to sort the data from the games data into a CSV format
we put the data into a dictionary to be sent off
then it send the dictionary to the Send() function
int player is the player number
string describer is the decription of the player behaviour
int score is the score they got
int numberOfTiles is the number of tiles the have in their nest
int cuckooEggValue is the value of the cuckoo egg they stole
int eggsPlaced is the amount of eggs they placed in their nest
int eggsInNest is the amount of eggs in their nest
int playtestSession is the id of the playtest session
int objectiveScore is the amount of score due to completing objectves
int tileScore is the amount of score due to collecting tiles
int eggScore is the amount of score due to placing eggs in their nests
string objective1 is their objective one
string objective2 is their objective two
string objective3 is their objective three
int objectivesComplete is the amount of objectives complete
int eggsLeft is the amount of eggs left in their game
int gameNumber is the game id number
EXAMPLE:
PlayerData(0,"random",2,3)
Send("Player", ({player,0},{describer,"random"},{score,2},{numberOfTiles,3})));
*/
public void PlayerData(int player,
string describer,
int score,
int numberOfTiles,
int cuckooEggValue,
int eggsPlaced,
int eggsInNest,
int playtestSession,
int objectiveScore,
int tileScore,
int eggScore,
string objective1,
string objective2,
string objective3,
int objectivesComplete,
int eggsLeft,
int gameNumber
)
{
//convert data into dictionary
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "player", player },
{ "describer", describer },
{ "score", score },
{ "numberOfTiles", numberOfTiles },
{ "cuckooEggValue", cuckooEggValue },
{ "eggsPlaced", eggsPlaced },
{ "eggsInNest", eggsInNest},
{ "playtestSession", playtestSession },
{ "objectiveScore", objectiveScore },
{ "tileScore", tileScore },
{ "eggScore", eggScore },
{ "objective1", objective1 },
{ "objective2", objective2 },
{ "objective3", objective3 },
{ "objectivesComplete", objectivesComplete },
{ "eggsLeft", eggsLeft },
{ "gameNumber", gameNumber}
};
//send off the data
Send("Player", parameters);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
public class CSVExport : MonoBehaviour
{
public StringBuilder sb = new System.Text.StringBuilder();
private string contentData;
private string folder;
private string filePath;
private bool firstTime = false;
int globalIndex;
void Awake()
{
folder = Application.streamingAssetsPath + "/data";
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
filePath = Path.Combine(folder, "export.csv");
PlayerPrefs.SetInt("globalIndex",0);
}
public void Record(string eventName, Dictionary<string, object> data)
{
sb.Clear();
sb.AppendLine(eventName);
int index = 0;
foreach (KeyValuePair<string,object> value in data)
{
string v = value.ToString();
if (v.Length >= 2)
{
v = v.Substring(1);
v = v.Remove(v.Length - 1);
}
sb.AppendLine(v);
}
SaveToFile(sb.ToString());
}
public void SaveToFile(string content)
{
using (var writer = new StreamWriter(filePath, true))
{
writer.Write(content);
}
print(folder);
}
}
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System;
public class PlayerController : MonoBehaviour
{
[Header("number is assigned at the\nstart of game")]
public int playerNumber = -1;
[Header("describer is assigned by the\nPlayerBehaviour script")]
public string describer;
[Header("individual eggs and\ntheir values")]
public List<int> eggs;
[Header("the players hand in a round")]
public List<int> hand;
[Header("the amount of eggs to be\nput in the player hand\nevery round")]
public int handAmount;
[Header("the eggs used but not \nplaced into nest or not \ntaken by enemy cuckoo egg \n" +
"these eggs can be reshuffled \nand put back into the players hand")]
public List<int> eggsUsed;
[Header("the tiles won by the player \nand placed into their nest")]
public List<Tile> tiles;
[Header("the eggs the player placed into their nest")]
public List<int> eggsInNest;
[Header("the players objective card")]
public Objectives objective;
[Header("the players score")]
public int score;
[Header("the playtest session \nAssigned by game manager")]
public int playtestSession = 0;
[Header("cuckoo egg target")]
public int obj5Target = 0;
[Header("did someone use a \ncuckoo egg on the player")]
public bool gotEggStolenByCuckoo = false;
[Header("the number of the game")]
public int gameNumber;
#region private variables
private GameManager gm;
private int cuckooEggValue = 0;
private int eggsPlaced = 0;
private int objScore = 0;
private int tileScore = 0;
private int eggScore = 0;
private int objectivesComplete = 0;
private UGS_Analytics analytics;
private PlayerBehaviour behaviour;
private bool gotObj5 = false;
private const float Tolerance = 0.000000001f;
int obj1 = 0;
int obj2 = 0;
int obj3 = 0;
#endregion
private void Awake()
{
//assign variables
behaviour = GetComponent<PlayerBehaviour>();
describer = behaviour.description;
gm = FindObjectOfType<GameManager>();
analytics = FindObjectOfType<UGS_Analytics>();
}
/*
TakeHand(int amount)
Function used when player puts eggs in their hand
amount is the amount of eggs that will be placed into the players hand
EXAMPLE:
Eggs[1,2,3,4];
Hand[]
TakeHand(2);
Eggs[1,4];
Hand[2,3];
*/
private void TakeHand(int amount){
//repeat for the amount of eggs should be in the players hand
for(int i = 0; i < amount; i++){
//check if there are any eggs to take
if(eggs.Count <= 0){
//get the eggs previously used and put them back in the pile to
//take from
eggs = eggsUsed;
//shuffle the eggs
ShuffleEggs();
//clear the used eggs list to not duplicate eggs
eggsUsed.Clear();
}
//if there are eggs to take then put the top one in the players hand
if (eggs.Count != 0)
{
//add egg to hand
hand.Add(eggs[0]);
//remove egg from pile that player can take from
eggs.RemoveAt(0);
}
}
//order the hand from lowest to highest
hand.Sort();
//reverse it so its highest to lowest
hand.Reverse();
}
/*
ShuffleEggs()
Function used to shuffle the eggs
EXAMPLE:
Eggs[1,2,3,4];
ShuffleEggs();
Eggs[3,1,4,2];
*/
private void ShuffleEggs(){
//create a new list that gives a new guid to the eggs and
//orders them based on this new guid, basically making it random
var shuffledEggs = eggs.OrderBy(a => Guid.NewGuid()).ToList();
//replace the egg list with this new randomised order of eggs
eggs = shuffledEggs;
}
/*
Play()
Function used when the player must play
returns a vector three with the players actions
Returns:
Vector3(the players number,
the egg they are placing down,
the tile they choose to bet on)
EXAMPLE:
Play();
return Vector3(0,1,2);
*/
public Vector3 Play(){
//Get a value for the egg
int egg = PickEgg();
//Get the tile index its betting on
int tile = PickTile();
//return a vector3 with the details of the bet
return new Vector3(playerNumber,egg,tile);
}
/*
PickEgg()
Function used when the player must pick an egg to bet with
returns am integer with the value of the egg the player is betting with
Returns:
integer value of egg
EXAMPLE:
PickEgg();
return 2;
*/
private int PickEgg()
{
//Send the current hand to the player behaviour, and return the value of
//the egg
int egg = behaviour.PickEgg(hand);
//remove the egg chosen from the hand
hand.Remove(egg);
//if the egg is not the cuckoo egg or a null then add it the the eggsUsed list
//note: that used eggs can be re used in the players hand when the player
// runs out of Eggs[]
if(egg is not (13 or -5) && !gm.evenNewerCuckooEggRules){
eggsUsed.Add(egg);
}
else if (gm.evenNewerCuckooEggRules && egg != -5)
{
eggsUsed.Add(egg);
}
//return the value of the egg
return egg;
}
/*
PickTile()
Function used when the player must pick a tile to bet on
returns am integer with the index of the tile the player is betting on
Returns:
integer index of the position of the tile
EXAMPLE:
PickTile();
return 3;
*/
private int PickTile()
{
//Send the current dealt tiles to the player behaviour, and return the
//index of the tile the player will bet on
int tile = behaviour.PickTile(gm.dealtTiles);
//return the index of the tile the player will bet on
return tile;
}
/*
NewGame()
Function used when a new game starts, only called once to set up variables
such as the target for the cuckoo egg, it also shuffles the eggs and gives
the player a new hand
EXAMPLE:
obj5Target = null;
Eggs[1,2,3,4];
Hand[];
NewGame();
obj5Target = 3;
Eggs[4,1];
Hand[3,2];
*/
public void NewGame(){
//calls a new round function to shuffle eggs and deal a new hand to the player
NewRound();
//if the player number is 0 and the cuckoo egg objective is "below" it,
//then it will assign it the "top" player
if (playerNumber == 0 && objective.obj5Direction == -1)
obj5Target = gm.players.Length - 1;
//if the player number is the "top" and the cuckoo egg objective is "on top" of it,
//then it will assign it the "bottom" player
else if (playerNumber == gm.players.Length - 1 && objective.obj5Direction == 1)
obj5Target = 0;
//otherwise assign the cuckoo egg objective using math
//note: the obj.obj5Direction is either 1 or -1
else
obj5Target = playerNumber + objective.obj5Direction;
}
/*
NewRound()
Function used when a new round starts, called a few times per game,
it shuffles the eggs and deals the player their hand
EXAMPLE:
Eggs[1,2,3,4];
Hand[];
NewGame();
Eggs[4,1];
Hand[3,2];
*/
public void NewRound()
{
//shuffle Eggs[]
ShuffleEggs();
//populate the players Hand[] with the amount of eggs being dealt
//into their hand
TakeHand(handAmount);
}
/*
PlaceEggInNest(list<int> values)
Function used when the player must place an egg into their nest
the egg placed into the nest is removed for the usedEggs[] in order to
make sure the player cannot put the egg back into their hand
values is the options of eggs the player has to place into their nest
EXAMPLE:
EggsInNest[1,2];
PlaceEggInNest(values[3,4]);
EggsInNest[1,2,4];
*/
private void PlaceEggInNest(List<int> values)
{
//check if the player has eggs to be placed OR that the player has been
//given a cuckoo egg
if (values.Count == 0 || gotEggStolenByCuckoo)
return;
//Send the values to the player behaviour, and return the value of the
//egg that will be placed into the players nest
int eggChoice = behaviour.PlaceEggInNest(values);
//add the egg to the players nest
eggsInNest.Add(eggChoice);
//remove the egg from the eggsUsed[], prevents it from being used again
eggsUsed.Remove(eggChoice);
//statistics to see how many eggs where placed
eggsPlaced++;
}
/*
ReceiveTile(Tile tile)
Function used when the player has received a tile and places it in their nest
it also calls the PlaceEggInNest() in order to place an egg with the tile
tile is tile that was recieved
EXAMPLE:
tiles[hayTile,TwigTile];
EggsInNest[1,2];
ReceiveTile(soilTile);
tiles[hayTile,TwigTile,soilTile];
EggsInNest[1,2,4];
*/
public void ReceiveTile(Tile tile)
{
//add tile to the players nest
tiles.Add(tile);
//make a list to contain the players options of eggs they can place in their nest
List<int> eggChoices = new List<int>();
// go through each bet placed on the tile
foreach (Vector2 bet in tile.bets)
{
//if the bet was made by the player and its not a cuckoo egg
//note: its checking if its equal but eliminating
//the chance of a floating point value changing the result
if (Math.Abs(bet.x - playerNumber) < Tolerance && Math.Abs(bet.y - 13) > Tolerance)
{
//add the value of the egg that was bet to the choices the player will have
eggChoices.Add(Convert.ToInt32(bet.y));
}
}
//place one of the eggs into the nest
PlaceEggInNest(eggChoices);
//checks if the tile completed an objective
CheckObjectiveTile(tile);
}
/*
CheckObjectiveTile(Stats tile)
Function used when we must check if the tile completes part of the
players objective
tile is the tile being checked
EXAMPLE:
objective.obj3Done = false;
CheckObjectiveTile(soil);
objective.obj3Done = true;
*/
private void CheckObjectiveTile(Tile tile)
{
//if its the first objective
if (tile.nameOfTile == objective.obj1)
{
//add one to the counter
obj1++;
//check if the objective is complete
if (obj1 >= objective.obj1Amount)
//set bool to true
objective.obj1Done = true;
}
//if its the second objective
if (tile.nameOfTile == objective.obj2)
{
//add one to the counter
obj2++;
//check if the objective is complete
if (obj2 >= objective.obj2Amount)
//set bool to true
objective.obj2Done = true;
}
//if its the third objective
if (tile.nameOfTile == objective.obj3)
{
//add one to the counter
obj3++;
//check if the objective is complete
if (obj3 >= objective.obj3Amount)
//set bool to true
objective.obj3Done = true;
}
}
/*
ReceiveEgg(int egg,int player)
Function used when the player has received an egg and places it in their nest
if the egg received is a cuckoo egg it will add it to their nest and makes
sure that the player cannot place an egg into their nest if they received a
cuckoo egg with gotEggStolenByCuckoo
(worth 0 according to rules)
also can be used to check if the player managed to complete their cuckoo egg
objective, by inputting a 14 into the egg variable
egg is the value of the egg that the player is receiving
player is the player who sent the egg
EXAMPLES:
eggsInNest[1,2];
ReceiveEgg(3,4);
eggsInNest[1,2,3];
//
eggsInNest[1,2];
gotEggStolenByCuckoo = false;
ReceiveEgg(13,4);
eggsInNest[1,2,0];
gotEggStolenByCuckoo = true;
//
eggsInNest[1,2];
gotObj5 = false;
ReceiveEgg(14,4);
eggsInNest[1,2];
gotObj5 = true;
*/
public void ReceiveEgg(int egg,int player)
{
//checks if received egg is a cuckoo egg
if (egg == 13)
{
//add 0 to the nest
eggsInNest.Add(0);
//changes the variable to show the player was affected by a cuckoo egg
gotEggStolenByCuckoo = true;
}
//checks if received egg is a check for the cuckoo objective
else if(egg == 14)
{
if (hand.Contains(13))
hand.Remove(13);
if (eggsUsed.Contains(13))
eggsUsed.Remove(13);
if (eggs.Contains(13))
eggs.Remove(13);
//if the egg affected player from the cuckoo egg is the target, then complete the objective
if(obj5Target == player)
gotObj5 = true;
}
//if its not a check or a cuckoo egg (only called if newCuckooEggRules is false in GameManager
else if(!gm.newCuckooEggRules && !gm.evenNewerCuckooEggRules)
{
//adds egg to nest
eggsInNest.Add(egg);
//records value of the egg stolen
cuckooEggValue = egg;
//checks if the egg was stolen by the players objective
if (player == obj5Target)
{
//sets objective variable to true AKA its complete
gotObj5 = true;
}
}
}
/*
CountScore()
Function used when the player needs to count up their scores to see who wins the game
it also counts individual scores for tiles eggs and objectives
EXAMPLE:
score = 0;
eggScore = 0;
tileScore = 0;
objScore = 0;
CountScore();
score = 60;
eggScore = 20;
tileScore = 20;
objScore = 20;
*/
public void CountScore()
{
//go through each egg in the nest and add the value of the egg to eggScore
foreach (int i in eggsInNest)
eggScore += i;
//go through each tile in the nest and add the value of the tile to tileScore
foreach (Tile tile in tiles)
tileScore += tile.points;
//gets the objective score
objScore = ObjectiveScore();
//adds all the score categories together
score += objScore + tileScore + eggScore;
}
/*
ObjectiveScore()
Function used when the player needs to count up their objective score,
does not take into account tile value or tile amounts
only checks if the objectives from the objective card has been complete
EXAMPLE:
objScore = 0;
ObjectiveScore();
objScore = 20;
*/
private int ObjectiveScore()
{
//score counter and keeper
int i = 0;
//assuming player has gotten all of the objectives
bool success = true;
//check if obj1 was complete
if (objective.obj1Done)
{
//add the amount of points granted to the score counter
i += objective.objPoints1;
//add one to objectives complete
objectivesComplete++;
}
// if the objective is not considered complete then success is set to false
else
success = false;
//rest is same as first but with other objectives
if (objective.obj2Done)
{
i += objective.objPoints2;
objectivesComplete++;
}
else
success = false;
if (objective.obj3Done)
{
i += objective.objPoints3;
objectivesComplete++;
}
else
success = false;
if (success && tiles.Count <= 4)
{
success = false;
}
//check if the player managed to complete their cuckoo egg objective
if (gotObj5)
{
//add the amount of points the objective is worth
i += objective.obj5Points;
//add one to objectives complete
objectivesComplete++;
}
//if the player did not do all of the tile based objectives, then return
//with the current counted score
if (!success)
return i;
//else it will add the amount of bonus points granted
i += objective.objPoints4;
//add another objective complete
objectivesComplete++;
//return value
return i;
}
/*
SendData()
Function used when the player needs to send data off for analytics
send all the info in the function to UGS_Analytics which puts it into a csv
EXAMPLE:
csv = "";
SendData;
csv = "playerNumber = 0;
describer = "random"
score = 2
...
*/
public void SendData()
{
//get the game id
gameNumber = FindObjectOfType<ShowText>().game;
//send the following data to UGS_Analytics function
analytics.PlayerData(
playerNumber,
describer,
score,
tiles.Count,
cuckooEggValue,
eggsPlaced,
eggsInNest.Count,
playtestSession,
objScore,
tileScore,
eggScore,
objective.obj1,
objective.obj2,
objective.obj3,
objectivesComplete,
eggs.Count+eggsUsed.Count+hand.Count,
gameNumber
);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
public class PlayerBehaviour : MonoBehaviour
{
[Header("the description of the character")]
public string description;
[Header("** Picking eggs to bet **\nall values are a 3 way ratio")]
[Range(0, 100)] public float pickEggRandomness = 100;
[Range(0, 100)] public float picksCalculatedEgg = 100;
[Range(0, 100)] public float usesCuckooEgg = 100;
[Header("the preferred value to pick when placing a bet \n(if picksCalculatedEgg)")]
[Range(-20, 80)] public float pickingEggPreferredValues = 0;
[Header("** Picking card to bet on **\nall values are a 4 way ratio")]
[Range(0, 100)] public float pickCardRandomness = 100;
[Range(0, 100)] public float pointDriven = 50;
[Range(0, 100)] public float objectiveDriven = 0;
[Range(0, 100)] public float sociallyDriven = 0;
[Range(0, 100)] public float antiSociallyDriven = 0;
[Range(0, 100)] public float safelyDriven = 0;
[Header("ints the player considers positive/negative \nthese ints will give affect to the " +
"\nsocial \nantisocial \nobjective \ndrives of the player")]
[Range(-2, 8)] public int intPlayerConsidersNegative = -1;
[Range(-2, 8)] public int intPlayerConsidersPositive = 1;
[Range(0, 8)] public int intPlayerConsidersHighPoints = 4;
[Header("boosts to social ratio if the eggs \nchosen are negative or cuckoo egg")]
[Range(0, 100)] public float socialBoostIfEggNegative = 10;
[Range(0, 100)] public float socialBoostIfEggIsCuckoo = 10;
[Range(0, 100)] public float antiSocialBoostIfEggPositive = 10;
[Header("boosts to objective ratio if the eggs \nchosen are negative or cuckoo egg")]
[Range(0, 100)] public float objectiveBoostIfEggIsCuckoo = 10;
[Header("** Placing egg to put in nest ** \nall values are a 2 way ratio")]
[Range(0, 100)] public float placeEggRandomness = 100;
[Range(0, 100)] public float placingCalculatedEgg = 50;
[Header("the preferred value to pick when placing egg into nest \n(if placingCalculatedEgg)")]
[Range(-20, 80)] public float placingEggPreferredValues = 0;
[Header("the amount the player will consider to be a safe bet \n" +
"the player will bet on another option if its a safe bet")]
[Range(-3,51)] public int pointsOnTileThreshold = 0;
[Range(0, 100)] public float increaseBetOnTileSafetyPerRound;
[Range(0, 100)] public float increaseBetOnTileSafetyIfEggContested;
#region private vars
private GameManager gm;
private PlayerController pc;
private int pn;
private int pickedEgg = 0;
#endregion
// Start is called before the first frame update
void Start()
{
//assign variables
gm = FindObjectOfType<GameManager>();
pc = GetComponent<PlayerController>();
while (pn == -1)
{
pn = pc.playerNumber;
}
}
/*
PickEgg(List<int> hand)
Function used when player needs to choose an egg to bet with
takes into account the players personality
returns the value of the egg chosen
EXAMPLE:
PickEgg(hand[1,2,3]);
return 3;
*/
public int PickEgg(List<int> hand)
{
//if there is no more eggs in hand, return error value
if (!hand.Any())
return -5;
//create a temporary hand to manipulate
List<int> handTemp = hand;
//use cuckoo egg variable
float uce = usesCuckooEgg;
//if there isn't a cuckoo egg in hand then set the UCE to 0
if (!handTemp.Contains(13))
uce = 0;
//generate a float using the values for random egg, cuckoo egg and calculated placing
float behaviourChoice = Random.Range(0, pickEggRandomness + uce + picksCalculatedEgg);
//if the value is withing the random ratio
if (behaviourChoice <= pickEggRandomness)
{
//choose a random egg in the hand
pickedEgg = hand[Random.Range(0, handTemp.Count)];
//return the value
return pickedEgg;
}
//if we choose a cuckoo egg
if (behaviourChoice < uce + pickEggRandomness)
{
//get top value (hand will be ordered from highest to lowest, prevents bugs)
pickedEgg = hand[0];
//return value
return pickedEgg;
}
//if neither of the above then get preferred value and divide by 10
float value = pickingEggPreferredValues / 10;
//round the value to closest int
value = Mathf.Round(value);
//get the closest value in the hand to the preferred value
int closest = handTemp.Aggregate((x,y) => Math.Abs(x-value) < Math.Abs(y-value) ? x : y);
//assign value to picked egg
pickedEgg = closest;
//return value
return closest;
}
/*
PickTile(List<int> tiles)
Function used when player needs to choose a tile to bet on
takes into account the players personality
returns the index of the tile chosen
EXAMPLE:
PickTile(tiles[soil,twig,hay]);
return 1;
*/
public int PickTile(List<Tile> tiles)
{
//set the ratio variable that we can change
float od = objectiveDriven;
float sd = sociallyDriven;
float pd = pointDriven;
float asd = antiSociallyDriven;
float sad = safelyDriven;
sad += increaseBetOnTileSafetyPerRound * gm.rounds;
// check if the egg being bet is negative
if (pickedEgg <= intPlayerConsidersNegative)
//add the social boost
sd += socialBoostIfEggNegative;
if (pickedEgg >= intPlayerConsidersPositive)
asd += antiSocialBoostIfEggPositive;
// check if the egg being bet is the cuckoo
else if (pickedEgg == 13)
{
//add the social boost
sd += socialBoostIfEggIsCuckoo;
//add the objective boost
od += objectiveBoostIfEggIsCuckoo;
}
// list to contain the amount of bets on each tile
List<int> eggAmounts = new();
// list to contain the tiles that are in the players objective
List<Tile> objTiles = new();
// list to contain the tiles that are considered high points
List<Tile> highPointTiles = new();
// list to contain the tiles that are not considered safe bets
List<Tile> notSafeTiles = new();
List<Tile> extremelyNotSafeTiles = new();
//for each tile
foreach (Tile tile in tiles)
{
//add the amount of bets to the list
eggAmounts.Add(tile.bets.Count);
//check if tile has high points
if(tile.points >= intPlayerConsidersHighPoints)
//add tile to the point list
highPointTiles.Add(tile);
//check if tile is in objective
if(IsTileObj(tile))
//add tile to the objective list
objTiles.Add(tile);
int betValue = valueOfPlayerEggsOnTile(pn, tile);
int playerBets = amountOfPlayerBetsOnTile(pn, tile);
if (tile.bets.Count > 0 && betValue <= pointsOnTileThreshold && betValue > 0)
{
notSafeTiles.Add(tile);
foreach (var player in gm.players)
{
if (amountOfPlayerBetsOnTile(player.playerNumber, tile) >= playerBets && player.playerNumber != pn)
{
extremelyNotSafeTiles.Add(tile);
sad += increaseBetOnTileSafetyIfEggContested;
}
}
}
}
//check if there are bets on the tiles
if (eggAmounts.Max() == 0)
{
//set socially driven to 0
sd = 0;
}
//if there are no tiles counting towards the objective or the egg is negative
if (!objTiles.Any() || pickedEgg <= -1)
{
//make sure that its not the cuckoo egg and the objective wasn't affected by it
if (pickedEgg != 13 || objectiveBoostIfEggIsCuckoo == 0)
//set objective driven to 0
od = 0;
}
//check if there are highpoint cards or if the picked egg is negative
if (!highPointTiles.Any() || pickedEgg <= -1)
//set point driven to 0
pd = 0;
//check if there are highpoint cards or if the picked egg is negative
if (!notSafeTiles.Any() || pickedEgg <= -1)
//set point driven to 0
sad = 0;
//generate a float using the values for random tile, socially driven, point driven and objective driven
float behaviourChoice = Random.Range(0, pickCardRandomness+sd+od+pd+asd+sad);
//if the value is withing the random ratio
if(behaviourChoice <= pickCardRandomness)
//return a random tile index
return Random.Range(0, tiles.Count);
//if the value is withing the socially driven ratio
if(behaviourChoice <= sd + pickCardRandomness)
//return a tile considered to be social
return TileSocialPath( tiles, eggAmounts, od, pd, sad);
//if the value is withing the objective driven ratio
if(behaviourChoice <= od + sd + pickCardRandomness)
//return a tile considered to be in the players objective
return TileObjectivePath(tiles, objTiles, sd, pd, asd, sad);
//if the value is withing the point driven ratio
if (behaviourChoice <= pd + sd + od + pickCardRandomness)
//return a tile considered to be high points
return TilePointPath(tiles, highPointTiles, sd, od, asd, sad);
//if the value is withing the anti social driven ratio
if (behaviourChoice <= asd + pd + sd + od + pickCardRandomness)
//return a tile considered to be high points
return TileAntiSocialPath(tiles, eggAmounts, sd, od, sad);
//if the value is withing the anti social driven ratio
if (behaviourChoice <= sad + asd + pd + sd + od + pickCardRandomness)
//return a tile considered to be high points
return TileSafePath(tiles, notSafeTiles,extremelyNotSafeTiles, sd, pd, od, asd);
//if nothing is chosen warn us
print("error");
//return first tile
return 0;
}
int TileSafePath(List<Tile> tiles,
List<Tile> notSafeTiles,
List<Tile> extremelyNotSafeTiles,
float sd,
float pd,
float od,
float asd)
{
if (extremelyNotSafeTiles.Count == 1)
{
return tiles.IndexOf(extremelyNotSafeTiles[0]);
}
if (notSafeTiles.Count == 1)
{
return tiles.IndexOf(notSafeTiles[0]);
}
Tile defaultTileToBet = notSafeTiles[0];
//make new lists to hold the tiles with the following attributes
List<Tile> objTiles = new();
List<Tile> highPointTiles = new();
List<int> eggAmounts = new();
foreach (Tile tile in notSafeTiles)
{
//add the card to anti social list
eggAmounts.Add(tile.bets.Count);
//is the tile in objective
if (IsTileObj(tile))
//add to objective list
objTiles.Add(tile);
//is the tile high points
if (tile.points >= intPlayerConsidersHighPoints)
//add tile to the high point list
highPointTiles.Add(tile);
//and the bet is guaranteed to be above the safe threshold
if (pickedEgg + valueOfPlayerEggsOnTile(pn, tile) >= pointsOnTileThreshold && pickedEgg != 13)
{
//if the value is greater than the last option
if (valueOfPlayerEggsOnTile(pn, tile) > valueOfPlayerEggsOnTile(pn, defaultTileToBet))
defaultTileToBet = tile;
//or its a tile considered extremely not safe
else if(extremelyNotSafeTiles.Contains(tile))
defaultTileToBet = tile;
}
}
//check if player can be objective driven
if (!objTiles.Any() && pickedEgg != 13)
od = 0;
//check if there are bets on the tiles
if (eggAmounts.Max() == 0)
//set socially driven to 0
sd = 0;
//check if there are the same amount of bets on each tile
if (eggAmounts.Max() == eggAmounts.Min())
{
asd = 0;
sd = 0;
}
if (!highPointTiles.Any())
pd = 0;
//if the not player is sociably, objective or anti socially driven
if (sd + od + asd + pd <= 0)
//return tile that player was already betting on
return tiles.IndexOf(defaultTileToBet);
//change the sub fractions in order to "guarantee" their all different
od += Random.value/100;
asd += Random.value/100;
sd += Random.value/100;
pd += Random.value/100;
List<float> drives = new() { od, asd, sd, pd };
foreach (Tile tile in notSafeTiles)
{
//if player is more objective driven, and they have cuckoo egg, and the tile has their target
if (od == drives.Max() && pickedEgg == 13 && DoesTileHavePlayer(pc.obj5Target, tile) && !hasPlayerBetCuckooOnTile(pn,tile))
return tiles.IndexOf(tile);
//if the player is more objective driven and the tile is on their objectives list and making sure its not 13 in order not to cancel the above option
if (od == drives.Max() && objTiles.Contains(tile) && pickedEgg != 13)
return tiles.IndexOf(tile);
//if they are more point driven and the tile is in high points list
if (pd == drives.Max() && highPointTiles.Contains(tile))
return tiles.IndexOf(tile);
if (sd == drives.Max() && tile.bets.Count >= eggAmounts.Max())
return tiles.IndexOf(tile);
if (asd == drives.Max() && tile.bets.Count >= eggAmounts.Min())
return tiles.IndexOf(tile);
}
//if they cant find a good option then return default card
return tiles.IndexOf(defaultTileToBet);
}
int TileAntiSocialPath(List<Tile> tiles,
List<int> eggAmounts,
float pd,
float od,
float sad)
{
//make new lists to hold the tiles with the following attributes
List<Tile> antiSocialTiles = new();
List<Tile> objTiles = new();
List<Tile> highPointTiles = new();
List<Tile> notSafeTiles = new();
List<Tile> extremelyNotSafeTiles = new();
int i = 0;
foreach (Tile tile in tiles)
{
//if the tile has not a lot of bets
if (eggAmounts[i] == eggAmounts.Min())
{
//add the card to anti social list
antiSocialTiles.Add(tile);
//is the tile in objective
if (IsTileObj(tile))
{
//add to objective list
objTiles.Add(tile);
}
//is the tile high points
if (tile.points >= intPlayerConsidersHighPoints)
{
//add tile to the high point list
highPointTiles.Add(tile);
}
int betValue = valueOfPlayerEggsOnTile(pn, tile);
int playerBets = amountOfPlayerBetsOnTile(pn, tile);
if (tile.bets.Count > 0 && betValue <= pointsOnTileThreshold && betValue > 0)
{
notSafeTiles.Add(tile);
foreach (var player in gm.players)
{
if (amountOfPlayerBetsOnTile(player.playerNumber, tile) >= playerBets && player.playerNumber != pn)
{
extremelyNotSafeTiles.Add(tile);
sad += increaseBetOnTileSafetyIfEggContested;
}
}
}
}
//increment integer count
i++;
}
Tile defaultTileToBetOn = antiSocialTiles[0];
//if there is only one option
if (antiSocialTiles.Count == 1)
{
//return index of the card
return tiles.IndexOf(antiSocialTiles[0]);
}
//check if the tile is in objectives list
if (!objTiles.Any())
od = 0;
//check if the tile options have highpoint
if (!highPointTiles.Any())
pd = 0;
if (!notSafeTiles.Any())
sad = 0;
else if(extremelyNotSafeTiles.Any())
//make sure that the extremely not safe tiles are chosen
notSafeTiles = extremelyNotSafeTiles;
//if the not player is sociably or point driven
if (od + pd + sad <= 0)
//return tile that player was already betting on
return tiles.IndexOf(defaultTileToBetOn);
//change the sub fractions in order to "guarantee" their all different
od += Random.value;
sad += Random.value;
pd += Random.value;
List<float> drives = new() { od, sad, pd };
foreach (Tile tile in antiSocialTiles)
{
//if player is more objective driven, and they have cuckoo egg, and the tile has their target
if (od == drives.Max() && pickedEgg == 13 && DoesTileHavePlayer(pc.obj5Target, tile) && !hasPlayerBetCuckooOnTile(pn,tile))
return tiles.IndexOf(tile);
//if the player is more objective driven and the tile is on their objectives list
if (od == drives.Max() && objTiles.Contains(tile) && pickedEgg != 13)
return tiles.IndexOf(tile);
//if they are more point driven and the tile is in high points list
if (pd == drives.Max() && highPointTiles.Contains(tile))
return tiles.IndexOf(tile);
if (sad == drives.Max() && notSafeTiles.Contains(tile))
return tiles.IndexOf(tile);
}
//if they cant find a good option then return default card
return tiles.IndexOf(defaultTileToBetOn);
}
/*
TilePointPath(List<Tile> tiles,
List<int> eggAmounts,
List<Tile>objTiles,
List<Tile> highPointTiles,
float sd,
float od)
Function used to return a tile with high points
tiles is the list of all the tiles that the player can choose
eggAmounts is how many bets there are on each tile
objTiles is the tiles that are in the players objective
highPointTiles is the tiles that have high points
sd is how much the player is socially driven
od is how much the player is objective driven
returns the index of the tile chosen
EXAMPLE:
TilePointPath(tiles[soil,twig,hay]);
return 0;
*/
int TilePointPath(List<Tile> tiles,
List<Tile> highPointTiles,
float sd,
float od,
float asd,
float sad)
{
//if there is one option then return it
if (highPointTiles.Count == 1)
//uses the tiles[] as that has the original order an d therefore index of the tile
return tiles.IndexOf(highPointTiles[0]);
//select a random tile to be the tile the player will bet on by default
Tile tileWithBetAlready = highPointTiles[0];
//create new list to contain amount of bets
List<int> eggAmounts = new();
//create new list to contain tiles in objective
List<Tile> objTiles = new();
List<Tile> notSafeTiles = new();
List<Tile> extremelyNotSafeTiles = new();
//for each tile
foreach (Tile tile in highPointTiles)
{
//add the amount of bets
eggAmounts.Add(tile.bets.Count);
//check if tile is in objective
if(IsTileObj(tile))
//add tile to the objective list
objTiles.Add(tile);
//check if player has already bet on it
if (valueOfPlayerEggsOnTile(pn,tile) > valueOfPlayerEggsOnTile(pn,tileWithBetAlready))
//if value of bet is higher than last selected tile then change default tile
tileWithBetAlready = tile;
int betValue = valueOfPlayerEggsOnTile(pn, tile);
int playerBets = amountOfPlayerBetsOnTile(pn, tile);
if (tile.bets.Count > 0 && betValue <= pointsOnTileThreshold && betValue > 0)
{
notSafeTiles.Add(tile);
foreach (var player in gm.players)
{
if (amountOfPlayerBetsOnTile(player.playerNumber, tile) >= playerBets && player.playerNumber != pn)
{
extremelyNotSafeTiles.Add(tile);
sad += increaseBetOnTileSafetyIfEggContested;
}
}
}
}
//check if player can be objective driven
if (!objTiles.Any() && pickedEgg != 13)
od = 0;
//check if there are bets on the tiles
if (eggAmounts.Max() == 0)
//set socially driven to 0
sd = 0;
//check if there are the same amount of bets on each tile
if (eggAmounts.Max() == eggAmounts.Min())
{
asd = 0;
sd = 0;
}
if (!notSafeTiles.Any())
sad = 0;
else if(extremelyNotSafeTiles.Any())
//make sure that the extremely not safe tiles are chosen
notSafeTiles = extremelyNotSafeTiles;
//if the not player is sociably, objective or anti socially driven
if (sd + od + asd + sad <= 0)
//return tile that player was already betting on
return tiles.IndexOf(tileWithBetAlready);
//change the sub fractions in order to "guarantee" their all different
od += Random.value/100;
asd += Random.value/100;
sd += Random.value/100;
sad += Random.value/100;
List<float> drives = new() { od, asd, sd, sad};
//foreach tile
foreach (Tile tile in highPointTiles)
{
//if player is more objective driven, and they have cuckoo egg, and the tile has their target
if (od == drives.Max() && pickedEgg == 13 && DoesTileHavePlayer(pc.obj5Target, tile) && !hasPlayerBetCuckooOnTile(pn,tile))
return tiles.IndexOf(tile);
//if player is more sociably driven, and the tile has the higher amount of bets compared to the other tiles
if (sd == drives.Max() && tile.bets.Count == eggAmounts.Max())
return tiles.IndexOf(tile);
//if player is more objective driven, and the tile is in their objective
if (od == drives.Max() && objTiles.Contains(tile) && pickedEgg != 13)
return tiles.IndexOf(tile);
if (asd == drives.Max() && tile.bets.Count == eggAmounts.Min())
return tiles.IndexOf(tile);
if (sad == drives.Max() && notSafeTiles.Contains(tile))
return tiles.IndexOf(tile);
}
//if not successful then return a random one
return tiles.IndexOf(tileWithBetAlready);
}
/*
TileObjectivePath(List<Tile> tiles,
List<Tile>objTiles,
float sd,
float od)
Function used to return a tile that completes the players objective
tiles is the list of all the tiles that the player can choose
objTiles is the tiles that are in the players objective
sd is how much the player is socially driven
od is how much the player is objective driven
returns the index of the tile chosen
EXAMPLE:
TileObjectivePath(tiles[soil,twig,hay]);
return 1;
*/
int TileObjectivePath(List<Tile> tiles,
List<Tile>objTiles,
float sd,
float pd,
float asd,
float sad)
{
//if there is only one tile that is for the objective
if (objTiles.Count == 1 && pickedEgg != 13)
{
//return the index of the tile
return tiles.IndexOf(objTiles[0]);
}
//if the player is here for the cuckoo egg
if (!objTiles.Any() || pickedEgg == 13)
{
List<int> targetEggAmounts = new();
//for each tile
foreach (Tile tile in tiles)
{
//add tile to target amount list
if(!hasPlayerBetCuckooOnTile(pn,tile))
targetEggAmounts.Add(amountOfPlayerBetsOnTile(pc.obj5Target,tile));
}
foreach (Tile tile in tiles)
{
//the tile has the most amount of target bets on it
if(amountOfPlayerBetsOnTile(pc.obj5Target,tile) == targetEggAmounts.Max())
//return the tile index
return tiles.IndexOf(tile);
}
}
//select a random tile to be the tile the player will bet on by default
Tile tileWithBetAlready = objTiles[0];
//create new list to contain amount of bets
List<int> eggAmounts = new();
List<Tile> notSafeTiles = new();
List<Tile> extremelyNotSafeTiles = new();
List<Tile> highPointTiles = new();
foreach (Tile tile in objTiles)
{
//add the amount of bets
eggAmounts.Add(tile.bets.Count);
//check if tile has high points
if(tile.points >= intPlayerConsidersHighPoints)
//add tile to the point list
highPointTiles.Add(tile);
//check if player has already bet on it
if (valueOfPlayerEggsOnTile(pn,tile) > valueOfPlayerEggsOnTile(pn,tileWithBetAlready))
//if value of bet is higher than last selected tile then change default tile
tileWithBetAlready = tile;
int betValue = valueOfPlayerEggsOnTile(pn, tile);
int playerBets = amountOfPlayerBetsOnTile(pn, tile);
if (tile.bets.Count > 0 && betValue <= pointsOnTileThreshold && betValue > 0)
{
notSafeTiles.Add(tile);
foreach (var player in gm.players)
{
if (amountOfPlayerBetsOnTile(player.playerNumber, tile) >= playerBets && player.playerNumber != pn)
{
extremelyNotSafeTiles.Add(tile);
sad += increaseBetOnTileSafetyIfEggContested;
}
}
}
}
//check if the tile options have bets on them
if (eggAmounts.Max() == 0)
sd = 0;
//check if the tile options have highpoint
if (!highPointTiles.Any())
pd = 0;
//check if there are the same amount of bets on each tile
if (eggAmounts.Max() == eggAmounts.Min())
{
asd = 0;
sd = 0;
}
if (!notSafeTiles.Any())
sad = 0;
else if(extremelyNotSafeTiles.Any())
//make sure that the extremely not safe tiles are chosen
notSafeTiles = extremelyNotSafeTiles;
//if the not player is sociably, objective or anti socially driven
if (sd + pd + asd + sad <= 0)
//return tile that player was already betting on
return tiles.IndexOf(tileWithBetAlready);
//change the sub fractions in order to "guarantee" their all different
pd += Random.value/100;
asd += Random.value/100;
sd += Random.value/100;
sad += Random.value/100;
List<float> drives = new() { pd, asd, sd, sad};
//for each tile in objective tile
foreach (Tile tile in objTiles)
{
// if player is more social and it has the highest amount of bets out of all the options
if (sd == drives.Max() && tile.bets.Count >= eggAmounts.Max())
return tiles.IndexOf(tile);
// if the player is point driven and tile is high points
if (pd == drives.Max() && highPointTiles.Contains(tile))
return tiles.IndexOf(tile);
if (asd == drives.Max() && tile.bets.Count >= eggAmounts.Min())
return tiles.IndexOf(tile);
if(sad == drives.Max() && notSafeTiles.Contains(tile))
return tiles.IndexOf(tile);
}
//if cannot do the above return the tile they where already betting on
return tiles.IndexOf(tileWithBetAlready);
}
/*
TileSocialPath(List<Tile> tiles,
List<int>eggAmounts,
float od,
float pd)
Function used to return a tile with high bets
tiles is the list of all the tiles that the player can choose
eggAmounts is the amount of bets on each tile
od is how much the player is objective driven
pd is how much the player is point driven
returns the index of the tile chosen
EXAMPLE:
TileObjectivePath(tiles[soil,twig,hay]);
return 2;
*/
int TileSocialPath(List<Tile> tiles,
List<int> eggAmounts,
float od,
float pd,
float sad)
{
//make new lists to hold the tiles with the following attributes
List<Tile> socialTiles = new();
List<Tile> objTiles = new();
List<Tile> highPointTiles = new();
List<Tile> notSafeTiles = new();
List<Tile> extremelyNotSafeTiles = new();
int i = 0;
foreach (Tile tile in tiles)
{
//if the tile has a lot of bets
if (eggAmounts[i] == eggAmounts.Max())
{
//add the card to social list
socialTiles.Add(tile);
//is the tile in objective
if (IsTileObj(tile))
{
//add to objective list
objTiles.Add(tile);
}
//is the tile high points
if (tile.points >= intPlayerConsidersHighPoints)
{
//add tile to the high point list
highPointTiles.Add(tile);
}
int betValue = valueOfPlayerEggsOnTile(pn, tile);
int playerBets = amountOfPlayerBetsOnTile(pn, tile);
if (tile.bets.Count > 0 && betValue <= pointsOnTileThreshold && betValue > 0)
{
notSafeTiles.Add(tile);
foreach (var player in gm.players)
{
if (amountOfPlayerBetsOnTile(player.playerNumber, tile) >= playerBets && player.playerNumber != pn)
{
extremelyNotSafeTiles.Add(tile);
sad += increaseBetOnTileSafetyIfEggContested;
}
}
}
}
//increment integer count
i++;
}
Tile tileWithBetAlready = socialTiles[0];
//if there is only one option
if (socialTiles.Count == 1)
{
//return index of the card
return tiles.IndexOf(socialTiles[0]);
}
//check if the tile is in objectives list
if (!objTiles.Any())
od = 0;
//check if the tile options have highpoint
if (!highPointTiles.Any())
pd = 0;
if (!notSafeTiles.Any())
sad = 0;
else if(extremelyNotSafeTiles.Any())
//make sure that the extremely not safe tiles are chosen
notSafeTiles = extremelyNotSafeTiles;
//if the not player is sociably or point driven
if (od + pd + sad <= 0)
//return tile that player was already betting on
return tiles.IndexOf(tileWithBetAlready);
//change the sub fractions in order to "guarantee" their all different
pd += Random.value/100;
od += Random.value/100;
sad += Random.value/100;
List<float> drives = new() { pd, od, sad};
foreach (Tile tile in socialTiles)
{
//if player is more objective driven, and they have cuckoo egg, and the tile has their target
if (od == drives.Max() && pickedEgg == 13 && DoesTileHavePlayer(pc.obj5Target, tile) && !hasPlayerBetCuckooOnTile(pn,tile))
return tiles.IndexOf(tile);
//if they are more point driven and the tile is in high points list
if (pd == drives.Max() && highPointTiles.Contains(tile))
return tiles.IndexOf(tile);
//if the player is more objective driven and the tile is on their objectives list
if (od == drives.Max() && objTiles.Contains(tile) && pickedEgg != 13)
return tiles.IndexOf(tile);
if (pd == drives.Max() && notSafeTiles.Contains(tile))
return tiles.IndexOf(tile);
}
//if they cant find a good option then return default card
return tiles.IndexOf(tileWithBetAlready);
}
/*
PlaceEggInNest(List<int> values)
Function used to return the value of an egg that they will place into their nest
values is the options of eggs the player has to place into their nest
returns the value of the egg chosen
EXAMPLE:
TPlaceEggInNest(values[1,2,3]);
return 3;
*/
public int PlaceEggInNest(List<int> values)
{
//generate a float using the values for random egg and calculated placement
float behaviourChoice = Random.Range(0, placingCalculatedEgg + placeEggRandomness);
//if the float is withing the random ratio
if (behaviourChoice <= placeEggRandomness)
{
//return a random value
return values[Random.Range(0, values.Count)];
}
//divide the preferred egg to place by 10
float value = placingEggPreferredValues / 10;
//round the value to closest int
value = Mathf.Round(value);
//find the closest value of egg we have to the preferred egg
int closest = values.Aggregate((x,y) => Math.Abs(x-value) < Math.Abs(y-value) ? x : y);
//return the value of the egg
return closest;
}
/*
IsTileObj(Tile tile)
Function used to return a bool stating whether a tile is in the players objective
tile is the tile we are checking
returns a bool depending if player has tile in objective
EXAMPLE:
IsTileObj(Tile tile);
return true;
*/
private bool IsTileObj(Tile tile)
{
//check if the tile is their first objective and if the objective is already finished
if (tile.nameOfTile == pc.objective.obj1 && !pc.objective.obj1Done)
//return true
return true;
//repeat for every tile based objective
if (tile.nameOfTile == pc.objective.obj2 && !pc.objective.obj2Done)
return true;
if (tile.nameOfTile == pc.objective.obj3 && !pc.objective.obj3Done)
return true;
//return false
return false;
}
/*
DoesTileHavePlayer(int player, Tile tile)
Function used to return a bool stating whether a player has bet on a tile
player is the player number that we are checking for
tile is the tile we are checking
returns a bool depending if player has bet on tile
EXAMPLE:
DoesTileHavePlayer(int player, Tile tile);
return false;
*/
private bool DoesTileHavePlayer(int player, Tile tile)
{
//for each bet on tile
foreach (Vector2 bet in tile.bets)
{
//check if it is the player bet
if (Convert.ToInt32(bet.x) == player)
//return true
return true;
}
//return false
return false;
}
/*
valueOfPlayerEggsOnTile(int player, Tile tile)
Function used to return an int of the value of all the bets a player has placed on the tile
player is the player number that we are checking for
tile is the tile we are checking
returns an int of value of total egg values bet by one player
EXAMPLE:
valueOfPlayerEggsOnTile(int player, Tile tile);
return 17;
*/
private int valueOfPlayerEggsOnTile(int playerNumber, Tile tile)
{
//set counting var
float num = 0;
//foreach bet
foreach (Vector2 bet in tile.bets)
{
//check if it is the player bet
if (Convert.ToInt32(bet.x) == playerNumber && Convert.ToInt32(bet.y) != 13)
//add the bet to the counting var
num += bet.y;
}
//return the value of the counting var
return Convert.ToInt32(num);
}
private bool hasPlayerBetCuckooOnTile(int playerNumber, Tile tile)
{
//foreach bet
foreach (Vector2 bet in tile.bets)
{
//check if it is the player bet and its a cuckoo egg
if (Convert.ToInt32(bet.x) == playerNumber && Convert.ToInt32(bet.y) == 13)
//add the bet to the counting var
return true;
}
//return the value of the counting var
return false;
}
private int amountOfPlayerBetsOnTile(int playerNumber, Tile tile)
{
//set counting var
int num = 0;
//foreach bet
foreach (Vector2 bet in tile.bets)
{
//check if it is the player bet
if (Convert.ToInt32(bet.x) == playerNumber)
//add the bet to the counting var
num += 1;
}
//return the value of the counting var
return Convert.ToInt32(num);
}
}
using UnityEngine;
public class Objectives : MonoBehaviour
{
public int amountOfObj = 5;
public string obj1;
public int obj1Amount;
public int objPoints1;
public bool obj1Done = false;
public string obj2;
public int obj2Amount;
public int objPoints2;
public bool obj2Done = false;
public string obj3;
public int obj3Amount;
public int objPoints3;
public bool obj3Done = false;
public string obj4;
public int objPoints4;
public bool obj4Done = false;
public string obj5;
public int obj5Direction;
public int obj5Points;
public bool obj5Done = false;
}
using UnityEngine;
using TMPro;
using UnityEngine.Serialization;
public class ShowText : MonoBehaviour
{
[Header("the text box to write to")]
public TMP_Text textBox;
[Header("the current game number id")]
public int game = 0;
// Start is called before the first frame update
void Start()
{
//get all other <ShowText> classes and put them into list
ShowText[] tbs = FindObjectsOfType<ShowText>();
//if its the only one in scene
if (tbs.Length == 1)
{
//set dont destroy on load
DontDestroyOnLoad(this);
//set dont destroy on load on parent
DontDestroyOnLoad(transform.parent);
}
//if there is more than one
else
{
//for each one
foreach (ShowText tb in tbs)
{
//increment the game number id
tb.game++;
}
//then destroy current one as the other one is the main one
Destroy(gameObject);
}
}
// Update is called once per frame
void Update()
{
//set the text to display the current game number
textBox.text = game.ToString();
}
}
import os
import sys
input_file = "export.csv"
export = open(input_file,'r')
df = export.readlines()
output_file = 'output.csv'
maxDataPerLine = len(df)/3
row0 = "GameCompleted"
row1 = "Winner"
row2 = "winnerScore"
row3 = "rounds"
row4 = "cardsLeft"
row5 = "cardsUsed"
row6 = "cardsNotPickedUp"
row7 = "playerNumber"
row8 = "playtestSession"
row9 = "draw"
row10 = "describer"
row11 = "Game ID"
row13 = "Player"
row14 = "Decriber"
row15 = "score"
row16 = "numberOfTiles"
row17 = "cuckooEggValue"
row18 = "eggsPlaced"
row19 = "eggsInNest"
row20 = "playtestSession"
row21 = "objectiveScore"
row22 = "tileScore"
row23 = "eggScore"
row24 = "objective1"
row25 = "objective2"
row26 = "objective3"
row27 = "objectivesComplete"
row28 = "eggsLeft"
row29 = "Game ID"
row40 = "GameCompleted"
row41 = "Winner"
row42 = "winnerScore"
row43 = "rounds"
row44 = "cardsLeft"
row45 = "cardsUsed"
row46 = "cardsNotPickedUp"
row47 = "playerNumber"
row48 = "playtestSession"
row49 = "draw"
row50 = "describer"
row51 = "Game ID"
row53 = "Player"
row54 = "Decriber"
row55 = "score"
row56 = "numberOfTiles"
row57 = "cuckooEggValue"
row58 = "eggsPlaced"
row59 = "eggsInNest"
row60 = "playtestSession"
row61 = "objectiveScore"
row62 = "tileScore"
row63 = "eggScore"
row64 = "objective1"
row65 = "objective2"
row66 = "objective3"
row67 = "objectivesComplete"
row68 = "eggsLeft"
row69 = "Game ID"
row70 = "GameCompleted"
row71 = "Winner"
row72 = "winnerScore"
row73 = "rounds"
row74 = "cardsLeft"
row75 = "cardsUsed"
row76 = "cardsNotPickedUp"
row77 = "playerNumber"
row78 = "playtestSession"
row79 = "draw"
row80 = "describer"
row81 = "Game ID"
row83 = "Player"
row84 = "Decriber"
row85 = "score"
row86 = "numberOfTiles"
row87 = "cuckooEggValue"
row88 = "eggsPlaced"
row89 = "eggsInNest"
row90 = "playtestSession"
row91 = "objectiveScore"
row92 = "tileScore"
row93 = "eggScore"
row94 = "objective1"
row95 = "objective2"
row96 = "objective3"
row97 = "objectivesComplete"
row98 = "eggsLeft"
row99 = "Game ID"
index = 0
for line in df:
if(index != 0):
print(str(index/len(df) * 100) + "%")
if "GameCompleted" in line:
indextemp = index+1
if(index <= maxDataPerLine):
row1 += df[indextemp].strip().replace("Winner,",",")
indextemp += 1
row2 += df[indextemp].strip().replace("winnerScore,",",")
indextemp += 1
row3 += df[indextemp].strip().replace("rounds,",",")
indextemp += 1
row4 += df[indextemp].strip().replace("cardsLeft,",",")
indextemp += 1
row5 += df[indextemp].strip().replace("cardsUsed,",",")
indextemp += 1
row6 += df[indextemp].strip().replace("cardsNotPickedUp,",",")
indextemp += 1
row7 += df[indextemp].strip().replace("playerNumber,",",")
indextemp += 1
row8 += df[indextemp].strip().replace("playtestSession,",",")
indextemp += 1
row9 += df[indextemp].strip().replace("draw,",",")
indextemp += 1
row10 += df[indextemp].strip().replace("describer,",",")
indextemp += 1
row11 += df[indextemp].strip().replace("gameNumber,",",")
elif index <= maxDataPerLine*2:
row41 += df[indextemp].strip().replace("Winner,",",")
indextemp += 1
row42 += df[indextemp].strip().replace("winnerScore,",",")
indextemp += 1
row43 += df[indextemp].strip().replace("rounds,",",")
indextemp += 1
row44 += df[indextemp].strip().replace("cardsLeft,",",")
indextemp += 1
row45 += df[indextemp].strip().replace("cardsUsed,",",")
indextemp += 1
row46 += df[indextemp].strip().replace("cardsNotPickedUp,",",")
indextemp += 1
row47 += df[indextemp].strip().replace("playerNumber,",",")
indextemp += 1
row48 += df[indextemp].strip().replace("playtestSession,",",")
indextemp += 1
row49 += df[indextemp].strip().replace("draw,",",")
indextemp += 1
row50 += df[indextemp].strip().replace("describer,",",")
indextemp += 1
row51 += df[indextemp].strip().replace("gameNumber,",",")
else:
row71 += df[indextemp].strip().replace("Winner,",",")
indextemp += 1
row72 += df[indextemp].strip().replace("winnerScore,",",")
indextemp += 1
row73 += df[indextemp].strip().replace("rounds,",",")
indextemp += 1
row74 += df[indextemp].strip().replace("cardsLeft,",",")
indextemp += 1
row75 += df[indextemp].strip().replace("cardsUsed,",",")
indextemp += 1
row76 += df[indextemp].strip().replace("cardsNotPickedUp,",",")
indextemp += 1
row77 += df[indextemp].strip().replace("playerNumber,",",")
indextemp += 1
row78 += df[indextemp].strip().replace("playtestSession,",",")
indextemp += 1
row79 += df[indextemp].strip().replace("draw,",",")
indextemp += 1
row80 += df[indextemp].strip().replace("describer,",",")
indextemp += 1
row81 += df[indextemp].strip().replace("gameNumber,",",")
if "Player" in line:
indextemp = index+1
if(index<=maxDataPerLine):
row13 += df[indextemp].strip().replace("player,",",")
indextemp += 1
row14 += df[indextemp].strip().replace("describer,",",")
indextemp+=1
row15 += df[indextemp].strip().replace("score,", ",")
indextemp += 1
row16 += df[indextemp].strip().replace("numberOfTiles,", ",")
indextemp += 1
row17 += df[indextemp].strip().replace("cuckooEggValue,", ",")
indextemp += 1
row18 += df[indextemp].strip().replace("eggsPlaced,", ",")
indextemp += 1
row19 += df[indextemp].strip().replace("eggsInNest,", ",")
indextemp += 1
row20 += df[indextemp].strip().replace("playtestSession,", ",")
indextemp += 1
row21 += df[indextemp].strip().replace("objectiveScore,", ",")
indextemp += 1
row22 += df[indextemp].strip().replace("tileScore,", ",")
indextemp += 1
row23 += df[indextemp].strip().replace("eggScore,", ",")
indextemp += 1
row24 += df[indextemp].strip().replace("objective1,", ",")
indextemp += 1
row25 += df[indextemp].strip().replace("objective2,", ",")
indextemp += 1
row26 += df[indextemp].strip().replace("objective3,", ",")
indextemp += 1
row27 += df[indextemp].strip().replace("objectivesComplete,", ",")
indextemp += 1
row28 += df[indextemp].strip().replace("eggsLeft,", ",")
indextemp += 1
row29 += df[indextemp].strip().replace("gameNumber,",",")
elif index <= maxDataPerLine*2:
row53 += df[indextemp].strip().replace("player,",",")
indextemp += 1
row54 += df[indextemp].strip().replace("describer,",",")
indextemp+=1
row55 += df[indextemp].strip().replace("score,", ",")
indextemp += 1
row56 += df[indextemp].strip().replace("numberOfTiles,", ",")
indextemp += 1
row57 += df[indextemp].strip().replace("cuckooEggValue,", ",")
indextemp += 1
row58 += df[indextemp].strip().replace("eggsPlaced,", ",")
indextemp += 1
row59 += df[indextemp].strip().replace("eggsInNest,", ",")
indextemp += 1
row60 += df[indextemp].strip().replace("playtestSession,", ",")
indextemp += 1
row61 += df[indextemp].strip().replace("objectiveScore,", ",")
indextemp += 1
row62 += df[indextemp].strip().replace("tileScore,", ",")
indextemp += 1
row63 += df[indextemp].strip().replace("eggScore,", ",")
indextemp += 1
row64 += df[indextemp].strip().replace("objective1,", ",")
indextemp += 1
row65 += df[indextemp].strip().replace("objective2,", ",")
indextemp += 1
row66 += df[indextemp].strip().replace("objective3,", ",")
indextemp += 1
row67 += df[indextemp].strip().replace("objectivesComplete,", ",")
indextemp += 1
row68 += df[indextemp].strip().replace("eggsLeft,", ",")
indextemp += 1
row69 += df[indextemp].strip().replace("gameNumber,",",")
else:
row83 += df[indextemp].strip().replace("player,",",")
indextemp += 1
row84 += df[indextemp].strip().replace("describer,",",")
indextemp+=1
row85 += df[indextemp].strip().replace("score,", ",")
indextemp += 1
row86 += df[indextemp].strip().replace("numberOfTiles,", ",")
indextemp += 1
row87 += df[indextemp].strip().replace("cuckooEggValue,", ",")
indextemp += 1
row88 += df[indextemp].strip().replace("eggsPlaced,", ",")
indextemp += 1
row89 += df[indextemp].strip().replace("eggsInNest,", ",")
indextemp += 1
row90 += df[indextemp].strip().replace("playtestSession,", ",")
indextemp += 1
row91 += df[indextemp].strip().replace("objectiveScore,", ",")
indextemp += 1
row92 += df[indextemp].strip().replace("tileScore,", ",")
indextemp += 1
row93 += df[indextemp].strip().replace("eggScore,", ",")
indextemp += 1
row94 += df[indextemp].strip().replace("objective1,", ",")
indextemp += 1
row95 += df[indextemp].strip().replace("objective2,", ",")
indextemp += 1
row96 += df[indextemp].strip().replace("objective3,", ",")
indextemp += 1
row97 += df[indextemp].strip().replace("objectivesComplete,", ",")
indextemp += 1
row98 += df[indextemp].strip().replace("eggsLeft,", ",")
indextemp += 1
row99 += df[indextemp].strip().replace("gameNumber,",",")
index += 1
def writeToOutput():
f = open('output.csv','w')#
f.write(row0 + "\n")
f.write(row1 + "\n")
f.write(row2 + "\n")
f.write(row3 + "\n")
f.write(row4 + "\n")
f.write(row5 + "\n")
f.write(row6 + "\n")
f.write(row7 + "\n")
f.write(row8 + "\n")
f.write(row9 + "\n")
f.write(row10 + "\n")
f.write(row11 + "\n")
f.write("\n")
f.write(row13 + "\n")
f.write(row14 + "\n")
f.write(row15 + "\n")
f.write(row16 + "\n")
f.write(row17 + "\n")
f.write(row18 + "\n")
f.write(row19 + "\n")
f.write(row20 + "\n")
f.write(row21 + "\n")
f.write(row22 + "\n")
f.write(row23 + "\n")
f.write(row24 + "\n")
f.write(row25 + "\n")
f.write(row26 + "\n")
f.write(row27 + "\n")
f.write(row28 + "\n")
f.write(row29 + "\n")
f.write("\n\n")
f.write(row40+ "\n")
f.write(row41+ "\n")
f.write(row42+ "\n")
f.write(row43+ "\n")
f.write(row44+ "\n")
f.write(row45+ "\n")
f.write(row46+ "\n")
f.write(row47+ "\n")
f.write(row48+ "\n")
f.write(row49+ "\n")
f.write(row50+ "\n")
f.write(row51+ "\n")
f.write("\n")
f.write(row53+ "\n")
f.write(row54+ "\n")
f.write(row55+ "\n")
f.write(row56+ "\n")
f.write(row57+ "\n")
f.write(row58+ "\n")
f.write(row59+ "\n")
f.write(row60+ "\n")
f.write(row61+ "\n")
f.write(row62+ "\n")
f.write(row63+ "\n")
f.write(row64+ "\n")
f.write(row65+ "\n")
f.write(row66+ "\n")
f.write(row67+ "\n")
f.write(row68+ "\n")
f.write(row69+ "\n")
f.write("\n\n")
f.write(row70+ "\n")
f.write(row71+ "\n")
f.write(row72+ "\n")
f.write(row73+ "\n")
f.write(row74+ "\n")
f.write(row75+ "\n")
f.write(row76+ "\n")
f.write(row77+ "\n")
f.write(row78+ "\n")
f.write(row79+ "\n")
f.write(row80+ "\n")
f.write(row81+ "\n")
f.write("\n")
f.write(row83+ "\n")
f.write(row84+ "\n")
f.write(row85+ "\n")
f.write(row86+ "\n")
f.write(row87+ "\n")
f.write(row88+ "\n")
f.write(row89+ "\n")
f.write(row90+ "\n")
f.write(row91+ "\n")
f.write(row92+ "\n")
f.write(row93+ "\n")
f.write(row94+ "\n")
f.write(row95+ "\n")
f.write(row96+ "\n")
f.write(row97+ "\n")
f.write(row98+ "\n")
f.write(row99+ "\n")
f.close()
export.close()
writeToOutput()
Now if the code counts towards the word count then we are up to over 14000 words, which I think is pretty neat, but we ain't done, baby!!!!!! if you want to read more on this go to week 7 in my weekly logs to learn more!
This unity project had so many bugs throughout development, such as an undetected bug that meant that objective-based players would keep their points from game to game, meaning a bunch of our data was tainted, or bugs that meant the program would crash with certain rare conditions, so when I left the program running in the background for ages, it would crash without us realising, and much much more.
This was a great opportunity for me as it allowed me to practice my digital game development skills outside of college, and honestly, I had fun doing it, it was cool to see something that I enjoyed doing have an impact on the game!
Another spot i had some saying in was the balancing and changing mechanics. for example due to my game, we managed to see that the tiles and objectives needed to change points, so i made a little chart and made sure that the objectives where different, and more enticing and balanced for the players:
this was one of the changes i had an effect on, and the objectives still follow this point system. there are many small changes that i also contributed to, whether that was by brainstorming with my overlord, Igor, or testing on my unity project. But one of the ways I think I contributed the most, was by...
Ooohhhhh yeah, we tested the hell out of this game, but not as much as I expected going into Bioviva, we had monthly tests, at best. but mainly with adults, which is odd for a kids' game, none the less they were always useful. If you're a die-hard fan of my work, then you know in week 9 I went to an Écolothèque to playtest with some of the kids there! we had lots of fun and it was super insightful, and because Igor knows that I'll soon be replacing him, he let me go and playtest on my own, of course, I went with Honourine and Emiline as they were testing their game as well. you can read my results here!
I learned about the importance of explaining things clearly and the dangers of overexplaining, it was good practice for my ability to extract useful information and take notes on things that were not said, but things that happened, noticing how they placed their eggs, what strategies they developed, and most importantly their retention! in the end I concluded we needed to smoothen the gameplay loop, make it more streamlined, and easier to pick up and play. we ended up reducing the number of moving elements in the game, instead of 4 piles of eggs ( your deck, your hand, your discard and your nest eggs) we lowered it down to 3 (your deck, your hand, your nest eggs), this made the game significantly quicker and more streamlined. we changed how the cuckoo egg was played to make it more streamlined and consistent with the other eggs. and many many more changes that I can't remember due to the fact that the game is always evolving it's hard to pinpoint when and where changes were made.
An issue I ran into here, (definitely not adding this to tick boxes) as stated above, was overexplaining. For the first playtest group I had 6 players, more than what the game is playable at now due to this reason, and I spent ages explaining the rules to the game, I was overwhelmed by the questions and trying to think clearly. in the end, I learned that it was best to give up, as Pooh says, sometimes doing nothing leads to the best of something. I let them start playing, if they got stuck, they would either come up with a solution they thought intuitive or ask me, to which I would respond. not the best thing in the world but it worked, they played a full game, which taught us many issues with the game.
My fan collage of the Écolothèque :)
Also as of writing this, I went back to the Écolothèque! This time with Igor and some others to do another playtest! you can read my results here to see what I learned from this time around! In Week 20 I go a bit more in-depth on what I learned, but to keep this evergrowing wall of text I'll leave it at that!
this is a board game, which means I get to play with scissors! I had to physically cut out things to play and test the game, which was fun! but also played a big role in the design, as we had to think about how the players would place the cards, place the tiles, hold the cards, what they could touch, whether was it easy to pick elements up, could kids choke on the elements, how much money was it gonna cost to do something that looked cool, all of these factors could only be tested by physically making and creating!
As a team, we had to physically create board games for our clients at showrooms, and during December we had a lot of them to do, each prototype for FOV had to have -
36 tuiles Matériau (paille, brindilles, mousse, plumes, terre, écorce)
36 supports de nid
10 Nids
65 cartes Œuf de cinq couleurs différentes
1 jeton 1er joueur
which is a lot when each element has to be printed onto a sticker sheet, cut out, aligned onto cardboard, stuck on with no bubbles, cut out from the cardboard, aligned onto another sticker sheet, stuck on and cut with new sticker sheet for each element, one box would take hours to make, so when we had an order of a couple dozen prototypes between all of our upcoming games, it was a big task, we had the whole office helping out which was fun.
As you can expect our biggest enemy here was papercuts time, but we made sure to keep our pace with group music and good banter.
Igor waiting for me to finish making a FOV Prototype
cc BioDev (09/11/23)
oh boi, this one, yeah this was a bit controversial lol.
Bioviva is an educational-focused company, we strive to educate and have fun, which I love, it adds challenge to the design which I will explain more in the Escape booklet, but it also can lead to some tough design decisions.
While the game was being presented to clients, they gave us feedback and said that the game was fun to play, but that it would be hard to sell. why you cant ask? Because a game about realistically doing what birds do doesn't sound fun.
and while games like wingspan work, we aren't selling it to the same audience, so don't even get me started.
I did some research by looking at the top games of our clients, seeing what they do and finding out if there is a pattern or secret winning formula. I came up with this GoogleSlides:
which without me presenting is useless :) The conclusion is that we're gonna make the birds gamble instead, as it creates more of a "WTF is happening" moment when a potential consumer sees it on the shelf, and what kid doesn't want to learn how to gamble!?!
you can read more about this wacky presentation in week 15 :) or if you don't care / already did it, continue to my next topic:
This is my main project when it comes to Bioviva, Defis Nature Escape: Aventures Polaires ( or the escape for short) is a booklet where the player is going to complete puzzles on a page, the puzzle will reveal the next page number, this continues until the end of the story of the booklet. it's a game that uses the hit card game defis nature to tell an educational and fun story. in each escape, there are 2 booklets and an interactive element. I created the second booklet and the interactive element for this game :)
This title was about a polare adventure, and I did it on the arctic!
it was very fun to do and really pushed me in terms of game design, narrative design, playtesting, planning, coffee drinking, overtime, printing, drawing and much more that I will talk about. its what I did most of the time at work, due to the fact that Igor allowed me to do what the hell I wanted with it. and although I'm pretty sure he regrets that, I'm happy he did. the game will be released in March and I can't wait to see kids suffer through it.
The latest printed booklet as of the picture being taken
Images of me working on the escape, this was definitely taken while i worked and not when I noticed I needed a picture of me working in the rubric of the assignment :)
Me working at Adelines my clean desk
Now technically I can leave it at this, as I'm over 8 pages in (not including the code) but luckily for you, I'm having fun writing this. And I want to log everything I do for my portfolio and stuff :)
So I'm gonna split this into sections and explain how I went about it, good luck, you'll need it.
I was told about the escape in my first week at Bioviva, Igor, who at the time didn't know who I was, what my skills were, or if I was his intern or just a dude who walked in the door, told me that I was going to do the second booklet of this next escape on my own, I was going to make the story, the games, the writing, the design, on my own, with just his supervision.
Like a good stereotypical male, I held in my anxiety and suppressed my tears, my first "assignment" was to play all the previous escapes to see how they worked and what to expect. As I played through the games I noted what I liked about the games, and what I disliked, I noted what elements I wanted to inspire from and which ones I wanted to avoid.
After I started concepting the story, jotted down ideas on how I wanted the story to flow, what animals I would eliminate, and the difficulty curve. Below is my black book of ideas, its one of the three note books i have for game design, one for ideas and story (black book) one for ideas and notes (yellow book) and one for game layouts and sketches (big drawing book) organising and splitting information is important (i learned this here especially)
Playing each escape took time, and took up most of my time for the first week at the company. and it took probably longer than it should have.
I'm French, well I have French citizenship, but I've never lived there fully, I've only really been on holidays. I learned French through my mom, but always refused to write, so I struggled to spell, read and understand puns, for these escapes, I struggled to understand everything the first time around, an issue I didn't foresee. Throughout my time here I improved, and now my spelling and reading have vastly improved :)
This issue of reading and writing stuck through the entire development, but thankfully Igor and the rest of the team were understanding! shoutout to Raph who explained all his puns and insults to me :)
The story is my file "fil rouge" as Igor says. I'm writing a storybook essentially, if the story flops or is bad, the game is bad, it's a narrative-focused game at the end of the day. So from early on I started jotting down ideas. here is my first story idea that I recorded:
A young adventurer who is helping a scientist on a mission,
They have heard about an animal who needs to be rescued in the ice,
But it isnt going to be easy! There are big fishing ships that dont want you seeing the real state of the arctic!
As you move from the sea in your sailing ship, to the iceburgs on foot, you realise that the artic not what you expected, although there are animals there, they all need help as their home is slowly melting!
(As you help the animals and learn about them you eliminate cards)
As you get closer to the location of the animal that need help you try to find the animal that needs help! It end up bieng an animal entangled in fishing lines that ison a little iceberg, if you hadnt came it would perished from the melting ice and from the polution of over fishing!
It's the barebones and riddled with spelling mistakes! but it was a start, I kept developing this further, and I went back and took notes from my previous narrative design lectures to start getting things down.
I managed to find notes on archetypes, character design, world-building and much more. so I started building my world for my story!
Archetype:
The quest
The hero (player) oftern accompinied by sidekicks, travels in search of a preceless treasure and must defeat evil and over come powerfull odds, and ends when he gets both the treasure and the girl
World building:
World is set in 2024
In this world its the fishermen who control the artic, no one esle can operate or be there without thier permission. Fishermen are first, and animals are at the bottom of the list.
Protqgist is the player
Characters:
Scientist
spotter
enemy fishermen / fisherwomen
note: these people are not bad people, they are forced to be carless and mean from their captains
owner of the trawlers
Geography:
Open ocean followed by icebergs getting denser and closer to eachoter as you aproach the big ice of the arctic, where the "mainland" is a dangerous land filled zith crevases and deep rivers cause by the melting ice.
Around trwlers there is noticably less wildlife than there is in other areas.
What is good in this world?
The will power and optism from the crew on the sailing ship
What is bad in this world?
The ruthlessnes from the fishermen in this world
The pollution caused by the twalers
The scientist:
confident when their feild of expertise arrises, for example interesting facts and info about the animals, but losses all confidence once they are confronted with challenges, aka the puzzles the player will encounter, (has terrible sight to give reason to not nieng abe to help in all casses) the scientist works in animal conservation and most notably artctic animals, they have never worked these qnimqls, only ever studied them in books, and never saw them in the wild, meaning they are always super excited to finnally see them in their natural habitat. Notably they are terrified of danger, they always run and hide in their office when danger arrives, taking a bit of time to come back out once the danger has past.
They recieved a letter from an annonymes fishermen/fisherwoman stating that their was an animal that was in danger and if nothing was done then the overfishing in the area would continue to endanger the widllife so without hesitating the scientist asked if there was a zero emmison ship and a crew that was willing to sail to the dangerous arctic in order to save the animals from the big fishing companies. Within days they hastilly moved their lab into the ships quarters, meaning it looks super messy but has everything they need to continue researching and studieng the animals
The scientist always wears ther lab coat, and has a pair of glasses, when their is uslefull info to help the player the scientist will be seen ajusting their glasses, if its just cool info for the player but not necessary the scientis will be smiling with glee and excited, then for not puzzles and challenges the scientest glasses will be crooked and they zont seem to happy about the challenge,
Spotter:
Super enthusiastic and sparkly the spotter never backs down and is always on the watch from the eagles nest! They are quick thinking affereing some impromtu tools to the player if they need it, they live for the sea, working on many traditional sailing boats they know how everything works on it, they also know a lot about the personality of the animals, not necesrily about the scientific background, poteentially giving hints about eating habbits, or unique visually percivable facts about animals. They are always there to help they player, except if the challenge is based on scientific accuracy, but visual and interactive puzzles they can help.
They were te second person to apply to the crew, after the player of course, and wanted to find out more about the animals in the area, only ever seeing them for short periods of previous expiditions,
They always wear a spotted bandana like a pirate, and speaks clear and concicely, whenever they spot something they anounce it while pointing, whenever they give infor their hands are on their hips and chest puffed out to show they are proud of their knowledge, and if they are out of their depth the have their eyebrow raised and head forward o show they have no clue whats happpening, while stille showing their interested in learning more.
Enemy fishermen / fisherwomen:
These people come off as angry and determined, but they arent the smartist, they tend to throw themselves towards the player and try to obviously sabotage the player, they are not hiding that they want the sailing ship to leave. They dont communicate to the player, hidig their emotions and de huminising them a bit, meaning the player wont feel as bad if they fall overboard.
They always throw a challenge to the player before their captain comes to stop the player, acting as a warning and comic relief before the player is meet with a big challenge,
They dress in big red overalls and some will wear hats, their tops change colours depending on the character
One will wear a green jumper and have blond hair, they will be a reacurring fisher and be the one that sent the note to the scientist, no ones esle will share the hairstyle, juper and hair colour
owner of the trawlers:
This person is in charge of the money, they wear a dark suit that blends in with the dark polution they create, they are increadibly smart but also over confident, they keep themselves hidden and are vigiliint, they make their way from ship to ship, shouting orders to the fishers in order to stop you, money is the only thing on their mind, if confronted by the player, they are angry and scared, sort of wobbly voice, (suprised and scared that someone dares to rise up to them) they are desperate to get everything they want!
They will be the last challenge in the story, revealing themselves with the informent. They will throw everything they have at the player to stop them!
they wear a dark suit and a red tie to make them stand out, a bit like the Gman
Remember Bioviva is an education and environmentally friendly company, so they try to push a message with each game they release, whether that's food chains (Dino Picnic) or bees (La Ronde du Pollen). And this wasn't going to be an exception. I decided to base the booklet on overfishing and its effects, Inspired by SEASPIRACY. So I focused on fishing and the animals which it affects, which is all of them. to avoid being a hypocrite I became a carnitarian, someone who eats everything but fish. this was tough as I love sushi, but in the end, I ended up giving up most of the meat in my diet to, darn you Bioviva!!
But I needed inspiration for my story, a line to inspire myself from. Originally I went down the line of my childhood hero, Ernest Shackleton, I loved his stories and his life story! I read more books about it and did a lot of research, originally the story was going to be based on a big sailing ship as it is 0 emitions and that is good!
But one night as I was hydrating with some colleagues, one of them, Julian, a guy on the commercial team explained to me that the game was originally going to be made in partnership with UNO MONDO! An organisation that leads arctic expeditions for scientific research aboard the Northabout, an Irish-made sailing boat made to sail in the harsh arctic conditions.
BINGO! I now had a real boat with an interesting story to inspire from! and I did, I changed from a big sailing ship to a small sailing boat, modelled to be a replica of the Northabout!
My fan collage of the boat :)
I got an interview with the Uno Mondo team to learn more about life at sea and about the Northabout! I got inspired by some of their stories and really loved learning more about them!
Just that summer I had gone sailing in Poland for a week, so I was all in for this, I loved the idea of it! And it got me hopefully as often they took people that didn't have a lot of sailing experience on their voyages, one day I hope to go and sail with them! but other than that I inspired my story off of it. I decided to name my boat Endurance tho, as I felt like it was best suited as a sister of the Northabout, the name of it being the name of the ship that Shackleton sailed many years ago.
as I developed the game, I kept in mind the size, history and capability of the boat, being accurate with its use and skillset was important to me.
A surprise addition to the story nearing the end of development was a couple of chocolate jars I requested into the illustrations. During December I watched mountains on stage, a mountaineering film event that happens 2 times a year. As I was watching one of the mini-documentaries Via Sedna, I noticed something similar, by coincidence the Via Sedna sailed on the Northabout, following very closely the route I had planned for my story!! They did a climbing expedition in the north of Greenland, which is sick seeing as I love rock climbing and the northbound! AAAANNNDD On top of that, two of the crew members, Nadia Royo Cremer and Capucine Cotteaux, were at the cinema with us!! so I got to ask them questions and learn more about the boat! In their honour and reference to them, I added some chocolate pots to the illustrations, as during their voyage they packed a lot of it to stay happy, but unfortunately ran out when they were halfway through the expedition. It was really cool to have this little piece of lore in the story, and although no one will see it, it is something I can think of every time I see it, or maybe one of the crew members will notice it and smile.
One thing to note is the story plot and bad guys, I originally wanted the story to be, fishing is bad, but then I realised that fishing itself isn't bad, it's how it's exploited. So I did what any sain person would do and copied and pasted the Borderlands 2 story into mine.
Yes, that's right, my kid's story is inspired by the 18+ game about blowing people up and overthrowing a psycho serial killer who is killing for the sake of getting what he wants. except substitute the blowing up people for cunningly freeing animals, (the action) and the serial killer for a businessman (bad person) who is harming (can't say kill in front of kids) animals for the sake of money (getting what he wants) as stupid as it seems, borderlands to was actually a big source of inspiration for the story when it came to ideas.
=
My main struggle with the story was linking the cards that were being eliminated through the game, they had to make sense both for the story and scientifically, I couldn't talk to the animals, or show two animals that wouldn't be together in nature. One way I got around this was by using my bad guys! the fishing boats would wrangle up and trap animals that shouldn't be together, this helped the message I was trying to convey and allowed me to justify animal links where there sometimes were none.
Ohh yeah, who would have guessed I would talk about game design... Me, I did cause it is the topic of everything I do in this report.
The first step of making the actual game was planning the content, AKA the cards the player would play with, this took a while longer than expected cause I had to research if the animals came from the same places and if I could link all animals together. As I'll explain momentarily, I messed this up quite a bit. Here is the first list:
Renard polaire = Arctic fox
Pygargue empereur = Emperor eagle
Ours polaire = Polar bear
Petit pingouin = Razorbill
Requin du Groenland = Greenland shark
Phoque à capuchon = Hooded seal
Lièvre arctique = Arctic hare
Phoque annelé = Ringed seal
Narval = Narwhal
Flétan = Halibut
Rorqual boréal = Sei whale
Lagopède des saules = Willow ptarmigan
now the Emperor Eagle is Italic not because it is fancy, but because that bird does not show up anywhere near where the other animals are, I started developing and making a story cause I assumed that the bird could fly everywhere. But then I checked the IUCN Red List, the place we get most of our info from, and saw that it only lived in a small part of the world in Russia. My boat didn't go near Russia... So I changed the story a bit and substituted the Snow goose! and ohhhhh boy, did that open a can of worms, not literally.
This little scare halfway through development made me double-check everything! Originally I was meant to end on the Willow ptarmigan, (I can't even say that in my head don't worry) but that bird for soooooommmmmmeeeee reason, didn't go near Greenland, it avoided it like the plague, but its cousin, the alpine Parmegan was happy in Greenland!! and I couldn't use it due to the fact that we had already sent copyright malarkey out. so after a week of panicking and changes, I found the Willow Parmegan lived in Ireland, how convenient seeing as my boat was sailing past it. so I chucked in a page about the cliffs of Moher and the Willow Parmegan and called it a day. sike!!! it took me ages to think of how to fit it into the story, how to eliminate the card before leaving the coast of Ireland, and it meant I would have to choose another animal to end with. But I persevered and learned from my mistakes. Instead of choosing a bird that's from Ireland, I choose the Razorbill! a bird native to the Irish coast! but also Greenlands coast so don't get me started again. I am from Ireland and by God I was going to make sure that my Irish boat was going by the Irish coast to save an Irish bird! in a French story in the French language, made in France inspired by a French company. I don't think Igor noticed all the Irish stuff, but hey it's my booklet now.
here is the final list of animals with their characteristics included!
As you can see which animal has a length, weight, longevity and gestation/incubation period. This is super important and useful as it allows me to design my games around finding an animal, and then telling the player to go to that animal's longevity for the page, if they don't bother completing the puzzle then they have to check the 12 animals, which is doable if they want to be a filthy dirty lying little cheater. but most won't bother cause we live in the age of TikTok where if a task lasts longer than 5 seconds they give up.
This ability to link animals through their description and characteristics meant that I could easily and interestingly link the story and go from page to page without letting the player know where they are going next!
I wrote most of my ideas into my sketchbook, scribbling things like the difficulty curve I wanted to follow, I did over 25 pages in that book, here are some of them to flick through if you wish, it details games, the quadrant (which ill explain later).
as highlighted here I'm doing difficulty curves that follow the basic principle of increasing difficulty till a test, then reset, but not fully, then continue for a total of three "chapters" and end with a big hard finish and a little bit of room at the end to relax and not end abruptly, This curve would define the games I make, the story that follows and the pace of the game.
Where did I keep track of my work? an excellent question I ask myself every time I start working! and the answer is simple, everywhere! As mentioned above, initial ideas ended up in my notes and sketchbook. then once I got an idea I liked, I would put it into the master Excel that Igor gave to me, this excel was a lifesaver! it allowed me to write what the story, game, cards used, eliminated, and referenced on each page. Completing the booklet before touching InDesign. For prototyping my ideas I would use a mix of Photoshop and Illustrator, 2 software I got pretty quick at using for prototyping. here is the latest version of this Excel, I must warn you that when I moved onto InDesign I no longer needed this Excel, and worked entirely on InDesign!
You may notice my favourite language on this Excel, Frenglish, a mix of French and English, The amount of back and forward translations I had to do is pretty funny tbh. I work in English, but my final work is in French, so somewhere along the way I have to do my translation, which ends up in documents you can only read if you understand both languages!
One of the most important parts of the project to keep track of was the story! It was what all the games and flow of the game were based on. here are my story documents, I had to zoom out to the max on the documents, then zoom out to the max on Google to fit all of it in, it's a lot of words!
The Flow of my work looked something like this:
continue a part of the story (context)
Brainstorm themes and ideas
Prototype game if needs be
Implement the game into InDesign or Excel
But the Idesign slowly took over everything during November, and by December these documents became obsolete, this was to make sure I didn't get blinded by information.
Looking at the sketchbook that I scanned above, You can see that some pages start with "Theme:" This is so I had a basis when making the games, For example for one of the pages the theme was going up, so I brainstormed some ideas, got inspired from the original jump man by Nintendo, and ended up with a game where you bring you answer questions and move animals up on the page. "WhAt Do YoU MeAn 'InSpIrEd By NiNtEnDo?!?!?'". Well, most games are based on something. No idea is original, it's all about how you present it and improve it. For example, if you remember my story is based on Borderlands 2, inspiration can come from anywhere! whether something should or should not be an inspiration for a kid's game is another debate.
So after making sure that the puzzle fits a theme tied to the story, I've jotted down a few ideas. I take my favorite idea, draw it in Illustrator, and put it into my booklet for testing! let's take a closer look at a specific page to see what I mean and show you my workflow.
Here i define my themes for the game on this page.
Then I note some games that remind me of the themes. Inspiration for the game.
Initial ideations and how I want the Game to look / flow.
Elements I want to incorporate in the game / how to progress.
Ideas with implementations of incorporating elements that i want.
What Emotions I wanr the player to feel at different stages of the game.
First implementation in the booklet.
Tested version that has been altered based on user input.
This is a very very shortened version of the process. It skips over the page placement that has to be done and the thought process for that, but that's, and is more User Experience things, such as having the instructions first, presenting the goal and play area, and having a reading space in an open area that doesn't "matter" so that the player can comfortably write without blocking information. There! I ended up explaining it anyway!
Then I just repeated this process for each and every page!!
Ohhhhhhh the beautiful quadrant! this is my pride and joy for the Escape! this perfect brilliant unbeatable idea came to me one dark dreary morning, as I was wondering to myself... "What can I add to this game". honestly, I don't remember what I was thinking, but I do know the idea came to me around the second week of my internship! I had my first idea for my story, the big sailing ship inspired by Shackleton, and wanted a tool that the player could use to navigate the game. So I first looked up a sexton, and quickly realised that the sexton is a very complex tool! At the end of the day, we are making a kid's game, this means we need to remember that kids are both smart and extremely stupid, so we can't have anything too complex for them to do or use, especially if it's a core feature of the game! So I started exploring and searching for alternatives. through extensive scientific research on Google Images and Wikipedia, I landed on the lost brother of the Sexton! can you guess? (don't cheat by reading the heading of this section) That's right! it's a compass!!!!!!!!!!
Initial idea of quadrant
Nah it's a Quadrant, got you a bit confused, didn't I :) The Quadrant is a tool used by early navigators to calculate their latitude while on the seas, working similarly, but not at all, like a Sexton, you point it towards the North Star and then a piece of dangling string will tell you your latitude! It is so simple that even a snot-eating little monster could use it!
But of course this lead to the first slew of issues. MOOOOONNNEEEEEYYYYYYYYYYY!!!!!
Seeing as we need to keep our games made in France and 100% cardboard and recycled stuff, we can't just print whatever we want, this provides restrictions on how we can design the quadrant. First off each element needs to be Flat, with no 3D printing, or plastic moulds. so cardboard it is, and that brings its slew of issues, such as structural integrity and ease of use. At first, I designed without thinking of these restrictions, but then my overlord Igor graced me with his knowledge and experience on the subject. Showing me previous examples and explaining why we were restricted in all these aspects. the main thing outside of the environment was budget, hence why we couldn't make it out of wood!
I started a lengthy design process, Trying out different ideas that I came up with, none of these ideas hit the 3 criteria of intuitive, fun and printable. Most ideas fell flat, as I was getting stuck on the idea of having a moving part, which was not only a bit harder to make printable, but also super unintuitive! So one day, i decided to scrap everything and make a bunch of new ideas, unrelatited to my initial ideas!
As you can tell, some of these ideas still use the movement aspect i mentioned above! except for one, the last one, the holy grail of this hell of prototyping! it was intuitive, fun (hopefully) and easily printable!!!!! PERFECT!!!!!!!!!! WOOOOO!!!!! sorry i get a bit excited, I've been typing away for the past few hours and my fingers hurt.
After testing all these ideas on my poor colleagues, I immediately noticed something was special on this last design. So I iterated and iterated and iterated and iterated and iterated and iterated and iterated and iterated and iterated... you get the point.
[insert iteration montage!
Ohhhh yea, that's beautiful, in the end, the Quadrant has 2 points which can be lined up to reveal a third, this allows us to make puzzles that reveal stars on a star map and corresponding points on the quadrant, AKA find the north star and all the quadrant, revealing the latitude! BOOM!
But I didn't stop there, "What about the third hole?!" well little Timmy let me tell you! If you cut out the third hole attach a piece of string, and line it up with the North Star.... it tells you your real latitude!!!!!!!!!! I didn't get paid enough for this... but seriously it's a semi-accurate measurement, which allows the kids to keep using the elements of the game after completing it, allowing them to go on their adventures! I know, I know... I'm a genius, but alas, the fun and joy this project brought me made me realise that I love the iterative design process, killing off your ideas and slowly punching your ideas until they shape into something beautiful. figuratively not literally of course.
my booklet has pwetty pictures :) well, I think it does, as of writing this I've only seen the rough drafts of them! due to the college being a little stingy and saying my work placement can't cut into my second semester, I didn't have time to finish the booklet :( big sad moment. but I did plan what illustrations would be shown in my booklet! you wanna see them? well too bad, they're not my drawings and I can't show them (also I'm not bothered asking for permission, this report is long enough. but here is the big brief document and ref images if you want to read through it and see what sort of things I wanted to show in the booklet!
Pour rappel :
- Les dimensions d’une page de livret : 17 cm de haut sur 13 cm de large
- Quand une information est soulignée, c’est qu’elle est importante à faire figurer.
- Quand une information est soulignée et en gras, c’est que cet élément revient plusieurs fois dans le livret et doit donc être représenté de la même manière à chaque fois.
- Un renvoi vers une illustration (cf suivi d’un chiffre) indique quelle illustration doit être la référence pour les autres occurrences.
- « (ref illus XX) » indique qu’une référence portant ce nom fait partie du dossier pour aider à comprendre ce qui est souhaité.
- Les textes doivent toujours être sur un calque séparé car nous devront les réécrire en cas de traduction du jeu.
01 = Illustration pleine page
L’endurance (ref illus 001, 012 ,013, 014, 015) (replic de Northabout)
vue sur le côté du bateau, avec la rampe qui se dirige vers le joueur.
Delphine (cf 02) tend la main pour aider le joueur à monter sur le bateau.
il y a un plein pot de chocolat caché dans la scène
02- portrait
un portrait de Delphine souriante (cheveux bruns, vêtue d'une épaisse veste d'hiver jaune, yeux bleus, elle est patinée par la mer avec quelques rides)
03 – ½ page
JEU.
grille 6x6
une image d'une cabine de bateau à voile, il manque des espaces (les garder vides et blancs) qui seront remplis avec les éléments du jeu (droit)
(ref illus 002)
04 - ½ page
une illustration du capitaine du bateau de pêche Vollauste (cf 06) en gros plan, il veut que le joueur quitte le bateau et cela se voit.
le capitaine (le capitaine est un homme bien habillé, avec une belle montre, des bagues et un bracelet. il a une belle barbe bien entretenue) parle et son manteau porte l'emblème d'un harfang des neiges et un logo qui represent lanimal stilyse. (ref illus 004)
Il y a également un signe de harfang des neiges stylisé sur la porte en arrière-plan.
le harfang des neiges stylisé est de face et se distingue de son environnement. le joueur devra être capable de dire que l'emblème est un harfang des neiges pour le jeu et l'histoire.
05 - ⅛ page
un renard polaire (ref illus 005) tête dans la neige, remuant la queue. on ne voit que le bas de son corps, sa queue et ses deux pattes arrière
06 – ½ page carre
Le Vollauste (ref illus 003) pêche de nuit, un queue bébé rorqual boréal est coincé dans son filet. À gauche, Delphine (cf 02) de L’endurance (cf 01) lance un appel en direction du bateau de pêche. Les pêcheurs doivent avoir l'air malheureux, la pêcheuse (cf 16) est également présent, et le capitaine (cf 04) regarde l'équipage travailler.
harfang des neiges stylisé (cf 04) figure sur le flanc du Vollauste.
07- ½ page
Un bateaux de peche (pas le Vollauste) est montré de nuit. Au centre, nous voyons un escalier menant à une porte marquée d'un symbole d'infirmerie. Delphine (cf 02) est aidée à monter les marches par un pêcheur portant une blouse de médecin. Sur le côté gauche, il y a une zone ouverte se dirigeant vers la proue du bateau. Sur le côté droit, il y a une porte légèrement ouverte, indiquant qu'elle mène à l'intérieur du bateau.
harfang des neiges stylisé (cf 04) figure sur le flanc du bateau
08 – ¼ page
scène sur la côte glacée du Groenland, un renard poursuit un lièvre.
d'autres lièvres se cachent dans la neige
09 – 1/4 page
Delphine (cf 02), qui marche a reculons faisant face a la silhouette de l’ours dan le brouillard.
10 – 1 page
un phoque sur un petit morceaux banquées en train de fondre, quelques morceaux de filets de pêche flottent dans l'eau et de déchet.
11 – ½ page hauteur
les falaises de moher de face with the obriens tower on top (ref illus 006)
12 – ⅓ page carre
Delphine (cf 02) ramasse un “ ballon “ mais il s’agit en fait d’un phoque à capuchon, le phoque et Delphine ont l’air surpris de se voir, à l’arrière-plan on voit la silhouette d’un ours (cf 09) dan le brouare.
13 – ⅓ page
JEU
le pont extérieur du bateau de pêche vue de en haut,
4 grosses casse d'où sortent des flétans,
mur de boîtes sur le côté droit, pas d'interaction (faire en sorte qu'il soit visuellement différent)
et des barrières métalliques tout autour.
sur la gauche il y a le bord du bateau qui mène à la mer, il y a un trou au milieu indiquant que les boîtes doivent être jetées ici.
(ref illus 007)
14 - ⅔ page carre
Photo
une photo en très ancein et abimé, le capitaine (cf 04) plus jeune et avec sans montre ou bijoux. le homme d’affaires (cf 18) serrer la main, il semble plus jeune, et il n'a pas de cravate l’homme d’affaires a un billet de banque dans l’autre main.
Un panneau indique que l'objectif / le prix est le flétan, et que les requins et les phoques sont interdits / concurrents. (ref illus 009)
15 – ½ page
la côte française en arrière-plan Delphine (cf 02) est à la barre du bateau, l'Endurance (cf 01).
Il y a une ballon rouge (cf 03) sur le pont. Il y a un plein pot de chocolat caché dans la scène.
16 – ½ page
3 pêcheurs avec un numéro sur un badge, le numero sont 17, 29, 31
attitude neutral
la pêcheuse (une jeune fille blonde, toujours habillée d'une combinaison arctique rouge. elle a un piercing à la narine droite. yeux bleus-verts) elle un regard triste, son badge indique 28
attitude réserve et inquiet
et enfin, le capitaine (cf 04) que nous avons vu lors du sauvetage du rorqual boréal, son badge porte le numéro 6.
attitude arogant
17 – ½ page
Photo
quelques bateaux de pêche qui ressemblent aux Vollauste (cf 06) sont alignés. Ou centre de limage, un pêcheur triste tient un poisson, autour de lui se trouvent de l'argent et des pièces de monnaie, dont certaines tombent dans l'eau, ce qui est censé représenter le fait que l'industrie ne se préoccupe pas des pêcheurs ou des poissons, mais uniquement des profits.
harfang des neiges stylisé (cf 04) sur le flanc de bateau.
18 – ½ page
Photo
il y a une manifestation à côté du Vollauste (cf 06), et on voit le capitaine (cf 04) se faire payer par homme d’affaires (ref illus 008) , tous deux souriants
harfang des neiges stylisé (cf 04) sur le bateaux et sur le panneau de proteste et la pêcheuse (cf 16) est devant la foule et brandit une pancarte contre le harfang des neiges stylisé (cf 04).
l'homme d'affaires tient un billet de banque
19 – 1 double page format largeur
Delphine (cf 02) tient la photo (a gauche), elle nous tend une photo. un pot de chocolat à moitié plein est caché derrière elle.
La photo :
il y a un équipage de pêche, devant la Vollauste (cf 06). l’équipage n’a pas l’air très heureux et ne sont pas proches les uns des autres comme on pourrait l'attendre d'une photo de groupe, à côté d’eux se trouve et la pêcheuse (cf 16), elle a l’air la moins heureuse.
harfang des neiges stylisé (cf 04) et sur le bateaux et sur le capitaine (cf 04) et homme d’affaires (cf 18) en arrière-plan
on voit l’homme d’affaires (ref illus 18) a deux billet de banque à la main
20 – 1/4 page
un bébé rorqual boréal heureux humoristique
21 – ½ page carree
un narval
un requin du Groenland (cf 22)
un flétan (cf 13)
(ref illus 010)
23 – ½ page
Vollauste (cf 06)) est à droite, la pêcheuse (cf 16) porte la casquette du capitaine,
l'équipage est heureux et ils la tiennent en l'air pour la célébrer.
l'Endurance (cf 01) est à gauche, on voit Delphine (cf 02) faire signe à l'autre navire,
le soleil se lève au milieu des deux bateaux
,un pot de chocolat à moitié plein est caché sur l'Endurance (cf 01)
Illus 007
Illus 011
Illus 014
Illus 005
Illus 009
Illus 012
Illus 015
Illus 001
Illus 006
Illus 008
Illus 010
Illus 013
Illus 002
Illus 003
Illus 004
Yes, that's the G-Man, don't question it... you may also notice the number of pictures from the Northabout. It's my fav boat don't judge me.
So we worked with an illustrator and had some back and forwards before I departed, in the end, one or two things from the brief changed, but nothing major!
In the end, I couldn't be here for the release of the project :(
I would've stayed, but for some reason, and I just can't put my finger on it, I decided not to stay till the release of the game.
But aside from that, here is the latest version of the booklet! You can print and play it (that's piracy and I will know and I will come for you) or go out and buy it!
Beautiful I know, *sniffs* if you need tissues *sniff* get them yourself *sniff* ohh it's soooo beautiful, I could stare at this for hours!
and that concludes the Escape booklet, i mean i could talk about more as there are so many details, but in the end, this report is wayyyyyyyy too long, so I'll keep it brief and if you want to learn more about it, feel free to contact me :)
Did I do what I set out to do? Did I achieve my goals and learn from this experience? Did I enjoy it and am I proud of what I did? Well if you scroll up to the start and time it, I think you'll get your answer in the form of a score out of 5, depending on how long it takes you to scroll it is probably over 5/5. So yes I loved it! I achieved my goals, and I'm proud of what I completed!
I learned many great things about Game Design, Narrative Design, Prototyping, Teamwork, Coffee making, Collaboration, Game Development, Budgeting, restrictions, the Game market, Game Publishing, and Printing to mention a few!
I learned how to master software such as Photoshop, Illustrator, InDesign, Google Sites, Adobe Acrobat and a few more that I can't think of right now.
My goal as mentioned at the start was to make and release a commercial game, and through my escape booklet, I've done that.
I think this report shows how much passion I had for these projects. I loved the Team, and the values they had, you can tell that everyone in Bioviva believed in what they did.
I have become a better Game designer from this experience, and I can say that without a doubt, it opened my eyes to the world of board games. It also showed me the state of the state of our world's natural beauty, and how we are all going to be dead in a few years if nothing is done and how we can use games to convey a message.
During the last six months, I've been learning so much about nature, and the second I get back into college my first assignment is to simulate it for the metaverse, thanks Brian, which has made me realise that I prefer the route Bioviva took, in making games that make us aware of what's around us, and not try to fake it or replace it. I think this is going to be something I keep doing in my games as it's really frightening how people in this concrete jungle are unaware of the impact they can have with their games. My goal from now on is not to make games for the sake of making some money, but instead use my games to have an impact on something in the real world. I have always thought that our games can't have an impact on the real world, and I agree, but why can't they make us aware of it and give us the choice to do something about it?
Thank you for enduring my report! If you have any queries, feel free to contact me on my contact page which you can find here!
I'd like to dedicate this Report to my girlfriend Delphine, who has to deal with my rambling and overly long explanations, as you can tell it happens a lot!
Also, I want to apologise to Séamus Hickey who has to correct this, I'm so sorry.
This report is a total of 12,205 words, 66,225 characters, over 27 pages if you don't include the coding snippets and comments! If I added the code and comments that would ADD 10,721 words, 109,935 characters and 23 pages. So let's not add that I'm already a couple of pages above the recommended...4 pages :0
Total hours worked: 692 hours
or 28.833333 nonstop days
or 4.1190476 nonstop weeks
at €4 an hour 🤣