Added battleship files

This commit is contained in:
2025-11-07 11:22:04 +03:00
parent a11b2df651
commit 6d98d7d446
38 changed files with 1826 additions and 0 deletions

BIN
Battleships/.DS_Store vendored Normal file

Binary file not shown.

121
Battleships/Lesson1.html Normal file
View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Lesson 1: Battleships in Python!</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f8ff;
color: #2c3e50;
padding: 20px;
max-width: 900px;
margin: 0 auto;
}
h1 {
color: #2980b9;
text-align: center;
border-bottom: 3px solid #3498db;
padding-bottom: 10px;
}
h2 {
color: #2c3e50;
margin-top: 25px;
}
.outcome {
background: #e3f2fd;
padding: 15px;
border-left: 4px solid #3498db;
margin: 15px 0;
}
.why {
background: #e8f5e9;
padding: 15px;
border-left: 4px solid #2ecc71;
margin: 15px 0;
}
.step {
background: #fff8e1;
padding: 12px;
margin: 10px 0;
border-radius: 6px;
border-left: 4px solid #f39c12;
}
code {
background: #f1f1f1;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
}
.time {
font-weight: bold;
color: #e74c3c;
}
</style>
</head>
<body>
<h1>🎮 Lesson 1: Find the Hidden Ship!</h1>
<div class="outcome">
<h2>✅ Learning Outcomes</h2>
<ul>
<li>Use <code>random</code> to create hidden game elements</li>
<li>Get input from the player using <code>input()</code></li>
<li>Use <code>if</code> / <code>else</code> to give feedback</li>
<li>Run and debug a simple Python program</li>
</ul>
</div>
<div class="why">
<h2>💡 Why It Matters</h2>
<p>Every video game has <strong>hidden logic</strong> — secret levels, random enemies, or treasure locations. Learning to hide and reveal things with code is the first step to making your own games!</p>
<p>These same skills are used in apps, quizzes, and even smart home devices!</p>
</div>
<h2>⏱️ Lesson Plan (40 minutes)</h2>
<div class="step">
<span class="time">05 min</span><strong>Demo & Explain</strong><br>
Show the game: “Theres a secret ship! Can you find it?”<br>
Explain: Well write code that hides a ship and checks your guess.
</div>
<div class="step">
<span class="time">520 min</span><strong>Code Together</strong><br>
Type this starter code (or use your template):
<pre style="background:#2d2d2d;color:#f8f8f2;padding:10px;border-radius:6px;font-size:14px;">
import random
ship_row = random.randint(0, 4)
ship_col = random.randint(0, 4)
print("Guess the ship!")
guess_row = int(input("Row (0-4): "))
guess_col = int(input("Col (0-4): "))
if guess_row == ship_row and guess_col == ship_col:
print("🎯 HIT! You sank the ship!")
else:
print("💦 MISS!")
print("The ship was at", ship_row, ship_col)
</pre>
</div>
<div class="step">
<span class="time">2035 min</span><strong>Test & Improve</strong><br>
• Run the program 3 times<br>
• Try to break it (type a letter instead of number — what happens?)<br>
• 🌟 <em>Challenge</em>: Add a 2nd ship or limit to 3 guesses!
</div>
<div class="step">
<span class="time">3540 min</span><strong>Share & Celebrate</strong><br>
Pair up! Can your partner guess the ship in 2 tries?
</div>
<h2>🚀 You Just Learned:</h2>
<p>How to create <strong>interactive programs</strong> that respond to user choices — the heart of all games!</p>
</body>
</html>

138
Battleships/Lesson2.html Normal file
View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Lesson 2: Hunt All the Ships!</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #fff3e0;
color: #2c3e50;
padding: 20px;
max-width: 900px;
margin: 0 auto;
}
h1 {
color: #e67e22;
text-align: center;
border-bottom: 3px solid #f39c12;
padding-bottom: 10px;
}
h2 {
color: #2c3e50;
margin-top: 25px;
}
.outcome {
background: #ffecb3;
padding: 15px;
border-left: 4px solid #f39c12;
margin: 15px 0;
}
.why {
background: #dcedc8;
padding: 15px;
border-left: 4px solid #8bc34a;
margin: 15px 0;
}
.step {
background: #e1f5fe;
padding: 12px;
margin: 10px 0;
border-radius: 6px;
border-left: 4px solid #039be5;
}
code {
background: #f1f1f1;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
}
.time {
font-weight: bold;
color: #d32f2f;
}
</style>
</head>
<body>
<h1>🚢 Lesson 2: Hunt All the Ships!</h1>
<div class="outcome">
<h2>✅ Learning Outcomes</h2>
<ul>
<li>Use <strong>lists</strong> to store multiple ships</li>
<li>Create a <strong>game loop</strong> with turns</li>
<li>Track game state (<code>hits</code>, <code>turns</code>)</li>
<li>Make decisions with <code>for</code> loops and <code>if</code> inside lists</li>
</ul>
</div>
<div class="why">
<h2>💡 Why It Matters</h2>
<p>Real games dont end after one guess! They track <strong>score</strong>, <strong>lives</strong>, and <strong>progress</strong>. Learning to manage game state is how you build Pac-Man, Minecraft, or Roblox games!</p>
<p>Lists and loops are used in <em>every</em> programming language — from apps to robots.</p>
</div>
<h2>⏱️ Lesson Plan (40 minutes)</h2>
<div class="step">
<span class="time">05 min</span><strong>Review & Goal</strong><br>
“Last time: 1 ship. Today: <strong>3 ships</strong> and <strong>10 turns</strong>!”<br>
Show the enhanced game in action.
</div>
<div class="step">
<span class="time">520 min</span><strong>Upgrade Your Code</strong><br>
Edit your Lesson 1 file to this:
<pre style="background:#2d2d2d;color:#f8f8f2;padding:10px;border-radius:6px;font-size:14px;">
import random
ships = []
while len(ships) < 3:
r = random.randint(0, 4)
c = random.randint(0, 4)
if [r, c] not in ships:
ships.append([r, c])
print("3 ships hidden! 10 turns to find them all.")
hits = 0
for turn in range(10):
print("\nTurn", turn + 1)
guess_row = int(input("Row (0-4): "))
guess_col = int(input("Col (0-4): "))
if [guess_row, guess_col] in ships:
print("🎯 HIT!")
ships.remove([guess_row, guess_col])
hits += 1
if hits == 3:
print("🏆 You win!")
break
else:
print("💦 MISS!")
if hits < 3:
print("Game over! You found", hits, "ships.")
</pre>
</div>
<div class="step">
<span class="time">2035 min</span><strong>Level Up!</strong><br>
• Test the game (try to win!)<br>
• 🌟 <em>Challenge 1</em>: Let players use <strong>A, B, C</strong> for rows!<br>
<code>row_letter = input("Row (A-E): ").upper()</code><br>
<code>guess_row = ord(row_letter) - ord('A')</code><br>
• 🌟 <em>Challenge 2</em>: Show how many turns are left!
</div>
<div class="step">
<span class="time">3540 min</span><strong>Play & Reflect</strong><br>
Play your friends game! What makes it fun?<br>
“Now YOU can make games — not just play them!”
</div>
<h2>🌟 Youre Now a Game Coder!</h2>
<p>Youve learned the core ideas behind almost every game: <strong>hidden objects</strong>, <strong>player input</strong>, <strong>feedback</strong>, and <strong>win/lose conditions</strong>.</p>
</body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,9 @@
core.Microsoft*
core.mongo*
core.python*
env.py
__pycache__/
*.py[cod]
node_modules/
.github/
creds.json

View File

@@ -0,0 +1,84 @@
FROM gitpod/workspace-base
RUN echo "CI version from base"
### NodeJS ###
USER gitpod
ENV NODE_VERSION=16.13.0
ENV TRIGGER_REBUILD=1
RUN curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | PROFILE=/dev/null bash \
&& bash -c ". .nvm/nvm.sh \
&& nvm install $NODE_VERSION \
&& nvm use $NODE_VERSION \
&& nvm alias default $NODE_VERSION \
&& npm install -g typescript yarn node-gyp" \
&& echo ". ~/.nvm/nvm.sh" >> /home/gitpod/.bashrc.d/50-node
ENV PATH=$PATH:/home/gitpod/.nvm/versions/node/v${NODE_VERSION}/bin
### Python ###
USER gitpod
RUN sudo install-packages python3-pip
ENV PYTHON_VERSION 3.12.2
ENV PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH
RUN curl -fsSL https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash \
&& { echo; \
echo 'eval "$(pyenv init -)"'; \
echo 'eval "$(pyenv virtualenv-init -)"'; } >> /home/gitpod/.bashrc.d/60-python \
&& pyenv update \
&& pyenv install $PYTHON_VERSION \
&& pyenv global $PYTHON_VERSION \
&& python3 -m pip install --no-cache-dir --upgrade pip \
&& python3 -m pip install --no-cache-dir --upgrade \
setuptools wheel virtualenv pipenv pylint rope flake8 \
mypy autopep8 pep8 pylama pydocstyle bandit notebook \
twine \
&& sudo rm -rf /tmp/*USER gitpod
ENV PYTHONUSERBASE=/workspace/.pip-modules \
PIP_USER=yes
ENV PATH=$PYTHONUSERBASE/bin:$PATH
# Setup Heroku CLI
RUN curl https://cli-assets.heroku.com/install.sh | sh
RUN wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb && sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb && \
sudo rm -rf /var/cache/apt/* /var/lib/apt/lists/* /tmp/* /home/gitpod/*.deb && \
sudo chown -R gitpod:gitpod /home/gitpod/.cache/heroku/
# Setup PostgreSQL
RUN sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list' && \
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 && \
sudo apt-get update -y && \
sudo apt-get install -y postgresql-12
ENV PGDATA="/workspace/.pgsql/data"
RUN mkdir -p ~/.pg_ctl/bin ~/.pg_ctl/sockets \
&& echo '#!/bin/bash\n[ ! -d $PGDATA ] && mkdir -p $PGDATA && initdb --auth=trust -D $PGDATA\npg_ctl -D $PGDATA -l ~/.pg_ctl/log -o "-k ~/.pg_ctl/sockets" start\n' > ~/.pg_ctl/bin/pg_start \
&& echo '#!/bin/bash\npg_ctl -D $PGDATA -l ~/.pg_ctl/log -o "-k ~/.pg_ctl/sockets" stop\n' > ~/.pg_ctl/bin/pg_stop \
&& chmod +x ~/.pg_ctl/bin/*
# ENV DATABASE_URL="postgresql://gitpod@localhost"
# ENV PGHOSTADDR="127.0.0.1"
ENV PGDATABASE="postgres"
ENV PATH="/usr/lib/postgresql/12/bin:/home/gitpod/.nvm/versions/node/v${NODE_VERSION}/bin:$HOME/.pg_ctl/bin:$PATH"
# Add aliases
RUN echo 'alias run="python3 $GITPOD_REPO_ROOT/manage.py runserver 0.0.0.0:8000"' >> ~/.bashrc && \
echo 'alias heroku_config=". $GITPOD_REPO_ROOT/.vscode/heroku_config.sh"' >> ~/.bashrc && \
echo 'alias python=python3' >> ~/.bashrc && \
echo 'alias pip=pip3' >> ~/.bashrc && \
echo 'alias arctictern="python3 $GITPOD_REPO_ROOT/.vscode/arctictern.py"' >> ~/.bashrc && \
echo 'alias font_fix="python3 $GITPOD_REPO_ROOT/.vscode/font_fix.py"' >> ~/.bashrc && \
echo 'alias set_pg="export PGHOSTADDR=127.0.0.1"' >> ~/.bashrc && \
echo 'alias make_url="python3 $GITPOD_REPO_ROOT/.vscode/make_url.py "' >> ~/.bashrc
# Local environment variables
ENV PORT="8080"
ENV IP="0.0.0.0"

View File

@@ -0,0 +1,14 @@
image:
file: .gitpod.dockerfile
tasks:
- init: . ${GITPOD_REPO_ROOT}/.vscode/init_tasks.sh
command: /home/gitpod/.pg_ctl/bin/pg_start > /dev/null
- command: . ${GITPOD_REPO_ROOT}/.vscode/uptime.sh &
vscode:
extensions:
- ms-python.python
- formulahendry.auto-close-tag
- eventyret.bootstrap-4-cdn-snippet
- hookyqr.beautify
- matt-rudge.auto-open-preview-panel

View File

@@ -0,0 +1,8 @@
modules = ["web", "nodejs-20", "python-3.12"]
run = "python3 run.py"
[nix]
channel = "stable-24_05"
[deployment]
run = ["sh", "-c", "python3 run.py"]

View File

@@ -0,0 +1,186 @@
"""
arctictern.py
A little script that does a big migration
"""
import json
import os
import requests
import shutil
import subprocess
import sys
from os.path import exists
COLOURS = {"red": "\033[31m",
"blue": "\033[34m",
"green": "\033[32m",
"reset": "\033[0m",
"bold": "\033[1m"}
BASE_URL = "https://raw.githubusercontent.com/Code-Institute-Org/gitpod-full-template/main/"
CURRENT_VERSION = 1.0
THIS_VERSION = 1.0
UPGRADE_FILE_LIST = [{"filename": ".vscode/settings.json",
"url": ".vscode/settings.json"
},
{"filename": ".vscode/launch.json",
"url": ".vscode/launch.json"
},
{"filename": ".gitpod.yml",
"url": ".gitpod.yml"
},
{"filename": ".gitpod.dockerfile",
"url": ".gitpod.dockerfile"
},
{"filename": ".vscode/heroku_config.sh",
"url": ".vscode/heroku_config.sh"
},
{"filename": ".vscode/init_tasks.sh",
"url": ".vscode/init_tasks.sh"
},
{"filename": ".vscode/uptime.sh",
"url": ".vscode/uptime.sh"
},
{"filename": ".vscode/make_url.py",
"url": ".vscode/make_url.py"
},
{"filename": ".vscode/arctictern.py",
"url": ".vscode/arctictern.py"
}]
FINAL_LINES = "\nexport POST_UPGRADE_RUN=1\nsource ~/.bashrc\n"
def get_versions():
if exists(".vscode/version.txt"):
with open(".vscode/version.txt", "r") as f:
THIS_VERSION = float(f.read().strip())
else:
with open(".vscode/version.txt", "w") as f:
f.write(str(THIS_VERSION))
r = requests.get(BASE_URL + ".vscode/version.txt")
CURRENT_VERSION = float(r.content)
return {"this_version": THIS_VERSION,
"current_version": CURRENT_VERSION}
def needs_upgrade():
"""
Checks the version of the current template against
this version.
Returns True if upgrade is needed, False if not.
"""
versions = get_versions()
print(f"Upstream version: {versions['current_version']}")
print(f"Local version: {versions['this_version']}")
return versions["current_version"] > versions["this_version"]
def write_version():
versions = get_versions()
with open(".vscode/version.txt", "w") as f:
f.write(str(versions["current_version"]))
def build_post_upgrade():
r = requests.get(BASE_URL + ".vscode/upgrades.json")
upgrades = json.loads(r.content.decode("utf-8"))
content = ""
for k,v in upgrades.items():
if float(k) > THIS_VERSION:
print(f"Adding version changes for {k} to post_upgrade.sh")
content += v
if content:
content += FINAL_LINES
with open(".vscode/post_upgrade.sh", "w") as f:
f.writelines(content)
print("Built post_upgrade.sh. Restart your workspace for it to take effect.")
def process(file, suffix):
"""
Replaces and optionally backs up the files that
need to be changed.
Arguments: file - a path and filename
suffix - the suffix to the BASE_URL
"""
if file == ".gitpod.dockerfile" or file == ".gitpod.yml":
try:
shutil.copyfile(file, f"{file}.tmp")
except FileNotFoundError:
pass
with open(file, "wb") as f:
r = requests.get(BASE_URL + suffix)
f.write(r.content)
if exists(f"{file}.tmp"):
result = os.system(f"diff -q {file} {file}.tmp > /dev/null")
if result != 0:
os.remove(f"{file}.tmp")
return True
return False
def start_migration():
"""
Calls the process function and
renames the directory
"""
push_and_recreate = False
if not os.path.isdir(".vscode"):
print("Creating .vscode directory")
os.mkdir(".vscode")
for file in UPGRADE_FILE_LIST:
print(f"Processing: {file['filename']}")
result = process(file["filename"], file["url"])
if result == True:
push_and_recreate = True
if push_and_recreate:
write_version()
if needs_upgrade() and not push_and_recreate:
build_post_upgrade()
print("Changes saved.")
print("Please add, commit and push to GitHub.")
print("You may need to stop and restart your workspace for")
print("the changes to take effect.\n")
if push_and_recreate:
print(f"{COLOURS['red']}{COLOURS['bold']}*** IMPORTANT INFORMATION ***{COLOURS['reset']}")
print("The files used to create this workspace have been updated")
print("Please download any files that are in .gitignore and")
print("recreate this workspace by clicking on the Gitpod button")
print("in GitHub. Then, upload your saved files again.\n")
if __name__ == "__main__":
print(f"\n🐦 {COLOURS['blue']}{COLOURS['bold']}ArcticTern version 0.3{COLOURS['reset']}")
print("CI Template Migration Utility")
print("-----------------------------")
print("Upgrades the workspace to the latest version.\n")
if input("Start? Y/N ").lower() == "y":
start_migration()
else:
sys.exit("Migration cancelled by the user")

View File

@@ -0,0 +1,40 @@
#!/bin/bash
# Script to allow Heroku API key to be pasted
# exported as an environment variable
#
# Matt Rudge, May 2021
echo Heroku authentication configuration script
echo Code Institute, 2021
echo
echo Get your Heroku API key by going to https://dashboard.heroku.com
echo Go to Account Settings and click on Reveal to view your Heroku API key
echo
if [[ -z "${HEROKU_API_KEY}" ]]; then
echo Paste your Heroku API key here or press Enter to quit:
read apikey
if [[ -z "${apikey}" ]]; then
return 0
fi
echo export HEROKU_API_KEY=${apikey} >> ~/.bashrc
echo Added the export. Refreshing the terminal.
. ~/.bashrc > /dev/null
echo Done!
else
echo API key is already set.
echo
echo To reset the API key please input "'reset'":
read reset_trigger
if [[ ${reset_trigger} == reset ]]; then
unset HEROKU_API_KEY
unset reset_trigger
echo
echo API key removed!
else
unset reset_trigger
echo API key unchanged.
fi
echo
echo Exiting
fi

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# Gives a personalised greeting
# Adds configuration options for SQLite
# Creates run aliases
# Author: Matt Rudge
echo "Setting the greeting"
sed -i "s/USER_NAME/$GITPOD_GIT_USER_NAME/g" ${GITPOD_REPO_ROOT}/README.md
echo "Creating .sqliterc file"
echo ".headers on" > ~/.sqliterc
echo ".mode column" >> ~/.sqliterc
echo "Your workspace is ready to use. Happy coding!"

View File

@@ -0,0 +1,14 @@
# Simple utility for creating the Cloudinary URL from a
# cloudinary_python.txt file
# Matt Rudge, November 2021
import re
with open("cloudinary_python.txt") as f:
content = f.readlines()
cloud_name = re.findall(r"['](.*?)[']",content[15])[0]
api_key = re.findall(r"['](.*?)[']",content[16])[0]
api_secret = re.findall(r"['](.*?)[']",content[17])[0]
print(f"cloudinary://{api_key}:{api_secret}@{cloud_name}")

View File

@@ -0,0 +1,17 @@
{
"python.pythonPath": ".venv/bin/python",
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length=79" ],
"python.formatting.autopep8Args": ["--max-line-length=79" ],
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": true,
"python.linting.lintOnSave": true,
"python.linting.flake8Args": [
"--max-line-length=79"
],
"python.linting.pylintArgs": [
"--disable=C0111" // Disable missing docstring warnings if needed
],
"files.autoSave": "onFocusChange"
}

View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Pings the webhook so that we can gather
# basic usage stats. No personally identifiable
# data is captured here, and it is impossible to
# identify an individual user from the captured data.
# Matt Rudge, April 2021
UUID=$(cat /proc/sys/kernel/random/uuid)
URL=https://1xthkmzwg3.execute-api.eu-west-1.amazonaws.com/prod/lrsapi/
API_KEY=jceBCdeGZP9RDeUNCfM4jIQ39Cx0jtG51QgcwDwc
VERB="started"
clear
while true; do
DATA="{\"activity_time\":\"$(date +%Y-%m-%dT%H:%M:%S).000Z\",\"actor\":\"${UUID}\",\"verb\":\"${VERB}\",\"activity_object\":\"Gitpod Workspace\",\"extra_data\":\"{}\"}"
curl -s -X POST -H "x-api-key: ${API_KEY}" -d "${DATA}" ${URL} 1> /dev/null
VERB="running"
sleep 300
done

View File

@@ -0,0 +1 @@
web: node index.js

View File

@@ -0,0 +1,284 @@
# [Battleship Game](https://battleships-hedgemonkey-131f157c44ae.herokuapp.com/ "Click to see deployed app")
This is a simple command-line Battleship game written in Python, with a leader board stored in a google docs spreadsheet.
## Contents
<details>
<summary>Click here for Table of Contents</summary>
[Screenshots](#screenshots)
[Flow Chart Screenshots](#flow-chart-screenshots)
[Features](#features)
[Testing](#testing)
[Future](#future)
[Deployment](#deployment)
[Credits](#credits)
</details>
## Screenshots
| Heroku Deployed App |
| :---: |
| ![Screenshot of app on Heroku](readme/screenshot.jpg) |
| This is the app showing the Welcome screenwith Leaderboard with scores sourced through Google Docs API |
## Flow Chart Screenshots
| Flow Chart Screenshot |
| :---: |
| ![Screenshot of Flowchart](readme/flow_chart.jpg) |
| This is a rough flow chart displaying the program processes made using [Milanote](https://milanote.com/)
[Back to top](#contents)
## Features
* **Player vs. Computer:** Play against a challenging AI opponent.
* **Multiple Board Sizes:** Choose a board size between 5x5 and 9x9.
* **High Scores:** Track your best scores and compare them to others.
* **Colorful Interface:** Enjoy a visually enhanced gameplay experience with colored console output.
* **Leaderboard:** Top scores saved to a Google sheets leaderboard using `Google Docs API` and `gspread`
[Back to top](#contents)
## Testing
### Replit
Most of this project was developed using Replit and so testing was done as I was building the project step by step.
### Manual Testing
The following manual tests were conducted to ensure the application works as expected:
#### Test 1: Welcome Message
**Steps:**
1. Run the application.
2. Observe the welcome message displayed in the console.
**Expected Result:**
- The welcome message should be centered and displayed with appropriate colors.
**Actual Result:**
- The welcome message is displayed correctly.
**Screenshot:**
![Welcome Message](readme/welcome.jpg)
#### Test 2: High Scores Display
**Steps:**
1. Run the application.
2. Observe the high scores displayed after the welcome message.
**Expected Result:**
- The high scores should be displayed in a centered format with appropriate colors.
**Actual Result:**
- The high scores are displayed correctly.
**Screenshot:**
![High Scores](readme/welcome.jpg)
#### Test 3: Board Size Input
**Steps:**
1. Run the application.
2. Enter a valid board size (between 5 and 9).
**Expected Result:**
- The application should accept the input and proceed to the next step.
**Actual Result:**
- The application accepts valid inputs and proceeds correctly.
**Screenshot:**
![Board Size Input](readme/board_size.jpg)
#### Test 4: Invalid Board Size Input
**Steps:**
1. Run the application.
2. Enter an invalid board size (outside the range of 5 to 9).
**Expected Result:**
- The application should display an error message and prompt for input again.
**Actual Result:**
- The application displays an error message and prompts for input again.
**Screenshot:**
![Invalid Board Size Input](readme/board_size.jpg)
#### Test 5: Player Turn
**Steps:**
1. Run the application.
2. Enter valid coordinates for the player's turn.
**Expected Result:**
- The application should update the board and display the result (hit or miss).
**Actual Result:**
- The application updates the board and displays the result correctly.
**Screenshot:**
![Player Turn](readme/player_turn.jpg)
#### Test 6: Computer Turn
**Steps:**
1. Run the application.
2. Observe the computer's turn.
**Expected Result:**
- The application should update the board and display the result (hit or miss).
**Actual Result:**
- The application updates the board and displays the result correctly.
**Screenshot:**
![Computer Turn](readme/player_turn.jpg)
#### Test 7: Game Over
**Steps:**
1. Run the application.
2. Play the game until either the player or the computer wins.
**Expected Result:**
- The application should display the game over message and the final scores.
**Actual Result:**
- The application displays the game over message and the final scores correctly.
**Screenshot:**
![Game Over](readme/game_over.jpg)
#### Test 8: Save High Score
**Steps:**
1. Run the application.
2. Win the game and choose to save the high score.
3. Enter a name when prompted.
**Expected Result:**
The application should save the high score to the Google Sheets and display a success message.
**Actual Result:**
The application saves the high score and displays the success message correctly.
**Empty Username:** Players can intentionally leave the username field empty if they prefer not to submit a name. The high score will still be recorded on the leaderboard with a blank entry for the name.
**Screenshot:**
![Save High Score](readme/save_high_score.jpg)
[Back to top](#contents)
### PEP 8 Compliance
The project's Python code has been refactored to improve adherence to PEP 8 style guidelines. This enhances readability and maintainability. Specific improvements include:
* **Reduced Branching:** Complex conditional logic, particularly in the `display_boards` function, has been simplified to reduce the number of branches, improving code clarity.
* **Modernized String Formatting:** F-strings (formatted string literals) have been adopted throughout the codebase for more concise and readable string formatting.
* **Removed Redundant Code:** Unnecessary `else` clauses following `return`, `break`, and `continue` statements have been eliminated.
* **Import Order:** Imports have been reorganized to follow PEP 8 recommendations (standard library imports first, followed by third-party and then local imports).
These changes were guided by feedback from the `pylint` static analysis tool.
![Pylint Output](readme/pylint_screenshot.jpg)
[Back to top](#contents)
## Future
### Potential Future Features
* **Player vs. Player:** Perhaps add functionality to allow for a 2 player turn-based mode
* **Various Ship Sizes:** It would be nice to add different dized ships more akin to traditional Battleships game, with a choice of orientation
* **Number of Ships:** The added functionality to not only choose the board size but to also choose how many ships on the board would also be an attractive addition to the game
* **Choose Ship Placement:** The option to decide where you would like to place your ships on the board would also be a nice future feature to incorporate
[Back to top](#contents)
## Deployment
The site was deployed to Heroku. The steps to deploy are as follows:
- Set up a [Heroku](https://dashboard.heroku.com) Account and create a new App
- In Settings add the python and nodejs buildpacks
- In `Settings > Config Vars` Add the creds.json contents under the variable `CREDS`
- Link the [GitHub repository](https://github.com/hedgemonkey/battleships) to the Heroku app.
Heroku git URL
[https://git.heroku.com/battleships-hedgemonkey.git]
The live link can be found [here](https://battleships-hedgemonkey-131f157c44ae.herokuapp.com/)
[Back to top](#contents)
### Google Docs
This project uses a `Google Sheets` spreadsheet hosted on `Google Docs` accessed by `Google Cloud API` this is to keep track of past scores.
In order to deploy this yourself you would have to get the correct credentials to access this file or specify your own spreadsheet and provide your own Google Cloud API Credentials
To do this you ned to save your own credentials to `creds.json` and change the line `SHEET = GSPREAD_CLIENT.open('battleship_scores').sheet1` replacing `battleship_scores` with your own spreadsheet filename
The spreadsheet can be access [HERE](https://docs.google.com/spreadsheets/d/1cUhnYhy8DuxIEW6_BLnj0OFL38dNoRqmyVvkgnQDai8/edit?usp=sharing)
[Back to top](#contents)
### Local Deployment
You can clone or fork this project to make a local copy on your system.
[Back to top](#contents)
#### Cloning
You can clone the repository by following these steps:
1. Go to the [GitHub repository](https://github.com/Hedgemonkey/battleships).
2. Locate the Code button above the list of files and click it.
3. Select if you prefer to clone using HTTPS, SSH, or GitHub CLI and click the copy button to copy the URL to your clipboard.
4. Open Git Bash or Terminal.
5. Change the current working directory to the one where you want the cloned directory.
6. In your IDE Terminal, type the following command to clone my repository:
- `git clone https://github.com/hedgemonkey/battleships.git`
7. Press Enter to create your local clone.
[Back to top](#contents)
#### Forking
By forking the GitHub Repository, we make a copy of the original repository on our GitHub account to view and/or make changes without affecting the original owner's repository.
You can fork this repository by using the following steps:
1. Log in to GitHub and locate the [GitHub repository](https://github.com/Hedgemonkey/battleships).
2. At the top of the Repository (not the top of the page) just above the "Settings" Button on the menu, locate the "Fork" Button.
3. Once clicked, you should now have a copy of the original repository in your own GitHub account.
[Back to top](#contents)
### Local vs Deployment
There are no notable differences between my locally developed app and the Heroku deployed site aside from slight variances in the terminal colours.
[Back to top](#contents)
## Credits
In this section, I will reference the sources of my content and media, full disclosure of any resources used shall be detailed here.
- Milanote used to create Flow Chart Diagram
- Gemini Pro AI Used to help guide me when code wasn't doing as I intended
[Back to top](#contents)
### Content
[Back to top](#contents)
### Acknowledgements
- I would like to thank the [Code Institute Slack community](https://code-institute-room.slack.com) for the moral support and general information that helps with my studies.
- I would like to also thank Gemini Pro AI for assisting me whenever I needed a bit of quick advice, although it's suggestions often lead to more complications it's explinations and tips were incredibly helpful
- YTMusic for providing me with a soundtrack to work to
[Back to top](#contents)

View File

@@ -0,0 +1,60 @@
const Pty = require('node-pty');
const fs = require('fs');
exports.install = function () {
ROUTE('/');
WEBSOCKET('/', socket, ['raw']);
};
function socket() {
this.encodedecode = false;
this.autodestroy();
this.on('open', function (client) {
// Spawn terminal
client.tty = Pty.spawn('python3', ['run.py'], {
name: 'xterm-color',
cols: 80,
rows: 24,
cwd: process.env.PWD,
env: process.env
});
client.tty.on('exit', function (code, signal) {
client.tty = null;
client.close();
console.log("Process killed");
});
client.tty.on('data', function (data) {
client.send(data);
});
});
this.on('close', function (client) {
if (client.tty) {
client.tty.kill(9);
client.tty = null;
console.log("Process killed and terminal unloaded");
}
});
this.on('message', function (client, msg) {
client.tty && client.tty.write(msg);
});
}
if (process.env.CREDS != null) {
console.log("Creating creds.json file.");
fs.writeFile('creds.json', process.env.CREDS, 'utf8', function (err) {
if (err) {
console.log('Error writing file: ', err);
socket.emit("console_output", "Error saving credentials: " + err);
}
});
}

View File

@@ -0,0 +1,30 @@
// ===================================================
// Total.js start script
// https://www.totaljs.com
// ===================================================
const options = {};
// options.ip = '127.0.0.1';
options.port = parseInt(process.env.PORT);
// options.unixsocket = require('path').join(require('os').tmpdir(), 'app_name');
// options.config = { name: 'Total.js' };
// options.sleep = 3000;
// options.inspector = 9229;
// options.watch = ['private'];
// options.livereload = 'https://yourhostname';
// Enables cluster:
// options.cluster = 'auto';
// options.cluster_limit = 10; // max 10. threads (works only with "auto" scaling)
// Enables threads:
// options.cluster = 'auto';
// options.cluster_limit = 10; // max 10. threads (works only with "auto" scaling)
// options.timeout = 5000;
// options.threads = '/api/';
// options.logs = 'isolated';
var type = process.argv.indexOf('--release', 1) !== -1 || process.argv.indexOf('release', 1) !== -1 ? 'release' : 'debug';
// require('total4/' + type)(options);
require('total4').http('release', options);

View File

@@ -0,0 +1,106 @@
{
"name": "terminal",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "terminal",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"node-pty": "^0.10.1",
"node-static": "^0.7.11",
"total4": "^0.0.45"
}
},
"node_modules/colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/minimist": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==",
"license": "MIT"
},
"node_modules/nan": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
"license": "MIT"
},
"node_modules/node-pty": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.10.1.tgz",
"integrity": "sha512-JTdtUS0Im/yRsWJSx7yiW9rtpfmxqxolrtnyKwPLI+6XqTAPW/O2MjS8FYL4I5TsMbH2lVgDb2VMjp+9LoQGNg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"nan": "^2.14.0"
}
},
"node_modules/node-static": {
"version": "0.7.11",
"resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.11.tgz",
"integrity": "sha512-zfWC/gICcqb74D9ndyvxZWaI1jzcoHmf4UTHWQchBNuNMxdBLJMDiUgZ1tjGLEIe/BMhj2DxKD8HOuc2062pDQ==",
"license": "MIT",
"dependencies": {
"colors": ">=0.6.0",
"mime": "^1.2.9",
"optimist": ">=0.3.4"
},
"bin": {
"static": "bin/cli.js"
},
"engines": {
"node": ">= 0.4.1"
}
},
"node_modules/optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==",
"license": "MIT/X11",
"dependencies": {
"minimist": "~0.0.1",
"wordwrap": "~0.0.2"
}
},
"node_modules/total4": {
"version": "0.0.45",
"resolved": "https://registry.npmjs.org/total4/-/total4-0.0.45.tgz",
"integrity": "sha512-96vXqejddbAeNL6zpzbfm8ztiWL4G0fOuoUWniHDlu6oSz+DcxekHkzK0Y9X+ErlHCRTMsP2+ieMTqG6waowiA==",
"license": "MIT",
"bin": {
"total4": "bin/total4"
}
},
"node_modules/wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"name": "terminal",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lechien73/terminal.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/lechien73/terminal/issues"
},
"homepage": "https://github.com/lechien73/terminal#readme",
"dependencies": {
"node-static": "^0.7.11",
"node-pty": "^0.10.1",
"total4": "^0.0.45"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,20 @@
cachetools==5.5.0
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.3.2
cryptography==43.0.1
google-auth==2.34.0
google-auth-oauthlib==1.2.1
google-oauth==1.0.1
gspread==6.1.2
idna==3.8
oauthlib==3.2.2
pyasn1==0.6.0
pyasn1_modules==0.4.0
pycparser==2.22
pyOpenSSL==24.2.1
requests==2.32.3
requests-oauthlib==2.0.0
rsa==4.9
six==1.16.0
urllib3==2.2.2

View File

@@ -0,0 +1,371 @@
"""This module is a battleship game"""
import os
import re
import string # Corrected import order (see point 14)
import random
import gspread
from google.oauth2.service_account import Credentials
SCOPE = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive"
]
CREDS = Credentials.from_service_account_file('creds.json')
SCOPED_CREDS = CREDS.with_scopes(SCOPE)
GSPREAD_CLIENT = gspread.authorize(SCOPED_CREDS)
SHEET = GSPREAD_CLIENT.open('battleship_scores').sheet1
def center_text(text):
"""
Function to center text in the console
"""
console_width = os.get_terminal_size().columns
text_length = len(re.sub(r'\x1b\[[0-9;]*m', '', text))
padding = (console_width - text_length) // 2
return " " * padding + text + " " * padding
def pause():
"""
Function to pause and clear the screen
"""
input("\n" + Bcolors.OKCYAN + center_text("Press Enter to continue...") +
Bcolors.ENDC)
os.system('cls' if os.name == 'nt' else 'clear')
class Bcolors:
# pylint: disable=too-few-public-methods
"""
Colors for console output
"""
HEADER = '\033[95m'
OKBLUE = '\033[34m'
OKCYAN = '\033[96m'
OKGREEN = '\033[32m'
WARNING = '\033[93m'
RED = '\033[31m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def welcome_message():
"""
Function to display the welcome message with colors
"""
print(Bcolors.HEADER +
Bcolors.BOLD +
center_text("Welcome to Battleships!") +
Bcolors.ENDC)
print("\n" + Bcolors.BOLD +
center_text("Welcome to my simple battleships game!") +
Bcolors.ENDC)
print("\n" + Bcolors.BOLD +
center_text("The aim of the game is to hit your opponents ships" +
" in the least trys possible!") +
Bcolors.ENDC)
print("\n" + Bcolors.BOLD +
center_text("Just select a gameboard size between a 5x5 grid and a" +
" 9x9 grid!") +
Bcolors.ENDC)
print("\n" + Bcolors.BOLD +
center_text("Then enter the co-ordinates of where you want to" +
" shoot when prompted!") +
Bcolors.ENDC)
def display_high_scores():
"""
Function to display top 5 high scores
"""
print("\n" + Bcolors.HEADER + center_text("Current High Scores") +
Bcolors.ENDC)
print("\n" + Bcolors.OKCYAN + center_text("Top 5 Lowest Misses:") +
Bcolors.ENDC)
scores = SHEET.get_all_values()
sorted_scores = scores[1:]
sorted_scores.sort(key=lambda x: int(x[1]))
for i in range(1, 6):
if i <= len(sorted_scores):
print(center_text(f"{i}. {sorted_scores[i-1][0]}: {
sorted_scores[i-1][1]}"))
else:
print(f"{i}. N/A")
def get_board_size():
"""
Function to get valid board size input
"""
while True:
try:
size = int(input("Choose board size (5-9): "))
if 5 <= size <= 9:
return size
print("Invalid size. Please enter a number between 5 and 9")
except ValueError:
print("Invalid input. Please enter a number.")
def create_boards(size):
"""
Function to create and initialize boards
"""
player_board = [['O' for _ in range(size)] for _ in range(size)]
computer_board = [['O' for _ in range(size)] for _ in range(size)]
return player_board, computer_board
def place_ships(board, num_ships):
"""
Function to place ships randomly
"""
ship_locations = []
for _ in range(num_ships):
while True:
row = random.randint(0, len(board) - 1)
col = random.randint(0, len(board[0]) - 1)
if board[row][col] == 'O':
board[row][col] = 'S'
ship_locations.append((row, col))
break
return ship_locations
def display_boards(player_board, computer_board):
"""
Function to display both boards side by side with colors and spacing
"""
board_size = len(player_board)
header_spacing = board_size - 3 - max(0, board_size - 6)
print("\n" + center_text(Bcolors.RED + "Player Board " +
" "*header_spacing +
Bcolors.ENDC + " | " +
Bcolors.RED +
" Computer Board" +
Bcolors.ENDC))
print("\n" + center_text(Bcolors.OKCYAN + " " +
" ".join([str(i) for i in range(
1, board_size + 1)]) +
" " + Bcolors.ENDC +
" |" +
" " + Bcolors.OKCYAN +
" ".join([str(i) for i in range(
1, board_size + 1)]) +
" " +
Bcolors.ENDC))
letters = list(string.ascii_uppercase[:board_size])
for i in range(board_size):
player_row = []
computer_row = []
for j in range(board_size):
if player_board[i][j] == 'X':
player_row.append(Bcolors.RED +
Bcolors.BOLD +
'X' +
Bcolors.ENDC)
elif player_board[i][j] == 'S':
player_row.append(Bcolors.OKBLUE +
Bcolors.BOLD +
'S' +
Bcolors.ENDC)
elif player_board[i][j] == 'M':
player_row.append(Bcolors.OKGREEN +
Bcolors.BOLD +
'M' +
Bcolors.ENDC)
else:
player_row.append(player_board[i][j])
if computer_board[i][j] == 'X':
computer_row.append(Bcolors.RED +
Bcolors.BOLD +
'X' +
Bcolors.ENDC)
elif computer_board[i][j] == 'M':
computer_row.append(Bcolors.OKGREEN +
Bcolors.BOLD +
'M' +
Bcolors.ENDC)
else:
computer_row.append('O')
player_row_str = ' '.join(player_row)
computer_row_str = ' '.join(computer_row)
row_str = f"{Bcolors.OKCYAN}{letters[i]: <2} {Bcolors.ENDC}{
player_row_str: <{len(player_row_str) + 2}} | {
Bcolors.OKCYAN}{
letters[i]: <2}{
Bcolors.ENDC} {
computer_row_str: <{len(computer_row_str) + 2}}"
print(center_text(row_str))
def get_target(board):
"""
Function to get valid target coordinates
"""
while True:
try:
row_letter = input(f"Enter row (A-{chr(ord('A') + len(board) - 1)}). ").upper()
row = string.ascii_uppercase.index(row_letter.upper())
col = int(input(f"Enter column (1-{len(board)}). ")) - 1
if 0 <= row < len(board) and 0 <= col < len(board[0]):
return row, col
print("Invalid coordinates. Please enter numbers within the" +
" board size.")
except ValueError:
print("Invalid input. Please enter letters for Rows and numbers" +
" for columns.")
def player_turn(player_board, computer_board, computer_ships, misses):
"""
Function to handle player's turn
"""
print(Bcolors.OKBLUE + "\nYour turn:" + Bcolors.ENDC)
display_boards(player_board, computer_board)
while True:
row, col = get_target(computer_board)
if computer_board[row][col] == 'M' or computer_board[row][col] == 'X':
print(Bcolors.WARNING +
"You already tried that target. Choose another one.\n" +
Bcolors.ENDC)
else:
if (row, col) in computer_ships:
computer_board[row][col] = 'X'
computer_ships.remove((row, col))
print(Bcolors.OKGREEN +
"Hit! Target: " +
chr(ord('A') + row) +
" " +
str(col + 1) +
Bcolors.ENDC)
else:
computer_board[row][col] = 'M'
print(Bcolors.RED +
"Miss! Target: " +
chr(ord('A') + row) +
" " +
str(col + 1) +
Bcolors.ENDC)
misses += 1
print(Bcolors.OKCYAN +
"\nMisses: " +
Bcolors.ENDC +
str(misses))
break
return computer_board, computer_ships, misses
def computer_turn(player_board, player_ships, misses):
"""
Function to handle computer's turn
"""
print(Bcolors.OKCYAN + "\nComputer's turn:" + Bcolors.ENDC)
while True:
row = random.randint(0, len(player_board) - 1)
col = random.randint(0, len(player_board[0]) - 1)
if player_board[row][col] == 'M' or player_board[row][col] == 'X':
continue
if (row, col) in player_ships:
player_board[row][col] = 'X'
player_ships.remove((row, col))
print(Bcolors.OKGREEN +
"Computer hit! Target: " +
chr(ord('A') + row) +
" " +
str(col + 1) +
Bcolors.ENDC)
break
player_board[row][col] = 'M'
print(Bcolors.RED +
"Computer missed! Target: " +
chr(ord('A') + row) +
" " +
str(col + 1) +
Bcolors.ENDC)
misses += 1
break
pause()
return player_board, player_ships, misses
def play_game():
"""
Function to handle the game loop
"""
while True:
os.system('cl' if os.name == 'nt' else 'clear')
welcome_message()
display_high_scores()
board_size = get_board_size()
pause()
player_board, computer_board = create_boards(board_size)
player_ships = place_ships(player_board, 5)
computer_ships = place_ships(computer_board, 5)
player_misses = 0
computer_misses = 0
while player_ships and computer_ships:
computer_board, computer_ships, player_misses = player_turn(
player_board,
computer_board,
computer_ships,
player_misses)
if not computer_ships:
print(Bcolors.OKGREEN + "\nCongratulations! You win!" +
Bcolors.ENDC)
save_to_high_scores(player_misses)
break
player_board, player_ships, computer_misses = computer_turn(
player_board,
player_ships,
computer_misses)
if not player_ships:
print(Bcolors.RED + "\nYou lose! Better luck next time." +
Bcolors.ENDC)
break
print("\nFinal Score:")
print(f"Player: {player_misses}")
print(f"Computer: {computer_misses}")
while True:
play_again = input("\nPlay again? (y/n): ").lower()
if play_again in ('y', 'n'):
os.system('cls' if os.name == 'nt' else 'clear')
break
print("Invalid input. Please enter 'y' or 'n'.")
if play_again == 'n':
print("Goodbye!")
break
def save_to_high_scores(misses):
"""
Function to handle saving scores to the spreadsheet
"""
while True:
save_score = input("Do you want to save your score to the high " +
"scores? (y/n): ")
if save_score.lower() == 'y':
name = input("Enter your name: ")
SHEET.append_row([name, misses])
os.system('cls' if os.name == 'nt' else 'clear')
print("Score saved successfully!")
display_high_scores()
break
if save_score.lower() == 'n':
os.system('cls' if os.name == 'nt' else 'clear')
print("Score not saved.")
display_high_scores()
break
print("Invalid input. Please enter 'y' or 'n'.")
if __name__ == "__main__":
play_game()

View File

@@ -0,0 +1 @@
python-3.12.2

View File

@@ -0,0 +1,27 @@
<body>
<button onclick="window.location.reload()">Run Program</button>
<div id="terminal"></div>
<script>
var term = new Terminal({
cols: 80,
rows: 24
});
term.open(document.getElementById('terminal'));
term.writeln('Running startup command: python3 run.py');
term.writeln('');
var ws = new WebSocket(location.protocol.replace('http', 'ws') + '//' + location.hostname + (location.port ? (
':' + location.port) : '') + '/');
ws.onopen = function () {
new attach.attach(term, ws);
};
ws.onerror = function (e) {
console.log(e);
};
// Set focus in the terminal
document.getElementsByClassName("xterm-helper-textarea")[0].focus();
</script>
</body>

View File

@@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.5/xterm.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/3.14.5/addons/attach/attach.js"></script>
<title>Python Terminal by Code Institute</title>
<style>
body {
font-family: Arial;
}
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga"0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
background-color: rgb(0, 0, 0);
width: 730px;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}
button {
width: 200px;
height: 40px;
background-color: #E84610;
border: 1px solid grey;
color: white;
text-transform: uppercase;
margin: 10px;
border-radius: 8px;
}
</style>
</head>
@{body}
</html>

41
Battleships/lesson_1.py Normal file
View File

@@ -0,0 +1,41 @@
# BATTLESHIPS - Grade 7 Python Game
# Lesson 2: Full mini-game with 3 ships and turns!
import random
# Step 1: Create 3 hidden ships (as a list of [row, col])
ships = []
while len(ships) < 3:
r = random.randint(0, 4)
c = random.randint(0, 4)
if [r, c] not in ships: # avoid duplicates
ships.append([r, c])
print("3 ships are hidden on a 5x5 grid!")
print("You have 10 turns to find them all.")
hits = 0
turns = 10
# Step 2: Game loop
for turn in range(turns):
print("\nTurn", turn + 1)
# Get guess
guess_row = int(input("Row (0-4): "))
guess_col = int(input("Col (0-4): "))
# Check if guess is a ship
if [guess_row, guess_col] in ships:
print("🎯 HIT!")
ships.remove([guess_row, guess_col]) # remove found ship
hits += 1
if hits == 3:
print("🏆 You found all ships! You win!")
break
else:
print("💦 MISS!")
# Step 3: Game over message
if hits < 3:
print("Game over! You found", hits, "out of 3 ships.")

22
Battleships/lesson_2.py Normal file
View File

@@ -0,0 +1,22 @@
# BATTLESHIPS - Grade 7 Python Game
# Lesson 1: Find the hidden ship!
import random
# Step 1: Create a secret ship location (row 0-4, col 0-4)
ship_row = random.randint(0, 4)
ship_col = random.randint(0, 4)
# Step 2: Ask the player for a guess (we'll improve this later!)
print("Guess the ship location!")
guess_row = int(input("Row (0-4): "))
guess_col = int(input("Col (0-4): "))
# Step 3: Check if they hit the ship
if guess_row == ship_row and guess_col == ship_col:
print("🎯 HIT! You sank the ship!")
else:
print("💦 MISS! Try again.")
# Step 4: (Optional) Show where the ship really was
print("The ship was at row", ship_row, "and col", ship_col)