diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..beb1953 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +gevent = "*" +flask-login = "*" +flask-socketio = "*" +gevent-websocket = "*" + +[requires] +python_version = "3.7" diff --git a/README.md b/README.md index 6d4509e..ad9582e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ PiE Sheep # Shepherd Secret Hitler Onboarding + Join the liberals and discover the wolves in sheep's clothing, or help the fascists pull the wool over everyone's eyes! ### Getting Started + Hopefully you have been to the git tutorial by now. If you haven't, please talk to Alex or Akshit! First you need to fork this repository. You will do this by clicking on fork in the upper right corner of github. @@ -18,49 +20,54 @@ Lastly, you will need to clone the new repo onto your computer. Copy the link at ![Download button](https://github.com/pioneers/shepherd-onboarding/blob/master/readmefigures/Download%20button.png) - and run `git clone `. You can also optionally run `TODO` - +and run `git clone `. You can also optionally run `TODO` ## About the Project + Welcome to Sheep-ret (Secret?) Hitler! You will be implementing a web version of the party game Secret Hitler that mimics Shepherd's infrastructure. If you haven't already played IRL, you should first acquaint yourself with the [rules](https://secrethitler.com/assets/Secret_Hitler_Rules.pdf) of the game. -You will be writing code in three files: Shepherd.py, server.py and templates/game.html. In order for the game to fully operate, you will have to implement parts of the following functions. We have broken these down into three phases and recommend completing each phase separately, but you are free to move around. +You will be writing code in three files: Shepherd.py, server.py and templates/game.html. In order for the game to fully operate, you will have to implement parts of the following functions. We have broken these down into three phases and recommend completing each phase separately, but you are free to move around. 1. Shepherd.py - 1. `start_game` - 2. `player_joined_ongoing_game` - 3. `to_chancellor` - 4. `receive_vote` - 5. `president_discarded` - 6. `investigate_player` - 7. `call_special_election` + 1. `start_game` + 2. `player_joined_ongoing_game` + 3. `to_chancellor` + 4. `receive_vote` + 5. `president_discarded` + 6. `investigate_player` + 7. `call_special_election` 2. server.py - 1. `player_voted` - 2. `president_discarded` -3. game.html - 1. `chancellor_request` - 2. `chancellorVoteYes` and `chancellorVoteNo` + 1. `player_voted` + 2. `president_discarded` +3. templates/game.html + 1. `chancellor_request` + 2. `chancellorVoteYes` and `chancellorVoteNo` ### Phase 1 + **Shepherd.py** + 1. `start_game` 2. `player_joined_ongoing_game` 3. `to_chancellor` -**game.html** +**templates/game.html** + 1. `socket.on chancellor_request` 2. `chancellorVoteYes and chancellorVoteNo` After you have completed this phase, you should be able to start a game and the chancellor should be able to vote! ### Phase 2 + **server.py** + 1. `player_voted` 2. `president_discarded` -**game.html** +**templates/game.html** 3. `display_player_buttons` @@ -71,35 +78,42 @@ After you have completed this phase, you should be able to start a game and the 6. `investigate_player` ### Phase 3 + 7. `call_special_election` -You may write the functions in whatever order you like, but we would recommend that you work in order for each file (e.g. solve 1.1 before 1.2). +## General Tips and Instructions To run the game, enter `python3 server.py` in the terminal. You should see the messages + ``` # Pseudo-LCM: channel lcm_target_server registered # Pseudo-LCM: channel lcm_target_shepherd registered ``` -which mean that the Pseudo-LCM is up and running! + +which mean that the Pseudo-LCM is up and running! Go to localhost:5000 in the browser to play your game! To debug your code, use `print` statements in `.py` files, which display in the terminal, or `console.log` statements in `.html` files, which you can view in your browser using `right click -> Inspect`. `Shepherd.py` also prints information about the game state in the terminal whenever it receives a header, which you can change in the `diagnostics` function. ## About Shepherd + ### Welcome to Shepherd + You probably know the gist of what you've gotten into, so now we are going to talk some specifics. Shepherd is the field control software that PiE uses / maintains to get the game running. In short, Shepherd is a framework that facilitates communication between a variety of important programs that are working together to run the game. Shepherd's second (and equally important task) is to serve as an automated referee, keeping track of all the scores, timers, and rules for the game. ### Shepherd's parts + A typical year's Shepherd system might look like the following block diagram (don't worry, we are going to break it down): ![Typical Shepherd Block Diagram](https://github.com/pioneers/shepherd-onboarding/blob/master/readmefigures/Typical%20Shepherd%20Block%20Diagram.png) So really all of Shepherd's parts fall into a few categories: - * State Machine - * Front End Interfaces - * Servers - * Supporting backend utilities + +- State Machine +- Front End Interfaces +- Servers +- Supporting backend utilities It's worth noting that all communication flows through the central state machine, and that many of the peripheral programs are stateless (they don't remember anything). @@ -110,62 +124,65 @@ Here we have the same diagram as before, colored to show the form of communicati ![Shepherd Communication](https://github.com/pioneers/shepherd-onboarding/blob/master/readmefigures/Shepherd%20Communication.png) Here each of the greyed out blocks is initialized in its own thread. - * Red lines represent communication performed via LCM. - This is a library that is used to send data between two threads on the same machine and we have wrapped it in a file called LCM.py. There is a lot more to know about Shepherd's use of LCM, but we will cover that later. - * Green lines represent communication via Proto Buffs (this is new to shepherd this year), which is similar in functionality to JSON but is much lighter weight. - * Orange lines represent communication via SocketIO and JSON. We use these two libraries to communicate with other computers on the internet (or often just on the same WIFI network). - * Blue lines represent communication via the serial ports of the computer. This is used to communicate with [Arduinos](https://en.m.wikipedia.org/wiki/Arduino), which we use to power our field sensors. - * Black lines represent communication via a function call. This means that the two blocks shown are not running in separate threads and can simply be called / data returned normally. +- Red lines represent communication performed via LCM. + + This is a library that is used to send data between two threads on the same machine and we have wrapped it in a file called LCM.py. There is a lot more to know about Shepherd's use of LCM, but we will cover that later. + +- Green lines represent communication via Proto Buffs (this is new to shepherd this year), which is similar in functionality to JSON but is much lighter weight. +- Orange lines represent communication via SocketIO and JSON. We use these two libraries to communicate with other computers on the internet (or often just on the same WIFI network). +- Blue lines represent communication via the serial ports of the computer. This is used to communicate with [Arduinos](https://en.m.wikipedia.org/wiki/Arduino), which we use to power our field sensors. +- Black lines represent communication via a function call. This means that the two blocks shown are not running in separate threads and can simply be called / data returned normally. Now, we are going to dive a little into the nitty gritty of how the state machine works. Feel free to skip to the next section. As you've probably noticed, the main part of the state machine is Shepherd.py, however this file doesn't exist in a vacuum. Lets look at the helper files first: - * [Utils.py](https://github.com/pioneers/shepherd/blob/master/Utils.py) is perhaps the most important periphery. This file first and foremost defines the targets and headers that LCM uses to tie shepherd together (more on that below). This file also defines constants that are widely used in the code, and should be easy to find / change. There are also quite a few ENUMs that are defined in Utils.py, however these are not really python enums, just unique strings that serve the same purpose. Lastly, Utils.py defines the various timers that shepherd uses. +- [Utils.py](https://github.com/pioneers/shepherd/blob/master/Utils.py) is perhaps the most important periphery. This file first and foremost defines the targets and headers that LCM uses to tie shepherd together (more on that below). This file also defines constants that are widely used in the code, and should be easy to find / change. There are also quite a few ENUMs that are defined in Utils.py, however these are not really python enums, just unique strings that serve the same purpose. Lastly, Utils.py defines the various timers that shepherd uses. - * On the subject of timers, we come to [Timer.py](https://github.com/pioneers/shepherd/blob/master/Timer.py). This file contains a class Timer, which takes a `timer_type` enum from Utils.py, and creates a new timer object that can be later initialized with a duration using `timer.start_timer(durration)`. You can check if these timers are still running using the `timer.is_running()` function, as well as reset them with `timer.reset()` or `Timer.reset_all()`. Each of these timer instances will spawn a new thread, so that they can run un-interrupted, and if specified they will send an LCM message when they finish. +- On the subject of timers, we come to [Timer.py](https://github.com/pioneers/shepherd/blob/master/Timer.py). This file contains a class Timer, which takes a `timer_type` enum from Utils.py, and creates a new timer object that can be later initialized with a duration using `timer.start_timer(durration)`. You can check if these timers are still running using the `timer.is_running()` function, as well as reset them with `timer.reset()` or `Timer.reset_all()`. Each of these timer instances will spawn a new thread, so that they can run un-interrupted, and if specified they will send an LCM message when they finish. - The timer type enum in Utils.py is a dictionary with the following arguments: + The timer type enum in Utils.py is a dictionary with the following arguments: - * TYPE, a unique string used to identify the timer_type. - * NEEDS_FUNCTION, a boolean that tells the timer class whether or not it should send an empty LCM message to Shepherd.py when the timer runs out. - * FUNCTION, the LCM header to be sent if NEEDS_FUNCTION is true + - TYPE, a unique string used to identify the timer_type. + - NEEDS_FUNCTION, a boolean that tells the timer class whether or not it should send an empty LCM message to Shepherd.py when the timer runs out. + - FUNCTION, the LCM header to be sent if NEEDS_FUNCTION is true - * [Alliance.py](https://github.com/pioneers/shepherd/blob/master/Alliance.py) defines the Alliance class, which is responsible for holding information about an alliance such as the teams in the alliance, and the color of the alliance, as well as game-specific data for the alliance such as a score variable, which is subject to change each year. +- [Alliance.py](https://github.com/pioneers/shepherd/blob/master/Alliance.py) defines the Alliance class, which is responsible for holding information about an alliance such as the teams in the alliance, and the color of the alliance, as well as game-specific data for the alliance such as a score variable, which is subject to change each year. - * [Sheet.py](https://github.com/pioneers/shepherd/blob/master/Sheet.py) handles communication with a google sheet used for recording match scores and is populated with that day's match data. If no internet connection can be established, it will discard the scored rather than write them and it will pull match data from a downloaded CSV, the path to which is defined in Utils.py +- [Sheet.py](https://github.com/pioneers/shepherd/blob/master/Sheet.py) handles communication with a google sheet used for recording match scores and is populated with that day's match data. If no internet connection can be established, it will discard the scored rather than write them and it will pull match data from a downloaded CSV, the path to which is defined in Utils.py - * [Code.py](https://github.com/pioneers/shepherd/blob/master/Code.py) is a file that is subject to frequent change, however it will always be used to implement the seed generation and solution generation of the coding challenges. +- [Code.py](https://github.com/pioneers/shepherd/blob/master/Code.py) is a file that is subject to frequent change, however it will always be used to implement the seed generation and solution generation of the coding challenges. - * [LCM.py](https://github.com/pioneers/shepherd/blob/master/LCM.py) is a file that serves as a wrapper for LCM communication in shepherd. It is referenced by any file that engages in LCM communication. +- [LCM.py](https://github.com/pioneers/shepherd/blob/master/LCM.py) is a file that serves as a wrapper for LCM communication in shepherd. It is referenced by any file that engages in LCM communication. - * Audio.py, bot.py, and runtime_manager.py all have important uses, but are not finalized enough to be worth mentioning here. +- Audio.py, bot.py, and runtime_manager.py all have important uses, but are not finalized enough to be worth mentioning here. Lastly, lets talk about Shepherd.py. It might be useful to read the section on LCM before reading this, or you might want to read this section twice. [Shepherd.py](https://github.com/pioneers/shepherd/blob/master/Shepherd.py) is structured as follows: - * LCM queue and dispatch loop, which translates LCM requests to function calls, taking into account the GAME_STATE global variable as well as the given header. This uses the set of dictionaries found at the bottom of the file. +- LCM queue and dispatch loop, which translates LCM requests to function calls, taking into account the GAME_STATE global variable as well as the given header. This uses the set of dictionaries found at the bottom of the file. - LCM queues can be started via `lcm_start_read(target, queue)`, where target is the lcm target that this queue will receive messages from, and queue is a python Queue where those events will be stored. The event object is structured as `[header, args]`. + LCM queues can be started via `lcm_start_read(target, queue)`, where target is the lcm target that this queue will receive messages from, and queue is a python Queue where those events will be stored. The event object is structured as `[header, args]`. - LCM events may be sent using `lcm_send(target, header, args)`, where args is a dictionary of argument names and values. + LCM events may be sent using `lcm_send(target, header, args)`, where args is a dictionary of argument names and values. - * Evergreen functions, which are functions often invoked via LCM that are needed every year. These include the functions such as to_auto, which helps advance the game state, score keeping functions, information sharing functions, and a reset function. +- Evergreen functions, which are functions often invoked via LCM that are needed every year. These include the functions such as to_auto, which helps advance the game state, score keeping functions, information sharing functions, and a reset function. - * Game specific functions, which are also typically called via LCM, but are subject to change based on the current game. These are typically the functions responsible for implementing the rules of the game and serving as a referee. +- Game specific functions, which are also typically called via LCM, but are subject to change based on the current game. These are typically the functions responsible for implementing the rules of the game and serving as a referee. - * LCM header mappings, which are a collection of dictionaries that translate LCM headers to functions. There is one of these dictionaries for each game state, and when an event is processed by the dispatch loop, it will use the dictionary corresponding to the current game state. It is important to note that a header can map to different functions depending on the current game state, and that a function may be mapped to by multiple headers. +- LCM header mappings, which are a collection of dictionaries that translate LCM headers to functions. There is one of these dictionaries for each game state, and when an event is processed by the dispatch loop, it will use the dictionary corresponding to the current game state. It is important to note that a header can map to different functions depending on the current game state, and that a function may be mapped to by multiple headers. - * Evergreen variables, which are global variables that will be used every year. +- Evergreen variables, which are global variables that will be used every year. - * Game specific variables, which are global variables that are subject to change each year. +- Game specific variables, which are global variables that are subject to change each year. ### How Shepherd uses LCM + LCM is used to send messages asynchronously throughout the shepherd backend. We use these messages to request a certain action to be performed by another program. When an LCM message is sent to a piece of shepherd, that message is stored in a queue, where it will be processed in a FIFO (first in first out) order. Thus, there is constantly a queue of incoming requests that dictate the actions that our programs must take. When a message is pulled off the queue, it is dispatched via some dispatching code and runs the corresponding function. - * in Shepherd.py, this dispatching code uses dictionaries to map the LCM message to a function, which is then called. This method is not needed for the other smaller and simpler server files. +- in Shepherd.py, this dispatching code uses dictionaries to map the LCM message to a function, which is then called. This method is not needed for the other smaller and simpler server files. ![LCM Diagram](https://github.com/pioneers/shepherd-onboarding/blob/master/readmefigures/LCM%20Diagram.png) @@ -188,9 +205,11 @@ lcm_send(LCM_TARGETS.SCOREBOARD, SCOREBOARD_HEADER.STAGE, data) It is important to use an appropriate header, and to name all the arguments in the dictionary with the correct key. ## Shepherd Onboarding Project + That was a ton of information to handle, so in order to bring you up to speed, we are going to have you use the Shepherd framework to implement the game [Secret Hitler](https://secrethitler.com), [rules](https://secrethitler.com/assets/Secret_Hitler_rules.pdf), while using a much simpler version of Shepherd. ### Shepherd Changes + To begin with, the version of Shepherd we will be using for this project has been stripped down to just the bare essentials: ![Shepherd Onboarding Block Diagram](https://github.com/pioneers/shepherd-onboarding/blob/master/readmefigures/Shepherd%20Onboarding%20Block%20Diagram.png) @@ -198,9 +217,14 @@ To begin with, the version of Shepherd we will be using for this project has bee Phew! That's a lot easier to look at. Here we have just a state machine, a single server, and a single front-end webpage. LCM changes: - * LCM is really hard to install (maybe impossible on Windows?) and since we are creating such a simple game which does not require asynchronous pieces, we dropped the LCM communication altogether. That being said, since we want you to get used to communication via LCM, we have instead included a pseudo LCM implementation in LCM.py. This exposes almost the same functionality to the user, with the notable exception that it's just a glorified function call. - * Using our pseudo LCM for communication between the server and the state machine will teach you how the real LCM is used. In order to follow that abstraction, you should refrain from ever calling a function in the state machine from the server, or vice versa. - * Lastly, normal LCM is asynchronous, which means once you send an LCM message, your code will immediately move on to the next line. Because our pseudo LCM is nothing more than a glorified function call, the code will instead pause and process whatever your LCM message was intended to do. Therefore, you should take care not to get stuck in a recursive loop of LCM calls. + +- LCM is really hard to install (maybe impossible on Windows?) and since we are creating such a simple game which does not require asynchronous pieces, we dropped the LCM communication altogether. That being said, since we want you to get used to communication via LCM, we have instead included a pseudo LCM implementation in LCM.py. This exposes almost the same functionality to the user, with the notable exception that it's just a glorified function call. +- Using our pseudo LCM for communication between the server and the state machine will teach you how the real LCM is used. In order to follow that abstraction, you should refrain from ever calling a function in the state machine from the server, or vice versa. +- # Lastly, normal LCM is asynchronous, which means once you send an LCM message, your code will immediately move on to the next line. Because our pseudo LCM is nothing more than a glorified function call, the code will instead pause and process whatever your LCM message was intended to do. Therefore, you should take care not to get stuck in a recursive loop of LCM calls. + +* LCM is really hard to install (maybe impossible on Windows?) and since we are creating such a simple game which does not require asynchronous pieces, we dropped the LCM communication altogether. That being said, since we want you to get used to communication via LCM, we have instead included a pseudo LCM implementation in LCM.py. This exposes almost the same functionality to the user, with the notable exception that it's just a glorified function call. +* Using our pseudo LCM for communication between the server and the state machine will teach you how the real LCM is used. In order to follow that abstraction, you should refrain from ever calling a function in the state machine from the server, or vice versa. +* Lastly, normal LCM is asynchronous, which means once you send an LCM message, your code will immediately move on to the next line. Because our pseudo LCM is nothing more than a glorified function call, the code will instead pause and process whatever your LCM message was intended to do. Therefore, you should take care not to get stuck in a recursive loop of LCM calls ### Credit diff --git a/STUDENT_INSTRUCTIONS.md b/STUDENT_INSTRUCTIONS.md index 5a6e1c8..7d9a674 100644 --- a/STUDENT_INSTRUCTIONS.md +++ b/STUDENT_INSTRUCTIONS.md @@ -1,30 +1,32 @@ - Game parts to implement - - Shepherd.py - - player_joined_ongoing_game - - send policies enacted - - start_game - - deck creation/shuffle - - role assignment - - board initialization - - to_chancellor - - determine who is eligible to be selected for chancellor - - send chancellor request header - - receive_vote - - end game if Hitler is elected chancellor with 3+ fascist policies - - president_discarded - - all of the function - - investigate_player - - all of the function - - Utils.py - - BOARDS enum - - game.html - - socket.on chancellor_request - - chancellorVoteYes and chancellorVoteNo - - socket emit - - display_player_buttons - - body of forEach - - server.py - - player_voted - - Final challenge: all special election business + - Shepherd.py + 1. start_game + - deck creation/shuffle + - role assignment + - board initialization + 2. player_joined_ongoing_game + - send policies enacted + 3. to_chancellor + - determine who is eligible to be selected for chancellor + - send chancellor request header + 4. receive_vote + - for this function to be called, you must first implement #1 in server.py + - end game if Hitler is elected chancellor with 3+ fascist policies + 5. president_discarded + - for this function to be called, you must first implement #1 in server.py + - all of the function + 6. investigate_player + - all of the function + - Utils.py + - server.py + 1. player_voted + 2. president_discarded + - game.html + - socket.on chancellor_request + - chancellorVoteYes and chancellorVoteNo + - socket emit + - display_player_buttons + - body of forEach + - Final challenge: all special election business - Make flowchart of function calls in game flow -- Tests for game parts? \ No newline at end of file +- Tests for game parts? diff --git a/Shepherd.py b/Shepherd.py index af162e0..364fcb6 100644 --- a/Shepherd.py +++ b/Shepherd.py @@ -113,7 +113,8 @@ def player_joined_ongoing_game(args): if id in player_ids(PLAYERS): # is this someone reconnecting or joining for the first time? print("# Shepherd: Welcome back", name) - lcm_data = {"usernames": player_names(PLAYERS), "ids": player_ids(PLAYERS), "recipients": [id], "ongoing_game": True} + lcm_data = {"usernames": player_names(PLAYERS), "ids": player_ids( + PLAYERS), "recipients": [id], "ongoing_game": True} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.ON_JOIN, lcm_data) # individual setup @@ -125,7 +126,7 @@ def player_joined_ongoing_game(args): if player == other: player_roles.append([player.name, player.id, player.role]) else: - player_roles.append([other.name, other.id , ROLES.NONE]) + player_roles.append([other.name, other.id, ROLES.NONE]) elif player.role == ROLES.FASCIST or (player.role == ROLES.HITLER and len(PLAYERS) <= 6): for other in PLAYERS: player_roles.append([other.name, other.id, other.role]) @@ -134,7 +135,8 @@ def player_joined_ongoing_game(args): if id not in player_ids(SPECTATORS): SPECTATORS.append(Player(id, name)) print("# Shepherd: Welcome as a spectator", name) - lcm_data = {"usernames": player_names(PLAYERS), "ids": player_ids(PLAYERS), "recipients": [id], "ongoing_game": True} + lcm_data = {"usernames": player_names(PLAYERS), "ids": player_ids( + PLAYERS), "recipients": [id], "ongoing_game": True} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.ON_JOIN, lcm_data) # individual setup @@ -146,18 +148,22 @@ def player_joined_ongoing_game(args): player_roles.append([other.name, other.id, other.role]) lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.INDIVIDUAL_SETUP, lcm_data) - # policies enacted - lcm_data = {"liberal": BOARD.liberal_enacted, - "fascist": BOARD.fascist_enacted, + # BEGIN QUESTION 2 + # send the number of fascist and liberal policies enacted to the server + lcm_data = {_________: _____________________, + _________: _____________________, "recipients": [id]} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.POLICIES_ENACTED, lcm_data) + lcm_send(_______________, _________________, lcm_data) + # END QUESTION 2 # veto enabled if BOARD.can_veto: lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.VETO_ENABLED, {}) # repeat last server message - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.REPEAT_MESSAGE, {'recipients' : [id]}) + lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.REPEAT_MESSAGE, + {'recipients': [id]}) + def start_game(args): """ @@ -168,13 +174,18 @@ def start_game(args): lcm_data = {"players": len(PLAYERS)} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.NOT_ENOUGH_PLAYERS, lcm_data) return - deck = [ROLES.LIBERAL] * (len(PLAYERS) // 2 + 1) - deck += [ROLES.FASCIST] * ((len(PLAYERS) - 1) // 2 - 1) - deck += [ROLES.HITLER] + # BEGIN QUESTION 1: initialize the list deck with 1 hitler and the relevant number of fascist and liberal cards. Hint: don't use raw strings to represent the roles. Instead, look for a useful class in Utils.py. + # see the table on page 2 of the rules: https://secrethitler.com/assets/Secret_Hitler_Rules.pdf#page=2. For a challenge, try coming up with a formula for it. + + # END QUESTION 1 shuffle_deck(deck) - for i in range(len(PLAYERS)): - PLAYERS[i].role = deck[i] - BOARD = Board(len(PLAYERS)) + # BEGIN QUESTION 1 + # Assign roles for each player using the deck. + for i in range(_______________): + PLAYERS[i].role = __________________ + # Initialize the board. + BOARD = _______________ + # END QUESTION 1 for player in PLAYERS: player_roles = [] lcm_data = {"recipients": [player.id], "individual_role": player.role, "roles": player_roles, "powers": BOARD.board} @@ -183,7 +194,7 @@ def start_game(args): if player == other: player_roles.append([player.name, player.id, player.role]) else: - player_roles.append([other.name, other.id , ROLES.NONE]) + player_roles.append([other.name, other.id, ROLES.NONE]) elif player.role == ROLES.FASCIST or (player.role == ROLES.HITLER and len(PLAYERS) <= 6): for other in PLAYERS: player_roles.append([other.name, other.id, other.role]) @@ -193,19 +204,22 @@ def start_game(args): def to_chancellor(): """ - A function that moves the game into the chancellor phase. + A function that moves the game into the chancellor phase. This is done + by constructing a list of eligible players and sending the CHANCELLOR_REQUEST header to the server. + + Rules: + - if there are > 5 players, the ineligible players are the president, + previous president, and previous chancellor. + - if <= 5 players, only the president and previous chancellor are ineligible """ global GAME_STATE GAME_STATE = STATE.PICK_CHANCELLOR - if len(PLAYERS) > 5: - ineligibles = {player_id(PRESIDENT_INDEX), player_id( - PREVIOUS_PRESIDENT_INDEX), player_id(PREVIOUS_CHANCELLOR_INDEX)} - else: - ineligibles = {player_id(PRESIDENT_INDEX), player_id(PREVIOUS_CHANCELLOR_INDEX)} - eligibles = [d for d in player_ids(PLAYERS) if d not in ineligibles] + # BEGIN QUESTION 3 + lcm_data = {"president": player_id( PRESIDENT_INDEX), "eligibles": eligibles} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.CHANCELLOR_REQUEST, lcm_data) + lcm_send(______________, ________________, _______________) + # END QUESTION 3 def receive_chancellor_nomination(args): @@ -236,13 +250,14 @@ def receive_vote(args): if passed: PREVIOUS_PRESIDENT_INDEX = PRESIDENT_INDEX PREVIOUS_CHANCELLOR_INDEX = NOMINATED_CHANCELLOR_INDEX - if PLAYERS[NOMINATED_CHANCELLOR_INDEX].role == ROLES.HITLER and BOARD.fascist_enacted >= 3: - game_over(ROLES.FASCIST) - return + # BEGIN QUESTION 4: if chancellor is hitler, game_over is called and the function is terminated + + # END QUESTION 4 if len(CARD_DECK) < 3: reshuffle_deck() GAME_STATE = STATE.POLICY - lcm_data = {"president": player_id(PRESIDENT_INDEX), "cards": draw_cards(3)} + lcm_data = {"president": player_id( + PRESIDENT_INDEX), "cards": draw_cards(3)} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.PRESIDENT_DISCARD, lcm_data) else: @@ -257,21 +272,26 @@ def receive_vote(args): PREVIOUS_CHANCELLOR_INDEX = Player.NONE lcm_data = {"liberal": BOARD.liberal_enacted, "fascist": BOARD.fascist_enacted} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.POLICIES_ENACTED, lcm_data) + lcm_send(LCM_TARGETS.SERVER, + SERVER_HEADERS.POLICIES_ENACTED, lcm_data) PRESIDENT_INDEX = next_president_index() to_chancellor() def president_discarded(args): """ - A function that takes the policies left and passes them to the chancellor. + A function that takes the cards left and passes them to the chancellor. + `cards` contains the remaining two cards. """ global DISCARD_DECK - cards = args["cards"] - discarded = args["discarded"] - DISCARD_DECK.append(discarded) - lcm_data = {"chancellor": player_id(NOMINATED_CHANCELLOR_INDEX), "cards": cards, "can_veto": BOARD.can_veto} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.CHANCELLOR_DISCARD, lcm_data) + # BEGIN QUESTION 5 + cards = ______________ + discarded = ______________ + DISCARD_DECK.append(_____________) + lcm_data = {"chancellor": _______________, + "cards": ___________, "can_veto": BOARD.can_veto} + lcm_send(_________________________) + # END QUESTION 5 def chancellor_vetoed(args): @@ -301,11 +321,13 @@ def president_veto_answer(args): PREVIOUS_CHANCELLOR_INDEX = Player.NONE lcm_data = {"liberal": BOARD.liberal_enacted, "fascist": BOARD.fascist_enacted} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.POLICIES_ENACTED, lcm_data) + lcm_send(LCM_TARGETS.SERVER, + SERVER_HEADERS.POLICIES_ENACTED, lcm_data) PRESIDENT_INDEX = next_president_index() to_chancellor() else: - lcm_data = {"chancellor": player_id(NOMINATED_CHANCELLOR_INDEX), "cards": cards, "can_veto": BOARD.can_veto} + lcm_data = {"chancellor": player_id( + NOMINATED_CHANCELLOR_INDEX), "cards": cards, "can_veto": BOARD.can_veto} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.CHANCELLOR_DISCARD, lcm_data) @@ -359,22 +381,22 @@ def investigate_loyalty(): def investigate_player(args): """ A function that returns the loyalty (as a role) of the player the president - has asked to investigate. + has asked to investigate using the RECEIVE_INVESTIGATION header. Hitler + is treated as a fascist. """ - player = player_for_id(args["player"]) + # BEGIN QUESTION 6 + player = player_for_id(__________) player.investigated = True - loyalty = ROLES.LIBERAL if player.role == ROLES.LIBERAL else ROLES.FASCIST - lcm_data = {"president": player_id(PRESIDENT_INDEX), "loyalty": loyalty} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.RECEIVE_INVESTIGATION, lcm_data) + # find out the loyalty and send it to the server. def call_special_election(): """ - A function that begins the special election power. + A function that begins the special election power. + Send the appropriate header to the server with the correct data. + Anyone except the current president is eligible to be the next president. """ - president = player_id(PRESIDENT_INDEX) - lcm_data = {"president": president, "eligibles": [i for i in player_ids(PLAYERS) if i != president]} - lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.BEGIN_SPECIAL_ELECTION, lcm_data) + # BEGIN QUESTION 7 def perform_special_election(args): @@ -402,6 +424,7 @@ def end_policy_peek(args): """ to_chancellor() + def end_investigate_player(args): """ A function that ends the investigate player. @@ -410,12 +433,14 @@ def end_investigate_player(args): PRESIDENT_INDEX = next_president_index() to_chancellor() + def execution(): """ A function that begins the execution power. """ president = player_id(PRESIDENT_INDEX) - lcm_data = {"president": president, "eligibles": [i for i in player_ids(PLAYERS) if i != president]} + lcm_data = {"president": president, "eligibles": [ + i for i in player_ids(PLAYERS) if i != president]} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.BEGIN_EXECUTION, lcm_data) @@ -443,7 +468,7 @@ def perform_execution(args): SPECTATORS.append(player) PRESIDENT_INDEX = next_president_index() - lcm_data = { 'player': p_id } + lcm_data = {'player': p_id} lcm_send(LCM_TARGETS.SERVER, SERVER_HEADERS.PLAYER_EXECUTED, lcm_data) to_chancellor() @@ -500,6 +525,7 @@ def player_for_id(p_id): """ return PLAYERS[player_ids(PLAYERS).index(p_id)] + def spectator_for_id(s_id): """ Returns the player with a specified ID diff --git a/Utils.py b/Utils.py index ba54528..b9e4b36 100644 --- a/Utils.py +++ b/Utils.py @@ -27,7 +27,7 @@ class SHEPHERD_HEADERS: Header to tell shepherd the player's vote on the chancellor contains: id - the id of the voter - vote - true if the vote was yes + vote - ja if the vote was yes. """ PRESIDENT_DISCARDED = "president_discarded" @@ -242,8 +242,10 @@ class SERVER_HEADERS: # pylint: disable=invalid-name + class LCM_UTILS: - PRIVILEGED_HEADERS = [SERVER_HEADERS.CHANCELLOR_REQUEST,SERVER_HEADERS.AWAIT_VOTE,SERVER_HEADERS.PRESIDENT_DISCARD,SERVER_HEADERS.CHANCELLOR_DISCARD,SERVER_HEADERS.ASK_PRESIDENT_VETO,SERVER_HEADERS.BEGIN_INVESTIGATION,SERVER_HEADERS.RECEIVE_INVESTIGATION,SERVER_HEADERS.BEGIN_SPECIAL_ELECTION,SERVER_HEADERS.PERFORM_POLICY_PEEK,SERVER_HEADERS.BEGIN_EXECUTION,SERVER_HEADERS.GAME_OVER] + PRIVILEGED_HEADERS = [SERVER_HEADERS.CHANCELLOR_REQUEST, SERVER_HEADERS.AWAIT_VOTE, SERVER_HEADERS.PRESIDENT_DISCARD, SERVER_HEADERS.CHANCELLOR_DISCARD, SERVER_HEADERS.ASK_PRESIDENT_VETO, + SERVER_HEADERS.BEGIN_INVESTIGATION, SERVER_HEADERS.RECEIVE_INVESTIGATION, SERVER_HEADERS.BEGIN_SPECIAL_ELECTION, SERVER_HEADERS.PERFORM_POLICY_PEEK, SERVER_HEADERS.BEGIN_EXECUTION, SERVER_HEADERS.GAME_OVER] # pylint: disable=invalid-name @@ -292,9 +294,11 @@ class POWERS: class BOARDS: + # BEGIN QUESTION 1: each arrangement contains a list of 6 lists. List i contains each power that occurs after the (i + 1)th fascist policy is passed. FIVE_SIX = [[], [], [POWERS.POLICY_PEEK], [ POWERS.EXECUTION], [POWERS.EXECUTION, POWERS.VETO], []] SEVEN_EIGHT = [[], [POWERS.INVESTIGATE_LOYALTY], [POWERS.SPECIAL_ELECTION], [ POWERS.EXECUTION], [POWERS.EXECUTION, POWERS.VETO], []] NINE_TEN = [[POWERS.INVESTIGATE_LOYALTY], [POWERS.INVESTIGATE_LOYALTY], [ POWERS.SPECIAL_ELECTION], [POWERS.EXECUTION], [POWERS.EXECUTION, POWERS.VETO], []] + # END QUESTION 1 diff --git a/server.py b/server.py index 8927ec6..40bc69b 100644 --- a/server.py +++ b/server.py @@ -22,6 +22,7 @@ def hello_world(): return render_template('index.html') + @app.route('/test') def testy_boi(): return render_template('testing.html') @@ -62,18 +63,22 @@ def chancellor_nomination(chancellor_info): @socketio.on('player_voted') def player_voted(vote_info): + # BEGIN QUESTION 1 data = json.loads(vote_info) print('Player with id: ', data['id'], ' has voted ', data['vote']) - lcm_send(LCM_TARGETS.SHEPHERD, SHEPHERD_HEADERS.PLAYER_VOTED, data) + lcm_send(_____________, _________________, _______________) + # END QUESTION 1 @socketio.on('president_discarded') def president_discarded(policy_info): + # BEGIN QUESTION 2 data = json.loads(policy_info) print(data) print('after president discarded', data['discarded'], ', cards remaining are: ', data['cards']) - lcm_send(LCM_TARGETS.SHEPHERD, SHEPHERD_HEADERS.PRESIDENT_DISCARDED, data) + lcm_send(_______________, _________________, _______________) + # END QUESTION 2 @socketio.on('chancellor_discarded') @@ -97,7 +102,7 @@ def president_veto_answer(veto_info): data['veto'], '. Cards for chancellor are: ', data['cards']) lcm_send(LCM_TARGETS.SHEPHERD, SHEPHERD_HEADERS.PRESIDENT_VETO_ANSWER, data) - + @socketio.on('end_policy_peek') def end_policy_peek(peek_info): print('policy peek over.') @@ -110,11 +115,13 @@ def investigate_player(player_info): print('Decided to investigate ', data['player']) lcm_send(LCM_TARGETS.SHEPHERD, SHEPHERD_HEADERS.INVESTIGATE_PLAYER, data) + @socketio.on('end_investigate_player') def end_investigate_player(player_info): print('investigation over.') lcm_send(LCM_TARGETS.SHEPHERD, SHEPHERD_HEADERS.END_INVESTIGATE_PLAYER) + @socketio.on('special_election_pick') def special_election_pick(player_info): data = json.loads(player_info) diff --git a/static/assets/Fascist Board.png b/static/assets/fascist_board.png similarity index 100% rename from static/assets/Fascist Board.png rename to static/assets/fascist_board.png diff --git a/static/assets/Fascist Board Dark.png b/static/assets/fascist_board_dark.png similarity index 100% rename from static/assets/Fascist Board Dark.png rename to static/assets/fascist_board_dark.png diff --git a/static/assets/Fascist Card.png b/static/assets/fascist_card.png similarity index 100% rename from static/assets/Fascist Card.png rename to static/assets/fascist_card.png diff --git a/static/assets/Fascist Sheep.png b/static/assets/fascist_sheep.png similarity index 100% rename from static/assets/Fascist Sheep.png rename to static/assets/fascist_sheep.png diff --git a/static/assets/Fascist Win.png b/static/assets/fascist_win.png similarity index 100% rename from static/assets/Fascist Win.png rename to static/assets/fascist_win.png diff --git a/static/assets/Hitler Sheep.png b/static/assets/hitler_sheep.png similarity index 100% rename from static/assets/Hitler Sheep.png rename to static/assets/hitler_sheep.png diff --git a/static/assets/Liberal Board.png b/static/assets/liberal_board.png similarity index 100% rename from static/assets/Liberal Board.png rename to static/assets/liberal_board.png diff --git a/static/assets/Liberal Board Dark.png b/static/assets/liberal_board_dark.png similarity index 100% rename from static/assets/Liberal Board Dark.png rename to static/assets/liberal_board_dark.png diff --git a/static/assets/Liberal card.png b/static/assets/liberal_card.png similarity index 100% rename from static/assets/Liberal card.png rename to static/assets/liberal_card.png diff --git a/static/assets/Liberal Sheep.png b/static/assets/liberal_sheep.png similarity index 100% rename from static/assets/Liberal Sheep.png rename to static/assets/liberal_sheep.png diff --git a/static/assets/Liberal Win.png b/static/assets/liberal_win.png similarity index 100% rename from static/assets/Liberal Win.png rename to static/assets/liberal_win.png diff --git a/static/assets/Mystery Sheep.png b/static/assets/mystery_sheep.png similarity index 100% rename from static/assets/Mystery Sheep.png rename to static/assets/mystery_sheep.png diff --git a/static/assets/Shepherd Secret Hitler.png b/static/assets/shepherd_secret_hitler.png similarity index 100% rename from static/assets/Shepherd Secret Hitler.png rename to static/assets/shepherd_secret_hitler.png diff --git a/templates/game.html b/templates/game.html index 7ad7c52..4c0ca3e 100644 --- a/templates/game.html +++ b/templates/game.html @@ -1,669 +1,880 @@ - - - - - - - - - Game - - - - -

BAAA play here

- - - - -

- - -

-
- - -
- -
- + +

+ + +

+
+ + +
- + +
+ + + + + + + + + + + +
- - - + +
+ + + +
- - -
- - - - - -
- - -
- - - - -
-
- - -
- - liberal background - - - - liberal background - - - - liberal background - - - - liberal background - - - - liberal background - liberal win - - -
+
+ + +
+ + liberal background + + + + liberal background + + + + liberal background + + + + liberal background + + + + liberal background + liberal win + + +
-
-
- - fascist background - - - - - fascist background - - - - - fascist background - - - - - fascist background - - - - - fascist background - - - - - fascist background - fascist win - - -
+
+ + fascist background + + + + + fascist background + + + + + fascist background + + + + + fascist background + + + + + fascist background + + + + + fascist background + fascist win + + +
-
- - - + +