1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from textual import on
from textual.app import App, ComposeResult, Binding
from textual.widgets import Footer, Static
from textual.containers import Horizontal, Vertical, Grid
from textual.coordinate import Coordinate
from textual.events import MouseDown, MouseMove
from textual.screen import ModalScreen
from textual.widget import NoMatches
from support.ship import ShipIMG
from support.board import BattleshipBoard
class PlacementUI(App):
BINDINGS = [
Binding("e", "rotate", "Rotate held Ship Counter-Clockwise"),
Binding("q", "rotate", "Rotate held Ship Clockwise"),
Binding("enter", "confirm", "Confirm your Ship Placements", priority=1),
]
DEFAULT_CSS = """
Screen {
layers: below middle above above2 above3 above4 above5;
}
#box {
width: 100%;
height: 100%;
}
#board {
height: 100;
width: 100;
}
BattleshipBoard {
layer: middle;
}
#s1 {
layer: above;
}
#s2 {
layer: above2;
}
#s3 {
layer: above3;
}
#s4 {
layer: above4;
}
#s5 {
layer: above5;
}
RichLog {
height: 5%;
}
"""
def __init__(self):
super().__init__()
self.curr_widget = None
def compose(self) -> ComposeResult:
ship1 = ShipIMG("assets/2-vertical.png", 2, id="s1")
ship2 = ShipIMG("assets/3-vertical.png", 3, id="s2")
ship3 = ShipIMG("assets/3-vertical.png", 3, id="s3")
ship4 = ShipIMG("assets/4-vertical.png", 4, id="s4")
ship5 = ShipIMG("assets/5-vertical.png", 5, id="s5")
ship1.styles.offset = (2, 1)
ship2.styles.offset = (22, 1)
ship3.styles.offset = (42, 1)
ship4.styles.offset = (62, 1)
ship5.styles.offset = (82, 1)
vertical = Vertical()
vertical.styles.width = "30%"
yield Horizontal(*[vertical, Vertical(*[BattleshipBoard(cursor_background_priority="renderable", cell_padding=0, header_height=1, id="board", zebra_stripes=True, show_cursor=False), ship1, ship2, ship3, ship4, ship5]), vertical], id="box")
yield Footer()
def on_mouse_down(self, event: MouseDown) -> None:
widget, _ = self.screen.get_widget_at(*event.screen_offset)
if not isinstance(widget, ShipIMG):
return
self.curr_widget = widget
widget.drag_start = event.screen_offset
widget.start_offset = widget.styles.offset
def on_mouse_move(self, event: MouseMove) -> None:
if self.curr_widget is not None:
self.curr_widget.styles.offset = (int((event.screen_offset.x - self.curr_widget.drag_start.x + self.curr_widget.start_offset.x.value)/10)*10 + self.curr_widget.x_offset, int((event.screen_offset.y - self.curr_widget.drag_start.y + self.curr_widget.start_offset.y.value)/5)*5 + self.curr_widget.y_offset)
def on_mouse_up(self) -> None:
if self.curr_widget is not None:
self.curr_widget.drag_start = None
self.curr_widget.start_offset = None
self.curr_widget = None
def action_rotate(self):
if self.curr_widget is not None:
self.curr_widget.rotate()
def action_confirm(self):
offsets = dict()
coords = set()
ships = dict()
for i in range(5):
try:
ship = self.query_one("#s" + str(i + 1))
except NoMatches:
return
offsets["s" + str(i + 1)] = (ship.dir, ship.styles.offset)
ships["s" + str(i + 1)] = set()
x_offset = ship.styles.offset.x.value
y_offset = ship.styles.offset.y.value
curr_coord = Coordinate(int(y_offset / 5), int(x_offset / 10))
if ship.dir == 0:
for _ in range(ship.len):
if curr_coord.column > 7 or curr_coord.column < 0 or curr_coord.row < 0 or curr_coord.row > 7:
self.app.push_screen(InvalidPlacementScreen())
return
coords.add(curr_coord)
ships["s" + str(i + 1)].add(curr_coord)
curr_coord = Coordinate(curr_coord.row + 1, curr_coord.column)
else:
for _ in range(ship.len):
if curr_coord.column > 7 or curr_coord.column < 0 or curr_coord.row < 0 or curr_coord.row > 7:
self.app.push_screen(InvalidPlacementScreen())
return
coords.add(curr_coord)
ships["s" + str(i + 1)].add(curr_coord)
curr_coord = Coordinate(curr_coord.row, curr_coord.column + 1)
if len(coords) != 17:
self.app.push_screen(InvalidPlacementScreen())
return
self.exit(result=(offsets, coords, ships))
app = PlacementUI()
if __name__ == "__main__":
app.run()
class InvalidPlacementScreen(ModalScreen[None]):
BINDINGS = [
Binding("escape", "dismiss", priority=1),
]
DEFAULT_CSS = """
InvalidPlacementScreen {
align: center middle;
height: 100%;
width: 100%;
}
#dialog {
width: 80%;
height: auto;
border: thick $background 80%;
background: $surface;
margin-bottom: 0;
}
#warn {
color: red;
align: center middle;
text-align: center
}
"""
def __init__(self):
super().__init__()
def compose (self) -> ComposeResult:
yield Grid(Static("You did not provide a valid placement of your ships.\nPlease make sure all ships are on the board and not overlapping.\n(Press Escape to Exit this Screen)", id="warn"), id="dialog")