00
Days
00
Hours
00
Mins
00
Secs
Artificial Intelligence Challenge / Ends February 11
๐ŸŽ Prizes: $100 / $50 / $25 Amazon Gift Cards

Learn to Code via Tutorials on Repl.it

Posts
โ–ฒ
1
Mรฉthode des trapรจzes par tom delaye
Oรน l'on code la mรฉthode des trapรจzes pour approximer l'aire sous une courbe en python...
0
posted by TomDelaye204 (0) 2 hours ago
โ–ฒ
2
Learn how to implement advanced data structures in Python!
Advanced Data Structures implemented in python. https://github.com/thejerrytan/python-data-structures
0
posted by thejerrytan (1) 3 days ago
โ–ฒ
3
Python - Discrete Math Logic Parser
# (Python) Discrete Math Logic Parser ## What is Discrete Math Logic? Discrete Math Logic is a basic form of logic. In its simplest form, it can be represented by *p ^ q* In discrete math, the basic operators are: * ^ (which denotes AND) * v (which denotes OR) * ~ (which denotes NOT) There are more operators that exist but for this tutorial, we will only cover these simple ones. If you want to include the other operators, I will have them at the end of this post along with the code for each operator. ## Requirements * Basic Knowledge of Python * Classes * Functions * Knowledge of Recursion ## Logic Parser Layout Now for this tutorial, we are going to have 4 different python files. * `errors.py` which will hold all the `Exception`s we will use * `logic_var.py` which will hold the class for a variable such as `a` or `b` * `logic_node.py` which will be the basics for a left and right logical expression such as `a ^ b` * `logic_tree.py` which will be the primary class for a logical expression Now we can get started with the tutorial. ___ ### `errors.py` Now this is the simple stuff. All we really have in this file are just four simple classes that extend Python's `Exception` object. ```py class UnbalancedParentheses(Exception): pass class MissingTruthValue(Exception): pass class OperatorMismatch(Exception): pass class MissingValue(Exception): pass ``` And we're done with that part! That was easy. ___ ### `logic_var.py` In this file, we have a `LogicVar` class that will hold all information about a single variable in a logical expression. This class is very simple as we only have 2 attributes that a `LogicVar` object holds: * Whether or not it has a ~ (NOT) operator attached to it * The variable itself I'm going to layout the contents of each set of functions so hopefully you understand it better. ```py class LogicVar: def __init__(self, value = None, has_not = False, *, var_dict = None): # If the value is given, we are loading it in by the value and has_not parameters if value != None: self._value = value self._has_not = has_not # If the var_dict is given, we are recursively initializing this LogicVar. elif var_dict != None: # Try loading the value key from var_dict try: self._value = var_dict["value"] except: raise MissingValue("Required key for LogicVar. Must have \"value\" key in var_dict.") # Try loading the has_not key from var_dict try: self._has_not = var_dict["has_not"] except: self._has_not = has_not # If both value and var_dict are missing, we raise an Exception elif value == None and var_dict == None: raise MissingValue("Required variable for LogicVar. Must use either value or var_dict.") def __str__(self): if self.has_not(): return "~" + self.get_value() return self.get_value() def __eq__(self, value): if not isinstance(value, LogicVar): return False return ( self.get_value() == value.get_value() and self.has_not() == value.has_not() ) ... ``` All these functions are built-in functions for Python classes. The `__eq__` function is overridden so we can compare a `LogicVar` object with some other object later on. The `__str__` function is overridden so we can easily print out a `LogicVar` object when we print the parsed expression. ```py ... def get_value(self): return self._value def has_not(self): return self._has_not ... ``` These functions should be pretty straight forward. They are just getter methods for a `LogicVar` object. ```py ... def evaluate(self, truth_value = {}): if self.get_value() not in truth_value: raise MissingTruthValue("Required truth value for the variable \"{}\"".format(self.get_value())) if self.has_not(): return not truth_value[self.get_value()] return truth_value[self.get_value()] def get_truth_values(self, truth_values = []): # Keep track of evaluations evaluations = [] # Only run this if there is a has_not if self.has_not(): # Iterate through truth values for truth_value in truth_values: # Evaluate the value given by this LogicVar evaluation = { "expression": str(self), "truth_value": truth_value, "value": self.evaluate(truth_value) } # Only add the evaluation if it doesn't already exist if evaluation not in evaluations: evaluations.append(evaluation) return evaluations ``` Now the `evaluate` function will be given a dictionary of truth values to use to evaluate. For example, let's say that there is a `LogicVar` object whose variable is `a`, when the `evaluate` function runs, there is a `truth_value` given to it such as: ```py { "a": True, "b": True } ``` A truth value dictionary just holds the truth values for all the variables that exist in a logical expression. The `get_truth_values` function will run through all the possible truth values that exist for a logical expression. It depends on the amount of variables where this comes from. To explain better, let's say we are given an expression such as `a ^ b` All the possible truth values include: ``` | a | b | |---+---| | T | T | | T | F | | F | T | | F | F | ``` So there are 2โฟ possible combinations of truth values and that is where the list of truth values comes from. ___ ### `logic_node.py` Now this file holds the information behind a *logical expression* such as `a ^ b`. In that expression, `a` would be the left side, `b` would be the right side, and `^` would be the operator. This is exactly what the `LogicNode` class holds ```py class LogicNode: def __init__(self, operator = None, left = None, right = None, has_not = False, *, node_dict = None): # If operator, left, and right are given, we are loading it in by those parameters if operator != None and left != None and right != None: self._operator = operator self._left = left self._right = right self._has_not = has_not # If node_dict is given, we are recursively initializing this LogicNode. elif node_dict != None: # Try loading operator try: self._operator = node_dict["operator"] except: raise MissingValue("Required key for LogicNode. Must have \"operator\" key in node_dict.") # Try loading left try: self._left = node_dict["left"] except: raise MissingValue("Required key for LogicNode. Must have \"left\" key in node_dict.") # Try loading right try: self._right = node_dict["right"] except: raise MissingValue("Required key for LogicNode. Must have \"right\" key in node_dict.") # Try loading has_not try: self._has_not = node_dict["has_not"] except: self._has_not = has_not # If operator, left, right, and node_dict are all missing, raise an Exception elif (operator == None or left == None or right == None) or node_dict == None: raise MissingValue("Required variable(s) for LogicNode. Must use either operator, left, and right or node_dict.") # Turn the left and right values into either `LogicNode` or `LogicVar` objects if isinstance(self._left, dict): if "value" in self._left: self._left = LogicVar(var_dict = self._left) else: self._left = LogicNode(node_dict = self._left) if isinstance(self._right, dict): if "value" in self._right: self._right = LogicVar(var_dict = self._right) else: self._right = LogicNode(node_dict = self._right) def __str__(self): left = str(self.get_left()) right = str(self.get_right()) # Only put parentheses around expressions that don't have a ~ (NOT) operator attached to it if isinstance(self.get_left(), LogicNode) and not self.get_left().has_not(): left = "(" + left + ")" if isinstance(self.get_right(), LogicNode) and not self.get_right().has_not(): right = "(" + right + ")" operator = self.get_operator() if self.has_not(): return "~({} {} {})".format( left, operator, right ) return "{} {} {}".format( left, operator, right ) def __eq__(self, value): if not isinstance(value, LogicNode): return False return ( self.get_left() == compare.get_left() and self.get_right() == compare.get_right() and self.get_opoerator() == compare.get_operator() and self.has_not() == compare.has_not() ) ... ``` Again, this just sets up the `LogicNode` class and any object that is an instance of it. Notice that in the `__init__` method, the `left` and `right` parameters would normally take in a `LogicNode` or `LogicVar` object as the parameter. The way I will show how to load it in is using the `node_dict` keyword parameter.That will be shown in the next portion for the `LogicTree` class. ```py ... def get_operator(self): return self._operator def get_left(self): return self._left def get_right(self): return self._right def has_not(self): return self._has_not ... ``` More getter methods! There's really no need to explain this but I added it in just so there's not missing code for this tutorial. ```py ... def evaluate(self, truth_value = {}): left = self.get_left().evaluate(truth_value) right = self.get_right().evaluate(truth_value) if self._operator == "^": if self.has_not(): return not (left and right) return left and right if self._operator == "v": if self.has_not(): return not (left or right) return left or right def get_truth_values(self, truth_values = []): # Keep track of evaluations evaluations = [] # Get left evaluations left_evaluations = self.get_left().get_truth_values(truth_values) for left_evaluation in left_evaluations: if left_evaluation not in evaluations: evaluations.append(left_evaluation) # Get right evaluations right_evaluations = self.get_right().get_truth_values(truth_values) for right_evaluation in right_evaluations: if right_evaluation not in evaluations: evaluations.append(right_evaluation) # Iterate through truth values for truth_value in truth_values: # Evaluate this LogicNode object evaluation = { "expression": str(self), "truth_value": truth_value, "value": self.evaluate(truth_value) } # Only add the evaluation if it doesn't already exist if evaluation not in evaluations: evaluations.append(evaluation) return evaluations ``` Now the `evaluate` method in the `LogicNode` class will take into account the operator of the expression. So if the expression held in the `LogicNode` is an ^ (AND) operator, then it will evaluate the left recursively until it hits any `LogicVar` objects, and finally it will evaluate the right recursively. If there is a ~ (NOT) operator attached to the `LogicNode` object, it will handle that as well. The `get_truth_values` method will recursively evaluate both the left and right sides of the `LogicNode` object and then it will evaluate itself. Again, it will only add any evaluations that haven't already been made before. This is used when creating a truth table in the `LogicTree` class. It is helpful for sure when using it to evaluate a logical expression. ___ ### `logic_tree.py` Now comes the almost-grand finale of this tutorial: The `LogicTree` class which is the root of the expresssion parsing. ```py class LogicTree: def __init__(self, expression): self._expression = expression self._variables = [] self._root = None self.parse() def __str__(self): return str(self._root) def __eq__(self, value): if not isinstance(value, LogicTree): return False return ( self.get_expression() == value.get_expression() and self.get_variables() == value.get_variables() and self._root == value._root ) def get_expression(self): return self._expression def get_variables(self): return self._variables ... ``` This time I merged the getters in with the built-in methods only because the bulk of the code in this class isn't in the basics of the class. ```py ... def parse(self): expression = parse_expression(self.get_expression()) expression = expression["expression"] variables = expression["variables"] self._root = LogicNode(node_dict = expression) self._variables = variables self._expression = str(self._root) def make_table(self): lines = [] result = "" # Evaluate the root LogicNode evaluations = self.get_truth_values() table_dict = {} for evaluation in evaluations: if evaluation["expression"] not in table_dict: table_dict[evaluation["expression"]] = [] table_dict[evaluation["expression"]].append(evaluation["value"]) # Add column labels count = 0 length = len(table_dict) for column in table_dict: line = "| " + column.center(len(column)) if count > 0: line = " " + line elif count == length - 1: line += " |" result += line count += 1 lines.append(result) result = "" # Add label split line count = 0 for column in table_dict: line = "+" + "-".center(len(column) + 1, "-") if count > 0: line = "-" + line elif count == length - 1: line += "-+" result += line count += 1 lines.append(result) result = "" # Add truth values max_truths = -1 for column in table_dict: if max_truths == -1: max_truths = len(table_dict[column]) break for index in range(max_truths): count = 0 for column in table_dict: value = table_dict[column][index] if value: value = "T" elif value == False: value = "F" else: value = "-" line = "| " + value.center(len(column)) if count > 0: line = " " + line elif count == length - 1: line += " |" result += line count += 1 lines.append(result) result = "" return lines ... ``` Now this code may seem confusing at first but I'll break it down into what each section does. ```py # Evaluate the root LogicNode evaluations = self.get_truth_values() table_dict = {} for evaluation in evaluations: if evaluation["expression"] not in table_dict: table_dict[evaluation["expression"]] = [] table_dict[evaluation["expression"]].append(evaluation["value"]) ``` This code segment will just evaluate itself and get a list of truth values from the `LogicNode` object held in the `self._root` variable. The `get_truth_values` method will actually run all the evaluations for said object. ```py # Add column labels count = 0 length = len(table_dict) for column in table_dict: line = "| " + column.center(len(column)) if count > 0: line = " " + line elif count == length - 1: line += " |" result += line count += 1 lines.append(result) result = "" ``` This code segment will create the table labels in a truth table. For example, say your expression was `a ^ b` The resulting label would look like this: ``` | a | b | a ^ b | ``` It looks exactly as you would write a regular truth table! ```py # Add label split line count = 0 for column in table_dict: line = "+" + "-".center(len(column) + 1, "-") if count > 0: line = "-" + line elif count == length - 1: line += "-+" result += line count += 1 lines.append(result) result = "" ``` This code segment will add a line that separates the variable or expression in the label from the truth values. For example, we'll add onto the previous truth table: ``` | a | b | a ^ b | +---+---+-------+ ``` That just adds that last little line and makes it neat in raw text. ```py # Add truth values max_truths = -1 for column in table_dict: if max_truths == -1: max_truths = len(table_dict[column]) break for index in range(max_truths): count = 0 for column in table_dict: value = table_dict[column][index] if value: value = "T" elif value == False: value = "F" else: value = "-" line = "| " + value.center(len(column)) if count > 0: line = " " + line elif count == length - 1: line += " |" result += line count += 1 lines.append(result) result = "" ``` Now this next code segment has 2 parts. The first part, ```py max_truths = -1 for column in table_dict: if max_truths == -1: max_truths = len(table_dict[column]) break ``` will find the maximum amount of truth values that are given from an expression. For example, ``` | a | b | +---+---+ | T | T | | T | F | | F | T | | F | F | ``` this truth table would have 4 truth values to go through. The next part, ```py for index in range(max_truths): count = 0 for column in table_dict: value = table_dict[column][index] if value: value = "T" elif value == False: value = "F" else: value = "-" line = "| " + value.center(len(column)) if count > 0: line = " " + line elif count == length - 1: line += " |" result += line count += 1 lines.append(result) result = "" ``` will add all the other truth values to the table itself. So if you took the expression from above, `a ^ b` The final truth table would look like this: ``` | a | b | a ^ b | +---+---+-------+ | T | T | T | | T | F | F | | F | T | F | | F | F | F | ``` Now we'll move on to the `get_truth_value` and `get_truth_values` methods. This is where the height of the truth table comes from and how we manage to create every combination (not permutation) of truth values. ```py def get_truth_value(value, power): return ((value // 2 ** power) % 2) == 0 ``` This will be used in a `for` loop that iterates through 2โฟ combinations for each truth value. To simplify that, we'll use three different tables to express this. **1 variable has 2 possible truth values** ``` | a | +---+ | T | | F | ``` **2 variables have 4 possible truth values** ``` | a | b | +---+---+ | T | T | | T | F | | F | T | | F | F | ``` **3 variables have 8 possible truth values** ``` | a | b | c | +---+---+---+ | T | T | T | | T | T | F | | T | F | T | | T | F | F | | F | T | T | | F | T | F | | F | F | T | | F | F | F | ``` As you can see, we want to be able to hop back and forth between each `True` or `False` value for however many variables are in the expression. Now let's get to the code for the `get_truth_values` method. ```py ... def get_truth_values(self): # Create every possible truth combination for all variables truth_values = [] # Iterate through 2 ** variable_amount possible combinations for value in range(2 ** len(self.get_variables())): # Iterate through all variables value_dict = {} for index in range(len(self.get_variables())): # Get the power based off of the variable's index in the list power = len(self.get_variables()) - index - 1 variable = self.get_variables()[index] # Get the truth value using the get_truth_value function value_dict[variable] = get_truth_value(value, power) truth_values.append(value_dict) # Create truth values for other operations # For example, if there is a "~a", then there will be a column designated to that. # if there is a "~(a v b)", then there will be a column designated to that # as well as the "a v b" part. truth_evaluations = [] root_truth_evaluations = self._root.get_truth_values(truth_values) # Add all the truth evaluations from the root for truth_evaluation in root_truth_evaluations: if truth_evaluation not in truth_evaluations: truth_evaluations.append(truth_evaluation) # Add all the truth values as evaluations for truth_value in truth_values: for truth_variable in truth_value: truth_evaluation = { "expression": truth_variable, "truth_value": { truth_variable: truth_value[truth_variable] }, "value": truth_value[truth_variable] } truth_evaluations.append(truth_evaluation) truth_evaluations = sorted(truth_evaluations, key = lambda i: len(i["expression"])) return truth_evaluations ``` We'll go segment by segment to explain this method. ```py # Create every possible truth combination for all variables truth_values = [] # Iterate through 2 ** variable_amount possible combinations for value in range(2 ** len(self.get_variables())): # Iterate through all variables value_dict = {} for index in range(len(self.get_variables())): # Get the power based off of the variable's index in the list power = len(self.get_variables()) - index - 1 variable = self.get_variables()[index] # Get the truth value using the get_truth_value function value_dict[variable] = get_truth_value(value, power) truth_values.append(value_dict) ``` This code segment will create each dictionary for the combination of truth values given by how many variables there are. So the elements in the list that is used to evaluate expressions would match the first *n* columns where *n* is the number of different variables. So if the first row of the expression `a ^ b` would be: ``` | a | b | +---+---+ | T | T | ``` and the matching dictionary would be: ```py { "a": True, "b": True } ``` This will repeat for all the rows in the truth table. ```py # Create truth values for other operations # For example, if there is a "~a", then there will be a column designated to that. # if there is a "~(a v b)", then there will be a column designated to that # as well as the "a v b" part. truth_evaluations = [] root_truth_evaluations = self._root.get_truth_values(truth_values) # Add all the truth evaluations from the root for truth_evaluation in root_truth_evaluations: if truth_evaluation not in truth_evaluations: truth_evaluations.append(truth_evaluation) ``` As the first comment says, any variables with a ~ (NOT) operator attached to it will have their own column designated to it. This is to make it more like a truth table that you would write yourself to simplify the boolean evaluations. Again, it will only add evaluations that haven't already been evaluated. If we didn't check for that, there would be an *endless* amount of columns that exist. ```py # Add all the truth values as evaluations for truth_value in truth_values: for truth_variable in truth_value: truth_evaluation = { "expression": truth_variable, "truth_value": { truth_variable: truth_value[truth_variable] }, "value": truth_value[truth_variable] } truth_evaluations.append(truth_evaluation) truth_evaluations = sorted(truth_evaluations, key = lambda i: len(i["expression"])) ``` Now this last segment will add all the truth values for a **_single_** variable into it's own evaluation. The final line uses Python's built-in `sorted` method to sort the expressions by their lengths in order to show the build up to the final expression which is the one you want to evaluate. I know this has already been a long tutorial but just hold off for a few more explanations. The method that actually does the parsing, `parse_expression`, isn't *too* complex but it definitely requires some explanation or else you might be lost. ```py def parse_expression(expression, has_not = False): # Remove all spaces from the expression expression = expression.replace(" ", "") # Loop through and find any ^ (AND) or v (OR) operators as expressions has_not = has_not left = None operator = None right = None variables =[] parent_depth = 0 last = 0 char_has_not = False temp_has_not = False for index in range(len(expression)): char = expression[index] # Check for open parenthesis if char == "(": if parent_depth == 0: last = index + 1 parent_depth += 1 # Check for close parenthesis elif char == ")": parent_depth -= 1 # Parse expression if parenthesis depth reaches 0 if parent_depth == 0: # Check if there is a ~ (NOT) operator directly in front of the parenthesis if last - 1 > 0: if expression[last - 2] == "~": temp_has_not = True exp = parse_expression(expression[last: index], temp_has_not) if index == len(expression) - 1 and last == 0: has_not = temp_has_not temp_has_not = False # Check if there is no operator; Must be left side if operator == None: left = exp["expression"] else: right = exp["expression"] # No parenthesis depth anymore if parent_depth == 0: # Check for operator only if not within a parenthesis if char in ["^", "v"]: # Check if operator does not exist yet if operator == None: if char == "^": operator = "^" elif char == "v": operator = "v" # Operator exists; String of logical expressions exists # Make the left, operator, right into the left expression else: left = { "has_not": has_not, "left": left, "operator": operator, "right": right } if char == "^": operator = "^" elif char == "v": operator = "v" right = None has_not = False # Check for variable only if not within parentheses if ord(char) in range(ord('a'), ord('z') + 1) and ord(char) != ord('v'): # See if there is a ~ (NOT) operator directly in front of the variable if index > 0: if expression[index - 1] == "~": char_has_not = True else: char_has_not = False # Check if there is no operator; Must be left side if operator == None: left = { "has_not": char_has_not, "value": char } else: right = { "has_not": char_has_not, "value": char } char_has_not = False if char not in variables: variables.append(char) if parent_depth != 0: raise UnbalancedParentheses("You have a missing parenthesis somewhere.") variables.sort() # Check if the expression is a single expression wrapped in parentheses if operator == right == None: has_not = left["has_not"] operator = left["operator"] right = left["right"] left = left["left"] return { "expression": { "has_not": has_not, "left": left, "operator": operator, "right": right }, "variables": variables } ``` Now let's go step-by-step to explain what is going on. ```py # Remove all spaces from the expression expression = expression.replace(" ", "") # Loop through and find any ^ (AND) or v (OR) operators as expressions has_not = has_not left = None operator = None right = None variables = [] parent_depth = 0 last = 0 char_has_not = False temp_has_not = False ``` This is just the basic setup. All the variables we need, all the temporary variables we need. The reason for the `has_not` in the parameter will be explained in a little bit. ```py for index in range(len(expression)): char = expression[index] # Check for open parenthesis if char == "(": if parent_depth == 0: last = index + 1 parent_depth += 1 # Check for close parenthesis elif char == ")": parent_depth -= 1 # Parse expression if parenthesis depth reaches 0 if parent_depth == 0: # Check if there is a ~ (NOT) operator directly in front of the parenthesis if last - 1 > 0: if expression[last - 2] == "~": temp_has_not = True exp = parse_expression(expression[last: index], temp_has_not) if index == len(expression) - 1 and last == 0: has_not = temp_has_not temp_has_not = False # Check if there is no operator; Must be left side if operator == None: left = exp["expression"] else: right = exp["expression"] ``` Now the loop obviously goes through the expression as a whole which is stripped of whitespace so any spaces you put in an expression is just immediately removed. The first `if` statement, `if char == "("`, will help to keep track of the expression that is bound in parentheses. It will only match with the one that is at the same depth as it so if you have parentheses within parentheses, they will each get evaluated separately. The second `if` statement, `if char == ")"`, will determine if the current parenthesis closes off the parent parenthesis based off the `parent_depth`. If it does, then it will determine if there is a ~ (NOT) operator directly attached to it and will recursively parse through whatever is within the parenthesis. It will not include the initial set of parentheses or else that would cause infinite recursion and we don't want that. After the recursive call completes, it will reset the `has_not` variable. If we don't do that, we get incorrect expressions for specific original expressions. For example, if the original expression was `~(a ^ b) ^ c`, the parser would assume that the *entire* expression has a ~ (NOT) operator attached to it and when you printed that out, it would be `~(~(a ^ b) ^ c)`. Finally, it will determine if the parsed expression belongs to the left side or the right side based off if there is an operator or not. ```py # No parenthesis depth anymore if parent_depth == 0: # Check for operator only if not within a parenthesis if char in ["^", "v"]: # Check if operator does not exist yet if operator == None: if char == "^": operator = "^" elif char == "v": operator = "v" # Operator exists; String of logical expressions exists # Make the left, operator, right into the left expression else: left = { "has_not": has_not, "left": left, "operator": operator, "right": right } if char == "^": operator = "^" elif char == "v": operator = "v" right = None has_not = False ``` Once the parenthesis depth reaches `0`, it will find if there is an operator yet to get. The `else` statement is there in case you have an expression that doesn't include parentheses but has more than two variables to evaluate. For example, if you give the parser `a ^ b ^ c`, it will automatically put the `a ^ b` in the left side and the resulting expression will look like this: `(a ^ b) ^ c`. An easy way to get around that is by placing your own parentheses. ```py # Check for variable only if not within parentheses if ord(char) in range(ord('a'), ord('z') + 1) and ord(char) != ord('v'): # See if there is a ~ (NOT) operator directly in front of the variable if index > 0: if expression[index - 1] == "~": char_has_not = True else: char_has_not = False # Check if there is no operator; Must be left side if operator == None: left = { "has_not": char_has_not, "value": char } else: right = { "has_not": char_has_not, "value": char } char_has_not = False if char not in variables: variables.append(char) ``` This segment will only run if it is not in parentheses. What it will do is find any letter that is not the letter `v` (this is used as the OR operator) and add that to the list of variables which is used in the `parse` method mentioned earlier. It will then determine if the variable given will belong to the left or right side of the expression. Last, but not least, we have the final part of the `parse_expression` method. (yippee kayak!) ```py if parent_depth != 0: raise UnbalancedParentheses("You have a missing parenthesis somewhere.") variables.sort() # Check if the expression is a single expression wrapped in parentheses if operator == right == None: has_not = left["has_not"] operator = left["operator"] right = left["right"] left = left["left"] return { "expression": { "has_not": has_not, "left": left, "operator": operator, "right": right }, "variables": variables } ``` If the `for` loop reaches the end of the expression and the `parent_depth` is not `0`, it will obviously raise an error because you can't have an unclosed parenthesis! Then it will sort the variables in place so the truth table, when created, will show each variable in ascending order. The `if` statement there, `if operator == right == None`, is for any expression that might be enclosed in parentheses for no apparent reason *or* if the expression as a whole has a ~ (NOT) operator attached to it. For example, `~(a ^ b)` would be properly processed as the whole expression. Then obviously the `return` statement will give you the expression and all its attributes and the variables that are in the expression itself. ___ ### Other Discrete Math Operators * Implies (->) * ```py if self._operator == "->": if self.has_not(): return not (not left or right) return not left or right ``` * Biconditional (<->) * ```py if self._operator == "<->": if self.has_not(): return not (left == right) return left == right ``` * NAND (|) * ```py if self._operator == "|": if self.has_not(): return left and right return not (left and right) ``` * NOR (โฌ‡) * ```py if self._operator == "โฌ‡": if self.has_not(): return left or right return not (left or right) ``` ___ ## Final Comments I know this was a long tutorial. I did not expect it to be this long. However, I hope I helped to provide some inspiration to create a more complex logic parser. My [Logic Parser Repl](https://repl.it/@FellowHashbrown/Logic-Parser) actually has a few more operators that are in discrete math along with more operator flexibility meaning that you could type in `a and b` and it will be properly processed as `a ^ b`. ___ If you have any suggestions or see any issues, please let me know that way I can fix it! If you find any bugs in my parser, *definitely* let me know either by leaving a comment, pinging me in Repl.it's Discord Server, or pinging me in my own [Discord Server](https://discord.gg/W8yVrHt).
0
posted by FellowHashbrown (21) 5 days ago
โ–ฒ
1
How to make a list 'count' to 1000
First, you have to import random and time. When you have finished, type in time.sleep(5) We're using this to prevent a sudden crash. You should now type in print(list(range(1,1000))) as your final line. Your program should look like this:
2
posted by madmonster (2) 7 days ago
โ–ฒ
5
Discord.py - Rewrite Tutorial using commands extension
# Discord.py Rewrite **Note : ** *Before you start this tutorial, You should already know the language **python**, **how to create applications in discord** and you should've already **invited the bot to your server**.* ------ ## Step 1 : Setting up 1. *Create a new folder for your bot, it can be anything. For reference, **I** will name it* `Sample Bot`. 2. *Open up* `command prompt` *or* `terminal` *and type the following commands in it.* ``` $ pip install -U git+https://github.com/Rapptz/[email protected]#egg=discord.py[voice] ``` 3. *Now create a new file named* `main.py` *in your bot's folder. This file will hold the main bot.* 4. *Also create a new directory named* `cogs` *in your bot's directory, this will hold our command groups (aka cogs).* ***Now you're ready to code*** ------ ## Step 2 : The main bot 1. *Open up your* `main.py` *file. Given below is some basic code with everything explained using comments.* ```python from discord.ext import commands def get_prefix(client, message): prefixes = ['=', '=='] # sets the prefixes, u can keep it as an array of only 1 item if you need only one prefix if not message.guild: prefixes = ['=='] # Only allow '==' as a prefix when in DMs, this is optional # Allow users to @mention the bot instead of using a prefix when using a command. Also optional # Do `return prefixes` if u don't want to allow mentions instead of prefix. return commands.when_mentioned_or(*prefixes)(client, message) bot = commands.Bot( # Create a new bot command_prefix=get_prefix, # Set the prefix description='A bot used for tutorial', # Set a description for the bot owner_id=374886124126208000, # Your unique User ID case_insensitive=True # Make the commands case insensitive ) # case_insensitive=True is used as the commands are case sensitive by default @bot.event async def on_ready(): # Do this when the bot is logged in print(f'Logged in as {bot.user.name} - {bot.user.id}') # Print the name and ID of the bot logged in. return # Finally, login the bot bot.run('BOT TOKEN here', bot=True, reconnect=True) ``` 2. *Now run this file, commands extension will prepare a default* `help` *command for you, you should see something similar to this when you use it: -* ![Help Command](https://i.imgur.com/8CosQsj.png) *Note that it doesn't show help when* `=help` *is used as this was done in DMs, and we only allowed* `==help` *in DMs.* 3. *Next we add the commands from the* `cogs` *directory.* 4. *Inside the* `cogs` *directory, create a new file named* `basic.py`. 5. *Add the following code above the* ```python @bot.event async def on_ready(): ``` *part in the* `main.py` *file* ```python cogs = ['cogs.basic'] ``` *This basically points to the* `basic.py` *file in the* `cogs` *folder.* 6. *Next, change* ```python async def on_ready(): print(f'Logged in as {bot.user.name} - {bot.user.id}') return ``` *to* ```python async def on_ready(): print(f'Logged in as {bot.user.name} - {bot.user.id}') for cog in cogs: bot.load_extension(cog) return ``` *This will load the all the **files** specified in the **cogs** variable.* 7. *Next, open up the* `basic.py` *file and write the following code in it.* ```python from discord.ext import commands from datetime import datetime as d class Basic: def __init__(self, bot): self.bot = bot # Define a new command @commands.command( name='ping', description='The ping command', aliases=['p'] ) async def ping_command(self, ctx): start = d.timestamp(d.now()) # Gets the timestamp when the command was used msg = await ctx.send(content='Pinging') # Sends a message to the user in the channel the message with the command was received. # Notifies the user that pinging has started await msg.edit(content=f'Pong!\nOne message round-trip took {( d.timestamp( d.now() ) - start ) * 1000 }ms.') # Ping completed and round-trip duration show in ms # Since it takes a while to send the messages # it will calculate how much time it takes to edit an message. # It depends usually on your internet connection speed return def setup(bot): bot.add_cog(Basic(bot)) # Adds the Basic commands to the bot # Note: The "setup" function has to be there in every cog file ``` *This will add a new ping command to your bot!* 8. Save the file ------ ## Step 3 : Checking the new command 1. *Run the **main.py** again.* 2. *Use the* `help` *command. It should look something like this: -* ![New Help](https://i.imgur.com/fschYtb.png) *Observe the new **Basic** category and **ping** command listed under it. This means that our command was successfully loaded.* 3. *Now use the* `help ping` *command, should look something like this: -* ![Ping Help](https://i.imgur.com/IUUFYGv.png) *Note that in the example, it shows the prefix used by you!* 4. *Finally, let's use the **ping command**.* ![Ping Command](https://i.imgur.com/oeGwqqf.png) *This is an example run (my internet is a bit slow)* ------ ## Step 4 : Commands with arguments 1. *Open up the* `basic.py` *file and add the following command to it by typing it just below the* `return` *statement of the ping command. This is a say command that repeats the words said by the user (in bold): -* ```python @commands.command( name='say', description='The say command', aliases=['repeat', 'parrot'], usage='<text>' ) async def say_command(self, ctx): # The 'usage' only needs to show the parameters # As the rest of the format is generated automatically # Lets see what the parameters are: - # The self is just a regular reference to the class # ctx - is the Context related to the command # For more reference - https://discordpy.readthedocs.io/en/rewrite/ext/commands/api.html#context # Next we get the message with the command in it. msg = ctx.message.content # Extracting the text sent by the user # ctx.invoked_with gives the alias used # ctx.prefix gives the prefix used while invoking the command prefix_used = ctx.prefix alias_used = ctx.invoked_with text = msg[len(prefix_used) + len(alias_used):] # Next, we check if the user actually passed some text if text == '': # User didn't specify the text await ctx.send(content='You need to specify the text!') pass else: # User specified the text. await ctx.send(content=f"**{text}**") pass return ``` 2. *Next, let's use this command!* ![Say Command](https://i.imgur.com/gRFbtl9.png) *As you can see, works perfectly!* ------ ## Step 5 : Embeds 1. *Since, we've already done this, let's do one final command. The **embed** command.* **What this command will do?** - *Ask for the title of the embed.* - *Ask the user to specify the text for the embed description.* - Set a random color to the embed. - *Send the embed in the channel* **What this will help you learn?** - *How to create and send embeds.* - *How to wait for user's response.* 2. *In your **cogs** directory, create a new file named* `embed.py` 3. *In your* `main.py` *file, to the **cogs list** which formerly was* `cogs = ['cogs.basic']` *add a new **cog** i.e.* `'cogs.embed'`*, making it -* `cogs = ['cogs.basic', 'cogs.embed']` *. This will tell it to also treat the* `embed.py` *file as another **cog**.* 4. *Open up the* **embed.py** *file and write the following code in it: -* 5. ```python from discord.ext import commands import discord import random # These color constants are taken from discord.js library colors = { 'DEFAULT': 0x000000, 'WHITE': 0xFFFFFF, 'AQUA': 0x1ABC9C, 'GREEN': 0x2ECC71, 'BLUE': 0x3498DB, 'PURPLE': 0x9B59B6, 'LUMINOUS_VIVID_PINK': 0xE91E63, 'GOLD': 0xF1C40F, 'ORANGE': 0xE67E22, 'RED': 0xE74C3C, 'GREY': 0x95A5A6, 'NAVY': 0x34495E, 'DARK_AQUA': 0x11806A, 'DARK_GREEN': 0x1F8B4C, 'DARK_BLUE': 0x206694, 'DARK_PURPLE': 0x71368A, 'DARK_VIVID_PINK': 0xAD1457, 'DARK_GOLD': 0xC27C0E, 'DARK_ORANGE': 0xA84300, 'DARK_RED': 0x992D22, 'DARK_GREY': 0x979C9F, 'DARKER_GREY': 0x7F8C8D, 'LIGHT_GREY': 0xBCC0C0, 'DARK_NAVY': 0x2C3E50, 'BLURPLE': 0x7289DA, 'GREYPLE': 0x99AAB5, 'DARK_BUT_NOT_BLACK': 0x2C2F33, 'NOT_QUITE_BLACK': 0x23272A } class Embed: def __init__(self, bot): self.bot = bot @commands.command( name='embed', description='The embed command', ) async def embed_command(self, ctx): # Define a check function that validates the message received by the bot def check(ms): # Look for the message sent in the same channel where the command was used # As well as by the user who used the command. return ms.channel == ctx.message.channel and ms.author == ctx.message.author # First ask the user for the title await ctx.send(content='What would you like the title to be?') # Wait for a response and get the title msg = await self.bot.wait_for('message', check=check) title = msg.content # Set the title # Next, ask for the content await ctx.send(content='What would you like the Description to be?') msg = await self.bot.wait_for('message', check=check) desc = msg.content # Finally make the embed and send it msg = await ctx.send(content='Now generating the embed...') color_list = [c for c in colors.values()] # Convert the colors into a list # To be able to use random.choice on it embed = discord.Embed( title=title, description=desc, color=random.choice(color_list) ) # Also set the thumbnail to be the bot's pfp embed.set_thumbnail(url=self.bot.user.avatar_url) # Also set the embed author to the command user embed.set_author( name=ctx.message.author.name, icon_url=ctx.message.author.avatar_url ) await msg.edit( embed=embed, content=None ) # Editing the message # We have to specify the content to be 'None' here # Since we don't want it to stay to 'Now generating embed...' return def setup(bot): bot.add_cog(Embed(bot)) # Adds the Basic commands to the bot # Note: The "setup" function has to be there in every cog file ``` 6. *Now, let's run this command!* ![Imgur](https://i.imgur.com/PschlWI.png) 7. *And that's it!* ------ ## Step 6 : Changing the default help command 1. *As we've already seen, commands extension already comes with a built-in help command. Let's use it again!* ![Imgur](https://i.imgur.com/xM5UCOk.png) *Pretty clearly, it's very ugly. So, let's change it!* 2. *So, what we'll do is, remove the pre-defined **help** command and then create a new **help** command in the* `embed.py` *file.* 3. *Open up the **main.py** file and make the following changes: -* ```python @bot.event async def on_ready(): print(f'Logged in as {bot.user.name} - {bot.user.id}') bot.remove_command('help') # Removes the help command # Make sure to do this before loading the cogs for cog in cogs: bot.load_extension(cog) return ``` 4. *Open the* `embed.py` *file and make a new **help** command is given below: -* **NOTE : ** *We're making this in the **embed.py** file because this help command will be using embeds too. ( though it's not necessary to do even if it uses embeds, I just don't want to create another file with all those colors).* **What this help command will do?** - Get all the cogs registered. - If the user has passed a cog argument, then list all commands under that cog. - Else, list all cogs and commands under them. ```python @commands.command( name='help', description='The help command!', aliases=['commands', 'command'], usage='cog' ) async def help_command(self, ctx, cog='all'): # The third parameter comes into play when # only one word argument has to be passed by the user # Prepare the embed color_list = [c for c in colors.values()] help_embed = discord.Embed( title='Help', color=random.choice(color_list) ) help_embed.set_thumbnail(url=self.bot.user.avatar_url) help_embed.set_footer( text=f'Requested by {ctx.message.author.name}', icon_url=self.bot.user.avatar_url ) # Get a list of all cogs cogs = [c for c in self.bot.cogs.keys()] # If cog is not specified by the user, we list all cogs and commands if cog == 'all': for cog in cogs: # Get a list of all commands under each cog cog_commands = self.bot.get_cog_commands(cog) commands_list = '' for comm in cog_commands: commands_list += f'**{comm.name}** - *{comm.description}*\n' # Add the cog's details to the embed. help_embed.add_field( name=cog, value=commands_list, inline=False ).add_field( name='\u200b', value='\u200b', inline=False ) # Also added a blank field '\u200b' is a whitespace character. pass else: # If the cog was specified lower_cogs = [c.lower() for c in cogs] # If the cog actually exists. if cog.lower() in lower_cogs: # Get a list of all commands in the specified cog commands_list = self.bot.get_cog_commands(cogs[ lower_cogs.index(cog.lower()) ]) help_text='' # Add details of each command to the help text # Command Name # Description # [Aliases] # # Format for command in commands_list: help_text += f'```{command.name}```\n' \ f'**{command.description}**\n\n' # Also add aliases, if there are any if len(command.aliases) > 0: help_text += f'**Aliases :** `{"`, `".join(command.aliases)}`\n\n\n' else: # Add a newline character to keep it pretty # That IS the whole purpose of custom help help_text += '\n' # Finally the format help_text += f'Format: `@{self.bot.user.name}#{self.bot.user.discriminator}' \ f' {command.name} {command.usage if command.usage is not None else ""}`\n\n\n\n' help_embed.description = help_text else: # Notify the user of invalid cog and finish the command await ctx.send('Invalid cog specified.\nUse `help` command to list all cogs.') return await ctx.send(embed=help_embed) return ``` 5. *This is just an example and gives you an basic idea of what it is like. Below is a sample run of this new help command.* ![Main Help](https://i.imgur.com/q0mCFDb.png) *No **cog** specified, commands listed* ![Embed help](https://i.imgur.com/SEAZ2Cf.png) ***Embed cog*** *specified, show the details of commands under **Embed** category.* ![Invalid help](https://i.imgur.com/lITNGie.png) *Invalid **cog** specified, send error message.* ------ ### The End ***Those are all the basics you'll need to know, refer to https://discordpy.readthedocs.io/en/rewrite/ext/commands/api.html for reference on the discord.py commands extension. You can also refer to https://discordpy.readthedocs.io/en/rewrite/api.html for main library docs. All of the above code can be obtained at - https://repl.it/@TheDrone7/discordpy-rewrite.***
0
posted by TheDrone7 (21) 9 days ago
โ–ฒ
10
Debugging in python, fun and easy!
# Debug Without Errors Debugging is the most dreaded part of programming. It should just work! And sometimes bugs can lead to hours of extra work. However they don't need to be, and by following some of the steps presented in this guide, hopefully you'll be able to fight your next bugs with vigor and valiance! In this tutorial, we'll be using [this program, which is intentionally broken](https://repl.it/@21natzil/Debug-Example). Follow along and make the changes we talk about, and see it be fixed right before your eyes! (Don't try to figure out what it does, it just does random things). Alright, so when we run this program we get... ``` The max number cannot be 0 The max number cannot be 0 The max number cannot be 0 The max number cannot be 0 ``` Oh no! We didn't even get an exception! Where do we even begin? Forchanetly, this program is very small. However for larger projects this is a huge issues. A smart place to start, would be the line that displays the output. In this case, it's ```py except: print("The max number cannot be 0") ``` Now, in this program we only want this error to be raised in the max_number is 0! Clearly, the condition that must be met to trigger this line is wrong. The condition in this scenario is a try-except case, with no defined exceptions! This is bad code 101, also define what exception you want to catch, else wise python will catch all of them. Plus, the message doesn't need to be in a try-except statement. We can create an if-statement at the start of the function, where is the max number is 0, it will print the message and exit the function. If we remove the try-except statement and add the if statement, the function will look like this: ```py def handle_math(min_number: int, max_number: int): if max_number == 0: print("The max number cannot be 0") return print( get_data() * int(min_number / max_number) ) return (min_number / max_number) ``` Great problem solved and if we run that... ``` The max number cannot be 0 The max number cannot be 0 The max number cannot be 0 The max number cannot be 0 ``` What? We just fixed this error! Why is it still here? Don't panic, we know it's not the try-catch anymore. Think about it reasonably, this means that the code calling the function is calling it wrong, so let's take a look at it. ```py numbers = [0, 1, 2, 3] for i in range(len(numbers)): r = handle_math(5, numbers[i]) numbers.insert(0, r) ``` This is a great time to use the *rubber duck* technique. The idea is you read out your code to a rubber duck, although the rubber duck is optional. So let's do just that, we make a list, create a range from 0-3, call `handle_math` with 5 and the item in the list at an index from 0-3. Then we update the items in the list by adding the result of handle_math to the start of the list. Did you catch the error? Maybe we should visualize what's happening. The list when we start looks like the this with`i` being 0. |Items:| 0 | 1 | 2 | 3 | |--------|---|---|---|---| |Index:| **0** | 1 | 2 | 3 | The `handle_math` we return None, because the value passed in was 0. Then None is added to the start of the list, and then the loop increments `i` so it's now `1`, however now the list looks like: |Items:| N | 0 | 1 | 2 | 3 | |--------|---|---|---|---|---| |Index:| 0 | **1** | 2 | 3 | 4 | As you can see, the 0 moved up, and so it's index is `1`. The *range* generator doesn't compensate for that, and so `i` will always be `0`. To fix this, instead of iterating the index, let's iterate through each element in the list, which will look like: ```py numbers = [0, 1, 2, 3] for i in numbers.copy(): r = handle_math(5, i) numbers.insert(0, r) ``` *Phew*, we're finally done! Now if we run this we'll get: ``` The max number cannot be 0 Traceback (most recent call last): File "main.py", line 22, in <module> r = handle_math(5, i) File "main.py", line 15, in handle_math get_data() * (min_number / max_number) File "main.py", line 7, in get_data return resp.content.decode('ascii') UnicodeDecodeError: 'ascii' codec can't decode byte 0x81 in position 4: ordinal not in range(128) ``` Oh no, another bug! When will it end. Don't fret, we're actually making progress, and we can locate the current error with more ease! The problem now is this error message is super complicated. `'ascii' codec can't decode byte 0x81 in position 4: ordinal not in range(128)`? What does that even mean? Errors like these are can be intimidating, but through the power of google, we will prevail! If you simply paste your exception into google, the first site is a [stackover flow post](https://stackoverflow.com/questions/21129020/how-to-fix-unicodedecodeerror-ascii-codec-cant-decode-byte) which has a response that goes into great detail. If we read that, we'll realize that the issue is we're decoding bytes into ascii, which can't represent all the values in our data. Instead, we should use uft-8, which can handle all these values. Now that function looks like: ```py def get_data(size=16): with requests.Session() as session: with session.get(f"https://httpbin.org/stream-bytes/{size}") as resp: return resp.content.decode('utf-8') ``` Wew, nice! Now that we've done our research, we should be in the clear right? Running our code now gives us: ``` The max number cannot be 0 <<<<< << < ``` Which is actually what we wanted! We're done! *I know, this looks random and trust me it is, I just needed a broken program*. I hope you learned much about debugging, and maybe I'll make a follow up using `pdb` if this is well received. [You can find the finished, debugged version here.](https://repl.it/@21natzil/Debug-Fixed) ## Have a great day!
2
posted by 21natzil (400) 12 days ago
โ–ฒ
9
Making a discord bot using the discord.js Commando framework
# Discord.js Commando Framework tutorial *First of all, I recommend you all that you first go through the basics of JavaScript and Node.js from anywhere you'd like to though I personally recommend https://www.w3schools.com/ . Also, you should know how to create discord apps at https://discordapp.com/developers/applications/me and should've already invited the bot to your test server, this is only a tutorial for the commando framework, not the whole bot thing.* *Next, let's get started* - ### Step 1: Setting up the project 1. *Create a **new folder** (Make sure there are no spaces in your folder name) in your system and open **command prompt** or **power shell** in the folder.* 2. *Run the command* `npm init` *and fill out the form, If you don't know what you're doing, then leave everything to default and type in you name when it asks for* `author`. ```powershell PS E:\tutorial-bot> npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (tutorial-bot) version: (1.0.0) description: A new discord bot entry point: (index.js) test command: git repository: keywords: author: HS license: (ISC) About to write to E:\tutorial-bot\package.json: { "name": "tutorial-bot", "version": "1.0.0", "description": "A new discord bot", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "HS", "license": "ISC" } Is this ok? (yes) PS E:\tutorial-bot> ``` 3. *After this, you will see a new file named* `package.json` *in your folder.* 4. *Now use the following command to install the required libraries* ```powershell PS E:\tutorial-bot> npm install --save discord.js discord.js-commando fs sqlite path ``` 5. *Now you'll see a new folder named* `node-modules` *in your bot's folder and you're ready to start coding the bot.* - ### Step 2 : Writing the index.js file 1. *Create a new file in your bot's folder called* `index.js` *and open it with your favourite text editor.* 2. *Now write the following code in the beginning of your **index.js** file.* ```javascript const commando = require('discord.js-commando') const path = require('path') const sqlite = require('sqlite') ``` *These lines will import the necessary libraries to use.* 3. *Next, write the following code* ```javascript const client = new commando.CommandoClient({ owner: '374886124126208000', // Your ID here. commandPrefix: '==', // The prefix of your bot. unknownCommandResponse: false, // Set this to true if you want to send a message when a user uses the prefix not followed by a command }) ``` *This will create a* `CommandoClient` *Object which is an extension of* `discord.js.Client`*class and set the owner of the bot as yourself to allow you to use the owner only commands that come built-in with Commando.* 4. *Now we will import the default commands for our bot.* ```javascript client.registry.registerDefaults() ``` 5. *Now let's make a handler that displays a message in the console when the bot is logged in and ready to use.* ```javascript client.on('ready',()=>{ console.log(`Logged in and ready to be used.. use "${client.commandPrefix}help".`) }) ``` 6. *Next, set up the settings provider, Commando comes with built-in sqlite settings provider to allow default commands like* `prefix` *work properly.* ```javascript client.setProvider( sqlite.open(path.join(__dirname, 'settings.sqlite3')).then(db => new Commando.SQLiteProvider(db)) ).catch(console.error); ``` 7. *Last of all, login the bot* ```javascript client.login("BOT_TOKEN") ``` - ### Testing the defaults 1. *Run the bot by using the command* `node index` *in your console.* 2. *After a while you will see a message similar to this: -* ```powershell PS E:\tutorial-bot> node index Logged in and ready to be used.. use "==help". ``` *You will also observe that your bot comes online so it's ready to be checked out.* 3. *Use the help command which in my case is* `==help` *It shows something like this in your DMs: -* ``` To run a command in *Server Name*, use == command or @Bot command. For example, == prefix or @Bot prefix. To run a command in this DM, simply use command with no prefix. Use help <command> to view detailed information about a specific command. Use help all to view a list of all commands, not just available ones. Available commands in *Server Name* Commands groups: Lists all command groups. enable: Enables a command or command group. disable: Disables a command or command group. reload: Reloads a command or command group. load: Loads a new command. unload: Unloads a command. Utility help: Displays a list of available commands, or detailed information for a specified command. prefix: Shows or sets the command prefix. ping: Checks the bot's ping to the Discord server. eval: Executes JavaScript code. ``` #### Details - **groups**: The commands in a commando bot are grouped together under categories. the `group` command can be used the list these categories. - **enable/disable**: Commando framework comes with an in-built feature of enabling/disabling commands in different guilds, these commands do the job. - **load/unload**: These commands can be used to register commands from outside the code. - **reload**: This command can be used to reload a command after you've made changes to the code to refresh it. - **help**: The in-built help function ( very detailed ). - **prefix**: Commando also comes with a feature to change the default prefix in different servers, the `prefix` command helps the users do it. - **ping**: Does exactly what help says. - **eval**: runs the code passed as an extension of the `index.js` file and returns the output ( recommended not to use ). ***These are the commands that come built-in with commando framework.*** - ### Creating your own commands 1. *First of all, create a folder named* `commands` *in your bot's root directory.* 2. *Next make the following change in your code* ```javascript client.registry.registerDefaults() ``` *to* ```javascript client.registry.registerDefaults() .registerGroups([ ['test', 'Starter Commands'] ]) .registerCommandsIn(path.join(__dirname,"commands")) ``` *This will create a new group named **Starter Commands** for your bot which can be referred to in the code by the name **test** and also read the files inside the **commands** folder and treat them as commands for the bot*. 3. *Inside the **commands** folder, create another folder named* `test` *and inside the **test** folder, create a file named* `foo.js` *as the first command will be a simple foo-bar command.* 4. *Open the* `foo.js` *file and change it's contents to* ```javascript const { Command } = require('discord.js-commando') class fooCommand extends Command{ constructor(client){ super( client, { name: 'foo', memberName: 'foo', aliases: ['f'], group: 'test', description: 'Responds with "bar".', details: 'The traditional foo-bar.', examples: ['==foo','==f'] }) } run(msg){ msg.say('bar') } } module.exports = fooCommand ``` ##### Details - *The first line imports the predefined* `Command` *class from the Commando Framework.* - *Next, we created the command as a extension of the* `Command` *class.* - *The constructor is run as soon as our **index.js** reads this file. Inside our constructor, we set the basic details about our command.* **name, memberName**: set the name of the command. **aliases**: sets what other commands can be used to do the same thing. **description, details**: describe what the command does. **examples**: sets an example for the user on how to use the command. **group**: sets our command will be part of which command group. - *The next part is the **run** method, it is called whenever the user uses the command, the parameter **msg** is the message sent by the user, it's of the type* `CommandoMessage`. *We make our bot respond with* `bar`*whenever the user uses the* `foo ` *command.* - *Finally,* `module.exports = fooCommand` *exports the command making it readable by our **index.js** file.* 5. *Now, let's test out our foo command.* *Run the bot using* `node index` *and use the **help** command.* *You will see something new at the end of the help menu* ``` Starter Commands foo: Responds with "bar". ``` *Let's use help about our command, use the **help foo** command. You should see something like: -* ``` __Command **foo:**__ Responds with "bar". **Format:** == `foo` or `@Bot foo` **Aliases:** f **Group:** Starter Commands (`test:foo`) **Details:** The traditional foo-bar. **Examples:** ==foo ==f ``` *Let's use our command now, there are 4 ways to do it now.* - Use `==foo` or `==f` in a server - Use `foo` or `f` in DMs *You will see that the bot responds with* `bar` *wherever you use the command.* ------ 6. *Now that we are done with one command, let's make another one that takes in input (**arguments**) from the user.* 7. *Create a new file named* `say.js` *in your **test** folder.* 8. *Write the following code in the **say.js** file.* ```javascript const { Command } = require('discord.js-commando') class sayCommand extends Command { constructor(client) { super(client, { name: 'say', memberName: 'say', group: 'test', aliases: ['echo', 'repeat'], description: 'A command that repeats whatever you say.', details: "Make me repeat your wordsmaking it look like I'm a parrot", examples: ['==say Hello World', '==repeat Who Am I?'], clientPermissions: ["MANAGE_MESSAGES"], args: [{ key: 'text', prompt: 'What do you wish for me to say?', type: 'string' }] }) } run(msg, { text }) { msg.say(text) return msg.delete() } } module.exports = sayCommand ``` ##### Let's see what's new! - **client_permissions**: defines a list of permissions that your not will need for the command to work properly. - **args**: Defines what arguments the user has to enter. - **key**: defines the name of the argument. - **prompt**: defines what the bot will say if the user has not provided the argument - **type**: defines the type of the argument. - *The run method now has 2 parameters, the second one is the arguments, we are asking the user to pass.* - `msg.delete()`*deletes the message sent by the user.* 9. *Let's test out our say command* *If you use the **help** command now, you will see a new entry at the bottom for the **say** command.* *Use **help say** before using the command.* *You should see something similar to: -* ``` __Command **say:**__ A command that repeats whatever you say. **Format:** == `say <text>` or `@Bot say <text>` **Aliases:** echo, repeat **Group:** Starter Commands (`test:say`) **Details:** Make me repeat your words making it look like I'm a parrot **Examples:** ==say Hello World ==repeat Who Am I? ``` *Notice how **Commando** prepared the **format** for how to use the command, trust me it's a handy feature.* *Use the* `say` *command now without passing any arguments.* *You will see that the bot asks you for the text to say, also with a message like* ``` Respond with `cancel` to cancel the command. The command will automatically be cancelled in 30 seconds. ``` *This is another feature of Commando framework - command cancellation.* *You can either say something for the bot to repeat it or wait for 30 seconds to cancel the command or say cancel yourself.* *You can also pass on the argument while using the command as in the examples.* - ### Validating and throttling *Now let's think, what if the user was trying to send links or spam in the server using our say command?* *That can easily be prevented by validating the* `say` *command and making sure that there was no link in the argument passed.* *As for the spam, it can be prevented by throttling i.e. making sure that the command is not used more than a specified number of times in a specified amount of time.* *Here's a sample code (This is modified* `say.js` *file) : -* ```javascript const { Command } = require('discord.js-commando') class sayCommand extends Command { constructor(client) { super(client, { name: 'say', memberName: 'say', group: 'test', aliases: ['echo', 'repeat'], description: 'A command that repeats whatever you say.', details: "Make me repeat your wordsmaking it look like I'm a parrot", examples: ['==say Hello World', '==repeat Who Am I?'], clientPermissions: ["MANAGE_MESSAGES"], args: [{ key: 'text', prompt: 'What do you wish for me to say?', type: 'string', validate: text=>{ if(text.indexof("http://") > -1 || text.indexOf("https://") > -1) return "You are trying to send a link." // Message to display when invalid argument has been passed should be returned else return true // true should be returned when the argument is valid } }], throttling: { duration: 60, // This is the duration in seconds usages: 2 // Allowed number of usages in the given duration } }) } run(msg, { text }) { msg.say(text) return msg.delete() } } module.exports = sayCommand ``` ##### Now let's see: - - **argument.validate: ** a function that checks for validation of the argument passed. Returns `true` when valid and the `error message` when invalid. - **throttiling:** an object that specifies how many usages in how much duration of time are allowed. - ### Messing with the defaults *You can also modify the default commands: -* *You can find them at* ``` YourBotDirectory/node_modules/discord.js-commando/src/commands/ ``` *In there, you will see 2 folders containing the command files.* - `/util/` - `eval.js` - enables the owner to run javascript code from discord. - `help.js` - shows help for the various commands. - `ping.js` - displays the bot's ping to the discord server. - `prefix.js` - changes the prefix for the server ( guild Only command ). - `/commands/` - `enable.js` - enables a command in a server. - `disable.js` - disables a command in a server. - `groups.js` - lists all command groups. - `load.js` - loads a new command. - `reload.js` - reload a command. - `unload.js` - unloads a command. ------ **That's all you need to know to get started, put your imagination to the test and make amazing bots. Here's the link to the official docs for more reference: **https://discord.js.org/#/docs/commando/master/general/welcome **Also here's a link to a Commando bot by @kpostal10 named Bug Bot just for reference: **https://repl.it/@kpostal10/BugBot
10
posted by TheDrone7 (21) 19 days ago
โ–ฒ
11
Introduction to Machine Learning with Python and Repl.it
Hey all, I've written a tutorial to explain basic machine learning concepts and to show how to get started with the great Python scikit-learn library. I hope it helps, especially if you're taking part or wanting to take part in the Repl.it AI competition! The tutorial is published over here: https://www.codementor.io/garethdwyer/introduction-to-machine-learning-with-python-and-repl-it-rln7ywkhc As always, keep the feedback coming!
0
posted by GarethDwyer1 (78) 19 days ago
โ–ฒ
16
Build an ML app, with just a little JavaScript ๐Ÿค–
## Making a feature extractor with ml5.js and p5.js ![](https://ecstatic-rosalind-da34e4.netlify.com/main.gif) [Demo](https://feature-extractor--jajoosam.repl.co) โฏ๏ธ [Code](https://repl.it/@jajoosam/feature-extractor) ๐Ÿ‘จโ€๐Ÿ’ป [Video Walkthrough](https://ecstatic-rosalind-da34e4.netlify.com/feature-extractor-demo-compressed-3e7aac1a-1c37-4448-92ab-140b5dd84056.mp4) ๐ŸŽฆ Machine Learning is a super cool way to build AI apps - but I won't lie, it is a lot of math. Luckily, thanks to [ml5.js](https://ml5js.org) - we don't need to spend months understanding ML, we can apply it and develop some cool uses in our app within just a few hours ๐Ÿ˜„ This is a guide to build a feature extractor - something that can get a video stream from your webcam and predict what it sees, once it is trained with a little bit of data - just like in the video above! ## ๐Ÿด Forking the frontend I don't want to waste too much of your time with the frontend I used for this app - I think it'd be much better if I skipped right to the JavaScript, and logic. You should fork this repl - [https://repl.it/@jajoosam/feature-extractor-start](https://repl.it/@jajoosam/feature-extractor-start) - so that you have the HTML already there! It's essentially just a few `div`s for holding the video and some other text, along with some buttons to control our app! I've commented everything (except the CSS ๐Ÿ˜›) - so you should be able to go through the frontend and understand the basic layout of the app in a few minutes. Once you're done, head over to `script.js` ![](https://ecstatic-rosalind-da34e4.netlify.com/Untitled-8e918105-6ae0-4591-8362-7ed6e7068460.png) ## โœ๏ธ Declaring the variables We're going to have quite a few variables to declare, so the first thing we're going to do is create them. Our app is going to have a lot of functions, and it's important that all of them can access the variables - which is why we shall declare right at the start of our code ```javascript var featureExtractor, classifier, video, loss, redCount, blueCount; redCount = blueCount = 0; ``` I'll give you an overview of what these are for, but you'll understand them much better as we continue building our app. `featureExtractor` and `classifier` are variables we're going to store and initialize machine learning models in. `video` is where we're storing the webcam stream, while `loss` lets us know the progress of how far our on feature extractor has been trained. Finally, `blueCount` and `redCount` are counters for how many images there are in each category - and we initalize both of them with a value of `0`, in the next line. ## ๐Ÿ› ๏ธ The `setup()` `setup()` is a function which shall fire up as soon as our code is ready to run. Bacause of using [p5.js](https://p5js.org), our code is pretty readable and easy to understand here. ```javascript function setup() { // Tells p5 to not automatically create a canvas element. noCanvas(); // Starts capturing a video feed from the webcam video = createCapture(VIDEO); // Puts the video stream into the div in our html, with ID `video` video.parent('video'); // Initializes a feature extractor, yet to be trained - from ml5.js featureExtractor = ml5.featureExtractor('MobileNet'); classifier = featureExtractor.classification(video); // What we're doing next - setting up buttons! setupButtons(); } ``` This code goes in your `script.js` file right after you declare variables - essentially, it gets a video stream and displays it on our page, inside a `div` with the ID `video`. We also make some functions using the ml5.js library here, and pass the captured video as a parameter - you'll see what we do with these soon, but there's still a little more setup left! As you can see, at the end of `setup()`, we call `setupButtons()` - the function we're going to make next. Here, we're adding event listeners to buttons in our HTML - so we can run functions when they're clicked. Here's all the code we write for the `setupButtons()` function ๐Ÿ‘‡ ```javascript // A function to create the buttons function setupButtons() { buttonA = select('#red'); buttonB = select('#blue'); โ€‹ โ€‹ buttonA.mousePressed(function() { โ€‹ redCount++; โ€‹ classifier.addImage('red'); โ€‹ select('#redCount').html(redCount); โ€‹ }); โ€‹ โ€‹ buttonB.mousePressed(function() { โ€‹ blueCount++; โ€‹ classifier.addImage('blue'); โ€‹ select('#blueCount').html(blueCount); โ€‹ }); โ€‹ train = select('#train'); train.mousePressed(function() { classifier.train(function(lossValue) { // This is where we're actually training our model if (lossValue) { loss = lossValue; select('#info').html('Loss: ' + loss); } else { select('#info').html('Done Training! Final Loss: ' + loss); select('#train').style("display", "none"); select('#predict').style("display", "inline"); } }); }); // Predict Button buttonPredict = select('#predict'); buttonPredict.mousePressed(classify); } ``` That's the largest art of our app, and I'm going to break it up into several parts to make it easier to explain! ## ๐Ÿง™โ€โ™‚๏ธbUt wHaT Do tHeSe bUtToNs dO?!?!?! DON'T PANIC. Let's start off with this block: ```javascript buttonA = select('#red'); buttonB = select('#blue'); buttonA.mousePressed(function() { redCount++; classifier.addImage('red'); select('#redCount').html(redCount); }); buttonB.mousePressed(function() { blueCount++; classifier.addImage('blue'); select('#blueCount').html(blueCount); }); ``` `buttonA` and `buttonB` are nothing but the two different buttons we have in our app! ![](https://ecstatic-rosalind-da34e4.netlify.com/Untitled-c85ebc09-ab40-4060-a9ce-98966a9fe021.png) With `.mousePressed()` we're defining what happens when any of these buttons are pressed - which is: - Increase the count by 1, using the `++` operator - Capture the current frame from the webcam videom and add it to the classifier, with `classifier.addImage()` - Update the count on our app by changing the button's text, with `.html()` Next up, we have this whole block - where we train the classifer itself: ```javascript train = select('#train'); train.mousePressed(function() { classifier.train(function(lossValue) { // This is where we're actually training our model if (lossValue) { loss = lossValue; select('#info').html('Loss: ' + loss); } else { select('#info').html('Done Training! Final Loss: ' + loss); select('#train').style("display", "none"); select('#predict').style("display", "inline"); } }); }); ``` When the `Train ๐Ÿš‹` button on our app is pressed, we call `classifier.train()` - and with each iteration, the function we supply there is called - which is why we see the *[Loss](https://ml-cheatsheet.readthedocs.io/en/latest/loss_functions.html)* value keep changing. ![](https://ecstatic-rosalind-da34e4.netlify.com/CleanShot-2019-01-26-at-18-6a9809c8-75a3-4b88-b1ee-3931aa15d08c.04.57.gif) When the *Loss* value is `0`, then we hid the `Train ๐Ÿš‹` button, and show the previously hidden `Predict ๐Ÿ”ฎ` button! The last 2 lines of the `setupButtons()` function are about the predict button: ```javascript buttonPredict = select('#predict'); buttonPredict.mousePressed(classify); ``` It seems like we're calling this `classify` function - which is what we're going to build next! Keep going, we're almost done ๐Ÿ’ฏ ## ๐Ÿ”ฎ Predictions, and showing results Our `classifier()` function is pretty straightforward - this is all we do: ```javascript function classify() { classifier.classify(gotResults); } ``` We're just telling the ml5 `classifier` to classify the current frame in either of the categories (๐Ÿ”ด or ๐Ÿ”ต), and send the results to the `gotResults` function - which brings us to the final part of our app! As long as the classifier doesn't send us an error, we change the entire page's background color to either `red` or `blue` - and then call `classify()` again, which keeps our code running forever, and the predictions keep coming! ```javascript function gotResults(err, result) { if (err) { console.log(err); } select("body").style("background", result); classify(); } ``` That's all the code we have to write to build the example feature extractor. I highly recommend going through the final code so that you can see how everything fits together well - I know there are quite a few functions too keep track of, but I've commented everything to make it easier ๐Ÿ˜… https://repl.it/@jajoosam/feature-extractor Now go ahead, try it out! Here are some things I did to see if the feature extractor would work (it did!) ๐Ÿ‘‡ - Keep my head in the left in ๐Ÿ”ด, and right in ๐Ÿ”ต - Make a โœŒ๏ธ in ๐Ÿ”ด, and a โœ‹ in ๐Ÿ”ต - Wear different colored ๐Ÿ‘•s in ๐Ÿ”ด and ๐Ÿ”ต Let me know other cool things it worked with ๐Ÿ‘€ ## โœจ okay, but what do I **MAKE** with this? There are SOOOOO MANY THINGS you could do! A feature extractor gives you the ability to add this whole other, mysterious way of controlling, and getting input in your app! Here are a few ideas ๐Ÿ’ก - A gesture controlled game, right in the browser ๐ŸŽฎ - A mood tracker - leave the tab open all day, and train it with different face expressions ๐Ÿค” - Build your own **[hotdog or not](https://www.youtube.com/watch?v=pqTntG1RXSY)** app ๐ŸŒญ - Build an app which only lets you message others with gestures ๐Ÿ’ฌ ## ๐Ÿ‘จโ€๐Ÿซ what else is there? ml5.js can do crazy things! You can integrate image classification into your own app [like I did](https://repl.it/talk/challenge/male-A-scavenger-hunt-with-image-classification/10185), find similar words which fit into a text, get a precise skeleton of human bodies and even transfer the styles in images - look at what my picture looks like, when composed with the style of a cool painting ๐Ÿ–Œ๏ธ ![mepaint](https://ecstatic-rosalind-da34e4.netlify.com/mepaint.png) You should take a look at the many [examples on the ml5.js website](https://ml5js.org/docs/quick-start) - or take a look at [Dan Shiffman's videos on ml5.js](https://www.youtube.com/playlist?list=PLRqwX-V7Uu6YPSwT06y_AEYTqIwbeam3y), he covers loads of things you can do with it - and explains everything with a ton of energy. I highly recommend watching his videos on the [Coding Train](https://www.youtube.com/user/shiffman) YouTube! You have over 2 weeks left to submit something to the [Repl.it AI Challenge](https://repl.it/talk/challenge/Artificial-Intelligence-Competition/10058) ๐Ÿ“† If you have any questions or need any help with your ml5 submission, feel free to ping me @jajoosam on the Discord, or leave something in the comments :)
1
posted by jajoosam (380) 20 days ago
โ–ฒ
3
Make a discord-bot using the discord.js Commando framework
# Discord.js Commando Framework tutorial *First of all, I recommend you all that you first go through the basics of JavaScript and Node.js from anywhere you'd like to though I personally recommend https://www.w3schools.com/ . Also, you should know how to create discord apps at https://discordapp.com/developers/applications/me and should've already invited the bot to your test server, this is only a tutorial for the commando framework, not the whole bot thing.* *Next, let's get started* - ### Step 1: Setting up the project 1. *Create a **new folder** (Make sure there are no spaces in your folder name) in your system and open **command prompt** or **power shell** in the folder.* 2. *Run the command* `npm init` *and fill out the form, If you don't know what you're doing, then leave everything to default and type in you name when it asks for* `author`. ```powershell PS E:\tutorial-bot> npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (tutorial-bot) version: (1.0.0) description: A new discord bot entry point: (index.js) test command: git repository: keywords: author: HS license: (ISC) About to write to E:\tutorial-bot\package.json: { "name": "tutorial-bot", "version": "1.0.0", "description": "A new discord bot", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "HS", "license": "ISC" } Is this ok? (yes) PS E:\tutorial-bot> ``` 3. *After this, you will see a new file named* `package.json` *in your folder.* 4. *Now use the following command to install the required libraries* ```powershell PS E:\tutorial-bot> npm install --save discord.js discord.js-commando fs sqlite path ``` 5. *Now you'll see a new folder named* `node-modules` *in your bot's folder and you're ready to start coding the bot.* - ### Step 2 : Writing the index.js file 1. *Create a new file in your bot's folder called* `index.js` *and open it with your favourite text editor.* 2. *Now write the following code in the beginning of your **index.js** file.* ```javascript const commando = require('discord.js-commando') const path = require('path') const sqlite = require('sqlite') ``` *These lines will import the necessary libraries to use.* 3. *Next, write the following code* ```javascript const client = new commando.CommandoClient({ owner: '374886124126208000', // Your ID here. commandPrefix: '==', // The prefix of your bot. unknownCommandResponse: false, // Set this to true if you want to send a message when a user uses the prefix not followed by a command }) ``` *This will create a* `CommandoClient` *Object which is an extension of* `discord.js.Client`*class and set the owner of the bot as yourself to allow you to use the owner only commands that come built-in with Commando.* 4. *Now we will import the default commands for our bot.* ```javascript client.registry.registerDefaults() ``` 5. *Now let's make a handler that displays a message in the console when the bot is logged in and ready to use.* ```javascript client.on('ready',()=>{ console.log(`Logged in and ready to be used.. use "${client.commandPrefix}help".`) }) ``` 6. *Last of all, let's prepare the bot's login* ```javascript client.login('BOT_TOKEN') ``` - ### Testing the defaults 1. *Run the bot by using the command* `node index` *in your console.* 2. *After a while you will see a message similar to this: -* ```powershell PS E:\tutorial-bot> node index Logged in and ready to be used.. use "==help". ``` *You will also observe that your bot comes online so it's ready to be checked out.* 3. *Use the help command which in my case is* `==help` *It shows something like this in your DMs: -* ``` To run a command in *Server Name*, use == command or @Bot command. For example, == prefix or @Bot prefix. To run a command in this DM, simply use command with no prefix. Use help <command> to view detailed information about a specific command. Use help all to view a list of all commands, not just available ones. Available commands in *Server Name* Commands groups: Lists all command groups. enable: Enables a command or command group. disable: Disables a command or command group. reload: Reloads a command or command group. load: Loads a new command. unload: Unloads a command. Utility help: Displays a list of available commands, or detailed information for a specified command. prefix: Shows or sets the command prefix. ping: Checks the bot's ping to the Discord server. eval: Executes JavaScript code. ``` #### Details - **groups**: The commands in a commando bot are grouped together under categories. the `group` command can be used the list these categories. - **enable/disable**: Commando framework comes with an in-built feature of enabling/disabling commands in different guilds, these commands do the job. - **load/unload**: These commands can be used to register commands from outside the code. - **reload**: This command can be used to reload a command after you've made changes to the code to refresh it. - **help**: The in-built help function ( very detailed ). - **prefix**: Commando also comes with a feature to change the default prefix in different servers, the `prefix` command helps the users do it. - **ping**: Does exactly what help says. - **eval**: runs the code passed as an extension of the `index.js` file and returns the output ( recommended not to use ). ***These are the commands that come built-in with commando framework.*** - ### Creating your own commands 1. *First of all, create a folder named* `commands` *in your bot's root directory.* 2. *Next make the following change in your code* ```javascript client.registry.registerDefaults() ``` *to* ```javascript client.registry.registerDefaults() .registerGroups([ ['test', 'Starter Commands'] ]) .registerCommandsIn(path.join(__dirname,"commands")) ``` *This will create a new group named **Starter Commands** for your bot which can be referred to in the code by the name **test** and also read the files inside the **commands** folder and treat them as commands for the bot*. 3. *Inside the **commands** folder, create another folder named* `test` *and inside the **test** folder, create a file named* `foo.js` *as the first command will be a simple foo-bar command.* 4. *Open the* `foo.js` *file and change it's contents to* ```javascript const { Command } = require('discord.js-commando') class fooCommand extends Command{ constructor(client){ super( client, { name: 'foo', memberName: 'foo', aliases: ['f'], group: 'test', description: 'Responds with "bar".', details: 'The traditional foo-bar.', examples: ['==foo','==f'] }) } run(msg){ msg.say('bar') } } module.exports = fooCommand ``` ##### Details - *The first line imports the predefined* `Command` *class from the Commando Framework.* - *Next, we created the command as a extension of the* `Command` *class.* - *The constructor is run as soon as our **index.js** reads this file. Inside our constructor, we set the basic details about our command.* **name, memberName**: set the name of the command. **aliases**: sets what other commands can be used to do the same thing. **description, details**: describe what the command does. **examples**: sets an example for the user on how to use the command. **group**: sets our command will be part of which command group. - *The next part is the **run** method, it is called whenever the user uses the command, the parameter **msg** is the message sent by the user, it's of the type* `CommandoMessage`. *We make our bot respond with* `bar`*whenever the user uses the* `foo ` *command.* - *Finally,* `module.exports = fooCommand` *exports the command making it readable by our **index.js** file.* 5. *Now, let's test out our foo command.* *Run the bot using* `node index` *and use the **help** command.* *You will see something new at the end of the help menu* ``` Starter Commands foo: Responds with "bar". ``` *Let's use help about our command, use the **help foo** command. You should see something like: -* ``` __Command **foo:**__ Responds with "bar". **Format:** == `foo` or `@Bot foo` **Aliases:** f **Group:** Starter Commands (`test:foo`) **Details:** The traditional foo-bar. **Examples:** ==foo ==f ``` *Let's use our command now, there are 4 ways to do it now.* - Use `==foo` or `==f` in a server - Use `foo` or `f` in DMs *You will see that the bot responds with* `bar` *wherever you use the command.* ------ 6. *Now that we are done with one command, let's make another one that takes in input (**arguments**) from the user.* 7. *Create a new file named* `say.js` *in your **test** folder.* 8. *Write the following code in the **say.js** file.* ```javascript const { Command } = require('discord.js-commando') class sayCommand extends Command { constructor(client) { super(client, { name: 'say', memberName: 'say', group: 'test', aliases: ['echo', 'repeat'], description: 'A command that repeats whatever you say.', details: "Make me repeat your wordsmaking it look like I'm a parrot", examples: ['==say Hello World', '==repeat Who Am I?'], args: [{ key: 'text', prompt: 'What do you wish for me to say?', type: 'string' }] }) } run(msg, { text }) { msg.say(text) return msg.delete() } } module.exports = sayCommand ``` ##### Let's see what's new! - **args**: Defines what arguments the user has to enter. - **key**: defines the name of the argument. - **prompt**: defines what the bot will say if the user has not provided the argument - **type**: defines the type of the argument. - *The run method now has 2 parameters, the second one is the arguments, we are asking the user to pass.* - `msg.delete()`*deletes the message sent by the user.* 9. *Let's test out our say command* *If you use the **help** command now, you will see a new entry at the bottom for the **say** command.* *Use **help say** before using the command.* *You should see something similar to: -* ``` __Command **say:**__ A command that repeats whatever you say. **Format:** == `say <text>` or `@Bot say <text>` **Aliases:** echo, repeat **Group:** Starter Commands (`test:say`) **Details:** Make me repeat your words making it look like I'm a parrot **Examples:** ==say Hello World ==repeat Who Am I? ``` *Notice how **Commando** prepared the **format** for how to use the command, trust me it's a handy feature.* *Use the* `say` *command now without passing any arguments.* *You will see that the bot asks you for the text to say, also with a message like* ``` Respond with `cancel` to cancel the command. The command will automatically be cancelled in 30 seconds. ``` *This is another feature of Commando framework - command cancellation.* *You can either say something for the bot to repeat it or wait for 30 seconds to cancel the command or say cancel yourself.* *You can also pass on the argument while using the command as in the examples.* - ### Messing with the defaults *You can also modify the default commands: -* *You can find them at* ``` YourBotDirectory/node_modules/discord.js-commando/src/commands/ ``` *In there, you will see 2 folders containing the command files.* - `/util/` - `eval.js` - enables the owner to run javascript code from discord. - `help.js` - shows help for the various commands. - `ping.js` - displays the bot's ping to the discord server. - `prefix.js` - changes the prefix for the server ( guild Only command ). - `/commands/` - `enable.js` - enables a command in a server. - `disable.js` - disables a command in a server. - `groups.js` - lists all command groups. - `load.js` - loads a new command. - `reload.js` - reload a command. - `unload.js` - unloads a command. ------ **That's all you need to know to get started, put your imagination to the test and make amazing bots.**
2
posted 25 days ago
โ–ฒ
2
How to resize the Repl Talk post editor
The post editor of the Repl Talk discussion board has a pretty small text field. For example, with my setup only 4 lines of text fit in. However, you can resize it by clicking and dragging the bottom right corner of the text field, the one circled in this screenshot. The resizing handle is small and subtle and itโ€™s easy to miss it. ![screenshot-repl.it-2019.01.21-17-16-24](https://storage.googleapis.com/replit/images/1548087548334_823d5e7cacd274dd4aef5a3de805725a.pn)
0
posted by PaoloAmoroso (20) 28 days ago
โ–ฒ
1
Alternate keybinding for opening the command palette
In the source code editor of a REPL you can open the command palette by right-clicking or pressing the `F1` key. The latter keystroke, however, doesnโ€™t work on Chromebooks and other Chrome OS devices as the operating system rebinds the `F1`...`F10` function keys to different actions. But Repl.it provides an alternate keybinding for opening the command palette, `Shift+Ctrl+P`. On macOS you can instead press `Shift+Cmd+P`.
0
posted by PaoloAmoroso (20) 29 days ago
โ–ฒ
13
What tutorials would you like to see here?
Hey all, Last year I wrote several tutorials and shared them here. These tutorials were on [Discord Bots](https://www.codementor.io/garethdwyer/building-a-discord-bot-with-python-and-repl-it-miblcwejz), building a [Django Application](https://www.codementor.io/garethdwyer/creating-and-hosting-a-basic-web-application-with-django-with-repl-it-lohsyub20), [Web Scraping](https://www.codementor.io/garethdwyer/beginner-web-scraping-with-python-and-repl-it-nzr27jvnq), and [Algorithms](https://www.codementor.io/garethdwyer/quicksort-tutorial-python-implementation-with-line-by-line-explanation-p9h7jd3r6). I'm preparing some new topics to write about this year and I'd love to get your input on what kind of tutorials you'd like to see here. Please comment below with either (or both!) * Topics you'd like to see more of, for example - Data Science, Machine Learning, Web Development, Data Structures and Algorithms, Information Security (hacking), specific languages and frameworks, or any other area that you're interested in and would like to read and work through tutorials on. * Specific titles and topics - if you're battling with something specific, feel free to drop an exact title you'd like to see, like "Setting up a Flask web app on Repl with a PostgreSQL database hosted on AWS" If you already see the topic in the comments below, feel free to upvote it instead of duplicating. Looking forward to publishing more posts soon!
11
posted by GarethDwyer1 (78) 1 month ago
โ–ฒ
6
Creating Functions in Ruby: Part II
[First Part](https://repl.it/talk/learn/Creating-Functions-in-Ruby/9859) # Returning a value You may have noticed in the first tutorial that all the functions outputted text the console. Perhaps you don't want to do that. Maybe you just want a function to **return** a value. Every function returns a value. The value returned will be the last line of code that outputs a value. For example. ```ruby def my_function var1 = nil var2 = false var3 = true var3 end ``` Calling this function would return `true`. If we added `puts my_function` to the code it would output `true` to the console, the value of `var3`, which is the last line in the function. But what if we wanted to return `var1` or `var2`? That's where the `return` keyword comes in play. ```ruby def my_function var1 = nil var2 = false var3 = true return var1 end ``` When we call this function, we get the value of `var1`. We can output this to the console using `puts` or manipulate it in any way you want. # Variable arguments We know that we can specify multiple arguments for a function by seperating them by commas like this: `def func(arg1, arg2)`. But what if we don't know how many arguments our function will be taking? That's where **variable arguments** comes in hand. A variable argument looks like this: `*variable_name`. You see that asterisk? That tells Ruby that you are specifying a variable argument. Instead of passing a single value to the function, it passes an array. Here's an example. ```ruby def var_func_test(*args) puts *args[0] puts *args[1] puts *args[2] end var_func_test("Potato", 1, true) ``` This outputs: ``` Potato 1 true ``` What the function is doing with `*args` is storing all of the info given in an array. The array for this function would look like `["Potato", 1, true]`. # Some things to note 1. Variables declared within a function are inaccessible outside of the function, unless the variable is a global or class variable. 2. Functions can be put inside functions! You would call them like this: `func1::func2`. 3. You can create a default value for parameters. Like this: `def func(var1 = true)`. Now the function will automatically set var1 to true unless you set it to something else in the calling. Thanks for reading my tutorial. I hope it helped. Perhaps a part III might make it's way out.
2
posted by Zavexeon (49) 1 month ago
โ–ฒ
6
Creating Functions in Ruby
What is a function? A function is a block of code you can use over and over again, useful if you need to use a certain segment of code multiple times, but don't want to keep typing it all out. # Basic Syntax For the most simple function, you would format it like this. ```ruby def function_name #your code here end ``` The `def` keyword is telling Ruby that you are defining a function. The `function_name` is, well, the name of the function. At the end of the function we have the `end` keyword. This tells Ruby not to include any code below this keyword, essentially "ending" the function. Within the function you put your code. This can be any valid Ruby code. # Creating a simple function Here is an example of a simple function. ```ruby def hello puts "Hello!" end ``` Now we have a working function that should print `Hello!` to the console. But how do we get that code to activate? That's called calling the function. We call a function by putting it's name in the code, BELOW THE FUNCTION. If you try to call a function above where it is defined, it won't work. This is how we call a function. ```ruby def hello puts "Hello!" end hello #we called it right here ``` This will output `Hello!` to the console. # Adding parameters Functions can also take parameters, or data. When calling a function, you can also give it information for it to use. The syntax slightly changes, see. ```ruby def my_function(var) #your code end ``` That parentheses we see with the "var" in it is our parameter. That will be the name of the variable that our function uses. You now have to call the function like this `my_function(your_data)`. You input your data into a pair of parentheses following the calling of the function name. This can be a string, boolean, integer, etc. Here is an example of how you could use a parameter. ```ruby def is_cool(thing) puts "#{thing} is cool!" end is_cool("Coding") ``` This would put `Coding is cool!` to the console. You can also make functions take multiple variables. Just separate them by a comma like this. ```ruby def function_name(var1, var2) #code here end ``` So now you call it like this, `function_name(input1, input2)`. This will pass the value of both the inputs to the function. #Using those variables Well, now we can add parameters, but how do we use them? Easy! Here's a simple example. ```ruby def who_am_i(name, age) puts "I am #{name} and I am #{age} years old." #interpolation used here end who_am_i("Zavexeon", 16) ``` This puts to the console `I am Zavexeon and I am 16 years old.` We can also do math. ```ruby def add(num1, num2) puts num1 + num2 end add(1, 2) ``` This will output `3` to the console. Welp, this tutorial is getting a little long, so look out for a second part! I hope you found this tutorial useful, and if you want more advanced info, wait for my second one. Have a good day everyone. 0w0
1
posted by Zavexeon (49) 1 month ago
โ–ฒ
2
Moving an HTML Canvas Element
# Moving an HTML Canvas Element Moving an HTML canvas element can be extremely useful. You can make games and apps with moving and animated elements. Because the HTML canvas doesn't store which elements you create, you have to manually store them yourself. This tutorial will walk you through the steps of creating different functions and methods to move an element. # Setting up the Canvas First things first. Setting up the [canvas](https://www.w3schools.com/graphics/canvas_intro.asp) element is simple. In the HTML editor, add a canvas element inside the body element: ```html <canvas width="1000" height="500">An Error has Occured</canvas> ``` This creates a new canvas 1000 pixels wide and 500 pixels tall. This completes everything on the html side. ![the_new_canvas](https://storage.googleapis.com/replit/images/1546187830530_91c0eb27ea0a3de8a6efb8ae2a475ada.pn) # Setting up the Java-Script Code #### Defining Variables Next, we need to set up some Java-Script code. In the JS editor, add a variable to get the canvas element and canvas context: ```js const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); ``` Some other variables to add are the canvas height and width. These can be used to wrap objects or set canvas boundaries. ```js const max_width = canvas.width; const max_height = canvas.height; ``` We need a place to store our shapes (we will make them later). Create a new array to store them. ```js var shapes = []; ``` These are all of the initial variables we need. #### Functions Now we can start making some functions. We'll start with the shape constructor. This will create a shape object: ```js function Shape(x, y, w, h, f) { this.x = x || 0; this.y = y || 0; this.w = w || 1; this.h = h || 1; this.f = f || "black"; } ``` Next, we are going to create a method to draw a shape: ```js Shape.prototype.draw = function() { ctx.fillStyle = this.f; ctx.fillRect(this.x, this.y, this.w, this.h); } ``` In order to move a shape on the canvas, we need to clear the entire canvas, modify the shapes position and redraw everything. This function will clear the canvas and redraw the shapes in the given nested array: ```js function redraw(arrays) { ctx.clearRect(0, 0, max_width, max_height); for (a in arrays) { for (b in a) { arrays[a][b].draw(); } } } ``` #### Drawing the Shape Now we can make and move shapes. To make a shape, we can use the new Shape function: ```js var box = new Shape(0, 0, 100, 100, "blue") ``` We can change the x, y, width, height and color values later. Now we need to save the shape in the array we made. ```js shapes.push(box) ``` ![the_new_shape](https://storage.googleapis.com/replit/images/1546188603873_92d1c8ba5903d0ad1ff15822eb04a191.pn) Now comes the final part, moving the shape! We need to modify the x or y attributes of the shape and redraw the canvas. To modify the values, just add your_shape[value_name] = new_value. In this case: ```js box["x"] += 1; ``` Now that the attribute has been modified, redraw the canvas: ```js redraw([shapes]); ``` Repeatedly changing the shape attributes and then redrawing the canvas yields an animation. The [animation](https://pub-animation.geocube101.repl.co) above on the left uses the [setInterval()](https://www.w3schools.com/jsref/met_win_setinterval.asp) to change the x attribute repeatedly and redraws the canvas. You can see the code for the animation [here](https://repl.it/@Geocube101/PUB-Animation)
0
posted by Geocube101 (59) 2 months ago
โ–ฒ
12
Javascript Games Tutorial #2: Awari
# Javascript Games Tutorial #2: Awari ## Introduction In this tutorial, we present a variation on the Ashanti game _Awari_. We have translated the BASIC game from David H. Ahl's 1978 book, "BASIC Computer Games -- Microcomputer Edition" into Javascript. This tutorial builds on the concepts discussed in Javascript Games Tutorial #1: GuessIt. It extends those ideas by introducing a game board and a computer opponent -- these will be the main focus of the tutorial. ## The Rules Before we begin presenting code, let's define the rules of the game. The game is played on a board with 14 spaces: two rows of six small "bins", and two larger "bins", one on either side of the two rows (see the image below). ![basic_board](https://storage.googleapis.com/replit/images/1545874722135_776cc70a4ee0d8a1ed0690b0082add63.pn) In this image, the green bins (the top row and the large bin on the left), belong to the computer, and the brown ones (bottom row and large bin on the right) belong to the human. Each of the twelve small bins start with three tokens each. The large "scoring" areas are empty. ![start_board](https://storage.googleapis.com/replit/images/1545875467417_1d566d9bb6955156e27ab1170bd38043.pn) The human player moves first. You can select the tokens from any of the small bins in the bottom row. You then "sow" the tokens by dropping one into each bin, starting immediately to the right of the bin you chose and moving counterclockwise around the board. For example, if you drew the tokens in bin #4 and sowed them, this would be the result: ![sown_board](https://storage.googleapis.com/replit/images/1545875467394_24603cde782c6c200ab7b353f63268dc.pn) If the last token you sow falls into your scoring bin, you may immediately take another turn. You may only earn one extra move each turn, even if your last token lands in your scoring area during your extra move. If the last token you sow falls in an empty small bin and there are tokens in the small bin opposite it, you may collect the tokens from _both_ bins and move them into your scoring bin. Once you have completed your move (or _moves_, if you earned a bonus turn), the computer will take its turn, following the same rules. If, after any turn, all the small bins in either row are empty, the game ends and the winner is the player whose scoring area contains the most tokens. Ties are allowed. ## Concepts This tutorial will cover two topics: * Representing the game board * Creating a computer opponent As with tutorial #1, we will present high-level topics here, and provide comments within the code itself with extra context and detail. # Representing the Board The Awari board is really just a line with 14 elements that wraps around on itself: ![numbered_board](https://storage.googleapis.com/replit/images/1545876858375_b4b46f91a3d8b8caea57f547a488f7ce.pn) Notice that we have started our numbering at 0, rather than 1. We'll explain why in a bit. Each "bin" on the board holds exactly one piece of information: the number of tokens it contains. So, to represent this board in Javascript, we need a data structure consisting of adjacent cells, each of which can hold a single number. The "array" is the perfect fit. In tutorial #1, we learned to define an array as follows: ```js var board[14]; ``` It's a good convention to define "constants" (i.e., variables whose values never change) and then define your variable sizes in terms of these constants, rather than using the constants directly. So: ```js var BINS_PER_BOARD = 14; var board[BINS_PER_BOARD]; ``` This defines a linear list of cells. Recall from tutorial #1 that the first cell in the list has index '0': _board[0]_ = first cell in the list We can read values from the array as follows: ```js var numTokensInCellFive = board[4]; ``` and write them like this: ```js board[11] = 12; // puts 12 tokens in the twelfth cell ``` In general, we access the nth cell like this: _board[n - 1]_ Keep these general array behaviors in mind as we discuss the functions that will manipulate the values on the board. ## Manipulating the Board To initialize the board for a new game, we could do this: ```js var STARTING_TOKEN_COUNT = 3; // 3 tokens per bin at game start var setUpBoard = function() { for (var i=0; i<BINS_PER_BOARD; ++i) { if (i == PLAYER_SCORE_BIN || i == COMPUTER_SCORE_BIN) { board[i] = 0; } else { board[i] = STARTING_TOKEN_COUNT; } } }; ``` That's nice and simple, but we need a way to convey this configuration to the player. We can modify the 'print' function from tutorial #1 to facilitate display of an ASCII representation of the board. ```js var print = function(text, stayOnLine) { if (!text) text = ""; if (!stayOnLine) text = text + "<br>"; text = text.replace(/\s/g, "&nbsp"); document.writeln(text); document.body.style.fontFamily = "monospace"; }; ``` This new 'print' function has several key features. This line: ```js if (!text) text = ""; ``` checks to see if the 'text' argument is invalid (the '!', or 'not' operator, reverses the truth value of whatever follows. If the _text_ variable is invalid, it evaluates to _false_, and the 'not' operator will reverse this to _true_). If the text _is_ invalid, we replace it with a valid, but empty, string. This next line: ```js if (!stayOnLine) text = text + "<br>"; ``` checks the value of the 'stayOnLine' argument. Here again, we use '!' to reverse the truth value of the argument. So, is 'stayOnLine' is _false_, we add the final "<br>" line break to the text, which will force the output down to the next line. Conversely, if 'stayOnLine' is _true_, then !stayOnLine is _false_, and we don't add any <br>. *This means that the next call to _print_ will start where the last one left off.* We will use that to display our board in ASCII text. This line: ```js text = text.replace(/\s/g, "&nbsp"); ``` uses regular expressions (the text demarked with forward slashes /.../) to replace all whitespace characters (\s) globally (the trailing 'g') within the the string with html space characters ("&nbsp"). We do this because, normally, web browsers purge whitespace as they see fit. This makes it difficult to display ASCII-art style drawings. This allows us to use "regular" spaces in code and have them render as HTML spaces on the web page. Finally, we get to this line: ```js document.body.style.fontFamily = "monospace"; ``` which tells the web page to render everything in a monospace font. Modern web fonts usually render different letters in different widths. This looks beautiful, but it makes it hard to align text when displaying console output. This line causes our text to render in a monospace style, in which all characters have the same width. This makes it easy to line up all our game board elements. Armed with this modified _print_ function, we can now display the game board with a couple of relatively simple functions: ```js var PLAYER_SCORE_BIN = 6; var COMPUTER_SCORE_BIN = BINS_PER_BOARD - 1; var BINS_PER_PLAYER = 6; var drawBoard = function() { print(" COMPUTER'S BINS"); print(" ", true); drawBins(COMPUTER); print("COMP YOUR"); print("SCORE SCORE"); if (board[COMPUTER_SCORE_BIN] < 10) { print(" " + board[COMPUTER_SCORE_BIN], true); } else { print(" " + board[COMPUTER_SCORE_BIN], true); } print(" ", true); if (board[PLAYER_SCORE_BIN] < 10) { print (" " + board[PLAYER_SCORE_BIN]); } else { print (" " + board[PLAYER_SCORE_BIN]); } print(" ", true); drawBins(PLAYER); print(" (1) (2) (3) (4) (5) (6)"); print(" YOUR BINS"); print(""); ; var drawBins = function(startIndex) { var i = 0; for (i=0; i<BINS_PER_PLAYER; ++i) { var iBin = i + startIndex; if (startIndex === COMPUTER) { iBin = board.length - 2 - i; } var numTokens = board[iBin]; if (numTokens < 10) { print (" " + numTokens + " ", true); } else { print (" " + numTokens + " ", true); } } print(); }; ``` Now that we can display the board, how do we make it change in reponse to player input? Assume that we somehow obtain a number from 0-5 to identify the player bin from which to pull tokens to sow. Here is the algorithm that sows the tokens where _startIndex_ is the bin from which we are drawing tokens and _scoreBin_ is the index of the scoring bin for the current player (6 if it's the player's move and 13 if it's the computer's move): ```js var sowTokens = function(startIndex, scoreBin) { var i=0; var numTokens = board[startIndex]; // How many tokens in the selected bin? var doMoveAgain = false; var iBin = 0; // Remove tokens from the selected bins. board[startIndex] = 0; // Starting with the bin after the selected one, // loop numToken times... for (i=startIndex+1; i<startIndex+1 + numTokens; ++i) { // ...wrapping around if we went past the end of the board... iBin = i % BINS_PER_BOARD; // ...and adding 1 token to each space. board[iBin] += 1; } // Find the bin across from our end point. var acrossBin = (BINS_PER_BOARD - 2) - iBin; // Did we place the last token on our score bin? if (iBin === scoreBin) { doMoveAgain = true; } // Did our last token land in an empty bin across from a non-empty bin? else if (board[iBin] === 1 && board[acrossBin] > 0) { // Yes -- so gather up the tokens from both bins... var capturedTokens = board[acrossBin] + board[iBin]; board[acrossBin] = 0; board[iBin] = 0; // ...and pu them in our score bin. board[scoreBin] += capturedTokens; } return doMoveAgain; }; ``` There are a couple of lines that need further explanation. First, this line: ```js iBin = i % BINS_PER_BOARD; ``` As the comment says, it's supposed to wrap around if we go past the end of the board. This works because the modulus operator (%) works as follows: A % B = the remainder of A divided by B. In our case, B is always BINS_PER_BOARD, or 14. Try plugging some numbers in to see how this works. 3 % 14 = 3 7 % 14 = 7 11 % 14 = 11 etc. In fact, for any 'A' less than 14, we just get 'A' back. At exactly 14, we get 0, because 14 / 14 has a remainder of 0. Note that board[14 % 14] is the same as board[0] -- which returns us to the starting bin. 15 % 14 has a remainder of 1. So board[15 % 14] is the same as board[1]. In other words, we've wrapped back around to the second space. Similary, 18 % 14 has a remainder of 4, which wraps us back to the 5th space. And so on. Then there's this line: ```js var acrossBin = (BINS_PER_BOARD - 2) - iBin; ``` This formula returns the index of the small bin across from any other small bin. For instance, the bin across from bin the player's second bin (which has index 1) is: acrossBin = (14 - 2) - 1 = 11 With a quick glance at our diagrams above, we can see this is correct. Feel free to plug in more values to make sure you believe it. Finally, notice that sowTokens() returns the value of doMoveAgain. If this is 'true', this indicates the last token fell in the appropriate scoring bin. On the player's turn, let's assume that she presses a key from 1-6. We have to convert this text value to a number value and subtract one so it corresponds to the correct spaces in the array (0-5). We also have to ensure that the chosen bin contains some tokens. If all that is true, we then sow the tokens and watch out for the case in which the player earned a free move. That routine will look something like this: ```js var isExtraMove = false; var doPlayerMove = function(key) { // Use parseInt to convert the text value of the key // the user pressed into an integer value. Subtract 1 // because the user pressed 1-6 to select from the first // six spaces, but these correspond to locations 0-5 in // the 'board' array. var binIndex = parseInt(key) - 1; // Check: if the chosen space is empty, report an invalid move. if (board[binIndex] === 0) { // TODO: report illegal move and try again. } // Call 'sowTokens'. This distributes the tokens around the board // and returns 'true' if the last token fell in the PLAYER_SCORE_BIN. // If that hapened (sowTokens returnd 'true') and we're not already // in the extra move phrase... else if (sowTokens(binIndex, PLAYER_SCORE_BIN) && !isExtraMove) { // ...check for game over... if (isGameOver()) { // TODO: tell the player the game is over. } else { // ...grant the player a second move. isExtraMove = true; // TODO: allow player to move again. } } // If the player is already taking an extra move, or if his move // didn't end in his score bin, go here: else { if (isGameOver()) { // TODO: tell the player the game is over. } else { // TODO: let the computer move. } } }; ``` Finally, we need to check to see if the game has ended. Recall that the end game condition is either row of small bins being empty. The check for that looks like this: ```js var isGameOver = function() { var i=0; var topTotal = 0; var bottomTotal = 0; for (i=0; i<BINS_PER_PLAYER; ++i) { topTotal += board[i + PLAYER_SCORE_BIN + 1]; bottomTotal += board[i]; } return topTotal === 0 || bottomTotal === 0; }; ``` # Creating a Computer Opponent In this section, we will outline how to make a computer opponent for the game. Our opponent will not do much thinking -- it will just make the play that earns the most points, given the current state of the board. The simplest way to do that is to loop through the top row of small bins (from index 7 through 12), simulate a turn taken at each bin, and keep the best resulting board. The trick to this is using the same starting board configuration each time through the loop. This requires that we have a way to save the board, simulate a move, then restore the board: var tryAllMoves = function() { saveCurrentBoard(); for (int i=FIRST_COMPUTER_BIN; i<COMPUTER_SCORE_BIN; ++i) { if (board[i] > 0) { if (sowTokens(i, COMPUTER_SCORE_BIN)) { // TODO: go again, unless this is already a bonus move. } } loadSavedBoard(); } } Notice that we have to handle the case where the computer earns a bonus turn. This is tricky, because when this happens, we need to try all possible moves based on the new board configuration, then return to the *original* configuration and try the remaining possibilities. For example, suppose that we're trying all possible moves, and on move #3, the computer gets an extra turn. We then need to check all possible moves based on the board state after making move 3. Once we have tested those, we need to return back to the original configuration and try moves 4, 5, and 6: ![hierarchy](https://storage.googleapis.com/replit/images/1545960484387_3800468a9b78268985218545ef75be4f.pn) In this diagram, all the moves in green represent moves simulated starting from the board's current configuration. The moves in black would be simulated using the state of the board after making move 3. Ugh. This looks complicated. How are we going to use the new board configuration when simulating a bonus turn? How will we return to the old board configuration afterwards? Fortunately, there's a clever way to do all this, but first we have to learn about _variable scope_. ## Variable Scope and the Execution Stack In everyday English, **scope** means, "extent or range," and broadly refers to the breadth of a region. It's much the same in computer jargon: "scope" refers to the region of a program over which a variable is accessible. "Wait, what? Variables aren't accessible everywhere?" Short answer: usually, no. We have been writing our programs in a very sloppy way, so far, putting all our variables in the "global scope", which **is** accessible everywhere, but this isn't great practice. It's good for us because it makes life easier, allowing us to focus on the fundamentals of programming, but soon, we'll develop better habits. For now, though, we'll use a simple rule for scoping variables: **_In a Javascript program, a variable is visible to anything within the same set of curly braces._** Here's an example: ```js { var valueOut = 5; var myFunc = function() { var valueIn = 3; console.log("Sum is " + (valueOut + valueIn)); }; var myOtherFunc = function() { console.log("ValueOut: " + valueOut); console.log("valueIn: " + valueIn); // Error! }; }; ``` In this case, valueOut is visible everywhere, because everything is contained within that outer set of curly braces. The variable valueIn is visible only within the function _myFunc_, whose scope is defined by the curly braces wrapping its code. This means that _myOtherFunc_ will throw an error because it's trying to access valueIn, which is defined outside its braces. "But wait...isn't valueOut *also* defined outside the _myOtherFunc_'s braces?" Yes -- but _myOtherFunc_ is contained within the same braces that contain valueOut, so valueOut is still visible to _myOtherFunc_ -- they are both defined within the scopt of the outer braces. In contrast, _valueIn_ is defined only within the scope of _myFunc_, and _myOtherFunc_ is defined outside of _myFunc_'s scope. Now consider another example: ```js var myFunc2 = function() { var outVar = 3; for (var i=0; i<3; ++i) { var inVar = i + outVar; console.log("Value is " + inVar); } console.log("inVar = " + inVar); // Error! }; ``` The curly braces of the 'for' loop create a new _local scope_ in which we define _inVar_. Therefore, _inVar_ is available to any code inside those braces. Notice that the last _console.log_ function is outside this scope, so trying to access _inVar_ would be an error. There is one final note to add to this entire discussion: you can think of all the code you write as being surrounded by a single set of invisible braces. This is known as the _global scope_, and variables defined in that scopre are visible everywhere in your program. So far, we have been defining everything in the global scope. As we mentioned above, that makes it easy to focus on learning programming fundamentals, but it's generally not a good idea, especially for larger programs. OK, so now we know what scope is. Is that helpful? By itself, not so much, but in conjunction with the concept of the _execution stack_, it's going to solve our problem. So, what's the execution stack? Without getting too technical, it's the program that runs our program. It performs two important functions: 1) it makes sure our code runs in the correct order, and 2) it reserves memory our program needs to run, as it is running. #2 is the key for us: the execution stack is responsible for reserving the memory our program needs. More specifically, every time the stack executes one of our game functions, it reserves memory required by the variables in that function's _scope_. _This is true even if it's executing a function inside of another function._ Consider this function: var myFunc = function() { var value = 1; value = value + 1; if (value < 3) { // Call this function again. myFunc(); } }; Think through how this might work when we call it. First, the exectuion stack creates a new scope, and within that scope, it creates the variable _value_, which contains 1. Next, in adds 1 to _value_'s contents, which become 2. Since the value is less than 3, the execution stack calls _myFunc_ again. At this point, you might be tempted to think that _value_ already exists, so the next time we add 1 to it, it will increase to 3 and we will skip over the 'if', but that's not what happens. Each time the execution stack enters the _if_ statement and calls _myFunc_, it creates a _new_ scope, because this is a new call to the function. Within that scope is a new _value_ variable that gets set to 1. That means it always executes the contents of the _if_ clause. This leads to an endless cycle called _infinite recursion_. The word _recurse_ refers to the process in which a function calls itself, as in _myFunc_ above. The important takeaway here is that each version of the function maintains its own local data, which is visible only to that version of the function. With that knowledge, and these functions: ```js var copyBoard = function(srcBoard, destBoard) { destBoard.length = 0; for (var i=0; i<srcBoard.length; ++i) { destBoard.push(srcBoard[i]); } }; var saveBoard = function(boardCopy) { var i=0; boardCopy.length = 0; for (i=0; i<board.length; ++i) { boardCopy.push(board[i]); } }; var restoreBoard = function(boardCopy) { var i=0; for (i=0; i<board.length; ++i) { board[i] = boardCopy[i]; } } ``` we can now write the function that will act as the brain for our computer opponent: ```js var tryAllMoves = function(bestBoard) { // Find the best move from among the computer's six choices. var i = 0; var bestScore = -1; var bestBin = -1; var boardCopy = []; var newBestBoard = []; saveBoard(boardCopy); for (i=0; i<BINS_PER_PLAYER; ++i) { var iBin = PLAYER_SCORE_BIN + 1 + i; if (board[iBin] > 0) { if (sowTokens(iBin, COMPUTER_SCORE_BIN)) { if (!isExtraMove) { isExtraMove = true; tryAllMoves(newBestBoard); copyBoard(newBestBoard, board); } } if (board[COMPUTER_SCORE_BIN] > bestScore) { saveBoard(bestBoard); bestScore = board[COMPUTER_SCORE_BIN]; } restoreBoard(boardCopy); } } }; ``` Here are the keys to understanding how it works: 1) It accepts a single argument called bestBoard. This is an array into which the best move will be copied. 2) It defines a local variable called boardCopy into which we copy the current board configuration. 3) It uses restoreBoard(boardCopy) to return the board to it's original configuration after every simulated run. 4) If sowTokens() returns 'true', meaning the computer earned an extra turn, _it calls itself before restoring the board._ Think about #4 for a moment, then think about the last illustration with the green and black "Play #" diagram. When the computer moves, it calls _tryAllMoves_. The program will start making simulated plays. These correspond to the green "Play #" entries above. If the computer earns an extra turn, it will call _tryAllMoves_ again. Since the board hasn't been restored for this pass through the loop, it will reflect the state of the board after the current simulated play. As the program starts to execute this second version of _tryAllMoves_ (i.e., the black "Play #" entries above), it will save this modified board and start trying all the moves using **it** as the original state. Once this second version of _tryAllMoves_ has finished, it will copy its best result into the _bestBoard_ array, then exit back into the first version of _tryAllMoves_. This takes us back to the lower, green "Play #" entries in the diagram. In terms of code, we come back at the 'copyBoard(newBestBoard, board)' command inside the _if_ statement. This copies the best board from our free move back into the current board. The first version of _tryAllMoves_ then tests this configuration to see if it's the best result, and if so, copies it into the _bestBoard_ variable. Then it restores the board back to its original configuration and tries the next move. Don't worry if this makes your brain hurt. Recursion is a difficult concept to grasp. Carefully stepping through a specific example can help. But, believe it or not, that's all there is to our simple brain for Awari. There isn't much else to the program, as you will see if you examine the source code. One thing you'll notice if you play a few games is that your opponent will always choose exactly that same options, given a particular board configuration. Even if there are two plays that yield the identical amount of points, it will always pick the move closest to the player's starting goal. Here's a challenge: see if you can figure out why _tryAllMoves_ produces this behavior. Once you figure it out, see if you can modify it so that there's a chance it might pick a different move of equal worth. You will also notice that your opponent isn't smart enough to protect itself. Specifically, if it has tokens across from an empty space, these tokens are subject to theft from the human player. Can you modify _tryAllMoves_ to detect these vulnerabilities and avoid them? # Conclusion That wraps up this tutorial. We hope this gives you some idea how to represent your game board as data inside your program, as well as how to make your program "play" a game. That subject -- which blurs into "game AI" (artificial intelligence) is broad and deep -- we've barely scratched the surface of the tip of the iceberg. But, hopefully, you have some idea this solution works, and how you might adapt it for other, similar games.
11
posted by MarkKreitler (18) 2 months ago
โ–ฒ
27
Build your very own URL shortener ๐Ÿ”—๐Ÿš€
## Build a tiny URL shortener, using a remote database ![](https://boring-lamport-1b901a.netlify.com/2018-12-2214-5f2c1bbe-59a2-4ba7-9544-438b1ab5e1dd.39.01.gif) [Demo](https://l.4ty2.fun) โฏ๏ธ [Code](https://repl.it/@jajoosam/tyni) ๐Ÿ‘จโ€๐Ÿ’ป Setting up a URL shortener is a lot of work - either you have to pay, or spend hours setting up your own server. This is a guide to making your own URL shortener with [repl.it](http://repl.it) - using `express`, and a remote database - all on `node.js` ## ๐Ÿ› ๏ธ Getting our environment running First up, fork the [https://repl.it/@jajoosam/tyni-starter](https://repl.it/@jajoosam/tyni-starter) repl, so that you have a running project. Next, create a new file - `.env` ![](https://boring-lamport-1b901a.netlify.com/Untitled-675a864b-7c8c-487d-8ca7-595eb8af67ba.png) A `.env` file can store secrets for you, that no one else will be able to see. This is where we want to store our token for accessing the remote database. ## ๐Ÿ“ Making our database We're going to be using [jsonstore.io](http://jsonstore.io) for storing all our URLs. Head over to [jsonstore.io/get-token](https://www.jsonstore.io/get-token) - and copy the token you see - this is the secret we want to store in our `.env` file. Open up your `.env` file, and set `KEY` to your token, like this ๐Ÿ‘‡ ```bash KEY=yourTokenGoesHere ``` Remember to keep **no whitespace**, or your token might not be recognized right! When you open `index.js` you'll see that I've already initialized the database, and a small web server for you. Now let's get to making our API so we can shorten them URLs ๐Ÿš€ ## ๐Ÿ‘จโ€๐Ÿ’ป The API There are two parts to our URL shortener: 1. Storing the short link, corresponding to the long URL - in our database. 2. Redirecting visitors from the short link to the long link Both of these are super simple to implement, thanks to the `express` server we're using - we'll just be collecting `get` requests for both of the tasks. For adding links to our database, we have a special endpoint - requests to it have two parts: the long URL, and the short path. ```javascript app.get('/shorten', (req, res) => { db.write(req.query.key, {"url": req.query.url}); res.status(200); }); ``` Adding this to our code lets us correspond the short path (`key`) to the long `url`, and then we finally send a successful response. For the second task, we'll just be collecting the short path (`key`) from a request, finding the corresponding URL in our database, and then redirecting our visitor โฌ‡๏ธ ```javascript app.get('/:key', (req, res) => { db.read(req.params.key + "/url").then( (url) => { res.redirect(url); }); }); ``` That's prety much it - we have a fully functional URL shortener ๐Ÿคฏ - check it out for yourself, open a new URL which looks like this ๐Ÿ”— ``` https://tyni.jajoosam.repl.co/shorten?key=yay&url=https://dsh.re/50854 ``` Now, going to [`http://tyni.jajoosam.repl.co/yay`](http://tyni.jajoosam.repl.co/yay) will be nice to see ๐Ÿ‘‡ ![](https://boring-lamport-1b901a.netlify.com/1-8380b52f-92c2-4acc-87dc-64011c6fd502.jpg) Of course, you'll be replacing `tyni.jajoosam` in those URLs with your own repl! ## โœจ The Frontend Our URL shortener does work, but it's tedious, having to type out a huge URL before shortening it - we can make the whole process much simpler with a simple frontend. I've already created this - and gone for a neat and minimal look using [wing.css](https://github.com/kbrsh/wing) ![](https://boring-lamport-1b901a.netlify.com/Untitled-0fa3af54-ddae-46ca-abdf-4fcbcb218f8b.png) You just have to add code to send visitors to the hompage in the `static` folder ๐Ÿ  ```javascript app.get('/', (req, res) => { res.sendFile("static/index.html", {root: __dirname});; }); ``` If you browse through the `static` folder, you'll find a simple `HTML` file with a form, `CSS` to style our page, and most importantly, `JS` to send requests to our URL shortening API. The `HTML` is quite straightforward, we're asking for the long URL, and *optionally* a short path. Open up `script.js` and you'll see the `shorten()` function. Here's what the JS file does (*I've also annotated the code with comments*) ๐Ÿ‘‡ ๐Ÿ” Getting the path(`key`) and the long `url` from the form. ๐Ÿ“ Possibly generating a random 5 character hash as our path (in case there's no path entered) ๐Ÿ”— Sending a get request to our API, with our `key` and `url` as parameters ๐Ÿ–ฅ๏ธ Displaying the shortened URL on our page ## ๐ŸŒ Getting our custom domain Sure, our links are shorter - but we still don't have them on our own domain, and the `repl.co` links can be pretty long ๐Ÿ‘€ Luckily for us, the folks at [repl.it](http://repl.it) recently allowed custom domains to be used! That means this project could be something you actually use all the time ๐Ÿ˜„ Check out `dotcomboom`'s guide on [using custom domains](https://repl.it/talk/learn/How-to-use-a-custom-domain/8834), it should only take a few minutes. It also teaches you about getting free domains ๐Ÿ’ธ Be sure to put down any questions or improvements down in the comments ๐Ÿ’ฌ - and here's all the code for you to go over again ๐Ÿค– https://repl.it/@jajoosam/tyni
10
posted by jajoosam (380) 2 months ago
โ–ฒ
13
Game Tutorial: Space Invaders
Hi everyone, I put together a little [Space Invaders](https://repl.it/@ericqweinstein/Space-Invaders) game and thought I'd write a tutorial on how it works. (You'll want to run it in the [REPL Run](https://space-invaders.ericqweinstein.repl.run/) environment, since Space Invaders requires a fair amount of screen real estate to play.) Feel free to fork the REPL and add to it! The game is broken up into six main files: `game.py` (which handles the game logic), `invader.py` and `fleet.py` (which handle drawing individual invaders and a whole fleet of invaders, respectively), `player.py` (which manages moving the player on the screen), `laser.py` (so the player can fire at the invading fleet), and `main.py` (which ties everything together and creates a new game). Let's look at each one in turn. ## `game.py` The `game.py` file houses our `Game` class, which manages the behavior and data needed to run a Space Invaders game. We'll go through each method one at a time, but here's the file in its entirety if you're curious: ```py import curses import datetime as dt import sys from datetime import datetime from fleet import Fleet from player import Player class Game(object): def __init__(self, stdscr): self.stdscr = stdscr self._initialize_colors() self.last_tick = datetime.now() self.window = self.stdscr.getmaxyx() self.fleet = Fleet(stdscr, self.window) self.player = Player(stdscr, self.window) def run(self): while True: self.tick() def tick(self): self.update() def update(self): new_tick = dt.timedelta(milliseconds=10) self.last_tick += new_tick self.fleet.tick(self.last_tick) self.player.tick(self.last_tick) self.detect_collisions() if self.is_over(): if self.won(): self.end('You won!') else: self.end('Oh no, you lost!') def detect_collisions(self): for laser in self.player.lasers: for invader in self.fleet.invaders: if self._collision_found(laser, invader): invader.block_color += 1 if invader.block_color == 7: self.fleet.remaining_invaders -= 1 if invader.block_color > 8: invader.block_color = 8 def won(self): return self.fleet.remaining_invaders == 0 def lost(self): return self.fleet.y() >= self.player.y def is_over(self): return self.won() or self.lost() def end(self, message): sys.stdout.write(message) sys.exit(0) def _collision_found(self, laser, invader): # Left if laser.x + laser.width < invader.x: return False # Right elif invader.x + invader.width < laser.x: return False # Above elif laser.y + 1 < invader.y: return False # Below elif invader.y + 8 < laser.y: return False return True def _initialize_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_RED) curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLUE) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_GREEN) curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_CYAN) curses.init_pair(6, curses.COLOR_YELLOW, curses.COLOR_YELLOW) curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_WHITE) curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) curses.init_pair(10, 10, 10) ``` All right! Let's start with our `__init__()` method. ```py def __init__(self, stdscr): self.stdscr = stdscr self._initialize_colors() self.last_tick = datetime.now() self.window = self.stdscr.getmaxyx() self.fleet = Fleet(stdscr, self.window) self.player = Player(stdscr, self.window) ``` As you can see, when we initialize a new game, we save a reference to `stdscr` (a window object representing the entire screen). This is part of the `curses` Python library, which you can read more about [here](https://docs.python.org/3/howto/curses.html). We also call `_initialize_colors` to set up our terminal colors (more on this soon), initialize our `last_tick` to the current time, and save references to our window dimensions (`self.window = self.stdscr.getmaxyx()`), fleet of invaders (`self.fleet = Fleet(stdscr, self.window)`), and the human player (`self.player = Player(stdscr, self.window)`). Note that our fleet of invaders and player each get passed references to the overall screen in the form of `stdscr` and `self.window`; we'll see why in a little bit. Next, our `run` method just creates an infinite loop that starts our game a-tickin': ```py def run(self): while True: self.tick() ``` As for `tick`, all we do at the moment is delegate to our `update` method. (We could imagine including other functionality here as well; even though all we do is `update`, I like wrapping that behavior in `tick`, since it creates a common API for all our game components.) ```py def tick(self): self.update() ``` As for `update`, it handles... well, updating our game! ```py def update(self): new_tick = dt.timedelta(milliseconds=10) self.last_tick += new_tick self.fleet.tick(self.last_tick) self.player.tick(self.last_tick) self.detect_collisions() if self.is_over(): if self.won(): self.end('You won!') else: self.end('Oh no, you lost!') ``` First, we create a `new_tick` equal to ten milliseconds (this is how long we wait between updatesโ€”that is, the amount of time that passes between each refresh of the game screen). We update our `self.last_tick` by adding the `new_tick` amount, then call `tick` on our fleet and player so they can update, too (passing in the `self.last_tick` in order to keep our game clock synchronized). We check to see if there are any collisions (that is, if any of the lasers fired by the player have hit any of the invaders), and finally check `self.is_over()` to see if our game has ended, providing appropriate messages depending on whether the player has won or lost (more on this soon). Let's see how we detect collisions between lasers and invaders: ```py def detect_collisions(self): for laser in self.player.lasers: for invader in self.fleet.invaders: if self._collision_found(laser, invader): invader.block_color += 1 if invader.block_color == 8: self.fleet.remaining_invaders -= 1 if invader.block_color > 8: invader.block_color = 8 ``` We loop over all the lasers and invaders, and if we find a collision (more on this soon), we do three things: 1. We increment the invader's color (this has the effect of making the invader flicker when hit, since it will cycle through all the colors from red to black); we'll see more about how colors work with the `curses` library when we get to our `_initialize_colors()` method. 2. If the invader's color is `8` (this happens to be the color black), we decrement the number of `remaining_invaders` by one (treating the invader as destroyed). 3. If the invader's color ever exceeds `8`, we just set it back to `8` (to ensure the blocks that make up the invader stay black, matching the game background). The three methods we use to check whether the game has ended are `is_over()`, `won()`, and `lost()`; each is pretty short, so let's look at them all at once. ```py def won(self): return self.fleet.remaining_invaders == 0 def lost(self): return self.fleet.y() >= self.player.y def is_over(self): return self.won() or self.lost() ``` To check if a player has `won()`, we just check whether there are no remaining invaders. A player has `lost()` when the fleet's `y` value (its height above the bottom of the screen) is greater than or equal to the player's (meaning the fleet has landed/invaded, since it's gotten down to where the player is on the screen). The game `is_over()` when the player either wins or loses. We `end()` the game like so, by writing an appropriate message (like "You won!" or "Oh no, you lost!") and exiting the program using Python's `sys.exit()`. ```py def end(self, message): sys.stdout.write(message) sys.exit(0) ``` Okay! Let's get back to collision detection. We know there's a collision if any of part of a laser overlaps with any part of an invader. This can be a little tricky to compute, since we have to take the `x` and `y` coordinates of each block into account, as well as those blocks' heights and widths. One way to do it is to say that there's no collision if we shoot wide (too far left or right), high, or low, and that otherwise, we _must_ have a collision. So! That's what we do in `_collision_found()`: we check to see if we've missed by going too far left, right, high, or low, and if we haven't missed in those directions, we must have made a hit: ```py def _collision_found(self, laser, invader): # Too far left if laser.x + laser.width < invader.x: return False # Too far right elif invader.x + invader.width < laser.x: return False # Too high elif laser.y + 1 < invader.y: # The laser is one block wide return False # Too low elif invader.y + 8 < laser.y: # The invader is eight blocks high return False return True ``` Finally, we finish up our `Game` class with a little utility method that sets all the colors we're going to use (you can read more about setting colors in `curses` using the `init_pair()` function [here](https://docs.python.org/3/howto/curses.html#attributes-and-color): ```py def _initialize_colors(self): curses.start_color() curses.init_pair(1, curses.COLOR_RED, curses.COLOR_RED) curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLUE) curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_GREEN) curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_MAGENTA) curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_CYAN) curses.init_pair(6, curses.COLOR_YELLOW, curses.COLOR_YELLOW) curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_WHITE) curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_BLACK) curses.init_pair(10, 10, 10) ``` ## `invader.py` All right! Let's move on to our `Invader` class, where we'll start to see how to draw objects on the screen using `curses`. We'll also start to see a common API emerge among our game components: most of them have an `__init__()` method (to set up the object with attributes like location, color, and direction), a `draw()` method (to draw the object on the screen), and a `tick()` method (to determine how our game objects should change and behave with each game step). (Oftentimes, our `tick()` method just delegates to an `update()` method, but as mentioned, we wrap that for now in case we want to add extra functionality later.) Again, we'll go through each method one-by-one, but here's the whole class if you just want to dive in: ```py import curses from datetime import datetime class Invader(object): def __init__(self, stdscr, window, position): self.stdscr = stdscr self.window = window self.width = 11 self.speed = 5 self.direction = 1 self.range = (0, self.window[1] - self.width - 1) self.x = position[0] self.y = position[1] self.block_color = 1 self.empty_color = 8 self.block_width = 1 self.last_tick = datetime.now() self.move_threshold = 0.5 def __repr__(self): return [ [' ', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', ' '], [' ', ' ', ' ', 'O', ' ', ' ', ' ', 'O', ' ', ' ', ' '], [' ', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', ' '], [' ', 'O', 'O', ' ', 'O', 'O', 'O', ' ', 'O', 'O', ' '], ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'], ['O', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', 'O'], ['O', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', 'O'], [' ', ' ', ' ', 'O', 'O', ' ', 'O', 'O', ' ', ' ', ' '] ] def draw(self): for y, row in enumerate(self.__repr__()): for x, char in enumerate(row): if char == ' ': self._draw_block(x, y, self.empty_color) else: self._draw_block(x, y, self.block_color) def _draw_block(self, x, y, color): self.stdscr.addstr( self.y + y, self.x + x, ' ' * self.block_width, curses.color_pair(color) ) def _move(self, tick_number): # This is a kind of "brake" to ensure that the invaders don't move for every single game tick # (since we want to animate the player's motion quickly, but the invaders should move more slowly). if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: x = self.x + 1 x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x * self.speed * self.direction self.last_tick = datetime.now() def update(self, tick_number): self._move(tick_number) self.draw() def tick(self, tick_number): self.update(tick_number) ``` Okay! As usual, let's start by looking at our `__init__()` method: ```py def __init__(self, stdscr, window, position): self.stdscr = stdscr self.window = window self.width = 11 self.speed = 5 self.direction = 1 self.range = (0, self.window[1] - self.width - 1) self.x = position[0] self.y = position[1] self.block_color = 1 self.empty_color = 8 self.block_width = 1 self.last_tick = datetime.now() self.move_threshold = 0.5 ``` As mentioned, we start off by saving references to our screen and window objects via `self.stdscr = stdscr` and `self.window = window`. We also set a `width` (to help detect how far across the screen our invader extends) and `speed` (to control how quickly it moves), as well as a `direction` (+1 for left-to-right and -1 for right-to-left). We also set a `self.range` (equal to the max width minus one block and the width of our invader) that ensures our invaders don't try to wander off the screen, as well as `x` and `y` coordinates.(Note that we pass a `position` to our constructor to tell the invader where to draw itself on the screen; the `position` is an `(x, y`) tuple.) We set our `block_color` to `1` and `empty_color` to `8` (red and black, respectively), set our `last_tick` to the current time, and our `move_threshold` to 0.5 (this will help us slow our invaders down, ensuring they only move once every half-second). Next up is our `__repr__()` function! `__repr__()` is a built-in Python function that you can override to control the printed representation of your object. We return a two-dimensional list of characters, using `'O'` to represent a red block and `' '` to represent a black (empty) block. If you look closely, you can see it looks like our on-screen invader! ```py def __repr__(self): return [ [' ', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', ' '], [' ', ' ', ' ', 'O', ' ', ' ', ' ', 'O', ' ', ' ', ' '], [' ', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', ' '], [' ', 'O', 'O', ' ', 'O', 'O', 'O', ' ', 'O', 'O', ' '], ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'], ['O', ' ', 'O', 'O', 'O', 'O', 'O', 'O', 'O', ' ', 'O'], ['O', ' ', 'O', ' ', ' ', ' ', ' ', ' ', 'O', ' ', 'O'], [' ', ' ', ' ', 'O', 'O', ' ', 'O', 'O', ' ', ' ', ' '] ] ``` ![!invader](https://duaw26jehqd4r.cloudfront.net/items/0h3U0T1P2i1u2V0s072E/invader.png?v=a4032ac9) In order to `draw()` our invader, we iterate over the characters in our `self.__repr__()` two-dimensional list, drawing a red block when we see a `'O'` and an empty/black block when we see `' '`: ```py def draw(self): for y, row in enumerate(self.__repr__()): for x, char in enumerate(row): if char == ' ': self._draw_block(x, y, self.empty_color) else: self._draw_block(x, y, self.block_color) ``` Our `_draw_block()` method takes an `x` (column position), `y` (row position), and `color` and adds the block to the screen using `curses`' `stdscr.addstr()` method (which you can read more about [here](https://docs.python.org/3/library/curses.html#curses.window.addstr)): ```py def _draw_block(self, x, y, color): self.stdscr.addstr( self.y + y, self.x + x, ' ' * self.block_width, curses.color_pair(color) ) ``` Now that we know what we need in order to draw our invader, let's take a look at how we get it to move. Every game `tick`, we want to make a decision about our invader's position (using its `x` and `y` coordinates) so we can redraw it in its new position: ```py def _move(self, tick_number): # This is a kind of "brake" to ensure that the invaders don't move for every single game tick # (since we want to animate the player's motion quickly, but the invaders should move more slowly). if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: x = self.x + 1 x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x * self.speed * self.direction self.last_tick = datetime.now() ``` The first line of code in this method is a little confusing, but what we're doing is looking at the difference between the current time and our prior tick. If enough time has passed, update our `x` value by one (moving a little to the right), adjusting our `x` to the screen range minimum (in case we're about to fall off the left side of the screen) or the screen range maximum (in case we're about to fall off the right side of the screen). We update our `x` position by multiplying by our speed (how many columns we move per tick) and direction (+1 to go right-to-left, -1 to go left-to-right). Finally, we update our `self.last_tick` in preparation for the next game loop. In order to `update()` our screen, we just need to move and redraw: ```py def update(self, tick_number): self._move(tick_number) self.draw() ``` As mentioned, our `tick()` method just wraps `update()` for now: ```py def tick(self, tick_number): self.update(tick_number) ``` ...and that's all we need to set up our `Invader` class! Now let's look at what we need to do to organize our invaders into a `Fleet`. ## `fleet.py` Our `Fleet` class is pretty simple! We'll walk through its three methods (`__init__()`, `tick()`, and `y()`), but here's the whole thing: ```py from datetime import datetime from invader import Invader class Fleet(object): def __init__(self, stdscr, window): self.stdscr = stdscr # This is actually the width of an invader self.width = 11 self.window = window self.range = (0, self.window[1] - self.width - 1) self.invaders = [ Invader(stdscr, window, (5, 2)), Invader(stdscr, window, (20, 2)), Invader(stdscr, window, (35, 2)), Invader(stdscr, window, (50, 2)), ] self.step = 5 self.last_tick = datetime.now() self.move_threshold = 1 self.number_of_invaders = len(self.invaders) self.remaining_invaders = self.number_of_invaders def tick(self, tick_number): [invader.tick(tick_number) for invader in self.invaders] if self.invaders[self.number_of_invaders - 1].x + self.width // 2 >= max(self.range): # This is the "brake" for things that should animate more slowly than the main game loop. if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = -1 invader.y += self.step self.last_tick = datetime.now() elif self.invaders[0].x <= min(self.range): if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = 1 invader.y += self.step self.last_tick = datetime.now() def y(self): return self.invaders[0].y + 8 ``` As usual, our `__init__()` method starts by saving references to `stdscr` and `window`, as well as setting a `width` and `range` (these are actually identical to what we did in our `Invader` class, since we only need a single invader's width in order to determine whether we're about to crash into a wall). If you fork this REPL to add new functionality, fix bugs, or refactor the code, it might be a good idea to use the invader's width and range (rather than duplicating that code here)! Next, we create a list of `self.invaders`. In our case, we set up four invaders that are 15 blocks apart (`x`s of 5, 20, 35, and 50) and all at the same `y` (2). (Again, if you fork this code, it might be a good idea to set these `x` values based on the number of invaders we have, rather than hard-code them.) We also set a `step` of `5` (we'll use this to determine how far to "drop down" after our fleet has moved all the way across the screen), a `last_tick` of the current time, a `move_threshold` of 1 (similar to what we did to control the rate of movement for our invaders), a `number_of_invaders` equal to the length of our `self.invaders` list, and finally, `remaining_invaders` equal to `number_of_invaders` (we'll decrement this value as invaders are destroyed by the player). ```py def __init__(self, stdscr, window): self.stdscr = stdscr # This is actually the width of an invader self.width = 11 self.window = window self.range = (0, self.window[1] - self.width - 1) self.invaders = [ Invader(stdscr, window, (5, 2)), Invader(stdscr, window, (20, 2)), Invader(stdscr, window, (35, 2)), Invader(stdscr, window, (50, 2)), ] self.step = 5 self.last_tick = datetime.now() self.move_threshold = 1 self.number_of_invaders = len(self.invaders) self.remaining_invaders = self.number_of_invaders ``` Next, our `tick()` method controls the movement of our overall fleet. Since invaders have a tick method and can control their won left-to-right movement, we simply call `tick()` on all the invaders in `self.invaders` to move them left-to-right. We use the same "brake" we used for our invaders to prevent them from updating too quickly, and if our invading fleet is about to drive off the screen, we reverse direction, drop down, and update our `last_tick`. (The first branch in our `if` statement handles left-to-right movement causes us to drop down and reverse direction instead of falling off the right side of the screen; the `elif` handles right-to-left movement and preventing us from falling off the left side of the screen.) ```py def tick(self, tick_number): [invader.tick(tick_number) for invader in self.invaders] if self.invaders[self.number_of_invaders - 1].x + self.width // 2 >= max(self.range): # This is the "brake" for things that should animate more slowly than the main game loop. if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = -1 invader.y += self.step self.last_tick = datetime.now() elif self.invaders[0].x <= min(self.range): if datetime.now().timestamp() - self.last_tick.timestamp() > self.move_threshold: self.stdscr.clear() for invader in self.invaders: invader.direction = 1 invader.y += self.step self.last_tick = datetime.now() ``` Finally, we create a helper function called `y()` that just gets the current `y` value (row position) of our invading fleet. (All our invaders have the same `y` value, so we arbitrarily take the first one in our fleet, since a fleet should include at least one invader): ```py def y(self): return self.invaders[0].y + 8 # Invaders are 8 blocks tall. ``` Again, if you fork this REPL, it might be a good idea to set an `invader.height = 8` so we don't have to sprinkle this "magic number" throughout our code in order to take the invaders' heights into account. On to the `Player` class! ## `player.py` You know the drill by now! Here's the whole `Player` class: ```py import curses from datetime import datetime from laser import Laser class Player(object): def __init__(self, stdscr, window): self.stdscr = stdscr self.width = 6 self.window = window self.range = (0, self.window[1] - self.width) self.speed = 1 self.color = 3 self.x = self.window[1] // 2 self.y = self.window[0] - 5 self.lasers = [] def draw(self): self.stdscr.erase() self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) def tick(self, tick_number): [laser.tick(tick_number) for laser in self.lasers] self._handle_user_input() self.draw() def _handle_user_input(self): instruction = self.stdscr.getch() if instruction == curses.KEY_LEFT: x = self.x - 1 elif instruction == curses.KEY_RIGHT: x = self.x + 1 else: x = self.x if instruction == ord(' '): self.lasers.append(Laser(self.stdscr, self.x, self.y)) # Ensure we don't drive off the board x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x ``` In our `__init__()` method, we do a lot of familiar things: save references to our screen (`stdscr`) and window (`window`), set a width (`self.width = 6`), a window range (so we don't fly off the screen), a speed, a color, and `x` and `y` coordinates. And just like a `Fleet` has a list of invaders, a `Player` has a list of lasers to fire! (We'll see how lasers work soon.) ```py def __init__(self, stdscr, window): self.stdscr = stdscr self.width = 6 self.window = window self.range = (0, self.window[1] - self.width) self.speed = 1 self.color = 3 self.x = self.window[1] // 2 self.y = self.window[0] - 5 self.lasers = [] ``` Our `draw()` method is pretty straightforward: we erase our old position with `self.stdscr.erase()`, then draw a new player (which is just a green rectangle) at the specified `x` and `y` coordinates. ```py def draw(self): self.stdscr.erase() self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) ``` Our `tick` method does a few things (and we could delegate some of them to an `update()` method if we wanted!): we update each laser in our array of lasers, respond to user input (which we'll cover in just a minute), and redraw the screen to reflect our changes in the terminal. ```py def tick(self, tick_number): [laser.tick(tick_number) for laser in self.lasers] self._handle_user_input() self.draw() ``` Unlike our other game object, the `Player` has to respond to human input (and the game loop has to be pretty fast in order for the animation to be fastโ€”that's why we set the overall game loop to 10 milliseconds earlier, but we use our "brake" to ensure invaders move more slowly). To accomplish that, we have our `_handle_user_input()` method: ```py def _handle_user_input(self): instruction = self.stdscr.getch() if instruction == curses.KEY_LEFT: x = self.x - 1 elif instruction == curses.KEY_RIGHT: x = self.x + 1 else: x = self.x if instruction == ord(' '): self.lasers.append(Laser(self.stdscr, self.x, self.y)) # Ensure we don't drive off the board x = min(x, max(self.range)) x = max(x, min(self.range)) x = x - self.x self.x += x ``` Here, we use `curses`' `stdscr.getch()` method to determine what key the player is pressing, storing that in `instruction`. If it's the left arrow key (`if instruction == curses.KEY_LEFT`), we move left; if it's the right arrow key (`if instruction == curses.KEY_RIGHT`), we move right. We ensure we don't drive off the board by setting `x` to its min value (the left side of the screen) if we're about to go below that, and we set `x` to its max value (the right edge of the screen) any time we're about to go above that and drive off the right side of the screen. If the user presses the space bar (`if instruction == ord(' ')`), we fire a laser! Next up: the `Laser` class! ## `laser.py` This is a short oneโ€”here's the file in its entirety: ```py import curses class Laser(object): def __init__(self, stdscr, x, y): self.stdscr = stdscr self.x = x self.y = y self.color = 7 self.width = 1 def tick(self, tick_number): if (self.y <= 0): self.color = 8 else: self.y -= 1 self.draw() def draw(self): self.stdscr.addstr( self.y, self.x, ' ' * self.width, curses.color_pair(self.color) ) ``` There's not a ton going on here, so while we'll still go through each method, we won't look at each code snippet individually. As usual, we have `__init__()`, `tick()`, and `draw()` methods. There's nothing you haven't seen before in `__init__()` or `draw()`, so we'll focus on `tick()`, which does two things: it changes our laser color from white to black when it reaches the top of the screen (to simulate our lasers going off the top of the terminal window), and it decrements the laser's `y` value (moving it one row up on the screen) for each tick of the game. Since a laser gets its initial `x` and `y` values from the player, the end result is a little white laser bolt flying across the screen from the player toward the invading fleet! ## `main.py` Now that we have all the pieces of our game in place, we can tie everything together neatly by creating a new `Game` instance in `game.py`: ```py import curses from curses import wrapper from game import Game def main(stdscr): curses.curs_set(False) stdscr.nodelay(True) stdscr.clear() Game(stdscr).run() if __name__ == '__main__': wrapper(main) ``` We have only a single function here, `main`, that we call at the bottom of our file (using the `wrapper` object from `curses` in order to automate all the setup and teardown work needed to neatly move from the regular REPL into the game terminal; you can read more about it [here](https://docs.python.org/3.3/library/curses.html#curses.wrapper)). Here's what `main` does: 1. It sets `curses.curs_set(False)`, which makes the cursor invisible. 2. It sets `stdscr.nodelay(True)`, which makes our `stdscr.getch()` call non-blocking (that is, our game will keep ticking while it waits for user input). 3. It clears the screen in preparation for a new game using `stdscr.clear()`. 4. Finally, we create and run a new game via `Game(stdscr).run()`. ...and that's it! ![ta-da](https://duaw26jehqd4r.cloudfront.net/items/3T090O1J1G09271x3728/space_invaders.png?v=ded56fc9) There are lots of opportunities to make this game better: making it so multiple hits are required to kill an invader, adding multiple rows of invaders, adding scorekeeping functionality, making the invaders move faster over time, and so on. The sky's the limit! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/Space-Invaders) to add more functionality.
3
posted by ericqweinstein (152) 2 months ago
โ–ฒ
6
How to Make a Custom Code Theme for Repl.it
# Making a Custom Code Theme for Repl.it ### This tutorial will teach you how to write some basic CSS to change the colours of your code and how to have it automatically injected into repls. ###### This tutorial only explains for Chrome, but the concepts are fairly similar in other browsers, it's simply a matter of finding an add-on/plugin that does the same job. ## Step 1 #### Installing the extension ###### *You'll have to do this step differently if you're using a browser other than Chrome* Firstly, go to Chrome's extensions page ![image](https://storage.googleapis.com/replit/images/1545216200382_a8c806733f8f43270981df55289ae3ab.pn) Then, click the menu button in the top left corner and click "Open Chrome Web Store" at the bottom. Search `CSS JS injector` and click "Add to Chrome" on this result (it should be the first option). ![image](https://storage.googleapis.com/replit/images/1545216400288_695364f7ce6df75772834911fa93e7ca.pn) You should then see this icon appear to the right of your adress bar. ![image](https://storage.googleapis.com/replit/images/1545216568449_47772007bf34d47b5236066cb6aa47c9.pn) ## Step 2 #### Writing the CSS ###### *Again, this will be a bit different depending on which browser you are using.* Open any repl. Write some nonsense code which contains all of the colours you want to change, then press F12 to open Inspect Element and click the mouse button in the top left corner of the panel. When you've clicked it, it will turn blue. ![image](https://storage.googleapis.com/replit/images/1545216808633_deb03df50964a3a75007fbd2d8117df0.pn) Next, hover over one of the code colours you want to change. In this example I'm hovering over a string in a python repl. Above it, Inspect Element tells me that it is a `span.mtk5`. This is important, as this is what we will use to tell the CSS that we want to change the colour of strings. If you hover over code that is in a different colour, you'll see they are all `span.mtk` with a number on the end. ![image](https://storage.googleapis.com/replit/images/1545217525286_9e497b17b1e9abd90d61ed7371e97851.pn) Now click on the icon in the top-right corner. The first thing you want to do is check this box, which tells the extension we want to inject this CSS every time we visit a repl.it page: ![image](https://storage.googleapis.com/replit/images/1545217634499_71bb68723a56886fab1877538887b60d.pn) Now type in the CSS box any CSS you want to inject into the page. To change the colour of a python string, for example, we type ```css span.mtk5{ color: red; } ``` This is because when we hovered over the python string earlier, Inspect Element told us it was a `span.mtk5` (this means it's a `span` element, with class `mtk5`). Finally, press "Save Style", and refresh the page. If everything has worked, you should see this: ![image](https://storage.googleapis.com/replit/images/1545217883511_22e8ca80994d2704639a02e0fb8ed0bc.pn) You can now change the colours of different parts of the code, finding out which class they have using the same technique as before. ## Step 3 #### Next Steps ###### Here are some more tweaks you could make to Repl.it's Monaco editor using these techniques + Change the styles of other parts of the editor, e.g. change the background colour + Give code other styles: as well as changing the colours of the different classes, you can also (for example) make comments italic by finding out their class using the technique described earlier and putting `font-style: italic;` + Make things change colour when you hove over them e.g. ```css span.mtk5:hover{ color: blue; } ``` + Animate the colours: ```css @keyframes red-green-blue{ 0%,100%{color: red;} 33%{color:green;} 67%{color:blue;} } span.mtk5{ animation: red-green-blue 5s infinite; } ``` **Watch out: when you switch Repl.it from dark theme to light theme, the class names change! For example, python single-line comments are `span.mtk7` in dark theme, but when you switch to light theme they become `span.mtk11`. Also, every time you make an edit to your CSS, you will need to re-check the "Apply for domain" box.** Here's one I made earlier! ![image](https://storage.googleapis.com/replit/images/1545227116026_b6cffa9696c503325e4c91193ccb455f.pn) You'll notice I've changed the background of the editor to black, and I've made comments italic, as well as changing up the colours. ## Step 4 #### Improving the looks of your new theme This step is very easy - all you have to do is upvote this post. It has been scientifically proven that if you upvote this post, your tweaks will automatically look cooler. Plus, you will receive vee freebucks. ### Hope you enjoyed this post - let me know what cool custom themes you make! #### Oh, and one final amazing thing about this tutorial: **it doesn't just work on Repl.it!** ๐Ÿ˜‰
2
posted by minx28 (86) 2 months ago
โ–ฒ
6
Javascript Games: Lesson 1 -- GuessIt
# Introduction This is the first tutorial in a series designed to teach basic JavaScript game programming. It's the spiritual successor to BASIC Computer Games, in which David H. Ahl provided source code for 100 BASIC games. Today, we would think of those games as "terminal programs", displayed using ASCII text. We will be running our games in a web browser, and while they will initially be text-based, we will quickly move into the realm of canvas graphics. But first things first. In this tutorial, we will cover the boilerplate concepts behind all games: variables, output, input, functions, loops, and flow-of-control. Don't worry if you don't know what any of those mean -- that's what the tutorial is for. Once we have discussed these fundamentals, we'll present the code for our first game: GuessIt. # Fundamentals All games rely on the following foundational concepts. More advanced games involve additional ideas, but *every* game needs these: * variables -- named buckets that hold data * output -- how we show the game to the user * input -- how we accept data from the user * functions -- how we re-use code from at different points in a program * loops -- how we perform repeated tasks efficiently * flow-of-control -- how we make the game choose one path among many **Variables** Variables are just chunks of computer memory that hold data for us. You can think of a variable as a bucket with a name. You can put things in the bucket and take them out again. In some languages, there are different kinds of buckets for different kinds of data. Text goes in one kind of bucket, decimal numbers in another, and integers in yet another. In JavaScript, all buckets can hold anything you want. This simplifies life in some ways, and complicates it in others. To define a new bucket, we use the 'var' keyword, followed by the name of the bucket: ```js var myName; ``` (the semicolon tells JavaScript that it has reached the end of this line of code). Once you have defined a new bucket, you can refer to it by name elsewhere in your code. You can use *operators* like the equals sign to manipulate the data inside the bucket. For example, in JavaScript, the single equals sign (**=**) tells the computer to take whatever is on the right hand side and put it into the thing on the left hand side: ```js myName = "Mark"; ``` takes the text data inside the double quotes and sticks it in the bucket called myName. You can combine the definition of the bucket and the assignment of its value into a single line, if you like: ```js var myAge = 49; ``` Now the variable 'myAge' has a value of 49. Variables can hold text data, numerical data, code, graphics, sound, and any combination of these things wrapped into an 'object' (which we will cover in later tutorials). Variables can also contain lists of data. These lists are often called "Arrays", and you create a list by enclosing the entries in square brackets ('[' and ']') and separating them by commas: ```js var myDogs = ["Hershey", "Koda"]; ``` You can also break this up over multiple lines: ```js var myDogs = [ "Hershey", "Koda" ]; ``` That's a lot to remember, but don't sweat it. The most important thing is: *variables are buckets that hold data*. **Output** All games need to output data to the player(s). Our first games will run on a web page, so we will use HTML for output. We will rely on the "write line" command supplied by the document in which we are running: ```js document.writeln("Your message goes here..."); ``` Note that in HTML, the "string" of letters '<br>' (without quotes) is a "line break" that marks the end of a line of text. Use this to move down to the next line. For example: ```js document.writeln("First line.<br>"); document.writeln("Second line.<br>"); ``` These will appear as two separate lines in the web browser. Without the line breaks, they would appear together on a single line, even though we used two different writeln statements. **Input** Players need a way to affect the games they play, whether it's telling the dealer to "hit me" in Blackjack, or firing a weapon in Doom. In our case, we will be responding to keyboard events. To do this, we will rely on the "event listener" system supplied by the document in which we are running. An "event" is a change in the computer environment that affects the document. This could be many things: a key press, a mouse click, a message telling the browser to resize itself. Web documents supply an "event listener" system to capture and respond to important events. To listen for a particular event, we will call the document's "addEventListener" code: ```js document.addEventListener("keydown", function(event) { // ...your code here... }); ``` The above line tells the document to execute "...your code here..." whenever the user presses key down. Don't worry about the 'function(event)' bit -- we'll discuss that in the next section. For now, let's consider this concrete example: ```js document.addEventListener("keydown", function(event) { document.writeln("Someone pressed a key!"); }); ``` This displays the message, "Someone pressed a key!" each time the user presses a key. There are many other events to which the document can respond, like "keyup", "mousedown", "mouse", and "mousemove", just to name a few. We'll get to those when we need them. For now, "keydown" is good enough. Sometimes, you want your game to ignore events to which it previously responded. You can accomplish this using the "removeEventListener" command: document.removeEventListener("keydown"); This would undo the "addEventListener" we used above. **Functions** Often, you write a chunk of a code that you want to use over and over. For example, suppose you wrote a chess program. Games of chess can last many turns. You would hope that you could write the code to play a single turn, then simply re-use that code for each subsequent turn. *Functions* allow you to do this. A function is a chunk of self-contained code designed to perform a specific task. Functions often require data to complete their task, so when you define the function, you must indicate what data it's expecting as input. In JavaScript, function definitions look like this: function(list of input data) { // Chunk of code to execute using the input data }; If this seems confusing, think of a function as a meat grinder: you put stuff in, you turn the crank, and something useful comes out. The "stuff you put in" is the "list of input data". The "turn the crank" bit is the "chunk of code" part. The "something useful comes out," well...we haven't covered that. It's called the 'return value'. A specific example will clear all this up. Consider a function that computes the area of a rectangle: ```js function(length, width) { var area = length * width; return area; }; ``` You send in a length and a width, it multiplies them together to find the area, then it 'returns' that area back to you. Remember that addEventListener stuff? Recall that the function looked like this: ```js function(event) { document.writeln("Someone pressed a key!"); } ``` Now you know what the 'function' is for -- that tells JavaScript what function to execute when someone presses a key. The 'event' data is passed into the function from the web browser, and it tells the function things like what key was pressed. In fact, we could change the function to display that information as follows: ```js function(event) { document.writeln("Someone pressed the " + event.key + " key!"); } ``` Notice that this function doesn't return anything (there is no 'return' statement at the end). That's fine. Not all functions return a value. Sometimes you use them just to produce output when the crank is turned. One last note: you can store a function inside a variable. Doing so lets you execute (or call) the function using the bucket's name. This is handy shorthand for executing code! Example: ```js var print = function(theMessage) { document.writeln(theMessage + "<br>"); }; ``` Now, any time you want to display a message, you can use this: print("Fred ate bread!"); This will send the data "Fred ate bread" to the above function and print it out, automatically appending a <br> so that subsequent messages appear on subsequent lines in the document. Applying this to our area function, we get: ```js var areaOfRect = function(length, width) { var area = length * width; return area; } ``` And we could write a short program as follows: ```js var area = areaOfRect(3, 4); print("The area is " + area); ``` What do you think would be the output of this program? **Loops** Often, games need to rapidly repeat a task. For example, if you are drawing a square grid, you must print multiple horizontal and vertical lines. For something like tic-tac-toe, this isn't so bad, but for a game like Go, you wouldn't want to draw every line with a new command. Loops allow programmers to efficiently perform duplicate commands. There are several kinds of loops, all of them follow the same philosophy: the programmer defines a *loop condition* during which the program repeats a chunk of code over and over. The programmer follows the loop condition by the code to be repeated: [Loop Condition Goes Here] { // ...code to repeat goes here } Notice that in JavaScript, we use curly bracers ('{' and '}') to "section off" the code that will be repeated. JavaScript supports several different kinds of loops. We will start with just two: the 'while' loop and the 'for' loop (which is really just a 'while' loop in fancy clothes). The *while* loop looks like this: while (...something is true...) { // Do this code } Consider this example: ```js var greets = 0; while (greets < 5) { print("Hey there!"); greets = greets + 1; } ```` This probably looks confusing, especially this bit: greets = greets + 1; Clearly, this is nonsense! There is no number that equals itself plus one. True...but remember -- in JavaScript, the '=' symbol differs from what you learned in Math. In JavaScript, it says, "takes what's on the right-hand side and put it into the thing on the left-hand side." Also remember that 'greets' is a bucket that we initially fill with the value 0. So this: greets = greets + 1; reads as: "Take the value in greets (currently 0), add one to it, and place this back in the greets bucket." So, after executing this line the first time, greets' value changes from 0 to 1. We then hit the bottom curly brace and bounce back up to the 'while' statement, where we check the loop condition. Is the value in greets less than 5? Yep! So we once again execute the code inside the curly braces. While doing so, we again hit the line greets = greets + 1; but *this* time, greets contains '1'. So what happens? We add one to it, and store the resulting value (2) back in greets. Then we hit the bottom curly brace, bounce back up to the top of the loop, and check the conditions. 2 is still less than 5, so we go around again... ...and again... ...and again... ...and again... ...and finally we bounce back to the top, check the condition and see that greets is **equal** to 5, rather than less than 5, so the loop condition is false and we break out of the loop by instantly jumping past the last curly brace. That might seem like a silly example: why would we want to print out the same message 5 times? So let's consider a more useful example. Remember my dogs? ```js var myDogs = ["Hershey", "Koda"]; var count = 0; while (count < myDogs.length) { print(myDogs[count] + " is barking."); count = count + 1; } ``` Now we're using the *while* statement to *iterate* (or step through) a list of data. Notice that we use square brackets ('[' and ']') to access a single element of the list, and that the first element in the list is number 0. In other words: myDogs[0] contains the word "Hershey", and myDogs[1] contains the word "Koda". Also note that JavaScript will tell you how long any list is when you use the ".length" accessor: myDogs.length is 2. *While* loops are handy, but after you've written a few, you'll see that most of them fall into the following pattern: ```js var some_loop_counter = some initial value; while (condition_involving_the_loop_counter) { // ...loop code... some_loop_counter = some_loop_counter + some increment; } ``` Recognizing this pattern, the authors of JavaScript created the *for* loop to make your code more concise. The *for* loop works exactly like the *while* loop, but it lets you compress the syntax: for (initialize the counter; check the condition; update the counter) { // ...loop code... } For example, our doggy-loop above would look like this when written as a *for* loop: ```js for (var count = 0; count < myDogs.length; count = count + 1) { print("My dog " + myDogs[count] + " is barking."); } ``` If you want to compress this even further, you could take advantage of the math operators '+=' or '++': count += 1; is shorthand for count = count + 1; And both count++ and ++count tell JavaScript to increase the value of *count* by 1. There is a difference between these ++count and count++, but we'll save that for another tutorial. So, the super-compressed version of our doggy loop would be this: ```js for (var count = 0; count < myDogs.length; ++count) { print("My dog " + myDogs[count] + " is barking."); } ``` **Flow-of-Control** Finally, let's talk about "flow of control." That's just a fancy way of saying, "my game can execute one of several options at the appropriate time." For example, suppose you are making a dungeon crawler and you have AI monsters that can take one of several actions when encountering the player. You would use flow of control statements to pick one of those actions when an encounter occurs. As with loops, there are several flavors of decision-making in JavaScript. For now, we will restrict ourselves to the "if...then" statement. Generally speaking, that structure looks like this: If (condition 1 is true) { // Do this code } else if (condition 2 is true) { // Do *this* code instead } else if (condition 3 is true) { // etc. } // and so on for as many conditions as you like, until finally else { // Do this if nothing else up to this point was true. } Let's look at an example by modifying our doggy loop: ```js for (var i=0; i<myDogs.length; ++i) { if (myDogs[i] === "Hershey") { print(myDogs[i] + " is barking."); } else if (myDogs[i] === "Koda") { print(myDogs[i] + " is farting."); } else { print("That's not my dog!"); } } ``` Note that the triple-equals ('===') in JavaScript asks the question, "is equal to". So a line like this: if (myDogs[i] === "Hershey") Reads as, "if the ith element in myDogs is equal to the word Hershey". As you can see, this will print out: Hershey is barking. Koda is farting. Which might seem like a fart joke, but is sadly the status quo with my black lab, who is currently practicing his "art" 6 feet from my desk. Ugh. # Conclusion You now have all the building blocks you need to make a simple game. For our first outing, we will write "GuessIt" -- a variation on the card game Acey Ducey. In that game, you flip two cards, then place a bet as to whether a third card, when revealed, will fall between the first two. In GuessIt, we use randomly-generated numbers from 0 to 9, but otherwise, the game is identical. Take a look at the source code. We have broken it up into two files: _definitions.js -- in which we define all the functions and variables used in the game_ _commands.js -- which actually executes the commands defined in definitions.js to start the game_ If you just loaded definitions.js, the game would never start, because the definitions don't do anything but fill buckets with values and functions. If you just loaded commands.js, the program would throw an error because the commands rely on the buckets defined in definitions.js. You don't have to lay out your JavaScript programs this way -- we just did it to drive home the point that defining variables and functions is a *passive* activity, like setting up the game board. It doesn't actually result in the activity of playing the game itself. To do that, you have to execute the commands you created earlier. The code for GuessIt contains a lot of comments to help you understand what we're doing at each point. Some of it repeats what we have covered here, but in a more specific context. Once you are comfortable with what it's doing, try modifying the game. Here are some suggestions: Change the background color by using the command: document.body.style.backgroundColor = "gray"; // Or whatever common color name you want Change the amount of starting cash given to the user Change the payout formula Or, the biggie: Add a new eventListener when the player loses that resets her starting cash and allows her to play again Good luck, and have fun!
7
posted by MarkKreitler (18) 2 months ago
โ–ฒ
13
How to program MineSweeper in Python! (fully explained in depth tutorial)
# How to program MineSweeper in Python # *** *Difficulty: moderate* **Please note: this tutorial is currently in creation!** ##### Welcome to my tutorial on how to program the classic game, MineSweeper, in Python! Before you begin, I would highly recommend playing a few games to get the hang of the rules. You can do this either [online](http://minesweeperonline.com/#beginner) or in [my program](https://repl.it/@ThomasS1/MineSweeper). ![Vintage Windows MineSweeper](https://storage.googleapis.com/replit/images/1545220146051_48f373ca2fe75d80cde9b1235a088351.pn) We will be building our game in the Python terminal, which, as you probably know, has its limitations. Instead of clicking on the square in the grid, as you do in the original game, the player will type its coordinates. But what we will create is a fully functional 9x9 MineSweeper game, that will entertain you and your friends for hours on end (sorry, that sounded very cheesy and predictable). ###### Note: I would encourage you, especially if you are a beginner, to write out the code rather than just copy it, as this will help you to understand it. ### Part 1: Planning *** You should now understand the game mechanics of MineSweeper, but before we can get started we need to think about what the *computer* does. This will help us when writing our code later. First, just to avoid confusion later, a few definitions/variables I will use: * **Bomb:** I will use this word instead of 'mine', to avoid confusion with the possessive pronoun! In our game, bombs will be represented by asterixes, `*`. * **Marker:** a flag which the player can place to help them remember any locations of bombs they have deduced. In our game, these will be represented by the unicode flag symbol, `โš`. * **Solution grid:** this contains the locations of all the bombs and numbered cells. * **Known grid:** this contains the squares that the player knows about. * **To open:** to move data from the solution grid to the known grid, when the player makes their move. Now we've got that cleared up, here's a quick outline of what the program will do. * Display menu to player. * *If* they ask for instructions, print them. * Generate random locations of 10 bombs and place them in the solution grid. * Update the numbers around them in the solution grid. * Until the player wins/loses, loop: * Display the known grid to the player and ask for their move. * *If* they chose to open a square, open it. * *If* that square is a bomb, they lose. * Offer to play again. * *If* the number in that square is a 0, open up all of the squares around it automatically, as there could not be any bombs there, and do the same if any of those squares are 0, etc etc. * *If* all squares in the grid except the 10 bomb squares are open, they win! * *If* they chose to place a marker in a square, place that marker. ### Part 2: Setting up our program structure. *** Because this is not going to be a very long program, (about 250 lines - don't worry if you do think this is quite long!) we will write almost all of it in one file. The only exception will be the instruction text, which we will put in a seperate .txt (text) file to declutter our code a bit. So, if you're going to write the program as we go along, now is the time to create your repl, call it something, and create the new file. In the panel on the left, click 'files' and then 'new file'. Call it `instructions.txt`. Paste into it the following text: ``` INSTRUCTIONS ============ The aim of MineSweeper is to determine the locations of 10 bombs, randomly placed in a 9x9 grid. On each go, you type in the coordinates of a square, e.g. E4. If there is a bomb in that square, you lose. Otherwise, the number of bombs directly surrounding that square, including diagonally, will appear in that square. If that number is a 0, the squares around it will be 'opened' automatically, as there cannot be any bombs there, to save you time. If you think you know the position of a bomb, type 'M' followed by the coordinates of that square, e.g. ME4. You win by 'opening' all of the squares except those with bombs in. NB: It is luck on the first move, and you may get to a stage where it is luck later in the game too. Good luck! ``` In Part 5 we will write the code that gets the text from the file and prints it so the user can read it. ### Part 3: Beginning our code. *** Now move back to `main.py`, and write one of the following. If you're writing your code on repl.it: ```py import random, time, copy, replit from termcolor import cprint ``` Otherwise: ```py import random, time, copy from termcolor import cprint ``` This imports four/five packages, `random`, `time`, `copy`, and certain parts of `termcolor`, which we will use later in the program. If you are on repl.it, it also imports the module `replit` which allows us to clear the terminal. If you are on a different platform and know how to clear the terminal, that's fine - just use your method whenever I refer to `replit.clear()`. If you don't, that's ok too - sometimes I provide an alternative, but if I don't, just leave it out and it should be fine. But it doesn't do anything yet. Let's change that. First things first: we need to write an introduction for the player. Here's one I made earlier, but feel free to edit it / write your own. ```py #Introduction print() cprint('Welcome to MineSweeper v.3.0!', 'red') cprint('=============================', 'red') print() print('Excited to declare version 3.0 of MineSweeper as almost fully functional!') ``` 'cprint()' is a function we imported just a minute ago. It allows us to print colour in the terminal, with the syntax `cprint('text', 'colour')`. I've set it up to print the title in red, but you can choose any of the following colours: ``` grey red green yellow blue magenta cyan white ``` Termcolor has lots of other cool features such as text highlights - see the [package website](https://pypi.org/project/termcolor/). You can now run your program and it will, for the first time, do something! Oh and, if it hasn't already it will first chuck out some rubbish about importing `termcolor`. Ignore it - it only does it once. ### Part 4: A Python Sandwich *** By the end of this tutorial, you will be sick of functions (`def example():`). Apart from one line, which initiates all of the functions, the rest of the program... will be entirely functions. Why? Because MineSweeper is a repetitive game where a 'go' always leads to one of a few outcomes. This means that it is much simpler and faster to write the rest of the code in functions. Functions allow us to do one of two things. In the mathematical sense, they can be used as a quick way of writing one equation which would otherwise take up several lines (a mathematical function is a term you have probably heard of/used). But they can also be used for more substantial pieces of code that will be used several times, or as a substitute for a loop. Functions can do both, and quite often do. This may all sound a bit wierd, but hopefully it will become clear as we continue. Let's write the last line that's not part of a function. It's pretty simple, and it goes right at the end of our program. Leave some space for the rest of the code, and type: ``` reset() ``` The code we have written so far are the slices of bread, and what we will write now is the filling. There you go: a Python Sandwich! ###### Side note: this will be a *very* well filled sandwich. ### Part 5: `def`, `def`, `def` *** If you try to run your code now, it will give you an error that looks something like this... `NameError: name 'reset' is not defined` ... because we haven't defined `reset()`. That is what we need to do next. `reset()` will be the function that runs every time we want a new game. It resets all the variables, as well as printing the menu and starting the timer. In the space we left in Part 4, write this: ```py #Sets up the game. def reset(): print(''' MAIN MENU ========= -> For instructions on how to play, type 'I' -> To play immediately, type 'P' ''') choice = input('Type here: ').upper() if choice == 'I': replit.clear() #Prints instructions. print(open('instructions.txt', 'r').read()) input('Press [enter] when ready to play. ') elif choice != 'P': replit.clear() reset() ``` This, when called upon by `reset()`, prints the main menu and asks the player for your choice - to play immediately or to see the instructions. If the player typed 'I', it clears the terminal, then gets the text from the file `instructions.txt` that we made earlier, reads it, and prints it (`print(open('instructions.txt', 'r').read())`). It then waits until the player presses enter before continuing. If the player **didn't** type 'I' or 'P', it clears the terminal and runs `reset()` again, as they didn't enter one of the given options. If the player typed 'P', it continues straight on. ### Part 6: Generating the Solution Grid *** The next thing we need to think about is generating the solution grid for each particular game. It must have 10 bombs in random places, and every cell must be marked with how many bombs surround it First, we need to set up the array (two-dimensional list) for the solution grid. We'll call it `b`, for bombs. Copy this code inside `reset()`, and make sure they are both tabulated the correct distance. ```py #The solution grid. b = [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]] ``` But what is this mess of zeros? As you can see, `b` is a list, but it also *contains* some lists - 9 to be exact. Each of these lists contain 9 objects, which are all zeros at the moment. This essentially makes it a two-dimensional list, which is called an array, to define the solution grid. The next step is generating the random locations of the 10 bombs, and placing them in the solution grid. For each bomb, we need to do three things: * Generate a random location. * Check if there's already a bomb there; if there is, go back to the first step. * Place the bomb in the solution grid. We're going to do this through another function, and you'll see why later. To call it, add this to the end of `reset()`. ```py for n in range (0, 10): placeBomb(b) ``` That should be pretty self-explanatory - it simply calls upon `placeBomb(b)` 10 times. In case you don't know, that `(b)` is there because a function can't access external variables without being passed ('given') them. Now we need to define `placeBomb(b)`. Write this *after* `reset()`. ```py #Places a bomb in a random location. def placeBomb(b): r = random.randint(0, 8) c = random.randint(0, 8) #Checks if there's a bomb in the randomly generated location. If not, it puts one there. If there is, it requests a new location to try. currentRow = b[r] if not currentRow[c] == '*': currentRow[c] = '*' else: placeBomb(b) ``` This does all three things that we mentioned earlier: First, it generates a random location in the grid through the variables `r` (row) and `c` (column). Note that they are a random number from 0 to 8, because indexing in Python starts at 0 not 1. Next, it checks if there is already a bomb at that location. It gets the row (one of the lists in `b`) and then the column (one of the values in that row). If it's *not* already a bomb, it puts one there. If it is, it runs the function again. Once it has succesfully placed a bomb, it returns to the line in `reset()` which it was called from. Since it runs `placeBomb(b)` 10 times, we end up with 10 randomly placed bombs in the grid! The next thing we need to do is make sure that all the 0s in the grid are changed to the number of bombs surrounding that square. By far the easiest and quickest way of doing this is to add 1 to the numbers in all of the squares surrounding each bomb. This will lead to all of the numbers correctly reflecting the number of bombs surrounding them. We are going to do that with this code. Write it at the end of `reset()`. ```py for r in range (0, 9): for c in range (0, 9): value = l(r, c, b) if value == '*': updateValues(r, c, b) ``` The two `for` loops go through each square in the grid, by cycling through the x and y coordinates, `r` and `c`. In the next line, we have another function which we are yet to build, `l(r, c, b)`, which gets the value at the given coordinates - we'll write that in a minute. If that value is a bomb, it updates the numbers around it through yet another function, which we will write now. `updateValues(r, c, b)` is an annoying bit of code which is too boring, repetitive and overcomplicated for the code to be worth explaining now (if you want a challenge, feel free to read through it!). I will, however, explain what it does. The variables that you pass to it are the row and column of the bomb, as well as `b`, our solution grid. It goes through all 8 squares directly surrounding the given square, and adds 1 to the value there in the solution grid, unless that square is also a bomb. Don't worry if that's unclear, I'll explain it with an example in a moment. The full function is here (copy it anywhere after the `reset()` function): ```py #Adds 1 to all of the squares around a bomb. def updateValues(rn, c, b): #Row above. if rn-1 > -1: r = b[rn-1] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if not r[c] == '*': r[c] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 #Same row. r = b[rn] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 #Row below. if 9 > rn+1: r = b[rn+1] if c-1 > -1: if not r[c-1] == '*': r[c-1] += 1 if not r[c] == '*': r[c] += 1 if 9 > c+1: if not r[c+1] == '*': r[c+1] += 1 ``` The other function that we need is `l(r, c, b)`, which, as I mentioned earlier, gets the value in the solution grid at the given coordinates (`r` and `c`). It's a very simple bit of code, but we are going to be using it a *lot* and we will want to be able to write it as shorthand as possible (`l` stands for location). Here's the code (copy it after `reset()`): ``` #Gets the value of a coordinate on the grid. def l(r, c, b): row = b[r] c = row[c] return c ``` It's one of the more mathematical-type functions, as it only does one thing and then *returns* the output. Whenever we call this function, it will simply return the value at the given location - it doesn't change anything, it just gives us information. We'll see why this is so useful in Part 7! The first line gets the correct row from our solution grid, and the second gets the correct column from (value in) that row. The third line simply returns that value. We can actually shorten it to just two lines... ``` #Gets the value of a coordinate on the grid. def l(r, c, b): c = b[r][c] return c ``` ... because previously `row` in the second line was just standing for `b[r]`. And even to just one line! ``` #Gets the value of a coordinate on the grid. def l(r, c, b): return b[r][c] ``` Your program should now look something like [this](https://repl.it/@ThomasS1/MineSweeper-2). There shouldn't be any errors, but if you try to play a game it will just stop abruptly, because although we have the gird generation going on in the background, we haven't asked the program to print anything yet. That's all going to change in Part 7! #### Review of Part 6 Some of that was probably quite confusing, so I'll run through an example (with a smaller grid). If you feel comfortable so far, skip to Part 7. We started by setting up the variable for the solution grid, a two-dimensional list of zeros. So at the moment, our solution grid looks like this: ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` Next, we added 10 random bombs (I'll just do 4 here as it's a smaller grid): ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 0 โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` Then we went through each bomb and added one to each of the numbers around it, to get this: ``` โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 0 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ * โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ 0 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• โ†“ โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ 3 โ•‘ 3 โ•‘ 2 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 2 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` ### Part 7: Printing the Grid *** Part 6 was by far the longest and most complicated stage we've done, so well done for getting this far. Part 7 should be a bit easier! In this stage, we're going to write a function which prints the grid so our player can see it. Of course in the real game we'll print a blank grid at first, but so that we can see that the grid generation worked we'll print the solution grid for now. The function we're going to write will be called `printBoard(b)`. Eventually, it should output something like this (using our smaller example from earlier): ``` A B C D E โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•— 0 โ•‘ 1 โ•‘ * โ•‘ 1 โ•‘ 1 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 1 โ•‘ 2 โ•‘ 3 โ•‘ 3 โ•‘ 2 โ•‘ * โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 2 โ•‘ 2 โ•‘ * โ•‘ * โ•‘ 2 โ•‘ 1 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 3 โ•‘ 1 โ•‘ 2 โ•‘ 2 โ•‘ 1 โ•‘ 0 โ•‘ โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ 4 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ 0 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ• ``` The letters at the top and the numbers to the left will be used as coordinates when the player types their chosen move. To create the grid shape, we'll use some of the Unicode box-drawing characters: `โ•โ•‘โ•”โ•ฆโ•—โ• โ•ฌโ•ฃโ•šโ•ฉโ•` To start our function, type this: ```py #Prints the given board. def printBoard(b): ``` The first thing we need to do in our function is to move the grid from the player's previous go off the visible screen. We can do this in a couple of different ways: * Print 40 or so empty lines * Use the repl.it `clear()` function This will trick the player into thinking that we are updating the grid each time they make a move rather than reprinting it. To do this, type one of these inside the new function: If writing your code on repl.it: ```py replit.clear() ``` Otherwise: ```py for n in range (0, 40): print() ``` Next, we need to print the letters and then the 'top' of the grid/board/box. These two lines will do that: ```py print(' A B C D E F G H I') print(' โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•—') ``` Now for the slightly trickier bit. Until now, what we've printed will always be the same, whatever the board (`b`) that was passed to the function. But now we need to write the code that prints the grid *with the correct values in it*. It needs to get the data at each location in the grid. How do we do that? With `l(r, c, b)` of course! This is the code we're going to use - I'll explain it in just a second. Add it to our new function: ```py for r in range (0, 9): print(r,'โ•‘',l(r,0,b),'โ•‘',l(r,1,b),'โ•‘',l(r,2,b),'โ•‘',l(r,3,b),'โ•‘',l(r,4,b),'โ•‘',l(r,5,b),'โ•‘',l(r,6,b),'โ•‘',l(r,7,b),'โ•‘',l(r,8,b),'โ•‘') if not r == 8: print(' โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ') ``` Don't panic - this is a *lot* less complicated than it looks. Because we need to print a 9x9 grid, the `for` loop runs the above code 9 times, changing `r` (the row number) each time. Let's break down the `print()` line that follows: ![image](https://storage.googleapis.com/replit/images/1545223996589_a30373455341fcc31890e5cdeab8bab2.pn) As you can see, it is essentially made up of three things - the line number, the box-drawing characters, and the `l(r, c, b)` functions. Not as complicated as it might have looked at first, hopefully! The next two lines are an `if` statement, which prints the line that runs between each row on the board... `โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ` ... unless the row number is 8 (the 9th and last row). After the last row, we need to print the closing line - to do this, add the following after the loop: ```py print(' โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•') ``` And that's the `printBoard()` function finished! The complete function should look like this: ```py #Prints the given board. def printBoard(b): replit.clear() print(' A B C D E F G H I') print(' โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•ฆโ•โ•โ•โ•—') for r in range (0, 9): print(r,'โ•‘',l(r,0,b),'โ•‘',l(r,1,b),'โ•‘',l(r,2,b),'โ•‘',l(r,3,b),'โ•‘',l(r,4,b),'โ•‘',l(r,5,b),'โ•‘',l(r,6,b),'โ•‘',l(r,7,b),'โ•‘',l(r,8,b),'โ•‘') if not r == 8: print(' โ• โ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฌโ•โ•โ•โ•ฃ') print(' โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•ฉโ•โ•โ•โ•') ``` Now all we need to do is check that it - and all the code we've done so far - work! To do this, we just need to call the function, `printBoard(b)`, at the end of `reset()`. Just type `printBoard(b)`, and run the program. When the main menu comes up, type 'I' to check that the instructions are working, and then press enter to see your randomly generated grid! Working? Great - proceed to Part 8. Somthing wrong? Your entire program should now look like [this](https://repl.it/@ThomasS1/MineSweeper-3). ### Part 8: So nearly ready to start the gameplay! *** We are so nearly ready to start programming the actual gameplay - there are just a few more things we need to set up, primarily the **known grid**. Way back in Part 1 I mentioned the known grid. It contains the squares that the player knows about, and, unlike the solution grid, it changes when the player makes their move. At first, of course, it needs to be blank, as the player doesnt know anything about the grid. In `reset()`, *before* the line where we call on `printBoard(b)`, add the following: ```py #Sets the variable k to a grid of blank spaces, because nothing is yet known about the grid. k = [[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']] ``` `k`, the known grid, is another array like our solution grid, but it contains just blanks spaces because at the beginning of the game the player knows nothing about the solution grid. Data will be copied into it from the solution grid as the player makes their moves. We still have the line `printBoard(b)` at the end of the `reset()` function, but we don't want the player to be given the solution grid at the beginning! Instead, we want them to see a blank grid. To do this, we just need to pass the known grid to `printBoard` rather than the solution grid. So just change it to `printBoard(k)`! If you run your code now, when you start the game you should just see a blank grid; if not, check your code looks like [this](https://repl.it/@ThomasS1/MineSweeper-4). ### Part 9: Ready Player One *** We've now set up pretty much everything we need, and can (finally, I hear you sigh) get started on the gameplay! *Minesweeper* isn't just about locating all of the bombs in the grid, it's also about doing that in the fastest time possible. So let's set up a timer so players can see how well they did. The timer will work by getting the exact time at the start of the game, and then the same when the player wins/loses. It will then find the difference between them, and that's our time! So at the end of `reset(` add the following lines: ```py #Start timer startTime = time.time() ``` We'll then initiate gameplay by calling on our yet-to-be-created `play()` function: ```py #The game begins! play(b, k, startTime) ``` Now, of course, we need to write the `play()` function. It's the longest function in the program, at, excluding comments, about 40 lines long. Each time we run through the `play()` function, one 'go' happens - i.e, the player places their move and we update the known grid / end the game accordingly. Here's a rough breakdown of what the function will do: * Let the player choose a square (which will be verified as a valid square though another function, `choose()`). * *If* the player asked to place a marker: * Change the value in the known grid at the given location to the marker symbol, `โš`. * *Otherwise*: * Get the value at the given location. * *If* it is a bomb: * Tell the player they lose, and offer them the chance to play again. * *Otherwise*: * Change the value in the known grid at the given location to the real value. * If it is a zero, run `checkZeros()` (another function - we will make this later). * *If* there are only 10 'unopened' squares left in the grid: * Tell the player that they win, and offer them the chance to play again. So now let's start writing the function. As usual, we need to write `def play()`, but since our function needs the solution and known grids, as well as the time the game started (`b`, `k`, `startTime`), we'll write the following: ```py def play(b, k, startTime): ``` I appreciate that this is probably really annoying, but we are going to start our function by calling another funtion, `choose()`, which we'll write later. We will use it to get the coordinates of the square that our player wants to 'open' / place a marker in, which `play()` will use - for this reason, rather than just calling `choose()`, we want to set a variable to the output from that function. In fact, since `choose()` gives us two variables (the coordinates of the square that the player chose), we'll write this: ```py #Player chooses square. c, r = choose(b, k, startTime) ``` We now want to find out what the value at that square is, so we'll add the following, using our `l(r, c, b)` function from earlier: ```py #Gets the value at that location. v = l(r, c, b) ``` If there's a bomb in that location, we need to end the game. We also need to show the player the solution grid, tell them their time, and offer them the chance to play again. Write this in `play()` - it should be pretty self explanatory: ```py #If you hit a bomb, it ends the game. if v == '*': printBoard(b) print('You Lose!') #Print timer result. print('Time: ' + str(round(time.time() - startTime)) + 's') #Offer to play again. playAgain = input('Play again? (Y/N): ').lower() if playAgain == 'y': replit.clear() reset() else: quit() ``` The fourth line in the if statement just calculates the time the game took, by subtracting the start time from the current time, and prints it. Now, assuming that the game hasn't just ended, we need to put the value that the player 'found' into the known grid. To do that, we just write the following: ```py #Puts that value into the known grid (k). k[r][c] = v ``` Now, if that value is a zero, we need to run `checkZeros()`, a function that we'll write later. It will open up any unopened squares around that zero. Just add this: ```py #Runs checkZeros() if that value is a 0. if v == 0: checkZeros(k, b, r, c) printBoard(k) ``` The last thing to do in this function is to work out if the player has won. We'll do this by counting up how many unopened squares there are left.
4
posted by ThomasS1 (23) 2 months ago
โ–ฒ
1
MineSweeper
# How to program MineSweeper in Python # *** *Difficulty: moderate* **Please note: this tutorial is currently in creation!** ##### Welcome to my tutorial on how to program the classic game, MineSweeper, in Python! Before you begin, I would highly recommend playing a few games to get the hang of the rules. You can do this either [online](http://minesweeperonline.com/#beginner) or in [my program](https://repl.it/@ThomasS1/MineSweeper) (which we are going to build). We will be building our game in the Python terminal, which, as you probably know, has its limitations. Instead of clicking on the square in the grid, as you do in the original game, the player will type its coordinates. Also, the Python terminal cannot print colour in the terminal without extra packages, and those have limitations too. But what we will create is a fully functional 9x9 MineSweeper game, that will entertain you and your friends for hours on end (sorry, that sounded very cheesy and predictable). ### Parts List *** [Part 1: Planning]() [Part 2: Setting up our program structure.]() [] [] [] [] ### Part 1: Planning *** You should now understand the game mechanics of MineSweeper, but before we can get started we need to think about what the *computer* does. This will help us when writing our code later. First, just to avoid comfusion later, a few definitions/variables I will use: * **Bomb:** I will use this instead of mine, to avoid confusion with the possessive pronoun! * **Marker:** a flag which the player can place to help them remember any locations of bombs they have deduced. * **Solution grid:** only the computer knows the locations of all the bombs until the end of the game. * **Known grid:** this contains the squares that the player knows about. * **To open:** to move data from the solution grid to the known grid, i.e. when the player makes their move. Now we've got that cleared up, here's a quick outline of what the program will do. * Display menu to player. * *If* they ask for instructions, print them. * Generate random locations of 10 bombs and place them in the solution grid. * Update the numbers around them in the solution grid. * Until the player wins/loses, loop: * Display the known grid to the player and ask for their move. * *If* they chose to open a square, open it. * *If* that square is a bomb, they lose. * Offer to play again. * *If* that square is a 0, open up all of the squares around it automatically, as there cannot be any bombs there, and continue the chain reaction. * *If* all squares in the grid except the 10 bomb squares are open, they win! * *If* they chose to place a marker in a square, place that marker. ### Part 2: Setting up our program structure. *** Because this is not going to be a very long program, (about 250 lines - don't worry if you do think this is quite long!) we will write almost all of it in one file. The only exception will be the instruction text, which we will put in a seperate .txt (text) file to declutter our code a bit. So, if you're going to write the program as we go along, now is the time to create your repl, call it something, and create the new file. In the bar on the left, click 'files' and then 'new file'. Call it `instructions.txt`. Paste into it the following text: ``` INSTRUCTIONS ============ The aim of MineSweeper is to determine the locations of 10 bombs, randomly placed in a 9x9 grid. On each go, you type in the coordinates of a square, e.g. E4. If there is a bomb in that square, you lose. Otherwise, the number of bombs directly surrounding that square, including diagonally, will appear in that square. If that number is a 0, the squares around it will be 'opened' automatically, as there cannot be any bombs there, to save you time. If you think you know the position of a bomb, type 'M' followed by the coordinates of that square, e.g. ME4. You win by 'opening' all of the squares except those with bombs in. NB: It is luck on the first move. Good luck! ``` ### Part 3: Beggining our code. *** Now move back to `main.py`, and write the following: ```py import random, time from termcolor import colored, cprint ``` This imports three packages, `random`, `time`, and certain parts of `termcolor`, which we will use later in the program. But it doesn't do anything yet. Let's change that. First things first: we need to write an introduction for the player. Here's one I made earlier, but feel free to edit it / write your own. ```py #Introduction print() cprint('Welcome to MineSweeper v.3.0!', 'red') cprint('=============================', 'red') print() print('Excited to declare version 3.0 of MineSweeper as almost fully functional!') ``` 'cprint()' is a function we imported just a minute ago. It allows us to print colour in the terminal, with the syntax `cprint('text', 'colour')`. I've set it up to print the title in red, but you can choose any of the following colours: ``` grey red green yellow blue magenta cyan white ``` Termcolor has loads of other cool features such as text highlights - see the [package website](https://pypi.org/project/termcolor/). You can now run your program and it will, for the first time, do something! Oh and, if it hasn't already it will first chuck out some rubbish about importing `termcolor`. Ignore it - it only does it once. ### Part 4: A Python Sandwich *** Trust me, by the end of this tutorial you're going to be sick of functions (`def example():`). Apart from one line to initiate all the functions, that was the last stuff you're going to type that's not in a function. You know what, let's write that line. It goes right at the end of the program, and it's pretty simple: ```py reset() ``` Leave plenty of space in between it and what we wrote earlier - this is where the rest of the program is going to be written. Voila, a python sandwich! ### Part 5: reset() ***
0
posted by ThomasS1 (23) 2 months ago
โ–ฒ
4
How to scrape HTML tags from a webpage (synchronously)
# How to scrape HTML tags from a webpage (synchronously) This is a tutorial about how to scrape html for data from certain tags synchronously. This is very simple to edit. For this example, I'm going to be finding the results for a YouTube search. Now, this is a bit simpler than the async version, but you wouldn't want to use this in something like a Discord bot (my original use case). First we need to import our essential packages: ```python import requests import re ``` Requests will allow us to access webpages to get the tags, and re will allow us to search for the tags on the decompiled HTML. Now, we need to create a function, let's call it ytsearch. It will need 2 parameters - num and query. ```python def ytsearch(num, query): ``` Inside this function, the first thing we should do is to use requests.get on the url. To allow us to search for anything, we're going to format the url with the query. So, this is what that line should look like: ```python page = requests.get("https://youtube.com/results?search_query={}".format(query)) ``` This will give us a Response object, which we can get the html from using the following line: ```python html = str(page.content) ``` Next, we need to find all occurrences of the tag and scrape the video id from each. I've found that this occasionally gives duplicates of the same ID, so I've had to create 2 lists to remove duplicates. However, you probably won't need to do this for every use case. ```python dresults = (re.findall('href=\"/watch\?v=(.{11})', html)) results = [] for result in dresults: if result in results: continue else: results.append(result) ``` Let's walk through what this does. The first line uses regular expressions (through the re module) to find all occurrences of `href="/watch?v=`, and gets the next 11 characters after that. It saves all the IDs on the page to the list dresults (for results with duplicates). Then we have our final results list, which starts empty. The rest of the code copies each item from dresults to results if the value isn't already in there (removing duplicates). Now, you may remember we got `num` as an argument for our function. The problem with a user-specified number of items to retrieve is that if they enter a number that's too high, it will throw an error. Therefore, we need to include code to limit `num` to the length of `results`: ```python if num > len(results): num = len(results) ``` Finally, we just need to print out each full YouTube link! ```python for x in range(num): link = "https://youtu.be/" + results[x] print(link) ``` This will iterate through the results list num times, printing a youtu.be link each time. ## How to edit To edit this code, all you need to do is change the search parameters in `re.findall()`. You can change the number of characters you get by changing the number inside the brackets from 11 to something else. And that's it for the synchronous version! Stay tuned for the async version, coming soon! https://repl.it/@minermaniac447/Get-Youtube-Link-from-Search-Query-sync
1
posted by minermaniac447 (143) 2 months ago
โ–ฒ
4
Game Tutorial: Tetris
Hi everyone, I put together a little [Tetris](https://repl.it/@ericqweinstein/Tetris) game and thought I'd write a tutorial on how it works. (This code is based on work by [Alex Wilson](https://github.com/alexandrinaw/tetris).) Feel free to fork the REPL and add to it! The game is broken up into four main files: `board.py` (which manages the state of the Tetris "board"), `game.py` (which handles the game logic), `pieces.py` (which describes the different Tetris tetrominoes), and `main.py` (which ties everything together and creates a new game). Let's look at each one in turn. ## `board.py` This is the longest and most complex part of the game, so we'll go through it step-by-step. We'll also be using the [curses](https://docs.python.org/3/howto/curses.html) library for handling some of our terminal interactions, so if you're not familiar with it, feel free to peruse the documentation before diving in. Okay! We'll start with our `Board` class. The `__init__` method sets up the state of our game, including the number of rows and columns in our board, the current "fill pattern" of pieces on the board (`self.array`), the currently falling shape, the next shape, the score, the level, and some bookkeeping to manage the way we draw the board and how we handle scoring. It looks like this: ```py def __init__(self, columns=None, rows=None, level=None): self.num_rows = rows self.num_columns = columns self.array = [[None for _ in range(self.num_columns)] for _ in range(self.num_rows)] self.falling_shape = None self.next_shape = None self.score = 0 self.level = level or 1 self.preview_column = 12 self.preview_row = 1 self.starting_column = 4 self.starting_row = 0 self.drawer = BoardDrawer(self) self.points_per_line = 20 self.points_per_level = 200 ``` Next, our `start_game` game method initializes the score to zero and the level to one, then selects the first shape to fall. The `end_game` raises a custom `GameOverError` that we throw if we're unable to place a shape for some reason (which the game will catch and use to end itself; I'm not a huge fan of using exceptions for control flow, though, so feel free to refactor this if you fork the REPL!). The `new_shape` method handles setting the `falling_shape` to the prior `next_shape` and getting a new `next_shape`. The `remove_completed_lines` method handles removing lines that go all the way across the board (increasing the player's score appropriately). Finally, our last few methods handle shape management: `settle_falling_shape` and `_settle_shape` handle adding the falling shape to the array of shapes that have already fallen, `move_shape_left`, `move_shape_right`, and `rotate_shape` do exactly what you'd expect, `let_shape_fall` handles the falling shape as the game "ticks" forward (this is what we call each iteration through the game loop), `drop_shape` makes the shape fall immediately when the user hits the "Enter" key, and finally, `shape_cannot_be_placed` determines whether the shape can be successfully placed on the board. Because there's so much game logic here, we've included a separate `BoardDrawer` class (to manage drawing the board on the terminal, as opposed to managing the state of the board, which is the `Board` class' job). You can see this almost immediately in the `__init__` function: while `Board` doesn't know about `curses`, the library we use to draw in the terminal, the `BoardDrawer` class uses it extensively. The first several lines of our `__init__` function are just configuring `curses`; if you're interested in the details, feel free to check out [the documentation](https://docs.python.org/3/howto/curses.html)! After that (starting on line 183), we pass some state from our `Board` to our `BoardDrawer` so it knows things like where on the board to draw shapes (using rows and columns) and what size things like blocks and borders should be. After that, we have several methods we use to update our board in the terminal (they all begin with `update_`), as well as a couple of helper methods for clearing the player's score and refreshing the screen. Let's walk through each of these in turn. The `update_falling_piece` method redraws the blocks in the currently falling shape in order to re-draw it one row lower on the board. The `update_settled_pieces` does the same thing, but for the already-settled pieces on the board. Our `update_shadow` method handles updating the position of the "shadow" on our game board (where the currently falling piece will ultimately land). The `update_next_piece` method does the same thing as `update_falling_piece`, but for the "preview" piece (the one that will start falling next). `update_score_and_level` and `clear_score` do what you'd expect, and `update_border` handles drawing the screen borders, and the `update` method calls all of our `update_*` methods in order to update the overall board (it also calls `refresh_screen`, which ensures our changes are reflected on the board). Finally, we include a custom `GameOverError` exception class so we can throw `GameOverErrors` to handle ending the game. ## `game.py` Our `Game` class, by comparison, is pretty straightforward: here's where we put all the logic that runs the game itself. The important methods here are `__init__`, `pause`, `run`, `end,` `exit`, `start_ticking`, `stop_ticking`, `update`, and `process_user_input`. (Note that the ticking methods use `_tick`, which, as mentioned, is an iteration in the game loop: that is, a "step" in the game's progression.) The `__init__` method keeps references to our `Board` and `BoardDrawer`, keeps tracks of "ticks" through the game, and draws the board by calling `self.board.start_game()`. The `pause` method stops the game loop from iterating (continuing to "tick") in order to pause the game, and the `run` method handles the main game loop by taking user input and updating the board in response, only stopping on a `GameOverError` (which calls our `end` method, `exit`ing the program). The `start_ticking`, `stop_ticking`, and `_tick` methods are used to control the running and pausing of the game, and `update` (different from the `BoardDrawer`'s `update` method!) keeps track of all our ticking (that is, the state of the game itself). Finally, we use the `process_user_input` method to change the game state based on the keys the user presses: right and left to move the falling shape right or left, up to rotate the shape, down to make the shape fall faster, and "Enter" to make the shape drop into place immediately. We can also pause the game by hitting `p` or end it by hitting `q`. ## `pieces.py` The `pieces.py` file is long, but it's actually not very complex: we just have a `Block` class, a `Shape` class (a `Shape` is made up of four `Block`s), and then classes that inherit from `Shape` in order to form the seven shapes that occur in a game of Tetris (square, line, "T"-shape, "J"-shape, "L"-shape, "S"-shape, and "Z"-shape). The `Block` class is pretty simple! Here's the whole thing: ```py class Block(object): '''Represents one block in a tetris piece.''' def __init__(self, row_position, column_position, color): self.row_position = row_position self.column_position = column_position self.color = color ``` We can see that each block just has a position (column and row) as well as a color. The `Shape` class is where things get interesting, so we'll go through its methods one at a time. First, the `__init__` method sets the shape's position and color, as well as its orientation (since shapes can rotate) and its constituent blocks. Next, the `__eq__` method is a bit of Python magic that allows us to hook into `==`, meaning that we can now check if one `Shape` is equal to another using something like `shape_1 == shape_2`. We have a couple of convenience methods, `_get_random_orientation` and `_get_random_color`, which we use to set the orientation and color of new shapes (the ones that are "up next" in the preview window of the terminal). Our `_rotate` method uses `_rotate_blocks` internally to rotate the blocks that comprise our shape, and our `rotate_clockwise` and `rotate_counterclockwise` methods use `_rotate` in order to rotate our shapes clockwise and counter-clockwise (respectively) during gameplay. Next, we have four methods, `lower_shape_by_one_row`, `raise_shape_by_one_row`, `shift_shape_right_by_one_column`, and `shift_shape_left_by_one_column`, which all internally use our `_shift_by` method to move shapes around on the screen. The `move_to` method is used by our board to move the shapes into the positions described by methods like `rotate_clockwise` and `shift_shape_right_by_one_column`, and finally, our `random` method returns a randomly generated, complete tetromino (like a "Z"-shape or "J"-shape). Finally, we have seven different shapes (`SquareShape`, `TShape`, `LineShape`, `SShape`, `ZShape`, `LShape`, and `JShape`) that all inherit from `Shape`. Check out the comments to see how their orientations work (each shape has a `number_of_orientations` and a dict returned by `block_positions` that describes those orientations). For example, a square only has one orientation, since rotating it doesn't really do anything, but a "T" shape has four: โŠข, โŠค, โŠฃ, and โŠฅ. ## `main.py` Finally, our `main.py` file ties everything together neatly for us by creating a new `Game` instance and setting up a signal handler to quit the game if player types `Ctrl+C`. This file is so short that we can actually just reproduce it here: ```py import signal import sys from board import BoardDrawer from game import Game def main(): Game().run() def signal_handler(_signal, _frame): sys.exit(0) signal.signal(signal.SIGINT, signal_handler) if __name__ == '__main__': main() ``` ...and that's it! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/Tetris) to add more functionality.
0
posted by ericqweinstein (152) 3 months ago
โ–ฒ
21
How to make Rest Api in Python
## Introduction In today's lesson, we will learn how to build basic `Rest Api` in `Python` and `Flask`. We will specifically focus on two different way of creating apis, both will be using flask. List of two ways. 1. using flask 2. using flask extension called `flask restful` In this Lesson we are going to use `flask restful` to make our final api. But I'm also going to show to how to create one in `flask > `Note!` using flask is not the most official way of creating api. Flask is not efficient, code will look bad and have difficulty managing large files. > `flask restful` flask extension restful is the best way of creating api. Because it handlea big files wasily. Very easy to work with And it was created special for making rest apis. > I recommend you should use `flask restful` >`Here what our final api will look like` ![Api final Result](https://cdn.discordapp.com/attachments/485343377983012864/518852699572273182/restapi_final.gif) It will generate random content from a category and print it out in json format ## What is REST? Representational State Transfer (REST) is a software architectural style that defines a set of constraints to be used for creating web services. Web services that conform to the REST architectural style, termed RESTful web services, provide interoperability between computer systems on the Internet. RESTful web services allow the requesting systems to access and manipulate textual representations of web resources by using a uniform and predefined set of stateless operations. Other kinds of web services, such as SOAP web services, expose their own arbitrary sets of operations. In a RESTful web service, requests made to a resource's URI will elicit a response with a payload formatted in either HTML, XML, JSON, or some other format. For more info [click here](https://en.wikipedia.org/wiki/Representational_state_transfer) > **We will only work with json format** ## Requirements - Basic Python knowledge - Flask - Flask-Restful - Jsonify - json ## Installation Inside your repl, creating an empty file called `requirements.txt`. Once you have to empty txt file ready, copy and paste this **Flask==1.0.2** to your **requirements.txt** to install flask. ![Installation Successful](https://cdn.discordapp.com/attachments/487306993288347649/518847564968361984/unknown.png) If this started to happen, that mean flask have been install, and if not, something is wrong and should re-copy and paste `Flask==1.0.2` To install flask-restful, repeat the same procedure but use this line `Flask-RESTful==0.3.6` ## Creating web server We'll need to create basic web server. We need web server to run our code on [repl.it](https://repl.it/repls) here the basic code for creating basic web server ```python from flask import Flask from threading import Thread app = Flask('') @app.route('/') def home(): return "I'm alive" def run(): app.run(host='0.0.0.0',port=8080) t = Thread(target=run) t.start() ``` >I'm not going to explain this code, as I'm bad when it come to server stuff. But up there we just created a simple server. if you run this code this screen is going to pop up on top right hand right with this text `i'm alive`. Now we have successfully created a web server. ## Rest REST have 4 methods: - GET - PUT - POST - DELETE In this tutorial, we ony going to work with `GET` method. ## Creating api first we need to import another flask extension `jsonify`. ```python from flask import jsonify ``` >we need jsonify to convert data in json and send it out to our server. >Important Note:- we're going only going to work with json data, because json can be used in almost every modern language. ```python from flask import Flask, jsonify from threading import Thread app = Flask('') #make sure you code is between this code t = Thread(target=run) t.start() ``` here's a example of **GET** methods ```python from flask import Flask, jsonify from threading import Thread app = Flask('') @app.route('/') def home(): return "I'm alive" @app.route('/api/add', methods=['GET']) def add(): return jsonify({"2 + 2": 2 + 2}) def run(): app.run(host='0.0.0.0',port=7000) t = Thread(target=run) t.start() ``` ```python @app.route('/api/add', methods=['GET']) ``` **/api/add** is our api endpoint. you can name it what ever you want, im just gonna called it that. we need an endpoint to get to make requests to content, without any endpoint it will give you an error or simply return you home page if available. You also noticed that we in our **@app.route**. we have **methods**. We use methods to tell what kind of `rest option`, we are using. In this case we are `GET`. **ENDPOINT**= your web server url + your app route so my url is `https://rest-api-python.badvillain01.repl.co` + route `/api/add` our endpoint is `https://rest-api-python.badvillain01.repl.co/api/add` > **Note**:- your url name will be different than mine. so put your url name and route together. If you run my example you will get this result. `{"2 + 2":4}` > I recommend running my example first and once you have hold of it, then run you're owns. Now in this example we will take `user input` and convert this to json data. I'm going to use to previous example but add user input. So user will put any number and it will double user input. ```python @app.route('/api/add/<int:num>', methods=['GET']) def add(num): return jsonify({f"{num} + {num}" : num + num}) ``` You may have noticed that our in route we have this `<int:num>`. This is how to take input. `int` is specify what type of content is it. And `num` is the name of variable we will storing our input. Once we have our input. Then we are going to use `jsonify` to convert data into json format and print it out on server. If you run this code now and endpoint `/api/add/<any num you want>` i'll be using 23, json data should look something like that. ```py {"23 + 23" = 46} ``` Now he's another example that takes string as input ```py @app.route('/api/name/<string:name>', methods=['GET']) def get_name(name): return jsonify({"Your name": name}) ``` we bascially did same thing, just change our variable type to string. and if we run this example result show look like this ```json {"Your name": "bad"} ``` _That all i have for flask. If you wanna continued with flask, **Good Luck**, but i'll suggest you checkout `flask-restful`_ # Getting ready for flask-restful Api So, the api i'm creating have two main categories, `facts` and `quote`. And then inside the folder, there are four `json` files, that contain some sort of json content. _I hope this made any sense to you. If not, then i'm sorry_ ![v](https://cdn.discordapp.com/attachments/485343377983012864/518870277812256771/unknown.png) # flask-restful first we need to import two extension from `flask-restful`, We need `Resource`, and `Api`. ```py from flask_restful import Resource, Api ``` Here a simple example of **flask-restful** ```py from flask import Flask, jsonify from threading import Thread from flask_restful import Resource, Api app = Flask('') api = Api(app) class Test(Resource): def get(self): return "Example with Flask-Restful" #creating api endpoint api.add_resource(Test, '/api/restful') def run(): app.run(host='0.0.0.0',port=7210) t = Thread(target=run) t.start() ``` If you compare this with **flask**. You can clearly see this is more readable, official and best. #### How flask-restful works first we need to build `api` on top of `app`. ```py app = Flask('') api = Api(app) ``` We just creating `api`. Now we dont need `app`. And we are going to use `api` to add new content. ```py class Test(Resource): def get(self): return "Example with Flask-Restful" ``` > In `flask-restful`, all content need to be in class with `Resource` as parameters > **Methods** are a little different. You don't assign Methods in route endpoint, instead you add methods directly to class. `def get(self):` or `def post(self)` > Self: becuase we are using `def` inside class, so we need to add `self` as first parameter. To create `endpoint` in flask-restful, it's pretty easy. `api.add_resource(<your class name>, <your endpoint>)` For this example `api.add_resource(Test, '/api/restful')` If you run this now. You should get this `"Example with Flask-Restful"` **NOTE** ```py def get(self): return "Example with Flask-Restful" ``` As you see, we didn't use `jsonify`. **WHY**. Becuase the content we're returning in not `json`. So to return `json` data. Here's an example ```py def get(self): return jsonify({"Type": "flask-restful"}) ``` Output should be `{"Type":"flask-restful"}` ## Creating final Api Becuase i'm gonna be selecting random from json fromat, so i need to import `random` and also need to import `json` ```py import random import json ``` Now we are going to create a class called `Facts`. This class will return a random facts. ```py def get_facts(fact_type): if fact_type == "random": file_address = 'Facts/random.json' elif fact_type == "technology": file_address = 'Facts/technology.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) return fact class Facts(Resource): def get(self, fact_type): return get_facts(fact_type) api.add_resource(Facts, '/api/facts/<string:fact_type>') ``` So what i did that, instead of adding everything to my `Facts` class, i created a new def, outside of class. Now everythime, I'm calling Facts endpoint. It's sending requests back to `get_facts()` and return the data to `def get()`. And then this whole return the data to our server. > Creating new separate data will make your code more readable. you may wonder what this for ```py def get_facts(fact_type): if fact_type == "random": file_address = 'Facts/random.json' elif fact_type == "technology": file_address = 'Facts/technology.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) return fact ``` > **fact_type** is user input. So i only want user to choose from my inputs, so im using `if` statement to check user input. If user input is equal to one of my inputs, I'm creating a new variable called `file_address`. This will contain file address i want to open. ```py with open(file_address, 'r') as factfile: data = json.load(factfile) fact = random.choice(list(data['Facts'])) ``` now once `if else` are done. We need to open the file and select random items from it. Remember, we are storing file address in `file_address`. We are going to called this file `factfile` Once we open the file, we need to load all content to `json` file. `data = json.load(factfile)` and now open we have all content in json file, we need to random select one. `fact = random.choice(list(data['Facts']))` > data:- name of variable which contain our json content > data['Facts]: `Facts` is what we want from our json file. It will randomly select one thing from `Facts` and return to `def get_fact` and this will return everything to `class Facts` and this will return it to our server. Now if you run your code, the output should look like this ![Facts Results](https://cdn.discordapp.com/attachments/485343377983012864/518911100968763392/ezgifcomvideocutter.gif) Now we are going to do the same thing with `Quotes`. We'll create a class and remember to give `Resource` parameter to class. ```py def get_quotes(quote_type): if quote_type == "motivation": file_address = 'Quotes/motivation.json' elif quote_type == "funny": file_address = 'Quotes/funny.json' else: file_address = 'errormsg.json' with open(file_address, 'r') as quotefile: data = json.load(quotefile) quote = random.choice(list(data['Quotes'])) return quote class Quotes(Resource): def get(self, quote_type): return get_quotes(quote_type) api.add_resource(Quotes, '/api/quotes/<string:quote_type>') ``` So we are just doing the same thing we did previously. **Congrats**, you have successfully created your first rest api. >**I hope you learned something today** >**I know it's bad but i try my best to make a great tutorial** _If you have any question dm me on discord, ask in **repl.it official discord server** or comment below_ > If you see any error or mistake. Please notify me. > **Important Note**:- your repl web server won't stay up 24/7. I will die after 60 min. So i suggest using free service `UptimeRobot`. If you never used `UptimeRobot`, then read [this tutorial for help](https://repl.it/talk/learn/How-to-use-and-setup-UptimeRobot/9003) This api was just for tutorial. I'm not working on it anymore. [Please check out of my main api.](https://gold-miners-api.badvillain01.repl.co/docs.html) ### Source code https://repl.it/@badvillain01/rest-api-python
13
posted by badvillain01 (49) 3 months ago
โ–ฒ
10
How to use and setup UptimeRobot
# What is UptimeRobot? Uptime Robot is all about helping you to keep your websites up. It monitors your websites every 5 minutes and alerts you if your sites are down. > its 100% free > It will keep your server up until your server ran into problems. For more info [click here](https://uptimerobot.com/about) *** ## UptimeRobot setup Once you log into your **[UptimeRobot](https://uptimerobot.com/)** account. > Go to Dashboard (default screen for uptimerobot is dashboard) > To add new link. Click **Add New Monitor** ![image](https://storage.googleapis.com/replit/images/1543700457952_b004f02b1fcc5c4ebbfd9dd4ac6e72a3.pn) Once you click that. This Screen will popup ![image](https://storage.googleapis.com/replit/images/1543700591609_6372ec32f20a3b9e698c6d17065608ed.pn) Now Select Monitor type **HTTP(s)**. > Make sure type is `HTTP(s)` not `ping` or other Now details popped up like these ![image](https://storage.googleapis.com/replit/images/1543700614209_cab3cfa1204b5f5003ddf4808fa166d7.pn) For **Friendly Name** enter what would you like to call your Monitor. For **URL (or IP)** enter your web server link. > if you're using repl, it should look like this `https://your-repl-name.your-username.repl.co`, but with your repl name. For **Monitoring Interval**, I'll suggest leaving it to the default value. Now if you have all the info done. your page should look like this ![image](https://storage.googleapis.com/replit/images/1543700638871_a711ea26b16310c52679d9c346d21221.pn) Now you also have this box: ![image](https://storage.googleapis.com/replit/images/1543700789533_ce469a1259ce37bfa15f40f05c22911c.pn) > This means that if you select this, and your server is down or ran into any problems. UptimeRobot will alert you by sending an email. > I suggest you add **alert notify** Now click **Create Monitor** You have successfully created a monitor. Now your server will stay online, even if you're not running your code. *** On your dashboard, your monitor will appear. ![image](https://storage.googleapis.com/replit/images/1543700707091_8c6114aceaff448ed37a28029da8d3c1.pn) as you see, it showing that your server is down. Your server is not down. To fix this just give it 1-2 min and reload your page. ![image](https://storage.googleapis.com/replit/images/1543700724356_c5657b6dc83706fb46760a8ff8d4acb7.pn) There you go! Your Monitor is running.
0
posted by badvillain01 (49) 3 months ago
โ–ฒ
13
Game Tutorial: Tic-Tac-Toe (Ruby)
Hi everyone, I put together a little [tic-tac-toe](https://repl.it/@ericqweinstein/tictactoerb) game that uses the [minimax AI algorithm](https://en.wikipedia.org/wiki/Minimax) and thought I'd write a tutorial on how it works. (This code is based on work by [Clederson Cruz](https://github.com/Cledersonbc/tic-tac-toe-minimax); I've also created a [Python version](https://repl.it/@ericqweinstein/TicTacToe).) Feel free to fork the REPL and add to it! The game is only a couple hundred lines of Ruby in a single `main.py` file, so we'll walk through it in this tutorial step-by-step. (This will be almost exactly the same as my [tutorial for the Python version](https://repl.it/talk/learn/Game-Tutorial-Tic-Tac-Toe-Python/8926).) ## The Board The nice thing about tic-tac-toe is that the game is easy to model! While we could build out an entire `Board` class (and that's something you could add if you wanted to fork this REPL and update it), but for simplicity's sake, we'll just model our board is a two-dimensional array: ```rb @board = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ] ``` The board-related functions in our game will be `empty_cells` (which returns open spots on the board), `set_move` (which places an `X` or an `O` in the provided spot, assuming it's available), and `render` (which draws the board). Let's look at each one in turn. ```rb def empty_cells(state) [].tap do |cells| state.each_with_index do |row, x| row.each_with_index do |cell, y| cells << [x, y] if cell.zero? end end end end ``` Here, we iterate through all the cells on the board and check to see if the value is still `0` (which is how we initialized the board, as you can see above). If so, we add that cell's coordinates to the `cells` array, then return that array so we know which spots are still available for a player to choose. If you're curious about the `[].tap` magic, all that does is let us modify our array inside the block, then automatically return the completed array; you can read more about `Object#tap` [here](https://ruby-doc.org/core-2.5.3/Object.html#method-i-tap). ```rb def set_move(x, y, player) if empty_cells(@board).include? [x, y] @board[x][y] = player return true end false end ``` This is also pretty straightforward: we check to see if our chosen move is valid (that is, it's in the `empty_cells` array), and if so, we set that square on the board to `X` or `O` (depending on which player is moving), returning `true` on success and `false` if the move can't be made because the square is already occupied. ```rb def render(state, c_choice, h_choice) state.each do |row| puts "\n---------------" row.each do |cell| if cell == COMP print "| #{c_choice} |" elsif cell == HUMAN print "| #{h_choice} |" else print "| |" end end end puts "\n---------------" end ``` Our last board method just prints out the current state of the board, printing out every cell and showing a blank for free cells, `X` for cells taken by the `X` player, and `O` for cells taken by the `O` player. ## Evaluating Moves Next, we'll look at our functions for evaluating moves: `evaluate`, `wins`, and `game_over`. ```rb def evaluate(state) if wins(state, COMP) 1 elsif wins(state, HUMAN) -1 else 0 end end ``` So far, so good! We return `1` if the provided `state` (which is a two-dimensional array) would result in the computer winning, `-1` if the human player would win, and `0` if it would be a tie. There are two important things to note here: 1. We choose `-1` for a human player win because we want the minimax algorithm to run from the computer player's perspective: it's trying to **min**imize the **max**imum score the human player can achieve; 2. This function is run for _possible_ game states, meaning that the computer player uses this function to look at the results of all possible moves, all the possible moves those moves would allow, all the possible moves _those_ moves would allow, and so on, generally limited to a certain depth (at which point the computer chooses the move that leads to the worst "possible futures" for the human player; that is, the branch in the tree of possible states with the smallest possible maximum score). This might seem a little confusing now, but we'll go through all the details when we get to the `minimax` function itself. Next is the `wins` function, which looks like this: ```rb def wins(state, player) win_state = [ [state[0][0], state[0][1], state[0][2]], [state[1][0], state[1][1], state[1][2]], [state[2][0], state[2][1], state[2][2]], [state[0][0], state[1][0], state[2][0]], [state[0][1], state[1][1], state[2][1]], [state[0][2], state[1][2], state[2][2]], [state[0][0], state[1][1], state[2][2]], [state[2][0], state[1][1], state[0][2]] ] win_state.include? [player, player, player] end ``` Here, we simply return `true` if the player has achieved a set of winning moves. There are eight winning moves in tic-tac-toe: the top row, the middle row, the bottom row, the left column, the middle column, the right column, and the two diagonals (corresponding to the eight entries in our `win_state` array). ```rb def game_over(state) wins(state, HUMAN) || wins(state, COMP) end ``` This one's pretty easy! If either the human or the computer wins, the game is immediately over. ## The Minimax Algorithm On to the main event: the `minimax` algorithm! This is how our computer player decides which moves to make. As mentioned earlier, this algorithm looks at "possible futures" in the game, assigning expected scores to each, and makes the move that maximizes the score (for the computer) and minimizes the score (for the human). (Remember, since the algorithm is being used by the computer player to select a move, it will want to maximize its score and minimize the human opponent's score.) We take the state of the game, the depth to recurse to in our game tree, and a player, and initialize our "best move" to `-1`, `-1`, `-INFINITY` (that is, a non-existent square with worst/lowest possible score) for the computer player and `-1`, `-1`, `+INFINITY` (a non-existent square with the best/highest possible score) for a human player. Then, for as long as we're recursing and the game isn't over, we continually make imaginary moves and check the resulting scores, assigning as the "best" score the one that represents the highest possible (for the computer) or lowest possible (for the human). In the base case, we return the result of `evaluate` on our game state, and the overall algorithm returns the move with the highest perceived score for the computer and the lowest one for the human. ```rb def minimax(state, depth, player) if player == COMP best = [-1, -1, -INFINITY] else best = [-1, -1, INFINITY] end if depth.zero? || game_over(state) score = evaluate state return [-1, -1, score] end empty_cells(state).each do |cell| x, y = cell[0], cell[1] state[x][y] = player score = minimax state, depth - 1, -player state[x][y] = 0 score[0], score[1] = x, y if player == COMP if score[2] > best[2] best = score end else if score[2] < best[2] best = score end end end best end ``` ## Tying It All Together Finally, we'll complete our game with three functions to manage gameplay: `ai_turn`, `human_turn`, and `main`. ```rb def ai_turn(c_choice, h_choice) depth = empty_cells(@board).size return if depth.zero? || game_over(@board) clear_screen puts "Computer turn [#{c_choice}]" render @board, c_choice, h_choice if depth == 9 x = [0, 1, 2].sample y = [0, 1, 2].sample else move = minimax @board, depth, COMP x, y = move[0], move[1] end set_move x, y, COMP sleep 1 end ``` The computer player uses `minimax` to make the best possible move given the current state of the board, with the exception of when `depth == 9` (that is, every square on the board is empty), in which case since the computer player is moving first, there's no "best" move and the computer selects a random square. ```rb def human_turn(c_choice, h_choice) depth = empty_cells(@board).size return if depth.zero? || game_over(@board) # Hash of valid moves move = -1 moves = { 1 => [0, 0], 2 => [0, 1], 3 => [0, 2], 4 => [1, 0], 5 => [1, 1], 6 => [1, 2], 7 => [2, 0], 8 => [2, 1], 9 => [2, 2] } clear_screen puts "Human turn [#{h_choice}]" render @board, c_choice, h_choice while move < 1 || move > 9 begin print 'Use numpad (1..9): ' move = gets.chomp.to_i coord = moves[move] try_move = set_move coord[0], coord[1], HUMAN if !try_move puts 'Invalid move' move = -1 end rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end end ``` Here, we handle the input from the human player, mapping the numbers `1` through `9` to squares in our board (a two-dimensional array). ```rb def main h_choice = '' c_choice = '' first = '' while h_choice != 'O' && h_choice != 'X' begin puts 'Choose X or O:' h_choice = gets.chomp.upcase puts "Chosen: #{h_choice}" rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end if h_choice == 'X' c_choice = 'O' else c_choice = 'X' end while first != 'Y' && first != 'N' begin puts 'First to start? [y/n]: ' first = gets.chomp.upcase rescue SignalException puts 'Bye!' exit rescue StandardError puts 'Invalid choice' end end while empty_cells(@board).size > 0 && !game_over(@board) if first == 'N' ai_turn c_choice, h_choice first = '' end human_turn c_choice, h_choice ai_turn c_choice, h_choice end if wins @board, HUMAN clear_screen puts "Human turn [#{h_choice}]" render @board, c_choice, h_choice puts 'You win!' elsif wins @board, COMP clear_screen puts "Computer turn [#{c_choice}]" render @board, c_choice, h_choice puts 'Computer wins!' else clear_screen render @board, c_choice, h_choice puts 'It\'s a tie!' end exit 0 end ``` Finally, our `main` function lets the human player select `X` or `O` and whether they want to move first or second, then enters the game loop: as long as there's an empty square on the board, alternate human and computer player turns, displaying the appropriate messages for when the human player wins, loses, or ties with the computer. We kick everything off just by calling `main` at the very end of the file. ...and that's it! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/tictactoerb) to add more functionality.
3
posted by ericqweinstein (152) 3 months ago
โ–ฒ
3
Get started with Web Scraping ๐Ÿค–
## Learn to scrape the web by creating your own quotes website ๐Ÿค– ๐Ÿค” ![](https://dsc.cloud/Jajoosam/Ece4h5VzbOGNas2PsmkDr2hD23DCJwOm6b7zUeNt3S7jit1pK8MXoHGenIrPDpVNFUtbaMBivWbLFJuHq5WgnNRM9xZS4djo8TnD.gif) [Demo](https://quote-scraper.jajoosam.repl.co) โฏ๏ธ [Code](https://repl.it/@jajoosam/quote-scraper) ๐Ÿ‘จโ€๐Ÿ’ป There's so much information on the web, but so few APIs ๐Ÿ˜• Web Scraping lets you get content from any webpage, extracting information from `HTML` selectors. This is a super simple guide to help you scrape the web with `Node.js` , in less than 20 minutes ๐Ÿ•’ We'll learn to use developer tools to see `HTML` selectors, extract the content in `Node.js` with `x-ray` - and use `pug` to render quotes we get! ## ๐Ÿ› ๏ธ Setup Um, there's none really. Just load up this repl - [repl.it/@jajoosam/quote-scraper-starter](https://repl.it/@jajoosam/quote-scraper-starter) โœจ ![](https://dsc.cloud/Jajoosam/oeCd23rkkJ1pvV0hrCioQJlzUw3dCHBTodyhvq0g8AjxC9GAUmEbVUBKRs194SHrOyccgcDxcwPgj3cSDp2J3ddfk6aNoAETKNBb.png) We'll talk about all the tools and dependencies we use as we continue! ## ๐Ÿ“Š Understanding Structure [Quotesondesign.com](http://quotesondesign.com) has some nice quotes - and is an easy introduction to scraping stuff on the web. Another great part - it loads up a random quote each time! Open the website up, right click on the quote, and then hit `Inspect` ๐Ÿ‘‡ !()[(https://dsc.cloud/Jajoosam/gnFthrFnWt45BA1AtRUTTzOEr1PdUwIM4RODxVWydpLZAeVDXkgkw6JCtbTF0EYXUpA6ypZ0eZPNkqt18WN7A2TbiFojxRYd9GnY.gif) You can now see a view of the entire document ๐Ÿ“œ Go ahead and click all the small triangles to expand this view. You'll be able to see that the quote itself is inside a paragraph, in a `div` with id `quote-content`, while the author's name has an id of `quote-title`. ![](https://dsc.cloud/Jajoosam/NRMd90XWrYqR38kgs1EgPmlTlJ77jwxduW5bkBce6BRHkd8qjumaKzRQnNwRL5BVLC3IDnGydTAAx3yH5r2y2faJIyP2p9wB1y7L.png) ## โฌ‡๏ธ Getting the quotes To scrape information from a web page, you generally request its `HTML`, and then extract the content you want with selctors, like `id`s, `classes`, and `HTML tags`. When we inspected [Quotesondesign.com](http://quotesondesign.com), we saw that the quote itself was in a `div`, with `id=quote-content`, nested inside a `<p>` (Paragraph element) - while the author's name was inside an element with `id=quote-title`. Having this information makes scraping super easy ๐Ÿ™Œ - we'll use a library called [x-ray](https://github.com/matthewmueller/x-ray) - which makes our job very straightforward! It's already installed in the repl you're using. Try adding this code to your `index.js` file ๐Ÿ‘‡ ``` x('https://quotesondesign.com', { quote: "#quote-content p", author: "#quote-title" } )(function(err, result){ console.log(result) }); ``` Run your code, and the console on the bottom right will look something like this ๐Ÿ‘€ ![](https://dsc.cloud/Jajoosam/lIGAkvplrgDFKSO4zs9JQMJDi2k62rMb2avtZ0vKWR0o1CyBDOxKnln0HGNG9pLenZmjjYiI6gWYuBvGVraFG8BjSHlwxIJFHJ8H.png) `x-ray` gives you a nice `json` object, which you can now render! You've now successfully scraped the web ๐ŸŽ‰ ## ๐Ÿ“œ Render those quotes If you see the file tree in the sidebar, you'll see `quotes.pug` - a template which can render quotes passed to it. We're using the [pug](https://pugjs.org/api/getting-started.html) templating engine to do this - which we've initialized on line `6` One thing to note is that `pug` is whitespace sensitive: `HTML` tags are nested inside each other with tabs โŒจ๏ธ ![](https://dsc.cloud/Jajoosam/9jGShYBQ9zFliv41bjmwWfHHZJCeU0jxph9ISkihBWby28iyasRysT7IrEHgYAlgJyW1qQvgH0kR6liVNVDua2zjYHGtnxL9I80X.png) All we have to do now is pass the quote we get from `x-ray` to `pug`! This is very easy to do on our `express` server, just change your `app.get` block to this ๐Ÿ‘จโ€๐Ÿ’ป ``` app.get('/', (req, res) => { x('https://quotesondesign.com', { quote: "#quote-content p", author: "#quote-title" } )(function(err, result){ res.render('quote', result) console.log(result) }); }); ``` Run your repl, and this is what you'll see ๐Ÿ˜ฎ ![](https://dsc.cloud/Jajoosam/6O4MLq9QgQ4gbz69cYamgIEpqUwnoDEsU88pLw0MIZKZLSo6Mceup0RYh6VApkzXJeZpqIRb6ktnx3YLPMjqWOHSdr97FH4KZ4QS.png) Pretty neat, huh? We've not written any css of my own, the page looks readable just because of the simple [sakura.css](https://oxal.org/projects/sakura/) library. Remove line `4` in `quote.pug` and get ready for ugly ๐Ÿคฎ ## โšก Putting your skill to use There is a lot you can do with web scraping - and this guide has given you all the basic knowledge you need. I'm excited to see what you do with this ๐Ÿ˜„ **Here are a few cool things to try ๐Ÿ‘‡** - Scrape different data points - weather, latest news, bitcoin price ๐Ÿ˜› - and make a dashboard for yourself - Scrape [IMDb](https://imdb.com) to get a list of all movies currently in theatres ๐ŸŽฆ - Scrape [Repl Talk](https://repl.it/talk) and make an API for it ๐Ÿ‘จโ€๐Ÿ’ป Whatever you build, be sure to share it in the comments ๐Ÿ’ฌ [Here](https://blog.jajoosam.repl.run)'s what the final code looks like, feel free to refer to it ๐Ÿ‘‡ [https://repl.it/@jajoosam/quote-scraper](https://repl.it/@jajoosam/quote-scraper)
0
posted by jajoosam (380) 3 months ago
โ–ฒ
4
Game Tutorial: Tic-Tac-Toe (Python)
Hi everyone, I put together a little [tic-tac-toe](https://repl.it/@ericqweinstein/TicTacToe) game that uses the [minimax AI algorithm](https://en.wikipedia.org/wiki/Minimax) and thought I'd write a tutorial on how it works. (This code is based on work by [Clederson Cruz](https://github.com/Cledersonbc/tic-tac-toe-minimax).) Feel free to fork the REPL and add to it! The game is only a couple hundred lines of Python in a single `main.py` file, so we'll walk through it in this tutorial step-by-step. ## The Board The nice thing about tic-tac-toe is that the game is easy to model! While we could build out an entire `Board` class (and that's something you could add if you wanted to fork this REPL and update it), but for simplicity's sake, we'll just model our board is a two-dimensional list: ```py board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] ``` The board-related functions in our game will be `empty_cells` (which returns open spots on the board), `valid_move` (which just checks if a spot is empty/available), `set_move` (which places an `X` or an `O` in the provided spot, assuming it's available), and `render` (which draws the board). Let's look at each one in turn. ```py def empty_cells(state): cells = [] for x, row in enumerate(state): for y, cell in enumerate(row): if cell == 0: cells.append([x, y]) return cells ``` Here, we iterate through all the cells on the board and check to see if the value is still `0` (which is how we initialized the board, as you can see above). If so, we add that cell's coordinates to the `cells` list, then return that list so we know which spots are still available for a player to choose. ```py def valid_move(x, y): return [x, y] in empty_cells(board) ``` Easy! We just return `True` if the suggested (row, column) pair is in our list of empty cells, and `False` otherwise. ```py def set_move(x, y, player): if valid_move(x, y): board[x][y] = player return True return False ``` This is also pretty straightforward: we check to see if our chosen move is valid, and if so, we set that square on the board to `X` or `O` (depending on which player is moving), returning `True` on success and `False` if the move can't be made because the square is already occupied. ```py def render(state, c_choice, h_choice): for row in state: print('\n----------------') for cell in row: if cell == COMP: print('|', c_choice, '|', end='') elif cell == HUMAN: print('|', h_choice, '|', end='') else: print('|', ' ', '|', end='') print('\n----------------') ``` Our last board method just prints out the current state of the board, printing out every cell and showing a blank for free cells, `X` for cells taken by the `X` player, and `O` for cells taken by the `O` player. ## Evaluating Moves Next, we'll look at our functions for evaluating moves: `evaluate`, `wins`, and `game_over`. ```py def evaluate(state): if wins(state, COMP): score = 1 elif wins(state, HUMAN): score = -1 else: score = 0 return score ``` So far, so good! We return `1` if the provided `state` (which is a two-dimensional list) would result in the computer winning, `-1` if the human player would win, and `0` if it would be a tie. There are two important things to note here: 1. We choose `-1` for a human player win because we want the minimax algorithm to run from the computer player's perspective: it's trying to **min**imize the **max**imum score the human player can achieve; 2. This function is run for _possible_ game states, meaning that the computer player uses this function to look at the results of all possible moves, all the possible moves those moves would allow, all the possible moves _those_ moves would allow, and so on, generally limited to a certain depth (at which point the computer chooses the move that leads to the worst "possible futures" for the human player; that is, the branch in the tree of possible states with the smallest possible maximum score). This might seem a little confusing now, but we'll go through all the details when we get to the `minimax` function itself. Next is the `wins` function, which looks like this: ```py def wins(state, player): win_state = [ [state[0][0], state[0][1], state[0][2]], [state[1][0], state[1][1], state[1][2]], [state[2][0], state[2][1], state[2][2]], [state[0][0], state[1][0], state[2][0]], [state[0][1], state[1][1], state[2][1]], [state[0][2], state[1][2], state[2][2]], [state[0][0], state[1][1], state[2][2]], [state[2][0], state[1][1], state[0][2]], ] return [player, player, player] in win_state ``` Here, we simply return `True` if the player has achieved a set of winning moves. There are eight winning moves in tic-tac-toe: the top row, the middle row, the bottom row, the left column, the middle column, the right column, and the two diagonals (corresponding to the eight entries in our `win_state` list). ```py def game_over(state): return wins(state, HUMAN) or wins(state, COMP) ``` This one's pretty easy! If either the human or the computer wins, the game is immediately over. ## The Minimax Algorithm On to the main event: the `minimax` algorithm! This is how our computer player decides which moves to make. As mentioned earlier, this algorithm looks at "possible futures" in the game, assigning expected scores to each, and makes the move that maximizes the score (for the computer) and minimizes the score (for the human). (Remember, since the algorithm is being used by the computer player to select a move, it will want to maximize its score and minimize the human opponent's score.) We take the state of the game, the depth to recurse to in our game tree, and a player, and initialize our "best move" to `-1`, `-1`, `-infinity` (that is, a non-existent square with worst/lowest possible score) for the computer player and `-1`, `-1`, `+infinity` (a non-existent square with the best/highest possible score) for a human player. Then, for as long as we're recursing and the game isn't over, we continually make imaginary moves and check the resulting scores, assigning as the "best" score the one that represents the highest possible (for the computer) or lowest possible (for the human). In the base case, we return the result of `evaluate` on our game state, and the overall algorithm returns the move with the highest perceived score for the computer and the lowest one for the human. ```py def minimax(state, depth, player): if player == COMP: best = [-1, -1, -infinity] else: best = [-1, -1, +infinity] if depth == 0 or game_over(state): score = evaluate(state) return [-1, -1, score] for cell in empty_cells(state): x, y = cell[0], cell[1] state[x][y] = player score = minimax(state, depth - 1, -player) state[x][y] = 0 score[0], score[1] = x, y if player == COMP: if score[2] > best[2]: best = score # max value else: if score[2] < best[2]: best = score # min value return best ``` ## Tying It All Together Finally, we'll complete our game with three functions to manage gameplay: `ai_turn`, `human_turn`, and `main`. ```py def ai_turn(c_choice, h_choice): depth = len(empty_cells(board)) if depth == 0 or game_over(board): return clean() print('Computer turn [{}]'.format(c_choice)) render(board, c_choice, h_choice) if depth == 9: x = choice([0, 1, 2]) y = choice([0, 1, 2]) else: move = minimax(board, depth, COMP) x, y = move[0], move[1] set_move(x, y, COMP) sleep(1) ``` The computer player uses `minimax` to make the best possible move given the current state of the board, with the exception of when `depth == 9` (that is, every square on the board is empty), in which case since the computer player is moving first, there's no "best" move and the computer selects a random square. ```py def human_turn(c_choice, h_choice): depth = len(empty_cells(board)) if depth == 0 or game_over(board): return # Dictionary of valid moves move = -1 moves = { 1: [0, 0], 2: [0, 1], 3: [0, 2], 4: [1, 0], 5: [1, 1], 6: [1, 2], 7: [2, 0], 8: [2, 1], 9: [2, 2], } clean() print('Human turn [{}]'.format(h_choice)) render(board, c_choice, h_choice) while (move < 1 or move > 9): try: move = int(input('Use numpad (1..9): ')) coord = moves[move] try_move = set_move(coord[0], coord[1], HUMAN) if not try_move: print('Invalid move.') move = -1 except KeyboardInterrupt: print('Goodbye!') exit() except: print('Invalid move.') ``` Here, we handle the input from the human player, mapping the numbers `1` through `9` to squares in our board (a two-dimensional list). ```py def main(): clean() h_choice = '' c_choice = '' first = '' # Human chooses X or O to play while h_choice != 'O' and h_choice != 'X': try: print('') h_choice = input('Choose X or O\nChosen: ').upper() except KeyboardInterrupt: print('Goodbye!') exit() except: print('Invalid choice.') # Setting computer's choice if h_choice == 'X': c_choice = 'O' else: c_choice = 'X' # Human may starts first clean() while first != 'Y' and first != 'N': try: first = input('First to start? [y/n]: ').upper() except KeyboardInterrupt: print('Goodbye!') exit() except: print('Invalid choice.') # Main game loop while len(empty_cells(board)) > 0 and not game_over(board): if first == 'N': ai_turn(c_choice, h_choice) first = '' human_turn(c_choice, h_choice) ai_turn(c_choice, h_choice) # Game over message if wins(board, HUMAN): clean() print('Human turn [{}]'.format(h_choice)) render(board, c_choice, h_choice) print('You win!') elif wins(board, COMP): clean() print('Computer turn [{}]'.format(c_choice)) render(board, c_choice, h_choice) print('Computer wins!') else: clean() render(board, c_choice, h_choice) print('It\'s a tie!') exit() ``` Finally, our `main` function lets the human player select `X` or `O` and whether they want to move first or second, then enters the game loop: as long as there's an empty square on the board, alternate human and computer player turns, displaying the appropriate messages for when the human player wins, loses, or ties with the computer. We kick everything off just by calling `main` at the very end of the file. ...and that's it! I hope you enjoyed this tutorial, and feel free to [fork this REPL](https://repl.it/@ericqweinstein/TicTacToe) to add more functionality.
1
posted by ericqweinstein (152) 3 months ago
Load more