- Mastermind: 4-color sequence, 10 tries, black/white peg feedback - Hangman: 6 lives, progressive letter reveal, 10-word vocabulary - Both available in BBS (telnet) and web (WML/XHTML) sides - Shared sp_games persistence, scores table integration - Updated games index and menu navigation
145 lines
5.7 KiB
Python
145 lines
5.7 KiB
Python
"""Games for the BBS. Shares `scores`, `sp_games` and `matches` with the web
|
|
site, so a telnet user and a WAP user can play each other in the same match."""
|
|
import json
|
|
import random
|
|
import time
|
|
from bbsdb import q, q1, ex, ago
|
|
|
|
QUESTIONS = [
|
|
("What does WAP stand for?",
|
|
["Wireless Application Protocol", "Web Access Point",
|
|
"Wide Area Paging", "Wireless Audio Player"], 0),
|
|
("WML is based on which language?", ["HTML", "XML", "SGML", "JSON"], 1),
|
|
("Which company made the Nokia 7110?",
|
|
["Ericsson", "Motorola", "Nokia", "Siemens"], 2),
|
|
("A WML file is made up of one or more...",
|
|
["Pages", "Cards", "Frames", "Slides"], 1),
|
|
("What year was WAP 1.1 published?", ["1996", "1999", "2002", "2005"], 1),
|
|
("GPRS stands for General Packet Radio...?",
|
|
["System", "Service", "Standard", "Stream"], 1),
|
|
]
|
|
|
|
|
|
def games_menu(t, con, me):
|
|
while True:
|
|
t.header("GAMES")
|
|
t.line(" [1] Guess the Number (single player)")
|
|
t.line(" [2] Quick Quiz (single player)")
|
|
t.line(" [3] Noughts & Crosses (multiplayer)")
|
|
t.line(" [4] Nim - 21 sticks (multiplayer)")
|
|
t.line(" [5] Mastermind (single player)")
|
|
t.line(" [6] Hangman (single player)")
|
|
t.line(" [7] High scores")
|
|
t.line(" [Q] Back to main")
|
|
c = t.ask("\ngames> ").lower()
|
|
if c == "1":
|
|
guess(t, con, me)
|
|
elif c == "2":
|
|
quiz(t, con, me)
|
|
elif c == "3":
|
|
ttt(t, con, me)
|
|
elif c == "4":
|
|
nim(t, con, me)
|
|
elif c == "5":
|
|
mastermind(t, con, me)
|
|
elif c == "6":
|
|
hangman(t, con, me)
|
|
elif c == "7":
|
|
scores(t, con)
|
|
elif c.startswith("q") or c == "":
|
|
return
|
|
|
|
|
|
def add_score(con, game, uid, score, detail=""):
|
|
ex(con, "INSERT INTO scores (game,user_id,score,detail,created_at)"
|
|
" VALUES (?,?,?,?,?)", (game, uid, score, detail, int(time.time())))
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------ mastermind
|
|
def mastermind(t, con, me):
|
|
# Mastermind: 4 pegs, 6 colors, 10 tries
|
|
colors = ['1', '2', '3', '4', '5', '6'] # represent colors as digits
|
|
secret = [random.choice(colors) for _ in range(4)]
|
|
tries = 0
|
|
max_tries = 10
|
|
t.header("MASTERMIND")
|
|
t.line(" I have selected a sequence of 4 colors from 1-6.")
|
|
t.line(" You have 10 tries to guess the sequence.")
|
|
t.line(" Feedback: Black = correct color and position, White = correct color only.")
|
|
t.line(" Enter your guess as 4 digits (e.g. 1234) or Q to quit.")
|
|
while tries < max_tries:
|
|
s = t.ask("\n guess> ", maxlen=10)
|
|
if s.lower().startswith("q"):
|
|
t.line(" The code was %s." % ''.join(secret))
|
|
return
|
|
if len(s) != 4 or not all(c in colors for c in s):
|
|
t.line(" Enter exactly 4 digits from 1-6.")
|
|
continue
|
|
guess = list(s)
|
|
tries += 1
|
|
# Calculate black and white pegs
|
|
black = sum(1 for i in range(4) if guess[i] == secret[i])
|
|
# Count occurrences of each color in secret and guess for white pegs
|
|
secret_counts = {c: secret.count(c) for c in set(secret)}
|
|
guess_counts = {c: guess.count(c) for c in set(guess)}
|
|
white = sum(min(secret_counts.get(c, 0), guess_counts.get(c, 0)) for c in set(secret)) - black
|
|
if black == 4:
|
|
score = max(1, 100 - 10 * tries)
|
|
add_score(con, "mastermind", me["id"], score, "%d tries" % tries)
|
|
t.line(" Correct! %d in %d tries. Score %d." % (''.join(secret), tries, score))
|
|
return
|
|
else:
|
|
t.line(" Feedback: %d Black, %d White. (tries: %d)" % (black, white, tries))
|
|
t.line(" You ran out of tries. The code was %s." % ''.join(secret))
|
|
|
|
|
|
def hangman(t, con, me):
|
|
# Hangman: word guessing game, 6 lives
|
|
import string
|
|
WORDS = ["python", "bbs", "telnet", "wml", "matrix", "linux", "mastermind", "screens", "forum", "games"]
|
|
word = random.choice(WORDS)
|
|
word_letters = set(word)
|
|
alphabet = set(string.ascii_lowercase)
|
|
used_letters = set()
|
|
lives = 6
|
|
t.header("HANGMAN")
|
|
t.line("I am thinking of a word. It has %d letters." % len(word))
|
|
t.line("You have %d lives." % lives)
|
|
while len(word_letters) > 0 and lives > 0:
|
|
# What current word is (with dashes for unguessed letters)
|
|
word_list = [letter if letter in used_letters else "-" for letter in word]
|
|
t.line("Current word: " + " ".join(word_list))
|
|
t.line("Lives left: %d" % lives)
|
|
t.line("Used letters: " + " ".join(sorted(used_letters)))
|
|
# User input
|
|
t.line("Guess a letter:")
|
|
s = t.ask("> ").lower()
|
|
if len(s) != 1 or s not in alphabet:
|
|
t.line("Please enter a single letter from a-z.")
|
|
continue
|
|
# If user has already guessed this letter
|
|
if s in used_letters:
|
|
t.line("You have already used that letter. Please try again.")
|
|
continue
|
|
# Add letter to used
|
|
used_letters.add(s)
|
|
# Check if letter is in word
|
|
if s in word_letters:
|
|
word_letters.discard(s)
|
|
t.line("Good! %s is in the word." % s)
|
|
else:
|
|
lives -= 1
|
|
t.line("%s is not in the word." % s)
|
|
# Game over
|
|
if lives == 0:
|
|
t.line("You died! The word was %s." % word)
|
|
else:
|
|
t.line("Yay! You guessed the word %s!" % word)
|
|
score = max(1, 50 - (6 - lives) * 5)
|
|
add_score(con, "hangman", me["id"], score, "%d lives remaining" % lives)
|
|
|
|
def scores(t, con):
|
|
names = {"guess": "Guess the Number", "quiz": "Quick Quiz",
|
|
"ttt": "Noughts & Crosses", "nim": "Nim", "mastermind": "Mastermind", "hangman": "Hangman"}
|