init
This commit is contained in:
commit
53728db742
9 changed files with 755 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
__pycache__/
|
||||||
|
save.pickle
|
||||||
|
venv/
|
117
README.md
Normal file
117
README.md
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
# Scoundrel
|
||||||
|
|
||||||
|
This is a terminal port of the rogue-lite card game Scoundrel. I got really into Balatro for a week
|
||||||
|
and immediately started wanting to code a game. I stumbled upon a youtube video for a rogue-lite
|
||||||
|
card game you can play with just a deck of cards and decided to port it to the terminal as a preliminary exercise before I begin to write a new game (which may never happen, I have 5 other pressing projects that take priority over game development).
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
You must have python installed on your machine...
|
||||||
|
|
||||||
|
1. clone the repository
|
||||||
|
2. cd into the repository and create a virtual environment `python3 -m venv venv`
|
||||||
|
3. activate the virtual environment `source venv/bin/activate`
|
||||||
|
4. install requirements `pip install -r requirements.txt`
|
||||||
|
5. run the game `python3 main.py`
|
||||||
|
|
||||||
|
# Rules
|
||||||
|
### 1. INTRODUCTION:
|
||||||
|
|
||||||
|
Welcome to Scoundrel! The solo rogue-lite dungeon crawler card game!
|
||||||
|
This is the unneccessary terminal port of the game by Dominic DiTaranto.
|
||||||
|
If you enjoy this port, check out my website for other cool stuff
|
||||||
|
https://www.domdit.com
|
||||||
|
|
||||||
|
Table Of Contents:
|
||||||
|
1. INTRODUCTION
|
||||||
|
3. CONTROLS
|
||||||
|
2. RULES
|
||||||
|
|
||||||
|
### 2. CONTROLS
|
||||||
|
|
||||||
|
Arrow Keys - Select a Card
|
||||||
|
|
||||||
|
Enter Button - Engage with a Card
|
||||||
|
|
||||||
|
s - Skip a Room
|
||||||
|
|
||||||
|
b - Fight Barehanded
|
||||||
|
|
||||||
|
p - Pause/Unpause
|
||||||
|
|
||||||
|
h - See High Scores (from start menu)
|
||||||
|
|
||||||
|
u - Change User Name (from start menu)
|
||||||
|
|
||||||
|
Esc - Exit Game
|
||||||
|
|
||||||
|
? - Takes you to this help page.
|
||||||
|
|
||||||
|
### 3.a RULES - Setup and Rules
|
||||||
|
|
||||||
|
You are a dungeon explorer making your way through a series of rooms.
|
||||||
|
A room is made up of 4 cards.
|
||||||
|
|
||||||
|
In each room you may encounter:
|
||||||
|
- Equippable Weapons (Diamond Suit Cards)
|
||||||
|
- Health Potions (Heart Suit Cards)
|
||||||
|
- Enemies (Spade and Club Suit Cards)
|
||||||
|
- Merchants (Joker Cards) [Not Yet Implemented]
|
||||||
|
|
||||||
|
Your goal is to traverse the rooms until the deck runs out of cards.
|
||||||
|
|
||||||
|
### 3.b RULES - Selecting a Card
|
||||||
|
|
||||||
|
When you enter a room, select a card by navigating to it with the arrow keys
|
||||||
|
and pressing Enter.
|
||||||
|
|
||||||
|
If the card is a weapon, you equip that weapon. Each weapon does as much damage
|
||||||
|
as its value. Picking up a weapon will automatically discard your currently equipped weapon.
|
||||||
|
|
||||||
|
If the card is a health potion, you will gain life equal to the value of the card.
|
||||||
|
Your life may not exceed 20.
|
||||||
|
|
||||||
|
You can skip a room by pressing s, but you cannot skip two rooms in a row. the room
|
||||||
|
that you skip gets moved to the bottom of the deck to be traversed through later.
|
||||||
|
|
||||||
|
### 3.c RULES - Combat
|
||||||
|
|
||||||
|
If you select an enemy, you enter the combat phase.
|
||||||
|
|
||||||
|
If you do not have a weapon equipped you will take damage to your life points
|
||||||
|
equal to the value of the enemy card. This is called barehanded combat.
|
||||||
|
|
||||||
|
If you have an equipped weapon and the value of the weapon is greater than the enemy value
|
||||||
|
You do not take any damage to your health. Your weapon becomes damaged and you can only
|
||||||
|
attack enemies with a value lower than the previous enemy's value. This is the weapons damage rating.
|
||||||
|
|
||||||
|
If you attack an enemy with a weapon that has a value lower than the enemy's value, you
|
||||||
|
take damage equal to the enemy value - the weapon value. Example: if your weapon is a 5
|
||||||
|
and the enemy is an 8, you will take 3 damage.
|
||||||
|
|
||||||
|
### 3.d RULES - Combat Continued
|
||||||
|
|
||||||
|
If you attack an enemy with a value higher than the weapon's damage rating,
|
||||||
|
you take all of that damage as if it were barehanded. You will lose life equal
|
||||||
|
to the value of the enemy.
|
||||||
|
|
||||||
|
You should only attack enemies with a weapon that has a damage rating above or equal
|
||||||
|
to the enemy's value. There will be situations where you have no choice but to take
|
||||||
|
the barehanded damage combat. This is part of the strategy element.
|
||||||
|
|
||||||
|
Your Weapon's damage rating is shown in the brackets next to the weapon.
|
||||||
|
|
||||||
|
When a room only has 1 card left, you move on to the next room. That card follows you
|
||||||
|
into the next room
|
||||||
|
|
||||||
|
### 3.e RULES - Scoring
|
||||||
|
|
||||||
|
If you die, the remaining monster's values are subtracted from your score.
|
||||||
|
The negative value is your score.
|
||||||
|
|
||||||
|
If you beat the whole dungeon, your score is your positive life points.
|
||||||
|
|
||||||
|
|
||||||
|
# Future Updates
|
||||||
|
- Endless mode: reshuffle the deck and keep playing
|
||||||
|
- Jokers as Merchants: Add jokers back to the deck and have them either buff you or hurt you based on RNG.
|
41
assets/ascii_art.py
Normal file
41
assets/ascii_art.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
START_SCREEN = '''
|
||||||
|
▄████████ ▄████████ ▄██████▄ ███ █▄ ███▄▄▄▄ ████████▄ ▄████████ ▄████████ ▄█
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███▀▀▀██▄ ███ ▀███ ███ ███ ███ ███ ███
|
||||||
|
███ █▀ ███ █▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███▄▄▄▄██▀ ▄███▄▄▄ ███
|
||||||
|
▀███████████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀▀███▀▀▀▀▀ ▀▀███▀▀▀ ███
|
||||||
|
███ ███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ ▀███████████ ███ █▄ ███
|
||||||
|
▄█ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███ ███ ███ ███ ███ ███▌ ▄
|
||||||
|
▄████████▀ ████████▀ ▀██████▀ ████████▀ ▀█ █▀ ████████▀ ███ ███ ██████████ █████▄▄██
|
||||||
|
███ ███ ▀
|
||||||
|
Port by Dominic Ditaranto (https://www.domdit.com) v1.0.0
|
||||||
|
Press ? for Rules and Controls | Press Enter to Start
|
||||||
|
Press u to Set Your Username! (Currently: {username})
|
||||||
|
Press h to See High Score List!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
GAME_OVER = '''
|
||||||
|
SCORE: {score}
|
||||||
|
▄██████▄ ▄████████ ▄▄▄▄███▄▄▄▄ ▄████████
|
||||||
|
███ ███ ███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███
|
||||||
|
███ █▀ ███ ███ ███ ███ ███ ███ █▀
|
||||||
|
▄███ ███ ███ ███ ███ ███ ▄███▄▄▄
|
||||||
|
▀▀███ ████▄ ▀███████████ ███ ███ ███ ▀▀███▀▀▀
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ █▄
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||||
|
████████▀ ███ █▀ ▀█ ███ █▀ ██████████
|
||||||
|
|
||||||
|
▄██████▄ ▄█ █▄ ▄████████ ▄████████
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███
|
||||||
|
███ ███ ███ ███ ███ █▀ ███ ███
|
||||||
|
███ ███ ███ ███ ▄███▄▄▄ ▄███▄▄▄▄██▀
|
||||||
|
███ ███ ███ ███ ▀▀███▀▀▀ ▀▀███▀▀▀▀▀
|
||||||
|
███ ███ ███ ███ ███ █▄ ▀███████████
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███
|
||||||
|
▀██████▀ ▀██████▀ ██████████ ███ ███
|
||||||
|
███ ███
|
||||||
|
Press Enter To Return To Start Page | Press Esc to Quit
|
||||||
|
'''
|
116
assets/help.py
Normal file
116
assets/help.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
PAGE_ONE = '''
|
||||||
|
1. INTRODUCTION:
|
||||||
|
|
||||||
|
Welcome to Scoundrel! The solo rogue-lite dungeon crawler card game!
|
||||||
|
This is the unneccessary terminal port of the game by Dominic DiTaranto.
|
||||||
|
If you enjoy this port, check out my website for other cool stuff
|
||||||
|
https://www.domdit.com
|
||||||
|
|
||||||
|
Table Of Contents:
|
||||||
|
1. INTRODUCTION
|
||||||
|
3. CONTROLS
|
||||||
|
2. RULES
|
||||||
|
|
||||||
|
Use the up and down arrow keys to scroll through the help pages..
|
||||||
|
|
||||||
|
1
|
||||||
|
'''
|
||||||
|
|
||||||
|
PAGE_TWO = '''
|
||||||
|
2. CONTROLS
|
||||||
|
|
||||||
|
Arrow Keys Select a Card
|
||||||
|
Enter Button Engage with a Card
|
||||||
|
s Skip a Room
|
||||||
|
b Fight Barehanded
|
||||||
|
p Pause/Unpause
|
||||||
|
h See High Scores (from start menu)
|
||||||
|
u Change User Name (from start menu)
|
||||||
|
Esc Exit Game
|
||||||
|
? Takes you to this help page.
|
||||||
|
2
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
PAGE_THREE = '''
|
||||||
|
3.a RULES - Setup and Rules
|
||||||
|
|
||||||
|
You are a dungeon explorer making your way through a series of rooms.
|
||||||
|
A room is made up of 4 cards.
|
||||||
|
|
||||||
|
In each room you may encounter:
|
||||||
|
- Equippable Weapons (Diamond Suit Cards)
|
||||||
|
- Health Potions (Heart Suit Cards)
|
||||||
|
- Enemies (Spade and Club Suit Cards)
|
||||||
|
- Merchants (Joker Cards) [Not Yet Implemented]
|
||||||
|
|
||||||
|
Your goal is to traverse the rooms until the deck runs out of cards.
|
||||||
|
3
|
||||||
|
'''
|
||||||
|
|
||||||
|
PAGE_FOUR = '''
|
||||||
|
3.b RULES - Selecting a Card
|
||||||
|
|
||||||
|
When you enter a room, select a card by navigating to it with the arrow keys
|
||||||
|
and pressing Enter.
|
||||||
|
|
||||||
|
If the card is a weapon, you equip that weapon. Each weapon does as much damage
|
||||||
|
as its value. Picking up a weapon will automatically discard your currently equipped weapon.
|
||||||
|
|
||||||
|
If the card is a health potion, you will gain life equal to the value of the card.
|
||||||
|
Your life may not exceed 20.
|
||||||
|
|
||||||
|
You can skip a room by pressing s, but you cannot skip two rooms in a row. the room
|
||||||
|
that you skip gets moved to the bottom of the deck to be traversed through later.
|
||||||
|
4
|
||||||
|
'''
|
||||||
|
|
||||||
|
PAGE_FIVE = '''
|
||||||
|
3.c RULES - Combat
|
||||||
|
|
||||||
|
If you select an enemy, you enter the combat phase.
|
||||||
|
|
||||||
|
If you do not have a weapon equipped you will take damage to your life points
|
||||||
|
equal to the value of the enemy card. This is called barehanded combat.
|
||||||
|
|
||||||
|
If you have an equipped weapon and the value of the weapon is greater than the enemy value
|
||||||
|
You do not take any damage to your health. Your weapon becomes damaged and you can only
|
||||||
|
attack enemies with a value lower than the previous enemy's value. This is the weapons damage rating.
|
||||||
|
|
||||||
|
If you attack an enemy with a weapon that has a value lower than the enemy's value, you
|
||||||
|
take damage equal to the enemy value - the weapon value. Example: if your weapon is a 5
|
||||||
|
and the enemy is an 8, you will take 3 damage.
|
||||||
|
5
|
||||||
|
'''
|
||||||
|
|
||||||
|
PAGE_SIX = '''
|
||||||
|
3.d RULES - Combat Continued
|
||||||
|
|
||||||
|
If you attack an enemy with a value higher than the weapon's damage rating,
|
||||||
|
you take all of that damage as if it were barehanded. You will lose life equal
|
||||||
|
to the value of the enemy.
|
||||||
|
|
||||||
|
You should only attack enemies with a weapon that has a damage rating above or equal
|
||||||
|
to the enemy's value. There will be situations where you have no choice but to take
|
||||||
|
the barehanded damage combat. This is part of the strategy element.
|
||||||
|
|
||||||
|
Your Weapon's damage rating is shown in the brackets next to the weapon.
|
||||||
|
|
||||||
|
When a room only has 1 card left, you move on to the next room. That card follows you
|
||||||
|
into the next room
|
||||||
|
6
|
||||||
|
'''
|
||||||
|
|
||||||
|
PAGE_SEVEN = '''
|
||||||
|
3.e RULES - Scoring
|
||||||
|
|
||||||
|
If you die, the remaining monster's values are subtracted from your score.
|
||||||
|
The negative value is your score.
|
||||||
|
|
||||||
|
If you beat the whole dungeon, your score is your positive life points.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7
|
||||||
|
'''
|
38
card.py
Normal file
38
card.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from colorama import Fore, Back, Style
|
||||||
|
|
||||||
|
|
||||||
|
class Card:
|
||||||
|
def __init__(self, value, suit):
|
||||||
|
self.value = value
|
||||||
|
self.suit = suit
|
||||||
|
self.selected = False
|
||||||
|
|
||||||
|
self.suit_map = {
|
||||||
|
0: '♠',
|
||||||
|
1: '❤',
|
||||||
|
2: '♣',
|
||||||
|
3: '♦'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.value_map = {
|
||||||
|
1: 'A',
|
||||||
|
11: 'J',
|
||||||
|
12: 'Q',
|
||||||
|
13: 'K'
|
||||||
|
}
|
||||||
|
|
||||||
|
def display_card(self):
|
||||||
|
return f"{Back.WHITE}{Fore.RED if self.suit in [1,3] else Fore.BLUE}{self.value_map.get(self.value, self.value)}{self.suit_map[self.suit]} {Style.RESET_ALL}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = Back.WHITE if not self.selected else Back.GREEN
|
||||||
|
return f"{s}{Fore.RED if self.suit in [1,3] else Fore.BLUE}{self.value_map.get(self.value, self.value)}{self.suit_map[self.suit]} {Style.RESET_ALL}"
|
||||||
|
|
||||||
|
def help_text(self, weapon=None):
|
||||||
|
if self.suit in [0, 2]:
|
||||||
|
return f'Attack {self.display_card()} {"barehanded?" if not weapon else "with {}? [b]".format(weapon)}'
|
||||||
|
elif self.suit == 1:
|
||||||
|
return f"Use {self.display_card()} as a health potion?"
|
||||||
|
elif self.suit == 3:
|
||||||
|
return f"Equip {self.display_card()} as a weapon?"
|
||||||
|
|
29
deck.py
Normal file
29
deck.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import random
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from card import Card
|
||||||
|
|
||||||
|
|
||||||
|
class Deck:
|
||||||
|
def __init__(self):
|
||||||
|
self.deck = deque()
|
||||||
|
|
||||||
|
def generate_deck(self):
|
||||||
|
for suit in range(4):
|
||||||
|
if suit in [0, 2]:
|
||||||
|
for i in range(1, 14):
|
||||||
|
c = Card(i, suit)
|
||||||
|
self.deck.append(c)
|
||||||
|
elif suit in [1, 3]:
|
||||||
|
for i in range(2, 11):
|
||||||
|
c = Card(i, suit)
|
||||||
|
self.deck.append(c)
|
||||||
|
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
random.shuffle(self.deck)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
if len(self.deck) > 0:
|
||||||
|
return self.deck.popleft()
|
||||||
|
|
240
game.py
Normal file
240
game.py
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pynput.keyboard import Key, Listener, KeyCode
|
||||||
|
|
||||||
|
from assets.help import PAGE_ONE, PAGE_TWO, PAGE_THREE, PAGE_FOUR, PAGE_FIVE, PAGE_SIX, PAGE_SEVEN
|
||||||
|
from deck import Deck
|
||||||
|
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, username='Wayne Skyler'):
|
||||||
|
self.username = username
|
||||||
|
|
||||||
|
self.deck = Deck()
|
||||||
|
self.current_room_cards = []
|
||||||
|
self.selected_card = 0
|
||||||
|
self.graveyard = []
|
||||||
|
|
||||||
|
self.paused = False
|
||||||
|
|
||||||
|
self.room = 1
|
||||||
|
self.block_skip = False
|
||||||
|
|
||||||
|
self.weapon = None
|
||||||
|
self.defeated = []
|
||||||
|
|
||||||
|
self.max_health = 20
|
||||||
|
self.health = 20
|
||||||
|
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
self.help_screens = [PAGE_ONE, PAGE_TWO, PAGE_THREE, PAGE_FOUR, PAGE_FIVE, PAGE_SIX, PAGE_SEVEN]
|
||||||
|
self.current_help_screen = 0
|
||||||
|
self.on_help_screen = False
|
||||||
|
self.from_screen = None
|
||||||
|
|
||||||
|
|
||||||
|
self.score = len(self.deck.deck) * -1
|
||||||
|
self.save_file = 'save.pickle'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_card(self):
|
||||||
|
return self.current_room_cards[self.selected_card]
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.deck = Deck()
|
||||||
|
self.deck.generate_deck()
|
||||||
|
self.deck.shuffle()
|
||||||
|
|
||||||
|
while len(self.current_room_cards) < 4 and len(self.deck.deck) > 0:
|
||||||
|
self.current_room_cards.append(self.deck.draw())
|
||||||
|
|
||||||
|
self.current_room_cards[0].selected = True
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
os.system('clear')
|
||||||
|
print(f"Current Room: {self.room} | Health: {self.health}/{self.max_health} | ? for Rules & Controls")
|
||||||
|
print('\t'.join([str(x) for x in self.current_room_cards]))
|
||||||
|
print('\n')
|
||||||
|
print('\n')
|
||||||
|
print(f"Weapon: {self.weapon if self.weapon else 'None'} {'[{}]'.format(' '.join([str(x) for x in self.defeated]) if self.graveyard else None)}")
|
||||||
|
print(f"{self.current_card.help_text(self.weapon)} [Enter]")
|
||||||
|
|
||||||
|
def flash_message(self, msg):
|
||||||
|
for i in reversed(range(3)):
|
||||||
|
os.system('clear')
|
||||||
|
print(msg)
|
||||||
|
print(f'\n\tWait {str(i+1)} Seconds...')
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def toggle_pause(self):
|
||||||
|
self.paused = True if not self.paused else False
|
||||||
|
|
||||||
|
def input(self):
|
||||||
|
|
||||||
|
def on_press(key):
|
||||||
|
self.history.append(key)
|
||||||
|
|
||||||
|
if self.paused and key != KeyCode.from_char('p'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if key == Key.right:
|
||||||
|
self.change_selected_card()
|
||||||
|
elif key == Key.left:
|
||||||
|
self.change_selected_card(False)
|
||||||
|
elif key == Key.enter:
|
||||||
|
if self.current_card.suit in [0, 2]: # Interact with selected card
|
||||||
|
self.attack()
|
||||||
|
elif self.current_card.suit == 1:
|
||||||
|
self.health_potion()
|
||||||
|
elif self.current_card.suit == 3:
|
||||||
|
self.equip_weapon()
|
||||||
|
elif key == KeyCode.from_char('s'): # Skip Room
|
||||||
|
self.skip_room()
|
||||||
|
elif key == KeyCode.from_char('b'): # Attack Barehanded
|
||||||
|
weapon_copy = self.weapon
|
||||||
|
self.weapon = None
|
||||||
|
self.attack()
|
||||||
|
self.weapon = weapon_copy
|
||||||
|
elif key == KeyCode.from_char('p'):
|
||||||
|
self.toggle_pause()
|
||||||
|
elif key == Key.esc: # Quit The Game
|
||||||
|
self.store_high_score()
|
||||||
|
sys.exit()
|
||||||
|
if key == KeyCode.from_char('?'):
|
||||||
|
self.on_help_screen = True if not self.on_help_screen else False
|
||||||
|
|
||||||
|
if key == Key.up:
|
||||||
|
if self.current_help_screen != 0:
|
||||||
|
self.current_help_screen -= 1
|
||||||
|
self.help_screen()
|
||||||
|
if key == Key.down:
|
||||||
|
if self.current_help_screen < len(self.help_screens) - 1:
|
||||||
|
self.current_help_screen += 1
|
||||||
|
self.help_screen()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
with Listener(on_press=on_press) as listener:
|
||||||
|
listener.join()
|
||||||
|
|
||||||
|
def help_screen(self):
|
||||||
|
os.system('clear')
|
||||||
|
print(self.help_screens[self.current_help_screen])
|
||||||
|
|
||||||
|
def change_selected_card(self, right=True):
|
||||||
|
self.current_card.selected = False
|
||||||
|
if right:
|
||||||
|
if self.selected_card != len(self.current_room_cards) - 1:
|
||||||
|
self.selected_card += 1
|
||||||
|
else:
|
||||||
|
if self.selected_card > 0:
|
||||||
|
self.selected_card -= 1
|
||||||
|
self.current_card.selected = True
|
||||||
|
|
||||||
|
def attack(self):
|
||||||
|
if self.current_card.suit not in [0, 2]:
|
||||||
|
self.flash_message('\n\n\n\tSelected card is not an enemy!')
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.weapon:
|
||||||
|
self.health -= self.current_card.value
|
||||||
|
|
||||||
|
if self.health > 0:
|
||||||
|
self.send_selected_to_grave()
|
||||||
|
else:
|
||||||
|
if self.defeated and self.defeated[-1].value < self.current_card.value:
|
||||||
|
self.health -= self.current_card.value
|
||||||
|
self.flash_message('\n\n\n\tYou should not have done that! \n\tYour weapon was not strong enough; causing you to take all of the damage!' )
|
||||||
|
self.send_selected_to_grave()
|
||||||
|
return
|
||||||
|
if self.weapon.value < self.current_card.value:
|
||||||
|
damage = self.current_card.value - self.weapon.value
|
||||||
|
self.health -= damage
|
||||||
|
if self.health <= 0:
|
||||||
|
pass
|
||||||
|
self.defeated.append(self.current_card)
|
||||||
|
self.send_selected_to_grave()
|
||||||
|
|
||||||
|
def send_selected_to_grave(self):
|
||||||
|
self.graveyard.append(self.current_room_cards.pop(self.selected_card))
|
||||||
|
self.check_win()
|
||||||
|
self.selected_card = 0
|
||||||
|
if len(self.current_room_cards) == 1:
|
||||||
|
self.advance_room()
|
||||||
|
if len(self.current_room_cards) > 0:
|
||||||
|
self.current_card.selected = True
|
||||||
|
self.deselect_defeated()
|
||||||
|
|
||||||
|
def health_potion(self):
|
||||||
|
self.health += self.current_card.value
|
||||||
|
if self.health > 20:
|
||||||
|
self.health = 20
|
||||||
|
self.send_selected_to_grave()
|
||||||
|
|
||||||
|
def equip_weapon(self):
|
||||||
|
self.defeated = []
|
||||||
|
self.weapon = self.current_card
|
||||||
|
self.current_card.selected = False
|
||||||
|
self.send_selected_to_grave()
|
||||||
|
|
||||||
|
def deselect_defeated(self):
|
||||||
|
for i in self.defeated:
|
||||||
|
i.selected = False
|
||||||
|
|
||||||
|
def advance_room(self):
|
||||||
|
self.block_skip = False
|
||||||
|
self.room += 1
|
||||||
|
|
||||||
|
# TODO: This can be simplified with initialize room func
|
||||||
|
while len(self.current_room_cards) < 4:
|
||||||
|
self.current_room_cards.append(self.deck.draw())
|
||||||
|
self.current_room_cards[0].selected = True
|
||||||
|
|
||||||
|
def skip_room(self):
|
||||||
|
if self.block_skip:
|
||||||
|
self.flash_message('\n\n\n\tYou cannot run away!')
|
||||||
|
else:
|
||||||
|
for card in self.current_room_cards:
|
||||||
|
card.selected = False
|
||||||
|
self.deck.deck.append(card)
|
||||||
|
self.current_room_cards = []
|
||||||
|
self.advance_room()
|
||||||
|
self.block_skip = True
|
||||||
|
self.flash_message('\n\n\n\tRun Away, Coward!! 🤡')
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.current_room_cards = [x for x in self.current_room_cards if x is not None]
|
||||||
|
|
||||||
|
def store_high_score(self):
|
||||||
|
self.calculate_score()
|
||||||
|
with open(self.save_file, 'rb') as f:
|
||||||
|
save_data = pickle.load(f)
|
||||||
|
save_data['high_scores'].append((self.username, self.score))
|
||||||
|
sorted_high_scores = sorted(save_data['high_scores'], key=lambda x: x[1], reverse=True)[:9]
|
||||||
|
save_data['high_scores'] = sorted_high_scores
|
||||||
|
|
||||||
|
with open(self.save_file, 'wb') as f:
|
||||||
|
pickle.dump(save_data, f)
|
||||||
|
|
||||||
|
def calculate_score(self):
|
||||||
|
self.score = 0
|
||||||
|
for card in self.current_room_cards + list(self.deck.deck):
|
||||||
|
if card.suit in [0, 2]:
|
||||||
|
self.score -= card.value
|
||||||
|
|
||||||
|
if self.score == 0:
|
||||||
|
self.score += self.health
|
||||||
|
for card in self.current_room_cards + list(self.deck.deck):
|
||||||
|
if card.suit == 1:
|
||||||
|
self.score += card.value
|
||||||
|
|
||||||
|
def check_win(self):
|
||||||
|
if len(self.deck.deck) <= 0 and len(self.current_room_cards) <= 0:
|
||||||
|
self.calculate_score()
|
||||||
|
self.store_high_score()
|
||||||
|
return True
|
||||||
|
|
163
main.py
Normal file
163
main.py
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
import termios
|
||||||
|
|
||||||
|
from game import Game
|
||||||
|
from assets.ascii_art import START_SCREEN, GAME_OVER
|
||||||
|
from assets.help import PAGE_ONE, PAGE_TWO, PAGE_THREE, PAGE_FOUR, PAGE_FIVE, PAGE_SIX, PAGE_SEVEN
|
||||||
|
|
||||||
|
from pynput.keyboard import Key, Listener, KeyCode
|
||||||
|
|
||||||
|
|
||||||
|
class Main:
|
||||||
|
def __init__(self):
|
||||||
|
self.screen_mode = True
|
||||||
|
self.game = None
|
||||||
|
self.username = 'Wayne Skyler'
|
||||||
|
|
||||||
|
self.save_file = 'save.pickle'
|
||||||
|
self.save_data = None
|
||||||
|
|
||||||
|
self.help_screens = [PAGE_ONE, PAGE_TWO, PAGE_THREE, PAGE_FOUR, PAGE_FIVE, PAGE_SIX, PAGE_SEVEN]
|
||||||
|
self.current_help_screen = 0
|
||||||
|
self.on_help_screen = False
|
||||||
|
self.from_screen = None
|
||||||
|
|
||||||
|
if not os.path.isfile(self.save_file):
|
||||||
|
|
||||||
|
with open(self.save_file, 'wb') as f:
|
||||||
|
save_data = {
|
||||||
|
'username': self.username,
|
||||||
|
'high_scores': []
|
||||||
|
}
|
||||||
|
|
||||||
|
pickle.dump(save_data, f)
|
||||||
|
|
||||||
|
else:
|
||||||
|
with open(self.save_file, 'rb') as f:
|
||||||
|
self.save_data = pickle.load(f)
|
||||||
|
self.username = self.save_data['username']
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
self.screens()
|
||||||
|
|
||||||
|
def screens(self, mode=None):
|
||||||
|
while self.screen_mode:
|
||||||
|
if self.game and mode != 'win':
|
||||||
|
if self.game.health <= 0:
|
||||||
|
os.system('clear')
|
||||||
|
self.game.store_high_score()
|
||||||
|
print(GAME_OVER.format(score=self.game.score))
|
||||||
|
self.input(mode='game_over')
|
||||||
|
else:
|
||||||
|
os.system('clear')
|
||||||
|
if mode == 'win':
|
||||||
|
self.game.health = 0
|
||||||
|
self.game.flash_message(f'\n\n\n\twin. Congratulashon. {self.game.score}')
|
||||||
|
self.game = None
|
||||||
|
self.screens(mode=None)
|
||||||
|
else:
|
||||||
|
print(START_SCREEN.format(username=self.username))
|
||||||
|
self.input(mode='start')
|
||||||
|
|
||||||
|
def gameplay(self):
|
||||||
|
self.game = Game(username=self.username)
|
||||||
|
self.game.initialize()
|
||||||
|
while self.game.health > 0:
|
||||||
|
if not self.game.paused and not self.game.on_help_screen:
|
||||||
|
self.game.cleanup()
|
||||||
|
if self.game.check_win():
|
||||||
|
self.screen_mode = True
|
||||||
|
self.screens(mode='win')
|
||||||
|
if None in self.game.current_room_cards:
|
||||||
|
breakpoint()
|
||||||
|
self.game = None
|
||||||
|
self.game.display()
|
||||||
|
self.game.input()
|
||||||
|
else:
|
||||||
|
if self.game.paused:
|
||||||
|
self.pause_game()
|
||||||
|
self.game.input()
|
||||||
|
elif self.game.on_help_screen:
|
||||||
|
self.game.help_screen()
|
||||||
|
self.game.input()
|
||||||
|
|
||||||
|
self.screen_mode = True
|
||||||
|
self.screens()
|
||||||
|
|
||||||
|
def pause_game(self):
|
||||||
|
os.system('clear')
|
||||||
|
print('\n\n\t PAUSED')
|
||||||
|
print('\t Press p to unpause...')
|
||||||
|
|
||||||
|
def high_scores(self):
|
||||||
|
with open(self.save_file, 'rb') as f:
|
||||||
|
save_data = pickle.load(f)
|
||||||
|
|
||||||
|
os.system('clear')
|
||||||
|
print('\t\tHIGH SCORES\n')
|
||||||
|
for score in save_data['high_scores']:
|
||||||
|
print(f"\t{score[0]}\t{score[1]}")
|
||||||
|
|
||||||
|
print('\n\tPress Enter to Return to Start Screen \n')
|
||||||
|
|
||||||
|
self.input('game_over')
|
||||||
|
|
||||||
|
def help_screen(self):
|
||||||
|
os.system('clear')
|
||||||
|
print(self.help_screens[self.current_help_screen])
|
||||||
|
|
||||||
|
def input(self, mode='start'):
|
||||||
|
def on_press(key):
|
||||||
|
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
|
||||||
|
if key == Key.enter:
|
||||||
|
if mode == 'start':
|
||||||
|
self.screen_mode = False
|
||||||
|
self.gameplay()
|
||||||
|
if mode == 'game_over':
|
||||||
|
self.game = None
|
||||||
|
self.screen_mode = True
|
||||||
|
self.screens()
|
||||||
|
if key == Key.esc:
|
||||||
|
sys.exit()
|
||||||
|
if key == KeyCode.from_char('h'):
|
||||||
|
self.high_scores()
|
||||||
|
if key == KeyCode.from_char('u'):
|
||||||
|
self.username = input('Input Your Username: ')
|
||||||
|
with open(self.save_file, 'rb') as f:
|
||||||
|
self.save_data = pickle.load(f)
|
||||||
|
|
||||||
|
self.save_data['username'] = self.username
|
||||||
|
|
||||||
|
with open(self.save_file, 'wb') as f:
|
||||||
|
pickle.dump(self.save_data, f)
|
||||||
|
|
||||||
|
self.screens()
|
||||||
|
if key == KeyCode.from_char('?'):
|
||||||
|
if mode != 'game_help':
|
||||||
|
self.on_help_screen = True if not self.on_help_screen else False
|
||||||
|
if self.on_help_screen:
|
||||||
|
self.help_screen()
|
||||||
|
else:
|
||||||
|
self.screens()
|
||||||
|
elif mode == 'game_help':
|
||||||
|
self.game.on_help_screen = True if not self.game.on_help_screen else False
|
||||||
|
|
||||||
|
if key == Key.up:
|
||||||
|
if self.current_help_screen != 0:
|
||||||
|
self.current_help_screen -= 1
|
||||||
|
self.help_screen()
|
||||||
|
if key == Key.down:
|
||||||
|
if self.current_help_screen < len(self.help_screens) - 1:
|
||||||
|
self.current_help_screen += 1
|
||||||
|
self.help_screen()
|
||||||
|
|
||||||
|
|
||||||
|
with Listener(on_press=on_press) as listener:
|
||||||
|
listener.join()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main = Main()
|
||||||
|
main.main()
|
8
requirements.txt
Normal file
8
requirements.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
colorama==0.4.6
|
||||||
|
evdev==1.9.1
|
||||||
|
keyboard==0.13.5
|
||||||
|
pynput==1.8.0
|
||||||
|
PyRect==0.2.0
|
||||||
|
python-xlib==0.33
|
||||||
|
six==1.17.0
|
||||||
|
wheel==0.45.1
|
Loading…
Reference in a new issue